frely-cli 0.23.10 → 0.23.12

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.
Files changed (55) hide show
  1. package/dist/agent/local/approvals.js +14 -18
  2. package/dist/agent/local/approvals.js.map +1 -1
  3. package/dist/agent/run-store.d.ts +85 -0
  4. package/dist/agent/run-store.js +155 -0
  5. package/dist/agent/run-store.js.map +1 -0
  6. package/dist/agent/run.d.ts +45 -0
  7. package/dist/agent/run.js +254 -0
  8. package/dist/agent/run.js.map +1 -0
  9. package/dist/agent/workspace-read.d.ts +78 -0
  10. package/dist/agent/workspace-read.js +292 -0
  11. package/dist/agent/workspace-read.js.map +1 -0
  12. package/dist/agent-help.d.ts +44 -8
  13. package/dist/agent-help.js +9 -3
  14. package/dist/agent-help.js.map +1 -1
  15. package/dist/approval-code.d.ts +4 -0
  16. package/dist/approval-code.js +15 -0
  17. package/dist/approval-code.js.map +1 -0
  18. package/dist/cloud.d.ts +8 -51
  19. package/dist/cloud.js +33 -284
  20. package/dist/cloud.js.map +1 -1
  21. package/dist/controller/approval-wait.d.ts +21 -0
  22. package/dist/controller/approval-wait.js +28 -0
  23. package/dist/controller/approval-wait.js.map +1 -0
  24. package/dist/controller/client.d.ts +6 -3
  25. package/dist/controller/client.js +7 -8
  26. package/dist/controller/client.js.map +1 -1
  27. package/dist/controller/commands.d.ts +3 -6
  28. package/dist/controller/commands.js +30 -31
  29. package/dist/controller/commands.js.map +1 -1
  30. package/dist/controller/session.d.ts +7 -2
  31. package/dist/controller/session.js +79 -22
  32. package/dist/controller/session.js.map +1 -1
  33. package/dist/index.js +83 -16
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp-authorization.d.ts +3 -3
  36. package/dist/mcp-authorization.js +13 -15
  37. package/dist/mcp-authorization.js.map +1 -1
  38. package/dist/mcp-command.js +3 -2
  39. package/dist/mcp-command.js.map +1 -1
  40. package/dist/share/control.d.ts +10 -1
  41. package/dist/share/control.js +45 -2
  42. package/dist/share/control.js.map +1 -1
  43. package/dist/share/run.d.ts +0 -5
  44. package/dist/share/run.js +3 -14
  45. package/dist/share/run.js.map +1 -1
  46. package/dist/skill/access.d.ts +28 -4
  47. package/dist/skill/access.js +78 -17
  48. package/dist/skill/access.js.map +1 -1
  49. package/dist/skill/entry.d.ts +1 -1
  50. package/dist/skill/entry.js +1 -1
  51. package/dist/skill/router.d.ts +76 -0
  52. package/dist/skill/router.js +99 -42
  53. package/dist/skill/router.js.map +1 -1
  54. package/package.json +1 -1
  55. package/skills/frely-cli/SKILL.md +2 -2
@@ -1,8 +1,8 @@
1
1
  import { ensureDevice } from "../../device/control.js";
2
2
  import { openController, resultData, resultText } from "../../controller/client.js";
3
+ import { waitForApproval } from "../../controller/approval-wait.js";
3
4
  import { ControllerError, EXIT } from "../../controller/session.js";
4
5
  import { approvedHosts } from "./store.js";
5
- const sleepFor = (ms) => new Promise((done) => setTimeout(done, ms));
6
6
  /** When the relay does not say, an approval is taken to last as long as its default window. */
7
7
  const DEFAULT_WINDOW_MS = 12 * 3_600_000;
8
8
  /** The FrelyMCP resource name for one site. The relay shows it to the owner as "connect to <site>". */
