squadrant 0.12.0 → 0.13.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.
package/dist/index.js CHANGED
@@ -34,14 +34,14 @@ function getDefaultConfig() {
34
34
  models: {
35
35
  command: "opus",
36
36
  captain: "opus",
37
- crew: "opus",
37
+ crew: "sonnet",
38
38
  exploration: "haiku",
39
39
  review: "opus"
40
40
  },
41
41
  roles: {
42
42
  command: { agent: "claude", model: "opus" },
43
43
  captain: { agent: "claude", model: "opus" },
44
- crew: { agent: "claude", model: "opus" },
44
+ crew: { agent: "claude", model: "sonnet" },
45
45
  exploration: { agent: "claude", model: "haiku" },
46
46
  side: { agent: "claude", model: "opus" }
47
47
  },
@@ -579,7 +579,7 @@ var init_config_drift = __esm({
579
579
  }
580
580
  ];
581
581
  KNOWN_DEFAULT_HISTORY = [
582
- { path: "defaults.roles.crew.model", oldDefaults: ["sonnet"] },
582
+ { path: "defaults.roles.crew.model", oldDefaults: ["opus"] },
583
583
  { path: "defaults.roles.captain.model", oldDefaults: ["sonnet"] }
584
584
  ];
585
585
  KNOWN_DRIVERS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "opencode"]);
@@ -2632,7 +2632,8 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
2632
2632
  }
2633
2633
  } catch {
2634
2634
  }
2635
- await runtime.sendToPane(crew, message);
2635
+ const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg));
2636
+ await deliver(crew, message);
2636
2637
  }
2637
2638
  async function runCrewRead(project, name, runtime, workspaceId) {
2638
2639
  const crew = await findCrewPane(runtime, workspaceId, project, name);
@@ -2699,6 +2700,12 @@ var init_crew_spawn = __esm({
2699
2700
  }
2700
2701
  });
2701
2702
 
2703
+ // packages/core/dist/lifecycle-source.js
2704
+ var init_lifecycle_source = __esm({
2705
+ "packages/core/dist/lifecycle-source.js"() {
2706
+ }
2707
+ });
2708
+
2702
2709
  // packages/core/dist/index.js
2703
2710
  var init_dist2 = __esm({
2704
2711
  "packages/core/dist/index.js"() {
@@ -2731,6 +2738,7 @@ var init_dist2 = __esm({
2731
2738
  init_launch_workspace();
2732
2739
  init_side_session();
2733
2740
  init_crew_spawn();
2741
+ init_lifecycle_source();
2734
2742
  }
2735
2743
  });
2736
2744
 
@@ -2999,8 +3007,14 @@ function createCmuxDriver() {
2999
3007
  }
3000
3008
  },
3001
3009
  async sendToPane(pane, message) {
3002
- await cmux(["send", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, sanitizeForCmuxSend(message)]);
3003
- await cmux(["send-key", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, "Enter"]);
3010
+ await this.pasteToPane(pane, message);
3011
+ await this.sendKeyToPane(pane, "Enter");
3012
+ },
3013
+ async pasteToPane(pane, text) {
3014
+ await cmux(["send", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, sanitizeForCmuxSend(text)]);
3015
+ },
3016
+ async sendKeyToPane(pane, key) {
3017
+ await cmux(["send-key", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, key]);
3004
3018
  },
3005
3019
  async readPaneScreen(pane) {
3006
3020
  try {
@@ -3554,8 +3568,357 @@ var init_daemon_cmux = __esm({
3554
3568
  }
3555
3569
  });
3556
3570
 
3571
+ // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
3572
+ import { join as join13 } from "path";
3573
+ import { homedir as homedir8 } from "os";
3574
+ import { watch, readdirSync as readdirSync3, readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3575
+ function parseLifecycleState(s) {
3576
+ if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
3577
+ return s;
3578
+ }
3579
+ return "unknown";
3580
+ }
3581
+ function defaultIsPidAlive(pid) {
3582
+ try {
3583
+ process.kill(pid, 0);
3584
+ return true;
3585
+ } catch {
3586
+ return false;
3587
+ }
3588
+ }
3589
+ function defaultListFiles(dir) {
3590
+ try {
3591
+ return readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
3592
+ } catch {
3593
+ return [];
3594
+ }
3595
+ }
3596
+ function defaultReadFile(path29) {
3597
+ try {
3598
+ return readFileSync8(path29, "utf-8");
3599
+ } catch {
3600
+ return void 0;
3601
+ }
3602
+ }
3603
+ function defaultWatchDir(dir, cb) {
3604
+ const w = watch(dir, (_event, filename) => {
3605
+ if (typeof filename === "string" && filename.endsWith("-hook-sessions.json")) {
3606
+ cb();
3607
+ }
3608
+ });
3609
+ return () => w.close();
3610
+ }
3611
+ var CmuxStoreSource;
3612
+ var init_cmux_store_source = __esm({
3613
+ "packages/workspaces/dist/cmux-daemon/cmux-store-source.js"() {
3614
+ CmuxStoreSource = class {
3615
+ name = "cmux-store";
3616
+ stateDir;
3617
+ debounceMs;
3618
+ isPidAlive;
3619
+ listFiles;
3620
+ readFile;
3621
+ fileExists;
3622
+ watchDir;
3623
+ scheduleTimer;
3624
+ cancelTimer;
3625
+ log;
3626
+ deps;
3627
+ stopWatcher;
3628
+ debounceTimer;
3629
+ /** taskId → last reported snapshot (for snapshot() liveness floor). */
3630
+ cache = /* @__PURE__ */ new Map();
3631
+ constructor(opts = {}) {
3632
+ this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join13(homedir8(), ".cmuxterm");
3633
+ this.debounceMs = opts.debounceMs ?? 50;
3634
+ this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
3635
+ this.listFiles = opts.listFiles ?? defaultListFiles;
3636
+ this.readFile = opts.readFile ?? defaultReadFile;
3637
+ this.fileExists = opts.fileExists ?? existsSync10;
3638
+ this.watchDir = opts.watchDir ?? defaultWatchDir;
3639
+ this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
3640
+ this.cancelTimer = opts.cancelTimer ?? clearTimeout;
3641
+ this.log = opts.log ?? (() => {
3642
+ });
3643
+ }
3644
+ start(deps) {
3645
+ this.deps = deps;
3646
+ this.scan();
3647
+ try {
3648
+ this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced());
3649
+ } catch (e) {
3650
+ this.log(`cmux-store: failed to watch ${this.stateDir}: ${e.message}`);
3651
+ }
3652
+ }
3653
+ stop() {
3654
+ if (this.debounceTimer !== void 0) {
3655
+ this.cancelTimer(this.debounceTimer);
3656
+ this.debounceTimer = void 0;
3657
+ }
3658
+ this.stopWatcher?.();
3659
+ this.stopWatcher = void 0;
3660
+ this.deps = void 0;
3661
+ this.cache.clear();
3662
+ }
3663
+ /** Returns the last-reported snapshot for a known crew (liveness floor). */
3664
+ snapshot(taskId) {
3665
+ return this.cache.get(taskId);
3666
+ }
3667
+ // ── private ─────────────────────────────────────────────────────────────────
3668
+ scheduleDebounced() {
3669
+ if (this.debounceTimer !== void 0) {
3670
+ this.cancelTimer(this.debounceTimer);
3671
+ }
3672
+ this.debounceTimer = this.scheduleTimer(() => {
3673
+ this.debounceTimer = void 0;
3674
+ this.scan();
3675
+ }, this.debounceMs);
3676
+ }
3677
+ scan() {
3678
+ if (!this.deps)
3679
+ return;
3680
+ for (const filename of this.listFiles(this.stateDir)) {
3681
+ this.scanFile(filename);
3682
+ }
3683
+ }
3684
+ scanFile(filename) {
3685
+ const deps = this.deps;
3686
+ const filePath = join13(this.stateDir, filename);
3687
+ const lockPath = `${filePath}.lock`;
3688
+ if (this.fileExists(lockPath)) {
3689
+ this.log(`cmux-store: skipping ${filename} (locked)`);
3690
+ return;
3691
+ }
3692
+ const raw = this.readFile(filePath);
3693
+ if (!raw)
3694
+ return;
3695
+ let parsed;
3696
+ try {
3697
+ parsed = JSON.parse(raw);
3698
+ } catch {
3699
+ this.log(`cmux-store: failed to parse ${filename}`);
3700
+ return;
3701
+ }
3702
+ for (const session of Object.values(parsed.sessions ?? {})) {
3703
+ this.processSession(session, deps);
3704
+ }
3705
+ }
3706
+ processSession(session, deps) {
3707
+ if (!session.sessionId || !session.cwd || typeof session.pid !== "number")
3708
+ return;
3709
+ const hint = {
3710
+ cwd: session.cwd,
3711
+ pid: session.pid,
3712
+ sessionId: session.sessionId
3713
+ };
3714
+ const resolved = deps.resolve(hint);
3715
+ if (!resolved)
3716
+ return;
3717
+ let alive = this.isPidAlive(session.pid);
3718
+ if (!alive && session.isRestorable === true && session.agentLifecycle === "idle") {
3719
+ alive = true;
3720
+ }
3721
+ const snap = {
3722
+ taskId: resolved.id,
3723
+ state: parseLifecycleState(session.agentLifecycle),
3724
+ alive,
3725
+ // "agent": the store carries the agent's own reported lifecycle state,
3726
+ // not a scan inference. needsInput from the store is authoritative.
3727
+ origin: "agent",
3728
+ at: Math.floor((session.updatedAt ?? 0) * 1e3),
3729
+ pid: session.pid,
3730
+ ...session.lastBody ? { detail: { note: session.lastBody } } : {}
3731
+ };
3732
+ this.cache.set(resolved.id, snap);
3733
+ deps.report(snap);
3734
+ }
3735
+ };
3736
+ }
3737
+ });
3738
+
3739
+ // packages/workspaces/dist/native-hooks/native-hook-source.js
3740
+ import { join as join14 } from "path";
3741
+ import { homedir as homedir9 } from "os";
3742
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
3743
+ function installClaudeHooks(opts = {}) {
3744
+ const settingsPath = opts.settingsPath ?? join14(homedir9(), ".claude", "settings.json");
3745
+ const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
3746
+ const readFile6 = opts.readFile ?? defaultReadFile2;
3747
+ const writeFile5 = opts.writeFile ?? defaultWriteFile;
3748
+ const log = opts.log ?? (() => {
3749
+ });
3750
+ let settings = {};
3751
+ const raw = readFile6(settingsPath);
3752
+ if (raw) {
3753
+ try {
3754
+ settings = JSON.parse(raw);
3755
+ } catch {
3756
+ log(`native-hook: failed to parse ${settingsPath} \u2014 hooks section will be reset`);
3757
+ }
3758
+ }
3759
+ if (typeof settings.hooks !== "object" || settings.hooks === null || Array.isArray(settings.hooks)) {
3760
+ settings.hooks = {};
3761
+ }
3762
+ const hooks = settings.hooks;
3763
+ let changed = false;
3764
+ for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
3765
+ if (!Array.isArray(hooks[eventName])) {
3766
+ hooks[eventName] = [];
3767
+ }
3768
+ const entries = hooks[eventName];
3769
+ const command = `${hookCmd} claude ${sub}`;
3770
+ const hookMatcher = matcher ?? "";
3771
+ const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
3772
+ if (!alreadyPresent) {
3773
+ entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
3774
+ changed = true;
3775
+ }
3776
+ }
3777
+ if (changed) {
3778
+ writeFile5(settingsPath, JSON.stringify(settings, null, 2));
3779
+ }
3780
+ return settingsPath;
3781
+ }
3782
+ function mapSubToLifecycle(sub) {
3783
+ switch (sub) {
3784
+ case "session-start":
3785
+ return "running";
3786
+ case "prompt-submit":
3787
+ return "running";
3788
+ case "pre-tool-use":
3789
+ return "running";
3790
+ case "stop":
3791
+ return "idle";
3792
+ case "notification":
3793
+ return "needsInput";
3794
+ case "ask-question":
3795
+ return "needsInput";
3796
+ case "session-end":
3797
+ return "session-end";
3798
+ default:
3799
+ return null;
3800
+ }
3801
+ }
3802
+ function extractDetail(sub, payload) {
3803
+ if (!payload || typeof payload !== "object")
3804
+ return void 0;
3805
+ const p = payload;
3806
+ if (sub === "notification") {
3807
+ const note = typeof p.message === "string" ? p.message : void 0;
3808
+ return note ? { note } : void 0;
3809
+ }
3810
+ if (sub === "pre-tool-use") {
3811
+ const tool = typeof p.tool_name === "string" ? p.tool_name : void 0;
3812
+ return tool ? { tool } : void 0;
3813
+ }
3814
+ return void 0;
3815
+ }
3816
+ function defaultReadFile2(path29) {
3817
+ try {
3818
+ return readFileSync9(path29, "utf-8");
3819
+ } catch {
3820
+ return void 0;
3821
+ }
3822
+ }
3823
+ function defaultWriteFile(path29, content) {
3824
+ mkdirSync6(path29.replace(/\/[^/]+$/, ""), { recursive: true });
3825
+ writeFileSync7(path29, content, "utf-8");
3826
+ }
3827
+ var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
3828
+ var init_native_hook_source = __esm({
3829
+ "packages/workspaces/dist/native-hooks/native-hook-source.js"() {
3830
+ CLAUDE_HOOK_EVENTS = [
3831
+ ["SessionStart", "session-start"],
3832
+ ["UserPromptSubmit", "prompt-submit"],
3833
+ ["PreToolUse", "pre-tool-use"],
3834
+ ["Stop", "stop"],
3835
+ ["Notification", "notification"],
3836
+ ["PreToolUse", "ask-question", "AskUserQuestion"],
3837
+ ["SessionEnd", "session-end"]
3838
+ ];
3839
+ DEFAULT_HOOK_CMD = "squadrant hooks";
3840
+ NativeHookSource = class {
3841
+ name = "native-hook";
3842
+ hookInstall;
3843
+ log;
3844
+ deps;
3845
+ /** taskId → last-reported snapshot, for snapshot() liveness floor. */
3846
+ cache = /* @__PURE__ */ new Map();
3847
+ constructor(opts = {}) {
3848
+ this.hookInstall = opts.hookInstall ?? {};
3849
+ this.log = opts.log ?? (() => {
3850
+ });
3851
+ }
3852
+ start(deps) {
3853
+ this.deps = deps;
3854
+ }
3855
+ stop() {
3856
+ this.deps = void 0;
3857
+ this.cache.clear();
3858
+ }
3859
+ /** Returns the last-reported snapshot for a known crew (liveness floor poll). */
3860
+ snapshot(taskId) {
3861
+ return this.cache.get(taskId);
3862
+ }
3863
+ /**
3864
+ * Install squadrant-owned hooks into ~/.claude/settings.json.
3865
+ * Idempotent — safe to call on every project init or crew spawn.
3866
+ * Returns the path to the settings file.
3867
+ */
3868
+ install() {
3869
+ return installClaudeHooks(this.hookInstall);
3870
+ }
3871
+ /**
3872
+ * Receive a lifecycle hook event from the daemon and report a LifecycleSnapshot.
3873
+ *
3874
+ * The daemon's 'squadrant hooks claude <sub>' CLI subcommand calls this after
3875
+ * reading SQUADRANT_CREW_TASK_ID from the hook's process environment — the only
3876
+ * collision-proof correlation key (blueprint §2.2 priority 1).
3877
+ *
3878
+ * @param sub Sub-event alias: "session-start" | "prompt-submit" | "stop" | …
3879
+ * @param taskId SQUADRANT_CREW_TASK_ID extracted from the hook process env.
3880
+ * @param pid Optional: OS pid from the hook's process env or argv.
3881
+ * @param payload Optional: parsed JSON payload from hook stdin (best-effort detail).
3882
+ */
3883
+ handleHook(sub, taskId, pid, payload) {
3884
+ if (!this.deps)
3885
+ return;
3886
+ const mapped = mapSubToLifecycle(sub);
3887
+ if (mapped === null) {
3888
+ this.log(`native-hook: unknown sub '${sub}' for task ${taskId} \u2014 ignored`);
3889
+ return;
3890
+ }
3891
+ const isSessionEnd = mapped === "session-end";
3892
+ const state = isSessionEnd ? "unknown" : mapped;
3893
+ const detail = extractDetail(sub, payload);
3894
+ const snap = {
3895
+ taskId,
3896
+ state,
3897
+ alive: !isSessionEnd,
3898
+ origin: "agent",
3899
+ at: Date.now(),
3900
+ ...pid !== void 0 ? { pid } : {},
3901
+ ...detail ? { detail } : {}
3902
+ };
3903
+ this.cache.set(taskId, snap);
3904
+ this.deps.report(snap);
3905
+ }
3906
+ };
3907
+ }
3908
+ });
3909
+
3557
3910
  // packages/workspaces/dist/crew-pane.js
