UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

208 lines (207 loc) • 12.3 kB
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 }; }