@sema-agent/client-core 0.93.0 → 0.94.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 (156) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/README.md +31 -18
  3. package/dist/adapt/arms.js +8 -5
  4. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  5. package/dist/agentSession/engineSessionBackgroundStop.js +2 -1
  6. package/dist/agentsWireCaps.d.ts +15 -0
  7. package/dist/agentsWireCaps.js +111 -1
  8. package/dist/approvalReachWire.d.ts +18 -0
  9. package/dist/approvalReachWire.js +58 -0
  10. package/dist/approvalsStreamLiveCapability.js +16 -11
  11. package/dist/callerCwdCapability.d.ts +16 -0
  12. package/dist/callerCwdCapability.js +47 -0
  13. package/dist/controlRouter.js +3 -10
  14. package/dist/coreValuePorts.d.ts +64 -1
  15. package/dist/coreValuePorts.js +30 -0
  16. package/dist/detachWire.d.ts +1 -4
  17. package/dist/detachWire.js +1 -1
  18. package/dist/deviceExecutorManagementCapability.js +39 -34
  19. package/dist/deviceFlowPolling.d.ts +5 -0
  20. package/dist/deviceFlowPolling.js +28 -0
  21. package/dist/displayUntrusted.js +652 -21
  22. package/dist/engineErrorCodes.d.ts +5 -1
  23. package/dist/engineErrorCodes.js +9 -12
  24. package/dist/engineNoticeCodes.d.ts +22 -2
  25. package/dist/engineNoticeCodes.js +73 -6
  26. package/dist/engineRefusedToolName.d.ts +1 -0
  27. package/dist/engineRefusedToolName.js +8 -0
  28. package/dist/executionLaneCapability.js +14 -9
  29. package/dist/fileHistoryCaptureCapability.js +14 -9
  30. package/dist/fileHistoryLegNotice.d.ts +11 -0
  31. package/dist/fileHistoryLegNotice.js +50 -13
  32. package/dist/fleet/fleetLedger.js +5 -0
  33. package/dist/gateOutcome.d.ts +2 -1
  34. package/dist/gateOutcome.js +3 -0
  35. package/dist/gateVocabulary.js +1 -1
  36. package/dist/generated/engineAskFaceTables.d.ts +3 -0
  37. package/dist/generated/engineAskFaceTables.js +12 -0
  38. package/dist/generated/engineFactTables.d.ts +4 -2
  39. package/dist/generated/engineFactTables.js +13 -0
  40. package/dist/generated/engineNoticeTables.d.ts +5 -0
  41. package/dist/generated/engineNoticeTables.js +7 -0
  42. package/dist/generated/sdkFactTables.d.ts +3 -0
  43. package/dist/generated/sdkFactTables.js +17 -0
  44. package/dist/generated/settingsSchemaTables.d.ts +3 -0
  45. package/dist/generated/settingsSchemaTables.js +3 -0
  46. package/dist/headlessPermissionModeWire.d.ts +2 -2
  47. package/dist/headlessPermissionModeWire.js +11 -21
  48. package/dist/hitl/approvalHolds.d.ts +36 -0
  49. package/dist/hitl/approvalHolds.js +89 -0
  50. package/dist/hitl/approvalOutcomeNote.js +2 -1
  51. package/dist/hitl/approvalResolution.d.ts +3 -2
  52. package/dist/hitl/approvalResolution.js +21 -0
  53. package/dist/hitl/approvalsFeed.js +5 -0
  54. package/dist/hitl/askGateWire.js +3 -0
  55. package/dist/hitl/automaticAnswerVerdict.d.ts +8 -2
  56. package/dist/hitl/automaticAnswerVerdict.js +69 -21
  57. package/dist/hitl/decisionRefusal.d.ts +19 -0
  58. package/dist/hitl/decisionRefusal.js +63 -0
  59. package/dist/hitl/frameRouter.d.ts +10 -0
  60. package/dist/hitl/frameRouter.js +59 -3
  61. package/dist/hitl/gateLedger.d.ts +3 -0
  62. package/dist/hitl/gateLedger.js +17 -0
  63. package/dist/hitl/hitlBridge.d.ts +2 -2
  64. package/dist/hitl/hitlBridge.js +8 -10
  65. package/dist/hitl/hitlHostSurface.d.ts +6 -0
  66. package/dist/hitl/hitlHostSurface.js +25 -4
  67. package/dist/hitl/localAllowRule.js +2 -1
  68. package/dist/hitl/noAllowCause.d.ts +9 -0
  69. package/dist/hitl/noAllowCause.js +26 -0
  70. package/dist/hitl/parkResolver.js +6 -3
  71. package/dist/hitl/parkRowBirthWait.js +2 -1
  72. package/dist/hitl/persistedRulesWire.d.ts +6 -1
  73. package/dist/hitl/persistedRulesWire.js +12 -4
  74. package/dist/hitl/planReviewWire.d.ts +0 -1
  75. package/dist/hitl/planReviewWire.js +30 -9
  76. package/dist/hitl/sessionPolicyDeliverable.d.ts +1 -1
  77. package/dist/hitl/sessionPolicyDeliverable.js +3 -8
  78. package/dist/hitl/sessionPolicyWire.js +1 -1
  79. package/dist/hitl/toolApprovalWire.d.ts +37 -2
  80. package/dist/hitl/toolApprovalWire.js +392 -13
  81. package/dist/humanTurnRead.d.ts +15 -1
  82. package/dist/humanTurnRead.js +28 -2
  83. package/dist/index.d.ts +18 -6
  84. package/dist/index.js +14 -5
  85. package/dist/limitsWire.d.ts +0 -1
  86. package/dist/limitsWire.js +2 -10
  87. package/dist/liveModelCatalog.d.ts +14 -0
  88. package/dist/liveModelCatalog.js +61 -0
  89. package/dist/mcpProbeCapability.js +14 -9
  90. package/dist/mcpReconnect.js +13 -8
  91. package/dist/memoryComplianceCapability.js +15 -10
  92. package/dist/memoryOriginCapability.js +15 -10
  93. package/dist/modelIdentityResolvability.d.ts +4 -2
  94. package/dist/modelIdentityResolvability.js +31 -4
  95. package/dist/outputStyleSeatCapability.d.ts +16 -0
  96. package/dist/outputStyleSeatCapability.js +47 -0
  97. package/dist/ownKey.d.ts +1 -0
  98. package/dist/ownKey.js +10 -0
  99. package/dist/peerLaneCapability.js +16 -11
  100. package/dist/permissionRuleIssue.js +1 -1
  101. package/dist/permissionRulesWriteCapability.js +16 -11
  102. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  103. package/dist/permissionsAllowGrantCapability.js +38 -16
  104. package/dist/permissionsDeclaration.d.ts +10 -1
  105. package/dist/permissionsDeclaration.js +97 -15
  106. package/dist/protocolSegment.d.ts +1 -0
  107. package/dist/protocolSegment.js +13 -1
  108. package/dist/request/taskRequest.d.ts +11 -3
  109. package/dist/request/taskRequest.js +13 -1
  110. package/dist/resultFrameKeys.d.ts +4 -0
  111. package/dist/resultFrameKeys.js +13 -0
  112. package/dist/resumeRefusalCopy.js +6 -16
  113. package/dist/retryStatus.js +6 -4
  114. package/dist/rewindArchiveCapability.js +13 -8
  115. package/dist/{hitl/rosterMountedNames.d.ts → rosterMountedNames.d.ts} +1 -1
  116. package/dist/runCancelContext.js +2 -1
  117. package/dist/sealedKeyCapability.js +19 -14
  118. package/dist/seatContract.d.ts +33 -5
  119. package/dist/seatContract.js +143 -2
  120. package/dist/sessionPolicyCapability.js +15 -10
  121. package/dist/sessionReadRedactionCapability.d.ts +16 -0
  122. package/dist/sessionReadRedactionCapability.js +47 -0
  123. package/dist/sessionSyncFileHistoryCapability.js +14 -9
  124. package/dist/skillToolListsCapability.js +14 -9
  125. package/dist/sqlEngineCapability.js +18 -13
  126. package/dist/steerPriorityCapability.js +21 -16
  127. package/dist/subagent/agentStateDetail.d.ts +1 -0
  128. package/dist/subagent/agentStateDetail.js +41 -0
  129. package/dist/subagent/engineCompactWire.d.ts +15 -0
  130. package/dist/subagent/engineCompactWire.js +75 -13
  131. package/dist/supportFloor.js +1 -1
  132. package/dist/taskRequestWords.d.ts +2 -4
  133. package/dist/taskRequestWords.js +3 -2
  134. package/dist/types/engineState.d.ts +1 -1
  135. package/dist/userAttachmentEnvelope.d.ts +22 -1
  136. package/dist/userAttachmentEnvelope.js +90 -2
  137. package/dist/userInvokedSkillsCapability.js +14 -9
  138. package/dist/webSearchBackendCapability.js +21 -16
  139. package/dist/wireErrorTriage.d.ts +11 -0
  140. package/dist/wireErrorTriage.js +35 -0
  141. package/dist/wireFailureShape.d.ts +6 -0
  142. package/dist/wireFailureShape.js +30 -3
  143. package/dist/wireRefusalCopy.d.ts +6 -1
  144. package/dist/wireRefusalCopy.js +22 -8
  145. package/dist/wiringInstructionsAttribution.d.ts +10 -0
  146. package/dist/wiringInstructionsAttribution.js +67 -0
  147. package/dist/workflowClient.js +22 -1
  148. package/dist/workflowsJournalCapability.js +14 -9
  149. package/dist/writeProtectionCapability.js +28 -23
  150. package/docs/INTEGRATION-CLIENTS.md +817 -45
  151. package/package.json +5 -5
  152. package/dist/askSurvivesStandingAnswer.d.ts +0 -13
  153. package/dist/askSurvivesStandingAnswer.js +0 -53
  154. package/dist/hitl/askSurvivesPosture.d.ts +0 -20
  155. package/dist/hitl/askSurvivesPosture.js +0 -48
  156. /package/dist/{hitl/rosterMountedNames.js → rosterMountedNames.js} +0 -0
