@sema-agent/client-core 0.88.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 +104 -0
- package/README.md +11 -10
- 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/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.js +122 -55
- package/dist/engineNoticeCodes.d.ts +24 -1
- package/dist/engineNoticeCodes.js +145 -44
- package/dist/generated/engineNoticeTables.d.ts +3 -0
- package/dist/generated/engineNoticeTables.js +108 -0
- 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 +6 -3
- package/dist/index.js +5 -3
- package/dist/pluginHooksWire.d.ts +46 -4
- package/dist/pluginHooksWire.js +277 -24
- package/dist/providerRejection.d.ts +6 -0
- package/dist/providerRejection.js +52 -0
- package/dist/request/taskRequest.d.ts +2 -0
- package/dist/request/taskRequest.js +40 -8
- package/dist/runTerminal.d.ts +6 -0
- package/dist/runTerminal.js +23 -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/docs/INTEGRATION-CLIENTS.md +403 -11
- 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
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RunTerminalFailureDetail } from './runTerminal.js';
|
|
2
|
+
export interface ProviderRejectionResend {
|
|
3
|
+
resend: 'edit_and_resend' | 'not_by_editing';
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function providerRejectionResend(detail: RunTerminalFailureDetail | null | undefined): ProviderRejectionResend | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readRunTerminal } from './runTerminal.js';
|
|
2
|
+
const PROVIDER_STATUS_TIERS = Object.freeze([
|
|
3
|
+
Object.freeze([400, 400, 'invalid_request']),
|
|
4
|
+
Object.freeze([401, 401, 'authentication_failed']),
|
|
5
|
+
Object.freeze([403, 403, 'authentication_failed']),
|
|
6
|
+
Object.freeze([404, 404, 'model_not_found']),
|
|
7
|
+
Object.freeze([413, 413, 'invalid_request']),
|
|
8
|
+
Object.freeze([422, 422, 'invalid_request']),
|
|
9
|
+
Object.freeze([429, 429, 'rate_limit']),
|
|
10
|
+
Object.freeze([500, 599, 'server_error']),
|
|
11
|
+
]);
|
|
12
|
+
const UNTIERED = 'unknown';
|
|
13
|
+
function providerStatusTier(status) {
|
|
14
|
+
for (const [lo, hi, tier] of PROVIDER_STATUS_TIERS)
|
|
15
|
+
if (status >= lo && status <= hi)
|
|
16
|
+
return tier;
|
|
17
|
+
return UNTIERED;
|
|
18
|
+
}
|
|
19
|
+
const EDITABLE_TIER = 'invalid_request';
|
|
20
|
+
const NOT_BY_EDITING_TAIL = new Map([
|
|
21
|
+
['authentication_failed', ': check the provider credentials or access for this account.'],
|
|
22
|
+
['model_not_found', ': the model is not available from this provider — choose another model.'],
|
|
23
|
+
['rate_limit', ': the provider is rate-limiting this account — wait and try again.'],
|
|
24
|
+
['server_error', ': the provider reported an internal error — try again later.'],
|
|
25
|
+
[UNTIERED, ' for a reason that editing it will not fix — check the provider account or try again later.'],
|
|
26
|
+
]);
|
|
27
|
+
const PROVIDER_REJECTED = 'provider_rejected';
|
|
28
|
+
export function providerRejectionResend(detail) {
|
|
29
|
+
let read;
|
|
30
|
+
try {
|
|
31
|
+
const r = readRunTerminal({ terminal: { kind: 'failed', detail } });
|
|
32
|
+
read = r !== null && r.kind === 'failed' && Object.hasOwn(r, 'detail') ? r.detail : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (read === undefined || !Object.hasOwn(read, 'kind') || read.kind !== PROVIDER_REJECTED)
|
|
38
|
+
return undefined;
|
|
39
|
+
const status = Object.hasOwn(read, 'status') ? read.status : undefined;
|
|
40
|
+
if (status === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
const tier = providerStatusTier(status);
|
|
43
|
+
return tier === EDITABLE_TIER
|
|
44
|
+
? {
|
|
45
|
+
resend: 'edit_and_resend',
|
|
46
|
+
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.`,
|
|
47
|
+
}
|
|
48
|
+
: {
|
|
49
|
+
resend: 'not_by_editing',
|
|
50
|
+
text: `The model provider refused this request (HTTP ${status})${NOT_BY_EDITING_TAIL.get(tier) ?? NOT_BY_EDITING_TAIL.get(UNTIERED) ?? ''}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -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[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { refuseBadDisableSkills, refuseDisableSkillsConflict } from '../disableSkillsWire.js';
|
|
1
2
|
export const REQUEST_FIELD_MATRIX = [
|
|
2
3
|
{ field: 'objective', lanes: ['interactive', 'print', 'utility'], live: false, why: '本 turn 的真实输入,每条车道都必需', whyUtilityLane: '恒座:三条车道都必须带上本次提交的模型面输入' },
|
|
3
4
|
{ field: 'sessionId', lanes: ['interactive', 'print', 'utility'], live: false, why: '单命名空间(TP-A Step 2):壳 id 即引擎 session id;print 在非 live 下保留 mock 常量', whyUtilityLane: '恒座:side-channel 提交走**独立**会话 id(壳另铸一个,绝不复用用户会话那一个 —— 复用会把这次提交的转录混进用户会话)' },
|
|
@@ -25,6 +26,7 @@ export const REQUEST_FIELD_MATRIX = [
|
|
|
25
26
|
{ field: 'fileHistory', lanes: ['interactive', 'print'], live: true, why: '「这一次运行别捕获文件历史」的**请求级声明**(server 7.104.0 提交体键,单成员闭集 "disabled";`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 同收,子代继承)。两条用户车道都是提交面、都跑在用户的文件上 ⇒ 都能声明;「本会话不捕获」= 每个提交都带(续跑腿重放同一个体)。🔴 只在能力读数 `on-optional` 时发(老 server 对表外键 400),值由片段口 `fileHistoryDeclarationField` 唯一铸出。与 rewind 还原键同交 ⇒ 上游 400 `request.field_conflict`', whyUtilityLane: '无座:side-channel 强制零工具面(`excludeAllTools`),不会改任何文件 ⇒ 没有可捕获的东西;带上它只会在不认识这一键的老 server 上给这条提交多一种 400' },
|
|
26
27
|
{ field: 'inheritEnv', lanes: ['interactive', 'print'], live: true, why: '模型驱动的宿主 shell「继承多少用户环境」的**请求级声明**(server 7.106.0 提交体键,两词闭集 "all" / "scrub";`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 同收,续跑腿重放同判)。两条用户车道都是提交面、都可能在用户机器上跑 shell ⇒ 都能声明。本层两词原样透传、不按车道 / 部署预判(非 host 车道 × "all" 400、托管 × "all" 422 归上游判)。🔴 缺席 = 缺省清洗且清掉本会话上一条声明 ⇒ 要「本会话整份继承」就每个提交都带。🔴 只在服务端版本 ≥ 7.106.0 时发(老 server 对表外键整条 400),值由片段口 `inheritEnvDeclaration` 唯一铸出', whyUtilityLane: '无座:side-channel 强制零工具面(`excludeAllTools`),没有模型驱动的 shell 去继承环境;带上它只会在不认这一键的老服务端上给这条提交多一种 400' },
|
|
27
28
|
{ field: 'requireExistingSession', lanes: ['interactive', 'print'], live: true, why: '「只续已有会话」的**请求级断言**(sdk `TaskRequest.requireExistingSession`):`true` 且会话不在 ⇒ 服务端在任何东西跑起来之前拒(会话不在 / 没给会话 id 两形),而**不是**静默新开一个同 id 的空会话;缺席 / `false` = 历史的「找不到就新开」。两条用户车道都有「续某个会话」的入口 ⇒ 都能声明。本层只认字面 `true`(`false` 与缺席同义、不发);非布尔坏值响亮拒(上游对它 400);`true` 而入参没有可续的会话 id ⇒ 装配期按名响亮拒,不留给上游。上游两形的拒绝经 `requireExistingSessionRefusalFromError` 归成一句', whyUtilityLane: '无座:side-channel 每一次提交都走壳另铸的**独立**会话 id(见 sessionId 那一行),从不续任何已有会话 ⇒「必须已存在」对它没有意义,带上去只会让每一次提交都被拒' },
|
|
29
|
+
{ field: 'disableSkills', lanes: ['interactive', 'print'], live: true, why: '「这一发一只技能都不装」的**请求级声明**(出厂技能、调用方给的技能、子代定义里的技能一个都不进引擎;其余工具照挂 —— 与 excludeAllTools 是两件事,两键同带各自生效)。两条用户车道都是提交面 ⇒ 都能声明。本层只认字面 true(false 与缺席同义、不发);非布尔坏值响亮拒(上游对它 400);与非空 skills 或任一非空 agents[].skills 同在 ⇒ 装配期按名响亮拒(上游对这一组合 400),兜底层落 agents 之后同判。🔴 只在能力位读数为真时发(老服务端对表外键整条 400),值由片段口 disableSkillsDeclaration 唯一铸出', whyUtilityLane: '无座:同 skills 一行 —— side-channel 是零工具面,装不装技能都无从谈起' },
|
|
28
30
|
{ field: 'finalVerification', lanes: ['print'], live: true, why: 'P1-1 终验:无人值守车道才需要引擎自证;交互 REPL 由人当场看结果。#106 裁 B(让位+告知)后交互面默认关', whyUtilityLane: '无座:终验让引擎再跑一轮自证,而 side-channel 要的是一次小提交的即时答案;「无人值守才需要引擎自证」这条理由在本车道上让位给「小预算优先」' },
|
|
29
31
|
{ field: 'limits', lanes: ['print', 'utility'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc', whyUtilityLane: '有座:side-channel 必须带预算护栏(墙钟 / 输出 token / 轮数)—— 它同样没有人看着,而且没有后续回合可以中断' },
|
|
30
32
|
{ field: 'interactiveTools', lanes: ['print', 'utility'], live: true, why: '[909]B 件3:无人值守 stamp false,从 roster 源头灭掉 AskUserQuestion/plan 门。交互车道 stamp false 等于自废武功', whyUtilityLane: '有座:side-channel 绝不该弹交互问答 / plan 门(没有人在看这条提交)。它与 excludeAllTools 是**两条独立**的收紧声明、在这条车道上**并存**(本车道强制带卸载声明,见 `refuseUtilityWithoutToolUnload`;这一位是它旁边那条「连问都别问」的声明,不是它的降级替身)' },
|
|
@@ -76,18 +78,40 @@ export function taskRequestOmissionCauseDetail(cause) {
|
|
|
76
78
|
const word = typeof cause === 'string' && cause.length > 0 ? cause : '(none)';
|
|
77
79
|
return `a cause this build does not know (${word}) — this build does not know this cause: receipt causes come from this build's own frozen vocabulary, so a word outside it was not produced here; check whatever supplied it`;
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
upstream_absent:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS = Object.freeze({
|
|
82
|
+
upstream_absent: {
|
|
83
|
+
lead: 'Not sent with this request',
|
|
84
|
+
reason: 'this version has no way to send this setting in any request, so giving it or leaving it out makes no difference to what is sent',
|
|
85
|
+
},
|
|
86
|
+
other_channel: {
|
|
87
|
+
lead: 'Not sent from here',
|
|
88
|
+
reason: 'that part of the request belongs to a separate step, so what goes out for it, if anything, is decided there rather than by the value given here',
|
|
89
|
+
},
|
|
90
|
+
off_lane: {
|
|
91
|
+
lead: 'Not sent with this request',
|
|
92
|
+
reason: 'this kind of request does not carry this setting, so giving it or leaving it out makes no difference to what this request sends',
|
|
93
|
+
},
|
|
94
|
+
not_live: {
|
|
95
|
+
lead: 'Not sent with this request',
|
|
96
|
+
reason: 'the request was marked as not going to a live engine (for example an offline or test run), and this setting is only sent on requests that go to one',
|
|
97
|
+
},
|
|
84
98
|
});
|
|
85
|
-
const
|
|
99
|
+
const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_LEAD = 'Not sent with this request';
|
|
100
|
+
const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON = 'for a reason this version does not recognize';
|
|
101
|
+
const wholeOmissionNotice = (p) => `${p.lead}: ${p.reason}.`;
|
|
102
|
+
export const TASK_REQUEST_OMISSION_CAUSE_NOTICES = Object.freeze(Object.fromEntries(TASK_REQUEST_OMISSION_CAUSES.map((cause) => [cause, wholeOmissionNotice(TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS[cause])])));
|
|
103
|
+
const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE = `${TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_LEAD}, ${TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON}.`;
|
|
104
|
+
const omissionNoticePartsOf = (cause) => typeof cause === 'string' && Object.hasOwn(TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS, cause)
|
|
105
|
+
? TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS[cause]
|
|
106
|
+
: undefined;
|
|
86
107
|
export function taskRequestOmissionCauseNotice(cause) {
|
|
87
|
-
return
|
|
108
|
+
return omissionNoticePartsOf(cause) !== undefined
|
|
88
109
|
? TASK_REQUEST_OMISSION_CAUSE_NOTICES[cause]
|
|
89
110
|
: TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE;
|
|
90
111
|
}
|
|
112
|
+
export function taskRequestOmissionCausePhrase(cause) {
|
|
113
|
+
return omissionNoticePartsOf(cause)?.reason ?? TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON;
|
|
114
|
+
}
|
|
91
115
|
const row = (field) => ({ via: 'matrix', field });
|
|
92
116
|
const INPUT_KEY_ROUTES = {
|
|
93
117
|
objective: { via: 'always' },
|
|
@@ -141,6 +165,7 @@ const INPUT_KEY_ROUTES = {
|
|
|
141
165
|
fileHistory: row('fileHistory'),
|
|
142
166
|
inheritEnv: row('inheritEnv'),
|
|
143
167
|
requireExistingSession: row('requireExistingSession'),
|
|
168
|
+
disableSkills: row('disableSkills'),
|
|
144
169
|
rewind: row('resumeAt/resumeAtMode/restoreFiles/rewindFilesTo'),
|
|
145
170
|
repo: row('repo'),
|
|
146
171
|
council: row('council'),
|
|
@@ -214,6 +239,7 @@ const VALUE_LEVEL_IN_PLAY = Object.freeze({
|
|
|
214
239
|
memory: (v) => v === 'off',
|
|
215
240
|
fileHistory: (v) => v === FILE_HISTORY_DISABLED,
|
|
216
241
|
requireExistingSession: (v) => v === true,
|
|
242
|
+
disableSkills: (v) => v === true,
|
|
217
243
|
rewind: (v) => typeof v === 'object' && v !== null && rewindSubKeys().some((k) => hasOwn(v, k) && present(v[k])),
|
|
218
244
|
});
|
|
219
245
|
const inPlay = (key, v) => present(v) && (VALUE_LEVEL_IN_PLAY[key]?.(v) ?? true);
|
|
@@ -542,7 +568,7 @@ export function parseMaxCostUsd(text) {
|
|
|
542
568
|
return { ok: false, why: 'not_positive' };
|
|
543
569
|
return { ok: true, value };
|
|
544
570
|
}
|
|
545
|
-
const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession'];
|
|
571
|
+
const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession', 'disableSkills'];
|
|
546
572
|
function dataFirstSnapshot(input, afterData) {
|
|
547
573
|
let descs;
|
|
548
574
|
try {
|
|
@@ -612,6 +638,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
612
638
|
const fileHistoryIn = src.fileHistory;
|
|
613
639
|
const inheritEnvIn = src.inheritEnv;
|
|
614
640
|
const requireExistingSessionIn = src.requireExistingSession;
|
|
641
|
+
const disableSkillsIn = src.disableSkills;
|
|
615
642
|
const INPUT_KEYS = Object.keys(INPUT_KEY_ROUTES);
|
|
616
643
|
const i = snapshotByName(src, INPUT_KEYS, {
|
|
617
644
|
live,
|
|
@@ -624,6 +651,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
624
651
|
fileHistory: fileHistoryIn,
|
|
625
652
|
inheritEnv: inheritEnvIn,
|
|
626
653
|
requireExistingSession: requireExistingSessionIn,
|
|
654
|
+
disableSkills: disableSkillsIn,
|
|
627
655
|
settings: settingsIn,
|
|
628
656
|
});
|
|
629
657
|
const SETTINGS_KEYS = Object.keys(SETTINGS_KEY_ROUTES);
|
|
@@ -637,6 +665,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
637
665
|
refuseBadFileHistory(fileHistoryIn);
|
|
638
666
|
refuseBadInheritEnv(inheritEnvIn);
|
|
639
667
|
refuseBadRequireExistingSession(requireExistingSessionIn);
|
|
668
|
+
refuseBadDisableSkills(disableSkillsIn, 'buildTaskRequest');
|
|
640
669
|
const rewindRoute = INPUT_KEY_ROUTES.rewind;
|
|
641
670
|
const RETIRED_REWIND_FILES_KEY = 'rewindFiles';
|
|
642
671
|
const REWIND_KEYS = rewindRoute.via === 'matrix' ? rewindRoute.field.split('/') : [];
|
|
@@ -721,6 +750,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
721
750
|
'错误文本只报键名,不回显值。');
|
|
722
751
|
}
|
|
723
752
|
refuseRequireExistingWithoutSession(requireExistingSessionIn, i.sessionId);
|
|
753
|
+
refuseDisableSkillsConflict(disableSkillsIn, i.skills, Reflect.get(src, 'agents'), 'buildTaskRequest');
|
|
724
754
|
refuseUtilityWithoutToolUnload(lane, excludeAllToolsIn);
|
|
725
755
|
const carries = (route, value, key) => {
|
|
726
756
|
if (route.via === 'always')
|
|
@@ -796,6 +826,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
796
826
|
...(on('fileHistory') ? { fileHistory: FILE_HISTORY_DISABLED } : {}),
|
|
797
827
|
...(on('inheritEnv') ? { inheritEnv: inheritEnvIn } : {}),
|
|
798
828
|
...(on('requireExistingSession') ? { requireExistingSession: true } : {}),
|
|
829
|
+
...(on('disableSkills') ? { disableSkills: true } : {}),
|
|
799
830
|
...(Object.keys(settingsOut).length > 0 ? { settings: settingsOut } : {}),
|
|
800
831
|
};
|
|
801
832
|
return Object.freeze({ request: out, omitted: Object.freeze([...omitted, ...lateOmitted]) });
|
|
@@ -819,6 +850,7 @@ export function applyLiveRequestDefaults(req, host) {
|
|
|
819
850
|
out.retainSubagentSessions = true;
|
|
820
851
|
if (out.agents === undefined && host.agents !== undefined)
|
|
821
852
|
out.agents = host.agents;
|
|
853
|
+
refuseDisableSkillsConflict(out.disableSkills, out.skills, out.agents, 'applyLiveRequestDefaults');
|
|
822
854
|
if (typeof out.scenario === 'string' && host.mockScenarioKeys?.has(out.scenario)) {
|
|
823
855
|
delete out.scenario;
|
|
824
856
|
}
|
package/dist/runTerminal.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface RunTerminalPausedRead extends RunTerminalPause {
|
|
|
11
11
|
code?: string;
|
|
12
12
|
flatStatus?: string;
|
|
13
13
|
}
|
|
14
|
+
export interface RunTerminalFailureDetail {
|
|
15
|
+
kind: string;
|
|
16
|
+
status?: number;
|
|
17
|
+
code?: string;
|
|
18
|
+
}
|
|
14
19
|
export type RunTerminalRead = {
|
|
15
20
|
kind: 'completed';
|
|
16
21
|
plane: RunTerminalPlane;
|
|
@@ -20,6 +25,7 @@ export type RunTerminalRead = {
|
|
|
20
25
|
code?: string;
|
|
21
26
|
message?: string;
|
|
22
27
|
nestedPause?: RunTerminalPause;
|
|
28
|
+
detail?: RunTerminalFailureDetail;
|
|
23
29
|
} | {
|
|
24
30
|
kind: 'blocked';
|
|
25
31
|
plane: RunTerminalPlane;
|