@asterxsk/kiln 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -155
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/install.ps1 +27 -27
- package/agent/extensions/ask-user/install.sh +24 -24
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/install.ps1 +27 -27
- package/agent/extensions/background-terminals/install.sh +24 -24
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/destructive/README.md +31 -31
- package/agent/extensions/destructive/index.ts +88 -88
- package/agent/extensions/destructive/install.ps1 +27 -27
- package/agent/extensions/destructive/install.sh +24 -24
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/install.ps1 +27 -27
- package/agent/extensions/file-search/install.sh +24 -24
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/goal/README.md +50 -50
- package/agent/extensions/goal/index.ts +155 -155
- package/agent/extensions/goal/install.ps1 +27 -27
- package/agent/extensions/goal/install.sh +24 -24
- package/agent/extensions/kiln-update/README.md +38 -25
- package/agent/extensions/kiln-update/index.ts +177 -99
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/README.md +66 -66
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/install.ps1 +27 -27
- package/agent/extensions/modelconf/install.sh +24 -24
- package/agent/extensions/modelconf/package-lock.json +1809 -1809
- package/agent/extensions/modelconf/package.json +13 -13
- package/agent/extensions/modelconf/src/fuzzy.ts +26 -26
- package/agent/extensions/modelconf/src/glob.ts +13 -13
- package/agent/extensions/modelconf/src/persistence.test.ts +46 -46
- package/agent/extensions/modelconf/src/persistence.ts +164 -164
- package/agent/extensions/modelconf/src/ui/ModelConfView.test.ts +75 -75
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/modelconf/tsconfig.json +15 -15
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-search.ts +21 -21
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/search-types.ts +20 -20
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/summary-model-scope.test.mjs +106 -106
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/web-search-answer-render.test.mjs +66 -66
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/install.ps1 +27 -27
- package/agent/extensions/shared/install.sh +24 -24
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/skillsconf/README.md +74 -74
- package/agent/extensions/skillsconf/index.ts +92 -92
- package/agent/extensions/skillsconf/install.ps1 +27 -27
- package/agent/extensions/skillsconf/install.sh +24 -24
- package/agent/extensions/skillsconf/package-lock.json +1809 -1809
- package/agent/extensions/skillsconf/package.json +18 -18
- package/agent/extensions/skillsconf/src/delete-skill.test.ts +64 -64
- package/agent/extensions/skillsconf/src/delete-skill.ts +45 -45
- package/agent/extensions/skillsconf/src/filter.test.ts +80 -80
- package/agent/extensions/skillsconf/src/filter.ts +74 -74
- package/agent/extensions/skillsconf/src/fuzzy.ts +26 -26
- package/agent/extensions/skillsconf/src/persistence.test.ts +72 -72
- package/agent/extensions/skillsconf/src/persistence.ts +169 -169
- package/agent/extensions/skillsconf/src/ui/SkillConfView.test.ts +337 -337
- package/agent/extensions/skillsconf/src/ui/SkillConfView.ts +758 -758
- package/agent/extensions/skillsconf/src/ui/text-input.ts +91 -91
- package/agent/extensions/skillsconf/src/ui/tui-helpers.ts +144 -144
- package/agent/extensions/skillsconf/tsconfig.json +15 -15
- package/agent/extensions/statusline/index.ts +273 -282
- package/agent/extensions/statusline/install.ps1 +27 -27
- package/agent/extensions/statusline/install.sh +24 -24
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/install.ps1 +27 -27
- package/agent/extensions/subagents/install.sh +24 -24
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/install.ps1 +27 -27
- package/agent/extensions/todo/install.sh +24 -24
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/extensions/trim-context/README.md +54 -54
- package/agent/extensions/trim-context/index.ts +487 -487
- package/agent/extensions/trim-context/install.ps1 +27 -27
- package/agent/extensions/trim-context/install.sh +24 -24
- package/agent/keybindings.json +7 -7
- package/agent/version.txt +1 -1
- package/bin/kiln.js +20 -13
- package/package.json +1 -1
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -27
- package/agent/extensions/taste/install.sh +0 -24
|
@@ -1,27 +1,27 @@
|
|
|
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
|
-
# Drop npm_config_allow_scripts inherited from a parent npm/npx process:
|
|
6
|
-
# npm >=11 rejects it for project installs (EALLOWSCRIPTS). File config still applies.
|
|
7
|
-
Remove-Item Env:\npm_config_allow_scripts -ErrorAction SilentlyContinue
|
|
8
|
-
Remove-Item Env:\NPM_CONFIG_ALLOW_SCRIPTS -ErrorAction SilentlyContinue
|
|
9
|
-
$extDir = $PSScriptRoot
|
|
10
|
-
if (-not $extDir) { $extDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
|
11
|
-
if (-not (Test-Path "$extDir/package.json")) {
|
|
12
|
-
Write-Host " - no package.json — skipping" -ForegroundColor DarkGray
|
|
13
|
-
exit 0
|
|
14
|
-
}
|
|
15
|
-
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
|
|
16
|
-
Write-Host " x npm not found — install Node.js 22+" -ForegroundColor Red
|
|
17
|
-
exit 1
|
|
18
|
-
}
|
|
19
|
-
if (Test-Path "$extDir/package-lock.json") {
|
|
20
|
-
Write-Host " - npm ci" -ForegroundColor DarkGray
|
|
21
|
-
npm --prefix $extDir ci --no-audit --no-fund
|
|
22
|
-
} else {
|
|
23
|
-
Write-Host " - npm install" -ForegroundColor DarkGray
|
|
24
|
-
npm --prefix $extDir install --no-audit --no-fund
|
|
25
|
-
}
|
|
26
|
-
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
27
|
-
Write-Host " + done" -ForegroundColor Green
|
|
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
|
+
# Drop npm_config_allow_scripts inherited from a parent npm/npx process:
|
|
6
|
+
# npm >=11 rejects it for project installs (EALLOWSCRIPTS). File config still applies.
|
|
7
|
+
Remove-Item Env:\npm_config_allow_scripts -ErrorAction SilentlyContinue
|
|
8
|
+
Remove-Item Env:\NPM_CONFIG_ALLOW_SCRIPTS -ErrorAction SilentlyContinue
|
|
9
|
+
$extDir = $PSScriptRoot
|
|
10
|
+
if (-not $extDir) { $extDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
|
11
|
+
if (-not (Test-Path "$extDir/package.json")) {
|
|
12
|
+
Write-Host " - no package.json — skipping" -ForegroundColor DarkGray
|
|
13
|
+
exit 0
|
|
14
|
+
}
|
|
15
|
+
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
|
|
16
|
+
Write-Host " x npm not found — install Node.js 22+" -ForegroundColor Red
|
|
17
|
+
exit 1
|
|
18
|
+
}
|
|
19
|
+
if (Test-Path "$extDir/package-lock.json") {
|
|
20
|
+
Write-Host " - npm ci" -ForegroundColor DarkGray
|
|
21
|
+
npm --prefix $extDir ci --no-audit --no-fund
|
|
22
|
+
} else {
|
|
23
|
+
Write-Host " - npm install" -ForegroundColor DarkGray
|
|
24
|
+
npm --prefix $extDir install --no-audit --no-fund
|
|
25
|
+
}
|
|
26
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
27
|
+
Write-Host " + done" -ForegroundColor Green
|
|
@@ -1,24 +1,24 @@
|
|
|
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
|
-
# Drop npm_config_allow_scripts inherited from a parent npm/npx process:
|
|
6
|
-
# npm >=11 rejects it for project installs (EALLOWSCRIPTS). File config still applies.
|
|
7
|
-
unset npm_config_allow_scripts NPM_CONFIG_ALLOW_SCRIPTS 2>/dev/null || true
|
|
8
|
-
EXT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
-
if [ ! -f "$EXT_DIR/package.json" ]; then
|
|
10
|
-
echo " · no package.json — skipping"
|
|
11
|
-
exit 0
|
|
12
|
-
fi
|
|
13
|
-
if ! command -v npm >/dev/null 2>&1; then
|
|
14
|
-
echo " ✖ npm not found — install Node.js 22+" >&2
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
if [ -f "$EXT_DIR/package-lock.json" ]; then
|
|
18
|
-
echo " · npm ci"
|
|
19
|
-
npm --prefix "$EXT_DIR" ci --no-audit --no-fund
|
|
20
|
-
else
|
|
21
|
-
echo " · npm install"
|
|
22
|
-
npm --prefix "$EXT_DIR" install --no-audit --no-fund
|
|
23
|
-
fi
|
|
24
|
-
echo " ✓ done"
|
|
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
|
+
# Drop npm_config_allow_scripts inherited from a parent npm/npx process:
|
|
6
|
+
# npm >=11 rejects it for project installs (EALLOWSCRIPTS). File config still applies.
|
|
7
|
+
unset npm_config_allow_scripts NPM_CONFIG_ALLOW_SCRIPTS 2>/dev/null || true
|
|
8
|
+
EXT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
if [ ! -f "$EXT_DIR/package.json" ]; then
|
|
10
|
+
echo " · no package.json — skipping"
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
14
|
+
echo " ✖ npm not found — install Node.js 22+" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
if [ -f "$EXT_DIR/package-lock.json" ]; then
|
|
18
|
+
echo " · npm ci"
|
|
19
|
+
npm --prefix "$EXT_DIR" ci --no-audit --no-fund
|
|
20
|
+
else
|
|
21
|
+
echo " · npm install"
|
|
22
|
+
npm --prefix "$EXT_DIR" install --no-audit --no-fund
|
|
23
|
+
fi
|
|
24
|
+
echo " ✓ done"
|
|
@@ -1,276 +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
|
-
});
|
|
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
|
+
});
|