@sema-agent/client-core 0.83.0 → 0.83.1

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.
@@ -1,6 +1,6 @@
1
- const DISPLAY_UNSAFE = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
1
+ import { applyDisplayFace, LEGACY_ESCAPE_FACE, LEGACY_LABEL_FACE } from './displayUntrusted.js';
2
2
  export function escapeDisplayControlChars(s) {
3
- return s.replace(DISPLAY_UNSAFE, ch => `\\u${(ch.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, '0')}`);
3
+ return applyDisplayFace(s, LEGACY_ESCAPE_FACE);
4
4
  }
5
5
  export function capForDisplay(raw, max) {
6
6
  const escaped = escapeDisplayControlChars(raw);
@@ -19,7 +19,7 @@ export function capForDisplay(raw, max) {
19
19
  return cut > 0 ? escaped.slice(0, cut) : '';
20
20
  }
21
21
  export function collapseLabel(s) {
22
- return escapeDisplayControlChars(s.replace(/\s+/g, ' ').trim());
22
+ return applyDisplayFace(s, LEGACY_LABEL_FACE);
23
23
  }
24
24
  export function taskDescFromName(name, agentType, agentName) {
25
25
  if (!name || !agentType)
@@ -116,5 +116,5 @@ export function ruleStoreUnreadableDetail(kind) {
116
116
  return `your standing deny/ask rules could not be applied to this call; the reported reason ${word} is not one of the mechanisms this client's closed set knows, so answer this ask as usual`;
117
117
  }
118
118
  export function mandatedApprovalDetail() {
119
- return 'no saved allow rule and no standing posture can retire this question, so it is asked every time — answering it here settles this one call';
119
+ return 'no saved allow rule and no remembered answer can retire this question; each call is settled on its own — by you here, by a hook, or by an automatic check the deployment runs — and an answer covers only that call';
120
120
  }
@@ -1,10 +1,12 @@
1
+ import { displayUntrusted } from '../displayUntrusted.js';
1
2
  const NOTE_DISPLAY_MAX = 200;
2
3
  export const DECISION_NOTE_NOTICE_KEY = 'approval-decision-note';
3
4
  function cleanNote(raw) {
4
- const flat = raw
5
- .replace(/[\u0000-\u001f\u007f-\u009f]+/g, ' ')
5
+ const flat = displayUntrusted(raw, { credentialUrls: false, credentialWords: false, foldLines: false, mark: 'space' })
6
6
  .replace(/ {2,}/g, ' ')
7
7
  .trim();
8
+ if (flat === '')
9
+ return undefined;
8
10
  return flat.length <= NOTE_DISPLAY_MAX ? flat : `${flat.slice(0, NOTE_DISPLAY_MAX - 1)}…`;
9
11
  }
10
12
  export function readDecisionNoteAudit(ack) {
@@ -0,0 +1,14 @@
1
+ import type { SessionPermissionRules } from '@sema-agent/sdk';
2
+ export type SessionPolicyWithheldWhy = 'ask_no_bucket' | 'qualified_deny' | 'peer_wide' | 'wildcard' | 'not_a_tool_name';
3
+ export declare const SESSION_POLICY_WITHHELD_WHY: readonly SessionPolicyWithheldWhy[];
4
+ export interface SessionPolicyWithheldRule {
5
+ readonly rule: string;
6
+ readonly why: SessionPolicyWithheldWhy;
7
+ }
8
+ export interface SessionPolicyDeliverability {
9
+ readonly deliverable: SessionPermissionRules;
10
+ readonly withheld: readonly SessionPolicyWithheldRule[];
11
+ readonly unreadable?: true;
12
+ }
13
+ export declare function sessionPolicyDeliverable(behavior: 'deny' | 'ask', rules: readonly string[]): SessionPolicyDeliverability;
14
+ export declare function sessionPolicyWithheldNotice(why: SessionPolicyWithheldWhy): string;
@@ -0,0 +1,80 @@
1
+ const WITHHELD_NOTICE = {
2
+ ask_no_bucket: 'Not written to this session’s rule record: that record only holds rules that block outright, and this rule asks before running instead — recording it as a block would change what it means.',
3
+ qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
4
+ peer_wide: 'Not written to this session’s rule record: this rule covers every tool of one server or agent peer, and an engine that matches that record by exact tool name would block none of them.',
5
+ wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
6
+ not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, or has spaces around the name.',
7
+ };
8
+ export const SESSION_POLICY_WITHHELD_WHY = Object.freeze(Object.keys(WITHHELD_NOTICE));
9
+ const MAX_RULES = 10_000;
10
+ function unreadable() {
11
+ return { deliverable: {}, withheld: [], unreadable: true };
12
+ }
13
+ const COVERING_NAMESPACE_PREFIXES = ['mcp__', 'a2a__'];
14
+ function withheldWhyOf(rule) {
15
+ if (rule.length === 0)
16
+ return 'not_a_tool_name';
17
+ if (/(?:^|[^\\])\(/.test(rule))
18
+ return 'qualified_deny';
19
+ if (rule.includes('*'))
20
+ return 'wildcard';
21
+ for (const prefix of COVERING_NAMESPACE_PREFIXES) {
22
+ if (!rule.startsWith(prefix))
23
+ continue;
24
+ const segments = rule.slice(prefix.length).split('__');
25
+ if (segments[0] !== '' && segments.slice(1).join('__') === '')
26
+ return 'peer_wide';
27
+ }
28
+ if (rule.trim() !== rule)
29
+ return 'not_a_tool_name';
30
+ return undefined;
31
+ }
32
+ export function sessionPolicyDeliverable(behavior, rules) {
33
+ const entries = [];
34
+ try {
35
+ if (behavior !== 'deny' && behavior !== 'ask')
36
+ return unreadable();
37
+ if (!Array.isArray(rules))
38
+ return unreadable();
39
+ const n = rules.length;
40
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0 || n > MAX_RULES)
41
+ return unreadable();
42
+ for (let i = 0; i < n; i++) {
43
+ if (!Object.hasOwn(rules, i)) {
44
+ entries.push(undefined);
45
+ continue;
46
+ }
47
+ const x = rules[i];
48
+ entries.push(typeof x === 'string' ? x : undefined);
49
+ }
50
+ }
51
+ catch {
52
+ return unreadable();
53
+ }
54
+ const withheld = [];
55
+ if (behavior === 'ask') {
56
+ for (const r of entries)
57
+ withheld.push({ rule: r ?? '', why: 'ask_no_bucket' });
58
+ return { deliverable: {}, withheld };
59
+ }
60
+ const exact = [];
61
+ for (const r of entries) {
62
+ if (r === undefined) {
63
+ withheld.push({ rule: '', why: 'not_a_tool_name' });
64
+ continue;
65
+ }
66
+ const why = withheldWhyOf(r);
67
+ if (why === undefined)
68
+ exact.push(r);
69
+ else
70
+ withheld.push({ rule: r, why });
71
+ }
72
+ if (withheld.length > 0 || exact.length === 0)
73
+ return { deliverable: {}, withheld };
74
+ return { deliverable: { toolDeny: exact }, withheld };
75
+ }
76
+ export function sessionPolicyWithheldNotice(why) {
77
+ return typeof why === 'string' && Object.hasOwn(WITHHELD_NOTICE, why)
78
+ ? WITHHELD_NOTICE[why]
79
+ : 'Not written to this session’s rule record.';
80
+ }
@@ -1,8 +1,13 @@
1
+ import { type HooksWirePlan, type HooksWirePlanOptions } from './pluginHooksWire.js';
1
2
  import { type HookFailureNotice } from './notifications.js';
2
3
  import type { WireHooksConfig } from './finalVerifyWire.js';
3
4
  export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
4
5
  export type { WireHooksConfig };
5
- export declare function hooksForWire(): WireHooksConfig | undefined;
6
+ export interface HooksForWireOptions {
7
+ readonly plan?: HooksWirePlan;
8
+ }
9
+ export declare function hooksForWire(opts?: HooksForWireOptions): WireHooksConfig | undefined;
10
+ export declare function hooksWirePlan(opts?: HooksWirePlanOptions): HooksWirePlan;
6
11
  export declare function __resetHooksWireCapsForTests(): void;
7
12
  export declare function fleetHookFailureNoticeCapable(baseUrl: string | undefined): boolean;
8
13
  export declare const HOOK_FAILURE_WARN_PREFIX = "Hook error";
@@ -1,10 +1,15 @@
1
- import { engineCapNestedTrue } from './engineCapsCache.js';
1
+ import { engineCapNestedTrue, engineCapState } from './engineCapsCache.js';
2
+ import { displayUntrusted } from './displayUntrusted.js';
2
3
  import { collapseLabel } from './fleetTaskDesc.js';
3
4
  import { hostLog, hostSettings } from './host.js';
5
+ import { decidePluginHooks, filterSettingsHookGroups, occupancyOf, settingsHookDropText, } from './pluginHooksWire.js';
4
6
  import { MAX_HOOK_NOTICE_TEXT_CHARS } from './notifications.js';
5
7
  import { goalStopHookMatcher, __resetGoalStopHookForTests } from './goalStopHook.js';
6
8
  export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
7
9
  const EDITABLE_HOOK_SOURCES = ['userSettings', 'projectSettings', 'localSettings'];
10
+ const ALL_HOOK_SOURCES = ['policySettings', ...EDITABLE_HOOK_SOURCES];
11
+ const _hookSourceListsPin = [true, true];
12
+ void _hookSourceListsPin;
8
13
  function hooksLockedToPluginOnly(policyValue) {
9
14
  if (policyValue === true)
10
15
  return true;
@@ -42,19 +47,137 @@ function hooksOfSource(source) {
42
47
  return null;
43
48
  }
44
49
  }
45
- export function hooksForWire() {
50
+ let warnedNoPluginReader = new WeakSet();
51
+ const NO_PLUGIN_READER_WARNING = 'client-core: the installed settings port does not implement enabledPluginHooks() — ' +
52
+ 'plugin hooks are not sent to the engine and are not reported as not running. ' +
53
+ 'Implement SettingsPort.enabledPluginHooks() to list the enabled plugins and their hooks.';
54
+ let warnedNoPlanDrops = new WeakMap();
55
+ function noPlanDropWarning(event, reason) {
56
+ return (`client-core: ${displayUntrusted(event, { credentialUrls: false, credentialWords: false })} hooks from settings are not sent to the engine (${reason}), so they do not run there — ` +
57
+ `${settingsHookDropText(reason)}. Build the request with hooksForWire({ plan }) from hooksWirePlan() and show ` +
58
+ 'hooksNotRunNotice(plan) to tell the user which hooks will not run.');
59
+ }
60
+ function guardPluginTraversal(run) {
61
+ try {
62
+ return run();
63
+ }
64
+ catch (err) {
65
+ hostLog('debug', `hooksWireCaps: plugin hook data threw while being read — no plugin hooks projected this turn: ${String(err)}`);
66
+ return undefined;
67
+ }
68
+ }
69
+ function readPluginHooks(settings, warnIfAbsent) {
70
+ try {
71
+ if (typeof settings.enabledPluginHooks === 'function') {
72
+ const r = settings.enabledPluginHooks();
73
+ if (typeof r === 'object' && r !== null && Array.isArray(r.plugins)) {
74
+ return { state: 'present', reading: r };
75
+ }
76
+ hostLog('debug', 'hooksWireCaps: enabledPluginHooks() returned a malformed reading — no plugin hooks projected this turn');
77
+ return { state: 'failed' };
78
+ }
79
+ }
80
+ catch (err) {
81
+ hostLog('debug', `hooksWireCaps: enabledPluginHooks() threw — no plugin hooks projected this turn: ${String(err)}`);
82
+ return { state: 'failed' };
83
+ }
84
+ if (warnIfAbsent && !warnedNoPluginReader.has(settings)) {
85
+ warnedNoPluginReader.add(settings);
86
+ hostLog('warn', NO_PLUGIN_READER_WARNING);
87
+ }
88
+ return { state: 'absent' };
89
+ }
90
+ const NO_PLAN_ENGINE = { ownedByThisShell: null, pluginHooksCapable: null };
91
+ export function hooksForWire(opts = {}) {
92
+ const given = opts?.plan;
93
+ if (isHooksWirePlan(given))
94
+ return given.wire;
95
+ if (given !== undefined && given !== null) {
96
+ hostLog('debug', 'hooksForWire: opts.plan is not a plan from hooksWirePlan() — treated as absent (settings hooks only)');
97
+ }
98
+ if (isHooksWirePlan(opts))
99
+ return opts.wire;
100
+ return buildHooksWirePlan(NO_PLAN_ENGINE, new Set(), false).wire;
101
+ }
102
+ function isHooksWirePlan(v) {
103
+ try {
104
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
105
+ return false;
106
+ const o = v;
107
+ for (const k of PLAN_KEYS)
108
+ if (!Object.hasOwn(o, k))
109
+ return false;
110
+ const reader = o.pluginReader;
111
+ const engineFacts = o.engine;
112
+ const wire = o.wire;
113
+ return (Array.isArray(o.verdicts) &&
114
+ Array.isArray(o.excluded) &&
115
+ Array.isArray(o.settingsDropped) &&
116
+ (reader === 'present' || reader === 'absent' || reader === 'failed') &&
117
+ typeof engineFacts === 'object' &&
118
+ engineFacts !== null &&
119
+ (wire === undefined || (typeof wire === 'object' && wire !== null && !Array.isArray(wire))));
120
+ }
121
+ catch (err) {
122
+ hostLog('debug', `hooksForWire: a value offered as a plan threw while being read — treated as not a plan: ${String(err)}`);
123
+ return false;
124
+ }
125
+ }
126
+ const PLAN_KEYS = ['engine', 'pluginReader', 'verdicts', 'excluded', 'settingsDropped', 'wire'];
127
+ const _planKeysPin = [true, true];
128
+ void _planKeysPin;
129
+ export function hooksWirePlan(opts = {}) {
130
+ const o = opts ?? {};
131
+ const owned = o.engineOwnedByThisShell;
132
+ const engine = {
133
+ ownedByThisShell: owned === true ? true : owned === false ? false : null,
134
+ pluginHooksCapable: engineCapState(o.baseUrl, 'taskSettings') === 'unprobed'
135
+ ? null
136
+ : engineCapNestedTrue(o.baseUrl, 'taskSettings', 'pluginHooks'),
137
+ };
138
+ const shellEvents = new Set();
139
+ if (Array.isArray(o.shellHookEvents)) {
140
+ for (const e of o.shellHookEvents)
141
+ if (typeof e === 'string')
142
+ shellEvents.add(e);
143
+ }
144
+ return buildHooksWirePlan(engine, shellEvents, true);
145
+ }
146
+ function buildHooksWirePlan(engine, shellEvents, withPlugins) {
147
+ const settingsDropped = [];
148
+ let settingsStripped = 0;
149
+ const planOf = (pluginReader, wire, verdicts = [], excluded = []) => ({ engine, pluginReader, verdicts, excluded, settingsDropped, wire });
150
+ const closedPlan = (settings, reason) => {
151
+ if (!withPlugins)
152
+ return planOf('absent', undefined);
153
+ const read = readPluginHooks(settings, false);
154
+ if (read.reading === undefined)
155
+ return planOf(read.state, undefined);
156
+ const reading = read.reading;
157
+ const d = guardPluginTraversal(() => decidePluginHooks({
158
+ plugins: reading.plugins,
159
+ exclusion: reason,
160
+ managedOnly: false,
161
+ engine,
162
+ shellHookEvents: shellEvents,
163
+ occupied: new Map(),
164
+ }));
165
+ if (d === undefined)
166
+ return planOf('failed', undefined);
167
+ return planOf(read.state, undefined, d.verdicts, d.excluded);
168
+ };
46
169
  const settings = hostSettings();
47
170
  if (settings === undefined)
48
- return undefined;
171
+ return planOf('absent', undefined);
49
172
  try {
50
173
  if (settings.shouldSkipHookDueToTrust()) {
51
174
  hostLog('debug', 'hooksWireCaps: workspace not trusted — no hooks projected to the engine');
52
- return undefined;
175
+ return closedPlan(settings, 'workspace_untrusted');
53
176
  }
54
177
  }
55
178
  catch (err) {
56
179
  hostLog('debug', `hooksWireCaps: workspace trust check threw — fail-closed (no hooks projected to the engine): ${String(err)}`);
57
- return undefined;
180
+ return closedPlan(settings, 'governance_unreadable');
58
181
  }
59
182
  let policy = null;
60
183
  try {
@@ -62,25 +185,25 @@ export function hooksForWire() {
62
185
  }
63
186
  catch (err) {
64
187
  hostLog('debug', `hooksWireCaps: policySettings read threw — fail-closed (no hooks projected to the engine): ${String(err)}`);
65
- return undefined;
188
+ return closedPlan(settings, 'governance_unreadable');
66
189
  }
67
190
  if (policy?.disableAllHooks === true) {
68
191
  hostLog('debug', 'hooksWireCaps: disableAllHooks (managed) — no hooks projected to the engine');
69
- return undefined;
192
+ return closedPlan(settings, 'policy_disable_all');
70
193
  }
71
194
  const pluginOnlyHooks = hooksLockedToPluginOnly(policy?.strictPluginOnlyCustomization);
72
195
  if (pluginOnlyHooks) {
73
196
  hostLog('debug', 'hooksWireCaps: strictPluginOnlyCustomization locks the hooks surface (managed) — only policySettings hooks projected to the engine');
74
197
  }
75
198
  const managedByPolicy = policy?.allowManagedHooksOnly === true || pluginOnlyHooks;
76
- const disabledByNonManaged = !managedByPolicy && nonManagedDisableAllHooks(settings);
199
+ let mergedMemo;
200
+ const mergedDisable = () => (mergedMemo ??= nonManagedDisableAllHooks(settings));
201
+ const disabledByNonManaged = !managedByPolicy && mergedDisable();
77
202
  if (disabledByNonManaged) {
78
203
  hostLog('debug', 'hooksWireCaps: disableAllHooks (merged, non-managed) — only policySettings hooks projected to the engine');
79
204
  }
80
205
  const managedOnly = managedByPolicy || disabledByNonManaged;
81
- const sources = managedOnly
82
- ? ['policySettings']
83
- : ['policySettings', ...EDITABLE_HOOK_SOURCES];
206
+ const sources = managedOnly ? ['policySettings'] : ALL_HOOK_SOURCES;
84
207
  const merged = {};
85
208
  for (const source of sources) {
86
209
  const hooks = hooksOfSource(source);
@@ -89,18 +212,76 @@ export function hooksForWire() {
89
212
  for (const [event, groups] of Object.entries(hooks)) {
90
213
  if (!Array.isArray(groups) || groups.length === 0)
91
214
  continue;
92
- merged[event] = [...(merged[event] ?? []), ...groups];
215
+ const kept = filterSettingsHookGroups(source, event, groups, engine.pluginHooksCapable === true, shellEvents.has(event), !withPlugins);
216
+ settingsDropped.push(...kept.drops);
217
+ settingsStripped += kept.stripped;
218
+ if (kept.groups.length === 0)
219
+ continue;
220
+ merged[event] = [...(merged[event] ?? []), ...kept.groups];
93
221
  }
94
222
  }
95
223
  const goalMatcher = goalStopHookMatcher(managedOnly);
96
224
  if (goalMatcher !== null) {
97
225
  merged['Stop'] = [...(merged['Stop'] ?? []), goalMatcher];
98
226
  }
99
- return Object.keys(merged).length > 0 ? merged : undefined;
227
+ if (settingsDropped.length > 0) {
228
+ hostLog('debug', `hooksWireCaps: ${settingsDropped.length} settings hook entr${settingsDropped.length === 1 ? 'y' : 'ies'} not sent to the engine ` +
229
+ `(${[...new Set(settingsDropped.map((d) => d.reason))].join(', ')}) — see hooksWirePlan().settingsDropped`);
230
+ }
231
+ if (settingsStripped > 0) {
232
+ hostLog('debug', `hooksWireCaps: ${settingsStripped} settings hook entr${settingsStripped === 1 ? 'y' : 'ies'} carried a plugin key — ` +
233
+ 'stripped before sending (plugin context is only minted for entries from the plugin reader)');
234
+ }
235
+ const wireOf = () => (Object.keys(merged).length > 0 ? merged : undefined);
236
+ if (!withPlugins) {
237
+ let seen = warnedNoPlanDrops.get(settings);
238
+ for (const d of settingsDropped) {
239
+ if (!d.notRun)
240
+ continue;
241
+ const key = `${d.event}\u0000${d.reason}`;
242
+ if (seen?.has(key) === true)
243
+ continue;
244
+ if (seen === undefined) {
245
+ seen = new Set();
246
+ warnedNoPlanDrops.set(settings, seen);
247
+ }
248
+ seen.add(key);
249
+ hostLog('warn', noPlanDropWarning(d.event, d.reason));
250
+ }
251
+ return planOf('absent', wireOf());
252
+ }
253
+ const read = readPluginHooks(settings, true);
254
+ if (read.reading === undefined)
255
+ return planOf(read.state, wireOf());
256
+ const reading = read.reading;
257
+ const occupied = occupancyOf(merged);
258
+ const d = guardPluginTraversal(() => {
259
+ const plugins = reading.plugins;
260
+ const startupMode = reading.suppressedByStartupMode === true;
261
+ const managedOnlyPlugins = !startupMode && plugins.length > 0 && (policy?.allowManagedHooksOnly === true || mergedDisable());
262
+ return decidePluginHooks({
263
+ plugins,
264
+ exclusion: startupMode ? 'startup_mode' : undefined,
265
+ managedOnly: managedOnlyPlugins,
266
+ engine,
267
+ shellHookEvents: shellEvents,
268
+ occupied,
269
+ });
270
+ });
271
+ if (d === undefined)
272
+ return planOf('failed', wireOf());
273
+ if (d.unreadable > 0) {
274
+ hostLog('debug', `hooksWireCaps: ${d.unreadable} plugin hook source(s) or group(s) unreadable — skipped`);
275
+ }
276
+ for (const [event, groups] of d.projected)
277
+ merged[event] = [...(merged[event] ?? []), ...groups];
278
+ return planOf('present', wireOf(), d.verdicts, d.excluded);
100
279
  }
101
280
  export function __resetHooksWireCapsForTests() {
102
281
  __resetGoalStopHookForTests();
103
282
  warnedNoMergedReader = new WeakSet();
283
+ warnedNoPluginReader = new WeakSet();
284
+ warnedNoPlanDrops = new WeakMap();
104
285
  }
105
286
  export function fleetHookFailureNoticeCapable(baseUrl) {
106
287
  return engineCapNestedTrue(baseUrl, 'fleet', 'hookFailureNotice');
package/dist/host.d.ts CHANGED
@@ -1,10 +1,34 @@
1
1
  import type { AdapterLogLevel, TimerHandle } from './seam.js';
2
+ import type { WireHooksConfig } from './finalVerifyWire.js';
2
3
  import { type NotificationQueuePort } from './notifications.js';
3
4
  export type SettingsDocument = Record<string, unknown>;
4
5
  export interface SettingsPort {
5
6
  getSettingsForSource(source: string): SettingsDocument | null;
6
7
  shouldSkipHookDueToTrust(hook?: unknown): boolean;
7
8
  mergedDisableAllHooks?(): boolean | undefined;
9
+ enabledPluginHooks?(): PluginHooksReading;
10
+ }
11
+ export type PluginHookOptionValue = string | number | boolean;
12
+ export interface PluginHookSensitiveOption {
13
+ readonly sensitive: true;
14
+ }
15
+ export interface PluginHookPlainOption {
16
+ readonly sensitive: false;
17
+ readonly value?: PluginHookOptionValue;
18
+ }
19
+ export type PluginHookOption = PluginHookSensitiveOption | PluginHookPlainOption;
20
+ export interface PluginHookSource {
21
+ readonly id: string;
22
+ readonly name: string;
23
+ readonly root: string;
24
+ readonly dataDir: string;
25
+ readonly managed: boolean;
26
+ readonly options?: Readonly<Record<string, PluginHookOption>>;
27
+ readonly hooks: WireHooksConfig;
28
+ }
29
+ export interface PluginHooksReading {
30
+ readonly plugins: readonly PluginHookSource[];
31
+ readonly suppressedByStartupMode?: boolean;
8
32
  }
9
33
  export interface FsPort {
10
34
  getScratchpadDir(): string | undefined;
package/dist/index.d.ts CHANGED
@@ -56,6 +56,8 @@ export * from './modelCapabilityProbe.js';
56
56
  export * from './classifierStatus.js';
57
57
  export * from './engineToolLabelStore.js';
58
58
  export * from './fleetTaskDesc.js';
59
+ export { displayUntrusted } from './displayUntrusted.js';
60
+ export type { DisplayUntrustedOptions, DisplayMark } from './displayUntrusted.js';
59
61
  export type * from './types/engineState.js';
60
62
  export * from './hostEnv.js';
61
63
  export * from './unrefTimer.js';
@@ -135,6 +137,7 @@ export * from './detachWire.js';
135
137
  export * from './workflowMonitor.js';
136
138
  export * from './workflowClient.js';
137
139
  export * from './hooksWireCaps.js';
140
+ export { ENGINE_FIRED_HOOK_EVENTS, PLUGIN_HOOK_DISPOSITIONS, PLUGIN_HOOK_REASONS, PLUGIN_HOOK_EXCLUSION_REASONS, SETTINGS_HOOK_DROP_REASONS, isEngineFiredHookEvent, pluginHookVerdictOf, pluginHookReasonText, settingsHookDropText, pluginHookExecutorLabel, hooksNotRunNotice, type EngineFiredHookEvent, type PluginHookDisposition, type PluginHookReason, type PluginHookExclusionReason, type SettingsHookDropReason, type HookSettingsSource, type PluginHookKey, type PluginHookVerdict, type PluginHookExclusion, type SettingsHookDrop, type HooksWireEngineFacts, type PluginHookReaderState, type HooksWirePlan, type HooksWirePlanOptions, } from './pluginHooksWire.js';
138
141
  export * from './liveInitToolFace.js';
139
142
  export * from './printInitToolFace.js';
140
143
  export * from './model/providerPresets.js';
@@ -160,6 +163,7 @@ export * from './hitl/resumeRunningCard.js';
160
163
  export * from './hitl/persistedRulesWire.js';
161
164
  export * from './hitl/localAllowRule.js';
162
165
  export * from './hitl/sessionPolicyWire.js';
166
+ export * from './hitl/sessionPolicyDeliverable.js';
163
167
  export * from './hitl/approvalsFeed.js';
164
168
  export * from './hitl/livePendingAsk.js';
165
169
  export * from './hitl/crashConverged.js';
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ export * from './modelCapabilityProbe.js';
56
56
  export * from './classifierStatus.js';
57
57
  export * from './engineToolLabelStore.js';
58
58
  export * from './fleetTaskDesc.js';
59
+ export { displayUntrusted } from './displayUntrusted.js';
59
60
  export * from './hostEnv.js';
60
61
  export * from './unrefTimer.js';
61
62
  export * from './controlRouter.js';
@@ -133,6 +134,7 @@ export * from './detachWire.js';
133
134
  export * from './workflowMonitor.js';
134
135
  export * from './workflowClient.js';
135
136
  export * from './hooksWireCaps.js';
137
+ export { ENGINE_FIRED_HOOK_EVENTS, PLUGIN_HOOK_DISPOSITIONS, PLUGIN_HOOK_REASONS, PLUGIN_HOOK_EXCLUSION_REASONS, SETTINGS_HOOK_DROP_REASONS, isEngineFiredHookEvent, pluginHookVerdictOf, pluginHookReasonText, settingsHookDropText, pluginHookExecutorLabel, hooksNotRunNotice, } from './pluginHooksWire.js';
136
138
  export * from './liveInitToolFace.js';
137
139
  export * from './printInitToolFace.js';
138
140
  export * from './model/providerPresets.js';
@@ -158,6 +160,7 @@ export * from './hitl/resumeRunningCard.js';
158
160
  export * from './hitl/persistedRulesWire.js';
159
161
  export * from './hitl/localAllowRule.js';
160
162
  export * from './hitl/sessionPolicyWire.js';
163
+ export * from './hitl/sessionPolicyDeliverable.js';
161
164
  export * from './hitl/approvalsFeed.js';
162
165
  export * from './hitl/livePendingAsk.js';
163
166
  export * from './hitl/crashConverged.js';
@@ -1,3 +1,4 @@
1
+ import { applyDisplayFace, LEGACY_NAME_FACE } from './displayUntrusted.js';
1
2
  const WIRE_KEY_FROM = 'from';
2
3
  const WIRE_KEY_BODY = 'body';
3
4
  const WIRE_KEY_KIND = 'kind';
@@ -124,7 +125,7 @@ function neutralizeAuthorityTags(text) {
124
125
  function cleanName(v) {
125
126
  if (typeof v !== 'string')
126
127
  return undefined;
127
- const t = v.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, ' ').replace(/\s+/g, ' ').trim();
128
+ const t = applyDisplayFace(v, LEGACY_NAME_FACE);
128
129
  return t.length > 0 ? t : undefined;
129
130
  }
130
131
  function escapeEnvelopeTag(tag, text) {
@@ -0,0 +1,92 @@
1
+ import type { WireHooksConfig } from './finalVerifyWire.js';
2
+ import type { PluginHookSource } from './host.js';
3
+ export declare const ENGINE_FIRED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "StopFailure", "UserPromptSubmit"];
4
+ export type EngineFiredHookEvent = (typeof ENGINE_FIRED_HOOK_EVENTS)[number];
5
+ export declare const PLUGIN_HOOK_DISPOSITIONS: readonly ["engine", "shell", "not_run"];
6
+ export type PluginHookDisposition = (typeof PLUGIN_HOOK_DISPOSITIONS)[number];
7
+ 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"];
8
+ export type PluginHookReason = (typeof PLUGIN_HOOK_REASONS)[number];
9
+ export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrusted", "governance_unreadable", "policy_disable_all", "startup_mode", "managed_plugins_only"];
10
+ export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
11
+ export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported"];
12
+ export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
13
+ export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings';
14
+ export interface PluginHookKey {
15
+ readonly pluginId: string;
16
+ readonly pluginName: string;
17
+ readonly event: string;
18
+ readonly groupIndex: number;
19
+ readonly hookIndex: number;
20
+ }
21
+ export interface PluginHookVerdict extends PluginHookKey {
22
+ readonly disposition: PluginHookDisposition;
23
+ readonly reason: PluginHookReason;
24
+ }
25
+ export interface PluginHookExclusion extends PluginHookKey {
26
+ readonly reason: PluginHookExclusionReason;
27
+ }
28
+ export interface SettingsHookDrop {
29
+ readonly source: HookSettingsSource;
30
+ readonly event: string;
31
+ readonly groupIndex: number;
32
+ readonly hookIndex: number;
33
+ readonly reason: SettingsHookDropReason;
34
+ readonly notRun: boolean;
35
+ }
36
+ export interface HooksWireEngineFacts {
37
+ readonly ownedByThisShell: boolean | null;
38
+ readonly pluginHooksCapable: boolean | null;
39
+ }
40
+ export type PluginHookReaderState = 'present' | 'absent' | 'failed';
41
+ export interface HooksWirePlan {
42
+ readonly engine: HooksWireEngineFacts;
43
+ readonly pluginReader: PluginHookReaderState;
44
+ readonly verdicts: readonly PluginHookVerdict[];
45
+ readonly excluded: readonly PluginHookExclusion[];
46
+ readonly settingsDropped: readonly SettingsHookDrop[];
47
+ readonly wire: WireHooksConfig | undefined;
48
+ }
49
+ export interface HooksWirePlanOptions {
50
+ readonly baseUrl?: string;
51
+ readonly engineOwnedByThisShell?: boolean;
52
+ readonly shellHookEvents?: readonly string[];
53
+ }
54
+ export declare const ENGINE_HOOK_LIMITS: {
55
+ readonly maxGroupsPerEvent: 16;
56
+ readonly maxEntriesPerEvent: 32;
57
+ readonly maxCommandChars: 8192;
58
+ };
59
+ type WireHookGroup = NonNullable<WireHooksConfig[string]>[number];
60
+ export declare function isEngineFiredHookEvent(event: string): boolean;
61
+ export interface EventOccupancy {
62
+ readonly groups: number;
63
+ readonly entries: number;
64
+ }
65
+ export declare function occupancyOf(wire: WireHooksConfig): Map<string, EventOccupancy>;
66
+ export interface PluginHookDecisionInput {
67
+ readonly plugins: readonly PluginHookSource[];
68
+ readonly exclusion: PluginHookExclusionReason | undefined;
69
+ readonly managedOnly: boolean;
70
+ readonly engine: HooksWireEngineFacts;
71
+ readonly shellHookEvents: ReadonlySet<string>;
72
+ readonly occupied: ReadonlyMap<string, EventOccupancy>;
73
+ }
74
+ export interface PluginHookDecision {
75
+ readonly verdicts: readonly PluginHookVerdict[];
76
+ readonly excluded: readonly PluginHookExclusion[];
77
+ readonly projected: ReadonlyMap<string, readonly WireHookGroup[]>;
78
+ readonly unreadable: number;
79
+ }
80
+ export declare function decidePluginHooks(input: PluginHookDecisionInput): PluginHookDecision;
81
+ export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, hostFires: boolean, failLoudMcpTool: boolean): SettingsHookFilterResult;
82
+ export interface SettingsHookFilterResult {
83
+ readonly groups: WireHookGroup[];
84
+ readonly drops: SettingsHookDrop[];
85
+ readonly stripped: number;
86
+ }
87
+ export declare function pluginHookVerdictOf(plan: HooksWirePlan, pluginId: string, event: string, groupIndex: number, hookIndex: number): PluginHookVerdict | undefined;
88
+ export declare function pluginHookReasonText(reason: PluginHookReason): string;
89
+ export declare function settingsHookDropText(reason: SettingsHookDropReason): string;
90
+ export declare function pluginHookExecutorLabel(v: PluginHookVerdict): string;
91
+ export declare function hooksNotRunNotice(plan: HooksWirePlan): readonly string[];
92
+ export {};