frely-cli 0.23.9 → 0.23.11
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 +49 -20
- package/README.zh-CN.md +47 -19
- package/dist/agent/local/approvals.d.ts +24 -0
- package/dist/agent/local/approvals.js +79 -0
- package/dist/agent/local/approvals.js.map +1 -0
- package/dist/agent/local/command.d.ts +10 -0
- package/dist/agent/local/command.js +292 -0
- package/dist/agent/local/command.js.map +1 -0
- package/dist/agent/local/confine.d.ts +20 -0
- package/dist/agent/local/confine.js +52 -0
- package/dist/agent/local/confine.js.map +1 -0
- package/dist/agent/local/manifest.d.ts +17 -0
- package/dist/agent/local/manifest.js +55 -0
- package/dist/agent/local/manifest.js.map +1 -0
- package/dist/agent/local/model-proxy.d.ts +18 -0
- package/dist/agent/local/model-proxy.js +111 -0
- package/dist/agent/local/model-proxy.js.map +1 -0
- package/dist/agent/local/source.d.ts +28 -0
- package/dist/agent/local/source.js +64 -0
- package/dist/agent/local/source.js.map +1 -0
- package/dist/agent/local/store.d.ts +45 -0
- package/dist/agent/local/store.js +78 -0
- package/dist/agent/local/store.js.map +1 -0
- package/dist/agent/local/supervisor.d.ts +9 -0
- package/dist/agent/local/supervisor.js +62 -0
- package/dist/agent/local/supervisor.js.map +1 -0
- package/dist/agent/run-store.d.ts +80 -0
- package/dist/agent/run-store.js +154 -0
- package/dist/agent/run-store.js.map +1 -0
- package/dist/agent/run.d.ts +41 -0
- package/dist/agent/run.js +233 -0
- package/dist/agent/run.js.map +1 -0
- package/dist/agent/workspace-read.d.ts +78 -0
- package/dist/agent/workspace-read.js +292 -0
- package/dist/agent/workspace-read.js.map +1 -0
- package/dist/agent-help.d.ts +158 -14
- package/dist/agent-help.js +33 -8
- package/dist/agent-help.js.map +1 -1
- package/dist/cloud.d.ts +2 -42
- package/dist/cloud.js +8 -260
- package/dist/cloud.js.map +1 -1
- package/dist/controller/client.d.ts +6 -3
- package/dist/controller/client.js +7 -8
- package/dist/controller/client.js.map +1 -1
- package/dist/controller/session.d.ts +7 -2
- package/dist/controller/session.js +79 -22
- package/dist/controller/session.js.map +1 -1
- package/dist/index.js +114 -9
- package/dist/index.js.map +1 -1
- package/dist/provider/control.d.ts +10 -0
- package/dist/provider/control.js +19 -0
- package/dist/provider/control.js.map +1 -1
- package/dist/provider/share.js +4 -1
- package/dist/provider/share.js.map +1 -1
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.js +39 -1
- package/dist/runtime/sandbox.js.map +1 -1
- package/dist/share/allowance.d.ts +8 -0
- package/dist/share/allowance.js +22 -0
- package/dist/share/allowance.js.map +1 -0
- package/dist/share/control.d.ts +2 -0
- package/dist/share/control.js +14 -10
- package/dist/share/control.js.map +1 -1
- package/dist/share/service-config.d.ts +39 -0
- package/dist/share/service-config.js +85 -0
- package/dist/share/service-config.js.map +1 -0
- package/dist/share/service-control.d.ts +15 -0
- package/dist/share/service-control.js +31 -0
- package/dist/share/service-control.js.map +1 -0
- package/dist/share/service-run.d.ts +55 -0
- package/dist/share/service-run.js +285 -0
- package/dist/share/service-run.js.map +1 -0
- package/dist/share/service-state.d.ts +16 -0
- package/dist/share/service-state.js +66 -0
- package/dist/share/service-state.js.map +1 -0
- package/dist/skill/access.d.ts +25 -6
- package/dist/skill/access.js +59 -9
- package/dist/skill/access.js.map +1 -1
- package/dist/skill/cloud-item.d.ts +2 -2
- package/dist/skill/cloud-item.js.map +1 -1
- package/dist/skill/entry.d.ts +1 -1
- package/dist/skill/entry.js +1 -1
- package/dist/skill/remote-mcp.d.ts +3 -0
- package/dist/skill/remote-mcp.js +7 -1
- package/dist/skill/remote-mcp.js.map +1 -1
- package/dist/skill/router.d.ts +57 -2
- package/dist/skill/router.js +76 -40
- package/dist/skill/router.js.map +1 -1
- package/dist/uninstall.d.ts +4 -0
- package/dist/uninstall.js +14 -0
- package/dist/uninstall.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Where an Agent's code comes from: a Frely Agent page (`username/name`) or a public GitHub repository. */
|
|
2
|
+
export type AgentSource = {
|
|
3
|
+
kind: "frely";
|
|
4
|
+
user: string;
|
|
5
|
+
name: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "github";
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class SourceError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare function parseSource(target: string): AgentSource;
|
|
15
|
+
export declare const githubUrl: (source: {
|
|
16
|
+
owner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
}) => string;
|
|
19
|
+
/** The public record a Frely Agent page keeps for its repository. */
|
|
20
|
+
export interface RegisteredRepo {
|
|
21
|
+
repoUrl: string;
|
|
22
|
+
commit: string | undefined;
|
|
23
|
+
name: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Reads the repository the Agent's author registered on Frely. The commit is the one the author listed. */
|
|
26
|
+
export declare function resolveRegisteredRepo(user: string, name: string, relayUrl: string, fetchFn?: typeof fetch): Promise<RegisteredRepo>;
|
|
27
|
+
/** Downloads `repoUrl` into the empty-or-missing `dir`, at `commit` when one is given, else at the default branch. Returns the commit that is checked out. */
|
|
28
|
+
export declare function download(repoUrl: string, dir: string, commit: string | undefined): Promise<string>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
const run = promisify(execFile);
|
|
6
|
+
export class SourceError extends Error {
|
|
7
|
+
constructor(message) { super(message); this.name = "SourceError"; }
|
|
8
|
+
}
|
|
9
|
+
const FRELY_PART = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
10
|
+
const GITHUB_URL = /^https:\/\/github\.com\/([A-Za-z0-9][A-Za-z0-9-]{0,38})\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?\/?$/u;
|
|
11
|
+
const COMMIT = /^[0-9a-f]{40}$/u;
|
|
12
|
+
export function parseSource(target) {
|
|
13
|
+
const github = GITHUB_URL.exec(target);
|
|
14
|
+
if (github)
|
|
15
|
+
return { kind: "github", owner: github[1], repo: github[2] };
|
|
16
|
+
const [user, name, extra] = target.replace(/^@/u, "").split("/");
|
|
17
|
+
if (user !== undefined && name !== undefined && extra === undefined && FRELY_PART.test(user) && FRELY_PART.test(name))
|
|
18
|
+
return { kind: "frely", user, name };
|
|
19
|
+
throw new SourceError("Use a Frely Agent name such as username/name, or a public GitHub address such as https://github.com/owner/repo.");
|
|
20
|
+
}
|
|
21
|
+
export const githubUrl = (source) => `https://github.com/${source.owner}/${source.repo}`;
|
|
22
|
+
/** Reads the repository the Agent's author registered on Frely. The commit is the one the author listed. */
|
|
23
|
+
export async function resolveRegisteredRepo(user, name, relayUrl, fetchFn = fetch) {
|
|
24
|
+
const response = await fetchFn(`${relayUrl}/api/public/agent-repos/${encodeURIComponent(user)}/${encodeURIComponent(name)}`, { headers: { accept: "application/json" }, redirect: "error", signal: AbortSignal.timeout(15_000) });
|
|
25
|
+
if (response.status === 404)
|
|
26
|
+
throw new SourceError(`${user}/${name} is not a Frely Agent with a repository.`);
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
throw new SourceError(`Frely could not look up ${user}/${name} (HTTP ${response.status}).`);
|
|
29
|
+
const body = await response.json().catch(() => null);
|
|
30
|
+
if (!body || typeof body.repoUrl !== "string" || !GITHUB_URL.test(body.repoUrl))
|
|
31
|
+
throw new SourceError(`Frely returned no usable repository for ${user}/${name}.`);
|
|
32
|
+
const commit = typeof body.commit === "string" && COMMIT.test(body.commit) ? body.commit : undefined;
|
|
33
|
+
return { repoUrl: body.repoUrl.replace(/\.git$/u, "").replace(/\/$/u, ""), commit, name: typeof body.name === "string" ? body.name : undefined };
|
|
34
|
+
}
|
|
35
|
+
/** git with no prompts, no hooks, no submodules and https only: the repository is untrusted. */
|
|
36
|
+
async function git(args, cwd) {
|
|
37
|
+
const safe = ["-c", "protocol.allow=never", "-c", "protocol.https.allow=always", "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", "-c", "submodule.recurse=false"];
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await run("git", [...safe, ...args], { cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_CONFIG_NOSYSTEM: "1" }, timeout: 5 * 60_000, maxBuffer: 4 * 1024 * 1024 });
|
|
40
|
+
return stdout.trim();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const stderr = error.stderr?.trim().split("\n").slice(-3).join(" ");
|
|
44
|
+
throw new SourceError(`git ${args[0]} failed${stderr ? `: ${stderr}` : "."}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Downloads `repoUrl` into the empty-or-missing `dir`, at `commit` when one is given, else at the default branch. Returns the commit that is checked out. */
|
|
48
|
+
export async function download(repoUrl, dir, commit) {
|
|
49
|
+
await mkdir(dirname(dir), { recursive: true });
|
|
50
|
+
if (commit) {
|
|
51
|
+
await mkdir(dir, { recursive: true });
|
|
52
|
+
await git(["init", "--quiet"], dir);
|
|
53
|
+
await git(["fetch", "--quiet", "--depth", "1", "--no-tags", "--no-recurse-submodules", repoUrl, commit], dir);
|
|
54
|
+
await git(["checkout", "--quiet", "FETCH_HEAD"], dir);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await git(["clone", "--quiet", "--depth", "1", "--no-tags", "--single-branch", "--no-recurse-submodules", repoUrl, dir], undefined);
|
|
58
|
+
}
|
|
59
|
+
const head = await git(["rev-parse", "HEAD"], dir);
|
|
60
|
+
if (!COMMIT.test(head))
|
|
61
|
+
throw new SourceError("git did not report a commit.");
|
|
62
|
+
return head;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../../src/agent/local/source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is known about one Agent running on this computer. It lives with Frely's own files, not in the Agent's folder: the sandbox
|
|
3
|
+
* cannot read or write there, so the program it runs cannot change which sites it was approved for.
|
|
4
|
+
*/
|
|
5
|
+
export interface LocalAgentRecord {
|
|
6
|
+
version: 1;
|
|
7
|
+
slug: string;
|
|
8
|
+
/** What the person typed: `username/name` or a GitHub address. */
|
|
9
|
+
source: string;
|
|
10
|
+
repoUrl: string;
|
|
11
|
+
commit: string;
|
|
12
|
+
name: string;
|
|
13
|
+
workspace: string;
|
|
14
|
+
setup: string | undefined;
|
|
15
|
+
run: string;
|
|
16
|
+
/** Sites from frely.yaml, asked for when the Agent is added and started. */
|
|
17
|
+
network: string[];
|
|
18
|
+
addedAt: string;
|
|
19
|
+
setupDone: boolean;
|
|
20
|
+
/** Approved site -> when the approval ends (ISO time). */
|
|
21
|
+
approvals: Record<string, string>;
|
|
22
|
+
pid: number | undefined;
|
|
23
|
+
startedAt: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
export declare const SLUG: RegExp;
|
|
26
|
+
/** Records, logs and sockets: `~/.config/frely/agent-local`. */
|
|
27
|
+
export declare const stateDir: () => string;
|
|
28
|
+
/** One folder per Agent, the only place its program can write besides the system temp folder. */
|
|
29
|
+
export declare const agentsRoot: () => string;
|
|
30
|
+
export declare function assertSlug(slug: string): void;
|
|
31
|
+
export declare const workspaceOf: (slug: string) => string;
|
|
32
|
+
export declare const recordPath: (slug: string) => string;
|
|
33
|
+
export declare const logPath: (slug: string) => string;
|
|
34
|
+
/** Short enough for the ~100 character limit of a Unix socket path. */
|
|
35
|
+
export declare const socketPath: (slug: string) => string;
|
|
36
|
+
/** Whether `path` is inside the Agents folder (and not the folder itself), so a removal can never reach anything else. */
|
|
37
|
+
export declare function insideAgentsRoot(path: string): boolean;
|
|
38
|
+
export declare function readRecord(slug: string): Promise<LocalAgentRecord | undefined>;
|
|
39
|
+
export declare function writeRecord(record: LocalAgentRecord): Promise<void>;
|
|
40
|
+
export declare function listRecords(): Promise<LocalAgentRecord[]>;
|
|
41
|
+
export declare function removeRecordFiles(slug: string): Promise<void>;
|
|
42
|
+
/** Whether the process the record names is still running. */
|
|
43
|
+
export declare function isRunning(record: Pick<LocalAgentRecord, "pid">): boolean;
|
|
44
|
+
/** Approvals that have not ended yet, as a site list. */
|
|
45
|
+
export declare function approvedHosts(record: Pick<LocalAgentRecord, "approvals" | "network">, now?: number): string[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve, sep } from "node:path";
|
|
5
|
+
export const SLUG = /^[a-z0-9][a-z0-9-]{0,62}$/u;
|
|
6
|
+
const configRoot = () => process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
|
+
/** Records, logs and sockets: `~/.config/frely/agent-local`. */
|
|
8
|
+
export const stateDir = () => join(configRoot(), "frely", "agent-local");
|
|
9
|
+
/** One folder per Agent, the only place its program can write besides the system temp folder. */
|
|
10
|
+
export const agentsRoot = () => resolve(process.env.FRELY_AGENTS_DIR || join(homedir(), "frely-agents"));
|
|
11
|
+
export function assertSlug(slug) {
|
|
12
|
+
if (!SLUG.test(slug))
|
|
13
|
+
throw new Error("The Agent name must be lower-case letters, digits and dashes.");
|
|
14
|
+
}
|
|
15
|
+
export const workspaceOf = (slug) => join(agentsRoot(), slug);
|
|
16
|
+
export const recordPath = (slug) => join(stateDir(), `${slug}.json`);
|
|
17
|
+
export const logPath = (slug) => join(stateDir(), `${slug}.log`);
|
|
18
|
+
/** Short enough for the ~100 character limit of a Unix socket path. */
|
|
19
|
+
export const socketPath = (slug) => join(process.env.XDG_RUNTIME_DIR || stateDir(), `m-${createHash("sha256").update(slug).digest("hex").slice(0, 10)}.sock`);
|
|
20
|
+
/** Whether `path` is inside the Agents folder (and not the folder itself), so a removal can never reach anything else. */
|
|
21
|
+
export function insideAgentsRoot(path) {
|
|
22
|
+
const root = agentsRoot();
|
|
23
|
+
const target = resolve(path);
|
|
24
|
+
return target !== root && target.startsWith(root.endsWith(sep) ? root : root + sep);
|
|
25
|
+
}
|
|
26
|
+
export async function readRecord(slug) {
|
|
27
|
+
assertSlug(slug);
|
|
28
|
+
const raw = await readFile(recordPath(slug), "utf8").catch(() => undefined);
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const value = JSON.parse(raw);
|
|
33
|
+
return value.version === 1 && value.slug === slug && typeof value.run === "string" ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function writeRecord(record) {
|
|
40
|
+
await mkdir(stateDir(), { recursive: true, mode: 0o700 });
|
|
41
|
+
const target = recordPath(record.slug);
|
|
42
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
43
|
+
await writeFile(temp, JSON.stringify(record, null, 2) + "\n", { mode: 0o600 });
|
|
44
|
+
await rename(temp, target);
|
|
45
|
+
await chmod(target, 0o600).catch(() => undefined);
|
|
46
|
+
}
|
|
47
|
+
export async function listRecords() {
|
|
48
|
+
const names = await readdir(stateDir()).catch(() => []);
|
|
49
|
+
const records = [];
|
|
50
|
+
for (const name of names.filter((file) => file.endsWith(".json")).sort()) {
|
|
51
|
+
const record = await readRecord(name.slice(0, -5)).catch(() => undefined);
|
|
52
|
+
if (record)
|
|
53
|
+
records.push(record);
|
|
54
|
+
}
|
|
55
|
+
return records;
|
|
56
|
+
}
|
|
57
|
+
export async function removeRecordFiles(slug) {
|
|
58
|
+
await rm(recordPath(slug), { force: true });
|
|
59
|
+
await rm(logPath(slug), { force: true });
|
|
60
|
+
await rm(socketPath(slug), { force: true });
|
|
61
|
+
}
|
|
62
|
+
/** Whether the process the record names is still running. */
|
|
63
|
+
export function isRunning(record) {
|
|
64
|
+
if (record.pid === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
process.kill(record.pid, 0);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return error.code === "EPERM";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Approvals that have not ended yet, as a site list. */
|
|
75
|
+
export function approvedHosts(record, now = Date.now()) {
|
|
76
|
+
return record.network.filter((host) => { const end = record.approvals[host]; return end !== undefined && Date.parse(end) > now; });
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/agent/local/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class SupervisorError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Runs one Agent until it exits or this process is told to stop: the model proxy on a Unix socket, then the Agent's `local.run`
|
|
6
|
+
* inside the sandbox with only Frely's gateway, the proxy name and the approved sites reachable. Everything the program prints goes to
|
|
7
|
+
* this process's output, which `frely agent local start` points at the Agent's log file.
|
|
8
|
+
*/
|
|
9
|
+
export declare function superviseAgent(slug: string): Promise<number>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { requireLogin } from "../../auth.js";
|
|
2
|
+
import { terminateProcessTree } from "../../runtime/process-tree.js";
|
|
3
|
+
import { restrictSandboxNetwork, shutdownSandbox } from "../../runtime/sandbox.js";
|
|
4
|
+
import { agentEnv, gatewayOrigin, readApiKey, spawnConfined } from "./confine.js";
|
|
5
|
+
import { MODEL_HOST, startModelProxy } from "./model-proxy.js";
|
|
6
|
+
import { approvedHosts, readRecord, socketPath, writeRecord } from "./store.js";
|
|
7
|
+
export class SupervisorError extends Error {
|
|
8
|
+
constructor(message) { super(message); this.name = "SupervisorError"; }
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs one Agent until it exits or this process is told to stop: the model proxy on a Unix socket, then the Agent's `local.run`
|
|
12
|
+
* inside the sandbox with only Frely's gateway, the proxy name and the approved sites reachable. Everything the program prints goes to
|
|
13
|
+
* this process's output, which `frely agent local start` points at the Agent's log file.
|
|
14
|
+
*/
|
|
15
|
+
export async function superviseAgent(slug) {
|
|
16
|
+
const record = await readRecord(slug);
|
|
17
|
+
if (!record)
|
|
18
|
+
throw new SupervisorError(`No local Agent named ${slug}. Add it with frely agent local add.`);
|
|
19
|
+
if (!record.setupDone)
|
|
20
|
+
throw new SupervisorError(`${slug} was not set up yet. Run frely agent local add again to finish.`);
|
|
21
|
+
const login = await requireLogin().catch(() => { throw new SupervisorError("Frely login is required. Run `frely login`."); });
|
|
22
|
+
const origin = gatewayOrigin(login.config.relayUrl);
|
|
23
|
+
const apiKey = await readApiKey(origin);
|
|
24
|
+
if (!apiKey)
|
|
25
|
+
throw new SupervisorError(`${slug} needs a Frely API key for its model calls. Create one at ${login.config.relayUrl}/user/keys, then run: frely agent local key --api-key-stdin`);
|
|
26
|
+
const sites = approvedHosts(record);
|
|
27
|
+
const skipped = record.network.filter((host) => !sites.includes(host));
|
|
28
|
+
if (skipped.length > 0)
|
|
29
|
+
process.stderr.write(`Not approved, so blocked: ${skipped.join(", ")}. Run frely agent local start ${slug} again to ask.\n`);
|
|
30
|
+
const socket = socketPath(slug);
|
|
31
|
+
restrictSandboxNetwork({ allowedDomains: [new URL(origin).hostname, MODEL_HOST, ...sites], mitmProxy: { socketPath: socket, domains: [MODEL_HOST] } });
|
|
32
|
+
const proxy = await startModelProxy({ socketPath: socket, upstream: origin, authorization: async () => `Bearer ${apiKey}` });
|
|
33
|
+
let confined;
|
|
34
|
+
try {
|
|
35
|
+
confined = await spawnConfined(record.run, record.workspace, agentEnv(record.workspace, { model: true }));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
await proxy.close();
|
|
39
|
+
await shutdownSandbox();
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const { child } = confined;
|
|
43
|
+
child.stdout?.pipe(process.stdout, { end: false });
|
|
44
|
+
child.stderr?.pipe(process.stderr, { end: false });
|
|
45
|
+
await writeRecord({ ...record, pid: process.pid, startedAt: new Date().toISOString() });
|
|
46
|
+
process.stderr.write(`[frely] ${slug} started${sites.length ? `; sites: ${sites.join(", ")}` : ""}\n`);
|
|
47
|
+
const stop = () => { void terminateProcessTree(child); };
|
|
48
|
+
process.once("SIGTERM", stop);
|
|
49
|
+
process.once("SIGINT", stop);
|
|
50
|
+
const code = await new Promise((done) => { child.once("error", () => done(1)); child.once("close", (exit) => done(exit ?? 1)); });
|
|
51
|
+
const blocked = confined.violations();
|
|
52
|
+
if (blocked.length > 0)
|
|
53
|
+
process.stderr.write(`[frely] The sandbox blocked:\n${[...new Set(blocked)].slice(0, 10).join("\n")}\n`);
|
|
54
|
+
await proxy.close();
|
|
55
|
+
await shutdownSandbox();
|
|
56
|
+
const latest = await readRecord(slug);
|
|
57
|
+
if (latest)
|
|
58
|
+
await writeRecord({ ...latest, pid: undefined, startedAt: undefined });
|
|
59
|
+
process.stderr.write(`[frely] ${slug} stopped (exit ${code}).\n`);
|
|
60
|
+
return code;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/agent/local/supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { CallerItem } from "../skill/router.js";
|
|
2
|
+
import type { FileRead } from "./workspace-read.js";
|
|
3
|
+
/** Finished runs are kept this long on this computer, then removed the next time a run starts. */
|
|
4
|
+
export declare const RUN_RETENTION_MS: number;
|
|
5
|
+
export type RunStatus = "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
6
|
+
export interface RunRound {
|
|
7
|
+
readonly n: number;
|
|
8
|
+
readonly requestId?: string;
|
|
9
|
+
readonly startedAt: string;
|
|
10
|
+
readonly endedAt?: string;
|
|
11
|
+
readonly toolCalls: readonly string[];
|
|
12
|
+
readonly usage?: Readonly<{
|
|
13
|
+
input_tokens: number;
|
|
14
|
+
output_tokens: number;
|
|
15
|
+
total_tokens: number;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
/** Local record of one `frely agent run`. Full input and output stay on this computer (file mode 0600). */
|
|
19
|
+
export interface RunRecord {
|
|
20
|
+
readonly version: 1;
|
|
21
|
+
readonly runId: string;
|
|
22
|
+
readonly distributionId: string;
|
|
23
|
+
readonly modelId?: string;
|
|
24
|
+
readonly status: RunStatus;
|
|
25
|
+
readonly createdAt: string;
|
|
26
|
+
readonly updatedAt: string;
|
|
27
|
+
/** The detached worker that owns the run while it is `running`. */
|
|
28
|
+
readonly workerPid?: number;
|
|
29
|
+
readonly task: string;
|
|
30
|
+
/** Workspace root of `--read`; absent for a plain single call. */
|
|
31
|
+
readonly readRoot?: string;
|
|
32
|
+
readonly limits?: Readonly<{
|
|
33
|
+
totalBytes: number;
|
|
34
|
+
maxRounds: number;
|
|
35
|
+
}>;
|
|
36
|
+
readonly bridge?: Readonly<{
|
|
37
|
+
usedBytes: number;
|
|
38
|
+
roundsAnswered: number;
|
|
39
|
+
}>;
|
|
40
|
+
readonly items: readonly CallerItem[];
|
|
41
|
+
readonly rounds: readonly RunRound[];
|
|
42
|
+
readonly filesRead: readonly FileRead[];
|
|
43
|
+
readonly usage: Readonly<{
|
|
44
|
+
input_tokens: number;
|
|
45
|
+
output_tokens: number;
|
|
46
|
+
total_tokens: number;
|
|
47
|
+
}>;
|
|
48
|
+
readonly resumeCount: number;
|
|
49
|
+
readonly text?: string;
|
|
50
|
+
/** The Agent could not take local tools, so the run was done as one plain call. */
|
|
51
|
+
readonly toolsUnsupported?: boolean;
|
|
52
|
+
readonly error?: Readonly<{
|
|
53
|
+
code: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
export declare function runsDirectory(): string;
|
|
58
|
+
export declare function isRunId(value: string): boolean;
|
|
59
|
+
export declare function createRun(input: {
|
|
60
|
+
readonly distributionId: string;
|
|
61
|
+
readonly task: string;
|
|
62
|
+
readonly readRoot?: string;
|
|
63
|
+
readonly limits?: {
|
|
64
|
+
totalBytes: number;
|
|
65
|
+
maxRounds: number;
|
|
66
|
+
};
|
|
67
|
+
}): Promise<RunRecord>;
|
|
68
|
+
/** Atomic write: a reader never sees half a record. */
|
|
69
|
+
export declare function writeRun(record: RunRecord): Promise<RunRecord>;
|
|
70
|
+
export declare function readRun(runId: string): Promise<RunRecord | null>;
|
|
71
|
+
export declare function listRuns(): Promise<RunRecord[]>;
|
|
72
|
+
export declare function pruneRuns(now?: number): Promise<void>;
|
|
73
|
+
export declare function processAlive(pid: number | undefined): boolean;
|
|
74
|
+
/** `running` only counts while its worker is alive; otherwise the run was cut off (killed, rebooted) and is `interrupted`. */
|
|
75
|
+
export declare function effectiveStatus(record: RunRecord, now?: number): RunStatus;
|
|
76
|
+
/** A run can continue when a failed or interrupted run still has its Agent and (for `--read`) rounds to resume from. */
|
|
77
|
+
export declare function resumable(record: RunRecord): boolean;
|
|
78
|
+
/** `<id>` or `--last`. */
|
|
79
|
+
export declare function resolveRun(target: string | undefined): Promise<RunRecord>;
|
|
80
|
+
export declare function addUsage(total: RunRecord["usage"], usage: RunRecord["usage"] | undefined): RunRecord["usage"];
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/** Finished runs are kept this long on this computer, then removed the next time a run starts. */
|
|
6
|
+
export const RUN_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
7
|
+
export function runsDirectory() {
|
|
8
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "frely", "agent-runs");
|
|
9
|
+
}
|
|
10
|
+
const RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
11
|
+
export function isRunId(value) {
|
|
12
|
+
return RUN_ID.test(value);
|
|
13
|
+
}
|
|
14
|
+
function runPath(runId) {
|
|
15
|
+
if (!isRunId(runId))
|
|
16
|
+
throw new Error("Invalid run id.");
|
|
17
|
+
return join(runsDirectory(), `${runId}.json`);
|
|
18
|
+
}
|
|
19
|
+
export async function createRun(input) {
|
|
20
|
+
await mkdir(runsDirectory(), { recursive: true, mode: 0o700 });
|
|
21
|
+
await chmod(runsDirectory(), 0o700).catch(() => undefined);
|
|
22
|
+
await pruneRuns().catch(() => undefined);
|
|
23
|
+
const now = new Date().toISOString();
|
|
24
|
+
const record = {
|
|
25
|
+
version: 1,
|
|
26
|
+
runId: randomUUID(),
|
|
27
|
+
distributionId: input.distributionId,
|
|
28
|
+
status: "running",
|
|
29
|
+
createdAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
task: input.task,
|
|
32
|
+
...(input.readRoot === undefined ? {} : { readRoot: input.readRoot }),
|
|
33
|
+
...(input.limits === undefined ? {} : { limits: input.limits }),
|
|
34
|
+
items: [],
|
|
35
|
+
rounds: [],
|
|
36
|
+
filesRead: [],
|
|
37
|
+
usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
|
|
38
|
+
resumeCount: 0,
|
|
39
|
+
};
|
|
40
|
+
await writeRun(record);
|
|
41
|
+
return record;
|
|
42
|
+
}
|
|
43
|
+
/** Atomic write: a reader never sees half a record. */
|
|
44
|
+
export async function writeRun(record) {
|
|
45
|
+
const next = { ...record, updatedAt: new Date().toISOString() };
|
|
46
|
+
const path = runPath(record.runId);
|
|
47
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
48
|
+
await writeFile(temp, `${JSON.stringify(next)}\n`, { mode: 0o600 });
|
|
49
|
+
// On Windows a rename onto a file another process is reading fails briefly (EPERM/EBUSY/EACCES): retry.
|
|
50
|
+
for (let attempt = 0;; attempt += 1) {
|
|
51
|
+
try {
|
|
52
|
+
await rename(temp, path);
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const code = error.code;
|
|
57
|
+
if (attempt >= 50 || (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES")) {
|
|
58
|
+
await rm(temp, { force: true });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function readRun(runId) {
|
|
66
|
+
let raw;
|
|
67
|
+
for (let attempt = 0; attempt < 5 && raw === undefined; attempt += 1) {
|
|
68
|
+
try {
|
|
69
|
+
raw = await readFile(runPath(runId), "utf8");
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error.code === "ENOENT")
|
|
73
|
+
return null;
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (raw === undefined)
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const value = JSON.parse(raw);
|
|
81
|
+
return value.version === 1 && value.runId === runId ? value : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function listRuns() {
|
|
88
|
+
let names;
|
|
89
|
+
try {
|
|
90
|
+
names = await readdir(runsDirectory());
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const records = await Promise.all(names.filter((name) => name.endsWith(".json")).map((name) => readRun(name.slice(0, -5))));
|
|
96
|
+
return records.filter((record) => record !== null).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
97
|
+
}
|
|
98
|
+
export async function pruneRuns(now = Date.now()) {
|
|
99
|
+
for (const record of await listRuns()) {
|
|
100
|
+
if (record.status !== "running" && now - Date.parse(record.updatedAt) > RUN_RETENTION_MS)
|
|
101
|
+
await rm(runPath(record.runId), { force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function processAlive(pid) {
|
|
105
|
+
if (pid === undefined)
|
|
106
|
+
return false;
|
|
107
|
+
try {
|
|
108
|
+
process.kill(pid, 0);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return error.code === "EPERM";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** `running` only counts while its worker is alive; otherwise the run was cut off (killed, rebooted) and is `interrupted`. */
|
|
116
|
+
export function effectiveStatus(record, now = Date.now()) {
|
|
117
|
+
if (record.status !== "running")
|
|
118
|
+
return record.status;
|
|
119
|
+
// The worker records its pid as its first act; until then the run is only just starting.
|
|
120
|
+
if (record.workerPid === undefined)
|
|
121
|
+
return now - Date.parse(record.updatedAt) < WORKER_START_GRACE_MS ? "running" : "interrupted";
|
|
122
|
+
return processAlive(record.workerPid) ? "running" : "interrupted";
|
|
123
|
+
}
|
|
124
|
+
const WORKER_START_GRACE_MS = 20_000;
|
|
125
|
+
/** A run can continue when a failed or interrupted run still has its Agent and (for `--read`) rounds to resume from. */
|
|
126
|
+
export function resumable(record) {
|
|
127
|
+
const status = effectiveStatus(record);
|
|
128
|
+
return (status === "interrupted" || status === "failed") && (record.error?.code ?? "") !== "input_invalid";
|
|
129
|
+
}
|
|
130
|
+
/** `<id>` or `--last`. */
|
|
131
|
+
export async function resolveRun(target) {
|
|
132
|
+
if (target === undefined || target === "--last") {
|
|
133
|
+
const [latest] = await listRuns();
|
|
134
|
+
if (latest === undefined)
|
|
135
|
+
throw new Error("There are no agent runs on this computer.");
|
|
136
|
+
return latest;
|
|
137
|
+
}
|
|
138
|
+
if (!isRunId(target))
|
|
139
|
+
throw new Error("Use a run id from `frely agent runs`, or --last.");
|
|
140
|
+
const record = await readRun(target);
|
|
141
|
+
if (record === null)
|
|
142
|
+
throw new Error("This run is not on this computer (runs are removed after 7 days).");
|
|
143
|
+
return record;
|
|
144
|
+
}
|
|
145
|
+
export function addUsage(total, usage) {
|
|
146
|
+
if (usage === undefined)
|
|
147
|
+
return total;
|
|
148
|
+
return {
|
|
149
|
+
input_tokens: total.input_tokens + usage.input_tokens,
|
|
150
|
+
output_tokens: total.output_tokens + usage.output_tokens,
|
|
151
|
+
total_tokens: total.total_tokens + usage.total_tokens,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=run-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../src/agent/run-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SkillAccessError, type InstalledAgentSession } from "../skill/access.js";
|
|
2
|
+
import { type RunRecord } from "./run-store.js";
|
|
3
|
+
import { type ReadLimits } from "./workspace-read.js";
|
|
4
|
+
export interface RunDeps {
|
|
5
|
+
openSession(distributionId: string): Promise<InstalledAgentSession>;
|
|
6
|
+
invokePlain(distributionId: string, task: string, signal: AbortSignal): Promise<{
|
|
7
|
+
text: string;
|
|
8
|
+
modelId: string;
|
|
9
|
+
usage?: Readonly<Record<string, unknown>>;
|
|
10
|
+
}>;
|
|
11
|
+
/** Start the detached worker for a run. */
|
|
12
|
+
spawnWorker(runId: string): void;
|
|
13
|
+
stderr(text: string): void;
|
|
14
|
+
pollMs: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const defaultRunDeps: RunDeps;
|
|
17
|
+
/** Create the run record, start the detached worker and wait for the end. The same call also serves `frely agent result --wait`. */
|
|
18
|
+
export declare function startRun(input: {
|
|
19
|
+
readonly distributionId: string;
|
|
20
|
+
readonly task: string;
|
|
21
|
+
readonly read?: {
|
|
22
|
+
readonly root: string;
|
|
23
|
+
readonly limits: ReadLimits;
|
|
24
|
+
};
|
|
25
|
+
}, deps?: RunDeps): Promise<RunRecord>;
|
|
26
|
+
/** Poll the local record until the run is no longer running; prints each newly read file to stderr. */
|
|
27
|
+
export declare function waitForRun(runId: string, deps?: Pick<RunDeps, "pollMs" | "stderr">): Promise<RunRecord>;
|
|
28
|
+
/** A finished run as the foreground command reports it: the result, or the error the call would have thrown. */
|
|
29
|
+
export declare function runOutcome(record: RunRecord): Record<string, unknown>;
|
|
30
|
+
export declare function runFailure(record: RunRecord): SkillAccessError;
|
|
31
|
+
/** The state `frely agent result` and `frely agent runs` print for a run (no task text; the result text only when finished). */
|
|
32
|
+
export declare function runSummary(record: RunRecord): Record<string, unknown>;
|
|
33
|
+
export declare function listRunSummaries(): Promise<Record<string, unknown>[]>;
|
|
34
|
+
/** Stop a run: ask its worker to abort the call (the server stops and settles what was used), or mark an orphaned record. */
|
|
35
|
+
export declare function cancelRun(target: string | undefined, deps?: Pick<RunDeps, "pollMs">): Promise<RunRecord>;
|
|
36
|
+
/** Prepare an interrupted or failed run to continue with the items it already has, and start a worker for it. */
|
|
37
|
+
export declare function resumeRun(target: string | undefined, deps?: RunDeps): Promise<RunRecord>;
|
|
38
|
+
/** The worker side: runs the call (and the tool rounds) of one record, saving after every step. */
|
|
39
|
+
export declare function executeRun(runId: string, deps?: RunDeps, signal?: AbortSignal): Promise<void>;
|
|
40
|
+
/** Entry of `frely agent __worker <runId>`: cancel on SIGTERM, ignore the terminal hang-up. */
|
|
41
|
+
export declare function workerMain(runId: string, deps?: RunDeps): Promise<void>;
|