@sema-agent/client-core 0.96.0 → 0.97.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 (143) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +28 -22
  3. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  4. package/dist/adapter/activeRunSelfHeal.js +7 -7
  5. package/dist/adapter/downstream/terminalToSdkResult.js +6 -2
  6. package/dist/agentSession/backgroundView.js +2 -15
  7. package/dist/agentsWireCaps.d.ts +1 -1
  8. package/dist/agentsWireCaps.js +16 -10
  9. package/dist/approvalDecidedByCapability.d.ts +2 -0
  10. package/dist/approvalDecidedByCapability.js +0 -1
  11. package/dist/approvalsStreamLiveCapability.d.ts +2 -0
  12. package/dist/approvalsStreamLiveCapability.js +3 -2
  13. package/dist/attachmentSeatCapability.d.ts +2 -0
  14. package/dist/attachmentSeatCapability.js +0 -1
  15. package/dist/callerCwdCapability.d.ts +2 -0
  16. package/dist/callerCwdCapability.js +0 -1
  17. package/dist/centerWiringCapability.d.ts +2 -0
  18. package/dist/centerWiringCapability.js +4 -3
  19. package/dist/cloudConfigWireCaps.d.ts +2 -2
  20. package/dist/cloudConfigWireCaps.js +192 -56
  21. package/dist/declared/supportFloorDeclared.js +3 -3
  22. package/dist/deviceExecutorManagementCapability.d.ts +2 -0
  23. package/dist/deviceExecutorManagementCapability.js +3 -2
  24. package/dist/displayUntrusted.d.ts +3 -0
  25. package/dist/displayUntrusted.js +15 -0
  26. package/dist/engineCapReader.d.ts +8 -2
  27. package/dist/engineCapReader.js +15 -7
  28. package/dist/engineCapsCache.d.ts +11 -3
  29. package/dist/engineCapsCache.js +54 -23
  30. package/dist/engineErrorCodes.d.ts +3 -0
  31. package/dist/engineErrorCodes.js +9 -1
  32. package/dist/engineNoticeCodes.js +2 -3
  33. package/dist/executionLaneCapability.d.ts +2 -0
  34. package/dist/executionLaneCapability.js +3 -2
  35. package/dist/fileHistoryCaptureCapability.d.ts +2 -0
  36. package/dist/fileHistoryCaptureCapability.js +34 -8
  37. package/dist/fileHistoryLegNotice.js +4 -0
  38. package/dist/fleet/fleetLedger.js +129 -105
  39. package/dist/gateOutcome.js +2 -4
  40. package/dist/gateVocabulary.d.ts +1 -6
  41. package/dist/gateVocabulary.js +2 -5
  42. package/dist/generated/sdkFactTables.d.ts +2 -0
  43. package/dist/generated/sdkFactTables.js +7 -0
  44. package/dist/generated/toolNameTables.d.ts +0 -1
  45. package/dist/generated/toolNameTables.js +0 -42
  46. package/dist/hitl/approvalDecisionNoteAudit.js +2 -2
  47. package/dist/hitl/approvalEndingCause.js +2 -7
  48. package/dist/hitl/approvalHolds.d.ts +16 -1
  49. package/dist/hitl/approvalHolds.js +52 -12
  50. package/dist/hitl/askGateWire.js +1 -0
  51. package/dist/hitl/automaticAnswerVerdict.js +5 -7
  52. package/dist/hitl/decisionConflict.js +4 -3
  53. package/dist/hitl/decisionRefusal.js +19 -12
  54. package/dist/hitl/frameRouter.d.ts +3 -0
  55. package/dist/hitl/frameRouter.js +52 -25
  56. package/dist/hitl/gateLedger.d.ts +0 -5
  57. package/dist/hitl/gateLedger.js +0 -21
  58. package/dist/hitl/hitlBridge.js +18 -4
  59. package/dist/hitl/parkResolver.js +7 -3
  60. package/dist/hitl/replayedApproval.d.ts +2 -3
  61. package/dist/hitl/replayedApproval.js +7 -16
  62. package/dist/hitl/sessionPolicyDeliverable.d.ts +0 -1
  63. package/dist/hitl/sessionPolicyDeliverable.js +15 -17
  64. package/dist/hitl/sessionPolicyWire.d.ts +2 -0
  65. package/dist/hitl/sessionPolicyWire.js +15 -3
  66. package/dist/hitl/standingRows.d.ts +1 -0
  67. package/dist/hitl/standingRows.js +38 -17
  68. package/dist/hitl/suspendedReopen.js +2 -1
  69. package/dist/hitl/toolApprovalWire.d.ts +8 -8
  70. package/dist/hitl/toolApprovalWire.js +50 -24
  71. package/dist/index.d.ts +6 -4
  72. package/dist/index.js +4 -3
  73. package/dist/liveModelCatalog.d.ts +13 -0
  74. package/dist/liveModelCatalog.js +56 -0
  75. package/dist/mcpProbeCapability.d.ts +2 -0
  76. package/dist/mcpProbeCapability.js +3 -2
  77. package/dist/mcpWireCaps.d.ts +1 -0
  78. package/dist/mcpWireCaps.js +4 -1
  79. package/dist/memoryComplianceCapability.d.ts +2 -0
  80. package/dist/memoryComplianceCapability.js +3 -2
  81. package/dist/memoryOriginCapability.d.ts +2 -0
  82. package/dist/memoryOriginCapability.js +3 -2
  83. package/dist/modelIdentityResolvability.js +2 -2
  84. package/dist/noticePresentation.js +2 -2
  85. package/dist/outputStyleSeatCapability.d.ts +2 -0
  86. package/dist/outputStyleSeatCapability.js +0 -1
  87. package/dist/peerLaneCapability.d.ts +2 -0
  88. package/dist/peerLaneCapability.js +3 -2
  89. package/dist/permissionModesCapability.d.ts +2 -0
  90. package/dist/permissionModesCapability.js +0 -1
  91. package/dist/permissionRulesWriteCapability.d.ts +2 -0
  92. package/dist/permissionRulesWriteCapability.js +3 -2
  93. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  94. package/dist/permissionsAllowGrantCapability.js +4 -2
  95. package/dist/permissionsDeclaration.js +19 -21
  96. package/dist/request/taskRequest.d.ts +1 -4
  97. package/dist/request/taskRequest.js +74 -19
  98. package/dist/resumeRefusalCopy.js +4 -4
  99. package/dist/rewindArchiveCapability.js +5 -0
  100. package/dist/rewindArchiveReading.d.ts +2 -0
  101. package/dist/rewindArchiveReading.js +0 -1
  102. package/dist/runTerminal.d.ts +33 -4
  103. package/dist/runTerminal.js +170 -15
  104. package/dist/sdkWireTransit.d.ts +2 -1
  105. package/dist/sdkWireTransit.js +1 -0
  106. package/dist/sealedKeyCapability.d.ts +2 -0
  107. package/dist/sealedKeyCapability.js +0 -1
  108. package/dist/seatContract.d.ts +7 -4
  109. package/dist/seatContract.js +34 -20
  110. package/dist/sessionPolicyCapability.d.ts +2 -0
  111. package/dist/sessionPolicyCapability.js +3 -2
  112. package/dist/sessionReadRedactionCapability.d.ts +2 -0
  113. package/dist/sessionReadRedactionCapability.js +0 -1
  114. package/dist/sessionSyncFileHistoryCapability.d.ts +2 -0
  115. package/dist/sessionSyncFileHistoryCapability.js +3 -2
  116. package/dist/skillToolListsCapability.d.ts +2 -0
  117. package/dist/skillToolListsCapability.js +3 -2
  118. package/dist/sqlEngineCapability.d.ts +2 -0
  119. package/dist/sqlEngineCapability.js +4 -3
  120. package/dist/steerPriorityCapability.d.ts +2 -0
  121. package/dist/steerPriorityCapability.js +3 -2
  122. package/dist/storePostureCapability.d.ts +2 -0
  123. package/dist/storePostureCapability.js +4 -3
  124. package/dist/toolHistoryBadTurn.js +3 -2
  125. package/dist/toolResult.d.ts +11 -0
  126. package/dist/toolResult.js +53 -0
  127. package/dist/userInvokedSkillsCapability.d.ts +2 -0
  128. package/dist/userInvokedSkillsCapability.js +3 -2
  129. package/dist/webSearchBackendCapability.d.ts +2 -0
  130. package/dist/webSearchBackendCapability.js +3 -2
  131. package/dist/wireErrorTriage.js +58 -34
  132. package/dist/wireFailureShape.d.ts +1 -1
  133. package/dist/wireFailureShape.js +76 -25
  134. package/dist/workflowsJournalCapability.d.ts +2 -0
  135. package/dist/workflowsJournalCapability.js +3 -2
  136. package/dist/writeProtectionCapability.d.ts +2 -0
  137. package/dist/writeProtectionCapability.js +3 -2
  138. package/docs/INTEGRATION-CLIENTS.md +533 -14
  139. package/package.json +4 -4
  140. package/dist/engineRefusedToolName.d.ts +0 -1
  141. package/dist/engineRefusedToolName.js +0 -8
  142. package/dist/engineWordRenames.d.ts +0 -8
  143. package/dist/engineWordRenames.js +0 -13
