subharness 0.0.1

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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/adapters/claude-auth.d.ts +7 -0
  4. package/dist/adapters/claude-auth.js +87 -0
  5. package/dist/adapters/claude-auth.js.map +1 -0
  6. package/dist/adapters/claude-config.d.ts +6 -0
  7. package/dist/adapters/claude-config.js +37 -0
  8. package/dist/adapters/claude-config.js.map +1 -0
  9. package/dist/adapters/claude-input.d.ts +9 -0
  10. package/dist/adapters/claude-input.js +32 -0
  11. package/dist/adapters/claude-input.js.map +1 -0
  12. package/dist/adapters/claude-permissions.d.ts +3 -0
  13. package/dist/adapters/claude-permissions.js +24 -0
  14. package/dist/adapters/claude-permissions.js.map +1 -0
  15. package/dist/adapters/claude-settings.d.ts +14 -0
  16. package/dist/adapters/claude-settings.js +43 -0
  17. package/dist/adapters/claude-settings.js.map +1 -0
  18. package/dist/adapters/claude-tools.d.ts +9 -0
  19. package/dist/adapters/claude-tools.js +47 -0
  20. package/dist/adapters/claude-tools.js.map +1 -0
  21. package/dist/adapters/claude.d.ts +22 -0
  22. package/dist/adapters/claude.js +295 -0
  23. package/dist/adapters/claude.js.map +1 -0
  24. package/dist/adapters/codex-config.d.ts +25 -0
  25. package/dist/adapters/codex-config.js +66 -0
  26. package/dist/adapters/codex-config.js.map +1 -0
  27. package/dist/adapters/codex.d.ts +2 -0
  28. package/dist/adapters/codex.js +238 -0
  29. package/dist/adapters/codex.js.map +1 -0
  30. package/dist/adapters/fx-auth.d.ts +4 -0
  31. package/dist/adapters/fx-auth.js +116 -0
  32. package/dist/adapters/fx-auth.js.map +1 -0
  33. package/dist/adapters/fx-rpc.d.ts +29 -0
  34. package/dist/adapters/fx-rpc.js +124 -0
  35. package/dist/adapters/fx-rpc.js.map +1 -0
  36. package/dist/adapters/fx-tools.d.ts +19 -0
  37. package/dist/adapters/fx-tools.js +119 -0
  38. package/dist/adapters/fx-tools.js.map +1 -0
  39. package/dist/adapters/fx.d.ts +3 -0
  40. package/dist/adapters/fx.js +302 -0
  41. package/dist/adapters/fx.js.map +1 -0
  42. package/dist/adapters/mcp-tool-content.d.ts +12 -0
  43. package/dist/adapters/mcp-tool-content.js +8 -0
  44. package/dist/adapters/mcp-tool-content.js.map +1 -0
  45. package/dist/adapters/rpc.d.ts +28 -0
  46. package/dist/adapters/rpc.js +101 -0
  47. package/dist/adapters/rpc.js.map +1 -0
  48. package/dist/adapters/types.d.ts +28 -0
  49. package/dist/adapters/types.js +2 -0
  50. package/dist/adapters/types.js.map +1 -0
  51. package/dist/cli/args.d.ts +18 -0
  52. package/dist/cli/args.js +118 -0
  53. package/dist/cli/args.js.map +1 -0
  54. package/dist/cli/catalog-worker.d.ts +1 -0
  55. package/dist/cli/catalog-worker.js +22 -0
  56. package/dist/cli/catalog-worker.js.map +1 -0
  57. package/dist/cli/catalog.d.ts +26 -0
  58. package/dist/cli/catalog.js +41 -0
  59. package/dist/cli/catalog.js.map +1 -0
  60. package/dist/cli/help.d.ts +3 -0
  61. package/dist/cli/help.js +68 -0
  62. package/dist/cli/help.js.map +1 -0
  63. package/dist/cli/input.d.ts +5 -0
  64. package/dist/cli/input.js +69 -0
  65. package/dist/cli/input.js.map +1 -0
  66. package/dist/cli/main.d.ts +2 -0
  67. package/dist/cli/main.js +105 -0
  68. package/dist/cli/main.js.map +1 -0
  69. package/dist/cli/output.d.ts +7 -0
  70. package/dist/cli/output.js +43 -0
  71. package/dist/cli/output.js.map +1 -0
  72. package/dist/config/access.d.ts +21 -0
  73. package/dist/config/access.js +97 -0
  74. package/dist/config/access.js.map +1 -0
  75. package/dist/config/loader.d.ts +13 -0
  76. package/dist/config/loader.js +80 -0
  77. package/dist/config/loader.js.map +1 -0
  78. package/dist/config/oidc.d.ts +1 -0
  79. package/dist/config/oidc.js +53 -0
  80. package/dist/config/oidc.js.map +1 -0
  81. package/dist/config/project.d.ts +5 -0
  82. package/dist/config/project.js +56 -0
  83. package/dist/config/project.js.map +1 -0
  84. package/dist/config/resolve-access.d.ts +4 -0
  85. package/dist/config/resolve-access.js +42 -0
  86. package/dist/config/resolve-access.js.map +1 -0
  87. package/dist/errors.d.ts +9 -0
  88. package/dist/errors.js +14 -0
  89. package/dist/errors.js.map +1 -0
  90. package/dist/index.d.ts +4 -0
  91. package/dist/index.js +3 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/runtime/client.d.ts +9 -0
  94. package/dist/runtime/client.js +91 -0
  95. package/dist/runtime/client.js.map +1 -0
  96. package/dist/runtime/coordinator.d.ts +61 -0
  97. package/dist/runtime/coordinator.js +296 -0
  98. package/dist/runtime/coordinator.js.map +1 -0
  99. package/dist/runtime/daemon.d.ts +1 -0
  100. package/dist/runtime/daemon.js +28 -0
  101. package/dist/runtime/daemon.js.map +1 -0
  102. package/dist/runtime/definition.d.ts +6 -0
  103. package/dist/runtime/definition.js +19 -0
  104. package/dist/runtime/definition.js.map +1 -0
  105. package/dist/runtime/native-owner.d.ts +8 -0
  106. package/dist/runtime/native-owner.js +25 -0
  107. package/dist/runtime/native-owner.js.map +1 -0
  108. package/dist/runtime/observation.d.ts +1 -0
  109. package/dist/runtime/observation.js +15 -0
  110. package/dist/runtime/observation.js.map +1 -0
  111. package/dist/runtime/select-native.d.ts +3 -0
  112. package/dist/runtime/select-native.js +34 -0
  113. package/dist/runtime/select-native.js.map +1 -0
  114. package/dist/runtime/service.d.ts +5 -0
  115. package/dist/runtime/service.js +90 -0
  116. package/dist/runtime/service.js.map +1 -0
  117. package/dist/runtime/session-launcher.d.ts +6 -0
  118. package/dist/runtime/session-launcher.js +29 -0
  119. package/dist/runtime/session-launcher.js.map +1 -0
  120. package/dist/runtime/state.d.ts +14 -0
  121. package/dist/runtime/state.js +69 -0
  122. package/dist/runtime/state.js.map +1 -0
  123. package/dist/runtime/types.d.ts +81 -0
  124. package/dist/runtime/types.js +2 -0
  125. package/dist/runtime/types.js.map +1 -0
  126. package/dist/runtime/worker-client.d.ts +3 -0
  127. package/dist/runtime/worker-client.js +81 -0
  128. package/dist/runtime/worker-client.js.map +1 -0
  129. package/dist/runtime/worker.d.ts +1 -0
  130. package/dist/runtime/worker.js +67 -0
  131. package/dist/runtime/worker.js.map +1 -0
  132. package/dist/sdk/definitions.d.ts +6 -0
  133. package/dist/sdk/definitions.js +72 -0
  134. package/dist/sdk/definitions.js.map +1 -0
  135. package/dist/sdk/tool-result.d.ts +23 -0
  136. package/dist/sdk/tool-result.js +95 -0
  137. package/dist/sdk/tool-result.js.map +1 -0
  138. package/dist/sdk/tools.d.ts +9 -0
  139. package/dist/sdk/tools.js +73 -0
  140. package/dist/sdk/tools.js.map +1 -0
  141. package/dist/sdk/types.d.ts +49 -0
  142. package/dist/sdk/types.js +2 -0
  143. package/dist/sdk/types.js.map +1 -0
  144. package/dist/sdk/validation.d.ts +5 -0
  145. package/dist/sdk/validation.js +20 -0
  146. package/dist/sdk/validation.js.map +1 -0
  147. package/dist/shell.d.ts +2 -0
  148. package/dist/shell.js +5 -0
  149. package/dist/shell.js.map +1 -0
  150. package/package.json +61 -0
  151. package/sdk/access-config.md +52 -0
  152. package/sdk/adapter-contract.md +33 -0
  153. package/sdk/agent.md +52 -0
  154. package/sdk/authentication.md +11 -0
  155. package/sdk/cli/index.md +67 -0
  156. package/sdk/cli/output.md +28 -0
  157. package/sdk/completion-notifications.md +29 -0
  158. package/sdk/config.md +41 -0
  159. package/sdk/distribution.md +31 -0
  160. package/sdk/fx.md +71 -0
  161. package/sdk/harnesses.md +32 -0
  162. package/sdk/index.md +22 -0
  163. package/sdk/message-delivery.md +39 -0
  164. package/sdk/plugins/sub-agents.md +57 -0
  165. package/sdk/project-team.md +57 -0
  166. package/sdk/sessions.md +31 -0
  167. package/sdk/tools.md +73 -0
  168. package/sdk/v1-runtime.md +63 -0
