squadrant 0.12.1 → 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 +691 -69
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +582 -10
- package/dist/squadrantd.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2700,6 +2700,12 @@ var init_crew_spawn = __esm({
|
|
|
2700
2700
|
}
|
|
2701
2701
|
});
|
|
2702
2702
|
|
|
2703
|
+
// packages/core/dist/lifecycle-source.js
|
|
2704
|
+
var init_lifecycle_source = __esm({
|
|
2705
|
+
"packages/core/dist/lifecycle-source.js"() {
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
|
|
2703
2709
|
// packages/core/dist/index.js
|
|
2704
2710
|
var init_dist2 = __esm({
|
|
2705
2711
|
"packages/core/dist/index.js"() {
|
|
@@ -2732,6 +2738,7 @@ var init_dist2 = __esm({
|
|
|
2732
2738
|
init_launch_workspace();
|
|
2733
2739
|
init_side_session();
|
|
2734
2740
|
init_crew_spawn();
|
|
2741
|
+
init_lifecycle_source();
|
|
2735
2742
|
}
|
|
2736
2743
|
});
|
|
2737
2744
|
|
|
@@ -3561,6 +3568,345 @@ var init_daemon_cmux = __esm({
|
|
|
3561
3568
|
}
|
|
3562
3569
|
});
|
|
3563
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
|
+
|
|
3564
3910
|
// packages/workspaces/dist/crew-pane.js
|
|
3565
3911
|
import net from "net";
|
|
3566
3912
|
async function settleInputBox(runtime, pane) {
|
|
@@ -3692,7 +4038,9 @@ var dist_exports = {};
|
|
|
3692
4038
|
__export(dist_exports, {
|
|
3693
4039
|
CMUX_TIMEOUT: () => CMUX_TIMEOUT,
|
|
3694
4040
|
CmuxEventsBridge: () => CmuxEventsBridge,
|
|
4041
|
+
CmuxStoreSource: () => CmuxStoreSource,
|
|
3695
4042
|
DaemonCmux: () => DaemonCmux,
|
|
4043
|
+
NativeHookSource: () => NativeHookSource,
|
|
3696
4044
|
NotifierRegistry: () => NotifierRegistry,
|
|
3697
4045
|
RuntimeRegistry: () => RuntimeRegistry,
|
|
3698
4046
|
WorkspaceRegistry: () => WorkspaceRegistry,
|
|
@@ -3705,8 +4053,10 @@ __export(dist_exports, {
|
|
|
3705
4053
|
deriveRunState: () => deriveRunState,
|
|
3706
4054
|
findCrew: () => findCrew,
|
|
3707
4055
|
getFreePort: () => getFreePort,
|
|
4056
|
+
installClaudeHooks: () => installClaudeHooks,
|
|
3708
4057
|
isInsideCmux: () => isInsideCmux,
|
|
3709
4058
|
listProjectCrews: () => listProjectCrews,
|
|
4059
|
+
mapSubToLifecycle: () => mapSubToLifecycle,
|
|
3710
4060
|
resolveCaptainWorkspace: () => resolveCaptainWorkspace,
|
|
3711
4061
|
sendFirstTurnWhenReady: () => sendFirstTurnWhenReady
|
|
3712
4062
|
});
|
|
@@ -3717,6 +4067,8 @@ var init_dist3 = __esm({
|
|
|
3717
4067
|
init_workspaces2();
|
|
3718
4068
|
init_events_bridge();
|
|
3719
4069
|
init_daemon_cmux();
|
|
4070
|
+
init_cmux_store_source();
|
|
4071
|
+
init_native_hook_source();
|
|
3720
4072
|
init_crew_pane();
|
|
3721
4073
|
}
|
|
3722
4074
|
});
|
|
@@ -4646,13 +4998,99 @@ var init_app_server_client = __esm({
|
|
|
4646
4998
|
}
|
|
4647
4999
|
});
|
|
4648
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
|
+
|
|
4649
5087
|
// packages/agents/dist/codex/config.js
|
|
4650
5088
|
import { readFile as readFile5 } from "fs/promises";
|
|
4651
|
-
import { homedir as
|
|
4652
|
-
import { join as
|
|
5089
|
+
import { homedir as homedir11 } from "os";
|
|
5090
|
+
import { join as join16 } from "path";
|
|
4653
5091
|
async function resolveCodexModel() {
|
|
4654
|
-
const home = process.env["CODEX_HOME"] ??
|
|
4655
|
-
const configPath =
|
|
5092
|
+
const home = process.env["CODEX_HOME"] ?? join16(homedir11(), ".codex");
|
|
5093
|
+
const configPath = join16(home, "config.toml");
|
|
4656
5094
|
let text;
|
|
4657
5095
|
try {
|
|
4658
5096
|
text = await readFile5(configPath, "utf8");
|
|
@@ -5173,9 +5611,9 @@ var init_sse_bridge = __esm({
|
|
|
5173
5611
|
|
|
5174
5612
|
// packages/agents/dist/interactive/claude.js
|
|
5175
5613
|
import { execSync as execSync7 } from "child_process";
|
|
5176
|
-
import { readFileSync as
|
|
5177
|
-
import { homedir as
|
|
5178
|
-
import { join as
|
|
5614
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5615
|
+
import { homedir as homedir12 } from "os";
|
|
5616
|
+
import { join as join17 } from "path";
|
|
5179
5617
|
function probeClaudeSettingsFlag() {
|
|
5180
5618
|
try {
|
|
5181
5619
|
const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -5226,11 +5664,11 @@ function deriveTranscriptPath(sessionId, cwd) {
|
|
|
5226
5664
|
if (!sessionId || !cwd)
|
|
5227
5665
|
return null;
|
|
5228
5666
|
const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
5229
|
-
return
|
|
5667
|
+
return join17(homedir12(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
|
|
5230
5668
|
}
|
|
5231
5669
|
function readLastAssistantText(transcriptPath) {
|
|
5232
5670
|
try {
|
|
5233
|
-
const raw =
|
|
5671
|
+
const raw = readFileSync10(transcriptPath, "utf-8");
|
|
5234
5672
|
const lines = raw.split(/\r?\n/);
|
|
5235
5673
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
5236
5674
|
const line = lines[i].trim();
|
|
@@ -5347,6 +5785,18 @@ function classifyPaneTail(tail) {
|
|
|
5347
5785
|
}
|
|
5348
5786
|
return { kind: "approval", text: "Crew is awaiting permission approval." };
|
|
5349
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
|
+
}
|
|
5350
5800
|
const region = cleaned.filter((c) => c != null).join("\n");
|
|
5351
5801
|
const q = detectTrailingQuestion(region);
|
|
5352
5802
|
if (q)
|
|
@@ -5374,7 +5824,7 @@ function stripChrome(raw) {
|
|
|
5374
5824
|
return null;
|
|
5375
5825
|
return trimmed;
|
|
5376
5826
|
}
|
|
5377
|
-
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;
|
|
5378
5828
|
var init_pane_classifier = __esm({
|
|
5379
5829
|
"packages/agents/dist/interactive/pane-classifier.js"() {
|
|
5380
5830
|
init_claude2();
|
|
@@ -5388,6 +5838,7 @@ var init_pane_classifier = __esm({
|
|
|
5388
5838
|
/\bmaximum\s+retries\b/i
|
|
5389
5839
|
];
|
|
5390
5840
|
OPTION_RE = /^[❯>›]?\s*(\d+)\.\s+(.*\S)\s*$/;
|
|
5841
|
+
PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
|
|
5391
5842
|
PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
|
|
5392
5843
|
STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
|
|
5393
5844
|
}
|
|
@@ -5618,6 +6069,7 @@ var dist_exports2 = {};
|
|
|
5618
6069
|
__export(dist_exports2, {
|
|
5619
6070
|
AppServerClient: () => AppServerClient,
|
|
5620
6071
|
CapabilityRegistry: () => CapabilityRegistry,
|
|
6072
|
+
CodexAppServerSource: () => CodexAppServerSource,
|
|
5621
6073
|
CodexInteractiveDriver: () => CodexInteractiveDriver,
|
|
5622
6074
|
HEADLESS_ERROR_TAIL: () => HEADLESS_ERROR_TAIL,
|
|
5623
6075
|
MARKER_END: () => MARKER_END,
|
|
@@ -5656,6 +6108,7 @@ var init_dist4 = __esm({
|
|
|
5656
6108
|
init_drivers();
|
|
5657
6109
|
init_projection2();
|
|
5658
6110
|
init_app_server_client();
|
|
6111
|
+
init_codex_app_server_source();
|
|
5659
6112
|
init_driver();
|
|
5660
6113
|
init_normalize();
|
|
5661
6114
|
init_sse_bridge();
|
|
@@ -5672,11 +6125,11 @@ var init_dist4 = __esm({
|
|
|
5672
6125
|
// packages/cli/src/index.ts
|
|
5673
6126
|
init_dist();
|
|
5674
6127
|
init_dist2();
|
|
5675
|
-
import { Command as
|
|
5676
|
-
import { readFileSync as
|
|
6128
|
+
import { Command as Command29 } from "commander";
|
|
6129
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
5677
6130
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5678
|
-
import { dirname as dirname7, join as
|
|
5679
|
-
import { homedir as
|
|
6131
|
+
import { dirname as dirname7, join as join26 } from "path";
|
|
6132
|
+
import { homedir as homedir18 } from "os";
|
|
5680
6133
|
|
|
5681
6134
|
// packages/cli/src/commands/doctor.ts
|
|
5682
6135
|
init_dist();
|
|
@@ -5693,10 +6146,10 @@ import chalk3 from "chalk";
|
|
|
5693
6146
|
// packages/cli/src/commands/health-view.ts
|
|
5694
6147
|
init_dist2();
|
|
5695
6148
|
init_dist2();
|
|
5696
|
-
import { homedir as
|
|
5697
|
-
import { join as
|
|
6149
|
+
import { homedir as homedir10 } from "os";
|
|
6150
|
+
import { join as join15 } from "path";
|
|
5698
6151
|
import chalk2 from "chalk";
|
|
5699
|
-
var SOCK =
|
|
6152
|
+
var SOCK = join15(homedir10(), ".config", "squadrant", "squadrant.sock");
|
|
5700
6153
|
async function queryHealth(project) {
|
|
5701
6154
|
try {
|
|
5702
6155
|
const reply = await sendRequest(SOCK, { kind: "health", project });
|
|
@@ -6530,9 +6983,9 @@ init_dist4();
|
|
|
6530
6983
|
import { Command as Command8 } from "commander";
|
|
6531
6984
|
import { createConnection as createConnection3 } from "net";
|
|
6532
6985
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6533
|
-
import { homedir as
|
|
6534
|
-
import { join as
|
|
6535
|
-
import { mkdirSync as
|
|
6986
|
+
import { homedir as homedir14 } from "os";
|
|
6987
|
+
import { join as join19 } from "path";
|
|
6988
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
6536
6989
|
|
|
6537
6990
|
// packages/cli/src/commands/crew-output.ts
|
|
6538
6991
|
function tailLines(text, maxLines = 40, maxBytes = 4096) {
|
|
@@ -6590,11 +7043,11 @@ init_dist2();
|
|
|
6590
7043
|
import { Command as Command6 } from "commander";
|
|
6591
7044
|
import chalk8 from "chalk";
|
|
6592
7045
|
import { createConnection as createConnection2 } from "net";
|
|
6593
|
-
import { homedir as
|
|
6594
|
-
import { join as
|
|
7046
|
+
import { homedir as homedir13 } from "os";
|
|
7047
|
+
import { join as join18 } from "path";
|
|
6595
7048
|
import { createInterface } from "readline";
|
|
6596
7049
|
function socketPath() {
|
|
6597
|
-
return process.env.SQUADRANTD_SOCK ??
|
|
7050
|
+
return process.env.SQUADRANTD_SOCK ?? join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
6598
7051
|
}
|
|
6599
7052
|
function rule(width, ch = "\u2500") {
|
|
6600
7053
|
return ch.repeat(Math.max(0, width));
|
|
@@ -6830,7 +7283,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
6830
7283
|
});
|
|
6831
7284
|
|
|
6832
7285
|
// packages/cli/src/commands/crew-control.ts
|
|
6833
|
-
var SOCK2 =
|
|
7286
|
+
var SOCK2 = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
6834
7287
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
6835
7288
|
async function sendCodexFirstTurn(taskId, text) {
|
|
6836
7289
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -6935,10 +7388,10 @@ function buildSignalRequest(signal, o) {
|
|
|
6935
7388
|
return { kind: "event", project, event };
|
|
6936
7389
|
}
|
|
6937
7390
|
function defaultWriteResult(id, payload) {
|
|
6938
|
-
const dir =
|
|
6939
|
-
|
|
6940
|
-
const file =
|
|
6941
|
-
|
|
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);
|
|
6942
7395
|
return file;
|
|
6943
7396
|
}
|
|
6944
7397
|
function addControlPlaneCrewCommands(crew) {
|
|
@@ -7037,8 +7490,8 @@ addControlPlaneCrewCommands(crewControlCommand);
|
|
|
7037
7490
|
|
|
7038
7491
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
7039
7492
|
init_dist4();
|
|
7040
|
-
import { mkdirSync as
|
|
7041
|
-
import { join as
|
|
7493
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
7494
|
+
import { join as join20 } from "path";
|
|
7042
7495
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
7043
7496
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
7044
7497
|
"Bash(git status:*)",
|
|
@@ -7129,24 +7582,24 @@ function mergeCrewPermissions(settings) {
|
|
|
7129
7582
|
return next;
|
|
7130
7583
|
}
|
|
7131
7584
|
function writePerCrewSettingsLocal(o) {
|
|
7132
|
-
const dir =
|
|
7133
|
-
|
|
7134
|
-
const file =
|
|
7585
|
+
const dir = join20(o.projectCwd, ".claude");
|
|
7586
|
+
mkdirSync8(dir, { recursive: true });
|
|
7587
|
+
const file = join20(dir, "settings.local.json");
|
|
7135
7588
|
let existing = {};
|
|
7136
7589
|
try {
|
|
7137
|
-
const raw = healStaleCockpitRefs(
|
|
7590
|
+
const raw = healStaleCockpitRefs(readFileSync11(file, "utf-8"));
|
|
7138
7591
|
existing = JSON.parse(raw);
|
|
7139
7592
|
} catch {
|
|
7140
7593
|
}
|
|
7141
7594
|
const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
|
|
7142
7595
|
const merged = mergeCrewPermissions(withHooks);
|
|
7143
|
-
|
|
7596
|
+
writeFileSync9(file, JSON.stringify(merged, null, 2));
|
|
7144
7597
|
return file;
|
|
7145
7598
|
}
|
|
7146
7599
|
function writePerCrewOpencodeConfig(o) {
|
|
7147
|
-
const dir =
|
|
7148
|
-
|
|
7149
|
-
const file =
|
|
7600
|
+
const dir = join20(o.stateRoot, o.project, o.taskId);
|
|
7601
|
+
mkdirSync8(dir, { recursive: true });
|
|
7602
|
+
const file = join20(dir, "opencode.json");
|
|
7150
7603
|
const config = {
|
|
7151
7604
|
permission: {
|
|
7152
7605
|
read: "allow",
|
|
@@ -7161,7 +7614,7 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
7161
7614
|
external_directory: { "**": "allow" }
|
|
7162
7615
|
}
|
|
7163
7616
|
};
|
|
7164
|
-
|
|
7617
|
+
writeFileSync9(file, JSON.stringify(config, null, 2));
|
|
7165
7618
|
return file;
|
|
7166
7619
|
}
|
|
7167
7620
|
|
|
@@ -7454,8 +7907,8 @@ init_dist();
|
|
|
7454
7907
|
init_dist3();
|
|
7455
7908
|
import { Command as Command11 } from "commander";
|
|
7456
7909
|
import { execSync as execSync10 } from "child_process";
|
|
7457
|
-
import { homedir as
|
|
7458
|
-
import { join as
|
|
7910
|
+
import { homedir as homedir16 } from "os";
|
|
7911
|
+
import { join as join22 } from "path";
|
|
7459
7912
|
import chalk12 from "chalk";
|
|
7460
7913
|
|
|
7461
7914
|
// packages/web/dist/read-status.js
|
|
@@ -7651,9 +8104,9 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
7651
8104
|
// packages/web/dist/probes.js
|
|
7652
8105
|
init_dist();
|
|
7653
8106
|
init_dist();
|
|
7654
|
-
import { join as
|
|
7655
|
-
import { homedir as
|
|
7656
|
-
import { existsSync as
|
|
8107
|
+
import { join as join21 } from "path";
|
|
8108
|
+
import { homedir as homedir15 } from "os";
|
|
8109
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
7657
8110
|
import { execFile as execFile3 } from "child_process";
|
|
7658
8111
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
7659
8112
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
@@ -7689,7 +8142,7 @@ function vaultProbe(run, dir) {
|
|
|
7689
8142
|
return { state: "unknown", detail: "no vault configured" };
|
|
7690
8143
|
if (!run.pathExists(dir))
|
|
7691
8144
|
return { state: "gone", detail: "vault directory missing" };
|
|
7692
|
-
if (!run.pathExists(
|
|
8145
|
+
if (!run.pathExists(join21(dir, ".obsidian")))
|
|
7693
8146
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
7694
8147
|
return { state: "alive" };
|
|
7695
8148
|
} catch {
|
|
@@ -7757,13 +8210,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
7757
8210
|
const sessions = probeSessions(run);
|
|
7758
8211
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
7759
8212
|
}
|
|
7760
|
-
var SESSIONS_PATH =
|
|
8213
|
+
var SESSIONS_PATH = join21(homedir15(), ".config", "squadrant", "sessions.json");
|
|
7761
8214
|
function onPath(cli) {
|
|
7762
8215
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
7763
|
-
return dirs.some((d) =>
|
|
8216
|
+
return dirs.some((d) => existsSync11(join21(d, cli)));
|
|
7764
8217
|
}
|
|
7765
8218
|
function readSessionsHashes() {
|
|
7766
|
-
const raw = JSON.parse(
|
|
8219
|
+
const raw = JSON.parse(readFileSync12(SESSIONS_PATH, "utf-8"));
|
|
7767
8220
|
const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
|
|
7768
8221
|
return [...new Set(hashes)];
|
|
7769
8222
|
}
|
|
@@ -7777,7 +8230,7 @@ function defaultProbeRunners() {
|
|
|
7777
8230
|
}
|
|
7778
8231
|
}),
|
|
7779
8232
|
probeOnPath: async (cli) => onPath(cli),
|
|
7780
|
-
pathExists: (p) =>
|
|
8233
|
+
pathExists: (p) => existsSync11(p),
|
|
7781
8234
|
loadConfig: () => loadConfig(),
|
|
7782
8235
|
loadSessionsHashes: () => readSessionsHashes()
|
|
7783
8236
|
};
|
|
@@ -8430,7 +8883,7 @@ async function startWebServer(opts) {
|
|
|
8430
8883
|
|
|
8431
8884
|
// packages/cli/src/commands/dashboard.ts
|
|
8432
8885
|
init_dist();
|
|
8433
|
-
var SOCK3 =
|
|
8886
|
+
var SOCK3 = join22(homedir16(), ".config", "squadrant", "squadrant.sock");
|
|
8434
8887
|
function detectCurrentWorkspace2() {
|
|
8435
8888
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
8436
8889
|
const match = out.match(/workspace:\d+/);
|
|
@@ -8514,13 +8967,87 @@ init_dist();
|
|
|
8514
8967
|
init_dist4();
|
|
8515
8968
|
init_dist3();
|
|
8516
8969
|
init_dist2();
|
|
8517
|
-
init_dist2();
|
|
8518
8970
|
import { Command as Command12 } from "commander";
|
|
8519
8971
|
import { execSync as execSync11 } from "child_process";
|
|
8520
8972
|
import fs20 from "fs";
|
|
8521
8973
|
import path23 from "path";
|
|
8522
8974
|
import os12 from "os";
|
|
8523
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();
|
|
8524
9051
|
var CMUX_APP = "/Applications/cmux.app";
|
|
8525
9052
|
var TEMPLATES_DIR4 = path23.join(os12.homedir(), ".config", "squadrant", "templates");
|
|
8526
9053
|
var SESSIONS_PATH2 = path23.join(os12.homedir(), ".config", "squadrant", "sessions.json");
|
|
@@ -8604,12 +9131,43 @@ var launchCommand = new Command12("launch").description(
|
|
|
8604
9131
|
}
|
|
8605
9132
|
console.log("");
|
|
8606
9133
|
} else if (!project) {
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
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("");
|
|
8613
9171
|
} else {
|
|
8614
9172
|
if (!config.projects[project]) {
|
|
8615
9173
|
console.error(
|
|
@@ -9606,7 +10164,7 @@ init_dist2();
|
|
|
9606
10164
|
import { Command as Command22 } from "commander";
|
|
9607
10165
|
import fs25 from "fs";
|
|
9608
10166
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9609
|
-
import { dirname as dirname5, join as
|
|
10167
|
+
import { dirname as dirname5, join as join23 } from "path";
|
|
9610
10168
|
import chalk22 from "chalk";
|
|
9611
10169
|
function runConfigCheck(opts) {
|
|
9612
10170
|
const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
|
|
@@ -9742,7 +10300,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
9742
10300
|
}
|
|
9743
10301
|
});
|
|
9744
10302
|
function readPkgVersion2() {
|
|
9745
|
-
const pkgPath =
|
|
10303
|
+
const pkgPath = join23(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
9746
10304
|
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
9747
10305
|
}
|
|
9748
10306
|
|
|
@@ -10013,12 +10571,12 @@ var effortCommand = new Command26("effort").description("Get or set the global c
|
|
|
10013
10571
|
// packages/cli/src/commands/telegram.ts
|
|
10014
10572
|
init_dist();
|
|
10015
10573
|
init_dist2();
|
|
10016
|
-
import { join as
|
|
10574
|
+
import { join as join24, dirname as dirname6 } from "path";
|
|
10017
10575
|
import { emitKeypressEvents } from "readline";
|
|
10018
10576
|
import { Command as Command27 } from "commander";
|
|
10019
10577
|
import chalk27 from "chalk";
|
|
10020
10578
|
function defaultStateRoot() {
|
|
10021
|
-
return
|
|
10579
|
+
return join24(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
10022
10580
|
}
|
|
10023
10581
|
async function questionMasked() {
|
|
10024
10582
|
return new Promise((resolve3) => {
|
|
@@ -10339,25 +10897,88 @@ telegramCommand.command("send").argument("<project>", "project whose topic recei
|
|
|
10339
10897
|
}
|
|
10340
10898
|
});
|
|
10341
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
|
+
|
|
10342
10963
|
// packages/cli/src/index.ts
|
|
10343
10964
|
init_dist();
|
|
10344
10965
|
init_dist();
|
|
10345
10966
|
init_dist();
|
|
10346
10967
|
var __dirname = dirname7(fileURLToPath6(import.meta.url));
|
|
10347
|
-
var pkg = JSON.parse(
|
|
10968
|
+
var pkg = JSON.parse(readFileSync13(join26(__dirname, "..", "package.json"), "utf-8"));
|
|
10348
10969
|
ensureRuntimeSynced({
|
|
10349
|
-
sourceRoot:
|
|
10350
|
-
runtimeRoot:
|
|
10970
|
+
sourceRoot: join26(__dirname, ".."),
|
|
10971
|
+
runtimeRoot: join26(homedir18(), ".config", "squadrant")
|
|
10351
10972
|
});
|
|
10352
10973
|
if (process.argv[2] !== "config") {
|
|
10353
10974
|
try {
|
|
10354
|
-
const cfgPath =
|
|
10355
|
-
if (
|
|
10356
|
-
const cfg = JSON.parse(
|
|
10975
|
+
const cfgPath = join26(homedir18(), ".config", "squadrant", "config.json");
|
|
10976
|
+
if (existsSync12(cfgPath)) {
|
|
10977
|
+
const cfg = JSON.parse(readFileSync13(cfgPath, "utf-8"));
|
|
10357
10978
|
if (needsCheck(cfg, pkg.version)) {
|
|
10358
10979
|
const items = detectDrift(cfg, getDefaultConfig());
|
|
10359
10980
|
if (items.length === 0) {
|
|
10360
|
-
|
|
10981
|
+
writeFileSync10(cfgPath, JSON.stringify(withStamp(cfg, pkg.version), null, 2) + "\n");
|
|
10361
10982
|
} else {
|
|
10362
10983
|
const from = cfg._squadrantVersion ?? "an earlier version";
|
|
10363
10984
|
process.stderr.write(
|
|
@@ -10376,7 +10997,7 @@ if (process.argv[2] !== "config") {
|
|
|
10376
10997
|
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
10377
10998
|
ensureDaemon();
|
|
10378
10999
|
}
|
|
10379
|
-
var program = new
|
|
11000
|
+
var program = new Command29();
|
|
10380
11001
|
program.name("squadrant").description("Multi-project orchestration for your coding agents (Claude, Codex, opencode, Gemini)").version(pkg.version);
|
|
10381
11002
|
program.addCommand(doctorCommand);
|
|
10382
11003
|
program.addCommand(initCommand);
|
|
@@ -10403,6 +11024,7 @@ program.addCommand(groupCommand);
|
|
|
10403
11024
|
program.addCommand(cmuxCommand);
|
|
10404
11025
|
program.addCommand(effortCommand);
|
|
10405
11026
|
program.addCommand(telegramCommand);
|
|
11027
|
+
program.addCommand(hooksCommand());
|
|
10406
11028
|
program.parseAsync().catch((e) => {
|
|
10407
11029
|
process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
|
|
10408
11030
|
`);
|