@sema-agent/core
Version:
Stateless, task-oriented AI agent core
208 lines (207 loc) • 12.3 kB
JavaScript
import { createHash } from "node:crypto";
import { assemblePrompt } from "../../prompt-assembly/assemble.js";
import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
import { defaultTaskRegistry } from "../task-registry.js";
import { createSyntheticReadJudge } from "../synthetic-read-verdict.js";
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
import { decodeTextBytes } from "../../tools/fs/encoding.js";
import { applyContextShrinkToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
import { resolveKey } from "../../tools/fs/safety.js";
import { PROMPT_HASH_SALT } from "./prompt-hash-salt.js";
import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
import { thrownText } from "../thrown-value.js";
export function prepareTurnWiring(input) {
const { 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, callerPolicy, inheritedPolicies, persistedRuleLane, orgRuleLane, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder } = input;
const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
const cacheFingerprint = cacheBreakDetector
? { systemPrompt: systemPromptSeat.current, tools: toolsToFingerprintInputs(harnessTools) }
: undefined;
fpRef.current = cacheFingerprint;
const turnSnapshot = buildTurnPromptSnapshot({
provider: model.provider,
modelId: model.id,
artifactDigest: epochArtifactDigestForSnapshot,
promptProfile,
fableMitigations,
layoutGroups: (systemBlocks ?? [{ groupId: "body", cacheControlBoundary: true }]).map((b) => ({ groupId: b.groupId, cacheControlBoundary: b.cacheControlBoundary })),
stableSystemText: systemPromptSeat.current,
toolWire: toolsToFingerprintInputs(harnessTools),
thinkingLevel: thinking ?? null,
maxTokens: model.maxTokens,
hasOutputSchema: spec.outputSchema !== undefined,
sessionId,
taskId: spec.taskId,
}, model.api, (text) => `salted:${createHash("sha256").update(PROMPT_HASH_SALT).update(text).digest("hex")}`);
turnSnapshotRef.current = turnSnapshot;
promptManifest.snapshot = { cacheIdentity: turnSnapshot.cacheIdentity, elements: { ...turnSnapshot.elements } };
promptManifest.lowering = turnSnapshot.lowering;
const promptOverheadTokens = Math.ceil((systemPromptSeat.current.length +
harnessTools.reduce((a, t) => a + t.name.length + (t.description?.length ?? 0) + JSON.stringify(t.parameters ?? {}).length, 0)) / charsPerToken);
const announceAttachmentSkip = (path, reason) => {
deps.onError?.(new Error(`working-file attachment skipped — ${path}: ${reason}`), {
phase: "degraded",
sessionId,
classification: "attachment-read",
});
};
const readTaskFile = handsEnabled
? async (path) => {
try {
let key = path;
if (attachmentRootCanonical !== undefined) {
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, readDenyMatcher, resolvedReadFace);
if (!rk.ok) {
announceAttachmentSkip(path, rk.violation.message);
if (rk.violation.code === "read_path_denied" && rk.violation.pattern !== undefined) {
return { withheld: { pattern: rk.violation.pattern } };
}
return null;
}
key = rk.key;
}
const r = await executionEnv.readBinaryFile(key);
if (!r.ok) {
announceAttachmentSkip(path, r.error.message);
return null;
}
const d = decodeTextBytes(r.value);
if (d.malformed) {
announceAttachmentSkip(path, "malformed text encoding (truncated UTF-16 body)");
return null;
}
return d.text;
}
catch (err) {
announceAttachmentSkip(path, thrownText(err));
return null;
}
}
: undefined;
const normalizeAttachmentPath = handsEnabled
? async (path) => {
if (attachmentRootCanonical === undefined)
return path;
try {
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, handsCwdRef?.current, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, resolvedReadFace);
return rk.ok ? rk.key : path;
}
catch {
return path;
}
}
: undefined;
const isDedupStubResult = handsEnabled ? isReadDedupStubResult : undefined;
const recentlyReadFiles = handsEnabled && readFileStateForCheckpoint
? () => [...readFileStateForCheckpoint.entries()]
.filter(([, v]) => v.seededFromContext !== true)
.sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
.map(([path]) => path)
: undefined;
const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
? (attachedComplete, preserveReadState) => applyContextShrinkToReadFileState(readFileStateForCheckpoint, { kind: "summarized", attachedComplete, ...(preserveReadState !== undefined ? { preserveKeys: preserveReadState } : {}) })
: undefined;
const syntheticReadVerdict = createSyntheticReadJudge({
skillGrants,
...(denyNarrowingPolicy !== undefined ? { denyNarrowingPolicy } : {}),
...(callerPolicy !== undefined ? { callerPolicy } : {}),
inheritedPolicies,
...(persistedRuleLane !== undefined ? { persistedRuleLane } : {}),
...(orgRuleLane !== undefined ? { orgRuleLane } : {}),
signal: abortController.signal,
});
const changedFilesReadDenied = async (paths) => {
const deadlineAt = Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
return Promise.all(paths.map(async (path) => (await syntheticReadVerdict({ file_path: path }, "changed-files", { deadlineAt })).verdict === "deny"));
};
const detectExternalChanges = handsEnabled && readFileStateForCheckpoint
? async (maxFiles) => {
const candidates = [...readFileStateForCheckpoint.entries()]
.filter(([, e]) => e.lastReadAt !== undefined)
.filter(([, e]) => e.truncated !== true)
.sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
.slice(0, Math.max(0, maxFiles));
const changed = [];
const evicted = [];
const denied = await changedFilesReadDenied(candidates.map(([path]) => path));
for (const [i, [path, entry]] of candidates.entries()) {
if (denied[i] === true)
continue;
try {
const info = await executionEnv.fileInfo(path, abortController.signal);
if (!info.ok) {
if (info.error.code === "not_found") {
readFileStateForCheckpoint.delete(path);
evicted.push(path);
}
continue;
}
if (info.value.mtimeMs > (entry.lastReadAt ?? 0) + CHANGED_FILES_MTIME_EPS_MS) {
changed.push({ path, mtimeMs: info.value.mtimeMs });
}
}
catch {
}
}
return { changed, evicted };
}
: undefined;
const listBackgroundTasks = () => defaultTaskRegistry
.list({ owner: hostTaskId, scope: taskScope, sessionId })
.filter((t) => t.status === "pending" || t.status === "running" || t.status === "parked")
.map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}), status: t.status }));
const overheadState = { promptChars: 0 };
const centerCompactionCandidate = deps.promptSource !== undefined && centerAdoptionRef.current !== undefined
? () => {
const state = deps.promptSource.sourceState?.() ?? "unavailable";
const cand = deps.promptSource.current();
const toBundled = state === "disabled";
if (!toBundled && (cand === undefined || cand.artifact.artifactDigest === centerAdoptionRef.current.artifact.artifactDigest))
return undefined;
let a2;
try {
a2 = assemblePrompt(buildAssembleInputs(toBundled ? undefined : artifactDeclarations(cand.artifact)));
}
catch {
return undefined;
}
if (!toBundled && a2.centerMounted !== true)
return undefined;
const declared2 = (a2.sections ?? [])
.filter((sec) => sec.origin === "provider")
.map((sec) => ({ id: sec.id, slot: sec.slot, chars: sec.text.length, ...(sec.declaredContentHash !== undefined ? { contentHash: sec.declaredContentHash } : {}) }));
const overheadTokensAfter = preparedHolder.current.promptOverheadTokens -
Math.ceil(overheadState.promptChars / charsPerToken) +
Math.ceil(a2.systemPrompt.length / charsPerToken);
return {
...(toBundled
? { clear: true }
: {
centerArtifactDigest: cand.artifact.artifactDigest,
...(cand.sourceRevision !== undefined ? { sourceRevision: cand.sourceRevision } : {}),
}),
declaredSections: declared2,
overheadTokensAfter,
apply: (committedArtifactDigest) => {
systemPromptSeat.current = a2.systemPrompt;
harnessRef.current?.setSystemPrompt(a2.systemPrompt, a2.systemBlocks);
if (fpRef.current)
fpRef.current.systemPrompt = a2.systemPrompt;
turnSnapshotRef.current?.refreshStableSystem(a2.systemPrompt, committedArtifactDigest);
preparedHolder.current.epochDeclaredSections = declared2;
preparedHolder.current.promptOverheadTokens = overheadTokensAfter;
overheadState.promptChars = a2.systemPrompt.length;
centerAdoptionRef.current = toBundled
? undefined
: { artifact: cand.artifact, ...(cand.sourceRevision !== undefined ? { sourceRevision: cand.sourceRevision } : {}) };
},
};
}
: undefined;
overheadState.promptChars = systemPromptSeat.current.length;
const effectiveReadFaceObserved = carrierReadFace();
const effectiveWriteFaceObserved = carrierWriteFace();
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
return { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, syntheticReadVerdict, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved };
}