@a5c-ai/babysitter-opencode 5.0.1-staging.5cd43600 → 5.0.1-staging.c66885f8
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/hooks/babysitter-proxied-session-created.js +218 -0
- package/hooks/babysitter-proxied-session-idle.js +173 -0
- package/hooks/babysitter-proxied-shell-env.js +148 -0
- package/hooks/babysitter-proxied-tool-execute-after.js +164 -0
- package/hooks/babysitter-proxied-tool-execute-before.js +166 -0
- package/hooks/hooks.json +11 -11
- package/hooks/hooks.json.legacy +46 -0
- package/hooks/proxied-hooks.json +47 -0
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/versions.json +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Session Created Hook for OpenCode
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires when an OpenCode session is created. 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
|
+
* OpenCode plugin protocol:
|
|
11
|
+
* - Receives event context as JSON via process.argv or stdin
|
|
12
|
+
* - Outputs JSON to stdout
|
|
13
|
+
* - Exit 0 = success
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
const { execSync, execFileSync } = require("child_process");
|
|
19
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
20
|
+
const os = require("os");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const crypto = require("crypto");
|
|
23
|
+
|
|
24
|
+
const PLUGIN_ROOT = process.env.OPENCODE_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-session-created-hook.log");
|
|
29
|
+
const SDK_MARKER = path.join(PLUGIN_ROOT, ".babysitter-install-attempted");
|
|
30
|
+
const PROXY_MARKER = path.join(PLUGIN_ROOT, ".hooks-proxy-install-attempted");
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Logging
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// SDK version & install
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
function getSdkVersion() {
|
|
53
|
+
try {
|
|
54
|
+
const versions = JSON.parse(readFileSync(path.join(PLUGIN_ROOT, "versions.json"), "utf8"));
|
|
55
|
+
return versions.sdkVersion || "latest";
|
|
56
|
+
} catch {
|
|
57
|
+
return "latest";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getInstalledVersion(cmd) {
|
|
62
|
+
try {
|
|
63
|
+
return execSync(`${cmd} --version`, { stdio: "pipe", timeout: 10000 }).toString().trim();
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function installPackage(npmPkg, version, marker) {
|
|
70
|
+
if (existsSync(marker)) return;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
execSync(`npm i -g "${npmPkg}@${version}" --loglevel=error`, {
|
|
74
|
+
stdio: "pipe",
|
|
75
|
+
timeout: 120000,
|
|
76
|
+
});
|
|
77
|
+
blog(`Installed ${npmPkg} globally (${version})`);
|
|
78
|
+
} catch {
|
|
79
|
+
// Try user-local prefix
|
|
80
|
+
try {
|
|
81
|
+
const prefix = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local");
|
|
82
|
+
execSync(`npm i -g "${npmPkg}@${version}" --prefix "${prefix}" --loglevel=error`, {
|
|
83
|
+
stdio: "pipe",
|
|
84
|
+
timeout: 120000,
|
|
85
|
+
});
|
|
86
|
+
blog(`Installed ${npmPkg} to user prefix (${version})`);
|
|
87
|
+
} catch {
|
|
88
|
+
blog(`${npmPkg} installation failed`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try { writeFileSync(marker, version); } catch { /* best-effort */ }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Hooks-proxy resolution & install
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function resolveHooksProxy() {
|
|
100
|
+
// Check PATH first
|
|
101
|
+
try {
|
|
102
|
+
execSync("a5c-hooks-proxy --version", { stdio: "pipe", timeout: 5000 });
|
|
103
|
+
return "a5c-hooks-proxy";
|
|
104
|
+
} catch { /* not in PATH */ }
|
|
105
|
+
|
|
106
|
+
// Check user-local install
|
|
107
|
+
const localProxy = path.join(
|
|
108
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
109
|
+
".local", "bin", process.platform === "win32" ? "a5c-hooks-proxy.exe" : "a5c-hooks-proxy"
|
|
110
|
+
);
|
|
111
|
+
if (existsSync(localProxy)) {
|
|
112
|
+
return localProxy;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// CLI execution helpers
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
function runViaProxy(proxy, hookType, inputJson) {
|
|
123
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
124
|
+
const result = execSync(`"${proxy}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
125
|
+
input: inputJson,
|
|
126
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
127
|
+
timeout: 30000,
|
|
128
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
129
|
+
});
|
|
130
|
+
return result.toString("utf8").trim();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function runViaNpxProxy(version, hookType, inputJson) {
|
|
134
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
135
|
+
const result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${version}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
136
|
+
input: inputJson,
|
|
137
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
138
|
+
timeout: 60000,
|
|
139
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
140
|
+
});
|
|
141
|
+
return result.toString("utf8").trim();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Main
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function main() {
|
|
149
|
+
blog("Unified session-created hook invoked");
|
|
150
|
+
blog(`PLUGIN_ROOT=${PLUGIN_ROOT}`);
|
|
151
|
+
|
|
152
|
+
// Generate a session ID if OpenCode doesn't provide one
|
|
153
|
+
const sessionId = process.env.OPENCODE_SESSION_ID
|
|
154
|
+
|| process.env.BABYSITTER_SESSION_ID
|
|
155
|
+
|| crypto.randomUUID();
|
|
156
|
+
|
|
157
|
+
// Set env var so downstream hooks can pick it up
|
|
158
|
+
process.env.BABYSITTER_SESSION_ID = sessionId;
|
|
159
|
+
|
|
160
|
+
const sdkVersion = getSdkVersion();
|
|
161
|
+
|
|
162
|
+
// Ensure SDK is installed with version sync
|
|
163
|
+
const currentSdkVersion = getInstalledVersion("babysitter");
|
|
164
|
+
if (!currentSdkVersion || currentSdkVersion !== sdkVersion) {
|
|
165
|
+
blog(`SDK needs install/upgrade: installed=${currentSdkVersion || "none"}, required=${sdkVersion}`);
|
|
166
|
+
installPackage("@a5c-ai/babysitter-sdk", sdkVersion, SDK_MARKER);
|
|
167
|
+
} else {
|
|
168
|
+
blog(`SDK version OK: ${currentSdkVersion}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Ensure hooks-proxy is installed with version sync
|
|
172
|
+
const currentProxyVersion = getInstalledVersion("a5c-hooks-proxy");
|
|
173
|
+
if (!currentProxyVersion || currentProxyVersion !== sdkVersion) {
|
|
174
|
+
blog(`hooks-proxy needs install/upgrade: installed=${currentProxyVersion || "none"}, required=${sdkVersion}`);
|
|
175
|
+
installPackage("@a5c-ai/hooks-proxy-cli", sdkVersion, PROXY_MARKER);
|
|
176
|
+
} else {
|
|
177
|
+
blog(`hooks-proxy version OK: ${currentProxyVersion}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build hook input
|
|
181
|
+
const hookInput = JSON.stringify({
|
|
182
|
+
session_id: sessionId,
|
|
183
|
+
cwd: process.cwd(),
|
|
184
|
+
harness: "opencode",
|
|
185
|
+
plugin_root: PLUGIN_ROOT,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
blog(`Hook input: ${hookInput}`);
|
|
189
|
+
|
|
190
|
+
// Route through hooks-proxy (install or npx fallback)
|
|
191
|
+
const proxy = resolveHooksProxy();
|
|
192
|
+
let result;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
if (proxy) {
|
|
196
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
197
|
+
result = runViaProxy(proxy, "session-start", hookInput);
|
|
198
|
+
} else {
|
|
199
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
200
|
+
result = runViaNpxProxy(sdkVersion, "session-start", hookInput);
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
blog(`Hook execution failed: ${err.message}`);
|
|
204
|
+
result = "{}";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
blog(`Hook result: ${result}`);
|
|
208
|
+
|
|
209
|
+
// Output result
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(result);
|
|
212
|
+
process.stdout.write(JSON.stringify(parsed) + "\n");
|
|
213
|
+
} catch {
|
|
214
|
+
process.stdout.write("{}\n");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
main();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Session Idle Hook for OpenCode
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires when the OpenCode agent goes idle. Checks if the current babysitter
|
|
7
|
+
* run has pending effects that need attention. Since OpenCode does NOT have a
|
|
8
|
+
* blocking stop hook, this is fire-and-forget -- it outputs context about
|
|
9
|
+
* pending effects so the agent can decide whether to continue iterating.
|
|
10
|
+
*
|
|
11
|
+
* Delegates to `babysitter hook:run --hook-type stop` via hooks-proxy.
|
|
12
|
+
*
|
|
13
|
+
* OpenCode plugin protocol:
|
|
14
|
+
* - Receives event context as JSON via stdin
|
|
15
|
+
* - Outputs JSON to stdout
|
|
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.OPENCODE_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-session-idle-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 runViaProxy(proxy, hookType, inputJson) {
|
|
97
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
98
|
+
const result = execSync(`"${proxy}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
99
|
+
input: inputJson,
|
|
100
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
101
|
+
timeout: 30000,
|
|
102
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
103
|
+
});
|
|
104
|
+
return result.toString("utf8").trim();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function runViaNpxProxy(version, hookType, inputJson) {
|
|
108
|
+
const handler = `babysitter hook:run --harness unified --hook-type ${hookType} --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
109
|
+
const result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${version}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
110
|
+
input: inputJson,
|
|
111
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
112
|
+
timeout: 60000,
|
|
113
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
114
|
+
});
|
|
115
|
+
return result.toString("utf8").trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function main() {
|
|
119
|
+
blog("Unified session-idle hook invoked");
|
|
120
|
+
|
|
121
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
122
|
+
|| process.env.OPENCODE_SESSION_ID
|
|
123
|
+
|| "";
|
|
124
|
+
|
|
125
|
+
if (!sessionId) {
|
|
126
|
+
blog("No session ID -- nothing to check");
|
|
127
|
+
process.stdout.write("{}\n");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sdkVersion = getSdkVersion();
|
|
132
|
+
|
|
133
|
+
// Ensure hooks-proxy is installed
|
|
134
|
+
let proxy = resolveHooksProxy();
|
|
135
|
+
if (!proxy) {
|
|
136
|
+
installHooksProxy(sdkVersion);
|
|
137
|
+
proxy = resolveHooksProxy();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hookInput = JSON.stringify({
|
|
141
|
+
session_id: sessionId,
|
|
142
|
+
cwd: process.cwd(),
|
|
143
|
+
harness: "opencode",
|
|
144
|
+
plugin_root: PLUGIN_ROOT,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
blog(`Checking run status for session ${sessionId}`);
|
|
148
|
+
|
|
149
|
+
let result;
|
|
150
|
+
try {
|
|
151
|
+
if (proxy) {
|
|
152
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
153
|
+
result = runViaProxy(proxy, "stop", hookInput);
|
|
154
|
+
} else {
|
|
155
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
156
|
+
result = runViaNpxProxy(sdkVersion, "stop", hookInput);
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
blog(`Hook execution failed: ${err.message}`);
|
|
160
|
+
result = "{}";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
blog(`Hook result: ${result}`);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(result);
|
|
167
|
+
process.stdout.write(JSON.stringify(parsed) + "\n");
|
|
168
|
+
} catch {
|
|
169
|
+
process.stdout.write("{}\n");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main();
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Shell Environment Hook for OpenCode
|
|
4
|
+
* Routes through hooks-proxy when available for consistency,
|
|
5
|
+
* but primarily performs direct env var injection.
|
|
6
|
+
*
|
|
7
|
+
* Fires when OpenCode initializes a shell environment. Injects babysitter
|
|
8
|
+
* environment variables (BABYSITTER_SESSION_ID, BABYSITTER_STATE_DIR, etc.)
|
|
9
|
+
* so that subprocesses and other hooks can discover the active session.
|
|
10
|
+
*
|
|
11
|
+
* This is critical for OpenCode because it does NOT natively inject
|
|
12
|
+
* distinctive env vars into plugins -- the babysitter plugin must self-inject
|
|
13
|
+
* them via this hook.
|
|
14
|
+
*
|
|
15
|
+
* OpenCode plugin protocol:
|
|
16
|
+
* - Outputs env var assignments as JSON: { "env": { "KEY": "VALUE" } }
|
|
17
|
+
* - Exit 0 = success
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const { readFileSync, mkdirSync, appendFileSync, existsSync, writeFileSync } = require("fs");
|
|
23
|
+
const { execSync } = require("child_process");
|
|
24
|
+
const os = require("os");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
const crypto = require("crypto");
|
|
27
|
+
|
|
28
|
+
const PLUGIN_ROOT = process.env.OPENCODE_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
29
|
+
const GLOBAL_ROOT = process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(os.homedir(), ".a5c");
|
|
30
|
+
const STATE_DIR = process.env.BABYSITTER_STATE_DIR || path.join(GLOBAL_ROOT, "state");
|
|
31
|
+
const RUNS_DIR = process.env.BABYSITTER_RUNS_DIR || path.join(GLOBAL_ROOT, "runs");
|
|
32
|
+
const LOG_DIR = process.env.BABYSITTER_LOG_DIR || path.join(GLOBAL_ROOT, "logs");
|
|
33
|
+
const LOG_FILE = path.join(LOG_DIR, "babysitter-shell-env-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
|
+
blog("Unified shell-env hook invoked");
|
|
101
|
+
|
|
102
|
+
// Resolve or generate session ID
|
|
103
|
+
const sessionId = process.env.BABYSITTER_SESSION_ID
|
|
104
|
+
|| process.env.OPENCODE_SESSION_ID
|
|
105
|
+
|| crypto.randomUUID();
|
|
106
|
+
|
|
107
|
+
const sdkVersion = getSdkVersion();
|
|
108
|
+
|
|
109
|
+
// Ensure hooks-proxy is installed (for other hooks to use)
|
|
110
|
+
const proxy = resolveHooksProxy();
|
|
111
|
+
if (!proxy) {
|
|
112
|
+
installHooksProxy(sdkVersion);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build env vars to inject
|
|
116
|
+
const env = {
|
|
117
|
+
BABYSITTER_SESSION_ID: sessionId,
|
|
118
|
+
OPENCODE_SESSION_ID: sessionId,
|
|
119
|
+
BABYSITTER_STATE_DIR: STATE_DIR,
|
|
120
|
+
BABYSITTER_RUNS_DIR: RUNS_DIR,
|
|
121
|
+
OPENCODE_PLUGIN_ROOT: PLUGIN_ROOT,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Add SDK version for downstream hooks
|
|
125
|
+
if (sdkVersion && sdkVersion !== "latest") {
|
|
126
|
+
env.BABYSITTER_SDK_VERSION = sdkVersion;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add global state dir if defined
|
|
130
|
+
const globalStateDir = process.env.BABYSITTER_GLOBAL_STATE_DIR;
|
|
131
|
+
if (globalStateDir) {
|
|
132
|
+
env.BABYSITTER_GLOBAL_STATE_DIR = globalStateDir;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Note: shell-env is purely env injection — hooks-proxy routing is logged
|
|
136
|
+
// but the output is always the same env vars object. The proxy can
|
|
137
|
+
// potentially enrich env vars in the future.
|
|
138
|
+
const resolvedProxy = resolveHooksProxy();
|
|
139
|
+
if (resolvedProxy) {
|
|
140
|
+
blog(`hooks-proxy available: ${resolvedProxy} (env injection is direct)`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
blog(`Injecting env: ${JSON.stringify(env)}`);
|
|
144
|
+
|
|
145
|
+
process.stdout.write(JSON.stringify({ env }) + "\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main();
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Tool Execute After Hook for OpenCode
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires after a tool execution in OpenCode. Delegates to
|
|
7
|
+
* `babysitter hook:run --hook-type post-tool-use` via hooks-proxy.
|
|
8
|
+
*
|
|
9
|
+
* This hook can be used to:
|
|
10
|
+
* - Log tool execution results for babysitter run observability
|
|
11
|
+
* - Trigger babysitter effects based on tool outputs
|
|
12
|
+
* - Update session state after tool executions
|
|
13
|
+
*
|
|
14
|
+
* OpenCode plugin protocol:
|
|
15
|
+
* - Receives tool result context as JSON via stdin
|
|
16
|
+
* - Outputs JSON to stdout
|
|
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.OPENCODE_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-tool-after-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.OPENCODE_SESSION_ID
|
|
100
|
+
|| "";
|
|
101
|
+
|
|
102
|
+
if (!sessionId) {
|
|
103
|
+
process.stdout.write("{}\n");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Read stdin for tool result context
|
|
108
|
+
let inputData = "";
|
|
109
|
+
try {
|
|
110
|
+
inputData = require("fs").readFileSync(0, "utf8");
|
|
111
|
+
} catch {
|
|
112
|
+
// No stdin available
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
blog(`Unified tool-execute-after: session=${sessionId}`);
|
|
116
|
+
|
|
117
|
+
const hookInput = JSON.stringify({
|
|
118
|
+
session_id: sessionId,
|
|
119
|
+
cwd: process.cwd(),
|
|
120
|
+
harness: "opencode",
|
|
121
|
+
plugin_root: PLUGIN_ROOT,
|
|
122
|
+
tool_result: inputData ? JSON.parse(inputData) : {},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const sdkVersion = getSdkVersion();
|
|
126
|
+
|
|
127
|
+
// Ensure hooks-proxy is installed
|
|
128
|
+
let proxy = resolveHooksProxy();
|
|
129
|
+
if (!proxy) {
|
|
130
|
+
installHooksProxy(sdkVersion);
|
|
131
|
+
proxy = resolveHooksProxy();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const handler = `babysitter hook:run --harness unified --hook-type post-tool-use --plugin-root ${PLUGIN_ROOT} --state-dir ${STATE_DIR} --json`;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
let result;
|
|
138
|
+
if (proxy) {
|
|
139
|
+
blog(`Using hooks-proxy: ${proxy}`);
|
|
140
|
+
result = execSync(`"${proxy}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
141
|
+
input: hookInput,
|
|
142
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
143
|
+
timeout: 10000,
|
|
144
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
blog("hooks-proxy not found after install, using npx fallback");
|
|
148
|
+
result = execSync(`npx -y "@a5c-ai/hooks-proxy-cli@${sdkVersion}" invoke --adapter opencode --handler "${handler}" --json`, {
|
|
149
|
+
input: hookInput,
|
|
150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
151
|
+
timeout: 30000,
|
|
152
|
+
env: { ...process.env, BABYSITTER_STATE_DIR: STATE_DIR },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const output = result.toString("utf8").trim();
|
|
156
|
+
blog(`Hook result: ${output}`);
|
|
157
|
+
process.stdout.write((output || "{}") + "\n");
|
|
158
|
+
} catch (err) {
|
|
159
|
+
blog(`Post-tool-use hook failed: ${err.message} -- non-blocking`);
|
|
160
|
+
process.stdout.write("{}\n");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Tool Execute Before Hook for OpenCode
|
|
4
|
+
* Routes through hooks-proxy for all hook execution.
|
|
5
|
+
*
|
|
6
|
+
* Fires before a tool execution in OpenCode. Delegates to
|
|
7
|
+
* `babysitter hook:run --hook-type pre-tool-use` via hooks-proxy.
|
|
8
|
+
*
|
|
9
|
+
* This hook can be used to:
|
|
10
|
+
* - Log tool invocations for babysitter run observability
|
|
11
|
+
* - Block certain tool calls during specific orchestration phases
|
|
12
|
+
* - Inject babysitter context into tool arguments
|
|
13
|
+
*
|
|
14
|
+
* OpenCode plugin protocol:
|
|
15
|
+
* - Receives tool context as JSON via stdin
|
|
16
|
+
* - Outputs JSON to stdout (empty = allow, { block: true } = block)
|
|
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.OPENCODE_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-tool-before-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.OPENCODE_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 = require("fs").readFileSync(0, "utf8");
|
|
112
|
+
} catch {
|
|
113
|
+
// No stdin available
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
blog(`Unified tool-execute-before: session=${sessionId}`);
|
|
117
|
+
|
|
118
|
+
const hookInput = JSON.stringify({
|
|
119
|
+
session_id: sessionId,
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
harness: "opencode",
|
|
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 opencode --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 opencode --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
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"description": "Babysitter hook registration for OpenCode. Maps OpenCode plugin events to babysitter hook scripts.",
|
|
3
|
+
"description": "Babysitter hook registration for OpenCode. Maps OpenCode plugin events to unified babysitter hook scripts with hooks-proxy support.",
|
|
4
4
|
"hooks": {
|
|
5
5
|
"session.created": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"script": "hooks/session-created.js",
|
|
9
|
-
"description": "Initialize babysitter session state and inject context",
|
|
8
|
+
"script": "hooks/babysitter-proxied-session-created.js",
|
|
9
|
+
"description": "Initialize babysitter session state and inject context (proxied via a5c-hooks-proxy)",
|
|
10
10
|
"timeoutMs": 30000
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"session.idle": [
|
|
14
14
|
{
|
|
15
15
|
"type": "command",
|
|
16
|
-
"script": "hooks/session-idle.js",
|
|
17
|
-
"description": "Check for pending babysitter effects when agent goes idle",
|
|
16
|
+
"script": "hooks/babysitter-proxied-session-idle.js",
|
|
17
|
+
"description": "Check for pending babysitter effects when agent goes idle (proxied via a5c-hooks-proxy)",
|
|
18
18
|
"timeoutMs": 30000
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"shell.env": [
|
|
22
22
|
{
|
|
23
23
|
"type": "command",
|
|
24
|
-
"script": "hooks/shell-env.js",
|
|
25
|
-
"description": "Inject BABYSITTER_SESSION_ID and other env vars into shell",
|
|
24
|
+
"script": "hooks/babysitter-proxied-shell-env.js",
|
|
25
|
+
"description": "Inject BABYSITTER_SESSION_ID and other env vars into shell (proxied via a5c-hooks-proxy)",
|
|
26
26
|
"timeoutMs": 5000
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"tool.execute.before": [
|
|
30
30
|
{
|
|
31
31
|
"type": "command",
|
|
32
|
-
"script": "hooks/tool-execute-before.js",
|
|
33
|
-
"description": "Pre-tool-use hook for babysitter awareness",
|
|
32
|
+
"script": "hooks/babysitter-proxied-tool-execute-before.js",
|
|
33
|
+
"description": "Pre-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
34
34
|
"timeoutMs": 10000
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"tool.execute.after": [
|
|
38
38
|
{
|
|
39
39
|
"type": "command",
|
|
40
|
-
"script": "hooks/tool-execute-after.js",
|
|
41
|
-
"description": "Post-tool-use hook for babysitter awareness",
|
|
40
|
+
"script": "hooks/babysitter-proxied-tool-execute-after.js",
|
|
41
|
+
"description": "Post-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
42
42
|
"timeoutMs": 10000
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Babysitter hook registration for OpenCode. Maps OpenCode plugin events to babysitter hook scripts.",
|
|
4
|
+
"hooks": {
|
|
5
|
+
"session.created": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"script": "hooks/session-created.js",
|
|
9
|
+
"description": "Initialize babysitter session state and inject context",
|
|
10
|
+
"timeoutMs": 30000
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"session.idle": [
|
|
14
|
+
{
|
|
15
|
+
"type": "command",
|
|
16
|
+
"script": "hooks/session-idle.js",
|
|
17
|
+
"description": "Check for pending babysitter effects when agent goes idle",
|
|
18
|
+
"timeoutMs": 30000
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"shell.env": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"script": "hooks/shell-env.js",
|
|
25
|
+
"description": "Inject BABYSITTER_SESSION_ID and other env vars into shell",
|
|
26
|
+
"timeoutMs": 5000
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"tool.execute.before": [
|
|
30
|
+
{
|
|
31
|
+
"type": "command",
|
|
32
|
+
"script": "hooks/tool-execute-before.js",
|
|
33
|
+
"description": "Pre-tool-use hook for babysitter awareness",
|
|
34
|
+
"timeoutMs": 10000
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"tool.execute.after": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"script": "hooks/tool-execute-after.js",
|
|
41
|
+
"description": "Post-tool-use hook for babysitter awareness",
|
|
42
|
+
"timeoutMs": 10000
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "NOT ACTIVE — Alternative hooks.json using per-hook unified scripts with hooks-proxy support. To activate, replace hooks.json with this file.",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "Babysitter hook registration for OpenCode. Maps OpenCode plugin events to unified babysitter hook scripts with hooks-proxy support.",
|
|
5
|
+
"hooks": {
|
|
6
|
+
"session.created": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"script": "hooks/babysitter-proxied-session-created.js",
|
|
10
|
+
"description": "Initialize babysitter session state and inject context (proxied via a5c-hooks-proxy)",
|
|
11
|
+
"timeoutMs": 30000
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"session.idle": [
|
|
15
|
+
{
|
|
16
|
+
"type": "command",
|
|
17
|
+
"script": "hooks/babysitter-proxied-session-idle.js",
|
|
18
|
+
"description": "Check for pending babysitter effects when agent goes idle (proxied via a5c-hooks-proxy)",
|
|
19
|
+
"timeoutMs": 30000
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"shell.env": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"script": "hooks/babysitter-proxied-shell-env.js",
|
|
26
|
+
"description": "Inject BABYSITTER_SESSION_ID and other env vars into shell (proxied via a5c-hooks-proxy)",
|
|
27
|
+
"timeoutMs": 5000
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"tool.execute.before": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"script": "hooks/babysitter-proxied-tool-execute-before.js",
|
|
34
|
+
"description": "Pre-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
35
|
+
"timeoutMs": 10000
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"tool.execute.after": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"script": "hooks/babysitter-proxied-tool-execute-after.js",
|
|
42
|
+
"description": "Post-tool-use hook for babysitter awareness (proxied via a5c-hooks-proxy)",
|
|
43
|
+
"timeoutMs": 10000
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a5c-ai/babysitter-opencode",
|
|
3
|
-
"version": "5.0.1-staging.
|
|
3
|
+
"version": "5.0.1-staging.c66885f8",
|
|
4
4
|
"description": "Babysitter orchestration plugin for OpenCode with SDK-managed process-library bootstrapping and in-turn iteration model",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node test/integration.test.js",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
},
|
|
42
42
|
"homepage": "https://github.com/a5c-ai/babysitter/tree/main/plugins/babysitter-opencode#readme",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@a5c-ai/babysitter-sdk": "5.0.1-staging.
|
|
44
|
+
"@a5c-ai/babysitter-sdk": "5.0.1-staging.c66885f8"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babysitter",
|
|
3
|
-
"version": "5.0.1-staging.
|
|
3
|
+
"version": "5.0.1-staging.c66885f8",
|
|
4
4
|
"description": "Orchestrate complex, multi-step workflows with event-sourced state management, hook-based extensibility, and human-in-the-loop approval -- powered by the Babysitter SDK",
|
|
5
5
|
"author": "a5c.ai",
|
|
6
6
|
"license": "MIT",
|
package/versions.json
CHANGED