chatroom-cli 1.128.0 → 1.129.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
@@ -20251,6 +20251,16 @@ var init_agent_process_manager_service = __esm(() => {
20251
20251
  init_check_agent_slot();
20252
20252
  });
20253
20253
 
20254
+ // ../../services/backend/config/reliability.ts
20255
+ 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, MACHINE_COMMAND_LEASE_RENEWAL_INTERVAL_MS = 20000, MACHINE_COMMAND_DAEMON_ROUTINE_TTL_MS, SCOPE_TARGET_STOP_TIMEOUT_MS = 1e4, AGENT_LIFECYCLE_OPERATION_TIMEOUT_MS = 120000, CONNECTION_CLOSE_REQUEST_TTL_MS, ENHANCER_TERMINAL_JOB_RETENTION_MS;
20256
+ var init_reliability = __esm(() => {
20257
+ DAEMON_HEARTBEAT_INTERVAL_MS = 5 * 60000;
20258
+ DAEMON_HEARTBEAT_TTL_MS = 6 * DAEMON_HEARTBEAT_INTERVAL_MS;
20259
+ MACHINE_COMMAND_DAEMON_ROUTINE_TTL_MS = 5 * 60000;
20260
+ CONNECTION_CLOSE_REQUEST_TTL_MS = 10 * 60000;
20261
+ ENHANCER_TERMINAL_JOB_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
20262
+ });
20263
+
20254
20264
  // ../shared/src/domain/team-kind.ts
20255
20265
  var TEAM_PRESET_IDS;
