@sema-agent/client-core 0.102.0 → 0.104.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +23 -17
  3. package/dist/adapt/toolCards.d.ts +2 -1
  4. package/dist/adapt/toolCards.js +3 -1
  5. package/dist/adapter/downstream/eventToSdkMessage.d.ts +5 -1
  6. package/dist/adapter/downstream/eventToSdkMessage.js +23 -3
  7. package/dist/adapter/downstream/terminalToSdkResult.d.ts +0 -1
  8. package/dist/adapter/downstream/terminalToSdkResult.js +3 -2
  9. package/dist/adapter/downstream/wiringManifestView.js +4 -1
  10. package/dist/agentSession/engineAgentRegistryRead.js +3 -0
  11. package/dist/cloudConfigWireCaps.js +6 -3
  12. package/dist/coreValuePorts.d.ts +3 -1
  13. package/dist/coreValuePorts.js +5 -0
  14. package/dist/credentialPlaneCapability.d.ts +22 -0
  15. package/dist/credentialPlaneCapability.js +75 -0
  16. package/dist/deviceExecutorManagementCapability.d.ts +1 -0
  17. package/dist/deviceExecutorManagementCapability.js +3 -1
  18. package/dist/deviceRebind.d.ts +17 -4
  19. package/dist/deviceRebind.js +43 -14
  20. package/dist/engineAgentAbsence.d.ts +1 -0
  21. package/dist/engineErrorCodes.d.ts +1 -0
  22. package/dist/engineErrorCodes.js +3 -1
  23. package/dist/engineNoticeCodes.d.ts +26 -0
  24. package/dist/engineNoticeCodes.js +121 -12
  25. package/dist/engineRowRef.js +1 -0
  26. package/dist/errorSupplementRoles.js +1 -3
  27. package/dist/eventLeg.js +2 -1
  28. package/dist/executionPlacementWords.d.ts +3 -0
  29. package/dist/executionPlacementWords.js +2 -0
  30. package/dist/fleet/fleetProjection.js +15 -9
  31. package/dist/gateOutcome.d.ts +20 -0
  32. package/dist/gateOutcome.js +89 -0
  33. package/dist/generated/engineAskFaceTables.js +2 -2
  34. package/dist/generated/engineFactTables.d.ts +8 -3
  35. package/dist/generated/engineFactTables.js +58 -2
  36. package/dist/generated/engineNoticeTables.js +30 -0
  37. package/dist/generated/settingsSchemaDefaultModel.d.ts +1 -1
  38. package/dist/generated/settingsSchemaDefaultModel.js +2 -2
  39. package/dist/generated/settingsSchemaFns.d.ts +15 -0
  40. package/dist/generated/settingsSchemaFns.js +25 -2
  41. package/dist/generated/settingsSchemaHooksClassifier.js +109 -29
  42. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  43. package/dist/generated/settingsSchemaMcpRequestEntry.js +213 -93
  44. package/dist/generated/settingsSchemaMcpServerName.d.ts +1 -0
  45. package/dist/generated/settingsSchemaMcpServerName.js +8 -0
  46. package/dist/generated/settingsSchemaOwnKeyRead.d.ts +1 -0
  47. package/dist/generated/settingsSchemaOwnKeyRead.js +172 -0
  48. package/dist/generated/settingsSchemaPlainJsonObject.d.ts +1 -0
  49. package/dist/generated/settingsSchemaPlainJsonObject.js +28 -0
  50. package/dist/generated/settingsSchemaSafetyMergeSpec.js +10 -7
  51. package/dist/generated/settingsSchemaTierWords.d.ts +1 -0
  52. package/dist/generated/settingsSchemaTierWords.js +7 -0
  53. package/dist/generated/settingsSchemaTypes.d.ts +3 -1
  54. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  55. package/dist/hitl/automaticAnswerVerdict.js +47 -11
  56. package/dist/hitl/decisionConflict.js +11 -11
  57. package/dist/hitl/decisionRefusal.d.ts +1 -0
  58. package/dist/hitl/decisionRefusal.js +4 -2
  59. package/dist/hitl/editedRuleTextPrecheck.d.ts +1 -0
  60. package/dist/hitl/editedRuleTextPrecheck.js +26 -1
  61. package/dist/hitl/persistedRulesWire.js +1 -0
  62. package/dist/hitl/planReviewWire.d.ts +2 -1
  63. package/dist/hitl/planReviewWire.js +8 -12
  64. package/dist/hitl/sessionPolicyDeliverable.d.ts +0 -1
  65. package/dist/hitl/sessionPolicyDeliverable.js +31 -10
  66. package/dist/hitl/sessionPolicyWire.js +1 -1
  67. package/dist/hitl/toolApprovalWire.d.ts +20 -7
  68. package/dist/hitl/toolApprovalWire.js +171 -19
  69. package/dist/host.d.ts +0 -3
  70. package/dist/hostTurnSettings.js +3 -1
  71. package/dist/humanTurn.js +2 -2
  72. package/dist/i18n/messages.en.d.ts +5 -0
  73. package/dist/i18n/messages.en.js +6 -1
  74. package/dist/i18n/messages.zh.js +6 -1
  75. package/dist/index.d.ts +8 -4
  76. package/dist/index.js +3 -1
  77. package/dist/inheritEnvWire.d.ts +1 -0
  78. package/dist/inheritEnvWire.js +8 -5
  79. package/dist/jsonFaithful.js +5 -3
  80. package/dist/mcpEngineLeg.d.ts +3 -0
  81. package/dist/mcpEngineLeg.js +4 -1
  82. package/dist/mcpLiveness.d.ts +18 -0
  83. package/dist/mcpLiveness.js +48 -1
  84. package/dist/mcpReconnect.d.ts +3 -0
  85. package/dist/mcpReconnect.js +6 -0
  86. package/dist/mcpStatusSupersetKeys.js +24 -1
  87. package/dist/mcpWireCaps.d.ts +2 -3
  88. package/dist/mcpWireCaps.js +10 -9
  89. package/dist/memoryEntriesWire.d.ts +35 -1
  90. package/dist/memoryEntriesWire.js +110 -14
  91. package/dist/modsFace.js +7 -3
  92. package/dist/postureKnob.js +1 -1
  93. package/dist/prepareRefusal.d.ts +0 -1
  94. package/dist/prepareRefusal.js +0 -3
  95. package/dist/printModelStatus.d.ts +1 -0
  96. package/dist/printModelStatus.js +6 -2
  97. package/dist/promptProfileWireCaps.d.ts +1 -3
  98. package/dist/promptProfileWireCaps.js +2 -2
  99. package/dist/request/taskRequest.d.ts +1 -1
  100. package/dist/request/taskRequest.js +3 -3
  101. package/dist/retryStatus.d.ts +20 -1
  102. package/dist/retryStatus.js +31 -7
  103. package/dist/ruleCompileVerdict.d.ts +1 -0
  104. package/dist/ruleCompileVerdict.js +3 -1
  105. package/dist/seatContract.d.ts +7 -7
  106. package/dist/seatContract.js +49 -10
  107. package/dist/sessionMemoryErase.js +24 -2
  108. package/dist/sessionRowTag.d.ts +24 -0
  109. package/dist/sessionRowTag.js +77 -0
  110. package/dist/systemReminderTag.js +14 -4
  111. package/dist/turnFailurePresentation.js +8 -1
  112. package/dist/wireFailureShape.d.ts +12 -1
  113. package/dist/wireFailureShape.js +58 -24
  114. package/dist/wireRefusalCopy.d.ts +0 -3
  115. package/dist/wireRefusalCopy.js +0 -17
  116. package/docs/INTEGRATION-CLIENTS.md +400 -14
  117. package/package.json +5 -5
@@ -21,7 +21,7 @@ export const REQUEST_FIELD_MATRIX = [
21
21
  { field: 'selfOrchestration', lanes: ['interactive', 'print'], live: true, why: 'SEMA_SELF_ORCHESTRATION opt-in → core 挂 run_workflow', whyUtilityLane: '无座:它挂的是编排工具,与零工具面直接矛盾' },
22
22
  { field: 'deferTools', lanes: ['interactive', 'print'], live: true, why: '[1052]① Workflow 延迟披露;selfOrchestration 前置门', whyUtilityLane: '无座:工具延迟披露是工具面的位,零工具面上没有对象' },
23
23
  { field: 'retainBackgroundProcesses', lanes: ['interactive', 'print'], live: true, why: '缺省 ON(2026-07-26 起翻转);SEMA_RETAIN_BACKGROUND=0 逃生', whyUtilityLane: '无座:后台进程是工具面的产物,零工具面产不出' },
24
- { field: 'promptProfile', lanes: ['interactive', 'print'], live: true, why: 'core 1.328 呈现轴 A/B 口,缺省不 stamp = 引擎缺省 simple', whyUtilityLane: '无座:呈现轴是用户会话的偏好;side-channel 的系统提示由壳整段给出(见 systemPrompt 那一行),不再借呈现轴调形' },
24
+ { field: 'promptLean', lanes: ['interactive', 'print'], live: true, why: '提示词形轴(0.103.0 起布尔,取代退役的两词形轴键;sdk 28 / 服务端 7.137.0):缺省不 stamp = 目录缺省;映射单铸点 promptLeanFromEnv', whyUtilityLane: '无座:提示词形是用户会话的偏好;side-channel 的系统提示由壳整段给出(见 systemPrompt 那一行),不再借形轴调形' },
25
25
  { field: 'enableFork', lanes: ['interactive', 'print'], live: true, why: '显式布尔才 stamp([503]② fail-OPEN 教训:off 必须显式)', whyUtilityLane: '无座:分叉是会话面的动作,而 side-channel 提交是一次性的独立会话' },
26
26
  { field: 'attachments', lanes: ['interactive', 'print'], live: true, why: 'design/133 + G1 缺省开的 turn 边界 attachment', whyUtilityLane: '无座:turn 边界的附件配置属用户会话;side-channel 提交不带附件' },
27
27
  { field: 'skills', lanes: ['interactive', 'print'], live: true, why: '本地技能目录投影(布局取 CC 同款);两条车道都由 launchRepl 载入面注入', whyUtilityLane: '无座:技能挂的是工具,与零工具面矛盾' },
@@ -152,7 +152,7 @@ const INPUT_KEY_ROUTES = {
152
152
  selfOrchestration: row('selfOrchestration'),
153
153
  deferTools: row('deferTools'),
154
154
  retainBackgroundProcesses: row('retainBackgroundProcesses'),
155
- promptProfile: row('promptProfile'),
155
+ promptLean: row('promptLean'),
156
156
  enableFork: row('enableFork'),
157
157
  attachments: row('attachments'),
158
158
  attachmentIds: row('attachmentIds'),
@@ -1103,7 +1103,7 @@ export function assembleTaskRequest(input, laneOrIdentity) {
1103
1103
  ...(on('selfOrchestration') ? { selfOrchestration: i.selfOrchestration } : {}),
1104
1104
  ...(on('deferTools') ? { deferTools: i.deferTools } : {}),
1105
1105
  ...(on('retainBackgroundProcesses') ? { retainBackgroundProcesses: i.retainBackgroundProcesses } : {}),
1106
- ...(on('promptProfile') ? { promptProfile: i.promptProfile } : {}),
1106
+ ...(on('promptLean') ? { promptLean: i.promptLean } : {}),
1107
1107
  ...(on('enableFork') ? { enableFork: i.enableFork } : {}),
1108
1108
  ...(on('attachments') ? { attachments: attachmentsForWire } : {}),
1109
1109
  ...(on('repo') ? { repo: i.repo } : {}),
@@ -11,6 +11,9 @@ export type RetryStatus = {
11
11
  providerConditionSource?: string;
12
12
  elapsedMs?: number;
13
13
  timeoutMs?: number;
14
+ sleptMs?: number;
15
+ waitSegment?: BrainStatusWaitSegment | (string & {});
16
+ seat?: BrainStatusSeat | (string & {});
14
17
  detail?: string;
15
18
  } | {
16
19
  kind: 'error';
@@ -26,6 +29,9 @@ export type RetryStatus = {
26
29
  error: RetryStatusErrorPayload;
27
30
  elapsedMs?: number;
28
31
  timeoutMs?: number;
32
+ sleptMs?: number;
33
+ waitSegment?: BrainStatusWaitSegment | (string & {});
34
+ seat?: BrainStatusSeat | (string & {});
29
35
  } | {
30
36
  kind: 'waiting';
31
37
  deadline: number;
@@ -38,6 +44,9 @@ export type RetryStatus = {
38
44
  providerConditionSource?: string;
39
45
  elapsedMs?: number;
40
46
  timeoutMs?: number;
47
+ sleptMs?: number;
48
+ waitSegment?: BrainStatusWaitSegment | (string & {});
49
+ seat?: BrainStatusSeat | (string & {});
41
50
  terminal?: never;
42
51
  error: RetryStatusErrorPayload;
43
52
  };
@@ -69,8 +78,15 @@ export interface BrainStatusPayload {
69
78
  timeoutMs?: number;
70
79
  providerCondition?: string;
71
80
  providerConditionSource?: string;
81
+ sleptMs?: number;
82
+ waitSegment?: BrainStatusWaitSegment | (string & {});
83
+ seat?: BrainStatusSeat | (string & {});
72
84
  }
73
- export declare const BRAIN_STATUS_PAYLOAD_KEYS: readonly ["phase", "detail", "retryInSec", "retryInMs", "attempt", "maxRetries", "errClass", "retryAtMs", "errorStatus", "elapsedMs", "timeoutMs", "providerCondition", "providerConditionSource"];
85
+ export declare const BRAIN_STATUS_WAIT_SEGMENTS: readonly ["response_headers", "first_token"];
86
+ export type BrainStatusWaitSegment = (typeof BRAIN_STATUS_WAIT_SEGMENTS)[number];
87
+ export declare const BRAIN_STATUS_SEATS: readonly ["classifier"];
88
+ type BrainStatusSeat = (typeof BRAIN_STATUS_SEATS)[number];
89
+ export declare const BRAIN_STATUS_PAYLOAD_KEYS: readonly ["phase", "detail", "retryInSec", "retryInMs", "attempt", "maxRetries", "errClass", "retryAtMs", "errorStatus", "elapsedMs", "timeoutMs", "providerCondition", "providerConditionSource", "sleptMs", "waitSegment", "seat"];
74
90
  export declare function mapBrainStatusToRetry(p: BrainStatusPayload, nowMs: number): RetryStatus | null;
75
91
  export interface RetryStatusArmLike {
76
92
  readonly phase?: string;
@@ -86,6 +102,9 @@ export interface RetryStatusArmLike {
86
102
  readonly timeoutMs?: number;
87
103
  readonly providerCondition?: string;
88
104
  readonly providerConditionSource?: string;
105
+ readonly sleptMs?: number;
106
+ readonly waitSegment?: string;
107
+ readonly seat?: string;
89
108
  readonly parentToolCallId?: string;
90
109
  }
91
110
  export declare function retryStatusFromArm(arm: RetryStatusArmLike | null | undefined, nowMs: number): RetryStatus | null | undefined;
@@ -11,6 +11,12 @@ export const BRAIN_STATUS_PHASES = [
11
11
  'gave_up',
12
12
  'waiting_first_token',
13
13
  ];
14
+ export const BRAIN_STATUS_WAIT_SEGMENTS = Object.freeze(['response_headers', 'first_token']);
15
+ void true;
16
+ void true;
17
+ export const BRAIN_STATUS_SEATS = Object.freeze(['classifier']);
18
+ void true;
19
+ void true;
14
20
  export const BRAIN_STATUS_PAYLOAD_KEYS = [
15
21
  'phase',
16
22
  'detail',
@@ -25,6 +31,9 @@ export const BRAIN_STATUS_PAYLOAD_KEYS = [
25
31
  'timeoutMs',
26
32
  'providerCondition',
27
33
  'providerConditionSource',
34
+ 'sleptMs',
35
+ 'waitSegment',
36
+ 'seat',
28
37
  ];
29
38
  const _brainStatusKeyPin = [true, true];
30
39
  void _brainStatusKeyPin;
@@ -41,9 +50,11 @@ export function mapBrainStatusToRetry(p, nowMs) {
41
50
  const cause = typeof p.errClass === 'string' && p.errClass.length > 0 ? { errClass: p.errClass } : {};
42
51
  const producerTiming = typeof p.retryAtMs === 'number' ? { retryAtMs: p.retryAtMs } : {};
43
52
  const failureStatus = typeof p.errorStatus === 'number' ? { errorStatus: p.errorStatus } : {};
53
+ const providerCondition = p.providerCondition;
54
+ const providerConditionSource = p.providerConditionSource;
44
55
  const providerJudged = {
45
- ...(typeof p.providerCondition === 'string' && p.providerCondition.length > 0 ? { providerCondition: p.providerCondition } : {}),
46
- ...(typeof p.providerConditionSource === 'string' && p.providerConditionSource.length > 0 ? { providerConditionSource: p.providerConditionSource } : {}),
56
+ ...(typeof providerCondition === 'string' && providerCondition.length > 0 ? { providerCondition } : {}),
57
+ ...(typeof providerConditionSource === 'string' && providerConditionSource.length > 0 ? { providerConditionSource } : {}),
47
58
  };
48
59
  const saneMs = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
49
60
  const elapsedMs = saneMs(p.elapsedMs);
@@ -52,10 +63,18 @@ export function mapBrainStatusToRetry(p, nowMs) {
52
63
  ...(elapsedMs !== undefined ? { elapsedMs } : {}),
53
64
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
54
65
  };
66
+ const sleptMs = saneMs(p.sleptMs);
67
+ const waitSegment = p.waitSegment;
68
+ const seat = p.seat;
69
+ const waitFacts = {
70
+ ...(sleptMs !== undefined ? { sleptMs } : {}),
71
+ ...(typeof waitSegment === 'string' && waitSegment.length > 0 ? { waitSegment } : {}),
72
+ ...(typeof seat === 'string' && seat.length > 0 ? { seat } : {}),
73
+ };
55
74
  const stalledShown = typeof p.detail === 'string' && p.detail.length > 0 ? displayUntrusted(p.detail) : '';
56
75
  const stalledDetail = stalledShown.length > 0 ? { detail: stalledShown } : {};
57
76
  const shown = (absent) => (typeof p.detail === 'string' ? displayUntrusted(p.detail) : absent);
58
- const extra = { ...counts, ...cause, ...producerTiming, ...failureStatus, ...providerJudged, ...waitProgress };
77
+ const extra = { ...counts, ...cause, ...producerTiming, ...failureStatus, ...providerJudged, ...waitProgress, ...waitFacts };
59
78
  switch (p.phase) {
60
79
  case 'recovered':
61
80
  return null;
@@ -99,8 +118,8 @@ export function retryStatusFromArm(arm, nowMs) {
99
118
  const v = str(key);
100
119
  return v !== undefined && v.length > 0 ? v : undefined;
101
120
  };
102
- const [detail, retryInSec, retryInMs, attempt, maxRetries, errClass, retryAtMs, errorStatus, elapsedMs, timeoutMs, providerCondition, providerConditionSource] = [
103
- str('detail'), num('retryInSec'), num('retryInMs'), num('attempt'), num('maxRetries'), str('errClass'), num('retryAtMs'), num('errorStatus'), num('elapsedMs'), num('timeoutMs'), word('providerCondition'), word('providerConditionSource'),
121
+ const [detail, retryInSec, retryInMs, attempt, maxRetries, errClass, retryAtMs, errorStatus, elapsedMs, timeoutMs, providerCondition, providerConditionSource, sleptMs, waitSegment, seat] = [
122
+ str('detail'), num('retryInSec'), num('retryInMs'), num('attempt'), num('maxRetries'), str('errClass'), num('retryAtMs'), num('errorStatus'), num('elapsedMs'), num('timeoutMs'), word('providerCondition'), word('providerConditionSource'), num('sleptMs'), word('waitSegment'), word('seat'),
104
123
  ];
105
124
  const phase = str('phase');
106
125
  if (phase === undefined)
@@ -119,6 +138,9 @@ export function retryStatusFromArm(arm, nowMs) {
119
138
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
120
139
  ...(providerCondition !== undefined ? { providerCondition } : {}),
121
140
  ...(providerConditionSource !== undefined ? { providerConditionSource } : {}),
141
+ ...(sleptMs !== undefined ? { sleptMs } : {}),
142
+ ...(waitSegment !== undefined ? { waitSegment } : {}),
143
+ ...(seat !== undefined ? { seat } : {}),
122
144
  }, nowMs);
123
145
  }
124
146
  const WAIT_SWITCH_FRACTION = 0.5;
@@ -203,7 +225,8 @@ function sealParts(p) {
203
225
  }
204
226
  export function retryStatusParts(status, ctx) {
205
227
  const modelName = washedText(noticeText(ctx, 'modelName'))?.trim();
206
- const model = modelName === undefined || modelName === '' ? undefined : modelName;
228
+ const otherSeat = status !== null && noticeText(status, 'seat') !== undefined;
229
+ const model = otherSeat || modelName === undefined || modelName === '' ? undefined : modelName;
207
230
  if (status === null)
208
231
  return sealParts({ phase: 'cleared' });
209
232
  const armWord = noticeText(status, 'kind');
@@ -228,7 +251,7 @@ export function retryStatusParts(status, ctx) {
228
251
  const condition = reading === undefined ? undefined : `${reading.clause.charAt(0).toUpperCase()}${reading.clause.slice(1)}`;
229
252
  const serviceSide = http !== undefined && isServiceSideStatus(http);
230
253
  const knownArm = armWord === 'stalled' || armWord === 'error';
231
- const recourse = !knownArm
254
+ const recourseOfArm = !knownArm
232
255
  ? undefined
233
256
  : reading !== undefined
234
257
  ? conditionRecourseOf(conditionWord)
@@ -237,6 +260,7 @@ export function retryStatusParts(status, ctx) {
237
260
  : !terminal
238
261
  ? 'wait'
239
262
  : undefined;
263
+ const recourse = otherSeat && recourseOfArm === 'switch_model' ? (terminal ? undefined : 'wait') : recourseOfArm;
240
264
  const deadlineRaw = knownArm && !terminal && noticeNumber(status, 'retryAtMs') !== undefined ? noticeNumber(status, 'deadline') : undefined;
241
265
  const retryAtMs = deadlineRaw !== undefined && deadlineRaw > 0 ? deadlineRaw : undefined;
242
266
  return sealParts({
@@ -17,6 +17,7 @@ export declare function ruleCompileVerdictUnder(rule: string, behavior: 'deny' |
17
17
  export type DenyEntryInertReason = 'grammar' | 'content_unsupported' | 'wildcard_all' | 'namespace_wildcard';
18
18
  export type DenyEntryReading = {
19
19
  readonly kind: 'row';
20
+ readonly covering?: true;
20
21
  } | {
21
22
  readonly kind: 'inert';
22
23
  readonly reason: DenyEntryInertReason;
@@ -58,7 +58,9 @@ export function denyEntryReading(rule) {
58
58
  const kind = readOwnDataValue(reading, 'kind');
59
59
  if (kind === 'row') {
60
60
  const compiled = readOwnDataValue(reading, 'compiled');
61
- return typeof compiled === 'object' && compiled !== null ? { kind: 'row' } : { kind: 'unknown' };
61
+ if (typeof compiled !== 'object' || compiled === null)
62
+ return { kind: 'unknown' };
63
+ return readOwnDataValue(compiled, 'kind') === 'covering' ? { kind: 'row', covering: true } : { kind: 'row' };
62
64
  }
63
65
  if (kind !== 'inert')
64
66
  return { kind: 'unknown' };
@@ -14,6 +14,7 @@ import type { DeviceEnrollTokenOutcome, DeviceListOutcome, DeviceRebindOutcome,
14
14
  import type { McpPanelView } from "./mcpPanel.js";
15
15
  import type { McpToolToggleKey } from "./mcpToolToggles.js";
16
16
  import { type ThinkingLatchEvent, type ThinkingLatchRowWord } from "./thinkingLatchWords.js";
17
+ import type { SessionRowPatchFields } from "./sessionRowTag.js";
17
18
  export interface SeatValidationFailure {
18
19
  reason: string;
19
20
  field?: string;
@@ -48,6 +49,7 @@ export interface LocalSessionRecord {
48
49
  }>;
49
50
  parked?: boolean;
50
51
  pinned?: boolean;
52
+ rowTag?: string;
51
53
  thinkingLatch?: ThinkingLatchRowWord;
52
54
  }
53
55
  export declare const TOOL_PERMISSION_REQUEST_ID_DOMAINS: readonly ["live:", "durable:", "question:", "elicit:", "plan:"];
@@ -301,12 +303,10 @@ export declare const ATTACHMENT_UPLOAD_REQUEST_KEYS: readonly ["name", "mimeType
301
303
  export interface SessionScopedRequest {
302
304
  sessionId: string;
303
305
  }
304
- export interface SessionRowPatchRequest {
306
+ export interface SessionRowPatchRequest extends SessionRowPatchFields {
305
307
  sessionId: string;
306
- title?: string | null;
307
- pinned?: boolean;
308
308
  }
309
- export declare const SESSION_ROW_PATCH_REQUEST_KEYS: readonly ["sessionId", "title", "pinned"];
309
+ export declare const SESSION_ROW_PATCH_REQUEST_KEYS: readonly ["sessionId", "title", "pinned", "expectedRowTag"];
310
310
  export interface SetThinkingLatchRequest {
311
311
  readonly sessionId: string;
312
312
  readonly event: ThinkingLatchEvent;
@@ -688,7 +688,7 @@ export declare const SEAT_STORE_PROPS: readonly ["sessionsStateStore", "focusedS
688
688
  export declare const SEAT_EVENT_METHOD_NAMES: readonly ["onEvent", "onToolPermissionRequest"];
689
689
  export declare const LOCAL_SESSION_EVENT_KEYS: readonly ["type", "sessionId", "message", "error", "errorCategory", "code", "session", "userMessageUuid", "requestId", "outcome", "parked", "chrome", "_sema_turnFailure", "subagentContent"];
690
690
  export declare const TOOL_PERMISSION_REQUEST_KEYS: readonly ["requestId", "sessionId", "toolName", "toolCallId", "input", "message", "toctouBinding", "createdAtMs", "governanceForced", "delegation", "mandated", "mandate", "origin", "ruleOffersAbsence", "askClass", "classes", "sessionRow"];
691
- export declare const LOCAL_SESSION_RECORD_KEYS: readonly ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "thinkingLatch"];
691
+ export declare const LOCAL_SESSION_RECORD_KEYS: readonly ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "rowTag", "thinkingLatch"];
692
692
  export declare const SEND_MESSAGE_REQUEST_KEYS: readonly ["sessionId", "text", "model", "userMessageUuid", "permissionMode", "attachmentIds", "reasoningEffort", "retainBackgroundProcesses", "forwardSubagentEvents", "retainSubagentSessions", "settings", "scenario", "rewind"];
693
693
  export declare const SEAT_SEND_MESSAGE_KEY_ORIGINS: {
694
694
  readonly sessionId: {
@@ -738,7 +738,7 @@ export declare const SEAT_VALIDATOR_KEY_COVERAGE: {
738
738
  readonly LocalSessionRecord: {
739
739
  readonly guard: "isLocalSessionRecord";
740
740
  readonly check: (v: unknown) => SeatValidation;
741
- readonly validated: readonly ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "thinkingLatch"];
741
+ readonly validated: readonly ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "rowTag", "thinkingLatch"];
742
742
  readonly unchecked: readonly [];
743
743
  };
744
744
  readonly ToolPermissionRequest: {
@@ -815,7 +815,7 @@ export declare const SEAT_VALIDATOR_KEY_COVERAGE: {
815
815
  readonly SessionRowPatchRequest: {
816
816
  readonly guard: "isSessionRowPatchRequest";
817
817
  readonly check: (v: unknown) => SeatValidation;
818
- readonly validated: readonly ["sessionId", "title", "pinned"];
818
+ readonly validated: readonly ["sessionId", "title", "pinned", "expectedRowTag"];
819
819
  readonly unchecked: readonly [];
820
820
  };
821
821
  readonly SetThinkingLatchRequest: {
@@ -6,6 +6,7 @@ import { PROBE_VERDICTS } from "./modelCapabilityProbe.js";
6
6
  import { guardedRead, ownDataField, ownDataRefusalKeyOf, ownSendableField, readOwnDataValue } from "./ownKey.js";
7
7
  import { MCP_LIVENESS_STATES } from "./mcpLiveness.js";
8
8
  import { THINKING_LATCH_EVENTS, THINKING_LATCH_ROW_WORDS } from "./thinkingLatchWords.js";
9
+ import { EXECUTION_PLACEMENT_ROLES } from "./executionPlacementWords.js";
9
10
  const bad = (reason, field) => field === undefined ? { reason } : { reason, field };
10
11
  const nested = (prefix, verdict) => {
11
12
  const path = verdict.field === undefined
@@ -122,7 +123,22 @@ function shapeVerdict(spec, v, path, disc) {
122
123
  return true;
123
124
  }
124
125
  const shapeRule = (label, spec) => ({ label, test: (v, path) => shapeVerdict(spec, v, path) });
125
- const armsRule = (label, disc, arms) => ({
126
+ function keyGroupsVerdict(groups, v, path) {
127
+ let seen;
128
+ for (const g of groups) {
129
+ const present = g.filter((k) => !isAbsent(own(v, k)));
130
+ if (present.length === 0)
131
+ continue;
132
+ const missing = g.find((k) => !present.includes(k));
133
+ if (missing !== undefined)
134
+ return bad("missing key", pathAt(path, missing));
135
+ if (seen !== undefined)
136
+ return bad(`bad value (${seen.join("+")} | ${g.join("+")})`, pathAt(path, g[0]));
137
+ seen = g;
138
+ }
139
+ return true;
140
+ }
141
+ const armsRule = (label, disc, arms, groups = {}) => ({
126
142
  label,
127
143
  test: (v, path) => {
128
144
  if (!isPlainRecord(v))
@@ -130,7 +146,10 @@ const armsRule = (label, disc, arms) => ({
130
146
  const d = own(v, disc);
131
147
  if (typeof d !== "string" || !Object.hasOwn(arms, d))
132
148
  return bad("unknown arm", pathAt(path, disc));
133
- return shapeVerdict(arms[d], v, path, disc);
149
+ const verdict = shapeVerdict(arms[d], v, path, disc);
150
+ if (verdict !== true || !Object.hasOwn(groups, d))
151
+ return verdict;
152
+ return keyGroupsVerdict(groups[d], v, path);
134
153
  },
135
154
  });
136
155
  const isArraySlotKey = (k, n) => k === "length" || (/^(0|[1-9][0-9]*)$/.test(k) && Number(k) < n);
@@ -162,6 +181,7 @@ const R_BOOL = ruleOf("boolean", (v) => typeof v === "boolean");
162
181
  const R_THINKING_LATCH_ROW = oneOfRule("latch word", THINKING_LATCH_ROW_WORDS);
163
182
  const R_TRUE = ruleOf("true", (v) => v === true);
164
183
  const R_NUM = ruleOf("finite number", (v) => typeof v === "number" && Number.isFinite(v));
184
+ const R_NUM0 = ruleOf("finite non-negative number", (v) => typeof v === "number" && Number.isFinite(v) && v >= 0);
165
185
  const R_INT0 = ruleOf("non-negative integer", (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0);
166
186
  const R_INT1 = ruleOf("positive integer", (v) => typeof v === "number" && Number.isSafeInteger(v) && v > 0);
167
187
  const keyWords = (k) => k
@@ -558,7 +578,7 @@ export const SEAT_TURN_SETTINGS_ORIGINS = Object.freeze({
558
578
  });
559
579
  export const SEAT_REWIND_INTENT_KEYS = ["mode", "targetMessageUuid"];
560
580
  export const ATTACHMENT_UPLOAD_REQUEST_KEYS = ["name", "mimeType", "bytes"];
561
- export const SESSION_ROW_PATCH_REQUEST_KEYS = Object.freeze(["sessionId", "title", "pinned"]);
581
+ export const SESSION_ROW_PATCH_REQUEST_KEYS = Object.freeze(["sessionId", "title", "pinned", "expectedRowTag"]);
562
582
  const SET_THINKING_LATCH_REQUEST_KEYS = Object.freeze(["sessionId", "event"]);
563
583
  export const SESSION_SEARCH_RESULT_KEYS = ["sessionId", "title", "matches"];
564
584
  export const GET_TRANSCRIPT_RESULT_KEYS = ["sessionId", "messages"];
@@ -581,7 +601,6 @@ const DEVICE_ROSTER_ROW = shapeRule("device row", {
581
601
  const DEVICE_BINDING = shapeRule("device binding", {
582
602
  deviceId: reqF(R_NONEMPTY),
583
603
  rev: reqF(R_INT0),
584
- boundAt: optF(R_STR),
585
604
  });
586
605
  const checkDeviceListOutcome = seatShapeCheck(armsRule("listDevices outcome", "kind", {
587
606
  ok: { devices: reqF(arrayRule(DEVICE_ROSTER_ROW)), dropped: reqF(R_INT0) },
@@ -593,23 +612,24 @@ const checkDeviceListOutcome = seatShapeCheck(armsRule("listDevices outcome", "k
593
612
  }));
594
613
  const checkDeviceSessionBindingOutcome = seatShapeCheck(armsRule("readSessionBinding outcome", "kind", {
595
614
  ok: { rootSessionId: reqF(R_NONEMPTY), boundDevice: reqF(DEVICE_BINDING) },
615
+ unbound: { role: reqF(oneOfRule("placement role", EXECUTION_PLACEMENT_ROLES)) },
596
616
  "not-found": {},
617
+ unsupported: { reason: reqF(oneOfRule("unsupported reason", ["route", "capability"])) },
597
618
  unreadable: DEVICE_UNREADABLE_SPEC,
598
- "lane-absent": DEVICE_LANE_ABSENT_SPEC,
599
619
  refused: DEVICE_REFUSED_SPEC,
600
620
  transport: DEVICE_TRANSPORT_SPEC,
601
621
  unarmed: {},
602
622
  }));
603
623
  const checkDeviceRebindOutcome = seatShapeCheck(armsRule("rebindSession outcome", "kind", {
604
624
  ok: { deviceId: reqF(R_NONEMPTY), rev: reqF(R_INT0), workspaceCarryover: optF(oneOfRule("carryover", ["none"])) },
605
- "active-run": { activeTaskId: optF(R_STR) },
625
+ "active-run": { activeTaskId: optF(R_STR), children: optF(arrayRule(R_STR)), retirePath: optF(R_NONEMPTY) },
606
626
  "rev-conflict": { currentRev: optF(R_INT0) },
607
627
  "not-found": {},
608
628
  unconfirmed: DEVICE_UNCONFIRMED_SPEC,
609
629
  "lane-absent": DEVICE_LANE_ABSENT_SPEC,
610
630
  refused: DEVICE_REFUSED_SPEC,
611
631
  unarmed: {},
612
- }));
632
+ }, { "active-run": [["activeTaskId"], ["children", "retirePath"]] }));
613
633
  const ENROLL_TOKEN_ALLOW = new Set(["token"]);
614
634
  const checkDeviceEnrollTokenOutcome = seatShapeCheck(armsRule("issueEnrollToken outcome", "kind", {
615
635
  ok: { token: reqF(R_NONEMPTY), expiresAt: optF(R_STR), ttlSec: optF(R_INT1) },
@@ -882,6 +902,11 @@ function mcpPanelLivenessArms() {
882
902
  return arms;
883
903
  }
884
904
  const MCP_PANEL_LIVENESS_VIEW = armsRule("mcp liveness", "state", mcpPanelLivenessArms());
905
+ const MCP_PANEL_AUTHORIZATION = shapeRule("mcp authorization", {
906
+ reason: reqF(R_NONEMPTY),
907
+ authorizeUrl: optF(R_NONEMPTY),
908
+ resourceMetadataUrl: optF(R_NONEMPTY),
909
+ });
885
910
  const MCP_PANEL_LEG_ENTRY = shapeRule("leg entry", {
886
911
  name: reqF(R_NONEMPTY),
887
912
  status: reqF(R_NONEMPTY),
@@ -892,6 +917,8 @@ const MCP_PANEL_LEG_ENTRY = shapeRule("leg entry", {
892
917
  httpStatus: optF(R_NUM),
893
918
  liveness: optF(MCP_PANEL_LIVENESS_VIEW),
894
919
  livenessUnreadable: optF(R_TRUE),
920
+ authorization: optF(MCP_PANEL_AUTHORIZATION),
921
+ authorizationUnreadable: optF(R_TRUE),
895
922
  });
896
923
  const MCP_PANEL_LAST_LEG = shapeRule("last leg", {
897
924
  runId: reqF(R_NONEMPTY),
@@ -975,6 +1002,10 @@ const checkLocalSessionRecord = seatCheck((s) => {
975
1002
  if (!isAbsent(pinned) && typeof pinned !== "boolean") {
976
1003
  return bad("pinned must be a boolean when present (absent = not pinned or unknown)", "pinned");
977
1004
  }
1005
+ const rowTag = own(s, "rowTag");
1006
+ if (!isAbsent(rowTag) && (typeof rowTag !== "string" || rowTag.length === 0)) {
1007
+ return bad("rowTag must be a non-empty string when present (absent = this row cannot be written conditionally)", "rowTag");
1008
+ }
978
1009
  const latch = own(s, "thinkingLatch");
979
1010
  if (!isAbsent(latch)) {
980
1011
  const verdict = R_THINKING_LATCH_ROW.test(latch, "thinkingLatch");
@@ -1069,6 +1100,9 @@ const RETRY_COMMON_SPEC = {
1069
1100
  providerConditionSource: optF(R_STR),
1070
1101
  elapsedMs: optF(R_NUM),
1071
1102
  timeoutMs: optF(R_NUM),
1103
+ sleptMs: optF(R_NUM0),
1104
+ waitSegment: optF(R_NONEMPTY),
1105
+ seat: optF(R_NONEMPTY),
1072
1106
  };
1073
1107
  const RETRY_STATUS = armsRule("retry status", "kind", {
1074
1108
  stalled: { ...RETRY_COMMON_SPEC, detail: optF(R_STR) },
@@ -1441,8 +1475,12 @@ export const checkSessionRowPatchRequest = seatCheck((v) => {
1441
1475
  }
1442
1476
  if (!isAbsent(pinned) && typeof pinned !== "boolean")
1443
1477
  return bad("pinned must be a boolean when present", "pinned");
1478
+ const expectedRowTag = body.expectedRowTag;
1479
+ if (!isAbsent(expectedRowTag) && (typeof expectedRowTag !== "string" || expectedRowTag.length === 0)) {
1480
+ return bad("expectedRowTag must be the non-empty rowTag read from the same session row when present", "expectedRowTag");
1481
+ }
1444
1482
  if (isAbsent(title) && isAbsent(pinned))
1445
- return bad("nothing to change: carry at least one of title / pinned");
1483
+ return bad("nothing to change: carry at least one of title / pinned (expectedRowTag is a precondition, not a change)");
1446
1484
  return true;
1447
1485
  });
1448
1486
  export function isSessionRowPatchRequest(v) {
@@ -1734,6 +1772,7 @@ export const LOCAL_SESSION_RECORD_KEYS = [
1734
1772
  "queuedMessages",
1735
1773
  "parked",
1736
1774
  "pinned",
1775
+ "rowTag",
1737
1776
  "thinkingLatch",
1738
1777
  ];
1739
1778
  export const SEND_MESSAGE_REQUEST_KEYS = [
@@ -1779,7 +1818,7 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
1779
1818
  LocalSessionRecord: {
1780
1819
  guard: "isLocalSessionRecord",
1781
1820
  check: checkLocalSessionRecord,
1782
- validated: ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "thinkingLatch"],
1821
+ validated: ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "rowTag", "thinkingLatch"],
1783
1822
  unchecked: [],
1784
1823
  },
1785
1824
  ToolPermissionRequest: {
@@ -1900,7 +1939,7 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
1900
1939
  SessionRowPatchRequest: {
1901
1940
  guard: "isSessionRowPatchRequest",
1902
1941
  check: checkSessionRowPatchRequest,
1903
- validated: ["sessionId", "title", "pinned"],
1942
+ validated: ["sessionId", "title", "pinned", "expectedRowTag"],
1904
1943
  unchecked: [],
1905
1944
  },
1906
1945
  SetThinkingLatchRequest: {
@@ -245,7 +245,29 @@ const UNCONFIRMED_TEXT = {
245
245
  unreadable: 'Not confirmed: the result of this erase could not be read.',
246
246
  };
247
247
  const ERASED_TEXT = 'Erased: every memory entry recorded as coming from this session was erased or was already gone.';
248
- const PARTIAL_TEXT = 'Partly erased: some memory entries from this session were not erased yet.';
248
+ const PARTIAL_ENTRIES_TEXT = 'Partly erased: some memory entries from this session were not erased yet.';
249
+ const PARTIAL_UNKNOWN_TEXT = 'Partly erased: the engine reports this erase as not complete, and its receipt does not show whether memory entries or other stored records of them remain.';
250
+ function partialCarriersText(kinds) {
251
+ return kinds === 1
252
+ ? 'Partly erased: every memory entry recorded as coming from this session was erased or was already gone, but 1 kind of stored record that refers to them was not confirmed removed, and trying again may not remove it.'
253
+ : `Partly erased: every memory entry recorded as coming from this session was erased or was already gone, but ${kinds} kinds of stored records that refer to them were not confirmed removed, and trying again may not remove them.`;
254
+ }
255
+ function partialTextOf(cause) {
256
+ if (cause === null)
257
+ return PARTIAL_UNKNOWN_TEXT;
258
+ switch (cause.kind) {
259
+ case 'entries_pending':
260
+ return PARTIAL_ENTRIES_TEXT;
261
+ case 'carriers_residual':
262
+ return partialCarriersText(cause.carriers.length);
263
+ case 'unknown':
264
+ return PARTIAL_UNKNOWN_TEXT;
265
+ default: {
266
+ const unreachable = cause;
267
+ return unreachable;
268
+ }
269
+ }
270
+ }
249
271
  const RETRY_TEXT = {
250
272
  not_needed: 'Nothing more to do.',
251
273
  same_request_id: 'It is safe to try again with the same request id.',
@@ -414,7 +436,7 @@ export function sessionMemoryEraseOutcome(answer) {
414
436
  return { kind: 'erased', receipt: reading, text: ERASED_TEXT, retry: 'not_needed', retryText: RETRY_TEXT.not_needed };
415
437
  }
416
438
  const retry = memoryEraseReplayPosture(reading) === 'idempotent' ? 'same_request_id' : 'human_decision';
417
- return { kind: 'partial', receipt: reading, text: PARTIAL_TEXT, retry, retryText: RETRY_TEXT[retry] };
439
+ return { kind: 'partial', receipt: reading, text: partialTextOf(reading.partialCause), retry, retryText: RETRY_TEXT[retry] };
418
440
  }
419
441
  if (status < 400)
420
442
  return unconfirmedBy('unclassified', signed, { status });
@@ -0,0 +1,24 @@
1
+ export interface SessionRowPatchFields {
2
+ title?: string | null;
3
+ pinned?: boolean;
4
+ expectedRowTag?: string;
5
+ }
6
+ export interface SessionRowCurrentReading {
7
+ readonly rowTag?: string;
8
+ readonly title?: string;
9
+ readonly pinned?: boolean;
10
+ }
11
+ export type SessionRowPatchRefusal = {
12
+ readonly kind: 'already-applied';
13
+ readonly current: SessionRowCurrentReading;
14
+ } | {
15
+ readonly kind: 'row-changed';
16
+ readonly current?: SessionRowCurrentReading;
17
+ } | {
18
+ readonly kind: 'mirror';
19
+ readonly forkPath?: string;
20
+ } | {
21
+ readonly kind: 'tag-unsupported';
22
+ };
23
+ export declare function sessionRowTagOf(row: object | null | undefined): string | undefined;
24
+ export declare function sessionRowPatchRefusalOf(error: {} | null | undefined, sent: Readonly<SessionRowPatchFields> | null | undefined): SessionRowPatchRefusal | null;
@@ -0,0 +1,77 @@
1
+ import { guardedRead, readOwnDataValue } from './ownKey.js';
2
+ import { isSlotCarrier, readErrorRecord, readErrorString, readThrownSlot, slotReadOf } from './wireFailureShape.js';
3
+ const ROW_CHANGED = 'conflict.session_row_changed';
4
+ const SESSION_MIRROR = 'conflict.session_mirror';
5
+ const BODY_SHAPE = 'request.body_shape';
6
+ function ownData(v, key) {
7
+ if (v === null || typeof v !== 'object')
8
+ return undefined;
9
+ return guardedRead(() => readOwnDataValue(v, key));
10
+ }
11
+ export function sessionRowTagOf(row) {
12
+ const v = ownData(row, 'rowTag');
13
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
14
+ }
15
+ function currentRowOf(e) {
16
+ const row = guardedRead(() => readErrorRecord(e, 'currentRow'));
17
+ if (row === undefined)
18
+ return undefined;
19
+ const rowTag = sessionRowTagOf(row);
20
+ const title = ownData(row, 'title');
21
+ const pinned = ownData(row, 'pinned');
22
+ return Object.freeze({
23
+ ...(rowTag !== undefined ? { rowTag } : {}),
24
+ ...(typeof title === 'string' ? { title } : {}),
25
+ ...(typeof pinned === 'boolean' ? { pinned } : {}),
26
+ });
27
+ }
28
+ function pendingSlotOf(sent, key) {
29
+ if (sent === null || typeof sent !== 'object')
30
+ return { state: 'absent' };
31
+ const read = slotReadOf(sent, key);
32
+ if (read.presence === 'absent')
33
+ return { state: 'absent' };
34
+ if (read.presence !== 'present')
35
+ return { state: 'unreadable' };
36
+ return read.value === undefined ? { state: 'absent' } : { state: 'value', value: read.value };
37
+ }
38
+ function alreadyApplied(sent, current) {
39
+ const title = pendingSlotOf(sent, 'title');
40
+ const pinned = pendingSlotOf(sent, 'pinned');
41
+ if (title.state === 'unreadable' || pinned.state === 'unreadable')
42
+ return false;
43
+ let compared = 0;
44
+ if (title.state === 'value') {
45
+ if (typeof title.value !== 'string' || current.title !== title.value)
46
+ return false;
47
+ compared++;
48
+ }
49
+ if (pinned.state === 'value') {
50
+ if (typeof pinned.value !== 'boolean' || current.pinned !== pinned.value)
51
+ return false;
52
+ compared++;
53
+ }
54
+ return compared > 0;
55
+ }
56
+ export function sessionRowPatchRefusalOf(error, sent) {
57
+ if (!isSlotCarrier(error))
58
+ return null;
59
+ const status = readThrownSlot(error, 'status', 'data');
60
+ const code = readThrownSlot(error, 'errorCode', 'data');
61
+ if (status === 409 && code === ROW_CHANGED) {
62
+ const current = currentRowOf(error);
63
+ if (current !== undefined && alreadyApplied(sent, current))
64
+ return { kind: 'already-applied', current };
65
+ return { kind: 'row-changed', ...(current !== undefined ? { current } : {}) };
66
+ }
67
+ if (status === 409 && code === SESSION_MIRROR) {
68
+ const forkPath = guardedRead(() => readErrorString(error, 'forkPath'));
69
+ return { kind: 'mirror', ...(typeof forkPath === 'string' && forkPath.length > 0 ? { forkPath } : {}) };
70
+ }
71
+ if (status === 400 && code === BODY_SHAPE) {
72
+ const tag = ownData(sent, 'expectedRowTag');
73
+ if (typeof tag === 'string' && tag.length > 0)
74
+ return { kind: 'tag-unsupported' };
75
+ }
76
+ return null;
77
+ }
@@ -39,11 +39,21 @@ export function stripSystemReminderBlocks(text) {
39
39
  }
40
40
  return pos === 0 ? text : out + text.slice(pos);
41
41
  }
42
- const SYSTEM_REMINDER_TAG_TOKEN = /<[^A-Za-z0-9>]{0,12}system-reminder(?:[^>]{0,200}>)?/gi;
43
- const SYSTEM_REMINDER_TAG_HEAD = /<[^A-Za-z0-9>]{0,12}system-reminder/i;
42
+ const ENGINE_DEFUSAL_ENTITY = /&(lt|gt);/g;
43
+ const ENVELOPE_TAG_TOKEN = /<[^A-Za-z0-9>]{0,12}(?:system-reminder|task-notification)(?:[^>]{0,200}>)?/gi;
44
+ const CUT_TAG_HEAD = /<[^A-Za-z0-9>]{0,12}([A-Za-z-]{1,16})…/g;
45
+ const ENVELOPE_TAG_HEAD_BRACKET = /<(?=[^A-Za-z0-9>]{0,12}(?:system-reminder|task-notification|([A-Za-z-]{1,16})…))/gi;
46
+ const ENVELOPE_TAG_NAMES = Object.freeze(['system-reminder', 'task-notification']);
47
+ function isCutTagName(head) {
48
+ if (head === undefined || head === '')
49
+ return false;
50
+ const h = head.toLowerCase();
51
+ return ENVELOPE_TAG_NAMES.some((name) => name.length > h.length && name.startsWith(h));
52
+ }
44
53
  export function stripSystemReminderTagTokens(text) {
45
54
  if (typeof text !== 'string')
46
55
  return text;
47
- const once = text.replace(SYSTEM_REMINDER_TAG_TOKEN, ' ');
48
- return SYSTEM_REMINDER_TAG_HEAD.test(once) ? once.replace(/</g, '&lt;') : once;
56
+ const restored = text.replace(ENGINE_DEFUSAL_ENTITY, (_entity, name) => (name === 'lt' ? '<' : '>'));
57
+ const once = restored.replace(ENVELOPE_TAG_TOKEN, ' ').replace(CUT_TAG_HEAD, (head, name) => (isCutTagName(name) ? ' …' : head));
58
+ return once.replace(ENVELOPE_TAG_HEAD_BRACKET, (bracket, cutName) => (cutName === undefined || isCutTagName(cutName) ? ' ' : bracket));
49
59
  }
@@ -60,7 +60,14 @@ function isIdleWatchdog(err) {
60
60
  return false;
61
61
  }
62
62
  }
63
- const DETERMINISTIC_SERVER_CODES = new Set([CORRUPT_SESSION_ERROR_CODE, CORRUPT_ROW_ERROR_CODE, SCENARIO_ASSIGNMENT_UNKNOWN_ERROR_CODE, MEMORY_CONTROL_PLANE_CORRUPT]);
63
+ const DETERMINISTIC_SERVER_CODES = new Set([
64
+ CORRUPT_SESSION_ERROR_CODE,
65
+ CORRUPT_ROW_ERROR_CODE,
66
+ SCENARIO_ASSIGNMENT_UNKNOWN_ERROR_CODE,
67
+ MEMORY_CONTROL_PLANE_CORRUPT,
68
+ 'approvals.read_corrupt',
69
+ 'workflow.journal_incompatible',
70
+ ]);
64
71
  const BROWSER_FETCH_FAILURE = /\bfailed to fetch\b|\bnetworkerror\b|\bnetwork error\b|\bload failed\b/i;
65
72
  function httpShapeOf(verdict, code) {
66
73
  const status = verdict.status;