@goah/cli 0.11.5 → 0.13.0
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/dist/cli.js +165 -66
- package/dist/cli.js.map +1 -1
- package/dist/console/assets/index-3sYrDMmt.css +1 -0
- package/dist/console/assets/index-Rtc_B7cs.js +52 -0
- package/dist/console/assets/index-Rtc_B7cs.js.map +1 -0
- package/dist/console/index.html +2 -2
- package/dist/control.d.ts +13 -13
- package/dist/control.d.ts.map +1 -1
- package/dist/control.js +77 -50
- package/dist/control.js.map +1 -1
- package/dist/index.d.ts +6 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -28
- package/dist/index.js.map +1 -1
- package/dist/live-config.js +2 -2
- package/dist/live-config.js.map +1 -1
- package/dist/prompt-queue.d.ts.map +1 -1
- package/dist/prompt-queue.js +5 -0
- package/dist/prompt-queue.js.map +1 -1
- package/dist/setup-wizard.d.ts +13 -2
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +87 -63
- package/dist/setup-wizard.js.map +1 -1
- package/dist/thread-inspect.d.ts +47 -0
- package/dist/thread-inspect.d.ts.map +1 -0
- package/dist/thread-inspect.js +78 -0
- package/dist/thread-inspect.js.map +1 -0
- package/dist/transcript.d.ts +2 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +2 -0
- package/dist/transcript.js.map +1 -0
- package/dist/tui-theme.d.ts +1 -0
- package/dist/tui-theme.d.ts.map +1 -1
- package/dist/tui-theme.js +1 -0
- package/dist/tui-theme.js.map +1 -1
- package/dist/tui.d.ts +8 -2
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +291 -90
- package/dist/tui.js.map +1 -1
- package/dist/update.d.ts +1 -0
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +15 -7
- package/dist/update.js.map +1 -1
- package/dist/web-console.d.ts +1 -1
- package/dist/web-console.d.ts.map +1 -1
- package/dist/web-console.js +27 -37
- package/dist/web-console.js.map +1 -1
- package/dist/welcome.d.ts +2 -2
- package/dist/welcome.d.ts.map +1 -1
- package/dist/welcome.js +24 -36
- package/dist/welcome.js.map +1 -1
- package/node_modules/goah-ledger-contract/dist/execution.d.ts +272 -130
- package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +1 -1
- package/node_modules/goah-ledger-contract/dist/execution.js +31 -19
- package/node_modules/goah-ledger-contract/dist/execution.js.map +1 -1
- package/node_modules/goah-ledger-contract/dist/index.d.ts +1 -2
- package/node_modules/goah-ledger-contract/dist/index.d.ts.map +1 -1
- package/node_modules/goah-ledger-contract/dist/index.js +1 -2
- package/node_modules/goah-ledger-contract/dist/index.js.map +1 -1
- package/node_modules/goah-ledger-contract/dist/kernel.d.ts +1 -1
- package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +1 -1
- package/node_modules/goah-ledger-contract/dist/kernel.js +1 -1
- package/node_modules/goah-ledger-contract/dist/kernel.js.map +1 -1
- package/node_modules/goah-ledger-contract/dist/transcript.d.ts +63 -0
- package/node_modules/goah-ledger-contract/dist/transcript.d.ts.map +1 -0
- package/node_modules/goah-ledger-contract/dist/transcript.js +115 -0
- package/node_modules/goah-ledger-contract/dist/transcript.js.map +1 -0
- package/node_modules/goah-ledger-contract/package.json +3 -4
- package/node_modules/goah-ledger-sqlite/dist/index.d.ts +32 -23
- package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +1 -1
- package/node_modules/goah-ledger-sqlite/dist/index.js +764 -394
- package/node_modules/goah-ledger-sqlite/dist/index.js.map +1 -1
- package/node_modules/goah-ledger-sqlite/package.json +2 -2
- package/node_modules/goah-runner-pi/dist/configurator.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/configurator.js +186 -30
- package/node_modules/goah-runner-pi/dist/configurator.js.map +1 -1
- package/node_modules/goah-runner-pi/dist/credential-store.d.ts +2 -0
- package/node_modules/goah-runner-pi/dist/credential-store.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/credential-store.js +117 -23
- package/node_modules/goah-runner-pi/dist/credential-store.js.map +1 -1
- package/node_modules/goah-runner-pi/dist/index.d.ts +18 -6
- package/node_modules/goah-runner-pi/dist/index.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/index.js +97 -12
- package/node_modules/goah-runner-pi/dist/index.js.map +1 -1
- package/node_modules/goah-runner-pi/dist/pi-worker.d.ts +4 -2
- package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/pi-worker.js +220 -69
- package/node_modules/goah-runner-pi/dist/pi-worker.js.map +1 -1
- package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts +2 -0
- package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts.map +1 -0
- package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js +3 -0
- package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js.map +1 -0
- package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts +2 -0
- package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts.map +1 -0
- package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js +5 -0
- package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js.map +1 -0
- package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts +2 -0
- package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts.map +1 -0
- package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js +5 -0
- package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js.map +1 -0
- package/node_modules/goah-runner-pi/dist/test-helpers.d.ts +1 -1
- package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/test-helpers.js +3 -1
- package/node_modules/goah-runner-pi/dist/test-helpers.js.map +1 -1
- package/node_modules/goah-runner-pi/dist/verification-worker.d.ts.map +1 -1
- package/node_modules/goah-runner-pi/dist/verification-worker.js +3 -5
- package/node_modules/goah-runner-pi/dist/verification-worker.js.map +1 -1
- package/node_modules/goah-runner-pi/package.json +2 -2
- package/node_modules/goah-supervisor/dist/context-view.d.ts +4 -5
- package/node_modules/goah-supervisor/dist/context-view.d.ts.map +1 -1
- package/node_modules/goah-supervisor/dist/context-view.js +7 -20
- package/node_modules/goah-supervisor/dist/context-view.js.map +1 -1
- package/node_modules/goah-supervisor/dist/index.d.ts +38 -36
- package/node_modules/goah-supervisor/dist/index.d.ts.map +1 -1
- package/node_modules/goah-supervisor/dist/index.js +762 -575
- package/node_modules/goah-supervisor/dist/index.js.map +1 -1
- package/node_modules/goah-supervisor/dist/roles.d.ts +2 -1
- package/node_modules/goah-supervisor/dist/roles.d.ts.map +1 -1
- package/node_modules/goah-supervisor/dist/roles.js +20 -5
- package/node_modules/goah-supervisor/dist/roles.js.map +1 -1
- package/node_modules/goah-supervisor/dist/verification.d.ts +8 -29
- package/node_modules/goah-supervisor/dist/verification.d.ts.map +1 -1
- package/node_modules/goah-supervisor/dist/verification.js +71 -46
- package/node_modules/goah-supervisor/dist/verification.js.map +1 -1
- package/node_modules/goah-supervisor/package.json +2 -2
- package/node_modules/goah-testkit/dist/faux-runner-worker.js +16 -9
- package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +1 -1
- package/node_modules/goah-testkit/dist/index.d.ts +13 -16
- package/node_modules/goah-testkit/dist/index.d.ts.map +1 -1
- package/node_modules/goah-testkit/dist/index.js +27 -44
- package/node_modules/goah-testkit/dist/index.js.map +1 -1
- package/node_modules/goah-testkit/package.json +5 -5
- package/package.json +7 -8
- package/dist/console/assets/index-BJTGvlCH.js +0 -52
- package/dist/console/assets/index-BJTGvlCH.js.map +0 -1
- package/dist/console/assets/index-msBn41Cl.css +0 -1
- package/dist/inspect.d.ts +0 -61
- package/dist/inspect.d.ts.map +0 -1
- package/dist/inspect.js +0 -110
- package/dist/inspect.js.map +0 -1
- package/dist/metrics.d.ts +0 -2
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -2
- package/dist/metrics.js.map +0 -1
- package/dist/session.d.ts +0 -2
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -2
- package/dist/session.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/metrics.d.ts +0 -40
- package/node_modules/goah-ledger-contract/dist/metrics.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/metrics.js +0 -25
- package/node_modules/goah-ledger-contract/dist/metrics.js.map +0 -1
- package/node_modules/goah-ledger-contract/dist/session.d.ts +0 -63
- package/node_modules/goah-ledger-contract/dist/session.d.ts.map +0 -1
- package/node_modules/goah-ledger-contract/dist/session.js +0 -115
- package/node_modules/goah-ledger-contract/dist/session.js.map +0 -1
- package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts +0 -2
- package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts.map +0 -1
- package/node_modules/goah-testkit/dist/mock-connector-worker.js +0 -29
- package/node_modules/goah-testkit/dist/mock-connector-worker.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
2
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, resolve, sep } from "node:path";
|
|
4
|
+
import { delimiter, dirname, join, resolve, sep } from "node:path";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
6
8
|
import { fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
|
7
|
-
import {
|
|
9
|
+
import { TRANSCRIPT_FORMAT_VERSION } from "goah-ledger-contract";
|
|
8
10
|
import { runProcessWorker } from "./index.js";
|
|
9
11
|
import { createPiModel } from "./model-provider.js";
|
|
10
12
|
export function compactMessages(messages, maxRecent = 8) {
|
|
@@ -19,7 +21,7 @@ export function compactMessages(messages, maxRecent = 8) {
|
|
|
19
21
|
];
|
|
20
22
|
}
|
|
21
23
|
export async function runPiWorker() {
|
|
22
|
-
await runProcessWorker(async (request, emit, rpc) => {
|
|
24
|
+
await runProcessWorker(async (request, emit, rpc, controls) => {
|
|
23
25
|
const contextRecord = typeof request.context === "object" && request.context !== null && !Array.isArray(request.context) ? request.context : {};
|
|
24
26
|
const legacyRequest = request.turn === undefined;
|
|
25
27
|
const binding = request.turn?.goalBinding;
|
|
@@ -33,6 +35,7 @@ export async function runPiWorker() {
|
|
|
33
35
|
const configured = createPiModel(provider, modelId);
|
|
34
36
|
const { models } = configured;
|
|
35
37
|
const privateAuth = request.runtime && typeof request.runtime === "object" && !Array.isArray(request.runtime) ? request.runtime : {};
|
|
38
|
+
const protectedPaths = Array.isArray(privateAuth.protectedPaths) ? privateAuth.protectedPaths.filter((value) => typeof value === "string").map((path) => resolve(path)) : [];
|
|
36
39
|
const model = typeof privateAuth.baseUrl === "string" ? { ...configured.model, baseUrl: privateAuth.baseUrl } : configured.model;
|
|
37
40
|
if (provider === "faux") {
|
|
38
41
|
const faux = configured.faux;
|
|
@@ -43,21 +46,26 @@ export async function runPiWorker() {
|
|
|
43
46
|
const handoff = JSON.parse(process.env.GOAH_PI_FAUX_HANDOFF ?? "{}");
|
|
44
47
|
const current = contextRecord.workRecord && typeof contextRecord.workRecord === "object" && !Array.isArray(contextRecord.workRecord) ? contextRecord.workRecord : {};
|
|
45
48
|
const evidence = Array.isArray(contextRecord.sourceSeqs) ? contextRecord.sourceSeqs.filter((value) => typeof value === "number") : [];
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
]);
|
|
49
|
+
const outcome = ["progress", "waiting", "blocked", "completion_proposed"].includes(String(handoff.outcome)) ? String(handoff.outcome) : "progress";
|
|
50
|
+
const handoffEvidence = Array.isArray(handoff.evidence) && handoff.evidence.every((value) => typeof value === "number") ? handoff.evidence : evidence.length ? [Math.max(...evidence)] : [];
|
|
51
|
+
const record = `# Current State\n\nFaux Goal outcome: ${outcome}.\n\n# Observations\n\nSee the cited Ledger evidence.\n\n# Work Completed\n\nExecuted the scripted faux Goal turn.\n\n# Decisions\n\nRecord the scripted result from ${request.execution.id}.\n\n# Blockers\n\n${outcome === "blocked" ? "Blocked." : "None."}\n\n# Next Steps\n\nFollow the current Goal and explicit tools.\n`;
|
|
52
|
+
const finalResponse = fauxAssistantMessage([fauxText(String(handoff.message ?? `Goal work recorded with outcome ${outcome}.`)), fauxToolCall("handoff", { outcome, evidence: handoffEvidence })], { stopReason: "toolUse" });
|
|
53
|
+
const recordResponse = fauxAssistantMessage(fauxToolCall("work_record_update", { expectedRevision: Number(current.recordRevision ?? 0), content: record, reason: "record faux Goal progress", evidence: evidence.length ? [Math.max(...evidence)] : [] }), { stopReason: "toolUse" });
|
|
54
|
+
const fatalBatch = fauxAssistantMessage([fauxText("Stale completion attempt."), fauxToolCall("handoff", { outcome, evidence: handoffEvidence }), fauxToolCall("write", { path: "fatal.txt", content: "must not be written" })], { stopReason: "toolUse" });
|
|
55
|
+
faux.setResponses(process.env.GOAH_PI_FAUX_FATAL_BATCH === "1" ? [fatalBatch] : process.env.GOAH_PI_FAUX_HANDOFF_REPAIR === "1" ? [fauxAssistantMessage([fauxText("Premature completion attempt."), fauxToolCall("handoff", { outcome, evidence: handoffEvidence })], { stopReason: "toolUse" }), recordResponse, finalResponse] : [recordResponse, finalResponse]);
|
|
51
56
|
}
|
|
52
57
|
else if (!goalState.bound) {
|
|
53
58
|
faux.setResponses([fauxAssistantMessage([fauxText(process.env.GOAH_PI_FAUX_RESPONSE ?? "Hello from Goah.")])]);
|
|
54
59
|
}
|
|
55
60
|
else {
|
|
56
61
|
const handoff = JSON.parse(process.env.GOAH_PI_FAUX_HANDOFF ?? "{}");
|
|
57
|
-
|
|
62
|
+
const { message, ...handoffData } = handoff;
|
|
63
|
+
faux.setResponses([fauxAssistantMessage([fauxText(String(message ?? "Goal work recorded.")), fauxToolCall("handoff", handoffData)], { stopReason: "toolUse" })]);
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
let output = null;
|
|
67
|
+
let acceptedHandoff = null;
|
|
68
|
+
let revokedReason = "";
|
|
61
69
|
let response = "";
|
|
62
70
|
let responseFailure = "";
|
|
63
71
|
let compactions = 0;
|
|
@@ -80,15 +88,19 @@ export async function runPiWorker() {
|
|
|
80
88
|
: undefined;
|
|
81
89
|
if (contextRecord.workRecord && typeof contextRecord.workRecord === "object" && !Array.isArray(contextRecord.workRecord) && typeof contextRecord.workRecord.recordRevision === "number")
|
|
82
90
|
goalState.recordRevision = contextRecord.workRecord.recordRevision;
|
|
83
|
-
const tools = createTools(root, (value) => {
|
|
91
|
+
const tools = createTools(root, (value) => { if (!acceptedHandoff || !sameHandoff(acceptedHandoff.handoff, value))
|
|
92
|
+
throw new Error("Handoff tool executed without accepted validation"); output = { validationAttemptId: acceptedHandoff.attemptId, validationToken: acceptedHandoff.token, handoff: value }; }, rpc, capabilities, goalState, protectedPaths);
|
|
84
93
|
const contextPolicy = resolveContextPolicy(model.contextWindow, process.env);
|
|
85
|
-
emit({ type: "
|
|
94
|
+
emit({ type: "transcript.started", data: { formatVersion: TRANSCRIPT_FORMAT_VERSION, provider, model: modelId, runner: "pi", contextWindowTokens: model.contextWindow, maxOutputTokensPerTurn: model.maxTokens } });
|
|
86
95
|
const suppliedPrompt = typeof contextRecord.systemPrompt === "string" ? contextRecord.systemPrompt : undefined;
|
|
87
96
|
const activeContext = typeof contextRecord.text === "string" ? contextRecord.text : JSON.stringify(request.context);
|
|
88
97
|
const sourceSeqs = Array.isArray(contextRecord.sourceSeqs) ? contextRecord.sourceSeqs.filter((value) => Number.isInteger(value)) : [];
|
|
98
|
+
const basePrompt = process.env.GOAH_PI_SYSTEM_PROMPT ?? suppliedPrompt ?? (request.turn?.source.kind === "human" ? "You are Goah's primary Agent. Respond naturally to the Human." : `You are Goah Agent ${request.agent}. Inspect the supplied context and respond appropriately.`);
|
|
89
99
|
const systemPrompt = goalState.bound
|
|
90
|
-
? `${
|
|
91
|
-
:
|
|
100
|
+
? `${basePrompt}\nThis Turn is Goal-bound. You must update its Work Record, write a concise human-readable assistant message describing what happened, and call handoff exactly once with only machine-readable outcome and evidence. The message and Handoff are separate outputs. Treat the supplied context as authoritative.`
|
|
101
|
+
: request.turn?.source.kind === "human"
|
|
102
|
+
? `${basePrompt}\nThis Human Turn starts without a Goal binding. Finish with an ordinary response unless a Goal tool establishes a binding during this Turn; after a binding is established, update that Goal's Work Record, write a concise human-readable assistant message, and call handoff exactly once with separate machine-readable outcome and evidence. Treat the supplied context as authoritative.`
|
|
103
|
+
: `${basePrompt}\nThis Turn has no Goal binding. Finish with an ordinary response and do not call handoff. Treat the supplied context as authoritative.`;
|
|
92
104
|
const agent = new Agent({
|
|
93
105
|
initialState: {
|
|
94
106
|
systemPrompt,
|
|
@@ -129,9 +141,33 @@ export async function runPiWorker() {
|
|
|
129
141
|
}
|
|
130
142
|
return view;
|
|
131
143
|
},
|
|
132
|
-
|
|
144
|
+
beforeToolCall: async ({ assistantMessage, toolCall, args }) => { if (revokedReason)
|
|
145
|
+
return { block: true, reason: `Turn authority was revoked: ${revokedReason}`, terminate: true }; if (toolCall.name !== "handoff")
|
|
146
|
+
return; acceptedHandoff = null; output = null; const handoff = args; try {
|
|
147
|
+
const result = await rpc("goal.handoff.validate", { handoff, candidateMessage: assistantResponseText(assistantMessage) });
|
|
148
|
+
if (result.accepted) {
|
|
149
|
+
acceptedHandoff = { attemptId: result.attemptId, token: result.token, handoff };
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const reason = result.issues.map((issue) => `- ${issue.message}`).join("\n");
|
|
153
|
+
if (result.fatal)
|
|
154
|
+
revokedReason = reason;
|
|
155
|
+
return { block: true, reason: `Handoff was not accepted:\n${reason}`, terminate: result.fatal };
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
revokedReason = error instanceof Error ? error.message : String(error);
|
|
159
|
+
return { block: true, reason: `Handoff validation could not continue: ${revokedReason}`, terminate: true };
|
|
160
|
+
} },
|
|
161
|
+
shouldStopAfterTurn: () => output !== null || Boolean(revokedReason),
|
|
133
162
|
toolExecution: "sequential",
|
|
134
163
|
});
|
|
164
|
+
let acceptingSteering = true;
|
|
165
|
+
controls.onSteer((message) => {
|
|
166
|
+
if (!acceptingSteering)
|
|
167
|
+
return false;
|
|
168
|
+
agent.steer({ role: "user", content: message, timestamp: Date.now() });
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
135
171
|
agent.subscribe((event) => {
|
|
136
172
|
if (event.type === "turn_start")
|
|
137
173
|
emit({ type: "turn.started", data: {} });
|
|
@@ -139,7 +175,7 @@ export async function runPiWorker() {
|
|
|
139
175
|
const id = idFor(event.message);
|
|
140
176
|
if (!emittedUsers.has(id)) {
|
|
141
177
|
emittedUsers.add(id);
|
|
142
|
-
emit({ type: "message.user", data: { message:
|
|
178
|
+
emit({ type: "message.user", data: { message: transcriptMessage(event.message, id) } });
|
|
143
179
|
}
|
|
144
180
|
}
|
|
145
181
|
else if (event.type === "message_update") {
|
|
@@ -149,7 +185,8 @@ export async function runPiWorker() {
|
|
|
149
185
|
response = assistantResponseText(event.message);
|
|
150
186
|
if ((event.message.stopReason === "error" || event.message.stopReason === "aborted") && event.message.errorMessage)
|
|
151
187
|
responseFailure = event.message.errorMessage;
|
|
152
|
-
|
|
188
|
+
const handoffIntent = hasAgentToolCall(event.message, "handoff");
|
|
189
|
+
emit({ type: "message.assistant.completed", data: { message: transcriptMessage(event.message, idFor(event.message)), ...(handoffIntent ? { completionIntent: "handoff", commitState: "provisional" } : { completionIntent: "ordinary", commitState: "committed" }) } });
|
|
153
190
|
}
|
|
154
191
|
else if (event.type === "tool_execution_start")
|
|
155
192
|
emit({ type: "tool.called", data: { callId: event.toolCallId, name: event.toolName, arguments: JSON.parse(JSON.stringify(event.args)) } });
|
|
@@ -157,16 +194,26 @@ export async function runPiWorker() {
|
|
|
157
194
|
emit({ type: "tool.completed", data: { callId: event.toolCallId, messageId: `tool:${event.toolCallId}`, name: event.toolName, result: JSON.parse(JSON.stringify(event.result)), isError: event.isError } });
|
|
158
195
|
else if (event.type === "turn_end")
|
|
159
196
|
emit({ type: "turn.completed", data: {} });
|
|
160
|
-
else if (event.type === "agent_end")
|
|
161
|
-
emit({ type: "session.completed", data: {} });
|
|
162
197
|
});
|
|
163
|
-
|
|
198
|
+
const abortAgent = () => agent.abort();
|
|
199
|
+
process.once("SIGTERM", abortAgent);
|
|
200
|
+
process.once("SIGINT", abortAgent);
|
|
201
|
+
try {
|
|
202
|
+
await agent.prompt(`Turn started at: ${request.execution.startedAt}\n\n${activeContext}\n\nRunner root: ${root}. Manage local files directly when the goal requires them.`);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
acceptingSteering = false;
|
|
206
|
+
process.off("SIGTERM", abortAgent);
|
|
207
|
+
process.off("SIGINT", abortAgent);
|
|
208
|
+
}
|
|
209
|
+
if (revokedReason)
|
|
210
|
+
return { outcome: "abnormal", reason: revokedReason };
|
|
164
211
|
if (!goalState.bound)
|
|
165
212
|
return response ? { outcome: "response", response: { content: response } } : { outcome: "abnormal", reason: responseFailure || "Pi worker exited without a response" };
|
|
166
213
|
if (responseFailure)
|
|
167
214
|
return { outcome: "abnormal", reason: responseFailure };
|
|
168
215
|
if (!output)
|
|
169
|
-
return { outcome: "abnormal", reason: "Pi worker exited without
|
|
216
|
+
return { outcome: "abnormal", reason: "Pi worker exited without an accepted Handoff" };
|
|
170
217
|
return { outcome: "handoff", output };
|
|
171
218
|
});
|
|
172
219
|
}
|
|
@@ -185,32 +232,23 @@ function isJson(value) {
|
|
|
185
232
|
return value.every(isJson);
|
|
186
233
|
return Boolean(value && typeof value === "object" && Object.values(value).every(isJson));
|
|
187
234
|
}
|
|
188
|
-
function
|
|
235
|
+
function transcriptMessage(message, id) {
|
|
189
236
|
const value = JSON.parse(JSON.stringify(message));
|
|
190
237
|
const role = message.role === "assistant" ? "assistant" : message.role === "user" ? "user" : "tool";
|
|
191
238
|
return { id, role, content: (value.content ?? value), ...(value.usage !== undefined ? { usage: value.usage } : {}), ...(typeof value.stopReason === "string" ? { stopReason: value.stopReason } : {}), ...(typeof value.errorMessage === "string" ? { errorMessage: value.errorMessage } : {}) };
|
|
192
239
|
}
|
|
193
|
-
function createTools(root, handoff, rpc,
|
|
240
|
+
function createTools(root, handoff, rpc, capabilities, goalState, protectedPaths = []) {
|
|
194
241
|
const handoffTool = {
|
|
195
242
|
name: "handoff",
|
|
196
243
|
label: "Handoff",
|
|
197
|
-
description: "Record a structured handoff and end the
|
|
244
|
+
description: "Record a structured handoff and end the Goal-bound Turn.",
|
|
198
245
|
parameters: Type.Object({
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
nextSteps: Type.Optional(Type.Array(Type.String())),
|
|
202
|
-
blocker: Type.Optional(Type.String()),
|
|
203
|
-
material: Type.Optional(Type.Boolean()),
|
|
204
|
-
outcome: Type.Optional(Type.Union([Type.Literal("progress"), Type.Literal("waiting"), Type.Literal("blocked"), Type.Literal("completion_proposed")])),
|
|
205
|
-
evidence: Type.Optional(Type.Array(Type.Number())),
|
|
206
|
-
nextWakeAt: Type.Optional(Type.String()),
|
|
246
|
+
outcome: Type.Union([Type.Literal("progress"), Type.Literal("waiting"), Type.Literal("blocked"), Type.Literal("completion_proposed")]),
|
|
247
|
+
evidence: Type.Array(Type.Number()),
|
|
207
248
|
}),
|
|
208
249
|
execute: async (_id, params) => {
|
|
209
250
|
const input = params;
|
|
210
|
-
const
|
|
211
|
-
? { goalId: goalState.binding.goalId, goalRevision: goalState.binding.goalRevision, recordRevision: goalState.recordRevision, outcome: input.outcome ?? (input.blocker ? "blocked" : input.material ? "completion_proposed" : "progress"), evidence: input.evidence ?? [] }
|
|
212
|
-
: null;
|
|
213
|
-
const value = { handoff: compact ?? { observations: input.observations ?? [], results: input.results ?? [], nextSteps: input.nextSteps ?? [], ...(input.blocker ? { blocker: input.blocker } : {}), ...(input.material === true ? { material: true } : {}) }, mail: [], nextWakeAt: validateNextWakeAt(input.nextWakeAt, wakeStartedAt) };
|
|
251
|
+
const value = { outcome: input.outcome, evidence: input.evidence };
|
|
214
252
|
handoff(value);
|
|
215
253
|
return { content: [{ type: "text", text: "handoff recorded" }], details: value, terminate: true };
|
|
216
254
|
},
|
|
@@ -229,19 +267,19 @@ function createTools(root, handoff, rpc, wakeStartedAt, capabilities, goalState)
|
|
|
229
267
|
const readTool = {
|
|
230
268
|
name: "read", label: "Read", description: "Read a UTF-8 file inside the current working directory.",
|
|
231
269
|
parameters: Type.Object({ path: Type.String() }),
|
|
232
|
-
execute: async (_id, params) => { const input = params; return { content: [{ type: "text", text: await readFile(
|
|
270
|
+
execute: async (_id, params) => { const input = params; return { content: [{ type: "text", text: await readFile(scopedRunnerPath(root, input.path, protectedPaths), "utf8") }], details: {} }; },
|
|
233
271
|
};
|
|
234
272
|
const writeTool = {
|
|
235
273
|
name: "write", label: "Write", description: "Create or replace a UTF-8 file inside the current working directory.",
|
|
236
274
|
parameters: Type.Object({ path: Type.String(), content: Type.String() }),
|
|
237
|
-
execute: async (_id, params) => { const input = params; const path =
|
|
275
|
+
execute: async (_id, params) => { const input = params; const path = scopedRunnerPath(root, input.path, protectedPaths); await mkdir(dirname(path), { recursive: true }); await writeFile(path, input.content); return { content: [{ type: "text", text: "written" }], details: {} }; },
|
|
238
276
|
};
|
|
239
277
|
const editTool = {
|
|
240
278
|
name: "edit", label: "Edit", description: "Replace one exact text occurrence in a UTF-8 file inside the current working directory.",
|
|
241
279
|
parameters: Type.Object({ path: Type.String(), oldText: Type.String(), newText: Type.String() }),
|
|
242
280
|
execute: async (_id, params) => {
|
|
243
281
|
const input = params;
|
|
244
|
-
const path =
|
|
282
|
+
const path = scopedRunnerPath(root, input.path, protectedPaths);
|
|
245
283
|
const source = await readFile(path, "utf8");
|
|
246
284
|
const first = source.indexOf(input.oldText);
|
|
247
285
|
if (first < 0)
|
|
@@ -254,9 +292,9 @@ function createTools(root, handoff, rpc, wakeStartedAt, capabilities, goalState)
|
|
|
254
292
|
};
|
|
255
293
|
const bashTool = {
|
|
256
294
|
name: "bash", label: "Bash",
|
|
257
|
-
description: "Run a shell command inside the local runner root.
|
|
295
|
+
description: "Run a sandboxed shell command inside the local runner root. Goah credential/control state is masked. If protected state shares the root, use the write tool to create new top-level paths. The command's process group is killed after timeout.",
|
|
258
296
|
parameters: Type.Object({ command: Type.String(), timeoutMs: Type.Optional(Type.Number()) }), executionMode: "sequential",
|
|
259
|
-
execute: async (_id, params, signal) => runBashCommand(root, params, signal),
|
|
297
|
+
execute: async (_id, params, signal) => runBashCommand(root, params, signal, protectedPaths),
|
|
260
298
|
};
|
|
261
299
|
return [readTool, writeTool, editTool, bashTool, ...rpcTools, handoffTool];
|
|
262
300
|
}
|
|
@@ -267,23 +305,42 @@ export function bashTimeoutMs(requested, env = process.env) {
|
|
|
267
305
|
return Number.isFinite(value) && value > 0 ? Math.min(value, BASH_TIMEOUT_HARD_CAP_MS) : fallback;
|
|
268
306
|
}
|
|
269
307
|
/** Shell execution with a process-group timeout: a hung command becomes a model-visible tool error instead of a stalled wake. */
|
|
270
|
-
export async function runBashCommand(root, input, signal) {
|
|
308
|
+
export async function runBashCommand(root, input, signal, protectedPaths = []) {
|
|
271
309
|
const timeoutMs = bashTimeoutMs(input.timeoutMs);
|
|
272
|
-
const
|
|
310
|
+
const sandboxTemp = mkdtempSync(join(tmpdir(), "goah-bash-"));
|
|
311
|
+
const launch = sandboxedShell(input.command, root, protectedPaths, sandboxTemp);
|
|
312
|
+
if (!launch) {
|
|
313
|
+
if (sandboxTemp)
|
|
314
|
+
rmSync(sandboxTemp, { recursive: true, force: true });
|
|
315
|
+
return { content: [{ type: "text", text: "Bash is unavailable because this platform cannot isolate Goah credential and control state." }], details: { command: input.command }, isError: true };
|
|
316
|
+
}
|
|
317
|
+
const child = spawn(launch.command, launch.args, { cwd: root, env: toolEnvironment(sandboxTemp), detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"] });
|
|
273
318
|
let stdout = "";
|
|
274
319
|
let stderr = "";
|
|
275
320
|
let timedOut = false;
|
|
276
321
|
let outputOverflow = false;
|
|
277
|
-
child.stdout?.on("data", (chunk) => { stdout += chunk.toString(); if (stdout.length > 1_000_000)
|
|
278
|
-
outputOverflow = true; });
|
|
279
|
-
child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); if (stderr.length > 1_000_000)
|
|
280
|
-
outputOverflow = true; });
|
|
281
322
|
const killGroup = () => { if (child.pid) {
|
|
282
323
|
try {
|
|
283
324
|
process.kill(process.platform === "win32" ? child.pid : -child.pid, "SIGKILL");
|
|
284
325
|
}
|
|
285
326
|
catch { }
|
|
286
327
|
} };
|
|
328
|
+
const appendOutput = (channel, chunk) => {
|
|
329
|
+
if (outputOverflow)
|
|
330
|
+
return;
|
|
331
|
+
const text = chunk.toString();
|
|
332
|
+
const remaining = Math.max(0, 1_000_000 - stdout.length - stderr.length);
|
|
333
|
+
if (channel === "stdout")
|
|
334
|
+
stdout += text.slice(0, remaining);
|
|
335
|
+
else
|
|
336
|
+
stderr += text.slice(0, remaining);
|
|
337
|
+
if (text.length > remaining) {
|
|
338
|
+
outputOverflow = true;
|
|
339
|
+
killGroup();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
child.stdout?.on("data", (chunk) => appendOutput("stdout", chunk));
|
|
343
|
+
child.stderr?.on("data", (chunk) => appendOutput("stderr", chunk));
|
|
287
344
|
const timer = setTimeout(() => { timedOut = true; killGroup(); }, timeoutMs);
|
|
288
345
|
const onAbort = () => killGroup();
|
|
289
346
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -292,29 +349,22 @@ export async function runBashCommand(root, input, signal) {
|
|
|
292
349
|
child.once("close", (code, signalName) => resolve({ code, signal: signalName }));
|
|
293
350
|
});
|
|
294
351
|
try {
|
|
295
|
-
if (outputOverflow)
|
|
296
|
-
killGroup();
|
|
297
352
|
const result = await close;
|
|
298
353
|
if (timedOut)
|
|
299
354
|
return { content: [{ type: "text", text: `Command timed out after ${timeoutMs}ms and its process group was killed. Declare a larger timeoutMs for long-running commands.` }], details: { command: input.command, timedOutAfterMs: timeoutMs }, isError: true };
|
|
300
355
|
if (signal?.aborted)
|
|
301
356
|
return { content: [{ type: "text", text: "Command aborted with the wake." }], details: { command: input.command }, isError: true };
|
|
302
|
-
|
|
357
|
+
killGroup();
|
|
358
|
+
const failed = outputOverflow || result.code !== 0 || result.signal !== null;
|
|
359
|
+
return { content: [{ type: "text", text: `${stdout}${stderr}`.slice(-50_000) }], details: { command: input.command, exitCode: result.code, signal: result.signal, ...(outputOverflow ? { outputOverflow: true } : {}) }, ...(failed ? { isError: true } : {}) };
|
|
303
360
|
}
|
|
304
361
|
finally {
|
|
305
362
|
clearTimeout(timer);
|
|
306
363
|
signal?.removeEventListener("abort", onAbort);
|
|
364
|
+
if (sandboxTemp)
|
|
365
|
+
rmSync(sandboxTemp, { recursive: true, force: true });
|
|
307
366
|
}
|
|
308
367
|
}
|
|
309
|
-
export function validateNextWakeAt(value, wakeStartedAt) {
|
|
310
|
-
if (value === undefined)
|
|
311
|
-
return null;
|
|
312
|
-
const next = Date.parse(value);
|
|
313
|
-
const started = wakeStartedAt === null ? Number.NaN : Date.parse(wakeStartedAt);
|
|
314
|
-
if (!Number.isFinite(next) || !Number.isFinite(started) || next <= started)
|
|
315
|
-
throw new Error("nextWakeAt must be a valid time later than the current wake start");
|
|
316
|
-
return new Date(next).toISOString();
|
|
317
|
-
}
|
|
318
368
|
export function resolveContextPolicy(contextWindowTokens, env) {
|
|
319
369
|
if (!Number.isInteger(contextWindowTokens) || contextWindowTokens <= 0)
|
|
320
370
|
throw new Error("model context window is missing");
|
|
@@ -356,17 +406,14 @@ function createRpcTools(rpc, allowed, onResult) {
|
|
|
356
406
|
const definitions = [
|
|
357
407
|
["ledger.search", tool("ledger_search", "Search durable ledger facts.", "ledger.search", Type.Object({ query: Type.String(), limit: Type.Optional(Type.Number()) }))],
|
|
358
408
|
["memory.append", tool("memory_append", "Append a durable working-memory note that is injected into your future wakes. Record procedural knowledge, active hypotheses, and abandoned approaches with the reason; keep notes concise.", "memory.append", Type.Object({ note: Type.String() }))],
|
|
359
|
-
["mail.send", tool("send_mail", "Send
|
|
409
|
+
["mail.send", tool("send_mail", "Send durable Mail to another Goal owner. goalId is required and routes the Mail into that owner's Goal Turn. Human communication must use request_human.", "mail.send", Type.Object({ to: Type.String(), goalId: Type.String(), level: Type.Union([Type.Literal("fyi"), Type.Literal("decision"), Type.Literal("emergency")]), body: Type.Any() }))],
|
|
360
410
|
["schedule.set", tool("schedule_wake", "Schedule this agent's next wake.", "schedule.set", Type.Object({ at: Type.String(), reason: Type.String() }))],
|
|
361
|
-
["action.submit", tool("submit_action", "Submit a gated external action with evidence.", "action.submit", Type.Object({ id: Type.String(), kind: Type.String(), connector: Type.String(), payload: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
362
|
-
["audit.ack", tool("ack_audit_advice", "Acknowledge audit advice after incorporating it.", "audit.ack", Type.Object({ actionId: Type.String() }))],
|
|
363
|
-
["audit.write", tool("write_audit_advice", "Write audit advice for an action.", "audit.write", Type.Object({ actionId: Type.String(), body: Type.Any(), evidence: Type.Array(Type.Number()) }))],
|
|
364
411
|
["team.list", tool("team_list", "Read the ledger-derived team roster and liveness state.", "team.list", Type.Object({}))],
|
|
365
412
|
["goal.get", tool("get_goal", "Read the Goal bound to this Turn, or null when the Turn is unbound.", "goal.get", Type.Object({}))],
|
|
366
413
|
["goal.create", tool("create_goal", "Create a Root Goal from durable Human intent and bind this Turn. Do not use for greetings, questions, or routine single-turn work.", "goal.create", Type.Object({ objective: Type.String(), id: Type.Optional(Type.String()) }))],
|
|
367
414
|
["goal.work", tool("work_on_goal", "Bind this Human Turn to an existing active Goal owned by this Agent.", "goal.work", Type.Object({ goalId: Type.String() }))],
|
|
368
|
-
["goal.delegate", tool("delegate_goal", "Atomically create a Child Goal with observation and verification methods, its Work Record, decision brief, and initial Wake.", "goal.delegate", Type.Object({ id: Type.String(), parentGoalId: Type.String(), childGoal: Type.Object({ id: Type.String(), objective: Type.String(), observationMethod: Type.String(), verificationMethod: Type.String(), owner: Type.String() }), brief: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
369
|
-
["goal.reassign", tool("reassign_goal", "Atomically transfer a child goal, notify both owners, and queue the new owner.", "goal.reassign", Type.Object({ id: Type.String(), goalId: Type.String(), newOwner: Type.String(), brief: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
415
|
+
["goal.delegate", tool("delegate_goal", "Atomically create a Child Goal with observation and verification methods, its Work Record, decision brief, and initial Wake.", "goal.delegate", Type.Object({ id: Type.String(), parentGoalId: Type.String(), expectedParentRevision: Type.Number(), childGoal: Type.Object({ id: Type.String(), objective: Type.String(), observationMethod: Type.String(), verificationMethod: Type.String(), owner: Type.String() }), brief: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
416
|
+
["goal.reassign", tool("reassign_goal", "Atomically transfer a child goal, notify both owners, and queue the new owner.", "goal.reassign", Type.Object({ id: Type.String(), goalId: Type.String(), expectedGoalRevision: Type.Number(), newOwner: Type.String(), brief: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
370
417
|
["goal.revise", tool("revise_goal", "Revise a Child Goal objective, observation method, and verification method as one new revision.", "goal.revise", Type.Object({ goalId: Type.String(), objective: Type.String(), observationMethod: Type.String(), verificationMethod: Type.String(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
371
418
|
["goal.pause", tool("pause_goal", "Pause a child goal and suppress queued motion.", "goal.pause", Type.Object({ goalId: Type.String() }))],
|
|
372
419
|
["goal.resume", tool("resume_goal", "Resume a Goal. A direct Human Root resume binds the current Turn; Child Goal resume requires an already bound parent Turn.", "goal.resume", Type.Object({ goalId: Type.String() }))],
|
|
@@ -378,23 +425,125 @@ function createRpcTools(rpc, allowed, onResult) {
|
|
|
378
425
|
["work_record.diff", tool("work_record_diff", "Compare two revisions of a Goal Work Record.", "work_record.diff", Type.Object({ goalId: Type.Optional(Type.String()), fromRevision: Type.Number(), toRevision: Type.Number() }))],
|
|
379
426
|
["work_record.search", tool("work_record_search", "Search every Goal Work Record.", "work_record.search", Type.Object({ query: Type.String(), limit: Type.Optional(Type.Number()) }))],
|
|
380
427
|
["work_record.update", tool("work_record_update", "Create the next version of the Work Record bound to this Turn with Ledger evidence.", "work_record.update", Type.Object({ expectedRevision: Type.Number(), content: Type.String(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
381
|
-
["goal.put", tool("put_goal", "Create or update a goal using parent-layer authority.", "goal.put", Type.Object({ goal: Type.Any() }))],
|
|
428
|
+
["goal.put", tool("put_goal", "Create or update a Goal through the authoritative goal.changed protocol using parent-layer authority.", "goal.put", Type.Object({ goal: Type.Any(), reason: Type.String(), evidence: Type.Array(Type.Number()) }))],
|
|
382
429
|
];
|
|
383
430
|
return definitions.filter(([capability]) => !allowed || allowed.has(capability)).map(([, value]) => value);
|
|
384
431
|
}
|
|
385
|
-
function
|
|
386
|
-
const resolved = resolve(root, path);
|
|
387
|
-
|
|
432
|
+
export function scopedRunnerPath(root, path, protectedPaths = []) {
|
|
433
|
+
const resolved = canonicalPath(resolve(root, path));
|
|
434
|
+
const canonicalRoot = canonicalPath(root);
|
|
435
|
+
if (resolved !== canonicalRoot && !resolved.startsWith(`${canonicalRoot}${sep}`))
|
|
388
436
|
throw new Error("path escapes runner root");
|
|
437
|
+
if (protectedPaths.map(canonicalPath).some((privatePath) => resolved === privatePath || resolved.startsWith(`${privatePath}${sep}`)))
|
|
438
|
+
throw new Error("path is protected Goah state");
|
|
389
439
|
return resolved;
|
|
390
440
|
}
|
|
391
|
-
function
|
|
441
|
+
function sandboxedShell(command, root, protectedPaths, sandboxTemp) {
|
|
442
|
+
if (process.platform === "darwin" && existsSync("/usr/bin/sandbox-exec")) {
|
|
443
|
+
const workspacePaths = sandboxWorkspacePaths(canonicalPath(root), protectedPaths.map(canonicalPath));
|
|
444
|
+
const workspaceAncestors = pathAncestors(canonicalPath(root));
|
|
445
|
+
const toolchains = toolchainReadPaths(canonicalPath(root));
|
|
446
|
+
const readableAncestors = toolchains.flatMap(pathAncestors);
|
|
447
|
+
const readable = ["/System", "/usr", "/bin", "/sbin", "/Library", "/etc", "/dev", "/private/etc", "/private/var/select", "/Applications", ...toolchains, ...workspacePaths, ...(sandboxTemp ? [sandboxTemp] : [])].filter(existsSync).map(canonicalPath);
|
|
448
|
+
const writable = [...workspacePaths, ...(sandboxTemp ? [sandboxTemp] : []), "/dev"].map(canonicalPath);
|
|
449
|
+
const rules = ["(version 1)", "(deny default)", "(import \"system.sb\")", "(allow process*)", "(allow signal)", "(allow network*)", "(allow sysctl-read)", "(allow mach-lookup)", ...[...new Set([...workspaceAncestors, ...readableAncestors])].map((path) => `(allow file-read* (literal ${JSON.stringify(path)}))`), ...readable.map((path) => `(allow file-read* (subpath ${JSON.stringify(path)}))`), ...writable.map((path) => `(allow file-write* (subpath ${JSON.stringify(path)}))`), ...protectedPaths.map(canonicalPath).map((path) => `(deny file-read* file-write* (subpath ${JSON.stringify(path)}))`)];
|
|
450
|
+
return { command: "/usr/bin/sandbox-exec", args: ["-p", rules.join(" "), "/bin/sh", "-lc", command] };
|
|
451
|
+
}
|
|
452
|
+
if (process.platform === "linux") {
|
|
453
|
+
const bwrap = ["/usr/bin/bwrap", "/bin/bwrap"].find(existsSync);
|
|
454
|
+
if (bwrap) {
|
|
455
|
+
const canonicalRoot = canonicalPath(root);
|
|
456
|
+
if (["/usr", "/bin", "/sbin", "/lib", "/lib64", "/etc"].some((path) => canonicalRoot === path || canonicalRoot.startsWith(`${path}${sep}`)))
|
|
457
|
+
return null;
|
|
458
|
+
return { command: bwrap, args: linuxSandboxArgs(command, canonicalRoot, protectedPaths, sandboxTemp) };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
export function linuxSandboxArgs(command, root, protectedPaths, sandboxTemp) {
|
|
464
|
+
const canonicalRoot = canonicalPath(root);
|
|
465
|
+
const workspacePaths = sandboxWorkspacePaths(canonicalRoot, protectedPaths.map(canonicalPath));
|
|
466
|
+
const systemPaths = ["/usr", "/bin", "/sbin", "/lib", "/lib64", "/etc"].filter(existsSync).flatMap((path) => ["--ro-bind", path, path]);
|
|
467
|
+
const mounted = ["/tmp", "/usr", "/bin", "/sbin", "/lib", "/lib64", "/etc"];
|
|
468
|
+
const toolchains = toolchainReadPaths(canonicalRoot).filter((path) => !mounted.some((base) => path === base || path.startsWith(`${base}${sep}`)) && path !== canonicalRoot && !path.startsWith(`${canonicalRoot}${sep}`));
|
|
469
|
+
const destinations = [canonicalRoot, ...toolchains].flatMap((path) => pathAncestors(path).slice(1)).filter((path) => !mounted.some((base) => path === base || path.startsWith(`${base}${sep}`)));
|
|
470
|
+
const directories = [...new Set(destinations)].sort((left, right) => left.length - right.length).flatMap((path) => ["--dir", path]);
|
|
471
|
+
return ["--die-with-parent", "--unshare-all", "--share-net", ...systemPaths, "--dev", "/dev", "--proc", "/proc", "--tmpfs", "/tmp", ...(sandboxTemp ? ["--dir", canonicalPath(sandboxTemp)] : []), ...directories, ...toolchains.flatMap((path) => ["--ro-bind", path, path]), ...workspacePaths.flatMap((path) => ["--bind", path, path]), "--chdir", canonicalRoot, "/bin/sh", "-lc", command];
|
|
472
|
+
}
|
|
473
|
+
function toolchainReadPaths(root) {
|
|
474
|
+
const executable = canonicalPath(process.execPath);
|
|
475
|
+
const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((path) => resolve(path));
|
|
476
|
+
const prefixes = ["/opt/homebrew", "/usr/local"].filter((prefix) => executable.startsWith(`${prefix}${sep}`) || pathEntries.some((path) => path.startsWith(`${prefix}${sep}`)));
|
|
477
|
+
const home = canonicalPath(homedir());
|
|
478
|
+
const managedHomeRuntime = [join(home, ".nvm", "versions", "node"), join(home, ".asdf", "installs", "node"), join(home, ".volta", "tools", "image", "node")].find((prefix) => executable.startsWith(`${prefix}${sep}`));
|
|
479
|
+
const runtimeRoot = prefixes.length ? [] : managedHomeRuntime ? [executable.slice(0, executable.indexOf(`${sep}bin${sep}`, managedHomeRuntime.length))] : [dirname(executable)];
|
|
480
|
+
return [...new Set([...prefixes, ...runtimeRoot])].filter((path) => path !== root && !path.startsWith(`${root}${sep}`));
|
|
481
|
+
}
|
|
482
|
+
export function sandboxWorkspacePaths(root, protectedPaths) {
|
|
483
|
+
if (!protectedPaths.some((path) => path === root || path.startsWith(`${root}${sep}`)))
|
|
484
|
+
return [root];
|
|
485
|
+
return [...new Set(readdirSync(root).flatMap((name) => { try {
|
|
486
|
+
return [canonicalPath(join(root, name))];
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
return [];
|
|
490
|
+
} }))]
|
|
491
|
+
.filter((candidate) => candidate !== root && candidate.startsWith(`${root}${sep}`))
|
|
492
|
+
.filter((candidate) => !protectedPaths.some((path) => path === candidate || path.startsWith(`${candidate}${sep}`) || candidate.startsWith(`${path}${sep}`)));
|
|
493
|
+
}
|
|
494
|
+
function canonicalPath(path) {
|
|
495
|
+
let current = resolve(path);
|
|
496
|
+
const suffix = [];
|
|
497
|
+
while (!existsSync(current)) {
|
|
498
|
+
const parent = dirname(current);
|
|
499
|
+
if (parent === current)
|
|
500
|
+
break;
|
|
501
|
+
suffix.unshift(current.slice(parent.length + 1));
|
|
502
|
+
current = parent;
|
|
503
|
+
}
|
|
504
|
+
return resolve(realpathSync(current), ...suffix);
|
|
505
|
+
}
|
|
506
|
+
function pathAncestors(path) {
|
|
507
|
+
const values = [];
|
|
508
|
+
let current = resolve(path);
|
|
509
|
+
while (true) {
|
|
510
|
+
values.unshift(current);
|
|
511
|
+
const parent = dirname(current);
|
|
512
|
+
if (parent === current)
|
|
513
|
+
return values;
|
|
514
|
+
current = parent;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function toolEnvironment(sandboxTemp = null) {
|
|
392
518
|
const env = {};
|
|
393
519
|
for (const name of ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "SHELL", "TERM", "USER"])
|
|
394
520
|
if (process.env[name] !== undefined)
|
|
395
521
|
env[name] = process.env[name];
|
|
522
|
+
if (sandboxTemp) {
|
|
523
|
+
env.TMPDIR = sandboxTemp;
|
|
524
|
+
env.TMP = sandboxTemp;
|
|
525
|
+
env.TEMP = sandboxTemp;
|
|
526
|
+
}
|
|
527
|
+
if (process.platform === "darwin") {
|
|
528
|
+
const developer = ["/Library/Developer/CommandLineTools", "/Applications/Xcode.app/Contents/Developer"].find(existsSync);
|
|
529
|
+
if (developer)
|
|
530
|
+
env.DEVELOPER_DIR = developer;
|
|
531
|
+
}
|
|
532
|
+
Object.assign(env, gitIdentityEnvironment());
|
|
396
533
|
return env;
|
|
397
534
|
}
|
|
535
|
+
function gitIdentityEnvironment() {
|
|
536
|
+
try {
|
|
537
|
+
const source = readFileSync(join(homedir(), ".gitconfig"), "utf8");
|
|
538
|
+
const user = source.match(/^\[user\]\s*\n((?:[ \t].*(?:\n|$))*)/m)?.[1] ?? "";
|
|
539
|
+
const name = user.match(/^\s*name\s*=\s*(.+)$/m)?.[1]?.trim();
|
|
540
|
+
const email = user.match(/^\s*email\s*=\s*(.+)$/m)?.[1]?.trim();
|
|
541
|
+
return { ...(name ? { GIT_AUTHOR_NAME: name, GIT_COMMITTER_NAME: name } : {}), ...(email ? { GIT_AUTHOR_EMAIL: email, GIT_COMMITTER_EMAIL: email } : {}), GIT_CONFIG_GLOBAL: "/dev/null" };
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return { GIT_CONFIG_GLOBAL: "/dev/null" };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
398
547
|
function estimateMessages(messages) { return Math.ceil(JSON.stringify(messages).length / 4); }
|
|
399
548
|
function integerSetting(value, fallback) {
|
|
400
549
|
if (value === undefined)
|
|
@@ -423,6 +572,8 @@ export function assistantResponseText(message) {
|
|
|
423
572
|
.join("\n")
|
|
424
573
|
.trim();
|
|
425
574
|
}
|
|
575
|
+
function sameHandoff(left, right) { return left.outcome === right.outcome && left.evidence.length === right.evidence.length && left.evidence.every((value, index) => value === right.evidence[index]); }
|
|
576
|
+
function hasAgentToolCall(message, name) { return message.role === "assistant" && message.content.some((item) => item.type === "toolCall" && item.name === name); }
|
|
426
577
|
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url)))
|
|
427
578
|
await runPiWorker();
|
|
428
579
|
//# sourceMappingURL=pi-worker.js.map
|