@sema-agent/client-core 0.101.0 → 0.103.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 (125) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +19 -11
  3. package/dist/adapt/toolCards.d.ts +4 -2
  4. package/dist/adapt/toolCards.js +22 -6
  5. package/dist/adapt.d.ts +1 -1
  6. package/dist/adapt.js +1 -1
  7. package/dist/adapter/downstream/eventToSdkMessage.d.ts +5 -1
  8. package/dist/adapter/downstream/eventToSdkMessage.js +26 -70
  9. package/dist/adapter/downstream/terminalToSdkResult.js +0 -1
  10. package/dist/adapter/downstream/wiringManifestView.d.ts +1 -0
  11. package/dist/adapter/downstream/wiringManifestView.js +141 -0
  12. package/dist/cloudConfigWireCaps.js +6 -3
  13. package/dist/compensations.js +1 -1
  14. package/dist/credentialPlaneCapability.d.ts +22 -0
  15. package/dist/credentialPlaneCapability.js +75 -0
  16. package/dist/deviceExecutorManagementCapability.d.ts +1 -0
  17. package/dist/deviceExecutorManagementCapability.js +3 -1
  18. package/dist/displayUntrusted.d.ts +8 -4
  19. package/dist/displayUntrusted.js +769 -96
  20. package/dist/engineErrorCodes.d.ts +1 -0
  21. package/dist/engineErrorCodes.js +3 -1
  22. package/dist/engineNoticeCodes.d.ts +26 -0
  23. package/dist/engineNoticeCodes.js +121 -12
  24. package/dist/engineRowRef.js +2 -0
  25. package/dist/errorSupplementRoles.js +1 -3
  26. package/dist/eventLeg.js +2 -1
  27. package/dist/fleet/fleetProjection.js +16 -10
  28. package/dist/gateOutcome.d.ts +20 -0
  29. package/dist/gateOutcome.js +89 -0
  30. package/dist/generated/engineAskFaceTables.js +2 -2
  31. package/dist/generated/engineFactTables.d.ts +6 -2
  32. package/dist/generated/engineFactTables.js +41 -1
  33. package/dist/generated/engineNoticeTables.js +18 -0
  34. package/dist/generated/settingsSchemaDefaultModel.d.ts +1 -1
  35. package/dist/generated/settingsSchemaDefaultModel.js +2 -2
  36. package/dist/generated/settingsSchemaFns.d.ts +15 -0
  37. package/dist/generated/settingsSchemaFns.js +25 -2
  38. package/dist/generated/settingsSchemaHooksClassifier.js +109 -29
  39. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  40. package/dist/generated/settingsSchemaMcpRequestEntry.js +213 -93
  41. package/dist/generated/settingsSchemaMcpServerName.d.ts +1 -0
  42. package/dist/generated/settingsSchemaMcpServerName.js +8 -0
  43. package/dist/generated/settingsSchemaOwnKeyRead.d.ts +1 -0
  44. package/dist/generated/settingsSchemaOwnKeyRead.js +172 -0
  45. package/dist/generated/settingsSchemaPlainJsonObject.d.ts +1 -0
  46. package/dist/generated/settingsSchemaPlainJsonObject.js +28 -0
  47. package/dist/generated/settingsSchemaSafetyMergeSpec.js +10 -7
  48. package/dist/generated/settingsSchemaTierWords.d.ts +1 -0
  49. package/dist/generated/settingsSchemaTierWords.js +7 -0
  50. package/dist/generated/settingsSchemaTypes.d.ts +3 -1
  51. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  52. package/dist/hitl/decisionConflict.js +11 -11
  53. package/dist/hitl/decisionRefusal.d.ts +1 -0
  54. package/dist/hitl/decisionRefusal.js +4 -2
  55. package/dist/hitl/editedRuleTextPrecheck.d.ts +1 -0
  56. package/dist/hitl/editedRuleTextPrecheck.js +26 -1
  57. package/dist/hitl/persistedRulesWire.js +1 -0
  58. package/dist/hitl/planReviewWire.d.ts +2 -1
  59. package/dist/hitl/planReviewWire.js +8 -12
  60. package/dist/hitl/sessionPolicyDeliverable.d.ts +0 -1
  61. package/dist/hitl/sessionPolicyDeliverable.js +0 -5
  62. package/dist/hitl/sessionPolicyWire.js +1 -1
  63. package/dist/hitl/toolApprovalWire.d.ts +18 -6
  64. package/dist/hitl/toolApprovalWire.js +9 -4
  65. package/dist/i18n/messages.en.d.ts +43 -0
  66. package/dist/i18n/messages.en.js +44 -1
  67. package/dist/i18n/messages.zh.js +46 -1
  68. package/dist/index.d.ts +11 -2
  69. package/dist/index.js +7 -1
  70. package/dist/inheritEnvWire.d.ts +1 -0
  71. package/dist/inheritEnvWire.js +8 -5
  72. package/dist/interactiveHalt.d.ts +3 -1
  73. package/dist/interactiveHalt.js +2 -0
  74. package/dist/interactiveInterrupt.d.ts +2 -0
  75. package/dist/interactiveInterrupt.js +15 -0
  76. package/dist/mcpEngineLeg.d.ts +3 -0
  77. package/dist/mcpEngineLeg.js +4 -1
  78. package/dist/mcpLiveness.d.ts +18 -0
  79. package/dist/mcpLiveness.js +48 -1
  80. package/dist/mcpReconnect.d.ts +3 -0
  81. package/dist/mcpReconnect.js +6 -0
  82. package/dist/mcpStatusSupersetKeys.js +24 -1
  83. package/dist/mcpWireCaps.d.ts +2 -3
  84. package/dist/mcpWireCaps.js +9 -8
  85. package/dist/memoryEntriesWire.d.ts +35 -1
  86. package/dist/memoryEntriesWire.js +110 -14
  87. package/dist/modsFace.d.ts +8 -0
  88. package/dist/modsFace.js +63 -0
  89. package/dist/ownKey.d.ts +1 -0
  90. package/dist/ownKey.js +15 -0
  91. package/dist/postureKnob.js +1 -1
  92. package/dist/prepareRefusal.d.ts +0 -1
  93. package/dist/prepareRefusal.js +0 -3
  94. package/dist/printToolResultFrame.js +1 -1
  95. package/dist/promptProfileWireCaps.d.ts +1 -3
  96. package/dist/promptProfileWireCaps.js +2 -2
  97. package/dist/request/printNotification.js +1 -1
  98. package/dist/request/taskRequest.d.ts +22 -1
  99. package/dist/request/taskRequest.js +126 -10
  100. package/dist/retryStatus.d.ts +13 -1
  101. package/dist/retryStatus.js +20 -5
  102. package/dist/seam.js +1 -1
  103. package/dist/seatContract.d.ts +22 -6
  104. package/dist/seatContract.js +45 -6
  105. package/dist/sessionBackground.d.ts +165 -0
  106. package/dist/sessionBackground.js +781 -0
  107. package/dist/sessionBackgroundHandoff.d.ts +54 -0
  108. package/dist/sessionBackgroundHandoff.js +321 -0
  109. package/dist/sessionMap.js +4 -1
  110. package/dist/sessionMemoryErase.js +24 -2
  111. package/dist/settingsCredentialKeyPaths.js +2 -17
  112. package/dist/thinkingLatch.d.ts +69 -0
  113. package/dist/thinkingLatch.js +255 -0
  114. package/dist/thinkingLatchWords.d.ts +7 -0
  115. package/dist/thinkingLatchWords.js +4 -0
  116. package/dist/toolResult.js +2 -24
  117. package/dist/turnFailurePresentation.js +8 -1
  118. package/dist/wireFailureShape.d.ts +12 -1
  119. package/dist/wireFailureShape.js +58 -24
  120. package/dist/wireRefusalCopy.d.ts +0 -3
  121. package/dist/wireRefusalCopy.js +0 -17
  122. package/docs/INTEGRATION-CLIENTS.md +458 -14
  123. package/package.json +5 -5
  124. package/dist/argvFlagValue.d.ts +0 -8
  125. package/dist/argvFlagValue.js +0 -37
