@sema-agent/client-core 0.90.0 → 0.91.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 (88) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +24 -18
  3. package/dist/adapt/arms.js +9 -0
  4. package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
  5. package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
  6. package/dist/adapter/downstream/wiringManifestView.js +16 -0
  7. package/dist/adapter/runStream.js +37 -20
  8. package/dist/askSurvivesStandingAnswer.d.ts +10 -0
  9. package/dist/askSurvivesStandingAnswer.js +40 -0
  10. package/dist/attachmentSeatCapability.d.ts +11 -0
  11. package/dist/attachmentSeatCapability.js +27 -0
  12. package/dist/attachmentsWireCaps.d.ts +9 -0
  13. package/dist/attachmentsWireCaps.js +74 -21
  14. package/dist/compensations.js +10 -14
  15. package/dist/controlRouter.d.ts +9 -2
  16. package/dist/controlRouter.js +66 -28
  17. package/dist/decideFailureNote.js +4 -2
  18. package/dist/disableSkillsWire.d.ts +1 -0
  19. package/dist/disableSkillsWire.js +8 -1
  20. package/dist/displayUntrusted.js +153 -4
  21. package/dist/engineCapsCache.d.ts +3 -1
  22. package/dist/engineCapsCache.js +56 -6
  23. package/dist/engineClosedSets.d.ts +2 -0
  24. package/dist/engineClosedSets.js +3 -0
  25. package/dist/engineErrorCodes.d.ts +3 -0
  26. package/dist/engineErrorCodes.js +10 -0
  27. package/dist/generated/engineFactTables.d.ts +12 -0
  28. package/dist/generated/engineFactTables.js +47 -0
  29. package/dist/hitl/askGateWire.d.ts +1 -1
  30. package/dist/hitl/askGateWire.js +1 -1
  31. package/dist/hitl/crashConverged.d.ts +7 -0
  32. package/dist/hitl/crashConverged.js +57 -0
  33. package/dist/hitl/crashConvergedNotice.d.ts +4 -0
  34. package/dist/hitl/crashConvergedNotice.js +57 -0
  35. package/dist/hitl/hitlBridge.d.ts +7 -4
  36. package/dist/hitl/parkResolver.d.ts +7 -2
  37. package/dist/hitl/parkResolver.js +19 -7
  38. package/dist/hitl/planReviewWire.d.ts +1 -1
  39. package/dist/hitl/planReviewWire.js +18 -4
  40. package/dist/hitl/sessionPolicyWire.d.ts +5 -0
  41. package/dist/hitl/sessionPolicyWire.js +63 -0
  42. package/dist/hitl/suspendedReopen.js +2 -2
  43. package/dist/hitl/toolApprovalWire.d.ts +4 -1
  44. package/dist/hitl/toolApprovalWire.js +20 -0
  45. package/dist/hookMatcherVerdict.d.ts +29 -0
  46. package/dist/hookMatcherVerdict.js +265 -0
  47. package/dist/humanTurn.d.ts +91 -0
  48. package/dist/humanTurn.js +253 -0
  49. package/dist/humanTurnRead.d.ts +105 -0
  50. package/dist/humanTurnRead.js +331 -0
  51. package/dist/index.d.ts +25 -8
  52. package/dist/index.js +16 -6
  53. package/dist/inheritEnvWire.js +1 -1
  54. package/dist/liveModelCatalog.d.ts +2 -0
  55. package/dist/liveModelCatalog.js +51 -3
  56. package/dist/permissionRuleIssue.d.ts +1 -1
  57. package/dist/permissionRuleIssue.js +22 -3
  58. package/dist/permissionsAllowGrantCapability.d.ts +2 -1
  59. package/dist/permissionsAllowGrantCapability.js +11 -2
  60. package/dist/permissionsDeclaration.d.ts +23 -0
  61. package/dist/permissionsDeclaration.js +133 -53
  62. package/dist/promptDelivery.d.ts +11 -3
  63. package/dist/promptDelivery.js +164 -29
  64. package/dist/providerRejection.d.ts +1 -0
  65. package/dist/providerRejection.js +20 -0
  66. package/dist/readDenyTiers.d.ts +10 -0
  67. package/dist/readDenyTiers.js +29 -0
  68. package/dist/request/taskRequest.js +2 -2
  69. package/dist/resumeRefusalCopy.d.ts +11 -0
  70. package/dist/resumeRefusalCopy.js +61 -4
  71. package/dist/rewindArchiveCapability.js +4 -4
  72. package/dist/rewindWireCaps.d.ts +2 -1
  73. package/dist/rewindWireCaps.js +19 -2
  74. package/dist/seam.d.ts +2 -0
  75. package/dist/taskRequestWords.d.ts +7 -1
  76. package/dist/taskRequestWords.js +18 -8
  77. package/dist/toolHistoryBadTurn.d.ts +1 -0
  78. package/dist/toolHistoryBadTurn.js +89 -0
  79. package/dist/toolHistoryMismatch.d.ts +2 -1
  80. package/dist/toolHistoryMismatch.js +19 -6
  81. package/dist/userAttachmentEnvelope.d.ts +61 -0
  82. package/dist/userAttachmentEnvelope.js +204 -0
  83. package/dist/wireFailureShape.d.ts +1 -0
  84. package/dist/wireFailureShape.js +4 -0
  85. package/dist/wireRefusalCopy.d.ts +6 -0
  86. package/dist/wireRefusalCopy.js +26 -3
  87. package/docs/INTEGRATION-CLIENTS.md +875 -14
  88. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { readAsyncLaunchedAgentReceipt } from '../toolResult.js';
11
11
  import { isWorkflowAgentTaskId, recordWorkflowAgentTaskId } from '../workflow.js';
12
12
  import { isTerminalStatus } from '../runTerminal.js';
