@sema-agent/core 7.5.0 → 7.5.2
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 +44 -0
- package/dist/core/auto-mode.d.ts +9 -0
- package/dist/core/auto-mode.js +11 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/protocol-table.d.ts +5 -0
- package/dist/core/protocol-table.js +1 -0
- package/dist/core/runner/abort-race.d.ts +41 -0
- package/dist/core/runner/abort-race.js +38 -0
- package/dist/core/runner/checkpoint-scope.d.ts +44 -0
- package/dist/core/runner/checkpoint-scope.js +7 -0
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/content-ask-bindings.d.ts +27 -0
- package/dist/core/runner/content-ask-bindings.js +1 -0
- package/dist/core/runner/contracts.d.ts +1990 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +24 -1
- package/dist/core/runner/denial-limit-arms.js +21 -0
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
- package/dist/core/runner/inherited-ask-grants.js +29 -0
- package/dist/core/runner/park-commit.d.ts +108 -0
- package/dist/core/runner/park-commit.js +32 -0
- package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
- package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +110 -0
- package/dist/core/runner/prepare-ask-lane.js +133 -0
- package/dist/core/runner/prepare-boundary-parks.d.ts +105 -0
- package/dist/core/runner/prepare-boundary-parks.js +169 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-context-lane.d.ts +119 -0
- package/dist/core/runner/prepare-context-lane.js +230 -0
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +177 -0
- package/dist/core/runner/prepare-gate-stations.js +290 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +9 -11
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
- package/dist/core/runner/prepare-memory-engine-session.js +233 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-park-ask.d.ts +164 -0
- package/dist/core/runner/prepare-park-ask.js +377 -0
- package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
- package/dist/core/runner/prepare-policy-chain.js +584 -0
- package/dist/core/runner/prepare-project-context.d.ts +119 -0
- package/dist/core/runner/prepare-project-context.js +148 -0
- package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
- package/dist/core/runner/prepare-prompt-assembly.js +162 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +119 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +179 -0
- package/dist/core/runner/prepare-question-face.d.ts +101 -0
- package/dist/core/runner/prepare-question-face.js +84 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -7
- package/dist/core/runner/prepare-safety-scan.js +1 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
- package/dist/core/runner/prepare-suspend-saga.js +308 -0
- package/dist/core/runner/prepare-task.d.ts +14 -1976
- package/dist/core/runner/prepare-task.js +182 -5162
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
- package/dist/core/runner/prepare-turn-wiring.js +201 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +192 -0
- package/dist/core/runner/prepare-wiring-manifest.js +247 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -56
- package/dist/core/runner/prepare-workspace-restore.js +4 -38
- package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
- package/dist/core/runner/prompt-hash-salt.js +2 -0
- package/dist/core/runner/remote-env-retry.d.ts +29 -0
- package/dist/core/runner/remote-env-retry.js +16 -0
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +3 -0
- package/dist/core/tool-policy.d.ts +16 -0
- package/dist/core/tool-policy.js +3 -0
- package/dist/core/types.d.ts +2 -2
- package/dist/core/write-protect.js +3 -2
- package/package.json +6 -2
- /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
- /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 M10 — prepareTask's PROMPT-INPUTS phase: the prompt provider selection, the runtime
|
|
3
|
+
* feature flags the harness-context block gates on (so it never claims a mechanism the task lacks),
|
|
4
|
+
* the three-source user system prompt + the caller's append block, the fenced MCP server-instructions
|
|
5
|
+
* block, the environment facts (client zone + date basis, knowledge cutoff, the deployment's sandbox
|
|
6
|
+
* axes, resume-continuity facts on a durable-resume leg only, the hands facts + the one batched env
|
|
7
|
+
* probe with its cwd/pwd alignment guard) and the git-status lane probe. The body is the driver's
|
|
8
|
+
* stretch moved verbatim; the interface is the dependency list it had implicitly (design/238 R-1).
|
|
9
|
+
*
|
|
10
|
+
* ASYNC BY CONTENT: the stretch awaits the env probe (hands) and the git-status lane probe (always).
|
|
11
|
+
* It sits past prepareTask's first await, so the driver's `await` adds no window a caller could reach.
|
|
12
|
+
*
|
|
13
|
+
* The one input computed by the DRIVER rather than read here: `failClosedReason` — the
|
|
14
|
+
* self-orchestration fail-closed advisory's text. Its helper takes the whole `TaskSpec`/`RunnerDeps`
|
|
15
|
+
* (it audits several deployment seats), which this phase's narrow `spec`/`deps` seats cannot satisfy
|
|
16
|
+
* (design/238 R-4); the driver evaluates the helper in the Input literal and this phase only announces
|
|
17
|
+
* the result on the operator lane, exactly where the stretch always did.
|
|
18
|
+
*
|
|
19
|
+
* Nothing here throws on its own: the env probe and the alignment guard are best-effort (a stub/odd env
|
|
20
|
+
* just omits the shell-derived facts), and the git lane degrades through `onDegrade` → `deps.onError`.
|
|
21
|
+
*/
|
|
22
|
+
import type { ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
|
|
23
|
+
import type { Model } from "../../internal/llm.js";
|
|
24
|
+
import { type EnvironmentFacts, type PromptProvider } from "../../prompts/default.js";
|
|
25
|
+
import type { ReadFace } from "../../tools/fs/index.js";
|
|
26
|
+
import type { LockedPreflight } from "../locked-config.js";
|
|
27
|
+
import type { MaterializedMcp } from "../mcp.js";
|
|
28
|
+
import type { ResolvedRole } from "../roles.js";
|
|
29
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
30
|
+
import type { PrepareResume, PromptFeatureFlags, RunInternals, ToolFaceSnapshot } from "./contracts.js";
|
|
31
|
+
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
32
|
+
export interface PreparePromptInputsInput {
|
|
33
|
+
/** borrowed-readonly — the REBOUND spec (`spec′`). Read: `promptProvider`, `hooks` (flag), `systemPrompt`
|
|
34
|
+
* / `appendSystemPrompt` (the user prompt sources), `clientContext` (zone, email), `envFacts` (the
|
|
35
|
+
* deployment's sandbox + resume facts), `compaction` (the within-task flag). Never mutated. */
|
|
36
|
+
spec: Pick<TaskSpec, "promptProvider" | "hooks" | "systemPrompt" | "appendSystemPrompt" | "clientContext" | "envFacts" | "compaction">;
|
|
37
|
+
/** borrowed-readonly — deployment deps: `promptProvider` (fallback), `hooks` (flag), `onError` (the
|
|
38
|
+
* operator lane: fail-closed advisory, cwd misalignment, git-lane degrade), `modelKnowledgeCutoffs`.
|
|
39
|
+
* Receiver preserved (`deps.onError?.(…)`). */
|
|
40
|
+
deps: Pick<RunnerDeps, "promptProvider" | "hooks" | "onError" | "modelKnowledgeCutoffs">;
|
|
41
|
+
/** borrowed-readonly — the self-orchestration fail-closed advisory (null = nothing to announce),
|
|
42
|
+
* evaluated by the DRIVER against the whole spec/deps (see the module header). */
|
|
43
|
+
failClosedReason: string | null;
|
|
44
|
+
/** borrowed-readonly — the locked preflight; only `toolPolicy` presence is read (the policy flag). */
|
|
45
|
+
lockedPreflight: Pick<LockedPreflight, "toolPolicy">;
|
|
46
|
+
/** borrowed-readonly — whether self-orchestration is ACTIVE (the same gate that mounted the
|
|
47
|
+
* workflow tool): drives the orchestration prompt flags. */
|
|
48
|
+
selfOrchestrationActive: boolean;
|
|
49
|
+
/** borrowed-readonly — the task's execution env: `isIsolated` for the isolation flag, `exec` for the
|
|
50
|
+
* one batched probe, `canonicalPath` for the alignment guard. Never re-bound, never destroyed here. */
|
|
51
|
+
executionEnv: ExecutionEnv;
|
|
52
|
+
/** borrowed-readonly — the session's reminder mark (rides the prompt facts for the declaration). */
|
|
53
|
+
reminderMark: string;
|
|
54
|
+
/** borrowed-readonly — the resolved read face (the open-reads flag + the structured env fact). */
|
|
55
|
+
resolvedReadFace: ReadFace | undefined;
|
|
56
|
+
/** borrowed-readonly — the frozen tool face; only `defer` is read (the Workflow short-pointer flag). */
|
|
57
|
+
toolFaceSnapshot: Pick<ToolFaceSnapshot, "defer">;
|
|
58
|
+
/** borrowed-readonly — the resolved prompt profile (rides the flags to the legacy ctx). */
|
|
59
|
+
promptProfile: "simple" | "classic";
|
|
60
|
+
/** borrowed-readonly — the fable-mitigations profile axis (rides the flags with the profile). */
|
|
61
|
+
fableMitigations: boolean;
|
|
62
|
+
/** borrowed-readonly — trusted internals: `goalMode` (goal flag), `isolation` (worktree flag),
|
|
63
|
+
* `defaultSystemPrompt` (the third prompt source). `undefined` on a standalone prepare. */
|
|
64
|
+
internals: Pick<RunInternals, "goalMode" | "isolation" | "defaultSystemPrompt"> | undefined;
|
|
65
|
+
/** borrowed-readonly — the resolved, role-folded reasoning tier (the awareness flag's input). */
|
|
66
|
+
thinking: ThinkingLevel | undefined;
|
|
67
|
+
/** borrowed-readonly — the factory-minted isolated env, read for PRESENCE only (the worktree flag's
|
|
68
|
+
* belt over the fail-closed isolation guard). */
|
|
69
|
+
ownedEnv: ExecutionEnv | undefined;
|
|
70
|
+
/** borrowed-readonly — the leg's effective-delegation facts; only `isNonForkChild` is read. */
|
|
71
|
+
delegation: {
|
|
72
|
+
readonly isNonForkChild: boolean;
|
|
73
|
+
};
|
|
74
|
+
/** borrowed-readonly — the resolved role; only its `systemPrompt` preset is read (second source). */
|
|
75
|
+
resolvedRole: Pick<ResolvedRole, "systemPrompt">;
|
|
76
|
+
/** borrowed-readonly — the materialized MCP roster; only `serverInstructions` is read (fenced). */
|
|
77
|
+
mcp: Pick<MaterializedMcp, "serverInstructions">;
|
|
78
|
+
/** borrowed-readonly — the resolved model (id for the env line + the cutoff lookup). */
|
|
79
|
+
model: Model;
|
|
80
|
+
/** borrowed-readonly — the run's external-content-target fold (the single-source disclosure fact). */
|
|
81
|
+
externalContentTargetActive: boolean;
|
|
82
|
+
/** borrowed-readonly — the durable-resume carrier, read for PRESENCE only (resume facts copy on a
|
|
83
|
+
* resume leg and never on a fresh one). */
|
|
84
|
+
resume: PrepareResume | undefined;
|
|
85
|
+
/** borrowed-readonly — whether a real execution env is mounted (the hands facts + both probes). */
|
|
86
|
+
handsEnabled: boolean;
|
|
87
|
+
/** borrowed-readonly — the FINAL task root: the env `cwd` fact and the git lane's root. */
|
|
88
|
+
taskRootFinal: string;
|
|
89
|
+
/** borrowed-readonly — the canonical extra working roots (copied into the env facts). */
|
|
90
|
+
additionalRootsCanonical: readonly string[];
|
|
91
|
+
/** borrowed-readonly — the canonical extra read-only roots (copied into the env facts). */
|
|
92
|
+
additionalReadRootsCanonical: readonly string[];
|
|
93
|
+
/** borrowed-readonly — the acquired session's id (operator-line context). */
|
|
94
|
+
sessionId: string;
|
|
95
|
+
}
|
|
96
|
+
export interface PreparePromptInputsResult {
|
|
97
|
+
/** owned — the prompt provider in force (`spec` ?? `deps` ?? the default). */
|
|
98
|
+
provider: PromptProvider;
|
|
99
|
+
/** owned — the runtime feature flags (see {@link PromptFeatureFlags}). */
|
|
100
|
+
featureFlags: PromptFeatureFlags;
|
|
101
|
+
/** owned — the user system prompt: `spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals.defaultSystemPrompt`. */
|
|
102
|
+
userSystemPrompt: string | undefined;
|
|
103
|
+
/** owned — the caller's append block (`spec.appendSystemPrompt`). */
|
|
104
|
+
userAppendSystemPrompt: string | undefined;
|
|
105
|
+
/** owned — the fenced MCP server-instructions block, or undefined when no server declared any. */
|
|
106
|
+
mcpInstructionsBlock: string | undefined;
|
|
107
|
+
/** owned — the environment facts the env tail renders from. Filled here; no later phase writes it
|
|
108
|
+
* (the run loop re-renders only the date through `renderWithDate`). */
|
|
109
|
+
envFacts: EnvironmentFacts;
|
|
110
|
+
/** owned — whether the client zone validated (the date-change seam re-renders in the same zone). */
|
|
111
|
+
tzValid: boolean;
|
|
112
|
+
/** owned — the client's declared zone as given (paired with `tzValid`; unvalidated on its own). */
|
|
113
|
+
userTz: string | undefined;
|
|
114
|
+
/** owned — the git-status lane's run-local state (empty on a hands-less leg); the run loop and the
|
|
115
|
+
* checkpoint serializer mutate its fields from here on (`announced`, `pendingReceipt`, …). */
|
|
116
|
+
gitStatusRef: GitStatusLaneRef;
|
|
117
|
+
}
|
|
118
|
+
/** The M10 phase body — prepareTask's prompt-inputs stretch, verbatim (see the module header). */
|
|
119
|
+
export declare function preparePromptInputs(input: PreparePromptInputsInput): Promise<PreparePromptInputsResult>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { defaultPromptProvider, formatLocalDate, isValidTimeZone } from "../../prompts/default.js";
|
|
2
|
+
import { isIsolated } from "../remote-env.js";
|
|
3
|
+
import { probeGitStatusLane } from "./git-status-frame.js";
|
|
4
|
+
import { fenceMcpServerInstructions } from "./turn-attachments.js";
|
|
5
|
+
const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
|
|
6
|
+
export async function preparePromptInputs(input) {
|
|
7
|
+
const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId } = input;
|
|
8
|
+
const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
|
|
9
|
+
const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
|
|
10
|
+
const promptHooks = spec.hooks ?? deps.hooks;
|
|
11
|
+
const promptHooksEnabled = Boolean(promptHooks?.preToolUse || promptHooks?.postToolUse);
|
|
12
|
+
if (failClosedReason) {
|
|
13
|
+
deps.onError?.(new Error(failClosedReason), { phase: "config", sessionId });
|
|
14
|
+
}
|
|
15
|
+
const featureFlags = {
|
|
16
|
+
policyEnabled: promptPolicyEnabled,
|
|
17
|
+
hooksEnabled: promptHooksEnabled,
|
|
18
|
+
isolationEnabled: isIsolated(executionEnv),
|
|
19
|
+
reminderMark,
|
|
20
|
+
readFaceOpen: resolvedReadFace === "open",
|
|
21
|
+
orchestrationEnabled: selfOrchestrationActive,
|
|
22
|
+
orchestrationDeferred: selfOrchestrationActive && (toolFaceSnapshot.defer?.includes("Workflow") ?? false),
|
|
23
|
+
promptProfile,
|
|
24
|
+
fableMitigations,
|
|
25
|
+
goalEnabled: internals?.goalMode === true,
|
|
26
|
+
awarenessEnabled: thinking !== undefined && ULTRA_REASONING_TIERS.has(thinking),
|
|
27
|
+
worktreeIsolated: internals?.isolation === "worktree" && ownedEnv !== undefined,
|
|
28
|
+
withinTaskCompactionEnabled: (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true),
|
|
29
|
+
isSubagent: delegation.isNonForkChild,
|
|
30
|
+
};
|
|
31
|
+
const userSystemPrompt = spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals?.defaultSystemPrompt;
|
|
32
|
+
const userAppendSystemPrompt = spec.appendSystemPrompt;
|
|
33
|
+
const mcpInstructionsBlock = mcp.serverInstructions.length
|
|
34
|
+
? `# MCP Server Instructions\n${mcp.serverInstructions.map((s) => fenceMcpServerInstructions(s.server, s.text)).join("\n\n")}`
|
|
35
|
+
: undefined;
|
|
36
|
+
const userTz = spec.clientContext?.timeZone;
|
|
37
|
+
const tzValid = userTz !== undefined && isValidTimeZone(userTz);
|
|
38
|
+
const envFacts = { date: formatLocalDate(new Date(), tzValid ? userTz : undefined), modelId: model.id };
|
|
39
|
+
if (tzValid)
|
|
40
|
+
envFacts.timeZone = userTz;
|
|
41
|
+
if (spec.clientContext?.userEmail)
|
|
42
|
+
envFacts.userEmail = spec.clientContext.userEmail;
|
|
43
|
+
const declaredCutoff = deps.modelKnowledgeCutoffs?.[model.id];
|
|
44
|
+
if (declaredCutoff)
|
|
45
|
+
envFacts.knowledgeCutoff = declaredCutoff;
|
|
46
|
+
if (spec.envFacts?.profile)
|
|
47
|
+
envFacts.sandboxProfile = spec.envFacts.profile;
|
|
48
|
+
if (spec.envFacts?.capabilities && spec.envFacts.capabilities.length > 0)
|
|
49
|
+
envFacts.sandboxCapabilities = [...spec.envFacts.capabilities];
|
|
50
|
+
if (spec.envFacts?.pkgSource)
|
|
51
|
+
envFacts.sandboxPkgSource = spec.envFacts.pkgSource;
|
|
52
|
+
if (spec.envFacts?.egress === "none" || spec.envFacts?.egress === "allowlist" || spec.envFacts?.egress === "full")
|
|
53
|
+
envFacts.sandboxEgress = spec.envFacts.egress;
|
|
54
|
+
if (spec.envFacts?.scratchpadDir)
|
|
55
|
+
envFacts.scratchpadDir = spec.envFacts.scratchpadDir;
|
|
56
|
+
if (externalContentTargetActive)
|
|
57
|
+
envFacts.externalContentTarget = true;
|
|
58
|
+
if (resume !== undefined && spec.envFacts?.resumeFacts) {
|
|
59
|
+
const rf = spec.envFacts.resumeFacts;
|
|
60
|
+
const copy = {};
|
|
61
|
+
if (rf.processes === "preserved" || rf.processes === "lost")
|
|
62
|
+
copy.processes = rf.processes;
|
|
63
|
+
if (rf.scratch === "preserved" || rf.scratch === "lost")
|
|
64
|
+
copy.scratch = rf.scratch;
|
|
65
|
+
if (typeof rf.note === "string" && rf.note.length > 0)
|
|
66
|
+
copy.note = rf.note;
|
|
67
|
+
if (Object.keys(copy).length > 0)
|
|
68
|
+
envFacts.resumeFacts = copy;
|
|
69
|
+
}
|
|
70
|
+
if (handsEnabled) {
|
|
71
|
+
envFacts.cwd = taskRootFinal;
|
|
72
|
+
if (additionalRootsCanonical.length > 0) {
|
|
73
|
+
envFacts.additionalDirectories = [...additionalRootsCanonical];
|
|
74
|
+
}
|
|
75
|
+
if (resolvedReadFace !== undefined)
|
|
76
|
+
envFacts.readFace = resolvedReadFace;
|
|
77
|
+
if (additionalReadRootsCanonical.length > 0) {
|
|
78
|
+
envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const probe = await executionEnv.exec('uname -s; uname -r; (git rev-parse --is-inside-work-tree 2>/dev/null || echo false); (git symbolic-ref --short -q HEAD 2>/dev/null || echo "HEAD (detached)"); (git rev-parse --show-toplevel 2>/dev/null || echo); (test -n "$(git status --porcelain 2>/dev/null | head -1)" && echo dirty || echo clean); (s=$(ps -p $$ -o comm= 2>/dev/null); s=${s##*/}; echo "${s#-}"); (test "$(git rev-parse --git-dir 2>/dev/null)" != "$(git rev-parse --git-common-dir 2>/dev/null)" && echo linked || echo main); (pwd -P 2>/dev/null || pwd)', { cwd: envFacts.cwd, timeout: 10 });
|
|
82
|
+
if (probe.ok && probe.value.exitCode === 0) {
|
|
83
|
+
const lines = probe.value.stdout.split("\n");
|
|
84
|
+
if (lines.length >= 6) {
|
|
85
|
+
const [osName, osVer, git, branch, worktreeRoot, dirty] = lines;
|
|
86
|
+
if (osName?.trim())
|
|
87
|
+
envFacts.platform = osName.trim();
|
|
88
|
+
if (osVer?.trim())
|
|
89
|
+
envFacts.osVersion = osVer.trim();
|
|
90
|
+
envFacts.isGitRepo = (git ?? "").trim() === "true";
|
|
91
|
+
if (envFacts.isGitRepo) {
|
|
92
|
+
const b = (branch ?? "").trim();
|
|
93
|
+
if (b)
|
|
94
|
+
envFacts.gitBranch = b;
|
|
95
|
+
const root = (worktreeRoot ?? "").trim();
|
|
96
|
+
if (root)
|
|
97
|
+
envFacts.gitWorktreeRoot = root;
|
|
98
|
+
const d = (dirty ?? "").trim();
|
|
99
|
+
if (d === "dirty" || d === "clean")
|
|
100
|
+
envFacts.gitDirty = d === "dirty";
|
|
101
|
+
}
|
|
102
|
+
const sh = (lines[6] ?? "").trim();
|
|
103
|
+
if (sh)
|
|
104
|
+
envFacts.shell = sh;
|
|
105
|
+
if (envFacts.isGitRepo && (lines[7] ?? "").trim() === "linked")
|
|
106
|
+
envFacts.isLinkedWorktree = true;
|
|
107
|
+
const probedPwd = (lines[8] ?? "").trim();
|
|
108
|
+
if (probedPwd && envFacts.cwd && probedPwd !== envFacts.cwd) {
|
|
109
|
+
const canonOf = async (p) => {
|
|
110
|
+
try {
|
|
111
|
+
const c = await executionEnv.canonicalPath(p);
|
|
112
|
+
return c.ok ? c.value : p;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return p;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const canonicalProbed = await canonOf(probedPwd);
|
|
119
|
+
if (canonicalProbed !== (await canonOf(envFacts.cwd))) {
|
|
120
|
+
try {
|
|
121
|
+
deps.onError?.(new Error(`execution-env cwd misalignment: task root is "${envFacts.cwd}" but the env's shell reports pwd "${canonicalProbed}" — per-exec cwd injection may be ignored on this lane (execs/artifacts can land outside the workspace root)`), { phase: "config", sessionId });
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const gitStatusRef = await probeGitStatusLane({
|
|
134
|
+
executionEnv,
|
|
135
|
+
envFacts,
|
|
136
|
+
handsEnabled,
|
|
137
|
+
taskRoot: taskRootFinal,
|
|
138
|
+
onDegrade: (reason) => deps.onError?.(new Error(`env git snapshot degraded — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" }),
|
|
139
|
+
});
|
|
140
|
+
return { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef };
|
|
141
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
2
|
+
import { type MaterializedMcp, type McpDroppedTool } from "../mcp.js";
|
|
3
|
+
import { type MaterializedA2a } from "../a2a.js";
|
|
4
|
+
import type { LockedPreflight } from "../locked-config.js";
|
|
5
|
+
import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
|
|
6
|
+
import type { AnnounceOnceSink, IrreversibilityTier, Prepared, RollbackStack, ToolFaceSnapshot } from "./contracts.js";
|
|
7
|
+
export interface PrepareProtocolToolsInput {
|
|
8
|
+
/** borrowed-readonly — the frozen preflight; only `mcp` (the declared server entries) is read: the
|
|
9
|
+
* materialize input, the RefreshMcpTools mount predicate, and whether a real client was acquired. */
|
|
10
|
+
lockedPreflight: Pick<LockedPreflight, "mcp">;
|
|
11
|
+
/** borrowed-readonly — the REBOUND spec. Read: `principal` (the MCP header / the A2A identity), `tools`
|
|
12
|
+
* (the caller names both reserved-name guards check against), `a2a` (the peer entries). Never mutated. */
|
|
13
|
+
spec: Pick<TaskSpec, "principal" | "tools" | "a2a">;
|
|
14
|
+
/** borrowed-readonly — the deployment seats the materializers and their operator faces read: `onElicit`
|
|
15
|
+
* and `mcpImageResizer` ride into the MCP intake; `onError` takes the A2A warnings (no ledger — a peer is
|
|
16
|
+
* re-read every prepare); `mcpRevocations` + `onNotice` make the revocation wiring. */
|
|
17
|
+
deps: Pick<RunnerDeps, "onElicit" | "mcpImageResizer" | "onError" | "mcpRevocations" | "onNotice">;
|
|
18
|
+
/** borrowed-readonly — the leg's reminder mark (the MCP intake's disclosure bounding reads it). */
|
|
19
|
+
reminderMark: string;
|
|
20
|
+
/** borrowed-mutable — the reminder-disclosure counters: the MCP intake's bounding writes them here, and
|
|
21
|
+
* the run loop's attachment lanes keep writing the same object (`Prepared.reminderDisclosureCounts`). */
|
|
22
|
+
reminderDisclosureCounts: Prepared["reminderDisclosureCounts"];
|
|
23
|
+
/** borrowed-readonly — the run's invocation id (the revocation probe-failure notice names it). */
|
|
24
|
+
runId: string;
|
|
25
|
+
/** borrowed-readonly — the acquired session id (operator-line context). */
|
|
26
|
+
sessionId: string;
|
|
27
|
+
/** borrowed-mutable — the once-per-session ledger: its deduplicated operator sink (`onError`, one line per
|
|
28
|
+
* (code, message) per session) and the announce-lane prune of tools already delivered on this branch.
|
|
29
|
+
* The ledger module owns the record; this phase writes through those two methods and nothing else. */
|
|
30
|
+
onceLedger: AnnounceOnceSink & {
|
|
31
|
+
pruneDroppedTools(dropped: McpDroppedTool[]): void;
|
|
32
|
+
};
|
|
33
|
+
/** borrowed-mutable — the run's shared roster (identity is the contract: `Prepared.tools`, the wire order).
|
|
34
|
+
* Writers here: three pushes (the MCP tools through the wrapper, RefreshMcpTools, the A2A tools through the
|
|
35
|
+
* wrapper) and, at RUN time, the refresh closure's splice within a server's name-prefix domain. Later
|
|
36
|
+
* phases push their own mounts; the roster's full writer table is the delegation-surface phase's. */
|
|
37
|
+
tools: AgentTool[];
|
|
38
|
+
/** borrowed-readonly — the large-result wrapper for protocol tools (per-tool `_meta` bar, 50K default). */
|
|
39
|
+
remoteToolOffload: (tool: AgentTool) => AgentTool;
|
|
40
|
+
/** borrowed-readonly — the frozen tool face; `exclude` is read at REFRESH time so a deployment-excluded
|
|
41
|
+
* tool is never re-mounted by a model-triggered refresh. */
|
|
42
|
+
toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
|
|
43
|
+
/** borrowed-mutable — the effect table the fold writes under a has-guard (`?? "write"` default) and the
|
|
44
|
+
* refresh closure resets per prefix domain before re-folding; RefreshMcpTools registers itself `read`. */
|
|
45
|
+
toolEffects: Map<string, ToolEffect>;
|
|
46
|
+
/** borrowed-mutable — the explicit-negative axes (a caller's vouched `never` / `egress:false`); written by
|
|
47
|
+
* the fold and reset per prefix domain by the refresh closure. */
|
|
48
|
+
axisExplicitNegatives: Map<string, {
|
|
49
|
+
irreversible?: false;
|
|
50
|
+
egress?: false;
|
|
51
|
+
}>;
|
|
52
|
+
/** borrowed-mutable — the irreversibility tier, tightened to `always` by the fold (monotone). */
|
|
53
|
+
irreversibilityTier: IrreversibilityTier;
|
|
54
|
+
/** borrowed-mutable — the irreversible set, added to by the fold (tighten-only). */
|
|
55
|
+
irreversibleTools: Set<string>;
|
|
56
|
+
/** borrowed-mutable — the egress set, added to by the fold (tighten-only; the effect:"write" invariant). */
|
|
57
|
+
egressTools: Set<string>;
|
|
58
|
+
/** borrowed-readonly — the run's abort controller; its `signal` rides into the A2A materialization. */
|
|
59
|
+
abortController: Pick<AbortController, "signal">;
|
|
60
|
+
/** borrowed-mutable — the driver-owned prepare-failure stack; this phase pushes one entry per REAL handle
|
|
61
|
+
* acquired (the MCP clients, then the A2A peers) and nothing else touches it here. */
|
|
62
|
+
rollback: RollbackStack;
|
|
63
|
+
}
|
|
64
|
+
export interface PrepareProtocolToolsResult {
|
|
65
|
+
/** owned — the materialized MCP handle (`Prepared.mcp`): clients, axes, server instructions, statuses, the
|
|
66
|
+
* refresh and the `dispose` the Runner's finish calls. Its prepare-failure release is the rollback entry
|
|
67
|
+
* this phase pushed (only when a real client was acquired — the inert stub owns nothing). */
|
|
68
|
+
mcp: MaterializedMcp;
|
|
69
|
+
/** owned — the materialized A2A handle: the driver projects it onto `Prepared.a2a` only when it carries
|
|
70
|
+
* tools. Same rollback posture as `mcp`, pushed after it (disposed before it on unwind). */
|
|
71
|
+
a2a: MaterializedA2a;
|
|
72
|
+
/** borrowed-mutable — the RefreshMcpTools rebuild seam (`current` undefined until installed). Writers: the
|
|
73
|
+
* tool-disclosure mount, by exactly one of its two mutually exclusive arms (the deferred arm's
|
|
74
|
+
* rematerialize; the else-arm's bare setTools + fingerprint sync). Read by the refresh closure at run time. */
|
|
75
|
+
rebuildHarnessToolsRef: {
|
|
76
|
+
current?: () => Promise<void>;
|
|
77
|
+
};
|
|
78
|
+
/** borrowed-mutable — the content-origin re-classification seat. Writer: the memory-engine-session phase
|
|
79
|
+
* (installs the re-runnable pass ONCE when it arms — a mounted engine-memory session or an upstream recorder
|
|
80
|
+
* channel; undefined otherwise). Read by the rebuild closures before a swapped-in tool becomes callable. */
|
|
81
|
+
contentOriginWrapRef: {
|
|
82
|
+
current?: () => void;
|
|
83
|
+
};
|
|
84
|
+
/** borrowed-mutable — whether the tool_call gate handler was REGISTERED at prepare time. Writer: the driver,
|
|
85
|
+
* once, at gate registration. Read by the refresh closure's honesty warning. */
|
|
86
|
+
toolCallGateArmedRef: {
|
|
87
|
+
armed: boolean;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** The M5 phase body — prepareTask's protocol-tools stretch, verbatim (see the module header). */
|
|
91
|
+
export declare function prepareProtocolTools(input: PrepareProtocolToolsInput): Promise<PrepareProtocolToolsResult>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { materializeMcpTools } from "../mcp.js";
|
|
3
|
+
import { materializeA2aTools } from "../a2a.js";
|
|
4
|
+
import { deliverEngineNotice } from "../types.js";
|
|
5
|
+
export async function prepareProtocolTools(input) {
|
|
6
|
+
const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, tools, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
|
|
7
|
+
const mcp = lockedPreflight.mcp?.length
|
|
8
|
+
? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
|
|
9
|
+
: { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
|
|
10
|
+
if (lockedPreflight.mcp?.length)
|
|
11
|
+
rollback.push(function mcpDispose() { return mcp.dispose(); });
|
|
12
|
+
for (const w of mcp.warnings)
|
|
13
|
+
onceLedger.onError(w, { phase: "mcp", sessionId });
|
|
14
|
+
for (const d of mcp.droppedTools) {
|
|
15
|
+
onceLedger.onError(new Error(`MCP tool dropped at intake — ${d.tool} (server "${d.server}"): ${d.reason}`), { phase: "mcp", sessionId });
|
|
16
|
+
}
|
|
17
|
+
onceLedger.pruneDroppedTools(mcp.droppedTools);
|
|
18
|
+
{
|
|
19
|
+
const callerNames = new Set((spec.tools ?? []).flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
20
|
+
const clash = mcp.tools.find((t) => [t.name, ...(t.aliases ?? [])].some((name) => callerNames.has(name)));
|
|
21
|
+
if (clash) {
|
|
22
|
+
const e = new Error(`Tool name "${clash.name}" is reserved by an injected MCP tool — a caller tool of the same name would silently shadow it.`);
|
|
23
|
+
e.code = "config.reserved_tool_name";
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
|
|
28
|
+
const rebuildHarnessToolsRef = {};
|
|
29
|
+
const contentOriginWrapRef = {};
|
|
30
|
+
const toolCallGateArmedRef = { armed: false };
|
|
31
|
+
if (lockedPreflight.mcp?.length) {
|
|
32
|
+
tools.push({
|
|
33
|
+
name: "RefreshMcpTools",
|
|
34
|
+
label: "RefreshMcpTools",
|
|
35
|
+
description: "Refresh the tool list of connected MCP servers. Never dials or re-dials connections — it only re-reads the tool list over the existing connection. The refreshed tools are available immediately; you can call them on your next step. Use when a server's expected tool is missing, or the tool list looks stale after a connection recovered. Omit `server` to refresh every connected server.",
|
|
36
|
+
parameters: Type.Object({
|
|
37
|
+
server: Type.Optional(Type.String({ description: "Name of a single MCP server to refresh; omit to refresh all connected servers." })),
|
|
38
|
+
}),
|
|
39
|
+
execute: async (_toolCallId, params) => {
|
|
40
|
+
const serverArg = params?.server;
|
|
41
|
+
const results = await mcp.refresh(typeof serverArg === "string" && serverArg.length > 0 ? serverArg : undefined);
|
|
42
|
+
const lines = [];
|
|
43
|
+
let changed = false;
|
|
44
|
+
let anyActiveFailure = false;
|
|
45
|
+
for (const r of results) {
|
|
46
|
+
if (r.status !== "refreshed" || r.tools === undefined) {
|
|
47
|
+
lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
|
|
48
|
+
if (r.status === "failed")
|
|
49
|
+
anyActiveFailure = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
|
|
53
|
+
const pushable = r.tools.filter((t) => !excludedSet.has(t.name));
|
|
54
|
+
const excludedNow = r.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
|
|
55
|
+
const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(r.prefix)));
|
|
56
|
+
const restoreDomain = (m, snap) => {
|
|
57
|
+
for (const name of [...m.keys()])
|
|
58
|
+
if (name.startsWith(r.prefix))
|
|
59
|
+
m.delete(name);
|
|
60
|
+
for (const [name, v] of snap)
|
|
61
|
+
m.set(name, v);
|
|
62
|
+
};
|
|
63
|
+
const priorDomainEffects = domainSnapshot(toolEffects);
|
|
64
|
+
const priorDomainNegatives = domainSnapshot(axisExplicitNegatives);
|
|
65
|
+
for (const name of priorDomainEffects.keys())
|
|
66
|
+
toolEffects.delete(name);
|
|
67
|
+
for (const name of priorDomainNegatives.keys())
|
|
68
|
+
axisExplicitNegatives.delete(name);
|
|
69
|
+
try {
|
|
70
|
+
foldProtocolAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)), "MCP");
|
|
71
|
+
}
|
|
72
|
+
catch (foldErr) {
|
|
73
|
+
restoreDomain(toolEffects, priorDomainEffects);
|
|
74
|
+
restoreDomain(axisExplicitNegatives, priorDomainNegatives);
|
|
75
|
+
lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
|
|
76
|
+
anyActiveFailure = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let domainAnchor = -1;
|
|
80
|
+
for (let i = tools.length - 1; i >= 0; i--) {
|
|
81
|
+
const t = tools[i];
|
|
82
|
+
if (t.name.startsWith(r.prefix)) {
|
|
83
|
+
domainAnchor = i;
|
|
84
|
+
tools.splice(i, 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const refreshedMounts = pushable.map((t) => remoteToolOffload(t));
|
|
88
|
+
if (domainAnchor >= 0)
|
|
89
|
+
tools.splice(domainAnchor, 0, ...refreshedMounts);
|
|
90
|
+
else
|
|
91
|
+
tools.push(...refreshedMounts);
|
|
92
|
+
changed = true;
|
|
93
|
+
const detail = [];
|
|
94
|
+
const shownAdded = r.added.filter((n) => !excludedSet.has(n));
|
|
95
|
+
if (shownAdded.length > 0)
|
|
96
|
+
detail.push(`added: ${shownAdded.join(", ")}`);
|
|
97
|
+
if (r.removed.length > 0)
|
|
98
|
+
detail.push(`removed: ${r.removed.join(", ")}`);
|
|
99
|
+
if (excludedNow.length > 0)
|
|
100
|
+
detail.push(`excluded by deployment config (not mounted): ${excludedNow.join(", ")}`);
|
|
101
|
+
if ((r.dropped?.length ?? 0) > 0)
|
|
102
|
+
detail.push(`dropped: ${r.dropped.map((d) => `${d.tool} (${d.reason.length > 90 ? `${d.reason.slice(0, 90)}…` : d.reason})`).join("; ")}`);
|
|
103
|
+
lines.push(`${r.server}: refreshed — ${pushable.length} tool${pushable.length === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
|
|
104
|
+
}
|
|
105
|
+
if (changed)
|
|
106
|
+
await rebuildHarnessToolsRef.current?.();
|
|
107
|
+
if (changed && !toolCallGateArmedRef.armed && (irreversibleTools.size > 0 || egressTools.size > 0)) {
|
|
108
|
+
lines.push("WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.");
|
|
109
|
+
}
|
|
110
|
+
const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text }],
|
|
113
|
+
details: { results },
|
|
114
|
+
terminate: false,
|
|
115
|
+
...(anyActiveFailure && !changed ? { isError: true } : {}),
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
toolEffects.set("RefreshMcpTools", "read");
|
|
120
|
+
}
|
|
121
|
+
const foldProtocolAxes = (axes, protocolLabel) => {
|
|
122
|
+
for (const axis of axes) {
|
|
123
|
+
if (axis.irreversibility === "always") {
|
|
124
|
+
irreversibilityTier.set(axis.name, "always");
|
|
125
|
+
irreversibleTools.add(axis.name);
|
|
126
|
+
}
|
|
127
|
+
if (axis.irreversibility === "never") {
|
|
128
|
+
axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), irreversible: false });
|
|
129
|
+
}
|
|
130
|
+
if (axis.egress === false) {
|
|
131
|
+
axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), egress: false });
|
|
132
|
+
}
|
|
133
|
+
if (axis.egress) {
|
|
134
|
+
if (axis.effect !== undefined && axis.effect !== "write") {
|
|
135
|
+
const e = new Error(`${protocolLabel} tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
|
|
136
|
+
e.code = "config.egress_requires_write_effect";
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
egressTools.add(axis.name);
|
|
140
|
+
}
|
|
141
|
+
if (!toolEffects.has(axis.name))
|
|
142
|
+
toolEffects.set(axis.name, axis.effect ?? "write");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
foldProtocolAxes(mcp.toolAxes, "MCP");
|
|
146
|
+
const a2aDeclared = spec.a2a?.length ? spec.a2a : undefined;
|
|
147
|
+
const a2a = a2aDeclared
|
|
148
|
+
? await materializeA2aTools(a2aDeclared, spec.principal, abortController.signal)
|
|
149
|
+
: { tools: [], toolAxes: [], warnings: [], statuses: [], refresh: async () => [], dispose: async () => { } };
|
|
150
|
+
if (a2aDeclared)
|
|
151
|
+
rollback.push(function a2aDispose() { return a2a.dispose(); });
|
|
152
|
+
for (const w of a2a.warnings)
|
|
153
|
+
deps.onError?.(w, { phase: "a2a", sessionId });
|
|
154
|
+
{
|
|
155
|
+
const callerNames = new Set((spec.tools ?? []).flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
156
|
+
const clash = a2a.tools.find((t) => callerNames.has(t.name));
|
|
157
|
+
if (clash) {
|
|
158
|
+
const e = new Error(`Tool name "${clash.name}" is reserved by an injected A2A tool — a caller tool of the same name would silently shadow it.`);
|
|
159
|
+
e.code = "config.reserved_tool_name";
|
|
160
|
+
throw e;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
|
|
164
|
+
foldProtocolAxes(a2a.toolAxes, "A2A");
|
|
165
|
+
return { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef };
|
|
166
|
+
}
|
|
167
|
+
function mcpRevocationWiring(deps, runId) {
|
|
168
|
+
if (deps.mcpRevocations === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
const ledger = deps.mcpRevocations;
|
|
171
|
+
return {
|
|
172
|
+
isRevoked: (name) => ledger.isRevoked(name),
|
|
173
|
+
onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
|
|
174
|
+
code: "mcp.revocation_probe_failed",
|
|
175
|
+
message: `the mcpRevocations.isRevoked probe threw — MCP dispatch fails OPEN (no server treated as revoked) until the probe recovers: ${e instanceof Error ? e.message : String(e)}`,
|
|
176
|
+
detail: { message: e instanceof Error ? e.message : String(e), runId },
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 §1.2 M7 — prepareTask's QUESTION-FACE phase, verbatim from the driver: the leg's ONE checkpoint-store
|
|
3
|
+
* read, the durable mandate as it binds at the question seat, the content-ask binding table (with the leak
|
|
4
|
+
* bound the routing lane checks against — the constant below), the stranded-answer disclosure and the leg-end
|
|
5
|
+
* settle, the three-way mount decision (live face / durable face / override), and the AskUserQuestion mount
|
|
6
|
+
* with its redeemed-approval, posture, fallback and synthetic-continuation arms. The interface is the
|
|
7
|
+
* dependency list the segment had implicitly (design/238 R-1).
|
|
8
|
+
*
|
|
9
|
+
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between the
|
|
10
|
+
* offload-reader mount before it and the scheduler mount after it (design/390 §1.5 S1: a stretch with no await
|
|
11
|
+
* is extracted as a sync function — an `async` wrapper would open a microtask window). Read-stability of the
|
|
12
|
+
* host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
13
|
+
*
|
|
14
|
+
* One seat is a LIVE GETTER (design/238 D-3): the inherited-unavailable marker set is declared by the driver's
|
|
15
|
+
* gate-fold section AFTER this phase and read per call at RUN time, so it enters as `() => ReadonlySet` — a
|
|
16
|
+
* read before the driver declares it throws (temporal dead zone), which is the fail-loud an uninitialized
|
|
17
|
+
* live read owes.
|
|
18
|
+
*/
|
|
19
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
20
|
+
import { type OnQuestion } from "../ask-question.js";
|
|
21
|
+
import { type CheckpointStore } from "../checkpoint-store.js";
|
|
22
|
+
import type { RunnerDeps, RuntimeCaps, TaskSpec } from "../types.js";
|
|
23
|
+
import type { PrepareResume } from "./contracts.js";
|
|
24
|
+
import { type ContentAskBinding } from "./content-ask-bindings.js";
|
|
25
|
+
export { CONTENT_ASK_BINDING_CAP, type ContentAskBinding } from "./content-ask-bindings.js";
|
|
26
|
+
export interface PrepareQuestionFaceInput {
|
|
27
|
+
/** borrowed-readonly — the run's frozen question seat (spec > deps), resolved once at the top of prepare;
|
|
28
|
+
* the mount predicate and the live-face derivation both read this one value. */
|
|
29
|
+
frozenOnQuestion: OnQuestion | undefined;
|
|
30
|
+
/** borrowed-readonly — the REBOUND spec. Read: `checkpointStore` (the per-run off switch), `durableApproval`
|
|
31
|
+
* (the durable face's opt-in), `interactiveTools` (the override), `principal` (the tool's source identity),
|
|
32
|
+
* `interactiveQuestionFallback` (restores the continuation under a resolved posture). Never mutated. */
|
|
33
|
+
spec: Pick<TaskSpec, "checkpointStore" | "durableApproval" | "interactiveTools" | "principal" | "interactiveQuestionFallback">;
|
|
34
|
+
/** borrowed-readonly — the deployment seats: `checkpointStore` (the store the leg snapshot resolves against)
|
|
35
|
+
* and `onError` (the stranded-answer and synthetic-continuation operator lines; swallow-guarded). */
|
|
36
|
+
deps: Pick<RunnerDeps, "checkpointStore" | "onError">;
|
|
37
|
+
/** borrowed-readonly — the principal's resolved runtime caps as of this point (`forceDurableGate` is the
|
|
38
|
+
* mandate that outranks a live face only where a store exists to park the question). */
|
|
39
|
+
runtimeCaps: Pick<RuntimeCaps, "forceDurableGate"> | undefined;
|
|
40
|
+
/** borrowed-readonly — the resolved interaction posture (the door's value), threaded to the tool so both
|
|
41
|
+
* no-answer arms harden under it. */
|
|
42
|
+
resolvedInteractionPosture: "interactive" | "headless" | undefined;
|
|
43
|
+
/** borrowed-readonly — the durable-resume carrier; only the redeemed content-ask call id and its questions
|
|
44
|
+
* hash are read (the one call an operator's approval was spent on). */
|
|
45
|
+
resume: Pick<PrepareResume, "redeemedContentAskCallId" | "redeemedContentAskQuestionsHash"> | undefined;
|
|
46
|
+
/** borrowed-readonly — the acquired session id (the tool's source task id; operator-line context). */
|
|
47
|
+
sessionId: string;
|
|
48
|
+
/** borrowed-mutable — the run's shared roster (identity is the contract). Writer here: ONE push (the question
|
|
49
|
+
* tool) when the mount predicate holds; `contentAskRoutable` reads `includes` per call against the LIVE
|
|
50
|
+
* array (the roster is edited in place after the mount). */
|
|
51
|
+
tools: AgentTool[];
|
|
52
|
+
/** borrowed-readonly — LIVE GETTER over the inherited-unavailable marker set (declared by the driver's
|
|
53
|
+
* gate-fold section after this phase; `has` only, per call at run time). See the module header. */
|
|
54
|
+
inheritedUnavailableAsks: () => ReadonlySet<string>;
|
|
55
|
+
}
|
|
56
|
+
export interface PrepareQuestionFaceResult {
|
|
57
|
+
/** borrowed-readonly — THE leg's store snapshot (`resolveCheckpointStore(spec, deps)`): the mount face, the
|
|
58
|
+
* routability predicate, the manifest and suspendAsk all read this one value. */
|
|
59
|
+
checkpointStore: CheckpointStore | undefined;
|
|
60
|
+
/** borrowed-readonly — the run's live question face through the shared predicate (the reserved resume
|
|
61
|
+
* placeholder is a function and is NOT a live face), or undefined. */
|
|
62
|
+
liveQuestionFace: OnQuestion | undefined;
|
|
63
|
+
/** borrowed-mutable — the one-shot answer binding table for questions the GATE routed (scope = this leg).
|
|
64
|
+
* Writers (R-3): the routing lane (`set`, cap-checked against {@link CONTENT_ASK_BINDING_CAP} before every
|
|
65
|
+
* insertion), the mounted face here (`delete`, one-shot redemption or a mismatched-batch drop), the settle
|
|
66
|
+
* here (`clear`). Identity is the contract (the routing lane and the face share this exact map). */
|
|
67
|
+
contentAskBindings: Map<string, ContentAskBinding>;
|
|
68
|
+
/** owned — THE routability predicate for a content ask (per call, against the live roster and the marker set). */
|
|
69
|
+
contentAskRoutable: (toolCallId: string) => boolean;
|
|
70
|
+
/** borrowed-mutable — late answers noted before the leg settles. Writer: the routing lane's delivery-race
|
|
71
|
+
* watcher (`push`); reader: the settle here (merged into the result disclosure by delivery id). */
|
|
72
|
+
lateStrandedAnswers: Array<{
|
|
73
|
+
deliveryId: string;
|
|
74
|
+
toolCallId: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** owned — THE disclosure lane for an answer a person gave that no call could collect (swallow-guarded). */
|
|
77
|
+
discloseStrandedAnswers: (records: ReadonlyArray<{
|
|
78
|
+
deliveryId: string;
|
|
79
|
+
toolCallId: string;
|
|
80
|
+
}>, why: string) => void;
|
|
81
|
+
/** owned — the leg-end settle (`Prepared.settleContentAskBindings`): discloses surviving answers, clears the
|
|
82
|
+
* table, returns the records the result carries. */
|
|
83
|
+
settleContentAskBindings: () => ReadonlyArray<{
|
|
84
|
+
deliveryId: string;
|
|
85
|
+
toolCallId: string;
|
|
86
|
+
}>;
|
|
87
|
+
/** owned — whether a durable delivery face exists (store wired ∧ (durableApproval ∨ forceDurableGate));
|
|
88
|
+
* the manifest's composition-lie detector reuses this exact value. */
|
|
89
|
+
durableQuestionFace: boolean;
|
|
90
|
+
/** owned — the face the tool was mounted with: the gate-bound answer for THIS call id if one exists (one-shot),
|
|
91
|
+
* else the live face itself; the raw frozen seat when no live face exists. */
|
|
92
|
+
mountedQuestionFace: OnQuestion | undefined;
|
|
93
|
+
/** owned — whether THIS prepare mounted the first-party question tool (the routing lane keys on it, not on
|
|
94
|
+
* the reserved name). */
|
|
95
|
+
questionToolMounted: boolean;
|
|
96
|
+
/** borrowed-readonly — the mounted tool object itself (the exact roster member), so the routing lane can
|
|
97
|
+
* validate a batch against the schema the executing call is re-checked against; undefined when not mounted. */
|
|
98
|
+
mountedQuestionTool: AgentTool | undefined;
|
|
99
|
+
}
|
|
100
|
+
/** The M7 phase body — prepareTask's question-face stretch, verbatim (see the module header). */
|
|
101
|
+
export declare function prepareQuestionFace(input: PrepareQuestionFaceInput): PrepareQuestionFaceResult;
|