squadrant 0.19.1 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -199,8 +199,8 @@ function deepMerge(base, patch) {
199
199
  if (patch === null || typeof patch !== "object" || Array.isArray(patch))
200
200
  return patch ?? base;
201
201
  const out = { ...base };
202
- for (const [k, v] of Object.entries(patch)) {
203
- out[k] = deepMerge(out[k], v);
202
+ for (const [k, v2] of Object.entries(patch)) {
203
+ out[k] = deepMerge(out[k], v2);
204
204
  }
205
205
  return out;
206
206
  }
@@ -676,8 +676,8 @@ var init_runtime_sync = __esm({
676
676
  });
677
677
 
678
678
  // packages/shared/dist/lib/tool-compat.js
679
- function parseSemVer(v) {
680
- const m = v.match(/(\d+)\.(\d+)\.(\d+)/);
679
+ function parseSemVer(v2) {
680
+ const m = v2.match(/(\d+)\.(\d+)\.(\d+)/);
681
681
  if (!m)
682
682
  return null;
683
683
  return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
@@ -1823,12 +1823,12 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
1823
1823
  return;
1824
1824
  }
1825
1825
  const conn = createConnection(sockPath);
1826
- const finish = (v) => {
1826
+ const finish = (v2) => {
1827
1827
  try {
1828
1828
  conn.destroy();
1829
1829
  } catch {
1830
1830
  }
1831
- resolve4(v);
1831
+ resolve4(v2);
1832
1832
  };
1833
1833
  const timer = setTimeout(() => finish(false), timeoutMs);
1834
1834
  conn.on("connect", () => {
@@ -2494,17 +2494,20 @@ function getDaemonPid(target) {
2494
2494
  function forceKickstartAndVerify(target, opts = {}) {
2495
2495
  const pollAttempts = opts.pollAttempts ?? 15;
2496
2496
  const pollDelayMs = opts.pollDelayMs ?? 300;
2497
- const kickstartRetries = opts.kickstartRetries ?? 5;
2497
+ const kickstartRetries = opts.kickstartRetries ?? 10;
2498
2498
  const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
2499
2499
  const pidBefore = getDaemonPid(target);
2500
+ let kickstartError = null;
2500
2501
  for (let i = 0; i < kickstartRetries; i++) {
2501
2502
  try {
2502
2503
  execFileSync3("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
2504
+ kickstartError = null;
2503
2505
  break;
2504
2506
  } catch (e) {
2505
- if (i === kickstartRetries - 1)
2506
- throw e;
2507
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, kickstartRetryDelayMs);
2507
+ kickstartError = e;
2508
+ if (i < kickstartRetries - 1) {
2509
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, kickstartRetryDelayMs);
2510
+ }
2508
2511
  }
2509
2512
  }
2510
2513
  let pidAfter = null;
@@ -2516,7 +2519,16 @@ function forceKickstartAndVerify(target, opts = {}) {
2516
2519
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, pollDelayMs);
2517
2520
  }
2518
2521
  }
2519
- return { target, pidBefore, pidAfter, restarted: pidAfter !== null && pidAfter !== pidBefore };
2522
+ const restarted = pidAfter !== null && pidAfter !== pidBefore;
2523
+ if (kickstartError && !restarted)
2524
+ throw kickstartError;
2525
+ return {
2526
+ target,
2527
+ pidBefore,
2528
+ pidAfter,
2529
+ restarted,
2530
+ ...kickstartError ? { note: "kickstart -k refused; daemon restarted by bootstrap" } : {}
2531
+ };
2520
2532
  }
2521
2533
  function isOperatorInitiatedCommand(topLevelArg) {
2522
2534
  return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
@@ -3425,6 +3437,33 @@ import fs9 from "fs";
3425
3437
  import os4 from "os";
3426
3438
  import path9 from "path";
3427
3439
  import { randomUUID as randomUUID3 } from "crypto";
3440
+ async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
3441
+ const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
3442
+ for (; ; ) {
3443
+ const rec = await getTaskRecord(project, id).catch(() => void 0);
3444
+ if (rec?.firstTurnConfirmedAt)
3445
+ return true;
3446
+ if (Date.now() >= deadline)
3447
+ return false;
3448
+ await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
3449
+ }
3450
+ }
3451
+ function firstTrueOrBothFalse(a, b) {
3452
+ return new Promise((resolve4) => {
3453
+ let settledFalseCount = 0;
3454
+ const onSettle = (ok2) => {
3455
+ if (ok2) {
3456
+ resolve4(true);
3457
+ return;
3458
+ }
3459
+ settledFalseCount++;
3460
+ if (settledFalseCount === 2)
3461
+ resolve4(false);
3462
+ };
3463
+ a.then(onSettle, () => onSettle(false));
3464
+ b.then(onSettle, () => onSettle(false));
3465
+ });
3466
+ }
3428
3467
  async function listCrewPanes(runtime, workspaceId, project) {
3429
3468
  const surfaces = await runtime.listSurfaces(workspaceId);
3430
3469
  return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
@@ -3581,10 +3620,12 @@ async function runCrewSpawn(input, config, deps) {
3581
3620
  fs9.writeFileSync(spillFile, claudeFirstTurn, "utf8");
3582
3621
  claudeFirstTurn = `Full task is at ${spillFile} \u2014 cat it and follow it exactly.`;
3583
3622
  }
3584
- const claudeResult = await deps.sendFirstTurn(pane2, `${claudeFirstTurn}
3623
+ const sendPromise = deps.sendFirstTurn(pane2, `${claudeFirstTurn}
3585
3624
 
3586
3625
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
3587
- if (!claudeResult.delivered) {
3626
+ const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
3627
+ const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id)) : await scrapeDelivered;
3628
+ if (!delivered) {
3588
3629
  process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
3589
3630
  `);
3590
3631
  } else if (!hooksInstalled) {
@@ -3695,6 +3736,10 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
3695
3736
  const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
3696
3737
  throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${task.operatorHold.note ? `: ${task.operatorHold.note}` : ""}). The operator is working in that tab \u2014 sending a message disrupts their conversation. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
3697
3738
  }
3739
+ const isAttentionState = task?.state === "blocked" || task?.state === "awaiting-input" || task?.state === "review";
3740
+ if (task && !isAttentionState && task.firstTurnConfirmedAt && task.task === message) {
3741
+ throw new Error(`Crew '${name}' already confirmed receipt of this task \u2014 its first turn was delivered and is not being re-sent to avoid running it twice. If you have new instructions, send different text.`);
3742
+ }
3698
3743
  let reopened = false;
3699
3744
  try {
3700
3745
  if (task) {
@@ -3861,7 +3906,7 @@ async function runCrewList(project, runtime, workspaceId) {
3861
3906
  surfaceId: c.surfaceId
3862
3907
  }));
3863
3908
  }
3864
- var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
3909
+ var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, FIRST_TURN_HOOK_CONFIRM_WINDOW_MS, FIRST_TURN_HOOK_POLL_INTERVAL_MS, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
3865
3910
  var init_crew_spawn = __esm({
3866
3911
  "packages/core/dist/crew-spawn.js"() {
3867
3912
  init_control_channel();
@@ -3873,6 +3918,8 @@ var init_crew_spawn = __esm({
3873
3918
  FIRST_TURN_INLINE_MAX_BYTES = 1200;
3874
3919
  TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
3875
3920
  STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
3921
+ FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
3922
+ FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
3876
3923
  CLOSE_LOOKUP_RETRIES = 3;
3877
3924
  CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
3878
3925
  }
@@ -4042,9 +4089,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
4042
4089
  const lastDeferred = /* @__PURE__ */ new Map();
4043
4090
  const inFlightDelivery = () => {
4044
4091
  let worst = null;
4045
- for (const [project, v] of lastDeferred) {
4046
- if (!worst || v.deferCount > worst.deferCount)
4047
- worst = { project, ...v };
4092
+ for (const [project, v2] of lastDeferred) {
4093
+ if (!worst || v2.deferCount > worst.deferCount)
4094
+ worst = { project, ...v2 };
4048
4095
  }
4049
4096
  return worst;
4050
4097
  };
@@ -6408,6 +6455,326 @@ var init_lifecycle_source = __esm({
6408
6455
  }
6409
6456
  });
6410
6457
 
6458
+ // packages/core/dist/events/fact.js
6459
+ function stampFact(raw, id) {
6460
+ return { ...raw, ...id };
6461
+ }
6462
+ var init_fact = __esm({
6463
+ "packages/core/dist/events/fact.js"() {
6464
+ }
6465
+ });
6466
+
6467
+ // packages/core/dist/events/log.js
6468
+ var FactLog;
6469
+ var init_log = __esm({
6470
+ "packages/core/dist/events/log.js"() {
6471
+ FactLog = class {
6472
+ capacity;
6473
+ buffers = /* @__PURE__ */ new Map();
6474
+ constructor(opts = {}) {
6475
+ this.capacity = opts.capacity ?? 256;
6476
+ }
6477
+ push(fact) {
6478
+ let buf = this.buffers.get(fact.taskId);
6479
+ if (!buf) {
6480
+ buf = [];
6481
+ this.buffers.set(fact.taskId, buf);
6482
+ }
6483
+ buf.push(fact);
6484
+ while (buf.length > this.capacity)
6485
+ buf.shift();
6486
+ }
6487
+ /** Oldest-first snapshot. A fresh array; later pushes never grow it. */
6488
+ recent(taskId) {
6489
+ return [...this.buffers.get(taskId) ?? []];
6490
+ }
6491
+ /** Newline-delimited JSON, one fact per line, oldest first. */
6492
+ serialize(taskId) {
6493
+ return this.recent(taskId).map((f) => JSON.stringify(f)).join("\n") + "\n";
6494
+ }
6495
+ /** Release a finished crew's buffer. */
6496
+ drop(taskId) {
6497
+ this.buffers.delete(taskId);
6498
+ }
6499
+ };
6500
+ }
6501
+ });
6502
+
6503
+ // packages/core/dist/events/invariant.js
6504
+ function freshTrace() {
6505
+ return {
6506
+ depth: 0,
6507
+ oldestOpenAt: null,
6508
+ stallReported: false,
6509
+ unknownSeen: 0,
6510
+ liveness: /* @__PURE__ */ new Map()
6511
+ };
6512
+ }
6513
+ function checkFact(trace, fact, opts) {
6514
+ const out = [];
6515
+ if (fact.origin === "inferred" && TERMINALISING.has(fact.kind)) {
6516
+ out.push(v("I4", `inferred fact "${fact.kind}" from ${fact.source} cannot terminalise alone`, fact));
6517
+ }
6518
+ switch (fact.kind) {
6519
+ case "tool.opened":
6520
+ if (trace.depth === 0) {
6521
+ trace.oldestOpenAt = fact.at;
6522
+ trace.stallReported = false;
6523
+ }
6524
+ trace.depth += 1;
6525
+ break;
6526
+ case "tool.closed":
6527
+ if (trace.depth === 0) {
6528
+ out.push(v("I1", `tool.closed from ${fact.source} with no open tool`, fact));
6529
+ } else {
6530
+ trace.depth -= 1;
6531
+ if (trace.depth === 0) {
6532
+ trace.oldestOpenAt = null;
6533
+ trace.stallReported = false;
6534
+ }
6535
+ }
6536
+ break;
6537
+ case "turn.ended":
6538
+ if (trace.depth > 0) {
6539
+ out.push(v("I2", `turn.ended with ${trace.depth} tool call(s) still open`, fact));
6540
+ trace.depth = 0;
6541
+ trace.oldestOpenAt = null;
6542
+ trace.stallReported = false;
6543
+ }
6544
+ break;
6545
+ case "unknown":
6546
+ trace.unknownSeen += 1;
6547
+ out.push(v("I5", `unrecognised frame "${fact.name}" from ${fact.source}`, fact));
6548
+ break;
6549
+ case "process.observed": {
6550
+ const prior = [...trace.liveness.entries()].find(([src, s]) => src !== fact.source && s.alive !== fact.alive && fact.at - s.at <= (opts.disagreeWindowMs ?? -1));
6551
+ if (prior) {
6552
+ out.push(v("I6", `liveness disagreement: ${prior[0]} said alive=${prior[1].alive}, ${fact.source} says alive=${fact.alive}`, fact));
6553
+ }
6554
+ trace.liveness.set(fact.source, { alive: fact.alive, at: fact.at });
6555
+ break;
6556
+ }
6557
+ default:
6558
+ break;
6559
+ }
6560
+ if (opts.stallBudgetMs !== void 0 && trace.depth > 0 && trace.oldestOpenAt !== null && !trace.stallReported && fact.at - trace.oldestOpenAt > opts.stallBudgetMs) {
6561
+ trace.stallReported = true;
6562
+ out.push(v("I3", `tool open for ${fact.at - trace.oldestOpenAt}ms, past the stall budget`, fact));
6563
+ }
6564
+ return out;
6565
+ }
6566
+ var v, TERMINALISING;
6567
+ var init_invariant = __esm({
6568
+ "packages/core/dist/events/invariant.js"() {
6569
+ v = (code, message, f) => ({ code, message, taskId: f.taskId, at: f.at });
6570
+ TERMINALISING = /* @__PURE__ */ new Set(["session.ended"]);
6571
+ }
6572
+ });
6573
+
6574
+ // packages/core/dist/events/to-control-event.js
6575
+ function toControlEvent(fact) {
6576
+ if (fact.origin === "inferred" && TERMINALISING2.has(fact.kind))
6577
+ return [];
6578
+ switch (fact.kind) {
6579
+ case "turn.ended":
6580
+ return [{ type: "task.turn.completed", id: fact.taskId, turnId: fact.turnId ?? fact.source }];
6581
+ case "permission.requested":
6582
+ return [{
6583
+ type: "task.approval.requested",
6584
+ id: fact.taskId,
6585
+ requestId: fact.requestId,
6586
+ question: fact.question,
6587
+ kind: fact.tool
6588
+ }];
6589
+ case "input.requested":
6590
+ return [{
6591
+ type: "task.input.requested",
6592
+ id: fact.taskId,
6593
+ requestId: fact.requestId,
6594
+ question: fact.question
6595
+ }];
6596
+ case "session.ended":
6597
+ return [{ type: "task.session.ended", id: fact.taskId }];
6598
+ case "session.started":
6599
+ return [{
6600
+ type: "task.started",
6601
+ id: fact.taskId,
6602
+ ...fact.pid === void 0 ? {} : { pid: fact.pid },
6603
+ ...fact.sessionId === void 0 ? {} : { sessionId: fact.sessionId }
6604
+ }];
6605
+ case "prompt.submitted":
6606
+ return [{ type: "task.first-turn.confirmed", id: fact.taskId }];
6607
+ // Liveness-only. The facade still feeds these to reduceLifecycle; they
6608
+ // simply carry no ControlEvent of their own.
6609
+ case "tool.opened":
6610
+ case "tool.closed":
6611
+ case "activity":
6612
+ case "process.observed":
6613
+ case "unknown":
6614
+ return [];
6615
+ }
6616
+ }
6617
+ var TERMINALISING2;
6618
+ var init_to_control_event = __esm({
6619
+ "packages/core/dist/events/to-control-event.js"() {
6620
+ TERMINALISING2 = /* @__PURE__ */ new Set(["session.ended"]);
6621
+ }
6622
+ });
6623
+
6624
+ // packages/core/dist/events/conformance.js
6625
+ function assert(cond, msg) {
6626
+ if (!cond)
6627
+ throw new Error(`conformance: ${msg}`);
6628
+ }
6629
+ function runAdapterConformance(adapter, samples) {
6630
+ const call = (raw) => adapter.translate(raw);
6631
+ return [
6632
+ {
6633
+ name: `${adapter.name}: never throws on garbage`,
6634
+ run: () => {
6635
+ for (const g of GARBAGE) {
6636
+ try {
6637
+ call(g);
6638
+ } catch (e) {
6639
+ throw new Error(`threw on ${JSON.stringify(g)}: ${String(e)}`);
6640
+ }
6641
+ }
6642
+ }
6643
+ },
6644
+ {
6645
+ name: `${adapter.name}: never returns null or undefined`,
6646
+ run: () => {
6647
+ for (const g of [...GARBAGE, ...samples]) {
6648
+ const out = call(g);
6649
+ assert(Array.isArray(out), `returned a non-array for ${JSON.stringify(g)}`);
6650
+ }
6651
+ }
6652
+ },
6653
+ {
6654
+ name: `${adapter.name}: an unrecognised frame yields unknown, not an empty array`,
6655
+ run: () => {
6656
+ const out = call({ type: "definitely-not-a-real-event-name" });
6657
+ assert(out.length > 0, "silently dropped an unrecognised frame (the #542 shape)");
6658
+ assert(out.every((f) => f.kind === "unknown"), "an unrecognised frame must translate to kind 'unknown'");
6659
+ }
6660
+ },
6661
+ {
6662
+ name: `${adapter.name}: recognises its own samples`,
6663
+ run: () => {
6664
+ for (const s of samples) {
6665
+ const out = call(s);
6666
+ assert(out.length > 0, `produced nothing for its own sample ${JSON.stringify(s)}`);
6667
+ assert(out.some((f) => f.kind !== "unknown"), `failed to recognise its own sample ${JSON.stringify(s)}`);
6668
+ }
6669
+ }
6670
+ },
6671
+ {
6672
+ name: `${adapter.name}: declares a constant origin`,
6673
+ run: () => {
6674
+ assert(adapter.origin === "agent" || adapter.origin === "scan" || adapter.origin === "inferred", `invalid origin "${String(adapter.origin)}"`);
6675
+ }
6676
+ }
6677
+ ];
6678
+ }
6679
+ var GARBAGE;
6680
+ var init_conformance = __esm({
6681
+ "packages/core/dist/events/conformance.js"() {
6682
+ GARBAGE = [
6683
+ null,
6684
+ void 0,
6685
+ 0,
6686
+ "",
6687
+ "not json",
6688
+ [],
6689
+ {},
6690
+ { type: 42 },
6691
+ { type: "definitely-not-a-real-event-name" }
6692
+ ];
6693
+ }
6694
+ });
6695
+
6696
+ // packages/core/dist/events/source.js
6697
+ function createEventsSource(opts) {
6698
+ const now = opts.now ?? (() => Date.now());
6699
+ const log = new FactLog({ capacity: opts.capacity });
6700
+ const adapters = new Map(opts.adapters.map((a) => [a.name, a]));
6701
+ const traces = /* @__PURE__ */ new Map();
6702
+ const seqs = /* @__PURE__ */ new Map();
6703
+ let deps;
6704
+ const traceFor = (taskId) => {
6705
+ let t = traces.get(taskId);
6706
+ if (!t) {
6707
+ t = freshTrace();
6708
+ traces.set(taskId, t);
6709
+ }
6710
+ return t;
6711
+ };
6712
+ const nextSeq = (taskId) => {
6713
+ const n = seqs.get(taskId) ?? 0;
6714
+ seqs.set(taskId, n + 1);
6715
+ return n;
6716
+ };
6717
+ return {
6718
+ name: "events",
6719
+ start(d) {
6720
+ deps = d;
6721
+ },
6722
+ stop() {
6723
+ deps = void 0;
6724
+ },
6725
+ health() {
6726
+ return { active: deps !== void 0, error: null };
6727
+ },
6728
+ recent(taskId) {
6729
+ return log.recent(taskId);
6730
+ },
6731
+ dump(taskId) {
6732
+ return log.serialize(taskId);
6733
+ },
6734
+ ingest(source, raw, hint) {
6735
+ const adapter = adapters.get(source);
6736
+ if (!adapter || !deps)
6737
+ return;
6738
+ const rec = deps.resolve(hint);
6739
+ if (!rec)
6740
+ return;
6741
+ const taskId = rec.id;
6742
+ const at = now();
6743
+ let produced;
6744
+ try {
6745
+ const out = adapter.translate(raw);
6746
+ produced = Array.isArray(out) ? out : [{ kind: "unknown", name: `${source} returned non-array` }];
6747
+ } catch (e) {
6748
+ opts.log?.(`events: adapter ${source} threw: ${String(e)}`);
6749
+ produced = [{ kind: "unknown", name: `${source} threw` }];
6750
+ }
6751
+ for (const rawFact of produced) {
6752
+ const fact = stampFact(rawFact, {
6753
+ seq: nextSeq(taskId),
6754
+ taskId,
6755
+ at,
6756
+ source,
6757
+ origin: adapter.origin
6758
+ });
6759
+ log.push(fact);
6760
+ for (const v2 of checkFact(traceFor(taskId), fact, opts.check ?? {})) {
6761
+ opts.onViolation(v2);
6762
+ }
6763
+ for (const ev of toControlEvent(fact))
6764
+ opts.emit(ev);
6765
+ }
6766
+ }
6767
+ };
6768
+ }
6769
+ var init_source = __esm({
6770
+ "packages/core/dist/events/source.js"() {
6771
+ init_fact();
6772
+ init_log();
6773
+ init_invariant();
6774
+ init_to_control_event();
6775
+ }
6776
+ });
6777
+
6411
6778
  // packages/core/dist/index.js
6412
6779
  var dist_exports = {};
6413
6780
  __export(dist_exports, {
@@ -6422,6 +6789,7 @@ __export(dist_exports, {
6422
6789
  DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
6423
6790
  DeferDelivery: () => DeferDelivery,
6424
6791
  FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
6792
+ FactLog: () => FactLog,
6425
6793
  GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
6426
6794
  GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
6427
6795
  IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
@@ -6451,6 +6819,7 @@ __export(dist_exports, {
6451
6819
  capAllowed: () => capAllowed,
6452
6820
  capOutput: () => capOutput,
6453
6821
  captainSocketPath: () => captainSocketPath,
6822
+ checkFact: () => checkFact,
6454
6823
  classifyHealth: () => classifyHealth,
6455
6824
  closeWorkItem: () => closeWorkItem,
6456
6825
  computeTemplateHash: () => computeTemplateHash,
@@ -6463,6 +6832,7 @@ __export(dist_exports, {
6463
6832
  createDirectCrewPaneReader: () => createDirectCrewPaneReader,
6464
6833
  createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
6465
6834
  createEnsureCaptainAlive: () => createEnsureCaptainAlive,
6835
+ createEventsSource: () => createEventsSource,
6466
6836
  createInteractiveProbe: () => createInteractiveProbe,
6467
6837
  createIsCaptainAlive: () => createIsCaptainAlive,
6468
6838
  createLaunch: () => createLaunch,
@@ -6505,6 +6875,7 @@ __export(dist_exports, {
6505
6875
  formatInbound: () => formatInbound,
6506
6876
  formatInboundReceipt: () => formatInboundReceipt,
6507
6877
  formatLifecycle: () => formatLifecycle,
6878
+ freshTrace: () => freshTrace,
6508
6879
  getDaemonPid: () => getDaemonPid,
6509
6880
  healCmdFor: () => healCmdFor,
6510
6881
  isAuthorized: () => isAuthorized,
@@ -6561,6 +6932,7 @@ __export(dist_exports, {
6561
6932
  resolveSetupUserId: () => resolveSetupUserId,
6562
6933
  restartDaemonIfRunning: () => restartDaemonIfRunning,
6563
6934
  rotateIfNeeded: () => rotateIfNeeded,
6935
+ runAdapterConformance: () => runAdapterConformance,
6564
6936
  runCrewAnswer: () => runCrewAnswer,
6565
6937
  runCrewClose: () => runCrewClose,
6566
6938
  runCrewList: () => runCrewList,
@@ -6595,12 +6967,14 @@ __export(dist_exports, {
6595
6967
  sideNameFromTitle: () => sideNameFromTitle,
6596
6968
  sideNextAutoName: () => sideNextAutoName,
6597
6969
  sideTitleFor: () => sideTitleFor,
6970
+ stampFact: () => stampFact,
6598
6971
  startDaemon: () => startDaemon,
6599
6972
  startServer: () => startServer,
6600
6973
  stripBotMention: () => stripBotMention,
6601
6974
  surfaceVerdict: () => surfaceVerdict,
6602
6975
  timeoutGate: () => timeoutGate,
6603
6976
  titleFor: () => titleFor,
6977
+ toControlEvent: () => toControlEvent,
6604
6978
  topicKey: () => topicKey,
6605
6979
  topicName: () => topicName,
6606
6980
  tryAcquireDaemonLock: () => tryAcquireDaemonLock,
@@ -6647,6 +7021,12 @@ var init_dist2 = __esm({
6647
7021
  init_crew_spawn();
6648
7022
  init_crew_answer();
6649
7023
  init_lifecycle_source();
7024
+ init_fact();
7025
+ init_log();
7026
+ init_invariant();
7027
+ init_to_control_event();
7028
+ init_conformance();
7029
+ init_source();
6650
7030
  init_control_channel();
6651
7031
  init_captain_channel();
6652
7032
  }
@@ -6663,6 +7043,7 @@ init_dist2();
6663
7043
  init_dist2();
6664
7044
  init_dist2();
6665
7045
  init_dist2();
7046
+ init_dist2();
6666
7047
  import { join as join22, dirname as dirname4, resolve as resolve3 } from "path";
6667
7048
  import { homedir as homedir13 } from "os";
6668
7049
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -7301,9 +7682,9 @@ ${directive}` : directive;
7301
7682
  function withTimeout(p, ms, msg) {
7302
7683
  return new Promise((resolve4, reject) => {
7303
7684
  const t = setTimeout(() => reject(new Error(msg)), ms);
7304
- p.then((v) => {
7685
+ p.then((v2) => {
7305
7686
  clearTimeout(t);
7306
- resolve4(v);
7687
+ resolve4(v2);
7307
7688
  }, (e) => {
7308
7689
  clearTimeout(t);
7309
7690
  reject(e);
@@ -7312,6 +7693,7 @@ function withTimeout(p, ms, msg) {
7312
7693
  }
7313
7694
 
7314
7695
  // packages/agents/dist/opencode/sse-bridge.js
7696
+ var IGNORED_FRAME = /^(message|storage|file|lsp|installation)\./;
7315
7697
  var OpencodeSseBridge = class {
7316
7698
  controllers = /* @__PURE__ */ new Map();
7317
7699
  /** taskId → the crew's opencode server port (for permission-reply POSTs). */
@@ -7448,6 +7830,10 @@ var OpencodeSseBridge = class {
7448
7830
  return;
7449
7831
  }
7450
7832
  if (json?.type === "session.idle") {
7833
+ if (this.deps.ingest) {
7834
+ this.deps.ingest(json, taskId);
7835
+ return;
7836
+ }
7451
7837
  this.deps.emit({
7452
7838
  type: "task.turn.completed",
7453
7839
  id: taskId,
@@ -7457,6 +7843,10 @@ var OpencodeSseBridge = class {
7457
7843
  const p = json.properties;
7458
7844
  if (p?.id && p?.sessionID) {
7459
7845
  this.pendingPermByTask.set(taskId, { permID: p.id, sessionID: p.sessionID });
7846
+ if (this.deps.ingest) {
7847
+ this.deps.ingest(json, taskId);
7848
+ return;
7849
+ }
7460
7850
  const tool = p.permission ?? "a tool";
7461
7851
  const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
7462
7852
  this.deps.emit({
@@ -7466,11 +7856,24 @@ var OpencodeSseBridge = class {
7466
7856
  question: `opencode requests permission to run ${tool}${cmd}`,
7467
7857
  kind: tool
7468
7858
  });
7859
+ } else if (this.deps.ingest) {
7860
+ this.deps.ingest(json, taskId);
7469
7861
  }
7470
7862
  } else if (json?.type === "permission.replied") {
7471
7863
  this.pendingPermByTask.delete(taskId);
7864
+ this.deps.ingest?.(json, taskId);
7865
+ } else if (!IGNORED_FRAME.test(json?.type ?? "")) {
7866
+ this.deps.ingest?.(json, taskId);
7472
7867
  }
7473
7868
  }
7869
+ /** Test seam: exercise handleLine without an SSE stream. */
7870
+ handleLineForTest(rawLine, taskId) {
7871
+ this.handleLine(taskId, rawLine);
7872
+ }
7873
+ /** Test seam: read pendingPermByTask without exposing it publicly. */
7874
+ pendingPermForTest(taskId) {
7875
+ return this.pendingPermByTask.get(taskId);
7876
+ }
7474
7877
  };
7475
7878
 
7476
7879
  // packages/agents/dist/interactive/claude.js
@@ -8038,70 +8441,41 @@ var ClaudeReceiptListener = class {
8038
8441
  }
8039
8442
  };
8040
8443
 
8041
- // packages/agents/dist/opencode/control-source.js
8042
- var OpencodeControlSource = class {
8043
- name = "opencode-control";
8044
- deps;
8045
- active = false;
8046
- cache = /* @__PURE__ */ new Map();
8047
- start(deps) {
8048
- this.deps = deps;
8049
- this.active = true;
8050
- }
8051
- stop() {
8052
- this.deps = void 0;
8053
- this.active = false;
8054
- this.cache.clear();
8055
- }
8056
- /** Push-only source — no fallible startup of its own. */
8057
- health() {
8058
- return { active: this.active, error: null };
8059
- }
8060
- /** Liveness floor: origin must be "scan" and must not assert needsInput. */
8061
- snapshot(taskId) {
8062
- const s = this.cache.get(taskId);
8063
- if (!s)
8064
- return void 0;
8065
- return { ...s, origin: "scan", state: s.state === "needsInput" ? "running" : s.state };
8066
- }
8067
- /**
8068
- * Feed one ControlEvent from OpencodeSseBridge into the port.
8069
- * Wired in squadrantd.ts as: emit = (ev) => { source.observe(ev); …existing… }
8070
- */
8071
- observe(ev) {
8072
- if (!this.deps)
8073
- return;
8074
- const snap = toSnapshot2(ev);
8075
- if (!snap)
8076
- return;
8077
- this.cache.set(snap.taskId, snap);
8078
- this.deps.report(snap);
8079
- }
8080
- };
8081
- function toSnapshot2(ev) {
8082
- const now = Date.now();
8083
- switch (ev.type) {
8084
- // A permission was answered on the bus and the turn resumed.
8085
- case "task.started":
8086
- return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
8087
- // session.idle — the turn finished. Liveness, NOT completion (anti-#2576).
8088
- case "task.turn.completed":
8089
- return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
8090
- // permission.asked — opencode STATES it is gated. No guessing from pixels.
8091
- case "task.approval.requested":
8092
- return {
8093
- taskId: ev.id,
8094
- state: "needsInput",
8095
- alive: true,
8096
- origin: "agent",
8097
- at: now,
8098
- detail: { note: ev.question, reason: ev.kind }
8099
- };
8100
- // Terminal (task.done/blocked/cancelled) and notify-only events are ignored:
8101
- // terminal state comes exclusively from `squadrant crew signal`.
8102
- default:
8103
- return null;
8104
- }
8444
+ // packages/agents/dist/opencode/fact-adapter.js
8445
+ function createOpencodeFactAdapter(deps) {
8446
+ return {
8447
+ name: "opencode-sse",
8448
+ origin: "agent",
8449
+ translate(raw) {
8450
+ const f = typeof raw === "object" && raw !== null ? raw : {};
8451
+ const type = typeof f.type === "string" ? f.type : void 0;
8452
+ if (type === void 0)
8453
+ return [{ kind: "unknown", name: "non-object" }];
8454
+ const p = f.properties ?? {};
8455
+ if (type === "session.idle") {
8456
+ return [{
8457
+ kind: "turn.ended",
8458
+ turnId: typeof p.sessionID === "string" ? p.sessionID : void 0
8459
+ }];
8460
+ }
8461
+ if (type === "permission.asked") {
8462
+ if (typeof p.id !== "string" || typeof p.sessionID !== "string") {
8463
+ return [{ kind: "unknown", name: "permission.asked:incomplete" }];
8464
+ }
8465
+ const tool = typeof p.permission === "string" ? p.permission : "a tool";
8466
+ const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
8467
+ return [{
8468
+ kind: "permission.requested",
8469
+ question: `opencode requests permission to run ${tool}${cmd}`,
8470
+ requestId: deps.nextRequestId(),
8471
+ tool
8472
+ }];
8473
+ }
8474
+ if (type === "permission.replied")
8475
+ return [{ kind: "activity" }];
8476
+ return [{ kind: "unknown", name: type }];
8477
+ }
8478
+ };
8105
8479
  }
8106
8480
 
8107
8481
  // packages/workspaces/dist/runtimes/cmux.js
@@ -9716,7 +10090,37 @@ function startSquadrantd(opts = {}) {
9716
10090
  codexAppServerSource.observe(ev);
9717
10091
  }
9718
10092
  });
9719
- const opencodeControlSource = new OpencodeControlSource();
10093
+ const violationLogState = /* @__PURE__ */ new Map();
10094
+ const VIOLATION_LOG_SUMMARY_MS = 6e4;
10095
+ const onEventsViolation = (v2) => {
10096
+ const key = `${v2.taskId}:${v2.code}`;
10097
+ const prev = violationLogState.get(key);
10098
+ if (!prev) {
10099
+ violationLogState.set(key, { count: 1, loggedAt: Date.now() });
10100
+ log(`[events] ${v2.code} ${v2.taskId}: ${v2.message}`);
10101
+ return;
10102
+ }
10103
+ prev.count++;
10104
+ const now = Date.now();
10105
+ if (now - prev.loggedAt > VIOLATION_LOG_SUMMARY_MS) {
10106
+ log(`[events] ${v2.code} ${v2.taskId}: seen ${prev.count}x since last log (latest: ${v2.message})`);
10107
+ prev.count = 0;
10108
+ prev.loggedAt = now;
10109
+ }
10110
+ };
10111
+ const opencodeRequestIds = { n: 1 };
10112
+ const eventsSource = createEventsSource({
10113
+ adapters: [createOpencodeFactAdapter({ nextRequestId: () => opencodeRequestIds.n++ })],
10114
+ emit: (ev) => {
10115
+ const found = store.listAll().find((r) => r.id === ev.id);
10116
+ if (!found) return;
10117
+ void ctx.d.handle({ kind: "event", project: found.project, event: ev });
10118
+ if (ev.type === "task.approval.requested")
10119
+ ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
10120
+ },
10121
+ onViolation: onEventsViolation,
10122
+ check: { stallBudgetMs: 6e4, disagreeWindowMs: 5e3 }
10123
+ });
9720
10124
  const opencodeBridge = opts.opencodeBridge ?? new OpencodeSseBridge({
9721
10125
  emit: (ev) => {
9722
10126
  const found = store.listAll().find((r) => r.id === ev.id);
@@ -9724,8 +10128,8 @@ function startSquadrantd(opts = {}) {
9724
10128
  void ctx.d.handle({ kind: "event", project: found.project, event: ev });
9725
10129
  if (ev.type === "task.approval.requested")
9726
10130
  ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
9727
- opencodeControlSource.observe(ev);
9728
10131
  },
10132
+ ingest: (raw, taskId) => eventsSource.ingest("opencode-sse", raw, { taskId }),
9729
10133
  log
9730
10134
  });
9731
10135
  const cmuxEventsBridge = opts.cmuxEventsBridge ?? new CmuxEventsBridge({
@@ -9750,11 +10154,11 @@ function startSquadrantd(opts = {}) {
9750
10154
  ctx.cmuxEventsBridge = cmuxEventsBridge;
9751
10155
  const claudePeerRegistrySource = new ClaudePeerRegistrySource({ log });
9752
10156
  ctx.lifecycleSources = [
10157
+ eventsSource,
9753
10158
  cmuxStoreSource,
9754
10159
  nativeHookSource,
9755
10160
  codexAppServerSource,
9756
- claudePeerRegistrySource,
9757
- opencodeControlSource
10161
+ claudePeerRegistrySource
9758
10162
  ];
9759
10163
  const tgCfg = loadConfig().telegram;
9760
10164
  ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(
@@ -9810,6 +10214,37 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
9810
10214
  }
9811
10215
  });
9812
10216
  const h = startDaemon(ctx, { ...opts, launchHeadless }, PKG_VERSION);
10217
+ const startEventsLifecycleSource = () => {
10218
+ let eventsTaskIndex;
10219
+ let eventsTaskIndexAt = 0;
10220
+ const EVENTS_TASK_INDEX_TTL_MS = 500;
10221
+ const eventsSourceDeps = {
10222
+ resolve: (hint) => {
10223
+ if (!hint.taskId) return void 0;
10224
+ const now = Date.now();
10225
+ if (!eventsTaskIndex || now - eventsTaskIndexAt > EVENTS_TASK_INDEX_TTL_MS) {
10226
+ eventsTaskIndex = /* @__PURE__ */ new Map();
10227
+ for (const r of store.listAll()) {
10228
+ if (!TERMINAL_STATES.has(r.state)) eventsTaskIndex.set(r.id, { id: r.id });
10229
+ }
10230
+ eventsTaskIndexAt = now;
10231
+ }
10232
+ const cached = eventsTaskIndex.get(hint.taskId);
10233
+ if (cached) return cached;
10234
+ return store.listAll().find(
10235
+ (r) => r.id === hint.taskId && !TERMINAL_STATES.has(r.state)
10236
+ );
10237
+ },
10238
+ report: () => {
10239
+ },
10240
+ log
10241
+ };
10242
+ try {
10243
+ eventsSource.start(eventsSourceDeps);
10244
+ } catch (e) {
10245
+ log(`events source start failed: ${e.message}`);
10246
+ }
10247
+ };
9813
10248
  if (!process.env.VITEST) {
9814
10249
  const prevSnaps = /* @__PURE__ */ new Map();
9815
10250
  const storeDeps = {
@@ -9936,19 +10371,9 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
9936
10371
  } catch (e) {
9937
10372
  log(`claude peer registry source start failed: ${e.message}`);
9938
10373
  }
9939
- const opencodeSourceDeps = {
9940
- resolve: () => void 0,
9941
- report: () => {
9942
- },
9943
- // read-only slice: caching is internal to the source
9944
- log
9945
- };
9946
- try {
9947
- opencodeControlSource.start(opencodeSourceDeps);
9948
- } catch (e) {
9949
- log(`opencode control source start failed: ${e.message}`);
9950
- }
10374
+ startEventsLifecycleSource();
9951
10375
  }
10376
+ if (opts.forceStartEventsSource) startEventsLifecycleSource();
9952
10377
  if (!process.env.VITEST) {
9953
10378
  try {
9954
10379
  const buildMtimeMs = statSync4(SELF_PATH2).mtimeMs;
@@ -9985,7 +10410,7 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
9985
10410
  } catch {
9986
10411
  }
9987
10412
  try {
9988
- opencodeControlSource.stop();
10413
+ eventsSource.stop();
9989
10414
  } catch {
9990
10415
  }
9991
10416
  return origStop(reason);