squadrant 0.13.1 → 0.13.3
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/dist/index.js +105 -21
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +49 -4
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2487,8 +2487,9 @@ async function runCodexInteractiveSpawn(o) {
|
|
|
2487
2487
|
title
|
|
2488
2488
|
});
|
|
2489
2489
|
await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);
|
|
2490
|
-
|
|
2491
|
-
|
|
2490
|
+
const firstTurnText = o.firstTurn ?? o.task;
|
|
2491
|
+
if (firstTurnText && firstTurnText !== "(interactive)") {
|
|
2492
|
+
void o.sendCodexFirstTurn(rec.id, firstTurnText).catch((e) => {
|
|
2492
2493
|
process.stderr.write(`(first-turn delivery failed: ${e.message})
|
|
2493
2494
|
`);
|
|
2494
2495
|
});
|
|
@@ -2520,6 +2521,13 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
2520
2521
|
name,
|
|
2521
2522
|
base: resolveWorktreeBase(proj.path)
|
|
2522
2523
|
}) : proj.path;
|
|
2524
|
+
let firstTurnTask = input.task;
|
|
2525
|
+
if (input.taskFile && input.taskFile !== "-" && !input.shared) {
|
|
2526
|
+
const absTaskFile = path10.resolve(input.taskFile);
|
|
2527
|
+
const basename = path10.basename(absTaskFile);
|
|
2528
|
+
fs12.copyFileSync(absTaskFile, path10.join(spawnCwd, basename));
|
|
2529
|
+
firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;
|
|
2530
|
+
}
|
|
2523
2531
|
const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
|
|
2524
2532
|
if (route) {
|
|
2525
2533
|
deps.onRouted?.(route);
|
|
@@ -2535,6 +2543,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
2535
2543
|
return runCodexInteractiveSpawn({
|
|
2536
2544
|
project: input.project,
|
|
2537
2545
|
task: input.task,
|
|
2546
|
+
firstTurn: firstTurnTask !== input.task ? firstTurnTask : void 0,
|
|
2538
2547
|
cwd: spawnCwd,
|
|
2539
2548
|
runtime: deps.runtime,
|
|
2540
2549
|
workspaceId: captain.id,
|
|
@@ -2560,7 +2569,12 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
2560
2569
|
task: input.task,
|
|
2561
2570
|
name
|
|
2562
2571
|
});
|
|
2563
|
-
|
|
2572
|
+
let hooksInstalled = false;
|
|
2573
|
+
try {
|
|
2574
|
+
deps.writeSettingsLocal(spawnCwd);
|
|
2575
|
+
hooksInstalled = true;
|
|
2576
|
+
} catch {
|
|
2577
|
+
}
|
|
2564
2578
|
const cliCommand2 = agent.buildCommand({
|
|
2565
2579
|
prompt: input.task,
|
|
2566
2580
|
workdir: spawnCwd,
|
|
@@ -2578,9 +2592,15 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
2578
2592
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
2579
2593
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${cliCommand2}`);
|
|
2580
2594
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
2581
|
-
await deps.sendFirstTurn(pane2, `${
|
|
2595
|
+
const claudeResult = await deps.sendFirstTurn(pane2, `${firstTurnTask}
|
|
2582
2596
|
|
|
2583
2597
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
2598
|
+
if (!claudeResult.delivered) {
|
|
2599
|
+
process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
|
|
2600
|
+
`);
|
|
2601
|
+
} else if (!hooksInstalled) {
|
|
2602
|
+
await deps.emitEvent?.(input.project, { type: "task.first-turn.confirmed", id: rec.id });
|
|
2603
|
+
}
|
|
2584
2604
|
return { ...pane2, title: title2 };
|
|
2585
2605
|
}
|
|
2586
2606
|
if (agentName === "opencode") {
|
|
@@ -2620,7 +2640,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
|
2620
2640
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
2621
2641
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${cliCommand2}`);
|
|
2622
2642
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
2623
|
-
await deps.sendFirstTurn(pane2, `${
|
|
2643
|
+
const opencodeResult = await deps.sendFirstTurn(pane2, `${firstTurnTask}
|
|
2624
2644
|
|
|
2625
2645
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
2626
2646
|
// #235: confirm-on-delivery — sendFirstTurnWhenReady polls until "Ask
|
|
@@ -2628,6 +2648,12 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
2628
2648
|
// without duplicating the task. See crew-pane.ts SPLASH_MAX_CHECKS/EVERY_N.
|
|
2629
2649
|
splashMarker: "Ask anything\u2026"
|
|
2630
2650
|
});
|
|
2651
|
+
if (!opencodeResult.delivered) {
|
|
2652
|
+
process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
|
|
2653
|
+
`);
|
|
2654
|
+
} else {
|
|
2655
|
+
await deps.emitEvent?.(input.project, { type: "task.first-turn.confirmed", id: rec.id });
|
|
2656
|
+
}
|
|
2631
2657
|
return { ...pane2, title: title2 };
|
|
2632
2658
|
}
|
|
2633
2659
|
const cliCommand = agent.buildCommand({
|
|
@@ -2644,7 +2670,11 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
2644
2670
|
await deps.runtime.sendToPane(pane, cliCommand);
|
|
2645
2671
|
if (interactive) {
|
|
2646
2672
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane) ?? "";
|
|
2647
|
-
await deps.sendFirstTurn(pane,
|
|
2673
|
+
const genericResult = await deps.sendFirstTurn(pane, firstTurnTask, preLaunchScreen);
|
|
2674
|
+
if (!genericResult.delivered) {
|
|
2675
|
+
process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
|
|
2676
|
+
`);
|
|
2677
|
+
}
|
|
2648
2678
|
}
|
|
2649
2679
|
return { ...pane, title };
|
|
2650
2680
|
}
|
|
@@ -2665,8 +2695,12 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
2665
2695
|
}
|
|
2666
2696
|
} catch {
|
|
2667
2697
|
}
|
|
2668
|
-
const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg));
|
|
2669
|
-
await deliver(crew, message);
|
|
2698
|
+
const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));
|
|
2699
|
+
const { delivered } = await deliver(crew, message);
|
|
2700
|
+
if (!delivered) {
|
|
2701
|
+
process.stderr.write(`\u26A0\uFE0F Message not delivered to crew '${name}' \u2014 use 'squadrant crew send ${project} ${name}' to re-send.
|
|
2702
|
+
`);
|
|
2703
|
+
}
|
|
2670
2704
|
}
|
|
2671
2705
|
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
2672
2706
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
@@ -2871,6 +2905,27 @@ function parseDraftFromScreen(screen) {
|
|
|
2871
2905
|
}
|
|
2872
2906
|
return "";
|
|
2873
2907
|
}
|
|
2908
|
+
function hasCCInputBox(screen) {
|
|
2909
|
+
if (!screen)
|
|
2910
|
+
return false;
|
|
2911
|
+
const lines = screen.split(/\r?\n/);
|
|
2912
|
+
const HR_RE = /^\s*─{10,}\s*$/;
|
|
2913
|
+
let bottomHR = -1;
|
|
2914
|
+
let topHR = -1;
|
|
2915
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2916
|
+
if (HR_RE.test(lines[i])) {
|
|
2917
|
+
if (bottomHR === -1)
|
|
2918
|
+
bottomHR = i;
|
|
2919
|
+
else {
|
|
2920
|
+
topHR = i;
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
if (topHR === -1)
|
|
2926
|
+
return false;
|
|
2927
|
+
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
2928
|
+
}
|
|
2874
2929
|
function readInputBoxRaw(screen) {
|
|
2875
2930
|
if (!screen)
|
|
2876
2931
|
return null;
|
|
@@ -4003,9 +4058,9 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
4003
4058
|
if (draft !== "" && draft !== null)
|
|
4004
4059
|
sawDraft = true;
|
|
4005
4060
|
if (draft === "" && sawDraft)
|
|
4006
|
-
return;
|
|
4007
|
-
if (draft === null && afterScreen !== preSendScreen)
|
|
4008
|
-
return;
|
|
4061
|
+
return { delivered: true };
|
|
4062
|
+
if (draft === null && afterScreen !== preSendScreen && sawDraft)
|
|
4063
|
+
return { delivered: true };
|
|
4009
4064
|
const settled = await settleInputBox(runtime, pane);
|
|
4010
4065
|
if (settled)
|
|
4011
4066
|
sawDraft = true;
|
|
@@ -4015,6 +4070,7 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
4015
4070
|
}
|
|
4016
4071
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
4017
4072
|
}
|
|
4073
|
+
return { delivered: false };
|
|
4018
4074
|
}
|
|
4019
4075
|
async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acceptanceConfig) {
|
|
4020
4076
|
await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));
|
|
@@ -4023,7 +4079,8 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
4023
4079
|
let stable = false;
|
|
4024
4080
|
for (let i = 0; i < maxPolls && !stable; i++) {
|
|
4025
4081
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
4026
|
-
|
|
4082
|
+
const hasInputBox = !!acceptanceConfig?.splashMarker || hasCCInputBox(screen);
|
|
4083
|
+
if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen && hasInputBox) {
|
|
4027
4084
|
stable = true;
|
|
4028
4085
|
} else {
|
|
4029
4086
|
previousScreen = screen;
|
|
@@ -4037,13 +4094,16 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
4037
4094
|
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
4038
4095
|
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
4039
4096
|
if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
|
|
4040
|
-
return;
|
|
4097
|
+
return { delivered: true };
|
|
4041
4098
|
}
|
|
4042
4099
|
if ((check2 + 1) % SPLASH_RESEND_EVERY_N === 0 && check2 < SPLASH_MAX_CHECKS - 1) {
|
|
4043
4100
|
await runtime.sendToPane(pane, task);
|
|
4044
4101
|
}
|
|
4045
4102
|
}
|
|
4046
|
-
return;
|
|
4103
|
+
return { delivered: false };
|
|
4104
|
+
}
|
|
4105
|
+
if (!stable) {
|
|
4106
|
+
return confirmedSendToPane(runtime, pane, task);
|
|
4047
4107
|
}
|
|
4048
4108
|
await runtime.pasteToPane(pane, task);
|
|
4049
4109
|
let sawDraft = await settleInputBox(runtime, pane);
|
|
@@ -4057,9 +4117,9 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
4057
4117
|
if (draft !== "" && draft !== null)
|
|
4058
4118
|
sawDraft = true;
|
|
4059
4119
|
if (draft === "" && sawDraft)
|
|
4060
|
-
return;
|
|
4061
|
-
if (draft === null && afterScreen !== preSendScreen)
|
|
4062
|
-
return;
|
|
4120
|
+
return { delivered: true };
|
|
4121
|
+
if (draft === null && afterScreen !== preSendScreen && sawDraft)
|
|
4122
|
+
return { delivered: true };
|
|
4063
4123
|
const settled = await settleInputBox(runtime, pane);
|
|
4064
4124
|
if (settled)
|
|
4065
4125
|
sawDraft = true;
|
|
@@ -4069,6 +4129,10 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
4069
4129
|
}
|
|
4070
4130
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
4071
4131
|
}
|
|
4132
|
+
if (!sawDraft) {
|
|
4133
|
+
return confirmedSendToPane(runtime, pane, task);
|
|
4134
|
+
}
|
|
4135
|
+
return { delivered: false };
|
|
4072
4136
|
}
|
|
4073
4137
|
var SEND_FIRST_TURN_FLOOR_MS, POLL_INTERVAL_MS, SEND_FIRST_TURN_TIMEOUT_MS, POST_SEND_CHECK_MS, SPLASH_MAX_CHECKS, SPLASH_RESEND_EVERY_N, SETTLE_POLL_MS, SETTLE_MAX_POLLS, SUBMIT_RETRY_LIMIT;
|
|
4074
4138
|
var init_crew_pane = __esm({
|
|
@@ -5795,6 +5859,8 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
5795
5859
|
case "SubagentStop":
|
|
5796
5860
|
case "PostToolUse":
|
|
5797
5861
|
return { type: "task.progress", id: taskId, note: event.toLowerCase() };
|
|
5862
|
+
case "UserPromptSubmit":
|
|
5863
|
+
return { type: "task.first-turn.confirmed", id: taskId };
|
|
5798
5864
|
default:
|
|
5799
5865
|
return null;
|
|
5800
5866
|
}
|
|
@@ -5802,7 +5868,7 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
5802
5868
|
var EVENTS, claudeInteractive;
|
|
5803
5869
|
var init_claude2 = __esm({
|
|
5804
5870
|
"packages/agents/dist/interactive/claude.js"() {
|
|
5805
|
-
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification"];
|
|
5871
|
+
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
5806
5872
|
claudeInteractive = {
|
|
5807
5873
|
provider: "claude",
|
|
5808
5874
|
tier: "strong",
|
|
@@ -7042,6 +7108,7 @@ import chalk9 from "chalk";
|
|
|
7042
7108
|
// packages/cli/src/commands/crew-control.ts
|
|
7043
7109
|
init_dist2();
|
|
7044
7110
|
init_dist2();
|
|
7111
|
+
init_dist();
|
|
7045
7112
|
init_dist4();
|
|
7046
7113
|
import { Command as Command8 } from "commander";
|
|
7047
7114
|
import { createConnection as createConnection3 } from "net";
|
|
@@ -7467,7 +7534,16 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
7467
7534
|
const r = await squadrantdCall(buildStatusRequest(project, id));
|
|
7468
7535
|
process.stdout.write(JSON.stringify(r) + "\n");
|
|
7469
7536
|
});
|
|
7470
|
-
crew.command("tasks <project>").description("List control-plane tasks for a project (control-plane analogue of legacy `list`), or purge a task with --purge").option("--json", "Full JSON output (one or more records)").option("--id <taskId>", "Show only tasks matching this id prefix").option("--state <state>", "Filter by task state").option("--state-only <taskId>", "Print just the state string for a single task").option("--purge <taskId>", "Purge a task record from the store (default: only terminal records)").option("--force", "Force-purge a non-terminal record (use with --purge)").action(async (project, opts) => {
|
|
7537
|
+
crew.command("tasks <project>").description("List control-plane tasks for a project (control-plane analogue of legacy `list`), or purge a task with --purge").option("--json", "Full JSON output (one or more records)").option("--id <taskId>", "Show only tasks matching this id prefix").option("--state <state>", "Filter by task state").option("--state-only <taskId>", "Print just the state string for a single task").option("--purge <taskId>", "Purge a task record from the store (default: only terminal records)").option("--force", "Force-purge a non-terminal record (use with --purge)").option("--all-terminal", "Purge all terminal (done/cancelled/failed) records for the project").action(async (project, opts) => {
|
|
7538
|
+
if (opts.allTerminal) {
|
|
7539
|
+
const tasks = await squadrantdCall({ kind: "list", project });
|
|
7540
|
+
const terminal = tasks.filter((t) => TERMINAL_STATES.has(t.state));
|
|
7541
|
+
for (const t of terminal) {
|
|
7542
|
+
await squadrantdCall({ kind: "purge", project, id: t.id, force: false });
|
|
7543
|
+
}
|
|
7544
|
+
console.log(`purged ${terminal.length} terminal record(s)`);
|
|
7545
|
+
return;
|
|
7546
|
+
}
|
|
7471
7547
|
if (opts.purge) {
|
|
7472
7548
|
const r = await squadrantdCall({ kind: "purge", project, id: opts.purge, force: opts.force ?? false });
|
|
7473
7549
|
console.log(`purged ${r.provider}/${r.id} (was ${r.state})`);
|
|
@@ -7705,6 +7781,10 @@ async function runCrewSpawn2(input) {
|
|
|
7705
7781
|
sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
|
|
7706
7782
|
getFreePort,
|
|
7707
7783
|
sendCodexFirstTurn,
|
|
7784
|
+
// #466: wire delivery confirmation so the daemon stamps firstTurnConfirmedAt.
|
|
7785
|
+
emitEvent: async (p, event) => {
|
|
7786
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
7787
|
+
},
|
|
7708
7788
|
onRouted: (route) => console.log(
|
|
7709
7789
|
chalk9.dim(
|
|
7710
7790
|
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
@@ -7765,7 +7845,10 @@ crewCommand.command("spawn").description(
|
|
|
7765
7845
|
// opencode consumes the `approval` flag (→ bash:"ask" per-crew config).
|
|
7766
7846
|
...opts.approval ? { approvalPolicy: "untrusted", approval: true } : {},
|
|
7767
7847
|
...opts.shared ? { shared: true } : {},
|
|
7768
|
-
...opts.model ? { model: opts.model } : {}
|
|
7848
|
+
...opts.model ? { model: opts.model } : {},
|
|
7849
|
+
// #458: pass the raw file path (not stdin) so runCrewSpawn can copy it
|
|
7850
|
+
// into the isolated worktree root for relative-path access.
|
|
7851
|
+
...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
|
|
7769
7852
|
});
|
|
7770
7853
|
console.log(chalk9.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
|
|
7771
7854
|
} catch (err) {
|
|
@@ -10973,9 +11056,10 @@ async function sendToSock(req) {
|
|
|
10973
11056
|
function mapHookSub(sub, payload, taskId) {
|
|
10974
11057
|
switch (sub) {
|
|
10975
11058
|
case "session-start":
|
|
10976
|
-
case "prompt-submit":
|
|
10977
11059
|
case "pre-tool-use":
|
|
10978
11060
|
return { type: "task.progress", id: taskId, note: sub };
|
|
11061
|
+
case "prompt-submit":
|
|
11062
|
+
return { type: "task.first-turn.confirmed", id: taskId };
|
|
10979
11063
|
case "stop":
|
|
10980
11064
|
return mapClaudeHookToEvent("Stop", payload, taskId);
|
|
10981
11065
|
case "notification":
|