orchestrating 0.3.2 → 0.4.0

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 +79 -11
  2. package/package.json +1 -1
package/bin/orch CHANGED
@@ -676,6 +676,43 @@ async function handleDaemon(projectsDir) {
676
676
  }
677
677
  }
678
678
 
679
+ if (msg.type === "update_cli") {
680
+ process.stderr.write(`${GREEN}${PREFIX} Update requested — running npm i -g orchestrating${RESET}\n`);
681
+ try {
682
+ const result = execSync("npm i -g orchestrating 2>&1", { encoding: "utf-8", timeout: 60_000 });
683
+ // Get the new version
684
+ let ver = "unknown";
685
+ try {
686
+ ver = execSync("npm list -g orchestrating --depth=0 2>/dev/null", { encoding: "utf-8" })
687
+ .match(/orchestrating@([\d.]+)/)?.[1] || "unknown";
688
+ } catch {}
689
+ process.stderr.write(`${GREEN}${PREFIX} Updated to v${ver}${RESET}\n`);
690
+ if (sock.readyState === WebSocket.OPEN) {
691
+ sock.send(JSON.stringify({
692
+ type: "update_result",
693
+ hostname,
694
+ success: true,
695
+ version: ver,
696
+ }));
697
+ }
698
+ // Restart daemon after a short delay so the result message gets sent
699
+ process.stderr.write(`${GREEN}${PREFIX} Restarting daemon...${RESET}\n`);
700
+ setTimeout(() => {
701
+ process.exit(0); // launchd/systemd will restart us with the new version
702
+ }, 1000);
703
+ } catch (err) {
704
+ process.stderr.write(`${RED}${PREFIX} Update failed: ${err.message}${RESET}\n`);
705
+ if (sock.readyState === WebSocket.OPEN) {
706
+ sock.send(JSON.stringify({
707
+ type: "update_result",
708
+ hostname,
709
+ success: false,
710
+ error: err.message,
711
+ }));
712
+ }
713
+ }
714
+ }
715
+
679
716
  if (msg.type === "browse_directory") {
680
717
  const { requestId: browseReqId, path: dirPath } = msg;
681
718
  try {
@@ -1020,6 +1057,7 @@ if (adapter) {
1020
1057
  // Spawn claude with bidirectional stdin/stdout (stream-json mode)
1021
1058
  // Follow-ups are sent via stdin — no more kill/respawn.
1022
1059
  let claudeSessionId = null; // Claude's internal session ID (from init event)
1060
+ let pendingRespawn = null; // { prompt } — set when permission grant needs respawn
1023
1061
 
1024
1062
  function spawnClaude(claudeArgs) {
1025
1063
  const proc = spawn(command, claudeArgs, {
@@ -1067,8 +1105,8 @@ if (adapter) {
1067
1105
  if (yoloMode && event.kind === "permission_denied") {
1068
1106
  process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
1069
1107
  approvePermission(event.toolName, "session");
1070
- // Permissions are file-based need respawn for Claude to pick them up
1071
- respawnWithContinue(`Permission for ${event.toolName} was granted. Please retry your last action.`);
1108
+ // Queue respawnClaude will exit after this turn, then we respawn with -c
1109
+ pendingRespawn = { prompt: `Permission for ${event.toolName} was granted. Please retry your last action.` };
1072
1110
  }
1073
1111
  }
1074
1112
  });
@@ -1080,8 +1118,23 @@ if (adapter) {
1080
1118
  proc.on("exit", (code) => {
1081
1119
  childRunning = false;
1082
1120
  const exitCode = code ?? 0;
1083
- sendToServer({ type: "exit", sessionId, exitCode });
1084
- setTimeout(() => process.exit(exitCode), 200);
1121
+
1122
+ if (exitCode !== 0 || exitRequested) {
1123
+ // Non-zero exit or user requested stop — terminate
1124
+ sendToServer({ type: "exit", sessionId, exitCode });
1125
+ setTimeout(() => process.exit(exitCode), 200);
1126
+ } else if (pendingRespawn) {
1127
+ // Permission grant/deny triggered a respawn — do it now
1128
+ const { prompt: respawnPrompt } = pendingRespawn;
1129
+ pendingRespawn = null;
1130
+ respawnWithContinue(respawnPrompt);
1131
+ } else {
1132
+ // Normal exit (turn complete) — stay alive, emit idle, wait for follow-ups
1133
+ sendToServer({
1134
+ type: "agent_event", sessionId,
1135
+ event: { kind: "status", status: "idle" },
1136
+ });
1137
+ }
1085
1138
  });
1086
1139
 
1087
1140
  proc.on("error", (err) => {
@@ -1246,10 +1299,9 @@ if (adapter) {
1246
1299
 
1247
1300
  handleServerMessage = (msg) => {
1248
1301
  if (msg.type === "agent_input" && (msg.text || msg.images)) {
1249
- // Follow-up from dashboard — send via stdin (no respawn!)
1302
+ // Follow-up from dashboard
1250
1303
  if (childRunning) {
1251
1304
  process.stderr.write(`${DIM}[orch] Queuing input — claude is still processing${RESET}\n`);
1252
- // TODO: queue and send after current turn completes
1253
1305
  return;
1254
1306
  }
1255
1307
  let promptText = msg.text || "continue";
@@ -1266,17 +1318,33 @@ if (adapter) {
1266
1318
  const fileList = imagePaths.map((p) => `[Attached image: ${p} — use Read tool to view]`).join("\n");
1267
1319
  promptText = `${fileList}\n\n${promptText}`;
1268
1320
  }
1269
- sendFollowUp(promptText);
1321
+ // If process is still alive, send via stdin; otherwise respawn
1322
+ if (child && child.exitCode === null) {
1323
+ sendFollowUp(promptText);
1324
+ } else {
1325
+ respawnWithContinue(promptText);
1326
+ }
1270
1327
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "allow") {
1271
1328
  const scope = msg.scope || "session";
1272
1329
  approvePermission(msg.tool, scope);
1273
1330
  process.stderr.write(`${GREEN}Permission granted (${scope}): ${msg.tool}${RESET}\n`);
1274
- // Permissions are file-based in Claude Code need to restart the process
1275
- // so it picks up the updated settings.local.json
1276
- respawnWithContinue(`Permission for ${msg.tool} was granted. Please retry your last action.`);
1331
+ // Permissions are file-based Claude needs restart to pick up settings.local.json
1332
+ const respawnPrompt = `Permission for ${msg.tool} was granted. Please retry your last action.`;
1333
+ if (child && child.exitCode === null) {
1334
+ // Process still running — queue respawn for when it exits
1335
+ pendingRespawn = { prompt: respawnPrompt };
1336
+ } else {
1337
+ // Process already exited — respawn now
1338
+ respawnWithContinue(respawnPrompt);
1339
+ }
1277
1340
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "deny") {
1278
1341
  process.stderr.write(`${RED}Permission denied: ${msg.tool}${RESET}\n`);
1279
- respawnWithContinue(`Permission for ${msg.tool} was denied by the user. Do not retry this tool — find an alternative approach or skip this step.`);
1342
+ const denyPrompt = `Permission for ${msg.tool} was denied by the user. Do not retry this tool — find an alternative approach or skip this step.`;
1343
+ if (child && child.exitCode === null) {
1344
+ pendingRespawn = { prompt: denyPrompt };
1345
+ } else {
1346
+ respawnWithContinue(denyPrompt);
1347
+ }
1280
1348
  } else if (msg.type === "agent_permission" && msg.tool && msg.action === "revoke") {
1281
1349
  removePermission(msg.tool);
1282
1350
  broadcastPermissions();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {