@sema-agent/client-core 0.88.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +208 -0
- package/README.md +27 -21
- package/dist/adapter/activeRunSelfHeal.js +4 -2
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
- package/dist/adapter/downstream/wiringManifestView.d.ts +9 -0
- package/dist/adapter/downstream/wiringManifestView.js +27 -0
- package/dist/adapter/runStream.d.ts +2 -0
- package/dist/adapter/runStream.js +157 -32
- package/dist/attachmentsWireCaps.d.ts +13 -2
- package/dist/attachmentsWireCaps.js +25 -5
- package/dist/controlRouter.d.ts +7 -1
- package/dist/controlRouter.js +43 -1
- package/dist/coreValuePorts.d.ts +1 -0
- package/dist/disableSkillsWire.d.ts +17 -0
- package/dist/disableSkillsWire.js +52 -0
- package/dist/displayUntrusted.js +122 -55
- package/dist/engineCapReader.js +12 -1
- package/dist/engineNoticeCodes.d.ts +25 -9
- package/dist/engineNoticeCodes.js +161 -68
- package/dist/fileHistoryLegNotice.d.ts +20 -0
- package/dist/fileHistoryLegNotice.js +122 -0
- package/dist/gateOutcome.d.ts +1 -1
- package/dist/gateOutcome.js +1 -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 +107 -2
- package/dist/hitl/toolApprovalWire.d.ts +6 -2
- package/dist/hitl/toolApprovalWire.js +30 -0
- package/dist/hookToolAliases.d.ts +3 -0
- package/dist/hookToolAliases.js +10 -0
- package/dist/hooksWireCaps.js +80 -12
- package/dist/index.d.ts +14 -3
- package/dist/index.js +10 -3
- package/dist/permissionsAllowGrantCapability.d.ts +17 -0
- package/dist/permissionsAllowGrantCapability.js +75 -0
- package/dist/permissionsDeclaration.d.ts +25 -0
- package/dist/permissionsDeclaration.js +227 -0
- package/dist/pluginHooksWire.d.ts +46 -4
- package/dist/pluginHooksWire.js +277 -24
- package/dist/promptDelivery.d.ts +5 -2
- package/dist/promptDelivery.js +8 -3
- package/dist/providerRejection.d.ts +7 -0
- package/dist/providerRejection.js +58 -0
- package/dist/request/taskRequest.d.ts +2 -0
- package/dist/request/taskRequest.js +67 -9
- package/dist/runTerminal.d.ts +6 -0
- package/dist/runTerminal.js +23 -0
- package/dist/sdkRegistryTransit.d.ts +1 -0
- package/dist/sdkRegistryTransit.js +1 -0
- package/dist/seam.d.ts +3 -0
- package/dist/seatContract.d.ts +9 -1
- package/dist/seatContract.js +10 -0
- package/dist/subagent/engineSubagentSteer.d.ts +8 -1
- package/dist/subagent/engineSubagentSteer.js +26 -3
- package/dist/supportFloor.d.ts +10 -0
- package/dist/supportFloor.js +42 -0
- package/dist/taskRequestWords.d.ts +1 -0
- package/dist/taskRequestWords.js +14 -0
- package/docs/INTEGRATION-CLIENTS.md +896 -14
- package/package.json +3 -3
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
|
}
|
package/dist/promptDelivery.d.ts
CHANGED
|
@@ -14,7 +14,10 @@ export interface PromptDeliveryLedger {
|
|
|
14
14
|
batchOf(uuid: string): readonly string[] | undefined;
|
|
15
15
|
toRecords(): readonly PromptDeliveryRecord[];
|
|
16
16
|
fromRecords(records: readonly PromptDeliveryRecord[]): readonly PromptDeliveryRecord[];
|
|
17
|
+
knownUndeliveredMessage(message: object | null | undefined): boolean;
|
|
18
|
+
withoutUndelivered<T>(messages: readonly T[]): T[];
|
|
17
19
|
}
|
|
20
|
+
export declare function frameProvesDelivery(event: object | null | undefined): boolean;
|
|
18
21
|
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[];
|
|
22
|
+
export declare function promptKnownUndelivered(ledger: Pick<PromptDeliveryLedger, 'knownUndelivered'>, message: object | null | undefined): boolean;
|
|
23
|
+
export declare function withoutUndeliveredPrompts<T>(ledger: Pick<PromptDeliveryLedger, 'knownUndelivered'>, messages: readonly T[]): T[];
|
package/dist/promptDelivery.js
CHANGED
|
@@ -32,10 +32,12 @@ function readRecord(rec) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const PRE_RUN_FRAME_TYPES = new Set(['meta', 'approval_request', 'approval_revoke']);
|
|
35
|
-
function frameProvesDelivery(event) {
|
|
36
|
-
if (typeof event !== 'object' || event === null
|
|
35
|
+
export function frameProvesDelivery(event) {
|
|
36
|
+
if (typeof event !== 'object' || event === null)
|
|
37
37
|
return false;
|
|
38
38
|
try {
|
|
39
|
+
if (Array.isArray(event))
|
|
40
|
+
return false;
|
|
39
41
|
const type = event.type;
|
|
40
42
|
if (typeof type !== 'string' || type.length === 0 || PRE_RUN_FRAME_TYPES.has(type))
|
|
41
43
|
return false;
|
|
@@ -83,7 +85,7 @@ export function createPromptDeliveryLedger() {
|
|
|
83
85
|
add(u, 'd', added);
|
|
84
86
|
return Object.freeze(added);
|
|
85
87
|
};
|
|
86
|
-
|
|
88
|
+
const ledger = Object.freeze({
|
|
87
89
|
attempted(batchUuids) {
|
|
88
90
|
const batch = Object.freeze(cleanBatch(batchUuids));
|
|
89
91
|
const added = [];
|
|
@@ -122,7 +124,10 @@ export function createPromptDeliveryLedger() {
|
|
|
122
124
|
}
|
|
123
125
|
return Object.freeze(added);
|
|
124
126
|
},
|
|
127
|
+
knownUndeliveredMessage: (message) => promptKnownUndelivered(ledger, message),
|
|
128
|
+
withoutUndelivered: (messages) => withoutUndeliveredPrompts(ledger, messages),
|
|
125
129
|
});
|
|
130
|
+
return ledger;
|
|
126
131
|
}
|
|
127
132
|
function uuidOfMessage(m) {
|
|
128
133
|
if (typeof m !== 'object' || m === null)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RunTerminalFailureDetail } from './runTerminal.js';
|
|
2
|
+
import { type ToolHistoryErrorCarrier } from './toolHistoryMismatch.js';
|
|
3
|
+
export interface ProviderRejectionResend {
|
|
4
|
+
resend: 'edit_and_resend' | 'not_by_editing';
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function providerRejectionResend(detail: RunTerminalFailureDetail | null | undefined, carrier?: ToolHistoryErrorCarrier | null): ProviderRejectionResend | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readRunTerminal } from './runTerminal.js';
|
|
2
|
+
import { classifyToolHistoryMismatch } from './toolHistoryMismatch.js';
|
|
3
|
+
const PROVIDER_STATUS_TIERS = Object.freeze([
|
|
4
|
+
Object.freeze([400, 400, 'invalid_request']),
|
|
5
|
+
Object.freeze([401, 401, 'authentication_failed']),
|
|
6
|
+
Object.freeze([403, 403, 'authentication_failed']),
|
|
7
|
+
Object.freeze([404, 404, 'model_not_found']),
|
|
8
|
+
Object.freeze([413, 413, 'invalid_request']),
|
|
9
|
+
Object.freeze([422, 422, 'invalid_request']),
|
|
10
|
+
Object.freeze([429, 429, 'rate_limit']),
|
|
11
|
+
Object.freeze([500, 599, 'server_error']),
|
|
12
|
+
]);
|
|
13
|
+
const UNTIERED = 'unknown';
|
|
14
|
+
function providerStatusTier(status) {
|
|
15
|
+
for (const [lo, hi, tier] of PROVIDER_STATUS_TIERS)
|
|
16
|
+
if (status >= lo && status <= hi)
|
|
17
|
+
return tier;
|
|
18
|
+
return UNTIERED;
|
|
19
|
+
}
|
|
20
|
+
const EDITABLE_TIER = 'invalid_request';
|
|
21
|
+
const NOT_BY_EDITING_TAIL = new Map([
|
|
22
|
+
['authentication_failed', ': check the provider credentials or access for this account.'],
|
|
23
|
+
['model_not_found', ': the model is not available from this provider — choose another model.'],
|
|
24
|
+
['rate_limit', ': the provider is rate-limiting this account — wait and try again.'],
|
|
25
|
+
['server_error', ': the provider reported an internal error — try again later.'],
|
|
26
|
+
[UNTIERED, ' for a reason that editing it will not fix — check the provider account or try again later.'],
|
|
27
|
+
]);
|
|
28
|
+
function toolHistoryMismatchCarrier(carrier) {
|
|
29
|
+
return carrier !== undefined && carrier !== null && classifyToolHistoryMismatch(carrier) !== null;
|
|
30
|
+
}
|
|
31
|
+
const PROVIDER_REJECTED = 'provider_rejected';
|
|
32
|
+
export function providerRejectionResend(detail, carrier) {
|
|
33
|
+
let read;
|
|
34
|
+
try {
|
|
35
|
+
const r = readRunTerminal({ terminal: { kind: 'failed', detail } });
|
|
36
|
+
read = r !== null && r.kind === 'failed' && Object.hasOwn(r, 'detail') ? r.detail : undefined;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (read === undefined || !Object.hasOwn(read, 'kind') || read.kind !== PROVIDER_REJECTED)
|
|
42
|
+
return undefined;
|
|
43
|
+
const status = Object.hasOwn(read, 'status') ? read.status : undefined;
|
|
44
|
+
if (status === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (toolHistoryMismatchCarrier(carrier))
|
|
47
|
+
return undefined;
|
|
48
|
+
const tier = providerStatusTier(status);
|
|
49
|
+
return tier === EDITABLE_TIER
|
|
50
|
+
? {
|
|
51
|
+
resend: 'edit_and_resend',
|
|
52
|
+
text: `The model provider rejected this request as sent (HTTP ${status}). Change it — for example shorten it or remove an attachment — and send it again.`,
|
|
53
|
+
}
|
|
54
|
+
: {
|
|
55
|
+
resend: 'not_by_editing',
|
|
56
|
+
text: `The model provider refused this request (HTTP ${status})${NOT_BY_EDITING_TAIL.get(tier) ?? NOT_BY_EDITING_TAIL.get(UNTIERED) ?? ''}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -51,6 +51,7 @@ export interface TaskRequestInput {
|
|
|
51
51
|
fileHistory?: 'disabled';
|
|
52
52
|
inheritEnv?: InheritEnvWord;
|
|
53
53
|
requireExistingSession?: TaskRequest['requireExistingSession'];
|
|
54
|
+
disableSkills?: true;
|
|
54
55
|
rewind?: Record<string, unknown>;
|
|
55
56
|
repo?: string;
|
|
56
57
|
council?: boolean;
|
|
@@ -74,6 +75,7 @@ export interface TaskRequestOmission {
|
|
|
74
75
|
export declare function taskRequestOmissionCauseDetail(cause: unknown): string;
|
|
75
76
|
export declare const TASK_REQUEST_OMISSION_CAUSE_NOTICES: Readonly<Record<TaskRequestOmissionCause, string>>;
|
|
76
77
|
export declare function taskRequestOmissionCauseNotice(cause: unknown): string;
|
|
78
|
+
export declare function taskRequestOmissionCausePhrase(cause: unknown): string;
|
|
77
79
|
export interface TaskRequestAssembly {
|
|
78
80
|
readonly request: TaskRequestLike;
|
|
79
81
|
readonly omitted: readonly TaskRequestOmission[];
|