open-multi-agent-kit 0.99.0 → 1.0.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 (271) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +3 -3
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
  7. package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
  8. package/dist/bun/register-bundled-coding-agent.js +12 -0
  9. package/dist/bun/register-bundled-coding-agent.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +1 -1
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +1 -1
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +14 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/neo-cli.d.ts +9 -0
  21. package/dist/commands/neo-cli.d.ts.map +1 -0
  22. package/dist/commands/neo-cli.js +61 -0
  23. package/dist/commands/neo-cli.js.map +1 -0
  24. package/dist/core/agent-session.d.ts +29 -2
  25. package/dist/core/agent-session.d.ts.map +1 -1
  26. package/dist/core/agent-session.js +107 -30
  27. package/dist/core/agent-session.js.map +1 -1
  28. package/dist/core/bundled-skills.d.ts +4 -0
  29. package/dist/core/bundled-skills.d.ts.map +1 -0
  30. package/dist/core/bundled-skills.js +32 -0
  31. package/dist/core/bundled-skills.js.map +1 -0
  32. package/dist/core/cli-diagnostics.d.ts +6 -0
  33. package/dist/core/cli-diagnostics.d.ts.map +1 -0
  34. package/dist/core/cli-diagnostics.js +20 -0
  35. package/dist/core/cli-diagnostics.js.map +1 -0
  36. package/dist/core/context-budget-headroom.d.ts +12 -0
  37. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  38. package/dist/core/context-budget-headroom.js +35 -0
  39. package/dist/core/context-budget-headroom.js.map +1 -1
  40. package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
  41. package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
  42. package/dist/core/context-budget-v2-input-validation.js +79 -0
  43. package/dist/core/context-budget-v2-input-validation.js.map +1 -0
  44. package/dist/core/context-budget-v2-observability.d.ts +15 -0
  45. package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
  46. package/dist/core/context-budget-v2-observability.js +35 -0
  47. package/dist/core/context-budget-v2-observability.js.map +1 -0
  48. package/dist/core/context-budget-v2-planned-items.d.ts +5 -0
  49. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
  50. package/dist/core/context-budget-v2-planned-items.js +40 -0
  51. package/dist/core/context-budget-v2-planned-items.js.map +1 -0
  52. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  53. package/dist/core/context-budget-v2-planner.js +57 -38
  54. package/dist/core/context-budget-v2-planner.js.map +1 -1
  55. package/dist/core/context-budget-v2-selection.d.ts +19 -3
  56. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  57. package/dist/core/context-budget-v2-selection.js +35 -57
  58. package/dist/core/context-budget-v2-selection.js.map +1 -1
  59. package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
  60. package/dist/core/context-budget-v2-tiers.js +9 -42
  61. package/dist/core/context-budget-v2-tiers.js.map +1 -1
  62. package/dist/core/context-budget-v2-types.d.ts +8 -1
  63. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  64. package/dist/core/context-budget-v2-types.js.map +1 -1
  65. package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
  66. package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
  67. package/dist/core/extensions/bundled-virtual-modules.js +63 -0
  68. package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
  69. package/dist/core/extensions/loader.d.ts.map +1 -1
  70. package/dist/core/extensions/loader.js +2 -42
  71. package/dist/core/extensions/loader.js.map +1 -1
  72. package/dist/core/extensions/runner.d.ts +1 -0
  73. package/dist/core/extensions/runner.d.ts.map +1 -1
  74. package/dist/core/extensions/runner.js +6 -0
  75. package/dist/core/extensions/runner.js.map +1 -1
  76. package/dist/core/extensions/types.d.ts +10 -0
  77. package/dist/core/extensions/types.d.ts.map +1 -1
  78. package/dist/core/extensions/types.js.map +1 -1
  79. package/dist/core/index.d.ts +2 -1
  80. package/dist/core/index.d.ts.map +1 -1
  81. package/dist/core/index.js +2 -1
  82. package/dist/core/index.js.map +1 -1
  83. package/dist/core/loadout-runtime.d.ts +2 -8
  84. package/dist/core/loadout-runtime.d.ts.map +1 -1
  85. package/dist/core/loadout-runtime.js.map +1 -1
  86. package/dist/core/mcp/client.d.ts +27 -6
  87. package/dist/core/mcp/client.d.ts.map +1 -1
  88. package/dist/core/mcp/client.js +80 -20
  89. package/dist/core/mcp/client.js.map +1 -1
  90. package/dist/core/mcp/manager.d.ts +10 -1
  91. package/dist/core/mcp/manager.d.ts.map +1 -1
  92. package/dist/core/mcp/manager.js +68 -9
  93. package/dist/core/mcp/manager.js.map +1 -1
  94. package/dist/core/mcp/protocol.d.ts +9 -3
  95. package/dist/core/mcp/protocol.d.ts.map +1 -1
  96. package/dist/core/mcp/protocol.js +61 -15
  97. package/dist/core/mcp/protocol.js.map +1 -1
  98. package/dist/core/mcp/stdio-transport.d.ts +12 -1
  99. package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
  100. package/dist/core/mcp/stdio-transport.js +30 -2
  101. package/dist/core/mcp/stdio-transport.js.map +1 -1
  102. package/dist/core/mcp-descriptor-injection.d.ts +26 -0
  103. package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
  104. package/dist/core/mcp-descriptor-injection.js +25 -0
  105. package/dist/core/mcp-descriptor-injection.js.map +1 -0
  106. package/dist/core/mcp-public-presets.d.ts +1 -3
  107. package/dist/core/mcp-public-presets.d.ts.map +1 -1
  108. package/dist/core/mcp-public-presets.js +3 -15
  109. package/dist/core/mcp-public-presets.js.map +1 -1
  110. package/dist/core/model-registry.d.ts.map +1 -1
  111. package/dist/core/model-registry.js +24 -3
  112. package/dist/core/model-registry.js.map +1 -1
  113. package/dist/core/neo/catalog.d.ts +20 -0
  114. package/dist/core/neo/catalog.d.ts.map +1 -0
  115. package/dist/core/neo/catalog.js +50 -0
  116. package/dist/core/neo/catalog.js.map +1 -0
  117. package/dist/core/neo/setup.d.ts +3 -0
  118. package/dist/core/neo/setup.d.ts.map +1 -0
  119. package/dist/core/neo/setup.js +47 -0
  120. package/dist/core/neo/setup.js.map +1 -0
  121. package/dist/core/provider-default-models.d.ts +1 -0
  122. package/dist/core/provider-default-models.d.ts.map +1 -1
  123. package/dist/core/provider-default-models.js +1 -0
  124. package/dist/core/provider-default-models.js.map +1 -1
  125. package/dist/core/provider-display-names.d.ts.map +1 -1
  126. package/dist/core/provider-display-names.js +1 -0
  127. package/dist/core/provider-display-names.js.map +1 -1
  128. package/dist/core/provider-error-classification.d.ts +57 -0
  129. package/dist/core/provider-error-classification.d.ts.map +1 -0
  130. package/dist/core/provider-error-classification.js +103 -0
  131. package/dist/core/provider-error-classification.js.map +1 -0
  132. package/dist/core/provider-resilience.d.ts +10 -0
  133. package/dist/core/provider-resilience.d.ts.map +1 -1
  134. package/dist/core/provider-resilience.js +36 -3
  135. package/dist/core/provider-resilience.js.map +1 -1
  136. package/dist/core/provider-usage-commandcode.d.ts +9 -0
  137. package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
  138. package/dist/core/provider-usage-commandcode.js +198 -0
  139. package/dist/core/provider-usage-commandcode.js.map +1 -0
  140. package/dist/core/provider-usage-devin.d.ts +5 -3
  141. package/dist/core/provider-usage-devin.d.ts.map +1 -1
  142. package/dist/core/provider-usage-devin.js +15 -5
  143. package/dist/core/provider-usage-devin.js.map +1 -1
  144. package/dist/core/provider-usage-types.d.ts +1 -1
  145. package/dist/core/provider-usage-types.d.ts.map +1 -1
  146. package/dist/core/provider-usage-types.js.map +1 -1
  147. package/dist/core/provider-usage.d.ts.map +1 -1
  148. package/dist/core/provider-usage.js +6 -0
  149. package/dist/core/provider-usage.js.map +1 -1
  150. package/dist/core/resource-admission.d.ts +36 -0
  151. package/dist/core/resource-admission.d.ts.map +1 -1
  152. package/dist/core/resource-admission.js +59 -0
  153. package/dist/core/resource-admission.js.map +1 -1
  154. package/dist/core/resource-loader.d.ts.map +1 -1
  155. package/dist/core/resource-loader.js +2 -2
  156. package/dist/core/resource-loader.js.map +1 -1
  157. package/dist/core/run-usage-ledger.d.ts +47 -0
  158. package/dist/core/run-usage-ledger.d.ts.map +1 -0
  159. package/dist/core/run-usage-ledger.js +162 -0
  160. package/dist/core/run-usage-ledger.js.map +1 -0
  161. package/dist/core/run-usage-operation.d.ts +8 -0
  162. package/dist/core/run-usage-operation.d.ts.map +1 -0
  163. package/dist/core/run-usage-operation.js +12 -0
  164. package/dist/core/run-usage-operation.js.map +1 -0
  165. package/dist/core/sdk.d.ts.map +1 -1
  166. package/dist/core/sdk.js +5 -6
  167. package/dist/core/sdk.js.map +1 -1
  168. package/dist/core/session-failure-cause.d.ts.map +1 -1
  169. package/dist/core/session-failure-cause.js +14 -5
  170. package/dist/core/session-failure-cause.js.map +1 -1
  171. package/dist/core/session-prompt-lifecycle.d.ts +6 -0
  172. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
  173. package/dist/core/session-prompt-lifecycle.js +47 -2
  174. package/dist/core/session-prompt-lifecycle.js.map +1 -1
  175. package/dist/core/session-termination.d.ts.map +1 -1
  176. package/dist/core/session-termination.js +4 -2
  177. package/dist/core/session-termination.js.map +1 -1
  178. package/dist/core/subagent-lane-authority.d.ts +28 -0
  179. package/dist/core/subagent-lane-authority.d.ts.map +1 -0
  180. package/dist/core/subagent-lane-authority.js +119 -0
  181. package/dist/core/subagent-lane-authority.js.map +1 -0
  182. package/dist/core/subagent-lane-contract.d.ts +114 -0
  183. package/dist/core/subagent-lane-contract.d.ts.map +1 -0
  184. package/dist/core/subagent-lane-contract.js +18 -0
  185. package/dist/core/subagent-lane-contract.js.map +1 -0
  186. package/dist/core/subagent-lane-launcher.d.ts +3 -6
  187. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  188. package/dist/core/subagent-lane-launcher.js +53 -12
  189. package/dist/core/subagent-lane-launcher.js.map +1 -1
  190. package/dist/core/subagent-orchestration.d.ts +4 -17
  191. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  192. package/dist/core/subagent-orchestration.js.map +1 -1
  193. package/dist/core/verified-run/broker.d.ts.map +1 -1
  194. package/dist/core/verified-run/broker.js +4 -1
  195. package/dist/core/verified-run/broker.js.map +1 -1
  196. package/dist/core/workload-permit-pool.d.ts +1 -1
  197. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  198. package/dist/core/workload-permit-pool.js +3 -0
  199. package/dist/core/workload-permit-pool.js.map +1 -1
  200. package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
  201. package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
  202. package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
  203. package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
  204. package/dist/index.d.ts +3 -0
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +1 -0
  207. package/dist/index.js.map +1 -1
  208. package/dist/main.d.ts.map +1 -1
  209. package/dist/main.js +11 -18
  210. package/dist/main.js.map +1 -1
  211. package/dist/modes/acp/acp-agent.d.ts +24 -0
  212. package/dist/modes/acp/acp-agent.d.ts.map +1 -0
  213. package/dist/modes/acp/acp-agent.js +133 -0
  214. package/dist/modes/acp/acp-agent.js.map +1 -0
  215. package/dist/modes/acp/acp-mode.d.ts +4 -0
  216. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  217. package/dist/modes/acp/acp-mode.js +34 -0
  218. package/dist/modes/acp/acp-mode.js.map +1 -0
  219. package/dist/modes/acp/acp-session.d.ts +4 -0
  220. package/dist/modes/acp/acp-session.d.ts.map +1 -0
  221. package/dist/modes/acp/acp-session.js +76 -0
  222. package/dist/modes/acp/acp-session.js.map +1 -0
  223. package/dist/modes/acp/acp-transport.d.ts +5 -0
  224. package/dist/modes/acp/acp-transport.d.ts.map +1 -0
  225. package/dist/modes/acp/acp-transport.js +90 -0
  226. package/dist/modes/acp/acp-transport.js.map +1 -0
  227. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  228. package/dist/modes/interactive/interactive-mode.js +1 -0
  229. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  230. package/docs/audit-revalidation-2026-09-17.md +77 -0
  231. package/docs/devin-harness.md +33 -4
  232. package/docs/ecraf-normalization.md +83 -0
  233. package/docs/model-catalog-refresh.md +51 -1
  234. package/docs/models.md +1 -1
  235. package/docs/neo.md +134 -0
  236. package/docs/providers.md +72 -6
  237. package/docs/run-usage-ledger.md +56 -0
  238. package/docs/runtime-algorithms.md +44 -0
  239. package/docs/skills.md +1 -1
  240. package/docs/usage.md +2 -1
  241. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  242. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  243. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  244. package/examples/extensions/gondolin/package-lock.json +2 -2
  245. package/examples/extensions/gondolin/package.json +1 -1
  246. package/examples/extensions/sandbox/package-lock.json +2 -2
  247. package/examples/extensions/sandbox/package.json +1 -1
  248. package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
  249. package/examples/extensions/subagent/graph-result.ts +48 -0
  250. package/examples/extensions/subagent/index.ts +246 -111
  251. package/examples/extensions/subagent/managed-process-tree.ts +42 -0
  252. package/examples/extensions/subagent/managed-process.test.ts +24 -0
  253. package/examples/extensions/subagent/managed-process.ts +93 -112
  254. package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
  255. package/examples/extensions/subagent/subagent-stream.ts +161 -0
  256. package/examples/extensions/terminal-browser/README.md +54 -0
  257. package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
  258. package/examples/extensions/terminal-browser/bridge.ts +219 -0
  259. package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
  260. package/examples/extensions/terminal-browser/index.ts +215 -0
  261. package/examples/extensions/terminal-browser/placeholders.ts +49 -0
  262. package/examples/extensions/with-deps/package-lock.json +2 -2
  263. package/examples/extensions/with-deps/package.json +1 -1
  264. package/npm-shrinkwrap.json +18 -18
  265. package/package.json +8 -7
  266. package/resources/neo/skills/omk-browser/SKILL.md +32 -0
  267. package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
  268. package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
  269. package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
  270. package/resources/neo/skills/omk-research/SKILL.md +24 -0
  271. package/resources/neo/skills/omk-site/SKILL.md +28 -0