@@ -2,7 +2,7 @@ import type { AdapterContext, AdapterOutput } from '../seam.js';
2
2
  import { structuredToToolUseResult, type ToolKillCause, type ToolResultBody } from '../toolResult.js';
3
3
  import { type Frame, type IdOf } from './ids.js';
4
4
  import type { EngineRowRef } from '../engineRowRef.js';
5
- import { type GateOutcomeView } from '../gateOutcome.js';
5
+ import { type GateOutcomeView, type ToolInputRewriteView } from '../gateOutcome.js';
6
6
  type RichToolUseResult = NonNullable<ReturnType<typeof structuredToToolUseResult>>;
7
7
  export interface ToolResultOutcome {
8
8
  readonly body: ToolResultBody;
@@ -37,8 +37,10 @@ export interface ToolResultRowFlagParts {
37
37
  readonly _sema_interrupted_never_started?: true;
38
38
  readonly _sema_approval_window_expired?: true;
39
39
  readonly _sema_denial_limit_window_expired?: true;
40
+ readonly _sema_answered_by?: string;
41
+ readonly _sema_rewrite?: ToolInputRewriteView;
40
42
  }
41
- export declare function toolResultRowFlagParts(arm: object | null | undefined): ToolResultRowFlagParts;
43
+ export declare function toolResultRowFlagParts(arm: object | null | undefined, gate: GateOutcomeView | undefined): ToolResultRowFlagParts;
42
44
  interface ToolEndPayload {
43
45
  output?: unknown;
44
46
  structured?: unknown;
@@ -5,8 +5,8 @@ import { flattenWireOutput } from './wireShapes.js';
5
5
  import { toolResultBlocksOf } from '../toolResultContent.js';
6
6
  import { bashExitOf, bashExitReadingFailed } from '../bashExitReading.js';
7
7
  import { isCcToolDenialKind } from '../gateVocabulary.js';
8
- import { guardedRead, ownSnapshotWhenTyped, readFrameStringKey, readFrameTrueMark } from '../ownKey.js';
9
- import { gateOutcomeOf, isApprovalWindowExpiredGate, isDenialLimitAutoDeniedGate } from '../gateOutcome.js';
8
+ import { guardedRead, ownSnapshotWhenTyped, readFrameStringKey, readFrameTrueMark, readOwnDataValue } from '../ownKey.js';
9
+ import { gateDeniedBy, isApprovalWindowExpiredGate, isDeniedGate, isDenialLimitAutoDeniedGate, toolInputRewriteOf } from '../gateOutcome.js';
10
10
  import { TOOL_END_INTERRUPTED_NEVER_STARTED } from '../engineErrorCodes.js';
11
11
  function toolUseResultParts(v) {
12
12
  return v !== undefined ? { tool_use_result: v } : {};
@@ -53,18 +53,34 @@ export function toolResultRowIdentity(sources, toolCallId, ctx) {
53
53
  }
54
54
  return { uuid: deriveTranscriptId({ toolCallId }, ctx) };
55
55
  }
56
- export function toolResultRowFlagParts(arm) {
56
+ export function toolResultRowFlagParts(arm, gate) {
57
57
  if (arm === null || arm === undefined)
58
58
  return {};
59
- const gate = guardedRead(() => gateOutcomeOf(arm));
59
+ const notExecuted = readFrameTrueMark(arm, 'notExecuted');
60
+ const answeredBy = modAnsweredByOf(arm, notExecuted, gate);
61
+ const rewrite = toolInputRewriteOf(arm);
60
62
  return {
61
- ...(readFrameTrueMark(arm, 'notExecuted') ? { _sema_not_executed: true } : {}),
63
+ ...(notExecuted ? { _sema_not_executed: true } : {}),
62
64
  ...(readFrameTrueMark(arm, '_sema_collateral_abort') ? { _sema_collateral_abort: true } : {}),
63
65
  ...(readFrameStringKey(arm, 'errorCode') === TOOL_END_INTERRUPTED_NEVER_STARTED ? { _sema_interrupted_never_started: true } : {}),
64
66
  ...(isApprovalWindowExpiredGate(gate) ? { _sema_approval_window_expired: true } : {}),
65
67
  ...(isDenialLimitAutoDeniedGate(gate) ? { _sema_denial_limit_window_expired: true } : {}),
68
+ ...(answeredBy !== undefined ? { _sema_answered_by: answeredBy } : {}),
69
+ ...(rewrite !== undefined ? { _sema_rewrite: rewrite } : {}),
66
70
  };
67
71
  }
72
+ const MOD_ENGINE_ORIGIN = 'engine';
73
+ function modAnsweredByOf(arm, notExecuted, gate) {
74
+ const by = readFrameStringKey(arm, 'answeredBy');
75
+ if (by === undefined || by === MOD_ENGINE_ORIGIN || !notExecuted)
76
+ return undefined;
77
+ if (gate !== undefined && gate.unreadable !== undefined && gate.unreadable.length > 0)
78
+ return undefined;
79
+ const isErrorRaw = guardedRead(() => readOwnDataValue(arm, 'isError'));
80
+ if (isDeniedGate(gate))
81
+ return gateDeniedBy(gate) === 'hook' && isErrorRaw === true ? by : undefined;
82
+ return isErrorRaw === false ? by : undefined;
83
+ }
68
84
  export function createToolCardLedger(ctx, idOf) {
69
85
  const pending = new Map();
70
86
  let assistantResponseId = null;
@@ -110,7 +126,7 @@ export function createToolCardLedger(ctx, idOf) {
110
126
  ...toolResultEnvelopeParts(outcome),
111
127
  ...(isCcToolDenialKind(end?.denialKind) ? { _sema_denial_kind: end.denialKind } : {}),
112
128
  ...(end?.gate !== undefined ? { _sema_gate: end.gate } : {}),
113
- ...toolResultRowFlagParts(end?.closeArm),
129
+ ...toolResultRowFlagParts(end?.closeArm, end?.gate),
114
130
  }, ctx.now());
115
131
  }
116
132
  function* closeCard(p, end) {
package/dist/adapt.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare const ADAPTER_COVERAGE: {
13
13
  readonly transcriptKinds: readonly ["assistant_text", "assistant_thinking", "assistant_tool_use", "user_tool_result_decision", "user_task_notification", "system_passthrough"];
14
14
  readonly b3: readonly ["T38 三清第三清 + T57 第五处断闸:prompt_suggestions(turn 开场作废 + 子流断闸)", "T40 IDLE-FLUSH 1.5s 竞速(需 ctx.setTimer;缺席=不启用)", "T58 手动 iterator 的 finally cleanup(early break/throw 都要关上游生成器)", "T59 abort 半场(ctx.signal;中断后不产终答)", "T60 无 result 收尾腿(泄尾增量 → 清 liveness → 尾思考 → 尾文本段)"];
15
15
  readonly b4: readonly ["A 层第 14 臂 task_progress:workflow lane 三级门 + 判据④ belt 回植 + C1 alias + bindTaskCard(显式父/FIFO/含糊即 unbound)+ inline stats + T50 双形读 + SubagentStart + #6 resident 台账 + 面板 tick", "settlePanelTasks 三处 sweep 全接线(tool_end 显式关卡带报告 / result 防御 sweep / turn 末 + abort sweep)", "P0-2 tool label 侧信道(sanitizeToolUseBlock 摘 label + engineToolLabelStore 登记;0.5.0 真缺口,label 会随 tool_use block 落转录 → /compact 直发 provider 400)", "task_notification 的两件宿主义务收回库内自持:dropQueuedNotificationsForRun + clearEnginePanelTaskResident;stop hook 的 if-started 门由库解", "T8 flattenWireOutput 提为独立导出(A 层 settle 报告与 B 层卡体同一份)"];
16
- readonly b5: readonly ["B 层 structuredToToolUseResult 14 case(ask-user-question/bash/edit/create/update/text/grep/glob/notebook-edit/cron-create/cron-delete/cron-list/agent/task-output)+ workflow-run 登记副作用", "D 层 wireOutputToBody 3 臂(bash / taskoutput / 泛化);structured 白名单在场时 T11+T14 正则退位,缺席保回落", "E 层 4 臂:#117a bg Bash 回执 + #158 Monitor 回执(同臂按工具名分,registration.source 判别;判定在库→chrome bgshell_register)+ CC-331 交出后台的 Bash(结构化 detached 位同形登记,一张卡至多一条;结构化位自述登记失败时两臂都不铸)· TodoWrite oldTodos 富卡 · ReportFindings 按名认领 · Remember→memory_saved 系统消息", "tool_end_result → user tool_result 正常路径(卡本体);result 与 turn 末两处开卡兜底关闭(abort 不兜底,cli 同)", "task/task-list structured → chrome task_ledger_sync{source:\"structured\"}(判定归一在库、落盘留宿主)", "T7 诚实缺席(§8-4:不搬 mock 合成器,改 degraded 标记)· T20 diff hunks 进包(src/diff/patch.ts)"];
16
+ readonly b5: readonly ["B 层 structuredToToolUseResult 14 case(ask-user-question/bash/edit/create/update/text/grep/glob/notebook-edit/cron-create/cron-delete/cron-list/agent/task-output)+ workflow-run 登记副作用", "D 层 wireOutputToBody 3 臂(bash / taskoutput / 泛化);structured 白名单在场时 T11+T14 正则退位,缺席保回落", "E 层 4 臂:bg Bash 回执 + Monitor 回执(同臂按工具名分,registration.source 判别;判定在库→chrome bgshell_register)+ CC-331 交出后台的 Bash(结构化 detached 位同形登记,一张卡至多一条;结构化位自述登记失败时两臂都不铸)· TodoWrite oldTodos 富卡 · ReportFindings 按名认领 · Remember→memory_saved 系统消息", "tool_end_result → user tool_result 正常路径(卡本体);result 与 turn 末两处开卡兜底关闭(abort 不兜底,cli 同)", "task/task-list structured → chrome task_ledger_sync{source:\"structured\"}(判定归一在库、落盘留宿主)", "T7 诚实缺席(§8-4:不搬 mock 合成器,改 degraded 标记)· T20 diff hunks 进包(src/diff/patch.ts)"];
17
17
  readonly todo: readonly ["T13 的 **D 层** 半场(taskoutput block content)未随 structured 退位:退位会改 block content 形状(cli 那里是裸对象 = 件1 隐患),属行为面改动,无真实需求驱动 ⇒ 见 DIVERGENCE-8", "macrotask 让渡(cli 在提交前让出宏任务给 Ink;宿主渲染策略,不进库)"];
18
18
  };
19
19
  export declare const ADAPTER_DIVERGENCES: readonly [string, string, string, string, string, string, string, string, string, string, string, string];
package/dist/adapt.js CHANGED
@@ -56,7 +56,7 @@ export const ADAPTER_COVERAGE = {
56
56
  b5: [
57
57
  'B 层 structuredToToolUseResult 14 case(ask-user-question/bash/edit/create/update/text/grep/glob/notebook-edit/cron-create/cron-delete/cron-list/agent/task-output)+ workflow-run 登记副作用',
58
58
  'D 层 wireOutputToBody 3 臂(bash / taskoutput / 泛化);structured 白名单在场时 T11+T14 正则退位,缺席保回落',
59
- 'E 层 4 臂:#117a bg Bash 回执 + #158 Monitor 回执(同臂按工具名分,registration.source 判别;判定在库→chrome bgshell_register)+ CC-331 交出后台的 Bash(结构化 detached 位同形登记,一张卡至多一条;结构化位自述登记失败时两臂都不铸)· TodoWrite oldTodos 富卡 · ReportFindings 按名认领 · Remember→memory_saved 系统消息',
59
+ 'E 层 4 臂:bg Bash 回执 + Monitor 回执(同臂按工具名分,registration.source 判别;判定在库→chrome bgshell_register)+ CC-331 交出后台的 Bash(结构化 detached 位同形登记,一张卡至多一条;结构化位自述登记失败时两臂都不铸)· TodoWrite oldTodos 富卡 · ReportFindings 按名认领 · Remember→memory_saved 系统消息',
60
60
  'tool_end_result → user tool_result 正常路径(卡本体);result 与 turn 末两处开卡兜底关闭(abort 不兜底,cli 同)',
61
61
  'task/task-list structured → chrome task_ledger_sync{source:"structured"}(判定归一在库、落盘留宿主)',
62
62
  'T7 诚实缺席(§8-4:不搬 mock 合成器,改 degraded 标记)· T20 diff hunks 进包(src/diff/patch.ts)',
@@ -1,7 +1,7 @@
1
1
  import type { AgentEvent, CheckpointGate, TaskResult } from '@sema-agent/sdk';
2
2
  import { type WiringManifestView } from './wiringManifestView.js';
3
3
  import { type SDKMessage, type EmitContext, type ModelUsage } from '../types.js';
4
- import { type McpLivenessView } from '../../mcpLiveness.js';
4
+ import { type McpAuthorizationView, type McpLivenessView } from '../../mcpLiveness.js';
5
5
  import { type ContextUsageSectionRow, type PromptIdentityLayersRow, type PromptManifestIdentityRow, type PromptManifestSectionRow } from '../../ownKey.js';
6
6
  export declare const INTERNAL_SDK_ARM_TYPES: ReadonlySet<string>;
7
7
  export type EventProjectionNoneReason = 'usage_only' | 'terminal_delegated' | 'hitl_out_of_slice' | 'not_in_slice' | 'empty_payload';
@@ -38,6 +38,8 @@ export interface WiringManifestMcpEntryView {
38
38
  httpStatus?: number;
39
39
  liveness?: McpLivenessView;
40
40
  livenessUnreadable?: true;
41
+ authorization?: McpAuthorizationView;
42
+ authorizationUnreadable?: true;
41
43
  }
42
44
  export declare function projectMcpSection(raw: unknown): WiringManifestMcpEntryView[] | undefined;
43
45
  export type SubmitWiringManifestReading = {
@@ -76,6 +78,7 @@ export interface WiringManifestModMountedView {
76
78
  readonly name: string;
77
79
  readonly seat: string;
78
80
  readonly tier: string;
81
+ readonly provenance?: string;
79
82
  readonly events: readonly string[];
80
83
  readonly observe?: readonly string[];
81
84
  readonly skipped?: readonly WiringManifestModSkipView[];
@@ -84,6 +87,7 @@ export interface WiringManifestModRefusedView {
84
87
  readonly name: string;
85
88
  readonly seat: string;
86
89
  readonly tier: string;
90
+ readonly provenance?: string;
87
91
  readonly refusedBy: string;
88
92
  readonly reason: string;
89
93
  }
@@ -1,11 +1,11 @@
1
- import { readFleetSegment, readInstructionsSegment, wiringManifestViewOf } from './wiringManifestView.js';
1
+ import { modRowsOf, readFleetSegment, readInstructionsSegment, wiringManifestViewOf } from './wiringManifestView.js';
2
2
  import { stamp, snapshotSegmentIdentity, } from '../types.js';
3
3
  import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
4
- import { ccToolDenialKindForToolEnd, gateOutcomeOf } from '../../gateOutcome.js';
4
+ import { ccToolDenialKindForToolEnd, gateOutcomeOf, toolInputRewriteOf } from '../../gateOutcome.js';
5
5
  import { projectToolRoster, projectToolRosterDelta } from '../../toolRoster.js';
6
6
  import { projectHandsSection } from '../../handsSeam.js';
7
- import { readMcpLiveness } from '../../mcpLiveness.js';
8
- import { contextUsageReadingOf, humanInputAttachmentsOf, guardedRead, markMcpSectionDroppedNamedRows, ownSnapshotTyped, promptManifestIdentityOfFrame, promptManifestSectionViewOf, readFrameTrueMark, readOwnDataNonEmptyString } from '../../ownKey.js';
7
+ import { readMcpAuthorization, readMcpLiveness } from '../../mcpLiveness.js';
8
+ import { contextUsageReadingOf, humanInputAttachmentsOf, guardedRead, markMcpSectionDroppedNamedRows, ownSnapshotTyped, promptManifestIdentityOfFrame, promptManifestSectionViewOf, readFrameStringKey, readFrameTrueMark, readOwnDataFiniteNumber, readOwnDataNonEmptyString } from '../../ownKey.js';
9
9
  import { SEGMENT_END_IDENTITY_KEYS, eventSeq, stripToolUseSideChannels } from '../types.js';
10
10
  import { frozenSet } from '../../frozenSet.js';
11
11
  import { isCoreWorkflowRunTerminal } from '../../terminalStatuses.js';
@@ -73,6 +73,11 @@ const dropped = (why, type) => ({ kind: 'dropped', why, type });
73
73
  function isCompactionStartedFrame(ev) {
74
74
  return ev.type === 'compaction_started';
75
75
  }
76
+ const BEYOND_FLOOR_ARMS = Object.freeze(['cross_boundary_taint', 'approval_complete', 'session_titled']);
77
+ function isBeyondFloorArm(ev) {
78
+ const word = ev.type;
79
+ return typeof word === 'string' && BEYOND_FLOOR_ARMS.includes(word);
80
+ }
76
81
  function assertNeverArm(_ev) {
77
82
  }
78
83
  export function eventToSdkMessage(ev, ctx) {
@@ -106,6 +111,10 @@ function withEngineIdentity(p, ev, ctx, identityFrame = ev) {
106
111
  function projectEvent(ev, ctx) {
107
112
  if (isCompactionStartedFrame(ev))
108
113
  return compactionStartedProjection(ev, ctx);
114
+ if (isBeyondFloorArm(ev)) {
115
+ const word = ev.type;
116
+ return word === 'cross_boundary_taint' ? nothing('not_in_slice') : dropped('unsupported_arm', word);
117
+ }
109
118
  switch (ev.type) {
110
119
  case 'task_started':
111
120
  return taskStartedProjection(ev, ctx);
@@ -229,6 +238,8 @@ function projectEvent(ev, ctx) {
229
238
  const collateralAbort = ev._sema_collateral_abort;
230
239
  const denialKind = ccToolDenialKindForToolEnd(ev);
231
240
  const toolEndNotExecuted = readFrameTrueMark(ev, 'notExecuted');
241
+ const toolEndAnsweredBy = readFrameStringKey(ev, 'answeredBy');
242
+ const toolEndRewrite = toolInputRewriteOf(ev);
232
243
  return projected(stamp(ctx, armBody({
233
244
  type: 'tool_end_result',
234
245
  toolCallId: ev.toolCallId,
@@ -251,6 +262,8 @@ function projectEvent(ev, ctx) {
251
262
  ...(collateralAbort === true ? { _sema_collateral_abort: true } : {}),
252
263
  ...(denialKind !== undefined ? { _sema_denial_kind: denialKind } : {}),
253
264
  ...(toolEndNotExecuted ? { notExecuted: true } : {}),
265
+ ...(toolEndAnsweredBy !== undefined ? { answeredBy: toolEndAnsweredBy } : {}),
266
+ ...(toolEndRewrite !== undefined ? { rewrite: toolEndRewrite } : {}),
254
267
  ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
255
268
  ...(ev.parentToolCallId !== undefined
256
269
  ? { parentToolCallId: ev.parentToolCallId }
@@ -270,6 +283,10 @@ function projectEvent(ev, ctx) {
270
283
  const nonEmptyStr = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
271
284
  const providerCondition = nonEmptyStr(ev.providerCondition);
272
285
  const providerConditionSource = nonEmptyStr(ev.providerConditionSource);
286
+ const stWait = ev;
287
+ const sleptMsRaw = guardedRead(() => readOwnDataFiniteNumber(stWait, 'sleptMs'));
288
+ const sleptMs = sleptMsRaw !== undefined && sleptMsRaw >= 0 ? sleptMsRaw : undefined;
289
+ const waitSegment = readFrameStringKey(stWait, 'waitSegment');
273
290
  return projected(stamp(ctx, armBody({
274
291
  type: 'retry_status',
275
292
  phase: ev.phase,
@@ -285,6 +302,8 @@ function projectEvent(ev, ctx) {
285
302
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
286
303
  ...(providerCondition !== undefined ? { providerCondition } : {}),
287
304
  ...(providerConditionSource !== undefined ? { providerConditionSource } : {}),
305
+ ...(sleptMs !== undefined ? { sleptMs } : {}),
306
+ ...(waitSegment !== undefined ? { waitSegment } : {}),
288
307
  ...(typeof ev.eventId === 'string' ? { eventId: ev.eventId } : {}),
289
308
  ...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
290
309
  })));
@@ -553,6 +572,7 @@ export function projectMcpSection(raw) {
553
572
  ...(typeof r.delivered === 'string' && r.delivered.length > 0 ? { delivered: r.delivered } : {}),
554
573
  ...(typeof r.httpStatus === 'number' && Number.isFinite(r.httpStatus) ? { httpStatus: r.httpStatus } : {}),
555
574
  ...((cell) => cell.kind === 'observation' ? { liveness: cell.liveness } : cell.kind === 'unreadable' ? { livenessUnreadable: true } : {})(readMcpLiveness(r)),
575
+ ...((cell) => cell.kind === 'record' ? { authorization: cell.authorization } : cell.kind === 'unreadable' ? { authorizationUnreadable: true } : {})(readMcpAuthorization(r)),
556
576
  });
557
577
  }
558
578
  if (raw.length > 0 && rows.length === 0)
@@ -681,72 +701,8 @@ function projectModsSection(holder) {
681
701
  try {
682
702
  if (!Object.hasOwn(holder, 'mods'))
683
703
  return undefined;
684
- const raw = holder.mods;
685
- if (!Array.isArray(raw))
686
- return undefined;
687
- const rows = [];
688
- for (const r of raw) {
689
- const row = modRowOf(r);
690
- if (row !== undefined)
691
- rows.push(row);
692
- }
693
- if (raw.length > 0 && rows.length === 0)
694
- return undefined;
695
- return rows;
696
- }
697
- catch {
698
- return undefined;
699
- }
700
- }
701
- function modRowOf(r) {
702
- try {
703
- if (typeof r !== 'object' || r === null || Array.isArray(r))
704
- return undefined;
705
- const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
706
- const strList = (v) => (Array.isArray(v) && v.every((x) => typeof x === 'string') ? [...v] : undefined);
707
- const o = r;
708
- const name = str(o.name);
709
- const seat = str(o.seat);
710
- const tier = str(o.tier);
711
- if (name === undefined || seat === undefined || tier === undefined)
712
- return undefined;
713
- const eventsRaw = o.events;
714
- const observeRaw = o.observe;
715
- const skippedRaw = o.skipped;
716
- const refusedByRaw = o.refusedBy;
717
- const reasonRaw = o.reason;
718
- const hasMountedKey = eventsRaw !== undefined || observeRaw !== undefined || skippedRaw !== undefined;
719
- const hasRefusedKey = refusedByRaw !== undefined || reasonRaw !== undefined;
720
- if (hasMountedKey && hasRefusedKey)
721
- return undefined;
722
- if (hasRefusedKey) {
723
- const refusedBy = str(refusedByRaw);
724
- const reason = str(reasonRaw);
725
- return refusedBy === undefined || reason === undefined ? undefined : { name, seat, tier, refusedBy, reason };
726
- }
727
- const events = strList(eventsRaw);
728
- if (events === undefined)
729
- return undefined;
730
- const observe = observeRaw === undefined ? undefined : strList(observeRaw);
731
- const skipped = Array.isArray(skippedRaw)
732
- ? skippedRaw.flatMap((x) => {
733
- if (typeof x !== 'object' || x === null || Array.isArray(x))
734
- return [];
735
- const k = x;
736
- const registration = str(k.registration);
737
- const cause = str(k.cause);
738
- const why = str(k.reason);
739
- return registration === undefined || cause === undefined || why === undefined ? [] : [{ registration, cause, reason: why }];
740
- })
741
- : undefined;
742
- return {
743
- name,
744
- seat,
745
- tier,
746
- events,
747
- ...(observe !== undefined ? { observe } : {}),
748
- ...(skipped !== undefined && skipped.length > 0 ? { skipped } : {}),
749
- };
704
+ const rows = modRowsOf(holder.mods);
705
+ return rows === undefined ? undefined : rows.slice();
750
706
  }
751
707
  catch {
752
708
  return undefined;
@@ -654,7 +654,6 @@ function errorResult(ctx, parts) {
654
654
  subtype: 'success',
655
655
  ...head,
656
656
  result,
657
- errors,
658
657
  ...tail,
659
658
  });
660
659
  }
@@ -46,3 +46,4 @@ export declare function readInstructionsSegment(holder: object, key: 'instructio
46
46
  } | undefined;
47
47
  export declare function readFleetSegment(holder: object, key: 'fleet' | '_sema_fleet'): WiringManifestFleetView | undefined;
48
48
  export declare function wiringManifestViewOf(s: WiringManifestSections): WiringManifestView | undefined;
49
+ export declare function modRowsOf(records: unknown): readonly WiringManifestModView[] | undefined;
@@ -1,3 +1,4 @@
1
+ import { appendOwnElement } from '../../ownKey.js';
1
2
  const _viewPin = true;
2
3
  void _viewPin;
3
4
  const _viewKeysPin = true;
@@ -67,3 +68,143 @@ export function wiringManifestViewOf(s) {
67
68
  view.eventId = s.eventId;
68
69
  return view;
69
70
  }
71
+ const isModRecordObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
72
+ function ownSlot(o, key) {
73
+ try {
74
+ const d = Object.getOwnPropertyDescriptor(o, key);
75
+ if (d === undefined)
76
+ return { state: 'absent' };
77
+ if (!Object.hasOwn(d, 'value'))
78
+ return { state: 'unreadable' };
79
+ return d.value === undefined ? { state: 'absent' } : { state: 'value', value: d.value };
80
+ }
81
+ catch {
82
+ return { state: 'unreadable' };
83
+ }
84
+ }
85
+ function ownLength(v) {
86
+ const s = ownSlot(v, 'length');
87
+ return s.state === 'value' && typeof s.value === 'number' && Number.isSafeInteger(s.value) && s.value >= 0 ? s.value : undefined;
88
+ }
89
+ const nonEmpty = (s) => (s.state === 'value' && typeof s.value === 'string' && s.value.length > 0 ? s.value : undefined);
90
+ function ownStringList(v) {
91
+ try {
92
+ if (!Array.isArray(v))
93
+ return undefined;
94
+ const n = ownLength(v);
95
+ if (n === undefined)
96
+ return undefined;
97
+ const out = [];
98
+ for (let i = 0; i < n; i++) {
99
+ const x = ownSlot(v, String(i));
100
+ if (x.state !== 'value' || typeof x.value !== 'string')
101
+ return undefined;
102
+ appendOwnElement(out, x.value);
103
+ }
104
+ return out;
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ }
110
+ function skipOf(x) {
111
+ try {
112
+ if (!isModRecordObject(x))
113
+ return undefined;
114
+ const registration = nonEmpty(ownSlot(x, 'registration'));
115
+ const cause = nonEmpty(ownSlot(x, 'cause'));
116
+ const reason = nonEmpty(ownSlot(x, 'reason'));
117
+ return registration === undefined || cause === undefined || reason === undefined ? undefined : { registration, cause, reason };
118
+ }
119
+ catch {
120
+ return undefined;
121
+ }
122
+ }
123
+ function modRowOf(r) {
124
+ try {
125
+ if (!isModRecordObject(r))
126
+ return undefined;
127
+ const name = nonEmpty(ownSlot(r, 'name'));
128
+ const seat = nonEmpty(ownSlot(r, 'seat'));
129
+ const tier = nonEmpty(ownSlot(r, 'tier'));
130
+ const provenance = nonEmpty(ownSlot(r, 'provenance'));
131
+ const provenancePart = provenance !== undefined ? { provenance } : {};
132
+ if (name === undefined || seat === undefined || tier === undefined)
133
+ return undefined;
134
+ const events = ownSlot(r, 'events');
135
+ const observe = ownSlot(r, 'observe');
136
+ const skipped = ownSlot(r, 'skipped');
137
+ const refusedBy = ownSlot(r, 'refusedBy');
138
+ const reason = ownSlot(r, 'reason');
139
+ const hasMountedKey = events.state !== 'absent' || observe.state !== 'absent' || skipped.state !== 'absent';
140
+ const hasRefusedKey = refusedBy.state !== 'absent' || reason.state !== 'absent';
141
+ if (hasMountedKey && hasRefusedKey)
142
+ return undefined;
143
+ if (hasRefusedKey) {
144
+ const by = nonEmpty(refusedBy);
145
+ const why = nonEmpty(reason);
146
+ if (by === undefined || why === undefined)
147
+ return undefined;
148
+ const refused = { name, seat, tier, ...provenancePart, refusedBy: by, reason: why };
149
+ return refused;
150
+ }
151
+ const eventList = events.state === 'value' ? ownStringList(events.value) : undefined;
152
+ if (eventList === undefined)
153
+ return undefined;
154
+ const observeList = observe.state === 'value' ? ownStringList(observe.value) : undefined;
155
+ const skipList = skipped.state === 'value' ? skipListOf(skipped.value) : undefined;
156
+ const mounted = {
157
+ name,
158
+ seat,
159
+ tier,
160
+ ...provenancePart,
161
+ events: eventList,
162
+ ...(observeList !== undefined ? { observe: observeList } : {}),
163
+ ...(skipList !== undefined && skipList.length > 0 ? { skipped: skipList } : {}),
164
+ };
165
+ return mounted;
166
+ }
167
+ catch {
168
+ return undefined;
169
+ }
170
+ }
171
+ function skipListOf(v) {
172
+ try {
173
+ if (!Array.isArray(v))
174
+ return undefined;
175
+ const n = ownLength(v);
176
+ if (n === undefined)
177
+ return undefined;
178
+ const out = [];
179
+ for (let i = 0; i < n; i++) {
180
+ const x = ownSlot(v, String(i));
181
+ const s = x.state === 'value' ? skipOf(x.value) : undefined;
182
+ if (s !== undefined)
183
+ appendOwnElement(out, s);
184
+ }
185
+ return out;
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ }
191
+ export function modRowsOf(records) {
192
+ try {
193
+ if (!Array.isArray(records))
194
+ return undefined;
195
+ const n = ownLength(records);
196
+ if (n === undefined)
197
+ return undefined;
198
+ const rows = [];
199
+ for (let i = 0; i < n; i++) {
200
+ const x = ownSlot(records, String(i));
201
+ const row = x.state === 'value' ? modRowOf(x.value) : undefined;
202
+ if (row !== undefined)
203
+ appendOwnElement(rows, row);
204
+ }
205
+ return n > 0 && rows.length === 0 ? undefined : rows;
206
+ }
207
+ catch {
208
+ return undefined;
209
+ }
210
+ }
@@ -1,6 +1,6 @@
1
1
  import { mcpAllowToolsVerdict, mcpAssertionHeaderNotSentNotice, mcpElicitationVerdict, mcpToolAxesVerdict, mcpToolFacesVerdict, readMcpHttpIdentityHeaders } from './mcpWireCaps.js';
2
2
  import { skillToolListsVerdict, toCappedSpec } from './skillsWireCaps.js';
3
- import { SETTINGS_DESCRIBE_BAD_WIRE_NAME, SETTINGS_IS_USABLE_WIRE_NAME, SETTINGS_READ_REQUEST_MCP_TRANSPORT } from './generated/settingsSchemaFns.js';
3
+ import { SETTINGS_DESCRIBE_BAD_MCP_SERVER_NAME, SETTINGS_IS_USABLE_MCP_SERVER_NAME, SETTINGS_READ_REQUEST_MCP_TRANSPORT } from './generated/settingsSchemaFns.js';
4
4
  import { snapshotArgs, wireTransportOf } from './mcpWireTransport.js';
5
5
  import { msg, renderMessage } from './i18n/translate.js';
6
6
  import { TIER_ORDER } from './model/tierVocabulary.js';
@@ -66,7 +66,7 @@ export function cloudMcpToSpecs(servers, env) {
66
66
  recordDrop(name, 'missing-field');
67
67
  continue;
68
68
  }
69
- if (!SETTINGS_IS_USABLE_WIRE_NAME(name)) {
69
+ if (!SETTINGS_IS_USABLE_MCP_SERVER_NAME(name)) {
70
70
  recordDrop(name, 'name-unusable');
71
71
  continue;
72
72
  }
@@ -148,10 +148,12 @@ export function cloudMcpToSpecs(servers, env) {
148
148
  let axesDeclared;
149
149
  let elicitationDeclared;
150
150
  let allowDeclared;
151
+ let startupDeclared;
151
152
  try {
152
153
  axesDeclared = s.toolAxes;
153
154
  elicitationDeclared = s.elicitation;
154
155
  allowDeclared = s.allowTools;
156
+ startupDeclared = s.startupTimeoutMs;
155
157
  }
156
158
  catch {
157
159
  recordDrop(name, 'entry-unreadable');
@@ -199,6 +201,7 @@ export function cloudMcpToSpecs(servers, env) {
199
201
  ...(elVerdict.elicitation !== undefined ? { elicitation: elVerdict.elicitation } : {}),
200
202
  ...(facesShipped !== undefined ? { toolFaces: facesShipped } : {}),
201
203
  ...(axesShipped !== undefined ? { toolAxes: axesShipped } : {}),
204
+ ...(startupDeclared !== undefined ? { startupTimeoutMs: startupDeclared } : {}),
202
205
  });
203
206
  }
204
207
  return { specs, missingEnv: [...new Set(missingEnv)], droppedServers, degradedServers };
@@ -859,7 +862,7 @@ function droppedMcpNotices(carrier) {
859
862
  const label = name === undefined
860
863
  ? undefined
861
864
  : reason === 'name-unusable'
862
- ? SETTINGS_DESCRIBE_BAD_WIRE_NAME(name)
865
+ ? SETTINGS_DESCRIBE_BAD_MCP_SERVER_NAME(name)
863
866
  : reason === 'transport-invalid' || reason === 'allow-tools-invalid' || reason === 'entry-unreadable'
864
867
  ? `"${capForDisplay(name, DEGRADED_NAME_SHOWN)}"${pathLabel}`
865
868
  : `"${name}"${pathLabel}`;
@@ -87,7 +87,7 @@ export const MIGRATED_COMPENSATIONS = [
87
87
  {
88
88
  id: 'T3',
89
89
  what: '从落盘转录的 `<task-notification>` XML 反解回植去重键 —— 去重面纯内存,重启即蒸发,' +
90
- '「通知 user-turn 已落盘、ack 未落」的 crash 窗口会二次喂模型(#63)',
90
+ '「通知 user-turn 已落盘、ack 未落」的 crash 窗口会二次喂模型',
91
91
  disposition: 'in-library',
92
92
  libraryHalf: 'notifications.ts parseTranscriptNotificationSeeds(**唯一一份**解析;0.98.0 起逐块解析归线性块解析器,单条口 readTaskNotificationXml 读同一只)+ ' +
93
93
  'adapt.ts importLedgerFromTranscript(消费口)',
@@ -0,0 +1,22 @@
1
+ export type SandboxGitCredentialOriginView = {
2
+ readonly origin: string;
3
+ readonly delivers: true;
4
+ } | {
5
+ readonly origin: string;
6
+ readonly delivers: false;
7
+ readonly reason: string;
8
+ };
9
+ export type SandboxGitCredentialPlaneReading = {
10
+ readonly kind: 'unobserved';
11
+ } | {
12
+ readonly kind: 'not_reported';
13
+ } | {
14
+ readonly kind: 'unreadable';
15
+ } | {
16
+ readonly kind: 'off';
17
+ readonly reason: string;
18
+ } | {
19
+ readonly kind: 'on';
20
+ readonly origins: readonly SandboxGitCredentialOriginView[];
21
+ };
22
+ export declare function observedSandboxGitCredentialPlane(baseUrl?: string | undefined): SandboxGitCredentialPlaneReading;
@@ -0,0 +1,75 @@
1
+ import { engineCapValue } from './engineCapsCache.js';
2
+ import { engineWireTargetBaseUrl } from './engineWireTarget.js';
3
+ import { ownDataField } from './ownKey.js';
4
+ const UNOBSERVED = Object.freeze({ kind: 'unobserved' });
5
+ const NOT_REPORTED = Object.freeze({ kind: 'not_reported' });
6
+ const UNREADABLE = Object.freeze({ kind: 'unreadable' });
7
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
8
+ const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
9
+ function readOrigin(v) {
10
+ if (!isRecord(v))
11
+ return undefined;
12
+ const origin = ownDataField(v, 'origin');
13
+ const delivers = ownDataField(v, 'delivers');
14
+ if (!nonEmpty(origin))
15
+ return undefined;
16
+ if (delivers === true)
17
+ return Object.freeze({ origin, delivers: true });
18
+ if (delivers !== false)
19
+ return undefined;
20
+ const reason = ownDataField(v, 'reason');
21
+ return nonEmpty(reason) ? Object.freeze({ origin, delivers: false, reason }) : undefined;
22
+ }
23
+ function projectSandboxGit(plane) {
24
+ try {
25
+ if (!isRecord(plane))
26
+ return UNREADABLE;
27
+ const sg = ownDataField(plane, 'sandboxGit');
28
+ if (sg === undefined)
29
+ return NOT_REPORTED;
30
+ if (!isRecord(sg))
31
+ return UNREADABLE;
32
+ const state = ownDataField(sg, 'state');
33
+ if (state === 'off') {
34
+ const reason = ownDataField(sg, 'reason');
35
+ return nonEmpty(reason) ? Object.freeze({ kind: 'off', reason }) : UNREADABLE;
36
+ }
37
+ if (state !== 'on')
38
+ return UNREADABLE;
39
+ const list = ownDataField(sg, 'origins');
40
+ if (!Array.isArray(list))
41
+ return UNREADABLE;
42
+ const n = ownDataField(list, 'length');
43
+ if (typeof n !== 'number' || !Number.isInteger(n) || n < 0)
44
+ return UNREADABLE;
45
+ const origins = [];
46
+ for (let i = 0; i < n; i += 1) {
47
+ const row = readOrigin(ownDataField(list, String(i)));
48
+ if (row === undefined)
49
+ return UNREADABLE;
50
+ origins.push(row);
51
+ }
52
+ return Object.freeze({ kind: 'on', origins: Object.freeze(origins) });
53
+ }
54
+ catch {
55
+ return UNREADABLE;
56
+ }
57
+ }
58
+ export function observedSandboxGitCredentialPlane(baseUrl = engineWireTargetBaseUrl()) {
59
+ try {
60
+ const slot = engineCapValue(typeof baseUrl === 'string' && baseUrl.length > 0 ? baseUrl : undefined, 'credentialPlane');
61
+ switch (slot.kind) {
62
+ case 'unobserved':
63
+ return UNOBSERVED;
64
+ case 'not_reported':
65
+ return NOT_REPORTED;
66
+ case 'value':
67
+ return projectSandboxGit(slot.value);
68
+ default:
69
+ return UNREADABLE;
70
+ }
71
+ }
72
+ catch {
73
+ return UNOBSERVED;
74
+ }
75
+ }
@@ -13,6 +13,7 @@ export type DeviceExecutorManagementReading = {
13
13
  protocolVersion?: number;
14
14
  maxInflightPerDevice?: number;
15
15
  wsPath?: string;
16
+ relayUrl?: string;
16
17
  };
17
18
  export declare function projectDeviceExecutorManagementCapability(caps: unknown): DeviceExecutorManagementReading | undefined;
18
19
  export declare function observedDeviceExecutorManagement(baseUrl?: string | undefined): DeviceExecutorManagementReading;
@@ -1,6 +1,6 @@
1
1
  import { engineWireTargetBaseUrl } from './engineWireTarget.js';
2
2
  import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
3
- import { ownDataField } from './ownKey.js';
3
+ import { guardedRead, ownDataField } from './ownKey.js';
4
4
  import { capForDisplay } from './fleetTaskDesc.js';
5
5
  export function projectDeviceExecutorManagementCapability(caps) {
6
6
  try {
@@ -28,12 +28,14 @@ export function projectDeviceExecutorManagementCapability(caps) {
28
28
  const pv = ownDataField(d, 'protocolVersion');
29
29
  const mi = ownDataField(d, 'maxInflightPerDevice');
30
30
  const ws = ownDataField(d, 'wsPath');
31
+ const relay = guardedRead(() => ownDataField(d, 'relayUrl'));
31
32
  return {
32
33
  kind: 'present',
33
34
  management,
34
35
  ...(typeof pv === 'number' && Number.isFinite(pv) ? { protocolVersion: pv } : {}),
35
36
  ...(typeof mi === 'number' && Number.isFinite(mi) && mi >= 0 ? { maxInflightPerDevice: mi } : {}),
36
37
  ...(typeof ws === 'string' && ws.length > 0 ? { wsPath: ws } : {}),
38
+ ...(typeof relay === 'string' && relay.length > 0 ? { relayUrl: relay } : {}),
37
39
  };
38
40
  }
39
41
  catch {