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.
Files changed (151) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/CONTRIBUTING.md +60 -0
  3. package/LICENSE +202 -0
  4. package/README.md +573 -0
  5. package/README.zh-CN.md +571 -0
  6. package/SECURITY.md +35 -0
  7. package/dist/adapters/adapter-types.d.ts +138 -0
  8. package/dist/adapters/adapter-types.js +2 -0
  9. package/dist/adapters/adapter-types.js.map +1 -0
  10. package/dist/adapters/claude.d.ts +2 -0
  11. package/dist/adapters/claude.js +97 -0
  12. package/dist/adapters/claude.js.map +1 -0
  13. package/dist/adapters/codex.d.ts +3 -0
  14. package/dist/adapters/codex.js +120 -0
  15. package/dist/adapters/codex.js.map +1 -0
  16. package/dist/adapters/opencode.d.ts +4 -0
  17. package/dist/adapters/opencode.js +111 -0
  18. package/dist/adapters/opencode.js.map +1 -0
  19. package/dist/adapters/registry.d.ts +9 -0
  20. package/dist/adapters/registry.js +23 -0
  21. package/dist/adapters/registry.js.map +1 -0
  22. package/dist/cli/main.d.ts +2 -0
  23. package/dist/cli/main.js +978 -0
  24. package/dist/cli/main.js.map +1 -0
  25. package/dist/core/async-queue.d.ts +10 -0
  26. package/dist/core/async-queue.js +49 -0
  27. package/dist/core/async-queue.js.map +1 -0
  28. package/dist/core/diagnostics.d.ts +20 -0
  29. package/dist/core/diagnostics.js +4 -0
  30. package/dist/core/diagnostics.js.map +1 -0
  31. package/dist/core/event-contract.d.ts +32 -0
  32. package/dist/core/event-contract.js +128 -0
  33. package/dist/core/event-contract.js.map +1 -0
  34. package/dist/core/events.d.ts +147 -0
  35. package/dist/core/events.js +4 -0
  36. package/dist/core/events.js.map +1 -0
  37. package/dist/core/ids.d.ts +1 -0
  38. package/dist/core/ids.js +5 -0
  39. package/dist/core/ids.js.map +1 -0
  40. package/dist/core/redaction.d.ts +4 -0
  41. package/dist/core/redaction.js +51 -0
  42. package/dist/core/redaction.js.map +1 -0
  43. package/dist/core/runtime.d.ts +41 -0
  44. package/dist/core/runtime.js +83 -0
  45. package/dist/core/runtime.js.map +1 -0
  46. package/dist/core/schema-contract.d.ts +55 -0
  47. package/dist/core/schema-contract.js +143 -0
  48. package/dist/core/schema-contract.js.map +1 -0
  49. package/dist/detection/detect.d.ts +14 -0
  50. package/dist/detection/detect.js +293 -0
  51. package/dist/detection/detect.js.map +1 -0
  52. package/dist/detection/env.d.ts +2 -0
  53. package/dist/detection/env.js +15 -0
  54. package/dist/detection/env.js.map +1 -0
  55. package/dist/detection/executable-resolution.d.ts +12 -0
  56. package/dist/detection/executable-resolution.js +50 -0
  57. package/dist/detection/executable-resolution.js.map +1 -0
  58. package/dist/detection/invocation.d.ts +9 -0
  59. package/dist/detection/invocation.js +22 -0
  60. package/dist/detection/invocation.js.map +1 -0
  61. package/dist/goals/goal-scheduler.d.ts +31 -0
  62. package/dist/goals/goal-scheduler.js +518 -0
  63. package/dist/goals/goal-scheduler.js.map +1 -0
  64. package/dist/goals/goal-store.d.ts +37 -0
  65. package/dist/goals/goal-store.js +300 -0
  66. package/dist/goals/goal-store.js.map +1 -0
  67. package/dist/goals/goal-types.d.ts +103 -0
  68. package/dist/goals/goal-types.js +2 -0
  69. package/dist/goals/goal-types.js.map +1 -0
  70. package/dist/goals/planner-prompts.d.ts +3 -0
  71. package/dist/goals/planner-prompts.js +26 -0
  72. package/dist/goals/planner-prompts.js.map +1 -0
  73. package/dist/goals/task-graph.d.ts +9 -0
  74. package/dist/goals/task-graph.js +229 -0
  75. package/dist/goals/task-graph.js.map +1 -0
  76. package/dist/goals/validation-runner.d.ts +7 -0
  77. package/dist/goals/validation-runner.js +63 -0
  78. package/dist/goals/validation-runner.js.map +1 -0
  79. package/dist/index.d.ts +11 -0
  80. package/dist/index.js +2 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/parsers/claude-stream-json.d.ts +11 -0
  83. package/dist/parsers/claude-stream-json.js +102 -0
  84. package/dist/parsers/claude-stream-json.js.map +1 -0
  85. package/dist/parsers/codex-json.d.ts +8 -0
  86. package/dist/parsers/codex-json.js +107 -0
  87. package/dist/parsers/codex-json.js.map +1 -0
  88. package/dist/parsers/line-buffer.d.ts +7 -0
  89. package/dist/parsers/line-buffer.js +28 -0
  90. package/dist/parsers/line-buffer.js.map +1 -0
  91. package/dist/parsers/opencode-json.d.ts +8 -0
  92. package/dist/parsers/opencode-json.js +72 -0
  93. package/dist/parsers/opencode-json.js.map +1 -0
  94. package/dist/parsers/plain-lines.d.ts +6 -0
  95. package/dist/parsers/plain-lines.js +9 -0
  96. package/dist/parsers/plain-lines.js.map +1 -0
  97. package/dist/public-types.d.ts +143 -0
  98. package/dist/public-types.js +2 -0
  99. package/dist/public-types.js.map +1 -0
  100. package/dist/runs/process-runner.d.ts +35 -0
  101. package/dist/runs/process-runner.js +97 -0
  102. package/dist/runs/process-runner.js.map +1 -0
  103. package/dist/runs/prompt-transport.d.ts +10 -0
  104. package/dist/runs/prompt-transport.js +43 -0
  105. package/dist/runs/prompt-transport.js.map +1 -0
  106. package/dist/runs/run-result.d.ts +9 -0
  107. package/dist/runs/run-result.js +22 -0
  108. package/dist/runs/run-result.js.map +1 -0
  109. package/dist/runs/run-scheduler.d.ts +25 -0
  110. package/dist/runs/run-scheduler.js +552 -0
  111. package/dist/runs/run-scheduler.js.map +1 -0
  112. package/dist/runs/run-store.d.ts +42 -0
  113. package/dist/runs/run-store.js +297 -0
  114. package/dist/runs/run-store.js.map +1 -0
  115. package/dist/runs/run-types.d.ts +59 -0
  116. package/dist/runs/run-types.js +2 -0
  117. package/dist/runs/run-types.js.map +1 -0
  118. package/dist/smoke/parser-samples.d.ts +17 -0
  119. package/dist/smoke/parser-samples.js +186 -0
  120. package/dist/smoke/parser-samples.js.map +1 -0
  121. package/dist/storage/file-storage.d.ts +35 -0
  122. package/dist/storage/file-storage.js +271 -0
  123. package/dist/storage/file-storage.js.map +1 -0
  124. package/dist/storage/jsonl-store.d.ts +9 -0
  125. package/dist/storage/jsonl-store.js +138 -0
  126. package/dist/storage/jsonl-store.js.map +1 -0
  127. package/dist/storage/manifest-validation.d.ts +11 -0
  128. package/dist/storage/manifest-validation.js +102 -0
  129. package/dist/storage/manifest-validation.js.map +1 -0
  130. package/dist/storage/storage-lease.d.ts +40 -0
  131. package/dist/storage/storage-lease.js +223 -0
  132. package/dist/storage/storage-lease.js.map +1 -0
  133. package/dist/storage/storage-types.d.ts +55 -0
  134. package/dist/storage/storage-types.js +2 -0
  135. package/dist/storage/storage-types.js.map +1 -0
  136. package/dist/storage/store-inspection.d.ts +28 -0
  137. package/dist/storage/store-inspection.js +941 -0
  138. package/dist/storage/store-inspection.js.map +1 -0
  139. package/docs/api-schema-contract.md +92 -0
  140. package/docs/compatibility.md +832 -0
  141. package/docs/daemon-ready-contract.md +283 -0
  142. package/docs/production-readiness.md +281 -0
  143. package/docs/release-checklist.md +257 -0
  144. package/docs/release-publish-runbook.md +201 -0
  145. package/docs/release-report.md +517 -0
  146. package/docs/ssot.md +1257 -0
  147. package/examples/cli-dogfood.md +113 -0
  148. package/examples/library-goal.js +94 -0
  149. package/examples/library-run.js +84 -0
  150. package/package.json +79 -0
  151. 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,9 @@
1
+ export interface ProbeResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ cwd: string;
5
+ }
6
+ export declare function execProbe(command: string, args: string[], options?: {
7
+ env?: NodeJS.ProcessEnv;
8
+ timeoutMs?: number;
9
+ }): Promise<ProbeResult>;
@@ -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