@sema-agent/core
Version:
Stateless, task-oriented AI agent core
182 lines (181 loc) • 11.5 kB
JavaScript
import { denyFloorShadowsOf } from "./shell-read-judge.js";
import { GENERATOR_DEVICES, OPENED_PROVENANCES, passesUnasked, standardDeviceUnrebound } from "./shell-read-device.js";
import { renamed } from "./shell-read-word.js";
import { byteBoundIsSmall, implementationsOf, programRow, readArgRoles, selectedOptions, subcommandOf, writeReadings } from "./program-read-model.js";
import { programBasename, readProgramPosition } from "./bash-program-position.js";
export function programArgs(segment) {
const words = segment.argv.map((w) => ({ raw: w.raw, spelled: w.folded }));
const at = words.findIndex((w) => w.spelled === segment.verb?.name);
return at < 0 ? [] : words.slice(at + 1);
}
export function sedBackupNames(name, suffix) {
const appended = `${name}${suffix}`;
return suffix.includes("*") ? [suffix.replace(/\*/g, () => name), appended] : [appended];
}
export const anyOptionWord = (words) => words.some((w) => w.startsWith("-"));
function writesThrough(roles, row, words) {
const args = words.map((w) => w.spelled);
if (roles.length === 0 || readArgRoles(row, args).some((e) => e.kind === "option" && e.reading === "speculative"))
return false;
return roles.every((role) => role.through === true || (role.through === "plain" && !anyOptionWord(args)));
}
export function writesOnlyStandardDevices(program, args, line) {
const row = programRow(program);
if (row === undefined)
return false;
const words = args.map((a) => ({ raw: a, spelled: a }));
const readings = writeReadings(row, [...args]);
return (readings.length > 0 &&
readings.every((r) => {
const written = r.written.filter((w) => w.role.writtenAt?.length !== 0);
return !r.unexplained && written.length > 0 && writesThrough(written.map((w) => w.role), row, words) && written.every((w) => standardDeviceUnrebound(w.value ?? args[w.at], line, []));
}));
}
const WORD_PROVENANCES = new Set(["operand", "unlisted_word", "option_value"]);
function writeOwnersOf(segment) {
const pos = readProgramPosition(segment.argv);
const word = (i) => ({ raw: segment.argv[i].raw, spelled: segment.argv[i].folded });
const owners = [];
pos.launchers.forEach((l, i) => {
const row = programRow(l.name);
const end = pos.launchers[i + 1]?.index ?? pos.program?.index ?? segment.argv.length;
if (row !== undefined)
owners.push({ row, words: Array.from({ length: Math.max(0, end - l.index - 1) }, (_, k) => word(l.index + 1 + k)), program: false });
});
const program = pos.program !== undefined ? programRow(programBasename(pos.program.name)) : undefined;
if (program !== undefined && segment.verb !== undefined)
owners.push({ row: program, words: programArgs(segment), program: true });
return owners;
}
function writeTargetsOf(segment) {
const out = [];
const writeOnly = new Set();
const write = (c, seats, written, child, onlyIfExists, through) => void out.push({ candidate: { ...c, face: "write", ...(through === true ? { through } : {}) }, ...(seats !== undefined ? { seats } : {}), ...(written !== undefined ? { written } : {}), ...(child === true ? { child } : {}), ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) });
for (const r of segment.redirects)
if (r.direction === "out" && r.target !== undefined)
write(r.target, undefined, undefined, undefined, undefined, true);
for (const c of segment.reads)
if (c.provenance === "write_redirect_target")
write(c, undefined, undefined, undefined, undefined, true);
const owners = writeOwnersOf(segment).map(({ row, words, program }) => {
const args = words.map((w) => w.spelled);
const all = writeReadings(row, args);
const selected = implementationsOf(row).flatMap((view) => selectedOptions(view, args, "one-implementation"));
const moved = [...new Set(selected.flatMap((o) => o.writesAt ?? []))];
return { row, words, program, all, readings: all.filter((r) => r.written.length > 0 || (r.unexplained && r.operandRole !== undefined)), override: moved.length > 0 ? moved : undefined, beyondOperands: selected.some((o) => o.effect === "writes" || o.effect === "executes") };
});
const edited = new Map();
for (const c of segment.reads) {
if (!WORD_PROVENANCES.has(c.provenance))
continue;
const placed = owners.filter((o) => o.words.some((w) => w.raw === c.raw));
const fallback = placed.length === 0 ? owners.filter((o) => o.program) : [];
let only = placed.length > 0;
for (const o of [...placed, ...fallback]) {
if (o.readings.length === 0) {
only = false;
continue;
}
const at = new Set(o.words.flatMap((w, i) => (w.raw === c.raw ? [i] : [])));
const roles = [];
let readings = 0;
let unexplained = false;
for (const r of o.readings) {
const unplaced = (r.unexplained || at.size === 0) && r.operandRole !== undefined;
const found = unplaced ? [r.operandRole] : r.written.filter((w) => at.has(w.at)).map((w) => w.role);
if (found.length === 0)
continue;
roles.push(...found);
readings += 1;
unexplained ||= unplaced;
}
if (roles.length === 0) {
only = false;
continue;
}
only &&= !unexplained && readings === o.all.length && roles.every((role) => role.effect !== "edit-in-place" && role.writtenAt === undefined);
const stated = roles.some((role) => (o.override ?? role.seats) !== undefined);
const seats = stated ? [...new Set(roles.flatMap((role) => o.override ?? role.seats ?? ["landing"]))] : undefined;
const written = unexplained || roles.some((role) => role.writtenAt === undefined) ? undefined : [...new Set(roles.flatMap((role) => role.writtenAt ?? []))];
const speculative = readings < o.all.length && roles.every((role) => role.effect === "edit-in-place") ? c : undefined;
write(c, seats, written, undefined, speculative, !unexplained && writesThrough(roles, o.row, o.words) ? true : undefined);
if (roles.some((role) => role.effect === "edit-in-place") && c.expands === false)
edited.set(o, [...(edited.get(o) ?? []), { candidate: c, ...(speculative !== undefined ? { onlyIfExists: speculative } : {}) }]);
}
if (only)
writeOnly.add(c);
}
for (const o of owners) {
for (const c of copyDestinationChildren(segment, o.row, o.words, o.readings))
write(c, ["landing", "entry"], undefined, true);
out.push(...backupTargetsOf(edited.get(o) ?? [], o.readings, o.override));
}
const readWords = new Set();
for (const o of owners) {
if (!o.program)
continue;
const args = o.words.map((w) => w.spelled);
const found = subcommandOf(o.row, args);
const sub = found !== undefined && found.row.operands !== undefined ? found : undefined;
const at = sub === undefined ? 0 : sub.at + 1;
for (const e of readArgRoles(sub?.row ?? o.row, args.slice(at))) {
const role = e.kind === "operand" || e.kind === "value" ? e.role : undefined;
if (role?.kind === "path" && (role.effect === "read" || role.writtenAt?.length === 0))
readWords.add(o.words[at + e.at].raw);
}
}
return { targets: out, writeOnly, beyondOperands: owners.some((o) => o.beyondOperands), boundedRead: owners.some((o) => o.program && byteBoundIsSmall(o.row, o.words.map((w) => w.spelled))), readWords };
}
function backupTargetsOf(edited, readings, override) {
const suffixes = [...new Set(readings.flatMap((r) => r.suffixes))];
const follows = override !== undefined && !override.includes("entry");
return edited.flatMap(({ candidate: f, onlyIfExists }) => suffixes.flatMap((suffix) => follows
? [{ candidate: { ...renamed(f, `${f.spelled}${suffix}`), face: "write" }, seats: ["entry"], followed: { operand: f, suffix }, ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }]
: sedBackupNames(f.spelled, suffix).map((spelled) => ({ candidate: { ...renamed(f, spelled), face: "write" }, seats: ["entry"], ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }))));
}
function copyDestinationChildren(segment, row, words, readings) {
const candidateOf = (raw) => segment.reads.find((c) => c.raw === raw && WORD_PROVENANCES.has(c.provenance));
const children = new Map();
for (const r of readings) {
if (r.copy === undefined)
continue;
const destWord = words[r.copy.destination.at];
const dest = { raw: destWord.raw, spelled: r.copy.destination.value ?? destWord.spelled };
const destCandidate = candidateOf(dest.raw);
if (destCandidate !== undefined && destCandidate.expands !== false)
continue;
const destRole = r.written.find((w) => w.at === r.copy.destination.at)?.role;
if (destCandidate !== undefined && destRole !== undefined && passesUnasked({ ...destCandidate, face: "write", ...(writesThrough([destRole], row, words) ? { through: true } : {}) }))
continue;
for (const at of r.copy.sources) {
const src = words[at];
const source = candidateOf(src.raw);
if (source !== undefined && source.expands !== false)
continue;
const child = `${dest.spelled.replace(/\/+$/, "")}/${src.spelled.replace(/\/+$/, "").split("/").pop() ?? ""}`;
if (child.endsWith("/") || children.has(child))
continue;
children.set(child, destCandidate !== undefined && source !== undefined ? renamed(destCandidate, child) : { spelled: child, raw: src.raw, provenance: "unlisted_word", expands: "unresolvable", bases: [] });
}
}
return [...children.values()];
}
export function judgedCandidatesOf(segment) {
const { targets: writes, writeOnly, beyondOperands, boundedRead, readWords } = writeTargetsOf(segment);
const throughWritten = new Set(writes.filter((w) => w.candidate.through === true && w.candidate.device === true).map((w) => w.candidate.raw));
const opened = (c) => {
if (c.provenance === "unlisted_word" && c.device === true && !readWords.has(c.raw) && !throughWritten.has(c.raw)) {
const { device: _unread, ...judged } = c;
return judged;
}
if (boundedRead && c.device !== true && c.expands === false && c.speculative === undefined && c.denyOnly !== true && GENERATOR_DEVICES.has(c.spelled) && OPENED_PROVENANCES.has(c.provenance) && (c.provenance !== "unlisted_word" || readWords.has(c.raw)))
return { ...c, device: true };
if (!beyondOperands || c.device !== true || c.provenance === "stdin_redirect")
return c;
const { device: _device, ...judged } = c;
return judged;
};
const reads = segment.reads.flatMap((c) => (c.provenance === "write_redirect_target" ? [] : [opened(writeOnly.has(c) ? { ...c, denyOnly: true } : c)]));
const plain = (cs) => cs.map((candidate) => ({ candidate }));
return [...plain(reads), ...plain(denyFloorShadowsOf(reads)), ...writes, ...plain(denyFloorShadowsOf(writes.map((w) => w.candidate)))];
}