UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

182 lines (181 loc) • 11.5 kB
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)))]; }