@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.
@@ -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
+ }
@@ -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();
@@ -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.5cd43600",
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.5cd43600"
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
@@ -1,3 +1,3 @@
1
1
  {
2
- "sdkVersion": "5.0.1-staging.5cd43600"
2
+ "sdkVersion": "5.0.1-staging.6e094dee"
3
3
  }