chatroom-cli 1.115.4 → 1.116.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
@@ -27949,7 +27949,7 @@ var init_v_literals_of = __esm(() => {
27949
27949
  });
27950
27950
 
27951
27951
  // ../../services/backend/src/domain/entities/agent.ts
27952
- var AGENT_HARNESSES, AgentHarnessEnum, agentHarnessValidator, AGENT_TYPES, AgentTypeEnum, agentTypeValidator, MACHINE_COMMAND_TYPES, MachineCommandTypeEnum, machineCommandTypeValidator, MACHINE_COMMAND_STATUSES, MachineCommandStatusEnum, machineCommandStatusValidator, AGENT_START_REASONS, AgentStartReasonEnum, agentStartReasonValidator, USER_EXPLICIT_START_REASONS, EXPLICIT_DAEMON_START_REASONS, isExplicitDaemonStart = (reason) => EXPLICIT_DAEMON_START_REASONS.includes(reason), AGENT_STOP_REASONS, agentPostStopDesiredStateValidator, AgentStopReasonEnum, agentStopReasonValidator, MODEL_SOURCES, ModelSourceEnum, modelSourceValidator;
27952
+ var AGENT_HARNESSES, AgentHarnessEnum, agentHarnessValidator, AGENT_TYPES, AgentTypeEnum, agentTypeValidator, MACHINE_COMMAND_TYPES, MachineCommandTypeEnum, machineCommandTypeValidator, MACHINE_COMMAND_STATUSES, MachineCommandStatusEnum, machineCommandStatusValidator, AGENT_START_REASONS, AgentStartReasonEnum, agentStartReasonValidator, USER_EXPLICIT_START_REASONS, EXPLICIT_DAEMON_START_REASONS, isExplicitDaemonStart = (reason) => EXPLICIT_DAEMON_START_REASONS.includes(reason), AGENT_STOP_REASONS, agentPostStopDesiredStateValidator, AgentStopReasonEnum, MODEL_SOURCES, ModelSourceEnum, modelSourceValidator;
27953
27953
  var init_agent = __esm(() => {
27954
27954
  init_values();
27955
27955
  init_v_literals_of();
@@ -28017,7 +28017,6 @@ var init_agent = __esm(() => {
28017
28017
  ];
28018
28018
  agentPostStopDesiredStateValidator = v.union(v.literal("running"), v.literal("stopped"));
28019
28019
  AgentStopReasonEnum = Object.fromEntries(AGENT_STOP_REASONS.map((r) => [r, r]));
28020
- agentStopReasonValidator = v.union(...toLiteralValidators(AGENT_STOP_REASONS));
28021
28020
  MODEL_SOURCES = ["team_config", "none"];
28022
28021
  ModelSourceEnum = Object.fromEntries(MODEL_SOURCES.map((s) => [s, s]));
28023
28022
  modelSourceValidator = v.union(...toLiteralValidators(MODEL_SOURCES));
@@ -28127,7 +28126,7 @@ function normalizeAgentLifecycleFact(raw) {
28127
28126
  emittedAt: Number(rest.emittedAt)
28128
28127
  };
28129
28128
  }
28130
- if (kind === "spawned" || kind === "exited" || kind === "activity" || kind === "turn_failed" || kind === "chatroom_shutdown_complete") {
28129
+ if (kind === "spawned" || kind === "exited" || kind === "activity" || kind === "turn_failed" || kind === "status" || kind === "chatroom_shutdown_complete") {
28131
28130
  return rest;
28132
28131
  }
28133
28132
  throw new Error(`Unknown agent lifecycle fact kind: ${String(kind)}`);
@@ -28144,6 +28143,26 @@ function buildActivityLifecycleFact(params) {
28144
28143
  emittedAt
28145
28144
  };
28146
28145
  }
28146
+ function buildAgentStatusFact(params) {
28147
+ const emittedAt = params.emittedAt ?? Date.now();
28148
+ return {
28149
+ kind: "status",
28150
+ chatroomId: params.chatroomId,
28151
+ role: params.role,
28152
+ status: params.status,
28153
+ ...params.errorSource ? { errorSource: params.errorSource } : {},
28154
+ ...params.errorCode ? { errorCode: params.errorCode } : {},
28155
+ ...params.errorMessage ? { errorMessage: params.errorMessage } : {},
28156
+ revisionKey: buildAgentLifecycleRevisionKey("status", {
28157
+ chatroomId: params.chatroomId,
28158
+ role: params.role,
28159
+ status: params.status,
28160
+ errorCode: params.errorCode,
28161
+ emittedAt
28162
+ }),
28163
+ emittedAt
28164
+ };
28165
+ }
28147
28166
  function agentLifecycleDeliveryKey(machineId, fact) {
28148
28167
  if (fact.kind === "cleared_all_pids")
28149
28168
  return `${machineId}:__machine__`;
@@ -28155,23 +28174,55 @@ function buildAgentLifecycleRevisionKey(prefix, parts2) {
28155
28174
  return `${prefix}:${Object.entries(parts2).filter(([, v2]) => v2 !== undefined).map(([k, v2]) => `${k}=${v2}`).join(":")}`;
28156
28175
  }
28157
28176
 
28177
+ // src/daemon/domain/entities/agent-slot.ts
28178
+ function isAgentSlotStarted(slot) {
28179
+ return slot.state === AGENT_SLOT_STATE.RUNNING && slot.pid !== undefined;
28180
+ }
28181
+ function isAgentSlotStartInFlight(slot) {
28182
+ return slot.state === AGENT_SLOT_STATE.SPAWNING;
28183
+ }
28184
+ function isAgentSlotActive(slot) {
28185
+ return isAgentSlotStarted(slot) || isAgentSlotStartInFlight(slot);
28186
+ }
28187
+ function agentKey(chatroomId, role) {
28188
+ return `${chatroomId}:${role.toLowerCase()}`;
28189
+ }
28190
+ var AGENT_SLOT_STATE, AGENT_START_DISPOSITION, idleSlot = () => ({ state: AGENT_SLOT_STATE.IDLE });
28191
+ var init_agent_slot = __esm(() => {
28192
+ AGENT_SLOT_STATE = {
28193
+ IDLE: "idle",
28194
+ SPAWNING: "spawning",
28195
+ RUNNING: "running",
28196
+ STOPPING: "stopping"
28197
+ };
28198
+ AGENT_START_DISPOSITION = {
28199
+ STARTED: "started",
28200
+ ALREADY_STARTED: "already_started",
28201
+ START_IN_PROGRESS: "start_in_progress"
28202
+ };
28203
+ });
28204
+
28158
28205
  // src/daemon/domain/usecase/check-agent-slot.ts
28159
28206
  function isSlotIdle(state) {
28160
- return state === "idle";
28207
+ return state === AGENT_SLOT_STATE.IDLE;
28161
28208
  }
28162
28209
  function isSlotSpawning(state) {
28163
- return state === "spawning";
28210
+ return state === AGENT_SLOT_STATE.SPAWNING;
28164
28211
  }
28165
28212
  function isSlotRunning(state) {
28166
- return state === "running";
28213
+ return state === AGENT_SLOT_STATE.RUNNING;
28167
28214
  }
28168
28215
  function isSlotStopping(state) {
28169
- return state === "stopping";
28216
+ return state === AGENT_SLOT_STATE.STOPPING;
28170
28217
  }
28171
28218
  function isTurnPhaseIdle(phase) {
28172
28219
  return phase === "idle";
28173
28220
  }
28174
- var init_check_agent_slot = () => {};
28221
+ var AGENT_SLOT_STATES;
28222
+ var init_check_agent_slot = __esm(() => {
28223
+ init_agent_slot();
28224
+ AGENT_SLOT_STATES = Object.values(AGENT_SLOT_STATE);
28225
+ });
28175
28226
 
28176
28227
  // src/daemon/services/task-service/domain/usecase/native-cold-session-delivery.ts
28177
28228
  function taskRequestsNativeColdSession(task) {
@@ -28574,21 +28625,7 @@ function createConvexNativeTaskDeliveryGateway(backend2) {
28574
28625
  sessionId,
28575
28626
  machineId
28576
28627
  });
28577
- return rows.map((row) => ({
28578
- eventId: row._id,
28579
- machineId: row.machineId,
28580
- chatroomId: row.chatroomId,
28581
- taskId: row.taskId,
28582
- role: row.role,
28583
- ...row.assignee === undefined ? {} : {
28584
- assignee: row.assignee
28585
- },
28586
- eventType: row.eventType,
28587
- status: row.status,
28588
- createdAt: row.createdAt,
28589
- ...row.processedAt === undefined ? {} : { processedAt: row.processedAt },
28590
- task: row.task
28591
- }));
28628
+ return mapPendingTaskInboxRows(rows);
28592
28629
  },
28593
28630
  markTaskInboxEventProcessed: async ({ sessionId, machineId, eventId }) => {
28594
28631
  const result = await backend2.mutation(api.chatroomWorkspaceTaskInbox.markProcessed, {
@@ -28631,6 +28668,23 @@ function createConvexNativeTaskDeliveryGateway(backend2) {
28631
28668
  }
28632
28669
  };
28633
28670
  }
28671
+ function mapPendingTaskInboxRows(rows) {
28672
+ return rows.map((row) => ({
28673
+ eventId: row._id,
28674
+ machineId: row.machineId,
28675
+ chatroomId: row.chatroomId,
28676
+ taskId: row.taskId,
28677
+ role: row.role,
28678
+ ...row.assignee === undefined ? {} : {
28679
+ assignee: row.assignee
28680
+ },
28681
+ eventType: row.eventType,
28682
+ status: row.status,
28683
+ createdAt: row.createdAt,
28684
+ ...row.processedAt === undefined ? {} : { processedAt: row.processedAt },
28685
+ task: row.task
28686
+ }));
28687
+ }
28634
28688
  var init_convex_native_task_delivery_gateway = __esm(() => {
28635
28689
  init_api3();
28636
28690
  });
@@ -29693,7 +29747,7 @@ class AgentWorkManager {
29693
29747
  }
29694
29748
  async handleAgentTurnEnded(event) {
29695
29749
  const completion = event.completion;
29696
- if (completion && completion.status !== "completed") {
29750
+ if (completion.status !== "completed") {
29697
29751
  const activeTask = this.deps.agentTaskState.get({
29698
29752
  chatroomId: event.chatroomId,
29699
29753
  role: event.role
@@ -29741,6 +29795,16 @@ class AgentWorkManager {
29741
29795
  return { kind: "release-slot" };
29742
29796
  }
29743
29797
  this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
29798
+ try {
29799
+ await this.deps.lifecycleOutbox.enqueue(buildAgentStatusFact({
29800
+ chatroomId: event.chatroomId,
29801
+ role: event.role,
29802
+ status: "waiting"
29803
+ }));
29804
+ } catch (error) {
29805
+ console.error(`[NativeDelivery:turn-ended-outbox-error] chatroom=${event.chatroomId} role=${event.role} error=${error instanceof Error ? error.stack ?? error.message : String(error)}`);
29806
+ return { kind: "hold-slot", reason: "turn-ended-outbox-enqueue-failed" };
29807
+ }
29744
29808
  this.scheduleRoleDelivery(event.chatroomId, event.role);
29745
29809
  return { kind: "release-slot" };
29746
29810
  }
@@ -29782,7 +29846,7 @@ class AgentWorkManager {
29782
29846
  return;
29783
29847
  this.deps.agentTaskState.clear({ chatroomId, role });
29784
29848
  const slot = this.deps.agentMgr.getSlot(chatroomId, role);
29785
- if (!slot || slot.state === "idle" || slot.state === "stopping")
29849
+ if (!slot || slot.state === AGENT_SLOT_STATE.IDLE || slot.state === AGENT_SLOT_STATE.STOPPING)
29786
29850
  return;
29787
29851
  try {
29788
29852
  await this.deps.runSerializedForAgent({ chatroomId, role }, { timeoutMs: 120000 }, (ops, context5) => ops.stopAgent({
@@ -29871,6 +29935,7 @@ var init_agent_work_manager = __esm(() => {
29871
29935
  init_agent();
29872
29936
  init_chatroom_workspace_task_inbox();
29873
29937
  init_esm();
29938
+ init_agent_slot();
29874
29939
  init_native_cold_session_delivery();
29875
29940
  init_native_task_injector_logic();
29876
29941
  init_convex_native_task_delivery_gateway();
@@ -30281,6 +30346,9 @@ function defaultNativeTurnPhase() {
30281
30346
  function setNativeTurnPhase(slot, phase) {
30282
30347
  slot.nativeTurnPhase = phase;
30283
30348
  }
30349
+ var init_native_turn_phase = __esm(() => {
30350
+ init_agent_slot();
30351
+ });
30284
30352
 
30285
30353
  // src/daemon/services/agent-process-service/infrastructure/adapters/orphan-process-tracker.ts
30286
30354
  import { createHash } from "node:crypto";
@@ -30766,12 +30834,6 @@ var init_agent_lifecycle_types = __esm(() => {
30766
30834
  };
30767
30835
  });
30768
30836
 
30769
- // src/daemon/domain/entities/agent-slot.ts
30770
- function agentKey(chatroomId, role) {
30771
- return `${chatroomId}:${role.toLowerCase()}`;
30772
- }
30773
- var idleSlot = () => ({ state: "idle" });
30774
-
30775
30837
  // src/daemon/domain/usecase/transition-agent-slot.ts
30776
30838
  function makeError(from, event) {
30777
30839
  return {
@@ -30787,7 +30849,11 @@ function transitionFromIdle(slot, event) {
30787
30849
  return makeResult(slot);
30788
30850
  }
30789
30851
  if (event.type === "spawn_started") {
30790
- return makeResult({ ...slot, state: "spawning", pendingOperationKey: event.operationKey });
30852
+ return makeResult({
30853
+ ...slot,
30854
+ state: AGENT_SLOT_STATE.SPAWNING,
30855
+ pendingOperationKey: event.operationKey
30856
+ });
30791
30857
  }
30792
30858
  if (event.type === "process_exited") {
30793
30859
  return makeResult(slot);
@@ -30799,31 +30865,35 @@ function transitionFromSpawning(slot, event) {
30799
30865
  return makeResult({ ...slot, pendingOperationKey: event.operationKey });
30800
30866
  }
30801
30867
  if (event.type === "spawn_succeeded") {
30802
- return makeResult({ ...slot, state: "running", pid: event.pid });
30868
+ return makeResult({ ...slot, state: AGENT_SLOT_STATE.RUNNING, pid: event.pid });
30803
30869
  }
30804
30870
  if (event.type === "spawn_failed" || event.type === "process_exited") {
30805
- return makeResult({ state: "idle" });
30871
+ return makeResult({ state: AGENT_SLOT_STATE.IDLE });
30806
30872
  }
30807
30873
  return makeError(slot.state, event.type);
30808
30874
  }
30809
30875
  function transitionFromRunning(slot, event) {
30810
30876
  if (event.type === "stop_requested") {
30811
- return makeResult({ ...slot, state: "stopping", pendingOperationKey: event.operationKey });
30877
+ return makeResult({
30878
+ ...slot,
30879
+ state: AGENT_SLOT_STATE.STOPPING,
30880
+ pendingOperationKey: event.operationKey
30881
+ });
30812
30882
  }
30813
30883
  if (event.type === "process_exited") {
30814
30884
  if (slot.pid !== undefined && slot.pid !== event.pid) {
30815
30885
  return { ok: false, error: { _tag: "StalePid", expected: slot.pid, got: event.pid } };
30816
30886
  }
30817
- return makeResult({ state: "idle" });
30887
+ return makeResult({ state: AGENT_SLOT_STATE.IDLE });
30818
30888
  }
30819
30889
  if (event.type === "stale_process_detected") {
30820
- return makeResult({ state: "idle" });
30890
+ return makeResult({ state: AGENT_SLOT_STATE.IDLE });
30821
30891
  }
30822
30892
  return makeError(slot.state, event.type);
30823
30893
  }
30824
30894
  function transitionFromStopping(slot, event) {
30825
30895
  if (event.type === "stop_completed") {
30826
- return makeResult({ state: "idle" });
30896
+ return makeResult({ state: AGENT_SLOT_STATE.IDLE });
30827
30897
  }
30828
30898
  if (event.type === "process_exited") {
30829
30899
  return { ok: false, error: { _tag: "IgnoredDuplicateExit" } };
@@ -30832,29 +30902,34 @@ function transitionFromStopping(slot, event) {
30832
30902
  }
30833
30903
  function transitionSlot(slot, event) {
30834
30904
  switch (slot.state) {
30835
- case "idle":
30905
+ case AGENT_SLOT_STATE.IDLE:
30836
30906
  return transitionFromIdle(slot, event);
30837
- case "spawning":
30907
+ case AGENT_SLOT_STATE.SPAWNING:
30838
30908
  return transitionFromSpawning(slot, event);
30839
- case "running":
30909
+ case AGENT_SLOT_STATE.RUNNING:
30840
30910
  return transitionFromRunning(slot, event);
30841
- case "stopping":
30911
+ case AGENT_SLOT_STATE.STOPPING:
30842
30912
  return transitionFromStopping(slot, event);
30843
30913
  }
30844
30914
  }
30845
30915
  function shouldIgnoreProcessExit(slot, exitPid) {
30846
- if (slot.state === "stopping")
30916
+ if (slot.state === AGENT_SLOT_STATE.STOPPING)
30847
30917
  return true;
30848
30918
  if (slot.pid !== undefined && slot.pid !== exitPid)
30849
30919
  return true;
30850
30920
  return false;
30851
30921
  }
30922
+ var init_transition_agent_slot = __esm(() => {
30923
+ init_agent_slot();
30924
+ });
30852
30925
 
30853
30926
  // src/infrastructure/services/agent-lifecycle/agent-lifecycle-service.ts
30854
30927
  var AgentLifecycleServiceLive;
30855
30928
  var init_agent_lifecycle_service = __esm(() => {
30856
30929
  init_esm();
30857
30930
  init_agent_lifecycle_types();
30931
+ init_agent_slot();
30932
+ init_transition_agent_slot();
30858
30933
  AgentLifecycleServiceLive = exports_Layer.effect(AgentLifecycleService, exports_Effect.gen(function* () {
30859
30934
  const ports = yield* AgentLifecyclePorts;
30860
30935
  const slotsRef = yield* exports_Ref.make(new Map);
@@ -30911,15 +30986,16 @@ var init_agent_lifecycle_service = __esm(() => {
30911
30986
  const updatedSlot = { ...slot, harnessSessionId: spawnHandle.harnessSessionId };
30912
30987
  yield* setSlotInRef(key, updatedSlot);
30913
30988
  }
30914
- return { success: true, pid: slot.pid };
30989
+ return { success: true, pid: slot.pid, disposition: AGENT_START_DISPOSITION.STARTED };
30915
30990
  });
30916
30991
  const ensureRunning = (opts) => exports_Effect.gen(function* () {
30917
30992
  const key = agentKey(opts.chatroomId, opts.role);
30918
30993
  const currentSlot = yield* getSlotFromRef(key);
30919
- if (currentSlot && currentSlot.state !== "idle") {
30994
+ if (currentSlot && isAgentSlotActive(currentSlot)) {
30920
30995
  return {
30921
30996
  success: true,
30922
- pid: currentSlot.pid
30997
+ pid: currentSlot.pid,
30998
+ disposition: isAgentSlotStarted(currentSlot) ? AGENT_START_DISPOSITION.ALREADY_STARTED : AGENT_START_DISPOSITION.START_IN_PROGRESS
30923
30999
  };
30924
31000
  }
30925
31001
  const allowResult = ports.spawn.shouldAllowSpawn(opts.chatroomId, opts.reason);
@@ -30992,7 +31068,7 @@ var init_agent_lifecycle_service = __esm(() => {
30992
31068
  const listActive = () => exports_Ref.get(slotsRef).pipe(exports_Effect.map((map18) => {
30993
31069
  const results = [];
30994
31070
  for (const [key, slot] of map18) {
30995
- if (slot.state !== "idle") {
31071
+ if (isAgentSlotActive(slot)) {
30996
31072
  const [chatroomId, role] = key.split(":");
30997
31073
  results.push({ chatroomId, role, slot });
30998
31074
  }
@@ -31421,7 +31497,7 @@ class AgentProcessManager {
31421
31497
  return () => this.agentSessionLostHandlers.delete(handler);
31422
31498
  }
31423
31499
  clearSlotRuntimeState(slot) {
31424
- slot.state = "idle";
31500
+ slot.state = AGENT_SLOT_STATE.IDLE;
31425
31501
  slot.pid = undefined;
31426
31502
  slot.harness = undefined;
31427
31503
  slot.harnessSessionId = undefined;
@@ -31480,19 +31556,27 @@ class AgentProcessManager {
31480
31556
  } else if (slot.stopRequested) {
31481
31557
  return { success: false, error: "stop_requested" };
31482
31558
  }
31483
- if (slot.state === "running" && slot.pid && !isProcessAlive(this.deps.processes.kill, slot.pid)) {
31559
+ if (slot.state === AGENT_SLOT_STATE.RUNNING && slot.pid && !isProcessAlive(this.deps.processes.kill, slot.pid)) {
31484
31560
  this.clearSlotRuntimeState(slot);
31485
31561
  }
31486
- if (slot.state === "stopping" && (slot.stoppingSince === undefined || this.deps.clock.now() - slot.stoppingSince >= STOPPING_TIMEOUT_MS)) {
31562
+ if (slot.state === AGENT_SLOT_STATE.STOPPING && (slot.stoppingSince === undefined || this.deps.clock.now() - slot.stoppingSince >= STOPPING_TIMEOUT_MS)) {
31487
31563
  await this.forceClearStuckStoppingSlot(key, slot, opts.chatroomId, opts.role, "daemon.stop_timeout");
31488
31564
  }
31489
31565
  if (slot.pendingOperation) {
31490
- if (slot.state === "stopping") {
31566
+ if (slot.state === AGENT_SLOT_STATE.STOPPING) {
31491
31567
  await slot.pendingOperation;
31492
31568
  } else {
31493
31569
  return slot.pendingOperation;
31494
31570
  }
31495
31571
  }
31572
+ const pid = slot.pid;
31573
+ if (isAgentSlotStarted(slot) && pid !== undefined && isProcessAlive(this.deps.processes.kill, pid)) {
31574
+ return {
31575
+ success: true,
31576
+ pid,
31577
+ disposition: AGENT_START_DISPOSITION.ALREADY_STARTED
31578
+ };
31579
+ }
31496
31580
  const operation = this.executeEnsureRunning(key, slot, opts);
31497
31581
  slot.pendingOperation = operation;
31498
31582
  return operation;
@@ -31577,7 +31661,7 @@ class AgentProcessManager {
31577
31661
  }
31578
31662
  bindStopTarget(args2) {
31579
31663
  const slot = this.slots.get(agentKey2(args2.chatroomId, args2.role));
31580
- if (!slot || slot.pid !== args2.pid || slot.state !== "stopping")
31664
+ if (!slot || slot.pid !== args2.pid || slot.state !== AGENT_SLOT_STATE.STOPPING)
31581
31665
  return;
31582
31666
  slot.stopCommandId = args2.stopCommandId;
31583
31667
  slot.stopTargetKey = args2.targetKey;
@@ -31585,12 +31669,12 @@ class AgentProcessManager {
31585
31669
  async withScopedRoleStop(opts, fn2) {
31586
31670
  const key = agentKey2(opts.chatroomId, opts.role);
31587
31671
  const slot = this.slots.get(key);
31588
- if (!slot || !slot.pid || slot.state === "idle")
31672
+ if (!slot || !slot.pid || slot.state === AGENT_SLOT_STATE.IDLE)
31589
31673
  return { ok: false, reason: "no_slot" };
31590
- if (slot.state === "stopping" || slot.pendingOperation)
31674
+ if (slot.state === AGENT_SLOT_STATE.STOPPING || slot.pendingOperation)
31591
31675
  return { ok: false, reason: "concurrent" };
31592
31676
  this.markStopIntent(opts.chatroomId, opts.role, opts.reason, slot.pid);
31593
- slot.state = "stopping";
31677
+ slot.state = AGENT_SLOT_STATE.STOPPING;
31594
31678
  slot.stoppingSince = this.deps.clock.now();
31595
31679
  try {
31596
31680
  const value = await fn2();
@@ -31598,26 +31682,26 @@ class AgentProcessManager {
31598
31682
  await this.clearAgentPidQuietly(opts.chatroomId, opts.role);
31599
31683
  return { ok: true, value };
31600
31684
  } catch (error) {
31601
- slot.state = "running";
31685
+ slot.state = AGENT_SLOT_STATE.RUNNING;
31602
31686
  throw error;
31603
31687
  }
31604
31688
  }
31605
31689
  async handleStopEarlyReturns(slot, opts, key) {
31606
- if (!slot || slot.state === "idle") {
31690
+ if (!slot || slot.state === AGENT_SLOT_STATE.IDLE) {
31607
31691
  await this.killAndRecordForIdleSlot(slot, opts);
31608
31692
  return { success: true };
31609
31693
  }
31610
- if (slot.state === "stopping" && slot.pendingOperation) {
31694
+ if (slot.state === AGENT_SLOT_STATE.STOPPING && slot.pendingOperation) {
31611
31695
  await slot.pendingOperation;
31612
31696
  return { success: true };
31613
31697
  }
31614
31698
  const pid = slot.pid;
31615
31699
  if (!pid) {
31616
- slot.state = "idle";
31700
+ slot.state = AGENT_SLOT_STATE.IDLE;
31617
31701
  slot.pendingOperation = undefined;
31618
31702
  return { success: true };
31619
31703
  }
31620
- slot.state = "stopping";
31704
+ slot.state = AGENT_SLOT_STATE.STOPPING;
31621
31705
  const stopGeneration = slot.stopGeneration ?? 0;
31622
31706
  slot.stoppingSince = this.deps.clock.now();
31623
31707
  const operation = this.doStop(key, slot, pid, opts, stopGeneration);
@@ -31646,13 +31730,13 @@ class AgentProcessManager {
31646
31730
  }
31647
31731
  async runHandleAgentEnd(opts) {
31648
31732
  const slot = this.slots.get(agentKey2(opts.chatroomId, opts.role));
31649
- if (!slot || slot.pid !== opts.pid || slot.harness !== opts.harness || slot.state !== "running") {
31733
+ if (!slot || slot.pid !== opts.pid || slot.harness !== opts.harness || slot.state !== AGENT_SLOT_STATE.RUNNING) {
31650
31734
  console.log(`[AgentProcessManager] Ignoring stale agent_end: role=${opts.role} callbackPid=${opts.pid} currentPid=${slot?.pid ?? "none"} currentState=${slot?.state ?? "none"}`);
31651
31735
  return;
31652
31736
  }
31653
31737
  const capabilities = getHarnessCapabilities(opts.harness);
31654
31738
  this.updateSlotsMirror(opts.chatroomId, opts.role, {
31655
- state: slot?.state ?? "idle",
31739
+ state: slot?.state ?? AGENT_SLOT_STATE.IDLE,
31656
31740
  pid: slot?.pid,
31657
31741
  harness: slot?.harness,
31658
31742
  harnessSessionId: slot?.harnessSessionId,
@@ -31717,7 +31801,7 @@ class AgentProcessManager {
31717
31801
  async handleExit(opts) {
31718
31802
  const key = agentKey2(opts.chatroomId, opts.role);
31719
31803
  const slot = this.slots.get(key);
31720
- if (!slot || slot.pid !== opts.pid || slot.state === "stopping") {
31804
+ if (!slot || slot.pid !== opts.pid || slot.state === AGENT_SLOT_STATE.STOPPING) {
31721
31805
  return;
31722
31806
  }
31723
31807
  if (slot.stopRequested && slot.expectedStopPid === opts.pid) {
@@ -31762,7 +31846,7 @@ class AgentProcessManager {
31762
31846
  };
31763
31847
  }
31764
31848
  resetSlotAfterExit(slot) {
31765
- slot.state = "idle";
31849
+ slot.state = AGENT_SLOT_STATE.IDLE;
31766
31850
  slot.pid = undefined;
31767
31851
  slot.startedAt = undefined;
31768
31852
  slot.pendingOperation = undefined;
@@ -31807,15 +31891,15 @@ class AgentProcessManager {
31807
31891
  model: slot.model ?? "",
31808
31892
  message: classification2.message
31809
31893
  });
31810
- this.deps.backend.mutation(api.daemon.agentEvents.agentProviderUnavailable, {
31811
- sessionId: this.deps.sessionId,
31812
- machineId: this.deps.machineId,
31894
+ this.deps.lifecycleOutbox.enqueue(buildAgentStatusFact({
31813
31895
  chatroomId,
31814
31896
  role,
31815
- reason: classification2.reason,
31816
- model: slot.model ?? "",
31817
- message: classification2.message
31818
- }).catch(() => {});
31897
+ status: "error",
31898
+ errorSource: "runtime",
31899
+ errorCode: classification2.reason,
31900
+ errorMessage: `${classification2.message}${slot.model ? ` (model: ${slot.model})` : ""}`,
31901
+ emittedAt: this.deps.clock.now()
31902
+ })).catch(() => {});
31819
31903
  }
31820
31904
  getSlot(chatroomId, role) {
31821
31905
  return this.getSlotFromMirror(chatroomId, role);
@@ -31823,7 +31907,7 @@ class AgentProcessManager {
31823
31907
  listActive() {
31824
31908
  const result = [];
31825
31909
  for (const [key, slot] of this.slots) {
31826
- if (slot.state === "running" || slot.state === "spawning") {
31910
+ if (slot.state === AGENT_SLOT_STATE.RUNNING || slot.state === AGENT_SLOT_STATE.SPAWNING) {
31827
31911
  const [chatroomId, role] = key.split(":");
31828
31912
  result.push({ chatroomId, role, slot });
31829
31913
  }
@@ -31841,7 +31925,7 @@ class AgentProcessManager {
31841
31925
  async clearStuckStoppingSlot(chatroomId, role, options) {
31842
31926
  const key = agentKey2(chatroomId, role);
31843
31927
  const slot = this.slots.get(key);
31844
- if (!slot || slot.state !== "stopping") {
31928
+ if (!slot || slot.state !== AGENT_SLOT_STATE.STOPPING) {
31845
31929
  return false;
31846
31930
  }
31847
31931
  const elapsed3 = slot.stoppingSince ? this.deps.clock.now() - slot.stoppingSince : STOPPING_TIMEOUT_MS;
@@ -31917,7 +32001,7 @@ class AgentProcessManager {
31917
32001
  getOrCreateSlot(key) {
31918
32002
  let slot = this.slots.get(key);
31919
32003
  if (!slot) {
31920
- slot = { state: "idle" };
32004
+ slot = { state: AGENT_SLOT_STATE.IDLE };
31921
32005
  this.slots.set(key, slot);
31922
32006
  }
31923
32007
  return slot;
@@ -31946,9 +32030,9 @@ class AgentProcessManager {
31946
32030
  }
31947
32031
  async killInMemorySlotIfAlive(key, chatroomId, role) {
31948
32032
  const slot = this.slots.get(key);
31949
- if (slot?.pid && isProcessAlive(this.deps.processes.kill, slot.pid) && (slot.state === "running" || slot.state === "spawning")) {
32033
+ if (slot?.pid && isProcessAlive(this.deps.processes.kill, slot.pid) && (slot.state === AGENT_SLOT_STATE.RUNNING || slot.state === AGENT_SLOT_STATE.SPAWNING)) {
31950
32034
  const pid = slot.pid;
31951
- slot.state = "stopping";
32035
+ slot.state = AGENT_SLOT_STATE.STOPPING;
31952
32036
  const stopGeneration = this.bumpStopGeneration(slot);
31953
32037
  slot.stoppingSince = this.deps.clock.now();
31954
32038
  await this.doStop(key, slot, pid, { chatroomId, role, reason: "daemon.respawn" }, stopGeneration);
@@ -31972,7 +32056,7 @@ class AgentProcessManager {
31972
32056
  }
31973
32057
  const key = agentKey2(chatroomId, role);
31974
32058
  const currentSlot = this.slots.get(key);
31975
- if (currentSlot?.pid === pid && currentSlot.state !== "idle") {
32059
+ if (currentSlot?.pid === pid && currentSlot.state !== AGENT_SLOT_STATE.IDLE) {
31976
32060
  return;
31977
32061
  }
31978
32062
  await this.stopPersistedProcess(pid, harness);
@@ -32071,7 +32155,7 @@ class AgentProcessManager {
32071
32155
  }
32072
32156
  }
32073
32157
  resetSlotIdle(slot) {
32074
- slot.state = "idle";
32158
+ slot.state = AGENT_SLOT_STATE.IDLE;
32075
32159
  slot.pendingOperation = undefined;
32076
32160
  }
32077
32161
  checkRateLimitGate(opts, slot) {
@@ -32164,7 +32248,7 @@ class AgentProcessManager {
32164
32248
  return { ok: true, spawnResult };
32165
32249
  }
32166
32250
  assignRunningSlotState(key, slot, opts, spawnResult, wantResume, pid) {
32167
- slot.state = "running";
32251
+ slot.state = AGENT_SLOT_STATE.RUNNING;
32168
32252
  slot.pid = pid;
32169
32253
  slot.harness = opts.agentHarness;
32170
32254
  slot.harnessSessionId = spawnResult.harnessSessionId;
@@ -32336,7 +32420,7 @@ class AgentProcessManager {
32336
32420
  });
32337
32421
  }
32338
32422
  async doEnsureRunning(key, slot, opts) {
32339
- slot.state = "spawning";
32423
+ slot.state = AGENT_SLOT_STATE.SPAWNING;
32340
32424
  const authorization = await this.deps.backend.mutation(api.machines.authorizeAgentStart, {
32341
32425
  sessionId: this.deps.sessionId,
32342
32426
  machineId: this.deps.machineId,
@@ -32364,7 +32448,11 @@ class AgentProcessManager {
32364
32448
  if (!spawn2.ok)
32365
32449
  return spawn2.result;
32366
32450
  await this.finalizeRunningSlot(key, slot, opts, spawn2.spawnResult, wantResume);
32367
- return { success: true, pid: spawn2.spawnResult.pid };
32451
+ return {
32452
+ success: true,
32453
+ pid: spawn2.spawnResult.pid,
32454
+ disposition: AGENT_START_DISPOSITION.STARTED
32455
+ };
32368
32456
  } catch (e) {
32369
32457
  this.resetSlotIdle(slot);
32370
32458
  return { success: false, error: `Unexpected error: ${e.message}` };
@@ -32398,7 +32486,7 @@ class AgentProcessManager {
32398
32486
  }
32399
32487
  }
32400
32488
  resetSlotAfterStop(slot) {
32401
- slot.state = "idle";
32489
+ slot.state = AGENT_SLOT_STATE.IDLE;
32402
32490
  slot.pid = undefined;
32403
32491
  slot.startedAt = undefined;
32404
32492
  slot.pendingOperation = undefined;
@@ -32499,10 +32587,12 @@ var init_agent_process_manager = __esm(() => {
32499
32587
  init_api3();
32500
32588
  init_agent_lifecycle_runtime();
32501
32589
  init_agent_lifecycle_types();
32590
+ init_agent_slot();
32502
32591
  init_agent_stop();
32503
32592
  init_spawn_policy();
32504
32593
  init_classify_provider_error();
32505
32594
  init_native_spawn_presence();
32595
+ init_native_turn_phase();
32506
32596
  });
32507
32597
 
32508
32598
  // src/daemon/services/agent-process-service/infrastructure/harness-activity-emitter.ts
@@ -32535,6 +32625,7 @@ var init_agent_process_service2 = __esm(() => {
32535
32625
  init_agent_task_state();
32536
32626
  init_agent_process_command_bus();
32537
32627
  init_service();
32628
+ init_native_turn_phase();
32538
32629
  init_agent_process_manager();
32539
32630
  init_execute_stop_targets_adapter();
32540
32631
  init_harness_activity_emitter();
@@ -32610,8 +32701,14 @@ function createTaskService(deps) {
32610
32701
  const gateway = createConvexNativeTaskDeliveryGateway(deps.backend);
32611
32702
  const taskInboxState = new TaskInboxState;
32612
32703
  const listeners = new Set;
32613
- let inboxPollTimer;
32614
- let inboxPollInFlight = false;
32704
+ let stopInboxWatch;
32705
+ let inboxStopped = false;
32706
+ const pendingEvents = new Map;
32707
+ const scheduledEventIds = new Set;
32708
+ const deliveredEventIds = new Set;
32709
+ const deliveryRetryTimers = new Map;
32710
+ const acknowledgementRetryTimers = new Map;
32711
+ const taskChains = new Map;
32615
32712
  const notify = async (notification) => {
32616
32713
  await Promise.all([...listeners].map((listener) => Promise.resolve(listener(notification))));
32617
32714
  };
@@ -32638,45 +32735,130 @@ function createTaskService(deps) {
32638
32735
  ]);
32639
32736
  return true;
32640
32737
  };
32641
- const pollTaskInbox = async () => {
32642
- if (inboxPollInFlight)
32738
+ const taskKey2 = (event) => `${event.chatroomId}:${event.role.toLowerCase()}:${event.taskId}`;
32739
+ const clearRetryTimer = (timers, eventId) => {
32740
+ const timer = timers.get(eventId);
32741
+ if (timer)
32742
+ clearTimeout(timer);
32743
+ timers.delete(eventId);
32744
+ };
32745
+ const retryDelivery = (eventId) => {
32746
+ if (inboxStopped || deliveryRetryTimers.has(eventId) || !pendingEvents.has(eventId))
32747
+ return;
32748
+ const timer = setTimeout(() => {
32749
+ deliveryRetryTimers.delete(eventId);
32750
+ const event = pendingEvents.get(eventId);
32751
+ if (event)
32752
+ scheduleEvent(event);
32753
+ }, TASK_INBOX_RETRY_DELAY_MS);
32754
+ deliveryRetryTimers.set(eventId, timer);
32755
+ timer.unref?.();
32756
+ };
32757
+ const retryAcknowledgement = (eventId) => {
32758
+ if (inboxStopped || acknowledgementRetryTimers.has(eventId) || !pendingEvents.has(eventId)) {
32643
32759
  return;
32644
- inboxPollInFlight = true;
32760
+ }
32761
+ const timer = setTimeout(() => {
32762
+ acknowledgementRetryTimers.delete(eventId);
32763
+ const event = pendingEvents.get(eventId);
32764
+ if (event)
32765
+ acknowledgeEvent(event);
32766
+ }, TASK_INBOX_RETRY_DELAY_MS);
32767
+ acknowledgementRetryTimers.set(eventId, timer);
32768
+ timer.unref?.();
32769
+ };
32770
+ const acknowledgeEvent = async (event) => {
32645
32771
  try {
32646
- const events = await gateway.listPendingTaskInboxEvents({
32772
+ await gateway.markTaskInboxEventProcessed({
32647
32773
  sessionId: deps.sessionId,
32648
- machineId: deps.machineId
32774
+ machineId: deps.machineId,
32775
+ eventId: event.eventId
32649
32776
  });
32650
- for (const event of events) {
32777
+ } catch (error) {
32778
+ console.warn("[TaskService] task inbox acknowledgement failed:", error);
32779
+ retryAcknowledgement(event.eventId);
32780
+ }
32781
+ };
32782
+ const scheduleEvent = (event) => {
32783
+ if (inboxStopped || deliveredEventIds.has(event.eventId) || scheduledEventIds.has(event.eventId)) {
32784
+ return Promise.resolve();
32785
+ }
32786
+ scheduledEventIds.add(event.eventId);
32787
+ const previous = taskChains.get(taskKey2(event)) ?? Promise.resolve();
32788
+ const current = previous.catch(() => {
32789
+ return;
32790
+ }).then(async () => {
32791
+ try {
32651
32792
  if (!applyInboxEvent(event))
32652
- continue;
32793
+ return;
32653
32794
  await notify({ kind: "inbox-event", event });
32654
- await gateway.markTaskInboxEventProcessed({
32655
- sessionId: deps.sessionId,
32656
- machineId: deps.machineId,
32657
- eventId: event.eventId
32658
- });
32795
+ deliveredEventIds.add(event.eventId);
32796
+ } catch (error) {
32797
+ console.warn("[TaskService] task inbox delivery failed:", error);
32798
+ retryDelivery(event.eventId);
32799
+ return;
32800
+ } finally {
32801
+ scheduledEventIds.delete(event.eventId);
32802
+ }
32803
+ await acknowledgeEvent(event);
32804
+ });
32805
+ taskChains.set(taskKey2(event), current);
32806
+ current.finally(() => {
32807
+ if (taskChains.get(taskKey2(event)) === current)
32808
+ taskChains.delete(taskKey2(event));
32809
+ });
32810
+ return current;
32811
+ };
32812
+ const reconcilePendingEvents = (events) => {
32813
+ const nextIds = new Set(events.map((event) => event.eventId));
32814
+ for (const eventId of pendingEvents.keys()) {
32815
+ if (!nextIds.has(eventId)) {
32816
+ pendingEvents.delete(eventId);
32817
+ deliveredEventIds.delete(eventId);
32818
+ clearRetryTimer(deliveryRetryTimers, eventId);
32819
+ clearRetryTimer(acknowledgementRetryTimers, eventId);
32659
32820
  }
32660
- } catch (error) {
32661
- console.warn("[TaskService] task inbox poll failed:", error);
32662
- } finally {
32663
- inboxPollInFlight = false;
32664
32821
  }
32822
+ return Promise.all(events.map((event) => {
32823
+ pendingEvents.set(event.eventId, event);
32824
+ return scheduleEvent(event);
32825
+ })).then(() => {
32826
+ return;
32827
+ });
32665
32828
  };
32666
32829
  const service4 = {
32667
- startTaskInbox: async () => {
32668
- await pollTaskInbox();
32669
- inboxPollTimer ??= setInterval(() => void pollTaskInbox(), 1000);
32670
- inboxPollTimer.unref?.();
32830
+ startTaskInbox: async (wsClient2) => {
32831
+ inboxStopped = false;
32832
+ if (wsClient2 && !stopInboxWatch) {
32833
+ stopInboxWatch = wsClient2.onUpdate(api.chatroomWorkspaceTaskInbox.listPending, { sessionId: deps.sessionId, machineId: deps.machineId }, (rows) => {
32834
+ reconcilePendingEvents(mapPendingTaskInboxRows(rows));
32835
+ }, (error) => console.warn(`[daemon] task-inbox watch error: ${String(error)}`));
32836
+ } else if (!wsClient2) {
32837
+ const events = await gateway.listPendingTaskInboxEvents({
32838
+ sessionId: deps.sessionId,
32839
+ machineId: deps.machineId
32840
+ });
32841
+ await reconcilePendingEvents(events);
32842
+ }
32671
32843
  },
32672
32844
  subscribe: (listener) => {
32673
32845
  listeners.add(listener);
32674
32846
  return () => listeners.delete(listener);
32675
32847
  },
32676
32848
  stopTaskInbox: () => {
32677
- if (inboxPollTimer)
32678
- clearInterval(inboxPollTimer);
32679
- inboxPollTimer = undefined;
32849
+ inboxStopped = true;
32850
+ stopInboxWatch?.();
32851
+ stopInboxWatch = undefined;
32852
+ pendingEvents.clear();
32853
+ scheduledEventIds.clear();
32854
+ deliveredEventIds.clear();
32855
+ for (const timer of deliveryRetryTimers.values())
32856
+ clearTimeout(timer);
32857
+ deliveryRetryTimers.clear();
32858
+ for (const timer of acknowledgementRetryTimers.values())
32859
+ clearTimeout(timer);
32860
+ acknowledgementRetryTimers.clear();
32861
+ taskChains.clear();
32680
32862
  },
32681
32863
  listPendingTaskInboxEvents: () => gateway.listPendingTaskInboxEvents({
32682
32864
  sessionId: deps.sessionId,
@@ -32712,8 +32894,10 @@ function createTaskService(deps) {
32712
32894
  };
32713
32895
  return service4;
32714
32896
  }
32897
+ var TASK_INBOX_RETRY_DELAY_MS = 1000;
32715
32898
  var init_task_service = __esm(() => {
32716
32899
  init_chatroom_workspace_task_inbox();
32900
+ init_api3();
32717
32901
  init_convex_native_task_delivery_gateway();
32718
32902
  });
32719
32903
 
@@ -83736,7 +83920,7 @@ async function registerAgent(chatroomId, options, deps) {
83736
83920
  var registerAgentEffect = (chatroomId, options) => exports_Effect.gen(function* () {
83737
83921
  const backend2 = yield* BackendService;
83738
83922
  const machine = yield* RegisterAgentMachineService;
83739
- const { role, type, allowTypeChange } = options;
83923
+ const { role, type } = options;
83740
83924
  const sessionId = yield* requireSessionIdEffect((a) => ({
83741
83925
  _tag: "NotAuthenticated",
83742
83926
  convexUrl: a.convexUrl,
@@ -83768,27 +83952,12 @@ var registerAgentEffect = (chatroomId, options) => exports_Effect.gen(function*
83768
83952
  return yield* exports_Effect.fail({ _tag: "MachineNotRegistered" });
83769
83953
  }
83770
83954
  const config2 = yield* machine.loadMachineConfig();
83771
- yield* backend2.mutation(api.machines.recordRemoteAgentRegistered, {
83772
- sessionId,
83773
- chatroomId,
83774
- role,
83775
- machineId
83776
- }).pipe(exports_Effect.catchAll(() => exports_Effect.succeed(undefined)));
83777
83955
  yield* exports_Effect.sync(() => {
83778
83956
  console.log(`✅ Registered as remote agent for role "${role}"`);
83779
83957
  console.log(` Machine: ${config2?.hostname ?? "unknown"} (${machineId})`);
83780
83958
  console.log(` Working directory: ${process.cwd()}`);
83781
83959
  });
83782
83960
  } else {
83783
- yield* backend2.mutation(api.machines.recordCustomAgentRegistered, {
83784
- sessionId,
83785
- chatroomId,
83786
- role,
83787
- allowTypeChange
83788
- }).pipe(exports_Effect.mapError((cause3) => ({
83789
- _tag: "RegisterFailed",
83790
- cause: cause3
83791
- })));
83792
83961
  yield* exports_Effect.sync(() => {
83793
83962
  console.log(`✅ Registered as custom agent for role "${role}"`);
83794
83963
  });
@@ -84442,11 +84611,12 @@ var getNextTaskEffect = (chatroomId, options) => exports_Effect.gen(function* ()
84442
84611
  const machineId = yield* exports_Effect.promise(() => getMachineId().then((id3) => id3 ?? undefined).catch(() => {
84443
84612
  return;
84444
84613
  }));
84445
- const teamConfigs = yield* backend2.query(api.machines.getTeamAgentConfigs, {
84614
+ const lastSentRequest = yield* backend2.query(api.agents.getLastSentLaunchRequest, {
84446
84615
  sessionId,
84447
- chatroomId
84448
- }).pipe(exports_Effect.catchAll(() => exports_Effect.succeed([])));
84449
- const participantAgentType = teamConfigs?.find((c) => c.role.toLowerCase() === options.role.toLowerCase())?.type;
84616
+ chatroomId,
84617
+ role: options.role
84618
+ }).pipe(exports_Effect.catchAll(() => exports_Effect.succeed(null)));
84619
+ const participantAgentType = lastSentRequest?.agentType;
84450
84620
  yield* backend2.mutation(api.participants.join, {
84451
84621
  sessionId,
84452
84622
  chatroomId,
@@ -84531,25 +84701,39 @@ var init_get_next_task = __esm(() => {
84531
84701
  });
84532
84702
 
84533
84703
  // ../shared/src/domain/team-presets.ts
84534
- function getTeamPreset(teamId) {
84704
+ function getTeamPresetId(teamId) {
84535
84705
  const normalized = teamId.trim().toLowerCase();
84536
- return normalized === "duo" || normalized === "solo" ? TEAM_PRESETS[normalized] : undefined;
84706
+ if (normalized === "duo" || normalized === TEAM_STRUCTURE_IDS.duo)
84707
+ return "duo";
84708
+ if (normalized === "solo" || normalized === TEAM_STRUCTURE_IDS.solo)
84709
+ return "solo";
84710
+ return;
84711
+ }
84712
+ function getTeamPreset(teamId) {
84713
+ const presetId = getTeamPresetId(teamId);
84714
+ return presetId ? TEAM_PRESETS[presetId] : undefined;
84537
84715
  }
84538
84716
  function listTeamPresetIds() {
84539
84717
  return [...TEAM_PRESET_IDS];
84540
84718
  }
84541
- var TEAM_PRESET_IDS, DEFAULT_TEAM_PRESET_ID = "duo", TEAM_PRESETS;
84719
+ var TEAM_PRESET_IDS, DEFAULT_TEAM_PRESET_ID = "duo", TEAM_STRUCTURE_IDS, TEAM_PRESETS;
84542
84720
  var init_team_presets = __esm(() => {
84543
84721
  init_agent_role();
84544
84722
  TEAM_PRESET_IDS = ["duo", "solo"];
84723
+ TEAM_STRUCTURE_IDS = {
84724
+ duo: "duo@1",
84725
+ solo: "solo@1"
84726
+ };
84545
84727
  TEAM_PRESETS = {
84546
84728
  duo: {
84729
+ structureId: TEAM_STRUCTURE_IDS.duo,
84547
84730
  name: "Duo",
84548
84731
  description: "A planner and builder working as a pair, planner as coordinator, with optional ephemeral enhancer",
84549
84732
  roles: ["planner", "enhancer", "builder"],
84550
84733
  entryPoint: "planner"
84551
84734
  },
84552
84735
  solo: {
84736
+ structureId: TEAM_STRUCTURE_IDS.solo,
84553
84737
  name: "Solo",
84554
84738
  description: "A single agent working independently, with optional ephemeral enhancer",
84555
84739
  roles: ["solo", "enhancer"],
@@ -84622,10 +84806,7 @@ async function setTeam(chatroomId, teamId, deps) {
84622
84806
  await d.backend.mutation(api.chatrooms.updateTeam, {
84623
84807
  sessionId,
84624
84808
  chatroomId,
84625
- teamId,
84626
- teamName: preset.name,
84627
- teamRoles: preset.roles,
84628
- teamEntryPoint: preset.entryPoint
84809
+ teamStructureId: teamId
84629
84810
  });
84630
84811
  console.log(`✅ Team set to ${teamId} (${preset.name})`);
84631
84812
  console.log(` Roles: ${preset.roles.join(", ")}`);
@@ -84672,7 +84853,7 @@ function configuredWorkingDir(config3) {
84672
84853
  return config3?.workingDir;
84673
84854
  }
84674
84855
  async function getConfigs(deps, sessionId, chatroomId) {
84675
- return await deps.backend.query(api.machines.getTeamAgentConfigs, {
84856
+ return await deps.backend.query(api.agents.listLastSentLaunchRequests, {
84676
84857
  sessionId,
84677
84858
  chatroomId
84678
84859
  });
@@ -84688,7 +84869,6 @@ async function getAgentConfig(chatroomId, role, deps) {
84688
84869
  console.log(` Model: ${config3.model ?? "(unset)"}`);
84689
84870
  console.log(` Working directory: ${config3.workingDir ?? "(unset)"}`);
84690
84871
  console.log(` Machine ID: ${config3.machineId ?? "(unset)"}`);
84691
- console.log(` Desired state: ${config3.desiredState ?? "(unset)"}`);
84692
84872
  }
84693
84873
  async function setAgentConfig(chatroomId, options, deps) {
84694
84874
  const d = deps ?? await createDefaultDeps9();
@@ -84696,17 +84876,19 @@ async function setAgentConfig(chatroomId, options, deps) {
84696
84876
  const machineId = await requireMachineId(d);
84697
84877
  const machineConfig = await d.machine.loadMachineConfig();
84698
84878
  const workingDir = options.workingDir ?? configuredWorkingDir(machineConfig) ?? process.cwd();
84699
- await d.backend.mutation(api.machines.saveTeamAgentConfig, {
84879
+ await d.backend.mutation(api.machines.sendCommand, {
84700
84880
  sessionId,
84701
- chatroomId,
84702
- role: options.role,
84703
- type: "remote",
84704
84881
  machineId,
84705
- agentHarness: options.harness,
84706
- model: options.model,
84707
- workingDir
84882
+ type: "start-agent",
84883
+ payload: {
84884
+ chatroomId,
84885
+ role: options.role,
84886
+ agentHarness: options.harness,
84887
+ model: options.model,
84888
+ workingDir
84889
+ }
84708
84890
  });
84709
- console.log(`✅ Agent config saved for ${options.role}`);
84891
+ console.log(`✅ Start command sent for ${options.role}`);
84710
84892
  console.log(` Harness: ${options.harness}`);
84711
84893
  console.log(` Model: ${options.model}`);
84712
84894
  console.log(` Working directory: ${workingDir}`);
@@ -89607,7 +89789,7 @@ function createAgentLifecycleSend(session2) {
89607
89789
  machineId: session2.machineId,
89608
89790
  fact: toConvexLifecycleFact(fact)
89609
89791
  };
89610
- const result = await session2.backend.mutation(api.machines.projectAgentLifecycleFact, args2);
89792
+ const result = await session2.backend.mutation(fact.kind === "activity" ? api.machines.recordAgentActivityHeartbeat : api.machines.projectAgentLifecycleFact, args2);
89611
89793
  return result;
89612
89794
  };
89613
89795
  }
@@ -89717,12 +89899,16 @@ function daemonSessionToLayers(init2) {
89717
89899
  lastPushedModels: init2.lastPushedModels,
89718
89900
  lastPushedHarnessFingerprint: init2.lastPushedHarnessFingerprint,
89719
89901
  logEvent: init2.logEvent,
89720
- taskService: init2.taskService
89902
+ taskService: init2.taskService,
89903
+ lifecycleOutbox: {
89904
+ enqueue: (fact) => enqueueAgentLifecycleFact(init2.agentLifecycleOutbox, init2.machineId, fact)
89905
+ }
89721
89906
  }));
89722
89907
  }
89723
89908
  var init_daemon_layers = __esm(() => {
89724
89909
  init_esm();
89725
89910
  init_daemon_services();
89911
+ init_agent_lifecycle_outbox_runtime();
89726
89912
  init_backend();
89727
89913
  init_clock2();
89728
89914
  init_fs();
@@ -90615,10 +90801,9 @@ Run: ${formatAuthLoginCommand(convexUrl)}`);
90615
90801
  yield* connectDaemonEffect(client4, typedSessionId, machineId);
90616
90802
  return { typedSessionId, config: config3, machineId, agentServices, cachedModels };
90617
90803
  }), activeLogSink, activeLogEvent, connectDaemonEffect = (client4, sessionId, machineId) => exports_Effect.tryPromise({
90618
- try: () => client4.mutation(api.machines.updateDaemonStatus, {
90804
+ try: () => client4.mutation(api.machines.markDaemonOnline, {
90619
90805
  sessionId: asConvexSessionId(sessionId),
90620
- machineId,
90621
- connected: true
90806
+ machineId
90622
90807
  }),
90623
90808
  catch: (e) => e
90624
90809
  }).pipe(exports_Effect.catchAll((error) => {
@@ -95994,13 +96179,16 @@ function createStartAgentDeps(session2, processManagerService) {
95994
96179
  machineId: session2.machineId,
95995
96180
  error: args2.error
95996
96181
  });
95997
- await session2.backend.mutation(api.daemon.agentEvents.agentStartFailed, {
95998
- sessionId: session2.sessionId,
95999
- machineId: session2.machineId,
96000
- chatroomId: args2.chatroomId,
96001
- role: args2.role,
96002
- error: args2.error
96003
- });
96182
+ if (session2.lifecycleOutbox) {
96183
+ await session2.lifecycleOutbox.enqueue(buildAgentStatusFact({
96184
+ chatroomId: args2.chatroomId,
96185
+ role: args2.role,
96186
+ status: "error",
96187
+ errorSource: "configuration",
96188
+ errorCode: "agent.startFailed",
96189
+ errorMessage: args2.error
96190
+ }));
96191
+ }
96004
96192
  } catch (err) {
96005
96193
  console.log(` ⚠️ Failed to emit startFailed event: ${err.message}`);
96006
96194
  }
@@ -102743,7 +102931,7 @@ var init_command_run_subscription = __esm(() => {
102743
102931
  });
102744
102932
 
102745
102933
  // src/daemon/entry/task-inbox-runtime.ts
102746
- var startTaskInboxEffect = () => exports_Effect.gen(function* () {
102934
+ var startTaskInboxEffect = (wsClient2) => exports_Effect.gen(function* () {
102747
102935
  const session2 = yield* DaemonSessionService;
102748
102936
  const agentMgr = yield* DaemonAgentProcessManagerService;
102749
102937
  const commandService = yield* DaemonAgentProcessManagerCommandService;
@@ -102776,7 +102964,7 @@ var startTaskInboxEffect = () => exports_Effect.gen(function* () {
102776
102964
  lifecycleOutbox,
102777
102965
  taskService: session2.taskService
102778
102966
  });
102779
- yield* exports_Effect.tryPromise(() => session2.taskService.startTaskInbox()).pipe(exports_Effect.catchAll((error) => {
102967
+ yield* exports_Effect.tryPromise(() => session2.taskService.startTaskInbox(wsClient2)).pipe(exports_Effect.catchAll((error) => {
102780
102968
  console.warn("[TaskService] task inbox bootstrap failed:", error);
102781
102969
  return exports_Effect.void;
102782
102970
  }));
@@ -103149,10 +103337,9 @@ var init_on_daemon_shutdown = __esm(() => {
103149
103337
  console.log(`[${formatTimestamp()}] Shutdown stops: ${totalStopped} stopped`);
103150
103338
  }
103151
103339
  }
103152
- yield* exports_Effect.promise(() => session2.backend.mutation(api.machines.updateDaemonStatus, {
103340
+ yield* exports_Effect.promise(() => session2.backend.mutation(api.machines.markDaemonOffline, {
103153
103341
  sessionId: session2.sessionId,
103154
- machineId: session2.machineId,
103155
- connected: false
103342
+ machineId: session2.machineId
103156
103343
  }).catch(() => {}));
103157
103344
  });
103158
103345
  });
@@ -103305,7 +103492,7 @@ function createDaemonRuntime(deps) {
103305
103492
  break;
103306
103493
  }
103307
103494
  });
103308
- taskInboxHandle = yield* startTaskInboxEffect();
103495
+ taskInboxHandle = yield* startTaskInboxEffect(deps.wsClient);
103309
103496
  logObserverSubscriptionHandle = startLogObserverSubscription({ sessionId: session2.sessionId, machineId: session2.machineId }, deps.wsClient);
103310
103497
  const commandRunRuntime = yield* exports_Effect.runtime();
103311
103498
  agenticQueryWorkerHandle = startAgenticQuerySubscriptions({
@@ -103955,45 +104142,6 @@ var init_agentic_query_session = __esm(() => {
103955
104142
  init_api3();
103956
104143
  });
103957
104144
 
103958
- // src/daemon/infrastructure/convex/subscribers/chatroom-workspace-agent-commands.ts
103959
- function startChatroomWorkspaceAgentCommandsSubscriber(deps, onClaimed) {
103960
- let draining = false;
103961
- let queued = false;
103962
- let stopped = false;
103963
- const args2 = { sessionId: deps.sessionId, machineId: deps.machineId };
103964
- const drain = async () => {
103965
- if (stopped || draining) {
103966
- queued = true;
103967
- return;
103968
- }
103969
- draining = true;
103970
- try {
103971
- do {
103972
- queued = false;
103973
- let claimed;
103974
- while (claimed = await deps.wsClient.mutation(api.chatroomWorkspaceAgentCommandsInbox.claimNext, args2))
103975
- await onClaimed(claimed);
103976
- } while (queued && !stopped);
103977
- } finally {
103978
- draining = false;
103979
- }
103980
- };
103981
- const unsub = deps.wsClient.onUpdate(api.chatroomWorkspaceAgentCommandsInbox.watchNext, args2, (commandId) => {
103982
- if (commandId)
103983
- drain();
103984
- }, (err) => console.warn(`[daemon] workspace-agent-command watch error: ${String(err)}`));
103985
- drain();
103986
- return {
103987
- async stop() {
103988
- stopped = true;
103989
- unsub();
103990
- }
103991
- };
103992
- }
103993
- var init_chatroom_workspace_agent_commands = __esm(() => {
103994
- init_api3();
103995
- });
103996
-
103997
104145
  // src/daemon/infrastructure/convex/subscribers/command-run.ts
103998
104146
  function runId(run3) {
103999
104147
  return typeof run3._id === "string" ? run3._id : run3._id.toString();
@@ -104375,6 +104523,10 @@ function startAllSubscribers(deps) {
104375
104523
  const enhancerJob = startEnhancerJobSubscriber2(deps, onEvent);
104376
104524
  const gitRequest = startGitRequestSubscriber(deps, onEvent);
104377
104525
  const machineCommands = startMachineCommandInboxSubscriber(deps, async (claimed) => {
104526
+ if (claimed.type === "agent.stop") {
104527
+ await deps.onAgentStopCommand?.(claimed);
104528
+ return;
104529
+ }
104378
104530
  if (claimed.type === "daemon.workspaceListChanged") {
104379
104531
  enhancerJob.refreshChatrooms();
104380
104532
  gitRequest.refreshWorkspaces();
@@ -104386,7 +104538,6 @@ function startAllSubscribers(deps) {
104386
104538
  claimedCommand: claimed
104387
104539
  });
104388
104540
  });
104389
- const workspaceAgentCommands = deps.onWorkspaceAgentCommand ? startChatroomWorkspaceAgentCommandsSubscriber(deps, deps.onWorkspaceAgentCommand) : undefined;
104390
104541
  const commandRun = startCommandRunSubscriber(deps, onEvent);
104391
104542
  const fileTree = startFileTreeRequestSubscriber(deps, onEvent);
104392
104543
  const fileTreeRelease = startFileTreeReleaseRequestSubscriber(deps, onEvent);
@@ -104398,7 +104549,6 @@ function startAllSubscribers(deps) {
104398
104549
  async stopAll() {
104399
104550
  await Promise.all([
104400
104551
  machineCommands.stop(),
104401
- workspaceAgentCommands?.stop(),
104402
104552
  commandRun.stop(),
104403
104553
  gitRequest.stop(),
104404
104554
  fileTree.stop(),
@@ -104416,7 +104566,6 @@ var init_subscriber_registry = __esm(() => {
104416
104566
  init_event_router();
104417
104567
  init_agentic_query_prompt();
104418
104568
  init_agentic_query_session();
104419
- init_chatroom_workspace_agent_commands();
104420
104569
  init_command_run();
104421
104570
  init_enhancer_job();
104422
104571
  init_file_content_request();
@@ -133579,14 +133728,17 @@ async function startDaemon() {
133579
133728
  sessionId: asConvexSessionId(init2.sessionId),
133580
133729
  machineId: init2.machineId,
133581
133730
  router: createDefaultEventRouterDeps(),
133582
- onWorkspaceAgentCommand: (command) => executeChatroomStopCommand({
133583
- apm: init2.agentProcessManager,
133584
- chatroomId: command.chatroomId,
133585
- commandId: command._id,
133586
- role: command.command.type === "stop_agent" ? command.command.role : undefined,
133587
- finalizeChatroom: command.command.type === "stop_all_agents" ? command.command.finalizeChatroom : undefined,
133588
- runSerializedForAgent: init2.agentProcessManagerService.runSerializedForAgent
133589
- })
133731
+ onAgentStopCommand: (command) => {
133732
+ const stopCommand = command;
133733
+ return executeChatroomStopCommand({
133734
+ apm: init2.agentProcessManager,
133735
+ chatroomId: stopCommand.chatroomId,
133736
+ commandId: stopCommand.commandId,
133737
+ role: stopCommand.role,
133738
+ finalizeChatroom: stopCommand.finalizeChatroom,
133739
+ runSerializedForAgent: init2.agentProcessManagerService.runSerializedForAgent
133740
+ });
133741
+ }
133590
133742
  });
133591
133743
  console.log(`[daemon] Local web UI: http://127.0.0.1:${localWeb.port}/`);
133592
133744
  const layers = daemonSessionToLayers(init2);
@@ -134389,7 +134541,7 @@ agentConfigCommand.command("get").description("Show agent configuration for a ro
134389
134541
  const { getAgentConfig: getAgentConfig2 } = await Promise.resolve().then(() => (init_agent2(), exports_agent));
134390
134542
  await getAgentConfig2(options.chatroomId, options.role);
134391
134543
  });
134392
- agentConfigCommand.command("set").description("Save agent harness and model configuration").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Agent role").requiredOption("--harness <harness>", "Agent harness").requiredOption("--model <model>", "Model identifier").option("--working-dir <dir>", "Agent working directory").action(async (options) => {
134544
+ agentConfigCommand.command("set").description("Send a one-time agent start request with the harness and model").requiredOption("--chatroom-id <id>", "Chatroom identifier").requiredOption("--role <role>", "Agent role").requiredOption("--harness <harness>", "Agent harness").requiredOption("--model <model>", "Model identifier").option("--working-dir <dir>", "Agent working directory").action(async (options) => {
134393
134545
  await maybeRequireAuth();
134394
134546
  const { setAgentConfig: setAgentConfig2 } = await Promise.resolve().then(() => (init_agent2(), exports_agent));
134395
134547
  await setAgentConfig2(options.chatroomId, options);
@@ -134856,4 +135008,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
134856
135008
  });
134857
135009
  program2.parse();
134858
135010
 
134859
- //# debugId=1E9B97C58361134164756E2164756E21
135011
+ //# debugId=D0F3FF58E9CFA79B64756E2164756E21