orchestrating 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/bin/orch +90 -96
  2. 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,90 @@ if (adapter) {
328
328
  process.exit(1);
329
329
  }
330
330
 
331
- const claudeArgs = adapter.buildArgs(prompt, adapterFlags);
332
- if (yoloMode) {
333
- claudeArgs.push("--dangerously-skip-permissions");
334
- }
335
- child = spawn(command, claudeArgs, {
336
- stdio: ["pipe", "pipe", "pipe"],
337
- cwd: process.cwd(),
338
- env: (() => {
339
- const e = { ...process.env };
340
- delete e.CLAUDECODE;
341
- delete e.ANTHROPIC_API_KEY;
342
- return e;
343
- })(),
344
- });
345
-
346
- // Parse NDJSON from stdout line-by-line
347
- const rl = readline.createInterface({ input: child.stdout });
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
+ const proc = spawn(command, claudeArgs, {
346
+ stdio: ["pipe", "pipe", "pipe"],
347
+ cwd: process.cwd(),
348
+ env: childEnv,
349
+ });
350
+ childRunning = true;
351
+ child = proc;
348
352
 
349
- // Broadcast initial permission state once connected
350
- let permissionsSent = false;
353
+ // Parse NDJSON from stdout line-by-line
354
+ const rl = readline.createInterface({ input: proc.stdout });
355
+ let permissionsSent = false;
351
356
 
352
- rl.on("line", (line) => {
353
- if (!permissionsSent) {
354
- permissionsSent = true;
355
- broadcastPermissions();
356
- }
357
- if (!line.trim()) return;
358
- let raw;
359
- try {
360
- raw = JSON.parse(line);
361
- } catch {
362
- return;
363
- }
357
+ rl.on("line", (line) => {
358
+ if (!permissionsSent) {
359
+ permissionsSent = true;
360
+ broadcastPermissions();
361
+ }
362
+ if (!line.trim()) return;
363
+ let raw;
364
+ try {
365
+ raw = JSON.parse(line);
366
+ } catch {
367
+ return;
368
+ }
364
369
 
365
- // Normalize and relay each event
366
- const events = normalizeClaudeEvent(raw);
367
- for (const event of events) {
368
- printLocalEvent(event);
369
- sendToServer({ type: "agent_event", sessionId, event });
370
+ // Normalize and relay each event
371
+ const events = normalizeClaudeEvent(raw);
372
+ for (const event of events) {
373
+ printLocalEvent(event);
374
+ sendToServer({ type: "agent_event", sessionId, event });
370
375
 
371
- // Yolo mode: auto-approve permission denials immediately
372
- if (yoloMode && event.kind === "permission_denied") {
373
- process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
374
- approvePermission(event.toolName, "session");
376
+ // Yolo mode: auto-approve permission denials immediately
377
+ if (yoloMode && event.kind === "permission_denied") {
378
+ process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
379
+ approvePermission(event.toolName, "session");
380
+ }
375
381
  }
376
- }
377
- });
382
+ });
378
383
 
379
- child.stderr.on("data", (buf) => {
380
- process.stderr.write(buf);
381
- });
384
+ proc.stderr.on("data", (buf) => {
385
+ process.stderr.write(buf);
386
+ });
382
387
 
383
- child.on("exit", (code) => {
384
- const exitCode = code ?? 0;
385
- sendToServer({ type: "exit", sessionId, exitCode });
386
- setTimeout(() => process.exit(exitCode), 200);
387
- });
388
+ proc.on("exit", (code) => {
389
+ childRunning = false;
390
+ const exitCode = code ?? 0;
391
+ if (exitCode !== 0 || exitRequested) {
392
+ // Non-zero exit or user interrupted — shut down
393
+ sendToServer({ type: "exit", sessionId, exitCode });
394
+ setTimeout(() => process.exit(exitCode), 200);
395
+ } else {
396
+ // Successful completion — stay alive for follow-up from dashboard
397
+ sendToServer({
398
+ type: "agent_event", sessionId,
399
+ event: { kind: "status", status: "idle" },
400
+ });
401
+ }
402
+ });
388
403
 
389
- child.on("error", (err) => {
390
- console.error("Failed to start:", err.message);
391
- process.exit(1);
392
- });
404
+ proc.on("error", (err) => {
405
+ childRunning = false;
406
+ console.error("Failed to start:", err.message);
407
+ process.exit(1);
408
+ });
393
409
 
394
- // Confirmation-type tools — these need "yes" response, not permission grants
395
- const CONFIRMATION_TOOLS = new Set(["ExitPlanMode", "EnterPlanMode"]);
410
+ return proc;
411
+ }
396
412
 
397
413
  // Auto-approve a tool permission (used by yolo mode and manual approval)
398
414
  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
415
  let permEntry = toolName;
410
416
  const mcpMatch = toolName.match(/^(mcp__[^_]+(?:__[^_]+)?)__/);
411
417
  if (mcpMatch) {
@@ -442,41 +448,28 @@ if (adapter) {
442
448
  }
443
449
 
444
450
  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
451
  }
452
452
 
453
- // No local stdin forwarding in structured mode — input comes from server only
454
- // (stdin is reserved for stream-json messages to Claude)
453
+ // Start the initial claude process
454
+ const initialArgs = adapter.buildArgs(prompt, adapterFlags);
455
+ if (yoloMode) {
456
+ initialArgs.push("--dangerously-skip-permissions");
457
+ }
458
+ spawnClaude(initialArgs);
455
459
 
456
460
  handleServerMessage = (msg) => {
457
461
  if (msg.type === "agent_input" && (msg.text || msg.images)) {
458
- // Forward user message from dashboard to Claude's stdin as stream-json
459
- let content;
460
- if (msg.images && msg.images.length > 0) {
461
- // Build content array with images + optional text
462
- content = [];
463
- for (const img of msg.images) {
464
- content.push({
465
- type: "image",
466
- source: { type: "base64", media_type: img.mediaType, data: img.data },
467
- });
468
- }
469
- if (msg.text) {
470
- content.push({ type: "text", text: msg.text });
471
- }
472
- } else {
473
- content = msg.text;
462
+ // Follow-up from dashboard spawn new claude with -c (continue)
463
+ if (childRunning) {
464
+ // Child still running, queue or ignore
465
+ process.stderr.write(`${DIM}[orch] Ignoring input claude is still running${RESET}\n`);
466
+ return;
467
+ }
468
+ const followUpArgs = ["--output-format", "stream-json", "--verbose", "-c", "-p", msg.text || "continue"];
469
+ if (yoloMode) {
470
+ followUpArgs.push("--dangerously-skip-permissions");
474
471
  }
475
- const inputMsg = JSON.stringify({
476
- type: "user",
477
- message: { role: "user", content },
478
- });
479
- child.stdin.write(inputMsg + "\n");
472
+ spawnClaude(followUpArgs);
480
473
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "allow") {
481
474
  const scope = msg.scope || "session";
482
475
  approvePermission(msg.tool, scope);
@@ -855,6 +848,7 @@ function cleanup() {
855
848
 
856
849
  process.on("SIGINT", () => {
857
850
  if (adapter) {
851
+ exitRequested = true;
858
852
  child.kill("SIGINT");
859
853
  } else {
860
854
  child.stdin.write("\x03");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {