@sema-agent/client-core 0.91.0 → 0.92.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 (123) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +31 -21
  3. package/dist/adapt/arms.js +4 -12
  4. package/dist/adapt/panelTasks.d.ts +0 -2
  5. package/dist/adapt/panelTasks.js +6 -26
  6. package/dist/adapt.js +2 -2
  7. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  8. package/dist/adapter/activeRunSelfHeal.js +5 -27
  9. package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
  10. package/dist/adapter/runStream.js +3 -1
  11. package/dist/askSurvivesStandingAnswer.d.ts +4 -1
  12. package/dist/askSurvivesStandingAnswer.js +13 -0
  13. package/dist/autoModeUnavailable.js +3 -2
  14. package/dist/cloudConfigWireCaps.js +5 -4
  15. package/dist/compensations.js +5 -5
  16. package/dist/controlRouter.d.ts +23 -23
  17. package/dist/controlRouter.js +114 -49
  18. package/dist/coreValuePorts.d.ts +4 -2
  19. package/dist/coreValuePorts.js +5 -0
  20. package/dist/decideFailureNote.js +4 -2
  21. package/dist/decideReceipt.d.ts +9 -0
  22. package/dist/decideReceipt.js +62 -1
  23. package/dist/displayUntrusted.js +2 -1
  24. package/dist/effectiveFacts.d.ts +14 -4
  25. package/dist/effectiveFacts.js +45 -5
  26. package/dist/engineAgentAbsence.js +2 -1
  27. package/dist/engineCapReader.js +16 -10
  28. package/dist/engineCapsCache.d.ts +2 -0
  29. package/dist/engineCapsCache.js +13 -2
  30. package/dist/engineErrorCodes.d.ts +14 -0
  31. package/dist/engineErrorCodes.js +37 -0
  32. package/dist/engineNoticeCodes.d.ts +14 -0
  33. package/dist/engineNoticeCodes.js +71 -0
  34. package/dist/executionLaneCapability.js +2 -0
  35. package/dist/gateOutcome.js +2 -17
  36. package/dist/gateVocabulary.d.ts +2 -4
  37. package/dist/gateVocabulary.js +3 -11
  38. package/dist/generated/engineFactTables.d.ts +16 -1
  39. package/dist/generated/engineFactTables.js +81 -0
  40. package/dist/generated/engineNoticeTables.js +3 -0
  41. package/dist/generated/settingsSchemaTables.d.ts +8 -0
  42. package/dist/generated/settingsSchemaTables.js +40 -0
  43. package/dist/hitl/allowRuleShadow.d.ts +21 -0
  44. package/dist/hitl/allowRuleShadow.js +167 -0
  45. package/dist/hitl/askClassPair.d.ts +14 -0
  46. package/dist/hitl/askClassPair.js +42 -0
  47. package/dist/hitl/askGateWire.js +1 -0
  48. package/dist/hitl/askSurvivesPosture.d.ts +5 -0
  49. package/dist/hitl/askSurvivesPosture.js +17 -0
  50. package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
  51. package/dist/hitl/automaticAnswerVerdict.js +143 -0
  52. package/dist/hitl/crashConverged.d.ts +1 -0
  53. package/dist/hitl/crashConverged.js +1 -1
  54. package/dist/hitl/frameRouter.d.ts +2 -0
  55. package/dist/hitl/frameRouter.js +66 -14
  56. package/dist/hitl/gateLedger.d.ts +6 -0
  57. package/dist/hitl/gateLedger.js +34 -5
  58. package/dist/hitl/hitlHostSurface.d.ts +2 -0
  59. package/dist/hitl/hitlHostSurface.js +7 -0
  60. package/dist/hitl/livePendingAsk.js +24 -5
  61. package/dist/hitl/parkResolver.js +3 -1
  62. package/dist/hitl/persistedRulesWire.d.ts +4 -1
  63. package/dist/hitl/persistedRulesWire.js +9 -2
  64. package/dist/hitl/planReviewWire.js +2 -2
  65. package/dist/hitl/replayedApproval.d.ts +21 -0
  66. package/dist/hitl/replayedApproval.js +89 -0
  67. package/dist/hitl/sessionPolicyDeliverable.js +20 -2
  68. package/dist/hitl/sessionPolicyWire.d.ts +8 -0
  69. package/dist/hitl/sessionPolicyWire.js +12 -2
  70. package/dist/hitl/toolApprovalWire.d.ts +6 -1
  71. package/dist/hitl/toolApprovalWire.js +20 -40
  72. package/dist/hookMatcherVerdict.js +2 -1
  73. package/dist/humanTurn.js +3 -4
  74. package/dist/index.d.ts +15 -5
  75. package/dist/index.js +12 -3
  76. package/dist/inheritEnvWire.js +3 -4
  77. package/dist/mcpLiveness.js +2 -1
  78. package/dist/memoryVerbsWire.d.ts +1 -1
  79. package/dist/memoryVerbsWire.js +3 -0
  80. package/dist/model/catalog.js +2 -1
  81. package/dist/model/providerPresets.d.ts +3 -1
  82. package/dist/model/tierVocabulary.d.ts +2 -1
  83. package/dist/model/tierVocabulary.js +3 -8
  84. package/dist/peerFrames.js +2 -2
  85. package/dist/permissionRuleIssue.js +9 -6
  86. package/dist/permissionsDeclaration.d.ts +7 -1
  87. package/dist/permissionsDeclaration.js +29 -20
  88. package/dist/pluginHooksWire.js +2 -11
  89. package/dist/promptProfileWireCaps.d.ts +2 -1
  90. package/dist/protocolSegment.d.ts +9 -0
  91. package/dist/protocolSegment.js +28 -0
  92. package/dist/request/taskRequest.js +1 -15
  93. package/dist/ruleCompileVerdict.d.ts +2 -0
  94. package/dist/ruleCompileVerdict.js +13 -0
  95. package/dist/ruleRemovalConsequence.d.ts +2 -1
  96. package/dist/runCancelContext.d.ts +12 -0
  97. package/dist/runCancelContext.js +83 -1
  98. package/dist/seam.js +1 -1
  99. package/dist/seatContract.d.ts +2 -1
  100. package/dist/sessionMemoryStatus.d.ts +2 -4
  101. package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
  102. package/dist/sessionSyncFileHistoryCapability.js +50 -0
  103. package/dist/skillToolListsCapability.d.ts +18 -0
  104. package/dist/skillToolListsCapability.js +50 -0
  105. package/dist/sqlEngineCapability.js +1 -8
  106. package/dist/steerPriorityCapability.d.ts +18 -0
  107. package/dist/steerPriorityCapability.js +80 -0
  108. package/dist/steerReceipt.d.ts +9 -0
  109. package/dist/steerReceipt.js +25 -0
  110. package/dist/subagent/engineSubagentSteer.js +2 -9
  111. package/dist/supportFloor.js +2 -2
  112. package/dist/toolResult.d.ts +8 -0
  113. package/dist/toolResult.js +47 -3
  114. package/dist/types/engineState.d.ts +2 -12
  115. package/dist/userInvokedSkillsCapability.d.ts +18 -0
  116. package/dist/userInvokedSkillsCapability.js +50 -0
  117. package/dist/wireFailureShape.d.ts +2 -0
  118. package/dist/wireFailureShape.js +4 -0
  119. package/dist/workflowsJournalCapability.d.ts +18 -0
  120. package/dist/workflowsJournalCapability.js +50 -0
  121. package/dist/writeProtectionCapability.d.ts +2 -4
  122. package/docs/INTEGRATION-CLIENTS.md +624 -12
  123. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { cancelConflictContent, cancelConflictFromError } from './wireRefusalCopy.js';
