Skip to content

Commit 0a90b5a

Browse files
committed
feat(omp): package skillopt sleep extension
1 parent b7a55a9 commit 0a90b5a

9 files changed

Lines changed: 221 additions & 13 deletions

File tree

modules/agents/omp/default.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ let
6666
url = "https://registry.npmjs.org/@ogulcancelik/pi-herdr/-/pi-herdr-0.2.5.tgz";
6767
hash = "sha256-k7Bh17ULoYnlT13u5z3Kvm/iRK7AA0YzJK4ZGNcY+LY=";
6868
};
69+
skilloptSleepPlugin = ../../../packages/pi-packages/pi-skillopt-sleep;
6970
ompPackage = pkgs.stdenvNoCC.mkDerivation {
7071
name = "${cfg.package.pname or "omp"}-isolated";
7172
dontUnpack = true;
@@ -396,6 +397,10 @@ in
396397
${ompPackage}/bin/omp plugin link ${lib.escapeShellArg "${herdrPlugin}"} --force --json >/dev/null
397398
'';
398399

400+
home.activation.omp-skillopt-sleep-plugin = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
401+
${ompPackage}/bin/omp plugin link ${lib.escapeShellArg "${skilloptSleepPlugin}"} --force --json >/dev/null
402+
'';
403+
399404
home.activation.omp-mcp-cleanup = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
400405
${pkgs.python3}/bin/python3 <<'PY'
401406
import pathlib

packages/pi-packages/bun.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# pi-skillopt-sleep
2+
3+
OMP extension package for Microsoft SkillOpt-Sleep.
4+
5+
It ships:
6+
7+
- `extensions/skillopt-sleep.ts` — registers the `skillopt_sleep_omp` tool and `/skillopt-sleep` command.
8+
- `skills/skillopt-sleep-omp/SKILL.md` — on-demand playbook bundled with the extension.
9+
- `scripts/skillopt-sleep-omp.py` — mirrors OMP JSONL sessions into a Claude-compatible SkillOpt-Sleep home, then delegates to `python -m skillopt_sleep`.
10+
11+
The package follows OMP extension authoring conventions: `package.json` uses `omp.extensions`, while conventional `skills/` content is discovered from the package when linked.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { fileURLToPath } from "node:url";
2+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3+
import { Type } from "@sinclair/typebox";
4+
5+
const RUNNER = fileURLToPath(new URL("../scripts/skillopt-sleep-omp.py", import.meta.url));
6+
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
7+
8+
const ACTIONS = ["status", "harvest", "dry-run", "run", "adopt", "schedule", "unschedule"] as const;
9+
type Action = (typeof ACTIONS)[number];
10+
11+
const textResult = (text: string, details: Record<string, unknown> = {}) => ({
12+
content: [{ type: "text" as const, text }],
13+
details,
14+
});
15+
16+
const splitArgs = (input: string): string[] => {
17+
const args: string[] = [];
18+
let current = "";
19+
let quote: '"' | "'" | null = null;
20+
let escaped = false;
21+
22+
for (const char of input) {
23+
if (escaped) {
24+
current += char;
25+
escaped = false;
26+
continue;
27+
}
28+
if (char === "\\") {
29+
escaped = true;
30+
continue;
31+
}
32+
if (quote) {
33+
if (char === quote) quote = null;
34+
else current += char;
35+
continue;
36+
}
37+
if (char === '"' || char === "'") {
38+
quote = char;
39+
continue;
40+
}
41+
if (/\s/.test(char)) {
42+
if (current) {
43+
args.push(current);
44+
current = "";
45+
}
46+
continue;
47+
}
48+
current += char;
49+
}
50+
51+
if (escaped) current += "\\";
52+
if (current) args.push(current);
53+
return args;
54+
};
55+
56+
const runSkillOpt = async (
57+
pi: ExtensionAPI,
58+
args: string[],
59+
options: { cwd: string; timeoutMs?: number }
60+
) => {
61+
const result = await pi.exec("python3", [RUNNER, ...args], {
62+
cwd: options.cwd,
63+
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
64+
});
65+
const stdout = result.stdout?.trim() ?? "";
66+
const stderr = result.stderr?.trim() ?? "";
67+
if (result.code !== 0) {
68+
throw new Error(
69+
[`skillopt-sleep failed with exit code ${result.code}`, stdout, stderr]
70+
.filter(Boolean)
71+
.join("\n\n")
72+
);
73+
}
74+
return { stdout, stderr, code: result.code };
75+
};
76+
77+
export default function skilloptSleepExtension(pi: ExtensionAPI) {
78+
pi.registerTool({
79+
name: "skillopt_sleep_omp",
80+
label: "SkillOpt Sleep for OMP",
81+
description:
82+
"Run Microsoft SkillOpt-Sleep over local OMP sessions via status, harvest, dry-run, run, adopt, schedule, or unschedule.",
83+
parameters: Type.Object({
84+
action: Type.Union(
85+
[
86+
Type.Literal("status"),
87+
Type.Literal("harvest"),
88+
Type.Literal("dry-run"),
89+
Type.Literal("run"),
90+
Type.Literal("adopt"),
91+
Type.Literal("schedule"),
92+
Type.Literal("unschedule"),
93+
],
94+
{ description: "SkillOpt-Sleep action to run." }
95+
),
96+
args: Type.Optional(
97+
Type.Array(Type.String(), {
98+
description:
99+
"Additional SkillOpt-Sleep flags, e.g. ['--backend', 'mock', '--max-tasks', '3'].",
100+
})
101+
),
102+
timeoutMs: Type.Optional(
103+
Type.Number({ description: "Command timeout in milliseconds. Defaults to 10 minutes." })
104+
),
105+
}),
106+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
107+
const action = params.action as Action;
108+
const args = [action, ...((params.args as string[] | undefined) ?? [])];
109+
const result = await runSkillOpt(pi, args, {
110+
cwd: ctx.cwd,
111+
timeoutMs: params.timeoutMs as number | undefined,
112+
});
113+
return textResult(result.stdout || result.stderr || "skillopt-sleep completed", result);
114+
},
115+
});
116+
117+
pi.registerCommand("skillopt-sleep", {
118+
description: "Run SkillOpt-Sleep for OMP, e.g. /skillopt-sleep dry-run --backend mock",
119+
handler: async (input, ctx) => {
120+
const args = splitArgs(input.trim() || "status");
121+
try {
122+
const result = await runSkillOpt(pi, args, { cwd: ctx.cwd });
123+
ctx.ui.notify(result.stdout || result.stderr || "skillopt-sleep completed", "info");
124+
} catch (error) {
125+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
126+
}
127+
},
128+
});
129+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "pi-skillopt-sleep",
3+
"version": "0.1.0",
4+
"description": "OMP extension for running Microsoft SkillOpt-Sleep over local OMP sessions.",
5+
"type": "module",
6+
"keywords": [
7+
"omp-extension",
8+
"pi-extension",
9+
"skillopt",
10+
"sleep",
11+
"coding-agent"
12+
],
13+
"license": "MIT",
14+
"author": "Edmund Miller",
15+
"omp": {
16+
"extensions": [
17+
"./extensions/skillopt-sleep.ts"
18+
]
19+
},
20+
"pi": {
21+
"extensions": [
22+
"./extensions/skillopt-sleep.ts"
23+
]
24+
},
25+
"scripts": {
26+
"typecheck": "tsc --noEmit",
27+
"check": "tsc --noEmit"
28+
},
29+
"peerDependencies": {
30+
"@mariozechner/pi-coding-agent": "*",
31+
"@sinclair/typebox": "*"
32+
},
33+
"devDependencies": {
34+
"@mariozechner/pi-coding-agent": "*",
35+
"@sinclair/typebox": "*",
36+
"@types/node": "^22.0.0",
37+
"typescript": "^5.0.0"
38+
}
39+
}

skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py renamed to packages/pi-packages/pi-skillopt-sleep/scripts/skillopt-sleep-omp.py

File renamed without changes.

skills/catalog/skillopt-sleep-omp/SKILL.md renamed to packages/pi-packages/pi-skillopt-sleep/skills/skillopt-sleep-omp/SKILL.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,25 @@ Run from the project whose memory/skills should evolve:
4949

5050
```bash
5151
# Safe smoke check, no proposal staged
52-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py dry-run \
52+
/skillopt-sleep dry-run \
5353
--backend mock --max-sessions 5 --max-tasks 3 --progress
5454

5555
# Full cycle; stages a proposal only
56-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py run \
56+
/skillopt-sleep run \
5757
--backend codex --max-sessions 10 --max-tasks 5 --progress
5858

5959
# Inspect staged proposals and history
60-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py status
60+
/skillopt-sleep status
6161

6262
# Adopt only after explicit review/approval
63-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py adopt
63+
/skillopt-sleep adopt
6464

6565
# Nightly wrapper schedule; this installs cron for the OMP wrapper, not the
6666
# upstream bare `python -m skillopt_sleep run`.
67-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py schedule \
67+
/skillopt-sleep schedule \
6868
--hour 3 --minute 17 --backend codex --max-sessions 10 --max-tasks 5
6969

70-
python skills/catalog/skillopt-sleep-omp/scripts/skillopt-sleep-omp.py unschedule
70+
/skillopt-sleep unschedule
7171
```
7272

7373
Actions are `status`, `harvest`, `dry-run`, `run`, `adopt`, `schedule`, and
@@ -79,6 +79,8 @@ such as `--target-skill-path`, `--tasks-file`, `--backend`, `--model`,
7979
`--edit-budget`, `--lookback-hours`, `--max-sessions`, `--max-tasks`,
8080
`--progress`, and `--json` pass through unchanged.
8181

82+
Slash command usage is `/skillopt-sleep <action> [flags...]`; the extension also exposes the `skillopt_sleep_omp` tool for structured calls.
83+
8284
Wrapper-only flags:
8385

8486
| Flag | Meaning |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "NodeNext",
5+
"moduleResolution": "NodeNext",
6+
"strict": true,
7+
"skipLibCheck": true,
8+
"noEmit": true,
9+
"types": ["node"]
10+
},
11+
"include": ["extensions/**/*.ts"]
12+
}

skills/flake.nix

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,6 @@
230230
"opencode"
231231
"enable"
232232
];
233-
ompEnabled = moduleEnabled [
234-
"modules"
235-
"agents"
236-
"omp"
237-
"enable"
238-
];
239233
hermesEnabled =
240234
moduleEnabled [
241235
"modules"
@@ -249,7 +243,7 @@
249243
"enable"
250244
];
251245
targetEnabled = {
252-
agents = codexEnabled || piEnabled || opencodeEnabled || hermesEnabled || ompEnabled;
246+
agents = codexEnabled || piEnabled || opencodeEnabled || hermesEnabled;
253247
codex = codexEnabled;
254248
pi = piEnabled;
255249
claude = claudeEnabled;

0 commit comments

Comments
 (0)