3558
3911
  import net from "net";
3912
+ async function settleInputBox(runtime, pane) {
3913
+ let prev = await runtime.readPaneScreen(pane) ?? "";
3914
+ for (let i = 0; i < SETTLE_MAX_POLLS; i++) {
3915
+ await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));
3916
+ const cur = await runtime.readPaneScreen(pane) ?? "";
3917
+ if (cur === prev)
3918
+ return;
3919
+ prev = cur;
3920
+ }
3921
+ }
3559
3922
  function getFreePort() {
3560
3923
  return new Promise((resolve3, reject) => {
3561
3924
  const srv = net.createServer();
@@ -3589,6 +3952,23 @@ async function resolveCaptainWorkspace(project) {
3589
3952
  }
3590
3953
  return { runtime, workspaceId: captain.id };
3591
3954
  }
3955
+ async function confirmedSendToPane(runtime, pane, message) {
3956
+ const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
3957
+ await runtime.pasteToPane(pane, message);
3958
+ await settleInputBox(runtime, pane);
3959
+ await runtime.sendKeyToPane(pane, "Enter");
3960
+ for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
3961
+ await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
3962
+ const afterScreen = await runtime.readPaneScreen(pane) ?? "";
3963
+ const draft = parseDraftFromScreen(afterScreen);
3964
+ if (draft === "")
3965
+ return;
3966
+ if (draft === null && afterScreen !== preSendScreen)
3967
+ return;
3968
+ await settleInputBox(runtime, pane);
3969
+ await runtime.sendKeyToPane(pane, "Enter");
3970
+ }
3971
+ }
3592
3972
  async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acceptanceConfig) {
3593
3973
  await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));
3594
3974
  const maxPolls = Math.floor((SEND_FIRST_TURN_TIMEOUT_MS - SEND_FIRST_TURN_FLOOR_MS) / POLL_INTERVAL_MS);
@@ -3604,8 +3984,8 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
3604
3984
  }
3605
3985
  }
3606
3986
  const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
3607
- await runtime.sendToPane(pane, task);
3608
3987
  if (acceptanceConfig?.splashMarker) {
3988
+ await runtime.sendToPane(pane, task);
3609
3989
  for (let check2 = 0; check2 < SPLASH_MAX_CHECKS; check2++) {
3610
3990
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
3611
3991
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
@@ -3616,21 +3996,25 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
3616
3996
  await runtime.sendToPane(pane, task);
3617
3997
  }
3618
3998
  }
3619
- } else {
3620
- const retryLimit = acceptanceConfig?.retryLimit ?? 2;
3621
- for (let attempt = 0; attempt < retryLimit; attempt++) {
3622
- await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
3623
- const afterScreen = await runtime.readPaneScreen(pane) ?? "";
3624
- if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
3625
- return;
3626
- }
3627
- if (attempt < retryLimit - 1) {
3628
- await runtime.sendToPane(pane, task);
3629
- }
3630
- }
3999
+ return;
4000
+ }
4001
+ await runtime.pasteToPane(pane, task);
4002
+ await settleInputBox(runtime, pane);
4003
+ await runtime.sendKeyToPane(pane, "Enter");
4004
+ const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;
4005
+ for (let attempt = 0; attempt < retryLimit; attempt++) {
4006
+ await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
4007
+ const afterScreen = await runtime.readPaneScreen(pane) ?? "";
4008
+ const draft = parseDraftFromScreen(afterScreen);
4009
+ if (draft === "")
4010
+ return;
4011
+ if (draft === null && afterScreen !== preSendScreen)
4012
+ return;
4013
+ await settleInputBox(runtime, pane);
4014
+ await runtime.sendKeyToPane(pane, "Enter");
3631
4015
  }
3632
4016
  }
3633
- 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;
4017
+ 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;
3634
4018
  var init_crew_pane = __esm({
3635
4019
  "packages/workspaces/dist/crew-pane.js"() {
3636
4020
  init_dist();
@@ -3643,6 +4027,9 @@ var init_crew_pane = __esm({
3643
4027
  POST_SEND_CHECK_MS = 750;
3644
4028
  SPLASH_MAX_CHECKS = 20;
3645
4029
  SPLASH_RESEND_EVERY_N = 4;
4030
+ SETTLE_POLL_MS = 400;
4031
+ SETTLE_MAX_POLLS = 8;
4032
+ SUBMIT_RETRY_LIMIT = 4;
3646
4033
  }
3647
4034
  });
3648
4035
 
@@ -3651,20 +4038,25 @@ var dist_exports = {};
3651
4038
  __export(dist_exports, {
3652
4039
  CMUX_TIMEOUT: () => CMUX_TIMEOUT,
3653
4040
  CmuxEventsBridge: () => CmuxEventsBridge,
4041
+ CmuxStoreSource: () => CmuxStoreSource,
3654
4042
  DaemonCmux: () => DaemonCmux,
4043
+ NativeHookSource: () => NativeHookSource,
3655
4044
  NotifierRegistry: () => NotifierRegistry,
3656
4045
  RuntimeRegistry: () => RuntimeRegistry,
3657
4046
  WorkspaceRegistry: () => WorkspaceRegistry,
3658
4047
  classifyStartupSurface: () => classifyStartupSurface,
3659
4048
  cmuxLocal: () => cmuxLocal,
4049
+ confirmedSendToPane: () => confirmedSendToPane,
3660
4050
  createCmuxDriver: () => createCmuxDriver,
3661
4051
  createCmuxNotifier: () => createCmuxNotifier,
3662
4052
  createObsidianDriver: () => createObsidianDriver,
3663
4053
  deriveRunState: () => deriveRunState,
3664
4054
  findCrew: () => findCrew,
3665
4055
  getFreePort: () => getFreePort,
4056
+ installClaudeHooks: () => installClaudeHooks,
3666
4057
  isInsideCmux: () => isInsideCmux,
3667
4058
  listProjectCrews: () => listProjectCrews,
4059
+ mapSubToLifecycle: () => mapSubToLifecycle,
3668
4060
  resolveCaptainWorkspace: () => resolveCaptainWorkspace,
3669
4061
  sendFirstTurnWhenReady: () => sendFirstTurnWhenReady
3670
4062
  });
@@ -3675,6 +4067,8 @@ var init_dist3 = __esm({
3675
4067
  init_workspaces2();
3676
4068
  init_events_bridge();
3677
4069
  init_daemon_cmux();
4070
+ init_cmux_store_source();
4071
+ init_native_hook_source();
3678
4072
  init_crew_pane();
3679
4073
  }
3680
4074
  });
@@ -4604,13 +4998,99 @@ var init_app_server_client = __esm({
4604
4998
  }
4605
4999
  });
4606
5000
 
5001
+ // packages/agents/dist/codex/codex-app-server-source.js
5002
+ function toSnapshot(ev) {
5003
+ const now = Date.now();
5004
+ switch (ev.type) {
5005
+ // ── running: a turn is live ──────────────────────────────────────────────
5006
+ case "task.started":
5007
+ case "task.reattached":
5008
+ case "task.turn.started":
5009
+ case "task.delta":
5010
+ case "task.progress":
5011
+ return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
5012
+ // ── idle: turn ended, crew alive, awaiting next input ────────────────────
5013
+ // task.failed: the turn ended with an error, but the crew process is alive.
5014
+ // task.session.ended: process is gone (alive:false) — signals liveness loss.
5015
+ case "task.turn.completed":
5016
+ return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
5017
+ case "task.failed":
5018
+ return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
5019
+ case "task.session.ended":
5020
+ return { taskId: ev.id, state: "idle", alive: false, origin: "agent", at: now };
5021
+ // ── needsInput: crew is blocked on a human ───────────────────────────────
5022
+ case "task.approval.requested":
5023
+ return {
5024
+ taskId: ev.id,
5025
+ state: "needsInput",
5026
+ alive: true,
5027
+ origin: "agent",
5028
+ at: now,
5029
+ detail: { note: ev.question, reason: ev.kind }
5030
+ };
5031
+ case "task.input.requested":
5032
+ return {
5033
+ taskId: ev.id,
5034
+ state: "needsInput",
5035
+ alive: true,
5036
+ origin: "agent",
5037
+ at: now,
5038
+ detail: { note: ev.question }
5039
+ };
5040
+ // ── terminal / notify-only — ignored ────────────────────────────────────
5041
+ // task.done, task.blocked, task.cancelled: terminal state from crew signal only.
5042
+ // task.session, task.stalled, task.quiet, task.idle, task.timeout, etc.: no-op.
5043
+ default:
5044
+ return null;
5045
+ }
5046
+ }
5047
+ var CodexAppServerSource;
5048
+ var init_codex_app_server_source = __esm({
5049
+ "packages/agents/dist/codex/codex-app-server-source.js"() {
5050
+ CodexAppServerSource = class {
5051
+ name = "codex-appserver";
5052
+ deps;
5053
+ /** taskId → last reported snapshot (for snapshot() liveness floor). */
5054
+ cache = /* @__PURE__ */ new Map();
5055
+ start(deps) {
5056
+ this.deps = deps;
5057
+ }
5058
+ stop() {
5059
+ this.deps = void 0;
5060
+ this.cache.clear();
5061
+ }
5062
+ /** Returns the last-reported snapshot for a known crew (liveness floor). */
5063
+ snapshot(taskId) {
5064
+ return this.cache.get(taskId);
5065
+ }
5066
+ /**
5067
+ * Feed a ControlEvent from CodexInteractiveDriver into this source.
5068
+ * The daemon wires: emit = (ev) => { source.observe(ev); handle(ev); }
5069
+ *
5070
+ * All events that carry lifecycle meaning for a codex crew are mapped to a
5071
+ * LifecycleSnapshot and reported. Events that are terminal signals (task.done,
5072
+ * task.cancelled, task.blocked) or notify-only (task.stalled, task.quiet, etc.)
5073
+ * are ignored — terminal state still comes exclusively from `squadrant crew signal`
5074
+ * (anti-#2576 invariant).
5075
+ */
5076
+ observe(ev) {
5077
+ const snap = toSnapshot(ev);
5078
+ if (!snap || !this.deps)
5079
+ return;
5080
+ this.cache.set(snap.taskId, snap);
5081
+ this.deps.report(snap);
5082
+ }
5083
+ };
5084
+ }
5085
+ });
5086
+
4607
5087
  // packages/agents/dist/codex/config.js
4608
5088
  import { readFile as readFile5 } from "fs/promises";
