@a5c-ai/babysitter-sdk 0.0.184 → 0.0.185

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.
Files changed (52) hide show
  1. package/dist/cli/commands/instructions.d.ts.map +1 -1
  2. package/dist/cli/commands/instructions.js +41 -0
  3. package/dist/cli/main.js +2 -2
  4. package/dist/config/defaults.d.ts +11 -0
  5. package/dist/config/defaults.d.ts.map +1 -1
  6. package/dist/config/defaults.js +55 -0
  7. package/dist/config/index.d.ts +1 -1
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +2 -1
  10. package/dist/harness/claudeCode.d.ts +2 -2
  11. package/dist/harness/claudeCode.d.ts.map +1 -1
  12. package/dist/harness/claudeCode.js +47 -72
  13. package/dist/harness/codex.d.ts.map +1 -1
  14. package/dist/harness/codex.js +10 -49
  15. package/dist/harness/cursor.d.ts.map +1 -1
  16. package/dist/harness/cursor.js +23 -34
  17. package/dist/harness/customAdapter.d.ts.map +1 -1
  18. package/dist/harness/customAdapter.js +8 -3
  19. package/dist/harness/discovery.js +7 -7
  20. package/dist/harness/geminiCli.d.ts.map +1 -1
  21. package/dist/harness/geminiCli.js +20 -27
  22. package/dist/harness/githubCopilot.d.ts.map +1 -1
  23. package/dist/harness/githubCopilot.js +17 -40
  24. package/dist/harness/index.d.ts +1 -0
  25. package/dist/harness/index.d.ts.map +1 -1
  26. package/dist/harness/index.js +3 -1
  27. package/dist/harness/installSupport.d.ts +1 -0
  28. package/dist/harness/installSupport.d.ts.map +1 -1
  29. package/dist/harness/installSupport.js +7 -3
  30. package/dist/harness/invoker.js +1 -1
  31. package/dist/harness/ohMyPi.d.ts +0 -14
  32. package/dist/harness/ohMyPi.d.ts.map +1 -1
  33. package/dist/harness/ohMyPi.js +163 -24
  34. package/dist/harness/opencode.d.ts +28 -0
  35. package/dist/harness/opencode.d.ts.map +1 -0
  36. package/dist/harness/opencode.js +578 -0
  37. package/dist/harness/pi.d.ts +1 -11
  38. package/dist/harness/pi.d.ts.map +1 -1
  39. package/dist/harness/pi.js +81 -105
  40. package/dist/harness/registry.d.ts.map +1 -1
  41. package/dist/harness/registry.js +2 -0
  42. package/dist/prompts/context.d.ts +9 -0
  43. package/dist/prompts/context.d.ts.map +1 -1
  44. package/dist/prompts/context.js +128 -20
  45. package/dist/prompts/index.d.ts +1 -1
  46. package/dist/prompts/index.d.ts.map +1 -1
  47. package/dist/prompts/index.js +2 -1
  48. package/dist/prompts/templates/completion-proof.md +8 -0
  49. package/dist/prompts/templates/critical-rules.md +5 -5
  50. package/dist/prompts/types.d.ts +4 -5
  51. package/dist/prompts/types.d.ts.map +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,578 @@