2
- import { uuid } from './adapter/types.js';
2
+ import { steerPriorityMode } from './steerPriorityCapability.js';
3
3
  export class ControlSafetyError extends Error {
4
4
  code;
5
5
  cause;
@@ -16,7 +16,6 @@ export class ControlSafetyError extends Error {
16
16
  this.status = status;
17
17
  }
18
18
  }
19
- const BAND_RANK = { now: 0, next: 1, later: 2 };
20
19
  export class ControlRouter {
21
20
  client;
22
21
  taskId = null;
@@ -24,8 +23,6 @@ export class ControlRouter {
24
23
  jobId;
25
24
  scenario;
26
25
  systemPrompt;
27
- queue = [];
28
- seq = 0;
29
26
  constructor(client, opts = {}) {
30
27
  this.client = client;
31
28
  this.sessionId = opts.sessionId;
@@ -63,7 +60,16 @@ export class ControlRouter {
63
60
  if (this.taskId === null) {
64
61
  throw controlSafetyRefusal('not_running', 'no run in flight to steer', 'submit a turn first');
65
62
  }
66
- const body = mode !== undefined ? { text, mode } : { text };
63
+ let priority;
64
+ let reading;
65
+ try {
66
+ priority = opts?.priority;
67
+ reading = opts?.steerPriority;
68
+ }
69
+ catch (e) {
70
+ throw steerLocalRefusal('steer_input_invalid', 'unknown', e);
71
+ }
72
+ const body = steerBodyOf({ text, ...(mode !== undefined ? { mode } : {}), ...(priority !== undefined ? { priority: priority } : {}) }, (reading ?? UNOBSERVED_STEER_PRIORITY));
67
73
  try {
68
74
  return await this.client.runs.steer(this.taskId, body, opts?.signal !== undefined ? { signal: opts.signal } : undefined);
69
75
  }
@@ -92,38 +98,11 @@ export class ControlRouter {
92
98
  'check what that decision did to the run before retrying the cancel', e);
93
99
  }
94
100
  if (isNotFound(e)) {
95
- throw controlSafetyRefusal('not_found', 'runs.cancel 404 — non-owner / unknown run (no existence leak)', undefined, e);
101
+ throw controlSafetyRefusal('not_found', 'runs.cancel 404', `${CANCEL_NOT_FOUND_COPY.happened} — ${CANCEL_NOT_FOUND_COPY.nextStep}`, e);
96
102
  }
97
103
  throw e;
98
104
  }
99
105
  }
100
- enqueue(text, priority = 'next') {
101
- const cmd = { id: this.nextId(), text, priority };
102
- this.queue.push(cmd);
103
- return cmd;
104
- }
105
- dequeue(id) {
106
- const i = this.queue.findIndex((c) => c.id === id);
107
- if (i < 0)
108
- return false;
109
- this.queue.splice(i, 1);
110
- return true;
111
- }
112
- pending() {
113
- return [...this.queue].sort((a, b) => BAND_RANK[a.priority] - BAND_RANK[b.priority]);
114
- }
115
- async drainNext(opts) {
116
- const ordered = this.pending();
117
- const head = ordered[0];
118
- if (!head)
119
- return null;
120
- this.dequeue(head.id);
121
- return this.submit(head.text, opts);
122
- }
123
- nextId() {
124
- this.seq += 1;
125
- return `q-${this.seq}-${uuid()}`;
126
- }
127
106
  }
