@rallycry/conveyor-agent 8.4.1 → 8.5.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/dist/{chunk-B4QHEMMV.js → chunk-3T4SJEH6.js} +181 -24
- package/dist/chunk-3T4SJEH6.js.map +1 -0
- package/dist/{chunk-VDH55LTT.js → chunk-63FTZOL5.js} +227 -32
- package/dist/chunk-63FTZOL5.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +37 -0
- package/dist/index.js +2 -2
- package/dist/{tag-audit-handler-SWVMCAJH.js → tag-audit-handler-PKYLDJHH.js} +2 -2
- package/dist/{task-audit-handler-CZ2WWJFO.js → task-audit-handler-WINYLZMU.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-B4QHEMMV.js.map +0 -1
- package/dist/chunk-VDH55LTT.js.map +0 -1
- /package/dist/{tag-audit-handler-SWVMCAJH.js.map → tag-audit-handler-PKYLDJHH.js.map} +0 -0
- /package/dist/{task-audit-handler-CZ2WWJFO.js.map → task-audit-handler-WINYLZMU.js.map} +0 -0
|
@@ -95,22 +95,31 @@ function parseEnvelope(line) {
|
|
|
95
95
|
}
|
|
96
96
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
97
97
|
const record = parsed;
|
|
98
|
+
if (record.kind === "pre_tool_use") {
|
|
99
|
+
if (typeof record.id !== "string" || typeof record.tool_name !== "string") return null;
|
|
100
|
+
const toolInput = typeof record.tool_input === "object" && record.tool_input !== null ? record.tool_input : {};
|
|
101
|
+
return {
|
|
102
|
+
kind: "pre_tool_use",
|
|
103
|
+
request: { id: record.id, tool_name: record.tool_name, tool_input: toolInput }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
98
106
|
const result = {};
|
|
99
107
|
if (typeof record.tool_name === "string") result.tool_name = record.tool_name;
|
|
100
108
|
if (typeof record.elapsed_time_seconds === "number") {
|
|
101
109
|
result.elapsed_time_seconds = record.elapsed_time_seconds;
|
|
102
110
|
}
|
|
103
|
-
return result;
|
|
111
|
+
return { kind: "progress", progress: result };
|
|
104
112
|
}
|
|
105
113
|
var HookSocketServer = class {
|
|
106
|
-
constructor(socketPath, onProgress) {
|
|
114
|
+
constructor(socketPath, onProgress, onPreToolUse) {
|
|
107
115
|
this.socketPath = socketPath;
|
|
108
116
|
this.onProgress = onProgress;
|
|
117
|
+
this.onPreToolUse = onPreToolUse;
|
|
109
118
|
}
|
|
110
119
|
socketPath;
|
|
111
120
|
onProgress;
|
|
121
|
+
onPreToolUse;
|
|
112
122
|
server = null;
|
|
113
|
-
buffer = "";
|
|
114
123
|
_closed = false;
|
|
115
124
|
async listen() {
|
|
116
125
|
await unlink(this.socketPath).catch(() => void 0);
|
|
@@ -142,19 +151,41 @@ var HookSocketServer = class {
|
|
|
142
151
|
}
|
|
143
152
|
handleConnection(socket) {
|
|
144
153
|
socket.on("error", () => void 0);
|
|
154
|
+
let buffer = "";
|
|
145
155
|
socket.on("data", (chunk) => {
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
buffer += chunk.toString("utf8");
|
|
157
|
+
let index = buffer.indexOf("\n");
|
|
158
|
+
while (index >= 0) {
|
|
159
|
+
const line = buffer.slice(0, index);
|
|
160
|
+
buffer = buffer.slice(index + 1);
|
|
161
|
+
this.dispatch(line, socket);
|
|
162
|
+
index = buffer.indexOf("\n");
|
|
163
|
+
}
|
|
148
164
|
});
|
|
149
165
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
dispatch(line, socket) {
|
|
167
|
+
const envelope = parseEnvelope(line);
|
|
168
|
+
if (!envelope) return;
|
|
169
|
+
if (envelope.kind === "progress") {
|
|
170
|
+
this.onProgress(envelope.progress);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
void this.respondToPreToolUse(envelope.request, socket);
|
|
174
|
+
}
|
|
175
|
+
async respondToPreToolUse(request, socket) {
|
|
176
|
+
let verdict;
|
|
177
|
+
try {
|
|
178
|
+
verdict = this.onPreToolUse ? await this.onPreToolUse(request) : { decision: "allow" };
|
|
179
|
+
} catch (err) {
|
|
180
|
+
verdict = {
|
|
181
|
+
decision: "deny",
|
|
182
|
+
reason: `Conveyor validation failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
socket.write(`${JSON.stringify({ id: request.id, ...verdict })}
|
|
187
|
+
`);
|
|
188
|
+
} catch {
|
|
158
189
|
}
|
|
159
190
|
}
|
|
160
191
|
};
|
|
@@ -380,6 +411,9 @@ function buildSpawnArgs(input) {
|
|
|
380
411
|
args.push("--permission-mode", "plan");
|
|
381
412
|
}
|
|
382
413
|
args.push("--settings", input.settingsPath);
|
|
414
|
+
if (input.appendSystemPrompt) {
|
|
415
|
+
args.push("--append-system-prompt", input.appendSystemPrompt);
|
|
416
|
+
}
|
|
383
417
|
if (input.mcpConfigPath) {
|
|
384
418
|
args.push("--mcp-config", input.mcpConfigPath);
|
|
385
419
|
if (input.strictMcpConfig) {
|
|
@@ -434,8 +468,33 @@ function projectSlug(cwd) {
|
|
|
434
468
|
function sessionTranscriptPath(cwd, sessionId) {
|
|
435
469
|
return join(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
|
|
436
470
|
}
|
|
471
|
+
function configHomePlansDir() {
|
|
472
|
+
return join(claudeConfigHome(), "plans");
|
|
473
|
+
}
|
|
474
|
+
var ALLOW_RULES = [
|
|
475
|
+
"Bash",
|
|
476
|
+
"BashOutput",
|
|
477
|
+
"Edit",
|
|
478
|
+
"Write",
|
|
479
|
+
"Read",
|
|
480
|
+
"Glob",
|
|
481
|
+
"Grep",
|
|
482
|
+
"WebFetch",
|
|
483
|
+
"WebSearch",
|
|
484
|
+
"NotebookEdit",
|
|
485
|
+
"Task",
|
|
486
|
+
"TodoWrite",
|
|
487
|
+
"ToolSearch",
|
|
488
|
+
"KillShell",
|
|
489
|
+
"SlashCommand",
|
|
490
|
+
"Skill",
|
|
491
|
+
"mcp__conveyor__*"
|
|
492
|
+
];
|
|
437
493
|
var HOOK_HELPER_SOURCE = `"use strict";
|
|
438
494
|
const net = require("node:net");
|
|
495
|
+
const crypto = require("node:crypto");
|
|
496
|
+
|
|
497
|
+
const PRE_TOOL_USE_TIMEOUT_MS = 110000;
|
|
439
498
|
|
|
440
499
|
let raw = "";
|
|
441
500
|
process.stdin.setEncoding("utf8");
|
|
@@ -443,17 +502,21 @@ process.stdin.on("data", (chunk) => {
|
|
|
443
502
|
raw += chunk;
|
|
444
503
|
});
|
|
445
504
|
process.stdin.on("end", () => {
|
|
446
|
-
let
|
|
505
|
+
let payload = {};
|
|
447
506
|
try {
|
|
448
507
|
const parsed = JSON.parse(raw);
|
|
449
|
-
if (parsed && typeof parsed
|
|
508
|
+
if (parsed && typeof parsed === "object") payload = parsed;
|
|
450
509
|
} catch {
|
|
451
|
-
|
|
510
|
+
payload = {};
|
|
511
|
+
}
|
|
512
|
+
if (payload.hook_event_name === "PreToolUse") {
|
|
513
|
+
preToolUse(payload);
|
|
514
|
+
} else {
|
|
515
|
+
relayProgress(typeof payload.tool_name === "string" ? payload.tool_name : "");
|
|
452
516
|
}
|
|
453
|
-
relay(toolName);
|
|
454
517
|
});
|
|
455
518
|
|
|
456
|
-
function
|
|
519
|
+
function relayProgress(toolName) {
|
|
457
520
|
const socketPath = process.env.CONVEYOR_HOOK_SOCKET;
|
|
458
521
|
let done = false;
|
|
459
522
|
const finish = () => {
|
|
@@ -485,6 +548,76 @@ function relay(toolName) {
|
|
|
485
548
|
finish();
|
|
486
549
|
});
|
|
487
550
|
}
|
|
551
|
+
|
|
552
|
+
function preToolUse(payload) {
|
|
553
|
+
const socketPath = process.env.CONVEYOR_HOOK_SOCKET;
|
|
554
|
+
let done = false;
|
|
555
|
+
const respond = (decision, reason) => {
|
|
556
|
+
if (done) return;
|
|
557
|
+
done = true;
|
|
558
|
+
process.stdout.write(
|
|
559
|
+
JSON.stringify({
|
|
560
|
+
hookSpecificOutput: {
|
|
561
|
+
hookEventName: "PreToolUse",
|
|
562
|
+
permissionDecision: decision,
|
|
563
|
+
...(reason ? { permissionDecisionReason: reason } : {}),
|
|
564
|
+
},
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
process.exit(0);
|
|
568
|
+
};
|
|
569
|
+
const denyUnavailable = () =>
|
|
570
|
+
respond(
|
|
571
|
+
"deny",
|
|
572
|
+
"Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
|
|
573
|
+
);
|
|
574
|
+
if (!socketPath) {
|
|
575
|
+
denyUnavailable();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const id = crypto.randomBytes(8).toString("hex");
|
|
579
|
+
const timer = setTimeout(denyUnavailable, PRE_TOOL_USE_TIMEOUT_MS);
|
|
580
|
+
const client = net.connect(socketPath, () => {
|
|
581
|
+
client.write(
|
|
582
|
+
JSON.stringify({
|
|
583
|
+
kind: "pre_tool_use",
|
|
584
|
+
id,
|
|
585
|
+
tool_name: typeof payload.tool_name === "string" ? payload.tool_name : "",
|
|
586
|
+
tool_input:
|
|
587
|
+
payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : {},
|
|
588
|
+
}) + "\\n",
|
|
589
|
+
);
|
|
590
|
+
});
|
|
591
|
+
let buffer = "";
|
|
592
|
+
client.on("data", (chunk) => {
|
|
593
|
+
buffer += chunk.toString("utf8");
|
|
594
|
+
let index = buffer.indexOf("\\n");
|
|
595
|
+
while (index >= 0) {
|
|
596
|
+
const line = buffer.slice(0, index);
|
|
597
|
+
buffer = buffer.slice(index + 1);
|
|
598
|
+
try {
|
|
599
|
+
const verdict = JSON.parse(line);
|
|
600
|
+
if (verdict && verdict.id === id) {
|
|
601
|
+
clearTimeout(timer);
|
|
602
|
+
client.end();
|
|
603
|
+
respond(verdict.decision === "allow" ? "allow" : "deny", verdict.reason);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
/* keep scanning */
|
|
608
|
+
}
|
|
609
|
+
index = buffer.indexOf("\\n");
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
client.on("error", () => {
|
|
613
|
+
clearTimeout(timer);
|
|
614
|
+
denyUnavailable();
|
|
615
|
+
});
|
|
616
|
+
client.on("close", () => {
|
|
617
|
+
clearTimeout(timer);
|
|
618
|
+
denyUnavailable();
|
|
619
|
+
});
|
|
620
|
+
}
|
|
488
621
|
`;
|
|
489
622
|
async function writeHookSettings(dir) {
|
|
490
623
|
const helperPath = join(dir, "hook-helper.cjs");
|
|
@@ -493,21 +626,24 @@ async function writeHookSettings(dir) {
|
|
|
493
626
|
await writeFile(helperPath, HOOK_HELPER_SOURCE, "utf8");
|
|
494
627
|
await chmod(helperPath, 493);
|
|
495
628
|
const settings = {
|
|
496
|
-
// Auto-approve
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
// no write prompts) and build mode passes `--dangerously-skip-permissions`
|
|
505
|
-
// (which ignores the allow-list entirely). So this list only needs the MCP
|
|
506
|
-
// glob, which plan mode would otherwise prompt for.
|
|
629
|
+
// Auto-approve tool calls so the interactive (PTY) agent never stops to
|
|
630
|
+
// prompt the viewer. This only suppresses per-tool approval prompts — it
|
|
631
|
+
// does NOT relax the planning gate: in discovery/plan mode the spawn passes
|
|
632
|
+
// `--permission-mode plan`, which keeps the agent read-only (no edits/commits
|
|
633
|
+
// until it exits plan mode) regardless of this allow-list. In build mode the
|
|
634
|
+
// spawn already bypasses prompts via `--dangerously-skip-permissions`.
|
|
635
|
+
// NOTE: a bare "*" rule is INVALID (the CLI rejects it and parks the TUI on
|
|
636
|
+
// a Settings Warning dialog at boot) — hence the explicit ALLOW_RULES list.
|
|
507
637
|
permissions: {
|
|
508
|
-
allow:
|
|
638
|
+
allow: ALLOW_RULES
|
|
509
639
|
},
|
|
510
640
|
hooks: {
|
|
641
|
+
PreToolUse: [
|
|
642
|
+
{
|
|
643
|
+
matcher: "ExitPlanMode",
|
|
644
|
+
hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 120 }]
|
|
645
|
+
}
|
|
646
|
+
],
|
|
511
647
|
PostToolUse: [
|
|
512
648
|
{
|
|
513
649
|
matcher: "*",
|
|
@@ -703,6 +839,9 @@ var PtySession = class {
|
|
|
703
839
|
// plus the path to the `--mcp-config` that points at them.
|
|
704
840
|
toolServers = [];
|
|
705
841
|
mcpConfigPath = null;
|
|
842
|
+
// Plan-dialog auto-accept state (see armPlanDialogAutoAccept).
|
|
843
|
+
pendingPlanApproval = false;
|
|
844
|
+
planApprovalTimer = null;
|
|
706
845
|
onIdle(listener) {
|
|
707
846
|
this.idleListeners.push(listener);
|
|
708
847
|
}
|
|
@@ -731,7 +870,11 @@ var PtySession = class {
|
|
|
731
870
|
try {
|
|
732
871
|
this.tempDir = await mkdtemp(join3(tmpdir(), "conveyor-pty-"));
|
|
733
872
|
const socketPath = join3(this.tempDir, "hook.sock");
|
|
734
|
-
this.socket = new HookSocketServer(
|
|
873
|
+
this.socket = new HookSocketServer(
|
|
874
|
+
socketPath,
|
|
875
|
+
(progress) => this.handleProgress(progress),
|
|
876
|
+
(request) => this.handlePreToolUse(request)
|
|
877
|
+
);
|
|
735
878
|
await this.socket.listen();
|
|
736
879
|
const { settingsPath } = await writeHookSettings(this.tempDir);
|
|
737
880
|
await this.setupToolServers();
|
|
@@ -777,6 +920,7 @@ var PtySession = class {
|
|
|
777
920
|
async teardown() {
|
|
778
921
|
if (this._toreDown) return;
|
|
779
922
|
this._toreDown = true;
|
|
923
|
+
this.disarmPlanDialogAutoAccept();
|
|
780
924
|
this.unsubInput?.();
|
|
781
925
|
this.unsubInput = null;
|
|
782
926
|
this.unsubResize?.();
|
|
@@ -831,6 +975,7 @@ var PtySession = class {
|
|
|
831
975
|
model: this.options.model,
|
|
832
976
|
permissionMode: this.options.permissionMode,
|
|
833
977
|
settingsPath,
|
|
978
|
+
...this.options.appendSystemPrompt ? { appendSystemPrompt: this.options.appendSystemPrompt } : {},
|
|
834
979
|
// Only this config: ignore the user's ~/.claude.json / project .mcp.json
|
|
835
980
|
// so the agent's tool set is deterministic (and a stale personal conveyor
|
|
836
981
|
// server doesn't load).
|
|
@@ -874,7 +1019,56 @@ var PtySession = class {
|
|
|
874
1019
|
...progress.elapsed_time_seconds === void 0 ? {} : { elapsed_time_seconds: progress.elapsed_time_seconds }
|
|
875
1020
|
});
|
|
876
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* PreToolUse request from the hook helper → the runner's `canUseTool`
|
|
1024
|
+
* verdict. Only ExitPlanMode is matched by the settings hook, so this is
|
|
1025
|
+
* the Conveyor plan-exit gate (validation + identification trigger run
|
|
1026
|
+
* inside the callback). Absent callback → allow (mirrors the SDK default).
|
|
1027
|
+
*/
|
|
1028
|
+
async handlePreToolUse(request) {
|
|
1029
|
+
const canUseTool = this.options.canUseTool;
|
|
1030
|
+
let verdict;
|
|
1031
|
+
if (canUseTool) {
|
|
1032
|
+
const result = await canUseTool(request.tool_name, request.tool_input);
|
|
1033
|
+
verdict = result.behavior === "allow" ? { decision: "allow" } : { decision: "deny", reason: result.message };
|
|
1034
|
+
} else {
|
|
1035
|
+
verdict = { decision: "allow" };
|
|
1036
|
+
}
|
|
1037
|
+
if (verdict.decision === "allow" && request.tool_name === "ExitPlanMode" && this.options.planDialogAutoAccept) {
|
|
1038
|
+
this.armPlanDialogAutoAccept();
|
|
1039
|
+
}
|
|
1040
|
+
return verdict;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* A hook "allow" does not bypass the CLI's plan-approval dialog — it renders
|
|
1044
|
+
* right after the verdict. Press Enter (default: "Yes, auto-accept edits")
|
|
1045
|
+
* until the turn resumes; the next transcript record only lands once the
|
|
1046
|
+
* dialog is answered, so it doubles as the stop signal.
|
|
1047
|
+
*/
|
|
1048
|
+
armPlanDialogAutoAccept() {
|
|
1049
|
+
if (this.planApprovalTimer) return;
|
|
1050
|
+
this.pendingPlanApproval = true;
|
|
1051
|
+
let presses = 0;
|
|
1052
|
+
const press = () => {
|
|
1053
|
+
if (this._toreDown || !this.pendingPlanApproval || presses >= 6) {
|
|
1054
|
+
this.disarmPlanDialogAutoAccept();
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
presses++;
|
|
1058
|
+
this.writeStdin("\r");
|
|
1059
|
+
};
|
|
1060
|
+
this.planApprovalTimer = setInterval(press, 1500);
|
|
1061
|
+
setTimeout(press, 700);
|
|
1062
|
+
}
|
|
1063
|
+
disarmPlanDialogAutoAccept() {
|
|
1064
|
+
this.pendingPlanApproval = false;
|
|
1065
|
+
if (this.planApprovalTimer) {
|
|
1066
|
+
clearInterval(this.planApprovalTimer);
|
|
1067
|
+
this.planApprovalTimer = null;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
877
1070
|
handleTranscriptEvent(event) {
|
|
1071
|
+
if (this.pendingPlanApproval) this.disarmPlanDialogAutoAccept();
|
|
878
1072
|
this.queue.push(event);
|
|
879
1073
|
if (event.type === "result") {
|
|
880
1074
|
this.sawResult = true;
|
|
@@ -1113,9 +1307,10 @@ function createServiceLogger(service) {
|
|
|
1113
1307
|
export {
|
|
1114
1308
|
defineTool,
|
|
1115
1309
|
sessionTranscriptPath,
|
|
1310
|
+
configHomePlansDir,
|
|
1116
1311
|
ensureClaudeCredentials,
|
|
1117
1312
|
removeConveyorCredentials,
|
|
1118
1313
|
createHarness,
|
|
1119
1314
|
createServiceLogger
|
|
1120
1315
|
};
|
|
1121
|
-
//# sourceMappingURL=chunk-
|
|
1316
|
+
//# sourceMappingURL=chunk-63FTZOL5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/harness/types.ts","../src/harness/claude-code/index.ts","../src/harness/pty/session.ts","../src/harness/pty/event-queue.ts","../src/harness/pty/hook-socket.ts","../src/harness/pty/jsonl-tailer.ts","../src/harness/pty/record-mapper.ts","../src/harness/pty/spawn-args.ts","../src/harness/pty/settings.ts","../src/harness/pty/tool-server.ts","../src/harness/pty/mcp-server.ts","../src/harness/pty/credentials.ts","../src/harness/pty/index.ts","../src/harness/index.ts","../src/utils/logger.ts"],"sourcesContent":["/**\n * Harness-neutral types for agent query execution.\n *\n * These types abstract the underlying agent SDK so that the runner,\n * execution, and tool layers never reference SDK-specific imports\n * directly. The only place that should import from\n * `@anthropic-ai/claude-agent-sdk` is `harness/claude-code/`.\n */\n\nimport type { z } from \"zod\";\n\n// ── Events emitted by the harness during a query ────────────────────────\n\nexport interface HarnessSystemInitEvent {\n type: \"system\";\n subtype: \"init\";\n session_id?: string;\n model: string;\n}\n\nexport interface HarnessCompactBoundaryEvent {\n type: \"system\";\n subtype: \"compact_boundary\";\n compact_metadata: { trigger: \"manual\" | \"auto\"; pre_tokens: number };\n}\n\nexport interface HarnessTaskStartedEvent {\n type: \"system\";\n subtype: \"task_started\";\n task_id: string;\n description: string;\n}\n\nexport interface HarnessTaskProgressEvent {\n type: \"system\";\n subtype: \"task_progress\";\n task_id: string;\n description: string;\n usage?: { tool_uses: number; duration_ms: number };\n}\n\nexport type HarnessSystemEvent =\n | HarnessSystemInitEvent\n | HarnessCompactBoundaryEvent\n | HarnessTaskStartedEvent\n | HarnessTaskProgressEvent;\n\nexport interface HarnessContentBlock {\n type: string;\n text?: string;\n name?: string;\n input?: unknown;\n id?: string;\n}\n\nexport interface HarnessAssistantEvent {\n type: \"assistant\";\n message: {\n role: \"assistant\";\n content: HarnessContentBlock[];\n usage?: {\n input_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n };\n}\n\nexport interface HarnessResultSuccessEvent {\n type: \"result\";\n subtype: \"success\";\n result: string;\n total_cost_usd: number;\n modelUsage?: Record<string, unknown>;\n sessionId?: string;\n}\n\nexport interface HarnessResultErrorEvent {\n type: \"result\";\n subtype: \"error\";\n errors: string[];\n sessionId?: string;\n}\n\nexport type HarnessResultEvent = HarnessResultSuccessEvent | HarnessResultErrorEvent;\n\nexport interface HarnessRateLimitEvent {\n type: \"rate_limit_event\";\n rate_limit_info: {\n status: string;\n rateLimitType?: string;\n utilization?: number;\n resetsAt?: unknown;\n };\n}\n\nexport interface HarnessToolProgressEvent {\n type: \"tool_progress\";\n tool_name?: string;\n elapsed_time_seconds?: number;\n}\n\nexport type HarnessEvent =\n | HarnessSystemEvent\n | HarnessAssistantEvent\n | HarnessResultEvent\n | HarnessRateLimitEvent\n | HarnessToolProgressEvent;\n\n// ── User message fed into a query ───────────────────────────────────────\n\nexport interface HarnessUserMessage {\n type: \"user\";\n session_id: string;\n message: { role: \"user\"; content: string | unknown[] };\n parent_tool_use_id: null;\n}\n\n// ── Tool definition (harness-neutral) ───────────────────────────────────\n\nexport interface HarnessToolAnnotations {\n readOnlyHint?: boolean;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- generic tool handler */\nexport interface HarnessToolDefinition {\n name: string;\n description: string;\n schema: z.ZodRawShape;\n handler: (\n input: any,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>;\n annotations?: HarnessToolAnnotations;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n// ── Hook types ──────────────────────────────────────────────────────────\n\nexport interface HarnessHookInput {\n hook_event_name: string;\n tool_name: string;\n tool_response: unknown;\n}\n\nexport interface HarnessHookOutput {\n continue: boolean;\n}\n\nexport type HarnessPostToolUseHook = (input: HarnessHookInput) => Promise<HarnessHookOutput>;\n\n// ── MCP server handle (opaque to the runner) ────────────────────────────\n\n/** Opaque handle returned by `AgentHarness.createMcpServer()`. */\nexport type HarnessMcpServer = unknown;\n\n// ── PTY relay bridge ────────────────────────────────────────────────────\n\n/**\n * Bidirectional bridge between a PTY harness session and the S2 server relay.\n *\n * Kept harness-neutral (no AgentConnection import) so this module never depends\n * on the connection layer. Only the PTY harness consumes it; the SDK harness\n * ignores it. The runner adapts an `AgentConnection` into this shape.\n */\nexport interface PtyBridge {\n /** Forward a raw chunk of terminal output to the relay (fire-and-forget). */\n sendOutput(data: string, dims?: { cols: number; rows: number }): void;\n /** Subscribe to user keystrokes forwarded from the relay. Returns unsubscribe. */\n onInput(handler: (data: string) => void): () => void;\n /** Subscribe to reconciled terminal-resize events from the relay. Returns unsubscribe. */\n onResize(handler: (cols: number, rows: number) => void): () => void;\n}\n\n// ── Query options ───────────────────────────────────────────────────────\n\nexport interface HarnessQueryOptions {\n model: string;\n systemPrompt: unknown;\n cwd: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n allowDangerouslySkipPermissions: boolean;\n tools: { type: \"preset\"; preset: \"claude_code\" };\n mcpServers: Record<string, HarnessMcpServer>;\n settingSources?: (\"user\" | \"project\" | \"local\")[];\n sandbox?: { enabled: boolean };\n maxTurns?: number;\n maxBudgetUsd?: number;\n effort?: string;\n thinking?: unknown;\n betas?: unknown;\n disallowedTools?: string[];\n abortController?: AbortController;\n enableFileCheckpointing?: boolean;\n canUseTool?: (\n toolName: string,\n input: Record<string, unknown>,\n ) => Promise<\n | { behavior: \"allow\"; updatedInput?: Record<string, unknown> }\n | { behavior: \"deny\"; message: string }\n >;\n hooks?: Record<string, { hooks: HarnessPostToolUseHook[]; timeout: number }[]>;\n resume?: string;\n sessionId?: string;\n stderr?: (data: string) => void;\n /**\n * How the PTY harness delivers the query prompt to the spawned CLI:\n * - \"submit\" (default): paste the prompt and press Enter — the turn runs\n * immediately (auto-mode tasks, follow-up chat messages, resumes).\n * - \"prefill\": paste the prompt into the TUI input WITHOUT submitting, so\n * a human at the Connected-TUI terminal can review/edit/Enter it\n * (manual-mode initial instructions).\n * The SDK harness has no interactive input box and ignores this option.\n */\n promptDelivery?: \"submit\" | \"prefill\";\n /**\n * Extra system-prompt text passed to the spawned CLI via\n * `--append-system-prompt`. PTY harness only — the SDK harness already\n * receives the full system prompt through `systemPrompt.append` and ignores\n * this field.\n */\n appendSystemPrompt?: string;\n /**\n * PTY harness only: after the PreToolUse hook allows an ExitPlanMode call,\n * the interactive CLI still shows the plan-approval dialog (a hook \"allow\"\n * does not bypass it). When set, the session presses Enter to accept the\n * default option (\"Yes, auto-accept edits\") so an autonomous agent\n * continues building in the same session without a human at the terminal.\n * Conveyor's validation already happened in the hook — the dialog is\n * residual UX at that point.\n */\n planDialogAutoAccept?: boolean;\n}\n\n// ── Tool definition helper ──────────────────────────────────────────────\n\n/**\n * Construct a `HarnessToolDefinition` with the same signature as the SDK's\n * `tool()` helper, but without importing the SDK. The harness implementation\n * converts these into SDK-native tools when `createMcpServer()` is called.\n */\nexport function defineTool<T extends z.ZodRawShape>(\n name: string,\n description: string,\n schema: T,\n handler: (\n input: z.infer<z.ZodObject<T>>,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>,\n options?: { annotations?: HarnessToolAnnotations },\n): HarnessToolDefinition {\n return {\n name,\n description,\n schema,\n handler: handler as HarnessToolDefinition[\"handler\"],\n annotations: options?.annotations,\n };\n}\n\n// ── AgentHarness interface ──────────────────────────────────────────────\n\nexport interface AgentHarness {\n /** Start a streaming query and return an async generator of events. */\n executeQuery(options: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void>;\n\n /** Wrap an array of harness-neutral tool definitions into an MCP server. */\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer;\n}\n","/**\n * ClaudeCodeHarness — wraps `@anthropic-ai/claude-agent-sdk` behind the\n * generic `AgentHarness` interface.\n *\n * This is the ONLY module that should import from the SDK.\n */\n\nimport { query, tool, createSdkMcpServer } from \"@anthropic-ai/claude-agent-sdk\";\nimport type {\n AgentHarness,\n HarnessEvent,\n HarnessQueryOptions,\n HarnessToolDefinition,\n HarnessMcpServer,\n HarnessUserMessage,\n} from \"../types.js\";\n\nexport class ClaudeCodeHarness implements AgentHarness {\n async *executeQuery(opts: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void> {\n const sdkEvents = query({\n prompt: opts.prompt as Parameters<typeof query>[0][\"prompt\"],\n options: {\n ...(opts.options as Parameters<typeof query>[0][\"options\"]),\n ...(opts.resume ? { resume: opts.resume } : {}),\n ...(opts.options.sessionId ? { sessionId: opts.options.sessionId } : {}),\n ...(opts.options.abortController ? { abortController: opts.options.abortController } : {}),\n },\n });\n\n for await (const event of sdkEvents) {\n yield event as unknown as HarnessEvent;\n }\n }\n\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer {\n const sdkTools = config.tools.map((t) =>\n tool(\n t.name,\n t.description,\n t.schema,\n t.handler as Parameters<typeof tool>[3],\n t.annotations ? { annotations: t.annotations } : undefined,\n ),\n );\n return createSdkMcpServer({ name: config.name, tools: sdkTools });\n }\n}\n","/**\n * PtySession — orchestrates one `claude` CLI run under a pseudo-terminal.\n *\n * It merges two event sources into a single stream:\n * 1. the on-disk transcript JSONL (tailed via JsonlTailer), and\n * 2. PostToolUse progress envelopes relayed over a Unix socket.\n *\n * Raw PTY stdout is never turned into HarnessEvents — only the trusted\n * transcript file and the validated socket envelopes become events. When a\n * PtyBridge is supplied, that raw stdout is instead mirrored verbatim to the\n * S2 relay so the S5 \"Connected TUI\" terminal can render it, and relayed\n * keystrokes / resizes are fed back into the pty.\n *\n * `node-pty` is loaded lazily so that SDK-only consumers never pull in the\n * native module.\n */\n\nimport { mkdtemp, mkdir, rm, stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport type { HarnessEvent, HarnessQueryOptions, HarnessUserMessage, PtyBridge } from \"../types.js\";\nimport { AsyncEventQueue } from \"./event-queue.js\";\nimport {\n HookSocketServer,\n type ToolProgress,\n type PreToolUseRequest,\n type PreToolUseVerdict,\n} from \"./hook-socket.js\";\nimport { JsonlTailer } from \"./jsonl-tailer.js\";\nimport { buildSpawnArgs, resolveClaudeBinary, buildExitErrors } from \"./spawn-args.js\";\nimport { writeHookSettings, sessionTranscriptPath } from \"./settings.js\";\nimport { PtyToolServer, startToolServers } from \"./tool-server.js\";\n\n// Cap on the rolling tail of raw terminal output retained for diagnostics when\n// `claude` exits without a result. Bounded so a long-running session can't grow\n// this unboundedly; only the most recent bytes (where a failure surfaces) matter.\nconst MAX_DIAGNOSTIC_OUTPUT = 4000;\n\ninterface PtyProcess {\n onData(listener: (data: string) => void): void;\n onExit(listener: (event: { exitCode: number }) => void): void;\n write(data: string): void;\n resize(cols: number, rows: number): void;\n kill(signal?: string): void;\n}\n\ninterface PtySpawnOptions {\n name: string;\n cols: number;\n rows: number;\n cwd: string;\n env: Record<string, string>;\n}\n\ntype PtySpawn = (file: string, args: string[], options: PtySpawnOptions) => PtyProcess;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction extractSpawn(mod: unknown): PtySpawn | null {\n if (!isRecord(mod)) return null;\n if (typeof mod.spawn === \"function\") return mod.spawn as PtySpawn;\n const def = mod.default;\n if (isRecord(def) && typeof def.spawn === \"function\") return def.spawn as PtySpawn;\n return null;\n}\n\nasync function loadPtySpawn(): Promise<PtySpawn> {\n const mod: unknown = await import(\"node-pty\");\n const spawn = extractSpawn(mod);\n if (!spawn) throw new Error(\"node-pty: spawn export not found\");\n return spawn;\n}\n\nexport function inheritedEnv(socketPath: string): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (typeof value === \"string\") env[key] = value;\n }\n // Do NOT force CLAUDE_CONFIG_DIR. Forcing it to ~/.claude makes the CLI read\n // <dir>/.claude.json — a fresh, un-onboarded config — instead of the user's\n // real ~/.claude.json, which re-triggers the first-run theme picker + login\n // even though valid credentials already exist in ~/.claude/. The loop above\n // already propagates an explicitly-set CLAUDE_CONFIG_DIR (pods/codespaces),\n // and the config dir is unchanged either way, so transcript tailing (which\n // uses claudeConfigHome()) still resolves to the same projects/ directory.\n env.CONVEYOR_HOOK_SOCKET = socketPath;\n return env;\n}\n\n/**\n * The exact bytes written to the CLI's stdin to deliver a prompt. The text is\n * wrapped in bracketed-paste markers so embedded newlines land as literal\n * lines in the input box instead of submitting it early. With \"submit\"\n * delivery (the default) a trailing Enter follows the paste; with \"prefill\"\n * the text is left in the input box unsubmitted for the human at the\n * Connected-TUI terminal.\n */\nexport function buildPromptBytes(text: string, delivery?: \"submit\" | \"prefill\"): string {\n const paste = `\\x1b[200~${text}\\x1b[201~`;\n return delivery === \"prefill\" ? paste : `${paste}\\r`;\n}\n\n/** Current size of the transcript, or 0 if it does not exist yet. */\nasync function transcriptSize(path: string): Promise<number> {\n try {\n return (await stat(path)).size;\n } catch {\n return 0;\n }\n}\n\nexport class PtySession {\n private readonly queue = new AsyncEventQueue<HarnessEvent>();\n private socket: HookSocketServer | null = null;\n private tailer: JsonlTailer | null = null;\n private pty: PtyProcess | null = null;\n private tempDir = \"\";\n private sawResult = false;\n // Rolling tail of raw PTY output, retained only to enrich the error when the\n // CLI exits before emitting a result (the bytes are otherwise relayed to S5\n // and never become events). Trimmed to MAX_DIAGNOSTIC_OUTPUT on every write.\n private recentOutput = \"\";\n private _toreDown = false;\n private readonly exitListeners: ((code: number) => void)[] = [];\n private readonly idleListeners: (() => void)[] = [];\n private abortHandler: (() => void) | null = null;\n private cols = 120;\n private rows = 40;\n private unsubInput: (() => void) | null = null;\n private unsubResize: (() => void) | null = null;\n // In-process HTTP MCP servers exposing the harness tools to the spawned CLI,\n // plus the path to the `--mcp-config` that points at them.\n private toolServers: PtyToolServer[] = [];\n private mcpConfigPath: string | null = null;\n // Plan-dialog auto-accept state (see armPlanDialogAutoAccept).\n private pendingPlanApproval = false;\n private planApprovalTimer: NodeJS.Timeout | null = null;\n\n constructor(\n private readonly prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>,\n private readonly options: HarnessQueryOptions,\n private readonly resume?: string,\n private readonly bridge?: PtyBridge,\n ) {}\n\n onIdle(listener: () => void): void {\n this.idleListeners.push(listener);\n }\n\n onExit(listener: (code: number) => void): void {\n this.exitListeners.push(listener);\n }\n\n get isToreDown(): boolean {\n return this._toreDown;\n }\n\n get hookSocketPath(): string | null {\n return this.socket ? this.socket.socketPath : null;\n }\n\n events(): AsyncGenerator<HarnessEvent, void> {\n return this.queue.drain();\n }\n\n async start(): Promise<void> {\n // Fail fast before allocating resources: the tailer can only locate the\n // transcript if we know which session id the CLI will write to.\n const sessionId = this.resume ?? this.options.sessionId;\n if (!sessionId) {\n throw new Error(\"PtySession requires options.sessionId or a resume target\");\n }\n // stop()/softStop() abort the query's shared AbortController. The SDK\n // harness consumes it natively; under the PTY harness we must translate an\n // abort into teardown() — otherwise a mid-turn `claude` (which may never\n // emit a transcript `result` and does not exit) is left running while the\n // event consumer stays parked on the queue.\n const signal = this.options.abortController?.signal;\n if (signal?.aborted) {\n // Already aborted before we allocated anything — tear down (which closes\n // the empty queue so a parked consumer completes) and bail.\n await this.teardown();\n return;\n }\n try {\n this.tempDir = await mkdtemp(join(tmpdir(), \"conveyor-pty-\"));\n const socketPath = join(this.tempDir, \"hook.sock\");\n this.socket = new HookSocketServer(\n socketPath,\n (progress) => this.handleProgress(progress),\n (request) => this.handlePreToolUse(request),\n );\n await this.socket.listen();\n const { settingsPath } = await writeHookSettings(this.tempDir);\n // Expose the harness's in-process tools to the spawned CLI over a loopback\n // HTTP MCP server, written into a `--mcp-config` consumed by spawn().\n await this.setupToolServers();\n const transcriptPath = sessionTranscriptPath(this.options.cwd, sessionId);\n await mkdir(dirname(transcriptPath), { recursive: true });\n // On resume the transcript already holds the prior session's records; start\n // tailing at EOF so historical records aren't replayed as fresh events.\n const startOffset = this.resume ? await transcriptSize(transcriptPath) : 0;\n this.tailer = new JsonlTailer(transcriptPath, (event) => this.handleTranscriptEvent(event));\n this.tailer.start(startOffset);\n await this.spawn(settingsPath, socketPath);\n // Register after spawn so the handler has a live pty to kill, then\n // re-check: an abort racing with allocation (fired before the listener\n // attached) would otherwise be missed.\n if (signal) {\n this.abortHandler = () => {\n void this.teardown();\n };\n signal.addEventListener(\"abort\", this.abortHandler, { once: true });\n if (signal.aborted) {\n await this.teardown();\n return;\n }\n }\n // Wire relayed keystrokes / resizes into the live pty. Registered only\n // after a successful spawn so writeStdin/resizePty have a process to\n // target; the unsubscribers are cleared in teardown().\n if (this.bridge) {\n this.unsubInput = this.bridge.onInput((data) => this.writeStdin(data));\n this.unsubResize = this.bridge.onResize((cols, rows) => this.resizePty(cols, rows));\n }\n await this.feedPrompt();\n } catch (err) {\n // A failure after we begin allocating (socket listen, tailer interval,\n // temp dir, spawn) would otherwise leak those resources: the PtyHarness\n // consumer only calls teardown() once start() has resolved.\n await this.teardown();\n throw err;\n }\n }\n\n writeStdin(text: string): void {\n this.pty?.write(text);\n }\n\n /** Apply a relayed resize to the live pty (reconciled dims from the server). */\n private resizePty(cols: number, rows: number): void {\n if (cols <= 0 || rows <= 0) return;\n this.cols = cols;\n this.rows = rows;\n try {\n this.pty?.resize(cols, rows);\n } catch {\n /* pty already exited */\n }\n }\n\n async teardown(): Promise<void> {\n if (this._toreDown) return;\n this._toreDown = true;\n this.disarmPlanDialogAutoAccept();\n this.unsubInput?.();\n this.unsubInput = null;\n this.unsubResize?.();\n this.unsubResize = null;\n if (this.abortHandler) {\n this.options.abortController?.signal.removeEventListener(\"abort\", this.abortHandler);\n this.abortHandler = null;\n }\n try {\n this.pty?.kill();\n } catch {\n /* pty already exited */\n }\n this.pty = null;\n this.tailer?.close();\n this.tailer = null;\n if (this.socket) {\n await this.socket.close();\n this.socket = null;\n }\n for (const toolServer of this.toolServers) {\n try {\n await toolServer.close();\n } catch {\n /* already closed */\n }\n }\n this.toolServers = [];\n this.mcpConfigPath = null;\n this.queue.close();\n if (this.tempDir) {\n await rm(this.tempDir, { recursive: true, force: true });\n this.tempDir = \"\";\n }\n }\n\n /**\n * Serve the harness's in-process tools to the spawned CLI over loopback HTTP\n * (handlers run in THIS process against the live task-token connection) and\n * record the `--mcp-config` path for spawn(). No-op when the harness was\n * constructed without tools (e.g. SDK-only callers).\n */\n private async setupToolServers(): Promise<void> {\n const { servers, mcpConfigPath } = await startToolServers(\n this.options.mcpServers ?? {},\n this.tempDir,\n );\n this.toolServers = servers;\n this.mcpConfigPath = mcpConfigPath;\n }\n\n private async spawn(settingsPath: string, socketPath: string): Promise<void> {\n const args = buildSpawnArgs({\n resume: this.resume,\n sessionId: this.options.sessionId,\n model: this.options.model,\n permissionMode: this.options.permissionMode,\n settingsPath,\n ...(this.options.appendSystemPrompt\n ? { appendSystemPrompt: this.options.appendSystemPrompt }\n : {}),\n // Only this config: ignore the user's ~/.claude.json / project .mcp.json\n // so the agent's tool set is deterministic (and a stale personal conveyor\n // server doesn't load).\n ...(this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath, strictMcpConfig: true } : {}),\n });\n const spawn = await loadPtySpawn();\n const pty = spawn(resolveClaudeBinary(), args, {\n name: \"xterm-color\",\n cols: this.cols,\n rows: this.rows,\n cwd: this.options.cwd,\n env: inheritedEnv(socketPath),\n });\n // Mirror raw terminal output to the relay (and on to the S5 terminal). The\n // first chunk creates the server-side scrollback ring — which is what makes\n // the Connected-TUI terminal appear, i.e. this doubles as the attach\n // handshake. Stdout is intentionally NOT turned into HarnessEvents.\n pty.onData((data) => {\n this.bridge?.sendOutput(data, { cols: this.cols, rows: this.rows });\n // Retain a bounded tail so finalizeOnExit can explain an early exit.\n this.recentOutput = (this.recentOutput + data).slice(-MAX_DIAGNOSTIC_OUTPUT);\n });\n pty.onExit((event) => {\n void this.finalizeOnExit(event.exitCode);\n });\n this.pty = pty;\n }\n\n private deliverPrompt(text: string): void {\n this.writeStdin(buildPromptBytes(text, this.options.promptDelivery));\n }\n\n private async feedPrompt(): Promise<void> {\n if (typeof this.prompt === \"string\") {\n this.deliverPrompt(this.prompt);\n return;\n }\n for await (const message of this.prompt) {\n const content = message.message.content;\n const text = typeof content === \"string\" ? content : JSON.stringify(content);\n this.deliverPrompt(text);\n }\n }\n\n private handleProgress(progress: ToolProgress): void {\n this.queue.push({\n type: \"tool_progress\",\n ...(progress.tool_name === undefined ? {} : { tool_name: progress.tool_name }),\n ...(progress.elapsed_time_seconds === undefined\n ? {}\n : { elapsed_time_seconds: progress.elapsed_time_seconds }),\n });\n }\n\n /**\n * PreToolUse request from the hook helper → the runner's `canUseTool`\n * verdict. Only ExitPlanMode is matched by the settings hook, so this is\n * the Conveyor plan-exit gate (validation + identification trigger run\n * inside the callback). Absent callback → allow (mirrors the SDK default).\n */\n private async handlePreToolUse(request: PreToolUseRequest): Promise<PreToolUseVerdict> {\n const canUseTool = this.options.canUseTool;\n let verdict: PreToolUseVerdict;\n if (canUseTool) {\n const result = await canUseTool(request.tool_name, request.tool_input);\n verdict =\n result.behavior === \"allow\"\n ? { decision: \"allow\" }\n : { decision: \"deny\", reason: result.message };\n } else {\n verdict = { decision: \"allow\" };\n }\n if (\n verdict.decision === \"allow\" &&\n request.tool_name === \"ExitPlanMode\" &&\n this.options.planDialogAutoAccept\n ) {\n this.armPlanDialogAutoAccept();\n }\n return verdict;\n }\n\n /**\n * A hook \"allow\" does not bypass the CLI's plan-approval dialog — it renders\n * right after the verdict. Press Enter (default: \"Yes, auto-accept edits\")\n * until the turn resumes; the next transcript record only lands once the\n * dialog is answered, so it doubles as the stop signal.\n */\n private armPlanDialogAutoAccept(): void {\n if (this.planApprovalTimer) return;\n this.pendingPlanApproval = true;\n let presses = 0;\n const press = (): void => {\n if (this._toreDown || !this.pendingPlanApproval || presses >= 6) {\n this.disarmPlanDialogAutoAccept();\n return;\n }\n presses++;\n this.writeStdin(\"\\r\");\n };\n this.planApprovalTimer = setInterval(press, 1500);\n setTimeout(press, 700);\n }\n\n private disarmPlanDialogAutoAccept(): void {\n this.pendingPlanApproval = false;\n if (this.planApprovalTimer) {\n clearInterval(this.planApprovalTimer);\n this.planApprovalTimer = null;\n }\n }\n\n private handleTranscriptEvent(event: HarnessEvent): void {\n // Any new transcript record after an allowed ExitPlanMode means the plan\n // dialog was answered — stop pressing Enter.\n if (this.pendingPlanApproval) this.disarmPlanDialogAutoAccept();\n this.queue.push(event);\n if (event.type === \"result\") {\n this.sawResult = true;\n for (const listener of this.idleListeners) listener();\n // A transcript `result` marks end-of-query. Close the stream so the\n // generator completes — mirroring the SDK harness's per-query model.\n // Interactive `claude` does not exit after a result, so we must not wait\n // for process exit to terminate the event stream.\n this.queue.close();\n }\n }\n\n private async finalizeOnExit(exitCode: number): Promise<void> {\n // A teardown-driven kill (stop/softStop/abort) fires this exit handler, but\n // the turn is already discarded — pushing a synthetic error result here\n // would be spurious and would race teardown()'s queue.close().\n if (this._toreDown) return;\n if (this.tailer) {\n this.tailer.close();\n await this.tailer.flush();\n }\n if (!this.sawResult) {\n // Fold the swallowed terminal tail into the error so a bare exit (missing\n // `claude` binary, auth stop, unknown flag) is diagnosable from the logs\n // rather than only from the live S5 terminal.\n this.queue.push({\n type: \"result\",\n subtype: \"error\",\n errors: buildExitErrors(exitCode, this.recentOutput, resolveClaudeBinary()),\n });\n }\n for (const listener of this.exitListeners) listener(exitCode);\n this.queue.close();\n }\n}\n","/**\n * AsyncEventQueue — bridges callback-style producers (transcript tailer, hook\n * socket) into a single async-generator consumer. Producers call `push`;\n * the consumer drains via the async generator returned by `drain()`.\n */\nexport class AsyncEventQueue<T> {\n private readonly items: T[] = [];\n private closed = false;\n private wake: (() => void) | null = null;\n\n push(item: T): void {\n if (this.closed) return;\n this.items.push(item);\n this.signal();\n }\n\n close(): void {\n this.closed = true;\n this.signal();\n }\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n private signal(): void {\n const wake = this.wake;\n if (wake) {\n this.wake = null;\n wake();\n }\n }\n\n async *drain(): AsyncGenerator<T, void> {\n while (!this.closed || this.items.length > 0) {\n if (this.items.length > 0) {\n const item = this.items.shift();\n if (item !== undefined) yield item;\n continue;\n }\n await new Promise<void>((resolve) => {\n this.wake = resolve;\n });\n }\n }\n}\n","/**\n * Unix-domain socket server that receives hook envelopes from the hook helper:\n *\n * - PostToolUse progress envelopes (fire-and-forget) → `tool_progress` events\n * - PreToolUse requests (`kind: \"pre_tool_use\"`) → request/response: the\n * handler's verdict is written back on the SAME connection, correlated by\n * `id`, so the helper can print a permissionDecision for the CLI.\n *\n * Only newline-delimited JSON envelopes are parsed, and malformed lines are\n * dropped — raw PTY stdout is never parsed here.\n */\n\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport { unlink } from \"node:fs/promises\";\n\nexport interface ToolProgress {\n tool_name?: string;\n elapsed_time_seconds?: number;\n}\n\nexport interface PreToolUseRequest {\n id: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n}\n\nexport interface PreToolUseVerdict {\n decision: \"allow\" | \"deny\";\n reason?: string;\n}\n\nexport type HookEnvelope =\n | { kind: \"progress\"; progress: ToolProgress }\n | { kind: \"pre_tool_use\"; request: PreToolUseRequest };\n\nexport function parseEnvelope(line: string): HookEnvelope | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n\n if (record.kind === \"pre_tool_use\") {\n if (typeof record.id !== \"string\" || typeof record.tool_name !== \"string\") return null;\n const toolInput =\n typeof record.tool_input === \"object\" && record.tool_input !== null\n ? (record.tool_input as Record<string, unknown>)\n : {};\n return {\n kind: \"pre_tool_use\",\n request: { id: record.id, tool_name: record.tool_name, tool_input: toolInput },\n };\n }\n\n // Legacy progress envelope: any line without a `kind` field.\n const result: ToolProgress = {};\n if (typeof record.tool_name === \"string\") result.tool_name = record.tool_name;\n if (typeof record.elapsed_time_seconds === \"number\") {\n result.elapsed_time_seconds = record.elapsed_time_seconds;\n }\n return { kind: \"progress\", progress: result };\n}\n\nexport class HookSocketServer {\n private server: Server | null = null;\n private _closed = false;\n\n constructor(\n public readonly socketPath: string,\n private readonly onProgress: (progress: ToolProgress) => void,\n private readonly onPreToolUse?: (request: PreToolUseRequest) => Promise<PreToolUseVerdict>,\n ) {}\n\n async listen(): Promise<void> {\n await unlink(this.socketPath).catch(() => undefined);\n await new Promise<void>((resolve, reject) => {\n const server = createServer((socket) => this.handleConnection(socket));\n server.on(\"error\", reject);\n server.listen(this.socketPath, () => {\n resolve();\n });\n this.server = server;\n });\n }\n\n get isClosed(): boolean {\n return this._closed;\n }\n\n async close(): Promise<void> {\n if (this._closed) return;\n this._closed = true;\n const server = this.server;\n this.server = null;\n if (server) {\n await new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n });\n }\n await unlink(this.socketPath).catch(() => undefined);\n }\n\n private handleConnection(socket: Socket): void {\n // Swallow connection-level errors (e.g. ECONNRESET when the hook helper is\n // killed mid-write during teardown). Without an \"error\" listener Node\n // treats it as unhandled and crashes the process.\n socket.on(\"error\", () => undefined);\n // Per-connection buffer: concurrent hook helpers each get their own\n // accumulation so interleaved chunks from separate connections can't\n // corrupt one another's lines.\n let buffer = \"\";\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString(\"utf8\");\n let index = buffer.indexOf(\"\\n\");\n while (index >= 0) {\n const line = buffer.slice(0, index);\n buffer = buffer.slice(index + 1);\n this.dispatch(line, socket);\n index = buffer.indexOf(\"\\n\");\n }\n });\n }\n\n private dispatch(line: string, socket: Socket): void {\n const envelope = parseEnvelope(line);\n if (!envelope) return;\n if (envelope.kind === \"progress\") {\n this.onProgress(envelope.progress);\n return;\n }\n void this.respondToPreToolUse(envelope.request, socket);\n }\n\n private async respondToPreToolUse(request: PreToolUseRequest, socket: Socket): Promise<void> {\n let verdict: PreToolUseVerdict;\n try {\n verdict = this.onPreToolUse\n ? await this.onPreToolUse(request)\n : { decision: \"allow\" as const };\n } catch (err) {\n verdict = {\n decision: \"deny\",\n reason: `Conveyor validation failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`,\n };\n }\n try {\n socket.write(`${JSON.stringify({ id: request.id, ...verdict })}\\n`);\n } catch {\n /* helper hung up — its own timeout fallback covers this */\n }\n }\n}\n","/**\n * Tails a Claude Code transcript JSONL file, emitting a `HarnessEvent` for\n * each newly-appended record. Polls on a short interval because the file is\n * written by a separate process (the `claude` CLI under the PTY).\n *\n * All reads are serialized through a promise chain so the polling interval\n * and the final `flush()` on exit can never read overlapping byte ranges.\n */\n\nimport { open } from \"node:fs/promises\";\nimport type { HarnessEvent } from \"../types.js\";\nimport { mapTranscriptRecord } from \"./record-mapper.js\";\n\nconst POLL_INTERVAL_MS = 25;\n\nexport class JsonlTailer {\n private offset = 0;\n private buffer = \"\";\n private timer: ReturnType<typeof setInterval> | null = null;\n private chain: Promise<void> = Promise.resolve();\n private _closed = false;\n\n constructor(\n private readonly path: string,\n private readonly onEvent: (event: HarnessEvent) => void,\n ) {}\n\n /**\n * Begin tailing. Pass `fromOffset` to skip bytes already present when the\n * tail starts (used on resume so the prior session's records aren't replayed).\n */\n start(fromOffset = 0): void {\n if (this.timer) return;\n this.offset = fromOffset;\n this.timer = setInterval(() => {\n if (this._closed) return;\n void this.enqueueRead();\n }, POLL_INTERVAL_MS);\n }\n\n close(): void {\n this._closed = true;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Read any remaining bytes and flush a trailing line without a newline. */\n async flush(): Promise<void> {\n await this.enqueueRead();\n if (this.buffer.length > 0) {\n this.emitLine(this.buffer);\n this.buffer = \"\";\n }\n }\n\n private enqueueRead(): Promise<void> {\n this.chain = this.chain.then(() => this.readOnce());\n return this.chain;\n }\n\n private async readOnce(): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>> | null = null;\n try {\n handle = await open(this.path, \"r\");\n const stats = await handle.stat();\n if (stats.size <= this.offset) return;\n const length = stats.size - this.offset;\n const buf = Buffer.alloc(length);\n await handle.read(buf, 0, length, this.offset);\n this.offset = stats.size;\n this.consume(buf.toString(\"utf8\"));\n } catch {\n /* transient read error (e.g. file not yet created); retry on next poll */\n } finally {\n if (handle) await handle.close();\n }\n }\n\n private consume(chunk: string): void {\n this.buffer += chunk;\n let index = this.buffer.indexOf(\"\\n\");\n while (index >= 0) {\n const line = this.buffer.slice(0, index);\n this.buffer = this.buffer.slice(index + 1);\n this.emitLine(line);\n index = this.buffer.indexOf(\"\\n\");\n }\n }\n\n private emitLine(line: string): void {\n const trimmed = line.trim();\n if (trimmed.length === 0) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return;\n }\n const event = mapTranscriptRecord(parsed);\n if (event) this.onEvent(event);\n }\n}\n","/**\n * Pure transforms from Claude Code transcript JSONL records into\n * harness-neutral `HarnessEvent`s. This is the parity core: the events it\n * produces must match what `ClaudeCodeHarness` yields for the same logical\n * messages (see `helpers/mock-sdk.ts`).\n *\n * Transcript files are a trusted on-disk artifact written by the `claude`\n * CLI, so parsing their lines is allowed — unlike raw PTY stdout, which is\n * never parsed.\n */\n\nimport type {\n HarnessEvent,\n HarnessAssistantEvent,\n HarnessContentBlock,\n HarnessResultEvent,\n HarnessSystemInitEvent,\n} from \"../types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Custom predicate so narrowing yields `unknown[]`, not `any[]`. */\nfunction isUnknownArray(value: unknown): value is unknown[] {\n return Array.isArray(value);\n}\n\nfunction stringField(record: Record<string, unknown>, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const value = record[key];\n if (typeof value === \"string\") return value;\n }\n return undefined;\n}\n\nfunction numberField(record: Record<string, unknown>, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const value = record[key];\n if (typeof value === \"number\") return value;\n }\n return undefined;\n}\n\nfunction mapSystem(record: Record<string, unknown>): HarnessSystemInitEvent | null {\n if (record.subtype !== \"init\") return null;\n const event: HarnessSystemInitEvent = {\n type: \"system\",\n subtype: \"init\",\n model: stringField(record, \"model\") ?? \"\",\n };\n const sessionId = stringField(record, \"session_id\", \"sessionId\");\n if (sessionId !== undefined) event.session_id = sessionId;\n return event;\n}\n\ntype AssistantUsage = NonNullable<HarnessAssistantEvent[\"message\"][\"usage\"]>;\n\nfunction mapUsage(message: Record<string, unknown>): AssistantUsage | undefined {\n const usage = message.usage;\n if (!isRecord(usage)) return undefined;\n const result: AssistantUsage = {};\n const input = numberField(usage, \"input_tokens\");\n const cacheRead = numberField(usage, \"cache_read_input_tokens\");\n const cacheCreation = numberField(usage, \"cache_creation_input_tokens\");\n if (input !== undefined) result.input_tokens = input;\n if (cacheRead !== undefined) result.cache_read_input_tokens = cacheRead;\n if (cacheCreation !== undefined) result.cache_creation_input_tokens = cacheCreation;\n return result;\n}\n\nfunction mapContentBlock(raw: unknown): HarnessContentBlock | null {\n if (!isRecord(raw)) return null;\n const type = stringField(raw, \"type\");\n if (type === undefined) return null;\n const block: HarnessContentBlock = { type };\n const text = stringField(raw, \"text\");\n if (text !== undefined) block.text = text;\n const name = stringField(raw, \"name\");\n if (name !== undefined) block.name = name;\n const id = stringField(raw, \"id\");\n if (id !== undefined) block.id = id;\n if (\"input\" in raw) block.input = raw.input;\n return block;\n}\n\nfunction mapAssistant(record: Record<string, unknown>): HarnessAssistantEvent | null {\n const message = record.message;\n if (!isRecord(message)) return null;\n const rawContent: unknown[] = isUnknownArray(message.content) ? message.content : [];\n const content: HarnessContentBlock[] = [];\n for (const item of rawContent) {\n const block = mapContentBlock(item);\n if (block) content.push(block);\n }\n const result: HarnessAssistantEvent = {\n type: \"assistant\",\n message: { role: \"assistant\", content },\n };\n const usage = mapUsage(message);\n if (usage !== undefined) result.message.usage = usage;\n return result;\n}\n\nfunction mapResultSuccess(record: Record<string, unknown>): HarnessResultEvent {\n const event: HarnessResultEvent = {\n type: \"result\",\n subtype: \"success\",\n result: stringField(record, \"result\") ?? \"\",\n total_cost_usd: numberField(record, \"total_cost_usd\", \"totalCostUsd\") ?? 0,\n };\n const modelUsage = record.modelUsage;\n if (isRecord(modelUsage)) event.modelUsage = modelUsage;\n const sessionId = stringField(record, \"session_id\", \"sessionId\");\n if (sessionId !== undefined) event.sessionId = sessionId;\n return event;\n}\n\nfunction mapResultError(record: Record<string, unknown>): HarnessResultEvent {\n const rawErrors = isUnknownArray(record.errors) ? record.errors : [];\n const errors = rawErrors.filter((e): e is string => typeof e === \"string\");\n const event: HarnessResultEvent = { type: \"result\", subtype: \"error\", errors };\n const sessionId = stringField(record, \"session_id\", \"sessionId\");\n if (sessionId !== undefined) event.sessionId = sessionId;\n return event;\n}\n\nfunction mapResult(record: Record<string, unknown>): HarnessResultEvent | null {\n if (record.subtype === \"success\") return mapResultSuccess(record);\n if (record.subtype === \"error\") return mapResultError(record);\n return null;\n}\n\nexport function mapTranscriptRecord(raw: unknown): HarnessEvent | null {\n if (!isRecord(raw)) return null;\n switch (raw.type) {\n case \"system\":\n return mapSystem(raw);\n case \"assistant\":\n return mapAssistant(raw);\n case \"result\":\n return mapResult(raw);\n default:\n return null;\n }\n}\n","/**\n * The `claude` CLI process boundary: resolve the binary, build its argv, and\n * interpret its exit when it dies before producing a result. Kept pure so it\n * can be unit-tested without a real spawn.\n */\n\nexport interface SpawnArgsInput {\n resume?: string;\n sessionId?: string;\n model: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n settingsPath: string;\n /** Extra system-prompt text appended via `--append-system-prompt`. */\n appendSystemPrompt?: string;\n mcpConfigPath?: string;\n /** When set with mcpConfigPath, the CLI uses ONLY that config and ignores the\n * user's `~/.claude.json` / project `.mcp.json` servers. */\n strictMcpConfig?: boolean;\n}\n\nexport function resolveClaudeBinary(): string {\n return process.env.CONVEYOR_CLAUDE_BIN ?? \"claude\";\n}\n\nexport function buildSpawnArgs(input: SpawnArgsInput): string[] {\n const args: string[] = [];\n if (input.resume) {\n args.push(\"--resume\", input.resume);\n } else if (input.sessionId) {\n args.push(\"--session-id\", input.sessionId);\n }\n args.push(\"--model\", input.model);\n if (input.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-skip-permissions\");\n } else {\n args.push(\"--permission-mode\", \"plan\");\n }\n args.push(\"--settings\", input.settingsPath);\n if (input.appendSystemPrompt) {\n args.push(\"--append-system-prompt\", input.appendSystemPrompt);\n }\n if (input.mcpConfigPath) {\n args.push(\"--mcp-config\", input.mcpConfigPath);\n if (input.strictMcpConfig) {\n args.push(\"--strict-mcp-config\");\n }\n }\n return args;\n}\n\n// ─── Exit diagnostics ──────────────────────────────────────────────────────\n// The PTY harness never turns raw `claude` stdout/stderr into HarnessEvents —\n// it relays them to the S5 terminal instead. So when `claude` dies before\n// emitting a transcript result, the captured agent logs show only the generic\n// \"claude exited (code N) without a result\"; the real reason (a missing binary,\n// an auth/onboarding stop, an unknown CLI flag) scrolled past in the live\n// terminal only. These helpers fold a bounded, ANSI-stripped tail of that\n// scrollback back into the error so the failure is self-describing in the logs.\n\n// ESC (0x1B) built via fromCharCode so no control char appears as a source\n// literal (keeps the no-control-regex lint rule happy). Matches CSI sequences\n// (ESC [ ... final-byte) — the bulk of TUI escape noise; any stray ESC bytes\n// from other sequences are removed by the control-char pass below.\nconst ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\\\[[0-9;?]*[ -/]*[@-~]`, \"g\");\n\n/**\n * Strip ANSI escapes + terminal control noise and collapse to the last few\n * readable lines, capped at `maxChars`. Returns \"\" when nothing printable\n * remains (e.g. the process produced no output at all).\n */\nexport function cleanTerminalOutput(raw: string, maxChars = 1200): string {\n const noAnsi = raw.replace(ANSI_CSI, \"\");\n // Drop C0 control chars (and DEL) except tab; fold CR into newline.\n let out = \"\";\n for (const ch of noAnsi) {\n const code = ch.charCodeAt(0);\n if (ch === \"\\r\" || ch === \"\\n\") out += \"\\n\";\n else if (ch === \"\\t\") out += ch;\n else if (code < 0x20 || code === 0x7f) continue;\n else out += ch;\n }\n const lines = out\n .split(\"\\n\")\n .map((line) => line.trimEnd())\n .filter((line) => line.trim().length > 0);\n const text = lines.join(\"\\n\").trim();\n return text.length > maxChars ? `…${text.slice(-maxChars)}` : text;\n}\n\n/**\n * True when the terminal tail shows node-pty failed to exec the target binary\n * (missing `claude` CLI on PATH). node-pty prints `execvp(3) failed.: No such\n * file or directory` to the pty and the child exits with code 1.\n */\nexport function isMissingBinaryFailure(tail: string): boolean {\n return /execvp\\(\\d+\\) failed|no such file or directory|command not found/i.test(tail);\n}\n\n/**\n * Build the `errors[]` for a `claude` process that exited before emitting a\n * transcript result. The first entry is kept byte-for-byte stable so existing\n * consumers/log scrapers that key off it keep working; richer context is\n * appended after it.\n */\nexport function buildExitErrors(exitCode: number, rawOutput: string, binary: string): string[] {\n const errors = [`claude exited (code ${exitCode}) without a result`];\n const tail = cleanTerminalOutput(rawOutput);\n if (isMissingBinaryFailure(tail)) {\n errors.push(\n `The \\`${binary}\\` CLI could not be started — it is not installed or not on PATH. ` +\n `Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) ` +\n `or set CONVEYOR_CLAUDE_BIN to its absolute path.`,\n );\n }\n if (tail) {\n errors.push(`Last terminal output before exit:\\n${tail}`);\n }\n return errors;\n}\n","/**\n * Claude Code config-dir paths and per-run settings/hook materialization.\n *\n * The PTY harness writes a throwaway `settings.json` (passed via\n * `claude --settings`) wiring two hooks that relay envelopes to a Unix socket:\n * - PostToolUse (all tools): fire-and-forget tool-progress envelopes.\n * - PreToolUse (ExitPlanMode only): request/response — the runner's\n * `canUseTool` verdict (plan/property validation, identification trigger)\n * is returned as a permissionDecision so the plan-mode exit is gated by\n * Conveyor instead of the interactive dialog.\n * Writing a per-run file avoids clobbering the user's real `~/.claude/settings.json`.\n */\n\nimport { mkdir, writeFile, chmod } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function claudeConfigHome(): string {\n return process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), \".claude\");\n}\n\nexport function projectSlug(cwd: string): string {\n return cwd.replace(/\\//g, \"-\");\n}\n\nexport function sessionTranscriptPath(cwd: string, sessionId: string): string {\n return join(claudeConfigHome(), \"projects\", projectSlug(cwd), `${sessionId}.jsonl`);\n}\n\n/** Where the CLI writes plan-mode plan files (CC 2.x): `<config-home>/plans`. */\nexport function configHomePlansDir(): string {\n return join(claudeConfigHome(), \"plans\");\n}\n\n/**\n * Per-tool allow rules approximating \"allow everything\". The CLI rejects a\n * bare `\"*\"` rule (and an unanchored `\"mcp__conveyor\"`) as invalid — that\n * parks the TUI on a blocking \"Settings Warning\" screen at boot — so the\n * built-in tools are named explicitly and the Conveyor MCP tools matched by\n * the only valid glob form (`mcp__<server>__*`). The built-in names matter\n * for the acceptEdits continuation after an auto-mode plan exit (Bash/MCP\n * would otherwise prompt with nobody at the terminal); plan-permission spawns\n * are read-only regardless, and build-capable spawns pass\n * `--dangerously-skip-permissions` and never consult this list.\n */\nexport const ALLOW_RULES = [\n \"Bash\",\n \"BashOutput\",\n \"Edit\",\n \"Write\",\n \"Read\",\n \"Glob\",\n \"Grep\",\n \"WebFetch\",\n \"WebSearch\",\n \"NotebookEdit\",\n \"Task\",\n \"TodoWrite\",\n \"ToolSearch\",\n \"KillShell\",\n \"SlashCommand\",\n \"Skill\",\n \"mcp__conveyor__*\",\n];\n\n/**\n * CJS hook helper. Reads the hook payload from stdin and branches on\n * `hook_event_name`:\n *\n * - PostToolUse: relays a fire-and-forget tool-progress envelope and prints\n * `{continue:true}` so the CLI proceeds. A short fallback timer guarantees\n * exit even if the socket is unavailable.\n * - PreToolUse: writes a `pre_tool_use` request and waits for the runner's\n * correlated verdict on the same connection, printing a\n * permissionDecision allow/deny. On socket failure or timeout it fails\n * CLOSED (deny) — the model retries after fixing the reported problem,\n * and the runner's denial-escalation backstop bounds pathological loops.\n */\nconst HOOK_HELPER_SOURCE = `\"use strict\";\nconst net = require(\"node:net\");\nconst crypto = require(\"node:crypto\");\n\nconst PRE_TOOL_USE_TIMEOUT_MS = 110000;\n\nlet raw = \"\";\nprocess.stdin.setEncoding(\"utf8\");\nprocess.stdin.on(\"data\", (chunk) => {\n raw += chunk;\n});\nprocess.stdin.on(\"end\", () => {\n let payload = {};\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") payload = parsed;\n } catch {\n payload = {};\n }\n if (payload.hook_event_name === \"PreToolUse\") {\n preToolUse(payload);\n } else {\n relayProgress(typeof payload.tool_name === \"string\" ? payload.tool_name : \"\");\n }\n});\n\nfunction relayProgress(toolName) {\n const socketPath = process.env.CONVEYOR_HOOK_SOCKET;\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n process.stdout.write(JSON.stringify({ continue: true }));\n process.exit(0);\n };\n const fallback = setTimeout(finish, 250);\n if (!socketPath) {\n clearTimeout(fallback);\n finish();\n return;\n }\n const client = net.connect(socketPath, () => {\n // PostToolUse does not supply tool duration, so elapsed_time_seconds is\n // omitted rather than reported as a misleading 0 (the field is optional).\n const line = JSON.stringify({ tool_name: toolName }) + \"\\\\n\";\n client.write(line, () => {\n client.end();\n });\n });\n client.on(\"close\", () => {\n clearTimeout(fallback);\n finish();\n });\n client.on(\"error\", () => {\n clearTimeout(fallback);\n finish();\n });\n}\n\nfunction preToolUse(payload) {\n const socketPath = process.env.CONVEYOR_HOOK_SOCKET;\n let done = false;\n const respond = (decision, reason) => {\n if (done) return;\n done = true;\n process.stdout.write(\n JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: decision,\n ...(reason ? { permissionDecisionReason: reason } : {}),\n },\n }),\n );\n process.exit(0);\n };\n const denyUnavailable = () =>\n respond(\n \"deny\",\n \"Conveyor validation is unavailable right now. Wait a moment and call the tool again.\",\n );\n if (!socketPath) {\n denyUnavailable();\n return;\n }\n const id = crypto.randomBytes(8).toString(\"hex\");\n const timer = setTimeout(denyUnavailable, PRE_TOOL_USE_TIMEOUT_MS);\n const client = net.connect(socketPath, () => {\n client.write(\n JSON.stringify({\n kind: \"pre_tool_use\",\n id,\n tool_name: typeof payload.tool_name === \"string\" ? payload.tool_name : \"\",\n tool_input:\n payload.tool_input && typeof payload.tool_input === \"object\" ? payload.tool_input : {},\n }) + \"\\\\n\",\n );\n });\n let buffer = \"\";\n client.on(\"data\", (chunk) => {\n buffer += chunk.toString(\"utf8\");\n let index = buffer.indexOf(\"\\\\n\");\n while (index >= 0) {\n const line = buffer.slice(0, index);\n buffer = buffer.slice(index + 1);\n try {\n const verdict = JSON.parse(line);\n if (verdict && verdict.id === id) {\n clearTimeout(timer);\n client.end();\n respond(verdict.decision === \"allow\" ? \"allow\" : \"deny\", verdict.reason);\n return;\n }\n } catch {\n /* keep scanning */\n }\n index = buffer.indexOf(\"\\\\n\");\n }\n });\n client.on(\"error\", () => {\n clearTimeout(timer);\n denyUnavailable();\n });\n client.on(\"close\", () => {\n clearTimeout(timer);\n denyUnavailable();\n });\n}\n`;\n\nexport interface HookSettingsResult {\n settingsPath: string;\n helperPath: string;\n}\n\nexport async function writeHookSettings(dir: string): Promise<HookSettingsResult> {\n const helperPath = join(dir, \"hook-helper.cjs\");\n const settingsPath = join(dir, \"settings.json\");\n await mkdir(dir, { recursive: true });\n await writeFile(helperPath, HOOK_HELPER_SOURCE, \"utf8\");\n await chmod(helperPath, 0o755);\n const settings = {\n // Auto-approve tool calls so the interactive (PTY) agent never stops to\n // prompt the viewer. This only suppresses per-tool approval prompts — it\n // does NOT relax the planning gate: in discovery/plan mode the spawn passes\n // `--permission-mode plan`, which keeps the agent read-only (no edits/commits\n // until it exits plan mode) regardless of this allow-list. In build mode the\n // spawn already bypasses prompts via `--dangerously-skip-permissions`.\n // NOTE: a bare \"*\" rule is INVALID (the CLI rejects it and parks the TUI on\n // a Settings Warning dialog at boot) — hence the explicit ALLOW_RULES list.\n permissions: {\n allow: ALLOW_RULES,\n },\n hooks: {\n PreToolUse: [\n {\n matcher: \"ExitPlanMode\",\n hooks: [{ type: \"command\", command: `node ${JSON.stringify(helperPath)}`, timeout: 120 }],\n },\n ],\n PostToolUse: [\n {\n matcher: \"*\",\n hooks: [{ type: \"command\", command: `node ${JSON.stringify(helperPath)}` }],\n },\n ],\n },\n };\n await writeFile(settingsPath, JSON.stringify(settings, null, 2), \"utf8\");\n return { settingsPath, helperPath };\n}\n","/**\n * In-process MCP server that exposes the agent's Conveyor tools to the spawned\n * `claude` CLI over Streamable HTTP on loopback.\n *\n * The tool handlers must run IN the agent process — they call\n * `connection.call(...)` on the live task-token `AgentConnection`. So instead of\n * spawning a separate stdio MCP child and bridging calls back over a socket, we\n * serve the same in-process handlers over an HTTP MCP transport bound to\n * `127.0.0.1`, and point `claude` at it via `--mcp-config` (type: \"http\"). This\n * is the wiring the PTY harness deferred (\"S4\"); the SDK harness already gets\n * these tools in-process.\n *\n * Security: bound to loopback only (never 0.0.0.0) and gated by a random\n * per-run bearer token that `claude` echoes from the generated mcp-config.\n */\n\nimport { createServer, type Server as HttpServer } from \"node:http\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport type { HarnessMcpServer, HarnessToolDefinition } from \"../types.js\";\nimport { PtyMcpServer } from \"./mcp-server.js\";\n\nconst LOOPBACK = \"127.0.0.1\";\n\nexport interface PtyToolServerHandle {\n /** `http://127.0.0.1:<port>/mcp` — the URL written into the mcp-config. */\n url: string;\n /** Bearer token required on every request (defense-in-depth over loopback). */\n token: string;\n}\n\nexport class PtyToolServer {\n private http: HttpServer | null = null;\n private transport: StreamableHTTPServerTransport | null = null;\n private mcp: McpServer | null = null;\n private readonly token = randomBytes(24).toString(\"base64url\");\n\n constructor(\n private readonly name: string,\n private readonly tools: HarnessToolDefinition[],\n ) {}\n\n async start(): Promise<PtyToolServerHandle> {\n const mcp = new McpServer({ name: this.name, version: \"1.0.0\" });\n // The SDK's `tool()` is heavily generic over the zod shape; our tool defs are\n // intentionally loose-typed (`schema: z.ZodRawShape`, handler returns MCP\n // content). Cast the registrar to a simpler shape to bridge the boundary\n // without widening to `any` — the SDK still validates input against `schema`.\n const register = mcp.tool.bind(mcp) as unknown as (\n name: string,\n description: string,\n schema: unknown,\n cb: (args: unknown) => unknown,\n ) => void;\n for (const tool of this.tools) {\n register(tool.name, tool.description, tool.schema, (args) => tool.handler(args));\n }\n\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomBytes(16).toString(\"hex\"),\n enableJsonResponse: true,\n });\n await mcp.connect(transport);\n\n const server = createServer((req, res) => {\n void this.handle(req, res, transport);\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, LOOPBACK, () => resolve());\n });\n\n const address = server.address();\n const port = address && typeof address === \"object\" ? address.port : 0;\n\n this.http = server;\n this.transport = transport;\n this.mcp = mcp;\n return { url: `http://${LOOPBACK}:${port}/mcp`, token: this.token };\n }\n\n private async handle(\n req: IncomingMessage,\n res: ServerResponse,\n transport: StreamableHTTPServerTransport,\n ): Promise<void> {\n if (req.headers.authorization !== `Bearer ${this.token}`) {\n res.writeHead(401).end();\n return;\n }\n try {\n await transport.handleRequest(req, res);\n } catch {\n if (res.headersSent) res.end();\n else res.writeHead(500).end();\n }\n }\n\n async close(): Promise<void> {\n try {\n await this.transport?.close();\n } catch {\n /* already closed */\n }\n this.transport = null;\n try {\n await this.mcp?.close();\n } catch {\n /* already closed */\n }\n this.mcp = null;\n const http = this.http;\n this.http = null;\n if (http) {\n await new Promise<void>((resolve) => {\n http.close(() => resolve());\n });\n }\n }\n}\n\nexport interface ToolServersResult {\n servers: PtyToolServer[];\n /** Path to the written `--mcp-config`, or null when there were no tools. */\n mcpConfigPath: string | null;\n}\n\n/**\n * Start one loopback HTTP MCP server per harness tool group (in practice just\n * \"conveyor\") and write the `--mcp-config` the spawned `claude` consumes. No-op\n * (null config) when the harness was constructed without tools.\n */\nexport async function startToolServers(\n mcpServers: Record<string, HarnessMcpServer>,\n tempDir: string,\n): Promise<ToolServersResult> {\n const servers: PtyToolServer[] = [];\n const config: Record<string, { type: \"http\"; url: string; headers: Record<string, string> }> = {};\n for (const [name, handle] of Object.entries(mcpServers)) {\n const tools = handle instanceof PtyMcpServer ? handle.tools : [];\n if (tools.length === 0) continue;\n const server = new PtyToolServer(name, tools);\n const { url, token } = await server.start();\n servers.push(server);\n config[name] = { type: \"http\", url, headers: { Authorization: `Bearer ${token}` } };\n }\n if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null };\n const mcpConfigPath = join(tempDir, \"mcp-config.json\");\n await writeFile(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), \"utf8\");\n return { servers, mcpConfigPath };\n}\n","import type { HarnessToolDefinition } from \"../types.js\";\n\ntype ToolResult = Awaited<ReturnType<HarnessToolDefinition[\"handler\"]>>;\n\n/**\n * In-process MCP server handle for the PTY harness.\n *\n * S1 materializes tools as a directly-invokable in-process handle: callers\n * resolve a tool by name and invoke its handler. Wiring these tools to the\n * `claude` CLI over a live stdio MCP transport (via `--mcp-config`) is\n * deferred to S4 — the spawn layer already threads `mcpConfigPath` through\n * for that purpose.\n */\nexport class PtyMcpServer {\n constructor(\n public readonly name: string,\n public readonly tools: HarnessToolDefinition[],\n ) {}\n\n getTool(name: string): HarnessToolDefinition | undefined {\n return this.tools.find((tool) => tool.name === name);\n }\n\n invokeTool(name: string, input: unknown): Promise<ToolResult> {\n const tool = this.getTool(name);\n if (!tool) return Promise.reject(new Error(`Unknown tool: ${name}`));\n return tool.handler(input);\n }\n}\n","/**\n * Synthesizes `<configHome>/.credentials.json` from CLAUDE_CODE_OAUTH_TOKEN so\n * the interactive `claude` TUI starts authenticated in Conveyor-managed cloud\n * environments. The interactive TUI does NOT read CLAUDE_CODE_OAUTH_TOKEN —\n * that env var is honored only in headless/CI runs — so without a credentials\n * file a fresh pod's Connected TUI lands on the login-method picker.\n *\n * Safety policy: a credentials file whose `claudeAiOauth` carries a\n * refreshToken was written by a real interactive `/login` (the OAuth code flow\n * always issues one) and is never touched. Our synthesized file never has one —\n * that absence IS the \"written by Conveyor\" marker. If live verification ever\n * disproves that assumption, the fallback hardening is a sidecar marker file\n * (e.g. `<configHome>/.conveyor-credentials.json` holding a hash of the token\n * we wrote) — noted here so the next person doesn't reinvent the policy.\n *\n * Decision core is pure (planCredentialsWrite) so the overwrite policy can be\n * unit-tested without fs; the effectful wrappers never throw — a GCS-FUSE\n * hiccup must not kill PtySession.start().\n */\n\nimport { chmod, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { claudeConfigHome } from \"./settings.js\";\n\n// `claude setup-token` tokens are valid for ~1 year. The exact expiry is\n// unknowable from the token itself; claim a far-future expiresAt so the CLI\n// never decides the credential is expired and attempts a refresh it cannot\n// perform (no refreshToken). The file is re-synthesized on every spawn, so the\n// window slides.\nconst SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1000;\n// Rewrite our own file when it has less than this much runway left — a cheap\n// staleness guard for a long-lived file on the persistent user-home mount.\nconst REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport function claudeCredentialsPath(): string {\n return join(claudeConfigHome(), \".credentials.json\");\n}\n\n/**\n * Only act inside Conveyor-managed cloud environments (GKE claudespace pods\n * export CLAUDESPACE_NAME in entrypoint.sh; GitHub Codespaces set\n * CODESPACE_NAME/CODESPACES) — never touch a developer's real ~/.claude.\n */\nexport function isConveyorCloudEnv(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);\n}\n\nexport interface CredentialsWriteInput {\n isCloud: boolean;\n /** CLAUDE_CODE_OAUTH_TOKEN from the environment. */\n token: string | undefined;\n /** Current file contents; null when the file is missing. */\n existingRaw: string | null;\n now: number;\n}\n\nexport type CredentialsPlan =\n | { action: \"skip\"; reason: \"not-cloud\" | \"no-token\" | \"foreign-credentials\" | \"current\" }\n | { action: \"write\"; contents: string };\n\ninterface ParsedOauth {\n accessToken: unknown;\n refreshToken: unknown;\n expiresAt: unknown;\n}\n\nfunction parseClaudeAiOauth(raw: string | null): ParsedOauth | null {\n if (!raw || raw.trim() === \"\") return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const oauth = (parsed as Record<string, unknown>).claudeAiOauth;\n if (typeof oauth !== \"object\" || oauth === null) return null;\n const record = oauth as Record<string, unknown>;\n return {\n accessToken: record.accessToken,\n refreshToken: record.refreshToken,\n expiresAt: record.expiresAt,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * The synthesized shape is the empirical-iteration point: if a live pod's TUI\n * still shows the login picker, adjust scopes/subscriptionType HERE and\n * re-verify on a Claudespace diagnostic card.\n */\nfunction buildSynthesizedCredentials(token: string, now: number): string {\n return JSON.stringify({\n claudeAiOauth: {\n accessToken: token,\n expiresAt: now + SYNTH_TOKEN_TTL_MS,\n scopes: [\"user:inference\", \"user:profile\"],\n subscriptionType: \"max\",\n },\n });\n}\n\n/** Pure overwrite-policy core — see the decision table in the tests. */\nexport function planCredentialsWrite(input: CredentialsWriteInput): CredentialsPlan {\n if (!input.isCloud) return { action: \"skip\", reason: \"not-cloud\" };\n if (!input.token) return { action: \"skip\", reason: \"no-token\" };\n\n const contents = buildSynthesizedCredentials(input.token, input.now);\n const existing = parseClaudeAiOauth(input.existingRaw);\n // Missing, empty, unparseable, or shapeless file — ours to (re)write.\n if (!existing) return { action: \"write\", contents };\n // A refreshToken means a genuine interactive /login — never clobber.\n if (typeof existing.refreshToken === \"string\" && existing.refreshToken.length > 0) {\n return { action: \"skip\", reason: \"foreign-credentials\" };\n }\n const fresh =\n existing.accessToken === input.token &&\n typeof existing.expiresAt === \"number\" &&\n existing.expiresAt > input.now + REFRESH_SKEW_MS;\n // Skip when current to avoid write churn on the GCS-FUSE mount.\n if (fresh) return { action: \"skip\", reason: \"current\" };\n return { action: \"write\", contents };\n}\n\nasync function readRaw(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n\n/**\n * Materialize CLAUDE_CODE_OAUTH_TOKEN as a credentials file for the\n * interactive TUI. Best-effort: logs and returns on any failure.\n */\nexport async function ensureClaudeCredentials(env: NodeJS.ProcessEnv = process.env): Promise<void> {\n try {\n const path = claudeCredentialsPath();\n const plan = planCredentialsWrite({\n isCloud: isConveyorCloudEnv(env),\n token: env.CLAUDE_CODE_OAUTH_TOKEN,\n existingRaw: await readRaw(path),\n now: Date.now(),\n });\n if (plan.action === \"skip\") return;\n await mkdir(claudeConfigHome(), { recursive: true });\n await writeFile(path, plan.contents, { encoding: \"utf8\", mode: 0o600 });\n // The GCS-FUSE CSI mount (file-mode=700) may reject chmod; it already\n // enforces owner-only access, so this is belt-and-braces for local disks.\n await chmod(path, 0o600).catch(() => {});\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[conveyor-agent] claude credentials sync failed: ${message}\\n`);\n }\n}\n\n/**\n * Path of the CLI's main config. Lives at $HOME/.claude.json normally; when\n * CLAUDE_CONFIG_DIR is set the CLI relocates it under that directory.\n */\nexport function claudeJsonPath(): string {\n const configDir = process.env.CLAUDE_CONFIG_DIR;\n return configDir ? join(configDir, \".claude.json\") : join(homedir(), \".claude.json\");\n}\n\n/**\n * Pure decision core for the onboarding + trust seed. Returns the contents to\n * write, or null when no write is needed.\n *\n * Unlike the entrypoint's only-if-empty seeding, this MERGES into an existing\n * config: the CLI rewrites .claude.json during a first-run wizard (cache keys,\n * firstStartTime, …) WITHOUT setting hasCompletedOnboarding until the wizard\n * finishes. On a persistent user-home (GCS-FUSE) an abandoned wizard therefore\n * leaves a config that re-triggers onboarding on every subsequent pod — seen\n * live: credentials valid (`claude auth status` → loggedIn:true) yet the TUI\n * parked at the theme picker. All CLI-owned keys are preserved.\n *\n * `trustCwd` pre-accepts the folder-trust dialog for the workspace the TUI is\n * about to run in (`projects[\"<cwd>\"].hasTrustDialogAccepted`) — the dialog is\n * the third first-run gate after theme and login, also seen parking a live\n * pod. Trusting the Conveyor-managed repo clone is definitionally correct in\n * this environment. The theme default is only applied when creating a fresh\n * config: the CLI drops unused keys on rewrite, and re-adding theme on every\n * spawn would cause a guaranteed write per spawn for zero behavior change\n * (the theme picker is gated on hasCompletedOnboarding, not theme).\n */\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/** Parse the existing .claude.json; corrupt/non-object input becomes a fresh\n * config (the CLI treats unparseable files as fatal, so replacing is a repair). */\nfunction parseClaudeJson(existingRaw: string | null): Record<string, unknown> {\n if (!existingRaw || existingRaw.trim() === \"\") return {};\n try {\n return asRecord(JSON.parse(existingRaw));\n } catch {\n return {};\n }\n}\n\n/** Pre-accept the folder-trust dialog for `trustCwd`. Returns true if changed. */\nfunction seedWorkspaceTrust(config: Record<string, unknown>, trustCwd: string): boolean {\n const projects = asRecord(config.projects);\n const entry = asRecord(projects[trustCwd]);\n if (entry.hasTrustDialogAccepted === true) return false;\n entry.hasTrustDialogAccepted = true;\n projects[trustCwd] = entry;\n config.projects = projects;\n return true;\n}\n\nexport function planClaudeJsonSeed(existingRaw: string | null, trustCwd?: string): string | null {\n const config = parseClaudeJson(existingRaw);\n const isFresh = Object.keys(config).length === 0;\n let changed = false;\n if (config.hasCompletedOnboarding !== true) {\n config.hasCompletedOnboarding = true;\n changed = true;\n }\n if (isFresh && typeof config.theme !== \"string\") {\n config.theme = \"dark\";\n changed = true;\n }\n if (trustCwd && seedWorkspaceTrust(config, trustCwd)) {\n changed = true;\n }\n return changed ? JSON.stringify(config) : null;\n}\n\n/**\n * Suppress the CLI's first-run wizard (and, when `trustCwd` is given, the\n * folder-trust dialog for that workspace) in Conveyor cloud envs so the\n * spawned TUI boots straight into the REPL. Runs regardless of token presence\n * — with credentials the gates are pure friction; without them the\n * post-onboarding login screen is strictly better than the full wizard.\n * Best-effort, never throws.\n */\nexport async function ensureClaudeOnboarding(\n env: NodeJS.ProcessEnv = process.env,\n trustCwd?: string,\n): Promise<void> {\n try {\n if (!isConveyorCloudEnv(env)) return;\n const path = claudeJsonPath();\n const contents = planClaudeJsonSeed(await readRaw(path), trustCwd);\n if (contents === null) return;\n await writeFile(path, contents, \"utf8\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[conveyor-agent] claude onboarding seed failed: ${message}\\n`);\n }\n}\n\n/**\n * Delete the credentials file only when it is ours (no refreshToken). Used\n * when a runtime key update switches the agent to ANTHROPIC_API_KEY — stale\n * subscription credentials must not keep authenticating the next TUI spawn.\n */\nexport async function removeConveyorCredentials(\n env: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n try {\n if (!isConveyorCloudEnv(env)) return;\n const path = claudeCredentialsPath();\n const existing = parseClaudeAiOauth(await readRaw(path));\n if (existing && typeof existing.refreshToken === \"string\" && existing.refreshToken.length > 0) {\n return;\n }\n await rm(path, { force: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[conveyor-agent] claude credentials removal failed: ${message}\\n`);\n }\n}\n","/**\n * PtyHarness — drives the `claude` CLI under a pseudo-terminal behind the\n * generic `AgentHarness` interface. Selected at runtime via\n * `createHarness(\"pty\")`. As of conveyor-agent v8 this is the default for the\n * task chat path; set `CONVEYOR_HARNESS=sdk` to roll back to the SDK harness.\n */\n\nimport type {\n AgentHarness,\n HarnessEvent,\n HarnessQueryOptions,\n HarnessToolDefinition,\n HarnessMcpServer,\n HarnessUserMessage,\n PtyBridge,\n} from \"../types.js\";\nimport { PtySession } from \"./session.js\";\nimport { PtyMcpServer } from \"./mcp-server.js\";\nimport { ensureClaudeCredentials, ensureClaudeOnboarding } from \"./credentials.js\";\n\nexport { PtySession, PtyMcpServer };\n\nexport class PtyHarness implements AgentHarness {\n /**\n * `bridge` relays raw terminal I/O to/from the S2 server (and on to the S5\n * terminal). It is undefined for SDK-only callers and PTY runs that never\n * attach a relay; the session simply discards stdout in that case.\n */\n constructor(private readonly bridge?: PtyBridge) {}\n\n async *executeQuery(opts: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void> {\n const session = new PtySession(\n opts.prompt,\n opts.options,\n opts.resume ?? opts.options.resume,\n this.bridge,\n );\n // The interactive TUI ignores CLAUDE_CODE_OAUTH_TOKEN (headless-only); in\n // Conveyor cloud envs, materialize it as `<configHome>/.credentials.json`\n // so the CLI starts authenticated instead of at the login-method picker,\n // and complete the onboarding flags + workspace trust in .claude.json so\n // neither an abandoned first-run wizard on the persistent user-home nor\n // the folder-trust dialog can park the TUI.\n // Every PTY spawn (fresh, follow-up, resume, retry, mode restart) flows\n // through here — this is the single PtySession construction site.\n await ensureClaudeCredentials();\n await ensureClaudeOnboarding(process.env, opts.options.cwd);\n await session.start();\n try {\n for await (const event of session.events()) {\n yield event;\n }\n } finally {\n await session.teardown();\n }\n }\n\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer {\n return new PtyMcpServer(config.name, config.tools);\n }\n}\n","export { defineTool } from \"./types.js\";\n\nexport type {\n AgentHarness,\n HarnessEvent,\n HarnessSystemEvent,\n HarnessSystemInitEvent,\n HarnessCompactBoundaryEvent,\n HarnessTaskStartedEvent,\n HarnessTaskProgressEvent,\n HarnessAssistantEvent,\n HarnessContentBlock,\n HarnessResultEvent,\n HarnessResultSuccessEvent,\n HarnessResultErrorEvent,\n HarnessRateLimitEvent,\n HarnessToolProgressEvent,\n HarnessUserMessage,\n HarnessToolDefinition,\n HarnessToolAnnotations,\n HarnessMcpServer,\n HarnessQueryOptions,\n HarnessHookInput,\n HarnessHookOutput,\n HarnessPostToolUseHook,\n PtyBridge,\n} from \"./types.js\";\n\nexport { ClaudeCodeHarness } from \"./claude-code/index.js\";\nexport { PtyHarness } from \"./pty/index.js\";\n\nimport { ClaudeCodeHarness } from \"./claude-code/index.js\";\nimport { PtyHarness } from \"./pty/index.js\";\nimport type { AgentHarness, PtyBridge } from \"./types.js\";\n\nexport type HarnessKind = \"sdk\" | \"pty\";\n\n/**\n * Pick the harness implementation. `ptyBridge` is only consumed by the PTY\n * harness (to stream stdout to the S2 relay and receive keystrokes/resize);\n * the SDK harness ignores it.\n */\nexport function createHarness(kind: HarnessKind = \"sdk\", ptyBridge?: PtyBridge): AgentHarness {\n return kind === \"pty\" ? new PtyHarness(ptyBridge) : new ClaudeCodeHarness();\n}\n","/** Minimal structured logger for conveyor-agent (writes to stderr). */\nexport function createServiceLogger(service: string) {\n const prefix = `[conveyor-agent:${service}]`;\n return {\n info(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ${message}${extra}\\n`);\n },\n warn(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} WARN ${message}${extra}\\n`);\n },\n error(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ERROR ${message}${extra}\\n`);\n },\n };\n}\n"],"mappings":"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","names":["mkdir","join","createServer","writeFile","join","tool","tool","createServer","join","writeFile","isRecord","join","mkdir","chmod","mkdir","rm","writeFile","homedir","join","join","mkdir","writeFile","chmod","homedir","rm"]}
|