@asterxsk/kiln 0.1.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 (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/agent/AGENTS.md +67 -0
  4. package/agent/README.md +5 -0
  5. package/agent/extensions/AGENTS.md +68 -0
  6. package/agent/extensions/ask-user/index.ts +418 -0
  7. package/agent/extensions/ask-user/install.ps1 +23 -0
  8. package/agent/extensions/ask-user/install.sh +21 -0
  9. package/agent/extensions/ask-user/package-lock.json +769 -0
  10. package/agent/extensions/ask-user/package.json +19 -0
  11. package/agent/extensions/ask-user/prompt.ts +45 -0
  12. package/agent/extensions/ask-user/tsconfig.json +7 -0
  13. package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -0
  14. package/agent/extensions/background-terminals/index.ts +627 -0
  15. package/agent/extensions/background-terminals/install.ps1 +23 -0
  16. package/agent/extensions/background-terminals/install.sh +21 -0
  17. package/agent/extensions/background-terminals/manager.test.ts +735 -0
  18. package/agent/extensions/background-terminals/output.test.ts +109 -0
  19. package/agent/extensions/background-terminals/package-lock.json +769 -0
  20. package/agent/extensions/background-terminals/package.json +17 -0
  21. package/agent/extensions/background-terminals/prompt.test.ts +125 -0
  22. package/agent/extensions/background-terminals/ps.test.ts +82 -0
  23. package/agent/extensions/background-terminals/result-delivery.test.ts +44 -0
  24. package/agent/extensions/background-terminals/src/domain.ts +87 -0
  25. package/agent/extensions/background-terminals/src/manager.ts +907 -0
  26. package/agent/extensions/background-terminals/src/output.ts +84 -0
  27. package/agent/extensions/background-terminals/src/prompt.ts +142 -0
  28. package/agent/extensions/background-terminals/src/result-delivery.ts +27 -0
  29. package/agent/extensions/background-terminals/src/runtime.ts +36 -0
  30. package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -0
  31. package/agent/extensions/background-terminals/src/ui/ps.ts +621 -0
  32. package/agent/extensions/background-terminals/tsconfig.json +7 -0
  33. package/agent/extensions/destructive/README.md +31 -0
  34. package/agent/extensions/destructive/index.ts +88 -0
  35. package/agent/extensions/destructive/install.ps1 +23 -0
  36. package/agent/extensions/destructive/install.sh +21 -0
  37. package/agent/extensions/file-search/index.spec.ts +443 -0
  38. package/agent/extensions/file-search/index.ts +459 -0
  39. package/agent/extensions/file-search/install.ps1 +23 -0
  40. package/agent/extensions/file-search/install.sh +21 -0
  41. package/agent/extensions/file-search/package-lock.json +2253 -0
  42. package/agent/extensions/file-search/package.json +23 -0
  43. package/agent/extensions/file-search/src/args.ts +122 -0
  44. package/agent/extensions/file-search/src/binaries.ts +422 -0
  45. package/agent/extensions/file-search/src/output.ts +126 -0
  46. package/agent/extensions/file-search/src/process.ts +146 -0
  47. package/agent/extensions/file-search/src/prompt.ts +52 -0
  48. package/agent/extensions/file-search/tsconfig.json +7 -0
  49. package/agent/extensions/goal/README.md +50 -0
  50. package/agent/extensions/goal/index.ts +155 -0
  51. package/agent/extensions/goal/install.ps1 +23 -0
  52. package/agent/extensions/goal/install.sh +21 -0
  53. package/agent/extensions/modelconf/PLAN.md +915 -0
  54. package/agent/extensions/modelconf/README.md +66 -0
  55. package/agent/extensions/modelconf/index.ts +296 -0
  56. package/agent/extensions/modelconf/install.ps1 +23 -0
  57. package/agent/extensions/modelconf/install.sh +21 -0
  58. package/agent/extensions/modelconf/package-lock.json +1809 -0
  59. package/agent/extensions/modelconf/package.json +13 -0
  60. package/agent/extensions/modelconf/src/fuzzy.ts +26 -0
  61. package/agent/extensions/modelconf/src/glob.ts +13 -0
  62. package/agent/extensions/modelconf/src/persistence.test.ts +46 -0
  63. package/agent/extensions/modelconf/src/persistence.ts +164 -0
  64. package/agent/extensions/modelconf/src/ui/ModelConfView.test.ts +75 -0
  65. package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -0
  66. package/agent/extensions/modelconf/tsconfig.json +15 -0
  67. package/agent/extensions/pi-web-access/CHANGELOG.md +690 -0
  68. package/agent/extensions/pi-web-access/LICENSE +21 -0
  69. package/agent/extensions/pi-web-access/README.md +470 -0
  70. package/agent/extensions/pi-web-access/SECURITY.md +5 -0
  71. package/agent/extensions/pi-web-access/activity.ts +101 -0
  72. package/agent/extensions/pi-web-access/auth-fetch.ts +148 -0
  73. package/agent/extensions/pi-web-access/banner.png +0 -0
  74. package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -0
  75. package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -0
  76. package/agent/extensions/pi-web-access/content-find.ts +139 -0
  77. package/agent/extensions/pi-web-access/credential-source.ts +191 -0
  78. package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -0
  79. package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -0
  80. package/agent/extensions/pi-web-access/declared-web-links.ts +173 -0
  81. package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -0
  82. package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -0
  83. package/agent/extensions/pi-web-access/exa.ts +526 -0
  84. package/agent/extensions/pi-web-access/extract.ts +1196 -0
  85. package/agent/extensions/pi-web-access/feature-config.ts +29 -0
  86. package/agent/extensions/pi-web-access/fetch-params.ts +111 -0
  87. package/agent/extensions/pi-web-access/gemini-adc.ts +298 -0
  88. package/agent/extensions/pi-web-access/gemini-api.ts +353 -0
  89. package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -0
  90. package/agent/extensions/pi-web-access/gemini-search.ts +21 -0
  91. package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -0
  92. package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -0
  93. package/agent/extensions/pi-web-access/gemini-web.ts +487 -0
  94. package/agent/extensions/pi-web-access/github-api.ts +197 -0
  95. package/agent/extensions/pi-web-access/github-extract.ts +746 -0
  96. package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -0
  97. package/agent/extensions/pi-web-access/index.ts +1737 -0
  98. package/agent/extensions/pi-web-access/package-lock.json +5808 -0
  99. package/agent/extensions/pi-web-access/package.json +64 -0
  100. package/agent/extensions/pi-web-access/page-query.ts +96 -0
  101. package/agent/extensions/pi-web-access/pdf-extract.ts +409 -0
  102. package/agent/extensions/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  103. package/agent/extensions/pi-web-access/promise-try.d.ts +7 -0
  104. package/agent/extensions/pi-web-access/query-rewrite.ts +51 -0
  105. package/agent/extensions/pi-web-access/render-search-error.ts +170 -0
  106. package/agent/extensions/pi-web-access/rsc-extract.ts +338 -0
  107. package/agent/extensions/pi-web-access/search-types.ts +20 -0
  108. package/agent/extensions/pi-web-access/source-check.ts +282 -0
  109. package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -0
  110. package/agent/extensions/pi-web-access/storage.ts +521 -0
  111. package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -0
  112. package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -0
  113. package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -0
  114. package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -0
  115. package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -0
  116. package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -0
  117. package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -0
  118. package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -0
  119. package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -0
  120. package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -0
  121. package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -0
  122. package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -0
  123. package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -0
  124. package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -0
  125. package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -0
  126. package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -0
  127. package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -0
  128. package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -0
  129. package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -0
  130. package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -0
  131. package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -0
  132. package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -0
  133. package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -0
  134. package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -0
  135. package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -0
  136. package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -0
  137. package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -0
  138. package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -0
  139. package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -0
  140. package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -0
  141. package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -0
  142. package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -0
  143. package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -0
  144. package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -0
  145. package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -0
  146. package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -0
  147. package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -0
  148. package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -0
  149. package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -0
  150. package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -0
  151. package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -0
  152. package/agent/extensions/pi-web-access/test/summary-model-scope.test.mjs +106 -0
  153. package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -0
  154. package/agent/extensions/pi-web-access/test/web-search-answer-render.test.mjs +66 -0
  155. package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -0
  156. package/agent/extensions/pi-web-access/tsconfig.json +11 -0
  157. package/agent/extensions/pi-web-access/utils.ts +451 -0
  158. package/agent/extensions/pi-web-access/video-extract.ts +392 -0
  159. package/agent/extensions/pi-web-access/youtube-extract.ts +328 -0
  160. package/agent/extensions/shared/activity-status.ts +31 -0
  161. package/agent/extensions/shared/child-session.test.ts +270 -0
  162. package/agent/extensions/shared/child-session.ts +148 -0
  163. package/agent/extensions/shared/context-utilization.test.ts +48 -0
  164. package/agent/extensions/shared/context-utilization.ts +47 -0
  165. package/agent/extensions/shared/dashboard-state.ts +99 -0
  166. package/agent/extensions/shared/install.ps1 +23 -0
  167. package/agent/extensions/shared/install.sh +21 -0
  168. package/agent/extensions/shared/tool-call-timeout.test.ts +117 -0
  169. package/agent/extensions/shared/tool-call-timeout.ts +104 -0
  170. package/agent/extensions/skillsconf/README.md +74 -0
  171. package/agent/extensions/skillsconf/index.ts +92 -0
  172. package/agent/extensions/skillsconf/install.ps1 +23 -0
  173. package/agent/extensions/skillsconf/install.sh +21 -0
  174. package/agent/extensions/skillsconf/package-lock.json +1809 -0
  175. package/agent/extensions/skillsconf/package.json +18 -0
  176. package/agent/extensions/skillsconf/src/delete-skill.test.ts +64 -0
  177. package/agent/extensions/skillsconf/src/delete-skill.ts +45 -0
  178. package/agent/extensions/skillsconf/src/filter.test.ts +80 -0
  179. package/agent/extensions/skillsconf/src/filter.ts +74 -0
  180. package/agent/extensions/skillsconf/src/fuzzy.ts +26 -0
  181. package/agent/extensions/skillsconf/src/persistence.test.ts +72 -0
  182. package/agent/extensions/skillsconf/src/persistence.ts +169 -0
  183. package/agent/extensions/skillsconf/src/ui/SkillConfView.test.ts +337 -0
  184. package/agent/extensions/skillsconf/src/ui/SkillConfView.ts +758 -0
  185. package/agent/extensions/skillsconf/src/ui/text-input.ts +91 -0
  186. package/agent/extensions/skillsconf/src/ui/tui-helpers.ts +144 -0
  187. package/agent/extensions/skillsconf/tsconfig.json +15 -0
  188. package/agent/extensions/status line/index.ts +282 -0
  189. package/agent/extensions/status line/install.ps1 +23 -0
  190. package/agent/extensions/status line/install.sh +21 -0
  191. package/agent/extensions/subagents/by-the-way.test.ts +29 -0
  192. package/agent/extensions/subagents/claude.test.ts +119 -0
  193. package/agent/extensions/subagents/codex.test.ts +102 -0
  194. package/agent/extensions/subagents/context-usage.test.ts +107 -0
  195. package/agent/extensions/subagents/docs/design-plan.md +568 -0
  196. package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -0
  197. package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -0
  198. package/agent/extensions/subagents/index.ts +779 -0
  199. package/agent/extensions/subagents/install.ps1 +23 -0
  200. package/agent/extensions/subagents/install.sh +21 -0
  201. package/agent/extensions/subagents/manager.test.ts +276 -0
  202. package/agent/extensions/subagents/package-lock.json +2244 -0
  203. package/agent/extensions/subagents/package.json +19 -0
  204. package/agent/extensions/subagents/result-delivery.test.ts +27 -0
  205. package/agent/extensions/subagents/src/backend.ts +73 -0
  206. package/agent/extensions/subagents/src/backends/claude.ts +701 -0
  207. package/agent/extensions/subagents/src/backends/codex.ts +1060 -0
  208. package/agent/extensions/subagents/src/backends/pi.ts +575 -0
  209. package/agent/extensions/subagents/src/backends/stub.ts +300 -0
  210. package/agent/extensions/subagents/src/by-the-way.ts +21 -0
  211. package/agent/extensions/subagents/src/domain.ts +253 -0
  212. package/agent/extensions/subagents/src/format.ts +74 -0
  213. package/agent/extensions/subagents/src/manager.ts +736 -0
  214. package/agent/extensions/subagents/src/prompt.ts +92 -0
  215. package/agent/extensions/subagents/src/result-delivery.ts +20 -0
  216. package/agent/extensions/subagents/src/runtime.ts +53 -0
  217. package/agent/extensions/subagents/src/ui/takeover.ts +583 -0
  218. package/agent/extensions/subagents/src/ui/transcript.ts +201 -0
  219. package/agent/extensions/subagents/takeover.test.ts +29 -0
  220. package/agent/extensions/subagents/tsconfig.json +7 -0
  221. package/agent/extensions/taste/index.ts +443 -0
  222. package/agent/extensions/taste/install.ps1 +23 -0
  223. package/agent/extensions/taste/install.sh +21 -0
  224. package/agent/extensions/todo/AGENTS.md +38 -0
  225. package/agent/extensions/todo/LICENSE +21 -0
  226. package/agent/extensions/todo/config.ts +55 -0
  227. package/agent/extensions/todo/index.ts +151 -0
  228. package/agent/extensions/todo/install.ps1 +23 -0
  229. package/agent/extensions/todo/install.sh +21 -0
  230. package/agent/extensions/todo/locales/de.json +17 -0
  231. package/agent/extensions/todo/locales/en.json +15 -0
  232. package/agent/extensions/todo/locales/es.json +17 -0
  233. package/agent/extensions/todo/locales/fr.json +17 -0
  234. package/agent/extensions/todo/locales/pt-BR.json +17 -0
  235. package/agent/extensions/todo/locales/pt.json +17 -0
  236. package/agent/extensions/todo/locales/ru.json +17 -0
  237. package/agent/extensions/todo/locales/uk.json +17 -0
  238. package/agent/extensions/todo/locales/zh.json +17 -0
  239. package/agent/extensions/todo/package-lock.json +3358 -0
  240. package/agent/extensions/todo/package.json +67 -0
  241. package/agent/extensions/todo/state/i18n-bridge.ts +64 -0
  242. package/agent/extensions/todo/state/invariants.ts +20 -0
  243. package/agent/extensions/todo/state/replay.ts +38 -0
  244. package/agent/extensions/todo/state/selectors.ts +107 -0
  245. package/agent/extensions/todo/state/state-reducer.ts +326 -0
  246. package/agent/extensions/todo/state/state.ts +18 -0
  247. package/agent/extensions/todo/state/store.ts +82 -0
  248. package/agent/extensions/todo/state/task-graph.ts +57 -0
  249. package/agent/extensions/todo/todo-overlay.ts +200 -0
  250. package/agent/extensions/todo/todo.ts +155 -0
  251. package/agent/extensions/todo/tool/response-envelope.ts +109 -0
  252. package/agent/extensions/todo/tool/types.ts +206 -0
  253. package/agent/extensions/todo/verify-ref-system.js +0 -0
  254. package/agent/extensions/todo/view/format.ts +177 -0
  255. package/agent/extensions/trim-context/README.md +54 -0
  256. package/agent/extensions/trim-context/index.ts +487 -0
  257. package/agent/extensions/trim-context/install.ps1 +23 -0
  258. package/agent/extensions/trim-context/install.sh +21 -0
  259. package/agent/install.ps1 +527 -0
  260. package/agent/install.sh +511 -0
  261. package/agent/keybindings.json +7 -0
  262. package/bin/kiln.js +359 -0
  263. package/package.json +21 -0
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env pwsh
2
+ # Per-extension installer — Windows / PowerShell
3
+ # Installs npm dependencies for this extension. No-op if no package.json.
4
+ $ErrorActionPreference = "Stop"
5
+ $extDir = $PSScriptRoot
6
+ if (-not $extDir) { $extDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
7
+ if (-not (Test-Path "$extDir/package.json")) {
8
+ Write-Host " - no package.json — skipping" -ForegroundColor DarkGray
9
+ exit 0
10
+ }
11
+ if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
12
+ Write-Host " x npm not found — install Node.js 22+" -ForegroundColor Red
13
+ exit 1
14
+ }
15
+ if (Test-Path "$extDir/package-lock.json") {
16
+ Write-Host " - npm ci" -ForegroundColor DarkGray
17
+ npm --prefix $extDir ci --no-audit --no-fund
18
+ } else {
19
+ Write-Host " - npm install" -ForegroundColor DarkGray
20
+ npm --prefix $extDir install --no-audit --no-fund
21
+ }
22
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
23
+ Write-Host " + done" -ForegroundColor Green
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ # Per-extension installer — macOS / Linux
3
+ # Installs npm dependencies for this extension. No-op if no package.json.
4
+ set -eu
5
+ EXT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ if [ ! -f "$EXT_DIR/package.json" ]; then
7
+ echo " · no package.json — skipping"
8
+ exit 0
9
+ fi
10
+ if ! command -v npm >/dev/null 2>&1; then
11
+ echo " ✖ npm not found — install Node.js 22+" >&2
12
+ exit 1
13
+ fi
14
+ if [ -f "$EXT_DIR/package-lock.json" ]; then
15
+ echo " · npm ci"
16
+ npm --prefix "$EXT_DIR" ci --no-audit --no-fund
17
+ else
18
+ echo " · npm install"
19
+ npm --prefix "$EXT_DIR" install --no-audit --no-fund
20
+ fi
21
+ echo " ✓ done"
@@ -0,0 +1,276 @@
1
+ /**
2
+ * End-to-end smoke tests: manager behavior through a real ManagedRuntime,
3
+ * exactly as the tool handlers drive it. The registry is test-only: scripted
4
+ * stub sessions registered under the claude/codex names (the production
5
+ * backends launch real processes and have their own live test files), plus
6
+ * the real pi backend for its cheap registry precondition.
7
+ */
8
+
9
+ import assert from "node:assert/strict";
10
+ import test from "node:test";
11
+ import { Effect, Layer, ManagedRuntime } from "effect";
12
+ import { BackendRegistry, type SubagentBackend } from "./src/backend.ts";
13
+ import { piBackend } from "./src/backends/pi.ts";
14
+ import { makeStubBackend } from "./src/backends/stub.ts";
15
+ import type { BackendName, ParentContext, SpawnTask } from "./src/domain.ts";
16
+ import {
17
+ SubagentManager,
18
+ SubagentManagerLive,
19
+ type SubagentManagerShape,
20
+ } from "./src/manager.ts";
21
+ import { runTool } from "./src/runtime.ts";
22
+
23
+ const TestRegistryLive = Layer.sync(BackendRegistry, () => {
24
+ const backends: SubagentBackend[] = [
25
+ piBackend,
26
+ makeStubBackend({
27
+ backend: "claude",
28
+ defaultModelLabel: "claude/sonnet",
29
+ contextWindow: 200_000,
30
+ toolName: "Bash",
31
+ cadenceMs: 40,
32
+ }),
33
+ makeStubBackend({
34
+ backend: "codex",
35
+ defaultModelLabel: "codex/gpt-5-codex",
36
+ contextWindow: 272_000,
37
+ toolName: "shell",
38
+ cadenceMs: 30,
39
+ }),
40
+ ];
41
+ return new Map<BackendName, SubagentBackend>(
42
+ backends.map((backend) => [backend.name, backend]),
43
+ );
44
+ });
45
+
46
+ const createTestRuntime = () =>
47
+ ManagedRuntime.make(
48
+ SubagentManagerLive.pipe(Layer.provide(TestRegistryLive)),
49
+ );
50
+
51
+ const parent: ParentContext = {
52
+ parentCwd: process.cwd(),
53
+ projectTrusted: false,
54
+ };
55
+
56
+ function task(prompt: string): SpawnTask {
57
+ return { prompt, title: "test", cwd: process.cwd(), parent };
58
+ }
59
+
60
+ async function withManager(
61
+ run: (
62
+ manager: SubagentManagerShape,
63
+ runtime: ReturnType<typeof createTestRuntime>,
64
+ ) => Promise<void>,
65
+ ) {
66
+ const runtime = createTestRuntime();
67
+ try {
68
+ const manager = await runtime.runPromise(SubagentManager);
69
+ await run(manager, runtime);
70
+ } finally {
71
+ await runtime.dispose();
72
+ }
73
+ }
74
+
75
+ test("stub subagent completes and delivers a final result", async () => {
76
+ await withManager(async (manager, runtime) => {
77
+ const settled: Array<{ id: string; consumed: boolean }> = [];
78
+ manager.view.setOnSettled((snap, consumed) =>
79
+ settled.push({ id: snap.id, consumed }),
80
+ );
81
+
82
+ const snap = await runTool(
83
+ runtime,
84
+ manager.spawn("claude", task("Say hello to the tests")),
85
+ );
86
+ assert.equal(snap.status, "running");
87
+ assert.equal(snap.backend, "claude");
88
+ assert.ok(snap.meta.sessionFilePath);
89
+
90
+ await runTool(runtime, manager.waitFor([snap.id]));
91
+ const done = manager.view.get(snap.id);
92
+ assert.ok(done);
93
+ assert.equal(done.status, "done");
94
+ assert.match(
95
+ done.finalText,
96
+ /\[stub:claude\] completed: Say hello to the tests/,
97
+ );
98
+ assert.ok(done.turns >= 2);
99
+ assert.ok(done.transcript.some((item) => item.kind === "toolResult"));
100
+ // The waitFor marked the settle as consumed.
101
+ assert.deepEqual(settled, [{ id: snap.id, consumed: true }]);
102
+ });
103
+ });
104
+
105
+ test("FAIL: prompts settle as errors; unconsumed settles are delivered", async () => {
106
+ await withManager(async (manager, runtime) => {
107
+ const settled: Array<{ id: string; consumed: boolean }> = [];
108
+ manager.view.setOnSettled((snap, consumed) =>
109
+ settled.push({ id: snap.id, consumed }),
110
+ );
111
+
112
+ const snap = await runTool(
113
+ runtime,
114
+ manager.spawn("codex", task("FAIL: blow up please")),
115
+ );
116
+ // Poll without wait-interest so the settle is delivered unconsumed.
117
+ while (manager.view.get(snap.id)?.status === "running") {
118
+ await new Promise((resolve) => setTimeout(resolve, 50));
119
+ }
120
+ const failed = manager.view.get(snap.id);
121
+ assert.equal(failed?.status, "error");
122
+ assert.match(failed?.errorText ?? "", /task failed/);
123
+ assert.deepEqual(settled, [{ id: snap.id, consumed: false }]);
124
+ });
125
+ });
126
+
127
+ test("cancel interrupts a running stub subagent", async () => {
128
+ await withManager(async (manager, runtime) => {
129
+ const snap = await runTool(
130
+ runtime,
131
+ manager.spawn("claude", task("Long running task")),
132
+ );
133
+ const report = await runTool(runtime, manager.cancel([snap.id]));
134
+ assert.deepEqual(report, [
135
+ { id: snap.id, title: "test", status: "error", cancelled: true },
136
+ ]);
137
+ assert.equal(manager.view.get(snap.id)?.errorText, "Run was aborted");
138
+ });
139
+ });
140
+
141
+ test("spawn origin propagates to ids, snapshots, and settlement", async () => {
142
+ await withManager(async (manager, runtime) => {
143
+ const settled: Array<{ id: string; origin: string }> = [];
144
+ manager.view.setOnSettled((snap) =>
145
+ settled.push({ id: snap.id, origin: snap.origin }),
146
+ );
147
+
148
+ const model = await runTool(
149
+ runtime,
150
+ manager.spawn("codex", task("model task")),
151
+ );
152
+ const btw = await runTool(
153
+ runtime,
154
+ manager.spawn("claude", { ...task("side question"), origin: "btw" }),
155
+ );
156
+
157
+ assert.match(model.id, /^sa-/);
158
+ assert.equal(model.origin, "model");
159
+ assert.match(btw.id, /^btw-/);
160
+ assert.equal(btw.origin, "btw");
161
+
162
+ await runTool(runtime, manager.cancel([model.id, btw.id]));
163
+ assert.deepEqual(
164
+ settled.sort((a, b) => a.id.localeCompare(b.id)),
165
+ [
166
+ { id: btw.id, origin: "btw" },
167
+ { id: model.id, origin: "model" },
168
+ ].sort((a, b) => a.id.localeCompare(b.id)),
169
+ );
170
+ });
171
+ });
172
+
173
+ test("the global concurrency cap includes by-the-way sessions", async () => {
174
+ await withManager(async (manager, runtime) => {
175
+ const tasks: SpawnTask[] = [
176
+ { ...task("side question"), origin: "btw" },
177
+ task("Task 2"),
178
+ task("Task 3"),
179
+ task("Task 4"),
180
+ ];
181
+ const spawns = await runTool(
182
+ runtime,
183
+ Effect.forEach(tasks, (spawnTask) => manager.spawn("codex", spawnTask), {
184
+ concurrency: "unbounded",
185
+ }),
186
+ );
187
+ assert.equal(spawns.length, 4);
188
+ await assert.rejects(
189
+ runTool(
190
+ runtime,
191
+ manager.spawn("codex", {
192
+ ...task("another side question"),
193
+ origin: "btw",
194
+ }),
195
+ ),
196
+ /Max 4 subagents/,
197
+ );
198
+ });
199
+ });
200
+
201
+ test("the concurrency cap rejects a fifth running subagent", async () => {
202
+ await withManager(async (manager, runtime) => {
203
+ const spawns = await runTool(
204
+ runtime,
205
+ Effect.forEach(
206
+ [1, 2, 3, 4],
207
+ (n) => manager.spawn("codex", task(`Task ${n}`)),
208
+ { concurrency: "unbounded" },
209
+ ),
210
+ );
211
+ assert.equal(spawns.length, 4);
212
+ await assert.rejects(
213
+ runTool(runtime, manager.spawn("codex", task("Task 5"))),
214
+ /Max 4 subagents/,
215
+ );
216
+ });
217
+ });
218
+
219
+ test("pi spawn fails fast without the parent model registry", async () => {
220
+ await withManager(async (manager, runtime) => {
221
+ await assert.rejects(
222
+ runTool(runtime, manager.spawn("pi", task("needs a registry"))),
223
+ /model registry/,
224
+ );
225
+ // The failed spawn must release its concurrency reservation.
226
+ const snap = await runTool(runtime, manager.spawn("codex", task("ok")));
227
+ assert.equal(snap.backend, "codex");
228
+ });
229
+ });
230
+
231
+ test("idle restarts respect the concurrency cap", async () => {
232
+ await withManager(async (manager, runtime) => {
233
+ // Settle one subagent, then fill all four slots with running ones.
234
+ const settled = await runTool(
235
+ runtime,
236
+ manager.spawn("claude", task("early finisher")),
237
+ );
238
+ await runTool(runtime, manager.waitFor([settled.id]));
239
+ await runTool(
240
+ runtime,
241
+ Effect.forEach(
242
+ [1, 2, 3, 4],
243
+ (n) => manager.spawn("codex", task(`Task ${n}`)),
244
+ { concurrency: "unbounded" },
245
+ ),
246
+ );
247
+ // Restarting the settled one would be a fifth concurrent run.
248
+ await assert.rejects(
249
+ runTool(runtime, manager.send(settled.id, "go again")),
250
+ /Max 4 subagents/,
251
+ );
252
+ assert.equal(manager.view.get(settled.id)?.status, "done");
253
+ });
254
+ });
255
+
256
+ test("send steers an idle subagent into another turn", async () => {
257
+ await withManager(async (manager, runtime) => {
258
+ const snap = await runTool(
259
+ runtime,
260
+ manager.spawn("claude", task("First turn")),
261
+ );
262
+ await runTool(runtime, manager.waitFor([snap.id]));
263
+ const afterFirst = manager.view.get(snap.id);
264
+ assert.equal(afterFirst?.status, "done");
265
+
266
+ await runTool(runtime, manager.send(snap.id, "Second turn"));
267
+ // The fresh run flips the status back to running...
268
+ while (manager.view.get(snap.id)?.status !== "running") {
269
+ await new Promise((resolve) => setTimeout(resolve, 10));
270
+ }
271
+ await runTool(runtime, manager.waitFor([snap.id]));
272
+ const afterSecond = manager.view.get(snap.id);
273
+ assert.equal(afterSecond?.status, "done");
274
+ assert.match(afterSecond?.finalText ?? "", /Second turn/);
275
+ });
276
+ });