orchestrating 0.2.0 → 0.3.1
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 +56 -57
- package/package.json +1 -1
package/bin/orch
CHANGED
|
@@ -750,14 +750,14 @@ const ADAPTERS = {
|
|
|
750
750
|
buildArgs(prompt, flags) {
|
|
751
751
|
const args = [
|
|
752
752
|
"--output-format", "stream-json",
|
|
753
|
+
"--input-format", "stream-json",
|
|
753
754
|
"--verbose",
|
|
754
755
|
];
|
|
755
756
|
if (flags.continue) {
|
|
756
757
|
args.push("-c");
|
|
757
|
-
if (prompt) args.push("-p", prompt);
|
|
758
|
-
} else {
|
|
759
|
-
args.push("-p", prompt);
|
|
760
758
|
}
|
|
759
|
+
// With --input-format stream-json, the initial prompt is sent via stdin
|
|
760
|
+
// so we don't use -p here. The prompt is sent after spawn.
|
|
761
761
|
return args;
|
|
762
762
|
},
|
|
763
763
|
mode: "structured",
|
|
@@ -829,6 +829,7 @@ if (commandArgs.length === 0) {
|
|
|
829
829
|
}
|
|
830
830
|
|
|
831
831
|
// Resolve claude binary: prefer bundled @anthropic-ai/claude-code, fall back to system PATH
|
|
832
|
+
const commandName = commandArgs[0]; // original name for adapter lookup
|
|
832
833
|
let command = commandArgs[0];
|
|
833
834
|
let claudeBundled = false;
|
|
834
835
|
if (command === "claude") {
|
|
@@ -952,7 +953,7 @@ function removePermission(tool) {
|
|
|
952
953
|
}
|
|
953
954
|
|
|
954
955
|
// --- Determine mode: structured adapter or PTY ---
|
|
955
|
-
const adapter = ADAPTERS[
|
|
956
|
+
const adapter = ADAPTERS[commandName];
|
|
956
957
|
|
|
957
958
|
// Check for python3 in PTY mode
|
|
958
959
|
if (!adapter) {
|
|
@@ -1016,10 +1017,13 @@ if (adapter) {
|
|
|
1016
1017
|
return e;
|
|
1017
1018
|
})();
|
|
1018
1019
|
|
|
1019
|
-
// Spawn
|
|
1020
|
+
// Spawn claude with bidirectional stdin/stdout (stream-json mode)
|
|
1021
|
+
// Follow-ups are sent via stdin — no more kill/respawn.
|
|
1022
|
+
let claudeSessionId = null; // Claude's internal session ID (from init event)
|
|
1023
|
+
|
|
1020
1024
|
function spawnClaude(claudeArgs) {
|
|
1021
1025
|
const proc = spawn(command, claudeArgs, {
|
|
1022
|
-
stdio: ["
|
|
1026
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1023
1027
|
cwd: process.cwd(),
|
|
1024
1028
|
env: childEnv,
|
|
1025
1029
|
});
|
|
@@ -1043,18 +1047,28 @@ if (adapter) {
|
|
|
1043
1047
|
return;
|
|
1044
1048
|
}
|
|
1045
1049
|
|
|
1050
|
+
// Capture Claude's session ID for follow-ups
|
|
1051
|
+
if (raw.type === "system" && raw.subtype === "init" && raw.session_id) {
|
|
1052
|
+
claudeSessionId = raw.session_id;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Detect turn completion — Claude finished responding, ready for next input
|
|
1056
|
+
if (raw.type === "result") {
|
|
1057
|
+
childRunning = false; // "idle" — accepting input
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1046
1060
|
// Normalize and relay each event
|
|
1047
1061
|
const events = normalizeClaudeEvent(raw);
|
|
1048
1062
|
for (const event of events) {
|
|
1049
1063
|
printLocalEvent(event);
|
|
1050
1064
|
sendToServer({ type: "agent_event", sessionId, event });
|
|
1051
1065
|
|
|
1052
|
-
// Yolo mode: auto-approve permission denials
|
|
1053
|
-
// claude will pick it up on respawn via pendingPermissionGrant
|
|
1066
|
+
// Yolo mode: auto-approve permission denials
|
|
1054
1067
|
if (yoloMode && event.kind === "permission_denied") {
|
|
1055
1068
|
process.stderr.write(`${GREEN}[yolo] Auto-approving: ${event.toolName}${RESET}\n`);
|
|
1056
1069
|
approvePermission(event.toolName, "session");
|
|
1057
|
-
|
|
1070
|
+
// Send follow-up via stdin to retry (no respawn needed)
|
|
1071
|
+
sendFollowUp(`Permission for ${event.toolName} was granted. Please retry your last action.`);
|
|
1058
1072
|
}
|
|
1059
1073
|
}
|
|
1060
1074
|
});
|
|
@@ -1066,21 +1080,8 @@ if (adapter) {
|
|
|
1066
1080
|
proc.on("exit", (code) => {
|
|
1067
1081
|
childRunning = false;
|
|
1068
1082
|
const exitCode = code ?? 0;
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
setTimeout(() => process.exit(exitCode), 200);
|
|
1072
|
-
} else if (pendingPermissionGrant) {
|
|
1073
|
-
// Permission was granted while claude was running — respawn to retry
|
|
1074
|
-
const tool = pendingPermissionGrant;
|
|
1075
|
-
pendingPermissionGrant = null;
|
|
1076
|
-
process.stderr.write(`${GREEN}Retrying with new permission: ${tool}${RESET}\n`);
|
|
1077
|
-
respawnWithContinue(`Permission for ${tool} was granted. Please retry your last action.`);
|
|
1078
|
-
} else {
|
|
1079
|
-
sendToServer({
|
|
1080
|
-
type: "agent_event", sessionId,
|
|
1081
|
-
event: { kind: "status", status: "idle" },
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1083
|
+
sendToServer({ type: "exit", sessionId, exitCode });
|
|
1084
|
+
setTimeout(() => process.exit(exitCode), 200);
|
|
1084
1085
|
});
|
|
1085
1086
|
|
|
1086
1087
|
proc.on("error", (err) => {
|
|
@@ -1092,6 +1093,24 @@ if (adapter) {
|
|
|
1092
1093
|
return proc;
|
|
1093
1094
|
}
|
|
1094
1095
|
|
|
1096
|
+
// Send a follow-up message via stdin (no respawn!)
|
|
1097
|
+
function sendFollowUp(text) {
|
|
1098
|
+
if (!child || child.exitCode !== null) return;
|
|
1099
|
+
const msg = {
|
|
1100
|
+
type: "user",
|
|
1101
|
+
message: {
|
|
1102
|
+
role: "user",
|
|
1103
|
+
content: text,
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
try {
|
|
1107
|
+
child.stdin.write(JSON.stringify(msg) + "\n");
|
|
1108
|
+
childRunning = true; // Claude is now processing
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
process.stderr.write(`${RED}[orch] Failed to send via stdin: ${err.message}${RESET}\n`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1095
1114
|
// Auto-approve a tool permission (used by yolo mode and manual approval)
|
|
1096
1115
|
function approvePermission(toolName, scope) {
|
|
1097
1116
|
let permEntry = toolName;
|
|
@@ -1194,37 +1213,29 @@ if (adapter) {
|
|
|
1194
1213
|
}
|
|
1195
1214
|
}
|
|
1196
1215
|
|
|
1197
|
-
// Start the
|
|
1216
|
+
// Start the claude process (single process for entire session — no respawning)
|
|
1198
1217
|
const initialArgs = adapter.buildArgs(prompt, adapterFlags);
|
|
1199
1218
|
if (yoloMode) {
|
|
1200
1219
|
initialArgs.push("--dangerously-skip-permissions");
|
|
1201
1220
|
}
|
|
1202
1221
|
spawnClaude(initialArgs);
|
|
1203
1222
|
|
|
1204
|
-
//
|
|
1223
|
+
// Send initial prompt via stdin (not -p flag)
|
|
1205
1224
|
if (prompt) {
|
|
1225
|
+
sendFollowUp(prompt);
|
|
1226
|
+
// Emit the initial prompt as a user message so the dashboard shows it
|
|
1206
1227
|
sendToServer({
|
|
1207
1228
|
type: "agent_event", sessionId,
|
|
1208
1229
|
event: { kind: "user_message", text: prompt },
|
|
1209
1230
|
});
|
|
1210
1231
|
}
|
|
1211
1232
|
|
|
1212
|
-
// Respawn claude with -c to continue after permission grants or follow-up
|
|
1213
|
-
function respawnWithContinue(prompt) {
|
|
1214
|
-
const args = ["--output-format", "stream-json", "--verbose", "-c"];
|
|
1215
|
-
if (prompt) args.push("-p", prompt);
|
|
1216
|
-
if (yoloMode) args.push("--dangerously-skip-permissions");
|
|
1217
|
-
spawnClaude(args);
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
// Queue of permission grants received while claude is still running
|
|
1221
|
-
let pendingPermissionGrant = null;
|
|
1222
|
-
|
|
1223
1233
|
handleServerMessage = (msg) => {
|
|
1224
1234
|
if (msg.type === "agent_input" && (msg.text || msg.images)) {
|
|
1225
|
-
// Follow-up from dashboard —
|
|
1235
|
+
// Follow-up from dashboard — send via stdin (no respawn!)
|
|
1226
1236
|
if (childRunning) {
|
|
1227
|
-
process.stderr.write(`${DIM}[orch]
|
|
1237
|
+
process.stderr.write(`${DIM}[orch] Queuing input — claude is still processing${RESET}\n`);
|
|
1238
|
+
// TODO: queue and send after current turn completes
|
|
1228
1239
|
return;
|
|
1229
1240
|
}
|
|
1230
1241
|
let promptText = msg.text || "continue";
|
|
@@ -1241,28 +1252,16 @@ if (adapter) {
|
|
|
1241
1252
|
const fileList = imagePaths.map((p) => `[Attached image: ${p} — use Read tool to view]`).join("\n");
|
|
1242
1253
|
promptText = `${fileList}\n\n${promptText}`;
|
|
1243
1254
|
}
|
|
1244
|
-
|
|
1255
|
+
sendFollowUp(promptText);
|
|
1245
1256
|
} else if (msg.type === "agent_permission" && msg.tool && msg.action === "allow") {
|
|
1246
1257
|
const scope = msg.scope || "session";
|
|
1247
1258
|
approvePermission(msg.tool, scope);
|
|
1248
1259
|
process.stderr.write(`${GREEN}Permission granted (${scope}): ${msg.tool}${RESET}\n`);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
// Claude is still running — remember the grant and respawn when it exits
|
|
1252
|
-
pendingPermissionGrant = msg.tool;
|
|
1253
|
-
} else {
|
|
1254
|
-
// Claude already finished — respawn with -c to retry with new permission
|
|
1255
|
-
respawnWithContinue(`Permission for ${msg.tool} was granted. Please retry your last action.`);
|
|
1256
|
-
}
|
|
1260
|
+
// Send permission grant as follow-up via stdin
|
|
1261
|
+
sendFollowUp(`Permission for ${msg.tool} was granted. Please retry your last action.`);
|
|
1257
1262
|
} else if (msg.type === "agent_permission" && msg.tool && msg.action === "deny") {
|
|
1258
1263
|
process.stderr.write(`${RED}Permission denied: ${msg.tool}${RESET}\n`);
|
|
1259
|
-
|
|
1260
|
-
if (childRunning) {
|
|
1261
|
-
// Claude is still running — remember and respawn when it exits
|
|
1262
|
-
pendingPermissionGrant = null;
|
|
1263
|
-
} else {
|
|
1264
|
-
respawnWithContinue(`Permission for ${msg.tool} was denied by the user. Do not retry this tool — find an alternative approach or skip this step.`);
|
|
1265
|
-
}
|
|
1264
|
+
sendFollowUp(`Permission for ${msg.tool} was denied by the user. Do not retry this tool — find an alternative approach or skip this step.`);
|
|
1266
1265
|
} else if (msg.type === "agent_permission" && msg.tool && msg.action === "revoke") {
|
|
1267
1266
|
removePermission(msg.tool);
|
|
1268
1267
|
broadcastPermissions();
|
|
@@ -1274,7 +1273,7 @@ if (adapter) {
|
|
|
1274
1273
|
} else if (msg.type === "stop_session") {
|
|
1275
1274
|
process.stderr.write(`${RED}[orch] Stopped from dashboard${RESET}\n`);
|
|
1276
1275
|
exitRequested = true;
|
|
1277
|
-
if (
|
|
1276
|
+
if (child && child.exitCode === null) {
|
|
1278
1277
|
child.kill("SIGINT");
|
|
1279
1278
|
} else {
|
|
1280
1279
|
cleanup();
|
|
@@ -1586,7 +1585,7 @@ async function connectWs() {
|
|
|
1586
1585
|
token: authToken,
|
|
1587
1586
|
sessionId,
|
|
1588
1587
|
label: effectiveLabel,
|
|
1589
|
-
command,
|
|
1588
|
+
command: commandName,
|
|
1590
1589
|
args: spawnArgs,
|
|
1591
1590
|
cwd: process.cwd(),
|
|
1592
1591
|
hostname,
|