chatroom-cli 1.98.6 → 1.99.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
@@ -83515,6 +83515,321 @@ function harnessCapabilitiesFingerprint(harnesses, versions) {
83515
83515
  return `${h}::${v3}`;
83516
83516
  }
83517
83517
 
83518
+ // src/daemon/infrastructure/outbox/lib/durable-fifo-queue-schema.ts
83519
+ var DURABLE_FIFO_QUEUE_MIGRATIONS;
83520
+ var init_durable_fifo_queue_schema = __esm(() => {
83521
+ DURABLE_FIFO_QUEUE_MIGRATIONS = [
83522
+ `CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
83523
+ `CREATE TABLE IF NOT EXISTS fifo_outbox_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, delivery_key TEXT NOT NULL, payload_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
83524
+ `CREATE INDEX IF NOT EXISTS idx_fifo_outbox_pending ON fifo_outbox_entries(delivery_key, status, id)`
83525
+ ];
83526
+ });
83527
+
83528
+ // src/daemon/infrastructure/outbox/lib/durable-fifo-queue-store.ts
83529
+ import { DatabaseSync } from "node:sqlite";
83530
+ import { mkdirSync as mkdirSync4 } from "node:fs";
83531
+ import { dirname as dirname9 } from "node:path";
83532
+ function openDurableFifoQueueStore(dbPath) {
83533
+ mkdirSync4(dirname9(dbPath), { recursive: true });
83534
+ const db = new DatabaseSync(dbPath);
83535
+ for (const sql of DURABLE_FIFO_QUEUE_MIGRATIONS)
83536
+ db.exec(sql);
83537
+ db.prepare("UPDATE fifo_outbox_entries SET status='pending', updated_at=? WHERE status='in_flight'").run(Date.now());
83538
+ return {
83539
+ enqueue: (key, payloadJson) => {
83540
+ const now = Date.now();
83541
+ return Number(db.prepare("INSERT INTO fifo_outbox_entries(delivery_key,payload_json,created_at,updated_at) VALUES(?,?,?,?) RETURNING id").get(key, payloadJson, now, now).id);
83542
+ },
83543
+ claimNextBatch: (key, limit) => db.prepare("UPDATE fifo_outbox_entries SET status='in_flight', attempts=attempts+1, updated_at=? WHERE id IN (SELECT id FROM fifo_outbox_entries WHERE delivery_key=? AND status='pending' ORDER BY id LIMIT ?) RETURNING id, delivery_key as deliveryKey, payload_json as payloadJson, attempts").all(Date.now(), key, limit),
83544
+ markDone: (id3) => {
83545
+ db.prepare("DELETE FROM fifo_outbox_entries WHERE id=?").run(id3);
83546
+ },
83547
+ markPending: (id3) => {
83548
+ db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=NULL, updated_at=? WHERE id=?").run(Date.now(), id3);
83549
+ },
83550
+ updatePayload: (id3, payloadJson) => {
83551
+ db.prepare("UPDATE fifo_outbox_entries SET payload_json=?, updated_at=? WHERE id=?").run(payloadJson, Date.now(), id3);
83552
+ },
83553
+ markPendingRetry: (id3, error) => {
83554
+ db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=?, updated_at=? WHERE id=?").run(String(error), Date.now(), id3);
83555
+ },
83556
+ listPendingForRecovery: (key) => db.prepare("SELECT id, delivery_key as deliveryKey, payload_json as payloadJson, attempts FROM fifo_outbox_entries WHERE delivery_key=? AND status IN ('pending','in_flight') ORDER BY id").all(key),
83557
+ close: () => db.close()
83558
+ };
83559
+ }
83560
+ var init_durable_fifo_queue_store = __esm(() => {
83561
+ init_durable_fifo_queue_schema();
83562
+ });
83563
+
83564
+ // src/daemon/infrastructure/outbox/lib/outbox-retry-backoff.ts
83565
+ function computeExponentialRetryDelayMs(attempt, retryDelayMs, maxRetryDelayMs) {
83566
+ const exponent = Math.min(Math.max(0, attempt), MAX_RETRY_EXPONENT);
83567
+ return Math.min(retryDelayMs * 2 ** exponent, maxRetryDelayMs);
83568
+ }
83569
+ var MAX_RETRY_EXPONENT = 20;
83570
+
83571
+ // src/daemon/infrastructure/outbox/lib/fifo-batched-outbox.ts
83572
+ function createFifoBatchedOutbox(o) {
83573
+ const waiters = new Map;
83574
+ let running3;
83575
+ let stopped = false;
83576
+ let retryTimer;
83577
+ let retryAttempt = 0;
83578
+ const retryDelay = () => computeExponentialRetryDelayMs(retryAttempt++, o.retryDelayMs ?? 500, o.maxRetryDelayMs ?? 5000);
83579
+ const scheduleRetry = () => {
83580
+ if (!stopped && !retryTimer) {
83581
+ retryTimer = setTimeout(() => {
83582
+ retryTimer = undefined;
83583
+ drain().catch(() => {
83584
+ return;
83585
+ });
83586
+ }, retryDelay());
83587
+ retryTimer.unref?.();
83588
+ }
83589
+ };
83590
+ const drain = async () => {
83591
+ if (running3)
83592
+ return running3;
83593
+ running3 = (async () => {
83594
+ while (true) {
83595
+ const rows = o.store.claimNextBatch(o.deliveryKey, o.batchSize);
83596
+ if (!rows.length)
83597
+ break;
83598
+ try {
83599
+ const results = await o.send(rows.map((r) => o.deserialize(r.payloadJson)));
83600
+ let hasRetry = false;
83601
+ for (let i2 = 0;i2 < results.length; i2++) {
83602
+ const item = o.deserialize(rows[i2].payloadJson);
83603
+ const outcome = o.classifyOutcome?.(results[i2], item) ?? { kind: "success" };
83604
+ if (outcome.kind === "retry") {
83605
+ o.store.updatePayload(rows[i2].id, o.serialize(outcome.item));
83606
+ o.store.markPending(rows[i2].id);
83607
+ hasRetry = true;
83608
+ continue;
83609
+ }
83610
+ o.store.markDone(rows[i2].id);
83611
+ waiters.get(rows[i2].id)?.resolve(results[i2]);
83612
+ waiters.delete(rows[i2].id);
83613
+ }
83614
+ for (let i2 = results.length;i2 < rows.length; i2++)
83615
+ o.store.markPending(rows[i2].id);
83616
+ if (hasRetry) {
83617
+ scheduleRetry();
83618
+ break;
83619
+ }
83620
+ retryAttempt = 0;
83621
+ } catch (e) {
83622
+ o.onError?.(e);
83623
+ for (const r of rows)
83624
+ o.store.markPendingRetry(r.id, e);
83625
+ scheduleRetry();
83626
+ break;
83627
+ }
83628
+ }
83629
+ })().finally(() => {
83630
+ running3 = undefined;
83631
+ });
83632
+ return running3;
83633
+ };
83634
+ const enqueue = (item) => {
83635
+ if (stopped)
83636
+ return Promise.reject(new Error("Outbox is stopped"));
83637
+ const id3 = o.store.enqueue(o.deliveryKey, o.serialize(item));
83638
+ const p = new Promise((resolve4, reject) => waiters.set(id3, { resolve: resolve4, reject }));
83639
+ drain().catch(() => {
83640
+ return;
83641
+ });
83642
+ return p;
83643
+ };
83644
+ const flushNow = async () => {
83645
+ while (o.store.listPendingForRecovery(o.deliveryKey).length) {
83646
+ await drain();
83647
+ }
83648
+ };
83649
+ const stop4 = async () => {
83650
+ if (stopped)
83651
+ return;
83652
+ if (retryTimer)
83653
+ clearTimeout(retryTimer);
83654
+ retryTimer = undefined;
83655
+ await flushNow();
83656
+ stopped = true;
83657
+ };
83658
+ return { enqueue, flushNow, stop: stop4 };
83659
+ }
83660
+ var init_fifo_batched_outbox = () => {};
83661
+
83662
+ // src/daemon/infrastructure/outbox/lib/keyed-fifo-batched-outbox-registry.ts
83663
+ function createKeyedFifoBatchedOutboxRegistry(o) {
83664
+ const boxes = new Map;
83665
+ const get13 = (key) => {
83666
+ let b = boxes.get(key);
83667
+ if (!b) {
83668
+ b = createFifoBatchedOutbox({
83669
+ batchSize: o.batchSize,
83670
+ send: o.createSend(key),
83671
+ store: o.store,
83672
+ deliveryKey: key,
83673
+ serialize: o.serialize,
83674
+ deserialize: o.deserialize,
83675
+ retryDelayMs: o.retryDelayMs,
83676
+ maxRetryDelayMs: o.maxRetryDelayMs,
83677
+ onError: (e) => o.onError?.(key, e),
83678
+ classifyOutcome: o.classifyOutcome
83679
+ });
83680
+ boxes.set(key, b);
83681
+ }
83682
+ return b;
83683
+ };
83684
+ const stop4 = async (k) => {
83685
+ const b = boxes.get(k);
83686
+ if (b) {
83687
+ await b.stop();
83688
+ boxes.delete(k);
83689
+ }
83690
+ };
83691
+ return {
83692
+ enqueue: (k, i2) => get13(k).enqueue(i2),
83693
+ flushNow: async (k) => {
83694
+ if (k)
83695
+ await get13(k).flushNow();
83696
+ else
83697
+ for (const b of boxes.values())
83698
+ await b.flushNow();
83699
+ },
83700
+ stop: stop4,
83701
+ stopAll: async () => {
83702
+ for (const k of [...boxes.keys()])
83703
+ await stop4(k);
83704
+ o.store.close();
83705
+ }
83706
+ };
83707
+ }
83708
+ var init_keyed_fifo_batched_outbox_registry = __esm(() => {
83709
+ init_fifo_batched_outbox();
83710
+ });
83711
+
83712
+ // src/daemon/infrastructure/outbox/outbox-db-path.ts
83713
+ import { homedir as homedir5 } from "node:os";
83714
+ import { join as join17 } from "node:path";
83715
+ function resolveOutboxDbPath(machineId, kind) {
83716
+ return join17(homedir5(), ".chatroom", "daemon", machineId, `outbox-${kind}.sqlite`);
83717
+ }
83718
+ var init_outbox_db_path = () => {};
83719
+
83720
+ // src/daemon/domain/entities/agent-lifecycle-fact.ts
83721
+ function buildExitedLifecycleFact(auditArgs, emittedAt) {
83722
+ const { sessionId: _sessionId, machineId: _machineId, ...fields } = auditArgs;
83723
+ return {
83724
+ kind: "exited",
83725
+ ...fields,
83726
+ revisionKey: buildAgentLifecycleRevisionKey("exited", {
83727
+ chatroomId: auditArgs.chatroomId,
83728
+ role: auditArgs.role,
83729
+ pid: auditArgs.pid,
83730
+ emittedAt
83731
+ }),
83732
+ emittedAt
83733
+ };
83734
+ }
83735
+ function normalizeAgentLifecycleFact(raw) {
83736
+ if (!raw || typeof raw !== "object") {
83737
+ throw new Error("Invalid agent lifecycle fact");
83738
+ }
83739
+ const value = raw;
83740
+ const { sessionId: _sessionId, machineId: _machineId, ...rest } = value;
83741
+ const kind = rest.kind;
83742
+ if (kind === "cleared_all_pids") {
83743
+ return {
83744
+ kind: "cleared_all_pids",
83745
+ revisionKey: String(rest.revisionKey),
83746
+ emittedAt: Number(rest.emittedAt)
83747
+ };
83748
+ }
83749
+ if (kind === "spawned" || kind === "exited") {
83750
+ return rest;
83751
+ }
83752
+ throw new Error(`Unknown agent lifecycle fact kind: ${String(kind)}`);
83753
+ }
83754
+ function agentLifecycleDeliveryKey(machineId, fact) {
83755
+ if (fact.kind === "cleared_all_pids")
83756
+ return `${machineId}:__machine__`;
83757
+ return `${machineId}:${fact.chatroomId}:${fact.role.toLowerCase()}`;
83758
+ }
83759
+ function buildAgentLifecycleRevisionKey(prefix, parts2) {
83760
+ return `${prefix}:${Object.entries(parts2).filter(([, v3]) => v3 !== undefined).map(([k, v3]) => `${k}=${v3}`).join(":")}`;
83761
+ }
83762
+
83763
+ // src/daemon/infrastructure/outbox/agent-lifecycle-outbox.ts
83764
+ function createAgentLifecycleOutboxRegistry(machineId, createSend, options) {
83765
+ const store = openDurableFifoQueueStore(resolveOutboxDbPath(machineId, "agent-lifecycle"));
83766
+ return createKeyedFifoBatchedOutboxRegistry({
83767
+ store,
83768
+ batchSize: AGENT_LIFECYCLE_OUTBOX_BATCH_SIZE,
83769
+ createSend: (key) => async (facts) => {
83770
+ const results = [];
83771
+ for (const fact of facts)
83772
+ results.push(await createSend(key)(fact));
83773
+ return results;
83774
+ },
83775
+ serialize: JSON.stringify,
83776
+ deserialize: (serialized) => normalizeAgentLifecycleFact(JSON.parse(serialized)),
83777
+ retryDelayMs: 500,
83778
+ maxRetryDelayMs: 5 * 60000,
83779
+ onError: options?.onError,
83780
+ classifyOutcome: () => ({ kind: "success" })
83781
+ });
83782
+ }
83783
+ function agentLifecycleKey(machineId, fact) {
83784
+ return agentLifecycleDeliveryKey(machineId, fact);
83785
+ }
83786
+ var AGENT_LIFECYCLE_OUTBOX_BATCH_SIZE = 1;
83787
+ var init_agent_lifecycle_outbox = __esm(() => {
83788
+ init_durable_fifo_queue_store();
83789
+ init_keyed_fifo_batched_outbox_registry();
83790
+ init_outbox_db_path();
83791
+ });
83792
+
83793
+ // src/daemon/infrastructure/outbox/agent-lifecycle-send.ts
83794
+ function toConvexLifecycleFact(fact) {
83795
+ const normalized = normalizeAgentLifecycleFact(fact);
83796
+ if (normalized.kind === "cleared_all_pids")
83797
+ return normalized;
83798
+ return {
83799
+ ...normalized,
83800
+ chatroomId: normalized.chatroomId
83801
+ };
83802
+ }
83803
+ function createAgentLifecycleSend(session2) {
83804
+ return async (fact) => {
83805
+ const args2 = {
83806
+ sessionId: session2.sessionId,
83807
+ machineId: session2.machineId,
83808
+ fact: toConvexLifecycleFact(fact)
83809
+ };
83810
+ return await session2.backend.mutation(api.machines.projectAgentLifecycleFact, args2);
83811
+ };
83812
+ }
83813
+ var init_agent_lifecycle_send = __esm(() => {
83814
+ init_api3();
83815
+ });
83816
+
83817
+ // src/daemon/entry/agent-lifecycle-outbox-runtime.ts
83818
+ function createAgentLifecycleOutboxForSession(args2) {
83819
+ return createAgentLifecycleOutboxRegistry(args2.machineId, () => createAgentLifecycleSend({
83820
+ sessionId: args2.sessionId,
83821
+ machineId: args2.machineId,
83822
+ backend: args2.backend
83823
+ }));
83824
+ }
83825
+ function enqueueAgentLifecycleFact(registry2, machineId, fact) {
83826
+ return registry2.enqueue(agentLifecycleKey(machineId, fact), fact);
83827
+ }
83828
+ var init_agent_lifecycle_outbox_runtime = __esm(() => {
83829
+ init_agent_lifecycle_outbox();
83830
+ init_agent_lifecycle_send();
83831
+ });
83832
+
83518
83833
  // src/daemon/entry/daemon-services.ts