@@ -1,5 +1,6 @@
1
1
  import { settleWithinDeadline } from './abortableSleep.js';
2
2
  import { ownDataField } from './ownKey.js';
3
+ const NOT_LANDED = Object.freeze({ kind: 'not_landed' });
3
4
  const capsByBase = new Map();
4
5
  const inFlight = new Set();
5
6
  const settleByBase = new Map();
@@ -13,11 +14,16 @@ export function engineCapsGeneration(baseUrl) {
13
14
  return capsGeneration(baseUrl);
14
15
  }
15
16
  export function engineCapsLandingOf(baseUrl) {
16
- return capsByBase.get(baseUrl);
17
+ if (typeof baseUrl !== 'string' || baseUrl === '')
18
+ return NOT_LANDED;
19
+ return capsByBase.get(baseUrl) ?? NOT_LANDED;
17
20
  }
18
21
  export function kickEngineCapsProbe(baseUrl, probe) {
22
+ startEngineCapsProbe(baseUrl, probe);
23
+ }
24
+ function startEngineCapsProbe(baseUrl, probe) {
19
25
  if (!baseUrl || capsByBase.has(baseUrl) || inFlight.has(baseUrl))
20
- return;
26
+ return false;
21
27
  probeByBase.set(baseUrl, probe);
22
28
  const gen = capsGeneration(baseUrl);
23
29
  inFlight.add(baseUrl);
@@ -35,8 +41,7 @@ export function kickEngineCapsProbe(baseUrl, probe) {
35
41
  const caps = await probe();
36
42
  if (gen !== capsGeneration(baseUrl))
37
43
  return;
38
- if (caps && typeof caps === 'object')
39
- landCapsSnapshot(baseUrl, gen, caps);
44
+ landCapsSnapshot(baseUrl, gen, caps);
40
45
  }
41
46
  catch {
42
47
  }
@@ -49,6 +54,7 @@ export function kickEngineCapsProbe(baseUrl, probe) {
49
54
  markSettled();
50
55
  }
51
56
  })();