@@ -12,6 +12,8 @@ import { recordWorkflowAgentTaskId } from '../workflow.js';
12
12
  import { isTerminalStatus } from '../runTerminal.js';
13
13
  import { readToolHistoryMismatchKey } from '../toolHistoryMismatch.js';
14
14
  import { readProviderRejectionResendKey } from '../providerRejection.js';
15
+ import { readFrameStringKey } from '../ownKey.js';
16
+ import { washCredentials } from '../displayUntrusted.js';
15
17
  import { resolveEnginePanelTaskId } from '../engineAgentPanelStore.js';
16
18
  import { fleetRowAgentType } from '../fleet/fleetRowAgentType.js';
17
19
  import { chrome, mainLane, transcript, messageIdentityOf } from './ids.js';
@@ -126,12 +128,13 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
126
128
  }
127
129
  const textBlock = blocks.find(b => b?.type === 'text');
128
130
  if (textBlock && typeof textBlock.text === 'string' && textBlock.text.length > 0) {
129
- text.appendAnswer(textBlock.text);
131
+ const rowText = m._sema_api_error_message === true ? washCredentials(textBlock.text) : textBlock.text;
132
+ text.appendAnswer(rowText);
130
133
  text.beginAssistantMessage();
131
134
  yield* text.takeThinking();
132
135
  yield transcript({
133
136
  type: 'assistant',
134
- message: { role: 'assistant', content: [{ type: 'text', text: textBlock.text }], ...messageIdentityOf(m, ctx) },
137
+ message: { role: 'assistant', content: [{ type: 'text', text: rowText }], ...messageIdentityOf(m, ctx) },
135
138
  uuid: idOf(m),
136
139
  session_id: ctx.sessionId,
137
140
  parent_tool_use_id: null,
@@ -139,7 +142,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
139
142
  ...toolHistoryKeyCarry(m),
140
143
  ...providerResendKeyCarry(m),
141
144
  }, ctx.now());
142
- text.markEmittedText(textBlock.text);
145
+ text.markEmittedText(rowText);
143
146
  }
