@sema-agent/client-core 0.95.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 (233) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +61 -31
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/toolCards.d.ts +1 -0
  5. package/dist/adapt/toolCards.js +16 -3
  6. package/dist/adapt/wireShapes.js +2 -16
  7. package/dist/adapter/activeRunSelfHeal.d.ts +4 -2
  8. package/dist/adapter/activeRunSelfHeal.js +32 -6
  9. package/dist/adapter/downstream/eventToSdkMessage.js +11 -3
  10. package/dist/adapter/downstream/terminalToSdkResult.js +14 -11
  11. package/dist/adapter/runStream.js +30 -47
  12. package/dist/agentSession/backgroundView.js +2 -15
  13. package/dist/agentSession/engineAgentRegistryRead.js +5 -4
  14. package/dist/agentSession/engineSessionBackgroundStop.js +22 -15
  15. package/dist/agentsWireCaps.d.ts +1 -1
  16. package/dist/agentsWireCaps.js +29 -12
  17. package/dist/approvalDecidedByCapability.d.ts +2 -0
  18. package/dist/approvalDecidedByCapability.js +0 -1
  19. package/dist/approvalsStreamLiveCapability.d.ts +2 -0
  20. package/dist/approvalsStreamLiveCapability.js +3 -2
  21. package/dist/attachmentSeatCapability.d.ts +2 -0
  22. package/dist/attachmentSeatCapability.js +0 -1
  23. package/dist/callerCwdCapability.d.ts +2 -4
  24. package/dist/callerCwdCapability.js +0 -7
  25. package/dist/centerWiringCapability.d.ts +2 -4
  26. package/dist/centerWiringCapability.js +4 -9
  27. package/dist/classifierVerdictWire.js +4 -16
  28. package/dist/clientSlice.js +1 -1
  29. package/dist/cloudConfigWireCaps.d.ts +4 -4
  30. package/dist/cloudConfigWireCaps.js +241 -85
  31. package/dist/coreValuePorts.d.ts +11 -7
  32. package/dist/coreValuePorts.js +5 -8
  33. package/dist/credentialProvenance.d.ts +28 -0
  34. package/dist/credentialProvenance.js +76 -0
  35. package/dist/decideFailureNote.js +3 -1
  36. package/dist/decideReceipt.js +7 -10
  37. package/dist/declared/supportFloorDeclared.js +3 -3
  38. package/dist/deviceExecutorManagementCapability.d.ts +2 -0
  39. package/dist/deviceExecutorManagementCapability.js +3 -2
  40. package/dist/displayUntrusted.d.ts +10 -3
  41. package/dist/displayUntrusted.js +371 -60
  42. package/dist/engineCapReader.d.ts +8 -2
  43. package/dist/engineCapReader.js +15 -7
  44. package/dist/engineCapsCache.d.ts +17 -3
  45. package/dist/engineCapsCache.js +268 -43
  46. package/dist/engineDirectory.d.ts +79 -0
  47. package/dist/engineDirectory.js +186 -0
  48. package/dist/engineErrorCodes.d.ts +17 -1
  49. package/dist/engineErrorCodes.js +46 -16
  50. package/dist/engineNoticeCodes.d.ts +17 -0
  51. package/dist/engineNoticeCodes.js +54 -1
  52. package/dist/executionLaneCapability.d.ts +2 -0
  53. package/dist/executionLaneCapability.js +3 -2
  54. package/dist/fileHistoryCaptureCapability.d.ts +16 -0
  55. package/dist/fileHistoryCaptureCapability.js +98 -2
  56. package/dist/fileHistoryLegNotice.js +4 -0
  57. package/dist/fleet/bgSubagentRow.d.ts +15 -0
  58. package/dist/fleet/bgSubagentRow.js +67 -0
  59. package/dist/fleet/fleetLedger.d.ts +1 -0
  60. package/dist/fleet/fleetLedger.js +164 -126
  61. package/dist/fleet/fleetProjection.d.ts +1 -0
  62. package/dist/fleet/fleetProjection.js +10 -3
  63. package/dist/fleetTaskDesc.js +4 -17
  64. package/dist/gateOutcome.d.ts +12 -1
  65. package/dist/gateOutcome.js +56 -1
  66. package/dist/gateVocabulary.d.ts +2 -2
  67. package/dist/gateVocabulary.js +4 -3
  68. package/dist/generated/engineAskFaceTables.js +1 -1
  69. package/dist/generated/engineFactTables.d.ts +5 -3
  70. package/dist/generated/engineFactTables.js +17 -4
  71. package/dist/generated/engineNoticeTables.js +12 -3
  72. package/dist/generated/sdkFactTables.d.ts +2 -0
  73. package/dist/generated/sdkFactTables.js +7 -0
  74. package/dist/generated/settingsSchemaFns.d.ts +3 -0
  75. package/dist/generated/settingsSchemaFns.js +53 -0
  76. package/dist/generated/toolNameTables.d.ts +1 -1
  77. package/dist/generated/toolNameTables.js +1 -42
  78. package/dist/hitl/approvalDecisionNoteAudit.js +2 -2
  79. package/dist/hitl/approvalEndingCause.d.ts +2 -0
  80. package/dist/hitl/approvalEndingCause.js +26 -21
  81. package/dist/hitl/approvalHolds.d.ts +16 -1
  82. package/dist/hitl/approvalHolds.js +52 -12
  83. package/dist/hitl/askGateWire.js +1 -0
  84. package/dist/hitl/askParkRowRouting.js +1 -1
  85. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  86. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  87. package/dist/hitl/decisionConflict.js +4 -5
  88. package/dist/hitl/decisionRefusal.d.ts +8 -0
  89. package/dist/hitl/decisionRefusal.js +55 -14
  90. package/dist/hitl/frameRouter.d.ts +3 -1
  91. package/dist/hitl/frameRouter.js +58 -39
  92. package/dist/hitl/gateLedger.d.ts +0 -5
  93. package/dist/hitl/gateLedger.js +0 -21
  94. package/dist/hitl/hitlBridge.d.ts +6 -2
  95. package/dist/hitl/hitlBridge.js +46 -11
  96. package/dist/hitl/localAllowRule.d.ts +1 -0
  97. package/dist/hitl/localAllowRule.js +2 -2
  98. package/dist/hitl/noAllowCause.d.ts +2 -1
  99. package/dist/hitl/noAllowCause.js +3 -2
  100. package/dist/hitl/parkResolver.d.ts +1 -0
  101. package/dist/hitl/parkResolver.js +14 -56
  102. package/dist/hitl/parkRowBirthWait.d.ts +1 -0
  103. package/dist/hitl/parkRowBirthWait.js +2 -2
  104. package/dist/hitl/planReviewWire.d.ts +1 -0
  105. package/dist/hitl/planReviewWire.js +98 -20
  106. package/dist/hitl/replayedApproval.d.ts +3 -3
  107. package/dist/hitl/replayedApproval.js +21 -16
  108. package/dist/hitl/sessionPolicyDeliverable.d.ts +0 -1
  109. package/dist/hitl/sessionPolicyDeliverable.js +17 -17
  110. package/dist/hitl/sessionPolicyWire.d.ts +2 -0
  111. package/dist/hitl/sessionPolicyWire.js +15 -3
  112. package/dist/hitl/standingRows.d.ts +19 -0
  113. package/dist/hitl/standingRows.js +148 -0
  114. package/dist/hitl/suspendedReopen.js +2 -1
  115. package/dist/hitl/toolApprovalWire.d.ts +22 -10
  116. package/dist/hitl/toolApprovalWire.js +286 -47
  117. package/dist/hookMatcherVerdict.d.ts +1 -9
  118. package/dist/hookMatcherVerdict.js +21 -61
  119. package/dist/hooksWireCaps.js +2 -2
  120. package/dist/hostCallbackGuard.d.ts +1 -0
  121. package/dist/hostCallbackGuard.js +6 -0
  122. package/dist/index.d.ts +26 -12
  123. package/dist/index.js +20 -10
  124. package/dist/liveModelCatalog.d.ts +13 -0
  125. package/dist/liveModelCatalog.js +56 -0
  126. package/dist/mcpProbeCapability.d.ts +2 -4
  127. package/dist/mcpProbeCapability.js +3 -8
  128. package/dist/mcpWireCaps.d.ts +1 -0
  129. package/dist/mcpWireCaps.js +4 -1
  130. package/dist/memoryComplianceCapability.d.ts +2 -4
  131. package/dist/memoryComplianceCapability.js +3 -8
  132. package/dist/memoryOriginCapability.d.ts +2 -4
  133. package/dist/memoryOriginCapability.js +3 -8
  134. package/dist/memorySpecWire.js +2 -2
  135. package/dist/modelIdentityResolvability.d.ts +2 -2
  136. package/dist/modelIdentityResolvability.js +14 -5
  137. package/dist/noticePresentation.d.ts +68 -0
  138. package/dist/noticePresentation.js +247 -0
  139. package/dist/outputStyleSeatCapability.d.ts +2 -1
  140. package/dist/outputStyleSeatCapability.js +0 -4
  141. package/dist/ownKey.d.ts +2 -0
  142. package/dist/ownKey.js +18 -0
  143. package/dist/peerFrames.js +13 -9
  144. package/dist/peerLaneCapability.d.ts +2 -0
  145. package/dist/peerLaneCapability.js +3 -2
  146. package/dist/permissionModesCapability.d.ts +2 -4
  147. package/dist/permissionModesCapability.js +0 -7
  148. package/dist/permissionRuleIssue.js +0 -3
  149. package/dist/permissionRulesWriteCapability.d.ts +2 -0
  150. package/dist/permissionRulesWriteCapability.js +3 -2
  151. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  152. package/dist/permissionsAllowGrantCapability.js +4 -2
  153. package/dist/permissionsDeclaration.d.ts +2 -0
  154. package/dist/permissionsDeclaration.js +72 -59
  155. package/dist/printPermissionOutcome.d.ts +12 -0
  156. package/dist/printPermissionOutcome.js +50 -0
  157. package/dist/printToolResultFrame.d.ts +1 -0
  158. package/dist/printToolResultFrame.js +5 -9
  159. package/dist/promptBlockedCopy.d.ts +5 -0
  160. package/dist/promptBlockedCopy.js +35 -0
  161. package/dist/protocolSegment.d.ts +9 -6
  162. package/dist/protocolSegment.js +31 -21
  163. package/dist/providerRejection.d.ts +4 -0
  164. package/dist/providerRejection.js +75 -14
  165. package/dist/replayBody.d.ts +12 -0
  166. package/dist/replayBody.js +25 -0
  167. package/dist/request/taskRequest.d.ts +1 -4
  168. package/dist/request/taskRequest.js +74 -19
  169. package/dist/resumeRefusalCopy.js +9 -25
  170. package/dist/retryStatus.d.ts +9 -1
  171. package/dist/retryStatus.js +9 -2
  172. package/dist/rewindArchiveCapability.d.ts +3 -13
  173. package/dist/rewindArchiveCapability.js +12 -58
  174. package/dist/rewindArchiveReading.d.ts +17 -0
  175. package/dist/rewindArchiveReading.js +55 -0
  176. package/dist/ruleCompileVerdict.d.ts +9 -1
  177. package/dist/ruleCompileVerdict.js +29 -4
  178. package/dist/runTerminal.d.ts +48 -5
  179. package/dist/runTerminal.js +215 -12
  180. package/dist/sandboxWire.js +13 -0
  181. package/dist/sdkWireTransit.d.ts +2 -1
  182. package/dist/sdkWireTransit.js +1 -0
  183. package/dist/sealedKeyCapability.d.ts +2 -4
  184. package/dist/sealedKeyCapability.js +0 -7
  185. package/dist/seam.js +4 -2
  186. package/dist/seatContract.d.ts +76 -11
  187. package/dist/seatContract.js +222 -27
  188. package/dist/sessionPolicyCapability.d.ts +2 -0
  189. package/dist/sessionPolicyCapability.js +3 -2
  190. package/dist/sessionReadRedactionCapability.d.ts +2 -4
  191. package/dist/sessionReadRedactionCapability.js +0 -7
  192. package/dist/sessionSyncFileHistory.d.ts +4 -0
  193. package/dist/sessionSyncFileHistory.js +66 -0
  194. package/dist/sessionSyncFileHistoryCapability.d.ts +2 -1
  195. package/dist/sessionSyncFileHistoryCapability.js +3 -5
  196. package/dist/skillToolListsCapability.d.ts +2 -1
  197. package/dist/skillToolListsCapability.js +3 -5
  198. package/dist/sqlEngineCapability.d.ts +2 -0
  199. package/dist/sqlEngineCapability.js +4 -3
  200. package/dist/steerPriorityCapability.d.ts +2 -1
  201. package/dist/steerPriorityCapability.js +3 -5
  202. package/dist/storePostureCapability.d.ts +2 -4
  203. package/dist/storePostureCapability.js +4 -9
  204. package/dist/subagent/engineSubagentSteer.js +7 -5
  205. package/dist/subagentContentStore.js +4 -15
  206. package/dist/supportFloor.d.ts +1 -0
  207. package/dist/supportFloor.js +13 -9
  208. package/dist/systemReminderTag.d.ts +1 -0
  209. package/dist/systemReminderTag.js +8 -0
  210. package/dist/toolHistoryBadTurn.js +3 -2
  211. package/dist/toolHistoryMismatch.d.ts +0 -3
  212. package/dist/toolHistoryMismatch.js +3 -7
  213. package/dist/toolResult.d.ts +12 -1
  214. package/dist/toolResult.js +55 -1
  215. package/dist/toolResultContent.d.ts +2 -0
  216. package/dist/toolResultContent.js +63 -0
  217. package/dist/usageAnalytics.js +40 -16
  218. package/dist/userInvokedSkillsCapability.d.ts +2 -1
  219. package/dist/userInvokedSkillsCapability.js +3 -5
  220. package/dist/webSearchBackendCapability.d.ts +2 -0
  221. package/dist/webSearchBackendCapability.js +3 -2
  222. package/dist/wireErrorTriage.d.ts +2 -0
  223. package/dist/wireErrorTriage.js +91 -38
  224. package/dist/wireFailureShape.d.ts +20 -3
  225. package/dist/wireFailureShape.js +128 -72
  226. package/dist/workflowsJournalCapability.d.ts +2 -0
  227. package/dist/workflowsJournalCapability.js +3 -2
  228. package/dist/writeProtectionCapability.d.ts +2 -0
  229. package/dist/writeProtectionCapability.js +3 -2
  230. package/docs/INTEGRATION-CLIENTS.md +1784 -20
  231. package/package.json +5 -5
  232. package/dist/engineRefusedToolName.d.ts +0 -1
  233. package/dist/engineRefusedToolName.js +0 -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,11 +21,7 @@ export function projectMemoryComplianceCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'memoryCompliance',