57
+ return true;
52
58
  }
53
59
  const MAX_WITHIN_MS = 2_147_483_647;
54
60
  function waitBudgetOf(opts) {
@@ -118,9 +124,7 @@ export async function engineCapsSettled(baseUrl, opts) {
118
124
  }
119
125
  }
120
126
  export function engineCapState(baseUrl, key) {
121
- if (!baseUrl)
122
- return 'unprobed';
123
- return capStateIn(capsByBase.get(baseUrl)?.caps, key);
127
+ return stateOfSlot(slotInLanding(engineCapsLandingOf(baseUrl), key));
124
128
  }
125
129
  const SNAPSHOT_MAX_DEPTH = 64;
126
130
  const SNAPSHOT_MAX_NODES = 100_000;
@@ -156,10 +160,16 @@ function throwingAccessor() {
156
160
  };
157
161
  }
158
162
  function landCapsSnapshot(baseUrl, gen, caps) {
163
+ if (!isPlainCapsBody(caps))
164
+ return 'not_a_record';
159
165
  const snapshot = snapshotCapsBody(caps);
160
166
  if (gen !== capsGeneration(baseUrl))
161
- return;
162
- capsByBase.set(baseUrl, Object.freeze({ caps: snapshot }));
167
+ return 'superseded';
168
+ const record = snapshot === UNREADABLE_NODE
169
+ ? Object.freeze({ kind: 'unreadable' })
170
+ : Object.freeze({ kind: 'landed', caps: snapshot });
171
+ capsByBase.set(baseUrl, record);
172
+ return 'landed';
163
173
  }
