@sema-agent/core 5.37.0 → 5.38.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 +67 -0
- package/dist/agents/subagent.js +6 -0
- package/dist/agents/teacher.js +3 -0
- package/dist/agents/team.d.ts +7 -1
- package/dist/agents/team.js +11 -9
- package/dist/agents/verify.js +3 -0
- package/dist/core/auto-mode-prompt-assets.d.ts +5 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +26 -1
- package/dist/core/hooks.d.ts +129 -2
- package/dist/core/hooks.js +20 -3
- package/dist/core/runner/prepare-config-doors.d.ts +17 -0
- package/dist/core/runner/prepare-config-doors.js +33 -2
- package/dist/core/runner/prepare-task.d.ts +17 -2
- package/dist/core/runner/prepare-task.js +124 -41
- package/dist/core/runner/runtask.js +46 -11
- package/dist/core/tool-model-gate.d.ts +125 -0
- package/dist/core/tool-model-gate.js +303 -0
- package/dist/core/tool-policy.d.ts +1 -1
- package/dist/core/types.d.ts +189 -1
- package/dist/core/types.js +21 -0
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/orchestration/builtin-workflows.d.ts +68 -6
- package/dist/orchestration/builtin-workflows.js +26 -9
- package/dist/orchestration/run-workflow-tool.d.ts +10 -1
- package/dist/orchestration/run-workflow-tool.js +70 -27
- package/dist/orchestration/workflow-script-store.d.ts +8 -3
- package/dist/prompts/coordinator.d.ts +4 -1
- package/dist/prompts/coordinator.js +8 -0
- package/dist/prompts/default.d.ts +14 -4
- package/dist/prompts/default.js +2 -1
- package/dist/scenarios/full-body.d.ts +5 -0
- package/dist/scenarios/full-body.js +8 -4
- package/dist/tools/fs/fs-shared.d.ts +3 -2
- package/dist/tools/fs/fs-shared.js +19 -9
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +12 -1
|
@@ -29,7 +29,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
|
|
|
29
29
|
import { policyAskClassOf } from "../ask-class.js";
|
|
30
30
|
import { emitTrace } from "../trace.js";
|
|
31
31
|
import { createSessionRulePolicy } from "./session-rule-policy.js";
|
|
32
|
-
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, persistedRuleMandateOf, runToolGate } from "../hooks.js";
|
|
32
|
+
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, mintHookInvocationIdentity, persistedRuleMandateOf, runToolGate } from "../hooks.js";
|
|
33
33
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
34
34
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
35
35
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
@@ -92,13 +92,104 @@ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDec
|
|
|
92
92
|
import { durableParkGapFor } from "../park-selfcheck.js";
|
|
93
93
|
import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "../usage-window-store.js";
|
|
94
94
|
import { deliverEngineNotice } from "../types.js";
|
|
95
|
-
|
|
95
|
+
let announcedMaterializeEnvBySink = new WeakMap();
|
|
96
|
+
const announcedMaterializeEnvConsole = new Set();
|
|
97
|
+
function materializeEnvLedger(onNotice) {
|
|
98
|
+
if (typeof onNotice !== "function")
|
|
99
|
+
return announcedMaterializeEnvConsole;
|
|
100
|
+
let lines = announcedMaterializeEnvBySink.get(onNotice);
|
|
101
|
+
if (lines === undefined) {
|
|
102
|
+
lines = new Set();
|
|
103
|
+
announcedMaterializeEnvBySink.set(onNotice, lines);
|
|
104
|
+
}
|
|
105
|
+
return lines;
|
|
106
|
+
}
|
|
96
107
|
export function __resetMaterializeEnvAnnouncements() {
|
|
97
|
-
|
|
108
|
+
announcedMaterializeEnvBySink = new WeakMap();
|
|
109
|
+
announcedMaterializeEnvConsole.clear();
|
|
98
110
|
}
|
|
99
111
|
function emitMaterializeEnvNotice(onNotice, message, detail) {
|
|
112
|
+
const ledger = materializeEnvLedger(onNotice);
|
|
113
|
+
if (ledger.has(message))
|
|
114
|
+
return;
|
|
115
|
+
ledger.add(message);
|
|
100
116
|
deliverEngineNotice(onNotice, { code: "config.materialize_env_discarded", message, detail });
|
|
101
117
|
}
|
|
118
|
+
function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId) {
|
|
119
|
+
if (compModel === undefined)
|
|
120
|
+
return;
|
|
121
|
+
const compWindow = compModel.contextTokens ?? compModel.contextWindow;
|
|
122
|
+
const mainWindow = model.autoCompactTokens ?? model.contextTokens ?? model.contextWindow;
|
|
123
|
+
if (compWindow > 0 && mainWindow > 0 && compWindow < mainWindow) {
|
|
124
|
+
const merged = { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction };
|
|
125
|
+
const sanitized = sanitizeCompactionSettings(merged, mainWindow);
|
|
126
|
+
const tolerance = sanitized.clampTolerance ?? DEFAULT_CLAMP_TOLERANCE;
|
|
127
|
+
const headroom = Math.max(0, compWindow - Math.max(Math.floor(0.8 * summaryOutputBudgetTokens(compModel, sanitized)), 2048) - 512);
|
|
128
|
+
emitTrace(tracer, () => ({
|
|
129
|
+
kind: "compaction.window_config_warning",
|
|
130
|
+
version: 1,
|
|
131
|
+
taskId: hostTaskId,
|
|
132
|
+
compactionModelWindow: compWindow,
|
|
133
|
+
mainModelWindow: mainWindow,
|
|
134
|
+
...(tolerance < 1 ? { fallbackAt: Math.floor(headroom / (1 - tolerance)) } : {}),
|
|
135
|
+
ts: Date.now(),
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let announcedToolModelGateBySink = new WeakMap();
|
|
140
|
+
const announcedToolModelGateConsole = new Set();
|
|
141
|
+
function toolModelGateLedger(onNotice) {
|
|
142
|
+
if (typeof onNotice !== "function")
|
|
143
|
+
return announcedToolModelGateConsole;
|
|
144
|
+
let lines = announcedToolModelGateBySink.get(onNotice);
|
|
145
|
+
if (lines === undefined) {
|
|
146
|
+
lines = new Set();
|
|
147
|
+
announcedToolModelGateBySink.set(onNotice, lines);
|
|
148
|
+
}
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
export function __resetToolModelGateAnnouncements() {
|
|
152
|
+
announcedToolModelGateBySink = new WeakMap();
|
|
153
|
+
announcedToolModelGateConsole.clear();
|
|
154
|
+
}
|
|
155
|
+
function announceToolModelGate(onNotice, modelId, gate) {
|
|
156
|
+
const ledger = toolModelGateLedger(onNotice);
|
|
157
|
+
for (const [gateClass, removed] of gate.removedByClass) {
|
|
158
|
+
const line = `Model gate: default-mounted tool(s) ${removed.map((n) => JSON.stringify(n)).join(", ")} (class ${JSON.stringify(gateClass)}) ` +
|
|
159
|
+
`were not mounted for model ${JSON.stringify(modelId)} — the gate table marks this model as managing multi-step work without the scaffold. ` +
|
|
160
|
+
`Explicitly composed tools are exempt; restore via TaskSpec.restoreGatedTools, SEMA_TOOL_MODEL_GATE=off, or RunnerDeps.toolModelGate: false.`;
|
|
161
|
+
if (!ledger.has(line)) {
|
|
162
|
+
ledger.add(line);
|
|
163
|
+
deliverEngineNotice(onNotice, {
|
|
164
|
+
code: "config.tool_model_gate_removed",
|
|
165
|
+
message: line,
|
|
166
|
+
detail: {
|
|
167
|
+
modelId,
|
|
168
|
+
gateClass,
|
|
169
|
+
removed: [...removed],
|
|
170
|
+
restore: { spec: "TaskSpec.restoreGatedTools", env: "SEMA_TOOL_MODEL_GATE=off", deps: "RunnerDeps.toolModelGate: false" },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const gateClass of gate.unknownClasses) {
|
|
176
|
+
const line = `ToolSpec.modelGate names gate class ${JSON.stringify(gateClass)}, which no row of the merged gate table defines — the tag is inert ` +
|
|
177
|
+
`(fail-open: the tool stays mounted). Fix the tag, or define the class via RunnerDeps.toolModelGate.classes.`;
|
|
178
|
+
if (!ledger.has(line)) {
|
|
179
|
+
ledger.add(line);
|
|
180
|
+
deliverEngineNotice(onNotice, { code: "config.tool_model_gate_unknown_class", message: line, detail: { gateClass } });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (gate.discardedEnvRaw !== undefined) {
|
|
184
|
+
const line = `SEMA_TOOL_MODEL_GATE=${JSON.stringify(gate.discardedEnvRaw)} is not in the closed set (on|1|true|off|0|false, case-insensitive) — ` +
|
|
185
|
+
`not in force on this task (nothing the model gate would remove), but a task where the gate WOULD trim a default-mounted tool ` +
|
|
186
|
+
`will refuse to prepare under it (config.tool_model_gate_env_invalid). Fix or unset the flag.`;
|
|
187
|
+
if (!ledger.has(line)) {
|
|
188
|
+
ledger.add(line);
|
|
189
|
+
deliverEngineNotice(onNotice, { code: "config.tool_model_gate_env_invalid", message: line, detail: { raw: gate.discardedEnvRaw } });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
102
193
|
const readFaceClampAnnouncedSinks = new WeakSet();
|
|
103
194
|
let readFaceClampConsoleAnnounced = false;
|
|
104
195
|
export function __resetReadFaceClampAnnouncement() {
|
|
@@ -153,8 +244,9 @@ export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-worksp
|
|
|
153
244
|
function hasConversationContent(branch) {
|
|
154
245
|
return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
|
|
155
246
|
}
|
|
156
|
-
function
|
|
157
|
-
|
|
247
|
+
function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
|
|
248
|
+
const isDelegatedChild = internals?.isDelegatedChild !== undefined ? internals.isDelegatedChild === true : seedIsDelegatedChild === true;
|
|
249
|
+
return { isDelegatedChild, isNonForkChild: isDelegatedChild && internals?.insideFork !== true };
|
|
158
250
|
}
|
|
159
251
|
export function batchContextAt(messages, currentId) {
|
|
160
252
|
let batch = [];
|
|
@@ -238,6 +330,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
238
330
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
239
331
|
spec = doors.spec;
|
|
240
332
|
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
333
|
+
announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
|
|
241
334
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
242
335
|
let shellGatedBash = false;
|
|
243
336
|
let shellGatedMonitor = false;
|
|
@@ -249,25 +342,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
249
342
|
const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
|
|
250
343
|
const sessionId = acquired.sessionId;
|
|
251
344
|
const hostTaskId = spec.taskId ?? sessionId;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const mainWindow = model.autoCompactTokens ?? model.contextTokens ?? model.contextWindow;
|
|
255
|
-
if (compWindow > 0 && mainWindow > 0 && compWindow < mainWindow) {
|
|
256
|
-
const merged = { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction };
|
|
257
|
-
const sanitized = sanitizeCompactionSettings(merged, mainWindow);
|
|
258
|
-
const tolerance = sanitized.clampTolerance ?? DEFAULT_CLAMP_TOLERANCE;
|
|
259
|
-
const headroom = Math.max(0, compWindow - Math.max(Math.floor(0.8 * summaryOutputBudgetTokens(compModel, sanitized)), 2048) - 512);
|
|
260
|
-
emitTrace(deps.tracer, () => ({
|
|
261
|
-
kind: "compaction.window_config_warning",
|
|
262
|
-
version: 1,
|
|
263
|
-
taskId: hostTaskId,
|
|
264
|
-
compactionModelWindow: compWindow,
|
|
265
|
-
mainModelWindow: mainWindow,
|
|
266
|
-
...(tolerance < 1 ? { fallbackAt: Math.floor(headroom / (1 - tolerance)) } : {}),
|
|
267
|
-
ts: Date.now(),
|
|
268
|
-
}));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
345
|
+
const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
|
|
346
|
+
warnCompactionWindowHazard(deps.tracer, spec, model, compModel, hostTaskId);
|
|
271
347
|
const taskScope = internals?.registryScope ?? spec.principal ?? "default";
|
|
272
348
|
internals?.peerSelfRef?.addAxis("s", sessionId);
|
|
273
349
|
internals?.peerSelfRef?.addAxis("t", hostTaskId);
|
|
@@ -705,6 +781,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
705
781
|
excludeTools: toolFaceSnapshot.exclude,
|
|
706
782
|
deferTools: toolFaceSnapshot.defer,
|
|
707
783
|
alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
|
|
784
|
+
...(toolFaceSnapshot.restoreGated !== undefined ? { restoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
|
|
708
785
|
promptProfile,
|
|
709
786
|
...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
|
|
710
787
|
...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
|
|
@@ -876,6 +953,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
876
953
|
parentExcludeTools: toolFaceSnapshot.exclude,
|
|
877
954
|
parentDeferTools: toolFaceSnapshot.defer,
|
|
878
955
|
parentAlwaysLoadTools: toolFaceSnapshot.alwaysLoad,
|
|
956
|
+
...(toolFaceSnapshot.restoreGated !== undefined ? { parentRestoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
|
|
879
957
|
parentPromptProfile: promptProfile,
|
|
880
958
|
models: deps.models,
|
|
881
959
|
agents: deps.agents,
|
|
@@ -1581,7 +1659,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1581
1659
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
1582
1660
|
complianceDegraded,
|
|
1583
1661
|
parentAdmittedOrgScopes: foldAdmissionFreeze({
|
|
1584
|
-
delegated:
|
|
1662
|
+
delegated: delegation.isDelegatedChild ||
|
|
1585
1663
|
internals?.inheritedGate !== undefined ||
|
|
1586
1664
|
(resume?.seed.inheritedGate !== undefined &&
|
|
1587
1665
|
(resume.seed.inheritedGate.ancestorRules !== undefined ||
|
|
@@ -1650,7 +1728,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1650
1728
|
loaded = await Promise.resolve(deps.loadProjectMemory({
|
|
1651
1729
|
cwd: taskRootFinal,
|
|
1652
1730
|
handsEnabled,
|
|
1653
|
-
isSubagent:
|
|
1731
|
+
isSubagent: delegation.isNonForkChild,
|
|
1654
1732
|
...(internals?.agentName ? { agentName: internals.agentName } : {}),
|
|
1655
1733
|
sessionId,
|
|
1656
1734
|
phase: projectMemoryPhase,
|
|
@@ -1775,7 +1853,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1775
1853
|
awarenessEnabled: thinking !== undefined && ULTRA_REASONING_TIERS.has(thinking),
|
|
1776
1854
|
worktreeIsolated: internals?.isolation === "worktree" && ownedEnv !== undefined,
|
|
1777
1855
|
withinTaskCompactionEnabled: (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true),
|
|
1778
|
-
isSubagent:
|
|
1856
|
+
isSubagent: delegation.isNonForkChild,
|
|
1779
1857
|
};
|
|
1780
1858
|
const userSystemPrompt = spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals?.defaultSystemPrompt;
|
|
1781
1859
|
const userAppendSystemPrompt = spec.appendSystemPrompt;
|
|
@@ -2209,10 +2287,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2209
2287
|
const raw = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
|
|
2210
2288
|
if (raw !== undefined && raw !== "swap" && raw !== "static" && deferred.size === 0) {
|
|
2211
2289
|
const line = `SEMA_TOOL_MATERIALIZE_STRATEGY=${JSON.stringify(raw)} is not "swap" or "static" — inert on this task (no deferred tools), but a deferring task WITHOUT an explicit spec strategy will refuse to prepare under it (an explicit legal spec outranks and discards it, loudly). Fix or unset the flag.`;
|
|
2212
|
-
|
|
2213
|
-
announcedMaterializeEnv.add(line);
|
|
2214
|
-
emitMaterializeEnvNotice(deps.onNotice, line, { raw });
|
|
2215
|
-
}
|
|
2290
|
+
emitMaterializeEnvNotice(deps.onNotice, line, { raw });
|
|
2216
2291
|
}
|
|
2217
2292
|
}
|
|
2218
2293
|
if (deferred.size > 0) {
|
|
@@ -2231,10 +2306,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2231
2306
|
}
|
|
2232
2307
|
if (envStrategyInvalid) {
|
|
2233
2308
|
const line = `SEMA_TOOL_MATERIALIZE_STRATEGY=${JSON.stringify(rawEnvStrategy)} was ignored — not "swap" or "static", and the task spec pins toolMaterializeStrategy=${JSON.stringify(spec.toolMaterializeStrategy)} which outranks it. Fix or unset the env flag.`;
|
|
2234
|
-
|
|
2235
|
-
announcedMaterializeEnv.add(line);
|
|
2236
|
-
emitMaterializeEnvNotice(deps.onNotice, line, { raw: rawEnvStrategy, specStrategy: spec.toolMaterializeStrategy });
|
|
2237
|
-
}
|
|
2309
|
+
emitMaterializeEnvNotice(deps.onNotice, line, { raw: rawEnvStrategy, specStrategy: spec.toolMaterializeStrategy });
|
|
2238
2310
|
}
|
|
2239
2311
|
const envStrategy = envStrategyInvalid ? undefined : rawEnvStrategy;
|
|
2240
2312
|
const requestedStrategy = spec.toolMaterializeStrategy ?? envStrategy ?? "swap";
|
|
@@ -2785,7 +2857,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2785
2857
|
...((internals?.explicitAgentName ?? internals?.agentName) !== undefined
|
|
2786
2858
|
? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
|
|
2787
2859
|
: {}),
|
|
2788
|
-
...(
|
|
2860
|
+
...(delegation.isDelegatedChild ? { isDelegatedChild: true } : {}),
|
|
2789
2861
|
});
|
|
2790
2862
|
const riskAxesOf = (toolName) => {
|
|
2791
2863
|
const tier = irreversibilityTier.get(toolName);
|
|
@@ -3347,6 +3419,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3347
3419
|
retentionPolicyWired: deps.retentionPolicy !== undefined,
|
|
3348
3420
|
});
|
|
3349
3421
|
const parkLaneArmed = wiringManifest.parkLane.effective === true;
|
|
3422
|
+
const hookIdentity = mintHookInvocationIdentity({
|
|
3423
|
+
sessionId,
|
|
3424
|
+
taskId: spec.taskId ?? sessionId,
|
|
3425
|
+
legKind: wiringManifest.leg.kind,
|
|
3426
|
+
isDelegatedChild: delegation.isDelegatedChild,
|
|
3427
|
+
...(internals?.insideFork === true ? { insideFork: true } : {}),
|
|
3428
|
+
...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
|
|
3429
|
+
...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
|
|
3430
|
+
});
|
|
3350
3431
|
const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
|
|
3351
3432
|
const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
|
|
3352
3433
|
if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
|
|
@@ -3556,6 +3637,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3556
3637
|
: undefined,
|
|
3557
3638
|
gitAnnouncement: gitStatusRef.announced !== undefined ? { ...gitStatusRef.announced } : undefined,
|
|
3558
3639
|
delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
|
|
3640
|
+
isDelegatedChild: hookIdentity.isDelegatedChild ? true : undefined,
|
|
3559
3641
|
});
|
|
3560
3642
|
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
|
|
3561
3643
|
if (!checkpointStore)
|
|
@@ -4155,7 +4237,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4155
4237
|
if (blockedTracked)
|
|
4156
4238
|
blockedToolCalls.add(e.toolCallId);
|
|
4157
4239
|
if (notifyPermissionDenied) {
|
|
4158
|
-
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, source: "safety" });
|
|
4240
|
+
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, source: "safety", identity: hookIdentity });
|
|
4159
4241
|
}
|
|
4160
4242
|
return { block: true, reason: formatHookFeedback(complianceDeny), preToolContext: [] };
|
|
4161
4243
|
}
|
|
@@ -4166,7 +4248,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4166
4248
|
const planDenyReason = `Plan mode is active — "${e.toolName}" is a write/mutating tool and is read-only-blocked. ` +
|
|
4167
4249
|
`Research with read-only tools, then call ${PRESENT_PLAN_TOOL_NAME} with your plan to get it approved before acting.`;
|
|
4168
4250
|
if (notifyPermissionDenied) {
|
|
4169
|
-
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: planDenyReason, source: "planMode" });
|
|
4251
|
+
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: planDenyReason, source: "planMode", identity: hookIdentity });
|
|
4170
4252
|
}
|
|
4171
4253
|
return {
|
|
4172
4254
|
block: true,
|
|
@@ -4179,6 +4261,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4179
4261
|
result = await runToolGate({
|
|
4180
4262
|
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: spec.taskId ?? sessionId, site: f.site, message: f.error.message, ts: Date.now() })),
|
|
4181
4263
|
event: e,
|
|
4264
|
+
identity: hookIdentity,
|
|
4182
4265
|
preToolUse: ownGatePreToolUse,
|
|
4183
4266
|
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
|
|
4184
4267
|
adjudicate,
|
|
@@ -4291,7 +4374,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4291
4374
|
isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
|
|
4292
4375
|
content: e.content.map((c) => ({ ...c })),
|
|
4293
4376
|
details: clonedDetails,
|
|
4294
|
-
}, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}) });
|
|
4377
|
+
}, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity });
|
|
4295
4378
|
if (patch?.additionalContext) {
|
|
4296
4379
|
content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext) }];
|
|
4297
4380
|
changed = true;
|
|
@@ -4299,7 +4382,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4299
4382
|
}
|
|
4300
4383
|
}
|
|
4301
4384
|
else if (hooks?.postToolUse) {
|
|
4302
|
-
const patch = await hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}) });
|
|
4385
|
+
const patch = await hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity });
|
|
4303
4386
|
if (patch?.updatedOutput) {
|
|
4304
4387
|
content = patch.updatedOutput;
|
|
4305
4388
|
changed = true;
|
|
@@ -4586,7 +4669,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4586
4669
|
const effectiveReadFaceObserved = carrierReadFace();
|
|
4587
4670
|
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
4588
4671
|
const preparedHolder = {};
|
|
4589
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4672
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4590
4673
|
const prepared = buildPrepared();
|
|
4591
4674
|
preparedHolder.current = prepared;
|
|
4592
4675
|
return prepared;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
|
|
2
2
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
3
|
+
import { deliverDelegationLifecycle } from "../types.js";
|
|
3
4
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
4
5
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
5
6
|
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
@@ -757,7 +758,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
757
758
|
const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
|
|
758
759
|
if (!boundarySteered && !prepared.abortController.signal.aborted) {
|
|
759
760
|
try {
|
|
760
|
-
const r = await postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined);
|
|
761
|
+
const r = await postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity });
|
|
761
762
|
if (r?.additionalContext && injectedThisTurn === undefined) {
|
|
762
763
|
const body = sanitizeUntrustedText(r.additionalContext);
|
|
763
764
|
const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
|
|
@@ -1484,12 +1485,12 @@ export class Runner {
|
|
|
1484
1485
|
...(typeof h.preToolUse === "function" ? { preToolUse: (t, i, c) => h.preToolUse(t, i, c) } : {}),
|
|
1485
1486
|
...(typeof h.preToolUse === "function" && h.preToolUseObservational === true ? { preToolUseObservational: true } : {}),
|
|
1486
1487
|
...(typeof h.postToolUse === "function" ? { postToolUse: (t, i, o, c) => h.postToolUse(t, i, o, c) } : {}),
|
|
1487
|
-
...(typeof h.userPromptSubmit === "function" ? { userPromptSubmit: (p) => h.userPromptSubmit(p) } : {}),
|
|
1488
|
+
...(typeof h.userPromptSubmit === "function" ? { userPromptSubmit: (p, c) => h.userPromptSubmit(p, c) } : {}),
|
|
1488
1489
|
...(typeof h.stop === "function" ? { stop: (c) => h.stop(c) } : {}),
|
|
1489
1490
|
...(typeof h.postToolUseFailure === "function"
|
|
1490
1491
|
? { postToolUseFailure: (t, i, f, c) => h.postToolUseFailure(t, i, f, c) }
|
|
1491
1492
|
: {}),
|
|
1492
|
-
...(typeof h.postToolBatch === "function" ? { postToolBatch: (b) => h.postToolBatch(b) } : {}),
|
|
1493
|
+
...(typeof h.postToolBatch === "function" ? { postToolBatch: (b, m, c) => h.postToolBatch(b, m, c) } : {}),
|
|
1493
1494
|
...(typeof h.preCompact === "function" ? { preCompact: (c) => h.preCompact(c) } : {}),
|
|
1494
1495
|
...(typeof h.postCompact === "function" ? { postCompact: (c) => h.postCompact(c) } : {}),
|
|
1495
1496
|
...(typeof h.stopFailure === "function" ? { stopFailure: (c) => h.stopFailure(c) } : {}),
|
|
@@ -1679,6 +1680,13 @@ export class Runner {
|
|
|
1679
1680
|
durationMs: Date.now() - runStartedAt,
|
|
1680
1681
|
ts: Date.now(),
|
|
1681
1682
|
}));
|
|
1683
|
+
const owedTerminal = taskIdRef.delegationTerminalOwed;
|
|
1684
|
+
if (owedTerminal !== undefined) {
|
|
1685
|
+
taskIdRef.delegationTerminalOwed = undefined;
|
|
1686
|
+
deliverDelegationLifecycle(this.deps.onDelegationLifecycle, { phase: "terminal", identity: owedTerminal, status: "failed", turns: 0, ...(code !== undefined ? { errorCode: code } : {}) }, createSafeNotifier({
|
|
1687
|
+
onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
|
|
1688
|
+
}), "runtask.onDelegationLifecycle");
|
|
1689
|
+
}
|
|
1682
1690
|
queue.push({ type: "done", result: resultValue });
|
|
1683
1691
|
queue.close();
|
|
1684
1692
|
});
|
|
@@ -2037,6 +2045,20 @@ export class Runner {
|
|
|
2037
2045
|
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
|
|
2038
2046
|
notificationIdent = ident;
|
|
2039
2047
|
queue.push({ type: "wiring_manifest", manifest: prepared.wiringManifest, ...ident() });
|
|
2048
|
+
const delegationLifecycleNotifier = createSafeNotifier({
|
|
2049
|
+
onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
|
|
2050
|
+
});
|
|
2051
|
+
const emitDelegationLifecycle = (event) => {
|
|
2052
|
+
if (!prepared.hookIdentity.isDelegatedChild)
|
|
2053
|
+
return;
|
|
2054
|
+
if (this.deps.onDelegationLifecycle === undefined)
|
|
2055
|
+
return;
|
|
2056
|
+
deliverDelegationLifecycle(this.deps.onDelegationLifecycle, event, delegationLifecycleNotifier, "runtask.onDelegationLifecycle");
|
|
2057
|
+
};
|
|
2058
|
+
emitDelegationLifecycle({ phase: "spawn", identity: prepared.hookIdentity });
|
|
2059
|
+
if (taskIdRef !== undefined && prepared.hookIdentity.isDelegatedChild && this.deps.onDelegationLifecycle !== undefined) {
|
|
2060
|
+
taskIdRef.delegationTerminalOwed = prepared.hookIdentity;
|
|
2061
|
+
}
|
|
2040
2062
|
manualCompactRef.emitMooted = (reason) => {
|
|
2041
2063
|
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason, ...ident() });
|
|
2042
2064
|
};
|
|
@@ -2729,6 +2751,7 @@ export class Runner {
|
|
|
2729
2751
|
stopHookActive: consecutiveBlocks > 0,
|
|
2730
2752
|
consecutiveBlocks,
|
|
2731
2753
|
getBranch: () => prepared.session.getBranch(),
|
|
2754
|
+
identity: prepared.hookIdentity,
|
|
2732
2755
|
});
|
|
2733
2756
|
}
|
|
2734
2757
|
catch (err) {
|
|
@@ -2804,7 +2827,7 @@ export class Runner {
|
|
|
2804
2827
|
...this.seamCCompactionOptions(prepared),
|
|
2805
2828
|
...gitRestateOption(prepared),
|
|
2806
2829
|
...windowSafetyOptions(prepared.harness.getModel()),
|
|
2807
|
-
...this.compactionHookOptions(spec, prepared.sessionId, "forced"),
|
|
2830
|
+
...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity),
|
|
2808
2831
|
});
|
|
2809
2832
|
if (comp.compacted) {
|
|
2810
2833
|
compactionBreaker.failures = 0;
|
|
@@ -2869,7 +2892,7 @@ export class Runner {
|
|
|
2869
2892
|
runnerHooks: {
|
|
2870
2893
|
onError: this.deps.onError,
|
|
2871
2894
|
seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
|
|
2872
|
-
compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig),
|
|
2895
|
+
compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity),
|
|
2873
2896
|
recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
|
|
2874
2897
|
},
|
|
2875
2898
|
});
|
|
@@ -3099,7 +3122,7 @@ export class Runner {
|
|
|
3099
3122
|
const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3100
3123
|
if (userPromptSubmit) {
|
|
3101
3124
|
try {
|
|
3102
|
-
const decision = await userPromptSubmit(spec.objective);
|
|
3125
|
+
const decision = await userPromptSubmit(spec.objective, { identity: prepared.hookIdentity });
|
|
3103
3126
|
if (decision?.block) {
|
|
3104
3127
|
prepared.blockedRef.reason = formatHookFeedback(decision.block);
|
|
3105
3128
|
promptBlocked = true;
|
|
@@ -3503,6 +3526,7 @@ export class Runner {
|
|
|
3503
3526
|
result.errorCode !== "conflict") {
|
|
3504
3527
|
try {
|
|
3505
3528
|
await stopFailureHook({
|
|
3529
|
+
identity: prepared.hookIdentity,
|
|
3506
3530
|
error: result.errorMessage ?? "model error",
|
|
3507
3531
|
...(result.errorCode !== undefined ? { errorKind: result.errorCode } : {}),
|
|
3508
3532
|
turns: stats.turns,
|
|
@@ -3516,6 +3540,15 @@ export class Runner {
|
|
|
3516
3540
|
}
|
|
3517
3541
|
}
|
|
3518
3542
|
}
|
|
3543
|
+
emitDelegationLifecycle({
|
|
3544
|
+
phase: "terminal",
|
|
3545
|
+
identity: prepared.hookIdentity,
|
|
3546
|
+
status: result.status,
|
|
3547
|
+
turns: stats.turns,
|
|
3548
|
+
...(result.errorCode !== undefined ? { errorCode: result.errorCode } : {}),
|
|
3549
|
+
});
|
|
3550
|
+
if (taskIdRef !== undefined)
|
|
3551
|
+
taskIdRef.delegationTerminalOwed = undefined;
|
|
3519
3552
|
if (rs.degrade.degraded)
|
|
3520
3553
|
result.degraded = rs.degrade.degraded;
|
|
3521
3554
|
if (prepared.outputRef.set)
|
|
@@ -4503,15 +4536,17 @@ export class Runner {
|
|
|
4503
4536
|
consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
|
|
4504
4537
|
};
|
|
4505
4538
|
}
|
|
4506
|
-
compactionHookOptions(spec, sessionId, trigger) {
|
|
4539
|
+
compactionHookOptions(spec, sessionId, trigger, identity) {
|
|
4507
4540
|
const hooks = spec.hooks ?? this.deps.hooks;
|
|
4508
4541
|
const pre = hooks?.preCompact;
|
|
4509
4542
|
const post = hooks?.postCompact;
|
|
4543
|
+
const withIdentity = (ctx) => identity !== undefined ? { ...ctx, identity } : ctx;
|
|
4510
4544
|
return {
|
|
4511
4545
|
trigger,
|
|
4512
4546
|
...(pre
|
|
4513
4547
|
? {
|
|
4514
|
-
preCompact: async (
|
|
4548
|
+
preCompact: async (rawCtx) => {
|
|
4549
|
+
const ctx = withIdentity(rawCtx);
|
|
4515
4550
|
const report = (err) => {
|
|
4516
4551
|
try {
|
|
4517
4552
|
this.deps.onError?.(err, { phase: "hook", sessionId });
|
|
@@ -4535,9 +4570,9 @@ export class Runner {
|
|
|
4535
4570
|
: {}),
|
|
4536
4571
|
...(post
|
|
4537
4572
|
? {
|
|
4538
|
-
postCompact: async (
|
|
4573
|
+
postCompact: async (rawCtx) => {
|
|
4539
4574
|
try {
|
|
4540
|
-
await post.call(hooks,
|
|
4575
|
+
await post.call(hooks, withIdentity(rawCtx));
|
|
4541
4576
|
}
|
|
4542
4577
|
catch (err) {
|
|
4543
4578
|
try {
|
|
@@ -4581,7 +4616,7 @@ export class Runner {
|
|
|
4581
4616
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
4582
4617
|
...this.seamCCompactionOptions(prepared),
|
|
4583
4618
|
...gitRestateOption(prepared),
|
|
4584
|
-
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
4619
|
+
...this.compactionHookOptions(spec, prepared.sessionId, "auto", prepared.hookIdentity),
|
|
4585
4620
|
});
|
|
4586
4621
|
this.recordCompactionReuse(prepared, finishComp);
|
|
4587
4622
|
if (finishComp.unevaluableWindow) {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/277 — the tool-registration MODEL GATE (CC 233 counterpart, first formalized there as a
|
|
3
|
+
* family+version-floor table over the todo/task-board tool family).
|
|
4
|
+
*
|
|
5
|
+
* WHAT THE GATE IS: a DEFAULT-MOUNT trim, never a capability ban. A {@link ToolSpec.modelGate}
|
|
6
|
+
* tag declares "this entry is a default-mounted scaffold of the named class"; when the task's
|
|
7
|
+
* RESOLVED model id matches the class's rule below, that entry is dropped from the roster at
|
|
8
|
+
* prepare (true unmount — the schema never reaches `tools[]`). An EXPLICITLY composed tool is
|
|
9
|
+
* never tagged (the bundle only stamps its default arms), so "the user asked for it" is the
|
|
10
|
+
* first restore channel by construction. Restore channels beyond that:
|
|
11
|
+
* `TaskSpec.restoreGatedTools` (per task-tree), env `SEMA_TOOL_MODEL_GATE=off` (process),
|
|
12
|
+
* `RunnerDeps.toolModelGate: false` (deployment).
|
|
13
|
+
*
|
|
14
|
+
* FAIL-OPEN, BY CONTRACT (not by accident): sema is BYOM — model ids are an OPEN set, and this
|
|
15
|
+
* table encodes POSITIVE knowledge ("this family at this version and above manages multi-step
|
|
16
|
+
* work without the scaffold"). An id the table knows nothing about (deepseek-* / gpt-* / qwen-* / any
|
|
17
|
+
* custom name) is NOT gated: no knowledge ⇒ no trim ⇒ tool stays. The matcher's three pass-through
|
|
18
|
+
* edges (family absent, id shape unmatched, version below floor) all point the same way, and it is
|
|
19
|
+
* the only polarity compatible with BYOM — the reverse would trim every non-claude deployment's
|
|
20
|
+
* default face. The engine does NOT guarantee a canonical id either: a deployment-authored
|
|
21
|
+
* `Model.id` (a raw Model object, or a catalog key shadowing the tier expansion) that is not in
|
|
22
|
+
* canonical `claude-<family>-<version>` shape simply falls off the regex and stays open — the
|
|
23
|
+
* explicit-id discipline's deployment-side duty. To gate a non-claude strong model, write the
|
|
24
|
+
* exact id into a {@link ToolModelGateRule.modelIds} row (the only non-claude channel; no prefix
|
|
25
|
+
* or wildcard syntax exists).
|
|
26
|
+
*
|
|
27
|
+
* DIVERGENCE (registered, deliberate): CC judges the MAIN-LOOP canonical model once per process;
|
|
28
|
+
* sema judges the PER-TASK resolved model on every prepare — BYOM per-task models are first-class,
|
|
29
|
+
* so a delegated child on a different model re-judges under its own (strong parent / weak child ⇒
|
|
30
|
+
* the child gets the scaffold back). And the env valve's polarity is reversed (CC: enable the
|
|
31
|
+
* tools; sema: disable the GATE) because sema's class vocabulary is open — a per-family enable
|
|
32
|
+
* env would have to grow with every class.
|
|
33
|
+
*/
|
|
34
|
+
import type { ToolSpec } from "./types.js";
|
|
35
|
+
/** The shared tail extractor (BYOM ids may carry provider prefixes — "openrouter/anthropic/claude-fable-5"):
|
|
36
|
+
* boundary-aware LAST path segment, case-folded. Single source for every model-family/model-id
|
|
37
|
+
* comparison site (`isFableFamilyModelId` consumes it too — the prompt-shape axis, semantically
|
|
38
|
+
* independent but sharing the one tail-extraction posture). */
|
|
39
|
+
export declare function modelIdTail(id: string): string;
|
|
40
|
+
/** One gate class's matching rule (both axes optional; a rule with BOTH axes empty gates nothing —
|
|
41
|
+
* the per-class OFF shape a deployment reaches by explicitly clearing the axes). */
|
|
42
|
+
export interface ToolModelGateRule {
|
|
43
|
+
/** claude-syntax family+floor rows — meaningful only for ids whose TAIL SEGMENT matches
|
|
44
|
+
* `claude-<letters>-<digits(-digits)*>`. Version tuples compare positionally, missing positions
|
|
45
|
+
* read 0, tuple ≥ floor ⇒ gated (CC 233 comparator, verbatim semantics). Any other id shape
|
|
46
|
+
* falls through this axis entirely (fail-open). */
|
|
47
|
+
floors?: ReadonlyArray<readonly [family: string, floor: ReadonlyArray<number>]>;
|
|
48
|
+
/** Exact-id rows (explicit-id discipline): the resolved `Model.id`'s tail segment must equal the
|
|
49
|
+
* row case-insensitively. The ONLY channel by which a BYOM deployment gates its own non-claude
|
|
50
|
+
* strong model — deliberately no prefix/wildcard syntax (a substring guess against an open id
|
|
51
|
+
* set is how the `deepseek-chat` alias downgrade class of accident happens). */
|
|
52
|
+
modelIds?: ReadonlyArray<string>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The built-in gate table: gate-class → rule. CC 233 z_S floors, verbatim (evidence D2; the
|
|
56
|
+
* `mythos` row rides per the anchor). v1 vocabulary = ONE class, `"task-scaffold"` — the
|
|
57
|
+
* TodoWrite/TaskCreate/TaskGet/TaskUpdate/TaskList default bundle (`assembleCodeTools`). One class
|
|
58
|
+
* is evidence discipline, not mechanism limit: a family enters this table only with positive
|
|
59
|
+
* "strong models don't need it" evidence (a CC anchor or deployment measurement); classes are an
|
|
60
|
+
* open vocabulary and a deployment adds its own via `RunnerDeps.toolModelGate.classes`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const TOOL_MODEL_GATE_CLASSES: Readonly<Record<string, ToolModelGateRule>>;
|
|
63
|
+
/**
|
|
64
|
+
* Pure matcher: is `modelId` gated under `rule`? Every unmatched shape returns `false` (the
|
|
65
|
+
* fail-open contract in the module header). Exported for deployment pre-flight ("would my model
|
|
66
|
+
* lose the scaffold?") — the engine's own decision point calls this same function.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isModelGatedForClass(modelId: string, rule: ToolModelGateRule): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* #123 value screen for `TaskSpec.restoreGatedTools` (same posture as `assertReadFaceValue`: the
|
|
71
|
+
* VALUE is screened at the door, unconditionally — a garbage restore list must refuse loudly on
|
|
72
|
+
* every leg, never be silently read as "no restore" in the narrowing direction). Legal: absent,
|
|
73
|
+
* literal `true` (restore every class), or an array of strings (wire names; unknown names are
|
|
74
|
+
* inert by contract, like `excludeTools`).
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertRestoreGatedToolsValue(value: unknown): asserts value is true | readonly string[] | undefined;
|
|
77
|
+
/** Doors-facing decision (built + applied inside `prepareConfigDoors`' synchronous stretch —
|
|
78
|
+
* decision and application share one atomic window, no drift gap). */
|
|
79
|
+
export interface ToolModelGateDecision {
|
|
80
|
+
/** Present ⇔ at least one entry was removed: the fresh, PRIVATE survivor array `spec.tools` is
|
|
81
|
+
* rebound to (a caller mutating its original live array afterwards can neither re-add a removed
|
|
82
|
+
* entry nor displace anything — there are no indices left to misalign). `undefined` = no
|
|
83
|
+
* removal, the spec is left untouched. */
|
|
84
|
+
survivors: ToolSpec[] | undefined;
|
|
85
|
+
/** gate-class → removed wire names (sorted, unique) — the removal-notice payload. */
|
|
86
|
+
removedByClass: ReadonlyMap<string, readonly string[]>;
|
|
87
|
+
/** Stamped classes with no row in the merged table — announce-once material (the loud half of
|
|
88
|
+
* fail-open: a tag typo must not silently become "never gated" with nobody told). */
|
|
89
|
+
unknownClasses: readonly string[];
|
|
90
|
+
/** An env value outside the closed set that was NOT in force (nothing this prepare would gate) —
|
|
91
|
+
* discarded-value announce material. In-force garbage never lands here: it throws. */
|
|
92
|
+
discardedEnvRaw: string | undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The single decision point, called from `prepareConfigDoors` with the model id read ONCE into the
|
|
96
|
+
* decision (the Model object is mutable; decision and later reads must not diverge) and the frozen
|
|
97
|
+
* restore snapshot (never the live spec).
|
|
98
|
+
*
|
|
99
|
+
* Channel semantics (a UNION of loosening channels — any hit ⇒ don't trim; the order below is
|
|
100
|
+
* evaluation order, not priority):
|
|
101
|
+
* - `depsSeat === false` — deployment kill switch; nothing is scanned.
|
|
102
|
+
* - env `SEMA_TOOL_MODEL_GATE` `off|0|false` (case-folded) — process kill switch; `on|1|true` is
|
|
103
|
+
* the explicit default (no-op). A value outside the closed set follows the loud-bad-value
|
|
104
|
+
* dialect split (#123, the SEMA_TOOL_MATERIALIZE_STRATEGY precedent): where the value is IN
|
|
105
|
+
* FORCE — its reading would change this prepare's outcome, i.e. after folding the other
|
|
106
|
+
* channels there remains a stamped entry the gate would remove — the prepare REFUSES
|
|
107
|
+
* (`config.tool_model_gate_env_invalid`; a mistyped `off` must not silently select the
|
|
108
|
+
* narrowing arm). Everywhere else it is a DISCARDED value: announced once per process, never a
|
|
109
|
+
* veto of the configuration that outranks it.
|
|
110
|
+
* - `restoreGated === true` — every class exempt for this task; an array exempts the WHOLE class
|
|
111
|
+
* of any stamped tool it names (CC "opting into any tool of the family restores the family",
|
|
112
|
+
* judged against the FULL stamp set — a name `excludeTools` will later remove still selects its
|
|
113
|
+
* class here, while the exclusion itself stands downstream; unknown names inert).
|
|
114
|
+
* - a stamped class with no merged row ⇒ kept + reported in `unknownClasses` (fail-open, loud).
|
|
115
|
+
* - a merged rule with both axes empty ⇒ kept, silent (the documented per-class OFF).
|
|
116
|
+
* - the exclusion valve (`excludeTools`, applied downstream at the roster splice) ALWAYS wins in
|
|
117
|
+
* the result: no restore channel resurrects an excluded name.
|
|
118
|
+
*/
|
|
119
|
+
export declare function applyToolModelGate(input: {
|
|
120
|
+
tools: ReadonlyArray<ToolSpec> | undefined;
|
|
121
|
+
modelId: string;
|
|
122
|
+
depsSeat: unknown;
|
|
123
|
+
restoreGated: true | readonly string[] | undefined;
|
|
124
|
+
envRaw: string | undefined;
|
|
125
|
+
}): ToolModelGateDecision;
|