@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
@@ -12,6 +12,7 @@ export interface McpConfigLike {
12
12
  headers?: Record<string, string>;
13
13
  allowTools?: string[];
14
14
  toolFaces?: unknown;
15
+ toolAxes?: unknown;
15
16
  }
16
17
  export declare function mcpConfigToSpec(name: string, c: McpConfigLike): McpServerSpec | null;
17
18
  export declare function mcpConfigsToSpecs(servers: Record<string, McpConfigLike> | undefined): McpServerSpec[];
@@ -1,6 +1,9 @@
1
1
  export const MCP_CAPS = { items: 32 };
2
2
  function transparentKeys(c) {
3
- return c.toolFaces !== undefined ? { toolFaces: c.toolFaces } : {};
3
+ return {
4
+ ...(c.toolFaces !== undefined ? { toolFaces: c.toolFaces } : {}),
5
+ ...(c.toolAxes !== undefined ? { toolAxes: c.toolAxes } : {}),
6
+ };
4
7
  }
5
8
  export function mcpConfigToSpec(name, c) {
6
9
  const n = (name ?? '').trim();
@@ -1,6 +1,8 @@
1
1
  export declare const MEMORY_ENGINE_REQUIRED_CODE = "capability.memory_engine_required";
2
2
  export type MemoryComplianceReading = {
3
3
  kind: 'unobserved';
4
+ } | {
5
+ kind: 'unreadable';
4
6
  } | {
5
7
  kind: 'not_reported';
6
8
  } | {
@@ -1,5 +1,5 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
- import { createEngineCapReader } from './engineCapReader.js';
2
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
3
  import { ownDataField } from './ownKey.js';
4
4
  export const MEMORY_ENGINE_REQUIRED_CODE = 'capability.memory_engine_required';
5
5
  export function projectMemoryComplianceCapability(caps) {
@@ -21,7 +21,6 @@ export function projectMemoryComplianceCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'memoryCompliance',
23
23
  project: projectMemoryComplianceCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
25
  export function observedMemoryCompliance(baseUrl = engineWireTarget()?.baseUrl) {
27
26
  return reader.observed(baseUrl);
@@ -35,6 +34,8 @@ export function memoryComplianceVerbsAvailable(reading) {
35
34
  }
36
35
  export function memoryComplianceDoctorDetail(reading) {
37
36
  switch (reading.kind) {
37
+ case 'unreadable':
38
+ return unreadableReadingDetail('memory compliance face');
38
39
  case 'unobserved':
39
40
  return 'memory compliance face not observed — the engine reports it on /v1/capabilities (memoryCompliance); 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)';
40
41
  case 'not_reported':
@@ -1,5 +1,7 @@
1
1
  export type MemoryOriginReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -1,5 +1,5 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
- import { createEngineCapReader } from './engineCapReader.js';
2
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
3
  import { ownDataField } from './ownKey.js';
4
4
  import { MEMORY_ENGINE_REQUIRED_CODE } from './memoryComplianceCapability.js';
5
5
  export function projectMemoryOriginCapability(caps) {
@@ -21,7 +21,6 @@ export function projectMemoryOriginCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'memoryOrigin',
23
23
  project: projectMemoryOriginCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
25
  export function observedMemoryOrigin(baseUrl = engineWireTarget()?.baseUrl) {
27
26
  return reader.observed(baseUrl);
@@ -35,6 +34,8 @@ export function memoryOriginVerbsAvailable(reading) {
35
34
  }
36
35
  export function memoryOriginDoctorDetail(reading) {
37
36
  switch (reading.kind) {
37
+ case 'unreadable':
38
+ return unreadableReadingDetail('external-origin face');
38
39
  case 'unobserved':
39
40
  return 'external-origin face not observed — the engine reports it on /v1/capabilities (memoryOrigin); 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)';
40
41
  case 'not_reported':
@@ -1,4 +1,4 @@
1
- import { engineCapValue } from './engineCapsCache.js';
1
+ import { engineCapsLandingOf } from './engineCapsCache.js';
2
2
  import { engineWireTarget } from './engineWireTarget.js';
3
3
  function laneOrder() {
4
4
  return ['processEnv', 'settingsEnv', 'modelCatalogFile', 'followedSettingsFile', 'followedProviderEnv'];
@@ -212,5 +212,5 @@ export function modelSetupNotice(decision) {
212
212
  }
213
213
  }
214
214
  export function observedEngineAnswer(baseUrl = engineWireTarget()?.baseUrl) {
215
- return engineCapValue(baseUrl, 'version').kind === 'unobserved' ? 'not_observed' : 'answered';
215
+ return engineCapsLandingOf(baseUrl).kind === 'not_landed' ? 'not_observed' : 'answered';
216
216
  }
@@ -1,4 +1,4 @@
1
- import { capDisplay, displayUntrusted } from './displayUntrusted.js';
1
+ import { capDisplay, DISPLAY_CAP_BACKOFF_MAX, displayUntrusted } from './displayUntrusted.js';
2
2
  import { engineNoticeInCatalog, noticeAudienceOf } from './engineNoticeCodes.js';
3
3
  import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
4
4
  const DEFAULT_CUT_MARK = '…';
@@ -210,7 +210,7 @@ const PRINT_BUDGET_OPERATOR = 160;
210
210
  const PRINT_PREFIX = '[sema] ';
211
211
  const NO_TEXT_SENTENCE = 'Engine notice (no message text)';
212
212
  const PRINT_CODE_MAX = 160;
213
- const PRE_CAP_SLACK = 2;
213
+ const PRE_CAP_SLACK = DISPLAY_CAP_BACKOFF_MAX + 1;
214
214
  const PRINT_SESSION_MAX = 128;
215
215
  function lineFace(max) {
216
216
  return max === undefined ? { foldLines: false, mark: 'dot' } : { foldLines: false, mark: 'dot', max };
@@ -1,5 +1,7 @@
1
1
  export type OutputStyleSeatReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -17,7 +17,6 @@ export function projectOutputStyleSeatCapability(caps) {
17
17
  const reader = createEngineCapReader({
18
18
  name: 'outputStyleSeat',
19
19
  project: projectOutputStyleSeatCapability,
20
- makeUnobserved: () => ({ kind: 'unobserved' }),
21
20
  });
22
21
  export function noteEngineCapsForOutputStyleSeat(baseUrl, caps, opts) {
23
22
  reader.note(baseUrl, caps, opts);
@@ -1,5 +1,7 @@
1
1
  export type PeerLaneReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -1,5 +1,5 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
- import { createEngineCapReader } from './engineCapReader.js';
2
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
3
  import { ownDataField } from './ownKey.js';
4
4
  export function projectPeerLaneCapability(caps) {
5
5
  try {
@@ -21,7 +21,6 @@ export function projectPeerLaneCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'peerLane',
23
23
  project: projectPeerLaneCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
25
  export function noteEngineCapsForPeerLane(baseUrl, caps, opts) {
27
26
  reader.note(baseUrl, caps, opts);
@@ -38,6 +37,8 @@ export function peerLaneAvailable(reading) {
38
37
  }
39
38
  export function peerLaneDoctorDetail(reading) {
40
39
  switch (reading.kind) {
40
+ case 'unreadable':
41
+ return unreadableReadingDetail('cross-session lane');
41
42
  case 'unobserved':
42
43
  return 'cross-session lane not observed — the engine reports it on /v1/capabilities (peerLane); 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)';
43
44
  case 'not_reported':
@@ -1,5 +1,7 @@
1
1
  export type PermissionModesReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -38,7 +38,6 @@ export function projectPermissionModesCapability(caps) {
38
38
  const reader = createEngineCapReader({
39
39
  name: 'permissionModes',
40
40
  project: projectPermissionModesCapability,
41
- makeUnobserved: () => ({ kind: 'unobserved' }),
42
41
  });
43
42
  export function observedPermissionModes(baseUrl = engineWireTarget()?.baseUrl) {
44
43
  return reader.observed(baseUrl);
@@ -1,5 +1,7 @@
1
1
  export type PermissionRulesWriteReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -1,5 +1,5 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
- import { createEngineCapReader } from './engineCapReader.js';
2
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
3
  import { ownDataField } from './ownKey.js';
4
4
  export function projectPermissionRulesWriteCapability(caps) {
5
5
  try {
@@ -21,7 +21,6 @@ export function projectPermissionRulesWriteCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'permissionRulesWrite',
23
23
  project: projectPermissionRulesWriteCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
25
  export function noteEngineCapsForPermissionRulesWrite(baseUrl, caps, opts) {
27
26
  reader.note(baseUrl, caps, opts);
@@ -34,6 +33,8 @@ export function permissionRulesWriteAvailable(reading) {
34
33
  }
35
34
  export function permissionRulesWriteDoctorDetail(reading) {
36
35
  switch (reading.kind) {
36
+ case 'unreadable':
37
+ return unreadableReadingDetail('permission-rule write entry');
37
38
  case 'unobserved':
38
39
  return 'permission-rule write entry not observed — the engine reports it on /v1/capabilities (permissionRulesWrite); 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)';
39
40
  case 'not_reported':
@@ -1,5 +1,7 @@
1
1
  export type PermissionsAllowGrantReading = {
2
2
  kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
3
5
  } | {
4
6
  kind: 'not_reported';
5
7
  } | {
@@ -1,5 +1,5 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
- import { createEngineCapReader } from './engineCapReader.js';
2
+ import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
3
  import { ownDataField } from './ownKey.js';
4
4
  import { hasCoreValuePort } from './coreValuePorts.js';
5
5
  const ALLOW_GRANT_CAP_PATH = ['settings', 'permissions', 'allowGrant'];
@@ -35,7 +35,6 @@ export function projectPermissionsAllowGrantCapability(caps) {
35
35
  const reader = createEngineCapReader({
36
36
  name: ALLOW_GRANT_CAP_PATH.join('.'),
37
37
  project: projectPermissionsAllowGrantCapability,
38
- makeUnobserved: () => ({ kind: 'unobserved' }),
39
38
  });
40
39
  export function noteEngineCapsForPermissionsAllowGrant(baseUrl, caps, opts) {
41
40
  reader.note(baseUrl, caps, opts);
@@ -95,6 +94,9 @@ export function permissionsAllowGrantDoctorDetail(reading, settingsRead) {
95
94
  if (kind === 'present' && ownReadingField(reading, 'grants') === false) {
96
95
  return 'allow rules kept on this side — this engine says it does not read allow rules as rules of that kind, so they are not sent and matching approvals are still answered here';
97
96
  }
97
+ if (kind === 'unreadable') {
98
+ return `${unreadableReadingDetail('how this engine reads allow rules')}; allow rules are kept on this side (not sent), and matching approvals are still answered here`;
99
+ }
98
100
  if (kind === 'not_reported') {
99
101
  return 'allow rules kept on this side — this engine does not say how it reads allow rules; sending them to an engine that reads them as an exclusive list would block every tool not on it, so they are not sent and matching approvals are still answered here';
100
102
  }
@@ -29,19 +29,6 @@ export const PERMISSIONS_KEPT_REASONS = Object.freeze([
29
29
  'duplicate',
30
30
  'voids_allow_list',
31
31
  ]);
32
- function normalizeLegacyToolName(name) {
33
- switch (name) {
34
- case 'Task':
35
- return 'Agent';
36
- case 'KillShell':
37
- return 'TaskStop';
38
- case 'AgentOutputTool':
39
- case 'BashOutputTool':
40
- return 'TaskOutput';
41
- default:
42
- return name;
43
- }
44
- }
45
32
  function unescapedIndex(str, char, fromEnd) {
46
33
  for (let k = 0; k < str.length; k++) {
47
34
  const i = fromEnd ? str.length - 1 - k : k;
@@ -61,17 +48,17 @@ function unescapeRuleContent(content) {
61
48
  function parseRule(rule) {
62
49
  const open = unescapedIndex(rule, '(', false);
63
50
  if (open === -1)
64
- return { toolName: normalizeLegacyToolName(rule) };
51
+ return { toolName: rule };
65
52
  const close = unescapedIndex(rule, ')', true);
66
53
  if (close === -1 || close <= open || close !== rule.length - 1)
67
- return { toolName: normalizeLegacyToolName(rule) };
54
+ return { toolName: rule };
68
55
  const toolName = rule.substring(0, open);
69
56
  const rawContent = rule.substring(open + 1, close);
70
57
  if (!toolName)
71
- return { toolName: normalizeLegacyToolName(rule) };
58
+ return { toolName: rule };
72
59
  if (rawContent === '' || rawContent === '*')
73
- return { toolName: normalizeLegacyToolName(toolName) };
74
- return { toolName: normalizeLegacyToolName(toolName), ruleContent: unescapeRuleContent(rawContent) };
60
+ return { toolName };
61
+ return { toolName, ruleContent: unescapeRuleContent(rawContent) };
75
62
  }
76
63
  function requireRulePorts() {
77
64
  const required = ['MCP_NAMESPACE', 'judgeRuleToolName', 'protocolOf', 'ruleToolGrammarOf', 'validatePermissionRules'];
@@ -90,7 +77,7 @@ function nameCheck(name) {
90
77
  '装的必须是 core 的同名实现、原样装。判不出不等于够得着:不判就发会让整份请求被引擎拒收,不判就删会静默丢掉 deny 规则,所以这里拒绝装配。');
91
78
  }
92
79
  if (verdict.kind === 'retired')
93
- return { issue: 'retired_tool_name' };
80
+ return { retired: verdict.currentName !== undefined ? { currentName: verdict.currentName } : {} };
94
81
  if (verdict.kind === 'unprefixed_namespace')
95
82
  return { issue: 'unprefixed_namespaced_name' };
96
83
  const ns = protocolOf(name);
@@ -134,8 +121,17 @@ function judgeList(rules, behavior, allowForm = 'grant') {
134
121
  }
135
122
  const parsed = parseRule(rule);
136
123
  if (parsed.ruleContent === undefined) {
137
- const name = wholeServerRuleName(parsed.toolName);
138
- const check = nameCheck(name);
124
+ let name = wholeServerRuleName(parsed.toolName);
125
+ let check = nameCheck(name);
126
+ if ('retired' in check) {
127
+ const successor = check.retired.currentName;
128
+ if (behavior === 'allow' || successor === undefined)
129
+ return kept('retired_tool_name');
130
+ name = successor;
131
+ check = nameCheck(name);
132
+ if ('retired' in check)
133
+ return kept('retired_tool_name');
134
+ }
139
135
  if ('issue' in check)
140
136
  return kept(check.issue);
141
137
  if (behavior === 'allow' && 'unminted' in check)
@@ -148,6 +144,8 @@ function judgeList(rules, behavior, allowForm = 'grant') {
148
144
  return behavior === 'allow' ? { sent: true, wire: name, unchecked: false } : sentFate(name, check, verdict.kind === 'unknown');
149
145
  }
150
146
  const check = nameCheck(parsed.toolName);
147
+ if ('retired' in check)
148
+ return kept('retired_tool_name');
151
149
  if ('issue' in check)
152
150
  return kept(check.issue);
153
151
  if (ruleToolGrammarOf(parsed.toolName) === 'path' && parsed.ruleContent.startsWith('~/'))
@@ -36,6 +36,7 @@ export interface TaskRequestInput {
36
36
  attachments?: Record<string, unknown>;
37
37
  attachmentIds?: readonly string[];
38
38
  excludeTools?: readonly string[];
39
+ allowTools?: readonly string[];
39
40
  finalVerification?: boolean;
40
41
  limits?: Record<string, unknown>;
41
42
  interactiveTools?: false;
@@ -57,10 +58,6 @@ export interface TaskRequestInput {
57
58
  disableSkills?: TaskRequest['disableSkills'];
58
59
  rewind?: Record<string, unknown>;
59
60
  repo?: string;
60
- council?: boolean;
61
- debate?: boolean;
62
- rounds?: number;
63
- lenses?: readonly string[];
64
61
  settings?: {
65
62
  resolved?: Record<string, unknown>;
66
63
  webSearch?: Record<string, unknown>;
@@ -21,10 +21,6 @@ export const REQUEST_FIELD_MATRIX = [
21
21
  { field: 'settings.hooks', lanes: ['interactive', 'print'], live: true, why: '[495]① 用户 settings 文件 hooks 逐字上 wire', whyUtilityLane: '无座:hook 挂在工具门上(零工具面没有门),而且 side-channel 是壳自己的提交,不该继承用户那份 hook 表' },
22
22
  { field: 'settings.<resolved>', lanes: ['interactive', 'print'], live: true, why: 'settings resolver 的 effective 快照(hooks/env/model + 已解析权限投影)。两条车道同源:交互面 replEntry / print 面 printModeEngine 各跑一遍同一个 resolver,产物经同一个 buildWireSettingsStamp 投影。开放集(子键不可枚举)⇒ unregisteredRequestKeys 放行本车道 settings 的**表外**子键,但表内异车道子键仍点名', whyUtilityLane: '无座:这一位是**开放集 spread**(子键原样即 wire 键),摊开的是用户 settings 的已解析面(权限 / env / 模型)。side-channel 是壳自己的提交:摊上去既无用(零工具面没有工具门求值权限)又把用户的配置面带进了一条他没参与的提交。🔴 无座 ⇒ 本车道整只快照不产。🔴 **本行无座的前提由构造期校验兜住**:剥掉的是用户显式的 deny / ask,所以 `refuseUtilityWithoutToolUnload` 强制这条车道必须带字面 `excludeAllTools: true` —— 否则产出会是一条「既没卸工具、又没带用户 deny」的请求(比无人值守车道同一份输入更宽)' },
23
23
  { field: 'repo', lanes: ['interactive', 'print'], live: true, why: 'scan/code-review 场景的仓库坐标(passthrough;server 是权威校验方,本层不铸第二判官)', whyUtilityLane: '无座:场景键;side-channel 不走场景' },
24
- { field: 'council', lanes: ['interactive', 'print'], live: true, why: 'code-review council 形开关(server 读 `=== true`;非 true 不 stamp=与缺席同义,发小的那个)', whyUtilityLane: '无座:场景键;side-channel 不走场景' },
25
- { field: 'debate', lanes: ['interactive', 'print'], live: true, why: 'council 的 debate 变体开关(server 读 `=== true`)', whyUtilityLane: '无座:场景键;side-channel 不走场景' },
26
- { field: 'rounds', lanes: ['interactive', 'print'], live: true, why: 'debate 轮数(server clamp [1,3],非有限数被 server 归 undefined——本层原样透传不预 clamp)', whyUtilityLane: '无座:场景键;side-channel 不走场景' },
27
- { field: 'lenses', lanes: ['interactive', 'print'], live: true, why: 'code-review 镜头数(server 场景按数字读、非数字回 400;本层型面仍按旧形声明为字符串数组 —— 已知不一致,随该族退役处理)', whyUtilityLane: '无座:场景键;side-channel 不走场景' },
28
24
  { field: 'memoryCapture', lanes: ['interactive', 'print', 'utility'], live: true, why: '本会话永远不进长期记忆的**声明形**(server §12.4 body 键,单成员闭集 "off");每条车道都是提交面 ⇒ 都能声明。与 memoryWrite:false 不是一个轴(那是「本 run 只读平面」)。⚠️ parked 续跑腿结构性拿不到声明(决策/续跑端点从持久 body 重建 spec),那是引擎侧事实,不是本层的漏', whyUtilityLane: '有座:side-channel 提交是壳自己的一次性问答,**绝不该**进用户的长期记忆 —— 这一位正是那句声明,且它是收紧方向,带上去只会更严' },
29
25
  { field: 'memory', lanes: ['interactive', 'print', 'utility'], live: true, why: '这一次运行不挂记忆面的**请求级**开关(server §12.7 body 键,单成员闭集 "off");每条车道都是提交面 ⇒ 都能声明。与 memoryCapture(采集面)不是一个轴、可并存;与 memoryWrite:false(只读平面)也不是一个轴', whyUtilityLane: '有座:side-channel 提交不需要挂记忆面(不挂记忆段 / 索引 / 三只记忆工具),省 token 且零记忆写入;与 memoryCapture 不是一个轴,两条都带' },
30
26
  { field: 'fileHistory', lanes: ['interactive', 'print'], live: true, why: '「这一次运行别捕获文件历史」的**请求级声明**(server 7.104.0 提交体键,单成员闭集 "disabled";`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 同收,子代继承)。两条用户车道都是提交面、都跑在用户的文件上 ⇒ 都能声明;「本会话不捕获」= 每个提交都带(续跑腿重放同一个体)。🔴 只在能力读数 `on-optional` 时发(老 server 对表外键 400),值由片段口 `fileHistoryDeclarationField` 唯一铸出。与 rewind 还原键同交 ⇒ 上游 400 `request.field_conflict`', whyUtilityLane: '无座:side-channel 强制零工具面(`excludeAllTools`),不会改任何文件 ⇒ 没有可捕获的东西;带上它只会在不认识这一键的老 server 上给这条提交多一种 400' },
@@ -41,6 +37,7 @@ export const REQUEST_FIELD_MATRIX = [
41
37
  { field: 'maxTokens', lanes: ['print', 'utility'], live: true, why: '整任务 token 预算上限声明(sdk `TaskRequest.maxTokens`,请求体**顶层**位,与 `maxCostUsd` 同属上游那一族「调用方要求的上限」:向下夹到部署上限 —— 只许要得更少)。🔴 座位集与 `maxCostUsd` 那一行**逐字相同**:同一条预算轴的两种计价单位,分开定座就是让其中一条静默漂。🔴 坏值(非数 / 非有限 / 非正 / 非整)在本层**响亮拒**:上游对这一位的坏值是安静按「调用方没给」处理并回落部署上限,不是 4xx —— 部署没设上限时这一次运行就没有 token 闸,而声明人不会知道(见 `refuseBadMaxTokens`)', whyUtilityLane: '有座:与 maxCostUsd 同轴孪生 —— 小预算是本车道的定义之一,两种计价单位都该能声明' },
42
38
  { field: 'attachmentIds', lanes: ['interactive'], live: true, why: '附件**字节通道**(uploadAttachment 回执 id;server ≥1.289 绑定会话并把文件物化进 run 的 attachments/)。有上传入口的端才有 id 可引用:web/desktop 有,壳走 images / 本地文件路径,`-p` 车道连 uploadAttachment 都没有 ⇒ print 缺席是**没有来源**,不是漏。🔴 与上面的 `attachments` 同名不同物(那是 turn 边界的配置键),两条永远不许合并', whyUtilityLane: '无座:预上传附件句柄要先有上传入口才有 id 可引用,而 side-channel 没有上传面' },
43
39
  { field: 'excludeTools', lanes: ['interactive', 'print', 'utility'], live: true, why: 'design/161 W3(2026-07-30):真 wire 键(SDK types `excludeTools`)且早在 seatContract `START_SESSION_OPTION_KEYS` 里,却是 desktop 手写 taskReq 的第 9 键、矩阵外——而 stamp 门对未登记键**静默丢弃**,真实危险形=「用户显式排除的工具被静默放回」(权限方向回归,类型层不报)。今天只有 desktop 在发;壳的工具面走 roster/interactiveTools 另一条路。0.77.0 补 print 座(三问:① wire 位与车道无关 —— sdk 具名位、提交体真收 `body.excludeTools`;② 来源 —— print 今天没有排除入口,但这是**收紧方向**的声明,传了只进回执 = 端不读回执就把用户排除的工具放回;③ 口径 —— 仍是 live 门后的位,与交互面同一行)', whyUtilityLane: '有座:逐名排除是卸载声明旁边的第二条收紧声明(两键同带各自生效)。⚠️ 它**不是** excludeAllTools 的降级替身 —— 本车道强制带卸载声明(见 `refuseUtilityWithoutToolUnload`),不认那一键的上游会把整条提交拒掉,那时正解是换车道或不发' },
40
+ { field: 'allowTools', lanes: ['interactive', 'print'], live: true, why: '引擎的可用性选择器(收紧方向的声明:只给点名的工具;`[]` = 什么都不给,照发)。两条用户车道都是提交面、都有「只给这几只工具」的用户入口(终端 `--tools`)⇒ 都有座;坏形(不是串数组)在装配期响亮拒,不静默删键', whyUtilityLane: '无座:side-channel 恒带 excludeAllTools(零工具面),可用性选择器在零工具面上没有可选的东西' },
44
41
  { field: 'excludeAllTools', lanes: ['interactive', 'print', 'utility'], live: true, why: '0.78.0(sdk 11.x 具名位 `excludeAllTools`,server ≥7.90.0 请求面):整只工具面卸载 —— 调用方工具 / 脚手架 / 手 / MCP / A2A / skills 一个都不挂,终局必然纯文本(wire 上 `wiring_manifest.tools.count === 0`,整条流零 tool_start / tool_end / tool_disclosure)。与 excludeTools 是两种语义、刻意不折叠(`"*"` 是一个合法的工具名,「卸全部」不能写成 `excludeTools:["*"]`);两键同带各自生效。类型只收字面 `true`(server 只把 literal true 写上任务规格,`false` = 替引擎重申它自己的缺省 ⇒ 不发、与缺席同义、不进回执);坏值响亮拒(收紧方向的声明不许被打字错误安静丢掉)。每条车道都是提交面 ⇒ 都能声明。探测靠键闭集、没有能力位:没接线的 server(≤7.89.x)400 `request.body_shape` 并在 `unknownKeys` 里逐字点名,本层不替它猜;A2A 入站面不承载本键(零工具面走 skill 路由)', whyUtilityLane: '有座,而且**本车道强制**:零工具面就是这条车道的定义 —— side-channel 提交整只工具面卸载,终局必然纯文本。🔴 缺这一位(或给 `false`)⇒ 构造期响亮拒(`refuseUtilityWithoutToolUnload`):本车道上一整片「无座」的理由都压在这个前提上,其中 `settings.<resolved>` 那一行剥掉的是用户显式的 deny / ask ——前提不成立时产出会是一条「既没卸工具、又没带用户 deny」的请求' },
45
42
  { field: 'settings.ultracode', lanes: ['interactive'], live: true, why: 'design/111:sticky `/effort ultracode` 拨盘 + 当轮关键词嗅探,两个来源都只在交互面存在', gap: true, whyUtilityLane: '无座:sticky 拨盘与当轮关键词嗅探两个来源都只在交互面' },
46
43
  { field: 'systemPrompt', lanes: ['interactive', 'print', 'utility'], live: false, why: '整段替换系统提示(sdk `TaskRequest.systemPrompt`;上游字符数超限 400 响亮拒,本层原样透传)。三条车道都有来源:交互面 = CC QueryParams.systemPrompt,无人值守面 = `--system-prompt[-file]` 一类旗,side-channel 面 = 壳整段给出的那一份。与追加位 `appendSystemPrompt` 不是一个位(那一位归 live 兜底层,见 `LIVE_DEFAULT_FIELDS`)', whyUtilityLane: '有座:side-channel 提交的系统提示由壳整段给出 —— 它不是用户会话的续谈,不该继承用户那一份' },
@@ -175,10 +172,7 @@ const INPUT_KEY_ROUTES = {
175
172
  disableSkills: row('disableSkills'),
176
173
  rewind: row('resumeAt/resumeAtMode/restoreFiles/rewindFilesTo'),
177
174
  repo: row('repo'),
178
- council: row('council'),
179
- debate: row('debate'),
180
- rounds: row('rounds'),
181
- lenses: row('lenses'),
175
+ allowTools: row('allowTools'),
182
176
  settings: { via: 'settings' },
183
177
  };
184
178
  const SETTINGS_KEY_ROUTES = {
@@ -234,8 +228,6 @@ const rewindSubKeys = () => {
234
228
  return r.via === 'matrix' ? r.field.split('/') : [];
235
229
  };
236
230
  const VALUE_LEVEL_IN_PLAY = Object.freeze({
237
- council: (v) => v === true,
238
- debate: (v) => v === true,
239
231
  oneShot: (v) => v === true,
240
232
  interactiveTools: (v) => v === false,
241
233
  images: (v) => Array.isArray(v) && v.length > 0,
@@ -481,6 +473,66 @@ const withoutRetiredAttachmentsKeys = (v) => {
481
473
  delete copy[key];
482
474
  return copy;
483
475
  };
476
+ const ALLOW_TOOLS_MAX_LENGTH = 65_536;
477
+ const ALLOW_TOOLS_UNREADABLE_ITEM = Symbol('allowTools-unreadable-item');
478
+ function allowToolsSnapshot(v) {
479
+ if (!Array.isArray(v))
480
+ return v;
481
+ let n;
482
+ try {
483
+ n = Object.getOwnPropertyDescriptor(v, 'length')?.value;
484
+ }
485
+ catch {
486
+ return ALLOW_TOOLS_UNREADABLE_ITEM;
487
+ }
488
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0 || n > ALLOW_TOOLS_MAX_LENGTH)
489
+ return ALLOW_TOOLS_UNREADABLE_ITEM;
490
+ const out = new Array(n);
491
+ for (let k = 0; k < n; k += 1) {
492
+ let d;
493
+ try {
494
+ d = Object.getOwnPropertyDescriptor(v, k);
495
+ }
496
+ catch {
497
+ out[k] = ALLOW_TOOLS_UNREADABLE_ITEM;
498
+ continue;
499
+ }
500
+ if (d === undefined)
501
+ continue;
502
+ out[k] = 'value' in d ? d.value : ALLOW_TOOLS_UNREADABLE_ITEM;
503
+ }
504
+ return Object.freeze(out);
505
+ }
506
+ const allowToolsCopy = (v) => {
507
+ if (!Array.isArray(v))
508
+ return [];
509
+ const out = [];
510
+ for (let k = 0; k < v.length; k += 1) {
511
+ const d = Object.getOwnPropertyDescriptor(v, k);
512
+ if (d !== undefined && 'value' in d && typeof d.value === 'string')
513
+ out.push(d.value);
514
+ }
515
+ return out;
516
+ };
517
+ const refuseBadAllowTools = (v) => {
518
+ if (v === undefined || v === null)
519
+ return;
520
+ if (Array.isArray(v)) {
521
+ let ok = true;
522
+ for (let k = 0; k < v.length; k += 1) {
523
+ const d = Object.getOwnPropertyDescriptor(v, k);
524
+ if (d === undefined || !('value' in d) || typeof d.value !== 'string') {
525
+ ok = false;
526
+ break;
527
+ }
528
+ }
529
+ if (ok)
530
+ return;
531
+ }
532
+ throw new TypeError('buildTaskRequest: allowTools 只接受字符串数组(`[]` = 什么都不给;缺席 = 不选择)—— ' +
533
+ `收到 ${coarseShape(v)}。这一位是**可用性选择器**(收紧声明):本层若把坏形静默删键,这一次运行会挂上声明人以为已经收窄掉的全部工具。` +
534
+ '错误文本只报形状,不回显值。');
535
+ };
484
536
  const refuseBadExcludeAllTools = (v) => {
485
537
  if (v === undefined || v === null || v === true || v === false)
486
538
  return;
@@ -591,7 +643,8 @@ export function parseMaxCostUsd(text) {
591
643
  return { ok: false, why: 'not_positive' };
592
644
  return { ok: true, value };
593
645
  }
594
- const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession', 'disableSkills', 'approvalReach'];
646
+ const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession', 'disableSkills', 'approvalReach', 'allowTools'];
647
+ const MATERIALIZE_AT_READ = Object.freeze({ allowTools: (v) => allowToolsSnapshot(v) });
595
648
  function dataFirstSnapshot(input, afterData) {
596
649
  let descs;
597
650
  try {
@@ -604,10 +657,10 @@ function dataFirstSnapshot(input, afterData) {
604
657
  const accessors = [];
605
658
  for (const k of Object.keys(descs)) {
606
659
  const d = descs[k];
607
- if (d === undefined || d.enumerable !== true)
660
+ if (d === undefined || (d.enumerable !== true && routeOfTopLevelKey(k) === undefined))
608
661
  continue;
609
662
  if ('value' in d)
610
- bag[k] = d.value;
663
+ bag[k] = Object.hasOwn(MATERIALIZE_AT_READ, k) ? MATERIALIZE_AT_READ[k](d.value) : d.value;
611
664
  else
612
665
  accessors.push(k);
613
666
  }
@@ -616,8 +669,10 @@ function dataFirstSnapshot(input, afterData) {
616
669
  ...ACCESSOR_READ_ORDER.filter((k) => accessors.includes(k)),
617
670
  ...accessors.filter((k) => !ACCESSOR_READ_ORDER.includes(k)),
618
671
  ];
619
- for (const k of ordered)
620
- bag[k] = Reflect.get(input, k);
672
+ for (const k of ordered) {
673
+ const v = Reflect.get(input, k);
674
+ bag[k] = Object.hasOwn(MATERIALIZE_AT_READ, k) ? MATERIALIZE_AT_READ[k](v) : v;
675
+ }
621
676
  return bag;
622
677
  }
623
678
  export function assembleTaskRequest(input, lane) {
@@ -663,6 +718,7 @@ export function assembleTaskRequest(input, lane) {
663
718
  const requireExistingSessionIn = src.requireExistingSession;
664
719
  const disableSkillsIn = src.disableSkills;
665
720
  const approvalReachIn = src.approvalReach;
721
+ const allowToolsIn = src.allowTools;
666
722
  const INPUT_KEYS = Object.keys(INPUT_KEY_ROUTES);
667
723
  const i = snapshotByName(src, INPUT_KEYS, {
668
724
  live,
@@ -677,6 +733,7 @@ export function assembleTaskRequest(input, lane) {
677
733
  requireExistingSession: requireExistingSessionIn,
678
734
  disableSkills: disableSkillsIn,
679
735
  approvalReach: approvalReachIn,
736
+ allowTools: allowToolsIn,
680
737
  settings: settingsIn,
681
738
  });
682
739
  const SETTINGS_KEYS = Object.keys(SETTINGS_KEY_ROUTES);
@@ -692,6 +749,7 @@ export function assembleTaskRequest(input, lane) {
692
749
  refuseBadRequireExistingSession(requireExistingSessionIn);
693
750
  refuseBadDisableSkills(disableSkillsIn, 'buildTaskRequest');
694
751
  refuseBadApprovalReach(approvalReachIn, 'buildTaskRequest');
752
+ refuseBadAllowTools(allowToolsIn);
695
753
  const rewindRoute = INPUT_KEY_ROUTES.rewind;
696
754
  const RETIRED_REWIND_FILES_KEY = 'rewindFiles';
697
755
  const REWIND_KEYS = rewindRoute.via === 'matrix' ? rewindRoute.field.split('/') : [];
@@ -824,10 +882,7 @@ export function assembleTaskRequest(input, lane) {
824
882
  ...(on('enableFork') ? { enableFork: i.enableFork } : {}),
825
883
  ...(on('attachments') ? { attachments: attachmentsForWire } : {}),
826
884
  ...(on('repo') ? { repo: i.repo } : {}),
827
- ...(on('council') ? { council: true } : {}),
828
- ...(on('debate') ? { debate: true } : {}),
829
- ...(on('rounds') ? { rounds: i.rounds } : {}),
830
- ...(on('lenses') ? { lenses: i.lenses } : {}),
885
+ ...(on('allowTools') ? { allowTools: allowToolsCopy(i.allowTools) } : {}),
831
886
  ...(on('attachmentIds') ? { attachmentIds: [...(i.attachmentIds ?? [])] } : {}),
832
887
  ...(on('excludeTools') ? { excludeTools: [...(i.excludeTools ?? [])] } : {}),
833
888
  ...(on('excludeAllTools') ? { excludeAllTools: true } : {}),
@@ -1,5 +1,5 @@
1
1
  import { readBlockedBy, readErrorNumber, readErrorString } from './wireFailureShape.js';
2
- import { readOwnDataNonEmptyString } from './ownKey.js';
2
+ import { guardedRead, ownDataField, readOwnDataNonEmptyString } from './ownKey.js';
3
3
  import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, RESUME_SESSION_NOT_FOUND, REQUEST_FIELD_CONFLICT, CHECKPOINT_UNSUPPORTED_VERSION, CHECKPOINT_WALLTIME_AXIS_RETIRED, CHECKPOINT_CONFIG_REFUSED, isRefusedParkCode, refusalSaysStillPending, refusalRetriableReading, } from './engineErrorCodes.js';
4
4
  import { capForDisplay } from './fleetTaskDesc.js';
5
5
  import { engineVersionTier } from './supportFloor.js';
@@ -11,7 +11,7 @@ export const RESUME_REFUSAL_CODES = Object.freeze([
11
11
  export function resumeRefusalFromError(err) {
12
12
  if (typeof err !== 'object' || err === null)
13
13
  return null;
14
- const code = err.errorCode;
14
+ const code = guardedRead(() => ownDataField(err, 'errorCode'));
15
15
  if (typeof code !== 'string' || !RESUME_REFUSAL_CODES.includes(code))
16
16
  return null;
17
17
  if (code === RESUME_PLACEMENT_MISMATCH) {
@@ -274,8 +274,8 @@ export function refusedParkFromError(failure) {
274
274
  let code;
275
275
  let status;
276
276
  try {
277
- code = failure.errorCode;
278
- status = failure.status;
277
+ code = ownDataField(failure, 'errorCode');
278
+ status = ownDataField(failure, 'status');
279
279
  }
280
280
  catch {
281
281
  return null;
@@ -1,5 +1,6 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
2
  import { resetRewindArchiveReader, rewindArchiveReadingAt, rewindConversationAvailability, } from './rewindArchiveReading.js';
3
+ import { unreadableReadingDetail } from './engineCapReader.js';
3
4
  export { projectRewindArchiveCapability, rewindConversationAvailability } from './rewindArchiveReading.js';
4
5
  export function noteEngineCapsForRewindArchive(baseUrl, caps, opts) {
5
6
  void baseUrl;
@@ -21,6 +22,8 @@ export function rewindCodeArchiveAvailability(reading, mode) {
21
22
  }
22
23
  export function rewindArchiveDoctorDetail(reading, mode) {
23
24
  const lane = mode === 'both' ? 'rewind with code restore' : 'code-only rewind';
25
+ if (reading.kind === 'unreadable')
26
+ return unreadableReadingDetail(lane);
24
27
  if (reading.kind !== 'present') {
25
28
  return `${lane} not observed — the engine reports it on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether the code archive can be restored is unknown`;
26
29
  }
@@ -58,6 +61,8 @@ export function engineRewindRefusedNow(option, reading = observedRewindArchive()
58
61
  }
59
62
  export function rewindConversationDoctorDetail(reading) {
60
63
  const lane = 'rewind to a message';
64
+ if (reading.kind === 'unreadable')
65
+ return unreadableReadingDetail(lane);
61
66
  if (reading.kind !== 'present') {
62
67
  return `${lane} not observed — the engine reports conversation anchors on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether the conversation can be rewound is unknown`;
63
68
  }
@@ -1,6 +1,8 @@
1
1
  export type RewindArchiveBit = 'on' | 'off' | 'not_reported' | 'malformed';
2
2
  export type RewindArchiveReading = {
3
3
  kind: 'unobserved';
4
+ } | {
5
+ kind: 'unreadable';
4
6
  } | {
5
7
  kind: 'present';
6
8
  conversationAnchor: RewindArchiveBit;
@@ -37,7 +37,6 @@ export function projectRewindArchiveCapability(caps) {
37
37
  const reader = createEngineCapReader({
38
38
  name: 'rewindArchive',
39
39
  project: projectRewindArchiveCapability,
40
- makeUnobserved: () => ({ kind: 'unobserved' }),
41
40
  });
42
41
  export function rewindArchiveReadingAt(baseUrl) {
43
42
  return reader.observed(baseUrl);