164
174
  function snapshotCapsBody(body) {
165
175
  if (body === null || typeof body !== 'object')
@@ -237,8 +247,20 @@ function capSlotIn(caps, key) {
237
247
  return SLOT_UNREADABLE;
238
248
  }
239
249
  }
250
+ function slotInLanding(landing, key) {
251
+ switch (landing.kind) {
252
+ case 'not_landed':
253
+ return SLOT_UNOBSERVED;
254
+ case 'unreadable':
255
+ return SLOT_UNREADABLE;
256
+ case 'landed':
257
+ return capSlotIn(landing.caps, key);
258
+ }
259
+ }
240
260
  function capStateIn(caps, key) {
241
- const slot = capSlotIn(caps, key);
261
+ return stateOfSlot(capSlotIn(caps, key));
262
+ }
263
+ function stateOfSlot(slot) {
242
264
  switch (slot.kind) {
243
265
  case 'unobserved':
244
266
  return 'unprobed';
@@ -276,7 +298,8 @@ export async function engineCapStateVia(baseUrl, key, probe, opts) {
276
298
  try {
277
299
  if (signal?.aborted)
278
300
  return 'unprobed';
279
- return capStateIn(await probe(signal), key);
301
+ const body = await probe(signal);
302
+ return isPlainCapsBody(body) ? capStateIn(body, key) : 'unprobed';
280
303
  }
281
304
  catch {
282
305
  return 'unprobed';
@@ -297,9 +320,7 @@ export function engineCapNestedTrue(baseUrl, parentKey, key) {
297
320
  return state.kind === 'value' && state.value === true;
298
321
  }
299
322
  export function engineCapNestedValue(baseUrl, parentKey, key) {
300
- if (!baseUrl)
301
- return { kind: 'unobserved' };
302
- const parent = capSlotIn(capsByBase.get(baseUrl)?.caps, parentKey);
323
+ const parent = slotInLanding(engineCapsLandingOf(baseUrl), parentKey);
303
324
  if (parent.kind !== 'present')
304
325
  return valueStateOfSlot(parent);
305
326
  const group = parent.value;
@@ -308,9 +329,7 @@ export function engineCapNestedValue(baseUrl, parentKey, key) {
308
329
  return valueStateOfSlot(capSlotIn(group, key));
309
330
  }
310
331
  export function engineCapValue(baseUrl, key) {
311
- if (!baseUrl)
312
- return { kind: 'unobserved' };
313
- return valueStateOfSlot(capSlotIn(capsByBase.get(baseUrl)?.caps, key));
332
+ return valueStateOfSlot(slotInLanding(engineCapsLandingOf(baseUrl), key));
314
333
  }
315
334
  function valueStateOfSlot(slot) {
316
335
  switch (slot.kind) {
@@ -325,9 +344,7 @@ function valueStateOfSlot(slot) {
325
344
  }
326
345
  }
327
346
  export function engineCapString(baseUrl, key) {
328
- if (!baseUrl)
329
- return undefined;
330
- const slot = capSlotIn(capsByBase.get(baseUrl)?.caps, key);
347
+ const slot = slotInLanding(engineCapsLandingOf(baseUrl), key);
331
348
  return slot.kind === 'present' && typeof slot.value === 'string' && slot.value.length > 0 ? slot.value : undefined;
332
349
  }
333
350
  export function invalidateEngineCaps(baseUrl, probe) {
@@ -348,6 +365,19 @@ export function invalidateEngineCaps(baseUrl, probe) {
348
365
  if (probe !== undefined)
349
366
  kickEngineCapsProbe(baseUrl, probe);
350
367
  }
368
+ export function rotateEngineCapsProbe(baseUrl, probe) {
369
+ try {
370
+ if (typeof baseUrl !== 'string' || baseUrl === '' || typeof probe !== 'function')
371
+ return 'ignored';
372
+ if (startEngineCapsProbe(baseUrl, probe))
373
+ return 'started';
374
+ probeByBase.set(baseUrl, probe);
375
+ return 'registered';
376
+ }
377
+ catch {
378
+ return 'ignored';
379
+ }
380
+ }
351
381
  export async function refreshEngineCapsOnce(baseUrl, opts) {
352
382
  try {
353
383
  if (typeof baseUrl !== 'string' || baseUrl === '')
@@ -370,10 +400,9 @@ export async function refreshEngineCapsOnce(baseUrl, opts) {
370
400
  void (async () => {
371
401
  try {
372
402
  const caps = await probe();
373
- if (gen !== capsGeneration(baseUrl) || !capsByBase.has(baseUrl) || !isPlainCapsBody(caps))
403
+ if (gen !== capsGeneration(baseUrl) || !capsByBase.has(baseUrl))
374
404
  return false;
375
- landCapsSnapshot(baseUrl, gen, caps);
376
- return gen === capsGeneration(baseUrl);
405
+ return landCapsSnapshot(baseUrl, gen, caps) === 'landed';
377
406
  }
378
407
  catch {
379
408
  return false;
@@ -393,6 +422,8 @@ function isPlainCapsBody(caps) {
393
422
  try {
394
423
  if (typeof caps !== 'object' || caps === null)
395
424
  return false;
425
+ if (Array.isArray(caps))
426
+ return false;
396
427
  const proto = Object.getPrototypeOf(caps);
397
428
  if (proto !== null && Object.getPrototypeOf(proto) !== null)
398
429
  return false;
@@ -35,6 +35,9 @@ export declare const CAPABILITY_SELF_ORCHESTRATION_REQUIRED = "capability.self_o
35
35
  export declare const CAPABILITY_IMAGE_INDEX_REQUIRED = "capability.image_index_required";
36
36
  export declare const GATE_PARKED_ERROR_CODE = "gate.parked";
37
37
  export declare const ACTIVE_RUN_BUSY_ERROR_CODE = "conflict.session_active_run";
38
+ export declare const CONFLICT_IDEMPOTENCY_PAYLOAD_MISMATCH = "conflict.idempotency_payload_mismatch";
39
+ export declare function isIdempotencyPayloadMismatchCode(code: string | null | undefined): boolean;
40
+ export declare function idempotencyPayloadMismatchContent(): string;
38
41
  export interface BusyClassification {
39
42
  readonly busy: boolean;
40
43
  readonly handle: string | null;
@@ -1,4 +1,5 @@
1
1
  import { frozenSet } from './frozenSet.js';
2
+ import { ownDataField } from './ownKey.js';
2
3
  import { SDK_DENY_REASON_ROUTING_FAILURE, SDK_RUN_DEATH_VERDICT_CODES, SDK_STATUS_DERIVED_COARSE_CODES } from './generated/sdkFactTables.js';
3
4
  export const LIMITS_MAX_TOKENS_EXCEEDED = 'limits.max_tokens_exceeded';
4
5
  export const LIMITS_MAX_COST_EXCEEDED = 'limits.max_cost_exceeded';
@@ -65,6 +66,13 @@ export const CAPABILITY_SELF_ORCHESTRATION_REQUIRED = 'capability.self_orchestra
65
66
  export const CAPABILITY_IMAGE_INDEX_REQUIRED = 'capability.image_index_required';
66
67
  export const GATE_PARKED_ERROR_CODE = 'gate.parked';
67
68
  export const ACTIVE_RUN_BUSY_ERROR_CODE = 'conflict.session_active_run';
69
+ export const CONFLICT_IDEMPOTENCY_PAYLOAD_MISMATCH = 'conflict.idempotency_payload_mismatch';
70
+ export function isIdempotencyPayloadMismatchCode(code) {
71
+ return code === CONFLICT_IDEMPOTENCY_PAYLOAD_MISMATCH;
72
+ }
73
+ export function idempotencyPayloadMismatchContent() {
74
+ return 'This request reused an idempotency key that is already bound to a different request body, so the engine refused it and nothing ran · To retry, send exactly the same request again; to send a different request, use a new idempotency key';
75
+ }
68
76
  export function classifyActiveRunBusy(input) {
69
77
  const handle = typeof input.activeTaskId === 'string' && input.activeTaskId.length > 0 ? input.activeTaskId : null;
70
78
  if (input.errorCode === ACTIVE_RUN_BUSY_ERROR_CODE)
@@ -139,7 +147,7 @@ export function refusalSaysStillPending(err) {
139
147
  }
140
148
  export function refusalRetriableReading(err) {
141
149
  try {
142
- return Object.hasOwn(err, 'retriable') && err.retriable === true;
150
+ return ownDataField(err, 'retriable') === true;
143
151
  }
144
152
  catch {
145
153
  return undefined;
@@ -1,6 +1,5 @@
1
1
  import { AUTO_MODE_UNAVAILABLE_CAUSES } from './autoModeUnavailable.js';
2
2
  import { displayUntrusted } from './displayUntrusted.js';
3
- import { currentEngineWord } from './engineWordRenames.js';
4
3
  import { CORE_ENGINE_NOTICE_CODES, CORE_MCP_INJECTION_DROP_REASONS, CORE_NOTICE_AUDIENCE, CORE_USER_INPUT_UNDRAINED_FAMILIES, } from './generated/engineNoticeTables.js';
5
4
  import { CORE_ASK_UNRESOLVABLE_CAUSES, CORE_DURABLE_MANDATE_SOURCES, CORE_PERMISSION_MODE_FOLD_REASONS, } from './generated/engineFactTables.js';
6
5
  export const ENGINE_NOTICE_CODES = CORE_ENGINE_NOTICE_CODES;
@@ -9,13 +8,13 @@ function audienceRow(table, code) {
9
8
  return Object.hasOwn(table, code) ? table[code] : undefined;
10
9
  }
11
10
  export function engineNoticeInCatalog(code) {
12
- return typeof code === 'string' && (CATALOG.has(code) || CATALOG.has(currentEngineWord(code)));
11
+ return typeof code === 'string' && CATALOG.has(code);
13
12
  }
14
13
  export const ENGINE_NOTICE_AUDIENCE = CORE_NOTICE_AUDIENCE;
15
14
  export function noticeAudienceOf(code) {
16
15
  if (typeof code !== 'string')
17
16
  return 'operator';
18
- return audienceRow(ENGINE_NOTICE_AUDIENCE, code) ?? audienceRow(ENGINE_NOTICE_AUDIENCE, currentEngineWord(code)) ?? 'operator';
17
+ return audienceRow(ENGINE_NOTICE_AUDIENCE, code) ?? 'operator';
19
18
  }
20
19
  export const MCP_INJECTION_DROP_REASONS = CORE_MCP_INJECTION_DROP_REASONS;
21
20
  const DROP_REASONS = new Set(MCP_INJECTION_DROP_REASONS);
@@ -7,6 +7,8 @@ export interface ExecutionLaneCapabilityView {
7
7
  }
8
8
  export type ExecutionLaneReading = {
9
9
  kind: 'unobserved';
10
+ } | {
11
+ kind: 'unreadable';
10
12
  } | {
11
13
  kind: 'not_reported';
12
14
  } | {
@@ -1,6 +1,6 @@
1
1
  import { capForDisplay } from './fleetTaskDesc.js';
2
2
  import { engineWireTarget } from './engineWireTarget.js';
3
- import { createEngineCapReader } from './engineCapReader.js';
3
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
4
4
  import { ownDataField } from './ownKey.js';
5
5
  export function projectExecutionLaneCapability(caps) {
6
6
  try {
@@ -55,7 +55,6 @@ export function projectExecutionLaneWiring(wiring) {
55
55
  const reader = createEngineCapReader({
56
56
  name: 'executionLane',
57
57
  project: projectExecutionLaneCapability,
58
- makeUnobserved: () => ({ kind: 'unobserved' }),
59
58
  });
60
59
  export function noteEngineCapsForExecutionLane(baseUrl, caps, opts) {
61
60
  reader.note(baseUrl, caps, opts);
@@ -98,6 +97,8 @@ export function noOsSandboxContent() {
98
97
  const LANE_DETAIL_MAX = 40;
99
98
  export function executionLaneDoctorDetail(reading) {
100
99
  switch (reading.kind) {
100
+ case 'unreadable':
101
+ return unreadableReadingDetail('execution lane');
101
102
  case 'unobserved':
102
103
  return ('execution lane not observed — the engine reports it on /v1/capabilities; ' +
103
104
  'this process has no usable capabilities reading cached for it (none received, the last one was unreadable, or the last one was discarded when the engine connection was reset; this process cannot tell which)');
@@ -1,5 +1,7 @@
1
1
  export type FileHistoryCaptureReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -1,6 +1,6 @@
1
1
  import { capForDisplay } from './fleetTaskDesc.js';
2
2
  import { engineWireTarget } from './engineWireTarget.js';
3
- import { createEngineCapReader } from './engineCapReader.js';
3
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
4
4
  import { refreshEngineCapsOnce } from './engineCapsCache.js';
5
5
  import { ownDataField } from './ownKey.js';
6
6
  import { FILE_HISTORY_DISABLED } from './request/taskRequest.js';
@@ -22,7 +22,6 @@ export function projectFileHistoryCaptureCapability(caps) {
22
22
  const reader = createEngineCapReader({
23
23
  name: 'fileHistoryCapture',
24
24
  project: projectFileHistoryCaptureCapability,
25
- makeUnobserved: () => ({ kind: 'unobserved' }),
26
25
  });
27
26
  export function noteEngineCapsForFileHistoryCapture(baseUrl, caps, opts) {
28
27
  reader.note(baseUrl, caps, opts);
@@ -64,11 +63,36 @@ export function fileHistoryTightenVerdict(reading, captureOffByUser) {
64
63
  return 'already_off';
65
64
  return 'unjudged';
66
65
  }
67
- const FILE_HISTORY_UNJUDGED_REFUSAL = 'Nothing was sent: file checkpointing is turned off in your settings, but this engine did not report whether it can ' +
68
- 'stop recording file history for a session, so sending now could record it anyway. Try again in a moment; to send ' +
69
- 'regardless and let file history be recorded this time, turn file checkpointing back on in your settings.';
66
+ const FILE_HISTORY_REFUSAL_NOT_YET = "Nothing was sent: file checkpointing is turned off in your settings, but this engine's capabilities have not been read " +
67
+ 'yet, so whether it can stop recording file history for a session is unknown and sending now could record it anyway. ' +
68
+ 'Try again in a moment; to send regardless and let file history be recorded this time, turn file checkpointing back on in your settings.';
69
+ const FILE_HISTORY_REFUSAL_NOT_SAID = 'Nothing was sent: file checkpointing is turned off in your settings, but this engine did not report whether it can ' +
70
+ 'stop recording file history for a session, so sending now could record it anyway. Trying again will not change that ' +
71
+ 'while this engine reports the same; to send and let file history be recorded this time, turn file checkpointing back on in your settings.';
72
+ const FILE_HISTORY_REFUSAL_UNREADABLE = "Nothing was sent: file checkpointing is turned off in your settings, but this engine's report on whether it can stop " +
73
+ 'recording file history for a session could not be read by this client, so sending now could record it anyway. Trying ' +
74
+ 'again will not change that while this engine reports the same; to send and let file history be recorded this time, ' +
75
+ 'turn file checkpointing back on in your settings.';
76
+ function unjudgedRefusalOf(reading) {
77
+ let kind;
78
+ let word;
79
+ try {
80
+ kind = typeof reading === 'object' && reading !== null ? reading.kind : undefined;
81
+ word = kind === 'present' ? reading.word : undefined;
82
+ }
83
+ catch {
84
+ return FILE_HISTORY_REFUSAL_NOT_YET;
85
+ }
86
+ if (kind === 'unreadable')
87
+ return FILE_HISTORY_REFUSAL_UNREADABLE;
88
+ if (kind === 'not_reported')
89
+ return FILE_HISTORY_REFUSAL_NOT_SAID;
90
+ if (kind === 'present' && typeof word === 'string' && word.length > 0)
91
+ return FILE_HISTORY_REFUSAL_NOT_SAID;
92
+ return FILE_HISTORY_REFUSAL_NOT_YET;
93
+ }
70
94
  export function fileHistoryUnjudgedRefusal(reading, captureOffByUser) {
71
- return fileHistoryTightenVerdict(reading, captureOffByUser) === 'unjudged' ? FILE_HISTORY_UNJUDGED_REFUSAL : undefined;
95
+ return fileHistoryTightenVerdict(reading, captureOffByUser) === 'unjudged' ? unjudgedRefusalOf(reading) : undefined;
72
96
  }
73
97
  const FILE_HISTORY_REFRESH_WITHIN_MS = 3000;
74
98
  export async function judgeFileHistoryTighten(captureOffByUser, opts) {
@@ -76,7 +100,7 @@ export async function judgeFileHistoryTighten(captureOffByUser, opts) {
76
100
  const failClosed = () => {
77
101
  const reading = { kind: 'unobserved' };
78
102
  return closed
79
- ? { verdict: 'unjudged', reading, refreshed: false, refusal: FILE_HISTORY_UNJUDGED_REFUSAL }
103
+ ? { verdict: 'unjudged', reading, refreshed: false, refusal: FILE_HISTORY_REFUSAL_NOT_YET }
80
104
  : { verdict: 'not_requested', reading, refreshed: false, refusal: undefined };
81
105
  };
82
106
  try {
@@ -115,7 +139,7 @@ export async function judgeFileHistoryTighten(captureOffByUser, opts) {
115
139
  verdict: v2,
116
140
  reading: second,
117
141
  refreshed: outcome !== 'no_probe',
118
- refusal: v2 === 'unjudged' ? FILE_HISTORY_UNJUDGED_REFUSAL : undefined,
142
+ refusal: v2 === 'unjudged' ? unjudgedRefusalOf(second) : undefined,
119
143
  };
120
144
  }
121
145
  catch {
@@ -137,6 +161,8 @@ export function fileHistoryCaptureDoctorDetail(reading) {
137
161
  return unobserved;
138
162
  }
139
163
  switch (kind) {
164
+ case 'unreadable':
165
+ return unreadableReadingDetail('file history capture');
140
166
  case 'not_reported':
141
167
  return 'file history capture not reported by this engine; this does not say whether file history is captured here';
142
168
  case 'present':
@@ -89,6 +89,8 @@ function canonReading(r) {
89
89
  const kind = r.kind;
90
90
  if (kind === 'not_reported')
91
91
  return 'not_reported';
92
+ if (kind === 'unreadable')
93
+ return 'unreadable';
92
94
  if (kind === 'present') {
93
95
  const word = r.word;
94
96
  return typeof word === 'string' && word.length > 0 ? `present:${word}` : 'unobserved';
@@ -102,6 +104,8 @@ function canonReading(r) {
102
104
  function readingOfCanon(c) {
103
105
  if (c === 'not_reported')
104
106
  return { kind: 'not_reported' };
107
+ if (c === 'unreadable')
108
+ return { kind: 'unreadable' };
105
109
  if (c.startsWith('present:'))
106
110
  return { kind: 'present', word: c.slice('present:'.length) };
107
111
  return { kind: 'unobserved' };