@sema-agent/core 7.13.0 → 7.15.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 +110 -0
- package/dist/agents/subagent.d.ts +4 -9
- package/dist/agents/subagent.js +16 -12
- package/dist/agents/teacher.js +1 -1
- package/dist/brain/reasoning.d.ts +23 -0
- package/dist/brain/reasoning.js +35 -4
- package/dist/brain/stream-engine.js +35 -17
- package/dist/brain/timeout.d.ts +25 -4
- package/dist/brain/timeout.js +1 -1
- package/dist/core/ask-origin.d.ts +30 -11
- package/dist/core/ask-origin.js +10 -5
- package/dist/core/auto-mode.d.ts +3 -3
- package/dist/core/checkpoint-seat.d.ts +18 -0
- package/dist/core/checkpoint-seat.js +6 -0
- package/dist/core/checkpoint-store.d.ts +23 -31
- package/dist/core/checkpoint-store.js +7 -4
- package/dist/core/engine-notice.d.ts +3 -1
- package/dist/core/env-knob-announce.d.ts +16 -0
- package/dist/core/env-knob-announce.js +14 -0
- package/dist/core/gate-fold.js +2 -0
- package/dist/core/gate-lanes.js +21 -12
- package/dist/core/gate-outcome.d.ts +42 -3
- package/dist/core/gate-outcome.js +22 -0
- package/dist/core/governance-codes.js +1 -1
- package/dist/core/hooks.d.ts +9 -19
- package/dist/core/hooks.js +6 -4
- package/dist/core/mcp.d.ts +3 -2
- package/dist/core/mcp.js +5 -11
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/runner/advertised-writable-dirs.d.ts +74 -0
- package/dist/core/runner/advertised-writable-dirs.js +48 -0
- package/dist/core/runner/assemble-result.d.ts +5 -0
- package/dist/core/runner/contracts.d.ts +2 -2
- package/dist/core/runner/denial-limit-arms.d.ts +7 -9
- package/dist/core/runner/denial-limit-arms.js +8 -10
- package/dist/core/runner/gate-exit.d.ts +15 -3
- package/dist/core/runner/gate-exit.js +6 -4
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
- package/dist/core/runner/permission-rule-lanes.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +34 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +47 -12
- package/dist/core/runner/prepare-defer-classify.d.ts +1 -1
- package/dist/core/runner/prepare-defer-classify.js +12 -10
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -5
- package/dist/core/runner/prepare-gate-stations.js +2 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +18 -12
- package/dist/core/runner/prepare-hands-readface.js +32 -43
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +31 -34
- package/dist/core/runner/prepare-memory.js +73 -51
- package/dist/core/runner/prepare-park-ask.d.ts +5 -6
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -2
- package/dist/core/runner/prepare-policy-chain.js +7 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -2
- package/dist/core/runner/prepare-question-face.d.ts +10 -8
- package/dist/core/runner/prepare-question-face.js +1 -3
- package/dist/core/runner/prepare-safety-scan.js +1 -1
- package/dist/core/runner/prepare-task.js +60 -53
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +1 -1
- package/dist/core/runner/run-compaction-machinery.js +2 -0
- package/dist/core/runner/run-harness-handlers.js +3 -1
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runner/tool-defer-gate.d.ts +86 -0
- package/dist/core/runner/tool-defer-gate.js +57 -0
- package/dist/core/runner/tool-disclosure.d.ts +0 -36
- package/dist/core/runner/tool-disclosure.js +0 -43
- package/dist/core/runner-deps.d.ts +16 -9
- package/dist/core/runtime-caps.d.ts +21 -0
- package/dist/core/runtime-caps.js +5 -1
- package/dist/core/task-event.d.ts +11 -2
- package/dist/core/task-registry-shared.js +8 -6
- package/dist/core/task-result.d.ts +15 -0
- package/dist/core/task-spec.d.ts +8 -2
- package/dist/core/terminal-cause.d.ts +6 -2
- package/dist/core/tool-policy.d.ts +34 -28
- package/dist/core/tool-policy.js +29 -5
- package/dist/core/tool-roster.js +2 -0
- package/dist/core/tool-spec.d.ts +20 -16
- package/dist/core/types.d.ts +3 -2
- package/dist/core/types.js +1 -0
- package/dist/core/wiring-manifest.d.ts +16 -9
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +5 -4
- package/dist/orchestration/run-workflow-tool.d.ts +8 -8
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +4 -3
- package/dist/orchestration/workflow-primitives.js +3 -3
- package/dist/orchestration/workflow-types.d.ts +14 -0
- package/dist/orchestration/workflow.d.ts +41 -4
- package/dist/orchestration/workflow.js +23 -5
- package/dist/tools/fs/fs-bash.d.ts +41 -0
- package/dist/tools/fs/fs-bash.js +86 -29
- package/dist/tools/fs/fs-shared.js +9 -3
- package/dist/tools/fs/read-deny.d.ts +24 -8
- package/dist/tools/fs/read-deny.js +20 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +29 -1
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { AgentTool } from "../../internal/harness.js";
|
|
20
20
|
import { type OnQuestion } from "../ask-question.js";
|
|
21
|
-
import {
|
|
21
|
+
import type { CheckpointStore } from "../checkpoint-store.js";
|
|
22
22
|
import type { RunnerDeps, RuntimeCaps, TaskSpec } from "../types.js";
|
|
23
23
|
import type { PrepareResume } from "./contracts.js";
|
|
24
24
|
import { type ContentAskBinding } from "./content-ask-bindings.js";
|
|
@@ -28,13 +28,15 @@ export interface PrepareQuestionFaceInput {
|
|
|
28
28
|
/** borrowed-readonly — the run's frozen question seat (spec > deps), resolved once at the top of prepare;
|
|
29
29
|
* the mount predicate and the live-face derivation both read this one value. */
|
|
30
30
|
frozenOnQuestion: OnQuestion | undefined;
|
|
31
|
-
/** borrowed-readonly — the REBOUND spec. Read: `
|
|
32
|
-
* (the
|
|
33
|
-
*
|
|
34
|
-
spec: Pick<TaskSpec, "
|
|
35
|
-
/** borrowed-readonly — the deployment
|
|
36
|
-
*
|
|
37
|
-
deps: Pick<RunnerDeps, "
|
|
31
|
+
/** borrowed-readonly — the REBOUND spec. Read: `durableApproval` (the durable face's opt-in), `interactiveTools`
|
|
32
|
+
* (the override), `principal` (the tool's source identity), `interactiveQuestionFallback` (restores the
|
|
33
|
+
* continuation under a resolved posture). Never mutated. */
|
|
34
|
+
spec: Pick<TaskSpec, "durableApproval" | "interactiveTools" | "principal" | "interactiveQuestionFallback">;
|
|
35
|
+
/** borrowed-readonly — the deployment seat `onError` (the stranded-answer and synthetic-continuation operator
|
|
36
|
+
* lines; swallow-guarded). */
|
|
37
|
+
deps: Pick<RunnerDeps, "onError">;
|
|
38
|
+
/** borrowed-readonly — THE leg's checkpoint seat (minted once by the driver after the config doors); the mount face, the routability predicate, the manifest and suspendAsk all read this one value through the result. */
|
|
39
|
+
checkpointStore: CheckpointStore | undefined;
|
|
38
40
|
/** borrowed-readonly — the principal's resolved runtime caps as of this point (`forceDurableGate` is the
|
|
39
41
|
* mandate that outranks a live face only where a store exists to park the question). */
|
|
40
42
|
runtimeCaps: Pick<RuntimeCaps, "forceDurableGate"> | undefined;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { createAskUserQuestionTool, isLiveQuestionFace } from "../ask-question.js";
|
|
2
2
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
3
|
-
import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
4
3
|
import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
|
|
5
4
|
import { RosterBuilder } from "../tool-roster.js";
|
|
6
5
|
export { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
|
|
7
6
|
export function prepareQuestionFace(input) {
|
|
8
|
-
const { frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks } = input;
|
|
7
|
+
const { frozenOnQuestion, spec, deps, checkpointStore, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks } = input;
|
|
9
8
|
const tools = roster.tools;
|
|
10
9
|
const onQuestion = frozenOnQuestion;
|
|
11
10
|
const liveQuestionFace = isLiveQuestionFace(onQuestion) ? onQuestion : undefined;
|
|
12
11
|
const contentAskBindings = new Map();
|
|
13
|
-
const checkpointStore = resolveCheckpointStore(spec, deps);
|
|
14
12
|
const questionParkStoreWired = checkpointStore !== undefined;
|
|
15
13
|
const questionDurableMandateBinding = runtimeCaps?.forceDurableGate === true && questionParkStoreWired;
|
|
16
14
|
const contentAskRoutable = (toolCallId) => liveQuestionFace !== undefined &&
|
|
@@ -23,7 +23,7 @@ export function prepareSafetyScan(input) {
|
|
|
23
23
|
...(typeof t.modelGate === "string" ? { modelGate: t.modelGate } : {}),
|
|
24
24
|
});
|
|
25
25
|
if (bound !== undefined) {
|
|
26
|
-
const e = new Error(`
|
|
26
|
+
const e = new Error(`A caller tool cannot ride a roster row: ${bound.message}.`);
|
|
27
27
|
e.code = bound.code;
|
|
28
28
|
throw e;
|
|
29
29
|
}
|
|
@@ -68,12 +68,14 @@ import { defaultTaskRegistry } from "../task-registry.js";
|
|
|
68
68
|
import { announcePeerLaneMount } from "../../agents/peer-session-drain.js";
|
|
69
69
|
import { createSchedulerTools } from "../../tools/scheduler-tools.js";
|
|
70
70
|
import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
|
|
71
|
-
import { remainingBudgetMicroUsd } from "../checkpoint-store.js";
|
|
71
|
+
import { remainingBudgetMicroUsd, resolveCheckpointStore } from "../checkpoint-store.js";
|
|
72
|
+
import { checkpointSeatOf } from "../checkpoint-seat.js";
|
|
72
73
|
import { durableParkGapFor } from "../park-selfcheck.js";
|
|
73
74
|
import { deliverEngineNotice } from "../types.js";
|
|
74
75
|
import { ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
75
76
|
import { faceOf } from "../tool-roster.js";
|
|
76
77
|
import { catalogEntriesNamed, pathTargetOf } from "../tool-registry.js";
|
|
78
|
+
import { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
|
|
77
79
|
import { createStructuredProjector } from "./tool-output-projection.js";
|
|
78
80
|
function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId) {
|
|
79
81
|
if (compModel === undefined)
|
|
@@ -257,13 +259,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
257
259
|
runIdSink.runId = runId;
|
|
258
260
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
259
261
|
spec = doors.spec;
|
|
262
|
+
const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
|
|
260
263
|
const autoModeSeat = refuseInvalidAutoModeSeat(spec);
|
|
261
264
|
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
262
265
|
announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
|
|
263
266
|
const modelGateManifest = toolModelGateManifest(doors.modelGate);
|
|
264
267
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
265
|
-
let
|
|
266
|
-
let shellGatedMonitor = false;
|
|
268
|
+
let shellGatedTools = new Set();
|
|
267
269
|
refuseRequireExistingWithoutSession(spec);
|
|
268
270
|
const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
|
|
269
271
|
const sessionId = acquired.sessionId;
|
|
@@ -366,8 +368,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
366
368
|
let resolvedReadFace;
|
|
367
369
|
let readDenyAdditionsNormalized = [];
|
|
368
370
|
let handsLessResolvedFace;
|
|
369
|
-
const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
|
|
370
|
-
const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
|
|
371
|
+
const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
|
|
372
|
+
const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
|
|
371
373
|
const tools = roster.tools;
|
|
372
374
|
const pausedRef = {};
|
|
373
375
|
const remoteEnvFailures = [];
|
|
@@ -431,34 +433,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
431
433
|
const callIssuedAtRef = {};
|
|
432
434
|
const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
|
|
433
435
|
const memoryWriteGateRef = {};
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
|
|
439
|
-
handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
|
|
440
|
-
shellGatedBash = handsReadFace.shellGatedBash;
|
|
441
|
-
shellGatedMonitor = handsReadFace.shellGatedMonitor;
|
|
442
|
-
const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
|
|
443
|
-
const { checkpointStore, liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
|
|
444
|
-
if (spec.handsReadOnly !== true) {
|
|
445
|
-
const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
|
|
446
|
-
roster.mountAll(createSchedulerTools(executionEnv, {
|
|
447
|
-
scope: sessionScope,
|
|
448
|
-
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
449
|
-
...(sessionId !== undefined ? { sessionId } : {}),
|
|
450
|
-
requestStopAfterTurn,
|
|
451
|
-
}).map((t) => (envHandToolNames.add(t.name), t)), { source: "builtin", mountedBy: "scheduler" });
|
|
452
|
-
}
|
|
453
|
-
if (internals?.insideFork !== true &&
|
|
454
|
-
runnerSelf &&
|
|
455
|
-
sessionId !== undefined &&
|
|
456
|
-
hasSessionFork(runnerSelf.sessions) &&
|
|
457
|
-
agentForkDenial === "principal") {
|
|
458
|
-
deps.onError?.(new Error(`Fork DENIED for principal "${spec.principal ?? ""}" by runtimeCaps.allowFork=false ` +
|
|
459
|
-
`(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
|
|
460
|
-
}
|
|
461
|
-
const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
|
|
436
|
+
const advertisedWritableDirs = handsEnabled
|
|
437
|
+
? new AdvertisedWritableDirs(executionEnv, (dir, reason) => deps.onError?.(new Error(`${dir.advertiser} advertised writable directory skipped (cannot canonicalize): ${dir.path} — ${reason}`), { phase: "config", sessionId }))
|
|
438
|
+
: undefined;
|
|
439
|
+
const handsWriteHalfPlanned = handsEnabled && spec.handsReadOnly !== true;
|
|
462
440
|
const memoryPairNameDomain = new Set();
|
|
463
441
|
for (const t of tools) {
|
|
464
442
|
memoryPairNameDomain.add(t.name);
|
|
@@ -477,14 +455,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
477
455
|
return { fault: true };
|
|
478
456
|
}
|
|
479
457
|
})();
|
|
480
|
-
const { memoryEngineSession,
|
|
458
|
+
const { memoryEngineSession, memoryTools, composeInjection: composeMemoryInjection, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
|
|
481
459
|
spec,
|
|
482
460
|
deps,
|
|
483
461
|
sessionId,
|
|
484
462
|
taskRootPath: taskRootFinal,
|
|
485
463
|
memoryWriteGateRef,
|
|
486
464
|
reminderMark,
|
|
487
|
-
writeToolsMounted: tools.some((t) => t.name === "Write") &&
|
|
465
|
+
writeToolsMounted: (handsWriteHalfPlanned || tools.some((t) => t.name === "Write")) &&
|
|
488
466
|
!(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
|
|
489
467
|
!(handsEnabled && isRemoteExecutionEnv(executionEnv) && spec.memoryPersistenceCapable !== true),
|
|
490
468
|
memoryProvenance,
|
|
@@ -503,20 +481,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
503
481
|
...(internals?.memoryCaptureQueryDir !== undefined ? { parentCaptureQueryDir: internals.memoryCaptureQueryDir } : {}),
|
|
504
482
|
...(internals?.memoryCaptureAncestors !== undefined ? { captureAncestors: internals.memoryCaptureAncestors } : {}),
|
|
505
483
|
memoryPersistenceDeclared: spec.memoryPersistenceCapable,
|
|
506
|
-
|
|
507
|
-
tools.some((t) => {
|
|
508
|
-
if (toolFaceSnapshot.exclude?.includes(t.name) ?? false)
|
|
509
|
-
return false;
|
|
510
|
-
const effect = (t.effect ?? toolEffects.get(t.name) ?? "write");
|
|
511
|
-
if (effect === "read")
|
|
512
|
-
return false;
|
|
513
|
-
const face = faceOf(t);
|
|
514
|
-
const pt = face.pathTarget ?? pathTargetOf(t.name);
|
|
515
|
-
const family = face.family ?? catalogEntriesNamed(t.name)[0]?.face.family;
|
|
516
|
-
if (!((pt !== undefined && pt.access !== "read") || family === "shell"))
|
|
517
|
-
return false;
|
|
518
|
-
return !(handsEnabled && isRemoteExecutionEnv(executionEnv));
|
|
519
|
-
}),
|
|
484
|
+
...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}),
|
|
520
485
|
memorySearchToolsPlanned,
|
|
521
486
|
admissionCtx: {
|
|
522
487
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
@@ -540,6 +505,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
540
505
|
if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
|
|
541
506
|
internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
|
|
542
507
|
}
|
|
508
|
+
const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
|
|
509
|
+
const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
|
|
510
|
+
const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
|
|
511
|
+
resolvedReadFace = handsReadFace.resolvedReadFace;
|
|
512
|
+
readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
|
|
513
|
+
handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
|
|
514
|
+
shellGatedTools = handsReadFace.shellGatedTools;
|
|
515
|
+
const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
|
|
516
|
+
const { liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, checkpointStore, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
|
|
517
|
+
if (spec.handsReadOnly !== true) {
|
|
518
|
+
const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
|
|
519
|
+
roster.mountAll(createSchedulerTools(executionEnv, {
|
|
520
|
+
scope: sessionScope,
|
|
521
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
522
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
523
|
+
requestStopAfterTurn,
|
|
524
|
+
}).map((t) => (envHandToolNames.add(t.name), t)), { source: "builtin", mountedBy: "scheduler" });
|
|
525
|
+
}
|
|
526
|
+
if (internals?.insideFork !== true &&
|
|
527
|
+
runnerSelf &&
|
|
528
|
+
sessionId !== undefined &&
|
|
529
|
+
hasSessionFork(runnerSelf.sessions) &&
|
|
530
|
+
agentForkDenial === "principal") {
|
|
531
|
+
deps.onError?.(new Error(`Fork DENIED for principal "${spec.principal ?? ""}" by runtimeCaps.allowFork=false ` +
|
|
532
|
+
`(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
|
|
533
|
+
}
|
|
534
|
+
const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
|
|
543
535
|
if (internals?.delegationProvenance !== undefined && resume !== undefined) {
|
|
544
536
|
const live = internals.delegationProvenance.ref.current;
|
|
545
537
|
const seed = resume.seed.delegationProvenance;
|
|
@@ -553,6 +545,21 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
553
545
|
live.incomplete = true;
|
|
554
546
|
}
|
|
555
547
|
}
|
|
548
|
+
const rosterCanPersist = spec.memoryPersistenceCapable ??
|
|
549
|
+
tools.some((t) => {
|
|
550
|
+
if (toolFaceSnapshot.exclude?.includes(t.name) ?? false)
|
|
551
|
+
return false;
|
|
552
|
+
const effect = (t.effect ?? toolEffects.get(t.name) ?? "write");
|
|
553
|
+
if (effect === "read")
|
|
554
|
+
return false;
|
|
555
|
+
const face = faceOf(t);
|
|
556
|
+
const pt = face.pathTarget ?? pathTargetOf(t.name);
|
|
557
|
+
const family = face.family ?? catalogEntriesNamed(t.name)[0]?.face.family;
|
|
558
|
+
if (!((pt !== undefined && pt.access !== "read") || family === "shell"))
|
|
559
|
+
return false;
|
|
560
|
+
return !(handsEnabled && isRemoteExecutionEnv(executionEnv));
|
|
561
|
+
});
|
|
562
|
+
const { memoryBlock: memoryBlockFromEngine, recallDisciplineSegment: memoryRecallSegment, seedFiles: memorySeedFiles } = composeMemoryInjection({ rosterCanPersist });
|
|
556
563
|
if (memorySeedFiles?.length && seedContextFiles) {
|
|
557
564
|
try {
|
|
558
565
|
await seedContextFiles(memorySeedFiles);
|
|
@@ -563,7 +570,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
563
570
|
}
|
|
564
571
|
const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume });
|
|
565
572
|
const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
|
|
566
|
-
const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef } = await preparePromptInputs({ spec, deps, failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId });
|
|
573
|
+
const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef } = await preparePromptInputs({ spec, deps, failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
|
|
567
574
|
const { deferred, memoryBlock } = prepareDeferClassify({ spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
|
|
568
575
|
prepareMemoryEngineSession({ memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef });
|
|
569
576
|
const { systemPromptSeat, renderWithDate, systemBlocks, promptManifest, epochDeclaredSections, epochArtifactDigestForSnapshot, buildAssembleInputs, centerAdoptionRef } = await preparePromptAssembly({ session, deps, internals, resume, provider, userSystemPrompt, userAppendSystemPrompt, featureFlags, mcpInstructionsBlock, memoryBlock, envFacts, promptProfile, fableMitigations, tools, model, sessionId, reminderMark, onceLedger });
|
|
@@ -674,7 +681,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
674
681
|
releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
|
|
675
682
|
}
|
|
676
683
|
}
|
|
677
|
-
const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames,
|
|
684
|
+
const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedTools, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
|
|
678
685
|
const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster });
|
|
679
686
|
if (fileHistoryBoundary !== undefined) {
|
|
680
687
|
harness.on("tool_call", async () => {
|
|
@@ -685,8 +692,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
685
692
|
const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash });
|
|
686
693
|
const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
|
|
687
694
|
const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
|
|
688
|
-
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures,
|
|
689
|
-
prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController,
|
|
695
|
+
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
|
|
696
|
+
prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
|
|
690
697
|
const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef });
|
|
691
698
|
const preparedHolder = {};
|
|
692
699
|
const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, readDenyAdditionsNormalized, preparedHolder });
|
|
@@ -30,7 +30,7 @@ import type { GateOutcome } from "../gate-outcome.js";
|
|
|
30
30
|
import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
|
|
31
31
|
import { type UsageWindow } from "../usage-window-store.js";
|
|
32
32
|
import type { ToolRoster } from "../tool-roster.js";
|
|
33
|
-
import { type
|
|
33
|
+
import { type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry } from "../wiring-manifest.js";
|
|
34
34
|
import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGovernance } from "./contracts.js";
|
|
35
35
|
export interface PrepareWiringManifestInput {
|
|
36
36
|
/** borrowed-readonly — the REBOUND spec. Read: `onAsk` (the own seat), `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
|
|
@@ -107,7 +107,7 @@ export interface PrepareWiringManifestInput {
|
|
|
107
107
|
* source of the hand band's write-capable names for the ungated-write warning (what actually MOUNTED). */
|
|
108
108
|
toolRoster: ToolRoster;
|
|
109
109
|
/** borrowed-readonly — the arming outcome the caps phase minted (the manifest's auto-mode face). */
|
|
110
|
-
autoModeArmReason:
|
|
110
|
+
autoModeArmReason: AutoModeArmFact;
|
|
111
111
|
/** borrowed-readonly — the frozen preflight; only `mcp` (the declared servers the manifest face joins). */
|
|
112
112
|
lockedPreflight: Pick<LockedPreflight, "mcp">;
|
|
113
113
|
/** borrowed-mutable — the peer lane's late-bound facts. Writer here: `askEffective` ← the manifest's ask derivation (the
|
|
@@ -199,7 +199,7 @@ export function prepareWiringManifest(input) {
|
|
|
199
199
|
memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
|
|
200
200
|
retentionPolicyWired: deps.retentionPolicy !== undefined,
|
|
201
201
|
...(modelGateManifest !== undefined ? { modelGate: modelGateManifest } : {}),
|
|
202
|
-
autoMode: {
|
|
202
|
+
autoMode: { ...autoModeArmReason },
|
|
203
203
|
mcp: mcpManifestEntries(lockedPreflight.mcp, mcp.statuses),
|
|
204
204
|
tools: toolRoster,
|
|
205
205
|
});
|
|
@@ -11,6 +11,8 @@ export function runCompactionMachinery(input) {
|
|
|
11
11
|
const u = msg?.usage;
|
|
12
12
|
if (!u)
|
|
13
13
|
return;
|
|
14
|
+
if (msg.usageMissing === true)
|
|
15
|
+
stats.usageMissing = true;
|
|
14
16
|
const fam = cacheFamilyOf(m);
|
|
15
17
|
const price = runner.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
16
18
|
const priced = isModelPriced(m, runner.deps.pricing);
|
|
@@ -77,8 +77,10 @@ export function createHarnessHandlers(input) {
|
|
|
77
77
|
rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
|
|
78
78
|
}
|
|
79
79
|
if (m.role === "assistant" && "usage" in m && m.usage) {
|
|
80
|
-
if (m.usageMissing)
|
|
80
|
+
if (m.usageMissing) {
|
|
81
81
|
rs.turn.turnUsageMissing = true;
|
|
82
|
+
stats.usageMissing = true;
|
|
83
|
+
}
|
|
82
84
|
const u = m.usage;
|
|
83
85
|
stats.tokens += u.totalTokens || 0;
|
|
84
86
|
const { totalInputTokens: turnTotal, uncachedInputTokens: turnUncached, costMicroUsd: turnCostMicroUsd, } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
|
|
@@ -83,7 +83,7 @@ export function streamSettleBackstop(input) {
|
|
|
83
83
|
? { retryAfterMs: hinted }
|
|
84
84
|
: {};
|
|
85
85
|
})(),
|
|
86
|
-
stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0 },
|
|
86
|
+
stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0, usageMissing: true },
|
|
87
87
|
};
|
|
88
88
|
emitTrace(entryTracer.tracer, () => ({
|
|
89
89
|
kind: "task.end",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Model } from "../../internal/llm.js";
|
|
2
|
+
import type { ToolSpec } from "../types.js";
|
|
3
|
+
import type { ToolFingerprintInput } from "../cache-break-detector.js";
|
|
4
|
+
/**
|
|
5
|
+
* The deferral GATE (design/36): which mounted tool names ship as placeholders on a request. Pure
|
|
6
|
+
* registry logic over names and sizes — no harness, no I/O. `tool-disclosure.ts` owns the placeholder
|
|
7
|
+
* and ToolSearch mechanics that act on the set this module decides; `prepare-defer-classify.ts` is the
|
|
8
|
+
* phase that assembles the inputs from the mounted face.
|
|
9
|
+
*/
|
|
10
|
+
/** The deferral gate's threshold: the engine's candidate set is deferred when its inlined size reaches
|
|
11
|
+
* this fraction of the model's context window (that set measured, never the rest of the face). */
|
|
12
|
+
export declare const DEFER_THRESHOLD_FRACTION = 0.1;
|
|
13
|
+
/** The typed refusal a `deferMode` outside its declared domain (`"auto"` or absent) raises at the classifier —
|
|
14
|
+
* a bad value never selects an arm silently. */
|
|
15
|
+
export declare const DEFER_MODE_INVALID_CODE = "config.defer_mode_invalid";
|
|
16
|
+
/** The deferral gate's verdict — the two deferral lists, what the gate measured, and whether it opened. */
|
|
17
|
+
export interface DeferralGate {
|
|
18
|
+
/** The caller's LITERAL list: `TaskSpec.deferTools` names mounted on this face, minus the inline pins.
|
|
19
|
+
* Deferred on every request, whatever the gate says — an intentional deferral is not a size question. */
|
|
20
|
+
explicit: Set<string>;
|
|
21
|
+
/** The engine's SELF-SELECTED candidates: `ToolSpec.defer === true`, every remote-protocol name, built-ins
|
|
22
|
+
* that declare their own `defer`, and — under `deferMode: "auto"` — every caller spec; minus the inline
|
|
23
|
+
* pins and minus `explicit`. Deferred iff the gate opens. */
|
|
24
|
+
gated: Set<string>;
|
|
25
|
+
/** Σ inlined chars (name + description + serialized schema) over `gated` — the bytes the gate could reclaim. */
|
|
26
|
+
gatedChars: number;
|
|
27
|
+
/** `floor(contextTokens × fraction) × CHARS_PER_TOKEN`; the char reading of the token threshold. */
|
|
28
|
+
thresholdChars: number;
|
|
29
|
+
/** Whether `gated` is deferred on this request: `gatedChars ≥ thresholdChars`. */
|
|
30
|
+
open: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deferral has TWO rules, and only two.
|
|
34
|
+
*
|
|
35
|
+
* ① THE CALLER'S LIST IS LITERAL. `TaskSpec.deferTools` (arriving as `callerDeferNames`, pre-filtered to
|
|
36
|
+
* mounted wire names, built-ins included) is deferred on every request; `alwaysLoad` (`ToolSpec.alwaysLoad`,
|
|
37
|
+
* `alwaysLoadNames`) is inlined on every request and is judged FIRST — a pinned name is in neither list
|
|
38
|
+
* and is never measured. Neither depends on the gate: they are the deployment's stated intent.
|
|
39
|
+
*
|
|
40
|
+
* ② THE ENGINE'S OWN CANDIDATES SIT BEHIND ONE THRESHOLD GATE. The candidates are declared: explicit
|
|
41
|
+
* `ToolSpec.defer === true` on a caller spec, every REMOTE-PROTOCOL tool name (MCP servers AND A2A peers —
|
|
42
|
+
* the caller unions them), and `builtinDeferNames` (mounted BUILT-INS that declare their own `defer`; a
|
|
43
|
+
* built-in ToolSpec never reaches `specs`, so its declaration arrives as a mounted name). `deferMode:
|
|
44
|
+
* "auto"` WIDENS the candidate set to every caller spec — undeclared caller tools included — and nothing
|
|
45
|
+
* else: it is not a second gate, and engine built-ins the deployment did not name are candidates under no
|
|
46
|
+
* setting. The gate decides the set all or nothing: deferred iff `gatedChars ≥ thresholdChars`, where the
|
|
47
|
+
* threshold is {@link DEFER_THRESHOLD_FRACTION} of the model's context window (`contextTokens ??
|
|
48
|
+
* contextWindow`, in chars at {@link CHARS_PER_TOKEN}). The measure is the candidate set ONLY — the bytes
|
|
49
|
+
* the gate could reclaim — never the caller's list, the pins, or non-candidates: a request whose weight
|
|
50
|
+
* sits elsewhere gains nothing from a ToolSearch round-trip, so nothing about it argues for one. A window
|
|
51
|
+
* of zero reads as a zero threshold (every candidate byte is over budget), so the candidates defer.
|
|
52
|
+
*
|
|
53
|
+
* Invariants: `explicit ∩ gated = ∅`; `deferred = explicit ∪ (open ? gated : ∅)`; a pinned name is in no
|
|
54
|
+
* list; the same inputs always yield the same verdict (a pure function of the request).
|
|
55
|
+
*/
|
|
56
|
+
export declare function deferralGate(opts: {
|
|
57
|
+
specs: ReadonlyArray<ToolSpec>;
|
|
58
|
+
/** Every namespaced tool a remote protocol materialized for this task (MCP servers AND A2A peers —
|
|
59
|
+
* the caller unions them). The remote-roster shape (many tools, schemas the deployment does not
|
|
60
|
+
* control, drift between tasks) is the same for every protocol, so every protocol's names are
|
|
61
|
+
* candidates by being remote. Only an inline pin (or the caller's own list) takes a name out. */
|
|
62
|
+
protocolToolNames: ReadonlyArray<string>;
|
|
63
|
+
/** The MOUNTED face — the tools whose full definitions the request would carry. The measure reads
|
|
64
|
+
* each candidate's inlined size from here; a candidate absent from it weighs nothing. */
|
|
65
|
+
fullTools: ReadonlyArray<ToolFingerprintInput>;
|
|
66
|
+
/** `"auto"` widens the candidate set to every caller spec (see above). Any other value refuses. */
|
|
67
|
+
deferMode?: "auto";
|
|
68
|
+
model: Model;
|
|
69
|
+
/** Rule ① — `TaskSpec.deferTools`: ALREADY-MOUNTED wire names (built-ins included; `ToolSpec.defer`
|
|
70
|
+
* only reaches caller specs) the caller defers on every request. Caller pre-filters to mounted names. */
|
|
71
|
+
callerDeferNames?: ReadonlyArray<string>;
|
|
72
|
+
/** Rule ② — mounted BUILT-INS that declare their own `defer` (the engine's declaration, not the
|
|
73
|
+
* caller's), so they are candidates. Caller pre-filters to mounted names. */
|
|
74
|
+
builtinDeferNames?: ReadonlyArray<string>;
|
|
75
|
+
/** Rule ① — inline-pin names (`TaskSpec.alwaysLoadTools` + the MCP tools whose server declared
|
|
76
|
+
* `_meta["anthropic/alwaysLoad"]`, caller-collected; A2A has no such per-skill declaration, so the
|
|
77
|
+
* task-level list is a peer's only inline-keep channel): exempt from BOTH lists.
|
|
78
|
+
* `ToolSpec.alwaysLoad` rides the specs directly and needs no entry here. */
|
|
79
|
+
alwaysLoadNames?: ReadonlyArray<string>;
|
|
80
|
+
}): DeferralGate;
|
|
81
|
+
/**
|
|
82
|
+
* Decide which tool names are deferred: the caller's literal list, plus the gated candidates when the gate
|
|
83
|
+
* opens. THIS CONTRACT (with {@link deferralGate}) IS THE SINGLE AUTHORITY on the deferral sources and the
|
|
84
|
+
* gate; the `TaskSpec` / `RunnerDeps` JSDoc points here.
|
|
85
|
+
*/
|
|
86
|
+
export declare function classifyDeferred(opts: Parameters<typeof deferralGate>[0]): Set<string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { DEFAULT_CHARS_PER_TOKEN } from "../../internal/harness.js";
|
|
2
|
+
export const DEFER_THRESHOLD_FRACTION = 0.1;
|
|
3
|
+
const CHARS_PER_TOKEN = DEFAULT_CHARS_PER_TOKEN;
|
|
4
|
+
function inlinedChars(t) {
|
|
5
|
+
let schema = "";
|
|
6
|
+
try {
|
|
7
|
+
schema = JSON.stringify(t.parameters) ?? "";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
schema = "";
|
|
11
|
+
}
|
|
12
|
+
return t.name.length + t.description.length + schema.length;
|
|
13
|
+
}
|
|
14
|
+
export const DEFER_MODE_INVALID_CODE = "config.defer_mode_invalid";
|
|
15
|
+
export function deferralGate(opts) {
|
|
16
|
+
if (opts.deferMode !== undefined && opts.deferMode !== "auto") {
|
|
17
|
+
const e = new Error(`RunnerDeps.deferMode: invalid value ${JSON.stringify(opts.deferMode)} — expected "auto" or omitted; a bad value refuses loudly, nothing falls back to a default.`);
|
|
18
|
+
e.code = DEFER_MODE_INVALID_CODE;
|
|
19
|
+
throw e;
|
|
20
|
+
}
|
|
21
|
+
const pinned = new Set(opts.alwaysLoadNames ?? []);
|
|
22
|
+
for (const s of opts.specs) {
|
|
23
|
+
if (s.alwaysLoad === true)
|
|
24
|
+
pinned.add(s.name);
|
|
25
|
+
}
|
|
26
|
+
const explicit = new Set();
|
|
27
|
+
for (const name of opts.callerDeferNames ?? [])
|
|
28
|
+
if (!pinned.has(name))
|
|
29
|
+
explicit.add(name);
|
|
30
|
+
const gated = new Set();
|
|
31
|
+
const select = (name) => {
|
|
32
|
+
if (!pinned.has(name) && !explicit.has(name))
|
|
33
|
+
gated.add(name);
|
|
34
|
+
};
|
|
35
|
+
for (const s of opts.specs)
|
|
36
|
+
if (s.defer === true || opts.deferMode === "auto")
|
|
37
|
+
select(s.name);
|
|
38
|
+
for (const name of opts.protocolToolNames)
|
|
39
|
+
select(name);
|
|
40
|
+
for (const name of opts.builtinDeferNames ?? [])
|
|
41
|
+
select(name);
|
|
42
|
+
let gatedChars = 0;
|
|
43
|
+
for (const t of opts.fullTools)
|
|
44
|
+
if (gated.has(t.name))
|
|
45
|
+
gatedChars += inlinedChars(t);
|
|
46
|
+
const windowTokens = Math.max(0, opts.model.contextTokens ?? opts.model.contextWindow ?? 0);
|
|
47
|
+
const thresholdChars = Math.floor(windowTokens * DEFER_THRESHOLD_FRACTION) * CHARS_PER_TOKEN;
|
|
48
|
+
return { explicit, gated, gatedChars, thresholdChars, open: gatedChars >= thresholdChars };
|
|
49
|
+
}
|
|
50
|
+
export function classifyDeferred(opts) {
|
|
51
|
+
const gate = deferralGate(opts);
|
|
52
|
+
const deferred = new Set(gate.explicit);
|
|
53
|
+
if (gate.open)
|
|
54
|
+
for (const n of gate.gated)
|
|
55
|
+
deferred.add(n);
|
|
56
|
+
return deferred;
|
|
57
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { type TSchema } from "typebox";
|
|
2
2
|
import type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode } from "../../internal/harness-types.js";
|
|
3
|
-
import type { Model } from "../../internal/llm.js";
|
|
4
|
-
import type { ToolSpec } from "../types.js";
|
|
5
|
-
import type { ToolFingerprintInput } from "../cache-break-detector.js";
|
|
6
3
|
/**
|
|
7
4
|
* Deferred-tool dynamic disclosure (design/36). A deployment with hundreds of tools / several MCP
|
|
8
5
|
* servers can't inline every full JSON Schema into every request — turn-1 cache-creation tokens
|
|
@@ -56,39 +53,6 @@ export declare function deferHint(description: string, max?: number): string;
|
|
|
56
53
|
* announcement, anywhere. A bare interpolation is a prompt-injection hole (council BUG, design/36).
|
|
57
54
|
*/
|
|
58
55
|
export declare function safeName(name: string): string;
|
|
59
|
-
/**
|
|
60
|
-
* Decide which tool names are deferred. THIS CONTRACT IS THE SINGLE AUTHORITY on the default-mode
|
|
61
|
-
* source list (TaskSpec/RunnerDeps JSDoc points here), so every arm the body implements is named:
|
|
62
|
-
* deterministic by default = explicit `ToolSpec.defer === true`, plus every REMOTE-PROTOCOL tool name
|
|
63
|
-
* (MCP servers AND A2A peers — most numerous / most drift-prone), plus `deferNames`, i.e. the
|
|
64
|
-
* already-mounted wire names from `TaskSpec.deferTools` and from BUILT-INS that declare their own
|
|
65
|
-
* `defer` (a built-in ToolSpec never reaches `specs`, so its declared deferral has to arrive here as a
|
|
66
|
-
* mounted name — this is why the shared-memory pair defaults to deferred). With `deferMode: "auto"`, ADDITIONALLY
|
|
67
|
-
* defer the remaining user tools when their combined inlined schema would exceed ~10% of the model's
|
|
68
|
-
* context window — an explicit opt-in (council minor #6), because an implicit threshold makes the same
|
|
69
|
-
* TaskSpec yield different tools[] across models/contexts. Explicit `defer` always wins — except
|
|
70
|
-
* against an alwaysLoad pin (RB-400-a), which exempts a name from every deferral source.
|
|
71
|
-
*/
|
|
72
|
-
export declare function classifyDeferred(opts: {
|
|
73
|
-
specs: ReadonlyArray<ToolSpec>;
|
|
74
|
-
/** Every namespaced tool a remote protocol materialized for this task (MCP servers AND A2A peers —
|
|
75
|
-
* the caller unions them). The constant-defer arm is about the SHAPE of a remote roster (many tools,
|
|
76
|
-
* schemas the deployment does not control, drift between tasks), which is the same for every protocol
|
|
77
|
-
* in the table: naming one protocol here left the next one inlined by omission, with nobody deciding
|
|
78
|
-
* that. Only an alwaysLoad pin exempts a name. */
|
|
79
|
-
protocolToolNames: ReadonlyArray<string>;
|
|
80
|
-
fullTools: ReadonlyArray<ToolFingerprintInput>;
|
|
81
|
-
deferMode?: "auto";
|
|
82
|
-
model: Model;
|
|
83
|
-
/** TaskSpec.deferTools — defer ALREADY-MOUNTED tools by wire name (built-ins included;
|
|
84
|
-
* `ToolSpec.defer` only reaches caller specs). Caller pre-filters to mounted names. */
|
|
85
|
-
deferNames?: ReadonlyArray<string>;
|
|
86
|
-
/** RB-400-a — inline-pin names (`TaskSpec.alwaysLoadTools` + the MCP tools whose server declared
|
|
87
|
-
* `_meta["anthropic/alwaysLoad"]`, caller-collected; A2A has no such per-skill declaration, so the
|
|
88
|
-
* task-level list is a peer's only inline-keep channel): exempt from EVERY deferral source below.
|
|
89
|
-
* `ToolSpec.alwaysLoad` rides the specs directly and needs no entry here. */
|
|
90
|
-
alwaysLoadNames?: ReadonlyArray<string>;
|
|
91
|
-
}): Set<string>;
|
|
92
56
|
/** Build the registry the search ranks over (deferred tools only — non-deferred are already visible). */
|
|
93
57
|
export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, tools: ReadonlyArray<{
|
|
94
58
|
name: string;
|
|
@@ -2,11 +2,8 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { Value } from "typebox/value";
|
|
3
3
|
import { defineTool, errorResult } from "../tools.js";
|
|
4
4
|
import { formatZodValidationError, truncateError } from "../tool-errors.js";
|
|
5
|
-
import { DEFAULT_CHARS_PER_TOKEN } from "../../internal/harness.js";
|
|
6
5
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
7
6
|
export const TOOL_SEARCH_NAME = "ToolSearch";
|
|
8
|
-
const DEFER_AUTO_FRACTION = 0.1;
|
|
9
|
-
const CHARS_PER_TOKEN = DEFAULT_CHARS_PER_TOKEN;
|
|
10
7
|
export const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5;
|
|
11
8
|
const MAX_QUERY_RESULTS = 25;
|
|
12
9
|
export const DEFERRED_NO_PROGRESS_LIMIT = 3;
|
|
@@ -19,46 +16,6 @@ export function deferHint(description, max = 120) {
|
|
|
19
16
|
export function safeName(name) {
|
|
20
17
|
return name.replace(/[\x00-\x1f<>"`]/g, "");
|
|
21
18
|
}
|
|
22
|
-
function inlinedChars(t) {
|
|
23
|
-
let schema = "";
|
|
24
|
-
try {
|
|
25
|
-
schema = JSON.stringify(t.parameters) ?? "";
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
schema = "";
|
|
29
|
-
}
|
|
30
|
-
return t.description.length + schema.length;
|
|
31
|
-
}
|
|
32
|
-
export function classifyDeferred(opts) {
|
|
33
|
-
const pinned = new Set(opts.alwaysLoadNames ?? []);
|
|
34
|
-
for (const s of opts.specs) {
|
|
35
|
-
if (s.alwaysLoad === true)
|
|
36
|
-
pinned.add(s.name);
|
|
37
|
-
}
|
|
38
|
-
const deferred = new Set();
|
|
39
|
-
for (const s of opts.specs) {
|
|
40
|
-
if (s.defer === true && !pinned.has(s.name))
|
|
41
|
-
deferred.add(s.name);
|
|
42
|
-
}
|
|
43
|
-
for (const name of opts.protocolToolNames)
|
|
44
|
-
if (!pinned.has(name))
|
|
45
|
-
deferred.add(name);
|
|
46
|
-
for (const name of opts.deferNames ?? [])
|
|
47
|
-
if (!pinned.has(name))
|
|
48
|
-
deferred.add(name);
|
|
49
|
-
if (opts.deferMode === "auto") {
|
|
50
|
-
const callerNames = new Set(opts.specs.map((s) => s.name));
|
|
51
|
-
const candidates = opts.fullTools.filter((t) => !deferred.has(t.name) && !pinned.has(t.name) && callerNames.has(t.name));
|
|
52
|
-
const inlineFace = opts.fullTools.filter((t) => !deferred.has(t.name));
|
|
53
|
-
const total = inlineFace.reduce((n, t) => n + inlinedChars(t), 0);
|
|
54
|
-
const window = (opts.model.contextTokens ?? opts.model.contextWindow ?? 0) * CHARS_PER_TOKEN;
|
|
55
|
-
if (window > 0 && total > DEFER_AUTO_FRACTION * window) {
|
|
56
|
-
for (const t of candidates)
|
|
57
|
-
deferred.add(t.name);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return deferred;
|
|
61
|
-
}
|
|
62
19
|
export function buildDeferredRegistry(deferred, tools) {
|
|
63
20
|
const reg = new Map();
|
|
64
21
|
for (const t of tools) {
|