orchestrating 0.1.6 → 0.1.8

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 +46 -29
  2. package/package.json +1 -1
package/bin/orch CHANGED
@@ -342,7 +342,6 @@ if (adapter) {
342
342
 
343
343
  // Spawn a claude process and wire up output handling
344
344
  function spawnClaude(claudeArgs) {
345
- process.stderr.write(`${DIM}[orch] Spawning: ${command} ${claudeArgs.join(" ")}${RESET}\n`);
346
345
  const proc = spawn(command, claudeArgs, {
347
346
  stdio: ["ignore", "pipe", "pipe"],
348
347
  cwd: process.cwd(),
@@ -350,16 +349,12 @@ if (adapter) {
350
349
  });
351
350
  childRunning = true;
352
351
  child = proc;
353
- process.stderr.write(`${DIM}[orch] Child PID: ${proc.pid}${RESET}\n`);
354
352
 
355
353
  // Parse NDJSON from stdout line-by-line
356
354
  const rl = readline.createInterface({ input: proc.stdout });
357
355
  let permissionsSent = false;
358
- let lineCount = 0;
359
356
 
360
357
  rl.on("line", (line) => {
361
- lineCount++;
362
- process.stderr.write(`${DIM}[orch] stdout line #${lineCount}: ${line.slice(0, 120)}${RESET}\n`);
363
358
  if (!permissionsSent) {
364
359
  permissionsSent = true;
365
360
  broadcastPermissions();
@@ -369,41 +364,41 @@ if (adapter) {
369
364
  try {
370
365
  raw = JSON.parse(line);
371
366
  } catch {
372
- process.stderr.write(`${DIM}[orch] JSON parse failed for line${RESET}\n`);
373
367
  return;
374
368
  }
375
369
 
376
370
  // Normalize and relay each event
377
371
  const events = normalizeClaudeEvent(raw);
378
- process.stderr.write(`${DIM}[orch] Normalized ${events.length} events (type=${raw.type})${RESET}\n`);
379
372
  for (const event of events) {
380
373
  printLocalEvent(event);
381
374
  sendToServer({ type: "agent_event", sessionId, event });
382
375
 
383
- // Yolo mode: auto-approve permission denials immediately
376
+ // Yolo mode: auto-approve permission denials — save to settings,
377
+ // claude will pick it up on respawn via pendingPermissionGrant
384
378
  if (yoloMode && event.kind === "permission_denied") {
385
379
  process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
386
380
  approvePermission(event.toolName, "session");
381
+ pendingPermissionGrant = event.toolName;
387
382
  }
388
383
  }
389
384
  });
390
385
 
391
- proc.stdout.on("end", () => {
392
- process.stderr.write(`${DIM}[orch] stdout stream ended (${lineCount} lines total)${RESET}\n`);
393
- });
394
-
395
386
  proc.stderr.on("data", (buf) => {
396
- process.stderr.write(`${DIM}[orch] child stderr: ${RESET}`);
397
387
  process.stderr.write(buf);
398
388
  });
399
389
 
400
- proc.on("exit", (code, signal) => {
390
+ proc.on("exit", (code) => {
401
391
  childRunning = false;
402
- process.stderr.write(`${DIM}[orch] Child exited: code=${code} signal=${signal}${RESET}\n`);
403
392
  const exitCode = code ?? 0;
404
393
  if (exitCode !== 0 || exitRequested) {
405
394
  sendToServer({ type: "exit", sessionId, exitCode });
406
395
  setTimeout(() => process.exit(exitCode), 200);
396
+ } else if (pendingPermissionGrant) {
397
+ // Permission was granted while claude was running — respawn to retry
398
+ const tool = pendingPermissionGrant;
399
+ pendingPermissionGrant = null;
400
+ process.stderr.write(`${GREEN}Retrying with new permission: ${tool}${RESET}\n`);
401
+ respawnWithContinue(`Permission for ${tool} was granted. Please retry your last action.`);
407
402
  } else {
408
403
  sendToServer({
409
404
  type: "agent_event", sessionId,
@@ -414,7 +409,7 @@ if (adapter) {
414
409
 
415
410
  proc.on("error", (err) => {
416
411
  childRunning = false;
417
- process.stderr.write(`${RED}[orch] Spawn error: ${err.message}${RESET}\n`);
412
+ console.error("Failed to start:", err.message);
418
413
  process.exit(1);
419
414
  });
420
415
 
@@ -468,23 +463,37 @@ if (adapter) {
468
463
  }
469
464
  spawnClaude(initialArgs);
470
465
 
466
+ // Respawn claude with -c to continue after permission grants or follow-up
467
+ function respawnWithContinue(prompt) {
468
+ const args = ["--output-format", "stream-json", "--verbose", "-c"];
469
+ if (prompt) args.push("-p", prompt);
470
+ if (yoloMode) args.push("--dangerously-skip-permissions");
471
+ spawnClaude(args);
472
+ }
473
+
474
+ // Queue of permission grants received while claude is still running
475
+ let pendingPermissionGrant = null;
476
+
471
477
  handleServerMessage = (msg) => {
472
478
  if (msg.type === "agent_input" && (msg.text || msg.images)) {
473
479
  // Follow-up from dashboard — spawn new claude with -c (continue)
474
480
  if (childRunning) {
475
- // Child still running, queue or ignore
476
481
  process.stderr.write(`${DIM}[orch] Ignoring input — claude is still running${RESET}\n`);
477
482
  return;
478
483
  }
479
- const followUpArgs = ["--output-format", "stream-json", "--verbose", "-c", "-p", msg.text || "continue"];
480
- if (yoloMode) {
481
- followUpArgs.push("--dangerously-skip-permissions");
482
- }
483
- spawnClaude(followUpArgs);
484
+ respawnWithContinue(msg.text || "continue");
484
485
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "allow") {
485
486
  const scope = msg.scope || "session";
486
487
  approvePermission(msg.tool, scope);
487
488
  process.stderr.write(`${GREEN}Permission granted (${scope}): ${msg.tool}${RESET}\n`);
489
+
490
+ if (childRunning) {
491
+ // Claude is still running — remember the grant and respawn when it exits
492
+ pendingPermissionGrant = msg.tool;
493
+ } else {
494
+ // Claude already finished — respawn with -c to retry with new permission
495
+ respawnWithContinue(`Permission for ${msg.tool} was granted. Please retry your last action.`);
496
+ }
488
497
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "revoke") {
489
498
  removePermission(msg.tool);
490
499
  broadcastPermissions();
@@ -535,7 +544,18 @@ if (adapter) {
535
544
  if (process.stdin.isTTY) {
536
545
  process.stdin.setRawMode(false);
537
546
  }
538
- setTimeout(() => process.exit(exitCode), 200);
547
+ // Wait for WS to connect and flush buffer before exiting
548
+ // (fast commands like `ls` may finish before the WS handshake)
549
+ function tryExit() {
550
+ if (wsReady && sendBuffer.length === 0) {
551
+ setTimeout(() => process.exit(exitCode), 100);
552
+ } else {
553
+ setTimeout(tryExit, 50);
554
+ }
555
+ }
556
+ // Give up after 3 seconds regardless
557
+ setTimeout(() => process.exit(exitCode), 3000);
558
+ tryExit();
539
559
  });
540
560
 
541
561
  child.on("error", (err) => {
@@ -770,8 +790,6 @@ function connectWs() {
770
790
  ws = new WebSocket(serverUrl);
771
791
 
772
792
  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`);
775
793
  ws.send(JSON.stringify({
776
794
  type: "register",
777
795
  token: authToken,
@@ -816,8 +834,7 @@ function connectWs() {
816
834
  } catch {}
817
835
  });
818
836
 
819
- ws.on("close", (code, reason) => {
820
- process.stderr.write(`${DIM}[orch] WS closed: code=${code} reason=${reason}${RESET}\n`);
837
+ ws.on("close", () => {
821
838
  wsReady = false;
822
839
  ws = null;
823
840
  if (!authFailed) {
@@ -825,8 +842,8 @@ function connectWs() {
825
842
  }
826
843
  });
827
844
 
828
- ws.on("error", (err) => {
829
- process.stderr.write(`${DIM}[orch] WS error: ${err.message}${RESET}\n`);
845
+ ws.on("error", () => {
846
+ // Will trigger close
830
847
  });
831
848
  }
832
849
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {