@sema-agent/client-core 0.107.0 → 0.108.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 (98) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +19 -14
  3. package/dist/adapter/activeRunSelfHeal.d.ts +2 -0
  4. package/dist/adapter/activeRunSelfHeal.js +20 -3
  5. package/dist/checkpointRowVerdict.d.ts +5 -0
  6. package/dist/checkpointRowVerdict.js +26 -0
  7. package/dist/cloudConfigWireCaps.d.ts +10 -0
  8. package/dist/cloudConfigWireCaps.js +33 -0
  9. package/dist/coreValuePorts.d.ts +17 -1
  10. package/dist/coreValuePorts.js +5 -0
  11. package/dist/decideReceipt.js +2 -1
  12. package/dist/engineErrorCodes.d.ts +5 -0
  13. package/dist/engineErrorCodes.js +5 -0
  14. package/dist/engineNoticeCodes.d.ts +19 -1
  15. package/dist/engineNoticeCodes.js +65 -3
  16. package/dist/engineTargetSeat.js +1 -0
  17. package/dist/errorSupplementRoles.js +2 -0
  18. package/dist/eventLeg.js +1 -1
  19. package/dist/executionLaneWords.js +2 -1
  20. package/dist/gateOutcome.d.ts +10 -1
  21. package/dist/gateOutcome.js +48 -1
  22. package/dist/generated/engineFactTables.d.ts +1 -0
  23. package/dist/generated/engineFactTables.js +5 -0
  24. package/dist/generated/engineNoticeTables.js +3 -0
  25. package/dist/generated/settingsSchemaFns.d.ts +4 -2
  26. package/dist/generated/settingsSchemaFns.js +9 -3
  27. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  28. package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
  29. package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
  30. package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
  31. package/dist/generated/settingsSchemaTables.d.ts +2 -0
  32. package/dist/generated/settingsSchemaTables.js +10 -0
  33. package/dist/generated/settingsSchemaWireName.d.ts +1 -0
  34. package/dist/generated/settingsSchemaWireName.js +8 -0
  35. package/dist/headlessReconnectWire.js +12 -7
  36. package/dist/hitl/approvalAskers.d.ts +13 -0
  37. package/dist/hitl/approvalAskers.js +79 -0
  38. package/dist/hitl/approvalResolution.d.ts +3 -0
  39. package/dist/hitl/approvalResolution.js +22 -4
  40. package/dist/hitl/approvalsFeed.js +3 -12
  41. package/dist/hitl/askParkRowRouting.js +5 -1
  42. package/dist/hitl/decisionRefusal.d.ts +1 -0
  43. package/dist/hitl/decisionRefusal.js +5 -2
  44. package/dist/hitl/decisionSend.js +2 -6
  45. package/dist/hitl/frameRouter.d.ts +3 -2
  46. package/dist/hitl/frameRouter.js +28 -6
  47. package/dist/hitl/gateLedger.d.ts +2 -0
  48. package/dist/hitl/gateLedger.js +3 -1
  49. package/dist/hitl/hitlBridge.js +2 -3
  50. package/dist/hitl/parkResolver.js +32 -23
  51. package/dist/hitl/parkRowBirthWait.d.ts +2 -0
  52. package/dist/hitl/parkRowBirthWait.js +8 -1
  53. package/dist/hitl/persistedRulesWire.d.ts +5 -1
  54. package/dist/hitl/persistedRulesWire.js +20 -4
  55. package/dist/hitl/planReviewWire.js +12 -16
  56. package/dist/hitl/policyDenyCause.d.ts +3 -0
  57. package/dist/hitl/policyDenyCause.js +16 -0
  58. package/dist/hitl/sessionPolicyDeliverable.js +1 -1
  59. package/dist/hitl/toolApprovalWire.d.ts +8 -2
  60. package/dist/hitl/toolApprovalWire.js +22 -3
  61. package/dist/hooksReaderGenerations.d.ts +1 -0
  62. package/dist/hooksReaderGenerations.js +12 -0
  63. package/dist/hooksWireCaps.js +8 -4
  64. package/dist/i18n/messages.en.d.ts +38 -1
  65. package/dist/i18n/messages.en.js +40 -3
  66. package/dist/i18n/messages.zh.js +45 -3
  67. package/dist/index.d.ts +6 -1
  68. package/dist/index.js +4 -1
  69. package/dist/liveModelCatalog.d.ts +4 -2
  70. package/dist/liveModelCatalog.js +164 -26
  71. package/dist/model/catalogDefault.d.ts +25 -0
  72. package/dist/model/catalogDefault.js +44 -0
  73. package/dist/permissionWireCaps.d.ts +1 -0
  74. package/dist/permissionWireCaps.js +11 -0
  75. package/dist/pluginHooksWire.d.ts +5 -3
  76. package/dist/pluginHooksWire.js +116 -7
  77. package/dist/queuedDelivery.d.ts +1 -0
  78. package/dist/queuedDelivery.js +12 -0
  79. package/dist/resultFrameKeys.js +4 -2
  80. package/dist/resumeRefusalCopy.js +15 -8
  81. package/dist/runPermissionModeSwitch.d.ts +2 -1
  82. package/dist/runPermissionModeSwitch.js +16 -6
  83. package/dist/seatContract.d.ts +63 -10
  84. package/dist/seatContract.js +164 -11
  85. package/dist/sessionEngine.d.ts +2 -1
  86. package/dist/sessionEngine.js +5 -0
  87. package/dist/settingsHooksSalvage.d.ts +5 -0
  88. package/dist/settingsHooksSalvage.js +54 -0
  89. package/dist/subagentContentStore.js +1 -1
  90. package/dist/supportFloor.js +2 -0
  91. package/dist/taskNotificationAdmission.d.ts +19 -0
  92. package/dist/taskNotificationAdmission.js +84 -0
  93. package/dist/toolResult.js +2 -1
  94. package/dist/webSearchWireCaps.js +9 -4
  95. package/dist/wireRefusalCopy.d.ts +2 -1
  96. package/dist/wireRefusalCopy.js +2 -2
  97. package/docs/INTEGRATION-CLIENTS.md +816 -14
  98. package/package.json +4 -4
@@ -189,6 +189,45 @@ function rowIdentityUnreadable(row) {
189
189
  return true;
190
190
  }
191
191
  }
192
+ function aliasesOf(cat) {
193
+ const ALIASES_ABSENT = Object.freeze({ kind: 'absent' });
194
+ const ALIASES_UNREADABLE = Object.freeze({ kind: 'unreadable' });
195
+ try {
196
+ const d = Object.getOwnPropertyDescriptor(cat, 'aliases');
197
+ if (d === undefined)
198
+ return ALIASES_ABSENT;
199
+ if (!Object.hasOwn(d, 'value'))
200
+ return ALIASES_UNREADABLE;
201
+ const raw = d.value;
202
+ if (raw === undefined)
203
+ return ALIASES_ABSENT;
204
+ if (!Array.isArray(raw))
205
+ return ALIASES_UNREADABLE;
206
+ const n = ownDataOf(raw, 'length');
207
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0)
208
+ return ALIASES_UNREADABLE;
209
+ const entries = [];
210
+ for (let i = 0; i < n; i += 1) {
211
+ const ed = Object.getOwnPropertyDescriptor(raw, String(i));
212
+ if (ed === undefined || !Object.hasOwn(ed, 'value'))
213
+ return ALIASES_UNREADABLE;
214
+ const e = ed.value;
215
+ if (e === null || typeof e !== 'object' || !isPlainRecord(e))
216
+ return ALIASES_UNREADABLE;
217
+ const nd = Object.getOwnPropertyDescriptor(e, 'name');
218
+ if (nd === undefined || !Object.hasOwn(nd, 'value') || typeof nd.value !== 'string' || nd.value.length === 0)
219
+ return ALIASES_UNREADABLE;
220
+ const bit = atMentionBitOf(e);
221
+ if (bit === 'malformed' || bit === 'unreadable')
222
+ return ALIASES_UNREADABLE;
223
+ appendOwnElement(entries, Object.freeze({ name: nd.value, bit }));
224
+ }
225
+ return Object.freeze({ kind: 'listed', entries: Object.freeze(entries) });
226
+ }
227
+ catch {
228
+ return ALIASES_UNREADABLE;
229
+ }
230
+ }
192
231
  export function modelAcceptanceOf(model, catalog = getLiveModelCatalog()) {
193
232
  const name = typeof model === 'string' && model.length > 0 ? model : undefined;
194
233
  try {
@@ -211,22 +250,45 @@ export function modelAcceptanceOf(model, catalog = getLiveModelCatalog()) {
211
250
  defaultModel = undefined;
212
251
  }
213
252
  const withDefault = defaultModel === undefined ? {} : { defaultModel };
253
+ const cannotSay = () => Object.freeze({ verdict: 'unknown', why: 'catalog_unreadable', model: name });
214
254
  const reservedReading = () => Object.freeze({ verdict: 'unknown', why: 'reserved_word', model: name, ...withDefault });
215
- if (reserved)
216
- return reservedReading();
217
- const row = liveModelRowOf(catalog, name);
218
- if (row !== undefined) {
219
- const normalized = ownDataOf(row, 'name');
220
- if (typeof normalized !== 'string' || normalized.length === 0)
221
- return Object.freeze({ verdict: 'unknown', why: 'catalog_unreadable', model: name });
222
- if (isReservedModelWord(normalized))
255
+ const notMentionable = () => Object.freeze({ verdict: 'refused', why: 'not_at_mentionable', model: name, ...withDefault });
256
+ const byRow = (row) => {
257
+ if (ownDataOf(row, 'enabled') === false)
258
+ return Object.freeze({ verdict: 'refused', why: 'disabled', model: name, ...withDefault });
259
+ const bit = atMentionBitOf(row);
260
+ if (bit === 'refused')
261
+ return notMentionable();
262
+ if (bit === 'malformed' || bit === 'unreadable')
263
+ return cannotSay();
264
+ return Object.freeze({ verdict: 'accepted', why: 'listed', model: name, ...withDefault });
265
+ };
266
+ const aliases = aliasesOf(catalog);
267
+ if (aliases.kind === 'absent') {
268
+ if (reserved)
223
269
  return reservedReading();
224
- return ownDataOf(row, 'enabled') === false
225
- ? Object.freeze({ verdict: 'refused', why: 'disabled', model: name, ...withDefault })
226
- : Object.freeze({ verdict: 'accepted', why: 'listed', model: name, ...withDefault });
270
+ const row = liveModelRowOf(catalog, name);
271
+ if (row !== undefined) {
272
+ const normalized = ownDataOf(row, 'name');
273
+ if (typeof normalized !== 'string' || normalized.length === 0)
274
+ return cannotSay();
275
+ if (isReservedModelWord(normalized))
276
+ return reservedReading();
277
+ return byRow(row);
278
+ }
279
+ }
280
+ else {
281
+ const row = rows.find((r) => ownDataOf(r, 'name') === name);
282
+ if (row !== undefined)
283
+ return byRow(row);
284
+ if (aliases.kind === 'unreadable')
285
+ return cannotSay();
286
+ const alias = aliases.entries.find((a) => a.name === name);
287
+ if (alias !== undefined)
288
+ return alias.bit === 'refused' ? notMentionable() : Object.freeze({ verdict: 'accepted', why: 'aliased', model: name, ...withDefault });
227
289
  }
228
290
  if (rows.some(rowIdentityUnreadable))
229
- return Object.freeze({ verdict: 'unknown', why: 'catalog_unreadable', model: name });
291
+ return cannotSay();
230
292
  const dropped = droppedModelEntriesOf(catalog);
231
293
  if (droppedDisabledNamed(dropped, name))
232
294
  return Object.freeze({ verdict: 'refused', why: 'disabled', model: name, ...withDefault });
@@ -246,7 +308,7 @@ export function modelNotServedNotice(reading, lang) {
246
308
  if (ownDataOf(reading, 'verdict') !== 'refused')
247
309
  return undefined;
248
310
  const why = ownDataOf(reading, 'why');
249
- if (why !== 'not_listed' && why !== 'disabled')
311
+ if (why !== 'not_listed' && why !== 'disabled' && why !== 'not_at_mentionable')
250
312
  return undefined;
251
313
  const model = ownDataOf(reading, 'model');
252
314
  const shown = typeof model === 'string' && model.length > 0 ? quoteUntrustedForSentence(model, NOT_SERVED_NAME_MAX) : undefined;
@@ -254,6 +316,11 @@ export function modelNotServedNotice(reading, lang) {
254
316
  return undefined;
255
317
  const dm = ownDataOf(reading, 'defaultModel');
256
318
  const fallback = typeof dm === 'string' && dm.length > 0 ? quoteUntrustedForSentence(dm, NOT_SERVED_NAME_MAX) : undefined;
319
+ if (why === 'not_at_mentionable') {
320
+ return fallback === undefined
321
+ ? renderMessage(msg('model.notMentionable.noDefault', { model: shown }), lang)
322
+ : renderMessage(msg('model.notMentionable.withDefault', { model: shown, default: fallback }), lang);
323
+ }
257
324
  return fallback === undefined
258
325
  ? renderMessage(msg('model.notServed.noDefault', { model: shown }), lang)
259
326
  : renderMessage(msg('model.notServed.withDefault', { model: shown, default: fallback }), lang);
@@ -420,6 +487,24 @@ export function modelPlaneOf(catalog = getLiveModelCatalog()) {
420
487
  return unreadable;
421
488
  }
422
489
  }
490
+ export function modelPlaneSentence(reading, lang) {
491
+ try {
492
+ if (reading === null || typeof reading !== 'object' || Array.isArray(reading))
493
+ return undefined;
494
+ if (ownDataOf(reading, 'kind') !== 'read')
495
+ return undefined;
496
+ const served = ownDataOf(reading, 'servedTable');
497
+ const stance = ownDataOf(reading, 'catalog');
498
+ if (served !== 'env')
499
+ return undefined;
500
+ if (stance !== 'absent' && stance !== 'refused')
501
+ return undefined;
502
+ return renderMessage(msg('model.catalog.envServed', { stance }), lang);
503
+ }
504
+ catch {
505
+ return undefined;
506
+ }
507
+ }
423
508
  const CONFIG_DOMAIN_WORD = /^[a-z][a-z0-9_.-]{0,63}$/;
424
509
  export function modelCandidateRejectedContent(reading, lang) {
425
510
  try {
@@ -467,6 +552,7 @@ export function readLiveModelCatalog(body) {
467
552
  const droppedKey = envelopeKey(body, 'droppedEntries');
468
553
  const candidateKey = envelopeKey(body, 'candidateRejected');
469
554
  const planeKey = envelopeKey(body, 'modelPlane');
555
+ const aliasesKey = envelopeKey(body, 'aliases');
470
556
  const raw = modelsKey.present ? modelsKey.value : undefined;
471
557
  if (!Array.isArray(raw))
472
558
  return unreadableCatalog('models_not_array');
@@ -479,7 +565,7 @@ export function readLiveModelCatalog(body) {
479
565
  let unreadableRows = 0;
480
566
  for (let i = 0; i < len; i += 1) {
481
567
  const d = Object.getOwnPropertyDescriptor(raw, String(i));
482
- const row = d !== undefined && Object.hasOwn(d, 'value') ? liveModelRowCopy(d.value) : undefined;
568
+ const row = d !== undefined && Object.hasOwn(d, 'value') ? modelRowTransitCopy(d.value) : undefined;
483
569
  if (row === undefined || names.has(row.name)) {
484
570
  unreadableRows += 1;
485
571
  continue;
@@ -504,12 +590,60 @@ export function readLiveModelCatalog(body) {
504
590
  out.candidateRejected = frozenJsonCopyOf(candidateKey.value) ?? null;
505
591
  if (planeKey.present && planeKey.value !== undefined)
506
592
  out.modelPlane = frozenJsonCopyOf(planeKey.value) ?? null;
593
+ if (aliasesKey.present && aliasesKey.value !== undefined)
594
+ out.aliases = aliasesTransitCopy(aliasesKey.value);
507
595
  return Object.freeze({ kind: 'read', catalog: Object.freeze(out), unreadableRows, ...(defaultModelMalformed ? { defaultModelMalformed: true } : {}) });
508
596
  }
509
597
  catch {
510
598
  return unreadableCatalog('unreadable');
511
599
  }
512
600
  }
601
+ function aliasesTransitCopy(v) {
602
+ const copy = frozenJsonCopyOf(v);
603
+ if (copy === undefined)
604
+ return null;
605
+ const before = aliasesOf({ aliases: v });
606
+ const after = aliasesOf({ aliases: copy });
607
+ if (before.kind !== after.kind)
608
+ return null;
609
+ if (before.kind === 'listed' && after.kind === 'listed') {
610
+ if (before.entries.length !== after.entries.length)
611
+ return null;
612
+ for (let i = 0; i < before.entries.length; i += 1) {
613
+ const b = before.entries[i];
614
+ const a = after.entries[i];
615
+ if (b === undefined || a === undefined || b.name !== a.name || b.bit !== a.bit)
616
+ return null;
617
+ }
618
+ }
619
+ return copy;
620
+ }
621
+ const ACCEPTANCE_ROW_KEYS = Object.freeze(['id', 'enabled', 'atMentionable']);
622
+ function modelRowTransitCopy(row) {
623
+ const copy = liveModelRowCopy(row);
624
+ if (copy === undefined || row === null || typeof row !== 'object')
625
+ return copy;
626
+ let restated;
627
+ for (const key of ACCEPTANCE_ROW_KEYS) {
628
+ if (Object.hasOwn(copy, key))
629
+ continue;
630
+ let value;
631
+ try {
632
+ const d = Object.getOwnPropertyDescriptor(row, key);
633
+ if (d === undefined)
634
+ continue;
635
+ value = Object.hasOwn(d, 'value') ? (d.value === undefined ? undefined : (frozenJsonCopyOf(d.value) ?? null)) : null;
636
+ }
637
+ catch {
638
+ value = null;
639
+ }
640
+ if (value === undefined)
641
+ continue;
642
+ restated ??= { ...copy };
643
+ Object.defineProperty(restated, key, { value, enumerable: true, writable: true, configurable: true });
644
+ }
645
+ return restated === undefined ? copy : Object.freeze(restated);
646
+ }
513
647
  function droppedEntriesCopy(v) {
514
648
  if (!Array.isArray(v))
515
649
  return frozenJsonCopyOf(v) ?? null;
@@ -544,23 +678,27 @@ export function liveDefaultModelOf(catalog = getLiveModelCatalog()) {
544
678
  return Object.freeze({ kind: 'unknown', why: 'unreadable' });
545
679
  }
546
680
  }
681
+ function atMentionBitOf(o) {
682
+ const d = Object.getOwnPropertyDescriptor(o, 'atMentionable');
683
+ if (d === undefined)
684
+ return 'not_governed';
685
+ if (!(Object.hasOwn(d, 'value')))
686
+ return 'unreadable';
687
+ if (d.value === undefined)
688
+ return 'not_governed';
689
+ if (d.value === true)
690
+ return 'allowed';
691
+ if (d.value === false)
692
+ return 'refused';
693
+ return 'malformed';
694
+ }
547
695
  export function atMentionOf(model, catalog = getLiveModelCatalog()) {
548
696
  try {
549
697
  const target = targetRowOf(model, catalog);
550
698
  if (target.kind !== 'row')
551
699
  return Object.freeze({ kind: 'unknown', why: target.why });
552
- const d = Object.getOwnPropertyDescriptor(target.row, 'atMentionable');
553
- if (d === undefined)
554
- return Object.freeze({ kind: 'not_governed' });
555
- if (!(Object.hasOwn(d, 'value')))
556
- return Object.freeze({ kind: 'unknown', why: 'unreadable' });
557
- if (d.value === undefined)
558
- return Object.freeze({ kind: 'not_governed' });
559
- if (d.value === true)
560
- return Object.freeze({ kind: 'allowed' });
561
- if (d.value === false)
562
- return Object.freeze({ kind: 'refused' });
563
- return Object.freeze({ kind: 'unknown', why: 'malformed' });
700
+ const bit = atMentionBitOf(target.row);
701
+ return bit === 'malformed' || bit === 'unreadable' ? Object.freeze({ kind: 'unknown', why: bit }) : Object.freeze({ kind: bit });
564
702
  }
565
703
  catch {
566
704
  return Object.freeze({ kind: 'unknown', why: 'unreadable' });
@@ -1,4 +1,6 @@
1
1
  import { SETTINGS_RESOLVE_CATALOG_DEFAULT_MODEL, type SETTINGS_DEFAULT_ROLE_TIER_LADDER } from '../generated/settingsSchemaFns.js';
2
+ import { SETTINGS_MODEL_PLANE_REFUSAL_CODES } from '../generated/settingsSchemaTables.js';
3
+ import type { UiLanguage } from '../env/uiLanguage.js';
2
4
  export type CatalogDefaultModelsInput = Parameters<typeof SETTINGS_RESOLVE_CATALOG_DEFAULT_MODEL>[0];
3
5
  export type CatalogDefaultSource = 'roles.default' | 'fallback' | `tierGroup.${(typeof SETTINGS_DEFAULT_ROLE_TIER_LADDER)[number]}`;
4
6
  export interface CatalogDefaultDangling {
@@ -22,3 +24,26 @@ export interface CatalogDefaultSelect {
22
24
  }
23
25
  export type CatalogDefaultModel = CatalogDefaultNamed | CatalogDefaultSelect;
24
26
  export declare function resolveCatalogDefaultModel(models: CatalogDefaultModelsInput, has: (name: string) => boolean, fallback: string): CatalogDefaultModel;
27
+ export type CatalogDefaultRefusalCode = Extract<(typeof SETTINGS_MODEL_PLANE_REFUSAL_CODES)[number], 'config.default_model_missing'>;
28
+ export interface CatalogDefaultModelVerdictInput {
29
+ readonly models: CatalogDefaultModelsInput;
30
+ readonly has: (name: string) => boolean;
31
+ readonly fallback: string;
32
+ readonly face: object;
33
+ }
34
+ export interface CatalogDefaultResolvable {
35
+ readonly ok: true;
36
+ readonly default: CatalogDefaultModel;
37
+ }
38
+ export interface CatalogDefaultRefused {
39
+ readonly ok: false;
40
+ readonly code: CatalogDefaultRefusalCode;
41
+ readonly default: CatalogDefaultModel;
42
+ readonly detail: {
43
+ readonly cause: string;
44
+ readonly role: string;
45
+ };
46
+ }
47
+ export type CatalogDefaultModelVerdict = CatalogDefaultResolvable | CatalogDefaultRefused;
48
+ export declare function catalogDefaultModelVerdict(input: CatalogDefaultModelVerdictInput): CatalogDefaultModelVerdict | undefined;
49
+ export declare function catalogDefaultRefusalContent(verdict: CatalogDefaultModelVerdict, lang?: UiLanguage): string | undefined;
@@ -1,4 +1,48 @@
1
1
  import { SETTINGS_RESOLVE_CATALOG_DEFAULT_MODEL } from '../generated/settingsSchemaFns.js';
2
+ import { modelFaceResolvable } from '../coreValuePorts.js';
3
+ import { slotReadOf } from '../wireFailureShape.js';
4
+ import { displayUntrusted } from '../displayUntrusted.js';
5
+ import { msg, renderMessage } from '../i18n/translate.js';
2
6
  export function resolveCatalogDefaultModel(models, has, fallback) {
3
7
  return SETTINGS_RESOLVE_CATALOG_DEFAULT_MODEL(models, has, fallback);
4
8
  }
9
+ const CATALOG_DEFAULT_REFUSAL_CODE = 'config.default_model_missing';
10
+ export function catalogDefaultModelVerdict(input) {
11
+ const { models, has, fallback, face } = input;
12
+ const chain = resolveCatalogDefaultModel(models, has, fallback);
13
+ const verdict = modelFaceResolvable(face, { spec: { modelRole: 'default' } });
14
+ if (verdict === undefined || typeof verdict !== 'object' || verdict === null)
15
+ return undefined;
16
+ const okSlot = slotReadOf(verdict, 'ok');
17
+ if (okSlot.presence !== 'present')
18
+ return undefined;
19
+ if (okSlot.value === true)
20
+ return Object.freeze({ ok: true, default: chain });
21
+ if (okSlot.value !== false)
22
+ return undefined;
23
+ const codeSlot = slotReadOf(verdict, 'code');
24
+ const roleSlot = slotReadOf(verdict, 'role');
25
+ const code = codeSlot.presence === 'present' ? codeSlot.value : undefined;
26
+ const role = roleSlot.presence === 'present' ? roleSlot.value : undefined;
27
+ if (typeof code !== 'string' || code.length === 0 || typeof role !== 'string' || role.length === 0)
28
+ return undefined;
29
+ return Object.freeze({
30
+ ok: false,
31
+ code: CATALOG_DEFAULT_REFUSAL_CODE,
32
+ default: chain,
33
+ detail: Object.freeze({ cause: code, role }),
34
+ });
35
+ }
36
+ export function catalogDefaultRefusalContent(verdict, lang) {
37
+ if (typeof verdict !== 'object' || verdict === null || verdict.ok !== false)
38
+ return undefined;
39
+ const d = verdict.default;
40
+ if (typeof d !== 'object' || d === null)
41
+ return undefined;
42
+ if (d.kind === 'named' && typeof d.name === 'string') {
43
+ return renderMessage(msg('model.catalogDefault.refused.named', { name: displayUntrusted(d.name, { max: 64 }) }), lang);
44
+ }
45
+ if (d.kind === 'select')
46
+ return renderMessage(msg('model.catalogDefault.refused.select'), lang);
47
+ return undefined;
48
+ }
@@ -4,6 +4,7 @@ export type { PermissionMode };
4
4
  export interface PermissionModeField {
5
5
  permissionMode?: PermissionMode;
6
6
  }
7
+ export declare function permissionModeTitle(word: string): string | undefined;
7
8
  export declare function permissionModeField(mode: string | undefined, opts?: {
8
9
  stampDefault?: boolean;
9
10
  permissionModes?: PermissionModesReading | undefined;
@@ -2,6 +2,17 @@ import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
2
2
  import { permissionModeAdmitted } from './permissionModesCapability.js';
3
3
  const _permissionModePin = true;
4
4
  void _permissionModePin;
5
+ const PERMISSION_MODE_TITLES = {
6
+ default: 'Manual',
7
+ acceptEdits: 'Accept edits',
8
+ plan: 'Plan',
9
+ auto: 'Auto',
10
+ bypassPermissions: 'Bypass Permissions',
11
+ dontAsk: "Don't Ask",
12
+ };
13
+ export function permissionModeTitle(word) {
14
+ return typeof word === 'string' && Object.hasOwn(PERMISSION_MODE_TITLES, word) ? PERMISSION_MODE_TITLES[word] : undefined;
15
+ }
5
16
  const BIT_GATED_MODE = 'dontAsk';
6
17
  const KNOWN = new Set(PERMISSION_MODE_WORDS.filter((w) => w !== BIT_GATED_MODE));
7
18
  export function permissionModeField(mode, opts) {
@@ -8,11 +8,11 @@ export declare const ENGINE_FIRED_HOOK_EVENTS: readonly ["PreToolUse", "PostTool
8
8
  export type EngineFiredHookEvent = (typeof ENGINE_FIRED_HOOK_EVENTS)[number];
9
9
  export declare const PLUGIN_HOOK_DISPOSITIONS: readonly ["engine", "shell", "not_run"];
10
10
  export type PluginHookDisposition = (typeof PLUGIN_HOOK_DISPOSITIONS)[number];
11
- export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event", "no_executor", "engine_not_local", "engine_locality_unknown", "engine_capability_absent", "engine_capability_unknown", "not_command", "sensitive_options", "engine_limit", "shell_form_user_config", "user_config_unset", "malformed_entry", "engine_hooks_off", "engine_hooks_unknown", "engine_type_skipped", "engine_llm_limit", "engine_unconfirmed", "engine_elsewhere", "hooks_module", "scope_unsupported", "source_incomplete", "compaction_local_fallback"];
11
+ export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event", "no_executor", "engine_not_local", "engine_locality_unknown", "engine_capability_absent", "engine_capability_unknown", "not_command", "sensitive_options", "engine_limit", "shell_form_user_config", "user_config_unset", "malformed_entry", "engine_hooks_off", "engine_hooks_unknown", "engine_type_skipped", "engine_llm_limit", "engine_unconfirmed", "engine_elsewhere", "hooks_module", "scope_unsupported", "source_incomplete", "compaction_local_fallback", "key_not_read", "source_not_authored"];
12
12
  export type PluginHookReason = (typeof PLUGIN_HOOK_REASONS)[number];
13
13
  export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrusted", "governance_unreadable", "policy_disable_all", "startup_mode", "managed_plugins_only", "prompt_not_user_authored"];
14
14
  export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
15
- export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported"];
15
+ export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported", "key_not_read", "source_not_authored"];
16
16
  export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
17
17
  export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings';
18
18
  export interface PluginHookKey {
@@ -36,6 +36,7 @@ export interface SettingsHookDrop {
36
36
  readonly hookIndex: number;
37
37
  readonly reason: SettingsHookDropReason;
38
38
  readonly notRun: boolean;
39
+ readonly unreadKeys?: readonly (readonly (string | number)[])[];
39
40
  }
40
41
  export interface SettingsHookVerdict {
41
42
  readonly source: HookSettingsSource;
@@ -147,6 +148,7 @@ export interface PluginHookDecisionInput {
147
148
  readonly promptAuthoredByUser: boolean;
148
149
  readonly occupied: ReadonlyMap<string, EventOccupancy>;
149
150
  readonly suppressedLocally?: ReadonlySet<string>;
151
+ readonly readerNotRead: readonly string[];
150
152
  }
151
153
  export interface PluginHookDecision {
152
154
  readonly verdicts: readonly PluginHookVerdict[];
@@ -166,7 +168,7 @@ export declare function decideSessionHooks(input: SessionHookDecisionInput): {
166
168
  readonly verdicts: readonly SessionHookVerdict[];
167
169
  readonly unreadable: number;
168
170
  };
169
- export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, judge: SettingsHookJudge, mask?: HooksSalvageMask): SettingsHookFilterResult;
171
+ export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, judge: SettingsHookJudge, readerNotRead: readonly string[], mask?: HooksSalvageMask): SettingsHookFilterResult;
170
172
  export interface SettingsHookFilterResult {
171
173
  readonly groups: WireHookGroup[];
172
174
  readonly drops: SettingsHookDrop[];
@@ -1,8 +1,8 @@
1
1
  import { displayUntrusted } from './displayUntrusted.js';
2
- import { appendOwnElement } from './ownKey.js';
2
+ import { appendOwnElement, readOwnDataValue } from './ownKey.js';
3
3
  import { msg, renderMessage } from './i18n/translate.js';
4
4
  import { SETTINGS_ENGINE_OWNED_HOOK_EVENTS } from './generated/settingsSchemaTables.js';
5
- import { HOOK_SETTINGS_SOURCE_TEXT, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
5
+ import { HOOK_SETTINGS_SOURCE_TEXT, hooksEntryUnreadKeys, hooksGroupAuthoredSource, hooksGroupUnreadKeys, hooksKeyCoordinateText, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
6
6
  export const ENGINE_FIRED_HOOK_EVENTS = SETTINGS_ENGINE_OWNED_HOOK_EVENTS;
7
7
  export const PLUGIN_HOOK_DISPOSITIONS = ['engine', 'shell', 'not_run'];
8
8
  export const PLUGIN_HOOK_REASONS = [
@@ -29,6 +29,8 @@ export const PLUGIN_HOOK_REASONS = [
29
29
  'scope_unsupported',
30
30
  'source_incomplete',
31
31
  'compaction_local_fallback',
32
+ 'key_not_read',
33
+ 'source_not_authored',
32
34
  ];
33
35
  export const PLUGIN_HOOK_EXCLUSION_REASONS = [
34
36
  'workspace_untrusted',
@@ -38,7 +40,7 @@ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
38
40
  'managed_plugins_only',
39
41
  'prompt_not_user_authored',
40
42
  ];
41
- export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported'];
43
+ export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported', 'key_not_read', 'source_not_authored'];
42
44
  export function hooksWirePlanShapeOf(v) {
43
45
  try {
44
46
  if (typeof v !== 'object' || v === null || Array.isArray(v))
@@ -257,7 +259,9 @@ export function decidePluginHooks(input) {
257
259
  unreadable++;
258
260
  return;
259
261
  }
260
- ;
262
+ const authored = hooksGroupAuthoredSource(group);
263
+ const groupUnread = authored === 'unreadable' ? undefined : hooksGroupUnreadKeys(event, group);
264
+ const groupSourceAuthored = authored === 'unreadable' || (authored === 'present' && groupUnread !== undefined);
261
265
  group.hooks.forEach((entry, hookIndex) => {
262
266
  const key = { pluginId, pluginName, event, groupIndex, hookIndex };
263
267
  if (input.exclusion !== undefined) {
@@ -317,6 +321,15 @@ export function decidePluginHooks(input) {
317
321
  appendOwnElement(verdicts, { ...key, disposition: ex.disposition, reason: ex.reason });
318
322
  return;
319
323
  }
324
+ const entryUnread = isRecord(entry) ? hooksEntryUnreadKeys(event, entry, input.readerNotRead) : undefined;
325
+ if (groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined)) {
326
+ appendOwnElement(verdicts, { ...key, ...fallback(event, entryType, 'source_not_authored') });
327
+ return;
328
+ }
329
+ if (entryUnread !== undefined && ((groupUnread?.length ?? 0) > 0 || entryUnread.length > 0)) {
330
+ appendOwnElement(verdicts, { ...key, ...fallback(event, entryType, 'key_not_read') });
331
+ return;
332
+ }
320
333
  const plugin = { name: src.name, root: src.root, dataDir: src.dataDir };
321
334
  const options = projectedOptions(src.options);
322
335
  if (options !== undefined)
@@ -454,7 +467,18 @@ function carriesArgs(entry) {
454
467
  }
455
468
  return true;
456
469
  }
457
- export function filterSettingsHookGroups(source, event, groups, execFormCapable, shellRunsType, judge, mask) {
470
+ function unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread, entryUnread) {
471
+ if (entryUnread === undefined)
472
+ return Object.freeze([]);
473
+ const out = [];
474
+ for (const k of groupUnread)
475
+ appendOwnElement(out, Object.freeze(['hooks', event, groupIndex, k]));
476
+ for (const k of entryUnread)
477
+ if (k !== 'plugin')
478
+ appendOwnElement(out, Object.freeze(['hooks', event, groupIndex, 'hooks', hookIndex, k]));
479
+ return Object.freeze(out);
480
+ }
481
+ export function filterSettingsHookGroups(source, event, groups, execFormCapable, shellRunsType, judge, readerNotRead, mask) {
458
482
  const drops = [];
459
483
  const verdicts = [];
460
484
  const excluded = [];
@@ -470,20 +494,35 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
470
494
  }
471
495
  const kept = [];
472
496
  let changed = false;
497
+ const authored = hooksGroupAuthoredSource(group);
498
+ const groupUnread = authored === 'unreadable' ? undefined : hooksGroupUnreadKeys(event, group);
499
+ const groupSourceAuthored = authored === 'unreadable' || (authored === 'present' && groupUnread !== undefined);
473
500
  group.hooks.forEach((entry, hookIndex) => {
474
501
  if (mask !== undefined && mask.entryDropped(event, groupIndex, hookIndex)) {
475
502
  changed = true;
476
503
  return;
477
504
  }
478
- const reason = !isRecord(entry)
505
+ const earlier = !isRecord(entry)
479
506
  ? undefined
480
507
  : entry.type === 'mcp_tool'
481
508
  ? 'mcp_tool_unsupported'
482
509
  : entry.type === 'command' && carriesArgs(entry) && !execFormCapable
483
510
  ? 'exec_form_unsupported'
484
511
  : undefined;
512
+ const entryUnread = earlier === undefined && isRecord(entry) ? hooksEntryUnreadKeys(event, entry, readerNotRead) : undefined;
513
+ const sourceNotAuthored = earlier === undefined && groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined);
514
+ const unreadKeys = earlier === undefined && !sourceNotAuthored ? unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread ?? [], entryUnread) : [];
515
+ const reason = earlier ?? (sourceNotAuthored ? 'source_not_authored' : unreadKeys.length > 0 ? 'key_not_read' : undefined);
485
516
  if (reason !== undefined) {
486
- appendOwnElement(drops, { source, event, groupIndex, hookIndex, reason, notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined) });
517
+ appendOwnElement(drops, {
518
+ source,
519
+ event,
520
+ groupIndex,
521
+ hookIndex,
522
+ reason,
523
+ notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined),
524
+ ...(reason === 'key_not_read' ? { unreadKeys } : {}),
525
+ });
487
526
  changed = true;
488
527
  return;
489
528
  }
@@ -510,6 +549,8 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
510
549
  }
511
550
  appendOwnElement(kept, entry);
512
551
  });
552
+ if (((groupUnread?.length ?? 0) > 0 || groupSourceAuthored) && kept.length === 0)
553
+ changed = true;
513
554
  if (!changed)
514
555
  appendOwnElement(out, group);
515
556
  else if (kept.length > 0)
@@ -832,10 +873,14 @@ const REASON_TEXT = {
832
873
  scope_unsupported: 'hooks registered for a skill, an agent or by the host application are not sent to the engine, and this client does not run this event',
833
874
  source_incomplete: 'the plugin was reported without its id, name, root folder or data folder',
834
875
  compaction_local_fallback: renderMessage(msg('config.hooks.plan.reason.compactionLocalFallback')),
876
+ key_not_read: renderMessage(msg('config.hooks.plan.reason.keyNotRead')),
877
+ source_not_authored: renderMessage(msg('config.hooks.plan.reason.sourceNotAuthored')),
835
878
  };
836
879
  const DROP_TEXT = {
837
880
  exec_form_unsupported: 'exec-form hooks (with "args") are sent to the engine only once it reports support for them',
838
881
  mcp_tool_unsupported: 'MCP tool hooks are not supported by the engine',
882
+ key_not_read: REASON_TEXT.key_not_read,
883
+ source_not_authored: REASON_TEXT.source_not_authored,
839
884
  };
840
885
  const SOURCE_TEXT = HOOK_SETTINGS_SOURCE_TEXT;
841
886
  export function pluginHookReasonText(reason) {
@@ -898,8 +943,52 @@ function capNoticeLabel(s) {
898
943
  }
899
944
  return `${s.slice(0, end)}…`;
900
945
  }
946
+ const KEY_NOT_READ_SHOWN = 3;
947
+ function unreadPathsOfRow(d) {
948
+ const raw = d.unreadKeys;
949
+ if (!Array.isArray(raw))
950
+ return [];
951
+ return raw.filter((p) => Array.isArray(p) && p.length > 0 && p.every((x) => typeof x === 'string' || typeof x === 'number'));
952
+ }
953
+ function namedPathsOfRow(d) {
954
+ switch (d.reason) {
955
+ case 'key_not_read':
956
+ return unreadPathsOfRow(d);
957
+ case 'source_not_authored':
958
+ return typeof d.event === 'string' && Number.isInteger(d.groupIndex) && d.groupIndex >= 0 ? [['hooks', d.event, d.groupIndex, 'source']] : [];
959
+ case 'exec_form_unsupported':
960
+ case 'mcp_tool_unsupported':
961
+ return [];
962
+ }
963
+ }
964
+ function namedCoordinatesText(coordinates) {
965
+ const shown = coordinates.slice(0, KEY_NOT_READ_SHOWN).join(', ');
966
+ return coordinates.length > KEY_NOT_READ_SHOWN ? renderMessage(msg('config.hooks.plan.keyNotRead.more', { shown, rest: coordinates.length - KEY_NOT_READ_SHOWN })) : shown;
967
+ }
968
+ function keyNotReadLine(source, row) {
969
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
970
+ const hooks = `${row.count} ${events} ${row.count === 1 ? 'hook' : 'hooks'}`;
971
+ const coordinates = namedCoordinatesText(row.coordinates);
972
+ const line = renderMessage(msg(row.coordinates.length === 1 ? 'config.hooks.plan.keyNotRead.oneKey' : 'config.hooks.plan.keyNotRead.manyKeys', { layer: SOURCE_TEXT[source], hooks, coordinates }));
973
+ return row.onFailure ? renderMessage(msg('config.hooks.plan.keyNotRead.withOnFailure', { line })) : line;
974
+ }
975
+ function sourceNotAuthoredLine(source, row) {
976
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
977
+ const hooks = `${row.count} ${events} ${row.count === 1 ? 'hook' : 'hooks'}`;
978
+ const coordinates = namedCoordinatesText(row.coordinates);
979
+ return renderMessage(msg(row.coordinates.length === 1 ? 'config.hooks.plan.sourceNotAuthored.oneKey' : 'config.hooks.plan.sourceNotAuthored.manyKeys', { layer: SOURCE_TEXT[source], hooks, coordinates }));
980
+ }
901
981
  export function hooksNotRunNotice(plan) {
902
982
  const lines = [];
983
+ let closedBy;
984
+ try {
985
+ closedBy = isRecord(plan) ? readOwnDataValue(plan, 'closedBy') : undefined;
986
+ }
987
+ catch {
988
+ closedBy = undefined;
989
+ }
990
+ if (closedBy === 'policy_disable_all')
991
+ appendOwnElement(lines, renderMessage(msg('config.hooks.plan.disableAllHooks')));
903
992
  const byPlugin = new Map();
904
993
  for (const v of plan.verdicts) {
905
994
  if (v.disposition !== 'not_run')
@@ -916,9 +1005,27 @@ export function hooksNotRunNotice(plan) {
916
1005
  appendOwnElement(lines, `Plugin "${noticeLabel(row.name) || '(unnamed)'}": ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${pluginHookReasonText(row.reason)}.`);
917
1006
  }
918
1007
  const bySource = new Map();
1008
+ const namedBySource = new Map();
919
1009
  for (const d of plan.settingsDropped) {
920
1010
  if (!d.notRun)
921
1011
  continue;
1012
+ const named = d.reason === 'key_not_read' || d.reason === 'source_not_authored' ? namedPathsOfRow(d) : [];
1013
+ if (named.length > 0 && (d.reason === 'key_not_read' || d.reason === 'source_not_authored')) {
1014
+ const nk = `${d.source}\u0000${d.reason}`;
1015
+ const row = namedBySource.get(nk) ?? { source: d.source, reason: d.reason, events: [], count: 0, coordinates: [], onFailure: false };
1016
+ if (!row.events.includes(d.event))
1017
+ appendOwnElement(row.events, d.event);
1018
+ row.count++;
1019
+ for (const path of named) {
1020
+ const c = hooksKeyCoordinateText(path);
1021
+ if (!row.coordinates.includes(c))
1022
+ appendOwnElement(row.coordinates, c);
1023
+ if (d.reason === 'key_not_read' && path[path.length - 1] === 'onFailure')
1024
+ row.onFailure = true;
1025
+ }
1026
+ namedBySource.set(nk, row);
1027
+ continue;
1028
+ }
922
1029
  const k = `${d.source}\u0000${d.reason}`;
923
1030
  const row = bySource.get(k) ?? { source: d.source, reason: d.reason, events: [], count: 0 };
924
1031
  if (!row.events.includes(d.event))
@@ -930,6 +1037,8 @@ export function hooksNotRunNotice(plan) {
930
1037
  const events = row.events.map((e) => noticeLabel(e)).join(', ');
931
1038
  appendOwnElement(lines, `${SOURCE_TEXT[row.source]}: ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${settingsHookDropText(row.reason)}.`);
932
1039
  }
1040
+ for (const row of namedBySource.values())
1041
+ appendOwnElement(lines, row.reason === 'key_not_read' ? keyNotReadLine(row.source, row) : sourceNotAuthoredLine(row.source, row));
933
1042
  const settingsVerdicts = plan.settingsVerdicts;
934
1043
  const byVerdict = new Map();
935
1044
  for (const raw of Array.isArray(settingsVerdicts) ? settingsVerdicts : []) {
@@ -15,6 +15,7 @@ export interface QueueEntry {
15
15
  readonly intentKey?: string;
16
16
  readonly screening?: 'pending' | 'result';
17
17
  readonly upsExecutor?: 'shell' | 'engine' | 'unknown' | 'none';
18
+ readonly _sema_task_notification?: {} | null | undefined;
18
19
  }
19
20
  export type QueuedHoldReason = 'barrier' | 'share_incomplete' | 'not_steerable' | 'in_flight' | 'attempted' | 'no_run';
20
21
  export interface QueuedDeliveryPlan {