dominds 1.24.3 → 1.24.4

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.
@@ -13,6 +13,18 @@ const log_1 = require("../../log");
13
13
  const persistence_1 = require("../../persistence");
14
14
  const persistence_errors_1 = require("../../persistence-errors");
15
15
  const engine_1 = require("./engine");
16
+ function formatDriveError(error) {
17
+ if (error instanceof Error) {
18
+ return {
19
+ name: error.name,
20
+ message: error.message,
21
+ ...(typeof error.stack === 'string' && error.stack.trim() !== ''
22
+ ? { stack: error.stack }
23
+ : {}),
24
+ };
25
+ }
26
+ return { message: String(error) };
27
+ }
16
28
  function formatDriveTriggerForLog(trigger) {
17
29
  return {
18
30
  action: trigger.action,
@@ -26,7 +38,7 @@ function formatDriveTriggerForLog(trigger) {
26
38
  };
27
39
  }
28
40
  async function listLiveDialogsWithDurableDriveWork() {
29
- const liveDialogs = dialog_global_registry_1.globalDialogRegistry.getAll();
41
+ const liveDialogs = dialog_global_registry_1.globalDialogRegistry.consumeQueuedMainDialogs();
30
42
  const queued = [];
31
43
  for (const mainDialog of liveDialogs) {
32
44
  let watchedDialogIds;
@@ -42,6 +54,7 @@ async function listLiveDialogsWithDurableDriveWork() {
42
54
  }
43
55
  const candidateDialogs = [mainDialog];
44
56
  let hadCandidateInspectionError = false;
57
+ let hadStalledCandidateForRoot = false;
45
58
  for (const watchedDialogId of watchedDialogIds) {
46
59
  let watchedDialog;
47
60
  try {
@@ -95,13 +108,24 @@ async function listLiveDialogsWithDurableDriveWork() {
95
108
  if (!(0, dialog_drive_work_1.hasDurableDriveWork)(latest)) {
96
109
  continue;
97
110
  }
111
+ const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latest, dialog.id.selfId === dialog.id.rootId ? watchedDialogIds : []);
112
+ if (latest.backendDriveStall?.durableWorkFingerprint === durableWorkFingerprint) {
113
+ hadStalledCandidateForRoot = true;
114
+ log_1.log.warn('Backend driver skipped stalled durable work pending new facts', undefined, {
115
+ dialogId: dialog.id.valueOf(),
116
+ rootId: dialog.id.rootId,
117
+ selfId: dialog.id.selfId,
118
+ stallRecordId: latest.backendDriveStall.recordId,
119
+ });
120
+ continue;
121
+ }
98
122
  hasQueuedCandidateForRoot = true;
99
123
  queued.push({
100
124
  rootDialog: mainDialog,
101
125
  dialog,
102
126
  });
103
127
  }
104
- if (!hasQueuedCandidateForRoot && !hadCandidateInspectionError) {
128
+ if (!hasQueuedCandidateForRoot && !hadCandidateInspectionError && !hadStalledCandidateForRoot) {
105
129
  dialog_global_registry_1.globalDialogRegistry.clearDriveWake(mainDialog.id.rootId, {
106
130
  source: 'kernel_driver_backend_loop',
107
131
  reason: 'no_durable_drive_work',
@@ -111,29 +135,6 @@ async function listLiveDialogsWithDurableDriveWork() {
111
135
  }
112
136
  return queued;
113
137
  }
114
- async function reassertLiveRootWakeForDurableWork() {
115
- for (const mainDialog of dialog_global_registry_1.globalDialogRegistry.getAll()) {
116
- try {
117
- const rootHasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(mainDialog.id, mainDialog.status);
118
- const watchedDialogIds = await persistence_1.DialogPersistence.loadDriveWatchedDialogIds(mainDialog.id, mainDialog.status);
119
- if (!rootHasPendingNextStepTriggers && watchedDialogIds.length === 0) {
120
- continue;
121
- }
122
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(mainDialog.id.rootId, {
123
- source: 'kernel_driver_backend_loop',
124
- reason: rootHasPendingNextStepTriggers
125
- ? 'root_next_step_still_pending'
126
- : 'drive_watch_still_pending',
127
- });
128
- }
129
- catch (error) {
130
- log_1.log.error('Backend driver failed to reassert root wake for durable work', error, {
131
- rootId: mainDialog.id.rootId,
132
- selfId: mainDialog.id.selfId,
133
- });
134
- }
135
- }
136
- }
137
138
  async function driveQueuedDialogsOnce() {
138
139
  const dialogsToDrive = await listLiveDialogsWithDurableDriveWork();
139
140
  for (const { rootDialog, dialog } of dialogsToDrive) {
@@ -143,7 +144,6 @@ async function driveQueuedDialogsOnce() {
143
144
  await persistence_1.DialogPersistence.removeDriveWatchForDialog(dialog.id, dialog.status);
144
145
  continue;
145
146
  }
146
- const currentHasPendingNextStepTriggers = (latestForDrive?.nextStep.triggers.length ?? 0) > 0;
147
147
  const currentResumeInProgressGeneration = (0, dialog_generation_run_1.getRecoverableGenerationRunState)(latestForDrive) !== undefined;
148
148
  const currentHasBackendDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestForDrive);
149
149
  const executionMarker = latestForDrive?.executionMarker;
@@ -203,20 +203,27 @@ async function driveQueuedDialogsOnce() {
203
203
  const stillHasDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestAfterDrive);
204
204
  const shouldStayQueued = dialog.hasUpNext() || !status.canDrive || stillHasDurableWork;
205
205
  if (shouldStayQueued) {
206
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(rootDialog.id.rootId, {
207
- source: 'kernel_driver_backend_loop',
208
- reason: dialog.hasUpNext()
209
- ? 'post_drive_upnext_pending'
210
- : stillHasDurableWork
211
- ? 'post_drive_durable_work_pending'
212
- : 'post_drive_suspended',
213
- });
214
- if (dialog.id.selfId === dialog.id.rootId) {
215
- await persistence_1.DialogPersistence.upsertRootDriveWakeTrigger(dialog.id, dialog.hasUpNext()
216
- ? 'post_drive_upnext_pending'
217
- : stillHasDurableWork
218
- ? 'post_drive_durable_work_pending'
219
- : 'post_drive_suspended', dialog.status);
206
+ const canRetryImmediately = dialog.hasUpNext() || (status.canDrive && stillHasDurableWork);
207
+ if (canRetryImmediately) {
208
+ dialog_global_registry_1.globalDialogRegistry.wakeDrive(rootDialog.id.rootId, {
209
+ source: 'kernel_driver_backend_loop',
210
+ reason: dialog.hasUpNext()
211
+ ? 'post_drive_upnext_pending'
212
+ : 'post_drive_durable_work_pending',
213
+ });
214
+ if (dialog.id.selfId === dialog.id.rootId) {
215
+ await persistence_1.DialogPersistence.upsertRootDriveWakeTrigger(dialog.id, dialog.hasUpNext() ? 'post_drive_upnext_pending' : 'post_drive_durable_work_pending', dialog.status);
216
+ }
217
+ }
218
+ else {
219
+ log_1.log.debug('Backend driver left durable work parked until the blocking state changes', undefined, {
220
+ dialogId: dialog.id.valueOf(),
221
+ rootId: dialog.id.rootId,
222
+ selfId: dialog.id.selfId,
223
+ waitingQ4H: status.q4h,
224
+ backgroundCalleeDialogs: status.backgroundCalleeDialogs,
225
+ stillHasDurableWork,
226
+ });
220
227
  }
221
228
  }
222
229
  else {
@@ -289,14 +296,47 @@ async function driveQueuedDialogsOnce() {
289
296
  const rootHasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(rootId, dialog.status);
290
297
  const watchedDialogIds = await persistence_1.DialogPersistence.loadDriveWatchedDialogIds(rootId, dialog.status);
291
298
  if (rootHasPendingNextStepTriggers || watchedDialogIds.length > 0) {
292
- dialog_global_registry_1.globalDialogRegistry.wakeDrive(dialog.id.rootId, {
293
- source: 'kernel_driver_backend_loop',
294
- reason: 'drive_error_durable_work_pending',
299
+ const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latestAfterError, dialog.id.selfId === dialog.id.rootId ? watchedDialogIds : []);
300
+ const record = await persistence_1.DialogPersistence.appendBackendDriveStallRecord(dialog.id, {
301
+ dialogId: dialog.id.valueOf(),
302
+ rootId: dialog.id.rootId,
303
+ selfId: dialog.id.selfId,
304
+ status: dialog.status,
305
+ reason: 'backend_drive_error',
306
+ durableWorkFingerprint,
307
+ latestSummary: latestAfterError === null
308
+ ? null
309
+ : {
310
+ currentCourse: latestAfterError.currentCourse,
311
+ status: latestAfterError.status,
312
+ generating: latestAfterError.generating ?? false,
313
+ displayState: latestAfterError.displayState ?? null,
314
+ executionMarker: latestAfterError.executionMarker ?? null,
315
+ generationRunState: latestAfterError.generationRunState ?? null,
316
+ nextStepTriggerCount: latestAfterError.nextStep.triggers.length,
317
+ pendingRuntimePromptMsgId: latestAfterError.pendingRuntimePrompt?.msgId ?? null,
318
+ replyDelivery: latestAfterError.replyDelivery ?? null,
319
+ userWait: latestAfterError.userWait ?? null,
320
+ sideDialogFinalResponse: latestAfterError.sideDialogFinalResponse ?? null,
321
+ },
322
+ error: formatDriveError(err),
323
+ context: {
324
+ rootHasPendingNextStepTriggers,
325
+ watchedDialogCount: watchedDialogIds.length,
326
+ },
327
+ }, dialog.status);
328
+ log_1.log.warn('Backend driver persisted stalled durable work after drive error', undefined, {
329
+ dialogId: dialog.id.valueOf(),
330
+ rootId: dialog.id.rootId,
331
+ selfId: dialog.id.selfId,
332
+ stallRecordId: record.recordId,
333
+ rootHasPendingNextStepTriggers,
334
+ watchedDialogCount: watchedDialogIds.length,
295
335
  });
296
336
  }
297
337
  }
298
- catch (requeueErr) {
299
- log_1.log.error('Failed to requeue durable work after backend drive error', requeueErr, {
338
+ catch (stallErr) {
339
+ log_1.log.error('Failed to persist backend drive stall after drive error', stallErr, {
300
340
  dialogId: dialog.id.valueOf(),
301
341
  rootId: dialog.id.rootId,
302
342
  selfId: dialog.id.selfId,
@@ -304,7 +344,6 @@ async function driveQueuedDialogsOnce() {
304
344
  }
305
345
  }
306
346
  }
307
- await reassertLiveRootWakeForDurableWork();
308
347
  }
309
348
  function isBackendDriverAborted(options) {
310
349
  return options?.abortSignal?.aborted === true;
@@ -1,6 +1,6 @@
1
1
  import type { DialogDisplayState, DialogInterruptionReason } from '@longrun-ai/kernel/types/display-state';
2
2
  import type { DialogDiligencePrompt, DialogPrompt, DialogRunControlSpec, DialogRuntimeGuidePrompt, DialogRuntimePrompt, DialogRuntimeReplyPrompt, DialogRuntimeSideDialogPrompt, DialogUserPrompt } from '@longrun-ai/kernel/types/drive-intent';
3
- import type { CallSiteCourseNo, CallSiteGenseqNo } from '@longrun-ai/kernel/types/storage';
3
+ import type { CallSiteCourseNo, CallSiteGenseqNo, DialogBusinessContinuation } from '@longrun-ai/kernel/types/storage';
4
4
  import type { Dialog, DialogID } from '../../dialog';
5
5
  export type KernelDriverRunControl = DialogRunControlSpec;
6
6
  export type KernelDriverDriveSource = 'unspecified' | 'ws_user_message' | 'ws_user_answer' | 'ws_diligence_push' | 'ws_resume_dialog' | 'ws_resume_all' | 'kernel_driver_backend_loop' | 'kernel_driver_follow_up' | 'kernel_driver_sideDialog_init' | 'kernel_driver_sideDialog_resume' | 'kernel_driver_fbr_sideDialog_round' | 'kernel_driver_type_a_askerDialog_call' | 'kernel_driver_supply_response_caller_revive' | 'kernel_driver_idle_reminder_wake';
@@ -8,7 +8,14 @@ export type KernelDriverDriveOptions = Readonly<{
8
8
  suppressDiligencePush?: boolean;
9
9
  allowResumeFromInterrupted?: boolean;
10
10
  resumeInProgressGeneration?: boolean;
11
- criticalUserInterjectionRuntimeGuide?: string;
11
+ /**
12
+ * Business continuation identity for no-prompt driver iterations.
13
+ *
14
+ * This is deliberately part of the drive contract instead of being rediscovered from old
15
+ * transcript/assignment records. A continuation must tell the next iteration what business
16
+ * obligation it is continuing, or the driver treats it as no business continuation.
17
+ */
18
+ businessContinuation?: DialogBusinessContinuation;
12
19
  noPromptSideDialogResumeEntitlement?: Readonly<{
13
20
  callerDialogId: string;
14
21
  reason: 'reply_tellask_back_delivered';
@@ -112,6 +119,7 @@ export type KernelDriverCoreResult = {
112
119
  lastAssistantThinkingGenseq: number | null;
113
120
  lastFunctionCallGenseq: number | null;
114
121
  lastAssistantReplyTarget?: KernelDriverCalleeReplyTarget;
122
+ lastBusinessContinuation: DialogBusinessContinuation;
115
123
  fbrConclusion?: {
116
124
  responseText: string;
117
125
  responseGenseq: number;
@@ -87,7 +87,7 @@ export declare class DiskFileDialogStore extends DialogStore {
87
87
  * CRITICAL: This must be called BEFORE any substream events (thinking_start, markdown_start, etc.)
88
88
  * to ensure proper event ordering on the frontend.
89
89
  */
90
- notifyGeneratingStart(dialog: Dialog, msgId?: string): Promise<void>;
90
+ notifyGeneratingStart(dialog: Dialog, msgId?: string): Promise<readonly DialogNextStepTrigger[]>;
91
91
  private getResultArrivalBatchIdsFromAcceptedTriggers;
92
92
  /**
93
93
  * Notify end of LLM generation for frontend bubble management
@@ -259,6 +259,28 @@ type DialogLatestMutation = {
259
259
  kind: 'replace';
260
260
  next: DialogLatestFile;
261
261
  };
262
+ export type BackendDriveStallWrite = Readonly<{
263
+ dialogId: string;
264
+ rootId: string;
265
+ selfId: string;
266
+ status: DialogStatusKind;
267
+ reason: 'backend_drive_error';
268
+ durableWorkFingerprint: string;
269
+ latestSummary: Record<string, unknown> | null;
270
+ error: {
271
+ name?: string;
272
+ message: string;
273
+ stack?: string;
274
+ };
275
+ context: {
276
+ rootHasPendingNextStepTriggers: boolean;
277
+ watchedDialogCount: number;
278
+ };
279
+ }>;
280
+ export type BackendDriveStallRecord = BackendDriveStallWrite & Readonly<{
281
+ recordId: string;
282
+ recordedAt: string;
283
+ }>;
262
284
  type MainDialogWriteBackCancellationToken = Readonly<{
263
285
  scopeKey: string;
264
286
  generation: number;
@@ -285,6 +307,7 @@ export declare class DialogPersistence {
285
307
  private static readonly q4hWriteBack;
286
308
  private static readonly activeCalleesMutexes;
287
309
  private static readonly driveWatchMutexes;
310
+ private static readonly backendDriveStallMutexes;
288
311
  private static readonly courseAppendMutexes;
289
312
  private static readonly mainDialogWriteBackCancelGenerations;
290
313
  private static getLatestWriteBackMutex;
@@ -293,8 +316,10 @@ export declare class DialogPersistence {
293
316
  private static getCourseAppendMutexKey;
294
317
  private static getActiveCalleesMutex;
295
318
  private static getDriveWatchMutex;
319
+ private static getBackendDriveStallMutex;
296
320
  private static getLatestWriteBackKey;
297
321
  private static getDriveWatchKey;
322
+ private static getBackendDriveStallKey;
298
323
  private static getQ4HWriteBackKey;
299
324
  private static getActiveCalleesKey;
300
325
  private static getMainDialogWriteBackCancelScopeKey;
@@ -477,6 +502,9 @@ export declare class DialogPersistence {
477
502
  private static normalizeDriveWatchFile;
478
503
  private static loadDriveWatchFromDisk;
479
504
  private static writeDriveWatchToDisk;
505
+ private static getBackendDriveStallJsonlPath;
506
+ static buildBackendDriveDurableWorkFingerprint(latest: DialogLatestFile | null, watchedDialogIds?: readonly DialogID[]): string;
507
+ static appendBackendDriveStallRecord(dialogId: DialogID, write: BackendDriveStallWrite, status?: DialogStatusKind): Promise<BackendDriveStallRecord>;
480
508
  private static mutateDriveWatch;
481
509
  private static removeWithRetry;
482
510
  static loadDriveWatchedDialogIds(rootDialogId: DialogID, status?: DialogStatusKind): Promise<readonly DialogID[]>;
@@ -98,6 +98,7 @@ function summarizeLatestProjectionState(latest) {
98
98
  displayState: latest.displayState ?? null,
99
99
  executionMarker: latest.executionMarker ?? null,
100
100
  generationRunState: latest.generationRunState ?? null,
101
+ backendDriveStall: latest.backendDriveStall ?? null,
101
102
  nextStepTriggerCount: latest.nextStep.triggers.length,
102
103
  userWait: latest.userWait ?? null,
103
104
  replyDelivery: latest.replyDelivery ?? null,
@@ -135,6 +136,7 @@ function summarizeLatestMutationPatch(patch) {
135
136
  displayState: patch.displayState ?? null,
136
137
  executionMarker: patch.executionMarker ?? null,
137
138
  generationRunState: patch.generationRunState ?? null,
139
+ backendDriveStall: patch.backendDriveStall ?? null,
138
140
  nextStepTriggerCount: patch.nextStep?.triggers.length ?? null,
139
141
  userWait: patch.userWait ?? null,
140
142
  replyDelivery: patch.replyDelivery ?? null,
@@ -1427,6 +1429,32 @@ function parseDialogGenerationRunState(value) {
1427
1429
  }
1428
1430
  return null;
1429
1431
  }
1432
+ function parseDialogBackendDriveStallState(value) {
1433
+ if (!isRecord(value))
1434
+ return null;
1435
+ if (value.kind !== 'backend_drive_error')
1436
+ return null;
1437
+ if (typeof value.recordId !== 'string' || value.recordId.trim() === '')
1438
+ return null;
1439
+ if (typeof value.durableWorkFingerprint !== 'string' ||
1440
+ value.durableWorkFingerprint.trim() === '') {
1441
+ return null;
1442
+ }
1443
+ if (typeof value.failedAt !== 'string' || value.failedAt.trim() === '')
1444
+ return null;
1445
+ if (value.errorName !== undefined && typeof value.errorName !== 'string')
1446
+ return null;
1447
+ if (typeof value.errorMessage !== 'string' || value.errorMessage.trim() === '')
1448
+ return null;
1449
+ return {
1450
+ kind: 'backend_drive_error',
1451
+ recordId: value.recordId,
1452
+ durableWorkFingerprint: value.durableWorkFingerprint,
1453
+ failedAt: value.failedAt,
1454
+ ...(value.errorName === undefined ? {} : { errorName: value.errorName }),
1455
+ errorMessage: value.errorMessage,
1456
+ };
1457
+ }
1430
1458
  function parseStringArrayField(value) {
1431
1459
  if (!Array.isArray(value))
1432
1460
  return null;
@@ -1471,6 +1499,35 @@ function parseDialogFollowupReason(value) {
1471
1499
  return null;
1472
1500
  }
1473
1501
  }
1502
+ function parseDialogBusinessContinuation(value) {
1503
+ if (!isRecord(value))
1504
+ return null;
1505
+ switch (value.kind) {
1506
+ case 'none':
1507
+ return { kind: 'none' };
1508
+ case 'inter_dialog_reply': {
1509
+ const tellaskReplyDirective = parseTellaskReplyDirective(value.tellaskReplyDirective);
1510
+ if (tellaskReplyDirective === null)
1511
+ return null;
1512
+ if (value.calleeDialogReplyTarget === undefined) {
1513
+ return {
1514
+ kind: 'inter_dialog_reply',
1515
+ tellaskReplyDirective,
1516
+ };
1517
+ }
1518
+ const calleeDialogReplyTarget = parseDialogCalleeReplyTarget(value.calleeDialogReplyTarget);
1519
+ if (calleeDialogReplyTarget === null)
1520
+ return null;
1521
+ return {
1522
+ kind: 'inter_dialog_reply',
1523
+ tellaskReplyDirective,
1524
+ calleeDialogReplyTarget,
1525
+ };
1526
+ }
1527
+ default:
1528
+ return null;
1529
+ }
1530
+ }
1474
1531
  function parseDialogNextStepTrigger(value) {
1475
1532
  if (!isRecord(value))
1476
1533
  return null;
@@ -1537,6 +1594,13 @@ function parseDialogNextStepTrigger(value) {
1537
1594
  return null;
1538
1595
  reasons.push(reason);
1539
1596
  }
1597
+ const continuation = (() => {
1598
+ if (value.continuation === undefined)
1599
+ return undefined;
1600
+ return parseDialogBusinessContinuation(value.continuation);
1601
+ })();
1602
+ if (continuation === null)
1603
+ return null;
1540
1604
  return {
1541
1605
  ...base,
1542
1606
  kind: 'followup',
@@ -1545,6 +1609,7 @@ function parseDialogNextStepTrigger(value) {
1545
1609
  genseq: (0, storage_1.toCallSiteGenseqNo)(genseq),
1546
1610
  },
1547
1611
  reasons,
1612
+ ...(continuation === undefined ? {} : { continuation }),
1548
1613
  };
1549
1614
  }
1550
1615
  case 'mainline_diligence': {
@@ -1916,6 +1981,12 @@ function parseDialogLatestFile(value) {
1916
1981
  : parseDialogGenerationRunState(generationRunStateRaw);
1917
1982
  if (generationRunState === null)
1918
1983
  return null;
1984
+ const backendDriveStallRaw = value.backendDriveStall;
1985
+ const backendDriveStall = backendDriveStallRaw === undefined
1986
+ ? undefined
1987
+ : parseDialogBackendDriveStallState(backendDriveStallRaw);
1988
+ if (backendDriveStall === null)
1989
+ return null;
1919
1990
  const nextStepRaw = value.nextStep;
1920
1991
  if (nextStepRaw === undefined)
1921
1992
  return null;
@@ -2072,6 +2143,7 @@ function parseDialogLatestFile(value) {
2072
2143
  displayState,
2073
2144
  executionMarker,
2074
2145
  generationRunState,
2146
+ backendDriveStall,
2075
2147
  nextStep,
2076
2148
  userWait,
2077
2149
  replyDelivery,
@@ -2685,6 +2757,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
2685
2757
  generating: true,
2686
2758
  displayState: { kind: 'proceeding' },
2687
2759
  executionMarker: undefined,
2760
+ backendDriveStall: undefined,
2688
2761
  nextStep,
2689
2762
  generationRunState: {
2690
2763
  kind: 'open',
@@ -2707,6 +2780,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
2707
2780
  catch (err) {
2708
2781
  log_1.log.warn('Failed to persist gen_start event', err);
2709
2782
  }
2783
+ return acceptedTriggers;
2710
2784
  }
2711
2785
  getResultArrivalBatchIdsFromAcceptedTriggers(triggerIds) {
2712
2786
  const batchIds = [];
@@ -4741,6 +4815,14 @@ class DialogPersistence {
4741
4815
  this.driveWatchMutexes.set(key, created);
4742
4816
  return created;
4743
4817
  }
4818
+ static getBackendDriveStallMutex(key) {
4819
+ const existing = this.backendDriveStallMutexes.get(key);
4820
+ if (existing)
4821
+ return existing;
4822
+ const created = new async_fifo_mutex_1.AsyncFifoMutex();
4823
+ this.backendDriveStallMutexes.set(key, created);
4824
+ return created;
4825
+ }
4744
4826
  static getLatestWriteBackKey(dialogId, status) {
4745
4827
  // Include dialogs root dir to avoid cross-test/process.cwd collisions.
4746
4828
  return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}`;
@@ -4748,6 +4830,9 @@ class DialogPersistence {
4748
4830
  static getDriveWatchKey(rootDialogId, status) {
4749
4831
  return `${this.getDialogsRootDir()}|${status}|${rootDialogId.rootId}|drive-watch`;
4750
4832
  }
4833
+ static getBackendDriveStallKey(dialogId, status) {
4834
+ return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|backend-drive-stall`;
4835
+ }
4751
4836
  static getQ4HWriteBackKey(dialogId, status) {
4752
4837
  // Include dialogs root dir to avoid cross-test/process.cwd collisions.
4753
4838
  return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|q4h`;
@@ -6716,6 +6801,73 @@ class DialogPersistence {
6716
6801
  await fs.promises.writeFile(tempFile, jsonContent, 'utf-8');
6717
6802
  await this.renameWithRetry(tempFile, filePath, 5);
6718
6803
  }
6804
+ static getBackendDriveStallJsonlPath(dialogId, status) {
6805
+ return path.join(this.getDialogEventsPath(dialogId, status), 'backend-drive-stalls.jsonl');
6806
+ }
6807
+ static buildBackendDriveDurableWorkFingerprint(latest, watchedDialogIds = []) {
6808
+ if (latest === null) {
6809
+ return JSON.stringify({ latest: null });
6810
+ }
6811
+ const nextStepTriggers = sortNextStepTriggersForConsumption(latest.nextStep.triggers).map((trigger) => ({
6812
+ triggerId: trigger.triggerId,
6813
+ kind: trigger.kind,
6814
+ seq: trigger.seq,
6815
+ createdAt: trigger.createdAt,
6816
+ payload: trigger,
6817
+ }));
6818
+ return JSON.stringify({
6819
+ currentCourse: latest.currentCourse,
6820
+ status: latest.status,
6821
+ generating: latest.generating ?? false,
6822
+ displayState: latest.displayState ?? null,
6823
+ executionMarker: latest.executionMarker ?? null,
6824
+ nextStepTriggers,
6825
+ pendingRuntimePromptMsgId: latest.pendingRuntimePrompt?.msgId ?? null,
6826
+ generationRunState: latest.generationRunState ?? null,
6827
+ replyDelivery: latest.replyDelivery ?? null,
6828
+ sideDialogFinalResponse: latest.sideDialogFinalResponse ?? null,
6829
+ latestAssignmentAnchor: latest.latestAssignmentAnchor ?? null,
6830
+ userWait: latest.userWait ?? null,
6831
+ watchedDialogIds: watchedDialogIds.map((dialogId) => dialogId.valueOf()).sort(),
6832
+ });
6833
+ }
6834
+ static async appendBackendDriveStallRecord(dialogId, write, status = 'running') {
6835
+ const recordedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
6836
+ const record = {
6837
+ ...write,
6838
+ recordId: (0, node_crypto_1.randomUUID)(),
6839
+ recordedAt,
6840
+ };
6841
+ const key = this.getBackendDriveStallKey(dialogId, status);
6842
+ const mutex = this.getBackendDriveStallMutex(key);
6843
+ const release = await mutex.acquire();
6844
+ try {
6845
+ const filePath = this.getBackendDriveStallJsonlPath(dialogId, status);
6846
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
6847
+ await fs.promises.appendFile(filePath, `${JSON.stringify(record)}\n`, 'utf-8');
6848
+ await this.mutateDialogLatest(dialogId, () => ({
6849
+ kind: 'patch',
6850
+ patch: {
6851
+ backendDriveStall: {
6852
+ kind: 'backend_drive_error',
6853
+ recordId: record.recordId,
6854
+ durableWorkFingerprint: write.durableWorkFingerprint,
6855
+ failedAt: recordedAt,
6856
+ ...(write.error.name === undefined ? {} : { errorName: write.error.name }),
6857
+ errorMessage: write.error.message,
6858
+ },
6859
+ },
6860
+ }), status);
6861
+ return record;
6862
+ }
6863
+ finally {
6864
+ release();
6865
+ const current = this.backendDriveStallMutexes.get(key);
6866
+ if (current === mutex && !mutex.isLocked()) {
6867
+ this.backendDriveStallMutexes.delete(key);
6868
+ }
6869
+ }
6870
+ }
6719
6871
  static async mutateDriveWatch(rootDialogId, mutator, status = 'running') {
6720
6872
  const key = this.getDriveWatchKey(rootDialogId, status);
6721
6873
  const mutex = this.getDriveWatchMutex(key);
@@ -8632,5 +8784,6 @@ DialogPersistence.q4hWriteBackMutexes = new Map();
8632
8784
  DialogPersistence.q4hWriteBack = new Map();
8633
8785
  DialogPersistence.activeCalleesMutexes = new Map();
8634
8786
  DialogPersistence.driveWatchMutexes = new Map();
8787
+ DialogPersistence.backendDriveStallMutexes = new Map();
8635
8788
  DialogPersistence.courseAppendMutexes = new Map();
8636
8789
  DialogPersistence.mainDialogWriteBackCancelGenerations = new Map();
@@ -1,5 +1,6 @@
1
1
  import { type LanguageCode } from '@longrun-ai/kernel/types/language';
2
2
  export declare function formatSystemNoticePrefix(language: LanguageCode): string;
3
+ export declare function isAgentFacingCriticalUserInterjectionRemediationGuideContent(content: string): boolean;
3
4
  export declare function formatAutoMaintainedReminderManualMirrorBan(language: LanguageCode): string;
4
5
  export declare function formatCurrentUserLanguagePreference(workingLanguage: LanguageCode, uiLanguage: LanguageCode): string;
5
6
  export declare function formatUserLanguagePreferenceChangedNotice(workingLanguage: LanguageCode, previousUiLanguage: LanguageCode, nextUiLanguage: LanguageCode): string;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatSystemNoticePrefix = formatSystemNoticePrefix;
4
+ exports.isAgentFacingCriticalUserInterjectionRemediationGuideContent = isAgentFacingCriticalUserInterjectionRemediationGuideContent;
4
5
  exports.formatAutoMaintainedReminderManualMirrorBan = formatAutoMaintainedReminderManualMirrorBan;
5
6
  exports.formatCurrentUserLanguagePreference = formatCurrentUserLanguagePreference;
6
7
  exports.formatUserLanguagePreferenceChangedNotice = formatUserLanguagePreferenceChangedNotice;
@@ -23,6 +24,10 @@ const language_1 = require("@longrun-ai/kernel/types/language");
23
24
  function formatSystemNoticePrefix(language) {
24
25
  return language === 'zh' ? '【系统提示】' : '[System notice]';
25
26
  }
27
+ function isAgentFacingCriticalUserInterjectionRemediationGuideContent(content) {
28
+ return (content.startsWith(`${formatSystemNoticePrefix('zh')} 上下文状态:🔴 告急;收到用户插话`) ||
29
+ content.startsWith(`${formatSystemNoticePrefix('en')} Context state: 🔴 critical; user interjection received`));
30
+ }
26
31
  function formatAutoMaintainedReminderManualMirrorBan(language) {
27
32
  return language === 'zh'
28
33
  ? '这条状态由系统维护;禁止把它抄进、改写进、或同步维护到你手工创建的提醒项里。'
@@ -570,7 +575,7 @@ function formatAgentFacingCriticalUserInterjectionRemediationGuide(language, arg
570
575
  return [
571
576
  `${formatSystemNoticePrefix(language)} 上下文状态:🔴 告急;收到用户插话`,
572
577
  '',
573
- '下面紧跟的是一条真实用户消息,不是普通运行时处置提示;必须把它当作有效用户轮次处理,让用户看到你已经接住了这次插话。',
578
+ '本轮刚收到的用户消息是真实用户插话,不是普通运行时处置提示;必须把它当作有效用户轮次处理,让用户看到你已经接住了这次插话。',
574
579
  '',
575
580
  `这次用户轮次已计入告急处置倒计数。系统最多再提醒你 ${args.promptsRemainingAfterThis} 次,之后将自动清理头脑开启新一程对话。`,
576
581
  '',
@@ -584,7 +589,7 @@ function formatAgentFacingCriticalUserInterjectionRemediationGuide(language, arg
584
589
  return [
585
590
  `${formatSystemNoticePrefix(language)} Context state: 🔴 critical; user interjection received`,
586
591
  '',
587
- 'The next message is a real user message, not an ordinary runtime remediation notice. Treat it as an effective user turn and make the system reaction visible to the user.',
592
+ 'The user message just received in this turn is a real user interjection, not an ordinary runtime remediation notice. Treat it as an effective user turn and make the system reaction visible to the user.',
588
593
  '',
589
594
  `This user turn has been counted toward critical remediation. System will remind you ${args.promptsRemainingAfterThis} more time(s), then automatically clear mind.`,
590
595
  '',
@@ -2,12 +2,25 @@ import type { DomindsRuntimeMode, WebSocketMessage } from '@longrun-ai/kernel/ty
2
2
  import { type LanguageCode } from '@longrun-ai/kernel/types/language';
3
3
  import type { Server } from 'http';
4
4
  import { WebSocket, WebSocketServer } from 'ws';
5
+ import { Dialog } from '../dialog';
5
6
  import type { AuthConfig } from './auth';
7
+ export declare function resolveUserMessageLanguageCodeForTest(args: {
8
+ ws: WebSocket;
9
+ raw: unknown;
10
+ fallbackDialog?: Dialog;
11
+ }): LanguageCode;
6
12
  export declare function shouldQueueUserSupplementAtGenerationBoundary(args: {
7
13
  latestGenerating: boolean;
8
14
  inMemoryGenerating: boolean;
9
15
  isLocked: boolean;
10
16
  }): boolean;
17
+ type UserMessageIngressPrompt = {
18
+ content: string;
19
+ msgId: string;
20
+ grammar: 'markdown';
21
+ userLanguageCode?: LanguageCode;
22
+ };
23
+ export declare function wrapCriticalUserInterjectionPromptAtIngress(dialog: Dialog, prompt: UserMessageIngressPrompt): UserMessageIngressPrompt;
11
24
  /**
12
25
  * Handle incoming WebSocket messages
13
26
  */
@@ -20,3 +33,4 @@ export declare function setupWebSocketServer(httpServer: Server, clients: Set<We
20
33
  * Clean up all event channels and subscriptions
21
34
  */
22
35
  export declare function cleanupEventSystems(): void;
36
+ export {};