@sema-agent/client-core 0.87.0 → 0.89.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.
- package/CHANGELOG.md +177 -0
- package/README.md +20 -15
- package/dist/adapter/activeRunSelfHeal.js +4 -2
- package/dist/adapter/downstream/eventToSdkMessage.js +17 -2
- package/dist/adapter/downstream/wiringManifestView.d.ts +11 -0
- package/dist/adapter/downstream/wiringManifestView.js +28 -0
- package/dist/controlRouter.d.ts +7 -1
- package/dist/controlRouter.js +43 -1
- package/dist/disableSkillsWire.d.ts +17 -0
- package/dist/disableSkillsWire.js +52 -0
- package/dist/displayUntrusted.d.ts +4 -0
- package/dist/displayUntrusted.js +1742 -591
- package/dist/engineErrorCodes.d.ts +2 -0
- package/dist/engineErrorCodes.js +1 -0
- package/dist/engineNoticeCodes.d.ts +44 -1
- package/dist/engineNoticeCodes.js +211 -39
- package/dist/engineWireFor.d.ts +1 -0
- package/dist/engineWireFor.js +1 -1
- package/dist/gateOutcome.d.ts +4 -0
- package/dist/gateOutcome.js +5 -0
- package/dist/generated/engineFactTables.d.ts +1 -0
- package/dist/generated/engineFactTables.js +3 -0
- package/dist/generated/engineNoticeTables.d.ts +3 -0
- package/dist/generated/engineNoticeTables.js +110 -0
- package/dist/hitl/crashConverged.d.ts +1 -1
- package/dist/hitl/crashConverged.js +6 -1
- package/dist/hitl/planReviewWire.js +30 -10
- package/dist/hitl/toolApprovalWire.d.ts +6 -2
- package/dist/hitl/toolApprovalWire.js +30 -0
- package/dist/hooksWireCaps.js +80 -12
- package/dist/index.d.ts +12 -3
- package/dist/index.js +8 -3
- package/dist/pluginHooksWire.d.ts +46 -4
- package/dist/pluginHooksWire.js +277 -24
- package/dist/promptDelivery.d.ts +20 -0
- package/dist/promptDelivery.js +153 -0
- package/dist/providerRejection.d.ts +6 -0
- package/dist/providerRejection.js +52 -0
- package/dist/registryConflict.d.ts +3 -0
- package/dist/registryConflict.js +44 -0
- package/dist/request/taskRequest.d.ts +3 -0
- package/dist/request/taskRequest.js +64 -8
- package/dist/resumeRefusalCopy.d.ts +7 -0
- package/dist/resumeRefusalCopy.js +45 -1
- package/dist/runTerminal.d.ts +6 -0
- package/dist/runTerminal.js +23 -0
- package/dist/seam.d.ts +4 -0
- package/dist/seatContract.d.ts +9 -1
- package/dist/seatContract.js +10 -0
- package/dist/sessionMemoryErase.d.ts +76 -0
- package/dist/sessionMemoryErase.js +432 -0
- package/dist/subagent/engineSubagentResume.js +2 -1
- package/dist/subagent/engineSubagentSteer.d.ts +8 -1
- package/dist/subagent/engineSubagentSteer.js +26 -3
- package/docs/INTEGRATION-CLIENTS.md +767 -14
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export * from './deviceExecutorManagementCapability.js';
|
|
|
35
35
|
export * from './mcpReconnect.js';
|
|
36
36
|
export * from './leaderConflict.js';
|
|
37
37
|
export * from './runTerminal.js';
|
|
38
|
+
export { providerRejectionResend } from './providerRejection.js';
|
|
38
39
|
export * from './runCancelContext.js';
|
|
39
40
|
export * from './argvFlagValue.js';
|
|
40
41
|
export * from './readFacePosture.js';
|
|
@@ -59,7 +60,7 @@ export * from './fleetTaskDesc.js';
|
|
|
59
60
|
export { displayUntrusted, displayUntrustedMarks } from './displayUntrusted.js';
|
|
60
61
|
export * from './hostEnv.js';
|
|
61
62
|
export * from './unrefTimer.js';
|
|
62
|
-
export
|
|
63
|
+
export { ControlRouter, ControlSafetyError, errCodes, } from './controlRouter.js';
|
|
63
64
|
export * from './sseIdleTriage.js';
|
|
64
65
|
export * from './engineWireSdk.js';
|
|
65
66
|
export * from './classifierVerdictWire.js';
|
|
@@ -102,6 +103,7 @@ export * from './adapter/downstream/terminalToSdkResult.js';
|
|
|
102
103
|
export * from './adapter/runStream.js';
|
|
103
104
|
export * from './adapter/activeRunSelfHeal.js';
|
|
104
105
|
export * from './request/taskRequest.js';
|
|
106
|
+
export { disableSkillsDeclaration } from './disableSkillsWire.js';
|
|
105
107
|
export * from './clientSlice.js';
|
|
106
108
|
export * from './host.js';
|
|
107
109
|
export { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
@@ -134,6 +136,8 @@ export * from './wireErrorTriage.js';
|
|
|
134
136
|
export * from './corruptSession.js';
|
|
135
137
|
export * from './corruptSessionCopy.js';
|
|
136
138
|
export { classifyToolHistoryMismatch, resolveToolHistoryRewindTarget } from './toolHistoryMismatch.js';
|
|
139
|
+
export { classifyRegistryConflict, isScopeGoneError } from './registryConflict.js';
|
|
140
|
+
export { createPromptDeliveryLedger, promptKnownUndelivered, withoutUndeliveredPrompts } from './promptDelivery.js';
|
|
137
141
|
export * from './resumeRefusalCopy.js';
|
|
138
142
|
export * from './wireRefusalCopy.js';
|
|
139
143
|
export * from './inheritEnvWire.js';
|
|
@@ -143,7 +147,7 @@ export * from './detachWire.js';
|
|
|
143
147
|
export * from './workflowMonitor.js';
|
|
144
148
|
export * from './workflowClient.js';
|
|
145
149
|
export * from './hooksWireCaps.js';
|
|
146
|
-
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';
|
|
150
|
+
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, settingsHooksKeptLocally, } from './pluginHooksWire.js';
|
|
147
151
|
export * from './liveInitToolFace.js';
|
|
148
152
|
export * from './printInitToolFace.js';
|
|
149
153
|
export * from './model/providerPresets.js';
|
|
@@ -179,9 +183,10 @@ export * from './request/printNotification.js';
|
|
|
179
183
|
export * from './seatContract.js';
|
|
180
184
|
export * from './agentSession/contract.js';
|
|
181
185
|
export * from './agentSession/backgroundView.js';
|
|
182
|
-
export { READ_CREDENTIAL_NOT_ACCEPTED_NOTICE } from './readCredentialRefusal.js';
|
|
186
|
+
export { READ_CREDENTIAL_NOT_ACCEPTED_NOTICE, isReadCredentialRefusal } from './readCredentialRefusal.js';
|
|
183
187
|
export { readEngineAgentRegistry, isEngineAgentRegistryListingSuperseded } from './agentSession/engineAgentRegistryRead.js';
|
|
184
188
|
export { stopEngineSessionBackground, engineSessionBackgroundReceiptReadingOf, ENGINE_SESSION_BACKGROUND_RECEIPT_STATES, } from './agentSession/engineSessionBackgroundStop.js';
|
|
189
|
+
export { eraseSessionMemory, sessionMemoryEraseOutcome, SESSION_MEMORY_ERASE_REFUSAL_REASONS, SESSION_MEMORY_ERASE_UNCONFIRMED_WHYS, SESSION_MEMORY_ERASE_RETRIES, } from './sessionMemoryErase.js';
|
|
185
190
|
export * from './model/catalog.js';
|
|
186
191
|
export * from './model/catalogLoader.js';
|
|
187
192
|
export * from './model/providerAuth.js';
|
|
@@ -4,9 +4,9 @@ export declare const ENGINE_FIRED_HOOK_EVENTS: readonly ["PreToolUse", "PostTool
|
|
|
4
4
|
export type EngineFiredHookEvent = (typeof ENGINE_FIRED_HOOK_EVENTS)[number];
|
|
5
5
|
export declare const PLUGIN_HOOK_DISPOSITIONS: readonly ["engine", "shell", "not_run"];
|
|
6
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"];
|
|
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", "engine_hooks_off", "engine_hooks_unknown", "engine_type_skipped", "engine_llm_limit", "engine_unconfirmed", "engine_elsewhere"];
|
|
8
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"];
|
|
9
|
+
export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrusted", "governance_unreadable", "policy_disable_all", "startup_mode", "managed_plugins_only", "prompt_not_user_authored"];
|
|
10
10
|
export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
|
|
11
11
|
export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported"];
|
|
12
12
|
export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
|
|
@@ -33,9 +33,25 @@ export interface SettingsHookDrop {
|
|
|
33
33
|
readonly reason: SettingsHookDropReason;
|
|
34
34
|
readonly notRun: boolean;
|
|
35
35
|
}
|
|
36
|
+
export interface SettingsHookVerdict {
|
|
37
|
+
readonly source: HookSettingsSource;
|
|
38
|
+
readonly event: string;
|
|
39
|
+
readonly groupIndex: number;
|
|
40
|
+
readonly hookIndex: number;
|
|
41
|
+
readonly disposition: PluginHookDisposition;
|
|
42
|
+
readonly reason: PluginHookReason;
|
|
43
|
+
}
|
|
44
|
+
export interface SettingsHookExclusion {
|
|
45
|
+
readonly source: HookSettingsSource;
|
|
46
|
+
readonly event: string;
|
|
47
|
+
readonly groupIndex: number;
|
|
48
|
+
readonly hookIndex: number;
|
|
49
|
+
readonly reason: PluginHookExclusionReason;
|
|
50
|
+
}
|
|
36
51
|
export interface HooksWireEngineFacts {
|
|
37
52
|
readonly ownedByThisShell: boolean | null;
|
|
38
53
|
readonly pluginHooksCapable: boolean | null;
|
|
54
|
+
readonly hooksCapable: boolean | null;
|
|
39
55
|
}
|
|
40
56
|
export type PluginHookReaderState = 'present' | 'absent' | 'failed';
|
|
41
57
|
export interface HooksWirePlan {
|
|
@@ -44,19 +60,34 @@ export interface HooksWirePlan {
|
|
|
44
60
|
readonly verdicts: readonly PluginHookVerdict[];
|
|
45
61
|
readonly excluded: readonly PluginHookExclusion[];
|
|
46
62
|
readonly settingsDropped: readonly SettingsHookDrop[];
|
|
63
|
+
readonly settingsVerdicts: readonly SettingsHookVerdict[];
|
|
64
|
+
readonly settingsExcluded: readonly SettingsHookExclusion[];
|
|
47
65
|
readonly wire: WireHooksConfig | undefined;
|
|
48
66
|
}
|
|
49
67
|
export interface HooksWirePlanOptions {
|
|
50
68
|
readonly baseUrl?: string;
|
|
51
69
|
readonly engineOwnedByThisShell?: boolean;
|
|
52
70
|
readonly shellHookEvents?: readonly string[];
|
|
71
|
+
readonly shellHookTypesByEvent?: Readonly<Record<string, readonly string[]>>;
|
|
72
|
+
readonly promptAuthoredByUser?: boolean;
|
|
53
73
|
}
|
|
54
74
|
export declare const ENGINE_HOOK_LIMITS: {
|
|
55
75
|
readonly maxGroupsPerEvent: 16;
|
|
56
76
|
readonly maxEntriesPerEvent: 32;
|
|
57
77
|
readonly maxCommandChars: 8192;
|
|
78
|
+
readonly maxLlmEntriesPerEvent: 4;
|
|
79
|
+
};
|
|
80
|
+
export type ShellRunsHook = (event: string, type: string | undefined) => boolean;
|
|
81
|
+
type HookExecutor = {
|
|
82
|
+
readonly kind: 'excluded';
|
|
83
|
+
readonly reason: 'prompt_not_user_authored';
|
|
84
|
+
} | {
|
|
85
|
+
readonly kind: 'judged';
|
|
86
|
+
readonly disposition: PluginHookDisposition;
|
|
87
|
+
readonly reason: PluginHookReason;
|
|
58
88
|
};
|
|
59
89
|
type WireHookGroup = NonNullable<WireHooksConfig[string]>[number];
|
|
90
|
+
type WireHookEntry = WireHookGroup['hooks'][number];
|
|
60
91
|
export declare function isEngineFiredHookEvent(event: string): boolean;
|
|
61
92
|
export interface EventOccupancy {
|
|
62
93
|
readonly groups: number;
|
|
@@ -68,7 +99,8 @@ export interface PluginHookDecisionInput {
|
|
|
68
99
|
readonly exclusion: PluginHookExclusionReason | undefined;
|
|
69
100
|
readonly managedOnly: boolean;
|
|
70
101
|
readonly engine: HooksWireEngineFacts;
|
|
71
|
-
readonly
|
|
102
|
+
readonly shellRuns: ShellRunsHook;
|
|
103
|
+
readonly promptAuthoredByUser: boolean;
|
|
72
104
|
readonly occupied: ReadonlyMap<string, EventOccupancy>;
|
|
73
105
|
}
|
|
74
106
|
export interface PluginHookDecision {
|
|
@@ -78,12 +110,16 @@ export interface PluginHookDecision {
|
|
|
78
110
|
readonly unreadable: number;
|
|
79
111
|
}
|
|
80
112
|
export declare function decidePluginHooks(input: PluginHookDecisionInput): PluginHookDecision;
|
|
81
|
-
export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean,
|
|
113
|
+
export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, failLoudMcpTool: boolean, judge?: SettingsHookJudge): SettingsHookFilterResult;
|
|
82
114
|
export interface SettingsHookFilterResult {
|
|
83
115
|
readonly groups: WireHookGroup[];
|
|
84
116
|
readonly drops: SettingsHookDrop[];
|
|
85
117
|
readonly stripped: number;
|
|
118
|
+
readonly verdicts: SettingsHookVerdict[];
|
|
119
|
+
readonly excluded: SettingsHookExclusion[];
|
|
86
120
|
}
|
|
121
|
+
export type SettingsHookJudge = (event: string, group: WireHookGroup, entry: WireHookEntry) => HookExecutor;
|
|
122
|
+
export declare function createSettingsHookJudge(engine: HooksWireEngineFacts, shellRuns: ShellRunsHook, promptAuthoredByUser: boolean): SettingsHookJudge;
|
|
87
123
|
export interface SourcedSettingsHookGroup {
|
|
88
124
|
readonly source: HookSettingsSource;
|
|
89
125
|
readonly group: WireHookGroup;
|
|
@@ -94,6 +130,12 @@ export interface SettingsHookDedupResult {
|
|
|
94
130
|
readonly removed: number;
|
|
95
131
|
}
|
|
96
132
|
export declare function pluginHookVerdictOf(plan: HooksWirePlan, pluginId: string, event: string, groupIndex: number, hookIndex: number): PluginHookVerdict | undefined;
|
|
133
|
+
export declare function settingsHooksKeptLocally<T>(plan: HooksWirePlan, event: string, group: {
|
|
134
|
+
readonly matcher?: string;
|
|
135
|
+
readonly hooks: readonly T[];
|
|
136
|
+
}, opts?: {
|
|
137
|
+
readonly engineCompaction?: 'performed' | 'not_performed';
|
|
138
|
+
}): readonly T[];
|
|
97
139
|
export declare function pluginHookReasonText(reason: PluginHookReason): string;
|
|
98
140
|
export declare function settingsHookDropText(reason: SettingsHookDropReason): string;
|
|
99
141
|
export declare function pluginHookExecutorLabel(v: PluginHookVerdict): string;
|
package/dist/pluginHooksWire.js
CHANGED
|
@@ -25,6 +25,12 @@ export const PLUGIN_HOOK_REASONS = [
|
|
|
25
25
|
'shell_form_user_config',
|
|
26
26
|
'user_config_unset',
|
|
27
27
|
'malformed_entry',
|
|
28
|
+
'engine_hooks_off',
|
|
29
|
+
'engine_hooks_unknown',
|
|
30
|
+
'engine_type_skipped',
|
|
31
|
+
'engine_llm_limit',
|
|
32
|
+
'engine_unconfirmed',
|
|
33
|
+
'engine_elsewhere',
|
|
28
34
|
];
|
|
29
35
|
export const PLUGIN_HOOK_EXCLUSION_REASONS = [
|
|
30
36
|
'workspace_untrusted',
|
|
@@ -32,15 +38,69 @@ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
|
|
|
32
38
|
'policy_disable_all',
|
|
33
39
|
'startup_mode',
|
|
34
40
|
'managed_plugins_only',
|
|
41
|
+
'prompt_not_user_authored',
|
|
35
42
|
];
|
|
36
43
|
export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported'];
|
|
37
|
-
export const ENGINE_HOOK_LIMITS = { maxGroupsPerEvent: 16, maxEntriesPerEvent: 32, maxCommandChars: 8192 };
|
|
44
|
+
export const ENGINE_HOOK_LIMITS = { maxGroupsPerEvent: 16, maxEntriesPerEvent: 32, maxCommandChars: 8192, maxLlmEntriesPerEvent: 4 };
|
|
45
|
+
const ENGINE_HOOK_TYPE_RUNS = new Set(['command', 'http', 'prompt', 'agent']);
|
|
46
|
+
const COMPACTION_EVENTS = new Set(['PreCompact', 'PostCompact']);
|
|
47
|
+
const judged = (disposition, reason) => ({ kind: 'judged', disposition, reason });
|
|
48
|
+
function hookExecutorOf(x) {
|
|
49
|
+
if (x.event === 'UserPromptSubmit' && !x.promptAuthoredByUser)
|
|
50
|
+
return { kind: 'excluded', reason: 'prompt_not_user_authored' };
|
|
51
|
+
if (x.source === 'settings') {
|
|
52
|
+
const why = x.powershell
|
|
53
|
+
? { reason: 'engine_type_skipped', certain: true }
|
|
54
|
+
: x.engine.hooksCapable === false
|
|
55
|
+
? { reason: 'engine_hooks_off', certain: true }
|
|
56
|
+
: x.engine.hooksCapable === null
|
|
57
|
+
? { reason: 'engine_hooks_unknown', certain: false }
|
|
58
|
+
: x.llmPosition > ENGINE_HOOK_LIMITS.maxLlmEntriesPerEvent
|
|
59
|
+
? { reason: 'engine_llm_limit', certain: false }
|
|
60
|
+
: undefined;
|
|
61
|
+
const compaction = COMPACTION_EVENTS.has(x.event);
|
|
62
|
+
if (why === undefined) {
|
|
63
|
+
if (!x.shellRuns || x.engine.ownedByThisShell === true)
|
|
64
|
+
return judged('engine', 'projected');
|
|
65
|
+
return compaction ? judged('engine', 'engine_unconfirmed') : judged('shell', 'engine_elsewhere');
|
|
66
|
+
}
|
|
67
|
+
if (x.shellRuns && (why.certain || !compaction || why.reason === 'engine_llm_limit'))
|
|
68
|
+
return judged('shell', why.reason);
|
|
69
|
+
return why.certain ? judged('not_run', why.reason) : judged('engine', 'engine_unconfirmed');
|
|
70
|
+
}
|
|
71
|
+
const why = x.engine.ownedByThisShell === null
|
|
72
|
+
? 'engine_locality_unknown'
|
|
73
|
+
: !x.engine.ownedByThisShell
|
|
74
|
+
? 'engine_not_local'
|
|
75
|
+
: x.engine.pluginHooksCapable === null
|
|
76
|
+
? 'engine_capability_unknown'
|
|
77
|
+
: !x.engine.pluginHooksCapable
|
|
78
|
+
? 'engine_capability_absent'
|
|
79
|
+
: x.engine.hooksCapable === false
|
|
80
|
+
? 'engine_hooks_off'
|
|
81
|
+
: x.engine.hooksCapable === null
|
|
82
|
+
? 'engine_hooks_unknown'
|
|
83
|
+
: x.pluginBlock === 'not_command'
|
|
84
|
+
? 'not_command'
|
|
85
|
+
: x.powershell
|
|
86
|
+
? 'engine_type_skipped'
|
|
87
|
+
: x.pluginBlock;
|
|
88
|
+
if (why === undefined)
|
|
89
|
+
return judged('engine', 'projected');
|
|
90
|
+
return judged(x.shellRuns ? 'shell' : 'not_run', why);
|
|
91
|
+
}
|
|
38
92
|
const ENGINE_EVENTS = new Set(ENGINE_FIRED_HOOK_EVENTS);
|
|
39
93
|
const USER_CONFIG_REF_ONE = /\$\{user_config\.[^}]+\}/;
|
|
40
94
|
const USER_CONFIG_REF_ALL = /\$\{user_config\.([^}]+)\}/g;
|
|
41
95
|
function isRecord(v) {
|
|
42
96
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
43
97
|
}
|
|
98
|
+
function ownField(o, key) {
|
|
99
|
+
return Object.hasOwn(o, key) ? o[key] : undefined;
|
|
100
|
+
}
|
|
101
|
+
function ownView(v) {
|
|
102
|
+
return isRecord(v) ? Object.assign(Object.create(null), Object.fromEntries(Object.entries(v))) : v;
|
|
103
|
+
}
|
|
44
104
|
function nonEmptyString(v) {
|
|
45
105
|
return typeof v === 'string' && v.length > 0;
|
|
46
106
|
}
|
|
@@ -123,7 +183,7 @@ export function decidePluginHooks(input) {
|
|
|
123
183
|
const excluded = [];
|
|
124
184
|
const candidates = new Map();
|
|
125
185
|
let unreadable = 0;
|
|
126
|
-
const fallback = (event, reason) => input.
|
|
186
|
+
const fallback = (event, type, reason) => input.shellRuns(event, typeof type === 'string' ? type : undefined) ? { disposition: 'shell', reason } : { disposition: 'not_run', reason };
|
|
127
187
|
input.plugins.forEach((p, pluginIndex) => {
|
|
128
188
|
if (!isRecord(p) || !isRecord(p.hooks)) {
|
|
129
189
|
unreadable++;
|
|
@@ -155,6 +215,7 @@ export function decidePluginHooks(input) {
|
|
|
155
215
|
excluded.push({ ...key, reason: 'managed_plugins_only' });
|
|
156
216
|
return;
|
|
157
217
|
}
|
|
218
|
+
const entryType = isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined;
|
|
158
219
|
const shape = sourceOk ? classifyEntry(entry, src.options) : { kind: 'malformed' };
|
|
159
220
|
if (shape.kind === 'malformed') {
|
|
160
221
|
verdicts.push({ ...key, disposition: 'not_run', reason: 'malformed_entry' });
|
|
@@ -171,27 +232,28 @@ export function decidePluginHooks(input) {
|
|
|
171
232
|
if (!ENGINE_EVENTS.has(event)) {
|
|
172
233
|
verdicts.push({
|
|
173
234
|
...key,
|
|
174
|
-
...(input.
|
|
235
|
+
...(input.shellRuns(event, entryType)
|
|
175
236
|
? { disposition: 'shell', reason: 'shell_event' }
|
|
176
237
|
: { disposition: 'not_run', reason: 'no_executor' }),
|
|
177
238
|
});
|
|
178
239
|
return;
|
|
179
240
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
241
|
+
const ex = hookExecutorOf({
|
|
242
|
+
source: 'plugin',
|
|
243
|
+
event,
|
|
244
|
+
powershell: shape.kind !== 'other' && isRecord(entry) && entry.shell === 'powershell',
|
|
245
|
+
llmPosition: 0,
|
|
246
|
+
engine: input.engine,
|
|
247
|
+
shellRuns: input.shellRuns(event, entryType),
|
|
248
|
+
promptAuthoredByUser: input.promptAuthoredByUser,
|
|
249
|
+
...(shape.kind === 'other' ? { pluginBlock: 'not_command' } : declaresSensitive(src.options) ? { pluginBlock: 'sensitive_options' } : {}),
|
|
250
|
+
});
|
|
251
|
+
if (ex.kind === 'excluded') {
|
|
252
|
+
excluded.push({ ...key, reason: ex.reason });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (ex.disposition !== 'engine') {
|
|
256
|
+
verdicts.push({ ...key, disposition: ex.disposition, reason: ex.reason });
|
|
195
257
|
return;
|
|
196
258
|
}
|
|
197
259
|
const plugin = { name: src.name, root: src.root, dataDir: src.dataDir };
|
|
@@ -218,7 +280,7 @@ export function decidePluginHooks(input) {
|
|
|
218
280
|
const cmd = c.wireEntry.command;
|
|
219
281
|
if (typeof cmd === 'string' && cmd.length <= ENGINE_HOOK_LIMITS.maxCommandChars)
|
|
220
282
|
return true;
|
|
221
|
-
Object.assign(c.verdict, fallback(event, 'engine_limit'));
|
|
283
|
+
Object.assign(c.verdict, fallback(event, c.wireEntry.type, 'engine_limit'));
|
|
222
284
|
return false;
|
|
223
285
|
});
|
|
224
286
|
if (fits.length === 0)
|
|
@@ -233,7 +295,7 @@ export function decidePluginHooks(input) {
|
|
|
233
295
|
if (occ.groups + groupKeys.length > ENGINE_HOOK_LIMITS.maxGroupsPerEvent ||
|
|
234
296
|
occ.entries + fits.length > ENGINE_HOOK_LIMITS.maxEntriesPerEvent) {
|
|
235
297
|
for (const c of fits)
|
|
236
|
-
Object.assign(c.verdict, fallback(event, 'engine_limit'));
|
|
298
|
+
Object.assign(c.verdict, fallback(event, c.wireEntry.type, 'engine_limit'));
|
|
237
299
|
continue;
|
|
238
300
|
}
|
|
239
301
|
const groupsOut = [];
|
|
@@ -259,12 +321,13 @@ function carriesArgs(entry) {
|
|
|
259
321
|
}
|
|
260
322
|
return true;
|
|
261
323
|
}
|
|
262
|
-
export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
324
|
+
export function filterSettingsHookGroups(source, event, groups, execFormCapable, shellRunsType, failLoudMcpTool, judge) {
|
|
263
325
|
const drops = [];
|
|
326
|
+
const verdicts = [];
|
|
327
|
+
const excluded = [];
|
|
264
328
|
const out = [];
|
|
265
329
|
let stripped = 0;
|
|
266
330
|
const engineEvent = ENGINE_EVENTS.has(event);
|
|
267
|
-
const notRun = engineEvent && !hostFires;
|
|
268
331
|
const keepMcpTool = failLoudMcpTool && engineEvent;
|
|
269
332
|
groups.forEach((group, groupIndex) => {
|
|
270
333
|
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
@@ -284,10 +347,23 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
|
284
347
|
? 'exec_form_unsupported'
|
|
285
348
|
: undefined;
|
|
286
349
|
if (reason !== undefined) {
|
|
287
|
-
drops.push({ source, event, groupIndex, hookIndex, reason, notRun });
|
|
350
|
+
drops.push({ source, event, groupIndex, hookIndex, reason, notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined) });
|
|
288
351
|
changed = true;
|
|
289
352
|
return;
|
|
290
353
|
}
|
|
354
|
+
if (judge !== undefined && engineEvent && isRecord(entry) && judgeableSettingsEntry(entry)) {
|
|
355
|
+
const ex = judge(event, group, entry);
|
|
356
|
+
if (ex.kind === 'excluded') {
|
|
357
|
+
excluded.push({ source, event, groupIndex, hookIndex, reason: ex.reason });
|
|
358
|
+
changed = true;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
verdicts.push({ source, event, groupIndex, hookIndex, disposition: ex.disposition, reason: ex.reason });
|
|
362
|
+
if (ex.disposition === 'shell') {
|
|
363
|
+
changed = true;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
291
367
|
if (isRecord(entry) && Object.hasOwn(entry, 'plugin')) {
|
|
292
368
|
const copy = { ...entry };
|
|
293
369
|
delete copy.plugin;
|
|
@@ -303,7 +379,51 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
|
303
379
|
else if (kept.length > 0)
|
|
304
380
|
out.push({ ...group, hooks: kept });
|
|
305
381
|
});
|
|
306
|
-
return { groups: out, drops, stripped };
|
|
382
|
+
return { groups: out, drops, stripped, verdicts, excluded };
|
|
383
|
+
}
|
|
384
|
+
function judgeableSettingsEntry(entry) {
|
|
385
|
+
const type = entry.type;
|
|
386
|
+
if (typeof type !== 'string' || !ENGINE_HOOK_TYPE_RUNS.has(type))
|
|
387
|
+
return false;
|
|
388
|
+
const field = type === 'command' ? entry.command : type === 'http' ? entry.url : entry.prompt;
|
|
389
|
+
return nonEmptyString(field) && !field.includes('\u0000');
|
|
390
|
+
}
|
|
391
|
+
export function createSettingsHookJudge(engine, shellRuns, promptAuthoredByUser) {
|
|
392
|
+
const llm = new Map();
|
|
393
|
+
const seen = new Map();
|
|
394
|
+
return (event, group, entry) => {
|
|
395
|
+
let dk;
|
|
396
|
+
const entryKey = settingsHookEntryKey(entry);
|
|
397
|
+
if (entryKey !== undefined && isRecord(group)) {
|
|
398
|
+
try {
|
|
399
|
+
const { hooks: _hooks, ...rest } = group;
|
|
400
|
+
void _hooks;
|
|
401
|
+
dk = `${event}\u0000${JSON.stringify(rest)}\u0000${entryKey}`;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
dk = undefined;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const prior = dk === undefined ? undefined : seen.get(dk);
|
|
408
|
+
if (prior !== undefined)
|
|
409
|
+
return prior;
|
|
410
|
+
const type = entry.type;
|
|
411
|
+
const isLlm = type === 'prompt' || type === 'agent';
|
|
412
|
+
const ex = hookExecutorOf({
|
|
413
|
+
source: 'settings',
|
|
414
|
+
event,
|
|
415
|
+
powershell: type === 'command' && entry.shell === 'powershell',
|
|
416
|
+
llmPosition: isLlm ? (llm.get(event) ?? 0) + 1 : 0,
|
|
417
|
+
engine,
|
|
418
|
+
shellRuns: shellRuns(event, typeof type === 'string' ? type : undefined),
|
|
419
|
+
promptAuthoredByUser,
|
|
420
|
+
});
|
|
421
|
+
if (isLlm && ex.kind === 'judged' && ex.disposition !== 'shell')
|
|
422
|
+
llm.set(event, (llm.get(event) ?? 0) + 1);
|
|
423
|
+
if (dk !== undefined)
|
|
424
|
+
seen.set(dk, ex);
|
|
425
|
+
return ex;
|
|
426
|
+
};
|
|
307
427
|
}
|
|
308
428
|
function settingsHookEntryKey(entry) {
|
|
309
429
|
if (!isRecord(entry) || typeof entry.type !== 'string')
|
|
@@ -427,6 +547,116 @@ export function dedupeSettingsHookGroups(groups) {
|
|
|
427
547
|
export function pluginHookVerdictOf(plan, pluginId, event, groupIndex, hookIndex) {
|
|
428
548
|
return plan.verdicts.find((v) => v.pluginId === pluginId && v.event === event && v.groupIndex === groupIndex && v.hookIndex === hookIndex);
|
|
429
549
|
}
|
|
550
|
+
function skipMatcherKey(matcher) {
|
|
551
|
+
return matcher === undefined || matcher === null || matcher === '' || matcher === '*' ? 'null' : JSON.stringify(matcher);
|
|
552
|
+
}
|
|
553
|
+
export function settingsHooksKeptLocally(plan, event, group, opts) {
|
|
554
|
+
let raw;
|
|
555
|
+
try {
|
|
556
|
+
raw = group?.hooks;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
raw = undefined;
|
|
560
|
+
}
|
|
561
|
+
const hooks = raw;
|
|
562
|
+
if (!Array.isArray(hooks) || hooks.length === 0 || !ENGINE_EVENTS.has(event))
|
|
563
|
+
return hooks;
|
|
564
|
+
try {
|
|
565
|
+
if (COMPACTION_EVENTS.has(event) && opts?.engineCompaction === 'not_performed')
|
|
566
|
+
return hooks;
|
|
567
|
+
const planRec = plan;
|
|
568
|
+
if (!isRecord(planRec))
|
|
569
|
+
return hooks;
|
|
570
|
+
const excludedList = Object.hasOwn(planRec, 'settingsExcluded') ? planRec.settingsExcluded : [];
|
|
571
|
+
const wire = ownField(planRec, 'wire');
|
|
572
|
+
if (!Array.isArray(excludedList) || (wire !== undefined && !isRecord(wire)))
|
|
573
|
+
return hooks;
|
|
574
|
+
if (excludedList.some((e) => settingsExclusionRowOf(e)?.event === event))
|
|
575
|
+
return [];
|
|
576
|
+
const sent = new Set();
|
|
577
|
+
const groups = isRecord(wire) ? ownField(wire, event) : undefined;
|
|
578
|
+
if (Array.isArray(groups)) {
|
|
579
|
+
for (const g of groups) {
|
|
580
|
+
if (!isRecord(g))
|
|
581
|
+
continue;
|
|
582
|
+
const gh = ownField(g, 'hooks');
|
|
583
|
+
if (!Array.isArray(gh))
|
|
584
|
+
continue;
|
|
585
|
+
const mk = skipMatcherKey(ownField(g, 'matcher'));
|
|
586
|
+
for (const h of gh) {
|
|
587
|
+
if (isRecord(h) && Object.hasOwn(h, 'plugin'))
|
|
588
|
+
continue;
|
|
589
|
+
const k = settingsHookEntryKey(ownView(h));
|
|
590
|
+
if (k !== undefined)
|
|
591
|
+
sent.add(`${mk}\u0000${k}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (sent.size === 0)
|
|
596
|
+
return hooks;
|
|
597
|
+
const mk = skipMatcherKey(isRecord(group) ? group.matcher : undefined);
|
|
598
|
+
let kept = null;
|
|
599
|
+
hooks.forEach((h, i) => {
|
|
600
|
+
const k = settingsHookEntryKey(h);
|
|
601
|
+
const skip = k !== undefined && sent.has(`${mk}\u0000${k}`);
|
|
602
|
+
if (skip)
|
|
603
|
+
kept ??= hooks.slice(0, i);
|
|
604
|
+
else if (kept !== null)
|
|
605
|
+
kept.push(h);
|
|
606
|
+
});
|
|
607
|
+
return kept ?? hooks;
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
return hooks;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function settingsRowBaseOf(v) {
|
|
614
|
+
if (!isRecord(v))
|
|
615
|
+
return undefined;
|
|
616
|
+
const source = ownField(v, 'source');
|
|
617
|
+
const event = ownField(v, 'event');
|
|
618
|
+
const groupIndex = ownField(v, 'groupIndex');
|
|
619
|
+
const hookIndex = ownField(v, 'hookIndex');
|
|
620
|
+
if (typeof source !== 'string' || !Object.hasOwn(SOURCE_TEXT, source))
|
|
621
|
+
return undefined;
|
|
622
|
+
if (typeof event !== 'string' || !ENGINE_EVENTS.has(event))
|
|
623
|
+
return undefined;
|
|
624
|
+
if (!Number.isInteger(groupIndex) || groupIndex < 0 || !Number.isInteger(hookIndex) || hookIndex < 0)
|
|
625
|
+
return undefined;
|
|
626
|
+
return { source: source, event, groupIndex: groupIndex, hookIndex: hookIndex, o: v };
|
|
627
|
+
}
|
|
628
|
+
const DISPOSITION_SET = new Set(PLUGIN_HOOK_DISPOSITIONS);
|
|
629
|
+
const REASON_SET = new Set(PLUGIN_HOOK_REASONS);
|
|
630
|
+
const EXCLUSION_REASON_SET = new Set(PLUGIN_HOOK_EXCLUSION_REASONS);
|
|
631
|
+
function settingsVerdictRowOf(v) {
|
|
632
|
+
try {
|
|
633
|
+
const b = settingsRowBaseOf(v);
|
|
634
|
+
if (b === undefined)
|
|
635
|
+
return undefined;
|
|
636
|
+
const disposition = ownField(b.o, 'disposition');
|
|
637
|
+
const reason = ownField(b.o, 'reason');
|
|
638
|
+
if (typeof disposition !== 'string' || !DISPOSITION_SET.has(disposition) || typeof reason !== 'string' || !REASON_SET.has(reason))
|
|
639
|
+
return undefined;
|
|
640
|
+
return { source: b.source, event: b.event, groupIndex: b.groupIndex, hookIndex: b.hookIndex, disposition: disposition, reason: reason };
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function settingsExclusionRowOf(v) {
|
|
647
|
+
try {
|
|
648
|
+
const b = settingsRowBaseOf(v);
|
|
649
|
+
if (b === undefined)
|
|
650
|
+
return undefined;
|
|
651
|
+
const reason = ownField(b.o, 'reason');
|
|
652
|
+
if (typeof reason !== 'string' || !EXCLUSION_REASON_SET.has(reason))
|
|
653
|
+
return undefined;
|
|
654
|
+
return { source: b.source, event: b.event, groupIndex: b.groupIndex, hookIndex: b.hookIndex, reason: reason };
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
430
660
|
const REASON_TEXT = {
|
|
431
661
|
projected: 'it is sent to the engine',
|
|
432
662
|
shell_event: 'this client runs this event itself',
|
|
@@ -441,6 +671,12 @@ const REASON_TEXT = {
|
|
|
441
671
|
shell_form_user_config: 'its command uses ${user_config.…} in shell form, which is refused (use exec form with "args", or read $CLAUDE_PLUGIN_OPTION_<KEY>)',
|
|
442
672
|
user_config_unset: 'its command uses a plugin option that has not been set',
|
|
443
673
|
malformed_entry: 'the hook entry is malformed',
|
|
674
|
+
engine_hooks_off: 'the connected engine ignores hooks sent with requests',
|
|
675
|
+
engine_hooks_unknown: 'it is not known whether the connected engine runs hooks sent with requests',
|
|
676
|
+
engine_type_skipped: 'the engine does not run PowerShell command hooks',
|
|
677
|
+
engine_llm_limit: 'the engine may skip prompt or agent hooks after the first four on one event',
|
|
678
|
+
engine_unconfirmed: 'it is sent to the engine, which is expected to run it, but this client cannot confirm that',
|
|
679
|
+
engine_elsewhere: 'it is not confirmed that the engine was started by this client on this machine, so this client runs it itself',
|
|
444
680
|
};
|
|
445
681
|
const DROP_TEXT = {
|
|
446
682
|
exec_form_unsupported: 'exec-form hooks (with "args") are sent to the engine only once it reports support for them',
|
|
@@ -545,5 +781,22 @@ export function hooksNotRunNotice(plan) {
|
|
|
545
781
|
const events = row.events.map((e) => noticeLabel(e)).join(', ');
|
|
546
782
|
lines.push(`${SOURCE_TEXT[row.source]}: ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${settingsHookDropText(row.reason)}.`);
|
|
547
783
|
}
|
|
784
|
+
const settingsVerdicts = plan.settingsVerdicts;
|
|
785
|
+
const byVerdict = new Map();
|
|
786
|
+
for (const raw of Array.isArray(settingsVerdicts) ? settingsVerdicts : []) {
|
|
787
|
+
const v = settingsVerdictRowOf(raw);
|
|
788
|
+
if (v === undefined || v.disposition !== 'not_run')
|
|
789
|
+
continue;
|
|
790
|
+
const k = `${v.source}\u0000${v.reason}`;
|
|
791
|
+
const row = byVerdict.get(k) ?? { source: v.source, reason: v.reason, events: [], count: 0 };
|
|
792
|
+
if (!row.events.includes(v.event))
|
|
793
|
+
row.events.push(v.event);
|
|
794
|
+
row.count++;
|
|
795
|
+
byVerdict.set(k, row);
|
|
796
|
+
}
|
|
797
|
+
for (const row of byVerdict.values()) {
|
|
798
|
+
const events = row.events.map((e) => noticeLabel(e)).join(', ');
|
|
799
|
+
lines.push(`${SOURCE_TEXT[row.source]}: ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${pluginHookReasonText(row.reason)}.`);
|
|
800
|
+
}
|
|
548
801
|
return lines;
|
|
549
802
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentEvent } from '@sema-agent/sdk';
|
|
2
|
+
import { type SelfHealOutcome } from './adapter/activeRunSelfHeal.js';
|
|
3
|
+
export interface PromptDeliveryRecord {
|
|
4
|
+
readonly u: string;
|
|
5
|
+
readonly s: 'a' | 'd';
|
|
6
|
+
}
|
|
7
|
+
export interface PromptDeliveryLedger {
|
|
8
|
+
attempted(batchUuids: readonly string[]): readonly PromptDeliveryRecord[];
|
|
9
|
+
deliveredByFrame(batchUuids: readonly string[], event: AgentEvent): readonly PromptDeliveryRecord[];
|
|
10
|
+
deliveredBySteerReceipt(batchUuids: readonly string[], outcome: SelfHealOutcome): readonly PromptDeliveryRecord[];
|
|
11
|
+
isAttempted(uuid: string): boolean;
|
|
12
|
+
isDelivered(uuid: string): boolean;
|
|
13
|
+
knownUndelivered(uuid: string): boolean;
|
|
14
|
+
batchOf(uuid: string): readonly string[] | undefined;
|
|
15
|
+
toRecords(): readonly PromptDeliveryRecord[];
|
|
16
|
+
fromRecords(records: readonly PromptDeliveryRecord[]): readonly PromptDeliveryRecord[];
|
|
17
|
+
}
|
|
18
|
+
export declare function createPromptDeliveryLedger(): PromptDeliveryLedger;
|
|
19
|
+
export declare function promptKnownUndelivered(ledger: PromptDeliveryLedger, message: object | null | undefined): boolean;
|
|
20
|
+
export declare function withoutUndeliveredPrompts<T>(ledger: PromptDeliveryLedger, messages: readonly T[]): T[];
|