squadrant 0.13.1 → 0.13.2

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,
@@ -2578,9 +2587,15 @@ async function runCrewSpawn(input, config, deps) {
2578
2587
  const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
2579
2588
  await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${cliCommand2}`);
2580
2589
  const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
2581
- await deps.sendFirstTurn(pane2, `${input.task}
2590
+ const claudeResult = await deps.sendFirstTurn(pane2, `${firstTurnTask}
2582
2591
 
2583
2592
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
2593
+ if (!claudeResult.delivered) {
2594
+ 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.
2595
+ `);
2596
+ } else {
2597
+ await deps.emitEvent?.(input.project, { type: "task.first-turn.confirmed", id: rec.id });
2598
+ }
2584
2599
  return { ...pane2, title: title2 };
2585
2600
  }
2586
2601
  if (agentName === "opencode") {
@@ -2620,7 +2635,7 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
2620
2635
  const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
2621
2636
  await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${cliCommand2}`);
2622
2637
  const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
2623
- await deps.sendFirstTurn(pane2, `${input.task}
2638
+ const opencodeResult = await deps.sendFirstTurn(pane2, `${firstTurnTask}
2624
2639
 
2625
2640
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
2626
2641
  // #235: confirm-on-delivery — sendFirstTurnWhenReady polls until "Ask
@@ -2628,6 +2643,12 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
2628
2643
  // without duplicating the task. See crew-pane.ts SPLASH_MAX_CHECKS/EVERY_N.
2629
2644
  splashMarker: "Ask anything\u2026"
2630
2645
  });
2646
+ if (!opencodeResult.delivered) {
2647
+ 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.
2648
+ `);
2649
+ } else {
2650
+ await deps.emitEvent?.(input.project, { type: "task.first-turn.confirmed", id: rec.id });
2651
+ }
2631
2652
  return { ...pane2, title: title2 };
2632
2653
  }
