@sema-agent/core
Version:
Stateless, task-oriented AI agent core
166 lines (165 loc) • 11.8 kB
JavaScript
import { deliverEngineNotice } from "../../core/types.js";
import { createTaskOutputTool, createTaskStopTool } from "../../core/task-registry.js";
import { hasBackgroundShell } from "../../core/background-shell.js";
import {} from "./safety.js";
import { createRepoMapTool } from "./repo-map.js";
export * from "./fs-shared.js";
export * from "./fs-pdf.js";
export * from "./fs-read.js";
export * from "./fs-write.js";
export * from "./fs-search-tools.js";
export * from "./bash-readonly-classifier.js";
export * from "./fs-bash.js";
export * from "./read-deny.js";
export { readDenyHomesOf } from "./read-deny-home.js";
import { BASH_READONLY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, isRootedReadDenyPattern, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
import { withGateTimeout } from "../../core/gate-bounded-await.js";
import { carriedReadFaceOf, carryHomeViews, homeViewsAt, placeReadDenyAtHome, placementViewsOf, readDenyPlacedLater, sayPlacementLost } from "./read-deny-home.js";
import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
export * from "./containment-face.js";
import {} from "./fs-shared.js";
import { createModelFileReader, createReadFileTool } from "./fs-read.js";
import { createEditFileTool, createWriteFileTool, createNotebookEditTool } from "./fs-write.js";
import { createGrepTool, createGlobTool } from "./fs-search-tools.js";
import { createBashTool, createBashReadonlyTool, createEnvTaskOutputTool, createEnvTaskStopTool } from "./fs-bash.js";
export function handsWriteHalfMounts(opts) {
return opts.readOnly !== true;
}
export function backgroundOutputReadExemption(registry, seat, env) {
if (registry === undefined)
return undefined;
return (canonicalKey, c) => registry.isBackgroundOutputFile(canonicalKey, { owner: c?.taskId ?? seat.taskOwner, scope: c?.principal ?? seat.taskScope, ...(seat.sessionId !== undefined ? { sessionId: seat.sessionId } : {}) }, env);
}
export function createHandsToolkit(env, readFileState, rootCanonical, opts = {}) {
const { includeShell = false, readOnly = false, bashReadonlyAllow, commitCoAuthor = false, mountBackgroundTaskTools = true, additionalRoots, additionalReadRoots, } = opts;
const carriedFace = opts.readDenyHomes !== undefined ? carriedReadFaceOf(opts.readDenyHomes) : undefined;
const readFaceRoots = carriedFace?.readRoots ?? (additionalReadRoots === undefined ? additionalRoots : () => (additionalReadRoots.length === 0 ? additionalRoots : [...(additionalRoots ?? []), ...additionalReadRoots]));
const cwdRef = opts.cwdRef ?? { current: rootCanonical };
const bgOutput = backgroundOutputReadExemption(opts.taskRegistry, opts, env);
const readAdmission = opts.readAdmission;
const bgOutputReadExemption = bgOutput === undefined && readAdmission === undefined
? undefined
: (canonicalKey, c) => readAdmission?.(c.toolCallId, canonicalKey) === true || (bgOutput?.(canonicalKey, c) ?? false);
const { readDeny, writeDeny, afterPlacement } = carriedFace !== undefined ? { readDeny: carriedFace.readDeny, writeDeny: carriedFace.writeDeny, afterPlacement: (tool) => tool } : bandOwnTables(env, opts);
const readFace = resolveContainmentFace({
kind: "read",
depsFace: opts.readFace,
readOnlyMount: readOnly,
orgGoverned: false,
fullShellReachable: includeShell && !readOnly,
onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("read")),
});
const writeFace = resolveContainmentFace({
kind: "write",
depsFace: opts.writeFace,
readOnlyMount: readOnly,
orgGoverned: false,
fullShellReachable: includeShell && !readOnly,
onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("write")),
});
const writeFaceOf = opts.writeFaceLive ?? (() => writeFace);
const readCwd = readOnly ? undefined : cwdRef;
const reader = createModelFileReader({
env,
state: readFileState,
rootCanonical,
...(readCwd !== undefined ? { cwdRef: readCwd } : {}),
...(readFaceRoots !== undefined ? { additionalRoots: readFaceRoots } : {}),
imageDownsampler: opts.readImageDownsampler,
...(opts.pdfModelCapabilities !== undefined ? { pdfCapabilities: opts.pdfModelCapabilities } : {}),
...(bgOutputReadExemption !== undefined ? { bgOutputReadExemption } : {}),
...(readDeny !== undefined ? { readDeny } : {}),
...(readFace !== undefined ? { readFace } : {}),
...(opts.reminderMark !== undefined ? { reminderMark: opts.reminderMark } : {}),
...(opts.reminderDisclosureCounts !== undefined ? { reminderDisclosureCounts: opts.reminderDisclosureCounts } : {}),
});
if (opts.modelFileReaderSeat !== undefined)
opts.modelFileReaderSeat.current = reader;
const tools = [
afterPlacement(createReadFileTool(env, readFileState, rootCanonical, readCwd, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts, reader)),
];
if (handsWriteHalfMounts({ readOnly })) {
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny));
}
tools.push(afterPlacement(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)));
if (includeShell) {
tools.push(readOnly
? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
...(opts.bashDefaultTimeoutMs !== undefined ? { bashDefaultTimeoutMs: opts.bashDefaultTimeoutMs } : {}),
...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
...(opts.onNotice !== undefined ? { onNotice: opts.onNotice } : {}),
...(opts.shellReadAdmission !== undefined ? { shellReadAdmission: opts.shellReadAdmission } : {}),
...(opts.onToolProgress !== undefined ? { onToolProgress: opts.onToolProgress } : {}),
})
: createBashTool(env, rootCanonical, commitCoAuthor, cwdRef, {
taskRegistry: opts.taskRegistry,
taskOwner: opts.taskOwner,
taskScope: opts.taskScope,
...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
...(opts.retainBackgroundProcesses !== undefined ? { retainBackgroundProcesses: opts.retainBackgroundProcesses } : {}),
onTaskNotification: opts.taskNotification,
detachHub: opts.detachHub,
...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
...(opts.onToolProgress !== undefined ? { onToolProgress: opts.onToolProgress } : {}),
...(opts.queuedHumanInput !== undefined ? { queuedHumanInput: opts.queuedHumanInput } : {}),
...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
...(additionalRoots !== undefined ? { additionalRoots } : {}),
...(opts.shellReadAdmission !== undefined ? { shellReadAdmission: opts.shellReadAdmission } : {}),
...(opts.bashDefaultTimeoutMs !== undefined ? { bashDefaultTimeoutMs: opts.bashDefaultTimeoutMs } : {}),
...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
...(opts.onNotice !== undefined ? { onNotice: opts.onNotice } : {}),
...(opts.monitorToolActive !== undefined ? { monitorToolActive: opts.monitorToolActive } : {}),
...(opts.reminderMark !== undefined ? { reminderMark: opts.reminderMark } : {}),
}));
if (!readOnly && mountBackgroundTaskTools && hasBackgroundShell(env)) {
const sessionAxis = opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {};
tools.push(opts.taskRegistry
? createTaskOutputTool({
registry: opts.taskRegistry,
owner: opts.taskOwner,
scope: opts.taskScope,
...sessionAxis,
...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
notificationWired: opts.taskNotification !== undefined,
})
: createEnvTaskOutputTool(env), opts.taskRegistry
? createTaskStopTool({ registry: opts.taskRegistry, owner: opts.taskOwner, scope: opts.taskScope, ...sessionAxis })
: createEnvTaskStopTool(env));
}
}
return tools;
}
function bandOwnTables(env, opts) {
const readDenyAdditions = (opts.readDenyPatterns ?? []).map((e) => (typeof e === "object" && e !== null ? { ...e } : e));
const readDenyRules = opts.pathDenyRules === undefined ? undefined : [...opts.pathDenyRules];
const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: opts.readDenyBuiltinTiers, exclude: opts.readDenyBuiltinExclude });
const readDenyHomes = opts.readDenyHomes === undefined ? undefined : [...checkedReadDenyHomes(opts.readDenyHomes)];
const compileAt = (homes, linked) => compileReadDeny(readDenyAdditions, "HandsToolkitOptions.readDenyPatterns", readDenyBuiltinCfg, readDenyRules, homes, linked);
const unplaced = compileAt([]);
const carried = readDenyHomes !== undefined && opts.readDenyHomeLinks !== undefined;
const carriedViews = carried ? placementViewsOf(opts.readDenyHomes) : undefined;
if (carried && carriedViews === undefined)
sayPlacementLost();
const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored || isRootedReadDenyPattern(e.pattern));
const late = carried || placesNothing ? undefined : readDenyPlacedLater(carryHomeViews(unplaced, homeViewsAt(env, [])), () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
const readDeny = late?.matcher ??
(carried
? carriedViews !== undefined
? carryHomeViews(compileAt(readDenyHomes, opts.readDenyHomeLinks), carriedViews)
: carryHomeViews(unplaced, homeViewsAt(env, []))
: carryHomeViews(unplaced, homeViewsAt(env, readDenyHomes ?? [])));
const afterPlacement = (tool) => late === undefined
? tool
: {
...tool,
execute: async (...args) => {
await withGateTimeout(late.ready(), undefined, args[2], "readDenyPlacement").catch(() => undefined);
return await tool.execute(...args);
},
};
const writeDenyCompiled = compileWriteDeny(readDenyRules);
const writeDeny = writeDenyCompiled.ruleRows.length > 0 ? carryHomeViews(writeDenyCompiled, readDeny.homeViews) : undefined;
return { readDeny, writeDeny, afterPlacement };
}