@rallycry/conveyor-agent 8.4.1 → 8.5.1
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-VDH55LTT.js → chunk-6B545CHM.js} +352 -40
- package/dist/chunk-6B545CHM.js.map +1 -0
- package/dist/{chunk-B4QHEMMV.js → chunk-WZCO64YR.js} +344 -72
- package/dist/chunk-WZCO64YR.js.map +1 -0
- package/dist/cli.js +23 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +53 -3
- package/dist/index.js +2 -2
- package/dist/{tag-audit-handler-SWVMCAJH.js → tag-audit-handler-3IFB7YDV.js} +2 -2
- package/dist/{task-audit-handler-CZ2WWJFO.js → task-audit-handler-U5Q52YT2.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-3IFB7YDV.js.map} +0 -0
- /package/dist/{task-audit-handler-CZ2WWJFO.js.map → task-audit-handler-U5Q52YT2.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: "*",
|
|
@@ -520,6 +656,58 @@ async function writeHookSettings(dir) {
|
|
|
520
656
|
return { settingsPath, helperPath };
|
|
521
657
|
}
|
|
522
658
|
|
|
659
|
+
// src/harness/pty/output-coalescer.ts
|
|
660
|
+
var DEFAULT_FLUSH_MS = 40;
|
|
661
|
+
var DEFAULT_MAX_BUFFER_CHARS = 48 * 1024;
|
|
662
|
+
var PtyOutputCoalescer = class {
|
|
663
|
+
constructor(sink, getDims, flushMs = DEFAULT_FLUSH_MS, maxBufferChars = DEFAULT_MAX_BUFFER_CHARS) {
|
|
664
|
+
this.sink = sink;
|
|
665
|
+
this.getDims = getDims;
|
|
666
|
+
this.flushMs = flushMs;
|
|
667
|
+
this.maxBufferChars = maxBufferChars;
|
|
668
|
+
}
|
|
669
|
+
sink;
|
|
670
|
+
getDims;
|
|
671
|
+
flushMs;
|
|
672
|
+
maxBufferChars;
|
|
673
|
+
buffer = "";
|
|
674
|
+
timer = null;
|
|
675
|
+
disposed = false;
|
|
676
|
+
/** Buffer a chunk; flushes synchronously when the size threshold is hit. */
|
|
677
|
+
write(data) {
|
|
678
|
+
if (this.disposed || data === "") return;
|
|
679
|
+
this.buffer += data;
|
|
680
|
+
if (this.buffer.length >= this.maxBufferChars) {
|
|
681
|
+
this.flush();
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (!this.timer) {
|
|
685
|
+
this.timer = setTimeout(() => {
|
|
686
|
+
this.flush();
|
|
687
|
+
}, this.flushMs);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/** Ship the buffered bytes now (no-op when empty). Dims are read at flush
|
|
691
|
+
* time so a mid-buffer resize stamps the frame with the dims it will render
|
|
692
|
+
* under. */
|
|
693
|
+
flush() {
|
|
694
|
+
if (this.timer) {
|
|
695
|
+
clearTimeout(this.timer);
|
|
696
|
+
this.timer = null;
|
|
697
|
+
}
|
|
698
|
+
if (this.buffer === "") return;
|
|
699
|
+
const data = this.buffer;
|
|
700
|
+
this.buffer = "";
|
|
701
|
+
this.sink(data, this.getDims());
|
|
702
|
+
}
|
|
703
|
+
/** Final flush, then drop any future writes (session torn down). Idempotent. */
|
|
704
|
+
dispose() {
|
|
705
|
+
if (this.disposed) return;
|
|
706
|
+
this.flush();
|
|
707
|
+
this.disposed = true;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
523
711
|
// src/harness/pty/tool-server.ts
|
|
524
712
|
import { createServer as createServer2 } from "http";
|
|
525
713
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
@@ -659,9 +847,16 @@ function inheritedEnv(socketPath) {
|
|
|
659
847
|
env.CONVEYOR_HOOK_SOCKET = socketPath;
|
|
660
848
|
return env;
|
|
661
849
|
}
|
|
662
|
-
function buildPromptBytes(text
|
|
663
|
-
|
|
664
|
-
|
|
850
|
+
function buildPromptBytes(text) {
|
|
851
|
+
return `\x1B[200~${text}\x1B[201~`;
|
|
852
|
+
}
|
|
853
|
+
var SUBMIT_SETTLE_MS = 300;
|
|
854
|
+
var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
|
|
855
|
+
var SUBMIT_NUDGE_MAX_PRESSES = 5;
|
|
856
|
+
function sleep(ms) {
|
|
857
|
+
return new Promise((resolve) => {
|
|
858
|
+
setTimeout(resolve, ms);
|
|
859
|
+
});
|
|
665
860
|
}
|
|
666
861
|
async function transcriptSize(path) {
|
|
667
862
|
try {
|
|
@@ -691,6 +886,7 @@ var PtySession = class {
|
|
|
691
886
|
// CLI exits before emitting a result (the bytes are otherwise relayed to S5
|
|
692
887
|
// and never become events). Trimmed to MAX_DIAGNOSTIC_OUTPUT on every write.
|
|
693
888
|
recentOutput = "";
|
|
889
|
+
coalescer = null;
|
|
694
890
|
_toreDown = false;
|
|
695
891
|
exitListeners = [];
|
|
696
892
|
idleListeners = [];
|
|
@@ -703,6 +899,12 @@ var PtySession = class {
|
|
|
703
899
|
// plus the path to the `--mcp-config` that points at them.
|
|
704
900
|
toolServers = [];
|
|
705
901
|
mcpConfigPath = null;
|
|
902
|
+
// Plan-dialog auto-accept state (see armPlanDialogAutoAccept).
|
|
903
|
+
pendingPlanApproval = false;
|
|
904
|
+
planApprovalTimer = null;
|
|
905
|
+
// Submit-nudge state (see armSubmitNudge).
|
|
906
|
+
pendingSubmitNudge = false;
|
|
907
|
+
submitNudgeTimer = null;
|
|
706
908
|
onIdle(listener) {
|
|
707
909
|
this.idleListeners.push(listener);
|
|
708
910
|
}
|
|
@@ -731,7 +933,11 @@ var PtySession = class {
|
|
|
731
933
|
try {
|
|
732
934
|
this.tempDir = await mkdtemp(join3(tmpdir(), "conveyor-pty-"));
|
|
733
935
|
const socketPath = join3(this.tempDir, "hook.sock");
|
|
734
|
-
this.socket = new HookSocketServer(
|
|
936
|
+
this.socket = new HookSocketServer(
|
|
937
|
+
socketPath,
|
|
938
|
+
(progress) => this.handleProgress(progress),
|
|
939
|
+
(request) => this.handlePreToolUse(request)
|
|
940
|
+
);
|
|
735
941
|
await this.socket.listen();
|
|
736
942
|
const { settingsPath } = await writeHookSettings(this.tempDir);
|
|
737
943
|
await this.setupToolServers();
|
|
@@ -777,6 +983,8 @@ var PtySession = class {
|
|
|
777
983
|
async teardown() {
|
|
778
984
|
if (this._toreDown) return;
|
|
779
985
|
this._toreDown = true;
|
|
986
|
+
this.disarmPlanDialogAutoAccept();
|
|
987
|
+
this.disarmSubmitNudge();
|
|
780
988
|
this.unsubInput?.();
|
|
781
989
|
this.unsubInput = null;
|
|
782
990
|
this.unsubResize?.();
|
|
@@ -790,6 +998,8 @@ var PtySession = class {
|
|
|
790
998
|
} catch {
|
|
791
999
|
}
|
|
792
1000
|
this.pty = null;
|
|
1001
|
+
this.coalescer?.dispose();
|
|
1002
|
+
this.coalescer = null;
|
|
793
1003
|
this.tailer?.close();
|
|
794
1004
|
this.tailer = null;
|
|
795
1005
|
if (this.socket) {
|
|
@@ -831,6 +1041,7 @@ var PtySession = class {
|
|
|
831
1041
|
model: this.options.model,
|
|
832
1042
|
permissionMode: this.options.permissionMode,
|
|
833
1043
|
settingsPath,
|
|
1044
|
+
...this.options.appendSystemPrompt ? { appendSystemPrompt: this.options.appendSystemPrompt } : {},
|
|
834
1045
|
// Only this config: ignore the user's ~/.claude.json / project .mcp.json
|
|
835
1046
|
// so the agent's tool set is deterministic (and a stale personal conveyor
|
|
836
1047
|
// server doesn't load).
|
|
@@ -844,8 +1055,17 @@ var PtySession = class {
|
|
|
844
1055
|
cwd: this.options.cwd,
|
|
845
1056
|
env: inheritedEnv(socketPath)
|
|
846
1057
|
});
|
|
1058
|
+
const bridge = this.bridge;
|
|
1059
|
+
if (bridge) {
|
|
1060
|
+
this.coalescer = new PtyOutputCoalescer(
|
|
1061
|
+
(data, dims) => {
|
|
1062
|
+
bridge.sendOutput(data, dims);
|
|
1063
|
+
},
|
|
1064
|
+
() => ({ cols: this.cols, rows: this.rows })
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
847
1067
|
pty.onData((data) => {
|
|
848
|
-
this.
|
|
1068
|
+
this.coalescer?.write(data);
|
|
849
1069
|
this.recentOutput = (this.recentOutput + data).slice(-MAX_DIAGNOSTIC_OUTPUT);
|
|
850
1070
|
});
|
|
851
1071
|
pty.onExit((event) => {
|
|
@@ -853,18 +1073,54 @@ var PtySession = class {
|
|
|
853
1073
|
});
|
|
854
1074
|
this.pty = pty;
|
|
855
1075
|
}
|
|
856
|
-
deliverPrompt(text) {
|
|
857
|
-
this.writeStdin(buildPromptBytes(text
|
|
1076
|
+
async deliverPrompt(text) {
|
|
1077
|
+
this.writeStdin(buildPromptBytes(text));
|
|
1078
|
+
if (this.options.promptDelivery === "prefill") return;
|
|
1079
|
+
await sleep(SUBMIT_SETTLE_MS);
|
|
1080
|
+
if (this._toreDown) return;
|
|
1081
|
+
this.writeStdin("\r");
|
|
1082
|
+
this.armSubmitNudge();
|
|
858
1083
|
}
|
|
859
1084
|
async feedPrompt() {
|
|
860
1085
|
if (typeof this.prompt === "string") {
|
|
861
|
-
this.deliverPrompt(this.prompt);
|
|
1086
|
+
await this.deliverPrompt(this.prompt);
|
|
862
1087
|
return;
|
|
863
1088
|
}
|
|
864
1089
|
for await (const message of this.prompt) {
|
|
865
1090
|
const content = message.message.content;
|
|
866
1091
|
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
867
|
-
this.deliverPrompt(text);
|
|
1092
|
+
await this.deliverPrompt(text);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Even as a separate write, the submitting Enter can race CLI startup and
|
|
1097
|
+
* be swallowed, leaving the pasted prompt parked in the input box with
|
|
1098
|
+
* nothing to un-park it (the 45s parked-TUI watchdog only reports status).
|
|
1099
|
+
* Until the first transcript record proves the turn started, re-press Enter
|
|
1100
|
+
* on a bounded interval. Enter on an empty or mid-turn input box is a no-op.
|
|
1101
|
+
* Deliberate trade-off (same as the plan-dialog auto-accept below): a press
|
|
1102
|
+
* can accept the default of an unexpected startup dialog — in a headless
|
|
1103
|
+
* pod that unblocks the run rather than parking it forever.
|
|
1104
|
+
*/
|
|
1105
|
+
armSubmitNudge() {
|
|
1106
|
+
if (this.submitNudgeTimer) return;
|
|
1107
|
+
this.pendingSubmitNudge = true;
|
|
1108
|
+
let presses = 0;
|
|
1109
|
+
const press = () => {
|
|
1110
|
+
if (this._toreDown || !this.pendingSubmitNudge || presses >= SUBMIT_NUDGE_MAX_PRESSES) {
|
|
1111
|
+
this.disarmSubmitNudge();
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
presses++;
|
|
1115
|
+
this.writeStdin("\r");
|
|
1116
|
+
};
|
|
1117
|
+
this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_INTERVAL_MS);
|
|
1118
|
+
}
|
|
1119
|
+
disarmSubmitNudge() {
|
|
1120
|
+
this.pendingSubmitNudge = false;
|
|
1121
|
+
if (this.submitNudgeTimer) {
|
|
1122
|
+
clearInterval(this.submitNudgeTimer);
|
|
1123
|
+
this.submitNudgeTimer = null;
|
|
868
1124
|
}
|
|
869
1125
|
}
|
|
870
1126
|
handleProgress(progress) {
|
|
@@ -874,7 +1130,57 @@ var PtySession = class {
|
|
|
874
1130
|
...progress.elapsed_time_seconds === void 0 ? {} : { elapsed_time_seconds: progress.elapsed_time_seconds }
|
|
875
1131
|
});
|
|
876
1132
|
}
|
|
1133
|
+
/**
|
|
1134
|
+
* PreToolUse request from the hook helper → the runner's `canUseTool`
|
|
1135
|
+
* verdict. Only ExitPlanMode is matched by the settings hook, so this is
|
|
1136
|
+
* the Conveyor plan-exit gate (validation + identification trigger run
|
|
1137
|
+
* inside the callback). Absent callback → allow (mirrors the SDK default).
|
|
1138
|
+
*/
|
|
1139
|
+
async handlePreToolUse(request) {
|
|
1140
|
+
const canUseTool = this.options.canUseTool;
|
|
1141
|
+
let verdict;
|
|
1142
|
+
if (canUseTool) {
|
|
1143
|
+
const result = await canUseTool(request.tool_name, request.tool_input);
|
|
1144
|
+
verdict = result.behavior === "allow" ? { decision: "allow" } : { decision: "deny", reason: result.message };
|
|
1145
|
+
} else {
|
|
1146
|
+
verdict = { decision: "allow" };
|
|
1147
|
+
}
|
|
1148
|
+
if (verdict.decision === "allow" && request.tool_name === "ExitPlanMode" && this.options.planDialogAutoAccept) {
|
|
1149
|
+
this.armPlanDialogAutoAccept();
|
|
1150
|
+
}
|
|
1151
|
+
return verdict;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* A hook "allow" does not bypass the CLI's plan-approval dialog — it renders
|
|
1155
|
+
* right after the verdict. Press Enter (default: "Yes, auto-accept edits")
|
|
1156
|
+
* until the turn resumes; the next transcript record only lands once the
|
|
1157
|
+
* dialog is answered, so it doubles as the stop signal.
|
|
1158
|
+
*/
|
|
1159
|
+
armPlanDialogAutoAccept() {
|
|
1160
|
+
if (this.planApprovalTimer) return;
|
|
1161
|
+
this.pendingPlanApproval = true;
|
|
1162
|
+
let presses = 0;
|
|
1163
|
+
const press = () => {
|
|
1164
|
+
if (this._toreDown || !this.pendingPlanApproval || presses >= 6) {
|
|
1165
|
+
this.disarmPlanDialogAutoAccept();
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
presses++;
|
|
1169
|
+
this.writeStdin("\r");
|
|
1170
|
+
};
|
|
1171
|
+
this.planApprovalTimer = setInterval(press, 1500);
|
|
1172
|
+
setTimeout(press, 700);
|
|
1173
|
+
}
|
|
1174
|
+
disarmPlanDialogAutoAccept() {
|
|
1175
|
+
this.pendingPlanApproval = false;
|
|
1176
|
+
if (this.planApprovalTimer) {
|
|
1177
|
+
clearInterval(this.planApprovalTimer);
|
|
1178
|
+
this.planApprovalTimer = null;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
877
1181
|
handleTranscriptEvent(event) {
|
|
1182
|
+
if (this.pendingPlanApproval) this.disarmPlanDialogAutoAccept();
|
|
1183
|
+
if (this.pendingSubmitNudge) this.disarmSubmitNudge();
|
|
878
1184
|
this.queue.push(event);
|
|
879
1185
|
if (event.type === "result") {
|
|
880
1186
|
this.sawResult = true;
|
|
@@ -883,6 +1189,7 @@ var PtySession = class {
|
|
|
883
1189
|
}
|
|
884
1190
|
}
|
|
885
1191
|
async finalizeOnExit(exitCode) {
|
|
1192
|
+
this.coalescer?.flush();
|
|
886
1193
|
if (this._toreDown) return;
|
|
887
1194
|
if (this.tailer) {
|
|
888
1195
|
this.tailer.close();
|
|
@@ -1011,6 +1318,10 @@ function planClaudeJsonSeed(existingRaw, trustCwd) {
|
|
|
1011
1318
|
config.hasCompletedOnboarding = true;
|
|
1012
1319
|
changed = true;
|
|
1013
1320
|
}
|
|
1321
|
+
if (config.bypassPermissionsModeAccepted !== true) {
|
|
1322
|
+
config.bypassPermissionsModeAccepted = true;
|
|
1323
|
+
changed = true;
|
|
1324
|
+
}
|
|
1014
1325
|
if (isFresh && typeof config.theme !== "string") {
|
|
1015
1326
|
config.theme = "dark";
|
|
1016
1327
|
changed = true;
|
|
@@ -1113,9 +1424,10 @@ function createServiceLogger(service) {
|
|
|
1113
1424
|
export {
|
|
1114
1425
|
defineTool,
|
|
1115
1426
|
sessionTranscriptPath,
|
|
1427
|
+
configHomePlansDir,
|
|
1116
1428
|
ensureClaudeCredentials,
|
|
1117
1429
|
removeConveyorCredentials,
|
|
1118
1430
|
createHarness,
|
|
1119
1431
|
createServiceLogger
|
|
1120
1432
|
};
|
|
1121
|
-
//# sourceMappingURL=chunk-
|
|
1433
|
+
//# sourceMappingURL=chunk-6B545CHM.js.map
|