orchestrating 0.1.3 → 0.1.5
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/bin/orch +109 -101
- package/package.json +1 -1
package/bin/orch
CHANGED
|
@@ -151,7 +151,6 @@ const ADAPTERS = {
|
|
|
151
151
|
buildArgs(prompt, flags) {
|
|
152
152
|
const args = [
|
|
153
153
|
"--output-format", "stream-json",
|
|
154
|
-
"--input-format", "stream-json",
|
|
155
154
|
"--verbose",
|
|
156
155
|
];
|
|
157
156
|
if (flags.continue) {
|
|
@@ -307,6 +306,7 @@ if (!adapter) {
|
|
|
307
306
|
|
|
308
307
|
let child;
|
|
309
308
|
let handleServerMessage; // set per-mode
|
|
309
|
+
let exitRequested = false;
|
|
310
310
|
|
|
311
311
|
if (adapter) {
|
|
312
312
|
// ======== STRUCTURED MODE (Claude Code JSON streaming) ========
|
|
@@ -328,84 +328,101 @@ if (adapter) {
|
|
|
328
328
|
process.exit(1);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
child
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
331
|
+
// Confirmation-type tools — these need "yes" response, not permission grants
|
|
332
|
+
const CONFIRMATION_TOOLS = new Set(["ExitPlanMode", "EnterPlanMode"]);
|
|
333
|
+
let childRunning = false;
|
|
334
|
+
|
|
335
|
+
// Build clean env for child processes (no nesting detection, no leaked keys)
|
|
336
|
+
const childEnv = (() => {
|
|
337
|
+
const e = { ...process.env };
|
|
338
|
+
delete e.CLAUDECODE;
|
|
339
|
+
delete e.ANTHROPIC_API_KEY;
|
|
340
|
+
return e;
|
|
341
|
+
})();
|
|
342
|
+
|
|
343
|
+
// Spawn a claude process and wire up output handling
|
|
344
|
+
function spawnClaude(claudeArgs) {
|
|
345
|
+
process.stderr.write(`${DIM}[orch] Spawning: ${command} ${claudeArgs.join(" ")}${RESET}\n`);
|
|
346
|
+
const proc = spawn(command, claudeArgs, {
|
|
347
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
348
|
+
cwd: process.cwd(),
|
|
349
|
+
env: childEnv,
|
|
350
|
+
});
|
|
351
|
+
childRunning = true;
|
|
352
|
+
child = proc;
|
|
353
|
+
process.stderr.write(`${DIM}[orch] Child PID: ${proc.pid}${RESET}\n`);
|
|
354
|
+
|
|
355
|
+
// Parse NDJSON from stdout line-by-line
|
|
356
|
+
const rl = readline.createInterface({ input: proc.stdout });
|
|
357
|
+
let permissionsSent = false;
|
|
358
|
+
let lineCount = 0;
|
|
359
|
+
|
|
360
|
+
rl.on("line", (line) => {
|
|
361
|
+
lineCount++;
|
|
362
|
+
process.stderr.write(`${DIM}[orch] stdout line #${lineCount}: ${line.slice(0, 120)}${RESET}\n`);
|
|
363
|
+
if (!permissionsSent) {
|
|
364
|
+
permissionsSent = true;
|
|
365
|
+
broadcastPermissions();
|
|
366
|
+
}
|
|
367
|
+
if (!line.trim()) return;
|
|
368
|
+
let raw;
|
|
369
|
+
try {
|
|
370
|
+
raw = JSON.parse(line);
|
|
371
|
+
} catch {
|
|
372
|
+
process.stderr.write(`${DIM}[orch] JSON parse failed for line${RESET}\n`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
348
375
|
|
|
349
|
-
|
|
350
|
-
|
|
376
|
+
// Normalize and relay each event
|
|
377
|
+
const events = normalizeClaudeEvent(raw);
|
|
378
|
+
process.stderr.write(`${DIM}[orch] Normalized ${events.length} events (type=${raw.type})${RESET}\n`);
|
|
379
|
+
for (const event of events) {
|
|
380
|
+
printLocalEvent(event);
|
|
381
|
+
sendToServer({ type: "agent_event", sessionId, event });
|
|
382
|
+
|
|
383
|
+
// Yolo mode: auto-approve permission denials immediately
|
|
384
|
+
if (yoloMode && event.kind === "permission_denied") {
|
|
385
|
+
process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
|
|
386
|
+
approvePermission(event.toolName, "session");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
351
390
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
broadcastPermissions();
|
|
356
|
-
}
|
|
357
|
-
if (!line.trim()) return;
|
|
358
|
-
let raw;
|
|
359
|
-
try {
|
|
360
|
-
raw = JSON.parse(line);
|
|
361
|
-
} catch {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
391
|
+
proc.stdout.on("end", () => {
|
|
392
|
+
process.stderr.write(`${DIM}[orch] stdout stream ended (${lineCount} lines total)${RESET}\n`);
|
|
393
|
+
});
|
|
364
394
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
sendToServer({ type: "agent_event", sessionId, event });
|
|
395
|
+
proc.stderr.on("data", (buf) => {
|
|
396
|
+
process.stderr.write(`${DIM}[orch] child stderr: ${RESET}`);
|
|
397
|
+
process.stderr.write(buf);
|
|
398
|
+
});
|
|
370
399
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
400
|
+
proc.on("exit", (code, signal) => {
|
|
401
|
+
childRunning = false;
|
|
402
|
+
process.stderr.write(`${DIM}[orch] Child exited: code=${code} signal=${signal}${RESET}\n`);
|
|
403
|
+
const exitCode = code ?? 0;
|
|
404
|
+
if (exitCode !== 0 || exitRequested) {
|
|
405
|
+
sendToServer({ type: "exit", sessionId, exitCode });
|
|
406
|
+
setTimeout(() => process.exit(exitCode), 200);
|
|
407
|
+
} else {
|
|
408
|
+
sendToServer({
|
|
409
|
+
type: "agent_event", sessionId,
|
|
410
|
+
event: { kind: "status", status: "idle" },
|
|
411
|
+
});
|
|
375
412
|
}
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
child.stderr.on("data", (buf) => {
|
|
380
|
-
process.stderr.write(buf);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
child.on("exit", (code) => {
|
|
384
|
-
const exitCode = code ?? 0;
|
|
385
|
-
sendToServer({ type: "exit", sessionId, exitCode });
|
|
386
|
-
setTimeout(() => process.exit(exitCode), 200);
|
|
387
|
-
});
|
|
413
|
+
});
|
|
388
414
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
415
|
+
proc.on("error", (err) => {
|
|
416
|
+
childRunning = false;
|
|
417
|
+
process.stderr.write(`${RED}[orch] Spawn error: ${err.message}${RESET}\n`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
});
|
|
393
420
|
|
|
394
|
-
|
|
395
|
-
|
|
421
|
+
return proc;
|
|
422
|
+
}
|
|
396
423
|
|
|
397
424
|
// Auto-approve a tool permission (used by yolo mode and manual approval)
|
|
398
425
|
function approvePermission(toolName, scope) {
|
|
399
|
-
// For confirmation prompts, just send "yes" — no settings change needed
|
|
400
|
-
if (CONFIRMATION_TOOLS.has(toolName)) {
|
|
401
|
-
const confirmMsg = JSON.stringify({
|
|
402
|
-
type: "user",
|
|
403
|
-
message: { role: "user", content: "yes" },
|
|
404
|
-
});
|
|
405
|
-
child.stdin.write(confirmMsg + "\n");
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
426
|
let permEntry = toolName;
|
|
410
427
|
const mcpMatch = toolName.match(/^(mcp__[^_]+(?:__[^_]+)?)__/);
|
|
411
428
|
if (mcpMatch) {
|
|
@@ -442,41 +459,28 @@ if (adapter) {
|
|
|
442
459
|
}
|
|
443
460
|
|
|
444
461
|
broadcastPermissions();
|
|
445
|
-
|
|
446
|
-
const retryMsg = JSON.stringify({
|
|
447
|
-
type: "user",
|
|
448
|
-
message: { role: "user", content: `Permission granted for ${toolName}. Please retry.` },
|
|
449
|
-
});
|
|
450
|
-
child.stdin.write(retryMsg + "\n");
|
|
451
462
|
}
|
|
452
463
|
|
|
453
|
-
//
|
|
454
|
-
|
|
464
|
+
// Start the initial claude process
|
|
465
|
+
const initialArgs = adapter.buildArgs(prompt, adapterFlags);
|
|
466
|
+
if (yoloMode) {
|
|
467
|
+
initialArgs.push("--dangerously-skip-permissions");
|
|
468
|
+
}
|
|
469
|
+
spawnClaude(initialArgs);
|
|
455
470
|
|
|
456
471
|
handleServerMessage = (msg) => {
|
|
457
472
|
if (msg.type === "agent_input" && (msg.text || msg.images)) {
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
if (msg.text) {
|
|
470
|
-
content.push({ type: "text", text: msg.text });
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
content = msg.text;
|
|
473
|
+
// Follow-up from dashboard — spawn new claude with -c (continue)
|
|
474
|
+
if (childRunning) {
|
|
475
|
+
// Child still running, queue or ignore
|
|
476
|
+
process.stderr.write(`${DIM}[orch] Ignoring input — claude is still running${RESET}\n`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const followUpArgs = ["--output-format", "stream-json", "--verbose", "-c", "-p", msg.text || "continue"];
|
|
480
|
+
if (yoloMode) {
|
|
481
|
+
followUpArgs.push("--dangerously-skip-permissions");
|
|
474
482
|
}
|
|
475
|
-
|
|
476
|
-
type: "user",
|
|
477
|
-
message: { role: "user", content },
|
|
478
|
-
});
|
|
479
|
-
child.stdin.write(inputMsg + "\n");
|
|
483
|
+
spawnClaude(followUpArgs);
|
|
480
484
|
} else if (msg.type === "agent_permission" && msg.tool && msg.action === "allow") {
|
|
481
485
|
const scope = msg.scope || "session";
|
|
482
486
|
approvePermission(msg.tool, scope);
|
|
@@ -766,6 +770,8 @@ function connectWs() {
|
|
|
766
770
|
ws = new WebSocket(serverUrl);
|
|
767
771
|
|
|
768
772
|
ws.on("open", () => {
|
|
773
|
+
process.stderr.write(`${DIM}[orch] WS connected to ${serverUrl}${RESET}\n`);
|
|
774
|
+
process.stderr.write(`${DIM}[orch] Token: ${authToken ? authToken.slice(0, 20) + "..." : "(none)"}${RESET}\n`);
|
|
769
775
|
ws.send(JSON.stringify({
|
|
770
776
|
type: "register",
|
|
771
777
|
token: authToken,
|
|
@@ -810,7 +816,8 @@ function connectWs() {
|
|
|
810
816
|
} catch {}
|
|
811
817
|
});
|
|
812
818
|
|
|
813
|
-
ws.on("close", () => {
|
|
819
|
+
ws.on("close", (code, reason) => {
|
|
820
|
+
process.stderr.write(`${DIM}[orch] WS closed: code=${code} reason=${reason}${RESET}\n`);
|
|
814
821
|
wsReady = false;
|
|
815
822
|
ws = null;
|
|
816
823
|
if (!authFailed) {
|
|
@@ -818,8 +825,8 @@ function connectWs() {
|
|
|
818
825
|
}
|
|
819
826
|
});
|
|
820
827
|
|
|
821
|
-
ws.on("error", () => {
|
|
822
|
-
|
|
828
|
+
ws.on("error", (err) => {
|
|
829
|
+
process.stderr.write(`${DIM}[orch] WS error: ${err.message}${RESET}\n`);
|
|
823
830
|
});
|
|
824
831
|
}
|
|
825
832
|
|
|
@@ -855,6 +862,7 @@ function cleanup() {
|
|
|
855
862
|
|
|
856
863
|
process.on("SIGINT", () => {
|
|
857
864
|
if (adapter) {
|
|
865
|
+
exitRequested = true;
|
|
858
866
|
child.kill("SIGINT");
|
|
859
867
|
} else {
|
|
860
868
|
child.stdin.write("\x03");
|