4609
- import { homedir as homedir9 } from "os";
4610
- import { join as join14 } from "path";
5089
+ import { homedir as homedir11 } from "os";
5090
+ import { join as join16 } from "path";
4611
5091
  async function resolveCodexModel() {
4612
- const home = process.env["CODEX_HOME"] ?? join14(homedir9(), ".codex");
4613
- const configPath = join14(home, "config.toml");
5092
+ const home = process.env["CODEX_HOME"] ?? join16(homedir11(), ".codex");
5093
+ const configPath = join16(home, "config.toml");
4614
5094
  let text;
4615
5095
  try {
4616
5096
  text = await readFile5(configPath, "utf8");
@@ -5131,9 +5611,9 @@ var init_sse_bridge = __esm({
5131
5611
 
5132
5612
  // packages/agents/dist/interactive/claude.js
5133
5613
  import { execSync as execSync7 } from "child_process";
5134
- import { readFileSync as readFileSync8 } from "fs";
5135
- import { homedir as homedir10 } from "os";
5136
- import { join as join15 } from "path";
5614
+ import { readFileSync as readFileSync10 } from "fs";
5615
+ import { homedir as homedir12 } from "os";
5616
+ import { join as join17 } from "path";
5137
5617
  function probeClaudeSettingsFlag() {
5138
5618
  try {
5139
5619
  const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
@@ -5184,11 +5664,11 @@ function deriveTranscriptPath(sessionId, cwd) {
5184
5664
  if (!sessionId || !cwd)
5185
5665
  return null;
5186
5666
  const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5187
- return join15(homedir10(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
5667
+ return join17(homedir12(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
5188
5668
  }
5189
5669
  function readLastAssistantText(transcriptPath) {
5190
5670
  try {
5191
- const raw = readFileSync8(transcriptPath, "utf-8");
5671
+ const raw = readFileSync10(transcriptPath, "utf-8");
5192
5672
  const lines = raw.split(/\r?\n/);
5193
5673
  for (let i = lines.length - 1; i >= 0; i--) {
5194
5674
  const line = lines[i].trim();
@@ -5305,6 +5785,18 @@ function classifyPaneTail(tail) {
5305
5785
  }
5306
5786
  return { kind: "approval", text: "Crew is awaiting permission approval." };
5307
5787
  }
5788
+ const hasPickerFooter = cleaned.some((c) => c != null && PICKER_FOOTER_RE.test(c));
5789
+ if (options.length >= 2 && hasPickerFooter) {
5790
+ const firstOptCi = options[0].ci;
5791
+ for (let i = firstOptCi - 1; i >= 0; i--) {
5792
+ const c = cleaned[i];
5793
+ if (c == null)
5794
+ continue;
5795
+ if (c.endsWith("?"))
5796
+ return { kind: "question", text: c };
5797
+ }
5798
+ return { kind: "question", text: "Crew is awaiting a choice." };
5799
+ }
5308
5800
  const region = cleaned.filter((c) => c != null).join("\n");
5309
5801
  const q = detectTrailingQuestion(region);
5310
5802
  if (q)
@@ -5332,7 +5824,7 @@ function stripChrome(raw) {
5332
5824
  return null;
5333
5825
  return trimmed;
5334
5826
  }
5335
- var ERROR_BANNER_RE, OPTION_RE, PURE_CHROME_RE, STATUS_LINE_RE;
5827
+ var ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE;
5336
5828
  var init_pane_classifier = __esm({
5337
5829
  "packages/agents/dist/interactive/pane-classifier.js"() {
5338
5830
  init_claude2();
@@ -5346,6 +5838,7 @@ var init_pane_classifier = __esm({
5346
5838
  /\bmaximum\s+retries\b/i
5347
5839
  ];
5348
5840
  OPTION_RE = /^[❯>›]?\s*(\d+)\.\s+(.*\S)\s*$/;
5841
+ PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
5349
5842
  PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
5350
5843
  STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
5351
5844
  }
@@ -5576,6 +6069,7 @@ var dist_exports2 = {};
5576
6069
  __export(dist_exports2, {
5577
6070
  AppServerClient: () => AppServerClient,
5578
6071
  CapabilityRegistry: () => CapabilityRegistry,
6072
+ CodexAppServerSource: () => CodexAppServerSource,
5579
6073
  CodexInteractiveDriver: () => CodexInteractiveDriver,
5580
6074
  HEADLESS_ERROR_TAIL: () => HEADLESS_ERROR_TAIL,
5581
6075
  MARKER_END: () => MARKER_END,
@@ -5614,6 +6108,7 @@ var init_dist4 = __esm({
5614
6108
  init_drivers();
5615
6109
  init_projection2();
5616
6110
  init_app_server_client();
6111
+ init_codex_app_server_source();
5617
6112
  init_driver();
5618
6113
  init_normalize();
5619
6114
  init_sse_bridge();
@@ -5630,11 +6125,11 @@ var init_dist4 = __esm({
5630
6125
  // packages/cli/src/index.ts
5631
6126
  init_dist();
5632
6127
  init_dist2();
5633
- import { Command as Command28 } from "commander";
5634
- import { readFileSync as readFileSync11, existsSync as existsSync11, writeFileSync as writeFileSync9 } from "fs";
6128
+ import { Command as Command29 } from "commander";
6129
+ import { readFileSync as readFileSync13, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "fs";
5635
6130
  import { fileURLToPath as fileURLToPath6 } from "url";
5636
- import { dirname as dirname7, join as join23 } from "path";
5637
- import { homedir as homedir15 } from "os";
6131
+ import { dirname as dirname7, join as join26 } from "path";
6132
+ import { homedir as homedir18 } from "os";
5638
6133
 
5639
6134
  // packages/cli/src/commands/doctor.ts
5640
6135
  init_dist();
@@ -5651,10 +6146,10 @@ import chalk3 from "chalk";
5651
6146
  // packages/cli/src/commands/health-view.ts
5652
6147
  init_dist2();
5653
6148
  init_dist2();
5654
- import { homedir as homedir8 } from "os";
5655
- import { join as join13 } from "path";
6149
+ import { homedir as homedir10 } from "os";
6150
+ import { join as join15 } from "path";
5656
6151
  import chalk2 from "chalk";
5657
- var SOCK = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
6152
+ var SOCK = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
5658
6153
  async function queryHealth(project) {
5659
6154
  try {
5660
6155
  const reply = await sendRequest(SOCK, { kind: "health", project });
@@ -6488,9 +6983,9 @@ init_dist4();
6488
6983
  import { Command as Command8 } from "commander";
6489
6984
  import { createConnection as createConnection3 } from "net";
6490
6985
  import { randomUUID as randomUUID4 } from "crypto";
6491
- import { homedir as homedir12 } from "os";
6492
- import { join as join17 } from "path";
6493
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
6986
+ import { homedir as homedir14 } from "os";
6987
+ import { join as join19 } from "path";
6988
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
6494
6989
 
6495
6990
  // packages/cli/src/commands/crew-output.ts
6496
6991
  function tailLines(text, maxLines = 40, maxBytes = 4096) {
@@ -6548,11 +7043,11 @@ init_dist2();
6548
7043
  import { Command as Command6 } from "commander";
6549
7044
  import chalk8 from "chalk";
6550
7045
  import { createConnection as createConnection2 } from "net";
6551
- import { homedir as homedir11 } from "os";
6552
- import { join as join16 } from "path";
7046
+ import { homedir as homedir13 } from "os";
7047
+ import { join as join18 } from "path";
6553
7048
  import { createInterface } from "readline";
6554
7049
  function socketPath() {
6555
- return process.env.SQUADRANTD_SOCK ?? join16(homedir11(), ".config", "squadrant", "squadrant.sock");
7050
+ return process.env.SQUADRANTD_SOCK ?? join18(homedir13(), ".config", "squadrant", "squadrant.sock");
6556
7051
  }
6557
7052
  function rule(width, ch = "\u2500") {
6558
7053
  return ch.repeat(Math.max(0, width));
@@ -6788,7 +7283,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
6788
7283
  });
6789
7284
 
6790
7285
  // packages/cli/src/commands/crew-control.ts
6791
- var SOCK2 = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
7286
+ var SOCK2 = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
6792
7287
  var CODEX_FIRST_TURN_DELAY_MS = 1500;
6793
7288
  async function sendCodexFirstTurn(taskId, text) {
6794
7289
  await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
@@ -6893,10 +7388,10 @@ function buildSignalRequest(signal, o) {
6893
7388
  return { kind: "event", project, event };
6894
7389
  }
6895
7390
  function defaultWriteResult(id, payload) {
6896
- const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
6897
- mkdirSync6(dir, { recursive: true });
6898
- const file = join17(dir, `${id}.txt`);
6899
- writeFileSync7(file, payload);
7391
+ const dir = join19(homedir14(), ".config", "squadrant", "state", "_results");
7392
+ mkdirSync7(dir, { recursive: true });
7393
+ const file = join19(dir, `${id}.txt`);
7394
+ writeFileSync8(file, payload);
6900
7395
  return file;
6901
7396
  }
6902
7397
  function addControlPlaneCrewCommands(crew) {
@@ -6995,8 +7490,8 @@ addControlPlaneCrewCommands(crewControlCommand);
6995
7490
 
6996
7491
  // packages/cli/src/lib/per-crew-settings.ts
6997
7492
  init_dist4();
6998
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
6999
- import { join as join18 } from "path";
7493
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
7494
+ import { join as join20 } from "path";
7000
7495
  var CREW_PERMISSION_ALLOWLIST = [
7001
7496
  // git — read + safe mutations (reset/clean/config intentionally excluded)
7002
7497
  "Bash(git status:*)",
@@ -7087,24 +7582,24 @@ function mergeCrewPermissions(settings) {
7087
7582
  return next;
7088
7583
  }
7089
7584
  function writePerCrewSettingsLocal(o) {
7090
- const dir = join18(o.projectCwd, ".claude");
7091
- mkdirSync7(dir, { recursive: true });
7092
- const file = join18(dir, "settings.local.json");
7585
+ const dir = join20(o.projectCwd, ".claude");
7586
+ mkdirSync8(dir, { recursive: true });
7587
+ const file = join20(dir, "settings.local.json");
7093
7588
  let existing = {};
7094
7589
  try {
7095
- const raw = healStaleCockpitRefs(readFileSync9(file, "utf-8"));
7590
+ const raw = healStaleCockpitRefs(readFileSync11(file, "utf-8"));
7096
7591
  existing = JSON.parse(raw);
7097
7592
  } catch {
7098
7593
  }
7099
7594
  const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
7100
7595
  const merged = mergeCrewPermissions(withHooks);
7101
- writeFileSync8(file, JSON.stringify(merged, null, 2));
7596
+ writeFileSync9(file, JSON.stringify(merged, null, 2));
7102
7597
  return file;
7103
7598
  }
7104
7599
  function writePerCrewOpencodeConfig(o) {
7105
- const dir = join18(o.stateRoot, o.project, o.taskId);
7106
- mkdirSync7(dir, { recursive: true });
7107
- const file = join18(dir, "opencode.json");
7600
+ const dir = join20(o.stateRoot, o.project, o.taskId);
7601
+ mkdirSync8(dir, { recursive: true });
7602
+ const file = join20(dir, "opencode.json");
7108
7603
  const config = {
7109
7604
  permission: {
7110
7605
  read: "allow",
@@ -7119,7 +7614,7 @@ function writePerCrewOpencodeConfig(o) {
7119
7614
  external_directory: { "**": "allow" }
7120
7615
  }
7121
7616
  };
7122
- writeFileSync8(file, JSON.stringify(config, null, 2));
7617
+ writeFileSync9(file, JSON.stringify(config, null, 2));
7123
7618
  return file;
7124
7619
  }
7125
7620
 
@@ -7160,7 +7655,10 @@ async function runCrewSend2(project, name, message) {
7160
7655
  listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
7161
7656
  emitEvent: async (p, event) => {
7162
7657
  await squadrantdCall({ kind: "event", project: p, event });
7163
- }
7658
+ },
7659
+ // #448: use paste-settle-Enter confirmation for follow-up sends (same guard
7660
+ // as first-turn #447) so large messages don't strand in paste mode.
7661
+ sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg)
7164
7662
  });
7165
7663
  }
7166
7664
  async function runCrewRead2(project, name) {
@@ -7409,8 +7907,8 @@ init_dist();
7409
7907
  init_dist3();
7410
7908
  import { Command as Command11 } from "commander";
7411
7909
  import { execSync as execSync10 } from "child_process";
7412
- import { homedir as homedir14 } from "os";
7413
- import { join as join20 } from "path";
7910
+ import { homedir as homedir16 } from "os";
7911
+ import { join as join22 } from "path";
7414
7912
  import chalk12 from "chalk";
7415
7913
 
7416
7914
  // packages/web/dist/read-status.js
@@ -7606,9 +8104,9 @@ function mergeSnapshot(daemon, external, now) {
7606
8104
  // packages/web/dist/probes.js
7607
8105
  init_dist();
7608
8106
  init_dist();
7609
- import { join as join19 } from "path";
7610
- import { homedir as homedir13 } from "os";
7611
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
8107
+ import { join as join21 } from "path";
8108
+ import { homedir as homedir15 } from "os";
8109
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
7612
8110
  import { execFile as execFile3 } from "child_process";
7613
8111
  var DEFAULT_TIMEOUT_MS = 2e3;
7614
8112
  var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
@@ -7644,7 +8142,7 @@ function vaultProbe(run, dir) {
7644
8142
  return { state: "unknown", detail: "no vault configured" };
7645
8143
  if (!run.pathExists(dir))
7646
8144
  return { state: "gone", detail: "vault directory missing" };
7647
- if (!run.pathExists(join19(dir, ".obsidian")))
8145
+ if (!run.pathExists(join21(dir, ".obsidian")))
7648
8146
  return { state: "gone", detail: "no .obsidian/ (not a vault)" };
7649
8147
  return { state: "alive" };
7650
8148
  } catch {
@@ -7712,13 +8210,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
7712
8210
  const sessions = probeSessions(run);
7713
8211
  return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
7714
8212
  }
7715
- var SESSIONS_PATH = join19(homedir13(), ".config", "squadrant", "sessions.json");
8213
+ var SESSIONS_PATH = join21(homedir15(), ".config", "squadrant", "sessions.json");
7716
8214
  function onPath(cli) {
7717
8215
  const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
7718
- return dirs.some((d) => existsSync10(join19(d, cli)));
8216
+ return dirs.some((d) => existsSync11(join21(d, cli)));
7719
8217
  }
7720
8218
  function readSessionsHashes() {
7721
- const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
8219
+ const raw = JSON.parse(readFileSync12(SESSIONS_PATH, "utf-8"));
7722
8220
  const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
7723
8221
  return [...new Set(hashes)];
7724
8222
  }
@@ -7732,7 +8230,7 @@ function defaultProbeRunners() {
7732
8230
  }
7733
8231
  }),
7734
8232
  probeOnPath: async (cli) => onPath(cli),
7735
- pathExists: (p) => existsSync10(p),
8233
+ pathExists: (p) => existsSync11(p),
7736
8234
  loadConfig: () => loadConfig(),
7737
8235
  loadSessionsHashes: () => readSessionsHashes()
7738
8236
  };
@@ -8385,7 +8883,7 @@ async function startWebServer(opts) {
8385
8883
 
8386
8884
  // packages/cli/src/commands/dashboard.ts
8387
8885
  init_dist();
8388
- var SOCK3 = join20(homedir14(), ".config", "squadrant", "squadrant.sock");
8886
+ var SOCK3 = join22(homedir16(), ".config", "squadrant", "squadrant.sock");
8389
8887
  function detectCurrentWorkspace2() {
8390
8888
  const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
8391
8889
  const match = out.match(/workspace:\d+/);
@@ -8469,13 +8967,87 @@ init_dist();
8469
8967
  init_dist4();
8470
8968
  init_dist3();
8471
8969
  init_dist2();
8472
- init_dist2();
8473
8970
  import { Command as Command12 } from "commander";
8474
8971
  import { execSync as execSync11 } from "child_process";
8475
8972
  import fs20 from "fs";
8476
8973
  import path23 from "path";
8477
8974
  import os12 from "os";
8478
8975
  import chalk13 from "chalk";
8976
+
8977
+ // packages/cli/src/commands/launch-interactive.ts
8978
+ import checkbox, { Separator } from "@inquirer/checkbox";
8979
+ function getYesterday() {
8980
+ const d = /* @__PURE__ */ new Date();
8981
+ d.setDate(d.getDate() - 1);
8982
+ return d.toISOString().slice(0, 10);
8983
+ }
8984
+ function partitionByYesterday(entries, yesterday) {
8985
+ const y = [];
8986
+ const r = [];
8987
+ for (const e of entries) {
8988
+ if (e.lastLaunched === yesterday) {
8989
+ y.push(e);
8990
+ } else {
8991
+ r.push(e);
8992
+ }
8993
+ }
8994
+ return { yesterday: y, rest: r };
8995
+ }
8996
+ async function selectCaptainsInteractive(entries, yesterday = getYesterday()) {
8997
+ const { yesterday: yesterdayEntries, rest: restEntries } = partitionByYesterday(entries, yesterday);
8998
+ if (yesterdayEntries.length === 0) {
8999
+ const result2 = await checkbox({
9000
+ message: "Select captains to launch:",
9001
+ choices: entries.map((e) => ({
9002
+ name: `${e.captainName} (${e.projectName})`,
9003
+ value: e.projectName,
9004
+ checked: false
9005
+ })),
9006
+ pageSize: 20
9007
+ });
9008
+ return result2;
9009
+ }
9010
+ const initialChoices = [
9011
+ new Separator("\u2500\u2500 Opened yesterday \u2500\u2500"),
9012
+ ...yesterdayEntries.map((e) => ({
9013
+ name: `${e.captainName} (${e.projectName})`,
9014
+ value: e.projectName,
9015
+ checked: true
9016
+ })),
9017
+ new Separator(),
9018
+ { name: "Show all projects", value: "__show_all__", checked: false }
9019
+ ];
9020
+ const result = await checkbox({
9021
+ message: "Select captains to launch:",
9022
+ choices: initialChoices,
9023
+ pageSize: 20
9024
+ });
9025
+ if (result.includes("__show_all__")) {
9026
+ const allChecked = yesterdayEntries.map((e) => e.projectName);
9027
+ const result2 = await checkbox({
9028
+ message: "Select captains to launch (all projects):",
9029
+ choices: [
9030
+ new Separator("\u2500\u2500 Opened yesterday \u2500\u2500"),
9031
+ ...yesterdayEntries.map((e) => ({
9032
+ name: `${e.captainName} (${e.projectName})`,
9033
+ value: e.projectName,
9034
+ checked: allChecked.includes(e.projectName)
9035
+ })),
9036
+ ...restEntries.map((e) => ({
9037
+ name: `${e.captainName} (${e.projectName})`,
9038
+ value: e.projectName,
9039
+ checked: false
9040
+ }))
9041
+ ],
9042
+ pageSize: 30
9043
+ });
9044
+ return result2;
9045
+ }
9046
+ return result;
9047
+ }
9048
+
9049
+ // packages/cli/src/commands/launch.ts
9050
+ init_dist2();
8479
9051
  var CMUX_APP = "/Applications/cmux.app";
8480
9052
  var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
8481
9053
  var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
@@ -8559,12 +9131,43 @@ var launchCommand = new Command12("launch").description(
8559
9131
  }
8560
9132
  console.log("");
8561
9133
  } else if (!project) {
8562
- console.error(
8563
- chalk13.red(
8564
- "\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
8565
- )
8566
- );
8567
- process.exit(1);
9134
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
9135
+ console.error(
9136
+ chalk13.red(
9137
+ "\n \u2718 Specify a project name, or pass --all to launch every captain.\n For one-shot Command tasks, use `squadrant command --task <briefing|learnings-review|wiki-aggregate>`.\n"
9138
+ )
9139
+ );
9140
+ process.exit(1);
9141
+ }
9142
+ ensureCmuxReady();
9143
+ const sessions = loadSessions(SESSIONS_PATH2);
9144
+ const entries = Object.entries(config.projects).map(([name, proj]) => ({
9145
+ projectName: name,
9146
+ captainName: proj.captainName,
9147
+ lastLaunched: sessions.workspaces[proj.captainName]?.lastLaunched ?? null
9148
+ }));
9149
+ const selected = await selectCaptainsInteractive(entries);
9150
+ if (selected.length === 0) {
9151
+ console.log(chalk13.yellow("\n No captains selected.\n"));
9152
+ return;
9153
+ }
9154
+ console.log(chalk13.bold(`
9155
+ Launching ${selected.length} captain workspace(s) in parallel
9156
+ `));
9157
+ await Promise.all(selected.map(async (name) => {
9158
+ const proj = config.projects[name];
9159
+ const projPath = resolveHome(proj.path);
9160
+ const spokePath = resolveHome(proj.spokeVault);
9161
+ if (!fs20.existsSync(spokePath)) {
9162
+ const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
9163
+ await ensureSpokeLayout(spokeDriver);
9164
+ console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
9165
+ }
9166
+ console.log(chalk13.bold(`
9167
+ Captain: ${proj.captainName} (${name})`));
9168
+ await launchOne(proj.captainName, "captain", projPath, config.defaults.permissions?.captain || "auto", false, true, name);
9169
+ }));
9170
+ console.log("");
8568
9171
  } else {
8569
9172
  if (!config.projects[project]) {
8570
9173
  console.error(
@@ -9561,7 +10164,7 @@ init_dist2();
9561
10164
  import { Command as Command22 } from "commander";
9562
10165
  import fs25 from "fs";
9563
10166
  import { fileURLToPath as fileURLToPath5 } from "url";
9564
- import { dirname as dirname5, join as join21 } from "path";
10167
+ import { dirname as dirname5, join as join23 } from "path";
9565
10168
  import chalk22 from "chalk";
9566
10169
  function runConfigCheck(opts) {
9567
10170
  const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
@@ -9697,7 +10300,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
9697
10300
  }
9698
10301
  });
9699
10302
  function readPkgVersion2() {
9700
- const pkgPath = join21(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
10303
+ const pkgPath = join23(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
9701
10304
  return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
9702
10305
  }
9703
10306
 
@@ -9968,12 +10571,12 @@ var effortCommand = new Command26("effort").description("Get or set the global c
9968
10571
  // packages/cli/src/commands/telegram.ts
9969
10572
  init_dist();
9970
10573
  init_dist2();
9971
- import { join as join22, dirname as dirname6 } from "path";
10574
+ import { join as join24, dirname as dirname6 } from "path";
9972
10575
  import { emitKeypressEvents } from "readline";
9973
10576
  import { Command as Command27 } from "commander";
9974
10577
  import chalk27 from "chalk";
9975
10578
  function defaultStateRoot() {
9976
- return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
10579
+ return join24(dirname6(DEFAULT_CONFIG_PATH), "state");
9977
10580
  }
9978
10581
  async function questionMasked() {
9979
10582
  return new Promise((resolve3) => {
@@ -10294,25 +10897,88 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
10294
10897
  }
10295
10898
  });
10296
10899
 
10900
+ // packages/cli/src/commands/hooks.ts
10901
+ init_dist2();
10902
+ init_dist4();
10903
+ import { Command as Command28 } from "commander";
10904
+ import { join as join25 } from "path";
10905
+ import { homedir as homedir17 } from "os";
10906
+ var SOCK4 = join25(homedir17(), ".config", "squadrant", "squadrant.sock");
10907
+ async function sendToSock(req) {
10908
+ await sendRequest(SOCK4, req);
10909
+ }
10910
+ function mapHookSub(sub, payload, taskId) {
10911
+ switch (sub) {
10912
+ case "session-start":
10913
+ case "prompt-submit":
10914
+ case "pre-tool-use":
10915
+ return { type: "task.progress", id: taskId, note: sub };
10916
+ case "stop":
10917
+ return mapClaudeHookToEvent("Stop", payload, taskId);
10918
+ case "notification":
10919
+ return mapClaudeHookToEvent("Notification", payload, taskId);
10920
+ case "ask-question": {
10921
+ const q = typeof payload?.question === "string" ? payload.question : "awaiting input";
10922
+ return { type: "task.input.requested", id: taskId, requestId: 0, question: q };
10923
+ }
10924
+ case "session-end":
10925
+ return mapClaudeHookToEvent("SessionEnd", payload, taskId);
10926
+ default:
10927
+ return null;
10928
+ }
10929
+ }
10930
+ function hooksCommand() {
10931
+ const hooks = new Command28("hooks").description("(internal) receive lifecycle hook events from agent processes");
10932
+ hooks.command("claude <sub>", { hidden: true }).description("internal: bridge a NativeHookSource claude hook to squadrantd").action(async (sub) => {
10933
+ const taskId = process.env.SQUADRANT_CREW_TASK_ID;
10934
+ const project = process.env.SQUADRANT_CREW_PROJECT;
10935
+ if (!taskId || !project) {
10936
+ process.exit(0);
10937
+ }
10938
+ let stdin = "";
10939
+ try {
10940
+ for await (const chunk of process.stdin) stdin += chunk;
10941
+ } catch {
10942
+ }
10943
+ let payload = void 0;
10944
+ if (stdin.trim()) {
10945
+ try {
10946
+ payload = JSON.parse(stdin);
10947
+ } catch {
10948
+ }
10949
+ }
10950
+ const ev = mapHookSub(sub, payload, taskId);
10951
+ if (!ev) {
10952
+ process.exit(0);
10953
+ }
10954
+ try {
10955
+ await sendToSock({ kind: "event", project, event: ev });
10956
+ } catch {
10957
+ }
10958
+ process.exit(0);
10959
+ });
10960
+ return hooks;
10961
+ }
10962
+
10297
10963
  // packages/cli/src/index.ts
10298
10964
  init_dist();
10299
10965
  init_dist();
10300
10966
  init_dist();
10301
10967
  var __dirname = dirname7(fileURLToPath6(import.meta.url));
10302
- var pkg = JSON.parse(readFileSync11(join23(__dirname, "..", "package.json"), "utf-8"));
10968
+ var pkg = JSON.parse(readFileSync13(join26(__dirname, "..", "package.json"), "utf-8"));
10303
10969
  ensureRuntimeSynced({
10304
- sourceRoot: join23(__dirname, ".."),
10305
- runtimeRoot: join23(homedir15(), ".config", "squadrant")
10970
+ sourceRoot: join26(__dirname, ".."),
10971
+ runtimeRoot: join26(homedir18(), ".config", "squadrant")
10306
10972
  });
10307
10973
  if (process.argv[2] !== "config") {
10308
10974
  try {
10309
- const cfgPath = join23(homedir15(), ".config", "squadrant", "config.json");
10310
- if (existsSync11(cfgPath)) {
10311
- const cfg = JSON.parse(readFileSync11(cfgPath, "utf-8"));
10975
+ const cfgPath = join26(homedir18(), ".config", "squadrant", "config.json");
10976
+ if (existsSync12(cfgPath)) {
10977
+ const cfg = JSON.parse(readFileSync13(cfgPath, "utf-8"));
10312
10978
  if (needsCheck(cfg, pkg.version)) {
10313
10979
  const items = detectDrift(cfg, getDefaultConfig());
10314
10980
  if (items.length === 0) {
10315
- writeFileSync9(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
10981
+ writeFileSync10(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
10316
10982
  } else {
10317
10983
  const from = cfg._squadrantVersion ?? "an earlier version";
10318
10984
  process.stderr.write(
@@ -10331,7 +10997,7 @@ if (process.argv[2] !== "config") {
10331
10997
  if (!process.env.SQUADRANT_DAEMON_SKIP) {
10332
10998
  ensureDaemon();
10333
10999
  }
10334
- var program = new Command28();
11000
+ var program = new Command29();
10335
11001
  program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
10336
11002
  program.addCommand(doctorCommand);
10337
11003
  program.addCommand(initCommand);
@@ -10358,6 +11024,7 @@ program.addCommand(groupCommand);
10358
11024
  program.addCommand(cmuxCommand);
10359
11025
  program.addCommand(effortCommand);
10360
11026
  program.addCommand(telegramCommand);
11027
+ program.addCommand(hooksCommand());
10361
11028
  program.parseAsync().catch((e) => {
10362
11029
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
10363
11030
  `);