@@ -35,7 +35,6 @@ export async function approveSites(record, io, deps = {}) {
35
35
  }
36
36
  async function approveSite(controller, device, record, host, io, deps) {
37
37
  const now = deps.now ?? Date.now;
38
- const sleep = deps.sleep ?? sleepFor;
39
38
  const result = await controller.call("request_permission", {
40
39
  device, resource: siteResource(host), mode: "window",
41
40
  reason: `The local Agent ${record.slug} lists ${host} in its frely.yaml.`.slice(0, 300),
@@ -58,22 +57,19 @@ async function approveSite(controller, device, record, host, io, deps) {
58
57
  throw new ControllerError(`Approval needed for ${host}. Open the link above, approve it, then run the same command again.`, EXIT.approval);
59
58
  }
60
59
  io.err("Waiting for your decision…\n");
61
- const deadline = now() + (deps.waitMs ?? 10 * 60_000);
62
- for (;;) {
63
- const answer = resultData(await controller.call("permission_status", { code }));
64
- if (answer?.status === "approved")
65
- return typeof answer.expiresAt === "string" ? answer.expiresAt : new Date(now() + DEFAULT_WINDOW_MS).toISOString();
66
- if (answer?.status === "denied") {
67
- io.err(`${host} was declined; ${record.slug} will run without it.\n`);
68
- return undefined;
69
- }
70
- if (answer?.status === "expired" || answer?.status === "revoked") {
71
- io.err(`The request for ${host} ended without an answer; ${record.slug} will run without it.\n`);
72
- return undefined;
73
- }
74
- if (now() + (deps.pollMs ?? 3000) >= deadline)
75
- throw new ControllerError(`Still waiting for approval of ${host}. Run the same command again to keep waiting.`, EXIT.approval);
76
- await sleep(deps.pollMs ?? 3000);
60
+ const outcome = await waitForApproval(controller, { code }, deps.waitMs ?? 10 * 60_000, { now, ...(deps.sleep ? { sleep: deps.sleep } : {}), ...(deps.pollMs !== undefined ? { intervalMs: deps.pollMs } : {}) });
61
+ if (outcome.status === "approved")
62
+ return typeof outcome.data?.expiresAt === "string" ? outcome.data.expiresAt : new Date(now() + DEFAULT_WINDOW_MS).toISOString();
63
+ if (outcome.status === "denied") {
64
+ io.err(`${host} was declined; ${record.slug} will run without it.\n`);
65
+ return undefined;
66
+ }
67
+ if (outcome.status === "expired" || outcome.status === "revoked") {
68
+ io.err(`The request for ${host} ended without an answer; ${record.slug} will run without it.\n`);
69
+ return undefined;
77
70
  }
71
+ if (outcome.status === "error")
72
+ throw new ControllerError(`Could not check the approval of ${host}: ${outcome.message}`, EXIT.tool);
73
+ throw new ControllerError(`Still waiting for approval of ${host}. Run the same command again to keep waiting.`, EXIT.approval);
78
74
  }
79
75
  //# sourceMappingURL=approvals.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../../../src/agent/local/approvals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../../../src/agent/local/approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
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
+ /** `--progress-summary` / `--no-progress-summary`; absent means the account's setting. */
51
+ readonly progressSummary?: boolean;
52
+ /** Newest progress line of a running plain call. */
53
+ readonly progress?: string;
54
+ /** The Agent could not take local tools, so the run was done as one plain call. */
55
+ readonly toolsUnsupported?: boolean;
56
+ readonly error?: Readonly<{
57
+ code: string;
58
+ message: string;
59
+ }>;
60
+ }
61
+ export declare function runsDirectory(): string;
62
+ export declare function isRunId(value: string): boolean;
63
+ export declare function createRun(input: {
64
+ readonly distributionId: string;
65
+ readonly task: string;
66
+ readonly readRoot?: string;
67
+ readonly limits?: {
68
+ totalBytes: number;
69
+ maxRounds: number;
70
+ };
71
+ readonly progressSummary?: boolean;
72
+ }): Promise<RunRecord>;
73
+ /** Atomic write: a reader never sees half a record. */
74
+ export declare function writeRun(record: RunRecord): Promise<RunRecord>;
75
+ export declare function readRun(runId: string): Promise<RunRecord | null>;
76
+ export declare function listRuns(): Promise<RunRecord[]>;
77
+ export declare function pruneRuns(now?: number): Promise<void>;
78
+ export declare function processAlive(pid: number | undefined): boolean;
79
+ /** `running` only counts while its worker is alive; otherwise the run was cut off (killed, rebooted) and is `interrupted`. */
80
+ export declare function effectiveStatus(record: RunRecord, now?: number): RunStatus;
81
+ /** A run can continue when a failed or interrupted run still has its Agent and (for `--read`) rounds to resume from. */
82
+ export declare function resumable(record: RunRecord): boolean;
83
+ /** `<id>` or `--last`. */
84
+ export declare function resolveRun(target: string | undefined): Promise<RunRecord>;
85
+ export declare function addUsage(total: RunRecord["usage"], usage: RunRecord["usage"] | undefined): RunRecord["usage"];
@@ -0,0 +1,155 @@
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
+ ...(input.progressSummary === undefined ? {} : { progressSummary: input.progressSummary }),
35
+ items: [],
36
+ rounds: [],
37
+ filesRead: [],
38
+ usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
39
+ resumeCount: 0,
40
+ };
41
+ await writeRun(record);
42
+ return record;
43
+ }
44
+ /** Atomic write: a reader never sees half a record. */
45
+ export async function writeRun(record) {
46
+ const next = { ...record, updatedAt: new Date().toISOString() };
47
+ const path = runPath(record.runId);
48
+ const temp = `${path}.${process.pid}.tmp`;
49
+ await writeFile(temp, `${JSON.stringify(next)}\n`, { mode: 0o600 });
50
+ // On Windows a rename onto a file another process is reading fails briefly (EPERM/EBUSY/EACCES): retry.
51
+ for (let attempt = 0;; attempt += 1) {
52
+ try {
53
+ await rename(temp, path);
54
+ return next;
55
+ }
56
+ catch (error) {
57
+ const code = error.code;
58
+ if (attempt >= 50 || (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES")) {
59
+ await rm(temp, { force: true });
60
+ throw error;
61
+ }
62
+ await new Promise((resolve) => setTimeout(resolve, 20));
63
+ }
64
+ }
65
+ }
66
+ export async function readRun(runId) {
67
+ let raw;
68
+ for (let attempt = 0; attempt < 5 && raw === undefined; attempt += 1) {
69
+ try {
70
+ raw = await readFile(runPath(runId), "utf8");
71
+ }
72
+ catch (error) {
73
+ if (error.code === "ENOENT")
74
+ return null;
75
+ await new Promise((resolve) => setTimeout(resolve, 20));
76
+ }
77
+ }
78
+ if (raw === undefined)
79
+ return null;
80
+ try {
81
+ const value = JSON.parse(raw);
82
+ return value.version === 1 && value.runId === runId ? value : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ export async function listRuns() {
89
+ let names;
90
+ try {
91
+ names = await readdir(runsDirectory());
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ const records = await Promise.all(names.filter((name) => name.endsWith(".json")).map((name) => readRun(name.slice(0, -5))));
97
+ return records.filter((record) => record !== null).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
98
+ }
99
+ export async function pruneRuns(now = Date.now()) {
100
+ for (const record of await listRuns()) {
101
+ if (record.status !== "running" && now - Date.parse(record.updatedAt) > RUN_RETENTION_MS)
102
+ await rm(runPath(record.runId), { force: true });
103
+ }
104
+ }
105
+ export function processAlive(pid) {
106
+ if (pid === undefined)
107
+ return false;
108
+ try {
109
+ process.kill(pid, 0);
110
+ return true;
111
+ }
112
+ catch (error) {
113
+ return error.code === "EPERM";
114
+ }
115
+ }
116
+ /** `running` only counts while its worker is alive; otherwise the run was cut off (killed, rebooted) and is `interrupted`. */
117
+ export function effectiveStatus(record, now = Date.now()) {
118
+ if (record.status !== "running")
119
+ return record.status;
120
+ // The worker records its pid as its first act; until then the run is only just starting.
121
+ if (record.workerPid === undefined)
122
+ return now - Date.parse(record.updatedAt) < WORKER_START_GRACE_MS ? "running" : "interrupted";
123
+ return processAlive(record.workerPid) ? "running" : "interrupted";
124
+ }
125
+ const WORKER_START_GRACE_MS = 20_000;
126
+ /** A run can continue when a failed or interrupted run still has its Agent and (for `--read`) rounds to resume from. */
127
+ export function resumable(record) {
128
+ const status = effectiveStatus(record);
129
+ return (status === "interrupted" || status === "failed") && (record.error?.code ?? "") !== "input_invalid";
130
+ }
131
+ /** `<id>` or `--last`. */
132
+ export async function resolveRun(target) {
133
+ if (target === undefined || target === "--last") {
134
+ const [latest] = await listRuns();
135
+ if (latest === undefined)
136
+ throw new Error("There are no agent runs on this computer.");
137
+ return latest;
138
+ }
139
+ if (!isRunId(target))
140
+ throw new Error("Use a run id from `frely agent runs`, or --last.");
141
+ const record = await readRun(target);
142
+ if (record === null)
143
+ throw new Error("This run is not on this computer (runs are removed after 7 days).");
144
+ return record;
145
+ }
146
+ export function addUsage(total, usage) {
147
+ if (usage === undefined)
148
+ return total;
149
+ return {
150
+ input_tokens: total.input_tokens + usage.input_tokens,
151
+ output_tokens: total.output_tokens + usage.output_tokens,
152
+ total_tokens: total.total_tokens + usage.total_tokens,
153
+ };
154
+ }
155
+ //# 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,45 @@
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, options?: {
7
+ progressSummary?: boolean;
8
+ onProgress?: (sentence: string) => void;
9
+ }): Promise<{
10
+ text: string;
11
+ modelId: string;
12
+ usage?: Readonly<Record<string, unknown>>;
13
+ }>;
14
+ /** Start the detached worker for a run. */
15
+ spawnWorker(runId: string): void;
16
+ stderr(text: string): void;
17
+ pollMs: number;
18
+ }
19
+ export declare const defaultRunDeps: RunDeps;
20
+ /** Create the run record, start the detached worker and wait for the end. The same call also serves `frely agent result --wait`. */
21
+ export declare function startRun(input: {
22
+ readonly distributionId: string;
23
+ readonly task: string;
24
+ readonly read?: {
25
+ readonly root: string;
26
+ readonly limits: ReadLimits;
27
+ };
28
+ readonly progressSummary?: boolean;
29
+ }, deps?: RunDeps): Promise<RunRecord>;
30
+ /** Poll the local record until the run is no longer running; prints each newly read file to stderr. */
31
+ export declare function waitForRun(runId: string, deps?: Pick<RunDeps, "pollMs" | "stderr">): Promise<RunRecord>;
32
+ /** A finished run as the foreground command reports it: the result, or the error the call would have thrown. */
33
+ export declare function runOutcome(record: RunRecord): Record<string, unknown>;
34
+ export declare function runFailure(record: RunRecord): SkillAccessError;
35
+ /** The state `frely agent result` and `frely agent runs` print for a run (no task text; the result text only when finished). */
36
+ export declare function runSummary(record: RunRecord): Record<string, unknown>;
37
+ export declare function listRunSummaries(): Promise<Record<string, unknown>[]>;
38
+ /** Stop a run: ask its worker to abort the call (the server stops and settles what was used), or mark an orphaned record. */
39
+ export declare function cancelRun(target: string | undefined, deps?: Pick<RunDeps, "pollMs">): Promise<RunRecord>;
40
+ /** Prepare an interrupted or failed run to continue with the items it already has, and start a worker for it. */
41
+ export declare function resumeRun(target: string | undefined, deps?: RunDeps): Promise<RunRecord>;
42
+ /** The worker side: runs the call (and the tool rounds) of one record, saving after every step. */
43
+ export declare function executeRun(runId: string, deps?: RunDeps, signal?: AbortSignal): Promise<void>;
44
+ /** Entry of `frely agent __worker <runId>`: cancel on SIGTERM, ignore the terminal hang-up. */
45
+ export declare function workerMain(runId: string, deps?: RunDeps): Promise<void>;
@@ -0,0 +1,254 @@
1
+ import { spawn } from "node:child_process";
2
+ import { cliLaunchArguments } from "../cli-launch.js";
3
+ import { ClientToolsUnsupportedError, invokeInstalledAgent, openInstalledAgentSession, SkillAccessError, } from "../skill/access.js";
4
+ import { addUsage, createRun, effectiveStatus, listRuns, processAlive, readRun, resolveRun, resumable, writeRun, } from "./run-store.js";
5
+ import { READ_TOOLS, WorkspaceReadBridge } from "./workspace-read.js";
6
+ const MAX_TASK_BYTES = 128 * 1024;
7
+ const OVERVIEW_HEADER = "\n\n---\nLocal workspace (read-only file tools are available: read what you need, do not guess). Readable files:\n";
8
+ export const defaultRunDeps = {
9
+ openSession: (distributionId) => openInstalledAgentSession({ distributionId }),
10
+ invokePlain: async (distributionId, task, signal, options) => {
11
+ const result = await invokeInstalledAgent({
12
+ distributionId,
13
+ task,
14
+ signal,
15
+ ...(options?.progressSummary === undefined ? {} : { progressSummary: options.progressSummary }),
16
+ ...(options?.onProgress === undefined ? {} : { onProgress: options.onProgress }),
17
+ });
18
+ return { text: result.text, modelId: result.modelId, ...("usage" in result && result.usage ? { usage: result.usage } : {}) };
19
+ },
20
+ spawnWorker: (runId) => {
21
+ // Detached, so Ctrl-C, a host command timeout or a killed parent does not stop the call it is waiting for.
22
+ const launch = cliLaunchArguments(process.argv[1] ?? "", ["agent", "__worker", runId]);
23
+ spawn(launch[0], launch.slice(1), { detached: true, stdio: "ignore", windowsHide: true, env: process.env }).unref();
24
+ },
25
+ stderr: (text) => { process.stderr.write(text); },
26
+ pollMs: 400,
27
+ };
28
+ /** Create the run record, start the detached worker and wait for the end. The same call also serves `frely agent result --wait`. */
29
+ export async function startRun(input, deps = defaultRunDeps) {
30
+ if (!input.task.trim() || Buffer.byteLength(input.task, "utf8") > MAX_TASK_BYTES)
31
+ throw new SkillAccessError("input_invalid", "Agent input must be non-empty and at most 128 KiB.");
32
+ const record = await createRun({
33
+ distributionId: input.distributionId,
34
+ task: input.task,
35
+ ...(input.read === undefined ? {} : { readRoot: input.read.root, limits: input.read.limits }),
36
+ ...(input.progressSummary === undefined ? {} : { progressSummary: input.progressSummary }),
37
+ });
38
+ deps.stderr(`Run ${record.runId} started. If this command is cut off the run keeps going: frely agent result ${record.runId} --wait\n`);
39
+ deps.spawnWorker(record.runId);
40
+ return waitForRun(record.runId, deps);
41
+ }
42
+ /** Poll the local record until the run is no longer running; prints each newly read file to stderr. */
43
+ export async function waitForRun(runId, deps = defaultRunDeps) {
44
+ const seen = new Set();
45
+ while (true) {
46
+ const record = await readRun(runId);
47
+ if (record === null)
48
+ throw new SkillAccessError("remote_call_failed", "The run record is no longer on this computer.");
49
+ for (const file of record.filesRead) {
50
+ if (!seen.has(file.path)) {
51
+ seen.add(file.path);
52
+ deps.stderr(`read ${file.path}\n`);
53
+ }
54
+ }
55
+ if (effectiveStatus(record) !== "running")
56
+ return { ...record, status: effectiveStatus(record) };
57
+ await new Promise((resolve) => setTimeout(resolve, deps.pollMs));
58
+ }
59
+ }
60
+ /** A finished run as the foreground command reports it: the result, or the error the call would have thrown. */
61
+ export function runOutcome(record) {
62
+ if (record.status === "completed") {
63
+ return {
64
+ ok: true,
65
+ runId: record.runId,
66
+ distributionId: record.distributionId,
67
+ ...(record.modelId === undefined ? {} : { modelId: record.modelId }),
68
+ text: record.text ?? "",
69
+ ...(record.readRoot === undefined ? {} : { filesRead: record.filesRead, rounds: record.rounds.length }),
70
+ ...(record.usage.total_tokens > 0 ? { usage: record.usage } : {}),
71
+ ...(record.toolsUnsupported === true ? { toolsUnsupported: true } : {}),
72
+ };
73
+ }
74
+ throw runFailure(record);
75
+ }
76
+ export function runFailure(record) {
77
+ if (record.status === "interrupted") {
78
+ return new SkillAccessError("remote_call_failed", `The run was interrupted before it finished. Run \`frely agent result ${record.runId} --resume --wait\` to continue it; the interrupted round is billed again.`);
79
+ }
80
+ if (record.status === "cancelled")
81
+ return new SkillAccessError("remote_call_failed", `The run ${record.runId} was cancelled.`);
82
+ const code = (record.error?.code ?? "remote_call_failed");
83
+ return new SkillAccessError(code, record.error?.message ?? "The remote Frely Agent call failed.");
84
+ }
85
+ /** The state `frely agent result` and `frely agent runs` print for a run (no task text; the result text only when finished). */
86
+ export function runSummary(record) {
87
+ const status = effectiveStatus(record);
88
+ return {
89
+ ok: status === "running" || status === "completed",
90
+ runId: record.runId,
91
+ status,
92
+ distributionId: record.distributionId,
93
+ createdAt: record.createdAt,
94
+ updatedAt: record.updatedAt,
95
+ rounds: record.rounds.length,
96
+ filesRead: record.filesRead,
97
+ usage: record.usage,
98
+ ...(status === "completed" ? { text: record.text ?? "" } : {}),
99
+ ...(status === "running" && record.progress !== undefined ? { progress: record.progress } : {}),
100
+ ...(status === "completed" || record.error === undefined ? {} : { error: record.error }),
101
+ resumable: resumable(record),
102
+ ...(status === "running" ? { note: "Still running. Progress so far is listed; run `frely agent result <runId> --wait` to wait for the answer." } : {}),
103
+ };
104
+ }
105
+ export async function listRunSummaries() {
106
+ return (await listRuns()).slice(0, 20).map((record) => {
107
+ const { filesRead, usage, ...rest } = runSummary(record);
108
+ return { ...rest, filesRead: record.filesRead.length, totalTokens: usage.total_tokens };
109
+ });
110
+ }
111
+ /** Stop a run: ask its worker to abort the call (the server stops and settles what was used), or mark an orphaned record. */
112
+ export async function cancelRun(target, deps = defaultRunDeps) {
113
+ const record = await resolveRun(target);
114
+ if (record.status !== "running")
115
+ return record;
116
+ if (processAlive(record.workerPid)) {
117
+ try {
118
+ process.kill(record.workerPid, "SIGTERM");
119
+ }
120
+ catch { /* it just ended */ }
121
+ for (let waited = 0; waited < 5000; waited += deps.pollMs) {
122
+ await new Promise((resolve) => setTimeout(resolve, deps.pollMs));
123
+ const current = await readRun(record.runId);
124
+ if (current !== null && current.status !== "running")
125
+ return current;
126
+ }
127
+ }
128
+ const latest = await readRun(record.runId) ?? record;
129
+ if (latest.status !== "running")
130
+ return latest;
131
+ return writeRun({ ...latest, status: "cancelled" });
132
+ }
133
+ /** Prepare an interrupted or failed run to continue with the items it already has, and start a worker for it. */
134
+ export async function resumeRun(target, deps = defaultRunDeps) {
135
+ const record = await resolveRun(target);
136
+ if (!resumable(record))
137
+ throw new Error(effectiveStatus(record) === "running" ? "This run is still running." : "This run cannot be resumed.");
138
+ const { error: _error, workerPid: _pid, ...rest } = record;
139
+ const next = await writeRun({ ...rest, status: "running", resumeCount: record.resumeCount + 1 });
140
+ deps.stderr(`Resuming run ${record.runId}. The round that was interrupted is billed again.\n`);
141
+ deps.spawnWorker(record.runId);
142
+ return next;
143
+ }
144
+ /** The worker side: runs the call (and the tool rounds) of one record, saving after every step. */
145
+ export async function executeRun(runId, deps = defaultRunDeps, signal = new AbortController().signal) {
146
+ const loaded = await readRun(runId);
147
+ if (loaded === null || loaded.status !== "running")
148
+ return;
149
+ let record = await writeRun({ ...loaded, workerPid: process.pid });
150
+ const save = async (patch) => { record = await writeRun({ ...record, ...patch }); };
151
+ try {
152
+ if (record.readRoot === undefined) {
153
+ let lastProgress;
154
+ let pending = Promise.resolve();
155
+ const result = await deps.invokePlain(record.distributionId, record.task, signal, {
156
+ ...(record.progressSummary === undefined ? {} : { progressSummary: record.progressSummary }),
157
+ onProgress: (sentence) => {
158
+ if (sentence === lastProgress)
159
+ return;
160
+ lastProgress = sentence;
161
+ // Saved one after another, and before the final write below, so a late line cannot undo the result.
162
+ pending = pending.then(() => save({ progress: sentence })).catch(() => undefined);
163
+ },
164
+ });
165
+ await pending;
166
+ const { progress: _progress, ...withoutProgress } = record;
167
+ record = await writeRun({ ...withoutProgress, status: "completed", modelId: result.modelId, text: result.text });
168
+ return;
169
+ }
170
+ await runWithTools(record, save, () => record, deps, signal);
171
+ }
172
+ catch (error) {
173
+ if (signal.aborted) {
174
+ await save({ status: "cancelled" });
175
+ return;
176
+ }
177
+ const code = error instanceof SkillAccessError ? error.code : "remote_call_failed";
178
+ const message = error instanceof SkillAccessError ? error.message : "The remote Frely Agent call failed.";
179
+ await save({ status: "failed", error: { code, message } });
180
+ }
181
+ }
182
+ async function runWithTools(start, save, current, deps, signal) {
183
+ const limits = start.limits;
184
+ if (limits === undefined || start.readRoot === undefined)
185
+ throw new SkillAccessError("input_invalid", "The run has no workspace.");
186
+ const bridge = await WorkspaceReadBridge.open(start.readRoot, limits);
187
+ bridge.restore({ usedBytes: start.bridge?.usedBytes ?? 0, roundsAnswered: start.bridge?.roundsAnswered ?? 0, filesRead: start.filesRead });
188
+ const task = await withOverview(start.task, bridge);
189
+ const session = await deps.openSession(start.distributionId);
190
+ await save({ modelId: session.modelId });
191
+ const hardCap = limits.maxRounds + 3;
192
+ while (true) {
193
+ if (signal.aborted)
194
+ throw new Error("aborted");
195
+ const items = [...current().items];
196
+ const pending = unansweredCalls(items);
197
+ if (pending.length > 0) {
198
+ const outputs = await bridge.answerRound(pending);
199
+ for (const output of outputs)
200
+ items.push({ type: "function_call_output", call_id: output.callId, output: output.output });
201
+ await save({ items, filesRead: bridge.filesRead(), bridge: { usedBytes: bridge.state().usedBytes, roundsAnswered: bridge.state().roundsAnswered } });
202
+ }
203
+ if (current().rounds.length >= hardCap)
204
+ throw new SkillAccessError("remote_call_failed", "The Agent kept asking for files after the read budget was used up.");
205
+ const startedAt = new Date().toISOString();
206
+ let turn;
207
+ try {
208
+ turn = await session.turn({ task, tools: READ_TOOLS, items, signal });
209
+ }
210
+ catch (error) {
211
+ if (error instanceof ClientToolsUnsupportedError && current().rounds.length === 0) {
212
+ // The Agent takes no caller tools: do the plain single call, as if --read had not been given.
213
+ const result = await deps.invokePlain(start.distributionId, start.task, signal);
214
+ await save({ status: "completed", modelId: result.modelId, text: result.text, toolsUnsupported: true });
215
+ return;
216
+ }
217
+ throw error;
218
+ }
219
+ const round = {
220
+ n: current().rounds.length + 1,
221
+ ...(turn.requestId === undefined ? {} : { requestId: turn.requestId }),
222
+ startedAt,
223
+ endedAt: new Date().toISOString(),
224
+ toolCalls: turn.status === "tool_calls" ? turn.calls.map((call) => call.name) : [],
225
+ ...(turn.usage === undefined ? {} : { usage: turn.usage }),
226
+ };
227
+ const usage = addUsage(current().usage, turn.usage);
228
+ if (turn.status === "completed") {
229
+ await save({ status: "completed", text: turn.text, rounds: [...current().rounds, round], usage });
230
+ return;
231
+ }
232
+ await save({ rounds: [...current().rounds, round], usage, items: [...items, ...turn.items] });
233
+ }
234
+ }
235
+ function unansweredCalls(items) {
236
+ const answered = new Set(items.flatMap((item) => (item.type === "function_call_output" ? [item.call_id] : [])));
237
+ return items.flatMap((item) => (item.type === "function_call" && !answered.has(item.call_id) ? [{ callId: item.call_id, name: item.name, arguments: item.arguments }] : []));
238
+ }
239
+ /** Task plus the path tree of the readable files, kept inside the 128 KiB input limit. */
240
+ async function withOverview(task, bridge) {
241
+ const room = MAX_TASK_BYTES - Buffer.byteLength(task, "utf8") - Buffer.byteLength(OVERVIEW_HEADER, "utf8");
242
+ if (room < 256)
243
+ return task;
244
+ const overview = await bridge.overview(Math.min(8 * 1024, room));
245
+ return overview === "" ? task : `${task}${OVERVIEW_HEADER}${overview}`;
246
+ }
247
+ /** Entry of `frely agent __worker <runId>`: cancel on SIGTERM, ignore the terminal hang-up. */
248
+ export async function workerMain(runId, deps = defaultRunDeps) {
249
+ const controller = new AbortController();
250
+ process.on("SIGTERM", () => controller.abort());
251
+ process.on("SIGHUP", () => undefined);
252
+ await executeRun(runId, deps, controller.signal);
253
+ }
254
+ //# sourceMappingURL=run.js.map