agent-cli-runtime 0.1.0-alpha.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/CHANGELOG.md +51 -0
- package/CONTRIBUTING.md +60 -0
- package/LICENSE +202 -0
- package/README.md +573 -0
- package/README.zh-CN.md +571 -0
- package/SECURITY.md +35 -0
- package/dist/adapters/adapter-types.d.ts +138 -0
- package/dist/adapters/adapter-types.js +2 -0
- package/dist/adapters/adapter-types.js.map +1 -0
- package/dist/adapters/claude.d.ts +2 -0
- package/dist/adapters/claude.js +97 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +3 -0
- package/dist/adapters/codex.js +120 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/opencode.d.ts +4 -0
- package/dist/adapters/opencode.js +111 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/registry.d.ts +9 -0
- package/dist/adapters/registry.js +23 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +978 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/core/async-queue.d.ts +10 -0
- package/dist/core/async-queue.js +49 -0
- package/dist/core/async-queue.js.map +1 -0
- package/dist/core/diagnostics.d.ts +20 -0
- package/dist/core/diagnostics.js +4 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/event-contract.d.ts +32 -0
- package/dist/core/event-contract.js +128 -0
- package/dist/core/event-contract.js.map +1 -0
- package/dist/core/events.d.ts +147 -0
- package/dist/core/events.js +4 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/ids.d.ts +1 -0
- package/dist/core/ids.js +5 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/redaction.d.ts +4 -0
- package/dist/core/redaction.js +51 -0
- package/dist/core/redaction.js.map +1 -0
- package/dist/core/runtime.d.ts +41 -0
- package/dist/core/runtime.js +83 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/schema-contract.d.ts +55 -0
- package/dist/core/schema-contract.js +143 -0
- package/dist/core/schema-contract.js.map +1 -0
- package/dist/detection/detect.d.ts +14 -0
- package/dist/detection/detect.js +293 -0
- package/dist/detection/detect.js.map +1 -0
- package/dist/detection/env.d.ts +2 -0
- package/dist/detection/env.js +15 -0
- package/dist/detection/env.js.map +1 -0
- package/dist/detection/executable-resolution.d.ts +12 -0
- package/dist/detection/executable-resolution.js +50 -0
- package/dist/detection/executable-resolution.js.map +1 -0
- package/dist/detection/invocation.d.ts +9 -0
- package/dist/detection/invocation.js +22 -0
- package/dist/detection/invocation.js.map +1 -0
- package/dist/goals/goal-scheduler.d.ts +31 -0
- package/dist/goals/goal-scheduler.js +518 -0
- package/dist/goals/goal-scheduler.js.map +1 -0
- package/dist/goals/goal-store.d.ts +37 -0
- package/dist/goals/goal-store.js +300 -0
- package/dist/goals/goal-store.js.map +1 -0
- package/dist/goals/goal-types.d.ts +103 -0
- package/dist/goals/goal-types.js +2 -0
- package/dist/goals/goal-types.js.map +1 -0
- package/dist/goals/planner-prompts.d.ts +3 -0
- package/dist/goals/planner-prompts.js +26 -0
- package/dist/goals/planner-prompts.js.map +1 -0
- package/dist/goals/task-graph.d.ts +9 -0
- package/dist/goals/task-graph.js +229 -0
- package/dist/goals/task-graph.js.map +1 -0
- package/dist/goals/validation-runner.d.ts +7 -0
- package/dist/goals/validation-runner.js +63 -0
- package/dist/goals/validation-runner.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/claude-stream-json.d.ts +11 -0
- package/dist/parsers/claude-stream-json.js +102 -0
- package/dist/parsers/claude-stream-json.js.map +1 -0
- package/dist/parsers/codex-json.d.ts +8 -0
- package/dist/parsers/codex-json.js +107 -0
- package/dist/parsers/codex-json.js.map +1 -0
- package/dist/parsers/line-buffer.d.ts +7 -0
- package/dist/parsers/line-buffer.js +28 -0
- package/dist/parsers/line-buffer.js.map +1 -0
- package/dist/parsers/opencode-json.d.ts +8 -0
- package/dist/parsers/opencode-json.js +72 -0
- package/dist/parsers/opencode-json.js.map +1 -0
- package/dist/parsers/plain-lines.d.ts +6 -0
- package/dist/parsers/plain-lines.js +9 -0
- package/dist/parsers/plain-lines.js.map +1 -0
- package/dist/public-types.d.ts +143 -0
- package/dist/public-types.js +2 -0
- package/dist/public-types.js.map +1 -0
- package/dist/runs/process-runner.d.ts +35 -0
- package/dist/runs/process-runner.js +97 -0
- package/dist/runs/process-runner.js.map +1 -0
- package/dist/runs/prompt-transport.d.ts +10 -0
- package/dist/runs/prompt-transport.js +43 -0
- package/dist/runs/prompt-transport.js.map +1 -0
- package/dist/runs/run-result.d.ts +9 -0
- package/dist/runs/run-result.js +22 -0
- package/dist/runs/run-result.js.map +1 -0
- package/dist/runs/run-scheduler.d.ts +25 -0
- package/dist/runs/run-scheduler.js +552 -0
- package/dist/runs/run-scheduler.js.map +1 -0
- package/dist/runs/run-store.d.ts +42 -0
- package/dist/runs/run-store.js +297 -0
- package/dist/runs/run-store.js.map +1 -0
- package/dist/runs/run-types.d.ts +59 -0
- package/dist/runs/run-types.js +2 -0
- package/dist/runs/run-types.js.map +1 -0
- package/dist/smoke/parser-samples.d.ts +17 -0
- package/dist/smoke/parser-samples.js +186 -0
- package/dist/smoke/parser-samples.js.map +1 -0
- package/dist/storage/file-storage.d.ts +35 -0
- package/dist/storage/file-storage.js +271 -0
- package/dist/storage/file-storage.js.map +1 -0
- package/dist/storage/jsonl-store.d.ts +9 -0
- package/dist/storage/jsonl-store.js +138 -0
- package/dist/storage/jsonl-store.js.map +1 -0
- package/dist/storage/manifest-validation.d.ts +11 -0
- package/dist/storage/manifest-validation.js +102 -0
- package/dist/storage/manifest-validation.js.map +1 -0
- package/dist/storage/storage-lease.d.ts +40 -0
- package/dist/storage/storage-lease.js +223 -0
- package/dist/storage/storage-lease.js.map +1 -0
- package/dist/storage/storage-types.d.ts +55 -0
- package/dist/storage/storage-types.js +2 -0
- package/dist/storage/storage-types.js.map +1 -0
- package/dist/storage/store-inspection.d.ts +28 -0
- package/dist/storage/store-inspection.js +941 -0
- package/dist/storage/store-inspection.js.map +1 -0
- package/docs/api-schema-contract.md +92 -0
- package/docs/compatibility.md +832 -0
- package/docs/daemon-ready-contract.md +283 -0
- package/docs/production-readiness.md +281 -0
- package/docs/release-checklist.md +257 -0
- package/docs/release-publish-runbook.md +201 -0
- package/docs/release-report.md +517 -0
- package/docs/ssot.md +1257 -0
- package/examples/cli-dogfood.md +113 -0
- package/examples/library-goal.js +94 -0
- package/examples/library-run.js +84 -0
- package/package.json +79 -0
- package/scripts/dogfood.mjs +243 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { delimiter } from "node:path";
|
|
2
|
+
export function pathDirs(env, extraSearchPath = []) {
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
const dirs = [...extraSearchPath, ...(env.PATH ?? "").split(delimiter)].filter(Boolean);
|
|
5
|
+
return dirs.filter((dir) => {
|
|
6
|
+
if (seen.has(dir))
|
|
7
|
+
return false;
|
|
8
|
+
seen.add(dir);
|
|
9
|
+
return true;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function mergeEnv(...envs) {
|
|
13
|
+
return Object.assign({}, ...envs.filter(Boolean));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/detection/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,UAAU,QAAQ,CAAC,GAAsB,EAAE,kBAA4B,EAAE;IAC7E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,CAAC,GAAG,eAAe,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAG,IAA+E;IACzG,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentAdapterDef } from "../adapters/adapter-types.js";
|
|
2
|
+
export interface ExecutableResolutionOptions {
|
|
3
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
4
|
+
searchPath?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface ExecutableResolution {
|
|
7
|
+
configuredOverridePath: string | null;
|
|
8
|
+
pathResolvedPath: string | null;
|
|
9
|
+
selectedPath: string | null;
|
|
10
|
+
searchedLocations: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveExecutable(adapter: AgentAdapterDef, options?: ExecutableResolutionOptions): ExecutableResolution;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathDirs } from "./env.js";
|
|
4
|
+
export function resolveExecutable(adapter, options = {}) {
|
|
5
|
+
const env = options.env ?? process.env;
|
|
6
|
+
const configuredOverridePath = adapter.binEnvVar ? executablePath(env[adapter.binEnvVar]) : null;
|
|
7
|
+
const candidates = [adapter.bin, ...(adapter.fallbackBins ?? [])];
|
|
8
|
+
const searchedLocations = [];
|
|
9
|
+
let pathResolvedPath = null;
|
|
10
|
+
for (const dir of pathDirs(env, options.searchPath)) {
|
|
11
|
+
for (const candidate of candidates) {
|
|
12
|
+
for (const ext of executableExtensions()) {
|
|
13
|
+
const full = path.join(dir, `${candidate}${ext}`);
|
|
14
|
+
searchedLocations.push(full);
|
|
15
|
+
if (!pathResolvedPath && executablePath(full))
|
|
16
|
+
pathResolvedPath = full;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
configuredOverridePath,
|
|
22
|
+
pathResolvedPath,
|
|
23
|
+
selectedPath: configuredOverridePath ?? pathResolvedPath,
|
|
24
|
+
searchedLocations,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function executablePath(raw) {
|
|
28
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
29
|
+
return null;
|
|
30
|
+
const candidate = raw.trim();
|
|
31
|
+
if (!path.isAbsolute(candidate))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
if (!existsSync(candidate) || !statSync(candidate).isFile())
|
|
35
|
+
return null;
|
|
36
|
+
if (process.platform === "win32")
|
|
37
|
+
return candidate;
|
|
38
|
+
accessSync(candidate, constants.X_OK);
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function executableExtensions() {
|
|
46
|
+
if (process.platform !== "win32")
|
|
47
|
+
return [""];
|
|
48
|
+
return (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").map((ext) => ext.toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=executable-resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executable-resolution.js","sourceRoot":"","sources":["../../src/detection/executable-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAcpC,MAAM,UAAU,iBAAiB,CAAC,OAAwB,EAAE,UAAuC,EAAE;IACnG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,sBAAsB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjG,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,iBAAiB,GAAa,EAAE,CAAC;IACvC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,EAAE,CAAC;gBACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;gBAClD,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC7B,IAAI,CAAC,gBAAgB,IAAI,cAAc,CAAC,IAAI,CAAC;oBAAE,gBAAgB,GAAG,IAAI,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO;QACL,sBAAsB;QACtB,gBAAgB;QAChB,YAAY,EAAE,sBAAsB,IAAI,gBAAgB;QACxD,iBAAiB;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,GAAY;IAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE;YAAE,OAAO,IAAI,CAAC;QACzE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,SAAS,CAAC;QACnD,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB;IAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9F,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execFileP = promisify(execFile);
|
|
7
|
+
export async function execProbe(command, args, options = {}) {
|
|
8
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "agent-runtime-probe-"));
|
|
9
|
+
try {
|
|
10
|
+
const { stdout, stderr } = await execFileP(command, args, {
|
|
11
|
+
cwd,
|
|
12
|
+
env: options.env,
|
|
13
|
+
timeout: options.timeoutMs ?? 3_000,
|
|
14
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
15
|
+
});
|
|
16
|
+
return { stdout: String(stdout), stderr: String(stderr), cwd };
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
await rm(cwd, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=invocation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invocation.js","sourceRoot":"","sources":["../../src/detection/invocation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAQtC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,IAAc,EAAE,UAA2D,EAAE;IAC5H,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;YACxD,GAAG;YACH,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK;YACnC,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;SAC3B,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IACjE,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { GoalStore } from "./goal-store.js";
|
|
2
|
+
import type { CreateGoalRequest, GoalHandle } from "./goal-types.js";
|
|
3
|
+
import type { RunScheduler } from "../runs/run-scheduler.js";
|
|
4
|
+
export declare class GoalScheduler {
|
|
5
|
+
private readonly runScheduler;
|
|
6
|
+
private readonly store;
|
|
7
|
+
private readonly options;
|
|
8
|
+
private readonly currentRuns;
|
|
9
|
+
private readonly cancelRequested;
|
|
10
|
+
constructor(runScheduler: RunScheduler, store?: GoalStore, options?: {
|
|
11
|
+
maxConcurrentTasks?: number;
|
|
12
|
+
});
|
|
13
|
+
createGoal(request: CreateGoalRequest): Promise<GoalHandle>;
|
|
14
|
+
cancelGoal(goalId: string): Promise<void>;
|
|
15
|
+
shutdown(reason?: string, graceMs?: number): Promise<void>;
|
|
16
|
+
private execute;
|
|
17
|
+
private runReadyQueue;
|
|
18
|
+
private runTask;
|
|
19
|
+
private runTaskAttempt;
|
|
20
|
+
private runAndCollectText;
|
|
21
|
+
private cancelPending;
|
|
22
|
+
private cancelPendingExceptBlocked;
|
|
23
|
+
private cancelPendingFromStore;
|
|
24
|
+
private blockDependents;
|
|
25
|
+
private blockUnavailableDependents;
|
|
26
|
+
private setCurrentRun;
|
|
27
|
+
private clearCurrentRun;
|
|
28
|
+
private cancelCurrentRuns;
|
|
29
|
+
private updateAttemptEvidence;
|
|
30
|
+
private finish;
|
|
31
|
+
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { GoalStore } from "./goal-store.js";
|
|
2
|
+
import { createPlannerPrompt, createTaskPrompt } from "./planner-prompts.js";
|
|
3
|
+
import { parsePlannerOutput, TaskGraphError, validateTaskGraph } from "./task-graph.js";
|
|
4
|
+
import { runValidationCommands } from "./validation-runner.js";
|
|
5
|
+
import { diagnostic } from "../core/diagnostics.js";
|
|
6
|
+
export class GoalScheduler {
|
|
7
|
+
runScheduler;
|
|
8
|
+
store;
|
|
9
|
+
options;
|
|
10
|
+
currentRuns = new Map();
|
|
11
|
+
cancelRequested = new Set();
|
|
12
|
+
constructor(runScheduler, store = new GoalStore(), options = {}) {
|
|
13
|
+
this.runScheduler = runScheduler;
|
|
14
|
+
this.store = store;
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async createGoal(request) {
|
|
18
|
+
const goal = this.store.create(request);
|
|
19
|
+
const events = this.store.events(goal.id);
|
|
20
|
+
const handle = {
|
|
21
|
+
goalId: goal.id,
|
|
22
|
+
events,
|
|
23
|
+
cancel: async () => this.cancelGoal(goal.id),
|
|
24
|
+
};
|
|
25
|
+
void this.execute(goal.id, request).catch((error) => {
|
|
26
|
+
if (this.store.isTerminal(goal.id))
|
|
27
|
+
return;
|
|
28
|
+
if (this.cancelRequested.has(goal.id)) {
|
|
29
|
+
this.cancelPendingFromStore(goal.id);
|
|
30
|
+
this.finish(goal.id, { result: "cancelled", reason: "canceled", errorCode: "AGENT_CANCELLED" });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const code = error instanceof TaskGraphError ? error.code : "AGENT_EXECUTION_FAILED";
|
|
34
|
+
this.store.emit(goal.id, {
|
|
35
|
+
type: "scheduler_error",
|
|
36
|
+
code,
|
|
37
|
+
message: error instanceof Error ? error.message : String(error),
|
|
38
|
+
retryable: false,
|
|
39
|
+
});
|
|
40
|
+
this.finish(goal.id, { result: "failed", reason: terminalReasonFromErrorCode(code), errorCode: code });
|
|
41
|
+
});
|
|
42
|
+
return handle;
|
|
43
|
+
}
|
|
44
|
+
async cancelGoal(goalId) {
|
|
45
|
+
this.cancelRequested.add(goalId);
|
|
46
|
+
this.cancelPendingFromStore(goalId);
|
|
47
|
+
await this.cancelCurrentRuns(goalId);
|
|
48
|
+
}
|
|
49
|
+
async shutdown(reason = "Runtime shutdown", graceMs = 2_000) {
|
|
50
|
+
const goalIds = this.store.list({ status: "active" }).map((goal) => goal.id);
|
|
51
|
+
await Promise.all(goalIds.map((goalId) => this.cancelGoal(goalId)));
|
|
52
|
+
await Promise.race([
|
|
53
|
+
waitFor(() => goalIds.every((goalId) => this.store.isTerminal(goalId))),
|
|
54
|
+
delay(graceMs),
|
|
55
|
+
]);
|
|
56
|
+
for (const goalId of goalIds) {
|
|
57
|
+
if (this.store.isTerminal(goalId))
|
|
58
|
+
continue;
|
|
59
|
+
this.store.emit(goalId, {
|
|
60
|
+
type: "scheduler_error",
|
|
61
|
+
code: "AGENT_CANCELLED",
|
|
62
|
+
message: reason,
|
|
63
|
+
retryable: false,
|
|
64
|
+
});
|
|
65
|
+
this.cancelPendingFromStore(goalId);
|
|
66
|
+
this.finish(goalId, { result: "cancelled", reason: "canceled", errorCode: "AGENT_CANCELLED" });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async execute(goalId, request) {
|
|
70
|
+
this.store.emit(goalId, { type: "goal_started", goalId, objective: request.objective });
|
|
71
|
+
if (this.store.isTerminal(goalId))
|
|
72
|
+
return;
|
|
73
|
+
const plannerText = await this.runAndCollectText(goalId, undefined, {
|
|
74
|
+
agentId: request.plannerAgentId ?? request.defaultAgentId,
|
|
75
|
+
cwd: request.cwd,
|
|
76
|
+
prompt: createPlannerPrompt(request),
|
|
77
|
+
permissionPolicy: request.permissionPolicy,
|
|
78
|
+
model: request.model,
|
|
79
|
+
reasoning: request.reasoning,
|
|
80
|
+
contextBlocks: request.contextBlocks,
|
|
81
|
+
env: request.env,
|
|
82
|
+
timeoutMs: request.timeoutMs,
|
|
83
|
+
});
|
|
84
|
+
if (this.cancelRequested.has(goalId))
|
|
85
|
+
return this.finish(goalId, { result: "cancelled", reason: "canceled", errorCode: "AGENT_CANCELLED" });
|
|
86
|
+
const tasks = validateTaskGraph(parsePlannerOutput(plannerText), request);
|
|
87
|
+
this.store.setTasks(goalId, tasks);
|
|
88
|
+
for (const task of tasks)
|
|
89
|
+
this.store.emit(goalId, { type: "task_created", goalId, task });
|
|
90
|
+
if (this.store.isTerminal(goalId))
|
|
91
|
+
return;
|
|
92
|
+
this.store.setStatus(goalId, "running");
|
|
93
|
+
if (this.store.isTerminal(goalId))
|
|
94
|
+
return;
|
|
95
|
+
const outcome = await this.runReadyQueue(goalId, request, tasks);
|
|
96
|
+
this.finish(goalId, outcome);
|
|
97
|
+
}
|
|
98
|
+
async runReadyQueue(goalId, request, tasks) {
|
|
99
|
+
const maxConcurrentTasks = normalizeMaxConcurrentTasks(request.maxConcurrentTasks ?? this.options.maxConcurrentTasks);
|
|
100
|
+
const running = new Map();
|
|
101
|
+
let terminalOutcome = { result: "success", reason: "success" };
|
|
102
|
+
let stopScheduling = false;
|
|
103
|
+
const launchReady = () => {
|
|
104
|
+
while (!stopScheduling && !this.cancelRequested.has(goalId) && running.size < maxConcurrentTasks) {
|
|
105
|
+
const task = tasks.find((candidate) => candidate.status === "pending" && dependenciesSucceeded(candidate, tasks));
|
|
106
|
+
if (!task)
|
|
107
|
+
return;
|
|
108
|
+
task.status = "running";
|
|
109
|
+
this.store.updateTask(goalId, task);
|
|
110
|
+
const promise = this.runTask(goalId, request, task)
|
|
111
|
+
.then((outcome) => ({ task, outcome }));
|
|
112
|
+
running.set(task.id, promise);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
while (true) {
|
|
116
|
+
if (this.cancelRequested.has(goalId)) {
|
|
117
|
+
terminalOutcome = { result: "cancelled", reason: "canceled", errorCode: "AGENT_CANCELLED" };
|
|
118
|
+
stopScheduling = true;
|
|
119
|
+
this.cancelPending(goalId, tasks);
|
|
120
|
+
await this.cancelCurrentRuns(goalId);
|
|
121
|
+
}
|
|
122
|
+
launchReady();
|
|
123
|
+
if (running.size === 0) {
|
|
124
|
+
this.blockUnavailableDependents(goalId, tasks);
|
|
125
|
+
if (tasks.every((task) => task.status !== "pending" && task.status !== "running"))
|
|
126
|
+
break;
|
|
127
|
+
const hasReady = tasks.some((task) => task.status === "pending" && dependenciesSucceeded(task, tasks));
|
|
128
|
+
if (!hasReady) {
|
|
129
|
+
for (const task of tasks) {
|
|
130
|
+
if (task.status === "pending") {
|
|
131
|
+
task.status = "blocked";
|
|
132
|
+
this.store.updateTask(goalId, task);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (terminalOutcome.result === "success")
|
|
136
|
+
terminalOutcome = { result: "failed", reason: "failed" };
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (running.size === 0)
|
|
141
|
+
continue;
|
|
142
|
+
const settled = await Promise.race(running.values());
|
|
143
|
+
running.delete(settled.task.id);
|
|
144
|
+
if (settled.outcome.result === "failed")
|
|
145
|
+
terminalOutcome = settled.outcome;
|
|
146
|
+
if (settled.outcome.result === "cancelled" && terminalOutcome.result !== "failed")
|
|
147
|
+
terminalOutcome = settled.outcome;
|
|
148
|
+
if (settled.outcome.result !== "success" && !request.continueOnFailure) {
|
|
149
|
+
stopScheduling = true;
|
|
150
|
+
if (settled.outcome.result === "failed") {
|
|
151
|
+
this.blockDependents(goalId, tasks, settled.task.id);
|
|
152
|
+
this.cancelPendingExceptBlocked(goalId, tasks);
|
|
153
|
+
await this.cancelCurrentRuns(goalId);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.cancelPending(goalId, tasks);
|
|
157
|
+
await this.cancelCurrentRuns(goalId);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (settled.outcome.result !== "success") {
|
|
161
|
+
this.blockDependents(goalId, tasks, settled.task.id);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (terminalOutcome.result === "success" && tasks.some((task) => task.status === "failed" || task.status === "blocked")) {
|
|
165
|
+
return { result: "failed", reason: "failed" };
|
|
166
|
+
}
|
|
167
|
+
if (terminalOutcome.result === "success" && tasks.some((task) => task.status === "canceled")) {
|
|
168
|
+
return { result: "cancelled", reason: "canceled", errorCode: "AGENT_CANCELLED" };
|
|
169
|
+
}
|
|
170
|
+
return terminalOutcome;
|
|
171
|
+
}
|
|
172
|
+
async runTask(goalId, request, task) {
|
|
173
|
+
const retryPolicy = normalizeRetryPolicy(task.retryPolicy ?? request.retryPolicy);
|
|
174
|
+
const attempts = task.evidence?.attempts ? [...task.evidence.attempts] : [];
|
|
175
|
+
let finalResult = "failed";
|
|
176
|
+
let finalReason = "failed";
|
|
177
|
+
let validationResults = [];
|
|
178
|
+
let lastErrorCode;
|
|
179
|
+
for (let attemptNumber = 1; attemptNumber <= retryPolicy.maxAttempts; attemptNumber += 1) {
|
|
180
|
+
if (this.cancelRequested.has(goalId)) {
|
|
181
|
+
finalResult = "cancelled";
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
const attempt = await this.runTaskAttempt(goalId, request, task, attemptNumber, attempts);
|
|
185
|
+
finalResult = attempt.result;
|
|
186
|
+
finalReason = attempt.reason;
|
|
187
|
+
lastErrorCode = attempt.errorCode;
|
|
188
|
+
if (attempt.result === "success" && task.validationCommands?.length) {
|
|
189
|
+
validationResults = await runValidationCommands({
|
|
190
|
+
commands: task.validationCommands,
|
|
191
|
+
cwd: task.cwd,
|
|
192
|
+
env: request.env,
|
|
193
|
+
timeoutMs: request.validationTimeoutMs,
|
|
194
|
+
});
|
|
195
|
+
const failedValidation = validationResults.find((validation) => !validation.passed);
|
|
196
|
+
if (failedValidation) {
|
|
197
|
+
finalResult = "failed";
|
|
198
|
+
finalReason = failedValidation.classification === "timeout" ? "timeout" : "validation_failed";
|
|
199
|
+
lastErrorCode = failedValidation.classification === "timeout" ? "AGENT_TIMEOUT" : "AGENT_EXECUTION_FAILED";
|
|
200
|
+
attempt.evidence.result = "failed";
|
|
201
|
+
attempt.evidence.diagnostics = [
|
|
202
|
+
...attempt.evidence.diagnostics,
|
|
203
|
+
diagnostic(lastErrorCode, `Task ${task.id} validation ${failedValidation.classification}.`, {
|
|
204
|
+
retryable: isRetryable(lastErrorCode, retryPolicy),
|
|
205
|
+
exitCode: failedValidation.exitCode,
|
|
206
|
+
signal: failedValidation.signal,
|
|
207
|
+
}),
|
|
208
|
+
];
|
|
209
|
+
this.updateAttemptEvidence(goalId, task, attempts, attempt.evidence, finalResult, validationResults);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const retryable = !this.cancelRequested.has(goalId)
|
|
213
|
+
&& finalResult !== "success"
|
|
214
|
+
&& attemptNumber < retryPolicy.maxAttempts
|
|
215
|
+
&& isRetryable(lastErrorCode, retryPolicy);
|
|
216
|
+
this.store.emit(goalId, {
|
|
217
|
+
type: "task_attempt_finished",
|
|
218
|
+
goalId,
|
|
219
|
+
taskId: task.id,
|
|
220
|
+
attemptId: attempt.evidence.attemptId,
|
|
221
|
+
attemptNumber,
|
|
222
|
+
runId: attempt.evidence.runId,
|
|
223
|
+
result: finalResult,
|
|
224
|
+
retryable,
|
|
225
|
+
reason: finalReason,
|
|
226
|
+
errorCode: lastErrorCode,
|
|
227
|
+
});
|
|
228
|
+
if (!retryable)
|
|
229
|
+
break;
|
|
230
|
+
if (retryPolicy.backoffMs > 0)
|
|
231
|
+
await delay(retryPolicy.backoffMs);
|
|
232
|
+
}
|
|
233
|
+
if (this.cancelRequested.has(goalId)) {
|
|
234
|
+
finalResult = "cancelled";
|
|
235
|
+
finalReason = "canceled";
|
|
236
|
+
lastErrorCode = "AGENT_CANCELLED";
|
|
237
|
+
}
|
|
238
|
+
task.status = finalResult === "success" ? "succeeded" : finalResult === "cancelled" ? "canceled" : "failed";
|
|
239
|
+
task.evidence = {
|
|
240
|
+
runId: task.evidence?.runId,
|
|
241
|
+
result: finalResult,
|
|
242
|
+
attempts,
|
|
243
|
+
validationCommands: task.validationCommands ?? [],
|
|
244
|
+
validationResults,
|
|
245
|
+
summary: evidenceSummary(task.id, finalResult, attempts, validationResults),
|
|
246
|
+
};
|
|
247
|
+
this.store.updateTask(goalId, task);
|
|
248
|
+
if (!this.store.isTerminal(goalId)) {
|
|
249
|
+
this.store.emit(goalId, { type: "task_finished", goalId, taskId: task.id, result: finalResult, reason: finalReason, errorCode: lastErrorCode });
|
|
250
|
+
}
|
|
251
|
+
return { result: finalResult, reason: finalReason, errorCode: lastErrorCode };
|
|
252
|
+
}
|
|
253
|
+
async runTaskAttempt(goalId, request, task, attemptNumber, attempts) {
|
|
254
|
+
let result = "failed";
|
|
255
|
+
let runRecord = null;
|
|
256
|
+
const prompt = createTaskPrompt(request.objective, task);
|
|
257
|
+
const handle = await this.runScheduler.startRun({
|
|
258
|
+
agentId: task.agentId ?? request.defaultAgentId,
|
|
259
|
+
cwd: request.cwd,
|
|
260
|
+
prompt,
|
|
261
|
+
permissionPolicy: task.permissionPolicy,
|
|
262
|
+
model: request.model,
|
|
263
|
+
reasoning: request.reasoning,
|
|
264
|
+
env: request.env,
|
|
265
|
+
timeoutMs: request.taskTimeoutMs ?? request.timeoutMs,
|
|
266
|
+
});
|
|
267
|
+
const attemptEvidence = {
|
|
268
|
+
attemptId: `${task.id}:attempt:${attemptNumber}`,
|
|
269
|
+
runId: handle.runId,
|
|
270
|
+
startedAt: Date.now(),
|
|
271
|
+
diagnostics: [],
|
|
272
|
+
};
|
|
273
|
+
attempts.push(attemptEvidence);
|
|
274
|
+
task.evidence = {
|
|
275
|
+
runId: handle.runId,
|
|
276
|
+
attempts,
|
|
277
|
+
validationCommands: task.validationCommands ?? [],
|
|
278
|
+
summary: "",
|
|
279
|
+
};
|
|
280
|
+
this.store.updateTask(goalId, task);
|
|
281
|
+
this.setCurrentRun(goalId, task.id, handle.runId);
|
|
282
|
+
this.store.emit(goalId, { type: "task_attempt_started", goalId, taskId: task.id, attemptId: attemptEvidence.attemptId, attemptNumber, runId: handle.runId });
|
|
283
|
+
this.store.emit(goalId, { type: "task_started", goalId, taskId: task.id, runId: handle.runId });
|
|
284
|
+
if (this.store.isTerminal(goalId)) {
|
|
285
|
+
await handle.cancel();
|
|
286
|
+
return { result: "failed", reason: "failed", evidence: attemptEvidence };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
for await (const event of handle.events) {
|
|
290
|
+
this.store.emit(goalId, { type: "run_event", goalId, taskId: task.id, runId: handle.runId, event });
|
|
291
|
+
if (this.store.isTerminal(goalId)) {
|
|
292
|
+
await handle.cancel();
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
if (event.type === "run_finished")
|
|
296
|
+
result = event.result;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
this.clearCurrentRun(goalId, task.id);
|
|
301
|
+
}
|
|
302
|
+
runRecord = await this.runScheduler.getRun(handle.runId);
|
|
303
|
+
attemptEvidence.finishedAt = Date.now();
|
|
304
|
+
attemptEvidence.result = this.cancelRequested.has(goalId) ? "cancelled" : result;
|
|
305
|
+
attemptEvidence.diagnostics = summarizeAttemptDiagnostics(runRecord);
|
|
306
|
+
this.updateAttemptEvidence(goalId, task, attempts, attemptEvidence, attemptEvidence.result, []);
|
|
307
|
+
return {
|
|
308
|
+
result: attemptEvidence.result,
|
|
309
|
+
reason: terminalReasonFromRunRecord(runRecord, attemptEvidence.result),
|
|
310
|
+
errorCode: runRecord?.errorCode ?? firstDiagnosticCode(attemptEvidence.diagnostics),
|
|
311
|
+
evidence: attemptEvidence,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async runAndCollectText(goalId, taskId, request) {
|
|
315
|
+
const handle = await this.runScheduler.startRun(request);
|
|
316
|
+
this.setCurrentRun(goalId, taskId ?? "__planner__", handle.runId);
|
|
317
|
+
let text = "";
|
|
318
|
+
let result = "failed";
|
|
319
|
+
try {
|
|
320
|
+
for await (const event of handle.events) {
|
|
321
|
+
this.store.emit(goalId, { type: "run_event", goalId, taskId, runId: handle.runId, event });
|
|
322
|
+
if (this.store.isTerminal(goalId)) {
|
|
323
|
+
await handle.cancel();
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
if (event.type === "text_delta")
|
|
327
|
+
text += event.text;
|
|
328
|
+
if (event.type === "run_finished")
|
|
329
|
+
result = event.result;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
this.clearCurrentRun(goalId, taskId ?? "__planner__");
|
|
334
|
+
}
|
|
335
|
+
if (this.cancelRequested.has(goalId))
|
|
336
|
+
return "";
|
|
337
|
+
if (result !== "success")
|
|
338
|
+
throw new Error(`Planner run failed with ${result}`);
|
|
339
|
+
return text;
|
|
340
|
+
}
|
|
341
|
+
cancelPending(goalId, tasks) {
|
|
342
|
+
for (const task of tasks) {
|
|
343
|
+
if (task.status === "pending") {
|
|
344
|
+
task.status = "canceled";
|
|
345
|
+
this.store.updateTask(goalId, task);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
cancelPendingExceptBlocked(goalId, tasks) {
|
|
350
|
+
for (const task of tasks) {
|
|
351
|
+
if (task.status === "pending") {
|
|
352
|
+
task.status = "canceled";
|
|
353
|
+
this.store.updateTask(goalId, task);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
cancelPendingFromStore(goalId) {
|
|
358
|
+
const goal = this.store.get(goalId);
|
|
359
|
+
if (!goal)
|
|
360
|
+
return;
|
|
361
|
+
this.cancelPending(goalId, goal.tasks);
|
|
362
|
+
}
|
|
363
|
+
blockDependents(goalId, tasks, failedTaskId) {
|
|
364
|
+
let changed = true;
|
|
365
|
+
const blocked = new Set([failedTaskId]);
|
|
366
|
+
while (changed) {
|
|
367
|
+
changed = false;
|
|
368
|
+
for (const task of tasks) {
|
|
369
|
+
if (task.status !== "pending" || !task.dependencies.some((dep) => blocked.has(dep)))
|
|
370
|
+
continue;
|
|
371
|
+
task.status = "blocked";
|
|
372
|
+
blocked.add(task.id);
|
|
373
|
+
this.store.updateTask(goalId, task);
|
|
374
|
+
changed = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
blockUnavailableDependents(goalId, tasks) {
|
|
379
|
+
let changed = true;
|
|
380
|
+
while (changed) {
|
|
381
|
+
changed = false;
|
|
382
|
+
for (const task of tasks) {
|
|
383
|
+
if (task.status !== "pending")
|
|
384
|
+
continue;
|
|
385
|
+
const hasUnavailableDependency = task.dependencies.some((dep) => {
|
|
386
|
+
const dependency = tasks.find((candidate) => candidate.id === dep);
|
|
387
|
+
return dependency?.status === "failed" || dependency?.status === "blocked";
|
|
388
|
+
});
|
|
389
|
+
if (!hasUnavailableDependency)
|
|
390
|
+
continue;
|
|
391
|
+
task.status = "blocked";
|
|
392
|
+
this.store.updateTask(goalId, task);
|
|
393
|
+
changed = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
setCurrentRun(goalId, taskId, runId) {
|
|
398
|
+
const runs = this.currentRuns.get(goalId) ?? new Map();
|
|
399
|
+
runs.set(taskId, runId);
|
|
400
|
+
this.currentRuns.set(goalId, runs);
|
|
401
|
+
}
|
|
402
|
+
clearCurrentRun(goalId, taskId) {
|
|
403
|
+
const runs = this.currentRuns.get(goalId);
|
|
404
|
+
if (!runs)
|
|
405
|
+
return;
|
|
406
|
+
runs.delete(taskId);
|
|
407
|
+
if (runs.size === 0)
|
|
408
|
+
this.currentRuns.delete(goalId);
|
|
409
|
+
}
|
|
410
|
+
async cancelCurrentRuns(goalId) {
|
|
411
|
+
const runs = [...(this.currentRuns.get(goalId)?.values() ?? [])];
|
|
412
|
+
await Promise.all(runs.map((runId) => this.runScheduler.cancelRun(runId)));
|
|
413
|
+
}
|
|
414
|
+
updateAttemptEvidence(goalId, task, attempts, attempt, result, validationResults) {
|
|
415
|
+
const index = attempts.findIndex((candidate) => candidate.attemptId === attempt.attemptId);
|
|
416
|
+
if (index >= 0)
|
|
417
|
+
attempts[index] = attempt;
|
|
418
|
+
task.evidence = {
|
|
419
|
+
runId: attempt.runId,
|
|
420
|
+
result,
|
|
421
|
+
attempts,
|
|
422
|
+
validationCommands: task.validationCommands ?? [],
|
|
423
|
+
validationResults,
|
|
424
|
+
summary: evidenceSummary(task.id, result, attempts, validationResults),
|
|
425
|
+
};
|
|
426
|
+
this.store.updateTask(goalId, task);
|
|
427
|
+
}
|
|
428
|
+
finish(goalId, outcome) {
|
|
429
|
+
if (this.store.isTerminal(goalId))
|
|
430
|
+
return;
|
|
431
|
+
const result = outcome.result;
|
|
432
|
+
this.store.setStatus(goalId, result === "success" ? "succeeded" : result === "cancelled" ? "canceled" : "failed", result);
|
|
433
|
+
this.store.emit(goalId, { type: "goal_finished", goalId, result, reason: outcome.reason, errorCode: outcome.errorCode });
|
|
434
|
+
this.currentRuns.delete(goalId);
|
|
435
|
+
this.cancelRequested.delete(goalId);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function delay(ms) {
|
|
439
|
+
return new Promise((resolve) => {
|
|
440
|
+
const timer = setTimeout(resolve, ms);
|
|
441
|
+
timer.unref?.();
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function waitFor(predicate) {
|
|
445
|
+
while (!predicate())
|
|
446
|
+
await delay(20);
|
|
447
|
+
}
|
|
448
|
+
function normalizeMaxConcurrentTasks(value) {
|
|
449
|
+
if (!value || !Number.isFinite(value) || value < 1)
|
|
450
|
+
return 1;
|
|
451
|
+
return Math.floor(value);
|
|
452
|
+
}
|
|
453
|
+
function normalizeRetryPolicy(policy) {
|
|
454
|
+
return {
|
|
455
|
+
maxAttempts: Math.max(1, Math.floor(policy?.maxAttempts ?? 1)),
|
|
456
|
+
retryableErrorCodes: policy?.retryableErrorCodes ?? [],
|
|
457
|
+
backoffMs: Math.max(0, Math.floor(policy?.backoffMs ?? 0)),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function dependenciesSucceeded(task, tasks) {
|
|
461
|
+
return task.dependencies.every((dependencyId) => tasks.find((candidate) => candidate.id === dependencyId)?.status === "succeeded");
|
|
462
|
+
}
|
|
463
|
+
function isRetryable(errorCode, policy) {
|
|
464
|
+
return Boolean(errorCode && policy.retryableErrorCodes.includes(errorCode));
|
|
465
|
+
}
|
|
466
|
+
function terminalReasonFromRunRecord(run, result) {
|
|
467
|
+
if (result === "success")
|
|
468
|
+
return "success";
|
|
469
|
+
if (result === "cancelled")
|
|
470
|
+
return "canceled";
|
|
471
|
+
return terminalReasonFromErrorCode(run?.errorCode ?? firstDiagnosticCode(summarizeAttemptDiagnostics(run)));
|
|
472
|
+
}
|
|
473
|
+
function terminalReasonFromErrorCode(errorCode) {
|
|
474
|
+
if (errorCode === "AGENT_TIMEOUT")
|
|
475
|
+
return "timeout";
|
|
476
|
+
if (errorCode === "AGENT_CANCELLED")
|
|
477
|
+
return "canceled";
|
|
478
|
+
if (errorCode === "AGENT_RUNTIME_INTERRUPTED")
|
|
479
|
+
return "interrupted";
|
|
480
|
+
if (errorCode === "AGENT_TASK_GRAPH_INVALID")
|
|
481
|
+
return "task_graph_invalid";
|
|
482
|
+
if (errorCode === "AGENT_AUTH_REQUIRED" || errorCode === "auth_missing")
|
|
483
|
+
return "auth_missing";
|
|
484
|
+
if (errorCode === "AGENT_UNAVAILABLE" || errorCode === "AGENT_NOT_EXECUTABLE" || errorCode === "AGENT_MODEL_UNAVAILABLE")
|
|
485
|
+
return "unavailable";
|
|
486
|
+
if (errorCode === "AGENT_PROMPT_TOO_LARGE" || errorCode === "PERMISSION_POLICY_UNSUPPORTED")
|
|
487
|
+
return "validation_failed";
|
|
488
|
+
if (errorCode === "AGENT_EXECUTION_FAILED" || errorCode === "AGENT_STREAM_PARSE_FAILED" || errorCode === "AGENT_EVENT_PERSIST_FAILED")
|
|
489
|
+
return "execution_failed";
|
|
490
|
+
return "failed";
|
|
491
|
+
}
|
|
492
|
+
function summarizeAttemptDiagnostics(run) {
|
|
493
|
+
if (!run)
|
|
494
|
+
return [];
|
|
495
|
+
if (run.diagnostics.length > 0)
|
|
496
|
+
return run.diagnostics.map((item) => ({ ...item }));
|
|
497
|
+
if (!run.errorCode)
|
|
498
|
+
return [];
|
|
499
|
+
return [
|
|
500
|
+
diagnostic(run.errorCode, run.error ?? `Run ${run.id} finished with ${run.status}.`, {
|
|
501
|
+
exitCode: run.exitCode,
|
|
502
|
+
signal: run.signal,
|
|
503
|
+
retryable: false,
|
|
504
|
+
}),
|
|
505
|
+
];
|
|
506
|
+
}
|
|
507
|
+
function firstDiagnosticCode(diagnostics) {
|
|
508
|
+
return diagnostics[0]?.code;
|
|
509
|
+
}
|
|
510
|
+
function evidenceSummary(taskId, result, attempts, validationResults) {
|
|
511
|
+
const attemptSummary = `${attempts.length} attempt${attempts.length === 1 ? "" : "s"}`;
|
|
512
|
+
if (validationResults.length > 0) {
|
|
513
|
+
const passed = validationResults.filter((validation) => validation.passed).length;
|
|
514
|
+
return `Task ${taskId} finished with ${result} after ${attemptSummary}; ${passed}/${validationResults.length} validation commands passed.`;
|
|
515
|
+
}
|
|
516
|
+
return `Task ${taskId} finished with ${result} after ${attemptSummary}.`;
|
|
517
|
+
}
|
|
518
|
+
//# sourceMappingURL=goal-scheduler.js.map
|