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 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
- if (o.task && o.task !== "(interactive)") {
2491
- void o.sendCodexFirstTurn(rec.id, o.task).catch((e) => {
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
- deps.writeSettingsLocal(spawnCwd);
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, `${input.task}
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, `${input.task}
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, input.task, preLaunchScreen);
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
- if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen) {
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":