UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

223 lines (222 loc) • 13.1 kB
import { createHash } from "node:crypto"; import { assemblePrompt } from "../../prompt-assembly/assemble.js"; import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js"; import { adoptCenter } from "../../prompt-assembly/artifact.js"; import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js"; import { defaultTaskRegistry } from "../task-registry.js"; import { createSyntheticReadJudge } from "../synthetic-read-verdict.js"; import { createConsolidationWriteJudge } from "./memory-consolidation-write-judge.js"; import { toolCallFaceOf } from "../tool-roster.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, promptIdentityDigest, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, readRoots, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, skillGrants, callerPolicy, inheritedPolicies, persistedRuleLane, ruleLaneFacts, personRowsBeyondRun, orgRuleLane, stampCallTarget, abortController, hostTaskId, taskScope, buildAssembleInputs, identityFor, adoptsCandidates, centerAdoptionRef, harnessRef, 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: promptIdentityDigest, 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) { await readRoots.ready(abortController.signal); if (abortController.signal.aborted) return null; const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, abortController.signal, undefined, readRoots(), 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, abortController.signal); 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 { await readRoots.ready(abortController.signal); if (abortController.signal.aborted) return path; const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, abortController.signal, handsCwdRef?.current, readRoots(), 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 syntheticLayers = { skillGrants, ...(denyNarrowingPolicy !== undefined ? { denyNarrowingPolicy } : {}), ...(callerPolicy !== undefined ? { callerPolicy } : {}), inheritedPolicies, ...(persistedRuleLane !== undefined ? { persistedRuleLane } : {}), ...(orgRuleLane !== undefined ? { orgRuleLane } : {}), ...(stampCallTarget !== undefined ? { stampCallTarget } : {}), }; const syntheticReadVerdict = createSyntheticReadJudge({ ...syntheticLayers, signal: abortController.signal }); const consolidationWriteJudge = createConsolidationWriteJudge({ ruleStoreProvider: deps.permissionRuleStore, principal: spec.principal, localOwnerDeclared: deps.localOwnerRules === true, ...(callerPolicy !== undefined ? { callerPolicy } : {}) }, { rows: personRowsBeyondRun, lanes: ruleLaneFacts, layers: { skillGrants, ...(denyNarrowingPolicy !== undefined ? { denyNarrowingPolicy } : {}), inheritedPolicies, ...(stampCallTarget !== undefined ? { stampCallTarget } : {}) }, facts: () => { const p = preparedHolder.current; return { face: toolCallFaceOf(p?.toolRoster, "Write"), cwd: p?.cwdRef?.current, root: p?.handsRoot, advertisedWritableDirs: p?.advertisedWritableDirs, exemptRoots: p?.exemptRoots }; } }); 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 && adoptsCandidates ? () => { const state = deps.promptSource.sourceState(); const now = centerAdoptionRef.current; let next; if (state === "disabled") { if (now === undefined) return undefined; next = undefined; } else if (state === "active") { const cand = deps.promptSource.current(); if (cand === undefined || cand.artifactDigest === now?.artifact.artifactDigest) return undefined; next = adoptCenter(cand, cand.sourceRevision); if (next === undefined) return undefined; } else { return undefined; } let a2; try { a2 = assemblePrompt(buildAssembleInputs(next)); } catch { return undefined; } const promptIdentity = identityFor(next); const overheadTokensAfter = preparedHolder.current.promptOverheadTokens - Math.ceil(overheadState.promptChars / charsPerToken) + Math.ceil(a2.systemPrompt.length / charsPerToken); return { promptIdentity, overheadTokensAfter, apply: (committedIdentity) => { systemPromptSeat.current = a2.systemPrompt; harnessRef.current?.setSystemPrompt(a2.systemPrompt, a2.systemBlocks); if (fpRef.current) fpRef.current.systemPrompt = a2.systemPrompt; turnSnapshotRef.current?.refreshStableSystem(a2.systemPrompt, committedIdentity); preparedHolder.current.promptIdentity = promptIdentity; preparedHolder.current.promptOverheadTokens = overheadTokensAfter; overheadState.promptChars = a2.systemPrompt.length; centerAdoptionRef.current = next; }, }; } : 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, consolidationWriteJudge, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved }; }