aperta-cli 1.0.0-beta.2 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -36
- package/dashboard/dist/assets/index-GUF8oKgT.js +23 -0
- package/dashboard/dist/assets/index-sPVDvMWT.css +1 -0
- package/dashboard/dist/index.html +4 -4
- package/dist-cli/src/agent-harness.js +53 -138
- package/dist-cli/src/agent-harness.js.map +1 -1
- package/dist-cli/src/agent-runtime.js +147 -0
- package/dist-cli/src/agent-runtime.js.map +1 -0
- package/dist-cli/src/capture.js +4 -6
- package/dist-cli/src/capture.js.map +1 -1
- package/dist-cli/src/cli.js +2 -2
- package/dist-cli/src/cli.js.map +1 -1
- package/dist-cli/src/dashboard-data.js +13 -4
- package/dist-cli/src/dashboard-data.js.map +1 -1
- package/dist-cli/src/dashboard-server.js +87 -21
- package/dist-cli/src/dashboard-server.js.map +1 -1
- package/dist-cli/src/harness-intelligence.js +3 -2
- package/dist-cli/src/harness-intelligence.js.map +1 -1
- package/dist-cli/src/ledger.js +61 -2
- package/dist-cli/src/ledger.js.map +1 -1
- package/dist-cli/src/ownership-dossier.js +154 -0
- package/dist-cli/src/ownership-dossier.js.map +1 -0
- package/dist-cli/src/proof-graph.js +71 -6
- package/dist-cli/src/proof-graph.js.map +1 -1
- package/dist-cli/src/settings.js +28 -37
- package/dist-cli/src/settings.js.map +1 -1
- package/dist-cli/src/skills.js +8 -1
- package/dist-cli/src/skills.js.map +1 -1
- package/package.json +3 -2
- package/dashboard/dist/assets/index-BIU9XLtE.css +0 -1
- package/dashboard/dist/assets/index-DHF2W8ef.js +0 -11
|
@@ -4,7 +4,6 @@ import { access, copyFile, mkdir, mkdtemp, readFile, rename, rm, stat, writeFile
|
|
|
4
4
|
import { createConnection } from "node:net";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, isAbsolute, join, normalize, sep } from "node:path";
|
|
7
|
-
import { createInterface } from "node:readline";
|
|
8
7
|
import { promisify } from "node:util";
|
|
9
8
|
import { requestProviderAction } from "./coach.js";
|
|
10
9
|
import { createRepositorySnapshot, diffSnapshots } from "./git.js";
|
|
@@ -13,6 +12,7 @@ import { cleanExecutionOutput, safeEnvironment } from "./execution.js";
|
|
|
13
12
|
import { assertSkillAllowsAction, selectAgentSkill, skillPrompt } from "./skills.js";
|
|
14
13
|
import { privateCachePath } from "./storage.js";
|
|
15
14
|
import { initializeStore } from "./ledger.js";
|
|
15
|
+
import { agentVRuntime, eventAction } from "./agent-runtime.js";
|
|
16
16
|
const execFileAsync = promisify(execFile);
|
|
17
17
|
const runs = new Map();
|
|
18
18
|
// A first implementation often consumes most of the original 24-step budget.
|
|
@@ -188,7 +188,20 @@ function finalizeEvidence(run) {
|
|
|
188
188
|
}
|
|
189
189
|
export function classifyAgentError(reason) {
|
|
190
190
|
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
191
|
+
const failureCode = reason?.code;
|
|
191
192
|
const message = error.message.toLowerCase();
|
|
193
|
+
if (failureCode === "cancelled")
|
|
194
|
+
return "UserAborted";
|
|
195
|
+
if (failureCode === "timeout")
|
|
196
|
+
return "Timeout";
|
|
197
|
+
if (["authentication-required", "invocation-failed", "empty-response"].includes(failureCode ?? ""))
|
|
198
|
+
return "ProviderError";
|
|
199
|
+
if (failureCode === "engine-unavailable")
|
|
200
|
+
return "UnexpectedEnvironment";
|
|
201
|
+
if (["invalid-json", "output-invalid"].includes(failureCode ?? ""))
|
|
202
|
+
return "InvalidModelOutput";
|
|
203
|
+
if (["permission-denied", "unsupported-capability", "configuration-invalid"].includes(failureCode ?? ""))
|
|
204
|
+
return "InvalidArguments";
|
|
192
205
|
if (error.name === "AbortError" || /\bcancel(?:ed|led)?\b|user aborted/.test(message))
|
|
193
206
|
return "UserAborted";
|
|
194
207
|
if (/timed? out|timeout/.test(message))
|
|
@@ -983,144 +996,46 @@ export async function runModelAgent(root, intent, config, signal, fetcher = fetc
|
|
|
983
996
|
await cleanupWorkspace(root, workspace, worktree);
|
|
984
997
|
}
|
|
985
998
|
}
|
|
986
|
-
function
|
|
987
|
-
if (typeof value !== "string" || !value.trim())
|
|
988
|
-
return undefined;
|
|
989
|
-
const path = value.replaceAll("\\", "/");
|
|
990
|
-
const root = workspace.replaceAll("\\", "/").replace(/\/$/, "");
|
|
991
|
-
if (path === root)
|
|
992
|
-
return ".";
|
|
993
|
-
if (path.startsWith(`${root}/`))
|
|
994
|
-
return path.slice(root.length + 1);
|
|
995
|
-
return path.match(/\/aperta-agent-[^/]+\/(.+)$/)?.[1] ?? path;
|
|
996
|
-
}
|
|
997
|
-
function externalToolDetail(tool, path, input) {
|
|
998
|
-
const target = path ? ` ${path}` : "";
|
|
999
|
-
if (/^(?:read|view|open)$/.test(tool))
|
|
1000
|
-
return `Inspecting${target || " a repository file"}.`;
|
|
1001
|
-
if (/^(?:edit|write|patch|apply_patch)$/.test(tool))
|
|
1002
|
-
return `Updating${target || " repository content"}.`;
|
|
1003
|
-
if (/^(?:glob|grep|search|find)$/.test(tool)) {
|
|
1004
|
-
const query = [input.pattern, input.query, input.glob].find((value) => typeof value === "string");
|
|
1005
|
-
return query ? `Searching the repository for ${String(query).slice(0, 180)}.` : "Searching the repository.";
|
|
1006
|
-
}
|
|
1007
|
-
if (/^(?:run|bash|shell|command)$/.test(tool))
|
|
1008
|
-
return "Running a bounded repository command.";
|
|
1009
|
-
return `${tool.replaceAll("_", " ")} completed.`;
|
|
1010
|
-
}
|
|
1011
|
-
/** Converts provider-specific JSONL into the small, human-readable activity vocabulary Aperta owns. */
|
|
1012
|
-
export function normalizeExternalRuntimeEvent(event, workspace) {
|
|
1013
|
-
const type = typeof event.type === "string" ? event.type.toLowerCase() : "event";
|
|
1014
|
-
const subtype = typeof event.subtype === "string" ? event.subtype.toLowerCase() : "";
|
|
1015
|
-
if (type === "result" || type === "system" || subtype === "init")
|
|
1016
|
-
return null;
|
|
1017
|
-
const message = event.message && typeof event.message === "object" ? event.message : undefined;
|
|
1018
|
-
const content = Array.isArray(message?.content) ? message.content : Array.isArray(event.content) ? event.content : [];
|
|
1019
|
-
const claudeTool = content.find((item) => item && typeof item === "object" && item.type === "tool_use");
|
|
1020
|
-
const source = claudeTool ?? event;
|
|
1021
|
-
const input = source.input && typeof source.input === "object" ? source.input
|
|
1022
|
-
: event.args && typeof event.args === "object" ? event.args
|
|
1023
|
-
: event.arguments && typeof event.arguments === "object" ? event.arguments
|
|
1024
|
-
: {};
|
|
1025
|
-
const serialized = JSON.stringify(event);
|
|
1026
|
-
const name = [source.name, event.tool_name, event.toolName]
|
|
1027
|
-
.find((value) => typeof value === "string");
|
|
1028
|
-
const fallbackTool = serialized.match(/"([A-Za-z]+)ToolCall"/)?.[1];
|
|
1029
|
-
const tool = (name ?? fallbackTool ?? (type.includes("tool") ? "tool" : "")).toLowerCase();
|
|
1030
|
-
if (!tool || tool === "assistant")
|
|
1031
|
-
return null;
|
|
1032
|
-
const rawPath = [input.file_path, input.path, input.filePath, event.file_path, event.path, event.filePath]
|
|
1033
|
-
.find((value) => typeof value === "string");
|
|
1034
|
-
const path = externalWorkspacePath(rawPath, workspace);
|
|
1035
|
-
const failed = event.is_error === true || [event.status, subtype].some((value) => value === "failed" || value === "error");
|
|
1036
|
-
return { action: tool, detail: externalToolDetail(tool, path, input), path, status: failed ? "error" : "success" };
|
|
1037
|
-
}
|
|
1038
|
-
function cursorResultText(event) {
|
|
1039
|
-
const candidates = [];
|
|
1040
|
-
const visit = (value, key = "") => {
|
|
1041
|
-
if (typeof value === "string" && /^(?:result|text|content|message|summary)$/.test(key) && value.trim())
|
|
1042
|
-
candidates.push(value.trim());
|
|
1043
|
-
else if (Array.isArray(value))
|
|
1044
|
-
value.forEach((item) => visit(item, key));
|
|
1045
|
-
else if (value && typeof value === "object")
|
|
1046
|
-
for (const [childKey, child] of Object.entries(value))
|
|
1047
|
-
visit(child, childKey);
|
|
1048
|
-
};
|
|
1049
|
-
visit(event);
|
|
1050
|
-
return candidates.sort((a, b) => b.length - a.length)[0]?.slice(0, 2_000) ?? "";
|
|
1051
|
-
}
|
|
1052
|
-
export function externalRuntimeArgs(runtime, workspace, prompt, skill) {
|
|
1053
|
-
const claudeTools = skill && !skill.allowedTools.includes("repository.write") ? "Read,Glob,Grep" : "Read,Edit,Write,Glob,Grep";
|
|
1054
|
-
const args = runtime.kind === "cursor"
|
|
1055
|
-
? ["-p", prompt, "--force", "--output-format", "stream-json"]
|
|
1056
|
-
: runtime.kind === "claude"
|
|
1057
|
-
? ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", "48", "--permission-mode", "acceptEdits", "--tools", claudeTools, "--disable-slash-commands", "--no-session-persistence"]
|
|
1058
|
-
: ["run", "--format", "json", "--pure", "--auto", "--dir", workspace, prompt];
|
|
1059
|
-
if (runtime.model)
|
|
1060
|
-
args.push(runtime.kind === "opencode" ? "--model" : "--model", runtime.model);
|
|
1061
|
-
return args;
|
|
1062
|
-
}
|
|
1063
|
-
async function executeExternalTurn(root, workspace, run, runtime, prompt, signal) {
|
|
1064
|
-
const args = externalRuntimeArgs(runtime, workspace, prompt, run.skill);
|
|
999
|
+
async function executeExternalTurn(root, workspace, run, runtime, prompt, signal, runtimeEngine = agentVRuntime) {
|
|
1065
1000
|
const started = Date.now();
|
|
1066
|
-
const externalEnvironment = safeEnvironment();
|
|
1067
|
-
if (process.env.CURSOR_API_KEY)
|
|
1068
|
-
externalEnvironment.CURSOR_API_KEY = process.env.CURSOR_API_KEY;
|
|
1069
|
-
if (runtime.kind === "claude" && process.env.ANTHROPIC_API_KEY)
|
|
1070
|
-
externalEnvironment.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
1071
|
-
if (runtime.kind === "claude" && process.env.CLAUDE_CODE_OAUTH_TOKEN)
|
|
1072
|
-
externalEnvironment.CLAUDE_CODE_OAUTH_TOKEN = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
1073
|
-
if (runtime.kind === "opencode")
|
|
1074
|
-
externalEnvironment.OPENCODE_CONFIG_CONTENT = JSON.stringify({ permission: { "*": "deny", read: "allow", edit: run.skill.allowedTools.includes("repository.write") ? "allow" : "deny", glob: "allow", grep: "allow", list: "allow", lsp: "allow", bash: "deny", webfetch: "deny", task: "deny", skill: "deny", external_directory: "deny" } });
|
|
1075
|
-
const child = spawn(runtime.command, args, { cwd: workspace, stdio: ["ignore", "pipe", "pipe"], env: externalEnvironment });
|
|
1076
|
-
let stderr = "", summary = "", timedOut = false;
|
|
1077
|
-
child.stderr?.on("data", (chunk) => { stderr = `${stderr}${String(chunk)}`.slice(-24_000); });
|
|
1078
|
-
const abort = () => child.kill("SIGTERM");
|
|
1079
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
1080
|
-
const timeout = setTimeout(() => { timedOut = true; child.kill("SIGTERM"); }, 15 * 60_000);
|
|
1081
|
-
const exit = new Promise((resolve, reject) => {
|
|
1082
|
-
child.once("error", reject);
|
|
1083
|
-
child.once("close", (code) => resolve(code));
|
|
1084
|
-
});
|
|
1085
1001
|
try {
|
|
1086
|
-
if (
|
|
1087
|
-
throw new Error("
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1002
|
+
if (runtime.kind === "aperta")
|
|
1003
|
+
throw new Error("Aperta Native does not use the local CLI adapter");
|
|
1004
|
+
const result = await runtimeEngine.run({
|
|
1005
|
+
kind: runtime.kind,
|
|
1006
|
+
model: runtime.model || undefined,
|
|
1007
|
+
workspace,
|
|
1008
|
+
workspaceAccess: run.skill.allowedTools.includes("repository.write") ? "workspace-write" : "read-only",
|
|
1009
|
+
projectId: run.repo,
|
|
1010
|
+
runId: run.id,
|
|
1011
|
+
prompt,
|
|
1012
|
+
abortSignal: signal,
|
|
1013
|
+
events: {
|
|
1014
|
+
async emit(event) {
|
|
1015
|
+
if (event.type === "run.started")
|
|
1016
|
+
run.provenance = event.provenance;
|
|
1017
|
+
const action = eventAction(event);
|
|
1018
|
+
if (action && run.actions.length < 400) {
|
|
1019
|
+
run.actions.push({ index: run.actions.length + 1, ...action, ts: new Date().toISOString() });
|
|
1020
|
+
await persist(root, run);
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
});
|
|
1025
|
+
run.provenance = result.provenance;
|
|
1110
1026
|
run.telemetry.providerCalls++;
|
|
1111
|
-
run.telemetry.providerLatencyMs +=
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
throw new Error(`${label} exceeded Aperta's 15-minute turn limit`);
|
|
1117
|
-
if (code !== 0)
|
|
1118
|
-
throw new Error(`${label} exited with code ${code}: ${cleanExecutionOutput(stderr).slice(-4_000) || "no diagnostic output"}`);
|
|
1119
|
-
return summary;
|
|
1027
|
+
run.telemetry.providerLatencyMs += result.durationMs;
|
|
1028
|
+
run.telemetry.toolCalls += result.activityCount;
|
|
1029
|
+
run.actions.push({ index: run.actions.length + 1, action: "runtime", detail: `${result.activityCount} transport event${result.activityCount === 1 ? "" : "s"} · ${result.attempts} attempt${result.attempts === 1 ? "" : "s"}.`, status: "success", durationMs: result.durationMs, ts: new Date().toISOString() });
|
|
1030
|
+
await persist(root, run);
|
|
1031
|
+
return result.summary;
|
|
1120
1032
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
if (!run.telemetry.providerCalls) {
|
|
1035
|
+
run.telemetry.providerCalls++;
|
|
1036
|
+
run.telemetry.providerLatencyMs += Date.now() - started;
|
|
1037
|
+
}
|
|
1038
|
+
throw error;
|
|
1124
1039
|
}
|
|
1125
1040
|
}
|
|
1126
1041
|
export async function runExternalAgent(root, intent, runtime, signal, context = {}) {
|
|
@@ -1130,7 +1045,7 @@ export async function runExternalAgent(root, intent, runtime, signal, context =
|
|
|
1130
1045
|
if (cleanIntent.length < 10 || cleanIntent.length > 4_000)
|
|
1131
1046
|
throw new Error("Describe the change in 10 to 4,000 characters");
|
|
1132
1047
|
const previousRuns = context.previousRuns ?? [];
|
|
1133
|
-
const runtimeLabel = runtime.kind === "cursor" ? "Cursor" : runtime.kind === "claude" ? "Claude Code" : "OpenCode";
|
|
1048
|
+
const runtimeLabel = runtime.kind === "codex" ? "Codex CLI" : runtime.kind === "cursor" ? "Cursor" : runtime.kind === "claude" ? "Claude Code" : "OpenCode";
|
|
1134
1049
|
const selectedSkill = selectAgentSkill(cleanIntent);
|
|
1135
1050
|
const run = { id: randomUUID(), conversationId: conversationId(context.conversationId), turnIndex: previousRuns.length + 1, repo: root.split("/").at(-1) ?? "repository", intent: cleanIntent, status: "running", provider: runtime.kind, model: runtime.model || `${runtimeLabel} default`, createdAt: new Date().toISOString(), files: [], patch: "", actions: [], capabilities: [], skill: selectedSkill, verification: { status: "unavailable", plan: [], attempts: [] }, contract: defaultExecutionContract(cleanIntent, [], selectedSkill), promotion: { status: "review-required", allowed: false, requiresHumanReview: true, reason: "The run has not produced reviewable evidence yet." }, telemetry: { providerCalls: 0, providerLatencyMs: 0, toolCalls: 0, toolLatencyMs: 0, errors: [] }, context: { maxInputChars: MAX_AGENT_INPUT_CHARS, estimatedMaxInputTokens: Math.ceil(MAX_AGENT_INPUT_CHARS / 4), lastInputChars: 0, estimatedLastInputTokens: 0, maxOutputTokens: AGENT_OUTPUT_TOKENS, retryMaxOutputTokens: AGENT_RETRY_OUTPUT_TOKENS } };
|
|
1136
1051
|
await persist(root, run);
|
|
@@ -1154,7 +1069,7 @@ export async function runExternalAgent(root, intent, runtime, signal, context =
|
|
|
1154
1069
|
prompt += "\n\nAperta could not detect a supported project verification command in this repository. Explain that harness-level limitation clearly; do not claim that your runtime's lack of Bash is the reason.";
|
|
1155
1070
|
run.context.lastInputChars = prompt.length;
|
|
1156
1071
|
run.context.estimatedLastInputTokens = Math.ceil(prompt.length / 4);
|
|
1157
|
-
let summary = await executeExternalTurn(root, workspace, run, runtime, prompt, signal) || `${runtimeLabel} completed the requested turn.`;
|
|
1072
|
+
let summary = await executeExternalTurn(root, workspace, run, runtime, prompt, signal, context.runtimeEngine) || `${runtimeLabel} completed the requested turn.`;
|
|
1158
1073
|
const initialCandidate = await createRepositorySnapshot(workspace), initialDiff = await diffSnapshots(workspace, turnStart, initialCandidate);
|
|
1159
1074
|
if (initialDiff.files.length && !run.skill.allowedTools.includes("repository.write"))
|
|
1160
1075
|
throw new Error(`${run.skill.label} is read-only, but ${runtimeLabel} attempted to modify ${initialDiff.files.length} repository file${initialDiff.files.length === 1 ? "" : "s"}. Aperta discarded the isolated changes.`);
|
|
@@ -1180,7 +1095,7 @@ export async function runExternalAgent(root, intent, runtime, signal, context =
|
|
|
1180
1095
|
run.context.lastInputChars = prompt.length;
|
|
1181
1096
|
run.context.estimatedLastInputTokens = Math.ceil(prompt.length / 4);
|
|
1182
1097
|
await persist(root, run);
|
|
1183
|
-
summary = await executeExternalTurn(root, workspace, run, runtime, prompt, signal) || summary;
|
|
1098
|
+
summary = await executeExternalTurn(root, workspace, run, runtime, prompt, signal, context.runtimeEngine) || summary;
|
|
1184
1099
|
}
|
|
1185
1100
|
const after = await createRepositorySnapshot(workspace), diff = await diffSnapshots(workspace, before, after);
|
|
1186
1101
|
run.resultTree = after.tree;
|