@@ -6,11 +6,12 @@ import {
6
6
  spawn,
7
7
  } from "node:child_process";
8
8
  import type { Readable } from "node:stream";
9
+ import { processGroupState, signalProcessTree } from "./managed-process-tree.ts";
9
10
 
10
11
  type ManagedChild = ChildProcessByStdio<null, Readable, Readable>;
11
12
  type ManagedSpawnOptions = SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>;
12
13
 
13
- export type ManagedProcessReason = "completed" | "cutoff" | "aborted" | "spawn-error";
14
+ export type ManagedProcessReason = "completed" | "cutoff" | "aborted" | "spawn-error" | "signal" | "callback-error";
14
15
 
15
16
  export interface ManagedProcessCleanup {
16
17
  readonly termSent: boolean;
@@ -25,6 +26,10 @@ export interface ManagedProcessResult {
25
26
  readonly reason: ManagedProcessReason;
26
27
  readonly elapsedMs: number;
27
28
  readonly cleanup: ManagedProcessCleanup;
29
+ /** Snapshot at response time. False means the owner must retain its reservation. */
30
+ readonly terminationObserved: boolean;
31
+ /** Resolves only on confirmed termination or confirmed failure to start, never on a timeout. */
32
+ readonly settlement: Promise<void>;
28
33
  readonly errorMessage?: string;
29
34
  }
30
35
 
@@ -41,17 +46,13 @@ export interface RunManagedProcessOptions {
41
46
  readonly spawnProcess?: (command: string, args: readonly string[], options: ManagedSpawnOptions) => ManagedChild;
42
47
  }
43
48
 
44
- const DEFAULT_TERMINATION_GRACE_MS = 1_500;
45
- const DEFAULT_FORCE_SETTLE_MS = 2_000;
46
-
47
49
  export async function runManagedProcess(options: RunManagedProcessOptions): Promise<ManagedProcessResult> {
48
- const startedAtMs = Date.now();
50
+ const startedAtMs = performance.now();
49
51
  const processGroup = process.platform !== "win32";
50
52
  if (options.signal?.aborted) return withoutChild("aborted", startedAtMs, processGroup);
51
- const spawnProcess = options.spawnProcess ?? defaultSpawn;
52
53
  let child: ManagedChild;
53
54
  try {
54
- child = spawnProcess(options.command, options.args, {
55
+ child = (options.spawnProcess ?? defaultSpawn)(options.command, options.args, {
55
56
  cwd: options.cwd,
56
57
  shell: false,
57
58
  stdio: ["ignore", "pipe", "pipe"],
@@ -67,99 +68,114 @@ export async function runManagedProcess(options: RunManagedProcessOptions): Prom
67
68
  );
68
69
  }
69
70
  const pid = child.pid ?? -1;
71
+ let confirmSettlement: () => void = () => {};
72
+ const settlement = new Promise<void>((resolve) => {
73
+ confirmSettlement = resolve;
74
+ });
70
75
 
71
76
  return await new Promise<ManagedProcessResult>((resolve) => {
72
- let settled = false;
77
+ let responded = false;
78
+ let terminated = false;
79
+ let closeObserved = false;
80
+ let exitCode: number | null = null;
81
+ let processSignal: NodeJS.Signals | null = null;
73
82
  let reason: ManagedProcessReason = "completed";
74
83
  let errorMessage: string | undefined;
75
84
  let termSent = false;
76
85
  let killSent = false;
77
86
  let cutoffTimer: NodeJS.Timeout | undefined;
78
87
  let escalationTimer: NodeJS.Timeout | undefined;
79
- let forceSettleTimer: NodeJS.Timeout | undefined;
80
-
81
- const cleanup = (): void => {
82
- if (cutoffTimer !== undefined) clearTimeout(cutoffTimer);
83
- if (escalationTimer !== undefined) clearTimeout(escalationTimer);
84
- if (forceSettleTimer !== undefined) clearTimeout(forceSettleTimer);
88
+ let responseTimer: NodeJS.Timeout | undefined;
89
+ let observationTimer: NodeJS.Timeout | undefined;
90
+
91
+ const respond = (): void => {
92
+ if (responded) return;
93
+ responded = true;
94
+ if (cutoffTimer) clearTimeout(cutoffTimer);
95
+ if (responseTimer) clearTimeout(responseTimer);
85
96
  options.signal?.removeEventListener("abort", onAbort);
86
- };
87
-
88
- const settle = (exitCode: number | null, processSignal: NodeJS.Signals | null): void => {
89
- if (settled) return;
90
- settled = true;
91
- cleanup();
92
97
  child.stdout.destroy();
93
98
  child.stderr.destroy();
94
99
  resolve({
95
100
  pid,
96
- exitCode: normalizeExitCode(exitCode, reason),
101
+ exitCode: normalizeExitCode(exitCode, reason, processSignal, terminated),
97
102
  signal: processSignal,
98
103
  reason,
99
- elapsedMs: Date.now() - startedAtMs,
104
+ elapsedMs: performance.now() - startedAtMs,
100
105
  cleanup: { termSent, killSent, processGroup },
106
+ terminationObserved: terminated,
107
+ settlement,
101
108
  ...(errorMessage === undefined ? {} : { errorMessage }),
102
109
  });
103
110
  };
104
-
105
- const requestTermination = (nextReason: "cutoff" | "aborted"): void => {
106
- if (settled || reason !== "completed") return;
107
- reason = nextReason;
111
+ const confirm = (): void => {
112
+ if (terminated) return;
113
+ terminated = true;
114
+ if (escalationTimer) clearTimeout(escalationTimer);
115
+ if (observationTimer) clearTimeout(observationTimer);
116
+ confirmSettlement();
117
+ respond();
118
+ };
119
+ const observe = (): void => {
120
+ if (terminated || !closeObserved) return;
121
+ if (processGroupState(pid) === "gone") {
122
+ confirm();
123
+ return;
124
+ }
125
+ // After the cleanup deadline only observe: never send late signals to a reused PID.
126
+ observationTimer = setTimeout(observe, 25);
127
+ observationTimer.unref();
128
+ };
129
+ const startCleanup = (): void => {
130
+ if (terminated || termSent) return;
108
131
  termSent = true;
109
132
  signalProcessTree(child, "SIGTERM", processGroup);
110
- const graceMs = Math.max(0, options.terminationGraceMs ?? DEFAULT_TERMINATION_GRACE_MS);
133
+ const graceMs = Math.max(0, options.terminationGraceMs ?? 1_500);
111
134
  escalationTimer = setTimeout(() => {
112
- if (settled) return;
135
+ if (terminated) return;
113
136
  killSent = true;
114
137
  signalProcessTree(child, "SIGKILL", processGroup);
115
138
  }, graceMs);
116
- forceSettleTimer = setTimeout(
117
- () => settle(null, null),
118
- graceMs + Math.max(1, options.forceSettleMs ?? DEFAULT_FORCE_SETTLE_MS),
119
- );
139
+ responseTimer = setTimeout(respond, graceMs + Math.max(1, options.forceSettleMs ?? 2_000));
140
+ };
141
+ const requestTermination = (nextReason: ManagedProcessReason): void => {
142
+ if (responded || terminated) return;
143
+ if (reason === "completed") reason = nextReason;
144
+ startCleanup();
120
145
  };
121
-
122
146
  function onAbort(): void {
123
147
  requestTermination("aborted");
124
148
  }
125
-
149
+ const deliver = (callback: ((chunk: string) => void) | undefined, chunk: string): void => {
150
+ if (responded || reason === "callback-error") return;
151
+ try {
152
+ callback?.(chunk);
153
+ } catch {
154
+ errorMessage = "process.output_callback_failed";
155
+ requestTermination("callback-error");
156
+ }
157
+ };
126
158
  child.stdout.setEncoding("utf8");
127
159
  child.stderr.setEncoding("utf8");
128
- child.stdout.on("data", (chunk: string) => options.onStdout?.(chunk));
129
- child.stderr.on("data", (chunk: string) => options.onStderr?.(chunk));
130
- child.once("error", (error: Error) => {
131
- if (reason === "completed") reason = "spawn-error";
160
+ child.stdout.on("data", (chunk: string) => deliver(options.onStdout, chunk));
161
+ child.stderr.on("data", (chunk: string) => deliver(options.onStderr, chunk));
162
+ child.on("error", (error: Error) => {
163
+ if (terminated) return;
132
164
  errorMessage = error.message;
133
- settle(1, null);
165
+ if (pid < 1) {
166
+ reason = "spawn-error";
167
+ confirm();
168
+ } else requestTermination("spawn-error");
134
169
  });
135
- child.once("close", (code: number | null, processSignal: NodeJS.Signals | null) => {
136
- if ((reason === "cutoff" || reason === "aborted") && termSent && !killSent) {
137
- killSent = true;
138
- signalProcessTree(child, "SIGKILL", processGroup);
139
- settle(code, processSignal);
140
- return;
141
- }
142
- if (reason === "completed" && processGroupExists(pid)) {
143
- if (cutoffTimer !== undefined) clearTimeout(cutoffTimer);
144
- options.signal?.removeEventListener("abort", onAbort);
145
- termSent = true;
146
- signalProcessTree(child, "SIGTERM", processGroup);
147
- const graceMs = Math.max(0, options.terminationGraceMs ?? DEFAULT_TERMINATION_GRACE_MS);
148
- escalationTimer = setTimeout(() => {
149
- if (processGroupExists(pid)) {
150
- killSent = true;
151
- signalProcessTree(child, "SIGKILL", processGroup);
152
- }
153
- settle(code, processSignal);
154
- }, graceMs);
155
- return;
156
- }
157
- settle(code, processSignal);
170
+ child.once("close", (code: number | null, signal: NodeJS.Signals | null) => {
171
+ closeObserved = true;
172
+ exitCode = code;
173
+ processSignal = signal;
174
+ if (reason === "completed" && signal !== null) reason = "signal";
175
+ if (processGroupState(pid) !== "gone" && !responded) startCleanup();
176
+ observe();
158
177
  });
159
-
160
- if (options.cutoffMs > 0) {
161
- cutoffTimer = setTimeout(() => requestTermination("cutoff"), options.cutoffMs);
162
- }
178
+ if (options.cutoffMs > 0) cutoffTimer = setTimeout(() => requestTermination("cutoff"), options.cutoffMs);
163
179
  if (options.signal?.aborted) onAbort();
164
180
  else options.signal?.addEventListener("abort", onAbort, { once: true });
165
181
  });
@@ -176,8 +192,10 @@ function withoutChild(
176
192
  exitCode: reason === "aborted" ? 130 : 1,
177
193
  signal: null,
178
194
  reason,
179
- elapsedMs: Date.now() - startedAtMs,
195
+ elapsedMs: performance.now() - startedAtMs,
180
196
  cleanup: { termSent: false, killSent: false, processGroup },
197
+ terminationObserved: true,
198
+ settlement: Promise.resolve(),
181
199
  ...(errorMessage === undefined ? {} : { errorMessage }),
182
200
  };
183
201
  }
@@ -186,51 +204,14 @@ function defaultSpawn(command: string, args: readonly string[], options: Managed
186
204
  return spawn(command, [...args], options);
187
205
  }
188
206
 
189
- function processGroupExists(pid: number): boolean {
190
- if (process.platform === "win32" || pid < 1) return false;
191
- try {
192
- process.kill(-pid, 0);
193
- return true;
194
- } catch {
195
- return false;
196
- }
197
- }
198
-
199
- function signalProcessTree(child: ManagedChild, signal: "SIGTERM" | "SIGKILL", processGroup: boolean): void {
200
- const pid = child.pid;
201
- if (pid === undefined) return;
202
- if (process.platform === "win32") {
203
- if (signal === "SIGKILL") {
204
- try {
205
- spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
206
- detached: true,
207
- stdio: "ignore",
208
- windowsHide: true,
209
- }).unref();
210
- return;
211
- } catch {
212
- child.kill(signal);
213
- return;
214
- }
215
- }
216
- child.kill(signal);
217
- return;
218
- }
219
- try {
220
- process.kill(processGroup ? -pid : pid, signal);
221
- } catch {
222
- try {
223
- child.kill(signal);
224
- } catch {
225
- return;
226
- }
227
- }
228
- }
229
-
230
- function normalizeExitCode(exitCode: number | null, reason: ManagedProcessReason): number {
231
- if (exitCode !== null) return exitCode;
232
- if (reason === "completed") return 0;
207
+ function normalizeExitCode(
208
+ exitCode: number | null,
209
+ reason: ManagedProcessReason,
210
+ signal: NodeJS.Signals | null,
211
+ terminated: boolean,
212
+ ): number {
233
213
  if (reason === "cutoff") return 124;
234
214
  if (reason === "aborted") return 130;
235
- return 1;
215
+ if (!terminated || signal !== null || reason !== "completed") return exitCode || 1;
216
+ return exitCode ?? 1;
236
217
  }
@@ -1,6 +1,6 @@
1
1
  import type { Message } from "omk-ai";
2
2
  import type { AgentCheckpoint } from "./checkpoint-runtime.ts";
3
- import type { ManagedProcessCleanup, ManagedProcessReason } from "./managed-process.ts";
3
+ import type { ManagedProcessCleanup, ManagedProcessReason, ManagedProcessResult } from "./managed-process.ts";
4
4
 
5
5
  export interface UsageStats {
6
6
  input: number;
@@ -47,6 +47,20 @@ export interface AgentDeadlineMetadata {
47
47
  }
48
48
 
49
49
  export interface SingleResult {
50
+ nodeId?: string;
51
+ attemptId?: string;
52
+ dependencyDigests?: Readonly<Record<string, string>>;
53
+ process?: ManagedProcessResult;
54
+ stream?: {
55
+ stdoutBytes: number;
56
+ stderrBytes: number;
57
+ events: number;
58
+ messages: number;
59
+ stdoutDigest: string;
60
+ stderrDigest: string;
61
+ usageUnknown: boolean;
62
+ failure?: string;
63
+ };
50
64
  agent: string;
51
65
  agentSource: "user" | "project" | "unknown";
52
66
  task: string;
@@ -68,6 +82,8 @@ export interface SubagentAttemptResult {
68
82
  readonly reason: ManagedProcessReason;
69
83
  readonly elapsedMs: number;
70
84
  readonly cleanup: ManagedProcessCleanup;
85
+ readonly terminationObserved?: boolean;
86
+ readonly settlement?: Promise<void>;
71
87
  };
72
88
  }
73
89
 
@@ -0,0 +1,161 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { Message } from "omk-ai";
3
+ import type { SingleResult } from "./subagent-runtime-types.ts";
4
+
5
+ export const SUBAGENT_OUTPUT_LIMITS = {
6
+ stdoutBytes: 8 * 1024 * 1024,
7
+ stderrBytes: 256 * 1024,
8
+ lineBytes: 1024 * 1024,
9
+ events: 20_000,
10
+ messages: 1024,
11
+ } as const;
12
+
13
+ function record(value: unknown): value is Record<string, unknown> {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+
17
+ function nonnegative(value: unknown): value is number {
18
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
19
+ }
20
+
21
+ /** Bounds are per process attempt, including ignored events and incomplete lines. */
22
+ export function createSubagentStream(result: SingleResult, onMessage: () => void) {
23
+ let pending = "";
24
+ let terminal = false;
25
+ let assistantSeen = false;
26
+ const stdoutHash = createHash("sha256");
27
+ const stderrHash = createHash("sha256");
28
+ const stats = { stdoutBytes: 0, stderrBytes: 0, events: 0, messages: 0, usageUnknown: false };
29
+ let failure: string | undefined;
30
+ const fail = (reason: string): never => {
31
+ failure ??= reason;
32
+ throw new Error(failure);
33
+ };
34
+ const processLine = (line: string) => {
35
+ if (!line.trim()) return;
36
+ if (++stats.events > SUBAGENT_OUTPUT_LIMITS.events) fail("subagent.output.events_limit");
37
+ let event: unknown;
38
+ try {
39
+ event = JSON.parse(line);
40
+ } catch {
41
+ fail("subagent.stream.invalid_json");
42
+ }
43
+ if (!record(event) || typeof event.type !== "string") return fail("subagent.stream.invalid_event");
44
+ if (event.type === "prompt_settled") {
45
+ if (terminal) fail("subagent.stream.duplicate_terminal");
46
+ terminal = true;
47
+ if (event.outcome !== "completed") fail("subagent.stream.prompt_not_completed");
48
+ return;
49
+ }
50
+ if (event.type !== "message_end" && event.type !== "tool_result_end") return;
51
+ if (terminal) fail("subagent.stream.message_after_terminal");
52
+ const message = event.message;
53
+ if (!record(message) || typeof message.role !== "string") return fail("subagent.stream.invalid_message");
54
+ // The CLI also emits custom session and user messages, which are not assistant receipts.
55
+ if (message.role !== "assistant" && message.role !== "toolResult") return;
56
+ if (!Array.isArray(message.content)) return fail("subagent.stream.invalid_content");
57
+ for (const part of message.content) {
58
+ if (!record(part)) return fail("subagent.stream.invalid_content");
59
+ if (part.type === "text" || part.type === "thinking") {
60
+ if (typeof part[part.type] !== "string") fail("subagent.stream.invalid_content");
61
+ } else if (part.type === "toolCall") {
62
+ if (typeof part.name !== "string" || typeof part.id !== "string" || !record(part.arguments))
63
+ fail("subagent.stream.invalid_tool_call");
64
+ } else if (part.type === "image") {
65
+ if (typeof part.data !== "string" || typeof part.mimeType !== "string")
66
+ fail("subagent.stream.invalid_image");
67
+ } else fail("subagent.stream.invalid_content");
68
+ }
69
+ if (stats.messages >= SUBAGENT_OUTPUT_LIMITS.messages) fail("subagent.output.messages_limit");
70
+ if (message.role === "assistant") {
71
+ if (message.model !== undefined && typeof message.model !== "string") fail("subagent.stream.invalid_model");
72
+ if (message.errorMessage !== undefined && typeof message.errorMessage !== "string")
73
+ fail("subagent.stream.invalid_error");
74
+ if (
75
+ message.stopReason !== undefined &&
76
+ !["stop", "length", "toolUse", "error", "aborted"].includes(String(message.stopReason))
77
+ )
78
+ fail("subagent.stream.invalid_stop_reason");
79
+ const usage = message.usage;
80
+ if (usage === undefined) stats.usageUnknown = true;
81
+ else {
82
+ if (!record(usage) || !record(usage.cost)) return fail("subagent.stream.invalid_usage");
83
+ for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"] as const) {
84
+ if (!nonnegative(usage[key])) fail("subagent.stream.invalid_usage");
85
+ }
86
+ for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) {
87
+ if (!nonnegative(usage.cost[key])) fail("subagent.stream.invalid_usage");
88
+ }
89
+ }
90
+ assistantSeen = true;
91
+ }
92
+ const msg = message as unknown as Message;
93
+ result.messages.push(msg);
94
+ stats.messages++;
95
+ if (msg.role === "assistant") {
96
+ result.usage.turns++;
97
+ if (msg.usage) {
98
+ result.usage.input += msg.usage.input;
99
+ result.usage.output += msg.usage.output;
100
+ result.usage.cacheRead += msg.usage.cacheRead;
101
+ result.usage.cacheWrite += msg.usage.cacheWrite;
102
+ result.usage.cost += msg.usage.cost.total;
103
+ result.usage.contextTokens = Math.max(result.usage.contextTokens, msg.usage.totalTokens);
104
+ if (Object.values(result.usage).some((value) => !nonnegative(value)))
105
+ fail("subagent.stream.usage_overflow");
106
+ }
107
+ result.model ??= msg.model;
108
+ if (msg.stopReason) result.stopReason = msg.stopReason;
109
+ if (msg.errorMessage) result.errorMessage = msg.errorMessage;
110
+ }
111
+ onMessage();
112
+ };
113
+ return {
114
+ stdout(chunk: string) {
115
+ if (failure) throw new Error(failure);
116
+ stats.stdoutBytes += Buffer.byteLength(chunk, "utf8");
117
+ stdoutHash.update(chunk);
118
+ if (stats.stdoutBytes > SUBAGENT_OUTPUT_LIMITS.stdoutBytes) fail("subagent.output.stdout_limit");
119
+ let start = 0;
120
+ while (start < chunk.length) {
121
+ const newline = chunk.indexOf("\n", start);
122
+ const fragment = chunk.slice(start, newline < 0 ? undefined : newline);
123
+ if (
124
+ Buffer.byteLength(pending, "utf8") + Buffer.byteLength(fragment, "utf8") >
125
+ SUBAGENT_OUTPUT_LIMITS.lineBytes
126
+ )
127
+ fail("subagent.output.line_limit");
128
+ pending += fragment;
129
+ if (newline < 0) break;
130
+ const line = pending;
131
+ pending = "";
132
+ processLine(line);
133
+ start = newline + 1;
134
+ }
135
+ },
136
+ stderr(chunk: string) {
137
+ if (failure) throw new Error(failure);
138
+ stats.stderrBytes += Buffer.byteLength(chunk, "utf8");
139
+ stderrHash.update(chunk);
140
+ if (stats.stderrBytes > SUBAGENT_OUTPUT_LIMITS.stderrBytes) fail("subagent.output.stderr_limit");
141
+ result.stderr += chunk;
142
+ },
143
+ finish(requireReceipt: boolean) {
144
+ try {
145
+ if (!failure && pending.trim()) processLine(pending);
146
+ if (!failure && requireReceipt && !assistantSeen) fail("subagent.stream.missing_terminal_message");
147
+ } catch {
148
+ failure ??= "subagent.stream.callback_error";
149
+ } finally {
150
+ pending = "";
151
+ result.stream = {
152
+ ...stats,
153
+ stdoutDigest: stdoutHash.digest("hex"),
154
+ stderrDigest: stderrHash.digest("hex"),
155
+ ...(failure ? { failure } : {}),
156
+ };
157
+ }
158
+ return failure;
159
+ },
160
+ };
161
+ }
@@ -0,0 +1,54 @@
1
+ # terminal-browser extension
2
+
3
+ Run a real browser inside the OMK terminal UI.
4
+
5
+ Port of the [terminal-browser Claude Code plugin](https://github.com/zenbu-labs/terminal-browser/tree/main/claude-code-plugin)
6
+ (MIT, zenbu-labs). OMK has no function-hooks/Pane API, so the pane is a
7
+ `ctx.ui.custom` overlay that renders kitty unicode placeholders; the
8
+ `terminal-browser claude-bridge` HTTP API is reused unchanged.
9
+
10
+ ## Requirements
11
+
12
+ - `terminal-browser` CLI — `curl -fsSL https://terminal-browser.sh/install | bash` or `brew install terminal-browser`
13
+ - A terminal with kitty graphics protocol **and unicode placeholders**: ghostty, kitty, cmux, supacode, other libghostty terminals
14
+ - Interactive TUI mode (`omk`), not `-p`/RPC/JSON
15
+ - Not tmux/screen (multiplexers rewrite graphics escapes)
16
+
17
+ ## Usage
18
+
19
+ ```sh
20
+ omk --extension ./examples/extensions/terminal-browser
21
+ # or copy this directory to ~/.omk/agent/extensions/terminal-browser for auto-load
22
+ ```
23
+
24
+ | Command | Action |
25
+ | --- | --- |
26
+ | `/browser example.com` | open pane at URL (https assumed) |
27
+ | `/browser http://localhost:3000` | open pane at local dev server |
28
+ | `/browser` | toggle close when open; open start page otherwise |
29
+ | `/browser close` | close pane |
30
+ | `ctrl+q` (in pane) | close pane |
31
+
32
+ LLM tools: `terminal_browser_open { url? }`, `terminal_browser_close`.
33
+ In-page control: `terminal-browser action ...` (agent-browser-compatible CLI).
34
+ "Send to agent" (ctrl+g in the browser) inserts selected text into the OMK editor.
35
+
36
+ ## How it works
37
+
38
+ 1. `terminal-browser claude-bridge launch` spawns a detached bridge child that
39
+ reports `{ port, token }` and serves HTTP on `127.0.0.1` (`bridge.ts`).
40
+ 2. The bridge runs `terminal-browser open` with `PIXEL_EMBED`/`PIXEL_TTY`,
41
+ receives pixel frames, and writes kitty graphics to the TTY.
42
+ 3. The overlay component (`browser-surface.ts`) prints unicode placeholder cells;
43
+ the terminal composites the browser pixels over them.
44
+ 4. Input: SGR mouse + keys are translated to bridge `/input` events
45
+ (cell coordinates — the bridge scales to pixels).
46
+ 5. Polling `/state` at 250ms while open (1.2s idle) refreshes title/URL/placed
47
+ and drains the agent-text inbox into the editor.
48
+
49
+ ## Caveats
50
+
51
+ Upstream plugin caveats apply: 50fps cap, occasional redraw glitches (select
52
+ text to force repaint), mouse coordinates are cell-granular, and the bridge
53
+ makes loopback HTTP requests (OS may prompt for local-network access).
54
+ Override the binary with `TERMINAL_BROWSER_COMMAND`.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Wire types for the terminal-browser claude-bridge HTTP API.
3
+ * Ported from https://github.com/zenbu-labs/terminal-browser (MIT, zenbu-labs).
4
+ * Kept dependency-free: extensions cannot assume node_modules availability.
5
+ */
6
+
7
+ export type Placed = { imageId: number; cols: number; rows: number };
8
+
9
+ export type BridgeState = {
10
+ placed: Placed | null;
11
+ title: string;
12
+ url: string | null;
13
+ alive: boolean;
14
+ error: string | null;
15
+ inbox: number;
16
+ };
17
+
18
+ export type LaunchReport = { port: number; token: string } | { error: string; code: "tty" | "start" };
19
+
20
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
21
+
22
+ export const isBridgeState = (value: unknown): value is BridgeState =>
23
+ isRecord(value) && typeof value.alive === "boolean" && "placed" in value;
24
+
25
+ export const isLaunchReport = (value: unknown): value is LaunchReport =>
26
+ isRecord(value) && (typeof value.port === "number" || typeof value.error === "string");
27
+
28
+ export const takenTexts = (value: unknown): string[] =>
29
+ isRecord(value) && Array.isArray(value.texts) ? value.texts.filter((t): t is string => typeof t === "string") : [];