2633
2654
  const cliCommand = agent.buildCommand({
@@ -2644,7 +2665,11 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
2644
2665
  await deps.runtime.sendToPane(pane, cliCommand);
2645
2666
  if (interactive) {
2646
2667
  const preLaunchScreen = await deps.runtime.readPaneScreen(pane) ?? "";
2647
- await deps.sendFirstTurn(pane, input.task, preLaunchScreen);
2668
+ const genericResult = await deps.sendFirstTurn(pane, firstTurnTask, preLaunchScreen);
2669
+ if (!genericResult.delivered) {
2670
+ 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.
2671
+ `);
2672
+ }
2648
2673
  }
2649
2674
  return { ...pane, title };
2650
2675
  }
@@ -2665,8 +2690,12 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
2665
2690
  }
2666
2691
  } catch {
2667
2692
  }
2668
- const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg));
2669
- await deliver(crew, message);
2693
+ const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));
2694
+ const { delivered } = await deliver(crew, message);
2695
+ if (!delivered) {
2696
+ process.stderr.write(`\u26A0\uFE0F Message not delivered to crew '${name}' \u2014 use 'squadrant crew send ${project} ${name}' to re-send.
2697
+ `);
2698
+ }
2670
2699
  }
2671
2700
  async function runCrewRead(project, name, runtime, workspaceId) {
2672
2701
  const crew = await findCrewPane(runtime, workspaceId, project, name);
@@ -4003,9 +4032,9 @@ async function confirmedSendToPane(runtime, pane, message) {
4003
4032
  if (draft !== "" && draft !== null)
4004
4033
  sawDraft = true;
4005
4034
  if (draft === "" && sawDraft)
4006
- return;
4035
+ return { delivered: true };
4007
4036
  if (draft === null && afterScreen !== preSendScreen)
4008
- return;
4037
+ return { delivered: true };
4009
4038
  const settled = await settleInputBox(runtime, pane);
4010
4039
  if (settled)
4011
4040
  sawDraft = true;
@@ -4015,6 +4044,7 @@ async function confirmedSendToPane(runtime, pane, message) {
4015
4044
  }
4016
4045
  await runtime.sendKeyToPane(pane, "Enter");
4017
4046
  }
4047
+ return { delivered: false };
4018
4048
  }
4019
4049
  async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acceptanceConfig) {
4020
4050
  await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));
@@ -4023,7 +4053,8 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
4023
4053
  let stable = false;
4024
4054
  for (let i = 0; i < maxPolls && !stable; i++) {
4025
4055
  const screen = await runtime.readPaneScreen(pane) ?? "";
4026
- if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen) {
4056
+ const hasInputBox = !!acceptanceConfig?.splashMarker || parseDraftFromScreen(screen) !== null;
4057
+ if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen && hasInputBox) {
4027
4058
  stable = true;
4028
4059
  } else {
4029
4060
  previousScreen = screen;
@@ -4037,13 +4068,16 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
4037
4068
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
4038
4069
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
4039
4070
  if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
4040
- return;
4071
+ return { delivered: true };
4041
4072
  }
4042
4073
  if ((check2 + 1) % SPLASH_RESEND_EVERY_N === 0 && check2 < SPLASH_MAX_CHECKS - 1) {
4043
4074
  await runtime.sendToPane(pane, task);
4044
4075
  }
4045
4076
  }
4046
- return;
4077
+ return { delivered: false };
4078
+ }
4079
+ if (!stable) {
4080
+ return confirmedSendToPane(runtime, pane, task);
4047
4081
  }
4048
4082
  await runtime.pasteToPane(pane, task);
4049
4083
  let sawDraft = await settleInputBox(runtime, pane);
@@ -4057,9 +4091,9 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
4057
4091
  if (draft !== "" && draft !== null)
4058
4092
  sawDraft = true;
4059
4093
  if (draft === "" && sawDraft)
4060
- return;
4094
+ return { delivered: true };
4061
4095
  if (draft === null && afterScreen !== preSendScreen)
4062
- return;
4096
+ return { delivered: true };
4063
4097
  const settled = await settleInputBox(runtime, pane);
4064
4098
  if (settled)
4065
4099
  sawDraft = true;
@@ -4069,6 +4103,10 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
4069
4103
  }
4070
4104
  await runtime.sendKeyToPane(pane, "Enter");
4071
4105
  }
4106
+ if (!sawDraft) {
4107
+ return confirmedSendToPane(runtime, pane, task);
4108
+ }
4109
+ return { delivered: false };
4072
4110
  }
4073
4111
  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
4112
  var init_crew_pane = __esm({
@@ -7042,6 +7080,7 @@ import chalk9 from "chalk";
7042
7080
  // packages/cli/src/commands/crew-control.ts
7043
7081
  init_dist2();
7044
7082
  init_dist2();
7083
+ init_dist();
7045
7084
  init_dist4();
7046
7085
  import { Command as Command8 } from "commander";
7047
7086
  import { createConnection as createConnection3 } from "net";
@@ -7467,7 +7506,16 @@ function addControlPlaneCrewCommands(crew) {
7467
7506
  const r = await squadrantdCall(buildStatusRequest(project, id));
7468
7507
  process.stdout.write(JSON.stringify(r) + "\n");
7469
7508
  });
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) => {
7509
+ 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) => {
7510
+ if (opts.allTerminal) {
7511
+ const tasks = await squadrantdCall({ kind: "list", project });
7512
+ const terminal = tasks.filter((t) => TERMINAL_STATES.has(t.state));
7513
+ for (const t of terminal) {
7514
+ await squadrantdCall({ kind: "purge", project, id: t.id, force: false });
7515
+ }
7516
+ console.log(`purged ${terminal.length} terminal record(s)`);
7517
+ return;
7518
+ }
7471
7519
  if (opts.purge) {
7472
7520
  const r = await squadrantdCall({ kind: "purge", project, id: opts.purge, force: opts.force ?? false });
7473
7521
  console.log(`purged ${r.provider}/${r.id} (was ${r.state})`);
@@ -7705,6 +7753,10 @@ async function runCrewSpawn2(input) {
7705
7753
  sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
7706
7754
  getFreePort,
7707
7755
  sendCodexFirstTurn,
7756
+ // #466: wire delivery confirmation so the daemon stamps firstTurnConfirmedAt.
7757
+ emitEvent: async (p, event) => {
7758
+ await squadrantdCall({ kind: "event", project: p, event });
7759
+ },
7708
7760
  onRouted: (route) => console.log(
7709
7761
  chalk9.dim(
7710
7762
  `routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
@@ -7765,7 +7817,10 @@ crewCommand.command("spawn").description(
7765
7817
  // opencode consumes the `approval` flag (→ bash:"ask" per-crew config).
7766
7818
  ...opts.approval ? { approvalPolicy: "untrusted", approval: true } : {},
7767
7819
  ...opts.shared ? { shared: true } : {},
7768
- ...opts.model ? { model: opts.model } : {}
7820
+ ...opts.model ? { model: opts.model } : {},
7821
+ // #458: pass the raw file path (not stdin) so runCrewSpawn can copy it
7822
+ // into the isolated worktree root for relative-path access.
7823
+ ...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
7769
7824
  });
7770
7825
  console.log(chalk9.green(`\u2714 Crew '${pane.title}' spawned (${pane.surfaceId})`));
7771
7826
  } catch (err) {