13
13
  import { readToolHistoryMismatchKey } from '../toolHistoryMismatch.js';
14
+ import { readProviderRejectionResendKey } from '../providerRejection.js';
14
15
  import { resolveEnginePanelTaskId } from '../engineAgentPanelStore.js';
15
16
  import { fleetRowAgentType } from '../fleet/fleetRowAgentType.js';
16
17
  import { chrome, mainLane, transcript, messageIdentityOf } from './ids.js';
@@ -23,6 +24,13 @@ function toolHistoryKeyCarry(m) {
23
24
  const key = readToolHistoryMismatchKey(typeof raw === 'object' && raw !== null ? raw : undefined);
24
25
  return key === undefined ? {} : { _sema_tool_history_mismatch: key };
25
26
  }
27
+ function providerResendKeyCarry(m) {
28
+ if (m._sema_api_error_message !== true)
29
+ return {};
30
+ const raw = m._sema_provider_rejection_resend;
31
+ const key = readProviderRejectionResendKey(typeof raw === 'object' && raw !== null ? raw : undefined);
32
+ return key === undefined ? {} : { _sema_provider_rejection_resend: key };
33
+ }
26
34
  const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
27
35
  const message = m.message;
28
36
  const blocks = Array.isArray(message?.content)
@@ -129,6 +137,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
129
137
  parent_tool_use_id: null,
130
138
  ...(m._sema_api_error_message === true ? { _sema_api_error_message: true } : {}),
131
139
  ...toolHistoryKeyCarry(m),
140
+ ...providerResendKeyCarry(m),
132
141
  }, ctx.now());
133
142
  text.markEmittedText(textBlock.text);
134
143
  }
@@ -1,4 +1,4 @@
1
- import { readInstructionsSegment, wiringManifestViewOf } from './wiringManifestView.js';
1
+ import { readFleetSegment, readInstructionsSegment, wiringManifestViewOf } from './wiringManifestView.js';
2
2
  import { stamp, snapshotSegmentIdentity, } from '../types.js';
3
3
  import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
4
4
  import { ccToolDenialKindForToolEnd, gateOutcomeOf } from '../../gateOutcome.js';