128
107
  export function errCodes(e) {
129
108
  if (!e || typeof e !== 'object')
@@ -152,20 +131,72 @@ const STEERING_NAME_TO_SAFETY = new Map([
152
131
  ['SteeringAmbiguousTargetError', 'ambiguous_target'],
153
132
  ['SteeringStillRunningError', 'still_running'],
154
133
  ['SteeringLedgerUnavailableError', 'ledger_unavailable'],
134
+ ['SteeringQueueFullError', 'queue_full'],
135
+ ['SteeringDuplicateInputIdError', 'duplicate_input_id'],
155
136
  ['SteeringError', 'steering_other'],
156
137
  ]);
157
- const STEERING_ADVICE = {
158
- not_running: 'the run is suspended/terminal/on another replica — resolve its gate via decide, do not steer',
159
- invalid_content: 'the steer text carried a control-plane escape; surface it, do not strip-and-retry',
160
- queue_full: 'the parked steering queue on this checkpoint is full (core fails loud rather than evicting an accepted instruction) — wait for it to drain, do not resend',
161
- duplicate_input_id: 'this Idempotency-Key is already parked with DIFFERENT steering content — reissue with a fresh key, resending verbatim will never succeed',
162
- ambiguous_target: 'more than one live target matches — address it uniquely (sub-agent: parentToolCallId; workflow: a more specific label); retrying verbatim yields the same result',
163
- still_running: 'the target is STILL RUNNING — steer it instead of resuming, or wait for it to settle (this is the exact opposite of not_running: never collapse the two)',
164
- blocked_by_hook: "this deployment's userPromptSubmit gate refused the input (block, timeout and crash all report this one code, fail-closed) — the input was NOT accepted: no human_input frame, the inputId is not on the ledger, so editing the text and sending again is a normal retry, not a duplicate",
165
- ledger_unavailable: 'the server could not record this steer in its user-input ledger and refused it before anything reached the engine — the input was NOT delivered; resend the same text later (the same Idempotency-Key is fine)',
166
- steering_other: 'an unrecognized steering.* refusal (open set) — the steer did NOT land; log the errorCode verbatim, do not guess its meaning',
138
+ const STEERING_COPY = {
139
+ not_running: {
140
+ happened: 'that run is not accepting messages right now — it has just finished, is running on another server, or is not the active run of its session',
141
+ nextStep: 'check its current state and send the message to the run that is active',
142
+ },
143
+ invalid_content: {
144
+ happened: 'the message was refused because its text contains control markers the engine does not accept, so nothing was delivered',
145
+ nextStep: 'rewrite the message and send it again',
146
+ },
147
+ queue_full: {
148
+ happened: 'that run is paused and already holds as many waiting messages as it can, so this one was not accepted',
149
+ nextStep: 'send it again once the waiting messages have been delivered (when the run resumes or the session is woken)',
150
+ },
151
+ duplicate_input_id: {
152
+ happened: 'a different instruction was already accepted under this idempotency key, and that earlier instruction stands',
153
+ nextStep: 'if you changed the text, send the new text under a new key; otherwise do not resend it — a new key would deliver the same instruction twice',
154
+ },
155
+ ambiguous_target: {
156
+ happened: 'more than one running agent matches that target, so the message was not delivered',
157
+ nextStep: 'address a sub-agent by its tool-call id; a workflow agent cannot be singled out while another running agent shares its label, so send it again once only one does',
158
+ },
159
+ still_running: {
160
+ happened: 'that agent is still running, so it cannot be resumed',
161
+ nextStep: 'send it a message instead, or wait for it to finish',
162
+ },
163
+ blocked_by_hook: {
164
+ happened: "the message was not accepted: a prompt check on this deployment blocked it, so nothing was delivered",
165
+ nextStep: 'edit the message and send it again — that is not a duplicate submission',
166
+ },
167
+ ledger_unavailable: {
168
+ happened: 'the server could not record the message and refused it before anything reached the engine, so nothing was delivered',
169
+ nextStep: 'send the same text again later',
170
+ },
171
+ steering_other: {
172
+ happened: 'the message was refused for a reason this version does not recognise, so it did not land',
173
+ nextStep: 'report its error code if this keeps happening; do not guess what it means',
174
+ },
175
+ };
176
+ const adviceOfCopy = (c) => `${c.happened} — ${c.nextStep}`;
177
+ const STEERING_ADVICE = Object.freeze(Object.fromEntries(Object.entries(STEERING_COPY).map(([k, c]) => [k, adviceOfCopy(c)])));
178
+ const STEERING_OTHER_UNKNOWN_COPY = {
179
+ happened: 'the message was refused for a reason this version does not recognise, and whether it was delivered is not known',
180
+ nextStep: 'do not resend it as if it had certainly been dropped — check the run first, and report its error code if this keeps happening',
181
+ };
182
+ const CANCEL_NOT_FOUND_COPY = {
183
+ happened: 'this server has no run with that id for you — it does not exist, or it belongs to someone else',
184
+ nextStep: 'nothing was cancelled; refresh the run list',
185
+ };
186
+ const PRIORITY_NOT_APPLIED_COPY = {
187
+ advisory: {
188
+ happened: "this server accepts a steer priority but does not apply it — a 'now' message would not interrupt the running turn and a 'later' message would not wait",
189
+ nextStep: 'nothing was sent; to interrupt the turn now, send the text as an interrupt, or send it without a priority to have it delivered at the next turn boundary',
190
+ },
191
+ unknown: {
192
+ happened: 'whether this server applies a steer priority is not known — there is no usable capability reading for it',
193
+ nextStep: 'nothing was sent; wait for the capability reading and try again, or send it without a priority to have it delivered at the next turn boundary',
194
+ },
195
+ };
196
+ const STEER_INPUT_INVALID_COPY = {
197
+ happened: "the steer request could not be built — its priority must be 'now', 'next' or 'later', and its fields must be readable",
198
+ nextStep: 'nothing was sent; fix the request and send it again',
167
199
  };
168
- const STEERING_OTHER_UNKNOWN_ADVICE = 'an unrecognized steering.* refusal (open set) — treat delivery as unknown (do not resend as if it had certainly been dropped); log the errorCode verbatim, do not guess its meaning';
169
200
  export function steeringStatusIs4xx(e) {
170
201
  let status;
171
202
  try {
@@ -176,10 +207,10 @@ export function steeringStatusIs4xx(e) {
176
207
  }
177
208
  return status !== undefined && status >= 400 && status < 500;
178
209
  }
179
- function steeringAdviceOf(e, safety) {
210
+ function steeringCopyOf(e, safety) {
180
211
  if (safety === 'steering_other' && !steeringStatusIs4xx(e))
181
- return STEERING_OTHER_UNKNOWN_ADVICE;
182
- return STEERING_ADVICE[safety];
212
+ return STEERING_OTHER_UNKNOWN_COPY;
213
+ return STEERING_COPY[safety];
183
214
  }
184
215
  const STEERING_REFUSED_BEFORE_ENGINE = new Map([['ledger_unavailable', 503]]);
185
216
  export function steeringRefusedBeforeEngine(e) {
@@ -190,7 +221,8 @@ export function steeringRefusedBeforeEngine(e) {
190
221
  const safety = steeringVerdict(e);
191
222
  if (safety === undefined || STEERING_REFUSED_BEFORE_ENGINE.get(safety) !== status)
192
223
  return undefined;
193
- return { safety, advice: STEERING_ADVICE[safety] };
224
+ const copy = STEERING_COPY[safety];
225
+ return { safety, advice: STEERING_ADVICE[safety], happened: copy.happened, nextStep: copy.nextStep };
194
226
  }
195
227
  catch {
196
228
  return undefined;
@@ -217,7 +249,8 @@ export function steerRefusalAdviceOf(e) {
217
249
  if (verdict === undefined)
218
250
  return undefined;
219
251
  const safety = steeringSafetyOf(e, verdict);
220
- return { safety, advice: steeringAdviceOf(e, safety) };
252
+ const copy = steeringCopyOf(e, safety);
253
+ return { safety, advice: adviceOfCopy(copy), happened: copy.happened, nextStep: copy.nextStep };
221
254
  }
222
255
  catch {
223
256
  return undefined;
@@ -245,6 +278,38 @@ function controlSafetyRefusal(code, head, advice, cause) {
245
278
  const detail = { ...(advice !== undefined ? { advice } : {}), ...(status !== undefined ? { status } : {}) };
246
279
  return new ControlSafetyError(advice === undefined ? head : `${head} — ${advice}`, code, cause, detail);
247
280
  }
281
+ export const UNOBSERVED_STEER_PRIORITY = Object.freeze({ kind: 'unobserved' });
282
+ function steerLocalRefusal(code, mode, cause) {
283
+ const copy = code === 'steer_input_invalid' ? STEER_INPUT_INVALID_COPY : PRIORITY_NOT_APPLIED_COPY[mode === 'advisory' ? 'advisory' : 'unknown'];
284
+ return controlSafetyRefusal(code, 'steer not sent', adviceOfCopy(copy), cause);
285
+ }
286
+ function ownInputField(input, key) {
287
+ return Object.hasOwn(input, key) ? input[key] : undefined;
288
+ }
289
+ export function steerBodyOf(input, reading) {
290
+ let text;
291
+ let mode;
292
+ let priority;
293
+ try {
294
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
295
+ throw new TypeError('steer input is not an object');
296
+ text = ownInputField(input, 'text');
297
+ mode = ownInputField(input, 'mode');
298
+ priority = ownInputField(input, 'priority');
299
+ }
300
+ catch (e) {
301
+ throw steerLocalRefusal('steer_input_invalid', 'unknown', e);
302
+ }
303
+ const base = mode !== undefined ? { text, mode } : { text };
304
+ if (priority === undefined || priority === 'next')
305
+ return Object.freeze(base);
306
+ const tierMode = steerPriorityMode(reading);
307
+ if (priority !== 'now' && priority !== 'later')
308
+ throw steerLocalRefusal('steer_input_invalid', tierMode);
309
+ if (tierMode !== 'engine')
310
+ throw steerLocalRefusal('priority_not_applied', tierMode);
311
+ return Object.freeze({ ...base, priority });
312
+ }
248
313
  function isCancelSuspendedConflict(e) {
249
314
  const { name, status } = errCodes(e);
250
315
  return name === 'ConflictError' || status === 409;
@@ -32,7 +32,7 @@ export interface CoreProtocolNamespaceView {
32
32
  }
33
33
  export interface PermissionRuleView {
34
34
  rule: string;
35
- behavior: 'deny' | 'ask' | 'allow';
35
+ behavior: 'deny' | 'ask';
36
36
  source?: string;
37
37
  }
38
38
  export interface PermissionRuleIssueView {
@@ -59,8 +59,9 @@ export interface CoreValuePorts {
59
59
  ruleToolGrammarOf: (toolName: string) => 'command' | 'path' | undefined;
60
60
  validatePermissionRules: (rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView) => PermissionRuleIssueView[];
61
61
  DISCUSSION_WORKFLOW_NAME: string;
62
+ mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string;
62
63
  }
63
- export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME"];
64
+ export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME", "mintNamespacePrefix"];
64
65
  export type CoreValuePortName = (typeof CORE_VALUE_PORT_NAMES)[number];
65
66
  export declare function installCoreValuePorts(ports: Partial<CoreValuePorts> | null): () => void;
66
67
  export declare function hasCoreValuePorts(): boolean;
@@ -76,3 +77,4 @@ export declare function protocolOf(name: string): CoreProtocolNamespaceView | un
76
77
  export declare function ruleToolGrammarOf(toolName: string): 'command' | 'path' | undefined;
77
78
  export declare function validatePermissionRules(rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView): PermissionRuleIssueView[] | undefined;
78
79
  export declare function discussionWorkflowName(): string | undefined;
80
+ export declare function mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string | undefined;
@@ -9,6 +9,7 @@ export const CORE_VALUE_PORT_NAMES = Object.freeze([
9
9
  'ruleToolGrammarOf',
10
10
  'validatePermissionRules',
11
11
  'DISCUSSION_WORKFLOW_NAME',
12
+ 'mintNamespacePrefix',
12
13
  ]);
13
14
  let installed = null;
14
15
  export function installCoreValuePorts(ports) {
@@ -67,3 +68,7 @@ export function validatePermissionRules(rules, opts) {
67
68
  export function discussionWorkflowName() {
68
69
  return port('DISCUSSION_WORKFLOW_NAME');
69
70
  }
71
+ export function mintNamespacePrefix(ns, peer) {
72
+ const fn = port('mintNamespacePrefix');
73
+ return fn === undefined ? undefined : fn(ns, peer);
74
+ }
@@ -1,6 +1,6 @@
1
1
  import { corruptStoredRowContent, corruptStoredRowWhere } from './wireErrorTriage.js';
2
2
  import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, parkedResumeStartupContent, parkedResumeStartupFromError, resumePolicyBlockContent, resumePolicyBlockFromError, } from './resumeRefusalCopy.js';
3
- import { decideParkMovedContent, decideParkMovedFromError } from './decideReceipt.js';
3
+ import { askGoneContent, askGoneFromError, decideParkMovedContent, decideParkMovedFromError } from './decideReceipt.js';
4
4
  import { settingsPermissionBucketRefusalContent, settingsPermissionBucketRefusalFromError } from './wireRefusalCopy.js';
5
5
  export function decideFailureNote(err, decision) {
6
6
  const where = corruptStoredRowWhere(err);
@@ -9,10 +9,12 @@ export function decideFailureNote(err, decision) {
9
9
  const moved = decideParkMovedFromError(err);
10
10
  const bucket = settingsPermissionBucketRefusalFromError(err);
11
11
  const unsupported = checkpointUnsupportedVersionFromError(err);
12
+ const gone = askGoneFromError(err);
12
13
  return ((where === undefined ? '' : ` — ${corruptStoredRowContent(where)}`) +
13
14
  (fold === null ? '' : ` — ${resumePolicyBlockContent(fold)}`) +
14
15
  (bucket === null ? '' : ` — ${settingsPermissionBucketRefusalContent(bucket)}`) +
15
16
  (startup === null ? '' : ` — ${parkedResumeStartupContent(startup)}`) +
16
17
  (moved === null ? '' : ` — ${decideParkMovedContent(moved)}`) +
17
- (unsupported === null ? '' : ` — ${checkpointUnsupportedVersionContent(unsupported)}`));
18
+ (unsupported === null ? '' : ` — ${checkpointUnsupportedVersionContent(unsupported)}`) +
19
+ (gone === null ? '' : ` — ${askGoneContent(gone)}`));
18
20
  }
@@ -1,5 +1,6 @@
1
1
  import { type GateOutcomeView } from './gateOutcome.js';
2
2
  import { type ResumeReopenDetail } from './hitl/suspendedReopen.js';
3
+ import { type CrashConvergedRow } from './hitl/crashConverged.js';
3
4
  export interface DecideReceiptView {
4
5
  status?: string;
5
6
  taskId?: string;
@@ -27,3 +28,11 @@ export interface DecideParkMovedDetail {
27
28
  }
28
29
  export declare function decideParkMovedFromError(e: unknown): DecideParkMovedDetail | null;
29
30
  export declare function decideParkMovedContent(detail: Pick<DecideParkMovedDetail, 'code'>): string;
31
+ export interface AskGoneDetail {
32
+ code: 'gone.ask';
33
+ state?: 'VOID' | 'DENIED';
34
+ deniedReason?: string;
35
+ holderGone?: CrashConvergedRow['cause'];
36
+ }
37
+ export declare function askGoneFromError(e: unknown): AskGoneDetail | null;
38
+ export declare function askGoneContent(detail: AskGoneDetail): string;
@@ -1,8 +1,9 @@
1
1
  import { gateOutcomeOf } from './gateOutcome.js';
2
2
  import { resumeReopenFromError } from './hitl/suspendedReopen.js';
3
- import { DECIDE_CLAIM_LOST, DECIDE_GATE_MOVED, DECIDE_NOT_PARKED, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, isDecideParkMovedCode, } from './engineErrorCodes.js';
3
+ import { ASK_GONE, ASK_GONE_DENIED_WORD, DECIDE_CLAIM_LOST, DECIDE_GATE_MOVED, DECIDE_NOT_PARKED, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, isDecideParkMovedCode, } from './engineErrorCodes.js';
4
4
  import { effectiveWireErrorCode } from './wireErrorTriage.js';
5
5
  import { readErrorString } from './wireFailureShape.js';
6
+ import { isCrashConvergedCause } from './hitl/crashConverged.js';
6
7
  const STATUS_RESUMING = 'resuming';
7
8
  function str(v) {
8
9
  return typeof v === 'string' && v.length > 0 ? v : undefined;
@@ -106,3 +107,63 @@ export function decideParkMovedContent(detail) {
106
107
  const extra = code === DECIDE_CLAIM_LOST ? '; if this approval is still listed, wait a little before deciding it again' : '';
107
108
  return `${what} · This decision was not applied — fetch the pending approvals again and decide from what is pending now${extra}`;
108
109
  }
110
+ export function askGoneFromError(e) {
111
+ if (typeof e !== 'object' || e === null)
112
+ return null;
113
+ if (effectiveWireErrorCode(e) !== ASK_GONE)
114
+ return null;
115
+ const rawState = readErrorString(e, 'state');
116
+ const state = rawState === 'VOID' || rawState === 'DENIED' ? rawState : undefined;
117
+ const rawReason = readErrorString(e, 'deniedReason');
118
+ const deniedReason = rawReason === undefined || rawReason.length === 0
119
+ ? undefined
120
+ : state === 'DENIED'
121
+ ? rawReason === ASK_GONE_DENIED_WORD ? rawReason : undefined
122
+ : state === 'VOID' ? rawReason : undefined;
123
+ const holderGone = state === 'VOID' && isCrashConvergedCause(deniedReason) ? deniedReason : undefined;
124
+ return {
125
+ code: ASK_GONE,
126
+ ...(state !== undefined ? { state } : {}),
127
+ ...(deniedReason !== undefined ? { deniedReason } : {}),
128
+ ...(holderGone !== undefined ? { holderGone } : {}),
129
+ };
130
+ }
131
+ export function askGoneContent(detail) {
132
+ let state;
133
+ let deniedReason;
134
+ let holderGone;
135
+ try {
136
+ state = detail.state;
137
+ deniedReason = detail.deniedReason;
138
+ holderGone = detail.holderGone;
139
+ }
140
+ catch {
141
+ state = undefined;
142
+ deniedReason = undefined;
143
+ holderGone = undefined;
144
+ }
145
+ const st = state === 'VOID' || state === 'DENIED' ? state : undefined;
146
+ const safety = 'Your answer was not applied and nothing was run on it';
147
+ if (st === 'VOID' && (isCrashConvergedCause(holderGone) || isCrashConvergedCause(deniedReason))) {
148
+ return [
149
+ 'The engine run that was waiting for this answer ended before the answer arrived',
150
+ safety,
151
+ 'That run will not continue: submit the work again as a new task, and it will ask again',
152
+ ].join(' · ');
153
+ }
154
+ if (st === 'DENIED') {
155
+ return ['This approval was closed without a human decision and the request was denied', safety, 'The denial stands; this approval cannot be reopened'].join(' · ');
156
+ }
157
+ if (st === 'VOID') {
158
+ return [
159
+ 'This approval was withdrawn without a human decision',
160
+ safety,
161
+ 'Nothing is waiting for this answer any more; if the work still needs this step, wait for it to be asked again or submit the work again',
162
+ ].join(' · ');
163
+ }
164
+ return [
165
+ 'This approval was closed without a human decision',
166
+ safety,
167
+ 'It can no longer be decided; fetch the pending approvals again and decide from what is pending now',
168
+ ].join(' · ');
169
+ }
@@ -1,3 +1,4 @@
1
+ import { SETTINGS_SECRET_LITERAL_RE } from './generated/settingsSchemaTables.js';
1
2
  export const REDACTION_MARKER_PREFIX = '\u00abredacted';
2
3
  const MARKER_RE = /^\u00abredacted(?::[a-z-]{1,16}){0,2}\u00bb$/;
3
4
  const REDACTED_USERINFO = '\u00abredacted:userinfo\u00bb';
@@ -26,7 +27,7 @@ function pushEdit(edits, src, start, end, text) {
26
27
  const UNIT_NONE = 0;
27
28
  const UNIT_SEQUENCE = 1;
28
29
  const UNIT_SINGLE = 2;
29
- const SECRET_LITERAL_RE = /^(sk-|gh[opus]_|github_pat_|xox[baprse]-|xapp-|bearer\s|basic\s|eyJ[A-Za-z0-9_-]{10,}\.|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|-----BEGIN[A-Z0-9 ]*PRIVATE KEY)/i;
30
+ const SECRET_LITERAL_RE = SETTINGS_SECRET_LITERAL_RE;
30
31
  const SCHEME_TOKEN_RE = /(?:^|[^A-Za-z0-9_-])(?:bearer|basic)[ \t]+\S{8,4096}/i;
31
32
  const JWT_RE = /(?:^|[^A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{4,2048}/i;
32
33
  function scanForSecretLiteral(s) {
@@ -7,15 +7,25 @@ export interface EffectiveReasoningView {
7
7
  endpoint: string;
8
8
  dropped?: true;
9
9
  }
10
+ export type EffectiveMemoryProjectKey = {
11
+ readonly scope: string;
12
+ readonly absent?: never;
13
+ } | {
14
+ readonly absent: string;
15
+ readonly scope?: never;
16
+ };
17
+ export interface EffectiveMemoryScopeRow {
18
+ scope: string;
19
+ origin: 'deployment' | 'request';
20
+ shadowed?: number;
21
+ }
10
22
  export interface EffectiveMemoryScopesView {
11
23
  state: 'mounted' | 'memoryless' | 'none';
12
24
  reason?: string;
13
25
  contract?: 'v2' | 'legacy';
14
- scopes: readonly {
15
- scope: string;
16
- origin: 'deployment' | 'request';
17
- }[];
26
+ scopes: readonly EffectiveMemoryScopeRow[];
18
27
  writeScope: string | null;
28
+ projectKey?: EffectiveMemoryProjectKey;
19
29
  materializedResidue?: readonly string[];
20
30
  }
21
31
  export declare function readEffectiveReasoning(v: unknown): EffectiveReasoningView | undefined;
@@ -1,4 +1,37 @@
1
1
  const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
2
+ const nonBlank = (v) => typeof v === 'string' && v.trim().length > 0;
3
+ const UNREADABLE = Symbol('unreadable');
4
+ const protectedRead = (o, key) => {
5
+ try {
6
+ return o[key];
7
+ }
8
+ catch {
9
+ return UNREADABLE;
10
+ }
11
+ };
12
+ function readProjectKey(v) {
13
+ try {
14
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
15
+ return undefined;
16
+ const o = v;
17
+ const scope = protectedRead(o, 'scope');
18
+ const absent = protectedRead(o, 'absent');
19
+ if (scope === UNREADABLE || absent === UNREADABLE)
20
+ return undefined;
21
+ if ((scope === undefined) === (absent === undefined))
22
+ return undefined;
23
+ if (scope !== undefined)
24
+ return nonBlank(scope) ? { scope } : undefined;
25
+ return nonBlank(absent) ? { absent } : undefined;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ function readShadowed(r, isWriteHome) {
32
+ const n = protectedRead(r, 'shadowed');
33
+ return !isWriteHome && typeof n === 'number' && Number.isSafeInteger(n) && n > 0 ? n : undefined;
34
+ }
2
35
  export function readEffectiveReasoning(v) {
3
36
  if (typeof v !== 'object' || v === null)
4
37
  return undefined;
@@ -27,7 +60,8 @@ export function readEffectiveMemoryScopes(v) {
27
60
  return undefined;
28
61
  if (!Array.isArray(o.scopes))
29
62
  return undefined;
30
- if (o.writeScope !== null && typeof o.writeScope !== 'string')
63
+ const writeScope = o.writeScope;
64
+ if (writeScope !== null && typeof writeScope !== 'string')
31
65
  return undefined;
32
66
  const contract = o.contract === 'v2' || o.contract === 'legacy' ? o.contract : undefined;
33
67
  const reason = nonEmpty(o.reason) ? o.reason : undefined;
@@ -39,13 +73,19 @@ export function readEffectiveMemoryScopes(v) {
39
73
  if (typeof row !== 'object' || row === null)
40
74
  continue;
41
75
  const r = row;
42
- if (!nonEmpty(r.scope) || typeof r.origin !== 'string' || !ORIGINS.has(r.origin))
76
+ const scope = r.scope;
77
+ if (!nonEmpty(scope))
78
+ continue;
79
+ const origin = r.origin;
80
+ if (typeof origin !== 'string' || !ORIGINS.has(origin))
43
81
  continue;
44
- scopes.push({ scope: r.scope, origin: r.origin });
82
+ const shadowed = readShadowed(r, scope === writeScope);
83
+ scopes.push({ scope, origin: origin, ...(shadowed !== undefined ? { shadowed } : {}) });
45
84
  }
46
- return { state: 'mounted', contract, scopes, writeScope: o.writeScope };
85
+ const projectKey = readProjectKey(protectedRead(o, 'projectKey'));
86
+ return { state: 'mounted', contract, scopes, writeScope, ...(projectKey !== undefined ? { projectKey } : {}) };
47
87
  }
48
- if (o.scopes.length !== 0 || o.writeScope !== null)
88
+ if (o.scopes.length !== 0 || writeScope !== null)
49
89
  return undefined;
50
90
  if (o.state === 'memoryless') {
51
91
  if (reason !== 'mount-failed' && reason !== 'no-backend')
@@ -2,6 +2,7 @@ import { registryClockNowMs, registryListingProvenanceOf } from './agentSession/
2
2
  import { displayUntrusted } from './displayUntrusted.js';
3
3
  import { ABSENT_SETTLE_MS } from './fleetAgentPanelProjection.js';
4
4
  import { isEnginePanelTaskResident, isStaleEngineAgentPanelEnd, } from './engineAgentPanelStore.js';
5
+ import { CORE_DURABLE_AGENT_HANDLE_RE } from './generated/engineFactTables.js';
5
6
  export const ENGINE_AGENT_ABSENT_ROW_TEXT = 'engine no longer reports this agent · it may still be running';
6
7
  export const ENGINE_AGENT_ABSENCE_TTL_MS = 30 * 60_000;
7
8
  function effectiveTtl(ttlMs) {
@@ -132,7 +133,7 @@ export function engineAgentTerminalAfterDropLine(label, status, hasReport) {
132
133
  return `${displayUntrusted(label)} reported ${displayUntrusted(status)} after it was dropped from the task panel${tail}`;
133
134
  }
134
135
  const REGISTRY_LIST_LIMIT = 500;
135
- const REGISTRY_AGENT_HANDLE_RE = /^a[0-9a-f]{6,32}$/;
136
+ const REGISTRY_AGENT_HANDLE_RE = CORE_DURABLE_AGENT_HANDLE_RE;
136
137
  const REGISTRY_AGENT_TYPE = 'background_agent';
137
138
  function registryStatusReading(status) {
138
139
  const s = status;
@@ -1,4 +1,5 @@
1
1
  import { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
2
+ import { capsGenerationMarkOf, retainCapsGenerationMark } from './engineCapsCache.js';
2
3
  function freezeReading(reading, seen = new Set()) {
3
4
  if (reading === null || typeof reading !== 'object' || seen.has(reading))
4
5
  return reading;
@@ -11,7 +12,7 @@ function freezeReading(reading, seen = new Set()) {
11
12
  return reading;
12
13
  }
13
14
  export function createEngineCapReader(spec) {
14
- const readingByBase = new Map();
15
+ const cellByBase = new Map();
15
16
  return {
16
17
  note(baseUrl, caps, opts) {
17
18
  if (typeof baseUrl !== 'string' || baseUrl === '')
@@ -21,6 +22,7 @@ export function createEngineCapReader(spec) {
21
22
  return;
22
23
  if (!capsGenerationStillCurrent(baseUrl, gen))
23
24
  return;
25
+ const mark = retainCapsGenerationMark(baseUrl);
24
26
  let reading;
25
27
  try {
26
28
  reading = spec.project(caps);
@@ -28,29 +30,33 @@ export function createEngineCapReader(spec) {
28
30
  catch {
29
31
  reading = undefined;
30
32
  }
31
- if (!capsGenerationStillCurrent(baseUrl, gen))
33
+ if (capsGenerationMarkOf(baseUrl) !== mark)
32
34
  return;
33
35
  if (reading === undefined) {
34
- readingByBase.delete(baseUrl);
36
+ cellByBase.delete(baseUrl);
35
37
  return;
36
38
  }
37
- readingByBase.set(baseUrl, freezeReading(reading));
39
+ cellByBase.set(baseUrl, { reading: freezeReading(reading), mark });
38
40
  },
39
41
  observed(baseUrl) {
40
42
  if (typeof baseUrl !== 'string' || baseUrl === '')
41
43
  return spec.makeUnobserved();
42
- const hit = readingByBase.get(baseUrl);
43
- if (hit !== undefined)
44
- return hit;
45
- return spec.makeUnobserved();
44
+ const cell = cellByBase.get(baseUrl);
45
+ if (cell === undefined)
46
+ return spec.makeUnobserved();
47
+ if (cell.mark !== capsGenerationMarkOf(baseUrl)) {
48
+ cellByBase.delete(baseUrl);
49
+ return spec.makeUnobserved();
50
+ }
51
+ return cell.reading;
46
52
  },
47
53
  forget(baseUrl) {
48
54
  if (typeof baseUrl !== 'string' || baseUrl === '')
49
55
  return;
50
- readingByBase.delete(baseUrl);
56
+ cellByBase.delete(baseUrl);
51
57
  },
52
58
  __resetForTests() {
53
- readingByBase.clear();
59
+ cellByBase.clear();
54
60
  },
55
61
  };
56
62
  }
@@ -1,4 +1,6 @@
1
1
  export declare function engineCapsGeneration(baseUrl: string): number;
2
+ export declare function capsGenerationMarkOf(baseUrl: string): object | undefined;
3
+ export declare function retainCapsGenerationMark(baseUrl: string): object;
2
4
  export declare function kickEngineCapsProbe(baseUrl: string, probe: () => Promise<unknown>): void;
3
5
  export declare function engineCapsSettled(baseUrl: string | undefined, opts?: {
4
6
  withinMs?: number;
@@ -3,11 +3,22 @@ const inFlight = new Set();
3
3
  const settleByBase = new Map();
4
4
  const genByBase = new Map();
5
5
  function capsGeneration(baseUrl) {
6
- return genByBase.get(baseUrl) ?? 0;
6
+ return genByBase.get(baseUrl)?.n ?? 0;
7
7
  }
8
8
  export function engineCapsGeneration(baseUrl) {
9
9
  return capsGeneration(baseUrl);
10
10
  }
11
+ export function capsGenerationMarkOf(baseUrl) {
12
+ return genByBase.get(baseUrl);
13
+ }
14
+ export function retainCapsGenerationMark(baseUrl) {
15
+ let mark = genByBase.get(baseUrl);
16
+ if (mark === undefined) {
17
+ mark = Object.freeze({ n: 0 });
18
+ genByBase.set(baseUrl, mark);
19
+ }
20
+ return mark;
21
+ }
11
22
  export function kickEngineCapsProbe(baseUrl, probe) {
12
23
  if (!baseUrl || capsByBase.has(baseUrl) || inFlight.has(baseUrl))
13
24
  return;
@@ -165,7 +176,7 @@ export function invalidateEngineCaps(baseUrl, probe) {
165
176
  settleByBase.has(baseUrl) ||
166
177
  genByBase.has(baseUrl);
167
178
  if (hadState) {
168
- genByBase.set(baseUrl, capsGeneration(baseUrl) + 1);
179
+ genByBase.set(baseUrl, Object.freeze({ n: capsGeneration(baseUrl) + 1 }));
169
180
  capsByBase.delete(baseUrl);
170
181
  inFlight.delete(baseUrl);
171
182
  settleByBase.get(baseUrl)?.wake();