@a5c-ai/babysitter-omp 5.0.1-staging.5cd43600 → 5.0.1-staging.6e094dee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/index.legacy.ts +55 -0
- package/extensions/index.ts +41 -0
- package/hooks/babysitter-proxied-before-provider-request.js +167 -0
- package/hooks/babysitter-proxied-context.js +165 -0
- package/hooks/babysitter-proxied-session-start.js +227 -0
- package/hooks/babysitter-proxied-stop.js +179 -0
- package/hooks/babysitter-proxied-tool-call.js +166 -0
- package/hooks/hooks.json +46 -0
- package/hooks/proxied-hooks.json +47 -0
- package/package.json +4 -2
- package/versions.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
const COMMANDS = [
|
|
4
|
+
"assimilate",
|
|
5
|
+
"call",
|
|
6
|
+
"cleanup",
|
|
7
|
+
"contrib",
|
|
8
|
+
"doctor",
|
|
9
|
+
"forever",
|
|
10
|
+
"help",
|
|
11
|
+
"observe",
|
|
12
|
+
"plan",
|
|
13
|
+
"plugins",
|
|
14
|
+
"project-install",
|
|
15
|
+
"resume",
|
|
16
|
+
"retrospect",
|
|
17
|
+
"user-install",
|
|
18
|
+
"yolo",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
function toSkillPrompt(name: string, args: string): string {
|
|
22
|
+
return `/skill:${name}${args ? ` ${args}` : ""}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function activate(pi: ExtensionAPI): void {
|
|
26
|
+
const forwardBabysit = async (args: unknown) => {
|
|
27
|
+
pi.sendUserMessage(toSkillPrompt("babysit", String(args ?? "").trim()));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
pi.registerCommand("babysit", {
|
|
31
|
+
description: "Load the Babysitter orchestration skill",
|
|
32
|
+
handler: forwardBabysit,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
pi.registerCommand("babysitter", {
|
|
36
|
+
description: "Alias for /babysit",
|
|
37
|
+
handler: forwardBabysit,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (const name of COMMANDS) {
|
|
41
|
+
const forward = async (args: unknown) => {
|
|
42
|
+
pi.sendUserMessage(toSkillPrompt(name, String(args ?? "").trim()));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
pi.registerCommand(name, {
|
|
46
|
+
description: `Open the Babysitter ${name} skill`,
|
|
47
|
+
handler: forward,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
pi.registerCommand(`babysitter:${name}`, {
|
|
51
|
+
description: `Alias for /${name}`,
|
|
52
|
+
handler: forward,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
package/extensions/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
|
2
6
|
|
|
3
7
|
const COMMANDS = [
|
|
4
8
|
"assimilate",
|
|
@@ -22,7 +26,44 @@ function toSkillPrompt(name: string, args: string): string {
|
|
|
22
26
|
return `/skill:${name}${args ? ` ${args}` : ""}`;
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Run a proxied hook script and return parsed JSON result.
|
|
31
|
+
* Returns empty object on failure (hooks are best-effort).
|
|
32
|
+
*/
|
|
33
|
+
function runProxiedHook(
|
|
34
|
+
scriptName: string,
|
|
35
|
+
inputData?: Record<string, unknown>
|
|
36
|
+
): Record<string, unknown> {
|
|
37
|
+
const scriptPath = path.join(PLUGIN_ROOT, "hooks", scriptName);
|
|
38
|
+
try {
|
|
39
|
+
const result = execSync(`node "${scriptPath}"`, {
|
|
40
|
+
input: inputData ? JSON.stringify(inputData) : undefined,
|
|
41
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
42
|
+
timeout: 30000,
|
|
43
|
+
env: {
|
|
44
|
+
...process.env,
|
|
45
|
+
OMP_PLUGIN_ROOT: PLUGIN_ROOT,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return JSON.parse(result.toString("utf8").trim());
|
|
49
|
+
} catch {
|
|
50
|
+
// Hooks are best-effort -- never break the extension
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
export default function activate(pi: ExtensionAPI): void {
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Trigger session-start hook on activation
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
runProxiedHook("babysitter-proxied-session-start.js", {
|
|
60
|
+
event: "session_start",
|
|
61
|
+
cwd: process.cwd(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Register slash commands (unchanged from legacy)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
26
67
|
const forwardBabysit = async (args: unknown) => {
|
|
27
68
|
pi.sendUserMessage(toSkillPrompt("babysit", String(args ?? "").trim()));
|
|
28
69
|
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Before Provider Request Hook for Oh-My-Pi
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires before Oh-My-Pi sends a request to the LLM provider. Delegates to
|
|
7
|
+
* `babysitter hook:run --hook-type pre-tool-use` via hooks-proxy
|
|
8
|
+
* (mapped as a model-level interception point).
|
|
9
|
+
*
|
|
10
|
+
* This hook can be used to:
|
|
11
|
+
* - Log provider requests for observability
|
|
12
|
+
* - Inject babysitter metadata into request context
|
|
13
|
+
* - Track token usage across orchestration iterations
|
|
14
|
+
*
|
|
15
|
+
* Oh-My-Pi plugin protocol (programmatic):
|
|
16
|
+
* - Called from extension via execSync
|
|
17
|
+
* - Receives provider request context as JSON via stdin
|
|
18
|
+
* - Outputs JSON to stdout
|
|
19
|
+
* - Exit 0 = success
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
"use strict";
|
|
23
|
+
|
|
24
|
+
const { execSync } = require("child_process");
|
|
25
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
26
|
+
const os = require("os");
|
|
27
|
+
const path = require("path");
|
|
28
|
+
|
|
29
|
+
const PLUGIN_ROOT = process.env.OMP_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
30
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
31
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
32
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
33
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-omp-before-provider-hook.log");
|
|
34
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
35
|
+
|
|
36
|
+
function ensureDir(dir) {
|
|
37
|
+
try { mkdirSync(dir, { recursive: true }); } catch { /* best-effort */ }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function blog(msg) {
|
|
41
|
+
ensureDir(LOG_DIR);
|
|
42
|
+
const ts = new Date().toISOString();
|
|
43
|
+
try {
|
|
44
|
+
appendFileSync(LOG_FILE, `[INFO] ${ts} ${msg}\n`);
|
|
45
|
+
} catch { /* best-effort */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getSdkVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
51
|
+
return versions.sdkVersion || "latest";
|
|
52
|
+
} catch {
|
|
53
|
+
return "latest";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveHooksProxy() {
|
|
58
|
+
try {
|
|
59
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
60
|
+
return "a5c-hooks-proxy";
|
|
61
|
+
} catch { /* not in PATH */ }
|
|
62
|
+
|
|
63
|
+
const localProxy = path.join(
|
|
64
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
65
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
66
|
+
);
|
|
67
|
+
if (existsSync(localProxy)) {
|
|
68
|
+
return localProxy;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function installHooksProxy(version) {
|
|
75
|
+
if (existsSync(PROXY_MARKER)) return;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --loglevel=error`, {
|
|
79
|
+
stdio: "pipe",
|
|
80
|
+
timeout: 120000,
|
|
81
|
+
});
|
|
82
|
+
blog(`Installed hooks-proxy globally (${version})`);
|
|
83
|
+
} catch {
|
|
84
|
+
try {
|
|
85
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
86
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
87
|
+
stdio: "pipe",
|
|
88
|
+
timeout: 120000,
|
|
89
|
+
});
|
|
90
|
+
blog(`Installed hooks-proxy to user prefix (${version})`);
|
|
91
|
+
} catch {
|
|
92
|
+
blog("hooks-proxy installation failed");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try { writeFileSync(PROXY_MARKER, version); } catch { /* best-effort */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function main() {
|
|
100
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
101
|
+
|| process.env.OMP_SESSION_ID
|
|
102
|
+
|| process.env.PI_SESSION_ID
|
|
103
|
+
|| "";
|
|
104
|
+
|
|
105
|
+
if (!sessionId) {
|
|
106
|
+
process.stdout.write("{}\n");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Read stdin for provider request context
|
|
111
|
+
let inputData = "";
|
|
112
|
+
try {
|
|
113
|
+
inputData = readFileSync(0, "utf8");
|
|
114
|
+
} catch {
|
|
115
|
+
// No stdin available
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
blog(`Unified before-provider-request: session=${sessionId}`);
|
|
119
|
+
|
|
120
|
+
const hookInput = JSON.stringify({
|
|
121
|
+
session_id: sessionId,
|
|
122
|
+
cwd: process.cwd(),
|
|
123
|
+
harness: "oh-my-pi",
|
|
124
|
+
plugin_root: PLUGIN_ROOT,
|
|
125
|
+
provider_request: inputData ? JSON.parse(inputData) : {},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const sdkVersion = getSdkVersion();
|
|
129
|
+
|
|
130
|
+
// Ensure hooks-proxy is installed
|
|
131
|
+
let proxy = resolveHooksProxy();
|
|
132
|
+
if (!proxy) {
|
|
133
|
+
installHooksProxy(sdkVersion);
|
|
134
|
+
proxy = resolveHooksProxy();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const handler = `babysitter hook:run --harness unified --hook-type pre-tool-use --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
let result;
|
|
141
|
+
if (proxy) {
|
|
142
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
143
|
+
result = execSync(`"${proxy}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
144
|
+
input: hookInput,
|
|
145
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
146
|
+
timeout: 10000,
|
|
147
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
151
|
+
result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${sdkVersion}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
152
|
+
input: hookInput,
|
|
153
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
154
|
+
timeout: 30000,
|
|
155
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const output = result.toString("utf8").trim();
|
|
159
|
+
blog(`Hook result: ${output}`);
|
|
160
|
+
process.stdout.write((output || "{}") + "\n");
|
|
161
|
+
} catch (err) {
|
|
162
|
+
blog(`Before-provider-request hook failed: ${err.message} -- non-blocking`);
|
|
163
|
+
process.stdout.write("{}\n");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main();
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Context Hook for Oh-My-Pi
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires when Oh-My-Pi requests context injection before the agent
|
|
7
|
+
* processes a turn. Delegates to
|
|
8
|
+
* `babysitter hook:run --hook-type user-prompt-submit` via hooks-proxy.
|
|
9
|
+
*
|
|
10
|
+
* Oh-My-Pi supports native additional context and chained context
|
|
11
|
+
* propagation with session-before short-circuit semantics.
|
|
12
|
+
*
|
|
13
|
+
* Oh-My-Pi plugin protocol (programmatic):
|
|
14
|
+
* - Called from extension via execSync
|
|
15
|
+
* - Receives context request as JSON via stdin
|
|
16
|
+
* - Outputs JSON to stdout with context additions
|
|
17
|
+
* - Exit 0 = success
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const { execSync } = require("child_process");
|
|
23
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
24
|
+
const os = require("os");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
|
|
27
|
+
const PLUGIN_ROOT = process.env.OMP_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
28
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
29
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
30
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
31
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-omp-context-hook.log");
|
|
32
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
33
|
+
|
|
34
|
+
function ensureDir(dir) {
|
|
35
|
+
try { mkdirSync(dir, { recursive: true }); } catch { /* best-effort */ }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function blog(msg) {
|
|
39
|
+
ensureDir(LOG_DIR);
|
|
40
|
+
const ts = new Date().toISOString();
|
|
41
|
+
try {
|
|
42
|
+
appendFileSync(LOG_FILE, `[INFO] ${ts} ${msg}\n`);
|
|
43
|
+
} catch { /* best-effort */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getSdkVersion() {
|
|
47
|
+
try {
|
|
48
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
49
|
+
return versions.sdkVersion || "latest";
|
|
50
|
+
} catch {
|
|
51
|
+
return "latest";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveHooksProxy() {
|
|
56
|
+
try {
|
|
57
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
58
|
+
return "a5c-hooks-proxy";
|
|
59
|
+
} catch { /* not in PATH */ }
|
|
60
|
+
|
|
61
|
+
const localProxy = path.join(
|
|
62
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
63
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
64
|
+
);
|
|
65
|
+
if (existsSync(localProxy)) {
|
|
66
|
+
return localProxy;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function installHooksProxy(version) {
|
|
73
|
+
if (existsSync(PROXY_MARKER)) return;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --loglevel=error`, {
|
|
77
|
+
stdio: "pipe",
|
|
78
|
+
timeout: 120000,
|
|
79
|
+
});
|
|
80
|
+
blog(`Installed hooks-proxy globally (${version})`);
|
|
81
|
+
} catch {
|
|
82
|
+
try {
|
|
83
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
84
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
85
|
+
stdio: "pipe",
|
|
86
|
+
timeout: 120000,
|
|
87
|
+
});
|
|
88
|
+
blog(`Installed hooks-proxy to user prefix (${version})`);
|
|
89
|
+
} catch {
|
|
90
|
+
blog("hooks-proxy installation failed");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try { writeFileSync(PROXY_MARKER, version); } catch { /* best-effort */ }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main() {
|
|
98
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
99
|
+
|| process.env.OMP_SESSION_ID
|
|
100
|
+
|| process.env.PI_SESSION_ID
|
|
101
|
+
|| "";
|
|
102
|
+
|
|
103
|
+
if (!sessionId) {
|
|
104
|
+
process.stdout.write("{}\n");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Read stdin for context request
|
|
109
|
+
let inputData = "";
|
|
110
|
+
try {
|
|
111
|
+
inputData = readFileSync(0, "utf8");
|
|
112
|
+
} catch {
|
|
113
|
+
// No stdin available
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
blog(`Unified context: session=${sessionId}`);
|
|
117
|
+
|
|
118
|
+
const hookInput = JSON.stringify({
|
|
119
|
+
session_id: sessionId,
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
harness: "oh-my-pi",
|
|
122
|
+
plugin_root: PLUGIN_ROOT,
|
|
123
|
+
context_request: inputData ? JSON.parse(inputData) : {},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const sdkVersion = getSdkVersion();
|
|
127
|
+
|
|
128
|
+
// Ensure hooks-proxy is installed
|
|
129
|
+
let proxy = resolveHooksProxy();
|
|
130
|
+
if (!proxy) {
|
|
131
|
+
installHooksProxy(sdkVersion);
|
|
132
|
+
proxy = resolveHooksProxy();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const handler = `babysitter hook:run --harness unified --hook-type user-prompt-submit --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
let result;
|
|
139
|
+
if (proxy) {
|
|
140
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
141
|
+
result = execSync(`"${proxy}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
142
|
+
input: hookInput,
|
|
143
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
144
|
+
timeout: 10000,
|
|
145
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
149
|
+
result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${sdkVersion}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
150
|
+
input: hookInput,
|
|
151
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
152
|
+
timeout: 30000,
|
|
153
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const output = result.toString("utf8").trim();
|
|
157
|
+
blog(`Hook result: ${output}`);
|
|
158
|
+
process.stdout.write((output || "{}") + "\n");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
blog(`Context hook failed: ${err.message} -- returning empty context`);
|
|
161
|
+
process.stdout.write("{}\n");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main();
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Session Start Hook for Oh-My-Pi
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires when an Oh-My-Pi session begins. Ensures the babysitter SDK CLI
|
|
7
|
+
* and hooks-proxy are installed, then delegates to
|
|
8
|
+
* `babysitter hook:run --hook-type session-start` via hooks-proxy.
|
|
9
|
+
*
|
|
10
|
+
* Oh-My-Pi plugin protocol (programmatic):
|
|
11
|
+
* - Called from extension activate() via execSync
|
|
12
|
+
* - Receives event context as JSON via stdin
|
|
13
|
+
* - Outputs JSON to stdout
|
|
14
|
+
* - Exit 0 = success
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
const { execSync } = require("child_process");
|
|
20
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
21
|
+
const os = require("os");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
const crypto = require("crypto");
|
|
24
|
+
|
|
25
|
+
const PLUGIN_ROOT = process.env.OMP_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
26
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
27
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
28
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
29
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-omp-session-start-hook.log");
|
|
30
|
+
const SDK_MARKER = path.join(PLUGIN_ROOT, ".babysitter-install-attempted");
|
|
31
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Logging
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function ensureDir(dir) {
|
|
38
|
+
try { mkdirSync(dir, { recursive: true }); } catch { /* best-effort */ }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function blog(msg) {
|
|
42
|
+
ensureDir(LOG_DIR);
|
|
43
|
+
const ts = new Date().toISOString();
|
|
44
|
+
try {
|
|
45
|
+
appendFileSync(LOG_FILE, `[INFO] ${ts} ${msg}\n`);
|
|
46
|
+
} catch { /* best-effort */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// SDK version & install
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
function getSdkVersion() {
|
|
54
|
+
try {
|
|
55
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
56
|
+
return versions.sdkVersion || "latest";
|
|
57
|
+
} catch {
|
|
58
|
+
return "latest";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getInstalledVersion(cmd) {
|
|
63
|
+
try {
|
|
64
|
+
return execSync(`${cmd} --version`, { stdio: "pipe", timeout: 10000 }).toString().trim();
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function installPackage(npmPkg, version, marker) {
|
|
71
|
+
if (existsSync(marker)) return;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
execSync(`npm i -g "${npmPkg}@${version}" --loglevel=error`, {
|
|
75
|
+
stdio: "pipe",
|
|
76
|
+
timeout: 120000,
|
|
77
|
+
});
|
|
78
|
+
blog(`Installed ${npmPkg} globally (${version})`);
|
|
79
|
+
} catch {
|
|
80
|
+
// Try user-local prefix
|
|
81
|
+
try {
|
|
82
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
83
|
+
execSync(`npm i -g "${npmPkg}@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
84
|
+
stdio: "pipe",
|
|
85
|
+
timeout: 120000,
|
|
86
|
+
});
|
|
87
|
+
blog(`Installed ${npmPkg} to user prefix (${version})`);
|
|
88
|
+
} catch {
|
|
89
|
+
blog(`${npmPkg} installation failed`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try { writeFileSync(marker, version); } catch { /* best-effort */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Hooks-proxy resolution & install
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
function resolveHooksProxy() {
|
|
101
|
+
// Check PATH first
|
|
102
|
+
try {
|
|
103
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
104
|
+
return "a5c-hooks-proxy";
|
|
105
|
+
} catch { /* not in PATH */ }
|
|
106
|
+
|
|
107
|
+
// Check user-local install
|
|
108
|
+
const localProxy = path.join(
|
|
109
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
110
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
111
|
+
);
|
|
112
|
+
if (existsSync(localProxy)) {
|
|
113
|
+
return localProxy;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// CLI execution helpers
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
function runViaProxy(proxy, hookType, inputJson) {
|
|
124
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
125
|
+
const result = execSync(`"${proxy}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
126
|
+
input: inputJson,
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
timeout: 30000,
|
|
129
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
130
|
+
});
|
|
131
|
+
return result.toString("utf8").trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function runViaNpxProxy(version, hookType, inputJson) {
|
|
135
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
136
|
+
const result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${version}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
137
|
+
input: inputJson,
|
|
138
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
139
|
+
timeout: 60000,
|
|
140
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
141
|
+
});
|
|
142
|
+
return result.toString("utf8").trim();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Main
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
function main() {
|
|
150
|
+
blog("Unified session-start hook invoked");
|
|
151
|
+
blog(`PLUGIN_ROOT=${PLUGIN_ROOT}`);
|
|
152
|
+
|
|
153
|
+
// Generate a session ID if Oh-My-Pi doesn't provide one
|
|
154
|
+
const sessionId = process.env.OMP_SESSION_ID
|
|
155
|
+
|| process.env.PI_SESSION_ID
|
|
156
|
+
|| process.env.BABYSITTER_SESSION_ID
|
|
157
|
+
|| crypto.randomUUID();
|
|
158
|
+
|
|
159
|
+
// Set env var so downstream hooks can pick it up
|
|
160
|
+
process.env.BABYSITTER_SESSION_ID = sessionId;
|
|
161
|
+
|
|
162
|
+
const sdkVersion = getSdkVersion();
|
|
163
|
+
|
|
164
|
+
// Ensure SDK is installed with version sync
|
|
165
|
+
const currentSdkVersion = getInstalledVersion("babysitter");
|
|
166
|
+
if (!currentSdkVersion || currentSdkVersion !== sdkVersion) {
|
|
167
|
+
blog(`SDK needs install/upgrade: installed=${currentSdkVersion || "none"}, required=${sdkVersion}`);
|
|
168
|
+
installPackage("@a5c-ai/babysitter-sdk", sdkVersion, SDK_MARKER);
|
|
169
|
+
} else {
|
|
170
|
+
blog(`SDK version OK: ${currentSdkVersion}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Ensure hooks-proxy is installed with version sync
|
|
174
|
+
const currentProxyVersion = getInstalledVersion("a5c-hooks-proxy");
|
|
175
|
+
if (!currentProxyVersion || currentProxyVersion !== sdkVersion) {
|
|
176
|
+
blog(`hooks-proxy needs install/upgrade: installed=${currentProxyVersion || "none"}, required=${sdkVersion}`);
|
|
177
|
+
installPackage("@a5c-ai/hooks-proxy-cli", sdkVersion, PROXY_MARKER);
|
|
178
|
+
} else {
|
|
179
|
+
blog(`hooks-proxy version OK: ${currentProxyVersion}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Read stdin if available
|
|
183
|
+
let stdinData = "";
|
|
184
|
+
try {
|
|
185
|
+
stdinData = readFileSync(0, "utf8");
|
|
186
|
+
} catch { /* no stdin */ }
|
|
187
|
+
|
|
188
|
+
// Build hook input
|
|
189
|
+
const hookInput = JSON.stringify({
|
|
190
|
+
session_id: sessionId,
|
|
191
|
+
cwd: process.cwd(),
|
|
192
|
+
harness: "oh-my-pi",
|
|
193
|
+
plugin_root: PLUGIN_ROOT,
|
|
194
|
+
...(stdinData ? { event_data: JSON.parse(stdinData) } : {}),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
blog(`Hook input: ${hookInput}`);
|
|
198
|
+
|
|
199
|
+
// Route through hooks-proxy (install or npx fallback)
|
|
200
|
+
const proxy = resolveHooksProxy();
|
|
201
|
+
let result;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
if (proxy) {
|
|
205
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
206
|
+
result = runViaProxy(proxy, "session-start", hookInput);
|
|
207
|
+
} else {
|
|
208
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
209
|
+
result = runViaNpxProxy(sdkVersion, "session-start", hookInput);
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
blog(`Hook execution failed: ${err.message}`);
|
|
213
|
+
result = "{}";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
blog(`Hook result: ${result}`);
|
|
217
|
+
|
|
218
|
+
// Output result
|
|
219
|
+
try {
|
|
220
|
+
const parsed = JSON.parse(result);
|
|
221
|
+
process.stdout.write(JSON.stringify(parsed) + "\n");
|
|
222
|
+
} catch {
|
|
223
|
+
process.stdout.write("{}\n");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main();
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Stop / Orchestration Hook for Oh-My-Pi
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires when the Oh-My-Pi agent completes a turn. Checks if the current
|
|
7
|
+
* babysitter run has pending effects that need attention. Delegates to
|
|
8
|
+
* `babysitter hook:run --hook-type stop` via hooks-proxy.
|
|
9
|
+
*
|
|
10
|
+
* Oh-My-Pi plugin protocol (programmatic):
|
|
11
|
+
* - Called from extension via execSync
|
|
12
|
+
* - Receives event context as JSON via stdin
|
|
13
|
+
* - Outputs JSON to stdout
|
|
14
|
+
* - Exit 0 = success
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
const { execSync } = require("child_process");
|
|
20
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
21
|
+
const os = require("os");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
const PLUGIN_ROOT = process.env.OMP_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
25
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
26
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
27
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
28
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-omp-stop-hook.log");
|
|
29
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
30
|
+
|
|
31
|
+
function ensureDir(dir) {
|
|
32
|
+
try { mkdirSync(dir, { recursive: true }); } catch { /* best-effort */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function blog(msg) {
|
|
36
|
+
ensureDir(LOG_DIR);
|
|
37
|
+
const ts = new Date().toISOString();
|
|
38
|
+
try {
|
|
39
|
+
appendFileSync(LOG_FILE, `[INFO] ${ts} ${msg}\n`);
|
|
40
|
+
} catch { /* best-effort */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSdkVersion() {
|
|
44
|
+
try {
|
|
45
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
46
|
+
return versions.sdkVersion || "latest";
|
|
47
|
+
} catch {
|
|
48
|
+
return "latest";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveHooksProxy() {
|
|
53
|
+
try {
|
|
54
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
55
|
+
return "a5c-hooks-proxy";
|
|
56
|
+
} catch { /* not in PATH */ }
|
|
57
|
+
|
|
58
|
+
const localProxy = path.join(
|
|
59
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
60
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
61
|
+
);
|
|
62
|
+
if (existsSync(localProxy)) {
|
|
63
|
+
return localProxy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function installHooksProxy(version) {
|
|
70
|
+
if (existsSync(PROXY_MARKER)) return;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --loglevel=error`, {
|
|
74
|
+
stdio: "pipe",
|
|
75
|
+
timeout: 120000,
|
|
76
|
+
});
|
|
77
|
+
blog(`Installed hooks-proxy globally (${version})`);
|
|
78
|
+
} catch {
|
|
79
|
+
try {
|
|
80
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
81
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
82
|
+
stdio: "pipe",
|
|
83
|
+
timeout: 120000,
|
|
84
|
+
});
|
|
85
|
+
blog(`Installed hooks-proxy to user prefix (${version})`);
|
|
86
|
+
} catch {
|
|
87
|
+
blog("hooks-proxy installation failed");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try { writeFileSync(PROXY_MARKER, version); } catch { /* best-effort */ }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runViaProxy(proxy, hookType, inputJson) {
|
|
95
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
96
|
+
const result = execSync(`"${proxy}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
97
|
+
input: inputJson,
|
|
98
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
99
|
+
timeout: 30000,
|
|
100
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
101
|
+
});
|
|
102
|
+
return result.toString("utf8").trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runViaNpxProxy(version, hookType, inputJson) {
|
|
106
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
107
|
+
const result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${version}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
108
|
+
input: inputJson,
|
|
109
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
110
|
+
timeout: 60000,
|
|
111
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
112
|
+
});
|
|
113
|
+
return result.toString("utf8").trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function main() {
|
|
117
|
+
blog("Unified stop hook invoked");
|
|
118
|
+
|
|
119
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
120
|
+
|| process.env.OMP_SESSION_ID
|
|
121
|
+
|| process.env.PI_SESSION_ID
|
|
122
|
+
|| "";
|
|
123
|
+
|
|
124
|
+
if (!sessionId) {
|
|
125
|
+
blog("No session ID -- nothing to check");
|
|
126
|
+
process.stdout.write("{}\n");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sdkVersion = getSdkVersion();
|
|
131
|
+
|
|
132
|
+
// Ensure hooks-proxy is installed
|
|
133
|
+
let proxy = resolveHooksProxy();
|
|
134
|
+
if (!proxy) {
|
|
135
|
+
installHooksProxy(sdkVersion);
|
|
136
|
+
proxy = resolveHooksProxy();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Read stdin if available
|
|
140
|
+
let stdinData = "";
|
|
141
|
+
try {
|
|
142
|
+
stdinData = readFileSync(0, "utf8");
|
|
143
|
+
} catch { /* no stdin */ }
|
|
144
|
+
|
|
145
|
+
const hookInput = JSON.stringify({
|
|
146
|
+
session_id: sessionId,
|
|
147
|
+
cwd: process.cwd(),
|
|
148
|
+
harness: "oh-my-pi",
|
|
149
|
+
plugin_root: PLUGIN_ROOT,
|
|
150
|
+
...(stdinData ? { event_data: JSON.parse(stdinData) } : {}),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
blog(`Checking run status for session ${sessionId}`);
|
|
154
|
+
|
|
155
|
+
let result;
|
|
156
|
+
try {
|
|
157
|
+
if (proxy) {
|
|
158
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
159
|
+
result = runViaProxy(proxy, "stop", hookInput);
|
|
160
|
+
} else {
|
|
161
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
162
|
+
result = runViaNpxProxy(sdkVersion, "stop", hookInput);
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
blog(`Hook execution failed: ${err.message}`);
|
|
166
|
+
result = "{}";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
blog(`Hook result: ${result}`);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(result);
|
|
173
|
+
process.stdout.write(JSON.stringify(parsed) + "\n");
|
|
174
|
+
} catch {
|
|
175
|
+
process.stdout.write("{}\n");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
main();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Tool Call Hook for Oh-My-Pi
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires before a tool execution in Oh-My-Pi. Delegates to
|
|
7
|
+
* `babysitter hook:run --hook-type pre-tool-use` via hooks-proxy.
|
|
8
|
+
*
|
|
9
|
+
* Note: Unlike Pi, Oh-My-Pi does NOT support tool input mutation.
|
|
10
|
+
* This hook is observer-only (logging/blocking but no mutation).
|
|
11
|
+
*
|
|
12
|
+
* Oh-My-Pi plugin protocol (programmatic):
|
|
13
|
+
* - Called from extension via execSync
|
|
14
|
+
* - Receives tool context as JSON via stdin
|
|
15
|
+
* - Outputs JSON to stdout (empty = allow, { block: true } = block)
|
|
16
|
+
* - Exit 0 = success
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const { execSync } = require("child_process");
|
|
22
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
23
|
+
const os = require("os");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
|
|
26
|
+
const PLUGIN_ROOT = process.env.OMP_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
27
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
28
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
29
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
30
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-omp-tool-call-hook.log");
|
|
31
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
32
|
+
|
|
33
|
+
function ensureDir(dir) {
|
|
34
|
+
try { mkdirSync(dir, { recursive: true }); } catch { /* best-effort */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function blog(msg) {
|
|
38
|
+
ensureDir(LOG_DIR);
|
|
39
|
+
const ts = new Date().toISOString();
|
|
40
|
+
try {
|
|
41
|
+
appendFileSync(LOG_FILE, `[INFO] ${ts} ${msg}\n`);
|
|
42
|
+
} catch { /* best-effort */ }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getSdkVersion() {
|
|
46
|
+
try {
|
|
47
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
48
|
+
return versions.sdkVersion || "latest";
|
|
49
|
+
} catch {
|
|
50
|
+
return "latest";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveHooksProxy() {
|
|
55
|
+
try {
|
|
56
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
57
|
+
return "a5c-hooks-proxy";
|
|
58
|
+
} catch { /* not in PATH */ }
|
|
59
|
+
|
|
60
|
+
const localProxy = path.join(
|
|
61
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
62
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
63
|
+
);
|
|
64
|
+
if (existsSync(localProxy)) {
|
|
65
|
+
return localProxy;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function installHooksProxy(version) {
|
|
72
|
+
if (existsSync(PROXY_MARKER)) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --loglevel=error`, {
|
|
76
|
+
stdio: "pipe",
|
|
77
|
+
timeout: 120000,
|
|
78
|
+
});
|
|
79
|
+
blog(`Installed hooks-proxy globally (${version})`);
|
|
80
|
+
} catch {
|
|
81
|
+
try {
|
|
82
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
83
|
+
execSync(`npm i -g "@a5c-ai/hooks-proxy-cli@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
84
|
+
stdio: "pipe",
|
|
85
|
+
timeout: 120000,
|
|
86
|
+
});
|
|
87
|
+
blog(`Installed hooks-proxy to user prefix (${version})`);
|
|
88
|
+
} catch {
|
|
89
|
+
blog("hooks-proxy installation failed");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try { writeFileSync(PROXY_MARKER, version); } catch { /* best-effort */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function main() {
|
|
97
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
98
|
+
|| process.env.OMP_SESSION_ID
|
|
99
|
+
|| process.env.PI_SESSION_ID
|
|
100
|
+
|| "";
|
|
101
|
+
|
|
102
|
+
if (!sessionId) {
|
|
103
|
+
// No session -- pass through without intervention
|
|
104
|
+
process.stdout.write("{}\n");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Read stdin for tool context
|
|
109
|
+
let inputData = "";
|
|
110
|
+
try {
|
|
111
|
+
inputData = readFileSync(0, "utf8");
|
|
112
|
+
} catch {
|
|
113
|
+
// No stdin available
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
blog(`Unified tool-call: session=${sessionId}`);
|
|
117
|
+
|
|
118
|
+
const hookInput = JSON.stringify({
|
|
119
|
+
session_id: sessionId,
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
harness: "oh-my-pi",
|
|
122
|
+
plugin_root: PLUGIN_ROOT,
|
|
123
|
+
tool_context: inputData ? JSON.parse(inputData) : {},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const sdkVersion = getSdkVersion();
|
|
127
|
+
|
|
128
|
+
// Ensure hooks-proxy is installed
|
|
129
|
+
let proxy = resolveHooksProxy();
|
|
130
|
+
if (!proxy) {
|
|
131
|
+
installHooksProxy(sdkVersion);
|
|
132
|
+
proxy = resolveHooksProxy();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const handler = `babysitter hook:run --harness unified --hook-type pre-tool-use --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
let result;
|
|
139
|
+
if (proxy) {
|
|
140
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
141
|
+
result = execSync(`"${proxy}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
142
|
+
input: hookInput,
|
|
143
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
144
|
+
timeout: 10000,
|
|
145
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
149
|
+
result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${sdkVersion}" invoke --adapter oh-my-pi --handler "${handler}" --json`, {
|
|
150
|
+
input: hookInput,
|
|
151
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
152
|
+
timeout: 30000,
|
|
153
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const output = result.toString("utf8").trim();
|
|
157
|
+
blog(`Hook result: ${output}`);
|
|
158
|
+
process.stdout.write((output || "{}") + "\n");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// On failure, allow the tool execution to proceed
|
|
161
|
+
blog(`Pre-tool-use hook failed: ${err.message} -- allowing execution`);
|
|
162
|
+
process.stdout.write("{}\n");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main();
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Babysitter hook registration for Oh-My-Pi. Maps Oh-My-Pi extension events to unified babysitter hook scripts with hooks-proxy support.",
|
|
4
|
+
"hooks": {
|
|
5
|
+
"session_start": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"script": "hooks/babysitter-proxied-session-start.js",
|
|
9
|
+
"description": "Initialize babysitter session state and inject context (proxied via a5c-hooks-proxy)",
|
|
10
|
+
"timeoutMs": 30000
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"tool_call": [
|
|
14
|
+
{
|
|
15
|
+
"type": "command",
|
|
16
|
+
"script": "hooks/babysitter-proxied-tool-call.js",
|
|
17
|
+
"description": "Pre-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
18
|
+
"timeoutMs": 10000
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"context": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"script": "hooks/babysitter-proxied-context.js",
|
|
25
|
+
"description": "Context injection for babysitter orchestration (proxied via a5c-hooks-proxy)",
|
|
26
|
+
"timeoutMs": 10000
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"before_provider_request": [
|
|
30
|
+
{
|
|
31
|
+
"type": "command",
|
|
32
|
+
"script": "hooks/babysitter-proxied-before-provider-request.js",
|
|
33
|
+
"description": "Before provider request hook for babysitter observability (proxied via a5c-hooks-proxy)",
|
|
34
|
+
"timeoutMs": 10000
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"stop": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"script": "hooks/babysitter-proxied-stop.js",
|
|
41
|
+
"description": "Check for pending babysitter effects when agent completes a turn (proxied via a5c-hooks-proxy)",
|
|
42
|
+
"timeoutMs": 30000
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Reference: hooks-proxy routing configuration for Oh-My-Pi. All hooks route through a5c-hooks-proxy with --adapter oh-my-pi.",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "Babysitter hook registration for Oh-My-Pi. Maps Oh-My-Pi extension events to unified babysitter hook scripts with hooks-proxy support.",
|
|
5
|
+
"hooks": {
|
|
6
|
+
"session_start": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"script": "hooks/babysitter-proxied-session-start.js",
|
|
10
|
+
"description": "Initialize babysitter session state and inject context (proxied via a5c-hooks-proxy)",
|
|
11
|
+
"timeoutMs": 30000
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"tool_call": [
|
|
15
|
+
{
|
|
16
|
+
"type": "command",
|
|
17
|
+
"script": "hooks/babysitter-proxied-tool-call.js",
|
|
18
|
+
"description": "Pre-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
19
|
+
"timeoutMs": 10000
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"context": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"script": "hooks/babysitter-proxied-context.js",
|
|
26
|
+
"description": "Context injection for babysitter orchestration (proxied via a5c-hooks-proxy)",
|
|
27
|
+
"timeoutMs": 10000
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"before_provider_request": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"script": "hooks/babysitter-proxied-before-provider-request.js",
|
|
34
|
+
"description": "Before provider request hook for babysitter observability (proxied via a5c-hooks-proxy)",
|
|
35
|
+
"timeoutMs": 10000
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"stop": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"script": "hooks/babysitter-proxied-stop.js",
|
|
42
|
+
"description": "Check for pending babysitter effects when agent completes a turn (proxied via a5c-hooks-proxy)",
|
|
43
|
+
"timeoutMs": 30000
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a5c-ai/babysitter-omp",
|
|
3
|
-
"version": "5.0.1-staging.
|
|
3
|
+
"version": "5.0.1-staging.6e094dee",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Babysitter package for oh-my-pi",
|
|
6
6
|
"keywords": [
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
"extensions": [
|
|
15
15
|
"./extensions"
|
|
16
16
|
],
|
|
17
|
+
"hooks": "./hooks/hooks.json",
|
|
17
18
|
"skills": [
|
|
18
19
|
"./skills"
|
|
19
20
|
]
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@a5c-ai/babysitter-sdk": "5.0.1-staging.
|
|
23
|
+
"@a5c-ai/babysitter-sdk": "5.0.1-staging.6e094dee"
|
|
23
24
|
},
|
|
24
25
|
"peerDependencies": {
|
|
25
26
|
"@oh-my-pi/pi-coding-agent": "*"
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"README.md",
|
|
43
44
|
"AGENTS.md",
|
|
44
45
|
"extensions/",
|
|
46
|
+
"hooks/",
|
|
45
47
|
"skills/",
|
|
46
48
|
"commands/",
|
|
47
49
|
"scripts/"
|
package/versions.json
CHANGED