@a5c-ai/babysitter-sdk 0.0.175 → 0.0.176-staging.83d08ece
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/dist/harness/geminiCli.d.ts +31 -0
- package/dist/harness/geminiCli.d.ts.map +1 -0
- package/dist/harness/geminiCli.js +705 -0
- package/dist/harness/index.d.ts +1 -0
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +3 -1
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI harness adapter.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes all Gemini CLI-specific behaviors:
|
|
5
|
+
* - Session ID resolution (GEMINI_SESSION_ID env var or hook stdin)
|
|
6
|
+
* - State directory conventions (.a5c/state/ by default)
|
|
7
|
+
* - Extension path resolution (GEMINI_EXTENSION_PATH or script-relative)
|
|
8
|
+
* - Session binding (run:create → state file with run association)
|
|
9
|
+
* - AfterAgent hook handler (deny/approve decision — equivalent to Stop hook)
|
|
10
|
+
* - SessionStart hook handler (baseline state file creation)
|
|
11
|
+
*
|
|
12
|
+
* Gemini CLI Hook Protocol:
|
|
13
|
+
* - Input: JSON via stdin
|
|
14
|
+
* - Output: JSON via stdout (plain text MUST NOT appear on stdout)
|
|
15
|
+
* - Stderr: debug/log output only
|
|
16
|
+
* - Exit 0: success, stdout parsed as JSON
|
|
17
|
+
* - Exit 2: system block (stderr used as rejection reason)
|
|
18
|
+
*
|
|
19
|
+
* AfterAgent hook output:
|
|
20
|
+
* - `{}` or `{"decision":"allow"}` → allow session to exit normally
|
|
21
|
+
* - `{"decision":"block","reason":"...","systemMessage":"..."}` → continue loop
|
|
22
|
+
* - `{"decision":"deny","systemMessage":"..."}` → deny/retry (newer Gemini CLI)
|
|
23
|
+
*
|
|
24
|
+
* Gemini CLI environment variables available in hooks:
|
|
25
|
+
* - GEMINI_SESSION_ID — unique ID for the current Gemini CLI session
|
|
26
|
+
* - GEMINI_PROJECT_DIR — absolute path to the project root
|
|
27
|
+
* - GEMINI_CWD — current working directory
|
|
28
|
+
*/
|
|
29
|
+
import type { HarnessAdapter } from "./types";
|
|
30
|
+
export declare function createGeminiCliAdapter(): HarnessAdapter;
|
|
31
|
+
//# sourceMappingURL=geminiCli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geminiCli.d.ts","sourceRoot":"","sources":["../../src/harness/geminiCli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAsBH,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,SAAS,CAAC;AAuuBjB,wBAAgB,sBAAsB,IAAI,cAAc,CAkEvD"}
|
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini CLI harness adapter.
|
|
4
|
+
*
|
|
5
|
+
* Centralizes all Gemini CLI-specific behaviors:
|
|
6
|
+
* - Session ID resolution (GEMINI_SESSION_ID env var or hook stdin)
|
|
7
|
+
* - State directory conventions (.a5c/state/ by default)
|
|
8
|
+
* - Extension path resolution (GEMINI_EXTENSION_PATH or script-relative)
|
|
9
|
+
* - Session binding (run:create → state file with run association)
|
|
10
|
+
* - AfterAgent hook handler (deny/approve decision — equivalent to Stop hook)
|
|
11
|
+
* - SessionStart hook handler (baseline state file creation)
|
|
12
|
+
*
|
|
13
|
+
* Gemini CLI Hook Protocol:
|
|
14
|
+
* - Input: JSON via stdin
|
|
15
|
+
* - Output: JSON via stdout (plain text MUST NOT appear on stdout)
|
|
16
|
+
* - Stderr: debug/log output only
|
|
17
|
+
* - Exit 0: success, stdout parsed as JSON
|
|
18
|
+
* - Exit 2: system block (stderr used as rejection reason)
|
|
19
|
+
*
|
|
20
|
+
* AfterAgent hook output:
|
|
21
|
+
* - `{}` or `{"decision":"allow"}` → allow session to exit normally
|
|
22
|
+
* - `{"decision":"block","reason":"...","systemMessage":"..."}` → continue loop
|
|
23
|
+
* - `{"decision":"deny","systemMessage":"..."}` → deny/retry (newer Gemini CLI)
|
|
24
|
+
*
|
|
25
|
+
* Gemini CLI environment variables available in hooks:
|
|
26
|
+
* - GEMINI_SESSION_ID — unique ID for the current Gemini CLI session
|
|
27
|
+
* - GEMINI_PROJECT_DIR — absolute path to the project root
|
|
28
|
+
* - GEMINI_CWD — current working directory
|
|
29
|
+
*/
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.createGeminiCliAdapter = createGeminiCliAdapter;
|
|
65
|
+
const path = __importStar(require("node:path"));
|
|
66
|
+
const node_fs_1 = require("node:fs");
|
|
67
|
+
const journal_1 = require("../storage/journal");
|
|
68
|
+
const runFiles_1 = require("../storage/runFiles");
|
|
69
|
+
const effectIndex_1 = require("../runtime/replay/effectIndex");
|
|
70
|
+
const completionProof_1 = require("../cli/completionProof");
|
|
71
|
+
const session_1 = require("../session");
|
|
72
|
+
const session_2 = require("../cli/commands/session");
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Constants
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
const HARNESS_NAME = "gemini-cli";
|
|
77
|
+
/** Default state directory for Gemini CLI sessions */
|
|
78
|
+
const DEFAULT_STATE_DIR = ".a5c/state";
|
|
79
|
+
function createHookLogger(hookName) {
|
|
80
|
+
const logDir = process.env.BABYSITTER_LOG_DIR || ".a5c/logs";
|
|
81
|
+
const logFile = logDir ? path.join(logDir, `${hookName}.log`) : null;
|
|
82
|
+
const context = {};
|
|
83
|
+
if (logFile) {
|
|
84
|
+
try {
|
|
85
|
+
(0, node_fs_1.mkdirSync)(logDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Best-effort
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function write(level, message) {
|
|
92
|
+
if (!logFile)
|
|
93
|
+
return;
|
|
94
|
+
const ts = new Date().toISOString();
|
|
95
|
+
const ctxParts = Object.entries(context).map(([k, v]) => `${k}=${v}`);
|
|
96
|
+
const ctxStr = ctxParts.length > 0 ? ` [${ctxParts.join(" ")}]` : "";
|
|
97
|
+
const line = `[${level}] ${ts}${ctxStr} ${message}\n`;
|
|
98
|
+
try {
|
|
99
|
+
(0, node_fs_1.appendFileSync)(logFile, line);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Best-effort
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
info: (msg) => write("INFO", msg),
|
|
107
|
+
warn: (msg) => write("WARN", msg),
|
|
108
|
+
error: (msg) => write("ERROR", msg),
|
|
109
|
+
setContext: (key, value) => {
|
|
110
|
+
context[key] = value;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Journal event helper
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
async function appendStopHookEvent(runDir, data) {
|
|
118
|
+
try {
|
|
119
|
+
await (0, journal_1.appendEvent)({
|
|
120
|
+
runDir,
|
|
121
|
+
eventType: "STOP_HOOK_INVOKED",
|
|
122
|
+
event: {
|
|
123
|
+
...data,
|
|
124
|
+
harness: HARNESS_NAME,
|
|
125
|
+
timestamp: new Date().toISOString(),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Best-effort: don't fail the hook if journal write fails
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Stdin reader
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
function readStdin() {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
let data = "";
|
|
139
|
+
process.stdin.setEncoding("utf8");
|
|
140
|
+
process.stdin.on("data", (chunk) => {
|
|
141
|
+
data += chunk;
|
|
142
|
+
});
|
|
143
|
+
process.stdin.on("end", () => resolve(data));
|
|
144
|
+
process.stdin.on("error", reject);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function parseHookInput(raw) {
|
|
148
|
+
const trimmed = raw.trim();
|
|
149
|
+
if (!trimmed)
|
|
150
|
+
return {};
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(trimmed);
|
|
153
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
154
|
+
return parsed;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Malformed JSON — treat as empty
|
|
159
|
+
}
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
function safeStr(obj, key) {
|
|
163
|
+
const val = obj[key];
|
|
164
|
+
return typeof val === "string" ? val : "";
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Pending-by-kind helper
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
function countPendingByKind(records) {
|
|
170
|
+
const counts = new Map();
|
|
171
|
+
for (const record of records) {
|
|
172
|
+
const key = record.kind ?? "unknown";
|
|
173
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
174
|
+
}
|
|
175
|
+
return Object.fromEntries(Array.from(counts.entries()).sort(([a], [b]) => a.localeCompare(b)));
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Cleanup helper
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
async function cleanupSession(filePath) {
|
|
181
|
+
try {
|
|
182
|
+
await (0, session_1.deleteSessionFile)(filePath);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Best-effort cleanup
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// State directory resolution
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
function resolveStateDirInternal(args) {
|
|
192
|
+
if (args.stateDir)
|
|
193
|
+
return path.resolve(args.stateDir);
|
|
194
|
+
if (args.pluginRoot) {
|
|
195
|
+
return path.resolve(args.pluginRoot, "state");
|
|
196
|
+
}
|
|
197
|
+
// Check Gemini CLI extension path env var
|
|
198
|
+
const extensionPath = process.env.GEMINI_EXTENSION_PATH ||
|
|
199
|
+
process.env.BABYSITTER_EXTENSION_PATH;
|
|
200
|
+
if (extensionPath) {
|
|
201
|
+
return path.resolve(extensionPath, "state");
|
|
202
|
+
}
|
|
203
|
+
// Default: project-relative .a5c/state/
|
|
204
|
+
return path.resolve(DEFAULT_STATE_DIR);
|
|
205
|
+
}
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// AfterAgent (Stop) hook handler
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
async function handleAfterAgentHookImpl(args) {
|
|
210
|
+
const { verbose } = args;
|
|
211
|
+
const log = createHookLogger("babysitter-after-agent-hook");
|
|
212
|
+
log.info("handleAfterAgentHook started");
|
|
213
|
+
// 1. Read hook input JSON from stdin
|
|
214
|
+
let rawInput;
|
|
215
|
+
try {
|
|
216
|
+
rawInput = await readStdin();
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
220
|
+
log.warn(`stdin read error: ${msg}`);
|
|
221
|
+
process.stdout.write("{}\n");
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
if (typeof process.stdin.unref === "function") {
|
|
226
|
+
process.stdin.unref();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const hookInput = parseHookInput(rawInput);
|
|
230
|
+
log.info("Hook input received");
|
|
231
|
+
// 2. Resolve session ID from hook input or env var
|
|
232
|
+
const sessionId = safeStr(hookInput, "session_id") ||
|
|
233
|
+
process.env.GEMINI_SESSION_ID ||
|
|
234
|
+
"";
|
|
235
|
+
if (!sessionId) {
|
|
236
|
+
log.info("No session ID in hook input — allowing exit");
|
|
237
|
+
process.stdout.write("{}\n");
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
log.setContext("session", sessionId);
|
|
241
|
+
log.info(`Session ID: ${sessionId}`);
|
|
242
|
+
// 3. Resolve state directory
|
|
243
|
+
const stateDir = resolveStateDirInternal(args);
|
|
244
|
+
const runsDir = args.runsDir || ".a5c/runs";
|
|
245
|
+
log.info(`Resolved stateDir: ${stateDir}`);
|
|
246
|
+
// 4. Read session state file
|
|
247
|
+
let filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
248
|
+
log.info(`Checking session file at: ${filePath}`);
|
|
249
|
+
let sessionFile;
|
|
250
|
+
try {
|
|
251
|
+
if (!(await (0, session_1.sessionFileExists)(filePath))) {
|
|
252
|
+
// Fallback: check .a5c/state/ directory
|
|
253
|
+
const fallbackPath = (0, session_1.getSessionFilePath)(path.resolve(DEFAULT_STATE_DIR), sessionId);
|
|
254
|
+
log.info(`Primary state file not found, trying fallback: ${fallbackPath}`);
|
|
255
|
+
if (await (0, session_1.sessionFileExists)(fallbackPath)) {
|
|
256
|
+
filePath = fallbackPath;
|
|
257
|
+
log.info(`Found session file at fallback path: ${filePath}`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
log.info(`No active babysitter loop for session ${sessionId} — allowing exit`);
|
|
261
|
+
process.stdout.write("{}\n");
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
sessionFile = await (0, session_1.readSessionFile)(filePath);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
log.warn(`Session file read error at ${filePath} — allowing exit`);
|
|
269
|
+
process.stdout.write("{}\n");
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
const { state } = sessionFile;
|
|
273
|
+
const prompt = sessionFile.prompt ?? "";
|
|
274
|
+
// 5. Check max iterations
|
|
275
|
+
if (state.maxIterations > 0 && state.iteration >= state.maxIterations) {
|
|
276
|
+
if (verbose) {
|
|
277
|
+
process.stderr.write(`[hook:run after-agent] Max iterations (${state.maxIterations}) reached\n`);
|
|
278
|
+
}
|
|
279
|
+
if (state.runId) {
|
|
280
|
+
await appendStopHookEvent(path.join(runsDir, state.runId), {
|
|
281
|
+
sessionId,
|
|
282
|
+
iteration: state.iteration,
|
|
283
|
+
decision: "approve",
|
|
284
|
+
reason: "max_iterations_reached",
|
|
285
|
+
runState: "",
|
|
286
|
+
pendingKinds: "",
|
|
287
|
+
hasPromise: false,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
await cleanupSession(filePath);
|
|
291
|
+
process.stdout.write("{}\n");
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
// 6. Check iteration timing (runaway loop detection)
|
|
295
|
+
const now = (0, session_1.getCurrentTimestamp)();
|
|
296
|
+
const updatedTimes = state.iteration >= 5
|
|
297
|
+
? (0, session_1.updateIterationTimes)(state.iterationTimes, state.lastIterationAt, now)
|
|
298
|
+
: state.iterationTimes;
|
|
299
|
+
if ((0, session_1.isIterationTooFast)(updatedTimes)) {
|
|
300
|
+
if (verbose) {
|
|
301
|
+
process.stderr.write(`[hook:run after-agent] Iteration too fast\n`);
|
|
302
|
+
}
|
|
303
|
+
if (state.runId) {
|
|
304
|
+
await appendStopHookEvent(path.join(runsDir, state.runId), {
|
|
305
|
+
sessionId,
|
|
306
|
+
iteration: state.iteration,
|
|
307
|
+
decision: "approve",
|
|
308
|
+
reason: "iteration_too_fast",
|
|
309
|
+
runState: "",
|
|
310
|
+
pendingKinds: "",
|
|
311
|
+
hasPromise: false,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
await cleanupSession(filePath);
|
|
315
|
+
process.stdout.write("{}\n");
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
const iteration = state.iteration;
|
|
319
|
+
const maxIterations = state.maxIterations;
|
|
320
|
+
const runId = state.runId ?? "";
|
|
321
|
+
if (runId)
|
|
322
|
+
log.setContext("run", runId);
|
|
323
|
+
// 7. Extract last assistant message text for completion proof check
|
|
324
|
+
// Gemini CLI provides prompt_response directly in the hook input
|
|
325
|
+
const promptResponse = safeStr(hookInput, "prompt_response");
|
|
326
|
+
let hasPromise = false;
|
|
327
|
+
let promiseValue = null;
|
|
328
|
+
if (promptResponse) {
|
|
329
|
+
promiseValue = (0, session_2.extractPromiseTag)(promptResponse);
|
|
330
|
+
hasPromise = promiseValue !== null;
|
|
331
|
+
log.info(`prompt_response extracted (${promptResponse.length} chars)`);
|
|
332
|
+
}
|
|
333
|
+
// Fallback: check transcript_path if prompt_response is missing
|
|
334
|
+
if (!hasPromise) {
|
|
335
|
+
const transcriptPath = safeStr(hookInput, "transcript_path");
|
|
336
|
+
if (transcriptPath) {
|
|
337
|
+
const resolvedTranscript = path.resolve(transcriptPath);
|
|
338
|
+
if ((0, node_fs_1.existsSync)(resolvedTranscript)) {
|
|
339
|
+
try {
|
|
340
|
+
const { readFileSync } = await Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
341
|
+
const content = readFileSync(resolvedTranscript, "utf-8");
|
|
342
|
+
// Simple extraction: look for <promise>...</promise> anywhere
|
|
343
|
+
promiseValue = (0, session_2.extractPromiseTag)(content);
|
|
344
|
+
hasPromise = promiseValue !== null;
|
|
345
|
+
log.info("Checked transcript for promise tag");
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
log.warn(`Transcript read error: ${transcriptPath}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// 8. If no run is bound, allow exit
|
|
354
|
+
if (!runId) {
|
|
355
|
+
log.info("No run associated with session — allowing exit");
|
|
356
|
+
await cleanupSession(filePath);
|
|
357
|
+
process.stdout.write("{}\n");
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
// 9. Get run state and completion proof
|
|
361
|
+
let runState = "";
|
|
362
|
+
let completionProof = "";
|
|
363
|
+
let pendingKinds = "";
|
|
364
|
+
try {
|
|
365
|
+
let runDir = path.isAbsolute(runId)
|
|
366
|
+
? runId
|
|
367
|
+
: path.join(runsDir, runId);
|
|
368
|
+
// Fallback: search alternative locations
|
|
369
|
+
if (!(0, node_fs_1.existsSync)(path.join(runDir, "run.json")) &&
|
|
370
|
+
!path.isAbsolute(runId)) {
|
|
371
|
+
const alternatives = [
|
|
372
|
+
path.join(".a5c", ".a5c", "runs", runId),
|
|
373
|
+
path.join(".a5c", "runs", runId),
|
|
374
|
+
];
|
|
375
|
+
for (const alt of alternatives) {
|
|
376
|
+
const resolved = path.resolve(alt);
|
|
377
|
+
if (resolved !== path.resolve(runDir) &&
|
|
378
|
+
(0, node_fs_1.existsSync)(path.join(resolved, "run.json"))) {
|
|
379
|
+
log.info(`Run not found at ${runDir}, using fallback: ${resolved}`);
|
|
380
|
+
runDir = resolved;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const metadata = await (0, runFiles_1.readRunMetadata)(runDir);
|
|
386
|
+
const journal = await (0, journal_1.loadJournal)(runDir);
|
|
387
|
+
const index = await (0, effectIndex_1.buildEffectIndex)({ runDir, events: journal });
|
|
388
|
+
const hasCompleted = journal.some((e) => e.type === "RUN_COMPLETED");
|
|
389
|
+
const hasFailed = journal.some((e) => e.type === "RUN_FAILED");
|
|
390
|
+
const pendingRecords = index.listPendingEffects();
|
|
391
|
+
const pendingByKind = countPendingByKind(pendingRecords);
|
|
392
|
+
const kindKeys = Object.keys(pendingByKind);
|
|
393
|
+
if (kindKeys.length > 0) {
|
|
394
|
+
pendingKinds = kindKeys.join(", ");
|
|
395
|
+
}
|
|
396
|
+
if (hasCompleted) {
|
|
397
|
+
runState = "completed";
|
|
398
|
+
completionProof = (0, completionProof_1.resolveCompletionProof)(metadata);
|
|
399
|
+
}
|
|
400
|
+
else if (hasFailed) {
|
|
401
|
+
runState = "failed";
|
|
402
|
+
}
|
|
403
|
+
else if (pendingRecords.length > 0) {
|
|
404
|
+
runState = "waiting";
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
runState = "created";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
runState = "";
|
|
412
|
+
}
|
|
413
|
+
log.info(`Run state: ${runState || "unknown"}`);
|
|
414
|
+
if (completionProof)
|
|
415
|
+
log.info("Completion proof available");
|
|
416
|
+
if (!runState) {
|
|
417
|
+
log.warn(`Run state unknown for ${runId} — allowing exit`);
|
|
418
|
+
if (runId) {
|
|
419
|
+
await appendStopHookEvent(path.join(runsDir, runId), {
|
|
420
|
+
sessionId,
|
|
421
|
+
iteration: state.iteration,
|
|
422
|
+
decision: "approve",
|
|
423
|
+
reason: "run_state_unknown",
|
|
424
|
+
runState,
|
|
425
|
+
pendingKinds,
|
|
426
|
+
hasPromise,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
process.stdout.write("{}\n");
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
// 10. Check completion proof match
|
|
433
|
+
if (hasPromise && completionProof && promiseValue === completionProof) {
|
|
434
|
+
log.info("Promise matches completion proof — allowing exit");
|
|
435
|
+
if (verbose) {
|
|
436
|
+
process.stderr.write(`[hook:run after-agent] Valid promise tag detected — run complete\n`);
|
|
437
|
+
}
|
|
438
|
+
if (runId) {
|
|
439
|
+
await appendStopHookEvent(path.join(runsDir, runId), {
|
|
440
|
+
sessionId,
|
|
441
|
+
iteration: state.iteration,
|
|
442
|
+
decision: "approve",
|
|
443
|
+
reason: "completion_proof_matched",
|
|
444
|
+
runState,
|
|
445
|
+
pendingKinds,
|
|
446
|
+
hasPromise,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
await cleanupSession(filePath);
|
|
450
|
+
process.stdout.write("{}\n");
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
// 11. Not complete — continue the loop
|
|
454
|
+
const nextIteration = iteration + 1;
|
|
455
|
+
const currentTime = (0, session_1.getCurrentTimestamp)();
|
|
456
|
+
const updatedState = {
|
|
457
|
+
...state,
|
|
458
|
+
iteration: nextIteration,
|
|
459
|
+
lastIterationAt: currentTime,
|
|
460
|
+
iterationTimes: updatedTimes,
|
|
461
|
+
};
|
|
462
|
+
try {
|
|
463
|
+
await (0, session_1.writeSessionFile)(filePath, updatedState, prompt);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
log.warn("Failed to update session state");
|
|
467
|
+
}
|
|
468
|
+
// 12. Build reason (re-injected as next turn prompt) and systemMessage (shown to user)
|
|
469
|
+
let iterationContext;
|
|
470
|
+
if (completionProof) {
|
|
471
|
+
iterationContext = `Babysitter iteration ${nextIteration} | Run completed! To finish: call 'babysitter run:status .a5c/runs/${runId} --json', extract 'completionProof' from the output, then output it in <promise>SECRET</promise> tags. Do not mention or reveal the secret otherwise.`;
|
|
472
|
+
}
|
|
473
|
+
else if (runState === "waiting" && pendingKinds) {
|
|
474
|
+
iterationContext = `Babysitter iteration ${nextIteration} | Waiting on: ${pendingKinds}. Check if pending effects are resolved, then call 'babysitter run:iterate .a5c/runs/${runId} --json'.`;
|
|
475
|
+
}
|
|
476
|
+
else if (runState === "failed") {
|
|
477
|
+
iterationContext = `Babysitter iteration ${nextIteration} | Run failed. Inspect the run journal and fix the issue, then proceed.`;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
iterationContext = `Babysitter iteration ${nextIteration} | Continue orchestration: call 'babysitter run:iterate .a5c/runs/${runId} --json'.`;
|
|
481
|
+
}
|
|
482
|
+
const reason = `${iterationContext}\n\n${prompt}`;
|
|
483
|
+
let systemMessage;
|
|
484
|
+
if (completionProof) {
|
|
485
|
+
systemMessage = `🔄 Babysitter iteration ${nextIteration}/${maxIterations} | Run completed! Extract promise tag to finish.`;
|
|
486
|
+
}
|
|
487
|
+
else if (runState === "waiting" && pendingKinds) {
|
|
488
|
+
systemMessage = `🔄 Babysitter iteration ${nextIteration}/${maxIterations} | Waiting on: ${pendingKinds}`;
|
|
489
|
+
}
|
|
490
|
+
else if (runState === "failed") {
|
|
491
|
+
systemMessage = `🔄 Babysitter iteration ${nextIteration}/${maxIterations} | Failed — check run state`;
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
systemMessage = `🔄 Babysitter iteration ${nextIteration}/${maxIterations} [${runState}]`;
|
|
495
|
+
}
|
|
496
|
+
// Output: block decision (continue loop)
|
|
497
|
+
// Using "block" decision with reason re-injected as next prompt context.
|
|
498
|
+
// Gemini CLI honors this to start a new turn with the reason as prompt context.
|
|
499
|
+
const output = {
|
|
500
|
+
decision: "block",
|
|
501
|
+
reason,
|
|
502
|
+
systemMessage,
|
|
503
|
+
};
|
|
504
|
+
if (runId) {
|
|
505
|
+
await appendStopHookEvent(path.join(runsDir, runId), {
|
|
506
|
+
sessionId,
|
|
507
|
+
iteration: state.iteration,
|
|
508
|
+
decision: "block",
|
|
509
|
+
reason: "continue_loop",
|
|
510
|
+
runState,
|
|
511
|
+
pendingKinds,
|
|
512
|
+
hasPromise,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
log.info(`Decision: block (iteration=${nextIteration}, maxIterations=${maxIterations})`);
|
|
516
|
+
if (verbose) {
|
|
517
|
+
process.stderr.write(`[hook:run after-agent] Blocking, iteration=${nextIteration} maxIterations=${maxIterations}\n`);
|
|
518
|
+
}
|
|
519
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// SessionStart hook handler
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
async function handleSessionStartHookImpl(args) {
|
|
526
|
+
const { verbose } = args;
|
|
527
|
+
const log = createHookLogger("babysitter-session-start-hook");
|
|
528
|
+
log.info("handleSessionStartHook started (gemini-cli)");
|
|
529
|
+
// 1. Read hook input JSON from stdin
|
|
530
|
+
let rawInput;
|
|
531
|
+
try {
|
|
532
|
+
rawInput = await readStdin();
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
process.stdout.write("{}\n");
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
finally {
|
|
539
|
+
if (typeof process.stdin.unref === "function") {
|
|
540
|
+
process.stdin.unref();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const hookInput = parseHookInput(rawInput);
|
|
544
|
+
// 2. Resolve session ID
|
|
545
|
+
const sessionId = safeStr(hookInput, "session_id") ||
|
|
546
|
+
process.env.GEMINI_SESSION_ID ||
|
|
547
|
+
"";
|
|
548
|
+
if (!sessionId) {
|
|
549
|
+
log.info("No session ID in hook input — skipping state file creation");
|
|
550
|
+
process.stdout.write("{}\n");
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
log.setContext("session", sessionId);
|
|
554
|
+
log.info(`Session ID: ${sessionId}`);
|
|
555
|
+
// 3. Resolve state directory and create baseline session file
|
|
556
|
+
const stateDir = resolveStateDirInternal(args);
|
|
557
|
+
log.info(`Resolved stateDir: ${stateDir}`);
|
|
558
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
559
|
+
try {
|
|
560
|
+
if (!(await (0, session_1.sessionFileExists)(filePath))) {
|
|
561
|
+
const nowTs = (0, session_1.getCurrentTimestamp)();
|
|
562
|
+
const state = {
|
|
563
|
+
active: true,
|
|
564
|
+
iteration: 1,
|
|
565
|
+
maxIterations: 256,
|
|
566
|
+
runId: "",
|
|
567
|
+
startedAt: nowTs,
|
|
568
|
+
lastIterationAt: nowTs,
|
|
569
|
+
iterationTimes: [],
|
|
570
|
+
};
|
|
571
|
+
await (0, session_1.writeSessionFile)(filePath, state, "");
|
|
572
|
+
log.info(`Created session state: ${filePath}`);
|
|
573
|
+
if (verbose) {
|
|
574
|
+
process.stderr.write(`[hook:run session-start] Created session state: ${filePath}\n`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
log.info(`Session state already exists: ${filePath}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
583
|
+
log.warn(`Failed to create session state: ${msg}`);
|
|
584
|
+
if (verbose) {
|
|
585
|
+
process.stderr.write(`[hook:run session-start] Failed to create session state: ${msg}\n`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// 4. Output empty object (no additional context to inject at session start)
|
|
589
|
+
process.stdout.write("{}\n");
|
|
590
|
+
return 0;
|
|
591
|
+
}
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// Session binding (run:create flow)
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
async function bindSessionImpl(opts) {
|
|
596
|
+
const { sessionId, runId, maxIterations = 256, prompt, verbose } = opts;
|
|
597
|
+
// Resolve state directory
|
|
598
|
+
const stateDir = resolveStateDirInternal({
|
|
599
|
+
stateDir: opts.stateDir,
|
|
600
|
+
pluginRoot: opts.pluginRoot,
|
|
601
|
+
});
|
|
602
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
603
|
+
// Check for existing session (prevent re-entrant runs)
|
|
604
|
+
if (await (0, session_1.sessionFileExists)(filePath)) {
|
|
605
|
+
try {
|
|
606
|
+
const existing = await (0, session_1.readSessionFile)(filePath);
|
|
607
|
+
if (existing.state.runId && existing.state.runId !== runId) {
|
|
608
|
+
return {
|
|
609
|
+
harness: HARNESS_NAME,
|
|
610
|
+
sessionId,
|
|
611
|
+
stateFile: filePath,
|
|
612
|
+
error: `Session already associated with run: ${existing.state.runId}`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
// Update existing session with run ID
|
|
616
|
+
await (0, session_1.updateSessionState)(filePath, { runId, active: true }, { state: existing.state, prompt: existing.prompt });
|
|
617
|
+
if (verbose) {
|
|
618
|
+
process.stderr.write(`[run:create] Updated existing session ${sessionId} with run ${runId}\n`);
|
|
619
|
+
}
|
|
620
|
+
return { harness: HARNESS_NAME, sessionId, stateFile: filePath };
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
// Corrupted state file — overwrite
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Create new session state with run associated
|
|
627
|
+
const nowTs = (0, session_1.getCurrentTimestamp)();
|
|
628
|
+
const state = {
|
|
629
|
+
active: true,
|
|
630
|
+
iteration: 1,
|
|
631
|
+
maxIterations,
|
|
632
|
+
runId,
|
|
633
|
+
startedAt: nowTs,
|
|
634
|
+
lastIterationAt: nowTs,
|
|
635
|
+
iterationTimes: [],
|
|
636
|
+
};
|
|
637
|
+
try {
|
|
638
|
+
await (0, session_1.writeSessionFile)(filePath, state, prompt);
|
|
639
|
+
}
|
|
640
|
+
catch (e) {
|
|
641
|
+
return {
|
|
642
|
+
harness: HARNESS_NAME,
|
|
643
|
+
sessionId,
|
|
644
|
+
error: `Failed to write session state: ${e instanceof Error ? e.message : String(e)}`,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (verbose) {
|
|
648
|
+
process.stderr.write(`[run:create] Session ${sessionId} initialized and bound to run ${runId}\n`);
|
|
649
|
+
}
|
|
650
|
+
return { harness: HARNESS_NAME, sessionId, stateFile: filePath };
|
|
651
|
+
}
|
|
652
|
+
// ---------------------------------------------------------------------------
|
|
653
|
+
// Adapter factory
|
|
654
|
+
// ---------------------------------------------------------------------------
|
|
655
|
+
function createGeminiCliAdapter() {
|
|
656
|
+
return {
|
|
657
|
+
name: HARNESS_NAME,
|
|
658
|
+
isActive() {
|
|
659
|
+
return !!(process.env.GEMINI_SESSION_ID ||
|
|
660
|
+
process.env.GEMINI_PROJECT_DIR ||
|
|
661
|
+
process.env.GEMINI_CWD);
|
|
662
|
+
},
|
|
663
|
+
resolveSessionId(parsed) {
|
|
664
|
+
if (parsed.sessionId)
|
|
665
|
+
return parsed.sessionId;
|
|
666
|
+
if (process.env.GEMINI_SESSION_ID)
|
|
667
|
+
return process.env.GEMINI_SESSION_ID;
|
|
668
|
+
return undefined;
|
|
669
|
+
},
|
|
670
|
+
resolveStateDir(args) {
|
|
671
|
+
return resolveStateDirInternal(args);
|
|
672
|
+
},
|
|
673
|
+
resolvePluginRoot(args) {
|
|
674
|
+
const root = args.pluginRoot ||
|
|
675
|
+
process.env.GEMINI_EXTENSION_PATH ||
|
|
676
|
+
process.env.BABYSITTER_EXTENSION_PATH;
|
|
677
|
+
return root ? path.resolve(root) : undefined;
|
|
678
|
+
},
|
|
679
|
+
bindSession(opts) {
|
|
680
|
+
return bindSessionImpl(opts);
|
|
681
|
+
},
|
|
682
|
+
/**
|
|
683
|
+
* Handle the AfterAgent hook — the Gemini CLI equivalent of the Stop hook.
|
|
684
|
+
* Reads prompt_response (and optionally transcript_path) from stdin JSON,
|
|
685
|
+
* checks for the completion proof, and outputs block/approve decision.
|
|
686
|
+
*/
|
|
687
|
+
handleStopHook(args) {
|
|
688
|
+
return handleAfterAgentHookImpl(args);
|
|
689
|
+
},
|
|
690
|
+
handleSessionStartHook(args) {
|
|
691
|
+
return handleSessionStartHookImpl(args);
|
|
692
|
+
},
|
|
693
|
+
findHookDispatcherPath(_startCwd) {
|
|
694
|
+
// Gemini CLI extensions don't use a hook dispatcher in the same way
|
|
695
|
+
const extensionPath = process.env.GEMINI_EXTENSION_PATH ||
|
|
696
|
+
process.env.BABYSITTER_EXTENSION_PATH;
|
|
697
|
+
if (extensionPath) {
|
|
698
|
+
const candidate = path.join(path.resolve(extensionPath), "hooks", "after-agent.sh");
|
|
699
|
+
if ((0, node_fs_1.existsSync)(candidate))
|
|
700
|
+
return candidate;
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
},
|
|
704
|
+
};
|
|
705
|
+
}
|
package/dist/harness/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { HarnessAdapter, SessionBindOptions, SessionBindResult, HookHandlerArgs, } from "./types";
|
|
2
2
|
export { createClaudeCodeAdapter } from "./claudeCode";
|
|
3
|
+
export { createGeminiCliAdapter } from "./geminiCli";
|
|
3
4
|
export { createNullAdapter } from "./nullAdapter";
|
|
4
5
|
export { detectAdapter, getAdapterByName, listSupportedHarnesses, getAdapter, setAdapter, resetAdapter, } from "./registry";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/harness/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/harness/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/harness/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resetAdapter = exports.setAdapter = exports.getAdapter = exports.listSupportedHarnesses = exports.getAdapterByName = exports.detectAdapter = exports.createNullAdapter = exports.createClaudeCodeAdapter = void 0;
|
|
3
|
+
exports.resetAdapter = exports.setAdapter = exports.getAdapter = exports.listSupportedHarnesses = exports.getAdapterByName = exports.detectAdapter = exports.createNullAdapter = exports.createGeminiCliAdapter = exports.createClaudeCodeAdapter = void 0;
|
|
4
4
|
var claudeCode_1 = require("./claudeCode");
|
|
5
5
|
Object.defineProperty(exports, "createClaudeCodeAdapter", { enumerable: true, get: function () { return claudeCode_1.createClaudeCodeAdapter; } });
|
|
6
|
+
var geminiCli_1 = require("./geminiCli");
|
|
7
|
+
Object.defineProperty(exports, "createGeminiCliAdapter", { enumerable: true, get: function () { return geminiCli_1.createGeminiCliAdapter; } });
|
|
6
8
|
var nullAdapter_1 = require("./nullAdapter");
|
|
7
9
|
Object.defineProperty(exports, "createNullAdapter", { enumerable: true, get: function () { return nullAdapter_1.createNullAdapter; } });
|
|
8
10
|
var registry_1 = require("./registry");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAkB9C;;;GAGG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAK9C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAKpE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAQD;;GAEG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAK3C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAExD;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC"}
|
package/dist/harness/registry.js
CHANGED
|
@@ -14,12 +14,14 @@ exports.getAdapter = getAdapter;
|
|
|
14
14
|
exports.setAdapter = setAdapter;
|
|
15
15
|
exports.resetAdapter = resetAdapter;
|
|
16
16
|
const claudeCode_1 = require("./claudeCode");
|
|
17
|
+
const geminiCli_1 = require("./geminiCli");
|
|
17
18
|
const nullAdapter_1 = require("./nullAdapter");
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Registry of known adapters (ordered by priority)
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
const knownAdapters = [
|
|
22
23
|
(0, claudeCode_1.createClaudeCodeAdapter)(),
|
|
24
|
+
(0, geminiCli_1.createGeminiCliAdapter)(),
|
|
23
25
|
];
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
25
27
|
// Auto-detection
|
package/package.json
CHANGED