1
+ "use strict";
2
+ /**
3
+ * OpenCode harness adapter.
4
+ *
5
+ * Centralizes all OpenCode-specific behaviors:
6
+ * - Session ID resolution (BABYSITTER_SESSION_ID, self-injected via shell.env hook)
7
+ * - State directory conventions (global state dir by default)
8
+ * - Session binding (run:create → state file with run association)
9
+ * - Minimal stop hook handler (no native stop hook — in-turn model)
10
+ * - Session-start hook handler (baseline state file creation)
11
+ *
12
+ * OpenCode characteristics:
13
+ * - Go binary, CLI command: `opencode`
14
+ * - Config directory: `.opencode/`
15
+ * - Plugin system: JS/TS modules in `.opencode/plugins/` with hooks for
16
+ * `tool.execute.before/after`, `session.idle`, `session.created`, `shell.env`
17
+ * - NO blocking stop hook — `session.idle` is fire-and-forget
18
+ * - Programmatic API: `@opencode-ai/sdk` with `session.create()`,
19
+ * `session.prompt()`, `event.subscribe()` (SSE)
20
+ * - Env vars: Does NOT inject distinctive env vars into plugins — weak
21
+ * caller detection. The babysitter plugin self-injects
22
+ * `BABYSITTER_SESSION_ID` via the `shell.env` hook.
23
+ * - Loop mechanism: in-turn (no stop-hook, must use SDK loop driver or
24
+ * in-turn orchestration)
25
+ * - Capabilities: [HeadlessPrompt] only
26
+ */
27
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ var desc = Object.getOwnPropertyDescriptor(m, k);
30
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
31
+ desc = { enumerable: true, get: function() { return m[k]; } };
32
+ }
33
+ Object.defineProperty(o, k2, desc);
34
+ }) : (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ o[k2] = m[k];
37
+ }));
38
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
39
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
40
+ }) : function(o, v) {
41
+ o["default"] = v;
42
+ });
43
+ var __importStar = (this && this.__importStar) || (function () {
44
+ var ownKeys = function(o) {
45
+ ownKeys = Object.getOwnPropertyNames || function (o) {
46
+ var ar = [];
47
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
48
+ return ar;
49
+ };
50
+ return ownKeys(o);
51
+ };
52
+ return function (mod) {
53
+ if (mod && mod.__esModule) return mod;
54
+ var result = {};
55
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
56
+ __setModuleDefault(result, mod);
57
+ return result;
58
+ };
59
+ })();
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.createOpenCodeAdapter = createOpenCodeAdapter;
62
+ const path = __importStar(require("node:path"));
63
+ const node_fs_1 = require("node:fs");
64
+ const journal_1 = require("../storage/journal");
65
+ const runFiles_1 = require("../storage/runFiles");
66
+ const effectIndex_1 = require("../runtime/replay/effectIndex");
67
+ const session_1 = require("../session");
68
+ const types_1 = require("./types");
69
+ const context_1 = require("../prompts/context");
70
+ const config_1 = require("../config");
71
+ // ---------------------------------------------------------------------------
72
+ // Constants
73
+ // ---------------------------------------------------------------------------
74
+ const HARNESS_NAME = "opencode";
75
+ function createHookLogger(hookName) {
76
+ const logDir = process.env.BABYSITTER_LOG_DIR || ".a5c/logs";
77
+ const logFile = logDir ? path.join(logDir, `${hookName}.log`) : null;
78
+ const context = {};
79
+ if (logFile) {
80
+ try {
81
+ (0, node_fs_1.mkdirSync)(logDir, { recursive: true });
82
+ }
83
+ catch {
84
+ // Best-effort
85
+ }
86
+ }
87
+ function write(level, message) {
88
+ if (!logFile)
89
+ return;
90
+ const ts = new Date().toISOString();
91
+ const ctxParts = Object.entries(context).map(([k, v]) => `${k}=${v}`);
92
+ const ctxStr = ctxParts.length > 0 ? ` [${ctxParts.join(" ")}]` : "";
93
+ const line = `[${level}] ${ts}${ctxStr} ${message}\n`;
94
+ try {
95
+ (0, node_fs_1.appendFileSync)(logFile, line);
96
+ }
97
+ catch {
98
+ // Best-effort
99
+ }
100
+ }
101
+ return {
102
+ info: (msg) => write("INFO", msg),
103
+ warn: (msg) => write("WARN", msg),
104
+ error: (msg) => write("ERROR", msg),
105
+ setContext: (key, value) => {
106
+ context[key] = value;
107
+ },
108
+ };
109
+ }
110
+ // ---------------------------------------------------------------------------
111
+ // Stdin reader
112
+ // ---------------------------------------------------------------------------
113
+ function readStdin() {
114
+ return new Promise((resolve, reject) => {
115
+ let data = "";
116
+ process.stdin.setEncoding("utf8");
117
+ process.stdin.on("data", (chunk) => {
118
+ data += chunk;
119
+ });
120
+ process.stdin.on("end", () => resolve(data));
121
+ process.stdin.on("error", reject);
122
+ });
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Hook input parsing
126
+ // ---------------------------------------------------------------------------
127
+ function parseHookInput(raw) {
128
+ const trimmed = raw.trim();
129
+ if (!trimmed)
130
+ return {};
131
+ try {
132
+ const parsed = JSON.parse(trimmed);
133
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
134
+ return parsed;
135
+ }
136
+ }
137
+ catch {
138
+ // Malformed JSON — treat as empty
139
+ }
140
+ return {};
141
+ }
142
+ function safeStr(obj, key) {
143
+ const val = obj[key];
144
+ return typeof val === "string" ? val : "";
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Pending-by-kind helper
148
+ // ---------------------------------------------------------------------------
149
+ function countPendingByKind(records) {
150
+ const counts = new Map();
151
+ for (const record of records) {
152
+ const key = record.kind ?? "unknown";
153
+ counts.set(key, (counts.get(key) ?? 0) + 1);
154
+ }
155
+ return Object.fromEntries(Array.from(counts.entries()).sort(([a], [b]) => a.localeCompare(b)));
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Cleanup helper
159
+ // ---------------------------------------------------------------------------
160
+ async function cleanupSession(filePath) {
161
+ try {
162
+ await (0, session_1.deleteSessionFile)(filePath);
163
+ }
164
+ catch {
165
+ // Best-effort cleanup
166
+ }
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // State directory resolution
170
+ // ---------------------------------------------------------------------------
171
+ function resolveStateDirInternal(args) {
172
+ if (args.stateDir)
173
+ return path.resolve(args.stateDir);
174
+ return (0, config_1.getGlobalStateDir)();
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Session ID resolution
178
+ // ---------------------------------------------------------------------------
179
+ function resolveSessionIdInternal(parsed) {
180
+ // 1. Explicit arg (highest priority)
181
+ if (parsed.sessionId)
182
+ return parsed.sessionId;
183
+ // 2. Cross-harness standard env var (self-injected by babysitter plugin's shell.env hook)
184
+ if (process.env.BABYSITTER_SESSION_ID)
185
+ return process.env.BABYSITTER_SESSION_ID;
186
+ // 3. OpenCode-specific env vars (set by babysitter plugin's shell.env hook)
187
+ if (process.env.OPENCODE_SESSION_ID)
188
+ return process.env.OPENCODE_SESSION_ID;
189
+ return undefined;
190
+ }
191
+ // ---------------------------------------------------------------------------
192
+ // Stop hook handler (minimal — no native stop hook)
193
+ // ---------------------------------------------------------------------------
194
+ /**
195
+ * Handles the stop hook for OpenCode.
196
+ *
197
+ * OpenCode does NOT have a native stop hook that can block/restart the agent.
198
+ * This handler checks run status and returns approve/block as a decision, but
199
+ * the actual loop driving happens externally (in-turn model or SDK loop driver).
200
+ *
201
+ * When a run is still in progress (has pending effects or is not completed),
202
+ * we output a block decision with context for the next iteration. When the
203
+ * run is completed/failed or no run is bound, we allow exit.
204
+ */
205
+ async function handleStopHookImpl(args) {
206
+ const { verbose } = args;
207
+ const log = createHookLogger("babysitter-opencode-stop-hook");
208
+ log.info("handleStopHook started (opencode)");
209
+ // 1. Read hook input JSON from stdin (if available)
210
+ let rawInput;
211
+ try {
212
+ rawInput = await readStdin();
213
+ }
214
+ catch {
215
+ rawInput = "";
216
+ }
217
+ finally {
218
+ if (typeof process.stdin.unref === "function") {
219
+ process.stdin.unref();
220
+ }
221
+ }
222
+ const hookInput = parseHookInput(rawInput);
223
+ // 2. Resolve session ID
224
+ const sessionId = safeStr(hookInput, "session_id") ||
225
+ process.env.BABYSITTER_SESSION_ID ||
226
+ process.env.OPENCODE_SESSION_ID ||
227
+ "";
228
+ if (!sessionId) {
229
+ log.info("No session ID — allowing exit");
230
+ process.stdout.write("{}\n");
231
+ return 0;
232
+ }
233
+ log.setContext("session", sessionId);
234
+ // 3. Resolve state directory and read session state
235
+ const stateDir = resolveStateDirInternal(args);
236
+ const runsDir = args.runsDir || ".a5c/runs";
237
+ const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
238
+ let sessionFile;
239
+ try {
240
+ if (!(await (0, session_1.sessionFileExists)(filePath))) {
241
+ log.info("No active session — allowing exit");
242
+ process.stdout.write("{}\n");
243
+ return 0;
244
+ }
245
+ sessionFile = await (0, session_1.readSessionFile)(filePath);
246
+ }
247
+ catch {
248
+ log.warn("Session file read error — allowing exit");
249
+ process.stdout.write("{}\n");
250
+ return 0;
251
+ }
252
+ const { state } = sessionFile;
253
+ const runId = state.runId ?? "";
254
+ // 4. Check max iterations
255
+ if (state.maxIterations > 0 && state.iteration >= state.maxIterations) {
256
+ if (verbose) {
257
+ process.stderr.write(`[hook:run stop] Max iterations (${state.maxIterations}) reached\n`);
258
+ }
259
+ await cleanupSession(filePath);
260
+ process.stdout.write("{}\n");
261
+ return 0;
262
+ }
263
+ // 5. If no run is bound, allow exit
264
+ if (!runId) {
265
+ log.info("No run associated with session — allowing exit");
266
+ await cleanupSession(filePath);
267
+ process.stdout.write("{}\n");
268
+ return 0;
269
+ }
270
+ log.setContext("run", runId);
271
+ // 6. Get run state
272
+ let runState = "";
273
+ let pendingKinds = "";
274
+ try {
275
+ const runDir = path.isAbsolute(runId)
276
+ ? runId
277
+ : path.join(runsDir, runId);
278
+ void (0, runFiles_1.readRunMetadata)(runDir);
279
+ const journal = await (0, journal_1.loadJournal)(runDir);
280
+ const index = await (0, effectIndex_1.buildEffectIndex)({ runDir, events: journal });
281
+ const hasCompleted = journal.some((e) => e.type === "RUN_COMPLETED");
282
+ const hasFailed = journal.some((e) => e.type === "RUN_FAILED");
283
+ const pendingRecords = index.listPendingEffects();
284
+ const pendingByKind = countPendingByKind(pendingRecords);
285
+ const kindKeys = Object.keys(pendingByKind);
286
+ if (kindKeys.length > 0) {
287
+ pendingKinds = kindKeys.join(", ");
288
+ }
289
+ if (hasCompleted) {
290
+ runState = "completed";
291
+ }
292
+ else if (hasFailed) {
293
+ runState = "failed";
294
+ }
295
+ else if (pendingRecords.length > 0) {
296
+ runState = "waiting";
297
+ }
298
+ else {
299
+ runState = "created";
300
+ }
301
+ }
302
+ catch {
303
+ runState = "";
304
+ }
305
+ log.info(`Run state: ${runState || "unknown"}`);
306
+ // 7. Allow exit if run is completed, failed, or state is unknown
307
+ if (runState === "completed" || runState === "failed" || !runState) {
308
+ await cleanupSession(filePath);
309
+ process.stdout.write("{}\n");
310
+ return 0;
311
+ }
312
+ // 8. Run still in progress — output block decision with context
313
+ const nextIteration = state.iteration + 1;
314
+ const currentTime = (0, session_1.getCurrentTimestamp)();
315
+ const updatedState = {
316
+ ...state,
317
+ iteration: nextIteration,
318
+ lastIterationAt: currentTime,
319
+ };
320
+ try {
321
+ await (0, session_1.writeSessionFile)(filePath, updatedState, sessionFile.prompt ?? "");
322
+ }
323
+ catch {
324
+ log.warn("Failed to update session state");
325
+ }
326
+ const output = {
327
+ decision: "block",
328
+ reason: pendingKinds
329
+ ? `Babysitter iteration ${nextIteration} | Waiting on: ${pendingKinds}. Call 'babysitter run:iterate .a5c/runs/${runId} --json'.`
330
+ : `Babysitter iteration ${nextIteration} | Continue orchestration: call 'babysitter run:iterate .a5c/runs/${runId} --json'.`,
331
+ };
332
+ log.info(`Decision: block (iteration=${nextIteration})`);
333
+ if (verbose) {
334
+ process.stderr.write(`[hook:run stop] Blocking, iteration=${nextIteration}\n`);
335
+ }
336
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
337
+ return 0;
338
+ }
339
+ // ---------------------------------------------------------------------------
340
+ // SessionStart hook handler
341
+ // ---------------------------------------------------------------------------
342
+ async function handleSessionStartHookImpl(args) {
343
+ const { verbose } = args;
344
+ const log = createHookLogger("babysitter-opencode-session-start-hook");
345
+ log.info("handleSessionStartHook started (opencode)");
346
+ // 1. Read hook input JSON from stdin
347
+ let rawInput;
348
+ try {
349
+ rawInput = await readStdin();
350
+ }
351
+ catch {
352
+ process.stdout.write("{}\n");
353
+ return 0;
354
+ }
355
+ finally {
356
+ if (typeof process.stdin.unref === "function") {
357
+ process.stdin.unref();
358
+ }
359
+ }
360
+ const hookInput = parseHookInput(rawInput);
361
+ // 2. Resolve session ID
362
+ // OpenCode does NOT auto-inject env vars — the babysitter plugin's
363
+ // shell.env hook self-injects BABYSITTER_SESSION_ID.
364
+ const sessionId = safeStr(hookInput, "session_id") ||
365
+ process.env.BABYSITTER_SESSION_ID ||
366
+ process.env.OPENCODE_SESSION_ID ||
367
+ "";
368
+ if (!sessionId) {
369
+ log.info("No session ID — skipping state file creation");
370
+ process.stdout.write("{}\n");
371
+ return 0;
372
+ }
373
+ log.setContext("session", sessionId);
374
+ log.info(`Session ID: ${sessionId}`);
375
+ // 3. Resolve state directory and create baseline session file
376
+ const stateDir = resolveStateDirInternal(args);
377
+ log.info(`Resolved stateDir: ${stateDir}`);
378
+ const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
379
+ try {
380
+ if (!(await (0, session_1.sessionFileExists)(filePath))) {
381
+ const nowTs = (0, session_1.getCurrentTimestamp)();
382
+ const state = {
383
+ active: true,
384
+ iteration: 1,
385
+ maxIterations: 256,
386
+ runId: "",
387
+ startedAt: nowTs,
388
+ lastIterationAt: nowTs,
389
+ iterationTimes: [],
390
+ };
391
+ await (0, session_1.writeSessionFile)(filePath, state, "");
392
+ log.info(`Created session state: ${filePath}`);
393
+ if (verbose) {
394
+ process.stderr.write(`[hook:run session-start] Created session state: ${filePath}\n`);
395
+ }
396
+ }
397
+ else {
398
+ log.info(`Session state already exists: ${filePath}`);
399
+ }
400
+ }
401
+ catch (e) {
402
+ const msg = e instanceof Error ? e.message : String(e);
403
+ log.warn(`Failed to create session state: ${msg}`);
404
+ if (verbose) {
405
+ process.stderr.write(`[hook:run session-start] Failed to create session state: ${msg}\n`);
406
+ }
407
+ }
408
+ // 4. Output empty object
409
+ process.stdout.write("{}\n");
410
+ return 0;
411
+ }
412
+ // ---------------------------------------------------------------------------
413
+ // Session binding (run:create flow)
414
+ // ---------------------------------------------------------------------------
415
+ async function bindSessionImpl(opts) {
416
+ const { sessionId, runId, maxIterations = 256, prompt, verbose } = opts;
417
+ // Resolve state directory
418
+ const stateDir = resolveStateDirInternal({
419
+ stateDir: opts.stateDir,
420
+ pluginRoot: opts.pluginRoot,
421
+ });
422
+ const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
423
+ // Check for existing session (prevent re-entrant runs)
424
+ if (await (0, session_1.sessionFileExists)(filePath)) {
425
+ try {
426
+ const existing = await (0, session_1.readSessionFile)(filePath);
427
+ if (existing.state.runId && existing.state.runId !== runId) {
428
+ return {
429
+ harness: HARNESS_NAME,
430
+ sessionId,
431
+ stateFile: filePath,
432
+ error: `Session already associated with run: ${existing.state.runId}`,
433
+ };
434
+ }
435
+ // Update existing session with run ID
436
+ await (0, session_1.updateSessionState)(filePath, { runId, active: true }, { state: existing.state, prompt: existing.prompt });
437
+ if (verbose) {
438
+ process.stderr.write(`[run:create] Updated existing session ${sessionId} with run ${runId}\n`);
439
+ }
440
+ return { harness: HARNESS_NAME, sessionId, stateFile: filePath };
441
+ }
442
+ catch {
443
+ // Corrupted state file — overwrite
444
+ }
445
+ }
446
+ // Create new session state with run associated
447
+ const nowTs = (0, session_1.getCurrentTimestamp)();
448
+ const state = {
449
+ active: true,
450
+ iteration: 1,
451
+ maxIterations,
452
+ runId,
453
+ startedAt: nowTs,
454
+ lastIterationAt: nowTs,
455
+ iterationTimes: [],
456
+ };
457
+ try {
458
+ await (0, session_1.writeSessionFile)(filePath, state, prompt);
459
+ }
460
+ catch (e) {
461
+ return {
462
+ harness: HARNESS_NAME,
463
+ sessionId,
464
+ error: `Failed to write session state: ${e instanceof Error ? e.message : String(e)}`,
465
+ };
466
+ }
467
+ if (verbose) {
468
+ process.stderr.write(`[run:create] Session ${sessionId} initialized and bound to run ${runId}\n`);
469
+ }
470
+ return { harness: HARNESS_NAME, sessionId, stateFile: filePath };
471
+ }
472
+ // ---------------------------------------------------------------------------
473
+ // Install helpers
474
+ // ---------------------------------------------------------------------------
475
+ function installOpenCodePlugin(_options) {
476
+ // OpenCode plugin installation is not yet automated.
477
+ // Plugins are JS/TS modules placed in .opencode/plugins/.
478
+ return {
479
+ harness: HARNESS_NAME,
480
+ summary: "OpenCode plugin installation is not yet automated. " +
481
+ "Place babysitter plugin files in .opencode/plugins/babysitter/ manually.",
482
+ };
483
+ }
484
+ // ---------------------------------------------------------------------------
485
+ // Adapter factory
486
+ // ---------------------------------------------------------------------------
487
+ function createOpenCodeAdapter() {
488
+ return {
489
+ name: HARNESS_NAME,
490
+ isActive() {
491
+ // OpenCode does NOT inject distinctive env vars into plugin subprocesses.
492
+ // The babysitter plugin's shell.env hook self-injects BABYSITTER_SESSION_ID.
493
+ // OPENCODE_CONFIG is a real OpenCode env var that indicates OpenCode context.
494
+ return !!(process.env.BABYSITTER_SESSION_ID ||
495
+ process.env.OPENCODE_CONFIG);
496
+ },
497
+ autoResolvesSessionId() {
498
+ // OpenCode does not natively inject session IDs — the babysitter
499
+ // plugin's shell.env hook self-injects BABYSITTER_SESSION_ID.
500
+ return false;
501
+ },
502
+ getMissingSessionIdHint() {
503
+ return ("OpenCode does not auto-inject session IDs. Use --session-id explicitly, " +
504
+ "or ensure the babysitter plugin's shell.env hook is configured to set " +
505
+ "BABYSITTER_SESSION_ID.");
506
+ },
507
+ supportsHookType(hookType) {
508
+ // OpenCode plugin hooks: session.created, session.idle,
509
+ // tool.execute.before, tool.execute.after, shell.env
510
+ // Maps to SDK hook types:
511
+ // session-start → session.created
512
+ // pre-tool-use → tool.execute.before
513
+ // post-tool-use → tool.execute.after
514
+ // Note: NO stop hook — session.idle is fire-and-forget
515
+ const supported = [
516
+ "session-start",
517
+ "pre-tool-use",
518
+ "post-tool-use",
519
+ ];
520
+ return supported.includes(hookType);
521
+ },
522
+ getUnsupportedHookMessage(hookType) {
523
+ if (hookType === "stop") {
524
+ return ("OpenCode does not support a blocking stop hook. " +
525
+ "The session.idle event is fire-and-forget. " +
526
+ "Use in-turn orchestration or the SDK loop driver instead.");
527
+ }
528
+ return `Hook type "${hookType}" is not supported by the OpenCode adapter.`;
529
+ },
530
+ getCapabilities() {
531
+ // No StopHook, no SessionBinding natively, no MCP.
532
+ // HeadlessPrompt via @opencode-ai/sdk programmatic API.
533
+ return [types_1.HarnessCapability.HeadlessPrompt];
534
+ },
535
+ resolveSessionId(parsed) {
536
+ return resolveSessionIdInternal(parsed);
537
+ },
538
+ resolveStateDir(args) {
539
+ return resolveStateDirInternal(args);
540
+ },
541
+ resolvePluginRoot(args) {
542
+ const root = args.pluginRoot ||
543
+ process.env.OPENCODE_PLUGIN_ROOT;
544
+ return root ? path.resolve(root) : undefined;
545
+ },
546
+ bindSession(opts) {
547
+ return bindSessionImpl(opts);
548
+ },
549
+ handleStopHook(args) {
550
+ return handleStopHookImpl(args);
551
+ },
552
+ handleSessionStartHook(args) {
553
+ return handleSessionStartHookImpl(args);
554
+ },
555
+ findHookDispatcherPath(startCwd) {
556
+ // OpenCode plugins are in .opencode/plugins/, not standalone scripts.
557
+ // Walk up from startCwd looking for .opencode/plugins/babysitter/
558
+ let current = path.resolve(startCwd);
559
+ const root = path.parse(current).root;
560
+ while (current !== root) {
561
+ const candidate = path.join(current, ".opencode", "plugins", "babysitter", "index.js");
562
+ if ((0, node_fs_1.existsSync)(candidate))
563
+ return candidate;
564
+ const tsCandidate = path.join(current, ".opencode", "plugins", "babysitter", "index.ts");
565
+ if ((0, node_fs_1.existsSync)(tsCandidate))
566
+ return tsCandidate;
567
+ current = path.dirname(current);
568
+ }
569
+ return null;
570
+ },
571
+ installPlugin(options) {
572
+ return Promise.resolve(installOpenCodePlugin(options));
573
+ },
574
+ getPromptContext(opts) {
575
+ return (0, context_1.createOpenCodeContext)(opts);
576
+ },
577
+ };
578
+ }
@@ -1,14 +1,4 @@
1
- /**
2
- * Oh-My-Pi harness adapter.
3
- *
4
- * Extends the SDK harness layer with "pi" support while reusing the
5
- * mature Claude stop/session-start hook handlers. The Pi adapter maps
6
- * Oh-My-Pi-specific environment conventions to the generic adapter interface.
7
- */
8
1
  import type { HarnessAdapter, HarnessInstallOptions, HarnessInstallResult } from "./types";
9
- export declare function installPiFamilyPlugin(args: {
10
- harness: "pi" | "oh-my-pi";
11
- options: HarnessInstallOptions;
12
- }): Promise<HarnessInstallResult>;
2
+ export declare function installPiPlugin(options: HarnessInstallOptions): Promise<HarnessInstallResult>;
13
3
  export declare function createPiAdapter(): HarnessAdapter;
14
4
  //# sourceMappingURL=pi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../src/harness/pi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EACV,cAAc,EAGd,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,SAAS,CAAC;AAsEjB,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,OAAO,EAAE,IAAI,GAAG,UAAU,CAAC;IAC3B,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+BhC;AAED,wBAAgB,eAAe,IAAI,cAAc,CA6GhD"}
1
+ {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../src/harness/pi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAGd,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,SAAS,CAAC;AAwHjB,wBAAsB,eAAe,CACnC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAM/B;AAED,wBAAgB,eAAe,IAAI,cAAc,CAmFhD"}