@@ -551,7 +551,8 @@ function wiringManifestSupersetBody(ev) {
551
551
  const hands = projectHandsSection(ev);
552
552
  const fileHistory = projectFileHistorySection(ev);
553
553
  const instructions = readInstructionsSegment(ev, 'instructions');
554
- if (modelGate === undefined && autoMode === undefined && mcp === undefined && tools === undefined && hooks === undefined && lsp === undefined && writeProtection === undefined && autoConsolidation === undefined && readDeny === undefined && hands === undefined && fileHistory === undefined && instructions === undefined)
554
+ const fleet = readFleetSegment(ev, 'fleet');
555
+ if (modelGate === undefined && autoMode === undefined && mcp === undefined && tools === undefined && hooks === undefined && lsp === undefined && writeProtection === undefined && autoConsolidation === undefined && readDeny === undefined && hands === undefined && fileHistory === undefined && instructions === undefined && fleet === undefined)
555
556
  return undefined;
556
557
  return {
557
558
  ...(modelGate !== undefined ? { _sema_modelGate: modelGate } : {}),
@@ -566,6 +567,7 @@ function wiringManifestSupersetBody(ev) {
566
567
  ...(hands !== undefined ? { _sema_hands: hands } : {}),
567
568
  ...(fileHistory !== undefined ? { _sema_fileHistory: fileHistory } : {}),
568
569
  ...(instructions !== undefined ? { _sema_instructions: instructions } : {}),
570
+ ...(fleet !== undefined ? { _sema_fleet: fleet } : {}),
569
571
  };
570
572
  }
571
573
  function projectFileHistorySection(holder) {
@@ -15,8 +15,12 @@ export type WiringManifestSections = {
15
15
  _sema_instructions?: {
16
16
  readonly selected: readonly string[];
17
17
  };
18
+ _sema_fleet?: WiringManifestFleetView;
18
19
  eventId?: string;
19
20
  };
21
+ export interface WiringManifestFleetView {
22
+ readonly subagentTranscripts: string;
23
+ }
20
24
  export interface WiringManifestView {
21
25
  modelGate?: WiringManifestModelGate;
22
26
  autoMode?: WiringManifestAutoMode;
@@ -32,9 +36,11 @@ export interface WiringManifestView {
32
36
  instructions?: {
33
37
  readonly selected: readonly string[];
34
38
  };
39
+ fleet?: WiringManifestFleetView;
35
40
  eventId?: string;
36
41
  }
37
42
  export declare function readInstructionsSegment(holder: object, key: 'instructions' | '_sema_instructions'): {
38
43
  readonly selected: readonly string[];
39
44
  } | undefined;
45
+ export declare function readFleetSegment(holder: object, key: 'fleet' | '_sema_fleet'): WiringManifestFleetView | undefined;
40
46
  export declare function wiringManifestViewOf(s: WiringManifestSections): WiringManifestView | undefined;
@@ -27,9 +27,24 @@ export function readInstructionsSegment(holder, key) {
27
27
  return undefined;
28
28
  }
29
29
  }
30
+ export function readFleetSegment(holder, key) {
31
+ try {
32
+ if (!Object.hasOwn(holder, key))
33
+ return undefined;
34
+ const raw = holder[key];
35
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw) || !Object.hasOwn(raw, 'subagentTranscripts'))
36
+ return undefined;
37
+ const tier = raw.subagentTranscripts;
38
+ return typeof tier === 'string' && tier.length > 0 ? Object.freeze({ subagentTranscripts: tier }) : undefined;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
30
44
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
31
45
  export function wiringManifestViewOf(s) {
32
46
  const instructions = readInstructionsSegment(s, '_sema_instructions');
47
+ const fleet = readFleetSegment(s, '_sema_fleet');
33
48
  const view = {
34
49
  ...(isObj(s._sema_modelGate) ? { modelGate: s._sema_modelGate } : {}),
35
50
  ...(isObj(s._sema_autoMode) ? { autoMode: s._sema_autoMode } : {}),
@@ -43,6 +58,7 @@ export function wiringManifestViewOf(s) {
43
58
  ...(isObj(s._sema_hands) && typeof s._sema_hands.mounted === 'boolean' ? { hands: s._sema_hands } : {}),
44
59
  ...(typeof s._sema_fileHistory === 'string' && s._sema_fileHistory.length > 0 ? { fileHistory: s._sema_fileHistory } : {}),
45
60
  ...(instructions !== undefined ? { instructions } : {}),
61
+ ...(fleet !== undefined ? { fleet } : {}),
46
62
  };
47
63
  if (Object.keys(view).length === 0)
48
64
  return undefined;
@@ -7,6 +7,7 @@ import { ACTIVE_RUN_BUSY_ERROR_CODE, OUTPUT_INVALID, isCorruptSessionCode, isLim
7
7
  import { corruptSessionContent } from '../corruptSessionCopy.js';
8
8
  import { classifyToolHistoryMismatch, noteToolHistoryTerminal, openToolHistoryGeneration, TOOL_HISTORY_REWIND_SENTENCE, toolHistoryMismatchRowKey, toolHistoryRepeatSentence, toolHistoryRewindOffered, } from '../toolHistoryMismatch.js';
9
9
  import { isReviewPark, readRunTerminal, runTerminalCode } from '../runTerminal.js';
10
+ import { providerRejectionResend } from '../providerRejection.js';
10
11
  import { washCredentials } from '../displayUntrusted.js';
11
12
  import { ccToolDenialKindForToolEnd, gateDeniedBy, gateOutcomeOf } from '../gateOutcome.js';
12
13
  import { resumeReopenContent, resumeReopenFromError, suspendedReopenOf } from '../hitl/suspendedReopen.js';
@@ -121,7 +122,7 @@ export function isModelOutputErrorRowText(text) {
121
122
  return text.trimStart().startsWith(`${MODEL_OUTPUT_ERROR_PREFIX}:`);
122
123
  }
123
124
  export const OUTCOME_UNKNOWN_ROW_PREFIX = 'Outcome unknown';
124
- function syntheticTerminalRow(ctx, text, historyKey) {
125
+ function syntheticTerminalRow(ctx, text, historyKey, resend) {
125
126
  return stamp(ctx, {
126
127
  uuid: undefined,
127
128
  session_id: undefined,
@@ -130,6 +131,7 @@ function syntheticTerminalRow(ctx, text, historyKey) {
130
131
  parent_tool_use_id: null,
131
132
  _sema_api_error_message: true,
132
133
  ...(historyKey !== undefined ? { _sema_tool_history_mismatch: historyKey } : {}),
134
+ ...(resend !== undefined ? { _sema_provider_rejection_resend: { resend: resend.resend, text: resend.text } } : {}),
133
135
  });
134
136
  }
135
137
  function engineRunIdOf(result) {
@@ -264,41 +266,52 @@ export function toolHistoryErrorCarrierOf(terminalOrResult) {
264
266
  return empty();
265
267
  }
266
268
  }
269
+ function terminalRowArmOf(facts) {
270
+ if (facts.errText === null)
271
+ return null;
272
+ return facts.busy.busy ? 'busy' : facts.governance ? 'governance' : isModelOutputErrorText(facts.errText) ? 'modelOutput' : 'apiError';
273
+ }
267
274
  function toolHistoryCarrierFromFacts(x, facts) {
268
- const empty = () => mintToolHistoryCarrier(undefined, undefined, undefined);
275
+ return apiErrorTerminalFromFacts(x, facts).carrier;
276
+ }
277
+ function apiErrorTerminalFromFacts(x, facts) {
278
+ const none = () => ({ carrier: mintToolHistoryCarrier(undefined, undefined, undefined), detail: undefined });
269
279
  try {
270
280
  const type = ownDataBit(x, 'type');
271
- if (facts.errText === null || facts.busy.busy || facts.governance || isModelOutputErrorText(facts.errText))
272
- return empty();
281
+ if (terminalRowArmOf(facts) !== 'apiError')
282
+ return none();
273
283
  if (type === 'failed')
274
- return mintToolHistoryCarrier(nonEmptyStr(ownDataBit(x, 'errorCode')), undefined, nonEmptyStr(ownDataBit(x, 'errorMessage')));
284
+ return { carrier: mintToolHistoryCarrier(nonEmptyStr(ownDataBit(x, 'errorCode')), undefined, nonEmptyStr(ownDataBit(x, 'errorMessage'))), detail: undefined };
275
285
  const result = type === 'done' ? plainObjectOf(ownDataBit(x, 'result')) : x;
276
286
  if (result === undefined)
277
- return empty();
287
+ return none();
278
288
  const snap = ownTerminalSnapshot(result);
279
289
  if (snap === undefined)
280
- return empty();
290
+ return none();
281
291
  const read = readRunTerminal(snap);
282
292
  if (read === null || read.kind !== 'failed')
283
- return empty();
293
+ return none();
284
294
  const detail = plainObjectOf(ownDataBit(read, 'detail'));
285
295
  const detailStatus = detail !== undefined && ownDataBit(detail, 'kind') === PROVIDER_REJECTED_DETAIL ? ownDataBit(detail, 'status') : undefined;
286
296
  const apiFailure = plainObjectOf(ownDataBit(result, 'apiFailure'));
287
297
  const apiStatus = apiFailure === undefined ? undefined : ownDataBit(apiFailure, 'status');
288
298
  const status = typeof apiStatus === 'number' && Number.isFinite(apiStatus) ? apiStatus : typeof detailStatus === 'number' ? detailStatus : undefined;
289
- return mintToolHistoryCarrier(nonEmptyStr(ownDataBit(read, 'code')), status, nonEmptyStr(ownDataBit(read, 'message')));
299
+ return {
300
+ carrier: mintToolHistoryCarrier(nonEmptyStr(ownDataBit(read, 'code')), status, nonEmptyStr(ownDataBit(read, 'message'))),
301
+ detail: detail,
302
+ };
290
303
  }
291
304
  catch {
292
- return empty();
305
+ return none();
293
306
  }
294
307
  }
295
- function toolHistoryCarrierOfRowFrame(ev, facts) {
308
+ function apiErrorTerminalOfRowFrame(ev, facts) {
296
309
  try {
297
310
  const x = plainObjectOf(ev);
298
- return x === undefined ? mintToolHistoryCarrier(undefined, undefined, undefined) : toolHistoryCarrierFromFacts(x, facts);
311
+ return x === undefined ? { carrier: mintToolHistoryCarrier(undefined, undefined, undefined), detail: undefined } : apiErrorTerminalFromFacts(x, facts);
299
312
  }
300
313
  catch {
301
- return mintToolHistoryCarrier(undefined, undefined, undefined);
314
+ return { carrier: mintToolHistoryCarrier(undefined, undefined, undefined), detail: undefined };
302
315
  }
303
316
  }
304
317
  const reportedDroppedTypes = new Set();
@@ -631,23 +644,24 @@ async function* runStreamInner(events, ctx, handle = {}) {
631
644
  const maxTokHint = /max_tokens/i.test(errText)
632
645
  ? '\nyour provider caps max_tokens lower — lower it in /model (press m, or M to type a value)'
633
646
  : '';
634
- const governance = rowFacts.governance;
635
647
  const busyHandle = busy.handle;
636
- const rowText = busy.busy
648
+ const arm = terminalRowArmOf(rowFacts);
649
+ const rowText = arm === 'busy'
637
650
  ? `This session is locked by an earlier run${busyHandle ? ` (run ${busyHandle})` : ''} that ` +
638
651
  `has not been released, so this message was NOT sent. Nothing releases it on its own — ` +
639
652
  `cancel that run (POST /v1/runs/${busyHandle ?? '<id>'}/cancel), or start a new session.`
640
- : governance
653
+ : arm === 'governance'
641
654
  ?
642
655
  `${isGovernanceStopRowText(errText) ? errText : `${RUN_STOPPED_MESSAGE_PREFIX}: ${errText}`}${maxTokHint}`
643
- : isModelOutputErrorText(errText)
656
+ : arm === 'modelOutput'
644
657
  ?
645
658
  `${MODEL_OUTPUT_ERROR_PREFIX}: ${errText}${maxTokHint}`
646
659
  : `API Error: ${errText}${maxTokHint}`;
647
660
  const sessionUnreadable = ev.type === 'failed'
648
661
  ? isCorruptSessionCode(ev.errorCode)
649
662
  : doneTerminal?.kind === 'failed' && isCorruptSessionCode(doneTerminal.code);
650
- const historyMismatch = classifyToolHistoryMismatch(toolHistoryCarrierOfRowFrame(ev, rowFacts));
663
+ const historyTerminal = apiErrorTerminalOfRowFrame(ev, rowFacts);
664
+ const historyMismatch = classifyToolHistoryMismatch(historyTerminal.carrier);
651
665
  const historyEchoedSid = ev.type === 'done' ? engineSessionIdOf(ev.result) : undefined;
652
666
  const historyRepeat = noteToolHistoryTerminal({
653
667
  sessionId: historyEchoedSid ?? ctx.sessionId,
@@ -657,10 +671,13 @@ async function* runStreamInner(events, ctx, handle = {}) {
657
671
  replay: ev.type === 'done' && ev.replay === true,
658
672
  mismatch: historyMismatch,
659
673
  });
660
- const rewindLine = historyMismatch !== null && ctx.lane !== 'print' && ctx.lane !== 'utility' && toolHistoryRewindOffered(ctx.offersRewind)
674
+ const rewindOffered = historyMismatch !== null && ctx.lane !== 'print' && ctx.lane !== 'utility' && toolHistoryRewindOffered(ctx.offersRewind);
675
+ const rewindLine = rewindOffered
661
676
  ? `\n${TOOL_HISTORY_REWIND_SENTENCE}${historyRepeat !== undefined && historyRepeat >= 2 ? ` ${toolHistoryRepeatSentence(historyRepeat)}` : ''}`
662
677
  : '';
663
- yield syntheticTerminalRow(ctx, `${sessionUnreadable ? `${rowText}\n${corruptSessionContent()}` : rowText}${rewindLine}`, historyMismatch === null ? undefined : toolHistoryMismatchRowKey(historyMismatch, historyRepeat));
678
+ const resend = historyTerminal.detail !== undefined ? providerRejectionResend(historyTerminal.detail, historyTerminal.carrier) : undefined;
679
+ const resendLine = resend !== undefined ? `\n${resend.text}` : '';
680
+ yield syntheticTerminalRow(ctx, `${sessionUnreadable ? `${rowText}\n${corruptSessionContent()}` : rowText}${rewindLine}${resendLine}`, historyMismatch === null ? undefined : toolHistoryMismatchRowKey(historyMismatch, historyRepeat, rewindOffered), resend);
664
681
  }
665
682
  else {
666
683
  const echoedSid = ev.type === 'done' ? engineSessionIdOf(ev.result) : undefined;
@@ -0,0 +1,10 @@
1
+ export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "content_question" | "ancestor" | "unverifiable_delete" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "operator_forced" | "shell_floor" | "write_protection_unresolved" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", import("./generated/engineFactTables.js").CoreModeArbitrationRow>>;
2
+ export type StandingAnswerVerdict = {
3
+ readonly kind: 'must_ask';
4
+ readonly by: string;
5
+ } | {
6
+ readonly kind: 'may_answer';
7
+ } | {
8
+ readonly kind: 'unknown';
9
+ };
10
+ export declare function askSurvivesStandingAnswer(card: object | null | undefined): StandingAnswerVerdict;
@@ -0,0 +1,40 @@
1
+ import { CORE_MODE_ARBITRATION } from './generated/engineFactTables.js';
2
+ export const MODE_ARBITRATION = CORE_MODE_ARBITRATION;
3
+ const MUST_ASK_MANDATED = Object.freeze({ kind: 'must_ask', by: 'mandated' });
4
+ const MAY_ANSWER = Object.freeze({ kind: 'may_answer' });
5
+ const UNKNOWN = Object.freeze({ kind: 'unknown' });
6
+ export function askSurvivesStandingAnswer(card) {
7
+ try {
8
+ if (card === null || card === undefined || typeof card !== 'object')
9
+ return UNKNOWN;
10
+ if (Object.hasOwn(card, 'mandated') && card.mandated === true)
11
+ return MUST_ASK_MANDATED;
12
+ const classes = Object.hasOwn(card, 'classes') ? card.classes : undefined;
13
+ if (!Array.isArray(classes))
14
+ return UNKNOWN;
15
+ const length = classes.length;
16
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0)
17
+ return UNKNOWN;
18
+ const names = [];
19
+ for (let i = 0; i < length; i += 1) {
20
+ if (!Object.hasOwn(classes, i))
21
+ return UNKNOWN;
22
+ const c = classes[i];
23
+ if (typeof c !== 'string')
24
+ return UNKNOWN;
25
+ names.push(c);
26
+ }
27
+ if (classes.length !== length)
28
+ return UNKNOWN;
29
+ for (const name of names) {
30
+ if (!Object.hasOwn(MODE_ARBITRATION, name))
31
+ return UNKNOWN;
32
+ if (MODE_ARBITRATION[name]?.ruleMayClear !== true)
33
+ return Object.freeze({ kind: 'must_ask', by: name });
34
+ }
35
+ return MAY_ANSWER;
36
+ }
37
+ catch {
38
+ return UNKNOWN;
39
+ }
40
+ }
@@ -0,0 +1,11 @@
1
+ export type AttachmentSeatReading = {
2
+ readonly kind: 'unobserved';
3
+ } | {
4
+ readonly kind: 'not_reported';
5
+ };
6
+ export declare function noteEngineCapsForAttachmentSeat(baseUrl: string, caps: unknown, opts?: {
7
+ generation?: number;
8
+ }): void;
9
+ export declare function observedAttachmentSeat(baseUrl?: string | undefined): AttachmentSeatReading;
10
+ export declare function forgetAttachmentSeatReading(baseUrl: string | undefined): void;
11
+ export declare function __resetAttachmentSeatReadingsForTests(): void;
@@ -0,0 +1,27 @@
1
+ import { engineWireTarget } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ function projectAttachmentSeat(caps) {
4
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
5
+ return undefined;
6
+ const p = Object.getPrototypeOf(caps);
7
+ if (p !== Object.prototype && p !== null)
8
+ return undefined;
9
+ return { kind: 'not_reported' };
10
+ }
11
+ const reader = createEngineCapReader({
12
+ name: 'attachmentSeat',
13
+ project: projectAttachmentSeat,
14
+ makeUnobserved: () => ({ kind: 'unobserved' }),
15
+ });
16
+ export function noteEngineCapsForAttachmentSeat(baseUrl, caps, opts) {
17
+ reader.note(baseUrl, caps, opts);
18
+ }
19
+ export function observedAttachmentSeat(baseUrl = engineWireTarget()?.baseUrl) {
20
+ return reader.observed(baseUrl);
21
+ }
22
+ export function forgetAttachmentSeatReading(baseUrl) {
23
+ reader.forget(baseUrl);
24
+ }
25
+ export function __resetAttachmentSeatReadingsForTests() {
26
+ reader.__resetForTests();
27
+ }
@@ -28,3 +28,12 @@ export declare class AttachmentsSpecRefused extends TypeError {
28
28
  });
29
29
  }
30
30
  export declare function attachmentsForRequest(env?: EnvLike): AttachmentsSpec | undefined;
31
+ export type AttachmentsSubmitVerdict = {
32
+ readonly ok: true;
33
+ readonly attachments: AttachmentsSpec;
34
+ } | {
35
+ readonly ok: false;
36
+ readonly sentence: string;
37
+ readonly refusedKey: string | undefined;
38
+ };
39
+ export declare function attachmentsSubmitVerdict(env?: EnvLike): AttachmentsSubmitVerdict;
@@ -1,6 +1,6 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
2
  import { envFlagOff } from './envFlag.js';
3
- import { retiredAttachmentsKeyWhy } from './taskRequestWords.js';
3
+ import { retiredAttachmentsKeyOf } from './taskRequestWords.js';
4
4
  export const ATTACHMENTS_ENV = 'SEMA_ATTACHMENTS';
5
5
  export const ATTACHMENTS_SPEC_KEYS = Object.freeze([
6
6
  'todoReminder',
@@ -48,25 +48,27 @@ function refuse(token, why) {
48
48
  const allowed = legalAttachmentsTokens();
49
49
  throw new AttachmentsSpecRefused(`${ATTACHMENTS_ENV}: refusing token "${token}" — ${why}. Legal tokens: ${allowed.join(' | ')}`, { token, why, allowed });
50
50
  }
51
- export function attachmentsForRequest(env = hostEnv()) {
52
- const raw = (env[ATTACHMENTS_ENV] ?? '').trim().toLowerCase();
51
+ function deny(kind, token, why, index, key) {
52
+ return { ok: false, refusal: { kind, token, why, entry: index + 1, key } };
53
+ }
54
+ function judgeAttachmentsEnv(raw) {
53
55
  if (envFlagOff(raw))
54
- return allDefaultOnOff();
56
+ return { ok: true, spec: allDefaultOnOff() };
55
57
  const tokens = raw.split(',').map(t => t.trim()).filter(t => t.length > 0);
56
- for (const token of tokens) {
57
- const named = retiredAttachmentsKeyWhy(token);
58
+ for (const [i, token] of tokens.entries()) {
59
+ const named = retiredAttachmentsKeyOf(token);
58
60
  if (named !== undefined)
59
- refuse(token, named);
61
+ return { ok: false, refusal: { kind: 'retired_key', token, why: named.why, entry: i + 1, key: named.key, retired: named } };
60
62
  }
61
63
  if (tokens.length === 0)
62
- return { backgroundTasks: true, toolsDelta: true };
64
+ return { ok: true, spec: { backgroundTasks: true, toolsDelta: true } };
63
65
  if (tokens.length === 1 && (tokens[0] === 'full' || tokens[0] === 'all'))
64
- return allLiteralTrue();
66
+ return { ok: true, spec: allLiteralTrue() };
65
67
  if (tokens.length === 1 && !isKnownToken(tokens[0])) {
66
- return { backgroundTasks: true, toolsDelta: true };
68
+ return { ok: true, spec: { backgroundTasks: true, toolsDelta: true } };
67
69
  }
68
70
  const spec = {};
69
- for (const token of tokens) {
71
+ for (const [i, token] of tokens.entries()) {
70
72
  if (token === 'full' || token === 'all') {
71
73
  Object.assign(spec, allLiteralTrue());
72
74
  continue;
@@ -79,11 +81,11 @@ export function attachmentsForRequest(env = hostEnv()) {
79
81
  if (token.startsWith('-')) {
80
82
  const key = canonicalKey(token.slice(1));
81
83
  if (key === undefined)
82
- refuse(token, 'unknown attachments key');
84
+ return deny('unknown_key', token, 'unknown attachments key', i, undefined);
83
85
  if (!isDefaultOnKey(key)) {
84
- refuse(token, `"${key}" belongs to the literal-true family whose OFF is "delete the key", not "send false" ` +
86
+ return deny('minus_on_literal_true_key', token, `"${key}" belongs to the literal-true family whose OFF is "delete the key", not "send false" ` +
85
87
  '(core wire contract, board [479] ask-2) — simply do not turn it on. ' +
86
- `Only the core DEFAULT-ON keys (${ATTACHMENTS_DEFAULT_ON_KEYS.join(' / ')}) accept an explicit false`);
88
+ `Only the core DEFAULT-ON keys (${ATTACHMENTS_DEFAULT_ON_KEYS.join(' / ')}) accept an explicit false`, i, key);
87
89
  }
88
90
  spec[key] = false;
89
91
  continue;
@@ -91,25 +93,76 @@ export function attachmentsForRequest(env = hostEnv()) {
91
93
  const [lhs, rhs] = splitAssign(token);
92
94
  if (rhs !== undefined) {
93
95
  if (canonicalKey(lhs) !== 'todoReminderMode')
94
- refuse(token, 'only `todoReminderMode` takes a `key=value` form');
96
+ return deny('value_form_on_other_key', token, 'only `todoReminderMode` takes a `key=value` form', i, canonicalKey(lhs));
95
97
  const mode = TODO_REMINDER_MODES.find(v => v === rhs);
96
98
  if (mode === undefined)
97
- refuse(token, `todoReminderMode must be one of ${TODO_REMINDER_MODES.join('|')}`);
99
+ return deny('todo_mode_bad_value', token, `todoReminderMode must be one of ${TODO_REMINDER_MODES.join('|')}`, i, 'todoReminderMode');
98
100
  spec.todoReminderMode = mode;
99
101
  continue;
100
102
  }
101
103
  const key = canonicalKey(lhs.startsWith('+') ? lhs.slice(1) : lhs);
102
104
  if (key === undefined)
103
- refuse(token, 'unknown attachments key');
105
+ return deny('unknown_key', token, 'unknown attachments key', i, undefined);
104
106
  if (isDefaultOnOnlyKey(key)) {
105
- refuse(token, `"${key}" is core DEFAULT-ON — turning it "on" is a no-op that would send a redundant true; ` +
106
- `use "-${key}" to turn it OFF`);
107
+ return deny('plus_on_default_on_key', token, `"${key}" is core DEFAULT-ON — turning it "on" is a no-op that would send a redundant true; ` + `use "-${key}" to turn it OFF`, i, key);
107
108
  }
108
109
  if (key === 'todoReminderMode')
109
- refuse(token, 'todoReminderMode needs a value: `todoReminderMode=baseline|off`');
110
+ return deny('todo_mode_needs_value', token, 'todoReminderMode needs a value: `todoReminderMode=baseline|off`', i, key);
110
111
  spec[key] = true;
111
112
  }
112
- return spec;
113
+ return { ok: true, spec };
114
+ }
115
+ function envRawOf(env) {
116
+ return (env[ATTACHMENTS_ENV] ?? '').trim().toLowerCase();
117
+ }
118
+ export function attachmentsForRequest(env = hostEnv()) {
119
+ const judged = judgeAttachmentsEnv(envRawOf(env));
120
+ if (!judged.ok)
121
+ return refuse(judged.refusal.token, judged.refusal.why);
122
+ return judged.spec;
123
+ }
124
+ function attachmentsWayOut() {
125
+ const on = OPT_IN_KEYS.join(', ');
126
+ const off = ATTACHMENTS_DEFAULT_ON_KEYS.join(', ');
127
+ return (`Accepted writing is either the single word off on its own (every attachment off; it cannot be combined with other entries), or a comma-separated list of: ` +
128
+ `full or all (every opt-in attachment on), default, the name of an attachment to turn on (${on}), a leading minus to turn off one of ${off}, and todoReminderMode=${TODO_REMINDER_MODES.join(' or todoReminderMode=')}. ` +
129
+ `To drop the setting altogether, unset ${ATTACHMENTS_ENV}; the defaults then apply.`);
130
+ }
131
+ function attachmentsRefusalSentence(r) {
132
+ const head = `The attachments setting (${ATTACHMENTS_ENV}) cannot be used as it is set: entry ${r.entry} of the list`;
133
+ const key = r.key ?? '';
134
+ switch (r.kind) {
135
+ case 'retired_key':
136
+ return `${head} names "${key}", a key that no longer exists — ${r.retired?.because ?? 'it was retired'}, and a request carrying that key is refused by the server. Remove that entry${r.retired === undefined ? '' : `; ${r.retired.remedy}`}. ${attachmentsWayOut()}`;
137
+ case 'unknown_key':
138
+ return `${head} is not an attachment this version knows. ${attachmentsWayOut()}`;
139
+ case 'minus_on_literal_true_key':
140
+ return key === 'todoReminderMode'
141
+ ? `${head} puts a minus on todoReminderMode, which is a setting with a value rather than something to turn off — write todoReminderMode=${TODO_REMINDER_MODES.join(' or todoReminderMode=')}, or leave it out of the list. A minus only works on ${ATTACHMENTS_DEFAULT_ON_KEYS.join(', ')}, which are on by default. ${attachmentsWayOut()}`
142
+ : `${head} puts a minus on ${key}, and a minus cannot switch that attachment off — it is on only when it is named, or when full, all or default switches it on. A minus only works on ${ATTACHMENTS_DEFAULT_ON_KEYS.join(', ')}, which are on by default. To leave ${key} out, do not name it, and if full, all or default is what switches it on, list the attachments you want one by one instead. ${attachmentsWayOut()}`;
143
+ case 'plus_on_default_on_key':
144
+ return `${head} turns on ${key}, which is already on by default. To turn it off write -${key}; otherwise leave it out of the list. ${attachmentsWayOut()}`;
145
+ case 'value_form_on_other_key':
146
+ return `${head} uses a name=value form, and only todoReminderMode takes one. ${attachmentsWayOut()}`;
147
+ case 'todo_mode_bad_value':
148
+ return `${head} gives todoReminderMode a value it does not accept; the values are ${TODO_REMINDER_MODES.join(' and ')}. ${attachmentsWayOut()}`;
149
+ case 'todo_mode_needs_value':
150
+ return `${head} names todoReminderMode without a value; write todoReminderMode=${TODO_REMINDER_MODES.join(' or todoReminderMode=')}. ${attachmentsWayOut()}`;
151
+ }
152
+ }
153
+ function attachmentsUnreadableSentence() {
154
+ return `The attachments setting (${ATTACHMENTS_ENV}) could not be read as text, so it cannot be used as it is set. Set it to a plain comma-separated list, or unset it. ${attachmentsWayOut()}`;
155
+ }
156
+ export function attachmentsSubmitVerdict(env = hostEnv()) {
157
+ try {
158
+ const judged = judgeAttachmentsEnv(envRawOf(env));
159
+ if (judged.ok)
160
+ return { ok: true, attachments: judged.spec };
161
+ return { ok: false, sentence: attachmentsRefusalSentence(judged.refusal), refusedKey: judged.refusal.key };
162
+ }
163
+ catch {
164
+ return { ok: false, sentence: attachmentsUnreadableSentence(), refusedKey: undefined };
165
+ }
113
166
  }
114
167
  function allDefaultOnOff() {
115
168
  const out = {};
@@ -7,8 +7,7 @@ export const MIGRATED_COMPENSATIONS = [
7
7
  libraryHalf: 'adapt.ts bindTaskCard()',
8
8
  chromeArm: null,
9
9
  hostHalf: null,
10
- retireOn: 'P1-2 到货且达标线 ≥ server 1.290(typed parentToolCallId 在每条 tick 上恒在)后,' +
11
- 'FIFO 回落臂可删 —— 但只在**确认不再支持老引擎**时删,见 bindTaskCard 的 old-engine fallback 注释',
10
+ retireOn: 'RL-31',
12
11
  },
13
12
  {
14
13
  id: 'T34',
@@ -26,7 +25,7 @@ export const MIGRATED_COMPENSATIONS = [
26
25
  libraryHalf: 'adapt.ts fireSubagentStart()(类型恒记 + 一生一次去重 + if-started 门)',
27
26
  chromeArm: 'subagent_lifecycle',
28
27
  hostHalf: '真执行用户 hook 进程(浏览器宿主根本不能执行 ⇒ 这一半必须留端)',
29
- retireOn: 'G21 / P3a(引擎侧发正式的 subagent 生命周期事件)后,壳替 fire 整条退役',
28
+ retireOn: 'RL-32',
30
29
  },
31
30
  {
32
31
  id: 'T36',
@@ -35,7 +34,7 @@ export const MIGRATED_COMPENSATIONS = [
35
34
  libraryHalf: 'adapt.ts settlePanelTasks 的四个调用点(tool_end 关卡 / result / turn 末 / abort)',
36
35
  chromeArm: 'inline_task_stats',
37
36
  hostHalf: null,
38
- retireOn: 'W6(引擎为每条子代发终态 tick)后,防御 sweep 降为纯兜底',
37
+ retireOn: 'RL-33',
39
38
  },
40
39
  {
41
40
  id: 'T44',
@@ -53,7 +52,7 @@ export const MIGRATED_COMPENSATIONS = [
53
52
  libraryHalf: 'adapt.ts task_progress 臂的 workflowRunId / wa-id / workflow-card-parent 三级门',
54
53
  chromeArm: null,
55
54
  hostHalf: null,
56
- retireOn: 'P1-2 到货后判据②③(形状嗅探 + 实例台账)可删,只留判据①(帧带 workflowRunId)',
55
+ retireOn: 'RL-34',
57
56
  },
58
57
  {
59
58
  id: 'T48',
@@ -63,7 +62,7 @@ export const MIGRATED_COMPENSATIONS = [
63
62
  chromeArm: null,
64
63
  hostHalf: 'footer 行过滤 isFooterTaskRow/filterFooterTaskRows —— 含「丢弃所有无 parentId 顶层行」' +
65
64
  '这条**端专属决策**(主 run 头部行由端自己合成渲染),留 TUI',
66
- retireOn: 'P1-2 到货后 fleet 行自带 parentToolCallId ⇒ 双生行不需要按尾段查',
65
+ retireOn: 'RL-35',
67
66
  },
68
67
  {
69
68
  id: 'T1',
@@ -83,8 +82,7 @@ export const MIGRATED_COMPENSATIONS = [
83
82
  libraryHalf: 'notifications.ts taskNotificationDedupKey + taskNotificationDedupKeyFromWire(裸 wire 入口)',
84
83
  chromeArm: null,
85
84
  hostHalf: null,
86
- retireOn: 'P1-3 到货(帧带 completionId)⇒ 键改成**读** completionId,重算算法整条删;' +
87
- '删之前要先有一批「同一完成在两条通道上 completionId 相同」的实测(否则换成了一把更差的键)',
85
+ retireOn: 'RL-36',
88
86
  },
89
87
  {
90
88
  id: 'T3',
@@ -95,8 +93,7 @@ export const MIGRATED_COMPENSATIONS = [
95
93
  'adapt.ts importLedgerFromTranscript(消费口)',
96
94
  chromeArm: null,
97
95
  hostHalf: null,
98
- retireOn: 'P1-3 到货 ⇒ **回植仍要做**(crash 窗口客观存在),但不必再解 XML —— 转录里直接带 ' +
99
- 'completionId。所以退的是「XML 反解」这一段,不是整条回植',
96
+ retireOn: 'RL-37',
100
97
  },
101
98
  {
102
99
  id: 'T25',
@@ -106,7 +103,7 @@ export const MIGRATED_COMPENSATIONS = [
106
103
  libraryHalf: 'toolResult.ts 终态轮询卡臂的预 seed + notifications.markEngineWorkflowNotified',
107
104
  chromeArm: null,
108
105
  hostHalf: null,
109
- retireOn: 'P1-3 到货 ⇒ 两个方向带同一个 completionId,预 seed 这一手(按 runId 抢跑)可删',
106
+ retireOn: 'RL-38',
110
107
  },
111
108
  {
112
109
  id: 'T53',
@@ -115,8 +112,7 @@ export const MIGRATED_COMPENSATIONS = [
115
112
  libraryHalf: 'adapt.ts task_notification / workflow_complete 两臂的四道门(逐道说明见本行上方注释)',
116
113
  chromeArm: null,
117
114
  hostHalf: null,
118
- retireOn: 'P1-3 到货 ⇒ 降到「①渲染去重(键换 completionId)+ ④队列撤单」两道;②③ 删。' +
119
- '🔴 判据不是「四变一」而是逐道过一遍上面那四行 —— 把 ④ 当成去重删掉会让队列里的旧条目重新上屏',
115
+ retireOn: 'RL-39',
120
116
  },
121
117
  {
122
118
  id: 'T49',
@@ -125,7 +121,7 @@ export const MIGRATED_COMPENSATIONS = [
125
121
  libraryHalf: 'adapt.ts task_progress 臂 registerSubagentAlias(explicitParent, taskId)',
126
122
  chromeArm: null,
127
123
  hostHalf: null,
128
- retireOn: 'P1-2 到货后 content 帧自带 taskId ⇒ alias 学习整条可删',
124
+ retireOn: 'RL-40',
129
125
  },
130
126
  ];
131
127
  export function compensationSplitViolations() {
@@ -21,7 +21,12 @@ export type ControlSafetyCode = 'not_running' | 'invalid_content' | 'queue_full'
21
21
  export declare class ControlSafetyError extends Error {
22
22
  readonly code: ControlSafetyCode;
23
23
  readonly cause?: unknown | undefined;
24
- constructor(message: string, code: ControlSafetyCode, cause?: unknown | undefined);
24
+ readonly advice?: string;
25
+ readonly status?: number;
26
+ constructor(message: string, code: ControlSafetyCode, cause?: unknown | undefined, detail?: {
27
+ readonly advice?: string;
28
+ readonly status?: number;
29
+ });
25
30
  }
26
31
  export type QueuePriority = 'now' | 'next' | 'later';
27
32
  export interface QueuedCommand {
@@ -72,8 +77,10 @@ export interface ErrCodes {
72
77
  }
73
78
  export declare function errCodes(e: unknown): ErrCodes;
74
79
  export declare function steeringStatusIs4xx(e: object): boolean;
75
- export interface SteeringRefusedBeforeEngine {
80
+ export interface SteerRefusalAdvice {
76
81
  safety: ControlSafetyCode;
77
82
  advice: string;
78
83
  }
84
+ export type SteeringRefusedBeforeEngine = SteerRefusalAdvice;
79
85
  export declare function steeringRefusedBeforeEngine(e: object): SteeringRefusedBeforeEngine | undefined;
86
+ export declare function steerRefusalAdviceOf(e: object): SteerRefusalAdvice | undefined;