@sema-agent/core 7.43.0 → 7.45.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 +245 -0
- package/dist/agents/subagent.d.ts +2 -1
- package/dist/agents/subagent.js +33 -14
- package/dist/agents/tool-filter.d.ts +65 -4
- package/dist/agents/tool-filter.js +127 -4
- package/dist/brain/errors.d.ts +23 -2
- package/dist/brain/errors.js +26 -2
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +10 -9
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +25 -1
- package/dist/core/allow-entry.js +29 -4
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +10 -4
- package/dist/core/checkpoint-store.d.ts +28 -15
- package/dist/core/checkpoint-store.js +4 -13
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +7 -6
- package/dist/core/effective-path-target.js +3 -7
- package/dist/core/engine-notice.d.ts +33 -14
- package/dist/core/engine-notice.js +14 -7
- package/dist/core/gate-lanes.js +2 -0
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +4 -3
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +2 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +7 -14
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +5 -7
- package/dist/core/memory-engine/layout.d.ts +41 -4
- package/dist/core/memory-engine/layout.js +35 -5
- package/dist/core/permission-rules.d.ts +4 -0
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +13 -0
- package/dist/core/runner/child-tree.js +3 -0
- package/dist/core/runner/contracts.d.ts +9 -18
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +4 -2
- package/dist/core/runner/prepare-task.js +10 -6
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +3 -5
- package/dist/core/runner/steer-admission.d.ts +10 -11
- package/dist/core/runner/steer-admission.js +6 -8
- package/dist/core/runner/stream-steer-verb.d.ts +5 -4
- package/dist/core/runner/stream-steer-verb.js +8 -10
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/steer-replay.d.ts +46 -0
- package/dist/core/steer-replay.js +22 -0
- package/dist/core/task-event.d.ts +3 -2
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/task-stream.d.ts +12 -6
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +15 -4
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/tool-spec.d.ts +20 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/orchestration/workflow-types.d.ts +5 -0
- package/dist/orchestration/workflow.d.ts +3 -2
- package/dist/orchestration/workflow.js +16 -7
- package/dist/tools/fs/removal-targets.d.ts +82 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/shell-read-write-targets.d.ts +27 -0
- package/dist/tools/fs/shell-read-write-targets.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +43 -5
- package/wire-changes/core@7.44.0.json +469 -0
- package/wire-changes/core@7.45.0.json +1084 -0
|
@@ -26,6 +26,7 @@ import { AllowEntryTable, screenAllowedToolsValue } from "../allow-entry.js";
|
|
|
26
26
|
import { compileOutputSchema } from "./strict-output-schema.js";
|
|
27
27
|
import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
|
|
28
28
|
import { foldAdmissionFreeze } from "../memory-admission.js";
|
|
29
|
+
import { deploymentCanRerootChild } from "./child-tree.js";
|
|
29
30
|
import { prepareMemory } from "./prepare-memory.js";
|
|
30
31
|
import { prepareMemoryEngineSession } from "./prepare-memory-engine-session.js";
|
|
31
32
|
import { prepareDeferClassify } from "./prepare-defer-classify.js";
|
|
@@ -363,9 +364,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
363
364
|
e.code = "isolation.unavailable";
|
|
364
365
|
throw e;
|
|
365
366
|
};
|
|
366
|
-
if (
|
|
367
|
+
if (!deploymentCanRerootChild(deps)) {
|
|
367
368
|
failIsolation("no executionEnvFactory is configured (a static/shared execution env cannot mint a per-agent worktree)");
|
|
368
369
|
}
|
|
370
|
+
else if (ownedEnvSeat.current === undefined) {
|
|
371
|
+
failIsolation("the executionEnvFactory returned no env instead of a worktree-rooted env");
|
|
372
|
+
}
|
|
369
373
|
else if (ownedEnvSeat.current === deps.executionEnv) {
|
|
370
374
|
ownedEnvSeat.current = undefined;
|
|
371
375
|
failIsolation("the executionEnvFactory returned the shared static env instead of a worktree-rooted env");
|
|
@@ -733,7 +737,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
733
737
|
releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
|
|
734
738
|
}
|
|
735
739
|
}
|
|
736
|
-
const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
|
|
740
|
+
const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, readFace: () => resolvedReadFace, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
|
|
737
741
|
const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, askSeat: doors.askSeat, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: handsReadFace.shellReadStationArmed, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
|
|
738
742
|
const writeProtectionCheck = writeProtection?.check;
|
|
739
743
|
writeProtectionJudgeRef.current = writeProtection?.judgeTarget;
|
|
@@ -743,18 +747,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
743
747
|
return undefined;
|
|
744
748
|
});
|
|
745
749
|
}
|
|
746
|
-
const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking: autoModeDenialTrackingRef, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
|
|
750
|
+
const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, handsRoot: attachmentRootCanonical, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking: autoModeDenialTrackingRef, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
|
|
747
751
|
const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, skillGrantCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
|
|
748
752
|
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 });
|
|
749
|
-
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef });
|
|
753
|
+
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, handsRoot: attachmentRootCanonical, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef });
|
|
750
754
|
const seatInputsFor = skillGateWiring({ tools, skillToolName: SKILL_TOOL_NAME, seatOf: skillSeatOf, mode: () => permissionMode.current, lane: skillGrantLane, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.skill_allowed", version: 1, taskId: hostTaskId, ...info, entries: [...info.entries], ts: Date.now() })), allowEntries: { deploymentAllows: { ...allowEntryLanes.deployment, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "deployment", ...info, entries: [...info.entries], ts: Date.now() })) }, requestAllows: { ...allowEntryLanes.request, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "request", ...info, entries: [...info.entries], ts: Date.now() })) } } });
|
|
751
|
-
const { approvedCallScreen } = 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, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, seatInputsFor, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
|
|
755
|
+
const { approvedCallScreen } = 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, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, readFace: () => resolvedReadFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, seatInputsFor, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
|
|
752
756
|
const postureFrameRef = { report: (err) => deps.onError?.(err, { phase: "degraded", sessionId }) };
|
|
753
757
|
const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas: rosterSeat, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, contextOverlay: legContextOverlay, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
|
|
754
758
|
const preparedHolder = {};
|
|
755
759
|
const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, 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, skillGrants: skillGrantLane, callerPolicy: lockedPreflight.toolPolicy, inheritedPolicies: (inheritedParentConstraints ?? []).map((c) => c.policy), persistedRuleLane: permissionRuleLane, orgRuleLane: permissionRuleOrgLane, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
|
|
756
760
|
await onceLedger.settle(session, announcedListingsRef);
|
|
757
|
-
const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: askSeatEffective(wiringManifest) }, ...(outputStyleFrame !== undefined ? { outputStyleFrame } : {}), permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, skillGrants: skillGrantLane, skillSpecs, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}), ...(approvedCallScreen !== undefined ? { approvedCallScreen } : {}) });
|
|
761
|
+
const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: askSeatEffective(wiringManifest) }, ...(outputStyleFrame !== undefined ? { outputStyleFrame } : {}), permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(attachmentRootCanonical !== undefined ? { handsRoot: attachmentRootCanonical } : {}), toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, skillGrants: skillGrantLane, skillSpecs, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}), ...(approvedCallScreen !== undefined ? { approvedCallScreen } : {}) });
|
|
758
762
|
rollback.commit();
|
|
759
763
|
commitModeSeats(abortController.signal);
|
|
760
764
|
const prepared = buildPrepared();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
|
|
2
|
+
import { isHostDecider } from "../gate-outcome.js";
|
|
2
3
|
import { CHECKPOINT_FEATURES, CheckpointError, checkpointVersionOf, winnerFromOutcome } from "../checkpoint-store.js";
|
|
3
4
|
import { policyCannotAnswer, screenApproverAttribution } from "../tool-policy.js";
|
|
4
5
|
import { sanitizeUntrustedText } from "../untrusted-text.js";
|
|
@@ -52,7 +53,7 @@ export function resumePolicyOutcome(input) {
|
|
|
52
53
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries no hostDecision — a policy_ask decide names who decided it ({ decidedBy: "person" | "sla_timeout" | "policy", approver? }); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
53
54
|
}
|
|
54
55
|
const decidedBy = hostDecisionRaw.decidedBy;
|
|
55
|
-
if (decidedBy
|
|
56
|
+
if (!isHostDecider(decidedBy)) {
|
|
56
57
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume hostDecision.decidedBy "${describeSuppliedValue(decidedBy)}" is outside the closed set — it is exactly "person", "sla_timeout" or "policy"; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
57
58
|
}
|
|
58
59
|
const cannotAllow = decision !== "allow"
|
|
@@ -614,7 +614,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
614
614
|
}
|
|
615
615
|
const executedArgs = humanRewrite !== undefined ? resolvedArgs : screened !== undefined ? screened.args : resolvedArgs;
|
|
616
616
|
const unseen = !deepJsonEqual(executedArgs, pendingAction.args);
|
|
617
|
-
const beltReq = () => ({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)), budget: beltBudget, ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}), ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs) });
|
|
617
|
+
const beltReq = () => ({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)), budget: beltBudget, ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}), ...(prepared.handsRoot !== undefined ? { root: prepared.handsRoot } : {}), ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs) });
|
|
618
618
|
const unjudgeable = unseen ? chainCannotJudge(resume.cp.state.inheritedGate) : undefined;
|
|
619
619
|
if (unjudgeable !== undefined) {
|
|
620
620
|
const chainDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its input changed after the approval and the inherited ancestor chain cannot judge the changed input (${unjudgeable}). This approval is spent — the call has to be re-issued.`, prepared.reminderMark);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
|
|
2
2
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
3
|
-
import { createLiveNotificationLane, DurableAppendObligations, isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, routeOrParkNotification, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
3
|
+
import { createLiveNotificationLane, DurableAppendObligations, injectionLaneOf, isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, routeOrParkNotification, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
4
4
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
5
5
|
import { pendingContentAskCallId } from "./decide-continuation.js";
|
|
6
6
|
export function runNotificationLane(input) {
|
|
@@ -76,7 +76,7 @@ async function runNotificationLaneAs(input, obligations) {
|
|
|
76
76
|
engineNoteKey: taskNotificationDedupKey(item.payload),
|
|
77
77
|
...(item.payload.task_type !== "external" && isTerminalTaskNotification(item.payload) ? { capPreferred: true } : {}),
|
|
78
78
|
};
|
|
79
|
-
const deliver = item.priority === "
|
|
79
|
+
const deliver = injectionLaneOf(item.priority) === "followUp"
|
|
80
80
|
? notificationHarness.followUp(xml, noteOptions)
|
|
81
81
|
: notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) });
|
|
82
82
|
void deliver.then(() => item.onDisposition?.("queued"), () => {
|
|
@@ -68,6 +68,8 @@ export declare class Runner implements RunnerSelfSeat {
|
|
|
68
68
|
* thin compositions OVER the Runner (`runGoal`) can emit at their terminal state without reaching
|
|
69
69
|
* into private deps. */
|
|
70
70
|
emitTaskOutcome(outcome: TaskOutcome): void;
|
|
71
|
+
/** Whether a child this Runner runs can be placed in another tree: {@link RunnerSelfSeat.canRerootChild}. */
|
|
72
|
+
canRerootChild(): boolean;
|
|
71
73
|
/** The deployment agent catalog (RunnerDeps.agents/builtinAgents), exposed READ-ONLY
|
|
72
74
|
* so the Agent-tool lane (`createSubagentTool`) defaults to the SAME registry the workflow lane
|
|
73
75
|
* consumes (single source; the types.ts "pass the same array" follow-on made structural). The array
|
|
@@ -40,6 +40,7 @@ import { resumeClaim } from "./resume-claim.js";
|
|
|
40
40
|
import { streamSettleBackstop } from "./stream-settle-backstop.js";
|
|
41
41
|
import { streamReap } from "./stream-reap.js";
|
|
42
42
|
import { streamSteerVerb } from "./stream-steer-verb.js";
|
|
43
|
+
import { steeringError } from "./steer-admission.js";
|
|
43
44
|
import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
|
|
44
45
|
import { streamHaltVerbs } from "./stream-halt-verbs.js";
|
|
45
46
|
import { streamModeVerb } from "./stream-mode-verb.js";
|
|
@@ -61,6 +62,7 @@ import { runCompactionMachinery } from "./run-compaction-machinery.js";
|
|
|
61
62
|
import { runStopAndFinalVerify } from "./run-stop-and-final-verify.js";
|
|
62
63
|
import { runRecoveryLanes } from "./run-recovery-lanes.js";
|
|
63
64
|
import { runGitLane } from "./run-git-lane.js";
|
|
65
|
+
import { deploymentCanRerootChild } from "./child-tree.js";
|
|
64
66
|
import { thrownText } from "../thrown-value.js";
|
|
65
67
|
export class Runner {
|
|
66
68
|
deps;
|
|
@@ -124,6 +126,7 @@ export class Runner {
|
|
|
124
126
|
emitTaskOutcome(outcome) {
|
|
125
127
|
emitTaskOutcome(this.deps, outcome);
|
|
126
128
|
}
|
|
129
|
+
canRerootChild() { return deploymentCanRerootChild(this.deps); }
|
|
127
130
|
get agentCatalog() {
|
|
128
131
|
return {
|
|
129
132
|
...(this.deps.agents ? { agents: [...this.deps.agents] } : {}),
|
|
@@ -411,11 +414,6 @@ export class Runner {
|
|
|
411
414
|
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
|
|
412
415
|
}).onRunRejected);
|
|
413
416
|
void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
|
|
414
|
-
const steeringError = (msg, code = "steering.not_running") => {
|
|
415
|
-
const e = new Error(`cannot steer: ${msg}`);
|
|
416
|
-
e.code = code;
|
|
417
|
-
return e;
|
|
418
|
-
};
|
|
419
417
|
const { reapSuspended } = streamReap({
|
|
420
418
|
live, runner: this.depsSeat,
|
|
421
419
|
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an accepted input is stamped
|
|
3
|
+
* with, and the acceptance frame's once-only seat. Two accepting arms share the ordinal — the stream's `steer`
|
|
4
|
+
* verb and the run body — which is why it is keyed on the leg's harness in a module-level WeakMap rather than
|
|
5
|
+
* held by either arm: one counter per leg, one ascending sequence across both. Also the stream verbs' typed
|
|
6
|
+
* refusal. (Whether a repeat under a used `inputId` is the same instruction is steer-replay.ts's one judgement,
|
|
7
|
+
* shared with the parked queue.)
|
|
7
8
|
*
|
|
8
9
|
* Run-loop machinery (layer 1), not a driven lane: nothing here takes an Input or hands back a Result, and
|
|
9
10
|
* nothing here reads host state. Named without the `run-` family prefix for that reason.
|
|
10
11
|
*/
|
|
11
|
-
import type {
|
|
12
|
+
import type { SteerReplayRefusalDetail } from "../steer-replay.js";
|
|
12
13
|
export declare function nextHumanInputSeq(key: object): number;
|
|
13
14
|
/** The identity keys a published `queued` frame carried beyond its own `eventId` (`parentToolCallId`, `sourceTaskId`): one fact, stated
|
|
14
15
|
* once by the frame's publisher and handed to whoever mints that input's other frames, so two frames of one input are never stamped
|
|
@@ -19,8 +20,6 @@ export type AcceptanceIdentity = {
|
|
|
19
20
|
};
|
|
20
21
|
export declare function armAcceptance(record: object, publish: () => AcceptanceIdentity): void;
|
|
21
22
|
export declare function announceAcceptance(record: object): AcceptanceIdentity | undefined;
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
* callers colliding on one id must not silently lose the second instruction. */
|
|
26
|
-
export declare function sameAcceptedSteerInput(a: AcceptedSteerInput, b: AcceptedSteerInput): boolean;
|
|
23
|
+
/** The stream verbs' typed refusal: `cannot steer: <msg>`, `code` (`steering.not_running` by default), and the
|
|
24
|
+
* refusal's `detail` when the code carries one (`steering.duplicate_input_id` always does). */
|
|
25
|
+
export declare function steeringError(msg: string, code?: string, detail?: SteerReplayRefusalDetail): Error;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { steerPriorityOf } from "../task-notification.js";
|
|
2
1
|
const humanInputOrdinals = new WeakMap();
|
|
3
2
|
export function nextHumanInputSeq(key) {
|
|
4
3
|
let box = humanInputOrdinals.get(key);
|
|
@@ -16,11 +15,10 @@ export function armAcceptance(record, publish) {
|
|
|
16
15
|
export function announceAcceptance(record) {
|
|
17
16
|
return acceptanceSeats.get(record)?.();
|
|
18
17
|
}
|
|
19
|
-
export function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
a.actor?.issuer === b.actor?.issuer);
|
|
18
|
+
export function steeringError(msg, code = "steering.not_running", detail) {
|
|
19
|
+
const e = new Error(`cannot steer: ${msg}`);
|
|
20
|
+
e.code = code;
|
|
21
|
+
if (detail !== undefined)
|
|
22
|
+
e.detail = detail;
|
|
23
|
+
return e;
|
|
26
24
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PushQueue } from "../push-queue.js";
|
|
2
2
|
import { type TaskEvent, type TaskSpec, type TaskStream } from "../types.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { LiveHandle, RunInternals, RunnerDepsSeat, TaskStreamLiveSeat } from "./contracts.js";
|
|
4
|
+
import { type SteerReplayRecord, type SteerReplayRefusalDetail } from "../steer-replay.js";
|
|
4
5
|
export interface StreamSteerVerbInput {
|
|
5
6
|
/** borrowed-readonly — the task spec (the hooks seat, `principal`, `taskId`). */
|
|
6
7
|
spec: TaskSpec;
|
|
@@ -9,7 +10,7 @@ export interface StreamSteerVerbInput {
|
|
|
9
10
|
/** borrowed-mutable — the stream's event queue: the `human_input` receipt is pushed here on acceptance. */
|
|
10
11
|
queue: PushQueue<TaskEvent>;
|
|
11
12
|
/** borrowed-mutable — this stream's accepted-input dedup domain (the SAME Map the driver clears when the run settles). */
|
|
12
|
-
acceptedSteerInputs: Map<string,
|
|
13
|
+
acceptedSteerInputs: Map<string, SteerReplayRecord>;
|
|
13
14
|
/** borrowed-readonly — the stream's live state: `resultValue` and `handle`, read at each liveness test. */
|
|
14
15
|
live: TaskStreamLiveSeat;
|
|
15
16
|
/** borrowed-readonly — the readiness promise (settles to the live handle, or `undefined` when the run ended first). */
|
|
@@ -18,8 +19,8 @@ export interface StreamSteerVerbInput {
|
|
|
18
19
|
orTimeout: <T>(p: Promise<T>) => Promise<T | undefined>;
|
|
19
20
|
/** borrowed-readonly — the readiness bound in ms; the birth-window poll's deadline reads it too. */
|
|
20
21
|
readyTimeoutMs: number;
|
|
21
|
-
/** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default). */
|
|
22
|
-
steeringError: (msg: string, code?: string) => Error;
|
|
22
|
+
/** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default; `detail` rides the error). */
|
|
23
|
+
steeringError: (msg: string, code?: string, detail?: SteerReplayRefusalDetail) => Error;
|
|
23
24
|
/** borrowed-readonly — the Runner's deployment deps, read LIVE (`hooks`, `onError`, `onNotice`, after awaits too). */
|
|
24
25
|
runner: RunnerDepsSeat;
|
|
25
26
|
}
|
|
@@ -3,10 +3,11 @@ import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
|
3
3
|
import { MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js";
|
|
4
4
|
import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
5
5
|
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
6
|
-
import { isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
6
|
+
import { injectionLaneOf, isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
7
7
|
import { deliverEngineNotice } from "../types.js";
|
|
8
8
|
import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
9
|
-
import { announceAcceptance, armAcceptance, nextHumanInputSeq
|
|
9
|
+
import { announceAcceptance, armAcceptance, nextHumanInputSeq } from "./steer-admission.js";
|
|
10
|
+
import { steerReplayRefusal } from "../steer-replay.js";
|
|
10
11
|
import { asThrownError } from "../thrown-value.js";
|
|
11
12
|
export function streamSteerVerb(input) {
|
|
12
13
|
const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
|
|
@@ -43,7 +44,6 @@ export function streamSteerVerb(input) {
|
|
|
43
44
|
};
|
|
44
45
|
let payload;
|
|
45
46
|
let mintsAFrame;
|
|
46
|
-
let replay;
|
|
47
47
|
const publishQueued = (h) => {
|
|
48
48
|
const identity = internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) } : {};
|
|
49
49
|
queue.push({
|
|
@@ -66,7 +66,7 @@ export function streamSteerVerb(input) {
|
|
|
66
66
|
if (!mintsAFrame)
|
|
67
67
|
return;
|
|
68
68
|
if (typeof inputId === "string")
|
|
69
|
-
acceptedSteerInputs.set(inputId,
|
|
69
|
+
acceptedSteerInputs.set(inputId, parkRecord);
|
|
70
70
|
announceAcceptance(parkRecord);
|
|
71
71
|
};
|
|
72
72
|
const deliver = async () => {
|
|
@@ -77,16 +77,14 @@ export function streamSteerVerb(input) {
|
|
|
77
77
|
throw steeringError("the task is not running");
|
|
78
78
|
payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
|
|
79
79
|
mintsAFrame = payload.trim().length !== 0;
|
|
80
|
-
replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
|
|
81
80
|
if (typeof inputId === "string") {
|
|
82
81
|
const prior = acceptedSteerInputs.get(inputId);
|
|
83
82
|
if (prior !== undefined) {
|
|
84
83
|
if (live.resultValue !== undefined || h.loop.ended)
|
|
85
84
|
throw steeringError("the task is no longer running");
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
85
|
+
const refusal = steerReplayRefusal(prior, parkRecord, { seat: "accepted" });
|
|
86
|
+
if (refusal !== undefined)
|
|
87
|
+
throw steeringError(refusal.message, "steering.duplicate_input_id", refusal.detail);
|
|
90
88
|
return;
|
|
91
89
|
}
|
|
92
90
|
}
|
|
@@ -135,7 +133,7 @@ export function streamSteerVerb(input) {
|
|
|
135
133
|
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
136
134
|
if (mintsAFrame)
|
|
137
135
|
armAcceptance(parkRecord, () => publishQueued(h));
|
|
138
|
-
if (priority === "
|
|
136
|
+
if (injectionLaneOf(priority) === "followUp") {
|
|
139
137
|
await h.harness.followUp(payload, noteOptions);
|
|
140
138
|
noteAccepted();
|
|
141
139
|
return;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sub-agent tool lists of one prepared task: the allow advisory for `TaskSpec.agents` and THE judgment of every denied-tools
|
|
3
|
+
* list a deployment writes — the task's own definitions (`TaskSpec.agents`), the definitions a mounted delegation tool carries
|
|
4
|
+
* (the `authored` faces — the engine's built-ins name tools of other vocabularies and are not judged) and its tool-level list.
|
|
5
|
+
* Universe = the task's assembled roster plus every mounted delegation tool's pool, names and aliases; a pool the engine cannot
|
|
6
|
+
* list (an `extraTools` factory) turns the refusals that need a roster into host notices. A tool's own lists are judged where
|
|
7
|
+
* the tool is mounted: only a tool a deployment mounted (depth 0) publishes them (`authored`, `agentToolLevelAllowTools`, `agentToolLevelDenyTools`), the
|
|
8
|
+
* engine's nested copy for a delegated child carries neither.
|
|
9
|
+
*
|
|
10
|
+
* Throws `config.agent.unknown_tool` (`refused`, the one site) — synchronous, like the phase that calls it.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
13
|
+
import type { TaskSpec } from "../types.js";
|
|
14
|
+
import type { AnnounceOnceSink } from "./contracts.js";
|
|
15
|
+
/**
|
|
16
|
+
* The names a definition's denied-tools entries stand for, for a lane that has neither a roster nor a notice sink at the point
|
|
17
|
+
* it builds its own policy (a workflow leg's agentType): each entry is read by the one reading, so `Tool(content)` denies the
|
|
18
|
+
* whole tool there too, and an entry that names no tool is refused by the same refusal. The stale-name and told dispositions
|
|
19
|
+
* need the leg's roster and a sink; the leg's own policy-name audit is what reads them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function deniedToolNamesOf(where: string, value: unknown): string[];
|
|
22
|
+
export interface SubAgentToolListsInput {
|
|
23
|
+
/** borrowed-readonly — the REBOUND spec: `agents` (the task's definitions) and `tools` (the mounted delegation tools). */
|
|
24
|
+
spec: Pick<TaskSpec, "agents" | "tools">;
|
|
25
|
+
/** borrowed-readonly — the FULLY ASSEMBLED mount array (names + aliases = the roster half of the universe). */
|
|
26
|
+
tools: ReadonlyArray<AgentTool>;
|
|
27
|
+
/** borrowed-readonly — the once-per-session ledger whose operator sink carries the notices. */
|
|
28
|
+
onceLedger: Pick<AnnounceOnceSink, "onError">;
|
|
29
|
+
/** borrowed-readonly — the acquired session's id (operator-line context). */
|
|
30
|
+
sessionId: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function auditSubAgentToolLists(input: SubAgentToolListsInput): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { notAListSentence, notAStringSentence, readDeniedEntry, readToolNameList, valueTypeLabel } from "../../agents/tool-filter.js";
|
|
2
|
+
import { compileAllowList, inertEntryPhrase, renderListEntry } from "../allow-entry.js";
|
|
3
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
|
+
const DENY_LIST_NAMES_ONLY = "To block by path or command, write a deny rule in the caller's permission policy: a denied-tools list only takes tool names (it narrows which tools the agent is handed).";
|
|
5
|
+
function refused(message) {
|
|
6
|
+
const e = new Error(message);
|
|
7
|
+
e.code = "config.agent.unknown_tool";
|
|
8
|
+
return e;
|
|
9
|
+
}
|
|
10
|
+
function refusal(where, flaw) {
|
|
11
|
+
return refused(`${where} declares tool ${inertEntryPhrase(flaw)} in its denied tools, but no tool of that spelling exists in this deployment — fix the entry or mount the tool.${flaw.reason === "grammar" ? ` ${DENY_LIST_NAMES_ONLY}` : ""}`);
|
|
12
|
+
}
|
|
13
|
+
function refusalNotAString(where, side, index, type) {
|
|
14
|
+
return refused(notAStringSentence(where, side, index, type));
|
|
15
|
+
}
|
|
16
|
+
function refusalNotAList(where, side, list) {
|
|
17
|
+
return refused(notAListSentence(where, side, list));
|
|
18
|
+
}
|
|
19
|
+
export function deniedToolNamesOf(where, value) {
|
|
20
|
+
const list = readToolNameList(value);
|
|
21
|
+
if (list.kind !== "entries")
|
|
22
|
+
throw refusalNotAList(where, "denied", list);
|
|
23
|
+
return list.entries.map((entry, index) => {
|
|
24
|
+
if (entry === "*")
|
|
25
|
+
return entry;
|
|
26
|
+
const read = readDeniedEntry(entry);
|
|
27
|
+
if (read.kind === "not_a_string")
|
|
28
|
+
throw refusalNotAString(where, "denied", index, read.type);
|
|
29
|
+
const flaw = compileAllowList([read.name], "deny").inert[0];
|
|
30
|
+
if (flaw !== undefined && flaw.reason === "grammar")
|
|
31
|
+
throw refusal(where, { ...flaw, entry: read.name });
|
|
32
|
+
return read.name;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function judgeDeniedToolNames(where, value, universe, advise) {
|
|
36
|
+
if (value === undefined || value === null)
|
|
37
|
+
return;
|
|
38
|
+
const list = readToolNameList(value);
|
|
39
|
+
if (list.kind !== "entries")
|
|
40
|
+
throw refusalNotAList(where, "denied", list);
|
|
41
|
+
let index = -1;
|
|
42
|
+
for (const entry of list.entries) {
|
|
43
|
+
index += 1;
|
|
44
|
+
if (entry === "*")
|
|
45
|
+
continue;
|
|
46
|
+
const read = readDeniedEntry(entry);
|
|
47
|
+
if (read.kind === "not_a_string")
|
|
48
|
+
throw refusalNotAString(where, "denied", index, read.type);
|
|
49
|
+
const { name, content } = read;
|
|
50
|
+
const raw = entry;
|
|
51
|
+
const { rows, inert } = compileAllowList([name], "deny", universe.known);
|
|
52
|
+
const flaw = inert[0] === undefined ? undefined : { ...inert[0], entry: raw };
|
|
53
|
+
const stale = flaw !== undefined && (flaw.reason === "tool_name" || flaw.reason === "near_miss");
|
|
54
|
+
if (flaw !== undefined && (flaw.reason === "grammar" || (stale && universe.complete)))
|
|
55
|
+
throw refusal(where, flaw);
|
|
56
|
+
if (flaw === undefined && universe.known.has(name)) {
|
|
57
|
+
if (content)
|
|
58
|
+
advise(new Error(`${where} declares ${renderListEntry(raw)} in its denied tools: this list reads tool names, not content, so the whole tool "${inlineUntrusted(name, 80)}" is denied to the agent. ${DENY_LIST_NAMES_ONLY}`));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
advise(new Error(`${where} declares tool ${flaw !== undefined ? inertEntryPhrase(flaw) : renderListEntry(raw)} in its denied tools, a name this task's roster and delegation pools do not carry — the entry hides nothing here. Advisory only: a list shared across deployments is not wrong because one lacks a tool, but a misspelling guards nothing.` +
|
|
62
|
+
(rows[0]?.compiled.kind === "namespace" ? " This list matches tool names exactly: a server-level entry hides none of that server's tools; name them, or write a deny rule in the caller's permission policy." : "") +
|
|
63
|
+
(universe.complete ? "" : " A delegation tool this task mounts injects tools the engine cannot enumerate (extraTools), so the name cannot be checked here.")));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function judgeAllowedToolList(where, value, known, advise) {
|
|
67
|
+
if (value === undefined || value === null)
|
|
68
|
+
return;
|
|
69
|
+
const list = readToolNameList(value);
|
|
70
|
+
if (list.kind !== "entries")
|
|
71
|
+
throw refusalNotAList(where, "allowed", list);
|
|
72
|
+
const unknownNames = [];
|
|
73
|
+
let index = -1;
|
|
74
|
+
for (const entry of list.entries) {
|
|
75
|
+
index += 1;
|
|
76
|
+
if (typeof entry !== "string")
|
|
77
|
+
throw refusalNotAString(where, "allowed", index, valueTypeLabel(entry));
|
|
78
|
+
if (known !== undefined && entry !== "*" && !known.has(entry))
|
|
79
|
+
unknownNames.push(entry);
|
|
80
|
+
}
|
|
81
|
+
if (unknownNames.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
advise(new Error(`${where} allows tool(s) ${unknownNames.map(renderListEntry).join(", ")} not present in this task's assembled roster — likely a typo (the entry would be item-filtered at spawn; the agent stays usable). Advisory only: the delegation pool can differ from this roster, so a tool mounted only on the delegation tool or produced by per-spawn extraTools makes this spurious.`));
|
|
84
|
+
}
|
|
85
|
+
export function auditSubAgentToolLists(input) {
|
|
86
|
+
const { spec, tools, onceLedger, sessionId } = input;
|
|
87
|
+
const advise = (err) => {
|
|
88
|
+
try {
|
|
89
|
+
onceLedger.onError(err, { phase: "config", sessionId });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const delegationTools = (spec.tools ?? []).filter((t) => t.agentToolFaces !== undefined);
|
|
95
|
+
const taskAgents = spec.agents ?? [];
|
|
96
|
+
if (taskAgents.length === 0 && delegationTools.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
99
|
+
const denyKnown = new Set(known);
|
|
100
|
+
for (const t of delegationTools)
|
|
101
|
+
for (const p of t.agentToolPool ?? [])
|
|
102
|
+
for (const n of [p.name, ...(p.aliases ?? [])])
|
|
103
|
+
denyKnown.add(n);
|
|
104
|
+
const universe = { known: denyKnown, complete: delegationTools.every((t) => t.agentToolPool !== undefined) };
|
|
105
|
+
for (const def of taskAgents) {
|
|
106
|
+
judgeAllowedToolList(`TaskSpec.agents: agent "${def.name}"`, def.allowTools, known, advise);
|
|
107
|
+
judgeDeniedToolNames(`TaskSpec.agents: agent "${def.name}"`, def.denyTools, universe, advise);
|
|
108
|
+
}
|
|
109
|
+
const replaced = new Set(taskAgents.map((d) => d.name));
|
|
110
|
+
for (const t of delegationTools) {
|
|
111
|
+
for (const face of t.agentToolFaces ?? []) {
|
|
112
|
+
if (face.authored === true && !replaced.has(face.name)) {
|
|
113
|
+
const faceWhere = `delegation tool "${t.name}": agent definition "${face.name}"`;
|
|
114
|
+
judgeAllowedToolList(faceWhere, face.allowTools, undefined, advise);
|
|
115
|
+
judgeDeniedToolNames(faceWhere, face.denyTools, universe, advise);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
judgeAllowedToolList(`delegation tool "${t.name}": the tool-level list`, t.agentToolLevelAllowTools, undefined, advise);
|
|
119
|
+
judgeDeniedToolNames(`delegation tool "${t.name}": the tool-level list`, t.agentToolLevelDenyTools, universe, advise);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -110,7 +110,7 @@ export interface SkillListingEntry {
|
|
|
110
110
|
* and the tool, so what the prompt promises is exactly what the tool serves. Each field is READ ONCE, BY NAME, and
|
|
111
111
|
* both consumers see the copy: the gate screens this list while the invoke substitutes from it, so a `get baseDir()`
|
|
112
112
|
* answering those two reads differently passed a screen that measured a value the model never received — and a rest
|
|
113
|
-
* copy would silently drop an INHERITED or non-enumerable list
|
|
113
|
+
* copy would silently drop an INHERITED or non-enumerable list. The two tables are read by {@link readSkillTable}. */
|
|
114
114
|
export declare function normalizeSkills(skills: SkillLike[] | undefined): SkillLike[];
|
|
115
115
|
/** [c209-C] The skills listing's lead line — single-sourced so the run loop's durable-resume
|
|
116
116
|
* transcript probe (MED-3② skills arm, runtask) matches EXACTLY what the initial frame carries
|
|
@@ -2,7 +2,7 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
3
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
4
|
import { SKILL_LISTING_DEFAULT_BUDGET, SKILL_LISTING_DESC_MAX_CHARS_DEFAULT } from "../skill-listing-budget.js";
|
|
5
|
-
import { skillNamedBy } from "../skill-grant.js";
|
|
5
|
+
import { readSkillTable, skillNamedBy } from "../skill-grant.js";
|
|
6
6
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
7
7
|
import { substituteSkillRoot, substituteSkillRootBounded } from "./skill-base-dir.js";
|
|
8
8
|
import { foldListingWithin } from "./listing-fold.js";
|
|
@@ -116,7 +116,8 @@ export function normalizeSkills(skills) {
|
|
|
116
116
|
seen.add(s.name);
|
|
117
117
|
const { name, description, content, allowedTools, disallowedTools, files, baseDir, ...rest } = s;
|
|
118
118
|
void rest;
|
|
119
|
-
|
|
119
|
+
const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)];
|
|
120
|
+
out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) });
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
return out.toSorted((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
@@ -52,12 +52,16 @@ export interface PutRulesOptions {
|
|
|
52
52
|
* this from a verified, role-checked principal (mirrors `steer.trusted`). */
|
|
53
53
|
operator?: boolean;
|
|
54
54
|
}
|
|
55
|
-
/** The {@link SessionPolicyError.detail} of a tool-name refusal: which bucket, which entry, the judgment's word,
|
|
56
|
-
* spelling to write instead when the judgment has one (a retired name's 1:1
|
|
55
|
+
/** The {@link SessionPolicyError.detail} of a tool-name refusal: which bucket, which entry, the judgment's word, the judgment's
|
|
56
|
+
* one-line sentence when it has one, and the spelling to write instead when the judgment has one (a retired name's 1:1
|
|
57
|
+
* successor, an unmintable segment's minted name). */
|
|
57
58
|
export interface SessionRuleNameRefusal {
|
|
58
59
|
field: SessionRuleToolNameField;
|
|
59
60
|
name: string;
|
|
60
61
|
verdict: Exclude<RuleToolNameVerdict["kind"], "current">;
|
|
62
|
+
/** The `guidance` of the judgment ({@link judgeRuleToolName}), byte for byte — the same string the refusal's sentence quotes. Present for
|
|
63
|
+
* `retired` and `unmintable_segment`, the two verdicts that carry one; absent for `unprefixed_namespace`, whose judgment has no sentence of its own. */
|
|
64
|
+
guidance?: string;
|
|
61
65
|
currentName?: string;
|
|
62
66
|
}
|
|
63
67
|
/** Typed error from {@link SessionPolicyStore.putRules}. `conflict` = stale `expectedRev`; `loosen_forbidden`
|
|
@@ -41,6 +41,7 @@ export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLA
|
|
|
41
41
|
continue;
|
|
42
42
|
const quoted = JSON.stringify(name);
|
|
43
43
|
const successor = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
|
|
44
|
+
const guidance = verdict.kind === "unprefixed_namespace" ? undefined : verdict.guidance;
|
|
44
45
|
let message;
|
|
45
46
|
switch (verdict.kind) {
|
|
46
47
|
case "retired":
|
|
@@ -57,7 +58,7 @@ export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLA
|
|
|
57
58
|
return exhaustive;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(successor !== undefined ? { currentName: successor } : {}) });
|
|
61
|
+
throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(guidance !== undefined ? { guidance } : {}), ...(successor !== undefined ? { currentName: successor } : {}) });
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -76,8 +76,16 @@ export declare function parkedSkillApprovalRefusal(row: {
|
|
|
76
76
|
readonly args: unknown;
|
|
77
77
|
readonly boundGrantDigest?: string;
|
|
78
78
|
}, args: unknown, skills: readonly SkillGrantSource[], skillToolName: string, site?: "claim" | "execution"): string | undefined;
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
79
|
+
/**
|
|
80
|
+
* A skill's table (`allowed-tools` / `disallowed-tools`) READ ONCE, the one reading every later reader takes instead of the host's
|
|
81
|
+
* value: absent (`undefined`, or `null` — a frontmatter's empty key) is no table; a list is its dense copy (by index, no iterator
|
|
82
|
+
* of the host's runs; a member that is not a string stays as it is); a value that is not a list, or a list whose read throws, is
|
|
83
|
+
* ONE entry that reads as nothing ({@link nonListValueLabel}), so each table applies the law it already has to an entry that reads
|
|
84
|
+
* as nothing — a deny table keeps its Skill from activating, an allow table is announced and grants nothing. A string is not spread.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readSkillTable(value: unknown): string[] | undefined;
|
|
87
|
+
/** A resume's skills read ONCE: each definition copied with its two tables ({@link readSkillTable}), so the parked Skill approval's
|
|
88
|
+
* binding and the resumed leg's prepare read one value — a later edit to the caller's own objects or arrays reaches neither. */
|
|
81
89
|
export declare function detachSkillLists<T extends SkillGrantSource>(skills: readonly T[]): T[];
|
|
82
90
|
/** Does this skill declare a list (either, non-empty)? The one test of "the Skill card is asked for this skill". */
|
|
83
91
|
export declare function declaresSkillLists(skill: Pick<SkillGrantSource, "allowedTools" | "disallowedTools">): boolean;
|