83519
83834
  function DaemonMutableStateServiceLive(init2) {
83520
83835
  return exports_Layer.effect(DaemonMutableStateService, exports_Effect.gen(function* () {
@@ -83526,7 +83841,10 @@ function DaemonMutableStateServiceLive(init2) {
83526
83841
  };
83527
83842
  }));
83528
83843
  }
83529
- var DaemonMachineService, DaemonMachineServiceLive = (ops) => exports_Layer.succeed(DaemonMachineService, {
83844
+ var AgentLifecycleOutboxService, AgentLifecycleOutboxServiceLive = (registry2, machineId) => exports_Layer.succeed(AgentLifecycleOutboxService, {
83845
+ enqueue: (fact) => exports_Effect.promise(() => enqueueAgentLifecycleFact(registry2, machineId, fact)),
83846
+ stopAll: () => exports_Effect.promise(() => registry2.stopAll())
83847
+ }), DaemonMachineService, DaemonMachineServiceLive = (ops) => exports_Layer.succeed(DaemonMachineService, {
83530
83848
  clearAgentPid: (machineId, chatroomId, role) => exports_Effect.promise(() => ops.clearAgentPid(machineId, chatroomId, role)),
83531
83849
  persistAgentPid: (machineId, chatroomId, role, pid, harness) => exports_Effect.promise(() => ops.persistAgentPid(machineId, chatroomId, role, pid, harness)),
83532
83850
  listAgentEntries: (machineId) => exports_Effect.promise(() => ops.listAgentEntries(machineId)),
@@ -83550,6 +83868,9 @@ var DaemonMachineService, DaemonMachineServiceLive = (ops) => exports_Layer.succ
83550
83868
  }), DaemonSessionService, DaemonMutableStateService;
83551
83869
  var init_daemon_services = __esm(() => {
83552
83870
  init_esm();
83871
+ init_agent_lifecycle_outbox_runtime();
83872
+ AgentLifecycleOutboxService = class AgentLifecycleOutboxService extends exports_Context.Tag("AgentLifecycleOutboxService")() {
83873
+ };
83553
83874
  DaemonMachineService = class DaemonMachineService extends exports_Context.Tag("DaemonMachineService")() {
83554
83875
  };
83555
83876
  DaemonSpawningService = class DaemonSpawningService extends exports_Context.Tag("DaemonSpawningService")() {
@@ -83572,7 +83893,7 @@ function daemonSessionToLayers(init2) {
83572
83893
  return exports_Layer.mergeAll(BackendServiceLive({
83573
83894
  mutation: (e, a) => init2.backend.mutation(e, a),
83574
83895
  query: (e, a) => init2.backend.query(e, a)
83575
- }), ProcessServiceLive, ClockServiceLive, FsServiceLive, DaemonMachineServiceLive(init2.machine), DaemonSpawningServiceLive(init2.spawning), DaemonAgentProcessManagerServiceLive(init2.agentProcessManager), DaemonMutableStateServiceLive({
83896
+ }), ProcessServiceLive, ClockServiceLive, FsServiceLive, DaemonMachineServiceLive(init2.machine), AgentLifecycleOutboxServiceLive(init2.agentLifecycleOutbox, init2.machineId), DaemonSpawningServiceLive(init2.spawning), DaemonAgentProcessManagerServiceLive(init2.agentProcessManager), DaemonMutableStateServiceLive({
83576
83897
  lastPushedGitState: init2.lastPushedGitState,
83577
83898
  lastPushedModels: init2.lastPushedModels,
83578
83899
  lastPushedHarnessFingerprint: init2.lastPushedHarnessFingerprint,
@@ -83696,10 +84017,18 @@ var init_register_listeners = __esm(() => {
83696
84017
 
83697
84018
  // src/daemon/entry/handlers/daemon-restart-cleanup.ts
83698
84019
  var clearStaleSpawnedPidsEffect = () => exports_Effect.gen(function* () {
83699
- const backend2 = yield* BackendService;
84020
+ const outbox = yield* AgentLifecycleOutboxService;
83700
84021
  const session2 = yield* DaemonSessionService;
83701
- const result = yield* backend2.mutation(api.machines.clearAllSpawnedPids, { sessionId: session2.sessionId, machineId: session2.machineId });
83702
- return result.clearedCount;
84022
+ const emittedAt = Date.now();
84023
+ const result = yield* outbox.enqueue({
84024
+ kind: "cleared_all_pids",
84025
+ revisionKey: buildAgentLifecycleRevisionKey("cleared_all", {
84026
+ machineId: session2.machineId,
84027
+ emittedAt
84028
+ }),
84029
+ emittedAt
84030
+ });
84031
+ return result.clearedCount ?? 0;
83703
84032
  }), reapOrphanCommandRunsEffect = () => exports_Effect.gen(function* () {
83704
84033
  const backend2 = yield* BackendService;
83705
84034
  const session2 = yield* DaemonSessionService;
@@ -83737,26 +84066,26 @@ import { createHash as createHash2 } from "node:crypto";
83737
84066
  import {
83738
84067
  appendFileSync,
83739
84068
  existsSync as existsSync6,
83740
- mkdirSync as mkdirSync4,
84069
+ mkdirSync as mkdirSync5,
83741
84070
  readFileSync as readFileSync8,
83742
84071
  renameSync,
83743
84072
  unlinkSync,
83744
84073
  writeFileSync as writeFileSync3
83745
84074
  } from "node:fs";
83746
- import { homedir as homedir5 } from "node:os";
83747
- import { join as join17 } from "node:path";
84075
+ import { homedir as homedir6 } from "node:os";
84076
+ import { join as join18 } from "node:path";
83748
84077
  function getUrlHash() {
83749
84078
  const url2 = getConvexUrl();
83750
84079
  return createHash2("sha256").update(url2).digest("hex").substring(0, 8);
83751
84080
  }
83752
84081
  function getChildPidsFilePath() {
83753
- const dir = join17(homedir5(), ".chatroom");
83754
- return join17(dir, `daemon-children-${getUrlHash()}.pids`);
84082
+ const dir = join18(homedir6(), ".chatroom");
84083
+ return join18(dir, `daemon-children-${getUrlHash()}.pids`);
83755
84084
  }
83756
84085
  function ensureChatroomDir2() {
83757
- const dir = join17(homedir5(), ".chatroom");
84086
+ const dir = join18(homedir6(), ".chatroom");
83758
84087
  if (!existsSync6(dir)) {
83759
- mkdirSync4(dir, { recursive: true, mode: 448 });
84088
+ mkdirSync5(dir, { recursive: true, mode: 448 });
83760
84089
  }
83761
84090
  }
83762
84091
  function readPids() {
@@ -83869,7 +84198,7 @@ var init_orphan_tracker = __esm(() => {
83869
84198
  // src/daemon/entry/handlers/process/output-store.ts
83870
84199
  import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile6, rm as rm2 } from "node:fs/promises";
83871
84200
  import { tmpdir } from "node:os";
83872
- import { join as join18 } from "node:path";
84201
+ import { join as join19 } from "node:path";
83873
84202
 
83874
84203
  class TempFileOutputStore {
83875
84204
  state;
@@ -83932,7 +84261,7 @@ function createOutputStore(runId) {
83932
84261
  if (!RUN_ID_RE.test(runId)) {
83933
84262
  throw new Error(`Invalid runId: ${runId}`);
83934
84263
  }
83935
- const filePath = join18(TEMP_DIR, `${runId}.log`);
84264
+ const filePath = join19(TEMP_DIR, `${runId}.log`);
83936
84265
  return new TempFileOutputStore(filePath);
83937
84266
  }
83938
84267
  async function ensureTempDir() {
@@ -83946,7 +84275,7 @@ async function cleanOrphanTempFiles() {
83946
84275
  var TAIL_WINDOW_BYTES, TEMP_DIR, RUN_ID_RE, MAX_TAIL_LINES_V2 = 50;
83947
84276
  var init_output_store = __esm(() => {
83948
84277
  TAIL_WINDOW_BYTES = 32 * 1024;
83949
- TEMP_DIR = join18(tmpdir(), "chatroom-cli", "runs");
84278
+ TEMP_DIR = join19(tmpdir(), "chatroom-cli", "runs");
83950
84279
  RUN_ID_RE = /^[a-z0-9]+$/i;
83951
84280
  });
83952
84281
 
@@ -84015,7 +84344,7 @@ async function logDaemonAuditEvent(logEvent, event) {
84015
84344
  }
84016
84345
 
84017
84346
  // ../../services/backend/config/reliability.ts
84018
- var HARNESS_SESSION_READY_TIMEOUT_MS = 5000, NATIVE_DELIVERY_RECONCILE_MS = 1e4, DAEMON_HEARTBEAT_INTERVAL_MS, DAEMON_HEARTBEAT_TTL_MS, AGENT_REQUEST_DEADLINE_MS = 120000, OBSERVED_FULL_PUSH_INTERVAL_MS, OBSERVED_SYNC_RECONCILE_MS, WORKSPACE_RECENCY_WINDOW_MS, WORKSPACE_LIST_RECONCILE_MS, CONNECTION_CLOSE_REQUEST_TTL_MS, ENHANCER_TERMINAL_JOB_RETENTION_MS;
84347
+ var HARNESS_SESSION_READY_TIMEOUT_MS = 5000, DAEMON_HEARTBEAT_INTERVAL_MS, DAEMON_HEARTBEAT_TTL_MS, AGENT_REQUEST_DEADLINE_MS = 120000, OBSERVED_FULL_PUSH_INTERVAL_MS, OBSERVED_SYNC_RECONCILE_MS, WORKSPACE_RECENCY_WINDOW_MS, WORKSPACE_LIST_RECONCILE_MS, CONNECTION_CLOSE_REQUEST_TTL_MS, ENHANCER_TERMINAL_JOB_RETENTION_MS;
84019
84348
  var init_reliability = __esm(() => {
84020
84349
  DAEMON_HEARTBEAT_INTERVAL_MS = 5 * 60000;
84021
84350
  DAEMON_HEARTBEAT_TTL_MS = 6 * DAEMON_HEARTBEAT_INTERVAL_MS;
@@ -98743,8 +99072,6 @@ var init_assigned_task_snapshot_contract = __esm(() => {
98743
99072
  assignedTo: exports_external.string().optional(),
98744
99073
  lastSeenAction: exports_external.string().nullable().optional(),
98745
99074
  lastStatus: exports_external.string().nullable().optional(),
98746
- spawnedAgentPid: exports_external.number().optional(),
98747
- desiredState: agentDesiredStateSchema.optional(),
98748
99075
  sessionAugmentation: sessionAugmentationSchema.optional()
98749
99076
  };
98750
99077
  assignedTaskSignalSchema = exports_external.object(assignedTaskSignalBootstrapFields);
@@ -98821,13 +99148,99 @@ function getNativeDeliveryLedger() {
98821
99148
  }
98822
99149
  var sharedLedger;
98823
99150
 
99151
+ // src/daemon/infrastructure/agent-operational/agent-operational-read-model.ts
99152
+ function isOperationalDesiredRunning(row) {
99153
+ return row?.operationalState === "running" || row?.operationalState === "starting";
99154
+ }
99155
+
99156
+ class AgentOperationalReadModel {
99157
+ rows = new Map;
99158
+ replace(rows) {
99159
+ const changed = [];
99160
+ const nextKeys = new Set;
99161
+ for (const row of rows) {
99162
+ const key = roleKey(row.chatroomId, row.role);
99163
+ nextKeys.add(key);
99164
+ const prev = this.rows.get(key);
99165
+ if (!prev || prev.revisionKey !== row.revisionKey)
99166
+ changed.push({ chatroomId: row.chatroomId, role: row.role });
99167
+ this.rows.set(key, row);
99168
+ }
99169
+ for (const [key, prev] of this.rows) {
99170
+ if (!nextKeys.has(key)) {
99171
+ changed.push({ chatroomId: prev.chatroomId, role: prev.role });
99172
+ this.rows.delete(key);
99173
+ }
99174
+ }
99175
+ return changed;
99176
+ }
99177
+ get(chatroomId, role) {
99178
+ return this.rows.get(roleKey(chatroomId, role));
99179
+ }
99180
+ }
99181
+ var roleKey = (chatroomId, role) => `${chatroomId}:${role.toLowerCase()}`;
99182
+
99183
+ // src/daemon/infrastructure/inbox/task-snapshot-state.ts
99184
+ function snapshotKey(taskId, role) {
99185
+ return `${taskId}:${role.toLowerCase()}`;
99186
+ }
99187
+
99188
+ class MachineTaskSnapshotState {
99189
+ snapshots = new Map;
99190
+ replace(snapshots) {
99191
+ this.snapshots.clear();
99192
+ this.upsert(snapshots);
99193
+ }
99194
+ applySignalPage(signals, snapshots) {
99195
+ const snapshotsByKey = new Map(snapshots.map((snapshot) => [
99196
+ snapshotKey(snapshot.taskId, snapshot.agentConfig.role),
99197
+ snapshot
99198
+ ]));
99199
+ for (const signal of signals) {
99200
+ const key = snapshotKey(signal.taskId, signal.targetRole);
99201
+ const snapshot = snapshotsByKey.get(key);
99202
+ if (snapshot) {
99203
+ this.snapshots.set(key, snapshot);
99204
+ } else {
99205
+ this.snapshots.delete(key);
99206
+ }
99207
+ }
99208
+ }
99209
+ listForRole(chatroomId, role) {
99210
+ const roleLower = role.toLowerCase();
99211
+ return [...this.snapshots.values()].filter((snapshot) => snapshot.chatroomId === chatroomId && snapshot.agentConfig.role.toLowerCase() === roleLower);
99212
+ }
99213
+ listAll() {
99214
+ return [...this.snapshots.values()];
99215
+ }
99216
+ upsert(snapshots) {
99217
+ for (const snapshot of snapshots) {
99218
+ this.snapshots.set(snapshotKey(snapshot.taskId, snapshot.agentConfig.role), snapshot);
99219
+ }
99220
+ }
99221
+ }
99222
+
99223
+ // src/daemon/entry/native-delivery/native-delivery-session-registry.ts
99224
+ function registerNativeDeliverySession(ctx) {
99225
+ registered = {
99226
+ ...ctx,
99227
+ taskSnapshotState: ctx.taskSnapshotState ?? new MachineTaskSnapshotState,
99228
+ agentOperationalReadModel: ctx.agentOperationalReadModel ?? new AgentOperationalReadModel
99229
+ };
99230
+ }
99231
+ function unregisterNativeDeliverySession() {
99232
+ registered = null;
99233
+ }
99234
+ function getNativeDeliverySession() {
99235
+ return registered;
99236
+ }
99237
+ var registered = null;
99238
+ var init_native_delivery_session_registry = () => {};
99239
+
98824
99240
  // src/daemon/domain/entities/assigned-task.ts
98825
99241
  function isDeliverableTaskStatus(status3) {
98826
99242
  return status3 === "pending" || status3 === "acknowledged";
98827
99243
  }
98828
- function isAgentDesiredRunning(desiredState) {
98829
- return desiredState === "running";
98830
- }
98831
99244
  var init_assigned_task = () => {};
98832
99245
 
98833
99246
  // src/daemon/domain/usecase/check-agent-slot.ts
@@ -98857,19 +99270,20 @@ function explainAgentReadyForNativeDeliveryBlock(task, slot) {
98857
99270
  if (!isNativeHarness(agentConfig.agentHarness)) {
98858
99271
  return `not_native_harness (harness=${agentConfig.agentHarness})`;
98859
99272
  }
98860
- if (!isAgentDesiredRunning(agentConfig.desiredState)) {
98861
- return `desired_state_not_running (desiredState=${agentConfig.desiredState})`;
98862
- }
98863
- if (agentConfig.spawnedAgentPid == null) {
98864
- return "spawned_pid_missing";
99273
+ const operational = getNativeDeliverySession()?.agentOperationalReadModel?.get(task.chatroomId, agentConfig.role);
99274
+ if (!isOperationalDesiredRunning(operational)) {
99275
+ return `operational_state_not_running (state=${operational?.operationalState ?? "missing"})`;
98865
99276
  }
98866
99277
  if (!slot) {
98867
- return `slot_missing (expectedPid=${agentConfig.spawnedAgentPid})`;
99278
+ return agentConfig.spawnedAgentPid == null ? "spawned_pid_missing" : `slot_missing (expectedPid=${agentConfig.spawnedAgentPid})`;
98868
99279
  }
98869
99280
  if (!isSlotRunning(slot.state)) {
98870
- return `slot_not_running (slotState=${slot.state}, expectedPid=${agentConfig.spawnedAgentPid})`;
99281
+ return `slot_not_running (slotState=${slot.state}, expectedPid=${agentConfig.spawnedAgentPid ?? "none"})`;
98871
99282
  }
98872
- if (slot.pid !== agentConfig.spawnedAgentPid) {
99283
+ if (slot.pid == null) {
99284
+ return "slot_pid_missing";
99285
+ }
99286
+ if (agentConfig.spawnedAgentPid != null && slot.pid !== agentConfig.spawnedAgentPid) {
98873
99287
  return `pid_mismatch (slotPid=${slot.pid ?? "none"}, snapshotPid=${agentConfig.spawnedAgentPid})`;
98874
99288
  }
98875
99289
  if (typeof slot.harnessSessionId !== "string" || slot.harnessSessionId.length === 0) {
@@ -98886,6 +99300,7 @@ function isDeliverableNativeTaskStatus(status3) {
98886
99300
  }
98887
99301
  var init_native_ready_invariant = __esm(() => {
98888
99302
  init_types();
99303
+ init_native_delivery_session_registry();
98889
99304
  init_assigned_task();
98890
99305
  init_check_agent_slot();
98891
99306
  });
@@ -98911,18 +99326,6 @@ function logNativeDeliveryNoTasks(role, chatroomId) {
98911
99326
  console.log(`[NativeDelivery:skip] ${role}@${chatroomId} — no pending tasks for role`);
98912
99327
  }
98913
99328
 
98914
- // src/daemon/entry/native-delivery/native-delivery-session-registry.ts
98915
- function registerNativeDeliverySession(ctx) {
98916
- registered = ctx;
98917
- }
98918
- function unregisterNativeDeliverySession() {
98919
- registered = null;
98920
- }
98921
- function getNativeDeliverySession() {
98922
- return registered;
98923
- }
98924
- var registered = null;
98925
-
98926
99329
  // src/daemon/entry/native-delivery/native-stale-turn-phase.ts
98927
99330
  function isStaleTurnInFlightWhileWaiting(task, slot) {
98928
99331
  if (!slot || isTurnPhaseIdle(slot.nativeTurnPhase ?? "idle"))
@@ -99409,38 +99812,21 @@ function mapAssignedTaskSnapshotList(rows) {
99409
99812
  return rows.map(mapAssignedTaskSnapshot);
99410
99813
  }
99411
99814
 
99412
- // src/daemon/infrastructure/inbox/fetch-machine-assigned-task-snapshots.ts
99413
- async function fetchMachineAssignedTaskSnapshots(sessionDeps, machineId) {
99414
- const result = await sessionDeps.backend.query(api.machines.listMachineAssignedTaskSnapshots, {
99415
- sessionId: sessionDeps.sessionId,
99416
- machineId
99417
- });
99418
- return mapAssignedTaskSnapshotList(parseAssignedTaskSnapshotRows(result?.tasks ?? []));
99419
- }
99420
- function filterSnapshotsForRole(snapshots, chatroomId, role) {
99421
- const roleLower = role.toLowerCase();
99422
- return snapshots.filter((row) => row.chatroomId === chatroomId && row.agentConfig.role.toLowerCase() === roleLower);
99423
- }
99424
- var init_fetch_machine_assigned_task_snapshots = __esm(() => {
99425
- init_assigned_task_snapshot_contract();
99426
- init_api3();
99427
- });
99428
-
99429
99815
  // src/daemon/entry/restart-orchestrator-in-flight.ts
99430
- function roleKey(chatroomId, role) {
99816
+ function roleKey2(chatroomId, role) {
99431
99817
  return `${chatroomId}:${role.toLowerCase()}`;
99432
99818
  }
99433
99819
  function markRestartOrchestratorInFlight(chatroomId, role, correlationId) {
99434
- inFlight.set(roleKey(chatroomId, role), correlationId);
99820
+ inFlight.set(roleKey2(chatroomId, role), correlationId);
99435
99821
  }
99436
99822
  function clearRestartOrchestratorInFlight(chatroomId, role, correlationId) {
99437
- const key = roleKey(chatroomId, role);
99823
+ const key = roleKey2(chatroomId, role);
99438
99824
  if (correlationId !== undefined && inFlight.get(key) !== correlationId)
99439
99825
  return;
99440
99826
  inFlight.delete(key);
99441
99827
  }
99442
99828
  function isRestartOrchestratorInFlight(chatroomId, role) {
99443
- return inFlight.has(roleKey(chatroomId, role));
99829
+ return inFlight.has(roleKey2(chatroomId, role));
99444
99830
  }
99445
99831
  function filterSnapshotsExcludingRestartInFlight(snapshots) {
99446
99832
  return snapshots.filter((row) => !isRestartOrchestratorInFlight(row.chatroomId, row.agentConfig.role));
@@ -99451,7 +99837,7 @@ var init_restart_orchestrator_in_flight = __esm(() => {
99451
99837
  });
99452
99838
 
99453
99839
  // src/daemon/entry/role-delivery-state.ts
99454
- function roleKey2(chatroomId, role) {
99840
+ function roleKey3(chatroomId, role) {
99455
99841
  return `${chatroomId}:${role.toLowerCase()}`;
99456
99842
  }
99457
99843
 
@@ -99460,10 +99846,10 @@ class RoleDeliveryState {
99460
99846
  inFlight = new Set;
99461
99847
  nativeNudgeFailures = new Map;
99462
99848
  getGeneration(chatroomId, role) {
99463
- return this.generation.get(roleKey2(chatroomId, role)) ?? 0;
99849
+ return this.generation.get(roleKey3(chatroomId, role)) ?? 0;
99464
99850
  }
99465
99851
  resetDeliveryState(chatroomId, role) {
99466
- const key = roleKey2(chatroomId, role);
99852
+ const key = roleKey3(chatroomId, role);
99467
99853
  const next4 = (this.generation.get(key) ?? 0) + 1;
99468
99854
  this.generation.set(key, next4);
99469
99855
  this.inFlight.delete(key);
@@ -99471,26 +99857,26 @@ class RoleDeliveryState {
99471
99857
  return next4;
99472
99858
  }
99473
99859
  tryAcquireDelivery(chatroomId, role) {
99474
- const key = roleKey2(chatroomId, role);
99860
+ const key = roleKey3(chatroomId, role);
99475
99861
  if (this.inFlight.has(key))
99476
99862
  return false;
99477
99863
  this.inFlight.add(key);
99478
99864
  return true;
99479
99865
  }
99480
99866
  releaseDelivery(chatroomId, role) {
99481
- this.inFlight.delete(roleKey2(chatroomId, role));
99867
+ this.inFlight.delete(roleKey3(chatroomId, role));
99482
99868
  }
99483
99869
  recordNativeNudgeFailure(chatroomId, role) {
99484
- const key = roleKey2(chatroomId, role);
99870
+ const key = roleKey3(chatroomId, role);
99485
99871
  const count3 = (this.nativeNudgeFailures.get(key) ?? 0) + 1;
99486
99872
  this.nativeNudgeFailures.set(key, count3);
99487
99873
  return count3;
99488
99874
  }
99489
99875
  clearNativeNudgeFailures(chatroomId, role) {
99490
- this.nativeNudgeFailures.delete(roleKey2(chatroomId, role));
99876
+ this.nativeNudgeFailures.delete(roleKey3(chatroomId, role));
99491
99877
  }
99492
99878
  getNativeNudgeFailures(chatroomId, role) {
99493
- return this.nativeNudgeFailures.get(roleKey2(chatroomId, role)) ?? 0;
99879
+ return this.nativeNudgeFailures.get(roleKey3(chatroomId, role)) ?? 0;
99494
99880
  }
99495
99881
  }
99496
99882
  function getRoleDeliveryState() {
@@ -99517,22 +99903,18 @@ class NativeTaskDeliveryCoordinator {
99517
99903
  if (!session2)
99518
99904
  return;
99519
99905
  const { runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps, machineId } = session2;
99520
- fetchMachineAssignedTaskSnapshots(sessionDeps, machineId).then((all8) => {
99521
- const tasks = filterSnapshotsForRole(all8, chatroomId, role);
99522
- if (tasks.length === 0) {
99523
- logNativeDeliveryNoTasks(role, chatroomId);
99524
- return;
99525
- }
99526
- this.reconcileAssignedTasks({
99527
- tasks,
99528
- runtime: runtime4,
99529
- effectContext: effectContext2,
99530
- agentMgr,
99531
- sessionDeps,
99532
- machineId
99533
- });
99534
- }).catch((err) => {
99535
- console.warn(`[NativeTaskDelivery] rehydrate failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`);
99906
+ const tasks = session2.taskSnapshotState?.listForRole(chatroomId, role) ?? [];
99907
+ if (tasks.length === 0) {
99908
+ logNativeDeliveryNoTasks(role, chatroomId);
99909
+ return;
99910
+ }
99911
+ this.reconcileAssignedTasks({
99912
+ tasks,
99913
+ runtime: runtime4,
99914
+ effectContext: effectContext2,
99915
+ agentMgr,
99916
+ sessionDeps,
99917
+ machineId
99536
99918
  });
99537
99919
  }
99538
99920
  reconcileAssignedTasks(params) {
@@ -99652,13 +100034,13 @@ function notifyNativeTurnIdle(params) {
99652
100034
  var coordinator;
99653
100035
  var init_native_task_delivery_coordinator = __esm(() => {
99654
100036
  init_esm();
100037
+ init_native_delivery_session_registry();
99655
100038
  init_native_stale_turn_phase();
99656
100039
  init_native_task_injector_logic();
99657
100040
  init_native_task_injector();
99658
100041
  init_api3();
99659
100042
  init_assigned_task();
99660
100043
  init_convex_error();
99661
- init_fetch_machine_assigned_task_snapshots();
99662
100044
  init_restart_orchestrator_in_flight();
99663
100045
  });
99664
100046
 
@@ -99880,15 +100262,19 @@ var init_restart_orchestrator = __esm(() => {
99880
100262
  function createStartAgentDeps(agentMgr, session2) {
99881
100263
  return {
99882
100264
  agentProcessManager: {
99883
- ensureRunning: async (args2) => exports_Effect.runPromise(agentMgr.ensureRunning({
99884
- chatroomId: args2.chatroomId,
99885
- role: args2.role,
99886
- agentHarness: args2.agentHarness,
99887
- model: args2.model,
99888
- workingDir: args2.workingDir,
99889
- reason: args2.reason,
99890
- wantResume: args2.wantResume
99891
- }))
100265
+ ensureRunning: async (args2) => {
100266
+ const result = await exports_Effect.runPromise(agentMgr.ensureRunning({
100267
+ chatroomId: args2.chatroomId,
100268
+ role: args2.role,
100269
+ agentHarness: args2.agentHarness,
100270
+ model: args2.model,
100271
+ workingDir: args2.workingDir,
100272
+ reason: args2.reason,
100273
+ wantResume: args2.wantResume
100274
+ }));
100275
+ if (!result.success) {}
100276
+ return result;
100277
+ }
99892
100278
  },
99893
100279
  session: {
99894
100280
  sessionId: session2.sessionId,
@@ -100144,9 +100530,9 @@ var init_daemon_process_scan = __esm(() => {
100144
100530
 
100145
100531
  // src/commands/machine/pid.ts
100146
100532
  import { createHash as createHash3 } from "node:crypto";
100147
- import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "node:fs";
100148
- import { homedir as homedir6 } from "node:os";
100149
- import { join as join19 } from "node:path";
100533
+ import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync6 } from "node:fs";
100534
+ import { homedir as homedir7 } from "node:os";
100535
+ import { join as join20 } from "node:path";
100150
100536
  function getUrlHash2() {
100151
100537
  const url3 = getConvexUrl();
100152
100538
  return createHash3("sha256").update(url3).digest("hex").substring(0, 8);
@@ -100156,11 +100542,11 @@ function getPidFileName() {
100156
100542
  }
100157
100543
  function ensureChatroomDir3() {
100158
100544
  if (!existsSync8(CHATROOM_DIR4)) {
100159
- mkdirSync5(CHATROOM_DIR4, { recursive: true, mode: 448 });
100545
+ mkdirSync6(CHATROOM_DIR4, { recursive: true, mode: 448 });
100160
100546
  }
100161
100547
  }
100162
100548
  function getPidFilePath() {
100163
- return join19(CHATROOM_DIR4, getPidFileName());
100549
+ return join20(CHATROOM_DIR4, getPidFileName());
100164
100550
  }
100165
100551
  function isProcessRunning(pid) {
100166
100552
  try {
@@ -100306,7 +100692,7 @@ var CHATROOM_DIR4, LOCK_RETRY_INTERVAL_MS = 500, LOCK_RETRY_MAX_WAIT_MS = 15000,
100306
100692
  var init_pid = __esm(() => {
100307
100693
  init_daemon_process_scan();
100308
100694
  init_client2();
100309
- CHATROOM_DIR4 = join19(homedir6(), ".chatroom");
100695
+ CHATROOM_DIR4 = join20(homedir7(), ".chatroom");
100310
100696
  });
100311
100697
 
100312
100698
  // src/infrastructure/machine/crash-loop-tracker.ts
@@ -102052,9 +102438,8 @@ class AgentProcessManager {
102052
102438
  console.log(` ⚠️ ${failureLog}: ${err.message}`);
102053
102439
  this.queueExitRetry({ role, args: exitArgs });
102054
102440
  });
102055
- this.deps.backend.mutation(api.daemon.agentEvents.agentExited, exitArgs).catch((err) => {
102056
- console.log(` ⚠️ Failed to apply agent exit state: ${err.message}`);
102057
- });
102441
+ const emittedAt = this.deps.clock.now();
102442
+ this.deps.lifecycleOutbox.enqueue(buildExitedLifecycleFact(exitArgs, emittedAt)).catch((err) => console.log(` ⚠️ Failed to enqueue agent exited lifecycle fact: ${err.message}`));
102058
102443
  }
102059
102444
  async clearAgentPidQuietly(chatroomId, role) {
102060
102445
  try {
@@ -102458,28 +102843,23 @@ class AgentProcessManager {
102458
102843
  }).catch((err) => {
102459
102844
  console.log(` ⚠️ Failed to record agent.started event: ${err.message}`);
102460
102845
  });
102461
- this.deps.backend.mutation(api.daemon.agentEvents.agentStarted, {
102462
- sessionId: this.deps.sessionId,
102463
- machineId: this.deps.machineId,
102846
+ const emittedAt = this.deps.clock.now();
102847
+ this.deps.lifecycleOutbox.enqueue({
102848
+ kind: "spawned",
102464
102849
  chatroomId: opts.chatroomId,
102465
102850
  role: opts.role,
102466
102851
  pid,
102467
102852
  model: opts.model,
102468
102853
  reason: opts.reason,
102469
- ...spawnResult.harnessSessionId ? { harnessSessionId: spawnResult.harnessSessionId } : {}
102470
- }).catch((err) => {
102471
- console.log(` ⚠️ Failed to apply agent.started state: ${err.message}`);
102472
- });
102473
- this.deps.backend.mutation(api.machines.updateSpawnedAgent, {
102474
- sessionId: this.deps.sessionId,
102475
- machineId: this.deps.machineId,
102476
- chatroomId: opts.chatroomId,
102477
- role: opts.role,
102478
- pid,
102479
- model: opts.model
102480
- }).catch((err) => {
102481
- console.log(` ⚠️ Failed to update PID in backend: ${err.message}`);
102482
- });
102854
+ ...spawnResult.harnessSessionId ? { harnessSessionId: spawnResult.harnessSessionId } : {},
102855
+ revisionKey: buildAgentLifecycleRevisionKey("spawned", {
102856
+ chatroomId: opts.chatroomId,
102857
+ role: opts.role,
102858
+ pid,
102859
+ emittedAt
102860
+ }),
102861
+ emittedAt
102862
+ }).catch((err) => console.log(` ⚠️ Failed to enqueue agent spawned lifecycle fact: ${err.message}`));
102483
102863
  }
102484
102864
  registerSpawnCallbacks(slot, opts, spawnResult, pid) {
102485
102865
  if (spawnResult.onLogLine) {
@@ -102834,6 +103214,11 @@ function assembleDaemonSessionInit(args2) {
102834
103214
  } = args2;
102835
103215
  deps.backend.mutation = (endpoint, args3) => client4.mutation(endpoint, args3);
102836
103216
  deps.backend.query = (endpoint, args3) => client4.query(endpoint, args3);
103217
+ const agentLifecycleOutbox = createAgentLifecycleOutboxForSession({
103218
+ sessionId: typedSessionId,
103219
+ machineId,
103220
+ backend: deps.backend
103221
+ });
102837
103222
  deps.agentProcessManager = new AgentProcessManager({
102838
103223
  logEvent: activeLogEvent ?? (async () => {
102839
103224
  return;
@@ -102849,7 +103234,8 @@ function assembleDaemonSessionInit(args2) {
102849
103234
  persistence: deps.machine,
102850
103235
  spawning: deps.spawning,
102851
103236
  crashLoop: new CrashLoopTracker,
102852
- convexUrl
103237
+ convexUrl,
103238
+ lifecycleOutbox: { enqueue: (fact) => enqueueAgentLifecycleFact(agentLifecycleOutbox, machineId, fact) }
102853
103239
  });
102854
103240
  return {
102855
103241
  client: client4,
@@ -102862,6 +103248,7 @@ function assembleDaemonSessionInit(args2) {
102862
103248
  machine: deps.machine,
102863
103249
  spawning: deps.spawning,
102864
103250
  agentProcessManager: deps.agentProcessManager,
103251
+ agentLifecycleOutbox,
102865
103252
  events: new DaemonEventBus,
102866
103253
  agentServices,
102867
103254
  lastPushedGitState: new Map,
@@ -103094,6 +103481,8 @@ var init_init_daemon = __esm(() => {
103094
103481
  init_daemon_startup_log();
103095
103482
  init_orphan_tracker();
103096
103483
  init_output_store();
103484
+ init_agent_lifecycle_outbox_runtime();
103485
+ init_agent_lifecycle_outbox_runtime();
103097
103486
  init_state_recovery();
103098
103487
  init_pid();
103099
103488
  init_storage();
@@ -105724,6 +106113,14 @@ var init_workspace_cache = __esm(() => {
105724
106113
 
105725
106114
  // src/daemon/infrastructure/git/run-command.ts
105726
106115
  import { spawn as spawn4 } from "node:child_process";
106116
+ function enqueueGit(cwd, task) {
106117
+ const tail = gitQueues.get(cwd) ?? Promise.resolve();
106118
+ const run3 = tail.then(task, task);
106119
+ gitQueues.set(cwd, run3.catch(() => {
106120
+ return;
106121
+ }));
106122
+ return run3;
106123
+ }
105727
106124
  function runCommandSpawn(command, args2, cwd, options) {
105728
106125
  const successExitCodes = options?.successExitCodes ?? [0];
105729
106126
  return new Promise((resolve4) => {
@@ -105769,7 +106166,7 @@ function runCommandSpawn(command, args2, cwd, options) {
105769
106166
  });
105770
106167
  }
105771
106168
  function runGit(args2, cwd, options) {
105772
- return runCommandSpawn("git", args2, cwd, options);
106169
+ return enqueueGit(cwd, () => runCommandSpawn("git", options?.readOnly ? ["--no-optional-locks", ...args2] : args2, cwd, options));
105773
106170
  }
105774
106171
  function runGh(args2, cwd, options) {
105775
106172
  return runCommandSpawn("gh", args2, cwd, {
@@ -105777,7 +106174,7 @@ function runGh(args2, cwd, options) {
105777
106174
  env: { ...process.env, NO_COLOR: "1" }
105778
106175
  });
105779
106176
  }
105780
- var DEFAULT_GIT_ENV;
106177
+ var DEFAULT_GIT_ENV, gitQueues;
105781
106178
  var init_run_command = __esm(() => {
105782
106179
  DEFAULT_GIT_ENV = {
105783
106180
  ...process.env,
@@ -105785,6 +106182,7 @@ var init_run_command = __esm(() => {
105785
106182
  GIT_PAGER: "cat",
105786
106183
  NO_COLOR: "1"
105787
106184
  };
106185
+ gitQueues = new Map;
105788
106186
  });
105789
106187
 
105790
106188
  // src/daemon/infrastructure/git/types.ts
@@ -105794,6 +106192,9 @@ function makeGitStateKey(machineId, workingDir) {
105794
106192
  var FULL_DIFF_MAX_BYTES = 500000, COMMITS_PER_PAGE = 20;
105795
106193
 
105796
106194
  // src/daemon/infrastructure/git/git-reader.ts
106195
+ function runGit2(args2, cwd, options) {
106196
+ return runGit(cwd ? args2 : args2, cwd, { ...options, readOnly: true });
106197
+ }
105797
106198
  function repoArgs(repoSlug) {
105798
106199
  return repoSlug ? ["--repo", repoSlug] : [];
105799
106200
  }
@@ -105810,14 +106211,14 @@ function isEmptyRepo(stderr) {
105810
106211
  return stderr.includes("does not have any commits yet") || stderr.includes("no commits yet") || stderr.includes("ambiguous argument 'HEAD'") || stderr.includes("unknown revision or path");
105811
106212
  }
105812
106213
  async function listUntrackedFiles(workingDir) {
105813
- const result = await runGit(["ls-files", "--others", "--exclude-standard"], workingDir);
106214
+ const result = await runGit2(["ls-files", "--others", "--exclude-standard"], workingDir);
105814
106215
  if ("error" in result)
105815
106216
  return [];
105816
106217
  return result.stdout.split(`
105817
106218
  `).map((line) => line.trim()).filter(Boolean);
105818
106219
  }
105819
106220
  async function getUntrackedFileDiff(workingDir, filePath) {
105820
- const result = await runGit(["diff", "--no-index", "--", NULL_DEVICE, filePath], workingDir, {
106221
+ const result = await runGit2(["diff", "--no-index", "--", NULL_DEVICE, filePath], workingDir, {
105821
106222
  successExitCodes: [0, 1]
105822
106223
  });
105823
106224
  if ("error" in result)
@@ -105878,7 +106279,7 @@ function truncateDiffContent(raw) {
105878
106279
  return { status: "available", content: raw, truncated: false };
105879
106280
  }
105880
106281
  async function runTrackedHeadDiff(workingDir, args2, options) {
105881
- const result = await runGit(args2, workingDir, options);
106282
+ const result = await runGit2(args2, workingDir, options);
105882
106283
  if ("error" in result) {
105883
106284
  const errMsg = result.error.message;
105884
106285
  if (isEmptyRepo(errMsg))
@@ -105907,13 +106308,13 @@ function classifyError(errMessage) {
105907
106308
  return { status: "error", message: errMessage.trim() };
105908
106309
  }
105909
106310
  async function isGitRepo(workingDir) {
105910
- const result = await runGit(["rev-parse", "--git-dir"], workingDir);
106311
+ const result = await runGit2(["rev-parse", "--git-dir"], workingDir);
105911
106312
  if ("error" in result)
105912
106313
  return false;
105913
106314
  return result.stdout.trim().length > 0;
105914
106315
  }
105915
106316
  async function getBranch(workingDir) {
105916
- const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
106317
+ const result = await runGit2(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
105917
106318
  if ("error" in result) {
105918
106319
  const errMsg = result.error.message;
105919
106320
  if (errMsg.includes("unknown revision") || errMsg.includes("No such file or directory") || errMsg.includes("does not have any commits")) {
@@ -105928,7 +106329,7 @@ async function getBranch(workingDir) {
105928
106329
  return { status: "available", branch };
105929
106330
  }
105930
106331
  async function isDirty(workingDir) {
105931
- const result = await runGit(["status", "--porcelain"], workingDir);
106332
+ const result = await runGit2(["status", "--porcelain"], workingDir);
105932
106333
  if ("error" in result)
105933
106334
  return false;
105934
106335
  return result.stdout.trim().length > 0;
@@ -105998,7 +106399,7 @@ async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
105998
106399
  if (skip > 0)
105999
106400
  logArgs.push(`--skip=${skip}`);
106000
106401
  logArgs.push(`--format=${format4}`);
106001
- const result = await runGit(logArgs, workingDir);
106402
+ const result = await runGit2(logArgs, workingDir);
106002
106403
  if ("error" in result) {
106003
106404
  return [];
106004
106405
  }
@@ -106020,7 +106421,7 @@ async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
106020
106421
  return commits;
106021
106422
  }
106022
106423
  async function getCommitDetail(workingDir, sha) {
106023
- const result = await runGit(["show", sha, "--format=", "--stat", "-p"], workingDir);
106424
+ const result = await runGit2(["show", sha, "--format=", "--stat", "-p"], workingDir);
106024
106425
  if ("error" in result) {
106025
106426
  const errMsg = result.error.message;
106026
106427
  const classified = classifyError(errMsg);
@@ -106041,7 +106442,7 @@ async function getCommitDetail(workingDir, sha) {
106041
106442
  }
106042
106443
  async function getCommitMetadata(workingDir, sha) {
106043
106444
  const format4 = "%s%x1f%b%x1f%an%x1f%aI";
106044
- const result = await runGit(["log", "-1", `--format=${format4}`, sha], workingDir);
106445
+ const result = await runGit2(["log", "-1", `--format=${format4}`, sha], workingDir);
106045
106446
  if ("error" in result)
106046
106447
  return null;
106047
106448
  const output = result.stdout.trim();
@@ -106067,7 +106468,7 @@ function parseRepoSlug(remoteUrl) {
106067
106468
  return null;
106068
106469
  }
106069
106470
  async function getOriginRepoSlug(cwd) {
106070
- const result = await runGit(["remote", "get-url", "origin"], cwd);
106471
+ const result = await runGit2(["remote", "get-url", "origin"], cwd);
106071
106472
  if ("error" in result)
106072
106473
  return null;
106073
106474
  const url3 = result.stdout.trim();
@@ -106204,7 +106605,7 @@ async function getPRCommits(cwd, prNumber) {
106204
106605
  }
106205
106606
  }
106206
106607
  async function getRemotes(cwd) {
106207
- const result = await runGit(["remote", "-v"], cwd);
106608
+ const result = await runGit2(["remote", "-v"], cwd);
106208
106609
  if ("error" in result)
106209
106610
  return [];
106210
106611
  if (!result.stdout.trim())
@@ -106228,14 +106629,14 @@ async function getRemotes(cwd) {
106228
106629
  return remotes;
106229
106630
  }
106230
106631
  async function getCommitsAhead(workingDir) {
106231
- const result = await runGit(["rev-list", "--count", "@{upstream}..HEAD"], workingDir);
106632
+ const result = await runGit2(["rev-list", "--count", "@{upstream}..HEAD"], workingDir);
106232
106633
  if ("error" in result)
106233
106634
  return 0;
106234
106635
  const count3 = parseInt(result.stdout.trim(), 10);
106235
106636
  return Number.isNaN(count3) ? 0 : count3;
106236
106637
  }
106237
106638
  async function getCommitsBehind(workingDir) {
106238
- const result = await runGit(["rev-list", "--count", "HEAD..@{upstream}"], workingDir);
106639
+ const result = await runGit2(["rev-list", "--count", "HEAD..@{upstream}"], workingDir);
106239
106640
  if ("error" in result)
106240
106641
  return 0;
106241
106642
  const count3 = parseInt(result.stdout.trim(), 10);
@@ -106329,16 +106730,12 @@ class GitStatePipeline {
106329
106730
  const results = new Map(preCollected);
106330
106731
  if (this.fields.length === 0)
106331
106732
  return results;
106332
- const entries2 = await Promise.all(this.fields.filter((f) => !results.has(f.key)).map(async (field) => {
106733
+ for (const field of this.fields.filter((f) => !results.has(f.key))) {
106333
106734
  try {
106334
- const raw = await field.collect(workingDir);
106335
- return { key: field.key, raw };
106735
+ results.set(field.key, await field.collect(workingDir));
106336
106736
  } catch {
106337
- return { key: field.key, raw: field.defaultValue };
106737
+ results.set(field.key, field.defaultValue);
106338
106738
  }
106339
- }));
106340
- for (const { key, raw } of entries2) {
106341
- results.set(key, raw);
106342
106739
  }
106343
106740
  return results;
106344
106741
  }
@@ -107503,16 +107900,16 @@ var init_jsonc = __esm(() => {
107503
107900
 
107504
107901
  // src/infrastructure/services/workspace/workspace-resolver.ts
107505
107902
  import { readFile as readFile7, readdir, stat as stat3 } from "node:fs/promises";
107506
- import { join as join20, basename, resolve as resolve4 } from "node:path";
107903
+ import { join as join21, basename, resolve as resolve4 } from "node:path";
107507
107904
  async function resolveGlobPatternStar(rootDir, cleaned) {
107508
- const parentDir = join20(rootDir, cleaned.slice(0, -2));
107905
+ const parentDir = join21(rootDir, cleaned.slice(0, -2));
107509
107906
  try {
107510
107907
  const entries2 = await readdir(parentDir, { withFileTypes: true });
107511
107908
  const dirs = [];
107512
107909
  for (const entry of entries2) {
107513
107910
  if (!entry.isDirectory())
107514
107911
  continue;
107515
- const dirPath = join20(parentDir, entry.name);
107912
+ const dirPath = join21(parentDir, entry.name);
107516
107913
  if (resolve4(dirPath).startsWith(resolve4(rootDir))) {
107517
107914
  dirs.push(dirPath);
107518
107915
  }
@@ -107523,7 +107920,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
107523
107920
  }
107524
107921
  }
107525
107922
  async function resolveLiteralPath(rootDir, cleaned) {
107526
- const dir = join20(rootDir, cleaned);
107923
+ const dir = join21(rootDir, cleaned);
107527
107924
  try {
107528
107925
  if (!resolve4(dir).startsWith(resolve4(rootDir)))
107529
107926
  return [];
@@ -107543,7 +107940,7 @@ async function resolveGlobPattern(rootDir, pattern) {
107543
107940
  }
107544
107941
  async function readPackageJson(dir) {
107545
107942
  try {
107546
- const content = await readFile7(join20(dir, "package.json"), "utf-8");
107943
+ const content = await readFile7(join21(dir, "package.json"), "utf-8");
107547
107944
  const pkg = JSON.parse(content);
107548
107945
  return {
107549
107946
  name: pkg.name || basename(dir),
@@ -107555,7 +107952,7 @@ async function readPackageJson(dir) {
107555
107952
  }
107556
107953
  async function readPnpmWorkspacePatterns(rootDir) {
107557
107954
  try {
107558
- const content = await readFile7(join20(rootDir, "pnpm-workspace.yaml"), "utf-8");
107955
+ const content = await readFile7(join21(rootDir, "pnpm-workspace.yaml"), "utf-8");
107559
107956
  const patterns = [];
107560
107957
  let inPackages = false;
107561
107958
  for (const line of content.split(`
@@ -107582,7 +107979,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
107582
107979
  }
107583
107980
  async function readPackageJsonWorkspacePatterns(rootDir) {
107584
107981
  try {
107585
- const content = await readFile7(join20(rootDir, "package.json"), "utf-8");
107982
+ const content = await readFile7(join21(rootDir, "package.json"), "utf-8");
107586
107983
  const pkg = JSON.parse(content);
107587
107984
  if (!pkg.workspaces)
107588
107985
  return [];
@@ -107663,11 +108060,11 @@ var MAX_NAME_LENGTH = 256;
107663
108060
 
107664
108061
  // src/infrastructure/services/workspace/command-discovery.ts
107665
108062
  import { access as access2, readFile as readFile8 } from "node:fs/promises";
107666
- import { join as join21, relative, basename as basename2 } from "node:path";
108063
+ import { join as join22, relative, basename as basename2 } from "node:path";
107667
108064
  async function detectPackageManager(workingDir) {
107668
108065
  for (const { file: file2, manager } of LOCKFILE_MAP) {
107669
108066
  try {
107670
- await access2(join21(workingDir, file2));
108067
+ await access2(join22(workingDir, file2));
107671
108068
  return manager;
107672
108069
  } catch {}
107673
108070
  }
@@ -107736,7 +108133,7 @@ function collectRootScriptCommands(scripts, pm, scriptPrefix, rootSw) {
107736
108133
  }
107737
108134
  async function readRootPackageJson(workingDir, pm, scriptPrefix) {
107738
108135
  let rootPackageName = basename2(workingDir);
107739
- const pkg = await readJsonFile(join21(workingDir, "package.json"), "root package.json");
108136
+ const pkg = await readJsonFile(join22(workingDir, "package.json"), "root package.json");
107740
108137
  if (!pkg)
107741
108138
  return { commands: [], rootPackageName };
107742
108139
  if (pkg.name)
@@ -107752,7 +108149,7 @@ async function readRootPackageJson(workingDir, pm, scriptPrefix) {
107752
108149
  }
107753
108150
  async function readTurboJson(workingDir, _turboPrefix, _rootSubWorkspace) {
107754
108151
  const turboTaskNames = [];
107755
- const turbo = await readJsonFile(join21(workingDir, "turbo.json"), "turbo.json");
108152
+ const turbo = await readJsonFile(join22(workingDir, "turbo.json"), "turbo.json");
107756
108153
  if (!turbo?.tasks || typeof turbo.tasks !== "object")
107757
108154
  return turboTaskNames;
107758
108155
  for (const taskName of Object.keys(turbo.tasks)) {
@@ -107765,7 +108162,7 @@ async function readTurboJson(workingDir, _turboPrefix, _rootSubWorkspace) {
107765
108162
  async function readDenoJson(workingDir) {
107766
108163
  const commands = [];
107767
108164
  for (const fileName of ["deno.json", "deno.jsonc"]) {
107768
- const deno = await readJsonFile(join21(workingDir, fileName), fileName);
108165
+ const deno = await readJsonFile(join22(workingDir, fileName), fileName);
107769
108166
  if (!deno?.tasks || typeof deno.tasks !== "object")
107770
108167
  continue;
107771
108168
  const pkgName = deno.name ?? basename2(workingDir);
@@ -107788,7 +108185,7 @@ async function readMakefile(workingDir) {
107788
108185
  const commands = [];
107789
108186
  for (const fileName of ["Makefile", "makefile"]) {
107790
108187
  try {
107791
- const content = await readFile8(join21(workingDir, fileName), "utf-8");
108188
+ const content = await readFile8(join22(workingDir, fileName), "utf-8");
107792
108189
  const targets = parseMakefileTargets(content);
107793
108190
  if (targets.length === 0)
107794
108191
  continue;
@@ -109994,7 +110391,7 @@ var init_assert_registered_working_dir = __esm(() => {
109994
110391
 
109995
110392
  // src/infrastructure/services/workspace/workspace-path-security.ts
109996
110393
  import { realpath } from "node:fs/promises";
109997
- import { dirname as dirname9, isAbsolute, relative as relative2, resolve as resolve5, sep } from "node:path";
110394
+ import { dirname as dirname10, isAbsolute, relative as relative2, resolve as resolve5, sep } from "node:path";
109998
110395
  import { gunzipSync } from "node:zlib";
109999
110396
  function validateRelativePathSegments(filePath) {
110000
110397
  if (filePath.includes("\x00"))
@@ -110022,7 +110419,7 @@ function assertContained(workspaceRoot, targetPath) {
110022
110419
  return { ok: true };
110023
110420
  }
110024
110421
  async function resolveNonExistentTarget(workspaceRoot, candidate) {
110025
- let current = dirname9(candidate);
110422
+ let current = dirname10(candidate);
110026
110423
  for (;; ) {
110027
110424
  try {
110028
110425
  const ancestorReal = await realpath(current);
@@ -110040,7 +110437,7 @@ async function resolveNonExistentTarget(workspaceRoot, candidate) {
110040
110437
  if (code2 !== "ENOENT") {
110041
110438
  return { ok: false, error: "Invalid file path" };
110042
110439
  }
110043
- const parent = dirname9(current);
110440
+ const parent = dirname10(current);
110044
110441
  if (parent === current) {
110045
110442
  return { ok: false, error: "Path escapes workspace" };
110046
110443
  }
@@ -110851,16 +111248,16 @@ var require_ignore = __commonJS((exports, module) => {
110851
111248
  // src/infrastructure/services/workspace/workspace-ignore.ts
110852
111249
  import { promises as fsPromises } from "node:fs";
110853
111250
  import { readFile as readFile10 } from "node:fs/promises";
110854
- import { homedir as homedir7 } from "node:os";
111251
+ import { homedir as homedir8 } from "node:os";
110855
111252
  import path3 from "node:path";
110856
111253
  function normalizeRelativePath(relativePath) {
110857
111254
  return relativePath.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+$/, "");
110858
111255
  }
110859
111256
  function expandUserPath(filePath) {
110860
111257
  if (filePath === "~")
110861
- return homedir7();
111258
+ return homedir8();
110862
111259
  if (filePath.startsWith("~/"))
110863
- return path3.join(homedir7(), filePath.slice(2));
111260
+ return path3.join(homedir8(), filePath.slice(2));
110864
111261
  return filePath;
110865
111262
  }
110866
111263
  function readExcludesFileFromGitconfig(content) {
@@ -110891,11 +111288,11 @@ async function readGitconfigExcludesFilePath(configPath) {
110891
111288
  return path3.isAbsolute(raw) ? expandUserPath(raw) : path3.resolve(path3.dirname(configPath), raw);
110892
111289
  }
110893
111290
  async function resolveGitExtraExcludeFilePaths(rootDir) {
110894
- const globalConfig2 = process.env.GIT_CONFIG_GLOBAL?.length ? process.env.GIT_CONFIG_GLOBAL : path3.join(homedir7(), ".gitconfig");
111291
+ const globalConfig2 = process.env.GIT_CONFIG_GLOBAL?.length ? process.env.GIT_CONFIG_GLOBAL : path3.join(homedir8(), ".gitconfig");
110895
111292
  const localConfig = path3.join(rootDir, ".git", "config");
110896
111293
  let excludesFile = await readGitconfigExcludesFilePath(localConfig) ?? await readGitconfigExcludesFilePath(globalConfig2);
110897
111294
  if (!excludesFile) {
110898
- excludesFile = path3.join(process.env.XDG_CONFIG_HOME?.length ? process.env.XDG_CONFIG_HOME : path3.join(homedir7(), ".config"), "git", "ignore");
111295
+ excludesFile = path3.join(process.env.XDG_CONFIG_HOME?.length ? process.env.XDG_CONFIG_HOME : path3.join(homedir8(), ".config"), "git", "ignore");
110899
111296
  }
110900
111297
  const candidates = [excludesFile, path3.join(rootDir, ".git", "info", "exclude")];
110901
111298
  const existing = [];
@@ -111347,7 +111744,8 @@ function porcelainUntrackedDeletedEvents(args2) {
111347
111744
  }
111348
111745
  async function readGitHead(workTree) {
111349
111746
  const result = await runGit(["rev-parse", "HEAD"], workTree, {
111350
- timeout: GIT_POLL_TIMEOUT_MS
111747
+ timeout: GIT_POLL_TIMEOUT_MS,
111748
+ readOnly: true
111351
111749
  });
111352
111750
  if ("error" in result) {
111353
111751
  if (isEmptyRepoHeadError(result.error))
@@ -111371,7 +111769,10 @@ async function readGitPorcelainStatus(node) {
111371
111769
  const args2 = ["status", "--porcelain=v1", "-z", "-uall", "--"];
111372
111770
  if (node.pathspec.length > 0)
111373
111771
  args2.push(...node.pathspec);
111374
- const result = await runGit(args2, node.workTree, { timeout: GIT_POLL_TIMEOUT_MS });
111772
+ const result = await runGit(args2, node.workTree, {
111773
+ timeout: GIT_POLL_TIMEOUT_MS,
111774
+ readOnly: true
111775
+ });
111375
111776
  if ("error" in result) {
111376
111777
  throw new GitWorkspaceCommandError({
111377
111778
  operation: "readGitPorcelainStatus",
@@ -111509,35 +111910,31 @@ function createGitWorkspaceChangeSource(options) {
111509
111910
  let needsReconcile = false;
111510
111911
  let tickHadFailure = false;
111511
111912
  let maxNodeFailures = 0;
111512
- const results = await Promise.allSettled(nodes.map(async (node) => {
111513
- const prev = state.get(node.workTree) ?? {
111514
- prevEntries: [],
111515
- prevHead: { head: null },
111516
- baselineEstablished: false
111517
- };
111518
- const result = await pollGitWorkspaceNode({
111519
- options,
111520
- node,
111521
- prev
111522
- });
111523
- allEvents.push(...result.events);
111524
- if (result.needsReconcile)
111525
- needsReconcile = true;
111526
- state.set(node.workTree, result.nextState);
111527
- nodeFailureCounts.set(node.workTree, 0);
111528
- return node;
111529
- }));
111530
- for (let i2 = 0;i2 < results.length; i2++) {
111531
- const result = results[i2];
111532
- const node = nodes[i2];
111533
- if (!node || result.status === "fulfilled")
111534
- continue;
111535
- tickHadFailure = true;
111536
- options.onError?.(result.reason);
111537
- const prevFailures = nodeFailureCounts.get(node.workTree) ?? 0;
111538
- const nextFailures = prevFailures + 1;
111539
- nodeFailureCounts.set(node.workTree, nextFailures);
111540
- maxNodeFailures = Math.max(maxNodeFailures, nextFailures);
111913
+ for (const node of nodes) {
111914
+ try {
111915
+ const prev = state.get(node.workTree) ?? {
111916
+ prevEntries: [],
111917
+ prevHead: { head: null },
111918
+ baselineEstablished: false
111919
+ };
111920
+ const result = await pollGitWorkspaceNode({
111921
+ options,
111922
+ node,
111923
+ prev
111924
+ });
111925
+ allEvents.push(...result.events);
111926
+ if (result.needsReconcile)
111927
+ needsReconcile = true;
111928
+ state.set(node.workTree, result.nextState);
111929
+ nodeFailureCounts.set(node.workTree, 0);
111930
+ } catch (reason) {
111931
+ tickHadFailure = true;
111932
+ options.onError?.(reason);
111933
+ const prevFailures = nodeFailureCounts.get(node.workTree) ?? 0;
111934
+ const nextFailures = prevFailures + 1;
111935
+ nodeFailureCounts.set(node.workTree, nextFailures);
111936
+ maxNodeFailures = Math.max(maxNodeFailures, nextFailures);
111937
+ }
111541
111938
  }
111542
111939
  if (tickHadFailure) {
111543
111940
  consecutiveFailureTicks += 1;
@@ -111595,7 +111992,7 @@ function createGitWorkspaceChangeSource(options) {
111595
111992
  }
111596
111993
  };
111597
111994
  }
111598
- var DEFAULT_POLL_INTERVAL_MS = 1000, MAX_BACKOFF_MS = 30000, PERSISTENT_FAILURE_THRESHOLD = 3;
111995
+ var DEFAULT_POLL_INTERVAL_MS = 3000, MAX_BACKOFF_MS = 30000, PERSISTENT_FAILURE_THRESHOLD = 3;
111599
111996
  var init_git_workspace_change_source = __esm(() => {
111600
111997
  init_git_workspace_porcelain();
111601
111998
  });
@@ -112024,9 +112421,9 @@ class NodeFsHandler {
112024
112421
  if (this.fsw.closed) {
112025
112422
  return;
112026
112423
  }
112027
- const dirname11 = sp.dirname(file2);
112424
+ const dirname12 = sp.dirname(file2);
112028
112425
  const basename4 = sp.basename(file2);
112029
- const parent = this.fsw._getWatchedDir(dirname11);
112426
+ const parent = this.fsw._getWatchedDir(dirname12);
112030
112427
  let prevStats = stats;
112031
112428
  if (parent.has(basename4))
112032
112429
  return;
@@ -112053,7 +112450,7 @@ class NodeFsHandler {
112053
112450
  prevStats = newStats2;
112054
112451
  }
112055
112452
  } catch (error51) {
112056
- this.fsw._remove(dirname11, basename4);
112453
+ this.fsw._remove(dirname12, basename4);
112057
112454
  }
112058
112455
  } else if (parent.has(basename4)) {
112059
112456
  const at = newStats.atimeMs;
@@ -113442,8 +113839,8 @@ function diffPathIndexes(previous, next4) {
113442
113839
  // src/infrastructure/services/workspace/workspace-sync-state.ts
113443
113840
  import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
113444
113841
  import * as fs11 from "node:fs/promises";
113445
- import { homedir as homedir8 } from "node:os";
113446
- import { join as join24 } from "node:path";
113842
+ import { homedir as homedir9 } from "node:os";
113843
+ import { join as join25 } from "node:path";
113447
113844
  function workspaceKeyFor(workingDir) {
113448
113845
  const normalized = normalizeWorkingDirForLookup(workingDir);
113449
113846
  return createHash7("sha256").update(normalized).digest("hex").slice(0, 16);
@@ -113457,10 +113854,10 @@ function buildPathIndex(entries2) {
113457
113854
  }
113458
113855
  function manifestPath(machineId, workingDir) {
113459
113856
  const key = workspaceKeyFor(workingDir);
113460
- return join24(SYNC_STATE_DIR, machineId, key, "manifest.json");
113857
+ return join25(SYNC_STATE_DIR, machineId, key, "manifest.json");
113461
113858
  }
113462
113859
  async function ensureDir(filePath) {
113463
- await fs11.mkdir(join24(filePath, ".."), { recursive: true, mode: 448 });
113860
+ await fs11.mkdir(join25(filePath, ".."), { recursive: true, mode: 448 });
113464
113861
  }
113465
113862
  async function loadWorkspaceSyncManifest(machineId, workingDir) {
113466
113863
  try {
@@ -113520,7 +113917,7 @@ function entriesFromPathIndex(paths) {
113520
113917
  }
113521
113918
  var SYNC_STATE_VERSION = "2", SYNC_STATE_DIR;
113522
113919
  var init_workspace_sync_state = __esm(() => {
113523
- SYNC_STATE_DIR = join24(homedir8(), ".chatroom", "sync-state");
113920
+ SYNC_STATE_DIR = join25(homedir9(), ".chatroom", "sync-state");
113524
113921
  });
113525
113922
 
113526
113923
  // src/infrastructure/services/workspace/workspace-file-tree-coordinator.ts
@@ -113897,12 +114294,12 @@ var init_durable_coalescing_state_schema = __esm(() => {
113897
114294
  });
113898
114295
 
113899
114296
  // src/daemon/infrastructure/outbox/lib/durable-coalescing-state-store.ts
113900
- import { mkdirSync as mkdirSync6 } from "node:fs";
113901
- import { dirname as dirname12 } from "node:path";
113902
- import { DatabaseSync } from "node:sqlite";
114297
+ import { mkdirSync as mkdirSync7 } from "node:fs";
114298
+ import { dirname as dirname13 } from "node:path";
114299
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
113903
114300
  function openDurableCoalescingStateStore(path8) {
113904
- mkdirSync6(dirname12(path8), { recursive: true });
113905
- const db = new DatabaseSync(path8);
114301
+ mkdirSync7(dirname13(path8), { recursive: true });
114302
+ const db = new DatabaseSync2(path8);
113906
114303
  for (const sql of DURABLE_COALESCING_STATE_MIGRATIONS)
113907
114304
  db.exec(sql);
113908
114305
  db.prepare("UPDATE coalescing_outbox_entries SET status='pending' WHERE status='in_flight'").run();
@@ -113919,13 +114316,6 @@ var init_durable_coalescing_state_store = __esm(() => {
113919
114316
  init_durable_coalescing_state_schema();
113920
114317
  });
113921
114318
 
113922
- // src/daemon/infrastructure/outbox/lib/outbox-retry-backoff.ts
113923
- function computeExponentialRetryDelayMs(attempt, retryDelayMs, maxRetryDelayMs) {
113924
- const exponent = Math.min(Math.max(0, attempt), MAX_RETRY_EXPONENT);
113925
- return Math.min(retryDelayMs * 2 ** exponent, maxRetryDelayMs);
113926
- }
113927
- var MAX_RETRY_EXPONENT = 20;
113928
-
113929
114319
  // src/daemon/infrastructure/outbox/lib/coalescing-state-outbox.ts
113930
114320
  function createCoalescingStateOutbox(options) {
113931
114321
  const durable = [options.store, options.deliveryKey, options.serialize, options.deserialize];
@@ -114102,14 +114492,6 @@ var init_keyed_coalescing_state_outbox_registry = __esm(() => {
114102
114492
  init_coalescing_state_outbox();
114103
114493
  });
114104
114494
 
114105
- // src/daemon/infrastructure/outbox/outbox-db-path.ts
114106
- import { homedir as homedir9 } from "node:os";
114107
- import { join as join25 } from "node:path";
114108
- function resolveOutboxDbPath(machineId, kind) {
114109
- return join25(homedir9(), ".chatroom", "daemon", machineId, `outbox-${kind}.sqlite`);
114110
- }
114111
- var init_outbox_db_path = () => {};
114112
-
114113
114495
  // src/daemon/infrastructure/outbox/workspace-file-tree-checkpoint-outbox.ts
114114
114496
  function createWorkspaceFileTreeCheckpointOutboxRegistry(machineId, createSend, options) {
114115
114497
  return createKeyedCoalescingStateOutboxRegistry({
@@ -114131,193 +114513,6 @@ var init_workspace_file_tree_checkpoint_outbox = __esm(() => {
114131
114513
  WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MAX_RETRY_DELAY_MS = 5 * 60000;
114132
114514
  });
114133
114515
 
114134
- // src/daemon/infrastructure/outbox/lib/durable-fifo-queue-schema.ts
114135
- var DURABLE_FIFO_QUEUE_MIGRATIONS;
114136
- var init_durable_fifo_queue_schema = __esm(() => {
114137
- DURABLE_FIFO_QUEUE_MIGRATIONS = [
114138
- `CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
114139
- `CREATE TABLE IF NOT EXISTS fifo_outbox_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, delivery_key TEXT NOT NULL, payload_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
114140
- `CREATE INDEX IF NOT EXISTS idx_fifo_outbox_pending ON fifo_outbox_entries(delivery_key, status, id)`
114141
- ];
114142
- });
114143
-
114144
- // src/daemon/infrastructure/outbox/lib/durable-fifo-queue-store.ts
114145
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
114146
- import { mkdirSync as mkdirSync7 } from "node:fs";
114147
- import { dirname as dirname13 } from "node:path";
114148
- function openDurableFifoQueueStore(dbPath) {
114149
- mkdirSync7(dirname13(dbPath), { recursive: true });
114150
- const db = new DatabaseSync2(dbPath);
114151
- for (const sql of DURABLE_FIFO_QUEUE_MIGRATIONS)
114152
- db.exec(sql);
114153
- db.prepare("UPDATE fifo_outbox_entries SET status='pending', updated_at=? WHERE status='in_flight'").run(Date.now());
114154
- return {
114155
- enqueue: (key, payloadJson) => {
114156
- const now = Date.now();
114157
- return Number(db.prepare("INSERT INTO fifo_outbox_entries(delivery_key,payload_json,created_at,updated_at) VALUES(?,?,?,?) RETURNING id").get(key, payloadJson, now, now).id);
114158
- },
114159
- claimNextBatch: (key, limit) => db.prepare("UPDATE fifo_outbox_entries SET status='in_flight', attempts=attempts+1, updated_at=? WHERE id IN (SELECT id FROM fifo_outbox_entries WHERE delivery_key=? AND status='pending' ORDER BY id LIMIT ?) RETURNING id, delivery_key as deliveryKey, payload_json as payloadJson, attempts").all(Date.now(), key, limit),
114160
- markDone: (id3) => {
114161
- db.prepare("DELETE FROM fifo_outbox_entries WHERE id=?").run(id3);
114162
- },
114163
- markPending: (id3) => {
114164
- db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=NULL, updated_at=? WHERE id=?").run(Date.now(), id3);
114165
- },
114166
- updatePayload: (id3, payloadJson) => {
114167
- db.prepare("UPDATE fifo_outbox_entries SET payload_json=?, updated_at=? WHERE id=?").run(payloadJson, Date.now(), id3);
114168
- },
114169
- markPendingRetry: (id3, error51) => {
114170
- db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=?, updated_at=? WHERE id=?").run(String(error51), Date.now(), id3);
114171
- },
114172
- listPendingForRecovery: (key) => db.prepare("SELECT id, delivery_key as deliveryKey, payload_json as payloadJson, attempts FROM fifo_outbox_entries WHERE delivery_key=? AND status IN ('pending','in_flight') ORDER BY id").all(key),
114173
- close: () => db.close()
114174
- };
114175
- }
114176
- var init_durable_fifo_queue_store = __esm(() => {
114177
- init_durable_fifo_queue_schema();
114178
- });
114179
-
114180
- // src/daemon/infrastructure/outbox/lib/fifo-batched-outbox.ts
114181
- function createFifoBatchedOutbox(o) {
114182
- const waiters = new Map;
114183
- let running3;
114184
- let stopped = false;
114185
- let retryTimer;
114186
- let retryAttempt = 0;
114187
- const retryDelay = () => computeExponentialRetryDelayMs(retryAttempt++, o.retryDelayMs ?? 500, o.maxRetryDelayMs ?? 5000);
114188
- const scheduleRetry = () => {
114189
- if (!stopped && !retryTimer) {
114190
- retryTimer = setTimeout(() => {
114191
- retryTimer = undefined;
114192
- drain2().catch(() => {
114193
- return;
114194
- });
114195
- }, retryDelay());
114196
- retryTimer.unref?.();
114197
- }
114198
- };
114199
- const drain2 = async () => {
114200
- if (running3)
114201
- return running3;
114202
- running3 = (async () => {
114203
- while (true) {
114204
- const rows = o.store.claimNextBatch(o.deliveryKey, o.batchSize);
114205
- if (!rows.length)
114206
- break;
114207
- try {
114208
- const results = await o.send(rows.map((r) => o.deserialize(r.payloadJson)));
114209
- let hasRetry = false;
114210
- for (let i2 = 0;i2 < results.length; i2++) {
114211
- const item = o.deserialize(rows[i2].payloadJson);
114212
- const outcome = o.classifyOutcome?.(results[i2], item) ?? { kind: "success" };
114213
- if (outcome.kind === "retry") {
114214
- o.store.updatePayload(rows[i2].id, o.serialize(outcome.item));
114215
- o.store.markPending(rows[i2].id);
114216
- hasRetry = true;
114217
- continue;
114218
- }
114219
- o.store.markDone(rows[i2].id);
114220
- waiters.get(rows[i2].id)?.resolve(results[i2]);
114221
- waiters.delete(rows[i2].id);
114222
- }
114223
- for (let i2 = results.length;i2 < rows.length; i2++)
114224
- o.store.markPending(rows[i2].id);
114225
- if (hasRetry) {
114226
- scheduleRetry();
114227
- break;
114228
- }
114229
- retryAttempt = 0;
114230
- } catch (e) {
114231
- o.onError?.(e);
114232
- for (const r of rows)
114233
- o.store.markPendingRetry(r.id, e);
114234
- scheduleRetry();
114235
- break;
114236
- }
114237
- }
114238
- })().finally(() => {
114239
- running3 = undefined;
114240
- });
114241
- return running3;
114242
- };
114243
- const enqueue = (item) => {
114244
- if (stopped)
114245
- return Promise.reject(new Error("Outbox is stopped"));
114246
- const id3 = o.store.enqueue(o.deliveryKey, o.serialize(item));
114247
- const p = new Promise((resolve8, reject) => waiters.set(id3, { resolve: resolve8, reject }));
114248
- drain2().catch(() => {
114249
- return;
114250
- });
114251
- return p;
114252
- };
114253
- const flushNow = async () => {
114254
- while (o.store.listPendingForRecovery(o.deliveryKey).length) {
114255
- await drain2();
114256
- }
114257
- };
114258
- const stop4 = async () => {
114259
- if (stopped)
114260
- return;
114261
- if (retryTimer)
114262
- clearTimeout(retryTimer);
114263
- retryTimer = undefined;
114264
- await flushNow();
114265
- stopped = true;
114266
- };
114267
- return { enqueue, flushNow, stop: stop4 };
114268
- }
114269
- var init_fifo_batched_outbox = () => {};
114270
-
114271
- // src/daemon/infrastructure/outbox/lib/keyed-fifo-batched-outbox-registry.ts
114272
- function createKeyedFifoBatchedOutboxRegistry(o) {
114273
- const boxes = new Map;
114274
- const get13 = (key) => {
114275
- let b = boxes.get(key);
114276
- if (!b) {
114277
- b = createFifoBatchedOutbox({
114278
- batchSize: o.batchSize,
114279
- send: o.createSend(key),
114280
- store: o.store,
114281
- deliveryKey: key,
114282
- serialize: o.serialize,
114283
- deserialize: o.deserialize,
114284
- retryDelayMs: o.retryDelayMs,
114285
- maxRetryDelayMs: o.maxRetryDelayMs,
114286
- onError: (e) => o.onError?.(key, e),
114287
- classifyOutcome: o.classifyOutcome
114288
- });
114289
- boxes.set(key, b);
114290
- }
114291
- return b;
114292
- };
114293
- const stop4 = async (k) => {
114294
- const b = boxes.get(k);
114295
- if (b) {
114296
- await b.stop();
114297
- boxes.delete(k);
114298
- }
114299
- };
114300
- return {
114301
- enqueue: (k, i2) => get13(k).enqueue(i2),
114302
- flushNow: async (k) => {
114303
- if (k)
114304
- await get13(k).flushNow();
114305
- else
114306
- for (const b of boxes.values())
114307
- await b.flushNow();
114308
- },
114309
- stop: stop4,
114310
- stopAll: async () => {
114311
- for (const k of [...boxes.keys()])
114312
- await stop4(k);
114313
- o.store.close();
114314
- }
114315
- };
114316
- }
114317
- var init_keyed_fifo_batched_outbox_registry = __esm(() => {
114318
- init_fifo_batched_outbox();
114319
- });
114320
-
114321
114516
  // src/daemon/infrastructure/outbox/workspace-file-tree-delta-outbox.ts
114322
114517
  function createWorkspaceFileTreeDeltaOutboxRegistry(machineId, createSend, options) {
114323
114518
  const store = openDurableFifoQueueStore(resolveOutboxDbPath(machineId, "file-tree-delta"));
@@ -114352,6 +114547,8 @@ var init_workspace_file_tree_delta_outbox = __esm(() => {
114352
114547
  var init_outbox = __esm(() => {
114353
114548
  init_workspace_file_tree_checkpoint_outbox();
114354
114549
  init_workspace_file_tree_delta_outbox();
114550
+ init_agent_lifecycle_outbox();
114551
+ init_agent_lifecycle_send();
114355
114552
  });
114356
114553
 
114357
114554
  // ../../services/backend/src/domain/workspace-file-tree/types.ts
@@ -115760,7 +115957,8 @@ var init_command_run_subscription = __esm(() => {
115760
115957
  // src/daemon/entry/task-delivery/task-delivery-logic.ts
115761
115958
  function isPendingAliveRunningTask(task) {
115762
115959
  const { agentConfig, status: status3 } = task;
115763
- return status3 === "pending" && agentConfig.spawnedAgentPid != null && isAgentDesiredRunning(agentConfig.desiredState);
115960
+ const op = getNativeDeliverySession()?.agentOperationalReadModel?.get(task.chatroomId, agentConfig.role);
115961
+ return status3 === "pending" && (agentConfig.spawnedAgentPid != null || op?.isAlive === true) && isOperationalDesiredRunning(op);
115764
115962
  }
115765
115963
  function isSlotUnavailableForPid(slot, pid, isPidAlive, now = Date.now()) {
115766
115964
  if (!slot) {
@@ -115783,7 +115981,7 @@ function isSlotUnavailableForPid(slot, pid, isPidAlive, now = Date.now()) {
115783
115981
  function isNativeRevivableTaskStatus(task) {
115784
115982
  const { status: status3 } = task;
115785
115983
  if (status3 === "pending") {
115786
- return task.agentConfig.spawnedAgentPid != null;
115984
+ return true;
115787
115985
  }
115788
115986
  if (status3 === "acknowledged") {
115789
115987
  return task.assignedTo?.toLowerCase() === task.agentConfig.role.toLowerCase();
@@ -115803,7 +116001,8 @@ function isNativeAgentSlotDown(task, health, now = Date.now()) {
115803
116001
  function isNativeActiveTaskAgentDown(task, health, now) {
115804
116002
  if (!isNativeHarness(task.agentConfig.agentHarness))
115805
116003
  return false;
115806
- if (!isAgentDesiredRunning(task.agentConfig.desiredState))
116004
+ const op = getNativeDeliverySession()?.agentOperationalReadModel?.get(task.chatroomId, task.agentConfig.role);
116005
+ if (!isOperationalDesiredRunning(op))
115807
116006
  return false;
115808
116007
  if (!isNativeRevivableTaskStatus(task))
115809
116008
  return false;
@@ -115827,7 +116026,8 @@ function isNativePendingTaskNeedingWake(task) {
115827
116026
  return false;
115828
116027
  if (task.status !== "pending")
115829
116028
  return false;
115830
- if (isAgentDesiredRunning(task.agentConfig.desiredState))
116029
+ const op = getNativeDeliverySession()?.agentOperationalReadModel?.get(task.chatroomId, task.agentConfig.role);
116030
+ if (isOperationalDesiredRunning(op))
115831
116031
  return false;
115832
116032
  return Boolean(task.agentConfig.workingDir);
115833
116033
  }
@@ -115903,10 +116103,10 @@ function listTasksReadyForNudge(tasks, now, cooldown, getSlot) {
115903
116103
  }
115904
116104
  var PENDING_IDLE_NUDGE_MS = 15000, NUDGE_COOLDOWN_MS = 60000;
115905
116105
  var init_task_delivery_logic = __esm(() => {
115906
- init_assigned_task();
115907
116106
  init_check_agent_slot();
115908
116107
  init_predicates();
115909
116108
  init_agent_process_manager();
116109
+ init_native_delivery_session_registry();
115910
116110
  init_native_ready_invariant();
115911
116111
  init_native_task_injector_logic();
115912
116112
  });
@@ -116006,7 +116206,7 @@ function runNativeWakeEffect(task, runtime4, effectContext2, agentMgr) {
116006
116206
  if (!ctx)
116007
116207
  return;
116008
116208
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
116009
- console.log(`[TaskMonitor] native wake ${role}@${chatroomId} — desiredState=stopped with pending task ${task.taskId}`);
116209
+ console.log(`[TaskMonitor] native wake ${role}@${chatroomId} — operational_state=stopped with pending task ${task.taskId}`);
116010
116210
  exports_Runtime.runFork(runtime4)(exports_Effect.gen(function* () {
116011
116211
  yield* agentMgr.ensureRunning({
116012
116212
  chatroomId,
@@ -116072,7 +116272,7 @@ async function wakeStoppedAgentsForPendingTasks(tasks, now, cooldown, runtime4,
116072
116272
  }
116073
116273
  }
116074
116274
  async function processTasksUpdate(runtime4, effectContext2, cooldown, agentMgr, sessionDeps, machineId, _pass, options) {
116075
- const tasks = options?.snapshots ? [...options.snapshots] : await fetchMachineAssignedTaskSnapshots(sessionDeps, machineId);
116275
+ const tasks = [...options.snapshots];
116076
116276
  const filteredTasks = filterSnapshotsExcludingRestartInFlight(tasks);
116077
116277
  if (filteredTasks.length === 0)
116078
116278
  return;
@@ -116104,11 +116304,48 @@ var init_task_delivery_processor = __esm(() => {
116104
116304
  init_native_task_injector_logic();
116105
116305
  init_api3();
116106
116306
  init_convex_error();
116107
- init_fetch_machine_assigned_task_snapshots();
116108
116307
  init_restart_orchestrator_in_flight();
116109
116308
  init_task_delivery_logic();
116110
116309
  });
116111
116310
 
116311
+ // src/daemon/infrastructure/agent-operational/enrich-snapshot-with-operational.ts
116312
+ function enrichSnapshotWithOperational(snapshot) {
116313
+ const op = getNativeDeliverySession()?.agentOperationalReadModel?.get(snapshot.chatroomId, snapshot.agentConfig.role);
116314
+ if (!op)
116315
+ return snapshot;
116316
+ return { ...snapshot, agentConfig: { ...snapshot.agentConfig, desiredState: isOperationalDesiredRunning(op) ? "running" : "stopped" } };
116317
+ }
116318
+ function enrichSnapshotsWithOperational(snapshots) {
116319
+ return snapshots.map(enrichSnapshotWithOperational);
116320
+ }
116321
+ var init_enrich_snapshot_with_operational = __esm(() => {
116322
+ init_native_delivery_session_registry();
116323
+ });
116324
+
116325
+ // src/daemon/infrastructure/agent-operational/subscribe-machine-agent-operational-status.ts
116326
+ function subscribeMachineAgentOperationalStatus(client4, options, onRows) {
116327
+ return client4.onUpdate(api.machines.subscribeMachineAgentOperationalStatus, { sessionId: options.sessionId, machineId: options.machineId }, (result) => {
116328
+ if (Array.isArray(result))
116329
+ onRows(result);
116330
+ });
116331
+ }
116332
+ var init_subscribe_machine_agent_operational_status = __esm(() => {
116333
+ init_api3();
116334
+ });
116335
+
116336
+ // src/daemon/infrastructure/inbox/fetch-machine-assigned-task-snapshots.ts
116337
+ async function fetchMachineAssignedTaskSnapshots(sessionDeps, machineId) {
116338
+ const result = await sessionDeps.backend.query(api.machines.listMachineAssignedTaskSnapshots, {
116339
+ sessionId: sessionDeps.sessionId,
116340
+ machineId
116341
+ });
116342
+ return mapAssignedTaskSnapshotList(parseAssignedTaskSnapshotRows(result?.tasks ?? []));
116343
+ }
116344
+ var init_fetch_machine_assigned_task_snapshots = __esm(() => {
116345
+ init_assigned_task_snapshot_contract();
116346
+ init_api3();
116347
+ });
116348
+
116112
116349
  // src/daemon/infrastructure/inbox/store/schema.ts
116113
116350
  var SCHEMA_VERSION = 1, MIGRATIONS;
116114
116351
  var init_schema = __esm(() => {
@@ -116261,12 +116498,16 @@ var init_inbox = __esm(() => {
116261
116498
 
116262
116499
  // src/daemon/infrastructure/inbox/task-inbox-delivery.ts
116263
116500
  async function handleTaskInboxUpdate(update6, deps) {
116501
+ const taskSnapshotState = deps.taskSnapshotState ?? getNativeDeliverySession()?.taskSnapshotState;
116502
+ taskSnapshotState?.applySignalPage(update6.signals, update6.snapshots);
116264
116503
  if (update6.snapshots.length === 0)
116265
116504
  return;
116266
- await processTasksUpdate(deps.runtime, deps.effectContext, deps.cooldown, deps.agentMgr, deps.sessionDeps, deps.machineId, "inbox-signal");
116505
+ await processTasksUpdate(deps.runtime, deps.effectContext, deps.cooldown, deps.agentMgr, deps.sessionDeps, deps.machineId, "inbox-signal", { snapshots: enrichSnapshotsWithOperational(update6.snapshots) });
116267
116506
  }
116268
116507
  var init_task_inbox_delivery = __esm(() => {
116508
+ init_native_delivery_session_registry();
116269
116509
  init_task_delivery_processor();
116510
+ init_enrich_snapshot_with_operational();
116270
116511
  });
116271
116512
 
116272
116513
  // src/daemon/infrastructure/inbox/task.ts
@@ -116376,14 +116617,19 @@ var init_task = __esm(() => {
116376
116617
 
116377
116618
  // src/daemon/entry/task-inbox-runtime.ts
116378
116619
  async function bootstrapMachineAssignedTaskSnapshots(deps) {
116620
+ await deps.sessionDeps.backend.mutation(api.machines.backfillAgentOperationalStatusForMachine, {
116621
+ sessionId: deps.sessionDeps.sessionId,
116622
+ machineId: deps.machineId
116623
+ });
116379
116624
  await deps.sessionDeps.backend.mutation(api.machines.syncMachineAssignedTaskSnapshotsMutation, {
116380
116625
  sessionId: deps.sessionDeps.sessionId,
116381
116626
  machineId: deps.machineId
116382
116627
  });
116383
116628
  const tasks = await fetchMachineAssignedTaskSnapshots(deps.sessionDeps, deps.machineId);
116629
+ deps.taskSnapshotState?.replace(tasks);
116384
116630
  if (!tasks.length)
116385
116631
  return;
116386
- await processTasksUpdate(deps.runtime, deps.effectContext, deps.cooldown, deps.agentMgr, deps.sessionDeps, deps.machineId, "bootstrap", { snapshots: tasks });
116632
+ await processTasksUpdate(deps.runtime, deps.effectContext, deps.cooldown, deps.agentMgr, deps.sessionDeps, deps.machineId, "bootstrap", { snapshots: enrichSnapshotsWithOperational(tasks) });
116387
116633
  }
116388
116634
  function isAbortError(error51) {
116389
116635
  return error51 instanceof Error && error51.name === "AbortError";
@@ -116403,7 +116649,7 @@ async function runInboxLoopWithRestart(options, onUpdate, isStopped) {
116403
116649
  }
116404
116650
  }
116405
116651
  }
116406
- var INBOX_RESTART_INITIAL_MS = 1000, INBOX_RESTART_MAX_MS = 30000, startTaskInboxEffect = (wsClient2) => exports_Effect.gen(function* () {
116652
+ var NATIVE_DELIVERY_RECONCILE_MS = 1e4, INBOX_RESTART_INITIAL_MS = 1000, INBOX_RESTART_MAX_MS = 30000, startTaskInboxEffect = (wsClient2) => exports_Effect.gen(function* () {
116407
116653
  const session2 = yield* DaemonSessionService;
116408
116654
  const agentMgr = yield* DaemonAgentProcessManagerService;
116409
116655
  const effectContext2 = yield* exports_Effect.context();
@@ -116426,14 +116672,20 @@ var INBOX_RESTART_INITIAL_MS = 1000, INBOX_RESTART_MAX_MS = 30000, startTaskInbo
116426
116672
  });
116427
116673
  const abort = new AbortController;
116428
116674
  let stopped = false;
116675
+ const taskSnapshotState = new MachineTaskSnapshotState;
116676
+ const agentOperationalReadModel = new AgentOperationalReadModel;
116429
116677
  registerNativeDeliverySession({
116430
116678
  runtime: runtime4,
116431
116679
  effectContext: effectContext2,
116432
116680
  agentMgr,
116433
116681
  sessionDeps,
116434
- machineId: session2.machineId
116682
+ machineId: session2.machineId,
116683
+ taskSnapshotState,
116684
+ agentOperationalReadModel
116435
116685
  });
116436
116686
  const cooldown = new RecoveryCooldown;
116687
+ let inboxUpdateInFlight = false;
116688
+ let reconcileInFlight = false;
116437
116689
  const inboxOptions = {
116438
116690
  client: wsClient2,
116439
116691
  sessionId: session2.sessionId,
@@ -116443,36 +116695,62 @@ var INBOX_RESTART_INITIAL_MS = 1000, INBOX_RESTART_MAX_MS = 30000, startTaskInbo
116443
116695
  signal: abort.signal
116444
116696
  };
116445
116697
  const inboxHandler = async (update6) => {
116446
- await handleTaskInboxUpdate(update6, {
116447
- runtime: runtime4,
116448
- effectContext: effectContext2,
116449
- cooldown,
116450
- agentMgr,
116451
- sessionDeps,
116452
- machineId: session2.machineId
116453
- });
116454
- inboxStore.save({ inboxType: "task", scopeKey: session2.machineId }, { afterSignalKey: update6.throughSignalKey });
116698
+ inboxUpdateInFlight = true;
116699
+ try {
116700
+ await handleTaskInboxUpdate(update6, {
116701
+ runtime: runtime4,
116702
+ effectContext: effectContext2,
116703
+ cooldown,
116704
+ agentMgr,
116705
+ sessionDeps,
116706
+ machineId: session2.machineId,
116707
+ taskSnapshotState
116708
+ });
116709
+ inboxStore.save({ inboxType: "task", scopeKey: session2.machineId }, { afterSignalKey: update6.throughSignalKey });
116710
+ } finally {
116711
+ inboxUpdateInFlight = false;
116712
+ }
116455
116713
  };
116456
- const reconcileTimer = setInterval(() => {
116457
- if (stopped)
116458
- return;
116459
- processTasksUpdate(runtime4, effectContext2, cooldown, agentMgr, sessionDeps, session2.machineId, "periodic-reconcile");
116460
- }, NATIVE_DELIVERY_RECONCILE_MS);
116461
116714
  yield* exports_Effect.tryPromise(() => bootstrapMachineAssignedTaskSnapshots({
116462
116715
  sessionDeps,
116463
116716
  runtime: runtime4,
116464
116717
  effectContext: effectContext2,
116465
116718
  cooldown,
116466
116719
  agentMgr,
116467
- machineId: session2.machineId
116720
+ machineId: session2.machineId,
116721
+ taskSnapshotState
116468
116722
  })).pipe(exports_Effect.catchAll((error51) => {
116469
116723
  console.warn("[TaskInbox] bootstrap failed:", error51);
116470
116724
  return exports_Effect.void;
116471
116725
  }));
116726
+ const unsubscribeOperational = subscribeMachineAgentOperationalStatus(wsClient2, {
116727
+ sessionId: session2.sessionId,
116728
+ machineId: session2.machineId,
116729
+ signal: abort.signal
116730
+ }, (rows) => {
116731
+ if (stopped)
116732
+ return;
116733
+ const changed = agentOperationalReadModel.replace(rows);
116734
+ const snapshots = changed.flatMap(({ chatroomId, role }) => taskSnapshotState.listForRole(chatroomId, role));
116735
+ if (snapshots.length === 0)
116736
+ return;
116737
+ processTasksUpdate(runtime4, effectContext2, cooldown, agentMgr, sessionDeps, session2.machineId, "operational-status", { snapshots: enrichSnapshotsWithOperational(snapshots) }).catch((error51) => console.warn("[TaskInbox] operational-status reconcile failed:", error51));
116738
+ }) ?? (() => {});
116739
+ const reconcileTimer = setInterval(() => {
116740
+ if (stopped || inboxUpdateInFlight || reconcileInFlight)
116741
+ return;
116742
+ reconcileInFlight = true;
116743
+ processTasksUpdate(runtime4, effectContext2, cooldown, agentMgr, sessionDeps, session2.machineId, "periodic-reconcile", { snapshots: enrichSnapshotsWithOperational(taskSnapshotState.listAll()) }).catch((error51) => {
116744
+ console.warn("[TaskInbox] local delivery reconciliation failed:", error51);
116745
+ }).finally(() => {
116746
+ reconcileInFlight = false;
116747
+ });
116748
+ }, NATIVE_DELIVERY_RECONCILE_MS);
116472
116749
  runInboxLoopWithRestart(inboxOptions, inboxHandler, () => stopped);
116473
116750
  return {
116474
116751
  stop() {
116475
116752
  stopped = true;
116753
+ unsubscribeOperational();
116476
116754
  abort.abort();
116477
116755
  clearInterval(reconcileTimer);
116478
116756
  unregisterNativeDeliverySession();
@@ -116481,12 +116759,14 @@ var INBOX_RESTART_INITIAL_MS = 1000, INBOX_RESTART_MAX_MS = 30000, startTaskInbo
116481
116759
  };
116482
116760
  });
116483
116761
  var init_task_inbox_runtime = __esm(() => {
116484
- init_reliability();
116485
116762
  init_esm();
116486
116763
  init_daemon_services();
116487
116764
  init_api3();
116765
+ init_native_delivery_session_registry();
116488
116766
  init_task_delivery_processor();
116489
116767
  init_task_delivery_logic();
116768
+ init_enrich_snapshot_with_operational();
116769
+ init_subscribe_machine_agent_operational_status();
116490
116770
  init_fetch_machine_assigned_task_snapshots();
116491
116771
  init_inbox();
116492
116772
  init_task_inbox_delivery();
@@ -116944,6 +117224,9 @@ function createDaemonRuntime(deps) {
116944
117224
  shutdownWatchdog.unref?.();
116945
117225
  if (heartbeatTimer)
116946
117226
  clearInterval(heartbeatTimer);
117227
+ await deps.agentLifecycleOutbox?.stopAll().catch(() => {
117228
+ return;
117229
+ });
116947
117230
  stopWorkers();
116948
117231
  await withTimeout2(exports_Effect.runPromise(exports_Effect.gen(function* () {
116949
117232
  const effectContext2 = yield* exports_Effect.context();
@@ -118163,10 +118446,10 @@ function startWorkspaceListSubscriber(deps, onEvent) {
118163
118446
  const unsub = deps.wsClient.onUpdate(api.workspaces.listRecentlyObservedWorkspacesForMachine, queryArgs, (workspaces) => {
118164
118447
  if (workspaces == null)
118165
118448
  return;
118166
- const snapshotKey = JSON.stringify(workspaces);
118167
- if (snapshotKey === lastSnapshotKey)
118449
+ const snapshotKey2 = JSON.stringify(workspaces);
118450
+ if (snapshotKey2 === lastSnapshotKey)
118168
118451
  return;
118169
- lastSnapshotKey = snapshotKey;
118452
+ lastSnapshotKey = snapshotKey2;
118170
118453
  onEvent({ type: "workspace.list-changed", machineId: deps.machineId });
118171
118454
  }, (err) => {
118172
118455
  console.warn(`[daemon] workspace-list subscriber error: ${err instanceof Error ? err.message : String(err)}`);
@@ -132765,7 +133048,11 @@ async function startDaemon() {
132765
133048
  console.log(`[daemon] Local web UI: http://127.0.0.1:${localWeb.port}/`);
132766
133049
  const layers = daemonSessionToLayers(init2);
132767
133050
  startBackgroundMachineCapabilitiesDiscovery(createStartBackgroundCapabilitiesDiscoveryDeps(layers));
132768
- const runtime4 = createDaemonRuntime({ wsClient: wsClient2, layers });
133051
+ const runtime4 = createDaemonRuntime({
133052
+ wsClient: wsClient2,
133053
+ layers,
133054
+ agentLifecycleOutbox: init2.agentLifecycleOutbox
133055
+ });
132769
133056
  try {
132770
133057
  await runtime4.run();
132771
133058
  } finally {
@@ -134010,4 +134297,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
134010
134297
  });
134011
134298
  program2.parse();
134012
134299
 
134013
- //# debugId=654C294BE572C9D764756E2164756E21
134300
+ //# debugId=D5DF842800E395EE64756E2164756E21