20256
20266
  var init_team_kind = __esm(() => {
@@ -20393,6 +20403,13 @@ var init_agent_role = __esm(() => {
20393
20403
  researcher: {
20394
20404
  tags: ["ephemeral" /* Ephemeral */],
20395
20405
  teams: [{ teamId: "duo", order: 25 }]
20406
+ },
20407
+ verifier: {
20408
+ tags: ["ephemeral" /* Ephemeral */],
20409
+ teams: [
20410
+ { teamId: "duo", order: 45 },
20411
+ { teamId: "solo", order: 45 }
20412
+ ]
20396
20413
  }
20397
20414
  };
20398
20415
  AGENT_ROLE_DEFINITIONS = Object.fromEntries(Object.entries(roleMetadata).map(([role, metadata]) => [role, { role, ...metadata }]));
@@ -35229,8 +35246,10 @@ var init_agent_lifecycle_fact = __esm(() => {
35229
35246
  function isDeliverableTaskStatus(status) {
35230
35247
  return status === "pending" || status === "acknowledged";
35231
35248
  }
35249
+ var ACTIVE_TASK_STATUSES;
35232
35250
  var init_assigned_task = __esm(() => {
35233
35251
  init_agent_role();
35252
+ ACTIVE_TASK_STATUSES = ["pending", "acknowledged", "in_progress"];
35234
35253
  });
35235
35254
 
35236
35255
  // src/daemon/infrastructure/local/harness/services/spawn-prompt.ts
@@ -35292,6 +35311,17 @@ var init_native_task_injector_logic = __esm(() => {
35292
35311
  };
35293
35312
  });
35294
35313
 
35314
+ // src/daemon/services/task-service/domain/usecase/uncovered-turn-nudge.ts
35315
+ function buildUncoveredTurnNudgePrompt(args2) {
35316
+ return [
35317
+ `Your previous turn ended, but task ${args2.taskId} has no handoff recorded.`,
35318
+ "If the work is complete, run `chatroom handoff` for this task now, as instructed in the task.",
35319
+ "If you are waiting on something (for example a background command or CI), do not end your turn: wait for it in the foreground, then run `chatroom handoff`.",
35320
+ "If you cannot do either, reply with one line saying what you are waiting on."
35321
+ ].join(`
35322
+ `);
35323
+ }
35324
+
35295
35325
  // ../../services/backend/src/domain/entities/participant.ts
35296
35326
  var NATIVE_WAITING_ACTION = "native:waiting", NATIVE_TASK_INJECTED_ACTION = "native:task-injected", GET_NEXT_TASK_STARTED_ACTION = "get-next-task:started";
35297
35327
 
@@ -36324,16 +36354,6 @@ var init_parse_session_augmentation = __esm(() => {
36324
36354
  init_team_agent_settings();
36325
36355
  });
36326
36356
 
36327
- // ../../services/backend/config/reliability.ts
36328
- 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, MACHINE_COMMAND_LEASE_RENEWAL_INTERVAL_MS = 20000, MACHINE_COMMAND_DAEMON_ROUTINE_TTL_MS, SCOPE_TARGET_STOP_TIMEOUT_MS = 1e4, AGENT_LIFECYCLE_OPERATION_TIMEOUT_MS = 120000, CONNECTION_CLOSE_REQUEST_TTL_MS, ENHANCER_TERMINAL_JOB_RETENTION_MS;
36329
- var init_reliability = __esm(() => {
36330
- DAEMON_HEARTBEAT_INTERVAL_MS = 5 * 60000;
36331
- DAEMON_HEARTBEAT_TTL_MS = 6 * DAEMON_HEARTBEAT_INTERVAL_MS;
36332
- MACHINE_COMMAND_DAEMON_ROUTINE_TTL_MS = 5 * 60000;
36333
- CONNECTION_CLOSE_REQUEST_TTL_MS = 10 * 60000;
36334
- ENHANCER_TERMINAL_JOB_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
36335
- });
36336
-
36337
36357
  // src/daemon/services/task-service/service/native-cold-session-before-inject.ts
36338
36358
  function sleep3(ms) {
36339
36359
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -36603,7 +36623,7 @@ function resumeHarnessWithPrompt(task, deps, prompt, harnessSessionId) {
36603
36623
  taskId,
36604
36624
  error: error51
36605
36625
  });
36606
- return;
36626
+ return yield* exports_Effect.fail(resumeResult.failure);
36607
36627
  }
36608
36628
  yield* exports_Effect.tryPromise({
36609
36629
  try: () => deps.audit.emit({
@@ -36732,6 +36752,8 @@ class AgentWorkManager {
36732
36752
  nativeTaskDeliveryQueue;
36733
36753
  deliveryTaskService;
36734
36754
  recoveryGates = new Map;
36755
+ respawnStartRetries = new Map;
36756
+ respawnRestartSeq = 0;
36735
36757
  disposed = false;
36736
36758
  constructor(deps) {
36737
36759
  this.deps = deps;
@@ -36763,7 +36785,10 @@ class AgentWorkManager {
36763
36785
  releaseTaskAfterTurnFailure: deps.taskService.releaseTaskAfterTurnFailure,
36764
36786
  recordDeliveryFailure: deps.taskService.recordDeliveryFailure,
36765
36787
  clearDeliveryFailure: deps.taskService.clearDeliveryFailure,
36766
- recordUncoveredTurnEnd: deps.taskService.recordUncoveredTurnEnd,
36788
+ recordTurnFailure: deps.taskService.recordTurnFailure,
36789
+ decideUncoveredTurnEnd: deps.taskService.decideUncoveredTurnEnd,
36790
+ confirmNudgeSent: deps.taskService.confirmNudgeSent,
36791
+ resetTurnTracking: deps.taskService.resetTurnTracking,
36767
36792
  isRedeliveryExhausted: deps.taskService.isRedeliveryExhausted,
36768
36793
  clearRedeliveryTracking: deps.taskService.clearRedeliveryTracking,
36769
36794
  forgetStaleTask: deps.taskService.forgetStaleTask,
@@ -36789,6 +36814,7 @@ class AgentWorkManager {
36789
36814
  this.unsubscribeTaskService = deps.taskService.subscribe((notification) => this.handleTaskServiceNotification(notification));
36790
36815
  }
36791
36816
  async handleAgentStarted(event) {
36817
+ this.cancelRespawnStart(recoveryKey(event.chatroomId, event.role), `agent_started:${event.reason ?? "unknown"}`);
36792
36818
  await this.waitForRoleRecovery(event.chatroomId, event.role);
36793
36819
  this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
36794
36820
  if (event.reason === "user.start") {
@@ -36906,9 +36932,9 @@ class AgentWorkManager {
36906
36932
  });
36907
36933
  }, delay3);
36908
36934
  }
36909
- async recoverTaskAfterUncoveredTurnEnd(event, activeTask, phase) {
36935
+ async recoverTaskAfterTurnFailure(event, activeTask, phase) {
36910
36936
  const { chatroomId, role } = event;
36911
- const { exceeded } = await this.deps.taskService.recordUncoveredTurnEnd({
36937
+ const { exceeded } = await this.deps.taskService.recordTurnFailure({
36912
36938
  chatroomId,
36913
36939
  role,
36914
36940
  taskId: activeTask.taskId
@@ -36937,7 +36963,7 @@ class AgentWorkManager {
36937
36963
  const errorDetail = completion.error;
36938
36964
  console.error(`[NativeDelivery:turn-failed] chatroom=${event.chatroomId} role=${event.role} task=${activeTask2?.taskId ?? "none"} turn=${completion.turnId} status=${completion.status} source=${completion.source} error=${errorDetail ?? "none"}`);
36939
36965
  if (activeTask2) {
36940
- const recovery = await this.recoverTaskAfterUncoveredTurnEnd(event, activeTask2, "turn-failed");
36966
+ const recovery = await this.recoverTaskAfterTurnFailure(event, activeTask2, "turn-failed");
36941
36967
  if (recovery === "exceeded") {
36942
36968
  this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
36943
36969
  return { kind: "release-slot" };
@@ -36977,14 +37003,41 @@ class AgentWorkManager {
36977
37003
  });
36978
37004
  if (activeTask) {
36979
37005
  const handoff = await this.deps.taskService.getLatestHandoff(event.chatroomId, event.role);
36980
- if (!handoff?.taskIds.includes(activeTask.taskId)) {
36981
- const recovery = await this.recoverTaskAfterUncoveredTurnEnd(event, activeTask, "turn-ended");
36982
- if (recovery === "recovery-failed") {
36983
- return { kind: "hold-slot", reason: "task-recovery-failed" };
37006
+ const covered = handoff?.taskIds.includes(activeTask.taskId) ?? false;
37007
+ if (covered) {
37008
+ this.deps.taskService.resetTurnTracking({
37009
+ chatroomId: event.chatroomId,
37010
+ role: event.role,
37011
+ taskId: activeTask.taskId
37012
+ });
37013
+ } else {
37014
+ const decision = await this.deps.taskService.decideUncoveredTurnEnd({
37015
+ chatroomId: event.chatroomId,
37016
+ role: event.role,
37017
+ taskId: activeTask.taskId,
37018
+ sessionKey: sessionKeyFor(event.pid, event.slot.harnessSessionId)
37019
+ });
37020
+ if (decision.action === "nudge") {
37021
+ await this.enqueueTurnEndedWaitingFact(event);
37022
+ this.scheduleUncoveredAction(event, activeTask, "nudge");
37023
+ return { kind: "release-slot" };
36984
37024
  }
37025
+ if (decision.action === "restart-session") {
37026
+ await this.enqueueTurnEndedWaitingFact(event);
37027
+ this.scheduleUncoveredAction(event, activeTask, "restart-session");
37028
+ return { kind: "release-slot" };
37029
+ }
37030
+ await this.enqueueTurnEndedWaitingFact(event);
37031
+ this.applyParkAction(event);
37032
+ return { kind: "release-slot" };
36985
37033
  }
36986
37034
  }
36987
37035
  this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
37036
+ await this.enqueueTurnEndedWaitingFact(event);
37037
+ this.scheduleRoleDelivery(event.chatroomId, event.role);
37038
+ return { kind: "release-slot" };
37039
+ }
37040
+ async enqueueTurnEndedWaitingFact(event) {
36988
37041
  try {
36989
37042
  await this.deps.lifecycleOutbox.enqueue(buildAgentStatusFact({
36990
37043
  chatroomId: event.chatroomId,
@@ -36994,8 +37047,244 @@ class AgentWorkManager {
36994
37047
  } catch (error51) {
36995
37048
  console.error(`[NativeDelivery:turn-ended-outbox-error] chatroom=${event.chatroomId} role=${event.role} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
36996
37049
  }
37050
+ }
37051
+ scheduleUncoveredAction(event, task, action, attempt = 0, delayMs = 0) {
37052
+ setTimeout(() => {
37053
+ if (this.disposed)
37054
+ return;
37055
+ const run = action === "nudge" ? this.sendUncoveredNudge(event, task, attempt) : this.restartSessionForUncoveredTask(event, task);
37056
+ run.catch((error51) => {
37057
+ console.error(`[NativeDelivery:uncovered-action-error] action=${action} chatroom=${event.chatroomId} role=${event.role} task=${task.taskId} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
37058
+ });
37059
+ }, delayMs);
37060
+ }
37061
+ async sendUncoveredNudge(event, task, attempt = 0) {
37062
+ const { chatroomId, role } = event;
37063
+ const sessionKey = sessionKeyFor(event.pid, event.slot.harnessSessionId);
37064
+ const deliveryState = getRoleDeliveryState();
37065
+ if (!deliveryState.tryAcquireDelivery(chatroomId, role)) {
37066
+ console.log(`[NativeDelivery:uncovered-nudge-skipped] chatroom=${chatroomId} role=${role} task=${task.taskId} reason=delivery_busy`);
37067
+ if (attempt + 1 < MAX_UNCOVERED_NUDGE_SEND_ATTEMPTS) {
37068
+ this.scheduleUncoveredAction(event, task, "nudge", attempt + 1, UNCOVERED_NUDGE_RETRY_MS);
37069
+ return;
37070
+ }
37071
+ await this.escalateUndeliverableNudge(event, task, sessionKey);
37072
+ return;
37073
+ }
37074
+ const generation = deliveryState.getGeneration(chatroomId, role);
37075
+ try {
37076
+ const skipReason = this.uncoveredNudgeSkipReason(event, task);
37077
+ if (skipReason) {
37078
+ console.log(`[NativeDelivery:uncovered-nudge-skipped] chatroom=${chatroomId} role=${role} task=${task.taskId} reason=${skipReason}`);
37079
+ return;
37080
+ }
37081
+ try {
37082
+ await exports_Effect.runPromise(this.deps.agentMgr.resumeTurnForSlot({
37083
+ chatroomId,
37084
+ role,
37085
+ prompt: buildUncoveredTurnNudgePrompt({ taskId: task.taskId })
37086
+ }));
37087
+ } catch (error51) {
37088
+ console.error(`[NativeDelivery:uncovered-nudge] chatroom=${chatroomId} role=${role} task=${task.taskId} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
37089
+ await this.escalateUndeliverableNudge(event, task, sessionKey);
37090
+ return;
37091
+ }
37092
+ this.deps.taskService.confirmNudgeSent({ chatroomId, role, taskId: task.taskId, sessionKey });
37093
+ } finally {
37094
+ deliveryState.releaseDelivery(chatroomId, role, generation);
37095
+ }
37096
+ }
37097
+ async escalateUndeliverableNudge(event, task, sessionKey) {
37098
+ const decision = await this.deps.taskService.decideUncoveredTurnEnd({
37099
+ chatroomId: event.chatroomId,
37100
+ role: event.role,
37101
+ taskId: task.taskId,
37102
+ sessionKey,
37103
+ nudgeUnavailable: true
37104
+ });
37105
+ if (decision.action === "restart-session") {
37106
+ this.scheduleUncoveredAction(event, task, "restart-session");
37107
+ } else {
37108
+ this.applyParkAction(event);
37109
+ }
37110
+ }
37111
+ async restartSessionForUncoveredTask(event, task) {
37112
+ const { chatroomId, role, pid } = event;
37113
+ const config3 = this.deps.configurationService.get(chatroomId, role);
37114
+ const slot = this.deps.agentMgr.getSlot(chatroomId, role);
37115
+ const harness = config3?.agentHarness ?? slot?.harness;
37116
+ const model = config3?.model ?? slot?.model;
37117
+ const workingDir = config3?.workingDir ?? slot?.workingDir;
37118
+ if (!harness || !model || !workingDir) {
37119
+ console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} task=${task.taskId} error=missing_agent_config`);
37120
+ const sessionKey = sessionKeyFor(event.pid, event.slot.harnessSessionId);
37121
+ await this.deps.taskService.decideUncoveredTurnEnd({
37122
+ chatroomId,
37123
+ role,
37124
+ taskId: task.taskId,
37125
+ sessionKey,
37126
+ nudgeUnavailable: true
37127
+ });
37128
+ this.applyParkAction(event);
37129
+ return;
37130
+ }
37131
+ const startInput = {
37132
+ chatroomId,
37133
+ role,
37134
+ agentHarness: harness,
37135
+ model,
37136
+ workingDir,
37137
+ reason: DaemonStartReasonCode.RESPAWN,
37138
+ wantResume: false
37139
+ };
37140
+ let outcome;
37141
+ try {
37142
+ outcome = await this.deps.runSerializedForAgent({ chatroomId, role, workingDir }, { timeoutMs: AGENT_LIFECYCLE_OPERATION_TIMEOUT_MS }, async (ops, context3) => {
37143
+ const current = this.deps.agentMgr.getSlot(chatroomId, role);
37144
+ if (this.deps.agentMgr.isStopRequested(chatroomId, role) || current?.pid !== pid || current?.harnessSessionId !== event.slot.harnessSessionId) {
37145
+ console.log(`[NativeDelivery:uncovered-restart-stale] chatroom=${chatroomId} role=${role} task=${task.taskId}`);
37146
+ return { kind: "skipped", reason: "stale_slot" };
37147
+ }
37148
+ await this.prepareRoleRecovery({ chatroomId, role });
37149
+ await ops.stopAgent({ chatroomId, role, reason: AgentStopReasonEnum["daemon.respawn"], pid, workingDir }, context3.signal);
37150
+ await this.recoverStoppedRole({ chatroomId, role, mode: "automatic" });
37151
+ return this.attemptRespawnStart(ops, startInput, context3.signal);
37152
+ });
37153
+ } catch (error51) {
37154
+ const message = error51 instanceof Error ? error51.message : String(error51);
37155
+ console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} task=${task.taskId} error=${message}`);
37156
+ if (this.recoveryGates.has(recoveryKey(chatroomId, role))) {
37157
+ this.scheduleRecoveryRetry(chatroomId, role);
37158
+ }
37159
+ return;
37160
+ }
37161
+ if (outcome.kind === "start-failed") {
37162
+ console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} task=${task.taskId} attempt=1 of ${MAX_RESPAWN_START_ATTEMPTS} error=${outcome.error}`);
37163
+ this.beginRespawnStartRetries({ chatroomId, role, workingDir, startInput });
37164
+ }
37165
+ }
37166
+ async attemptRespawnStart(ops, startInput, signal) {
37167
+ try {
37168
+ const started = await ops.startAgent(startInput, signal);
37169
+ return started.success ? { kind: "started" } : { kind: "start-failed", error: started.error ?? "start_rejected" };
37170
+ } catch (error51) {
37171
+ return {
37172
+ kind: "start-failed",
37173
+ error: error51 instanceof Error ? error51.message : String(error51)
37174
+ };
37175
+ }
37176
+ }
37177
+ beginRespawnStartRetries(request3) {
37178
+ if (this.disposed)
37179
+ return;
37180
+ const key = recoveryKey(request3.chatroomId, request3.role);
37181
+ this.cancelRespawnStart(key, "superseded_by_new_restart");
37182
+ this.respawnRestartSeq += 1;
37183
+ const pending = {
37184
+ restartId: this.respawnRestartSeq,
37185
+ request: request3,
37186
+ attempts: 1
37187
+ };
37188
+ this.respawnStartRetries.set(key, pending);
37189
+ this.armRespawnStartRetry(key, pending);
37190
+ }
37191
+ armRespawnStartRetry(key, pending) {
37192
+ if (this.disposed)
37193
+ return;
37194
+ const delay3 = Math.min(30000, 1000 * 2 ** (pending.attempts - 1));
37195
+ pending.timer = setTimeout(() => {
37196
+ pending.timer = undefined;
37197
+ this.retryRespawnStart(key, pending.restartId).catch((error51) => {
37198
+ console.error(`[NativeDelivery:uncovered-restart-error] chatroom=${pending.request.chatroomId} role=${pending.request.role} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
37199
+ });
37200
+ }, delay3);
37201
+ }
37202
+ async retryRespawnStart(key, restartId) {
37203
+ const pending = this.respawnStartRetries.get(key);
37204
+ if (!pending || pending.restartId !== restartId || this.disposed)
37205
+ return;
37206
+ const { chatroomId, role, workingDir, startInput } = pending.request;
37207
+ if (this.deps.agentMgr.isStopRequested(chatroomId, role)) {
37208
+ this.cancelRespawnStart(key, "stop_requested");
37209
+ return;
37210
+ }
37211
+ const slot = this.deps.agentMgr.getSlot(chatroomId, role);
37212
+ if (slot && isSlotRunning(slot.state)) {
37213
+ this.cancelRespawnStart(key, "agent_already_running");
37214
+ return;
37215
+ }
37216
+ const outcome = await this.deps.runSerializedForAgent({ chatroomId, role, workingDir }, { timeoutMs: AGENT_LIFECYCLE_OPERATION_TIMEOUT_MS }, async (ops, context3) => {
37217
+ if (this.respawnStartRetries.get(key)?.restartId !== restartId) {
37218
+ return { kind: "skipped", reason: "superseded" };
37219
+ }
37220
+ if (this.deps.agentMgr.isStopRequested(chatroomId, role)) {
37221
+ return { kind: "skipped", reason: "stop_requested" };
37222
+ }
37223
+ const current2 = this.deps.agentMgr.getSlot(chatroomId, role);
37224
+ if (current2 && isSlotRunning(current2.state)) {
37225
+ return { kind: "skipped", reason: "agent_already_running" };
37226
+ }
37227
+ return this.attemptRespawnStart(ops, startInput, context3.signal);
37228
+ });
37229
+ if (outcome.kind === "skipped") {
37230
+ this.cancelRespawnStart(key, outcome.reason);
37231
+ return;
37232
+ }
37233
+ const current = this.respawnStartRetries.get(key);
37234
+ if (current?.restartId !== restartId)
37235
+ return;
37236
+ if (outcome.kind === "started") {
37237
+ this.cancelRespawnStart(key, "started");
37238
+ return;
37239
+ }
37240
+ current.attempts += 1;
37241
+ if (current.attempts >= MAX_RESPAWN_START_ATTEMPTS) {
37242
+ this.respawnStartRetries.delete(key);
37243
+ console.error(`[NativeDelivery:uncovered-restart-failed] attempts exhausted chatroom=${chatroomId} role=${role} attempts=${current.attempts} error=${outcome.error}`);
37244
+ return;
37245
+ }
37246
+ console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} attempt=${current.attempts} of ${MAX_RESPAWN_START_ATTEMPTS} error=${outcome.error}`);
37247
+ this.armRespawnStartRetry(key, current);
37248
+ }
37249
+ cancelRespawnStart(key, reason) {
37250
+ const pending = this.respawnStartRetries.get(key);
37251
+ if (!pending)
37252
+ return;
37253
+ if (pending.timer)
37254
+ clearTimeout(pending.timer);
37255
+ pending.timer = undefined;
37256
+ this.respawnStartRetries.delete(key);
37257
+ if (reason === "started")
37258
+ return;
37259
+ console.log(`[NativeDelivery:uncovered-restart-cancelled] chatroom=${pending.request.chatroomId} role=${pending.request.role} attempts=${pending.attempts} reason=${reason}`);
37260
+ }
37261
+ uncoveredNudgeSkipReason(event, task) {
37262
+ const { chatroomId, role } = event;
37263
+ const slot = this.deps.agentMgr.getSlot(chatroomId, role);
37264
+ if (!slot || !isSlotRunning(slot.state))
37265
+ return "slot_not_running";
37266
+ if (slot.pid !== event.pid)
37267
+ return "pid_changed";
37268
+ if (slot.harnessSessionId !== event.slot.harnessSessionId)
37269
+ return "session_changed";
37270
+ if (!isTurnPhaseIdle(slot.nativeTurnPhase ?? "idle"))
37271
+ return "turn_not_idle";
37272
+ if (this.deps.agentMgr.isStopRequested(chatroomId, role))
37273
+ return "stop_requested";
37274
+ if (this.recoveryGates.has(recoveryKey(chatroomId, role)))
37275
+ return "recovery_in_progress";
37276
+ if (this.deps.agentTaskState.get({ chatroomId, role })?.taskId !== task.taskId) {
37277
+ return "task_not_active";
37278
+ }
37279
+ const current = this.deps.taskInboxState.getForRole(chatroomId, role, task.taskId);
37280
+ if (!current || !ACTIVE_TASK_STATUSES.includes(current.status)) {
37281
+ return "task_not_in_inbox";
37282
+ }
37283
+ return;
37284
+ }
37285
+ applyParkAction(event) {
37286
+ this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
36997
37287
  this.scheduleRoleDelivery(event.chatroomId, event.role);
36998
- return { kind: "release-slot" };
36999
37288
  }
37000
37289
  scheduleRoleDelivery(chatroomId, role) {
37001
37290
  setTimeout(() => {
@@ -37006,6 +37295,12 @@ class AgentWorkManager {
37006
37295
  }
37007
37296
  dispose() {
37008
37297
  this.disposed = true;
37298
+ for (const pending of this.respawnStartRetries.values()) {
37299
+ if (pending.timer)
37300
+ clearTimeout(pending.timer);
37301
+ pending.timer = undefined;
37302
+ }
37303
+ this.respawnStartRetries.clear();
37009
37304
  for (const gate of this.recoveryGates.values()) {
37010
37305
  if (gate.retryTimer)
37011
37306
  clearTimeout(gate.retryTimer);
@@ -37217,13 +37512,16 @@ class AgentWorkManager {
37217
37512
  this.deps.agentTaskState.start(args2);
37218
37513
  }
37219
37514
  }
37220
- var recoveryKey = (chatroomId, role) => `${chatroomId}:${role.toLowerCase()}`;
37515
+ var MAX_RESPAWN_START_ATTEMPTS = 3, MAX_UNCOVERED_NUDGE_SEND_ATTEMPTS = 3, UNCOVERED_NUDGE_RETRY_MS = 250, recoveryKey = (chatroomId, role) => `${chatroomId}:${role.toLowerCase()}`, sessionKeyFor = (pid, harnessSessionId) => `${pid}:${harnessSessionId ?? "none"}`;
37221
37516
  var init_agent_work_manager = __esm(() => {
37517
+ init_reliability();
37222
37518
  init_agent();
37223
37519
  init_chatroom_workspace_task_inbox();
37224
37520
  init_dist();
37225
37521
  init_agent_lifecycle_fact();
37226
37522
  init_agent_slot();
37523
+ init_assigned_task();
37524
+ init_check_agent_slot();
37227
37525
  init_native_task_injector_logic();
37228
37526
  init_convex_native_task_delivery_gateway();
37229
37527
  init_daemon_audit_port();
@@ -40322,6 +40620,27 @@ class TaskInboxState {
40322
40620
  }
40323
40621
  }
40324
40622
 
40623
+ // src/daemon/services/task-service/domain/usecase/uncovered-turn-ladder.ts
40624
+ function decideUncoveredTurnEnd(entry, sessionKey, opts = {}) {
40625
+ const current = entry ?? FRESH_ENTRY;
40626
+ if (isNudgeDue(current, sessionKey, opts))
40627
+ return { action: "nudge" };
40628
+ return current.sessionRestarts < MAX_SESSION_RESTARTS_PER_TASK ? { action: "restart-session" } : { action: "park" };
40629
+ }
40630
+ function isNudgeDue(entry, sessionKey, opts) {
40631
+ return !opts.nudgeUnavailable && entry.nudgedSessionKey !== sessionKey;
40632
+ }
40633
+ function withNudgeSent(entry, sessionKey) {
40634
+ return { nudgedSessionKey: sessionKey, sessionRestarts: entry?.sessionRestarts ?? 0 };
40635
+ }
40636
+ function withSessionRestart(entry) {
40637
+ return { nudgedSessionKey: undefined, sessionRestarts: (entry?.sessionRestarts ?? 0) + 1 };
40638
+ }
40639
+ var MAX_SESSION_RESTARTS_PER_TASK = 1, FRESH_ENTRY;
40640
+ var init_uncovered_turn_ladder = __esm(() => {
40641
+ FRESH_ENTRY = { sessionRestarts: 0 };
40642
+ });
40643
+
40325
40644
  // src/daemon/services/task-service/service/task-service.ts
40326
40645
  function createTaskService(deps) {
40327
40646
  const gateway = createConvexNativeTaskDeliveryGateway(deps.backend);
@@ -40339,18 +40658,13 @@ function createTaskService(deps) {
40339
40658
  let bootstrapRecoveryAttempts = 0;
40340
40659
  let bootstrapRecoveryTimer;
40341
40660
  let bootstrapRecoveryPromise;
40342
- const REDIVERY_ATTEMPT_CAP = 3;
40343
- const turnEndAttemptCounts = new Map;
40661
+ const TURN_FAILURE_RETRY_CAP = 3;
40662
+ const turnFailureCounts = new Map;
40663
+ const uncoveredLadder = new Map;
40344
40664
  const redeliveryExhaustedKeys = new Set;
40345
40665
  const redeliveryKey = (chatroomId, role, taskId) => `${chatroomId}:${role.toLowerCase()}:${taskId}`;
40346
- const recordUncoveredTurnEnd = async (args2) => {
40666
+ const parkTask = async (args2, attempts) => {
40347
40667
  const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
40348
- if (redeliveryExhaustedKeys.has(key))
40349
- return { exceeded: true };
40350
- const attempts = (turnEndAttemptCounts.get(key) ?? 0) + 1;
40351
- turnEndAttemptCounts.set(key, attempts);
40352
- if (attempts < REDIVERY_ATTEMPT_CAP)
40353
- return { exceeded: false };
40354
40668
  redeliveryExhaustedKeys.add(key);
40355
40669
  try {
40356
40670
  await gateway.recordDeliveryFailure({
@@ -40362,13 +40676,50 @@ function createTaskService(deps) {
40362
40676
  } catch (error51) {
40363
40677
  console.warn(`[TaskService] redelivery-exhausted failure record failed chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
40364
40678
  }
40365
- console.log(`[NativeDelivery:redelivery-exhausted] chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId} attempts=${attempts} — release/redelivery stopped; agent restart (user.start) resets`);
40679
+ const attemptsSuffix = attempts === undefined ? "" : ` attempts=${attempts}`;
40680
+ console.log(`[NativeDelivery:redelivery-exhausted] chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId}${attemptsSuffix} — release/redelivery stopped; agent restart (user.start) resets`);
40681
+ };
40682
+ const recordTurnFailure = async (args2) => {
40683
+ const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
40684
+ if (redeliveryExhaustedKeys.has(key))
40685
+ return { exceeded: true };
40686
+ const attempts = (turnFailureCounts.get(key) ?? 0) + 1;
40687
+ turnFailureCounts.set(key, attempts);
40688
+ if (attempts < TURN_FAILURE_RETRY_CAP)
40689
+ return { exceeded: false };
40690
+ await parkTask(args2, attempts);
40366
40691
  return { exceeded: true };
40367
40692
  };
40693
+ const decideUncoveredTurnEnd2 = async (args2) => {
40694
+ const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
40695
+ if (redeliveryExhaustedKeys.has(key))
40696
+ return { action: "park" };
40697
+ turnFailureCounts.delete(key);
40698
+ const entry = uncoveredLadder.get(key);
40699
+ const decision = decideUncoveredTurnEnd(entry, args2.sessionKey, {
40700
+ nudgeUnavailable: args2.nudgeUnavailable ?? false
40701
+ });
40702
+ if (decision.action === "restart-session") {
40703
+ uncoveredLadder.set(key, withSessionRestart(entry));
40704
+ } else if (decision.action === "park") {
40705
+ await parkTask(args2);
40706
+ }
40707
+ return decision;
40708
+ };
40709
+ const confirmNudgeSent = (args2) => {
40710
+ const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
40711
+ uncoveredLadder.set(key, withNudgeSent(uncoveredLadder.get(key), args2.sessionKey));
40712
+ };
40713
+ const resetTurnTracking = (args2) => {
40714
+ const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
40715
+ turnFailureCounts.delete(key);
40716
+ uncoveredLadder.delete(key);
40717
+ };
40368
40718
  const isRedeliveryExhausted = (args2) => redeliveryExhaustedKeys.has(redeliveryKey(args2.chatroomId, args2.role, args2.taskId));
40369
40719
  const clearRedeliveryTrackingForTask = (chatroomId, role, taskId) => {
40370
40720
  const key = redeliveryKey(chatroomId, role, taskId);
40371
- turnEndAttemptCounts.delete(key);
40721
+ turnFailureCounts.delete(key);
40722
+ uncoveredLadder.delete(key);
40372
40723
  redeliveryExhaustedKeys.delete(key);
40373
40724
  };
40374
40725
  const forgetStaleTask = ({
@@ -40398,9 +40749,13 @@ function createTaskService(deps) {
40398
40749
  };
40399
40750
  const clearRoleRedeliveryTracking = (chatroomId, role) => {
40400
40751
  const prefix = `${chatroomId}:${role.toLowerCase()}:`;
40401
- for (const key of [...turnEndAttemptCounts.keys()]) {
40752
+ for (const key of [...turnFailureCounts.keys()]) {
40753
+ if (key.startsWith(prefix))
40754
+ turnFailureCounts.delete(key);
40755
+ }
40756
+ for (const key of [...uncoveredLadder.keys()]) {
40402
40757
  if (key.startsWith(prefix))
40403
- turnEndAttemptCounts.delete(key);
40758
+ uncoveredLadder.delete(key);
40404
40759
  }
40405
40760
  for (const key of [...redeliveryExhaustedKeys]) {
40406
40761
  if (key.startsWith(prefix))
@@ -40802,7 +41157,10 @@ function createTaskService(deps) {
40802
41157
  taskId,
40803
41158
  ...expectedReason ? { expectedReason } : {}
40804
41159
  }),
40805
- recordUncoveredTurnEnd,
41160
+ recordTurnFailure,
41161
+ decideUncoveredTurnEnd: decideUncoveredTurnEnd2,
41162
+ confirmNudgeSent,
41163
+ resetTurnTracking,
40806
41164
  isRedeliveryExhausted,
40807
41165
  clearRedeliveryTracking: ({ chatroomId, role }) => {
40808
41166
  clearRoleRedeliveryTracking(chatroomId, role);
@@ -40817,6 +41175,7 @@ var init_task_service = __esm(() => {
40817
41175
  init_task_inbox_acknowledgement_retry_watcher();
40818
41176
  init_task_inbox_delivery_retry_watcher();
40819
41177
  init_api3();
41178
+ init_uncovered_turn_ladder();
40820
41179
  init_convex_native_task_delivery_gateway();
40821
41180
  });
40822
41181
 
@@ -99530,14 +99889,14 @@ var init_team_presets = __esm(() => {
99530
99889
  duo: {
99531
99890
  structureId: TEAM_STRUCTURE_IDS.duo,
99532
99891
  name: "Duo",
99533
- description: "A planner and builder working as a pair, planner as coordinator, with optional ephemeral architect, researcher, triage, and uiux-engineer roles",
99892
+ description: "A planner and builder working as a pair, planner as coordinator, with optional ephemeral architect, researcher, triage, uiux-engineer, and verifier roles",
99534
99893
  roles: getBuiltinRolesForTeam("duo"),
99535
99894
  entryPoint: "planner"
99536
99895
  },
99537
99896
  solo: {
99538
99897
  structureId: TEAM_STRUCTURE_IDS.solo,
99539
99898
  name: "Solo",
99540
- description: "A single agent working independently, with optional ephemeral architect, triage, and uiux-engineer roles",
99899
+ description: "A single agent working independently, with optional ephemeral architect, triage, uiux-engineer, and verifier roles",
99541
99900
  roles: getBuiltinRolesForTeam("solo"),
99542
99901
  entryPoint: "solo"
99543
99902
  }
@@ -100621,6 +100980,99 @@ var init_triage_handoff_templates = __esm(() => {
100621
100980
  init_command2();
100622
100981
  });
100623
100982
 
100983
+ // ../../services/backend/prompts/teams/verifier-handoff-templates.ts
100984
+ function normalizeEntryPointRole5(entryPointRole) {
100985
+ return entryPointRole.trim().toLowerCase() || "planner";
100986
+ }
100987
+ function getEntryPointToVerifierHandoffTemplate(entryPointRole) {
100988
+ const entryPoint = normalizeEntryPointRole5(entryPointRole);
100989
+ return `${getHandoffRecipientVisibilityCallout("verifier")}
100990
+
100991
+ ## Handoff Template (${entryPoint} → verifier)
100992
+
100993
+ The verifier judges, from chatroom logs only, whether the user's last message was achieved. It ${VERIFIER_TEST_RULE}; list any user-requested tests under Requested test runs. Treat the claimed outcome as a claim, not as evidence: the verifier does not trust it and checks every requirement against the logs themselves. Request this review only when the task involved code changes, in any conversation mode. The verdict returns to \`${entryPoint}\`.
100994
+
100995
+ ## Round
100996
+ <1 or 2; for 2, cite the round-1 verdict message id>
100997
+
100998
+ ## Mode
100999
+ <chat | code | code:enhanced>
101000
+
101001
+ ## User message id
101002
+ <id of the user's last message: the origin message id of the user task, or from \`chatroom messages anchor\`>
101003
+
101004
+ ## Context id
101005
+ <id from \`chatroom context read\`, or \`none\` if no context was read for this task>
101006
+
101007
+ ## Requested test runs
101008
+ <the test commands the user explicitly asked for, one per line, or exactly \`None requested.\`>
101009
+
101010
+ ## Claimed outcome
101011
+ <requirement-by-requirement what you believe is complete>
101012
+
101013
+ ## Evidence pointers
101014
+ <builder handoff message ids, commits, PRs, and test results as quoted in those handoffs>
101015
+
101016
+ ## Known gaps
101017
+ <anything incomplete, or exactly \`None.\`>
101018
+
101019
+ ## Handoff
101020
+ Send this request to the verifier with the command below, then stop. The verdict returns to \`${entryPoint}\`.
101021
+
101022
+ \`\`\`bash
101023
+ ${handoffCommand({
101024
+ chatroomId: "<chatroom-id>",
101025
+ role: entryPoint,
101026
+ nextRole: "verifier",
101027
+ cliEnvPrefix: ""
101028
+ })}
101029
+ \`\`\``;
101030
+ }
101031
+ function getVerifierToEntryPointHandoffTemplate(entryPointRole) {
101032
+ const entryPoint = normalizeEntryPointRole5(entryPointRole);
101033
+ return `${getHandoffRecipientVisibilityCallout(entryPoint)}
101034
+
101035
+ ## Handoff Template (Verifier → ${entryPoint})
101036
+
101037
+ Return one advisory verdict for the authoritative request. This review is non-binding: it uses chatroom logs, plus the requested test runs listed in the request, if any, deciding every requirement from the evidence those logs and runs actually contain. It ${VERIFIER_TEST_RULE}. Never accept the claimed outcome as proof. Use the exact sections below, in this order.
101038
+
101039
+ ## Verdict
101040
+ <exactly one of ${VERIFIER_VERDICTS.join(", ")} as the value; ${ACHIEVED} only if every requirement is met; ${NOT_ACHIEVED} if any requirement is unmet (logs show it was not done, or contradict it); ${INCONCLUSIVE} if none is unmet but any is unverified (logs are silent)>
101041
+
101042
+ ## Objective
101043
+ <user message id, any prior user messages used, objective in 1-2 sentences>
101044
+
101045
+ ## Requirements
101046
+ <numbered \`R1 — <requirement> — met | unmet | unverified — <evidence ref>\`>
101047
+
101048
+ ## Evidence
101049
+ <bullets: \`<message id or handoff> — "<short quote>" — supports R#\`; for a requested test run, \`<command> — exit <result> — supports R#\`>
101050
+
101051
+ ## Missing or unmet
101052
+ <per unmet or unverified requirement: what is missing and what evidence would resolve it; exactly \`None.\` if ${ACHIEVED}>
101053
+
101054
+ ## Scope and limits
101055
+ <state the test runs the verifier executed (command and exit result), or "No tests were run; none were requested."; log range read; whether the download was truncated; context id read and whether it matched the request>
101056
+
101057
+ ## Handoff
101058
+ Run the normal handoff command to \`${entryPoint}\` with this complete verdict, then stop.
101059
+
101060
+ \`\`\`bash
101061
+ ${handoffCommand({
101062
+ chatroomId: "<chatroom-id>",
101063
+ role: "verifier",
101064
+ nextRole: entryPoint,
101065
+ cliEnvPrefix: ""
101066
+ })}
101067
+ \`\`\``;
101068
+ }
101069
+ var VERIFIER_VERDICTS, VERIFIER_TEST_RULE = "runs no test the user did not ask for, and never builds, lints, or edits", ACHIEVED, NOT_ACHIEVED, INCONCLUSIVE;
101070
+ var init_verifier_handoff_templates = __esm(() => {
101071
+ init_command2();
101072
+ VERIFIER_VERDICTS = ["ACHIEVED", "NOT_ACHIEVED", "INCONCLUSIVE"];
101073
+ [ACHIEVED, NOT_ACHIEVED, INCONCLUSIVE] = VERIFIER_VERDICTS;
101074
+ });
101075
+
100624
101076
  // ../../services/backend/prompts/teams/duo/handoff-templates/planner.ts
100625
101077
  var duoPlannerHandoffContract;
100626
101078
  var init_planner = __esm(() => {
@@ -100631,16 +101083,26 @@ var init_planner = __esm(() => {
100631
101083
  init_researcher_handoff_templates();
100632
101084
  init_triage_handoff_templates();
100633
101085
  init_uiux_engineer_handoff_templates();
101086
+ init_verifier_handoff_templates();
100634
101087
  duoPlannerHandoffContract = {
100635
101088
  role: "planner",
100636
- receivesFrom: ["user", "builder", "architect", "uiux-engineer", "triage", "researcher"],
100637
- returnsTo: ["builder", "architect", "uiux-engineer", "triage", "researcher", "user"],
101089
+ receivesFrom: [
101090
+ "user",
101091
+ "builder",
101092
+ "architect",
101093
+ "uiux-engineer",
101094
+ "triage",
101095
+ "researcher",
101096
+ "verifier"
101097
+ ],
101098
+ returnsTo: ["builder", "architect", "uiux-engineer", "triage", "researcher", "verifier", "user"],
100638
101099
  outboundTemplates: {
100639
101100
  builder: () => getPlannerToBuilderHandoffTemplate(),
100640
101101
  architect: () => getEntryPointToArchitectHandoffTemplate("planner"),
100641
101102
  "uiux-engineer": () => getEntryPointToUiuxEngineerHandoffTemplate("planner"),
100642
101103
  triage: () => getEntryPointToTriageHandoffTemplate("planner"),
100643
101104
  researcher: () => getEntryPointToResearcherHandoffTemplate("planner"),
101105
+ verifier: () => getEntryPointToVerifierHandoffTemplate("planner"),
100644
101106
  user: (query) => query.conversationMode === "chat" ? getChatToUserHandoffTemplate() : getPlannerToUserReportTemplate({
100645
101107
  chatroomId: query.chatroomId,
100646
101108
  role: query.role,
@@ -100692,6 +101154,20 @@ var init_uiux_engineer = __esm(() => {
100692
101154
  };
100693
101155
  });
100694
101156
 
101157
+ // ../../services/backend/prompts/teams/duo/handoff-templates/verifier.ts
101158
+ var duoVerifierHandoffContract;
101159
+ var init_verifier = __esm(() => {
101160
+ init_verifier_handoff_templates();
101161
+ duoVerifierHandoffContract = {
101162
+ role: "verifier",
101163
+ receivesFrom: ["planner"],
101164
+ returnsTo: ["planner"],
101165
+ outboundTemplates: {
101166
+ planner: () => getVerifierToEntryPointHandoffTemplate("planner")
101167
+ }
101168
+ };
101169
+ });
101170
+
100695
101171
  // ../shared/src/domain/chatroom-role.ts
100696
101172
  var CHATROOM_ROLE_USER = "user";
100697
101173
  var init_chatroom_role = __esm(() => {
@@ -100836,6 +101312,7 @@ var init_handoff_templates = __esm(() => {
100836
101312
  init_researcher();
100837
101313
  init_triage();
100838
101314
  init_uiux_engineer();
101315
+ init_verifier();
100839
101316
  init_contracts();
100840
101317
  DUO_ROLE_HANDOFF_CATALOG = {
100841
101318
  planner: duoPlannerHandoffContract,
@@ -100843,7 +101320,8 @@ var init_handoff_templates = __esm(() => {
100843
101320
  architect: duoArchitectHandoffContract,
100844
101321
  researcher: duoResearcherHandoffContract,
100845
101322
  "uiux-engineer": duoUiuxEngineerHandoffContract,
100846
- triage: duoTriageHandoffContract
101323
+ triage: duoTriageHandoffContract,
101324
+ verifier: duoVerifierHandoffContract
100847
101325
  };
100848
101326
  DUO_ROLE_HANDOFF_CONTRACTS = Object.values(DUO_ROLE_HANDOFF_CATALOG);
100849
101327
  validatedDuoRoleHandoffContracts = (() => {
@@ -100888,14 +101366,16 @@ var init_solo = __esm(() => {
100888
101366
  init_architect_handoff_templates();
100889
101367
  init_triage_handoff_templates();
100890
101368
  init_uiux_engineer_handoff_templates();
101369
+ init_verifier_handoff_templates();
100891
101370
  soloHandoffContract = {
100892
101371
  role: "solo",
100893
- receivesFrom: ["user", "architect", "uiux-engineer", "triage"],
100894
- returnsTo: ["architect", "uiux-engineer", "triage", "user"],
101372
+ receivesFrom: ["user", "architect", "uiux-engineer", "triage", "verifier"],
101373
+ returnsTo: ["architect", "uiux-engineer", "triage", "verifier", "user"],
100895
101374
  outboundTemplates: {
100896
101375
  architect: () => getEntryPointToArchitectHandoffTemplate("solo"),
100897
101376
  "uiux-engineer": () => getEntryPointToUiuxEngineerHandoffTemplate("solo"),
100898
101377
  triage: () => getEntryPointToTriageHandoffTemplate("solo"),
101378
+ verifier: () => getEntryPointToVerifierHandoffTemplate("solo"),
100899
101379
  user: (query) => query.conversationMode === "chat" ? getChatToUserHandoffTemplate() : getSoloToUserReportTemplate({
100900
101380
  chatroomId: query.chatroomId,
100901
101381
  role: query.role,
@@ -100933,6 +101413,20 @@ var init_uiux_engineer2 = __esm(() => {
100933
101413
  };
100934
101414
  });
100935
101415
 
101416
+ // ../../services/backend/prompts/teams/solo/handoff-templates/verifier.ts
101417
+ var soloVerifierHandoffContract;
101418
+ var init_verifier2 = __esm(() => {
101419
+ init_verifier_handoff_templates();
101420
+ soloVerifierHandoffContract = {
101421
+ role: "verifier",
101422
+ receivesFrom: ["solo"],
101423
+ returnsTo: ["solo"],
101424
+ outboundTemplates: {
101425
+ solo: () => getVerifierToEntryPointHandoffTemplate("solo")
101426
+ }
101427
+ };
101428
+ });
101429
+
100936
101430
  // ../../services/backend/prompts/teams/solo/handoff-templates/index.ts
100937
101431
  function listSoloRoleHandoffContracts() {
100938
101432
  return validatedSoloRoleHandoffContracts;
@@ -100950,12 +101444,14 @@ var init_handoff_templates2 = __esm(() => {
100950
101444
  init_solo();
100951
101445
  init_triage2();
100952
101446
  init_uiux_engineer2();
101447
+ init_verifier2();
100953
101448
  init_contracts();
100954
101449
  SOLO_ROLE_HANDOFF_CATALOG = {
100955
101450
  solo: soloHandoffContract,
100956
101451
  architect: soloArchitectHandoffContract,
100957
101452
  "uiux-engineer": soloUiuxEngineerHandoffContract,
100958
- triage: soloTriageHandoffContract
101453
+ triage: soloTriageHandoffContract,
101454
+ verifier: soloVerifierHandoffContract
100959
101455
  };
100960
101456
  SOLO_ROLE_HANDOFF_CONTRACTS = Object.values(SOLO_ROLE_HANDOFF_CATALOG);
100961
101457
  validatedSoloRoleHandoffContracts = (() => {
@@ -101506,6 +102002,34 @@ chatroom handoff --chatroom-id="<chatroom-id>" --role="uiux-engineer" --next-rol
101506
102002
  The handoff must contain every required brief section, repository evidence, chosen flow, exact component/prop/state details, explicit states, accessibility details, ownership decisions, concrete classes/tokens, risks, acceptance criteria, tests, and implementation sequence the configured entry point needs to coordinate implementation. It must not contain implementation edits or alternative designs.`;
101507
102003
  });
101508
102004
 
102005
+ // ../../services/backend/prompts/cli/roles/verifier.ts
102006
+ var ACHIEVED2, NOT_ACHIEVED2, INCONCLUSIVE2, VERIFIER_GUIDANCE;
102007
+ var init_verifier3 = __esm(() => {
102008
+ init_verifier_handoff_templates();
102009
+ init_sections();
102010
+ [ACHIEVED2, NOT_ACHIEVED2, INCONCLUSIVE2] = VERIFIER_VERDICTS;
102011
+ VERIFIER_GUIDANCE = `## Verifier Operating Model
102012
+
102013
+ You are the verifier. Your main job is to check whether the user's goal was accomplished, judged from chatroom logs. You are advisory and read-only: do not edit files, implement code, run builds or linters, run \`chatroom context new\`, or act as the planner or builder. The one exception to running nothing is the test rule below: it ${VERIFIER_TEST_RULE}.
102014
+
102015
+ ${"{SESSION_CONTINUITY}"}
102016
+
102017
+ ${getInterRoleHandoffRule()}
102018
+
102019
+ 1. Read the injected verification request: round, mode, user message id, context id, requested test runs, claimed outcome, evidence pointers, and known gaps. The claimed outcome is a claim to be checked, never evidence.
102020
+ 2. Recover the objective from the logs. Run \`chatroom messages anchor --chatroom-id="<chatroom-id>" --role="verifier"\`, then \`chatroom messages download --chatroom-id="<chatroom-id>" --role="verifier" --since-message-id=<user message id> --limit=100\`. If the download is truncated, raise \`--limit\` and download again until the range is complete. If the anchor id differs from the request's user message id, use the request's id and report the mismatch. For terse follow-ups, recover intent from the prior user messages the anchor lists.
102021
+ 2a. Requested test runs: if the user's last message (or prior user messages for this objective) explicitly asks for tests to be run, run exactly those commands from the repository root, record the command and exit result, and count the output as evidence. Run no test the user did not ask for. If the request is absent, run nothing.
102022
+ 3. Run \`chatroom context read --chatroom-id="<chatroom-id>" --role="verifier"\` and note any mismatch with the request's context id.
102023
+ 4. Decompose the objective into numbered requirements R1..Rn. Split compound requests and ignore anything the user did not actually ask for.
102024
+ 5. Find evidence for every requirement in the logs alone: builder handoff reports, planner delegations, quoted test output, commits, and PRs. \`met\` requires concrete evidence in the logs. The planner's claimed outcome is a claim, not evidence. Silence means \`unverified\`. A contradiction, or a log showing the requirement was not done, means \`unmet\`. Never state that tests passed unless a log reports it, or you ran the requested tests yourself.
102025
+ 6. Choose the verdict by the rules in the injected verdict template: exactly one of ${VERIFIER_VERDICTS.join(", ")}. ${ACHIEVED2} only if every requirement is met; ${NOT_ACHIEVED2} if any requirement is unmet; ${INCONCLUSIVE2} if none is unmet but any is unverified.
102026
+ 7. Follow the injected \`<handoff-templates>\` verdict template exactly, in its order, and put the complete verdict in the handoff. Then hand off to the configured entry point with the normal command, then stop:
102027
+
102028
+ \`\`\`bash
102029
+ chatroom handoff --chatroom-id="<chatroom-id>" --role="verifier" --next-role="{ENTRY_POINT_ROLE}"
102030
+ \`\`\``;
102031
+ });
102032
+
101509
102033
  // ../../services/backend/prompts/cli/roles/planner-guidance-context.ts
101510
102034
  var init_planner_guidance_context = __esm(() => {
101511
102035
  init_env();
@@ -101529,6 +102053,7 @@ var init_fromContext2 = __esm(() => {
101529
102053
  init_planner2();
101530
102054
  init_researcher2();
101531
102055
  init_uiux_engineer3();
102056
+ init_verifier3();
101532
102057
  init_fromContext();
101533
102058
  });
101534
102059
 
@@ -101618,6 +102143,19 @@ var init_templates = __esm(() => {
101618
102143
  ],
101619
102144
  defaultHandoffTarget: "planner"
101620
102145
  },
102146
+ verifier: {
102147
+ role: "verifier",
102148
+ title: "Verifier",
102149
+ description: "You are the verifier responsible for judging, from chatroom logs only, whether the user's last message was achieved; you do not implement, test, or inspect code.",
102150
+ responsibilities: [
102151
+ "Recover the user's last message and the pinned context from the chatroom logs",
102152
+ "Break the objective into numbered requirements",
102153
+ "Check each requirement against concrete evidence in the logs, treating the planner claim as unverified",
102154
+ "Return one verdict (ACHIEVED, NOT_ACHIEVED, or INCONCLUSIVE) with evidence and the missing items",
102155
+ "Execute nothing: no tests, builds, or repository inspection"
102156
+ ],
102157
+ defaultHandoffTarget: "planner"
102158
+ },
101621
102159
  triage: {
101622
102160
  role: "triage",
101623
102161
  title: "Triage Agent",
@@ -110754,9 +111292,7 @@ function normalizeDeliveryAttachments(input) {
110754
111292
  return Object.values(attachments).some((items) => items && items.length > 0) ? attachments : undefined;
110755
111293
  }
110756
111294
  function normalizeTask(input) {
110757
- if (!input.taskId || input.taskStatus == null || input.taskContent == null)
110758
- return;
110759
- return { _id: input.taskId, status: input.taskStatus, content: input.taskContent };
111295
+ return input.task ?? undefined;
110760
111296
  }
110761
111297
  function toSerializableMessage(input) {
110762
111298
  const attachments = normalizeDeliveryAttachments(input);
@@ -111145,10 +111681,6 @@ async function createDefaultDeps17() {
111145
111681
  }
111146
111682
  };
111147
111683
  }
111148
- function formatBadges(message) {
111149
- const status = message.taskStatus ? ` (${message.taskStatus})` : "";
111150
- return `${status}`;
111151
- }
111152
111684
  function logMessageContent(content, full) {
111153
111685
  if (full) {
111154
111686
  console.log(` Content:
@@ -111220,11 +111752,10 @@ var listBySenderRoleEffect = (chatroomId, options) => exports_Effect.gen(functio
111220
111752
  console.log("─".repeat(60));
111221
111753
  for (const message of messages) {
111222
111754
  const timestamp = new Date(message._creationTime).toLocaleString();
111223
- const badges = formatBadges(message);
111224
111755
  console.log(`
111225
111756
  \uD83D\uDD39 ID: ${message._id}`);
111226
111757
  console.log(` Time: ${timestamp}`);
111227
- console.log(` Type: ${message.type}${badges}`);
111758
+ console.log(` Type: ${message.type}`);
111228
111759
  if (message.targetRole) {
111229
111760
  console.log(` Target: ${message.targetRole}`);
111230
111761
  }
@@ -111256,12 +111787,11 @@ var listBySenderRoleEffect = (chatroomId, options) => exports_Effect.gen(functio
111256
111787
  for (const message of messages) {
111257
111788
  const timestamp = new Date(message._creationTime).toLocaleString();
111258
111789
  const roleIndicator = message.senderRole.toLowerCase() === "user" ? "\uD83D\uDC64" : "\uD83E\uDD16";
111259
- const badges = formatBadges(message);
111260
111790
  console.log(`
111261
111791
  ${roleIndicator} ${message.senderRole}${message.targetRole ? ` → ${message.targetRole}` : ""}`);
111262
111792
  console.log(` ID: ${message._id}`);
111263
111793
  console.log(` Time: ${timestamp}`);
111264
- console.log(` Type: ${message.type}${badges}`);
111794
+ console.log(` Type: ${message.type}`);
111265
111795
  logMessageContent(message.content, options.full ?? false);
111266
111796
  }
111267
111797
  console.log(`
@@ -137918,4 +138448,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
137918
138448
  });
137919
138449
  program2.parse();
137920
138450
 
137921
- //# debugId=FEC6884486B357A364756E2164756E21
138451
+ //# debugId=8506C14FCD932D1964756E2164756E21