144
147
  const thinkingBlock = blocks.find(b => b?.type === 'thinking');
145
148
  if (thinkingBlock && typeof thinkingBlock.thinking === 'string') {
@@ -712,9 +715,9 @@ const resultArm = function* (m, { ctx, idOf, text, cards, panel, flags }) {
712
715
  return lowerBound && ot === 0 ? undefined : ot;
713
716
  });
714
717
  yield* text.takeAnswerSegment();
715
- const salvaged = m._sema_salvaged_result;
718
+ const salvaged = readFrameStringKey(m, '_sema_salvaged_result');
716
719
  const errorEnvelope = typeof m.subtype === 'string' && m.subtype.startsWith(ERROR_SUBTYPE_PREFIX);
717
- const terminalText = typeof salvaged === 'string' ? salvaged : !errorEnvelope && typeof m.result === 'string' ? m.result : '';
720
+ const terminalText = salvaged !== undefined ? salvaged : !errorEnvelope && typeof m.result === 'string' ? m.result : '';
718
721
  const committed = text.lastCommittedAnswerText;
719
722
  const terminalIdentity = messageIdentityOf(m, ctx);
720
723
  const emitTerminal = function* (body, tag) {
@@ -1,6 +1,6 @@
1
1
  import { isCcToolDenialKind, isCcToolDenialKindADenial, isGateDeniedByWord } from '../../gateVocabulary.js';
2
2
  import { stamp } from '../types.js';
3
- import { putOwnKey } from '../../ownKey.js';
3
+ import { putOwnKey, readFrameStringKey } from '../../ownKey.js';
4
4
  import { REDACTION_MARKER_PREFIX, washCredentials } from '../../displayUntrusted.js';
5
5
  import { readEffectiveReasoning, readEffectiveMemoryScopes } from '../../effectiveFacts.js';
6
6
  import { isReviewPark, readRunTerminal, runTerminalCode, runTerminalGateToolName, } from '../../runTerminal.js';
@@ -522,8 +522,7 @@ export function doneToSdkResult(ev, ctx, observed) {
522
522
  const durationMs = elapsedMs(ctx);
523
523
  const errorCode = runTerminalCode(terminal);
524
524
  const degraded = degradedOf(r);
525
- const parkFailedRaw = ev._sema_park_failed_reason;
526
- const parkFailedReason = typeof parkFailedRaw === 'string' && parkFailedRaw.length > 0 ? parkFailedRaw : undefined;
525
+ const parkFailedReason = readFrameStringKey(ev, '_sema_park_failed_reason');
527
526
  const errorBase = { durationMs, stats, model: r.model, errorCode, degraded, observed, facts: effectiveFactParts(rec), parkFailedReason, abortCause: abortCauseWordOf(ev) };
528
527
  if (terminal?.kind === 'failed') {
529
528
  const failedCode = terminal.code;
@@ -3,6 +3,7 @@ import { isStatusDerivedCoarseCode } from '../engineErrorCodes.js';
3
3
  import { clientHasVerbs, engineWireFor, INJECTED_WIRE_SETTLE_MS, noAnswerDetailFrom, sendOnce, settleInjected } from '../engineWireFor.js';
4
4
  import { engineWireDebugEnabled } from '../engineWireTarget.js';
5
5
  import { hostLog } from '../host.js';
6
+ import { wireFailureTextsOf } from '../wireFailureShape.js';
6
7
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
7
8
  const NOTHING_SENT = ' — nothing sent';
8
9
  const DETAIL_BAD_SESSION = `the session id must be a non-empty string${NOTHING_SENT}`;
@@ -164,7 +165,7 @@ function classifyFailure(e) {
164
165
  try {
165
166
  if (typeof e !== 'object' || e === null) {
166
167
  note('error (non-object failure)');
167
- return { kind: 'error', detail: `the stop request failed: ${String(e).slice(0, 200)}` };
168
+ return { kind: 'error', detail: `the stop request failed: ${wireFailureTextsOf(e).reason.slice(0, 200)}` };
168
169
  }
169
170
  const d = diagOf(e);
170
171
  if (d.status === undefined && d.errorCode === undefined) {
@@ -1,5 +1,6 @@
1
1
  import { type EngineProbeOpts, type WireAuthTokenSource } from './engineWireSdk.js';
2
2
  import type { ReasoningEffort } from './effortWire.js';
3
+ import type { ToolRosterView } from './toolRoster.js';
3
4
  export declare const MAX_TASK_AGENTS = 32;
4
5
  export declare const MAX_AGENT_NAME_CHARS = 64;
5
6
  export declare const MAX_AGENT_TEXT_CHARS = 4096;
@@ -56,11 +57,25 @@ export type ProjectAgentsDeps = {
56
57
  };
57
58
  effortToThinking: (effort: unknown) => WireThinkingLevel | undefined;
58
59
  skillSpecs?: TaskAgentWireSkill[];
60
+ toolRoster?: ToolRosterView | null;
61
+ withholdRefusedDenyLists?: boolean;
62
+ agentSourceLabel?: (agentName: string) => string | undefined;
59
63
  };
60
64
  export type ProjectAgentsResult = {
61
65
  agents: TaskAgentWireItem[] | undefined;
62
66
  warnings: string[];
67
+ toolListFlaws?: readonly AgentToolListFlaw[];
63
68
  };
69
+ export type AgentToolListFlawKind = 'retired' | 'unprefixed_namespace' | 'case_twin';
70
+ export interface AgentToolListFlaw {
71
+ readonly agent: string;
72
+ readonly list: 'tools' | 'disallowedTools';
73
+ readonly entry: string;
74
+ readonly kind: AgentToolListFlawKind;
75
+ readonly currentName?: string;
76
+ readonly toolName?: string;
77
+ }
78
+ export declare function agentToolListFlaws(agents: readonly TaskAgentWireItem[] | undefined, roster?: ToolRosterView | null): AgentToolListFlaw[];
64
79
  export declare function projectAgentsForWire(defs: readonly AgentDefLike[], deps: ProjectAgentsDeps): ProjectAgentsResult;
65
80
  export declare function engineSupportsTaskAgents(baseUrl: string, opts?: EngineProbeOpts): Promise<boolean | undefined>;
66
81
  export type WireConfig = {
@@ -2,6 +2,10 @@ import { hostEnv } from './hostEnv.js';
2
2
  import { makeEngineWireClient } from './engineWireSdk.js';
3
3
  import { frozenSet } from './frozenSet.js';
4
4
  import { settleWithinDeadline } from './abortableSleep.js';
5
+ import { isEngineRefusedToolName } from './engineRefusedToolName.js';
6
+ import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from './generated/toolNameTables.js';
7
+ import { rosterMountedNames } from './rosterMountedNames.js';
8
+ import { capForDisplay } from './fleetTaskDesc.js';
5
9
  export const MAX_TASK_AGENTS = 32;
6
10
  export const MAX_AGENT_NAME_CHARS = 64;
7
11
  export const MAX_AGENT_TEXT_CHARS = 4_096;
@@ -38,6 +42,82 @@ const SOURCE_RANK = {
38
42
  function sourceRank(source) {
39
43
  return SOURCE_RANK[source] ?? 5;
40
44
  }
45
+ const RETIRED_TABLE_CURRENT_NAMES = frozenSet([...CORE_RETIRED_TOOL_CURRENT_NAMES.values()].filter((v) => typeof v === 'string'));
46
+ function unescapedIndexOf(s, ch, last) {
47
+ for (let k = 0; k < s.length; k++) {
48
+ const i = last ? s.length - 1 - k : k;
49
+ if (s[i] !== ch)
50
+ continue;
51
+ let backslashes = 0;
52
+ for (let j = i - 1; j >= 0 && s[j] === '\\'; j--)
53
+ backslashes++;
54
+ if (backslashes % 2 === 0)
55
+ return i;
56
+ }
57
+ return -1;
58
+ }
59
+ function deniedEntryToolName(entry) {
60
+ if (entry !== entry.trim())
61
+ return undefined;
62
+ const open = unescapedIndexOf(entry, '(', false);
63
+ if (open === -1)
64
+ return entry;
65
+ const close = unescapedIndexOf(entry, ')', true);
66
+ if (close === -1 || close <= open || close !== entry.length - 1)
67
+ return entry;
68
+ const name = entry.substring(0, open);
69
+ return name.length === 0 ? entry : name.trim();
70
+ }
71
+ function toolNameFlawOf(entry, names, list) {
72
+ if (typeof entry !== 'string' || entry.length === 0 || entry === '*')
73
+ return undefined;
74
+ const name = list === 'disallowedTools' ? deniedEntryToolName(entry) : entry.includes('(') ? undefined : entry;
75
+ if (name === undefined || name.length === 0 || name === '*' || names.has(name))
76
+ return undefined;
77
+ const named = name !== entry ? { toolName: name } : {};
78
+ if (isEngineRefusedToolName(name)) {
79
+ const current = CORE_RETIRED_TOOL_CURRENT_NAMES.get(name);
80
+ if (current === undefined)
81
+ return { kind: 'unprefixed_namespace', ...named };
82
+ return typeof current === 'string' ? { kind: 'retired', currentName: current, ...named } : { kind: 'retired', ...named };
83
+ }
84
+ if (CORE_PROTOCOL_PREFIXES.some((prefix) => name.startsWith(prefix)))
85
+ return undefined;
86
+ const folded = name.toLowerCase();
87
+ for (const n of names)
88
+ if (n !== name && n.toLowerCase() === folded)
89
+ return { kind: 'case_twin', currentName: n, ...named };
90
+ return undefined;
91
+ }
92
+ export function agentToolListFlaws(agents, roster) {
93
+ if (!Array.isArray(agents) || agents.length === 0)
94
+ return [];
95
+ const mounted = rosterMountedNames(roster);
96
+ const names = typeof mounted === 'object' ? mounted : RETIRED_TABLE_CURRENT_NAMES;
97
+ const out = [];
98
+ for (const a of agents) {
99
+ if (a === null || typeof a !== 'object' || typeof a.name !== 'string')
100
+ continue;
101
+ for (const [list, entries] of [['disallowedTools', a.denyTools], ['tools', a.allowTools]]) {
102
+ if (!Array.isArray(entries))
103
+ continue;
104
+ for (const entry of entries) {
105
+ const f = toolNameFlawOf(entry, names, list);
106
+ if (f !== undefined)
107
+ out.push({ agent: a.name, list, entry: entry, ...f });
108
+ }
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ function toolListFlawPhrase(f) {
114
+ const entry = f.toolName !== undefined ? `${JSON.stringify(f.entry)} names the tool ${JSON.stringify(f.toolName)}, which` : JSON.stringify(f.entry);
115
+ if (f.kind === 'retired')
116
+ return `${entry} is a retired tool name${f.currentName !== undefined ? ` (now ${JSON.stringify(f.currentName)})` : ' (no longer available under any name)'}`;
117
+ if (f.kind === 'unprefixed_namespace')
118
+ return `${entry} contains "__" but does not start with a tool protocol prefix`;
119
+ return `${entry} differs from the tool ${JSON.stringify(f.currentName ?? '')} only in letter case`;
120
+ }
41
121
  export function projectAgentsForWire(defs, deps) {
42
122
  const warnings = [];
43
123
  const customs = defs
@@ -51,6 +131,7 @@ export function projectAgentsForWire(defs, deps) {
51
131
  warnings.push(`agents wire: ${customs.length} custom agents exceed the engine cap of ${MAX_TASK_AGENTS} — keeping the ${MAX_TASK_AGENTS} highest-precedence definitions, dropped: ${dropped.join(', ')}`);
52
132
  }
53
133
  const out = [];
134
+ const toolListFlaws = [];
54
135
  const seen = new Set();
55
136
  for (const def of capped) {
56
137
  const name = def.agentType;
@@ -94,6 +175,35 @@ export function projectAgentsForWire(defs, deps) {
94
175
  const denyTools = projectTools(def.disallowedTools, 'disallowedTools');
95
176
  if (denyTools)
96
177
  item.denyTools = denyTools;
178
+ const flaws = agentToolListFlaws([item], deps.toolRoster);
179
+ if (flaws.length > 0) {
180
+ let source;
181
+ try {
182
+ source = deps.agentSourceLabel?.(name);
183
+ }
184
+ catch {
185
+ source = undefined;
186
+ }
187
+ const who = `agent "${name}"${typeof source === 'string' && source.length > 0 ? ` (${capForDisplay(source, 120)})` : ''}`;
188
+ toolListFlaws.push(...flaws);
189
+ const denyFlaws = flaws.filter(f => f.list === 'disallowedTools');
190
+ const withhold = denyFlaws.length > 0 && deps.withholdRefusedDenyLists === true;
191
+ if (denyFlaws.length > 0) {
192
+ const what = `its disallowedTools ${denyFlaws.length === 1 ? 'entry' : 'entries'} ${denyFlaws.map(toolListFlawPhrase).join('; ')}`;
193
+ warnings.push(withhold
194
+ ? `agents wire: ${who} is not sent to the engine — ${what}, and this engine refuses to start a run whose agent definitions name a tool that way. Fix the entry in the agent definition; until then this agent is not available.`
195
+ : `agents wire: ${who}: ${what} — tool names are matched exactly, so ${denyFlaws.length === 1 ? 'this entry hides' : 'these entries hide'} nothing from the agent, and newer engines refuse to start a run with it. Fix the entry in the agent definition.`);
196
+ }
197
+ if (!withhold) {
198
+ for (const f of flaws) {
199
+ if (f.list !== 'tools')
200
+ continue;
201
+ warnings.push(`agents wire: ${who}: its tools entry ${toolListFlawPhrase(f)} — tool names are matched exactly, so this entry does not give the agent that tool. Fix the entry in the agent definition.`);
202
+ }
203
+ }
204
+ if (withhold)
205
+ continue;
206
+ }
97
207
  if (def.memory !== undefined) {
98
208
  if (typeof def.memory === 'string' && AGENT_MEMORY_WORDS.has(def.memory)) {
99
209
  item.memory = { scopes: [`agent:${name}`], writeScope: `agent:${name}` };
@@ -171,7 +281,7 @@ export function projectAgentsForWire(defs, deps) {
171
281
  }
172
282
  out.push(item);
173
283
  }
174
- return { agents: out.length > 0 ? out : undefined, warnings };
284
+ return { agents: out.length > 0 ? out : undefined, warnings, ...(toolListFlaws.length > 0 ? { toolListFlaws } : {}) };
175
285
  }
176
286
  export async function engineSupportsTaskAgents(baseUrl, opts) {
177
287
  if (opts?.client !== undefined) {
@@ -0,0 +1,18 @@
1
+ import { CORE_APPROVAL_REACH } from './generated/engineFactTables.js';
2
+ export type ApprovalReachWord = (typeof CORE_APPROVAL_REACH)[number];
3
+ export declare function refuseBadApprovalReach(v: unknown, who: string): void;
4
+ export type ApprovalReachWithheldCause = 'not_reported' | 'unsupported' | 'unprobed' | 'unreadable';
5
+ export type ApprovalReachDeclaration = {
6
+ readonly field: {
7
+ readonly approvalReach: Extract<ApprovalReachWord, 'none'>;
8
+ };
9
+ readonly withheld?: undefined;
10
+ } | {
11
+ readonly field: Readonly<Record<string, never>>;
12
+ readonly withheld?: {
13
+ readonly cause: ApprovalReachWithheldCause;
14
+ readonly text: string;
15
+ };
16
+ };
17
+ export declare function approvalReachDeclaration(approverReachable: boolean | null | undefined, baseUrl: string | undefined): ApprovalReachDeclaration;
18
+ export declare function approvalReachDeclarationSettled(approverReachable: boolean | null | undefined, baseUrl: string | undefined, withinMs: number): Promise<ApprovalReachDeclaration>;
@@ -0,0 +1,58 @@
1
+ import { CORE_APPROVAL_REACH } from './generated/engineFactTables.js';
2
+ import { engineCapState, engineCapsSettled } from './engineCapsCache.js';
3
+ const KEY = 'approvalReach';
4
+ const APPROVAL_REACH_NONE = 'none';
5
+ const WORDS = new Set(CORE_APPROVAL_REACH);
6
+ const shapeOf = (v) => {
7
+ try {
8
+ return v === null ? 'null' : Array.isArray(v) ? 'typeof=object / array' : typeof v === 'string' ? `typeof=string(长 ${v.length})` : `typeof=${typeof v}`;
9
+ }
10
+ catch {
11
+ return `typeof=${typeof v} / unreadable`;
12
+ }
13
+ };
14
+ export function refuseBadApprovalReach(v, who) {
15
+ if (v === undefined || v === null)
16
+ return;
17
+ if (typeof v === 'string' && WORDS.has(v))
18
+ return;
19
+ throw new TypeError(`${who}: approvalReach 只接受引擎闭集里的词(${[...CORE_APPROVAL_REACH].join(' / ')})或缺席(undefined/null)—— 收到 ${shapeOf(v)}。` +
20
+ '这一位是「这条运行上没有人能答审批」的声明,值只经片段口 approvalReachDeclaration 铸;本层若把坏值折成缺席,这一发会照常发审批卡而声明方不会知道。' +
21
+ '上游对闭集外的值同样拒收。错误文本只报形状,不回显值。');
22
+ }
23
+ function refuseBadApproverReachable(v, who) {
24
+ if (v === undefined || v === null || v === true || v === false)
25
+ return;
26
+ throw new TypeError(`${who}: approverReachable 只接受布尔或缺席(undefined/null)—— 收到 ${shapeOf(v)}。` +
27
+ '这一位是「这一车道上有没有能答审批的面」这一事实:false = 没有(片段口据此声明),true / 缺席 = 有或没说(不声明,与今天的行为相同)。错误文本只报形状,不回显值。');
28
+ }
29
+ const WITHHELD_TEXT = Object.freeze({
30
+ not_reported: 'Approval requests on this run will still be sent to this session: the connected engine does not report that it can be told nobody here can answer them, and an engine that does not know this setting rejects the whole request, so the setting was left out.',
31
+ unsupported: 'Approval requests on this run will still be sent to this session: the connected engine reports that it cannot be told nobody here can answer them, so the setting was left out.',
32
+ unprobed: 'Approval requests on this run will still be sent to this session: what the connected engine supports has not been read yet, and an engine that does not know this setting rejects the whole request, so the setting was held back.',
33
+ unreadable: 'Approval requests on this run will still be sent to this session: the connected engine reported whether it can be told nobody here can answer them in a form this version cannot read, so the setting was left out.',
34
+ });
35
+ const WITHHELD_CAUSE_OF = {
36
+ absent: 'not_reported',
37
+ false: 'unsupported',
38
+ unprobed: 'unprobed',
39
+ non_boolean: 'unreadable',
40
+ };
41
+ const EMPTY = Object.freeze({ field: Object.freeze({}) });
42
+ export function approvalReachDeclaration(approverReachable, baseUrl) {
43
+ refuseBadApproverReachable(approverReachable, 'approvalReachDeclaration');
44
+ if (approverReachable !== false)
45
+ return EMPTY;
46
+ const state = engineCapState(baseUrl, KEY);
47
+ if (state === 'true')
48
+ return Object.freeze({ field: Object.freeze({ approvalReach: APPROVAL_REACH_NONE }) });
49
+ const cause = WITHHELD_CAUSE_OF[state];
50
+ return Object.freeze({ field: Object.freeze({}), withheld: Object.freeze({ cause, text: WITHHELD_TEXT[cause] }) });
51
+ }
52
+ export async function approvalReachDeclarationSettled(approverReachable, baseUrl, withinMs) {
53
+ refuseBadApproverReachable(approverReachable, 'approvalReachDeclarationSettled');
54
+ if (approverReachable !== false)
55
+ return approvalReachDeclaration(approverReachable, baseUrl);
56
+ await engineCapsSettled(baseUrl, { withinMs: typeof withinMs === 'number' ? withinMs : 0 });
57
+ return approvalReachDeclaration(approverReachable, baseUrl);
58
+ }
@@ -1,18 +1,23 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
2
  import { createEngineCapReader } from './engineCapReader.js';
3
3
  export function projectApprovalsStreamLiveCapability(caps) {
4
- if (caps === null || typeof caps !== 'object')
5
- return undefined;
6
- if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
7
- return undefined;
8
- if (!Object.hasOwn(caps, 'approvalsStreamLive'))
9
- return { kind: 'not_reported' };
10
- const v = caps.approvalsStreamLive;
11
- if (v === undefined)
12
- return { kind: 'not_reported' };
13
- if (typeof v !== 'boolean')
4
+ try {
5
+ if (caps === null || typeof caps !== 'object')
6
+ return undefined;
7
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
8
+ return undefined;
9
+ if (!Object.hasOwn(caps, 'approvalsStreamLive'))
10
+ return { kind: 'not_reported' };
11
+ const v = caps.approvalsStreamLive;
12
+ if (v === undefined)
13
+ return { kind: 'not_reported' };
14
+ if (typeof v !== 'boolean')
15
+ return undefined;
16
+ return { kind: 'present', live: v };
17
+ }
18
+ catch {
14
19
  return undefined;
15
- return { kind: 'present', live: v };
20
+ }
16
21
  }
17
22
  const reader = createEngineCapReader({
18
23
  name: 'approvalsStreamLive',
@@ -0,0 +1,16 @@
1
+ export type CallerCwdReading = {
2
+ kind: 'unobserved';
3
+ } | {
4
+ kind: 'not_reported';
5
+ } | {
6
+ kind: 'present';
7
+ value: boolean;
8
+ };
9
+ export declare function projectCallerCwdCapability(caps: unknown): CallerCwdReading | undefined;
10
+ export declare function noteEngineCapsForCallerCwd(baseUrl: string, caps: unknown, opts?: {
11
+ generation?: number;
12
+ }): void;
13
+ export declare function observedCallerCwd(baseUrl?: string | undefined): CallerCwdReading;
14
+ export declare function callerCwdAdmitted(reading: CallerCwdReading | undefined): boolean;
15
+ export declare function forgetCallerCwdReading(baseUrl: string | undefined): void;
16
+ export declare function __resetCallerCwdReadingsForTests(): void;
@@ -0,0 +1,47 @@
1
+ import { engineWireTarget } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ import { readOwnDataValue } from './ownKey.js';
4
+ export function projectCallerCwdCapability(caps) {
5
+ try {
6
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
7
+ return undefined;
8
+ if (!Object.hasOwn(caps, 'callerCwd'))
9
+ return { kind: 'not_reported' };
10
+ const v = caps.callerCwd;
11
+ if (v === undefined)
12
+ return { kind: 'not_reported' };
13
+ return typeof v === 'boolean' ? { kind: 'present', value: v } : undefined;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ const reader = createEngineCapReader({
20
+ name: 'callerCwd',
21
+ project: projectCallerCwdCapability,
22
+ makeUnobserved: () => ({ kind: 'unobserved' }),
23
+ });
24
+ export function noteEngineCapsForCallerCwd(baseUrl, caps, opts) {
25
+ reader.note(baseUrl, caps, opts);
26
+ }
27
+ export function observedCallerCwd(baseUrl = engineWireTarget()?.baseUrl) {
28
+ return reader.observed(baseUrl);
29
+ }
30
+ export function callerCwdAdmitted(reading) {
31
+ try {
32
+ if (reading === null || reading === undefined || typeof reading !== 'object' || Array.isArray(reading))
33
+ return false;
34
+ if (readOwnDataValue(reading, 'kind') !== 'present')
35
+ return false;
36
+ return readOwnDataValue(reading, 'value') === true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ export function forgetCallerCwdReading(baseUrl) {
43
+ reader.forget(baseUrl);
44
+ }
45
+ export function __resetCallerCwdReadingsForTests() {
46
+ reader.__resetForTests();
47
+ }
@@ -90,12 +90,9 @@ export class ControlRouter {
90
90
  return await this.client.runs.cancel(this.taskId, opts?.signal !== undefined ? { signal: opts.signal } : undefined);
91
91
  }
92
92
  catch (e) {
93
- if (cancelConflictFromError(e)?.kind === 'run_not_running') {
94
- throw controlSafetyRefusal('cancel_run_not_running', 'runs.cancel 409 conflict.run_not_running', cancelConflictContent({ kind: 'run_not_running' }), e);
95
- }
96
- if (isCancelSuspendedConflict(e)) {
97
- throw controlSafetyRefusal('cancel_lost_race', 'runs.cancel 409', 'the pending gate was decided or expired concurrently (lost CAS race); ' +
98
- 'check what that decision did to the run before retrying the cancel', e);
93
+ const conflict = cancelConflictFromError(e);
94
+ if (conflict !== null) {
95
+ throw controlSafetyRefusal(conflict.kind === 'run_not_running' ? 'cancel_run_not_running' : 'cancel_lost_race', conflict.code === undefined ? 'runs.cancel 409' : `runs.cancel 409 ${conflict.code}`, cancelConflictContent(conflict), e);
99
96
  }
100
97
  if (isNotFound(e)) {
101
98
  throw controlSafetyRefusal('not_found', 'runs.cancel 404', `${CANCEL_NOT_FOUND_COPY.happened} — ${CANCEL_NOT_FOUND_COPY.nextStep}`, e);
@@ -310,10 +307,6 @@ export function steerBodyOf(input, reading) {
310
307
  throw steerLocalRefusal('priority_not_applied', tierMode);
311
308
  return Object.freeze({ ...base, priority });
312
309
  }
313
- function isCancelSuspendedConflict(e) {
314
- const { name, status } = errCodes(e);
315
- return name === 'ConflictError' || status === 409;
316
- }
317
310
  function isNotFound(e) {
318
311
  const { name, status } = errCodes(e);
319
312
  return name === 'NotFoundError' || status === 404;
@@ -48,6 +48,49 @@ export interface ValidatePermissionRulesOptionsView {
48
48
  readonly [base: string]: string | undefined;
49
49
  };
50
50
  }
51
+ export interface AllowListReadingView {
52
+ readonly inert: readonly {
53
+ readonly entry: string;
54
+ readonly reason: string;
55
+ }[];
56
+ }
57
+ export interface RouteModelView {
58
+ readonly id: string;
59
+ readonly baseUrl: string;
60
+ }
61
+ export interface RouteCredentialInputView {
62
+ optionsApiKey?: string;
63
+ optionsHeaders?: Record<string, string>;
64
+ modelHeaders?: Record<string, string>;
65
+ configApiKey?: string;
66
+ configHeaders?: Record<string, string>;
67
+ }
68
+ export interface RouteCredentialView {
69
+ source: 'per-model' | 'deployment-config' | 'absent' | (string & {});
70
+ apiKey?: string;
71
+ headersBorne?: boolean;
72
+ carrierBag?: 'options' | 'model' | 'config' | (string & {});
73
+ }
74
+ export type RouteAdjudicationView = {
75
+ ok: true;
76
+ posture: 'per-model' | 'paired' | 'unpinned' | 'keyless' | (string & {});
77
+ } | {
78
+ ok: false;
79
+ code: 'route.credential_mismatch' | 'route.credential_missing' | (string & {});
80
+ detail: RouteRefusalDetailView;
81
+ };
82
+ export interface RouteRefusalDetailView {
83
+ modelId: string;
84
+ entryBaseUrl: string;
85
+ requestBaseUrl: string;
86
+ configBaseUrl?: string;
87
+ fixHint: string;
88
+ }
89
+ export type RoutePairingStatusView = `ok:${string}` | `broken:${string}` | 'unknown';
90
+ export interface PerModelAuthView {
91
+ apiKey?: string;
92
+ headers?: Record<string, string>;
93
+ }
51
94
  export interface CoreValuePorts {
52
95
  resolveAutonomousLoopPrompt: (prompt: string, opts?: AutonomousLoopPromptOptionsView) => string | null;
53
96
  loosenReasons: (prior: SessionPermissionRules, next: SessionPermissionRules) => string[];
@@ -60,8 +103,18 @@ export interface CoreValuePorts {
60
103
  validatePermissionRules: (rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView) => PermissionRuleIssueView[];
61
104
  DISCUSSION_WORKFLOW_NAME: string;
62
105
  mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string;
106
+ compileAllowList(entries: readonly string[], behavior: 'allow' | 'deny', roster?: ReadonlySet<string>): AllowListReadingView;
107
+ routeKeyOf(url: string | undefined): string;
108
+ joinRequestUrl(root: string | undefined, suffix: string): string;
109
+ resolveRouteCredential(input: RouteCredentialInputView): RouteCredentialView;
110
+ adjudicateModelRoute(model: RouteModelView, credential: RouteCredentialView, config: {
111
+ baseUrl?: string;
112
+ }): RouteAdjudicationView;
113
+ routePairingStatus(brain: {
114
+ adjudicateRoute?(model: RouteModelView, perModelAuth?: PerModelAuthView): RouteAdjudicationView | undefined;
115
+ }, model: RouteModelView, perModelAuth?: PerModelAuthView): RoutePairingStatusView;
63
116
  }
64
- export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME", "mintNamespacePrefix"];
117
+ export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME", "mintNamespacePrefix", "compileAllowList", "routeKeyOf", "joinRequestUrl", "resolveRouteCredential", "adjudicateModelRoute", "routePairingStatus"];
65
118
  export type CoreValuePortName = (typeof CORE_VALUE_PORT_NAMES)[number];
66
119
  export declare function installCoreValuePorts(ports: Partial<CoreValuePorts> | null): () => void;
67
120
  export declare function hasCoreValuePorts(): boolean;
@@ -78,3 +131,13 @@ export declare function ruleToolGrammarOf(toolName: string): 'command' | 'path'
78
131
  export declare function validatePermissionRules(rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView): PermissionRuleIssueView[] | undefined;
79
132
  export declare function discussionWorkflowName(): string | undefined;
80
133
  export declare function mintNamespacePrefix(ns: CoreProtocolNamespaceView, peer: string): string | undefined;
134
+ export declare function compileAllowList(entries: readonly string[], behavior: 'allow' | 'deny', roster?: ReadonlySet<string>): AllowListReadingView | undefined;
135
+ export declare function routeKeyOf(url: string | undefined): string | undefined;
136
+ export declare function joinRequestUrl(root: string | undefined, suffix: string): string | undefined;
137
+ export declare function resolveRouteCredential(input: RouteCredentialInputView): RouteCredentialView | undefined;
138
+ export declare function adjudicateModelRoute(model: RouteModelView, credential: RouteCredentialView, config: {
139
+ baseUrl?: string;
140
+ }): RouteAdjudicationView | undefined;
141
+ export declare function routePairingStatus(brain: {
142
+ adjudicateRoute?(model: RouteModelView, perModelAuth?: PerModelAuthView): RouteAdjudicationView | undefined;
143
+ }, model: RouteModelView, perModelAuth?: PerModelAuthView): RoutePairingStatusView | undefined;
@@ -10,6 +10,12 @@ export const CORE_VALUE_PORT_NAMES = Object.freeze([
10
10
  'validatePermissionRules',
11
11
  'DISCUSSION_WORKFLOW_NAME',
12
12
  'mintNamespacePrefix',
13
+ 'compileAllowList',
14
+ 'routeKeyOf',
15
+ 'joinRequestUrl',
16
+ 'resolveRouteCredential',
17
+ 'adjudicateModelRoute',
18
+ 'routePairingStatus',
13
19
  ]);
14
20
  let installed = null;
15
21
  export function installCoreValuePorts(ports) {
@@ -72,3 +78,27 @@ export function mintNamespacePrefix(ns, peer) {
72
78
  const fn = port('mintNamespacePrefix');
73
79
  return fn === undefined ? undefined : fn(ns, peer);
74
80
  }
81
+ export function compileAllowList(entries, behavior, roster) {
82
+ const fn = port('compileAllowList');
83
+ return fn === undefined ? undefined : fn(entries, behavior, roster);
84
+ }
85
+ export function routeKeyOf(url) {
86
+ const fn = port('routeKeyOf');
87
+ return fn === undefined ? undefined : fn(url);
88
+ }
89
+ export function joinRequestUrl(root, suffix) {
90
+ const fn = port('joinRequestUrl');
91
+ return fn === undefined ? undefined : fn(root, suffix);
92
+ }
93
+ export function resolveRouteCredential(input) {
94
+ const fn = port('resolveRouteCredential');
95
+ return fn === undefined ? undefined : fn(input);
96
+ }
97
+ export function adjudicateModelRoute(model, credential, config) {
98
+ const fn = port('adjudicateModelRoute');
99
+ return fn === undefined ? undefined : fn(model, credential, config);
100
+ }
101
+ export function routePairingStatus(brain, model, perModelAuth) {
102
+ const fn = port('routePairingStatus');
103
+ return fn === undefined ? undefined : fn(brain, model, perModelAuth);
104
+ }
@@ -10,10 +10,7 @@ export type DetachResolution = {
10
10
  on: false;
11
11
  reason: 'env-off';
12
12
  };
13
- export declare function resolveHeadlessDetach(_baseUrl: string, env?: EnvLike, _opts?: {
14
- fetchImpl?: typeof fetch;
15
- timeoutMs?: number;
16
- }): Promise<DetachResolution>;
13
+ export declare function resolveHeadlessDetach(env?: EnvLike): Promise<DetachResolution>;
17
14
  export declare const DETACH_DURABLE_OFF_400_ANCHOR = "x-detach-on-disconnect requires a durable run";
18
15
  export declare function isDetachDurableOff400(err: unknown): boolean;
19
16
  export declare function detachDurableOffHint(err: unknown): string | null;
@@ -6,7 +6,7 @@ export const HEADLESS_DETACH_ENV = 'SEMA_HEADLESS_DETACH';
6
6
  export function headlessDetachDisabledByEnv(env = hostEnv()) {
7
7
  return envFlagOff(env[HEADLESS_DETACH_ENV]);
8
8
  }
9
- export async function resolveHeadlessDetach(_baseUrl, env = hostEnv(), _opts) {
9
+ export async function resolveHeadlessDetach(env = hostEnv()) {
10
10
  if (headlessDetachDisabledByEnv(env))
11
11
  return { on: false, reason: 'env-off' };
12
12
  return { on: true, reason: 'default-on' };