@sema-agent/client-core 0.88.0 → 0.90.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 (61) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +27 -21
  3. package/dist/adapter/activeRunSelfHeal.js +4 -2
  4. package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
  5. package/dist/adapter/downstream/wiringManifestView.d.ts +9 -0
  6. package/dist/adapter/downstream/wiringManifestView.js +27 -0
  7. package/dist/adapter/runStream.d.ts +2 -0
  8. package/dist/adapter/runStream.js +157 -32
  9. package/dist/attachmentsWireCaps.d.ts +13 -2
  10. package/dist/attachmentsWireCaps.js +25 -5
  11. package/dist/controlRouter.d.ts +7 -1
  12. package/dist/controlRouter.js +43 -1
  13. package/dist/coreValuePorts.d.ts +1 -0
  14. package/dist/disableSkillsWire.d.ts +17 -0
  15. package/dist/disableSkillsWire.js +52 -0
  16. package/dist/displayUntrusted.js +122 -55
  17. package/dist/engineCapReader.js +12 -1
  18. package/dist/engineNoticeCodes.d.ts +25 -9
  19. package/dist/engineNoticeCodes.js +161 -68
  20. package/dist/fileHistoryLegNotice.d.ts +20 -0
  21. package/dist/fileHistoryLegNotice.js +122 -0
  22. package/dist/gateOutcome.d.ts +1 -1
  23. package/dist/gateOutcome.js +1 -0
  24. package/dist/generated/engineFactTables.d.ts +1 -0
  25. package/dist/generated/engineFactTables.js +3 -0
  26. package/dist/generated/engineNoticeTables.d.ts +3 -0
  27. package/dist/generated/engineNoticeTables.js +107 -2
  28. package/dist/hitl/toolApprovalWire.d.ts +6 -2
  29. package/dist/hitl/toolApprovalWire.js +30 -0
  30. package/dist/hookToolAliases.d.ts +3 -0
  31. package/dist/hookToolAliases.js +10 -0
  32. package/dist/hooksWireCaps.js +80 -12
  33. package/dist/index.d.ts +14 -3
  34. package/dist/index.js +10 -3
  35. package/dist/permissionsAllowGrantCapability.d.ts +17 -0
  36. package/dist/permissionsAllowGrantCapability.js +75 -0
  37. package/dist/permissionsDeclaration.d.ts +25 -0
  38. package/dist/permissionsDeclaration.js +227 -0
  39. package/dist/pluginHooksWire.d.ts +46 -4
  40. package/dist/pluginHooksWire.js +277 -24
  41. package/dist/promptDelivery.d.ts +5 -2
  42. package/dist/promptDelivery.js +8 -3
  43. package/dist/providerRejection.d.ts +7 -0
  44. package/dist/providerRejection.js +58 -0
  45. package/dist/request/taskRequest.d.ts +2 -0
  46. package/dist/request/taskRequest.js +67 -9
  47. package/dist/runTerminal.d.ts +6 -0
  48. package/dist/runTerminal.js +23 -0
  49. package/dist/sdkRegistryTransit.d.ts +1 -0
  50. package/dist/sdkRegistryTransit.js +1 -0
  51. package/dist/seam.d.ts +3 -0
  52. package/dist/seatContract.d.ts +9 -1
  53. package/dist/seatContract.js +10 -0
  54. package/dist/subagent/engineSubagentSteer.d.ts +8 -1
  55. package/dist/subagent/engineSubagentSteer.js +26 -3
  56. package/dist/supportFloor.d.ts +10 -0
  57. package/dist/supportFloor.js +42 -0
  58. package/dist/taskRequestWords.d.ts +1 -0
  59. package/dist/taskRequestWords.js +14 -0
  60. package/docs/INTEGRATION-CLIENTS.md +896 -14
  61. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import { abortableSleep } from '../abortableSleep.js';
2
2
  import { corruptStoredRowContent, corruptStoredRowWhere, effectiveWireErrorCode } from '../wireErrorTriage.js';
3
+ import { steeringRefusedBeforeEngine, steeringStatusIs4xx } from '../controlRouter.js';
3
4
  export const PLAN_REVIEW_GATE_KIND = 'plan_review';
4
5
  export const PLAN_REVIEW_GATE_KINDS = Object.freeze([PLAN_REVIEW_GATE_KIND, 'dry_run_review']);
5
6
  export const ASK_PARK_GATE_KINDS = Object.freeze(['human', 'irreversible_ask', 'policy_ask', 'tool_approval']);
@@ -260,8 +261,9 @@ function reopenedOfOutcome(outcome) {
260
261
  }
261
262
  }