23
23
  project: projectMemoryComplianceCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
- export function noteEngineCapsForMemoryCompliance(baseUrl, caps, opts) {
27
- reader.note(baseUrl, caps, opts);
28
- }
29
25
  export function observedMemoryCompliance(baseUrl = engineWireTarget()?.baseUrl) {
30
26
  return reader.observed(baseUrl);
31
27
  }
@@ -38,6 +34,8 @@ export function memoryComplianceVerbsAvailable(reading) {
38
34
  }
39
35
  export function memoryComplianceDoctorDetail(reading) {
40
36
  switch (reading.kind) {
37
+ case 'unreadable':
38
+ return unreadableReadingDetail('memory compliance face');
41
39
  case 'unobserved':
42
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)';
43
41
  case 'not_reported':
@@ -48,6 +46,3 @@ export function memoryComplianceDoctorDetail(reading) {
48
46
  return 'memory compliance face on — the entry-provenance and erasure endpoints are mounted and the operator roster is non-empty on this engine; this says the door opens, not that any one call succeeds (a damaged control-plane ledger is a runtime fact and arrives as a typed 500)';
49
47
  }
50
48
  }
51
- export function __resetMemoryComplianceReadingsForTests() {
52
- reader.__resetForTests();
53
- }
@@ -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
  } | {
@@ -8,10 +10,6 @@ export type MemoryOriginReading = {
8
10
  kind: 'present';
9
11
  };
10
12
  export declare function projectMemoryOriginCapability(caps: unknown): MemoryOriginReading | undefined;
11
- export declare function noteEngineCapsForMemoryOrigin(baseUrl: string, caps: unknown, opts?: {
12
- generation?: number;
13
- }): void;
14
13
  export declare function observedMemoryOrigin(baseUrl?: string | undefined): MemoryOriginReading;
15
14
  export declare function memoryOriginVerbsAvailable(reading: MemoryOriginReading): 'yes' | 'no' | 'unknown';
16
15
  export declare function memoryOriginDoctorDetail(reading: MemoryOriginReading): string;
17
- export declare function __resetMemoryOriginReadingsForTests(): void;
@@ -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,11 +21,7 @@ export function projectMemoryOriginCapability(caps) {
21
21
  const reader = createEngineCapReader({
22
22
  name: 'memoryOrigin',
23
23
  project: projectMemoryOriginCapability,
24
- makeUnobserved: () => ({ kind: 'unobserved' }),
25
24
  });
26
- export function noteEngineCapsForMemoryOrigin(baseUrl, caps, opts) {
27
- reader.note(baseUrl, caps, opts);
28
- }
29
25
  export function observedMemoryOrigin(baseUrl = engineWireTarget()?.baseUrl) {
30
26
  return reader.observed(baseUrl);
31
27
  }
@@ -38,6 +34,8 @@ export function memoryOriginVerbsAvailable(reading) {
38
34
  }
39
35
  export function memoryOriginDoctorDetail(reading) {
40
36
  switch (reading.kind) {
37
+ case 'unreadable':
38
+ return unreadableReadingDetail('external-origin face');
41
39
  case 'unobserved':
42
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)';
43
41
  case 'not_reported':
@@ -48,6 +46,3 @@ export function memoryOriginDoctorDetail(reading) {
48
46
  return 'external-origin face on — the origin listing and clearance endpoints are mounted and the operator roster is non-empty on this engine; this says the door opens, not that any one call succeeds, and it says nothing about the listing being complete (the engine has no scope-enumeration read face, so an answer covers only the scopes the caller named and an empty one must never be read as a clean store)';
49
47
  }
50
48
  }
51
- export function __resetMemoryOriginReadingsForTests() {
52
- reader.__resetForTests();
53
- }
@@ -1,5 +1,5 @@
1
1
  import { CONFIG_MEMORY_PROJECT_KEY_SPELLING, CONFIG_MEMORY_WRITE_SCOPE_MISMATCH } from './engineErrorCodes.js';
2
- import { capForDisplay } from './fleetTaskDesc.js';
2
+ import { escapeDisplayControlChars } from './fleetTaskDesc.js';
3
3
  export const KNOWN_AGENT_MEMORY_SPEC_KEYS = Object.freeze([
4
4
  'enabled',
5
5
  'scopeContract',
@@ -128,7 +128,7 @@ function quotedKeyForDisplay(key) {
128
128
  let out = '';
129
129
  let truncated = false;
130
130
  for (const ch of key) {
131
- const tok = ch === '\\' ? '\\\\' : ch === '"' ? '\\"' : capForDisplay(ch, 8);
131
+ const tok = ch === '\\' ? '\\\\' : ch === '"' ? '\\"' : escapeDisplayControlChars(ch);
132
132
  if (out.length + tok.length > MEMORY_SPEC_KEY_DISPLAY_MAX) {
133
133
  truncated = true;
134
134
  break;
@@ -25,10 +25,10 @@ export interface RemoteEngineModelIdentityReading {
25
25
  }
26
26
  export type ModelIdentityReading = LocalEngineModelIdentityReading | RemoteEngineModelIdentityReading;
27
27
  export type ModelIdentityResolvability = 'resolvable' | 'not_resolvable' | 'unknown';
28
- export type ModelSetupDecision = 'offer_setup' | 'explain_unconfigured' | 'explain_route_gap' | 'proceed';
28
+ export type ModelSetupDecision = 'offer_setup' | 'offer_route_setup' | 'explain_unconfigured' | 'explain_route_gap' | 'explain_route_unconfigurable' | 'proceed';
29
29
  export declare function modelIdentityResolvability(reading: ModelIdentityReading): ModelIdentityResolvability;
30
30
  export declare function modelIdentityDetail(reading: ModelIdentityReading): string;
31
- export declare function modelSetupDecision(reading: ModelIdentityReading, hostCanConfigureModel: boolean): ModelSetupDecision;
31
+ export declare function modelSetupDecision(reading: ModelIdentityReading, hostCanConfigureModel: boolean, hostCanConfigureRoute?: boolean): ModelSetupDecision;
32
32
  export declare function modelSetupNotice(decision: ModelSetupDecision): string | null;
33
33
  export declare function observedEngineAnswer(baseUrl?: string | undefined): EngineAnswerObservation;
34
34
  export {};
@@ -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'];
@@ -178,10 +178,15 @@ export function modelIdentityDetail(reading) {
178
178
  return "this reading of the engine's model configuration could not be interpreted, so whether the engine has a model name is unknown";
179
179
  }
180
180
  }
181
- export function modelSetupDecision(reading, hostCanConfigureModel) {
181
+ export function modelSetupDecision(reading, hostCanConfigureModel, hostCanConfigureRoute) {
182
182
  const a = analyse(reading);
183
- if (a.verdict === 'not_resolvable' && a.why === 'route_gap')
183
+ if (a.verdict === 'not_resolvable' && a.why === 'route_gap') {
184
+ if (hostCanConfigureRoute === true)
185
+ return 'offer_route_setup';
186
+ if (hostCanConfigureRoute === false)
187
+ return 'explain_route_unconfigurable';
184
188
  return 'explain_route_gap';
189
+ }
185
190
  if (a.verdict !== 'not_resolvable')
186
191
  return 'proceed';
187
192
  if (hostCanConfigureModel === true)
@@ -196,12 +201,16 @@ export function modelSetupNotice(decision) {
196
201
  return 'no model name is configured for the engine, and it does not start without one — opening model setup';
197
202
  case 'explain_unconfigured':
198
203
  return 'this engine has no model configured, and it cannot be configured from this client — ask whoever runs the engine to set one up';
204
+ case 'offer_route_setup':
205
+ return 'a model name is configured, but the engine has no route to reach that model (a gateway address, or credentials for the channel it names), so it does not start — opening setup so the route can be set';
199
206
  case 'explain_route_gap':
200
- return 'a model name is configured, but the engine has no route to reach that model (a gateway address, or credentials for the channel it names), so it does not start — fix the route; model setup will not help';
207
+ return 'a model name is configured, but the engine has no route to reach that model (a gateway address, or credentials for the channel it names), so it does not start — the route has to be set before the engine can run';
208
+ case 'explain_route_unconfigurable':
209
+ return 'a model name is configured, but the engine has no route to reach that model (a gateway address, or credentials for the channel it names), so it does not start — the route cannot be set from this client; ask whoever runs the engine to set it';
201
210
  default:
202
211
  return null;
203
212
  }
204
213
  }
205
214
  export function observedEngineAnswer(baseUrl = engineWireTarget()?.baseUrl) {
206
- return engineCapValue(baseUrl, 'version').kind === 'unobserved' ? 'not_observed' : 'answered';
215
+ return engineCapsLandingOf(baseUrl).kind === 'not_landed' ? 'not_observed' : 'answered';
207
216
  }
@@ -0,0 +1,68 @@
1
+ export interface CutForDisplayOptions {
2
+ readonly boundary?: 'sentence' | 'word';
3
+ readonly mark?: string;
4
+ }
5
+ export interface CutForDisplayResult {
6
+ readonly shown: string;
7
+ readonly truncated: boolean;
8
+ }
9
+ export declare function cutForDisplay(text: string, budget: number, opts?: CutForDisplayOptions): CutForDisplayResult;
10
+ export type NoticePresentationAudience = 'user' | 'operator' | 'unknown';
11
+ export type NoticeOutlet = 'present' | 'absent' | 'unknown';
12
+ export type NoticeOccurrence = {
13
+ readonly kind: 'event';
14
+ } | {
15
+ readonly kind: 'occurrence';
16
+ readonly key: string;
17
+ };
18
+ export type NoticeSeatClass = 'persistent+hint' | 'persistent' | 'transient' | 'diagnostic';
19
+ export type NoticeDeployment = 'local' | 'remote';
20
+ export interface NoticePresentationFacts {
21
+ readonly audience: NoticePresentationAudience;
22
+ readonly outlet: NoticeOutlet;
23
+ readonly remedy?: string;
24
+ readonly occurrence: NoticeOccurrence;
25
+ readonly seatClass: NoticeSeatClass;
26
+ }
27
+ export declare const NOTICE_SEAT_CLASS: Readonly<Record<NoticePresentationAudience, Readonly<Record<NoticeOutlet, NoticeSeatClass>>>>;
28
+ export declare function noticeSeatClassOf(audience: NoticePresentationAudience, outlet: NoticeOutlet, deployment?: NoticeDeployment): NoticeSeatClass;
29
+ export declare function noticePresentationFacts(notice: unknown, deployment?: NoticeDeployment): NoticePresentationFacts;
30
+ export declare const OUTLET_KINDS: readonly ["slash-command", "cli-command", "http-endpoint", "env-or-settings", "operator-action", "identity-attach", "resend"];
31
+ export type OutletKind = (typeof OUTLET_KINDS)[number];
32
+ export type OutletReachability = 'reachable' | 'unreachable' | 'unknown';
33
+ export interface OutletReachabilityContext {
34
+ readonly lane: 'interactive' | 'print';
35
+ readonly engineManagedByShell: 'shell' | 'external-local' | 'remote' | 'unknown';
36
+ readonly configScope: 'reaches-engine' | 'does-not' | 'unknown';
37
+ readonly endpointAccess: 'has-address-and-credential' | 'no' | 'unknown';
38
+ readonly operator: 'present' | 'absent' | 'unknown';
39
+ readonly identityAttach: 'available' | 'unavailable' | 'unknown';
40
+ }
41
+ export declare function outletReachable(kind: OutletKind, ctx: OutletReachabilityContext): OutletReachability;
42
+ export interface OutletRenderingPlan {
43
+ readonly state: readonly OutletKind[];
44
+ readonly confirm: readonly OutletKind[];
45
+ readonly omit: readonly OutletKind[];
46
+ readonly nothingReachable: boolean;
47
+ }
48
+ export declare function planOutletRendering(kinds: readonly OutletKind[], ctx: OutletReachabilityContext): OutletRenderingPlan;
49
+ export declare const NOTICE_FULL_TEXT_CAP = 8000;
50
+ export type NoticePrintCarrier = {
51
+ readonly kind: 'transcript';
52
+ readonly sessionId: string;
53
+ } | {
54
+ readonly kind: 'stream-json';
55
+ } | {
56
+ readonly kind: 'none';
57
+ };
58
+ export interface NoticePrintLineInput {
59
+ readonly text: string | undefined;
60
+ readonly audience: NoticePresentationAudience;
61
+ readonly code?: string;
62
+ readonly carrier: NoticePrintCarrier;
63
+ }
64
+ export interface NoticePrintLineResult {
65
+ readonly line: string;
66
+ readonly truncated: boolean;
67
+ }
68
+ export declare function noticePrintLine(input: NoticePrintLineInput): NoticePrintLineResult;
@@ -0,0 +1,247 @@
1
+ import { capDisplay, DISPLAY_CAP_BACKOFF_MAX, displayUntrusted } from './displayUntrusted.js';
2
+ import { engineNoticeInCatalog, noticeAudienceOf } from './engineNoticeCodes.js';
3
+ import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
4
+ const DEFAULT_CUT_MARK = '…';
5
+ const CUT_KEEP_DIVISOR = 2;
6
+ const CLOSERS = '"\')]”’」』)】';
7
+ const ASCII_SENTENCE_END = '.!?;';
8
+ const WIDE_SENTENCE_END = '。!?;';
9
+ const ASCII_CLAUSE_END = ',:';
10
+ const WIDE_CLAUSE_END = ',、:';
11
+ const EM_DASH = '—';
12
+ function isWs(c) {
13
+ return c !== '' && c.trim() === '';
14
+ }
15
+ function readCutOptions(opts) {
16
+ if (typeof opts !== 'object' || opts === null)
17
+ return { boundary: 'sentence', mark: DEFAULT_CUT_MARK };
18
+ const boundary = guardedRead(() => readOwnDataValue(opts, 'boundary'));
19
+ const mark = guardedRead(() => readOwnDataValue(opts, 'mark'));
20
+ return { boundary: boundary === 'word' ? 'word' : 'sentence', mark: typeof mark === 'string' ? mark : DEFAULT_CUT_MARK };
21
+ }
22
+ function sentenceEndAt(src, j) {
23
+ const c = src.charAt(j);
24
+ const ascii = ASCII_SENTENCE_END.includes(c);
25
+ if (!ascii && !WIDE_SENTENCE_END.includes(c))
26
+ return -1;
27
+ let k = j + 1;
28
+ while (k < src.length && CLOSERS.includes(src.charAt(k)))
29
+ k++;
30
+ if (!ascii)
31
+ return k;
32
+ return k === src.length || isWs(src.charAt(k)) ? k : -1;
33
+ }
34
+ function clauseEndAt(src, j) {
35
+ const c = src.charAt(j);
36
+ if (WIDE_CLAUSE_END.includes(c))
37
+ return j + 1;
38
+ if (!ASCII_CLAUSE_END.includes(c))
39
+ return -1;
40
+ const k = j + 1;
41
+ return k === src.length || isWs(src.charAt(k)) ? k : -1;
42
+ }
43
+ function pickCut(src, limit, floor, boundary) {
44
+ let sentence = -1;
45
+ let clause = -1;
46
+ let word = -1;
47
+ for (let j = 0; j <= limit && j < src.length; j++) {
48
+ const c = src.charAt(j);
49
+ if (isWs(c)) {
50
+ word = j;
51
+ continue;
52
+ }
53
+ if (boundary === 'word')
54
+ continue;
55
+ const s = sentenceEndAt(src, j);
56
+ if (s !== -1 && s <= limit)
57
+ sentence = Math.max(sentence, s);
58
+ else if (c === EM_DASH && j > 0 && isWs(src.charAt(j - 1)) && (j + 1 === src.length || isWs(src.charAt(j + 1))))
59
+ sentence = Math.max(sentence, j);
60
+ const k = clauseEndAt(src, j);
61
+ if (k !== -1 && k <= limit)
62
+ clause = Math.max(clause, k);
63
+ }
64
+ if (boundary === 'word')
65
+ return word >= floor ? word : -1;
66
+ if (sentence >= floor)
67
+ return sentence;
68
+ if (clause >= floor)
69
+ return clause;
70
+ return word >= floor ? word : -1;
71
+ }
72
+ export function cutForDisplay(text, budget, opts) {
73
+ const src = typeof text === 'string' ? text : (guardedRead(() => String(text ?? '')) ?? '');
74
+ if (typeof budget !== 'number' || !Number.isFinite(budget))
75
+ return { shown: src, truncated: false };
76
+ const limit = Math.max(0, Math.floor(budget));
77
+ if (src.length <= limit)
78
+ return { shown: src, truncated: false };
79
+ const { boundary, mark } = readCutOptions(opts);
80
+ const room = limit - mark.length;
81
+ if (room <= 0)
82
+ return { shown: limit >= mark.length ? mark : '', truncated: true };
83
+ const head = capDisplay(src, room, 'escape');
84
+ const floor = Math.ceil(head.length / CUT_KEEP_DIVISOR);
85
+ const at = pickCut(src, head.length, floor, boundary);
86
+ const kept = (at === -1 ? head : src.slice(0, at)).trimEnd();
87
+ return { shown: `${kept}${mark}`, truncated: true };
88
+ }
89
+ export const NOTICE_SEAT_CLASS = Object.freeze({
90
+ user: Object.freeze({ present: 'persistent+hint', unknown: 'persistent+hint', absent: 'persistent' }),
91
+ operator: Object.freeze({ present: 'transient', unknown: 'transient', absent: 'diagnostic' }),
92
+ unknown: Object.freeze({ present: 'persistent+hint', unknown: 'persistent+hint', absent: 'persistent+hint' }),
93
+ });
94
+ export function noticeSeatClassOf(audience, outlet, deployment) {
95
+ const a = typeof audience === 'string' && Object.hasOwn(NOTICE_SEAT_CLASS, audience) ? audience : 'unknown';
96
+ const row = a === 'operator' && deployment !== 'remote' ? NOTICE_SEAT_CLASS.user : NOTICE_SEAT_CLASS[a];
97
+ return typeof outlet === 'string' && Object.hasOwn(row, outlet) ? row[outlet] : row.unknown;
98
+ }
99
+ const OCCURRENCE_KEY_BY_CODE = Object.freeze({
100
+ 'memory.harvest_quarantined': 'reportId',
101
+ 'memory.hold_opened': 'reportId',
102
+ 'memory.hold_released': 'reportId',
103
+ 'memory.hold_disposed': 'reportId',
104
+ 'memory.consolidation_recommended': 'reportId',
105
+ 'memory.capture_opted_out': 'sessionId',
106
+ 'memory.delegation_static_mark_waived': 'runId',
107
+ });
108
+ export function noticePresentationFacts(notice, deployment) {
109
+ const envelope = typeof notice === 'object' && notice !== null ? notice : undefined;
110
+ const code = guardedRead(() => readOwnDataValue(envelope, 'code'));
111
+ const detailRaw = guardedRead(() => readOwnDataValue(envelope, 'detail'));
112
+ const detail = typeof detailRaw === 'object' && detailRaw !== null ? detailRaw : undefined;
113
+ const audience = typeof code === 'string' && engineNoticeInCatalog(code) ? noticeAudienceOf(code) : 'unknown';
114
+ const remedy = guardedRead(() => readOwnDataNonEmptyString(detail, 'remedy'));
115
+ const outlet = remedy !== undefined ? 'present' : 'unknown';
116
+ const keyField = typeof code === 'string' && Object.hasOwn(OCCURRENCE_KEY_BY_CODE, code) ? OCCURRENCE_KEY_BY_CODE[code] : undefined;
117
+ const identity = keyField !== undefined ? guardedRead(() => readOwnDataNonEmptyString(detail, keyField)) : undefined;
118
+ const occurrence = Object.freeze(identity !== undefined ? { kind: 'occurrence', key: identity } : { kind: 'event' });
119
+ return Object.freeze({
120
+ audience,
121
+ outlet,
122
+ ...(remedy !== undefined ? { remedy } : {}),
123
+ occurrence,
124
+ seatClass: noticeSeatClassOf(audience, outlet, deployment),
125
+ });
126
+ }
127
+ export const OUTLET_KINDS = Object.freeze(['slash-command', 'cli-command', 'http-endpoint', 'env-or-settings', 'operator-action', 'identity-attach', 'resend']);
128
+ function ctxSlot(ctx, key, closed) {
129
+ if (typeof ctx !== 'object' || ctx === null)
130
+ return undefined;
131
+ const v = guardedRead(() => readOwnDataValue(ctx, key));
132
+ return typeof v === 'string' && closed.includes(v) ? v : undefined;
133
+ }
134
+ function isOutletKind(v) {
135
+ return typeof v === 'string' && OUTLET_KINDS.includes(v);
136
+ }
137
+ export function outletReachable(kind, ctx) {
138
+ if (!isOutletKind(kind))
139
+ return 'unknown';
140
+ const lane = ctxSlot(ctx, 'lane', ['interactive', 'print']);
141
+ switch (kind) {
142
+ case 'resend':
143
+ case 'cli-command':
144
+ return 'reachable';
145
+ case 'slash-command':
146
+ return lane === 'interactive' ? 'reachable' : lane === 'print' ? 'unreachable' : 'unknown';
147
+ case 'http-endpoint': {
148
+ if (lane === 'interactive')
149
+ return 'unreachable';
150
+ const access = ctxSlot(ctx, 'endpointAccess', ['has-address-and-credential', 'no', 'unknown']);
151
+ if (access === 'no')
152
+ return 'unreachable';
153
+ return lane === 'print' && access === 'has-address-and-credential' ? 'reachable' : 'unknown';
154
+ }
155
+ case 'env-or-settings': {
156
+ const scope = ctxSlot(ctx, 'configScope', ['reaches-engine', 'does-not', 'unknown']);
157
+ if (scope === 'reaches-engine')
158
+ return 'reachable';
159
+ if (scope === 'does-not')
160
+ return 'unreachable';
161
+ return ctxSlot(ctx, 'engineManagedByShell', ['shell', 'external-local', 'remote', 'unknown']) === 'remote' ? 'unreachable' : 'unknown';
162
+ }
163
+ case 'operator-action': {
164
+ const op = ctxSlot(ctx, 'operator', ['present', 'absent', 'unknown']);
165
+ return op === 'present' ? 'reachable' : op === 'absent' ? 'unreachable' : 'unknown';
166
+ }
167
+ case 'identity-attach': {
168
+ const id = ctxSlot(ctx, 'identityAttach', ['available', 'unavailable', 'unknown']);
169
+ return id === 'available' ? 'reachable' : id === 'unavailable' ? 'unreachable' : 'unknown';
170
+ }
171
+ }
172
+ }
173
+ const LIST_READ_MAX = 64;
174
+ function listItems(list) {
175
+ if (guardedRead(() => (Array.isArray(list) ? true : undefined)) !== true)
176
+ return [];
177
+ const length = guardedRead(() => readOwnDataValue(list, 'length'));
178
+ if (typeof length !== 'number' || !Number.isInteger(length) || length < 0)
179
+ return [];
180
+ const out = [];
181
+ for (let i = 0; i < Math.min(length, LIST_READ_MAX); i++) {
182
+ const item = guardedRead(() => readOwnDataValue(list, String(i)));
183
+ if (item !== undefined)
184
+ out.push(item);
185
+ }
186
+ return out;
187
+ }
188
+ export function planOutletRendering(kinds, ctx) {
189
+ const state = [];
190
+ const confirm = [];
191
+ const omit = [];
192
+ const seen = new Set();
193
+ for (const k of listItems(kinds)) {
194
+ if (!isOutletKind(k) || seen.has(k))
195
+ continue;
196
+ seen.add(k);
197
+ const r = outletReachable(k, ctx);
198
+ (r === 'reachable' ? state : r === 'unknown' ? confirm : omit).push(k);
199
+ }
200
+ return Object.freeze({
201
+ state: Object.freeze(state),
202
+ confirm: Object.freeze(confirm),
203
+ omit: Object.freeze(omit),
204
+ nothingReachable: state.length === 0 && confirm.length === 0 && omit.length > 0,
205
+ });
206
+ }
207
+ export const NOTICE_FULL_TEXT_CAP = 8000;
208
+ const PRINT_BUDGET_USER = 1000;
209
+ const PRINT_BUDGET_OPERATOR = 160;
210
+ const PRINT_PREFIX = '[sema] ';
211
+ const NO_TEXT_SENTENCE = 'Engine notice (no message text)';
212
+ const PRINT_CODE_MAX = 160;
213
+ const PRE_CAP_SLACK = DISPLAY_CAP_BACKOFF_MAX + 1;
214
+ const PRINT_SESSION_MAX = 128;
215
+ function lineFace(max) {
216
+ return max === undefined ? { foldLines: false, mark: 'dot' } : { foldLines: false, mark: 'dot', max };
217
+ }
218
+ function printCarrier(carrier) {
219
+ if (typeof carrier !== 'object' || carrier === null)
220
+ return { kind: 'none' };
221
+ const kind = guardedRead(() => readOwnDataValue(carrier, 'kind'));
222
+ if (kind === 'stream-json')
223
+ return { kind };
224
+ if (kind === 'transcript') {
225
+ const sid = guardedRead(() => readOwnDataNonEmptyString(carrier, 'sessionId'));
226
+ if (sid !== undefined) {
227
+ const shown = displayUntrusted(sid, lineFace(PRINT_SESSION_MAX));
228
+ if (shown !== '')
229
+ return { kind, sessionId: shown };
230
+ }
231
+ }
232
+ return { kind: 'none' };
233
+ }
234
+ export function noticePrintLine(input) {
235
+ const textRaw = guardedRead(() => readOwnDataValue(input, 'text'));
236
+ const audience = guardedRead(() => readOwnDataValue(input, 'audience'));
237
+ const codeRaw = guardedRead(() => readOwnDataValue(input, 'code'));
238
+ const carrier = printCarrier(guardedRead(() => readOwnDataValue(input, 'carrier')));
239
+ const washed = displayUntrusted(typeof textRaw === 'string' ? textRaw : '', lineFace(NOTICE_FULL_TEXT_CAP + PRE_CAP_SLACK));
240
+ const bounded = cutForDisplay(washed, NOTICE_FULL_TEXT_CAP);
241
+ const text = bounded.shown.trim() === '' ? NO_TEXT_SENTENCE : bounded.shown;
242
+ const budget = audience === 'operator' ? PRINT_BUDGET_OPERATOR : PRINT_BUDGET_USER;
243
+ const cut = carrier.kind === 'none' ? { shown: text, truncated: bounded.truncated } : cutForDisplay(text, budget);
244
+ const where = cut.truncated && carrier.kind === 'transcript' ? ` (full text: sema -r ${carrier.sessionId})` : '';
245
+ const code = typeof codeRaw === 'string' && codeRaw !== '' ? displayUntrusted(codeRaw, lineFace(PRINT_CODE_MAX)) : '';
246
+ return Object.freeze({ line: `${PRINT_PREFIX}${cut.shown}${where}${code !== '' ? ` [${code}]` : ''}`, truncated: cut.truncated });
247
+ }
@@ -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
  } | {
@@ -12,4 +14,3 @@ export declare function noteEngineCapsForOutputStyleSeat(baseUrl: string, caps:
12
14
  }): void;
13
15
  export declare function observedOutputStyleSeat(baseUrl?: string | undefined): OutputStyleSeatReading;
14
16
  export declare function outputStyleSeatAdmitted(reading: OutputStyleSeatReading | undefined): boolean;
15
- export declare function __resetOutputStyleSeatReadingsForTests(): void;
@@ -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);
@@ -37,6 +36,3 @@ export function outputStyleSeatAdmitted(reading) {
37
36
  return false;
38
37
  }
39
38
  }
40
- export function __resetOutputStyleSeatReadingsForTests() {
41
- reader.__resetForTests();
42
- }
package/dist/ownKey.d.ts CHANGED
@@ -3,9 +3,11 @@ export declare function readOwnNonEmptyString(v: object | null | undefined, key:
3
3
  export declare function readAskOrigin(carrier: object | null | undefined): string | undefined;
4
4
  export declare function readOwnDataNonEmptyString(v: object | null | undefined, key: string): string | undefined;
5
5
  export declare function readOwnDataValue<T>(v: object | null | undefined, key: string): T | undefined;
6
+ export declare function guardedRead<T>(read: () => T | undefined): T | undefined;
6
7
  export declare function ownDataField(v: object, key: string): unknown;
7
8
  export declare function ownSendableField(v: object, key: string): unknown;
8
9
  export declare function ownDataRefusalKeyOf(e: unknown): string | undefined;
9
10
  export declare function readFrameStringKey(frame: unknown, key: string): string | undefined;
11
+ export declare function readFrameTrueMark(frame: object | null | undefined, key: string): boolean;
10
12
  export declare function markMcpSectionDroppedNamedRows(rows: object, dropped: ReadonlyMap<string, number>): void;
11
13
  export declare function mcpSectionDroppedNamedRowsOf(rows: object | null | undefined, name: string): number;
package/dist/ownKey.js CHANGED
@@ -25,6 +25,14 @@ export function readOwnDataValue(v, key) {
25
25
  const d = Object.getOwnPropertyDescriptor(v, key);
26
26
  return d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
27
27
  }
28
+ export function guardedRead(read) {
29
+ try {
30
+ return read();
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
28
36
  export function ownDataField(v, key) {
29
37
  return ownField(v, key, false);
30
38
  }
@@ -56,6 +64,16 @@ export function readFrameStringKey(frame, key) {
56
64
  return undefined;
57
65
  }
58
66
  }
67
+ export function readFrameTrueMark(frame, key) {
68
+ try {
69
+ if (frame === null || frame === undefined || typeof frame !== 'object' || Array.isArray(frame))
70
+ return false;
71
+ return ownDataField(frame, key) === true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
59
77
  const DROPPED_NAMED_ROWS = Symbol.for('@sema-agent/client-core/mcpSectionDroppedNamedRows');
60
78
  export function markMcpSectionDroppedNamedRows(rows, dropped) {
61
79
  if (dropped.size === 0)
@@ -1,4 +1,4 @@
1
- import { applyDisplayFace, LEGACY_NAME_FACE } from './displayUntrusted.js';
1
+ import { applyDisplayFace, PEER_NAME_FACE } from './displayUntrusted.js';
2
2
  import { CORE_ENGINE_AUTHORITY_ENVELOPE_TAGS, CORE_PERMISSION_MODE_CLASSES } from './generated/engineFactTables.js';
3
3
  const WIRE_KEY_FROM = 'from';
4
4
  const WIRE_KEY_BODY = 'body';
@@ -42,14 +42,16 @@ function nonEmptyString(v) {
42
42
  function finiteNumber(v) {
43
43
  return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
44
44
  }
45
- function provenanceAgrees(n, lane, carrierFrom) {
45
+ function provenanceAgrees(n, lane, carrierFromRaw) {
46
46
  const prov = dataProp(n, WIRE_KEY_PROVENANCE);
47
47
  if (prov === null || typeof prov !== 'object')
48
48
  return false;
49
49
  if (dataProp(prov, WIRE_KEY_KIND) !== lane)
50
50
  return false;
51
- const provFrom = cleanName(dataProp(prov, WIRE_KEY_FROM));
52
- if (provFrom === undefined || (carrierFrom !== undefined && provFrom !== carrierFrom))
51
+ const provFromRaw = dataProp(prov, WIRE_KEY_FROM);
52
+ if (typeof provFromRaw !== 'string' || cleanName(provFromRaw) === undefined)
53
+ return false;
54
+ if (carrierFromRaw !== undefined && provFromRaw !== carrierFromRaw)
53
55
  return false;
54
56
  const taskId = nonEmptyString(dataProp(n, WIRE_KEY_TASK_ID));
55
57
  if (taskId === undefined || nonEmptyString(dataProp(prov, WIRE_KEY_PROV_TASK_ID)) !== taskId)
@@ -61,9 +63,10 @@ export function classifyPeerNotification(n) {
61
63
  const seq = finiteNumber(dataProp(n, WIRE_KEY_SEQ));
62
64
  const agentMessage = dataProp(n, WIRE_KEY_AGENT_MESSAGE);
63
65
  if (agentMessage !== undefined) {
64
- const from = cleanName(dataProp(agentMessage, WIRE_KEY_FROM));
66
+ const fromRaw = dataProp(agentMessage, WIRE_KEY_FROM);
67
+ const from = cleanName(fromRaw);
65
68
  const body = dataProp(agentMessage, WIRE_KEY_BODY);
66
- if (from !== undefined && typeof body === 'string' && provenanceAgrees(n, 'agent_message', from)) {
69
+ if (from !== undefined && typeof fromRaw === 'string' && typeof body === 'string' && provenanceAgrees(n, 'agent_message', fromRaw)) {
67
70
  const agentType = nonEmptyString(dataProp(dataProp(n, WIRE_KEY_PROVENANCE), WIRE_KEY_AGENT_TYPE));
68
71
  return {
69
72
  lane: 'agent_message',
@@ -77,9 +80,10 @@ export function classifyPeerNotification(n) {
77
80
  }
78
81
  const crossSessionMessage = dataProp(n, WIRE_KEY_CROSS_SESSION_MESSAGE);
79
82
  if (crossSessionMessage !== undefined) {
80
- const from = cleanName(dataProp(crossSessionMessage, WIRE_KEY_FROM));
83
+ const fromRaw = dataProp(crossSessionMessage, WIRE_KEY_FROM);
84
+ const from = cleanName(fromRaw);
81
85
  const body = dataProp(crossSessionMessage, WIRE_KEY_BODY);
82
- if (from === undefined || typeof body !== 'string' || !provenanceAgrees(n, 'cross_session_message', from))
86
+ if (from === undefined || typeof fromRaw !== 'string' || typeof body !== 'string' || !provenanceAgrees(n, 'cross_session_message', fromRaw))
83
87
  return null;
84
88
  const fromSession = nonEmptyString(dataProp(crossSessionMessage, WIRE_KEY_FROM_SESSION));
85
89
  const fromName = cleanName(dataProp(crossSessionMessage, WIRE_KEY_FROM_NAME));
@@ -117,7 +121,7 @@ function neutralizeAuthorityTags(text) {
117
121
  function cleanName(v) {
118
122
  if (typeof v !== 'string')
119
123
  return undefined;
120
- const t = applyDisplayFace(v, LEGACY_NAME_FACE);
124
+ const t = applyDisplayFace(v, PEER_NAME_FACE);
121
125
  return t.length > 0 ? t : undefined;
122
126
  }
123
127
  function escapeEnvelopeTag(tag, text) {
@@ -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':