pi-ca-leash 1.1.0 → 1.2.0
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/README.md +4 -3
- package/extensions/command-drivers.ts +4 -4
- package/extensions/index.ts +6 -4
- package/extensions/model-catalog.ts +6 -0
- package/extensions/runtime-driver.ts +1 -0
- package/extensions/runtime-safety.ts +1 -1
- package/extensions/tool-inputs.ts +2 -2
- package/node_modules/@pi-claude-code-agent/intercom-bridge/dist/bridge.js +1 -0
- package/node_modules/@pi-claude-code-agent/intercom-bridge/dist/bridge.js.map +1 -1
- package/node_modules/@pi-claude-code-agent/intercom-bridge/package.json +2 -2
- package/node_modules/@pi-claude-code-agent/runtime/dist/driver-config.js +1 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/driver-config.js.map +1 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-interactive.d.ts +89 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-interactive.js +404 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-interactive.js.map +1 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-pty.d.ts +146 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-pty.js +550 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-pty.js.map +1 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-sdk.js +20 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/drivers/claude-sdk.js.map +1 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/index.d.ts +1 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/index.js +1 -0
- package/node_modules/@pi-claude-code-agent/runtime/dist/index.js.map +1 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/runtime.js +17 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/runtime.js.map +1 -1
- package/node_modules/@pi-claude-code-agent/runtime/dist/types.d.ts +9 -1
- package/node_modules/@pi-claude-code-agent/runtime/package.json +1 -1
- package/node_modules/@pi-claude-code-agent/subagents-backend/package.json +2 -2
- package/node_modules/@pi-claude-code-agent/teams-backend/package.json +3 -3
- package/package.json +6 -5
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readSync, openSync, closeSync, fstatSync, writeFileSync, existsSync, } from "node:fs";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { coerceClaudeCliSessionId } from "./claude-cli.js";
|
|
5
|
+
import { parseClaudeSdkMessage } from "./claude-sdk.js";
|
|
6
|
+
import { enrichInitWithCapabilities, foldThinkingLevelForClaude } from "./thinking.js";
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
readyQuietMs: 600,
|
|
9
|
+
readyTimeoutMs: 15_000,
|
|
10
|
+
submitDelayMs: 40,
|
|
11
|
+
pollIntervalMs: 120,
|
|
12
|
+
turnTimeoutMs: 10 * 60_000,
|
|
13
|
+
};
|
|
14
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
15
|
+
/** POSIX single-quote a string for safe embedding in a shell command. */
|
|
16
|
+
function shSingleQuote(value) {
|
|
17
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
18
|
+
}
|
|
19
|
+
function buildInteractiveArgs(input) {
|
|
20
|
+
// No `-p`: bare `claude` launches the interactive TUI. We pre-assign the
|
|
21
|
+
// session id so we can find its transcript JSONL by filename, and inject a
|
|
22
|
+
// per-invocation settings file carrying our Stop hook.
|
|
23
|
+
const args = [
|
|
24
|
+
"--session-id",
|
|
25
|
+
input.claudeSessionId,
|
|
26
|
+
"--settings",
|
|
27
|
+
input.settingsPath,
|
|
28
|
+
// yolo-only: interactive permission prompts are not automated yet, so we
|
|
29
|
+
// always launch without them. `run()` refuses securityMode "safe" before
|
|
30
|
+
// ever reaching here, so this is never a silent sandbox downgrade.
|
|
31
|
+
"--dangerously-skip-permissions",
|
|
32
|
+
];
|
|
33
|
+
if (input.model)
|
|
34
|
+
args.push("--model", input.model);
|
|
35
|
+
if (input.appendSystemPrompt)
|
|
36
|
+
args.push("--append-system-prompt", input.appendSystemPrompt);
|
|
37
|
+
if (input.additionalDirectories?.length)
|
|
38
|
+
args.push("--add-dir", ...input.additionalDirectories);
|
|
39
|
+
if (input.effort)
|
|
40
|
+
args.push("--effort", input.effort);
|
|
41
|
+
return args;
|
|
42
|
+
}
|
|
43
|
+
function stopHookSettings(signalPath) {
|
|
44
|
+
// Append one line per turn end. `printf` is dependency-free (no jq/node) and
|
|
45
|
+
// the path is single-quoted so spaces in the storage dir are safe.
|
|
46
|
+
const command = `printf 'stop\\n' >> ${shSingleQuote(signalPath)}`;
|
|
47
|
+
return JSON.stringify({ hooks: { Stop: [{ hooks: [{ type: "command", command }] }] } }, null, 2);
|
|
48
|
+
}
|
|
49
|
+
/** Locate `<configDir>/projects/<any-slug>/<sessionId>.jsonl`. */
|
|
50
|
+
function findTranscript(configDir, sessionId) {
|
|
51
|
+
const projectsDir = join(configDir, "projects");
|
|
52
|
+
if (!existsSync(projectsDir))
|
|
53
|
+
return undefined;
|
|
54
|
+
const target = `${sessionId}.jsonl`;
|
|
55
|
+
let slugs;
|
|
56
|
+
try {
|
|
57
|
+
slugs = readdirSync(projectsDir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
for (const slug of slugs) {
|
|
63
|
+
const candidate = join(projectsDir, slug, target);
|
|
64
|
+
if (existsSync(candidate))
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Read bytes appended to `path` since `offset`. Returns text + new offset. */
|
|
70
|
+
function readAppended(path, offset) {
|
|
71
|
+
let fd;
|
|
72
|
+
try {
|
|
73
|
+
fd = openSync(path, "r");
|
|
74
|
+
const size = fstatSync(fd).size;
|
|
75
|
+
if (size <= offset)
|
|
76
|
+
return { text: "", offset };
|
|
77
|
+
const length = size - offset;
|
|
78
|
+
const buf = Buffer.allocUnsafe(length);
|
|
79
|
+
const read = readSync(fd, buf, 0, length, offset);
|
|
80
|
+
return { text: buf.subarray(0, read).toString("utf8"), offset: offset + read };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { text: "", offset };
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (fd !== undefined)
|
|
87
|
+
closeSync(fd);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export class ClaudeInteractiveDriver {
|
|
91
|
+
name = "claude-interactive";
|
|
92
|
+
executable;
|
|
93
|
+
injectedPtySpawn;
|
|
94
|
+
configDirOverride;
|
|
95
|
+
opts;
|
|
96
|
+
sessions = new Map();
|
|
97
|
+
constructor(options = {}) {
|
|
98
|
+
this.injectedPtySpawn = options.ptySpawn;
|
|
99
|
+
this.executable = options.executable ?? process.env.CLAUDE_CLI_EXECUTABLE ?? "claude";
|
|
100
|
+
this.configDirOverride = options.configDir;
|
|
101
|
+
this.opts = {
|
|
102
|
+
readyQuietMs: options.readyQuietMs ?? DEFAULTS.readyQuietMs,
|
|
103
|
+
readyTimeoutMs: options.readyTimeoutMs ?? DEFAULTS.readyTimeoutMs,
|
|
104
|
+
submitDelayMs: options.submitDelayMs ?? DEFAULTS.submitDelayMs,
|
|
105
|
+
pollIntervalMs: options.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
|
|
106
|
+
turnTimeoutMs: options.turnTimeoutMs ?? DEFAULTS.turnTimeoutMs,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
run(input, onEventRaw) {
|
|
110
|
+
const effort = input.thinkingLevel ? foldThinkingLevelForClaude(input.thinkingLevel) : undefined;
|
|
111
|
+
const onEvent = enrichInitWithCapabilities(onEventRaw, {
|
|
112
|
+
thinkingLevelSupported: true,
|
|
113
|
+
requestedThinkingLevel: input.thinkingLevel,
|
|
114
|
+
effectiveThinkingLevel: effort,
|
|
115
|
+
});
|
|
116
|
+
let turnAborted = false;
|
|
117
|
+
const done = (async () => {
|
|
118
|
+
// Refuse safe mode loudly rather than silently dropping the sandbox.
|
|
119
|
+
if (input.securityMode === "safe") {
|
|
120
|
+
await onEvent({
|
|
121
|
+
type: "error",
|
|
122
|
+
payload: {
|
|
123
|
+
message: "claude-interactive supports only securityMode 'yolo' for now " +
|
|
124
|
+
"(interactive permission prompts are not yet automated). " +
|
|
125
|
+
"Pass securityMode: 'yolo' to use this driver.",
|
|
126
|
+
code: "CLAUDE_INTERACTIVE_SAFE_UNSUPPORTED",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return { code: 1, signal: null };
|
|
130
|
+
}
|
|
131
|
+
let session;
|
|
132
|
+
try {
|
|
133
|
+
session = await this.ensureSession(input, effort);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
await onEvent({
|
|
137
|
+
type: "error",
|
|
138
|
+
payload: {
|
|
139
|
+
message: enrichSpawnError(error instanceof Error ? error.message : String(error)),
|
|
140
|
+
code: "CLAUDE_INTERACTIVE_SPAWN_ERROR",
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return { code: 1, signal: null };
|
|
144
|
+
}
|
|
145
|
+
await session.ready;
|
|
146
|
+
if (session.exited) {
|
|
147
|
+
await onEvent({
|
|
148
|
+
type: "error",
|
|
149
|
+
payload: {
|
|
150
|
+
message: `claude interactive process exited before the turn (code=${session.exitCode ?? "null"})`,
|
|
151
|
+
code: "CLAUDE_INTERACTIVE_EXITED",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return { code: session.exitCode ?? 1, signal: null };
|
|
155
|
+
}
|
|
156
|
+
// Drain any transcript lines already written by prior turns / init so a
|
|
157
|
+
// turn only emits its own messages. (On the first turn this drains the
|
|
158
|
+
// system/init line, which we DO want to emit — so we emit during drain.)
|
|
159
|
+
await this.pumpTranscript(session, onEvent);
|
|
160
|
+
// Type the prompt via bracketed paste so embedded newlines in a
|
|
161
|
+
// multi-line prompt don't submit early; Enter submits the whole thing.
|
|
162
|
+
session.proc.write(`\x1b[200~${input.prompt}\x1b[201~`);
|
|
163
|
+
await sleep(this.opts.submitDelayMs);
|
|
164
|
+
session.proc.write("\r");
|
|
165
|
+
// Wait for the turn to end: a new Stop-hook signal line. Stream
|
|
166
|
+
// transcript messages as they're written in the meantime.
|
|
167
|
+
const deadline = Date.now() + this.opts.turnTimeoutMs;
|
|
168
|
+
while (true) {
|
|
169
|
+
if (turnAborted) {
|
|
170
|
+
await this.pumpTranscript(session, onEvent);
|
|
171
|
+
return { code: 130, signal: "SIGINT" };
|
|
172
|
+
}
|
|
173
|
+
if (session.exited) {
|
|
174
|
+
await this.pumpTranscript(session, onEvent);
|
|
175
|
+
// /quit (graceful dispose) exits 0; any other exit mid-turn is an error.
|
|
176
|
+
if (session.disposed)
|
|
177
|
+
return { code: session.exitCode ?? 0, signal: null };
|
|
178
|
+
await onEvent({
|
|
179
|
+
type: "error",
|
|
180
|
+
payload: {
|
|
181
|
+
message: `claude interactive process exited mid-turn (code=${session.exitCode ?? "null"})`,
|
|
182
|
+
code: "CLAUDE_INTERACTIVE_EXITED",
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return { code: session.exitCode ?? 1, signal: null };
|
|
186
|
+
}
|
|
187
|
+
await this.pumpTranscript(session, onEvent);
|
|
188
|
+
if (this.consumeTurnEnd(session)) {
|
|
189
|
+
// Final flush — Stop fires after the last assistant message is
|
|
190
|
+
// written, but give one more read to catch a trailing line.
|
|
191
|
+
await this.pumpTranscript(session, onEvent);
|
|
192
|
+
return { code: 0, signal: null };
|
|
193
|
+
}
|
|
194
|
+
if (Date.now() > deadline) {
|
|
195
|
+
await onEvent({
|
|
196
|
+
type: "error",
|
|
197
|
+
payload: {
|
|
198
|
+
message: `claude-interactive turn timed out after ${this.opts.turnTimeoutMs}ms with no Stop hook`,
|
|
199
|
+
code: "CLAUDE_INTERACTIVE_TURN_TIMEOUT",
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
return { code: 1, signal: null };
|
|
203
|
+
}
|
|
204
|
+
await sleep(this.opts.pollIntervalMs);
|
|
205
|
+
}
|
|
206
|
+
})();
|
|
207
|
+
return {
|
|
208
|
+
kill: (_signal = "SIGINT") => {
|
|
209
|
+
// Interrupt = cancel the CURRENT turn, keep the session hot. ESC tells
|
|
210
|
+
// the TUI to abort the in-flight response. The process is NOT killed
|
|
211
|
+
// here — teardown happens via dispose() (`/quit`). The pump loop sees
|
|
212
|
+
// `turnAborted` and resolves the active turn.
|
|
213
|
+
turnAborted = true;
|
|
214
|
+
const session = this.sessions.get(input.sessionId);
|
|
215
|
+
session?.proc.write("\x1b");
|
|
216
|
+
},
|
|
217
|
+
done,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Graceful teardown of a session's interactive process. The runtime's
|
|
222
|
+
* `stop()` calls this. We type `/quit` (a real interactive command) so the
|
|
223
|
+
* TUI shuts down cleanly, flushing its transcript, then drop the session.
|
|
224
|
+
*/
|
|
225
|
+
async dispose(sessionId) {
|
|
226
|
+
const session = this.sessions.get(sessionId);
|
|
227
|
+
if (!session)
|
|
228
|
+
return;
|
|
229
|
+
this.sessions.delete(sessionId);
|
|
230
|
+
session.disposed = true;
|
|
231
|
+
if (session.exited)
|
|
232
|
+
return;
|
|
233
|
+
try {
|
|
234
|
+
session.proc.write("/quit\r");
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// fall through to force-kill
|
|
238
|
+
}
|
|
239
|
+
// Wait briefly for a clean exit, then force-kill.
|
|
240
|
+
const deadline = Date.now() + 3_000;
|
|
241
|
+
while (!session.exited && Date.now() < deadline) {
|
|
242
|
+
await sleep(50);
|
|
243
|
+
}
|
|
244
|
+
if (!session.exited) {
|
|
245
|
+
try {
|
|
246
|
+
session.proc.kill();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// already gone
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async ensureSession(input, effort) {
|
|
254
|
+
const existing = this.sessions.get(input.sessionId);
|
|
255
|
+
if (existing && !existing.exited && !existing.disposed)
|
|
256
|
+
return existing;
|
|
257
|
+
const claudeSessionId = coerceClaudeCliSessionId(input.sessionId);
|
|
258
|
+
const configDir = this.configDirOverride ??
|
|
259
|
+
input.env?.CLAUDE_CONFIG_DIR ??
|
|
260
|
+
process.env.CLAUDE_CONFIG_DIR ??
|
|
261
|
+
join(homedir(), ".claude");
|
|
262
|
+
const workDir = input.sessionStorageDir ?? join(tmpdir(), "pi-ca-leash-interactive", input.sessionId);
|
|
263
|
+
mkdirSync(workDir, { recursive: true });
|
|
264
|
+
const signalPath = join(workDir, "interactive-turn-end.signal");
|
|
265
|
+
writeFileSync(signalPath, ""); // create so offset tracking starts at 0
|
|
266
|
+
const settingsPath = join(workDir, "interactive-settings.json");
|
|
267
|
+
writeFileSync(settingsPath, stopHookSettings(signalPath));
|
|
268
|
+
const args = buildInteractiveArgs({
|
|
269
|
+
claudeSessionId,
|
|
270
|
+
settingsPath,
|
|
271
|
+
model: input.model,
|
|
272
|
+
appendSystemPrompt: input.appendSystemPrompt,
|
|
273
|
+
additionalDirectories: input.additionalDirectories,
|
|
274
|
+
effort,
|
|
275
|
+
});
|
|
276
|
+
const env = {};
|
|
277
|
+
for (const [k, v] of Object.entries(process.env))
|
|
278
|
+
if (v !== undefined)
|
|
279
|
+
env[k] = v;
|
|
280
|
+
for (const [k, v] of Object.entries(input.env ?? {}))
|
|
281
|
+
env[k] = v;
|
|
282
|
+
if (!env.TERM)
|
|
283
|
+
env.TERM = "xterm-256color";
|
|
284
|
+
const spawnPty = this.injectedPtySpawn ?? (await this.loadNodePty());
|
|
285
|
+
const proc = spawnPty(this.executable, args, {
|
|
286
|
+
cwd: input.cwd,
|
|
287
|
+
env,
|
|
288
|
+
cols: 120,
|
|
289
|
+
rows: 40,
|
|
290
|
+
name: env.TERM,
|
|
291
|
+
});
|
|
292
|
+
const session = {
|
|
293
|
+
proc,
|
|
294
|
+
claudeSessionId,
|
|
295
|
+
configDir,
|
|
296
|
+
signalPath,
|
|
297
|
+
signalOffset: 0,
|
|
298
|
+
transcriptOffset: 0,
|
|
299
|
+
jsonlRemainder: "",
|
|
300
|
+
ready: Promise.resolve(),
|
|
301
|
+
exited: false,
|
|
302
|
+
exitCode: null,
|
|
303
|
+
disposed: false,
|
|
304
|
+
};
|
|
305
|
+
// Readiness: resolve once PTY output has been quiet for `readyQuietMs`
|
|
306
|
+
// (the banner finished rendering) or `readyTimeoutMs` elapses.
|
|
307
|
+
let resolveReady;
|
|
308
|
+
session.ready = new Promise((resolve) => {
|
|
309
|
+
resolveReady = resolve;
|
|
310
|
+
});
|
|
311
|
+
let quietTimer;
|
|
312
|
+
let settled = false;
|
|
313
|
+
const settle = () => {
|
|
314
|
+
if (settled)
|
|
315
|
+
return;
|
|
316
|
+
settled = true;
|
|
317
|
+
if (quietTimer)
|
|
318
|
+
clearTimeout(quietTimer);
|
|
319
|
+
resolveReady();
|
|
320
|
+
};
|
|
321
|
+
const armQuiet = () => {
|
|
322
|
+
if (quietTimer)
|
|
323
|
+
clearTimeout(quietTimer);
|
|
324
|
+
quietTimer = setTimeout(settle, this.opts.readyQuietMs);
|
|
325
|
+
if (typeof quietTimer.unref === "function")
|
|
326
|
+
quietTimer.unref();
|
|
327
|
+
};
|
|
328
|
+
armQuiet();
|
|
329
|
+
const hardTimer = setTimeout(settle, this.opts.readyTimeoutMs);
|
|
330
|
+
if (typeof hardTimer.unref === "function")
|
|
331
|
+
hardTimer.unref();
|
|
332
|
+
proc.onData(() => {
|
|
333
|
+
if (!settled)
|
|
334
|
+
armQuiet();
|
|
335
|
+
});
|
|
336
|
+
proc.onExit(({ exitCode }) => {
|
|
337
|
+
session.exited = true;
|
|
338
|
+
session.exitCode = exitCode;
|
|
339
|
+
settle();
|
|
340
|
+
});
|
|
341
|
+
this.sessions.set(input.sessionId, session);
|
|
342
|
+
return session;
|
|
343
|
+
}
|
|
344
|
+
/** Read newly written transcript lines, parse, and emit them as messages. */
|
|
345
|
+
async pumpTranscript(session, onEvent) {
|
|
346
|
+
if (!session.transcriptPath) {
|
|
347
|
+
session.transcriptPath = findTranscript(session.configDir, session.claudeSessionId);
|
|
348
|
+
if (!session.transcriptPath)
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const { text, offset } = readAppended(session.transcriptPath, session.transcriptOffset);
|
|
352
|
+
session.transcriptOffset = offset;
|
|
353
|
+
if (!text)
|
|
354
|
+
return;
|
|
355
|
+
const buffer = session.jsonlRemainder + text;
|
|
356
|
+
const lines = buffer.split("\n");
|
|
357
|
+
session.jsonlRemainder = lines.pop() ?? "";
|
|
358
|
+
for (const line of lines) {
|
|
359
|
+
const trimmed = line.trim();
|
|
360
|
+
if (!trimmed)
|
|
361
|
+
continue;
|
|
362
|
+
let parsed;
|
|
363
|
+
try {
|
|
364
|
+
parsed = JSON.parse(trimmed);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
continue; // transcript may contain non-message bookkeeping lines
|
|
368
|
+
}
|
|
369
|
+
for (const message of parseClaudeSdkMessage(parsed)) {
|
|
370
|
+
await onEvent({ type: "message", payload: message });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** True if the Stop hook appended at least one new line since last checked. */
|
|
375
|
+
consumeTurnEnd(session) {
|
|
376
|
+
const { text, offset } = readAppended(session.signalPath, session.signalOffset);
|
|
377
|
+
session.signalOffset = offset;
|
|
378
|
+
return text.includes("stop");
|
|
379
|
+
}
|
|
380
|
+
async loadNodePty() {
|
|
381
|
+
let mod;
|
|
382
|
+
try {
|
|
383
|
+
// Non-literal specifier so TS does not statically resolve the optional
|
|
384
|
+
// native dependency at build time (it may be absent). Runtime import is
|
|
385
|
+
// identical to `import("node-pty")`.
|
|
386
|
+
const spec = "node-pty";
|
|
387
|
+
mod = (await import(spec));
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
throw new Error("claude-interactive driver requires the optional dependency 'node-pty'. " +
|
|
391
|
+
"Install it with `npm install node-pty`. " +
|
|
392
|
+
(error instanceof Error ? error.message : String(error)));
|
|
393
|
+
}
|
|
394
|
+
return mod.spawn;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function enrichSpawnError(message) {
|
|
398
|
+
const lower = message.toLowerCase();
|
|
399
|
+
if (lower.includes("enoent") || lower.includes("not found")) {
|
|
400
|
+
return `${message}\nHint: claude executable could not be spawned. Check PATH or set CLAUDE_CLI_EXECUTABLE.`;
|
|
401
|
+
}
|
|
402
|
+
return message;
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=claude-interactive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-interactive.js","sourceRoot":"","sources":["../../src/drivers/claude-interactive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { DriverEventEnvelope, RuntimeDriver, RuntimeDriverRunHandle, RuntimeDriverRunInput } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* `claude-pty` driver — drives the REAL Claude Code interactive TUI.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `claude-cli` / `claude-sdk` (which spawn a fresh one-shot process per
|
|
6
|
+
* turn and fake continuity via `--resume`), this driver keeps ONE long-lived
|
|
7
|
+
* interactive `claude` process alive per session, inside a PTY, and types each
|
|
8
|
+
* turn's prompt into it. Context stays hot in the live process — no per-turn
|
|
9
|
+
* cold start, no transcript reload.
|
|
10
|
+
*
|
|
11
|
+
* The hard problems of driving a TUI (no structured stdout, fragile
|
|
12
|
+
* turn-completion detection) are NOT solved by scraping the rendered screen.
|
|
13
|
+
* They are also NOT solved by the session transcript JSONL: the interactive
|
|
14
|
+
* TUI keeps its transcript in memory and never flushes the file the way the
|
|
15
|
+
* headless `-p` path does (verified against claude 2.1.x — the path the Stop
|
|
16
|
+
* hook reports never materialises, even after `/quit`).
|
|
17
|
+
*
|
|
18
|
+
* Instead we observe the session entirely through Claude Code HOOKS. We inject
|
|
19
|
+
* a per-invocation `--settings` file (so the user's `~/.claude/settings.json`
|
|
20
|
+
* and other running sessions are untouched) registering command hooks that
|
|
21
|
+
* append their JSON payload — one line each — to a per-session `hooks.jsonl`:
|
|
22
|
+
*
|
|
23
|
+
* - `PostToolUse` payloads carry `tool_name` / `tool_input` / `tool_response`
|
|
24
|
+
* → emitted as normalized tool_use + tool_result messages.
|
|
25
|
+
* - `Stop` payloads carry `last_assistant_message` AND mark the deterministic
|
|
26
|
+
* end of a turn → emitted as an assistant message, then the turn resolves.
|
|
27
|
+
*
|
|
28
|
+
* The PTY's only jobs: keep the TUI alive, type input (bracketed paste),
|
|
29
|
+
* answer the first-run trust dialog, send ESC to cancel a turn, and send
|
|
30
|
+
* `/quit` to tear the session down.
|
|
31
|
+
*/
|
|
32
|
+
export interface PtyProcessLike {
|
|
33
|
+
readonly pid: number;
|
|
34
|
+
write(data: string): void;
|
|
35
|
+
onData(listener: (data: string) => void): void;
|
|
36
|
+
onExit(listener: (event: {
|
|
37
|
+
exitCode: number;
|
|
38
|
+
signal?: number;
|
|
39
|
+
}) => void): void;
|
|
40
|
+
kill(signal?: string): void;
|
|
41
|
+
}
|
|
42
|
+
export type PtySpawnFn = (file: string, args: string[], options: {
|
|
43
|
+
cwd: string;
|
|
44
|
+
env: Record<string, string>;
|
|
45
|
+
cols: number;
|
|
46
|
+
rows: number;
|
|
47
|
+
name: string;
|
|
48
|
+
}) => PtyProcessLike;
|
|
49
|
+
export interface ClaudePtyDriverOptions {
|
|
50
|
+
/** Inject a PTY spawner (tests). Defaults to the Python pty allocator. */
|
|
51
|
+
ptySpawn?: PtySpawnFn;
|
|
52
|
+
executable?: string;
|
|
53
|
+
/** Python 3 interpreter used by the default PTY allocator. */
|
|
54
|
+
pythonExecutable?: string;
|
|
55
|
+
/** Override where Claude writes transcripts/config. Defaults to env / ~/.claude. */
|
|
56
|
+
configDir?: string;
|
|
57
|
+
/** Quiet window after spawn before the TUI is considered ready for input. */
|
|
58
|
+
readyQuietMs?: number;
|
|
59
|
+
/** Hard cap waiting for the TUI to settle after spawn. */
|
|
60
|
+
readyTimeoutMs?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Minimum time to keep watching for the first-run trust dialog before
|
|
63
|
+
* concluding it won't appear (an early quiet gap during terminal setup can
|
|
64
|
+
* precede the dialog by hundreds of ms — concluding "ready" then would type
|
|
65
|
+
* the prompt into a not-yet-rendered TUI). Trusted dirs pay this once.
|
|
66
|
+
*/
|
|
67
|
+
startupMinMs?: number;
|
|
68
|
+
/** Delay between bracketed-paste of the prompt and the submitting Enter. */
|
|
69
|
+
submitDelayMs?: number;
|
|
70
|
+
/** Poll cadence for the hook-payload file. */
|
|
71
|
+
pollIntervalMs?: number;
|
|
72
|
+
/** Hard cap for a single turn before giving up. */
|
|
73
|
+
turnTimeoutMs?: number;
|
|
74
|
+
}
|
|
75
|
+
export declare function buildPtyArgs(input: {
|
|
76
|
+
claudeSessionId: string;
|
|
77
|
+
settingsPath: string;
|
|
78
|
+
/**
|
|
79
|
+
* When true, launch with `--resume <id>` instead of `--session-id <id>` so
|
|
80
|
+
* the TUI reloads the existing transcript. Used only when respawning a
|
|
81
|
+
* session whose persistent process died mid-conversation — a fresh
|
|
82
|
+
* `--session-id` launch would otherwise start with no prior context.
|
|
83
|
+
*/
|
|
84
|
+
resume?: boolean;
|
|
85
|
+
model?: string;
|
|
86
|
+
appendSystemPrompt?: string;
|
|
87
|
+
additionalDirectories?: string[];
|
|
88
|
+
effort?: "low" | "medium" | "high" | "xhigh" | "max";
|
|
89
|
+
}): string[];
|
|
90
|
+
export declare class ClaudePtyDriver implements RuntimeDriver {
|
|
91
|
+
readonly name: "claude-pty";
|
|
92
|
+
private readonly executable;
|
|
93
|
+
private readonly pythonExecutable;
|
|
94
|
+
private readonly injectedPtySpawn?;
|
|
95
|
+
private readonly configDirOverride?;
|
|
96
|
+
private readonly opts;
|
|
97
|
+
private readonly sessions;
|
|
98
|
+
constructor(options?: ClaudePtyDriverOptions);
|
|
99
|
+
run(input: RuntimeDriverRunInput, onEventRaw: (event: DriverEventEnvelope) => Promise<void> | void): RuntimeDriverRunHandle;
|
|
100
|
+
/**
|
|
101
|
+
* Graceful teardown of a session's interactive process. The runtime's
|
|
102
|
+
* `stop()` calls this. We type `/quit` (a real interactive command) so the
|
|
103
|
+
* TUI shuts down cleanly, flushing its transcript, then drop the session.
|
|
104
|
+
*/
|
|
105
|
+
dispose(sessionId: string): Promise<void>;
|
|
106
|
+
private ensureSession;
|
|
107
|
+
/**
|
|
108
|
+
* Drive the one-time startup to a ready-for-input state.
|
|
109
|
+
*
|
|
110
|
+
* The naive "first quiet window = ready" heuristic is wrong: terminal-setup
|
|
111
|
+
* escapes are emitted, then there's a lull of a few hundred ms BEFORE the
|
|
112
|
+
* first-run trust dialog renders. Concluding "ready" in that lull types the
|
|
113
|
+
* prompt into a not-yet-rendered TUI and the dialog later swallows it.
|
|
114
|
+
*
|
|
115
|
+
* So we keep watching: if the trust dialog appears (it isn't suppressed by
|
|
116
|
+
* `--dangerously-skip-permissions` in interactive mode), accept it with Enter
|
|
117
|
+
* (preselected "Yes, I trust this folder"); otherwise only conclude "no
|
|
118
|
+
* dialog, ready" once output is quiet AND we've watched at least
|
|
119
|
+
* `startupMinMs` (so an early lull can't short-circuit). Either way we then
|
|
120
|
+
* wait for the post-dialog render to settle before returning.
|
|
121
|
+
*/
|
|
122
|
+
private awaitStartup;
|
|
123
|
+
/**
|
|
124
|
+
* Read newly appended hook payload lines, emit their normalized messages,
|
|
125
|
+
* and report whether a Stop hook (turn end) was among them.
|
|
126
|
+
*/
|
|
127
|
+
private pumpHooks;
|
|
128
|
+
/** Advance past hook lines already written (without emitting them). */
|
|
129
|
+
private drainHooks;
|
|
130
|
+
/** Pull complete newly-appended lines from the hooks file, advancing offset. */
|
|
131
|
+
private readHookLines;
|
|
132
|
+
/**
|
|
133
|
+
* Default PTY allocator — spawns Python 3 running {@link PTY_ALLOCATOR},
|
|
134
|
+
* which `pty.fork()`s a real pseudo-terminal, sets its window size, execs the
|
|
135
|
+
* target, and relays bytes between our stdio pipes and the pty master.
|
|
136
|
+
*
|
|
137
|
+
* Why Python instead of a native pty addon (node-pty): a real pty needs
|
|
138
|
+
* `openpty`/`forkpty` syscalls, so every JS pty library is either a native
|
|
139
|
+
* addon (needs a matching prebuild or a from-source compile — brittle on new
|
|
140
|
+
* Node ABIs) or a wrapper around a system binary. `python3` is present by
|
|
141
|
+
* default on macOS and most Linux, needs no compile step, and works headless
|
|
142
|
+
* (unlike the `script` binary, which requires its own controlling terminal).
|
|
143
|
+
* Tests inject their own `ptySpawn`, so this is only the production default.
|
|
144
|
+
*/
|
|
145
|
+
private readonly pythonPtySpawn;
|
|
146
|
+
}
|