262
263
  export function atMostOnceFailureClass(e) {
263
- const status = errorSlot(e, 'status');
264
- return typeof status === 'number' && status >= 400 && status < 500 ? 'rejected' : 'unknown';
264
+ if (typeof e === 'object' && e !== null && steeringStatusIs4xx(e))
265
+ return 'rejected';
266
+ return typeof e === 'object' && e !== null && steeringRefusedBeforeEngine(e) !== undefined ? 'rejected' : 'unknown';
265
267
  }
266
268
  export function readSteerDelivery(receipt) {
267
269
  try {
@@ -1,4 +1,4 @@
1
- import { wiringManifestViewOf } from './wiringManifestView.js';
1
+ import { 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';
@@ -550,7 +550,8 @@ function wiringManifestSupersetBody(ev) {
550
550
  const readDeny = projectReadDenySection(ev.readDeny);
551
551
  const hands = projectHandsSection(ev);
552
552
  const fileHistory = projectFileHistorySection(ev);
553
- if (modelGate === undefined && autoMode === undefined && mcp === undefined && tools === undefined && hooks === undefined && lsp === undefined && writeProtection === undefined && autoConsolidation === undefined && readDeny === undefined && hands === undefined && fileHistory === undefined)
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
555
  return undefined;
555
556
  return {
556
557
  ...(modelGate !== undefined ? { _sema_modelGate: modelGate } : {}),
@@ -564,6 +565,7 @@ function wiringManifestSupersetBody(ev) {
564
565
  ...(readDeny !== undefined ? { _sema_readDeny: readDeny } : {}),
565
566
  ...(hands !== undefined ? { _sema_hands: hands } : {}),
566
567
  ...(fileHistory !== undefined ? { _sema_fileHistory: fileHistory } : {}),
568
+ ...(instructions !== undefined ? { _sema_instructions: instructions } : {}),
567
569
  };
568
570
  }
569
571
  function projectFileHistorySection(holder) {
@@ -12,6 +12,9 @@ export type WiringManifestSections = {
12
12
  _sema_readDeny?: WiringManifestReadDenyView;
13
13
  _sema_hands?: WiringManifestHandsView;
14
14
  _sema_fileHistory?: string;
15
+ _sema_instructions?: {
16
+ readonly selected: readonly string[];
17
+ };
15
18
  eventId?: string;
16
19
  };
17
20
  export interface WiringManifestView {
@@ -26,6 +29,12 @@ export interface WiringManifestView {
26
29
  readDeny?: WiringManifestReadDenyView;
27
30
  hands?: WiringManifestHandsView;
28
31
  fileHistory?: string;
32
+ instructions?: {
33
+ readonly selected: readonly string[];
34
+ };
29
35
  eventId?: string;
30
36
  }
37
+ export declare function readInstructionsSegment(holder: object, key: 'instructions' | '_sema_instructions'): {
38
+ readonly selected: readonly string[];
39
+ } | undefined;
31
40
  export declare function wiringManifestViewOf(s: WiringManifestSections): WiringManifestView | undefined;
@@ -2,8 +2,34 @@ const _viewPin = true;
2
2
  void _viewPin;
3
3
  const _viewKeysPin = true;
4
4
  void _viewKeysPin;
5
+ export function readInstructionsSegment(holder, key) {
6
+ try {
7
+ if (!Object.hasOwn(holder, key))
8
+ return undefined;
9
+ const raw = holder[key];
10
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw) || !Object.hasOwn(raw, 'selected'))
11
+ return undefined;
12
+ const sel = raw.selected;
13
+ if (!Array.isArray(sel))
14
+ return undefined;
15
+ const out = [];
16
+ for (let i = 0; i < sel.length; i++) {
17
+ if (!Object.hasOwn(sel, i))
18
+ return undefined;
19
+ const v = sel[i];
20
+ if (typeof v !== 'string' || v.length === 0)
21
+ return undefined;
22
+ out.push(v);
23
+ }
24
+ return Object.freeze({ selected: Object.freeze(out) });
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
5
30
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
6
31
  export function wiringManifestViewOf(s) {
32
+ const instructions = readInstructionsSegment(s, '_sema_instructions');
7
33
  const view = {
8
34
  ...(isObj(s._sema_modelGate) ? { modelGate: s._sema_modelGate } : {}),
9
35
  ...(isObj(s._sema_autoMode) ? { autoMode: s._sema_autoMode } : {}),
@@ -16,6 +42,7 @@ export function wiringManifestViewOf(s) {
16
42
  ...(isObj(s._sema_readDeny) ? { readDeny: s._sema_readDeny } : {}),
17
43
  ...(isObj(s._sema_hands) && typeof s._sema_hands.mounted === 'boolean' ? { hands: s._sema_hands } : {}),
18
44
  ...(typeof s._sema_fileHistory === 'string' && s._sema_fileHistory.length > 0 ? { fileHistory: s._sema_fileHistory } : {}),
45
+ ...(instructions !== undefined ? { instructions } : {}),
19
46
  };
20
47
  if (Object.keys(view).length === 0)
21
48
  return undefined;
@@ -1,6 +1,7 @@
1
1
  import type { AgentEvent } from '@sema-agent/sdk';
2
2
  import { type SDKMessage, type EmitContext, type ModelUsage } from './types.js';
3
3
  import { type EngineTurnUsage } from './downstream/turnUsageToModelUsage.js';
4
+ import { type ToolHistoryErrorCarrier } from '../toolHistoryMismatch.js';
4
5
  export interface ActiveRunPendingGate {
5
6
  readonly kind: string | null;
6
7
  readonly decidePath: string | null;
@@ -27,6 +28,7 @@ export declare function isGovernanceTerminal(input: {
27
28
  status: string | undefined;
28
29
  errorCode: string | undefined;
29
30
  }): boolean;
31
+ export declare function toolHistoryErrorCarrierOf(terminalOrResult: object | null | undefined): ToolHistoryErrorCarrier;
30
32
  export declare function droppedWhySentence(why: string): string;
31
33
  export declare function _resetDroppedFrameReportForTest(): void;
32
34
  export declare function _droppedFrameMemoSizeForTest(): number;
@@ -140,11 +140,6 @@ function engineSessionIdOf(result) {
140
140
  const v = result?.sessionId;
141
141
  return typeof v === 'string' && v.length > 0 ? v : undefined;
142
142
  }
143
- function providerFailureStatusOf(result) {
144
- const af = result?.apiFailure;
145
- const status = typeof af === 'object' && af !== null ? af.status : undefined;
146
- return typeof status === 'number' && Number.isFinite(status) ? status : undefined;
147
- }
148
143
  export function isOutcomeUnknownRowText(text) {
149
144
  return text.trimStart().startsWith(`${OUTCOME_UNKNOWN_ROW_PREFIX}:`);
150
145
  }
@@ -155,6 +150,157 @@ export function isGovernanceTerminal(input) {
155
150
  return true;
156
151
  return input.errorCode === OUTPUT_INVALID;
157
152
  }
153
+ function terminalRowFacts(ev) {
154
+ const doneTerminal = ev.type === 'done' ? readRunTerminal(ev.result) : null;
155
+ const errTextRaw = ev.type === 'failed'
156
+ ? (ev.errorMessage ?? ev.errorCode ?? 'run failed')
157
+ : doneTerminal?.kind === 'failed'
158
+ ? (doneTerminal.message ?? doneTerminal.code ?? 'run failed')
159
+ : doneTerminal?.kind === 'blocked'
160
+ ? (doneTerminal.reason
161
+ ? `Run blocked: ${doneTerminal.reason}`
162
+ : 'Run blocked (the agent reported it cannot proceed)')
163
+ : null;
164
+ const errText = errTextRaw === '' ? null : errTextRaw;
165
+ if (errText === null)
166
+ return { doneTerminal, errText, activeTaskId: undefined, busy: { busy: false, handle: null }, governance: false };
167
+ const activeTaskId = (ev.type === 'done' ? ev.result?.activeTaskId : undefined) ??
168
+ (ev.type === 'failed' ? ev.activeTaskId : undefined);
169
+ const busy = classifyActiveRunBusy({
170
+ errorCode: ev.type === 'failed' ? ev.errorCode : runTerminalCode(doneTerminal),
171
+ activeTaskId,
172
+ });
173
+ const governance = isGovernanceTerminal({
174
+ status: ev.type === 'failed' ? undefined : doneTerminal?.kind === 'blocked' ? 'blocked' : undefined,
175
+ errorCode: ev.type === 'failed' ? ev.errorCode : runTerminalCode(doneTerminal),
176
+ });
177
+ return { doneTerminal, errText, activeTaskId, busy, governance };
178
+ }
179
+ const PROVIDER_REJECTED_DETAIL = 'provider_rejected';
180
+ function ownDataBit(o, k) {
181
+ try {
182
+ const d = Object.getOwnPropertyDescriptor(o, k);
183
+ return d !== undefined && Object.prototype.hasOwnProperty.call(d, 'value') ? d.value : undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
189
+ function plainObjectOf(v) {
190
+ if (typeof v !== 'object' || v === null)
191
+ return undefined;
192
+ try {
193
+ return Array.isArray(v) ? undefined : v;
194
+ }
195
+ catch {
196
+ return undefined;
197
+ }
198
+ }
199
+ function ownDataCopy(o, keys) {
200
+ const out = Object.create(null);
201
+ for (const k of keys) {
202
+ const v = ownDataBit(o, k);
203
+ if (v !== undefined)
204
+ out[k] = v;
205
+ }
206
+ return out;
207
+ }
208
+ function ownTerminalSnapshot(result) {
209
+ let causePlane;
210
+ try {
211
+ causePlane = 'terminal' in result;
212
+ }
213
+ catch {
214
+ return undefined;
215
+ }
216
+ const snap = ownDataCopy(result, ['status', 'errorCode', 'errorMessage']);
217
+ if (!causePlane)
218
+ return snap;
219
+ let d;
220
+ try {
221
+ d = Object.getOwnPropertyDescriptor(result, 'terminal');
222
+ }
223
+ catch {
224
+ return undefined;
225
+ }
226
+ if (d === undefined || !Object.prototype.hasOwnProperty.call(d, 'value'))
227
+ return undefined;
228
+ const t = plainObjectOf(d.value);
229
+ if (t === undefined) {
230
+ snap.terminal = null;
231
+ return snap;
232
+ }
233
+ const ts = ownDataCopy(t, ['kind', 'code', 'message']);
234
+ const detail = plainObjectOf(ownDataBit(t, 'detail'));
235
+ if (detail !== undefined)
236
+ ts.detail = ownDataCopy(detail, ['kind', 'status', 'code']);
237
+ snap.terminal = ts;
238
+ return snap;
239
+ }
240
+ function nonEmptyStr(v) {
241
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
242
+ }
243
+ function mintToolHistoryCarrier(code, status, message) {
244
+ const out = Object.create(null);
245
+ if (code !== undefined)
246
+ out.code = code;
247
+ if (status !== undefined)
248
+ out.status = status;
249
+ if (message !== undefined)
250
+ out.message = message;
251
+ return Object.freeze(out);
252
+ }
253
+ export function toolHistoryErrorCarrierOf(terminalOrResult) {
254
+ const empty = () => mintToolHistoryCarrier(undefined, undefined, undefined);
255
+ try {
256
+ const x = plainObjectOf(terminalOrResult);
257
+ if (x === undefined)
258
+ return empty();
259
+ const type = ownDataBit(x, 'type');
260
+ const frame = (type === 'done' || type === 'failed' ? x : { type: 'done', result: x });
261
+ return toolHistoryCarrierFromFacts(x, terminalRowFacts(frame));
262
+ }
263
+ catch {
264
+ return empty();
265
+ }
266
+ }
267
+ function toolHistoryCarrierFromFacts(x, facts) {
268
+ const empty = () => mintToolHistoryCarrier(undefined, undefined, undefined);
269
+ try {
270
+ const type = ownDataBit(x, 'type');
271
+ if (facts.errText === null || facts.busy.busy || facts.governance || isModelOutputErrorText(facts.errText))
272
+ return empty();
273
+ if (type === 'failed')
274
+ return mintToolHistoryCarrier(nonEmptyStr(ownDataBit(x, 'errorCode')), undefined, nonEmptyStr(ownDataBit(x, 'errorMessage')));
275
+ const result = type === 'done' ? plainObjectOf(ownDataBit(x, 'result')) : x;
276
+ if (result === undefined)
277
+ return empty();
278
+ const snap = ownTerminalSnapshot(result);
279
+ if (snap === undefined)
280
+ return empty();
281
+ const read = readRunTerminal(snap);
282
+ if (read === null || read.kind !== 'failed')
283
+ return empty();
284
+ const detail = plainObjectOf(ownDataBit(read, 'detail'));
285
+ const detailStatus = detail !== undefined && ownDataBit(detail, 'kind') === PROVIDER_REJECTED_DETAIL ? ownDataBit(detail, 'status') : undefined;
286
+ const apiFailure = plainObjectOf(ownDataBit(result, 'apiFailure'));
287
+ const apiStatus = apiFailure === undefined ? undefined : ownDataBit(apiFailure, 'status');
288
+ 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')));
290
+ }
291
+ catch {
292
+ return empty();
293
+ }
294
+ }
295
+ function toolHistoryCarrierOfRowFrame(ev, facts) {
296
+ try {
297
+ const x = plainObjectOf(ev);
298
+ return x === undefined ? mintToolHistoryCarrier(undefined, undefined, undefined) : toolHistoryCarrierFromFacts(x, facts);
299
+ }
300
+ catch {
301
+ return mintToolHistoryCarrier(undefined, undefined, undefined);
302
+ }
303
+ }
158
304
  const reportedDroppedTypes = new Set();
159
305
  const DROPPED_TYPE_MEMO_CAP = 64;
160
306
  const DROPPED_TYPE_DISPLAY_CAP = 60;
@@ -477,30 +623,15 @@ async function* runStreamInner(events, ctx, handle = {}) {
477
623
  continue;
478
624
  }
479
625
  if (ev.type === 'done' || ev.type === 'failed') {
480
- const doneTerminal = ev.type === 'done' ? readRunTerminal(ev.result) : null;
481
- const errText = ev.type === 'failed'
482
- ? (ev.errorMessage ?? ev.errorCode ?? 'run failed')
483
- : doneTerminal?.kind === 'failed'
484
- ? (doneTerminal.message ?? doneTerminal.code ?? 'run failed')
485
- : doneTerminal?.kind === 'blocked'
486
- ? (doneTerminal.reason
487
- ? `Run blocked: ${doneTerminal.reason}`
488
- : 'Run blocked (the agent reported it cannot proceed)')
489
- : null;
626
+ const rowFacts = terminalRowFacts(ev);
627
+ const doneTerminal = rowFacts.doneTerminal;
628
+ const errText = rowFacts.errText;
490
629
  if (errText) {
491
- const activeTaskId = (ev.type === 'done' ? ev.result?.activeTaskId : undefined) ??
492
- (ev.type === 'failed' ? ev.activeTaskId : undefined);
493
- const busy = classifyActiveRunBusy({
494
- errorCode: ev.type === 'failed' ? ev.errorCode : runTerminalCode(doneTerminal),
495
- activeTaskId,
496
- });
630
+ const busy = rowFacts.busy;
497
631
  const maxTokHint = /max_tokens/i.test(errText)
498
632
  ? '\nyour provider caps max_tokens lower — lower it in /model (press m, or M to type a value)'
499
633
  : '';
500
- const governance = isGovernanceTerminal({
501
- status: ev.type === 'failed' ? undefined : doneTerminal?.kind === 'blocked' ? 'blocked' : undefined,
502
- errorCode: ev.type === 'failed' ? ev.errorCode : runTerminalCode(doneTerminal),
503
- });
634
+ const governance = rowFacts.governance;
504
635
  const busyHandle = busy.handle;
505
636
  const rowText = busy.busy
506
637
  ? `This session is locked by an earlier run${busyHandle ? ` (run ${busyHandle})` : ''} that ` +
@@ -516,13 +647,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
516
647
  const sessionUnreadable = ev.type === 'failed'
517
648
  ? isCorruptSessionCode(ev.errorCode)
518
649
  : doneTerminal?.kind === 'failed' && isCorruptSessionCode(doneTerminal.code);
519
- const historyMismatch = !busy.busy && !governance && !isModelOutputErrorText(errText)
520
- ? classifyToolHistoryMismatch({
521
- code: ev.type === 'failed' ? ev.errorCode : runTerminalCode(doneTerminal),
522
- status: ev.type === 'done' ? providerFailureStatusOf(ev.result) : undefined,
523
- message: errText,
524
- })
525
- : null;
650
+ const historyMismatch = classifyToolHistoryMismatch(toolHistoryCarrierOfRowFrame(ev, rowFacts));
526
651
  const historyEchoedSid = ev.type === 'done' ? engineSessionIdOf(ev.result) : undefined;
527
652
  const historyRepeat = noteToolHistoryTerminal({
528
653
  sessionId: historyEchoedSid ?? ctx.sessionId,
@@ -6,7 +6,6 @@ export interface AttachmentsSpec {
6
6
  changedFiles?: true | {
7
7
  maxFiles?: number;
8
8
  };
9
- planModeReminder?: true;
10
9
  budgetUsd?: true;
11
10
  backgroundTasks?: boolean;
12
11
  toolsDelta?: true;
@@ -14,6 +13,18 @@ export interface AttachmentsSpec {
14
13
  agentListing?: boolean;
15
14
  skillsListing?: boolean;
16
15
  }
17
- export declare const ATTACHMENTS_SPEC_KEYS: readonly ["todoReminder", "todoReminderMode", "changedFiles", "planModeReminder", "budgetUsd", "backgroundTasks", "toolsDelta", "mcpInstructions", "agentListing", "skillsListing"];
16
+ export declare const ATTACHMENTS_SPEC_KEYS: readonly ["todoReminder", "todoReminderMode", "changedFiles", "budgetUsd", "backgroundTasks", "toolsDelta", "mcpInstructions", "agentListing", "skillsListing"];
18
17
  export declare const ATTACHMENTS_DEFAULT_ON_KEYS: readonly ["agentListing", "skillsListing", "backgroundTasks"];
18
+ export declare class AttachmentsSpecRefused extends TypeError {
19
+ readonly token: string;
20
+ readonly why: string;
21
+ readonly allowed: readonly string[];
22
+ readonly key?: string;
23
+ constructor(message: string, init: {
24
+ readonly token: string;
25
+ readonly why: string;
26
+ readonly allowed: readonly string[];
27
+ readonly key?: string;
28
+ });
29
+ }
19
30
  export declare function attachmentsForRequest(env?: EnvLike): AttachmentsSpec | undefined;
@@ -1,11 +1,11 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
2
  import { envFlagOff } from './envFlag.js';
3
+ import { retiredAttachmentsKeyWhy } from './taskRequestWords.js';
3
4
  export const ATTACHMENTS_ENV = 'SEMA_ATTACHMENTS';
4
5
  export const ATTACHMENTS_SPEC_KEYS = Object.freeze([
5
6
  'todoReminder',
6
7
  'todoReminderMode',
7
8
  'changedFiles',
8
- 'planModeReminder',
9
9
  'budgetUsd',
10
10
  'backgroundTasks',
11
11
  'toolsDelta',
@@ -17,7 +17,6 @@ export const ATTACHMENTS_DEFAULT_ON_KEYS = ['agentListing', 'skillsListing', 'ba
17
17
  const OPT_IN_KEYS = [
18
18
  'todoReminder',
19
19
  'changedFiles',
20
- 'planModeReminder',
21
20
  'budgetUsd',
22
21
  'backgroundTasks',
23
22
  'toolsDelta',
@@ -28,16 +27,37 @@ const DEFAULT_PAIR = ['backgroundTasks', 'toolsDelta'];
28
27
  const _attachmentsKeyPins = [true, true, true, true, true, true];
29
28
  void _attachmentsKeyPins;
30
29
  const TODO_REMINDER_MODES = ['baseline', 'off'];
30
+ export class AttachmentsSpecRefused extends TypeError {
31
+ token;
32
+ why;
33
+ allowed;
34
+ constructor(message, init) {
35
+ super(message);
36
+ this.name = 'AttachmentsSpecRefused';
37
+ this.token = init.token;
38
+ this.why = init.why;
39
+ this.allowed = Object.freeze([...init.allowed]);
40
+ if (init.key !== undefined)
41
+ this.key = init.key;
42
+ }
43
+ }
44
+ function legalAttachmentsTokens() {
45
+ return ['off', 'full', 'all', 'default', '<literalTrueKey>', '+<literalTrueKey>', ...DEFAULT_ON_ONLY_KEYS.map(k => `-${k}`), `todoReminderMode=${TODO_REMINDER_MODES.join('|')}`];
46
+ }
31
47
  function refuse(token, why) {
32
- throw new Error(`${ATTACHMENTS_ENV}: refusing token "${token}" — ${why}. ` +
33
- `Legal tokens: off | full | all | default | <literalTrueKey> | +<literalTrueKey> | ` +
34
- `-agentListing | -skillsListing | todoReminderMode=baseline|off`);
48
+ const allowed = legalAttachmentsTokens();
49
+ throw new AttachmentsSpecRefused(`${ATTACHMENTS_ENV}: refusing token "${token}" — ${why}. Legal tokens: ${allowed.join(' | ')}`, { token, why, allowed });
35
50
  }
36
51
  export function attachmentsForRequest(env = hostEnv()) {
37
52
  const raw = (env[ATTACHMENTS_ENV] ?? '').trim().toLowerCase();
38
53
  if (envFlagOff(raw))
39
54
  return allDefaultOnOff();
40
55
  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
+ if (named !== undefined)
59
+ refuse(token, named);
60
+ }
41
61
  if (tokens.length === 0)
42
62
  return { backgroundTasks: true, toolsDelta: true };
43
63
  if (tokens.length === 1 && (tokens[0] === 'full' || tokens[0] === 'all'))
@@ -17,7 +17,7 @@ export interface RunsResourceLike {
17
17
  export interface ControlClientLike {
18
18
  runs: RunsResourceLike;
19
19
  }
20
- export type ControlSafetyCode = 'not_running' | 'invalid_content' | 'queue_full' | 'duplicate_input_id' | 'ambiguous_target' | 'still_running' | 'blocked_by_hook' | 'steering_other' | 'cancel_lost_race' | 'cancel_run_not_running' | 'not_found';
20
+ export type ControlSafetyCode = 'not_running' | 'invalid_content' | 'queue_full' | 'duplicate_input_id' | 'ambiguous_target' | 'still_running' | 'blocked_by_hook' | 'ledger_unavailable' | 'steering_other' | 'cancel_lost_race' | 'cancel_run_not_running' | 'not_found';
21
21
  export declare class ControlSafetyError extends Error {
22
22
  readonly code: ControlSafetyCode;
23
23
  readonly cause?: unknown | undefined;
@@ -71,3 +71,9 @@ export interface ErrCodes {
71
71
  status?: number | undefined;
72
72
  }
73
73
  export declare function errCodes(e: unknown): ErrCodes;
74
+ export declare function steeringStatusIs4xx(e: object): boolean;
75
+ export interface SteeringRefusedBeforeEngine {
76
+ safety: ControlSafetyCode;
77
+ advice: string;
78
+ }
79
+ export declare function steeringRefusedBeforeEngine(e: object): SteeringRefusedBeforeEngine | undefined;
@@ -64,8 +64,9 @@ export class ControlRouter {
64
64
  catch (e) {
65
65
  const verdict = steeringVerdict(e);
66
66
  if (verdict !== undefined) {
67
+ const safety = steeringSafetyOf(e, verdict);
67
68
  const wireCode = errCodes(e).code ?? errCodes(e).name ?? 'steering.<unknown>';
68
- throw new ControlSafetyError(`${wireCode} — ${STEERING_ADVICE.get(verdict) ?? 'steering refused'}`, verdict, e);
69
+ throw new ControlSafetyError(`${wireCode} — ${steeringAdviceOf(e, safety) ?? 'steering refused'}`, safety, e);
69
70
  }
70
71
  throw e;
71
72
  }
@@ -138,12 +139,14 @@ const STEERING_CODE_TO_SAFETY = new Map([
138
139
  ['steering.ambiguous_label', 'ambiguous_target'],
139
140
  ['steering.still_running', 'still_running'],
140
141
  ['steering.blocked_by_hook', 'blocked_by_hook'],
142
+ ['steering.ledger_unavailable', 'ledger_unavailable'],
141
143
  ]);
142
144
  const STEERING_NAME_TO_SAFETY = new Map([
143
145
  ['SteeringNotRunningError', 'not_running'],
144
146
  ['SteeringInvalidContentError', 'invalid_content'],
145
147
  ['SteeringAmbiguousTargetError', 'ambiguous_target'],
146
148
  ['SteeringStillRunningError', 'still_running'],
149
+ ['SteeringLedgerUnavailableError', 'ledger_unavailable'],
147
150
  ['SteeringError', 'steering_other'],
148
151
  ]);
149
152
  const STEERING_ADVICE = new Map([
@@ -154,8 +157,47 @@ const STEERING_ADVICE = new Map([
154
157
  ['ambiguous_target', 'more than one live target matches — address it uniquely (sub-agent: parentToolCallId; workflow: a more specific label); retrying verbatim yields the same result'],
155
158
  ['still_running', 'the target is STILL RUNNING — steer it instead of resuming, or wait for it to settle (this is the exact opposite of not_running: never collapse the two)'],
156
159
  ['blocked_by_hook', "this deployment's userPromptSubmit gate refused the input (block, timeout and crash all report this one code, fail-closed) — the input was NOT accepted: no human_input frame, the inputId is not on the ledger, so editing the text and sending again is a normal retry, not a duplicate"],
160
+ ['ledger_unavailable', 'the server could not record this steer in its user-input ledger and refused it before anything reached the engine — the input was NOT delivered; resend the same text later (the same Idempotency-Key is fine)'],
157
161
  ['steering_other', 'an unrecognized steering.* refusal (open set) — the steer did NOT land; log the errorCode verbatim, do not guess its meaning'],
158
162
  ]);
163
+ const STEERING_OTHER_UNKNOWN_ADVICE = 'an unrecognized steering.* refusal (open set) — treat delivery as unknown (do not resend as if it had certainly been dropped); log the errorCode verbatim, do not guess its meaning';
164
+ export function steeringStatusIs4xx(e) {
165
+ let status;
166
+ try {
167
+ status = errCodes(e).status;
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ return status !== undefined && status >= 400 && status < 500;
173
+ }
174
+ function steeringAdviceOf(e, safety) {
175
+ if (safety === 'steering_other' && !(typeof e === 'object' && e !== null && steeringStatusIs4xx(e)))
176
+ return STEERING_OTHER_UNKNOWN_ADVICE;
177
+ return STEERING_ADVICE.get(safety);
178
+ }
179
+ const STEERING_REFUSED_BEFORE_ENGINE = new Map([['ledger_unavailable', 503]]);
180
+ export function steeringRefusedBeforeEngine(e) {
181
+ try {
182
+ const { status } = errCodes(e);
183
+ if (status === undefined)
184
+ return undefined;
185
+ const safety = steeringVerdict(e);
186
+ if (safety === undefined || STEERING_REFUSED_BEFORE_ENGINE.get(safety) !== status)
187
+ return undefined;
188
+ const advice = STEERING_ADVICE.get(safety);
189
+ return advice === undefined ? undefined : { safety, advice };
190
+ }
191
+ catch {
192
+ return undefined;
193
+ }
194
+ }
195
+ function steeringSafetyOf(e, verdict) {
196
+ if (!STEERING_REFUSED_BEFORE_ENGINE.has(verdict))
197
+ return verdict;
198
+ const refused = typeof e === 'object' && e !== null ? steeringRefusedBeforeEngine(e) : undefined;
199
+ return refused !== undefined && refused.safety === verdict ? verdict : 'steering_other';
200
+ }
159
201
  function steeringVerdict(e) {
160
202
  const { code, name } = errCodes(e);
161
203
  if (typeof code === 'string' && code.startsWith('steering.')) {
@@ -16,6 +16,7 @@ export interface AllowDenyPolicyOptionsView {
16
16
  deny?: string[];
17
17
  onInvalidName?: 'throw' | 'skip';
18
18
  onInvalidNameIssue?: (issue: AllowDenyInvalidNameIssueView) => void;
19
+ scope?: 'grant' | 'narrowing';
19
20
  }
20
21
  export interface AllowDenyInvalidNameIssueView {
21
22
  entry: string;
@@ -0,0 +1,17 @@
1
+ export declare function refuseBadDisableSkills(v: unknown, who: string): void;
2
+ export declare function refuseDisableSkillsConflict(disableSkills: unknown, skills: unknown, agents: unknown, who: string): void;
3
+ type DisableSkillsWithheldCause = 'not_reported' | 'unsupported' | 'unprobed' | 'unreadable';
4
+ export type DisableSkillsDeclaration = {
5
+ readonly field: {
6
+ readonly disableSkills: true;
7
+ };
8
+ readonly withheld?: undefined;
9
+ } | {
10
+ readonly field: Readonly<Record<string, never>>;
11
+ readonly withheld?: {
12
+ readonly cause: DisableSkillsWithheldCause;
13
+ readonly text: string;
14
+ };
15
+ };
16
+ export declare function disableSkillsDeclaration(want: unknown, baseUrl: string | undefined): DisableSkillsDeclaration;
17
+ export {};
@@ -0,0 +1,52 @@
1
+ import { engineCapState } from './engineCapsCache.js';
2
+ const KEY = 'disableSkills';
3
+ const shapeOf = (v) => {
4
+ try {
5
+ return v === null ? 'null' : Array.isArray(v) ? 'typeof=object / array' : typeof v === 'string' ? `typeof=string(长 ${v.length})` : `typeof=${typeof v}`;
6
+ }
7
+ catch {
8
+ return `typeof=${typeof v} / unreadable`;
9
+ }
10
+ };
11
+ export function refuseBadDisableSkills(v, who) {
12
+ if (v === undefined || v === null || v === true || v === false)
13
+ return;
14
+ throw new TypeError(`${who}: disableSkills 只接受布尔或缺席(undefined/null)—— 收到 ${shapeOf(v)}。` +
15
+ '这一位是「这一发一只技能都不装」的声明:本层若把坏值折成缺席,这一次运行会照常装上技能而声明人不会知道;' +
16
+ '上游对非布尔值同样拒收。错误文本只报形状,不回显值。');
17
+ }
18
+ const nonEmptyArray = (v) => Array.isArray(v) && v.length > 0;
19
+ const anyAgentCarriesSkills = (agents) => Array.isArray(agents) && agents.some((a) => typeof a === 'object' && a !== null && nonEmptyArray(a.skills));
20
+ export function refuseDisableSkillsConflict(disableSkills, skills, agents, who) {
21
+ if (disableSkills !== true)
22
+ return;
23
+ const inSkills = nonEmptyArray(skills);
24
+ const inAgents = anyAgentCarriesSkills(agents);
25
+ if (!inSkills && !inAgents)
26
+ return;
27
+ const which = inSkills && inAgents ? 'skills 与 agents[].skills' : inSkills ? 'skills' : 'agents[].skills';
28
+ throw new TypeError(`${who}: disableSkills: true(这一发一只技能都不装)与非空的 ${which} 同在 —— 二选一:要关技能就别交技能,要交技能就别带 disableSkills。` +
29
+ '上游对这一组合同样拒收(在任何东西跑起来之前);本层在装配前拒,不让它走一次往返。错误文本只报键名,不回显技能名。');
30
+ }
31
+ const WITHHELD_TEXT = Object.freeze({
32
+ not_reported: 'Skills were not turned off for this request: the connected engine does not report that it can turn them off, and an engine that does not know this setting rejects the whole request, so the setting was left out and this run keeps its usual skills.',
33
+ unsupported: 'Skills were not turned off for this request: the connected engine reports that it cannot turn them off, so the setting was left out and this run keeps its usual skills.',
34
+ unprobed: 'Skills were not turned off for this request: 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 and this run keeps its usual skills.',
35
+ unreadable: 'Skills were not turned off for this request: the connected engine reported whether it can turn them off in a form this version cannot read, so the setting was left out and this run keeps its usual skills.',
36
+ });
37
+ const WITHHELD_CAUSE_OF = {
38
+ absent: 'not_reported',
39
+ false: 'unsupported',
40
+ unprobed: 'unprobed',
41
+ non_boolean: 'unreadable',
42
+ };
43
+ export function disableSkillsDeclaration(want, baseUrl) {
44
+ refuseBadDisableSkills(want, 'disableSkillsDeclaration');
45
+ if (want !== true)
46
+ return Object.freeze({ field: Object.freeze({}) });
47
+ const state = engineCapState(baseUrl, KEY);
48
+ if (state === 'true')
49
+ return Object.freeze({ field: Object.freeze({ disableSkills: true }) });
50
+ const cause = WITHHELD_CAUSE_OF[state];
51
+ return Object.freeze({ field: Object.freeze({}), withheld: Object.freeze({ cause, text: WITHHELD_TEXT[cause] }) });
52
+ }