@@ -0,0 +1,42 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { parseEnv } from "node:util";
4
+ import { AgentError } from "../errors.js";
5
+ import { configError, parseConnection } from "./access.js";
6
+ import { resolveProject } from "./project.js";
7
+ import { validateOidc } from "./oidc.js";
8
+ export async function resolveAccess(connection, harness, cwd, env = process.env) {
9
+ parseConnection(connection);
10
+ const kind = typeof harness === "string" ? harness : harness.kind;
11
+ if (connection.type === "subscription") {
12
+ if (kind === "fx")
13
+ throw new AgentError("ACCESS_UNAVAILABLE", "fx does not support subscription access.");
14
+ return { type: "subscription" };
15
+ }
16
+ if (kind === "fx" && connection.type === "api-key") {
17
+ throw new AgentError("UNSUPPORTED_OPTION", "fx does not support direct provider API keys.");
18
+ }
19
+ const variable = connection.env ?? (connection.type === "api-key"
20
+ ? kind === "codex" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY"
21
+ : connection.type === "vercel-api-key" ? "AI_GATEWAY_API_KEY" : "VERCEL_OIDC_TOKEN");
22
+ const { mainRoot } = await resolveProject(cwd);
23
+ let token;
24
+ if (connection.envFile !== undefined) {
25
+ try {
26
+ token = parseEnv(await readFile(resolve(mainRoot, connection.envFile), "utf8"))[variable];
27
+ }
28
+ catch (error) {
29
+ if (error.code === "ENOENT")
30
+ throw new AgentError("ACCESS_UNAVAILABLE", `Credential file is missing: ${connection.envFile}`);
31
+ configError(`Cannot read credential variable ${variable} from its configured file.`);
32
+ }
33
+ }
34
+ else
35
+ token = env[variable];
36
+ if (!token?.trim())
37
+ throw new AgentError("ACCESS_UNAVAILABLE", `Credential variable ${variable} is missing or empty.`);
38
+ if (connection.type === "vercel-oidc")
39
+ await validateOidc(token, mainRoot, connection.project, env);
40
+ return { type: connection.type, token };
41
+ }
42
+ //# sourceMappingURL=resolve-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-access.js","sourceRoot":"","sources":["../../src/config/resolve-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAyB,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAA4B,EAC5B,OAAoC,EACpC,GAAW,EACX,GAAG,GAAsB,OAAO,CAAC,GAAG;IAEpC,eAAe,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAClE,IAAI,UAAU,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACvC,IAAI,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,0CAA0C,CAAC,CAAC;QAC1G,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAClC,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACnD,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,+CAA+C,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS;QAC/D,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,mBAAmB;QAC3D,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC;IACvF,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,KAAyB,CAAC;IAC9B,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACrC,IAAI,CAAC;YAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAAC,CAAC;QAClG,OAAO,KAAK,EAAE,CAAC;YACb,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,+BAA+B,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;YACxJ,WAAW,CAAC,mCAAmC,QAAQ,4BAA4B,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;;QAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,uBAAuB,QAAQ,uBAAuB,CAAC,CAAC;IACvH,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa;QAAE,MAAM,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACpG,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseEnv } from \"node:util\";\nimport type { ResolvedAccess } from \"../adapters/types.js\";\nimport { AgentError } from \"../errors.js\";\nimport type { HarnessConfig, HarnessKind } from \"../sdk/types.js\";\nimport { type AccessConnection, configError, parseConnection } from \"./access.js\";\nimport { resolveProject } from \"./project.js\";\nimport { validateOidc } from \"./oidc.js\";\n\nexport async function resolveAccess(\n connection: AccessConnection,\n harness: HarnessKind | HarnessConfig,\n cwd: string,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<ResolvedAccess> {\n parseConnection(connection);\n const kind = typeof harness === \"string\" ? harness : harness.kind;\n if (connection.type === \"subscription\") {\n if (kind === \"fx\") throw new AgentError(\"ACCESS_UNAVAILABLE\", \"fx does not support subscription access.\");\n return { type: \"subscription\" };\n }\n if (kind === \"fx\" && connection.type === \"api-key\") {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"fx does not support direct provider API keys.\");\n }\n const variable = connection.env ?? (connection.type === \"api-key\"\n ? kind === \"codex\" ? \"OPENAI_API_KEY\" : \"ANTHROPIC_API_KEY\"\n : connection.type === \"vercel-api-key\" ? \"AI_GATEWAY_API_KEY\" : \"VERCEL_OIDC_TOKEN\");\n const { mainRoot } = await resolveProject(cwd);\n let token: string | undefined;\n if (connection.envFile !== undefined) {\n try { token = parseEnv(await readFile(resolve(mainRoot, connection.envFile), \"utf8\"))[variable]; }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") throw new AgentError(\"ACCESS_UNAVAILABLE\", `Credential file is missing: ${connection.envFile}`);\n configError(`Cannot read credential variable ${variable} from its configured file.`);\n }\n } else token = env[variable];\n if (!token?.trim()) throw new AgentError(\"ACCESS_UNAVAILABLE\", `Credential variable ${variable} is missing or empty.`);\n if (connection.type === \"vercel-oidc\") await validateOidc(token, mainRoot, connection.project, env);\n return { type: connection.type, token };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export declare class AgentError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export type ErrorInfo = {
6
+ code: string;
7
+ message: string;
8
+ };
9
+ export declare function errorInfo(error: unknown): ErrorInfo;
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class AgentError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "AgentError";
7
+ }
8
+ }
9
+ export function errorInfo(error) {
10
+ return error instanceof AgentError
11
+ ? { code: error.code, message: error.message }
12
+ : { code: "INTERNAL_ERROR", message: error instanceof Error ? error.message : String(error) };
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IACP,IAAI;IAAhC,YAA4B,IAAY,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;oBADW,IAAI;QAE9B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAID,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,KAAK,YAAY,UAAU;QAChC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;QAC9C,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAClG,CAAC","sourcesContent":["export class AgentError extends Error {\n constructor(public readonly code: string, message: string) {\n super(message);\n this.name = \"AgentError\";\n }\n}\n\nexport type ErrorInfo = { code: string; message: string };\n\nexport function errorInfo(error: unknown): ErrorInfo {\n return error instanceof AgentError\n ? { code: error.code, message: error.message }\n : { code: \"INTERNAL_ERROR\", message: error instanceof Error ? error.message : String(error) };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export { agent, codex, claudeCode, fx } from "./sdk/definitions.js";
2
+ export { tool, toolResult } from "./sdk/tools.js";
3
+ export type { ToolContent, ToolResult } from "./sdk/tools.js";
4
+ export type { AgentDefinition, ClaudeCodeConfig, ClaudeCodeOptions, CodexConfig, CodexOptions, FxConfig, FxOptions, HarnessConfig, HarnessKind, ToolDefinition, } from "./sdk/types.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { agent, codex, claudeCode, fx } from "./sdk/definitions.js";
2
+ export { tool, toolResult } from "./sdk/tools.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { agent, codex, claudeCode, fx } from \"./sdk/definitions.js\";\nexport { tool, toolResult } from \"./sdk/tools.js\";\nexport type { ToolContent, ToolResult } from \"./sdk/tools.js\";\nexport type {\n AgentDefinition,\n ClaudeCodeConfig,\n ClaudeCodeOptions,\n CodexConfig,\n CodexOptions,\n FxConfig,\n FxOptions,\n HarnessConfig,\n HarnessKind,\n ToolDefinition,\n} from \"./sdk/types.js\";\n"]}
@@ -0,0 +1,9 @@
1
+ import type { OutputRecord } from '../cli/output.js';
2
+ import { type Endpoint } from './state.js';
3
+ interface ConnectionOptions {
4
+ directory?: string;
5
+ timeoutMs?: number;
6
+ }
7
+ export declare function connect(start: boolean, options?: ConnectionOptions): Promise<Endpoint>;
8
+ export declare function request(endpoint: Endpoint, body: unknown): AsyncGenerator<OutputRecord>;
9
+ export {};
@@ -0,0 +1,91 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { open, unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+ import { AgentError } from '../errors.js';
6
+ import { localProgram, privateDirectory, readEndpoint, stateDirectory, removeStaleStartupLock } from './state.js';
7
+ export async function connect(start, options = {}) {
8
+ const directory = options.directory ?? stateDirectory();
9
+ const available = await readEndpoint(directory);
10
+ if (available)
11
+ return available;
12
+ if (!start)
13
+ throw new AgentError('UNKNOWN_SESSION', 'No coordinator is reachable from this environment. Its sessions may have ended, or native permissions may block access to its local endpoint.');
14
+ await privateDirectory(directory);
15
+ const lock = join(directory, 'startup.lock');
16
+ const deadline = Date.now() + (options.timeoutMs ?? 15_000);
17
+ for (;;) {
18
+ let owner;
19
+ try {
20
+ owner = await open(lock, 'wx', 0o600);
21
+ }
22
+ catch (error) {
23
+ if (error.code !== 'EEXIST')
24
+ throw error;
25
+ await removeStaleStartupLock(lock);
26
+ }
27
+ if (owner) {
28
+ try {
29
+ await owner.writeFile(String(process.pid));
30
+ await owner.close();
31
+ const existing = await readEndpoint(directory);
32
+ if (existing)
33
+ return existing;
34
+ const program = localProgram('daemon');
35
+ const child = spawn(process.execPath, [...program.execArgv, program.file], { detached: true, stdio: 'ignore', env: { ...process.env, AGENT_STATE_DIR: directory } });
36
+ let spawnError = false;
37
+ child.once('error', () => { spawnError = true; });
38
+ child.unref();
39
+ while (Date.now() < deadline && !spawnError) {
40
+ await delay(50);
41
+ const endpoint = await readEndpoint(directory);
42
+ if (endpoint)
43
+ return endpoint;
44
+ }
45
+ throw new AgentError('STARTUP_FAILED', 'The local coordinator could not start.');
46
+ }
47
+ finally {
48
+ await owner.close().catch(() => { });
49
+ await unlink(lock).catch(() => { });
50
+ }
51
+ }
52
+ if (Date.now() >= deadline)
53
+ throw new AgentError('STARTUP_FAILED', 'Another command did not finish coordinator startup.');
54
+ await delay(50);
55
+ const endpoint = await readEndpoint(directory);
56
+ if (endpoint)
57
+ return endpoint;
58
+ }
59
+ }
60
+ export async function* request(endpoint, body) {
61
+ let response;
62
+ try {
63
+ response = await fetch(endpoint.url, { method: 'POST', headers: { authorization: `Bearer ${endpoint.token}`, 'content-type': 'application/json' }, body: JSON.stringify(body) });
64
+ }
65
+ catch {
66
+ throw new AgentError('COORDINATOR_UNAVAILABLE', 'Connection to the local coordinator failed. The task was not automatically retried.');
67
+ }
68
+ if (!response.ok || !response.body)
69
+ throw new AgentError('COORDINATOR_UNAVAILABLE', 'The coordinator rejected the request.');
70
+ let buffer = '';
71
+ const decoder = new TextDecoder();
72
+ try {
73
+ for await (const chunk of response.body) {
74
+ buffer += decoder.decode(chunk, { stream: true });
75
+ let newline;
76
+ while ((newline = buffer.indexOf('\n')) >= 0) {
77
+ const line = buffer.slice(0, newline);
78
+ buffer = buffer.slice(newline + 1);
79
+ if (line)
80
+ yield JSON.parse(line);
81
+ }
82
+ }
83
+ buffer += decoder.decode();
84
+ if (buffer.trim())
85
+ throw new Error('Incomplete response record');
86
+ }
87
+ catch {
88
+ throw new AgentError('COORDINATOR_UNAVAILABLE', 'The coordinator response was interrupted or invalid. The task was not automatically retried.');
89
+ }
90
+ }
91
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport { open, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { AgentError } from '../errors.js';\nimport type { OutputRecord } from '../cli/output.js';\nimport { type Endpoint, localProgram, privateDirectory, readEndpoint, stateDirectory, removeStaleStartupLock } from './state.js';\n\ninterface ConnectionOptions { directory?: string; timeoutMs?: number }\n\nexport async function connect(start: boolean, options: ConnectionOptions = {}): Promise<Endpoint> {\n const directory = options.directory ?? stateDirectory();\n const available = await readEndpoint(directory);\n if (available) return available;\n if (!start) throw new AgentError('UNKNOWN_SESSION','No coordinator is reachable from this environment. Its sessions may have ended, or native permissions may block access to its local endpoint.');\n await privateDirectory(directory);\n const lock = join(directory,'startup.lock');\n const deadline = Date.now() + (options.timeoutMs ?? 15_000);\n for (;;) {\n let owner;\n try { owner = await open(lock,'wx',0o600); }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n await removeStaleStartupLock(lock);\n }\n if (owner) {\n try {\n await owner.writeFile(String(process.pid)); await owner.close();\n const existing = await readEndpoint(directory);\n if (existing) return existing;\n const program = localProgram('daemon');\n const child = spawn(process.execPath,[...program.execArgv,program.file],{detached:true,stdio:'ignore',env:{...process.env,AGENT_STATE_DIR:directory}});\n let spawnError = false;\n child.once('error',()=>{spawnError=true;}); child.unref();\n while (Date.now() < deadline && !spawnError) {\n await delay(50);\n const endpoint = await readEndpoint(directory);\n if (endpoint) return endpoint;\n }\n throw new AgentError('STARTUP_FAILED','The local coordinator could not start.');\n } finally { await owner.close().catch(()=>{}); await unlink(lock).catch(()=>{}); }\n }\n if (Date.now() >= deadline) throw new AgentError('STARTUP_FAILED','Another command did not finish coordinator startup.');\n await delay(50);\n const endpoint = await readEndpoint(directory);\n if (endpoint) return endpoint;\n }\n}\n\nexport async function* request(endpoint: Endpoint, body: unknown): AsyncGenerator<OutputRecord> {\n let response: globalThis.Response;\n try {\n response = await fetch(endpoint.url,{method:'POST',headers:{authorization:`Bearer ${endpoint.token}`,'content-type':'application/json'},body:JSON.stringify(body)});\n } catch { throw new AgentError('COORDINATOR_UNAVAILABLE','Connection to the local coordinator failed. The task was not automatically retried.'); }\n if (!response.ok || !response.body) throw new AgentError('COORDINATOR_UNAVAILABLE','The coordinator rejected the request.');\n let buffer = '';\n const decoder = new TextDecoder();\n try {\n for await (const chunk of response.body) {\n buffer += decoder.decode(chunk,{stream:true});\n let newline;\n while ((newline = buffer.indexOf('\\n')) >= 0) {\n const line = buffer.slice(0,newline); buffer = buffer.slice(newline + 1);\n if (line) yield JSON.parse(line) as OutputRecord;\n }\n }\n buffer += decoder.decode();\n if (buffer.trim()) throw new Error('Incomplete response record');\n } catch {\n throw new AgentError('COORDINATOR_UNAVAILABLE','The coordinator response was interrupted or invalid. The task was not automatically retried.');\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { type DriverFactory, type Observation, type Receipt, type SessionInput, type Delivery } from './types.js';
2
+ export declare class Coordinator {
3
+ private readonly factory;
4
+ private sessions;
5
+ private tasks;
6
+ constructor(factory: DriverFactory);
7
+ parentContext(token: string): {
8
+ taskId: string;
9
+ reference: import("./types.js").ExecutionReference;
10
+ cwd: string;
11
+ };
12
+ run(input: SessionInput): Receipt;
13
+ send(id: string, prompt: string, delivery: Delivery, parentToken?: string): Promise<Receipt>;
14
+ observe(id: string, after?: string, readerToken?: string, signal?: AbortSignal): Promise<Observation>;
15
+ private acknowledge;
16
+ status(id: string, full?: boolean): {
17
+ version: number;
18
+ type: string;
19
+ taskId: string;
20
+ sessionId: string;
21
+ state: import("./types.js").TaskState;
22
+ error?: import("../errors.js").ErrorInfo | undefined;
23
+ response?: {
24
+ responseId: string;
25
+ text: string;
26
+ truncated: boolean;
27
+ } | undefined;
28
+ };
29
+ queue(id: string): {
30
+ version: number;
31
+ type: string;
32
+ sessionId: string;
33
+ paused: boolean;
34
+ active?: {
35
+ taskId: string;
36
+ state: import("./types.js").TaskState;
37
+ description: string;
38
+ } | undefined;
39
+ tasks: {
40
+ taskId: string;
41
+ state: import("./types.js").TaskState;
42
+ description: string;
43
+ }[];
44
+ };
45
+ resume(id: string): Promise<Receipt>;
46
+ close(): Promise<void>;
47
+ cancel(id: string): Promise<Receipt>;
48
+ private command;
49
+ private stop;
50
+ private finishStop;
51
+ private admit;
52
+ private validateAdmission;
53
+ private dispatch;
54
+ private execute;
55
+ private pending;
56
+ private deliver;
57
+ private continueParent;
58
+ private notify;
59
+ private task;
60
+ private session;
61
+ }
@@ -0,0 +1,296 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { waitForChange } from './observation.js';
3
+ import { AgentError, errorInfo } from '../errors.js';
4
+ import { terminal } from './types.js';
5
+ export class Coordinator {
6
+ factory;
7
+ sessions = new Map();
8
+ tasks = new Map();
9
+ constructor(factory) {
10
+ this.factory = factory;
11
+ }
12
+ parentContext(token) {
13
+ const session = [...this.sessions.values()].find(session => session.token === token);
14
+ const task = session?.active ? this.task(session.active) : undefined;
15
+ if (!session || !task || task.stopping || terminal(task.state))
16
+ throw new AgentError('INVALID_PARENT', 'The parent context is no longer active.');
17
+ return { taskId: task.id, reference: session.input.reference, cwd: session.input.cwd };
18
+ }
19
+ run(input) {
20
+ this.validateAdmission(undefined, input.prompt, input.parentToken);
21
+ const session = { id: `ses_${randomUUID()}`, token: randomUUID(), input,
22
+ tasks: [], paused: false, commands: Promise.resolve(), controls: 0 };
23
+ this.sessions.set(session.id, session);
24
+ return this.admit(session, input.prompt, false, input.parentToken);
25
+ }
26
+ async send(id, prompt, delivery, parentToken) {
27
+ const session = this.session(id);
28
+ const operation = async () => {
29
+ if (!['queue', 'steer', 'interrupt'].includes(delivery))
30
+ throw new AgentError('INVALID_ARGUMENT', 'Unknown delivery mode.');
31
+ this.validateAdmission(session, prompt, parentToken, delivery !== 'queue');
32
+ if (delivery === 'queue')
33
+ return this.admit(session, prompt, false, parentToken);
34
+ const task = session.active ? this.task(session.active) : undefined;
35
+ if (delivery === 'steer') {
36
+ if (!task || terminal(task.state) || task.stopping)
37
+ throw new AgentError('NO_ACTIVE_TASK', 'There is no active task accepting steering.');
38
+ if (!task.nativeActive) {
39
+ void this.execute(task, prompt);
40
+ return { version: 1, type: 'accepted', sessionId: id, taskId: task.id, delivery: 'steer' };
41
+ }
42
+ try {
43
+ await (await session.driver).steer(prompt);
44
+ return { version: 1, type: 'accepted', sessionId: id, taskId: task.id, delivery: 'steer' };
45
+ }
46
+ catch (error) {
47
+ if (!(error instanceof AgentError) || error.code !== 'UNSUPPORTED_DELIVERY')
48
+ throw error;
49
+ }
50
+ }
51
+ if (task && (!terminal(task.state) || task.stopUnconfirmed || task.state === 'failed'))
52
+ await this.stop(task, 'interrupted');
53
+ session.paused = false;
54
+ const receipt = this.admit(session, prompt, true, parentToken);
55
+ return { ...receipt, requestedDelivery: delivery, delivery: 'interrupt' };
56
+ };
57
+ return this.command(session, operation);
58
+ }
59
+ async observe(id, after, readerToken, signal) {
60
+ const task = this.task(id);
61
+ const position = after ? task.responses.findIndex(response => response.responseId === after) : -1;
62
+ if (after && position === -1)
63
+ throw new AgentError('UNKNOWN_RESPONSE', 'The response does not belong to this task.');
64
+ for (;;) {
65
+ const response = task.responses[position + 1];
66
+ if (response) {
67
+ if (terminal(response.state))
68
+ this.acknowledge(task, readerToken);
69
+ return response;
70
+ }
71
+ if (terminal(task.state)) {
72
+ this.acknowledge(task, readerToken);
73
+ return { version: 1, type: 'terminal', taskId: id,
74
+ sessionId: task.sessionId, state: task.state, ...(task.error ? { error: task.error } : {}) };
75
+ }
76
+ await waitForChange(task.changed, signal);
77
+ }
78
+ }
79
+ acknowledge(task, readerToken) {
80
+ if (!readerToken || !task.parentId || task.stopUnconfirmed)
81
+ return;
82
+ const parent = this.task(task.parentId);
83
+ if (this.session(parent.sessionId).token === readerToken && parent.nativeActive)
84
+ parent.inbox = parent.inbox.filter(result => result.taskId !== task.id);
85
+ }
86
+ status(id, full = false) {
87
+ const task = this.task(id), response = task.responses.at(-1);
88
+ return { version: 1, type: 'status', taskId: id, sessionId: task.sessionId, state: task.state,
89
+ ...(task.error ? { error: task.error } : {}), ...(response ? { response: {
90
+ responseId: response.responseId, text: full ? response.text : response.text.slice(0, 4000),
91
+ truncated: !full && response.text.length > 4000
92
+ } } : {}) };
93
+ }
94
+ queue(id) {
95
+ const session = this.session(id);
96
+ const summary = (task) => ({ taskId: task.id, state: task.state, description: task.prompt.slice(0, 120) });
97
+ return { version: 1, type: 'queue', sessionId: id, paused: session.paused,
98
+ ...(session.active ? { active: summary(this.task(session.active)) } : {}),
99
+ tasks: session.tasks.map(id => summary(this.task(id))) };
100
+ }
101
+ async resume(id) {
102
+ const session = this.session(id);
103
+ return this.command(session, async () => {
104
+ const task = session.active ? this.task(session.active) : undefined;
105
+ if (!task || task.state !== 'failed')
106
+ throw new AgentError('NOT_FAILED', 'This session has no failed task to resume.');
107
+ if (task.stopUnconfirmed || task.stopPromise)
108
+ throw new AgentError('CANCEL_FAILED', 'Confirm cancellation of the affected executions before recovery.');
109
+ task.stopping = false;
110
+ task.error = undefined;
111
+ session.paused = false;
112
+ void this.execute(task, '', true);
113
+ return { version: 1, type: 'started', sessionId: id, taskId: task.id, state: task.state, resumed: true };
114
+ });
115
+ }
116
+ async close() {
117
+ await Promise.allSettled([...this.sessions.values()].map(async (session) => (await session.driver)?.close()));
118
+ }
119
+ async cancel(id) {
120
+ const task = this.task(id);
121
+ return this.command(this.session(task.sessionId), async () => {
122
+ if (!terminal(task.state) || task.stopUnconfirmed)
123
+ await this.stop(task, 'cancelled');
124
+ else if (task.state === 'failed') {
125
+ const session = this.session(task.sessionId);
126
+ if (session.active === task.id) {
127
+ session.active = undefined;
128
+ session.paused = false;
129
+ }
130
+ }
131
+ return { version: 1, type: 'cancelled', taskId: id, sessionId: task.sessionId, state: task.state };
132
+ });
133
+ }
134
+ command(session, operation) {
135
+ session.controls++;
136
+ const result = session.commands.then(operation, operation).finally(() => {
137
+ session.controls--;
138
+ this.dispatch(session);
139
+ });
140
+ session.commands = result.catch(() => undefined);
141
+ return result;
142
+ }
143
+ stop(task, outcome) {
144
+ if (task.stopPromise)
145
+ return task.stopPromise;
146
+ task.stopPromise = this.finishStop(task, outcome).finally(() => {
147
+ task.stopPromise = undefined;
148
+ this.dispatch(this.session(task.sessionId));
149
+ });
150
+ return task.stopPromise;
151
+ }
152
+ async finishStop(task, outcome) {
153
+ const session = this.session(task.sessionId);
154
+ task.stopping = true;
155
+ session.tasks = session.tasks.filter(id => id !== task.id);
156
+ try {
157
+ const stops = [...task.children].map(id => this.task(id)).filter(child => !terminal(child.state) || child.stopUnconfirmed).map(child => this.stop(child, 'cancelled'));
158
+ if (task.nativeActive && session.driver)
159
+ stops.push(session.driver.then(async (driver) => {
160
+ await driver.interrupt();
161
+ task.nativeActive = false;
162
+ }));
163
+ const outcomes = await Promise.allSettled(stops);
164
+ if (outcomes.some(outcome => outcome.status === 'rejected'))
165
+ throw new Error('Cancellation unconfirmed');
166
+ }
167
+ catch {
168
+ task.state = 'failed';
169
+ task.error = { code: 'CANCEL_FAILED', message: 'Native execution could not be confirmed stopped.' };
170
+ task.stopUnconfirmed = true;
171
+ session.paused = true;
172
+ this.notify(task);
173
+ throw new AgentError('CANCEL_FAILED', task.error.message);
174
+ }
175
+ task.nativeActive = false;
176
+ task.state = outcome;
177
+ task.stopUnconfirmed = false;
178
+ task.error = undefined;
179
+ if (session.active === task.id) {
180
+ session.active = undefined;
181
+ session.paused = false;
182
+ }
183
+ this.notify(task);
184
+ this.deliver(task);
185
+ }
186
+ admit(session, prompt, front = false, parentToken) {
187
+ const parent = this.validateAdmission(session, prompt, parentToken, front);
188
+ const task = { id: `tsk_${randomUUID()}`, sessionId: session.id, prompt,
189
+ parentId: parent?.id, state: 'queued', responses: [], children: new Set(), inbox: [], nativeActive: false, stopping: false, changed: new Set() };
190
+ this.tasks.set(task.id, task);
191
+ parent?.children.add(task.id);
192
+ if (front)
193
+ session.tasks.unshift(task.id);
194
+ else
195
+ session.tasks.push(task.id);
196
+ this.dispatch(session);
197
+ return { version: 1, type: 'started', sessionId: session.id, taskId: task.id, state: task.state };
198
+ }
199
+ validateAdmission(session, prompt, parentToken, replacement = false) {
200
+ if (typeof prompt !== 'string' || !prompt.trim() || Buffer.byteLength(prompt) > 1024 * 1024)
201
+ throw new AgentError('INVALID_ARGUMENT', 'Prompt must contain text and cannot exceed 1 MiB.');
202
+ if (!replacement && session && session.tasks.length >= 100)
203
+ throw new AgentError('QUEUE_FULL', 'The session already has 100 queued tasks.');
204
+ const parent = parentToken ? this.task(this.parentContext(parentToken).taskId) : undefined;
205
+ let depth = 0, ancestor = parent;
206
+ while (ancestor) {
207
+ if (ancestor.sessionId === session?.id)
208
+ throw new AgentError('INVALID_PARENT', 'A managed task cannot queue work in its own or an ancestor session.');
209
+ depth++;
210
+ ancestor = ancestor.parentId ? this.task(ancestor.parentId) : undefined;
211
+ }
212
+ if (depth > 8)
213
+ throw new AgentError('DELEGATION_LIMIT', 'Maximum delegation depth is 8.');
214
+ return parent;
215
+ }
216
+ dispatch(session) {
217
+ if (session.active || session.paused || session.controls)
218
+ return;
219
+ const id = session.tasks.shift();
220
+ if (!id)
221
+ return;
222
+ session.active = id;
223
+ void this.execute(this.task(id), this.task(id).prompt);
224
+ }
225
+ async execute(task, prompt, recovering = false) {
226
+ const session = this.session(task.sessionId);
227
+ task.state = 'running';
228
+ task.nativeActive = true;
229
+ try {
230
+ session.driver ??= this.factory({ ...session.input, sessionId: session.id, token: session.token });
231
+ const driver = await session.driver;
232
+ if (task.stopping)
233
+ return;
234
+ const text = recovering ? await driver.resume() : await driver.turn(prompt);
235
+ if (task.stopping)
236
+ return;
237
+ if (Buffer.byteLength(text) > 1024 * 1024)
238
+ throw new AgentError('OUTPUT_TOO_LARGE', 'The response exceeds 1 MiB.');
239
+ task.nativeActive = false;
240
+ task.state = this.pending(task) ? 'waiting' : 'completed';
241
+ task.responses.push({ version: 1, type: 'response', sessionId: session.id, taskId: task.id,
242
+ responseId: `rsp_${randomUUID()}`, state: task.state, text });
243
+ this.notify(task);
244
+ if (task.state === 'completed') {
245
+ session.active = undefined;
246
+ this.deliver(task);
247
+ this.dispatch(session);
248
+ }
249
+ else
250
+ this.continueParent(task);
251
+ }
252
+ catch (error) {
253
+ if (task.stopping)
254
+ return;
255
+ task.nativeActive = false;
256
+ task.stopping = true;
257
+ const stopped = await Promise.allSettled([...task.children].map(id => this.task(id)).filter(child => !terminal(child.state) || child.stopUnconfirmed).map(child => this.stop(child, 'cancelled')));
258
+ task.stopUnconfirmed = stopped.some(result => result.status === 'rejected');
259
+ task.state = 'failed';
260
+ task.error = task.stopUnconfirmed
261
+ ? { code: 'CANCEL_FAILED', message: 'Descendant execution could not be confirmed stopped.' } : errorInfo(error);
262
+ session.paused = true;
263
+ this.notify(task);
264
+ this.deliver(task);
265
+ }
266
+ }
267
+ pending(task) {
268
+ return task.inbox.length > 0 || [...task.children].some(id => !terminal(this.task(id).state) || this.task(id).stopUnconfirmed);
269
+ }
270
+ deliver(child) {
271
+ if (!child.parentId || child.stopUnconfirmed)
272
+ return;
273
+ const parent = this.task(child.parentId);
274
+ if (parent.stopping || terminal(parent.state))
275
+ return;
276
+ parent.inbox.push({ taskId: child.id, state: child.state,
277
+ text: child.error?.message ?? child.responses.at(-1)?.text ?? child.state });
278
+ // Let an attached child command acknowledge delivery through its native tool output first.
279
+ setImmediate(() => this.continueParent(parent));
280
+ }
281
+ continueParent(task) {
282
+ if (task.nativeActive || task.stopping || terminal(task.state) || !task.inbox.length)
283
+ return;
284
+ const results = task.inbox.splice(0);
285
+ const prompt = 'Delegated task outcomes (continue the current task):\n' +
286
+ results.map(result => `${result.taskId} [${result.state}]:\n${result.text}`).join('\n\n');
287
+ void this.execute(task, prompt);
288
+ }
289
+ notify(task) { for (const resolve of task.changed)
290
+ resolve(); task.changed.clear(); }
291
+ task(id) { const task = this.tasks.get(id); if (!task)
292
+ throw new AgentError('UNKNOWN_TASK', 'Task is unavailable in this coordinator.'); return task; }
293
+ session(id) { const session = this.sessions.get(id); if (!session)
294
+ throw new AgentError('UNKNOWN_SESSION', 'Session is unavailable in this coordinator.'); return session; }
295
+ }
296
+ //# sourceMappingURL=coordinator.js.map