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.
Files changed (2) hide show
  1. package/bin/orch +109 -101
  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,101 @@ 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
+ 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
- // Broadcast initial permission state once connected
350
- let permissionsSent = false;
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
- 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
- }
391
+ proc.stdout.on("end", () => {
392
+ process.stderr.write(`${DIM}[orch] stdout stream ended (${lineCount} lines total)${RESET}\n`);
393
+ });
364
394
 
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 });
395
+ proc.stderr.on("data", (buf) => {
396
+ process.stderr.write(`${DIM}[orch] child stderr: ${RESET}`);
397
+ process.stderr.write(buf);
398
+ });
370
399
 
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");
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
- child.on("error", (err) => {
390
- console.error("Failed to start:", err.message);
391
- process.exit(1);
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
- // Confirmation-type tools — these need "yes" response, not permission grants
395
- const CONFIRMATION_TOOLS = new Set(["ExitPlanMode", "EnterPlanMode"]);
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
- // No local stdin forwarding in structured mode — input comes from server only
454
- // (stdin is reserved for stream-json messages to Claude)
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
- // 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;
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
- const inputMsg = JSON.stringify({
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
- // Will trigger close
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {