@outerlayer/cli 0.15.0 → 0.17.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 (84) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +11 -11
  3. package/dist/{agent-setup-CN2HUBEJ.js → agent-setup-T624CWPD.js} +8 -7
  4. package/dist/artifact-link-cmd-2F47LT7F.js +24 -0
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-2JSSXCKJ.js → check-5XE7XMC4.js} +22 -19
  7. package/dist/{chunk-HFKKFTXU.js → chunk-2MNPHL23.js} +19 -11
  8. package/dist/{chunk-YDSMROTB.js → chunk-3NSNIIBE.js} +8 -4
  9. package/dist/{chunk-FOJGZYFC.js → chunk-42ZI52JW.js} +1 -1
  10. package/dist/{chunk-NPQWKGUA.js → chunk-6Y5RUTHO.js} +1 -1
  11. package/dist/{chunk-PQUM37BK.js → chunk-BWQ66M6N.js} +1 -1
  12. package/dist/chunk-C5Y5ZJJZ.js +88 -0
  13. package/dist/{chunk-ASOSIKA4.js → chunk-G5NPRL2K.js} +27 -6
  14. package/dist/{chunk-QLTYTPYU.js → chunk-HGECYFEI.js} +64 -7
  15. package/dist/{chunk-SXWS25JC.js → chunk-KG5ECZGJ.js} +6 -6
  16. package/dist/{chunk-F7GBYX2P.js → chunk-LWSJQJC6.js} +18 -4
  17. package/dist/{chunk-UUDDPNRJ.js → chunk-MAK5LYRF.js} +33 -10
  18. package/dist/{chunk-MTF3PHVZ.js → chunk-NBGDBTO2.js} +3 -3
  19. package/dist/{chunk-KFRM23WD.js → chunk-NNRTPG7X.js} +2 -2
  20. package/dist/{chunk-MSASO5O4.js → chunk-NWQTTSBB.js} +1 -1
  21. package/dist/{chunk-2M63GG56.js → chunk-OHVJA253.js} +1 -1
  22. package/dist/{chunk-Z3NOIFDR.js → chunk-ONKJUM6R.js} +1 -1
  23. package/dist/{chunk-T5VPP6H7.js → chunk-OZGHL5Y5.js} +1 -1
  24. package/dist/{chunk-ZA5CHAFY.js → chunk-POJMEEJJ.js} +21 -4
  25. package/dist/{chunk-JR6ERP3M.js → chunk-S3NYLI2P.js} +3 -3
  26. package/dist/{chunk-HXLMZOKU.js → chunk-VQYBXPI7.js} +4 -4
  27. package/dist/{chunk-LCEXHCHD.js → chunk-WTE3MQML.js} +1 -1
  28. package/dist/{chunk-NXUH742G.js → chunk-WVUIRTWA.js} +10 -8
  29. package/dist/{chunk-JJEGKEFV.js → chunk-YU3VKZ5F.js} +1 -1
  30. package/dist/{chunk-3NLOVYJQ.js → chunk-YZ5F4PIS.js} +134 -38
  31. package/dist/{cli-RGGXQTWI.js → cli-A76AXELQ.js} +90 -79
  32. package/dist/{command-ETUAEXP2.js → command-R2QCVX55.js} +10 -9
  33. package/dist/{connect-cmd-JUAW6MU4.js → connect-cmd-JBKAQNSF.js} +1 -1
  34. package/dist/{context-adopt-PJUMW2EZ.js → context-adopt-NFHCLVUB.js} +5 -4
  35. package/dist/{context-materialize-FDHYKNDT.js → context-materialize-IV2TUUFO.js} +5 -4
  36. package/dist/{doctor-CORFPYY6.js → doctor-4TAIARIG.js} +15 -13
  37. package/dist/{emit-artifact-cmd-FOGNJXVR.js → emit-artifact-cmd-MPZVR56N.js} +4 -4
  38. package/dist/{emit-cmd-ZEYWLC2C.js → emit-cmd-IJ3GVRFD.js} +4 -3
  39. package/dist/{emit-criteria-cmd-HBP6XS5C.js → emit-criteria-cmd-EFGYI23V.js} +2 -2
  40. package/dist/{emit-finding-cmd-DLI4MKMN.js → emit-finding-cmd-5GF3V4LR.js} +160 -47
  41. package/dist/{emit-result-cmd-MUSL7GQA.js → emit-result-cmd-I4OADKMD.js} +4 -4
  42. package/dist/hidden-prompt-UXBFMQC5.js +68 -0
  43. package/dist/{hook-fast-WYQD6D7T.js → hook-fast-R4FVSZ2H.js} +1 -1
  44. package/dist/{import-capture-cmd-6QVCOIHK.js → import-capture-cmd-EQ35EARA.js} +3 -3
  45. package/dist/{import-ruler-cmd-KLRGRBCP.js → import-ruler-cmd-BGAZXCQM.js} +1 -1
  46. package/dist/index.js +3 -3
  47. package/dist/{init-OPB7HHXO.js → init-6XPDRDTC.js} +12 -11
  48. package/dist/{init-MIC37V6J.js → init-PWU5YVL7.js} +54 -29
  49. package/dist/{init-cmd-JR3RKNFB.js → init-cmd-XAVO7HYZ.js} +11 -10
  50. package/dist/{init-update-O5YYFGF4.js → init-update-CVCWD6VS.js} +10 -9
  51. package/dist/{loop-QNSM5GXS.js → loop-FK3LPMMZ.js} +19 -18
  52. package/dist/{machine-IT6GG7FF.js → machine-SQXEBAM2.js} +10 -9
  53. package/dist/{runner-env-XTC53CHN.js → runner-env-EN7YE7QX.js} +1 -1
  54. package/dist/runner-key-6SLBI7PP.js +174 -0
  55. package/dist/{session-sender-4M247XSV.js → session-sender-FO3LCREV.js} +3 -3
  56. package/dist/{starter-pack-IB4UUIX3.js → starter-pack-KF66HGZR.js} +3 -3
  57. package/dist/{status-7ZX57OFJ.js → status-7VNQN5PW.js} +13 -12
  58. package/dist/{sync-cmd-UWD5JOFU.js → sync-cmd-GVPPFZ3G.js} +2 -2
  59. package/dist/{watch-DBNB7R6X.js → watch-23HSAEI6.js} +2 -2
  60. package/dist/{work-claim-cmd-A6IS6CQ7.js → work-claim-cmd-LKL3GHFQ.js} +5 -5
  61. package/dist/{work-cmd-EQSW5DTD.js → work-cmd-Z2WXLZPH.js} +3 -3
  62. package/dist/{work-comment-cmd-7VLKI7FN.js → work-comment-cmd-AWHP56M2.js} +3 -3
  63. package/dist/{work-open-pr-cmd-JEVD4HMQ.js → work-open-pr-cmd-IOM4AVQQ.js} +4 -4
  64. package/dist/{work-pr-cmd-L77K32WK.js → work-pr-cmd-HNEUIEN7.js} +3 -3
  65. package/package.json +1 -1
  66. package/skill-pack/maintained/{amend → outerlayer-amend}/SKILL.md +4 -2
  67. package/skill-pack/maintained/{outerlayer → outerlayer-cli}/SKILL.md +5 -3
  68. package/skill-pack/maintained/{emitting-evidence → outerlayer-evidence}/SKILL.md +5 -3
  69. package/skill-pack/maintained/{reporting-findings → outerlayer-findings}/SKILL.md +26 -30
  70. package/skill-pack/shipped.json +20 -4
  71. package/skill-pack/template/build/SKILL.md +7 -1
  72. package/skill-pack/template/build/references/agent-briefs.md +7 -1
  73. package/skill-pack/template/build/references/evidence.md +1 -1
  74. package/skill-pack/template/build/references/release.md +14 -12
  75. package/skill-pack/template/build/references/review-briefs.md +6 -0
  76. package/skill-pack/template/build/scripts/criterion-citations.mjs +8 -3
  77. package/skill-pack/template/build/scripts/record-criteria.mjs +8 -2
  78. package/skill-pack/template/spec/SKILL.md +2 -0
  79. package/skill-pack/template/writing-specs/SKILL.md +1 -1
  80. package/skill-pack/validators/acceptance-criteria.yaml +6 -5
  81. package/skill-pack/validators/code-review.yaml +6 -5
  82. package/dist/runner-key-7SSNMUBQ.js +0 -154
  83. /package/skill-pack/maintained/{outerlayer → outerlayer-cli}/references/open-pull-request.md +0 -0
  84. /package/skill-pack/maintained/{emitting-evidence → outerlayer-evidence}/references/agents-snippet.md +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @outerlayer/cli
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 420b0db: `outerlayer runner init` takes `--factory <org>/<factory>`, the same name `outerlayer init --factory` takes, so a runner host no longer needs the Factory Id copied from the dashboard. With no factory named, a login that can use exactly one factory uses it and says which. A login that can use several is refused with each `--factory` value listed. `--app-id` still works, and a `--factory` and `--app-id` that name different factories are refused.
8
+
9
+ ### Patch Changes
10
+
11
+ - a3ac4ae: Count a test title such as `WC-01: counts words` as citing criterion WC-01. The starter `/build` skill's `criterion-citations.mjs` linked no test whose title put a colon straight after the id, so a build could report 0 of 4 criteria linked. A longer id such as `WC-010`, `WC-01a` or `WC-01:b` still does not cite WC-01.
12
+ - a897c68: The README says to install the CLI globally with `npm install -g @outerlayer/cli`, because `/build` and the other skills run the `outerlayer` command.
13
+ - 2a59794: Fix review results never being recorded on a new factory. The starter code-review and acceptance-criteria checks and the starter `/build` skill told agents to copy each report's link from the pull request's evidence comment, which a factory never posts until comments are turned on. They now take the link from the line `outerlayer sync` prints. A new `outerlayer sync --link <artifact id>` prints the link of an artifact an earlier sync already uploaded.
14
+ - 8196dc9: `outerlayer runner init` names the factory once when it picks the only one the login can use. A refused pasted key no longer repeats the gateway's "Mint a fresh key" advice beside its own step to create a new key with the Runner preset.
15
+ - 8196dc9: `outerlayer runner init` no longer tries to create the runner key with your login, which the gateway never allows. With no key in the config file, it prints the link to the factory's API keys page, asks you to paste a key made with the Runner preset (nothing is shown as you paste), checks it, and goes on. With no terminal it stops and prints the link and the config-file lines to use. `--key-name` is removed. Its output now marks warnings with `!` instead of a tick, no longer doubles ticks for lines from the VM, drops the PATH note about the VM's shell, and says whether capture on the Mac is affected when the runner key moves into the VM.
16
+ - 3ba60cd: No change in behaviour: the runner's wait for a stopped session sender's exit is simplified, and its tests check that the wait ends when the exit is logged.
17
+ - 0b7e87c: The starter skills run the CLI through `npx @outerlayer/cli` when `outerlayer` is not on PATH.
18
+ - f7553df: `outerlayer context materialize` and a container build now fetch the control plane and commit that OuterLayer stores for a repository, and read no `governs` list for it. `outerlayer doctor` reports which control plane governs the repository, its ref and the stored commit. `outerlayer connect` picks the factory that owns a repository linked to two factories, the one with the oldest connection, without asking.
19
+
20
+ ## 0.16.1
21
+
22
+ ### Patch Changes
23
+
24
+ - 8f583f5: A build's log now always says the session sender exited before it says sync started. The runner waited only until the sender's process was gone, and Node can report the exit a moment later, so the two lines could swap.
25
+
26
+ ## 0.16.0
27
+
28
+ ### Minor Changes
29
+
30
+ - 87c8154: `outerlayer emit finding` and `emit findings` now record the time every finding cost. The start is `--started-at`, else the first tool output that contains the finding's `--error` text. The end is `--ended-at`, else the newest event. When the session has events and no start is found, nothing is stored, and the command lists the recent events with their ids. Events now include the session's subagents, with ids such as `a4962c00:e23`. `--find <text>` searches every event and records nothing. `--untimed` records a finding with no time on purpose. A session with no transcript on the machine records findings as before. The starter `/build` skill now tells its agents to record the factory problems they hit; `outerlayer init --update` brings it to repositories set up earlier.
31
+ - a076be0: The maintained skills now start with `outerlayer-`, so none can land on a skill of your own: `amend` is `outerlayer-amend`, `emitting-evidence` is `outerlayer-evidence`, `reporting-findings` is `outerlayer-findings`, and `outerlayer` is `outerlayer-cli`. `outerlayer context emit` writes each under its new name and deletes the old copies, so run it and commit. `--check` reports old copies as `retired`. A skill of your own with an earlier name is left alone. Governed checkouts show each skill once, under its new name, before the control plane is updated. The runner runs `/outerlayer-amend` for a command that still says `/amend`, and says so; change `runner.commands.amend` to match. `outerlayer doctor` lists skill files that still use an earlier name. See https://docs.outerlayer.ai/upgrade.
32
+
33
+ ### Patch Changes
34
+
35
+ - 7b425ca: A runner build now gives the gateway 10 seconds, and one retry, to say which control plane governs the item's repository. When the lookup still fails, `context_unavailable` says whether it timed out, what status the gateway answered, or what network error occurred. Session starts keep their 2-second limit.
36
+
3
37
  ## 0.15.0
4
38
 
5
39
  ### Minor Changes
package/README.md CHANGED
@@ -13,12 +13,13 @@ network, and when, is below.
13
13
  Launch a session naming the work it's for, then sync:
14
14
 
15
15
  ```
16
- npx @outerlayer/cli login # sign in, once per machine
17
- npx @outerlayer/cli init # connect this repository to a factory, install the capture hooks
18
- npx @outerlayer/cli work build --issue 42 --local # create the work item; prints its number
19
- OUTERLAYER_WORK=7 claude # launch a session naming that number
20
- npx @outerlayer/cli sync --dry-run # see exactly what would leave your machine
21
- npx @outerlayer/cli sync # upload sessions launched that way
16
+ npm install -g @outerlayer/cli # puts `outerlayer` on your PATH, once per machine
17
+ outerlayer login # sign in, once per machine
18
+ outerlayer init # connect this repository to a factory, install the capture hooks
19
+ outerlayer work build --issue 42 --local # create the work item; prints its number
20
+ OUTERLAYER_WORK=7 claude # launch a session naming that number
21
+ outerlayer sync --dry-run # see exactly what would leave your machine
22
+ outerlayer sync # upload sessions launched that way
22
23
  ```
23
24
 
24
25
  ## Privacy, stated plainly
@@ -144,10 +145,9 @@ content:
144
145
  to it.
145
146
  - **`outerlayer runner init`** makes no network call when the config file
146
147
  already holds a runner key. With no key there and a saved login, it
147
- creates one: `POST /v1/api-keys` with your login's token, sending the key's
148
- name (this machine's name, or `--key-name`), the Runner preset's
149
- permissions and the factory id. It then checks the new key, unsigned, with
150
- `GET /v1/runner/host-key`. `runner init --vm` on a Mac also runs `limactl`,
148
+ asks the gateway which factories the login can use (`GET /v1/me/factories`)
149
+ and prints the factory's API keys page. You create the key there and paste
150
+ it. It checks the pasted key, unsigned, with `GET /v1/runner/host-key`. `runner init --vm` on a Mac also runs `limactl`,
151
151
  and Lima downloads a Linux VM image. Inside the VM, `apt` downloads Node and
152
152
  `npm` downloads this CLI. Your runner key goes to the VM on the standard
153
153
  input of a `limactl shell` command and is never a file or an argument on the
@@ -391,7 +391,7 @@ before it gives up.
391
391
  | `outerlayer work claim --item <n>\|--issue <n> --kind implement\|amend [--repo] [--host] [--seconds]` | Records a lease for this host (default: its own hostname), so two hosts never build the same item at once. The lease belongs to the runner key that claimed it: a live lease held by a different key is refused, even under the same host name, and claiming again with the same key extends it. The first claim creates the host key (`host-key.pem` beside the config file) and binds the runner key to it; from then on every request made with that key is signed. Lease length defaults to 900 seconds, the server's own cap. |
392
392
  | `outerlayer work renew --item <n>\|--issue <n> [--repo] [--host] [--seconds]` | Extends this host's own live lease. Refused if the lease has expired, or if a different runner key holds it (403 `claim_not_held`). |
393
393
  | `outerlayer work release --item <n>\|--issue <n> [--repo] [--host] [--session-id] [--outcome]` | Marks the lease this API key holds released, optionally recording how the attempt ended. Releasing an already-released lease is a no-op that returns the recorded release time and outcome. Run inside a session with no `--host`, it releases that session's own lease instead (`--session-id` names the session when it cannot be detected), so the item's review opens without ending the session. |
394
- | `outerlayer runner init [--vm\|--no-vm] [--app-id <id>] [--key-name <name>] [--config <path>]` | Writes a `runner` block with defaults, keeping every key the config already had. With no `apiKey` in the config and a saved login, it first creates a runner key from the login with the Runner preset, named after the machine or `--key-name`, on the factory `--app-id` names, and checks it before anything else changes. It names `builtin:vm` on a Linux machine where the user can use `/dev/kvm` and Docker Engine can build images once the guest kernel and helper are published (not yet), `builtin:container` on that machine until then, `builtin:container` on any other machine with Docker Engine, and `builtin:process` on one without, and writes no hook script. Refuses rather than overwriting an existing block. On macOS it asks whether to create a Linux VM with Lima, installs Docker Engine and the runner in it, and starts both at login; `--vm` answers yes and `--no-vm` answers no, and a run with no terminal answers no. The runner key and the host key then live only in the VM: the Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note, which `outerlayer doctor` reads. |
394
+ | `outerlayer runner init [--vm\|--no-vm] [--factory <org>/<factory>] [--app-id <id>] [--config <path>]` | Writes a `runner` block with defaults, keeping every key the config already had. With no `apiKey` in the config and a saved login, it prints the API keys page of the factory `--factory` or `--app-id` names (the only factory the login can use, when neither is given), asks you to paste a runner key made with the Runner preset, and checks it before anything else changes. With no terminal it stops and says what to put in the config. It names `builtin:vm` on a Linux machine where the user can use `/dev/kvm` and Docker Engine can build images once the guest kernel and helper are published (not yet), `builtin:container` on that machine until then, `builtin:container` on any other machine with Docker Engine, and `builtin:process` on one without, and writes no hook script. Refuses rather than overwriting an existing block. On macOS it asks whether to create a Linux VM with Lima, installs Docker Engine and the runner in it, and starts both at login; `--vm` answers yes and `--no-vm` answers no, and a run with no terminal answers no. The runner key and the host key then live only in the VM: the Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note, which `outerlayer doctor` reads. |
395
395
  | `outerlayer runner check [--config <path>]` | Validates the config, the hooks and the key, prints the settings the runner would use, and exits non-zero on a problem. Takes no work and writes no pid file. |
396
396
  | `outerlayer runner start [--config <path>]` | Runs the loop: claim work, run it, sync, clean up, report, release with an outcome. Repeat. Reads the `runner` block from the config file (default `~/.outerlayer/config.json`). Refuses to start on a bad config, an unrunnable hook, or a refused key. |
397
397
  | `outerlayer runner image --repository <owner/name> [--remote <url>] [--config <path>] [--json]` | Builds the image a repository's builds run in. Fetches the head of the default branch into the runner's cache, builds its `.devcontainer/devcontainer.json` with the devcontainer CLI, ignores every setting that reaches the host or widens the container's privileges, and adds this CLI and a pinned Claude Code. Reuses the image until the recipe or the tools change, keeps the newest three, and writes `recipe.json` beside the image record. Refuses a Compose recipe (`recipe_needs_docker`) and a repository with only named configurations (`recipe_ambiguous`). |
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { keepHandWritten, runAgentSetup } from './chunk-SXWS25JC.js';
4
- import './chunk-HFKKFTXU.js';
3
+ export { keepHandWritten, runAgentSetup } from './chunk-KG5ECZGJ.js';
4
+ import './chunk-2MNPHL23.js';
5
5
  import './chunk-XCK2YAY5.js';
6
- import './chunk-FOJGZYFC.js';
6
+ import './chunk-42ZI52JW.js';
7
7
  import './chunk-YRYMNBGQ.js';
8
- import './chunk-3NLOVYJQ.js';
9
- import './chunk-LCEXHCHD.js';
8
+ import './chunk-YZ5F4PIS.js';
9
+ import './chunk-WTE3MQML.js';
10
+ import './chunk-C5Y5ZJJZ.js';
10
11
  import './chunk-ZJSNLVCE.js';
11
12
  import './chunk-YO4PSLVB.js';
12
13
  import './chunk-BYT5O5RV.js';
13
- import './chunk-ZA5CHAFY.js';
14
- import './chunk-2M63GG56.js';
14
+ import './chunk-POJMEEJJ.js';
15
+ import './chunk-OHVJA253.js';
15
16
  import './chunk-PLMTLGYH.js';
16
17
  import './chunk-J5M7BSJQ.js';
17
18
  import './chunk-EABW6AJQ.js';
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { sanitizeTerminal, readEffectiveArtifactSpoolState } from './chunk-WUYTWNMA.js';
4
+ import './chunk-J5M7BSJQ.js';
5
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
+ import './chunk-Z5GEFMRW.js';
7
+
8
+ createRequire(import.meta.url);
9
+
10
+ // src/artifact-link-cmd.ts
11
+ function lookupArtifactLink(artifactId, home = homeFromEnv()) {
12
+ const shown = sanitizeTerminal(artifactId);
13
+ const entry = readEffectiveArtifactSpoolState(home).state.get(artifactId);
14
+ if (entry === void 0) return { ok: false, reason: `no artifact ${shown} is queued or uploaded on this machine` };
15
+ if (entry.state !== "uploaded") {
16
+ return { ok: false, reason: `artifact ${shown} is ${entry.state}, not uploaded yet \u2014 run \`outerlayer sync\` first` };
17
+ }
18
+ if (entry.url === void 0) {
19
+ return { ok: false, reason: `artifact ${shown} uploaded, but the gateway returned no link for it` };
20
+ }
21
+ return { ok: true, url: entry.url };
22
+ }
23
+
24
+ export { lookupArtifactLink };
@@ -1 +1 @@
1
- {"id":"2026-10-09T12:01:54.751Z+00e38ff","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-09T21:24:47.045Z+8773e8e","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
@@ -1,33 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { probeCredentials } from './chunk-NXUH742G.js';
4
- import './chunk-ASOSIKA4.js';
3
+ import { probeCredentials } from './chunk-WVUIRTWA.js';
4
+ import './chunk-G5NPRL2K.js';
5
+ import './chunk-RMWLBUCJ.js';
5
6
  import './chunk-O765JR4W.js';
6
7
  import './chunk-UGP76IZ5.js';
7
8
  import './chunk-3JVYMD4G.js';
8
9
  import './chunk-YETTWTAY.js';
9
- import './chunk-RMWLBUCJ.js';
10
10
  import './chunk-I2UOAV7X.js';
11
+ import './chunk-NNRTPG7X.js';
11
12
  import './chunk-HYZRY4KZ.js';
12
13
  import './chunk-ADJRGVZC.js';
13
- import './chunk-KFRM23WD.js';
14
- import './chunk-MSASO5O4.js';
14
+ import './chunk-NWQTTSBB.js';
15
15
  import './chunk-HHJDN6AU.js';
16
- import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem } from './chunk-F7GBYX2P.js';
16
+ import { placeholderQueues, placeholderProblem, permissionModeProblems, permissionModeProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem, formerSkillQueues, formerSkillProblem } from './chunk-LWSJQJC6.js';
17
17
  import './chunk-YYYRBYWG.js';
18
18
  import './chunk-TI7B7UEO.js';
19
19
  import './chunk-TBT347UY.js';
20
- import './chunk-HXLMZOKU.js';
21
- import './chunk-Z3NOIFDR.js';
22
- import './chunk-UUDDPNRJ.js';
20
+ import './chunk-VQYBXPI7.js';
21
+ import './chunk-ONKJUM6R.js';
22
+ import './chunk-MAK5LYRF.js';
23
23
  import './chunk-KMDLFI4P.js';
24
24
  import './chunk-QKFB4K7X.js';
25
25
  import './chunk-RSNSZ2LJ.js';
26
- import './chunk-T5VPP6H7.js';
26
+ import './chunk-OZGHL5Y5.js';
27
27
  import './chunk-RLYZLUQ7.js';
28
28
  import { parseRunnerEnv } from './chunk-6MZSX4M7.js';
29
- import './chunk-JR6ERP3M.js';
30
- import './chunk-NPQWKGUA.js';
29
+ import './chunk-S3NYLI2P.js';
30
+ import './chunk-6Y5RUTHO.js';
31
31
  import './chunk-IP7BIBXJ.js';
32
32
  import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-22RMZBGW.js';
33
33
  import './chunk-UXYLWVTQ.js';
@@ -41,7 +41,7 @@ import './chunk-4Q7OMKAT.js';
41
41
  import './chunk-CX22S5BA.js';
42
42
  import './chunk-F7CU5ABH.js';
43
43
  import './chunk-BKO6JEZI.js';
44
- import './chunk-YDSMROTB.js';
44
+ import './chunk-3NSNIIBE.js';
45
45
  import './chunk-WUYTWNMA.js';
46
46
  import './chunk-JXYVA45C.js';
47
47
  import './chunk-4KSQKQXE.js';
@@ -50,20 +50,21 @@ import './chunk-YRYMNBGQ.js';
50
50
  import './chunk-GQD22KKN.js';
51
51
  import './chunk-WZBHBH3J.js';
52
52
  import './chunk-5QRP3MVS.js';
53
- import './chunk-3NLOVYJQ.js';
54
- import './chunk-LCEXHCHD.js';
53
+ import './chunk-YZ5F4PIS.js';
54
+ import './chunk-WTE3MQML.js';
55
+ import './chunk-C5Y5ZJJZ.js';
55
56
  import './chunk-ZJSNLVCE.js';
56
57
  import './chunk-YO4PSLVB.js';
57
58
  import './chunk-BYT5O5RV.js';
58
- import './chunk-ZA5CHAFY.js';
59
- import './chunk-2M63GG56.js';
59
+ import './chunk-POJMEEJJ.js';
60
+ import './chunk-OHVJA253.js';
60
61
  import './chunk-PLMTLGYH.js';
61
62
  import './chunk-J5M7BSJQ.js';
62
63
  import './chunk-EABW6AJQ.js';
63
64
  import './chunk-RA3O54FC.js';
64
65
  import './chunk-V7NIVOFD.js';
65
66
  import './chunk-ZM2IMMYN.js';
66
- import './chunk-PQUM37BK.js';
67
+ import './chunk-BWQ66M6N.js';
67
68
  import './chunk-XCCLFVXM.js';
68
69
  import './chunk-BJ3KTMDH.js';
69
70
  import './chunk-VNVZDWO3.js';
@@ -130,6 +131,7 @@ async function runRunnerCheck(configPath, deps = {}) {
130
131
  return { ok: false, check: "claim permission", message: `${configPath}: ${claimProblem.problem}` };
131
132
  }
132
133
  const unverifiedReason = probe.ok ? claimProblem.reason : probe.reason;
134
+ const formerSkill = formerSkillQueues(config);
133
135
  const lines = [
134
136
  `config ${configPath}`,
135
137
  `host ${config.host}`,
@@ -145,7 +147,8 @@ async function runRunnerCheck(configPath, deps = {}) {
145
147
  `hook cleanup ${config.hooks.cleanup}`,
146
148
  `hook report ${config.hooks.report ?? "(none)"}`,
147
149
  `repos include ${config.repos?.include?.join(", ") ?? "(all)"}`,
148
- `repos exclude ${config.repos?.exclude?.join(", ") ?? "(none)"}`
150
+ `repos exclude ${config.repos?.exclude?.join(", ") ?? "(none)"}`,
151
+ ...formerSkill.length > 0 ? [`note ${formerSkillProblem(formerSkill)}`] : []
149
152
  ];
150
153
  return { ok: true, lines, unverifiedReason, summary: `${config.url} \xB7 app ${config.appId}` };
151
154
  }
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { C } from './chunk-YRYMNBGQ.js';
4
- import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-ZA5CHAFY.js';
5
- import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-2M63GG56.js';
4
+ import { retiredSkillPaths, removeRetiredSkills } from './chunk-C5Y5ZJJZ.js';
5
+ import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-POJMEEJJ.js';
6
+ import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-OHVJA253.js';
6
7
  import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, readdirSync } from 'fs';
7
8
  import { join, dirname, relative, sep } from 'path';
8
9
 
@@ -49,9 +50,9 @@ function walk(dir, repoRoot, insideOuterlayer, out) {
49
50
  }
50
51
  }
51
52
  }
52
- function buildEmitInput(repoRoot, targets, maintained) {
53
+ function buildEmitInput(repoRoot, targets, maintained, excluded = /* @__PURE__ */ new Set()) {
53
54
  const bundled = new Map(maintained.map((f) => [f.path, f.content]));
54
- const discovered = [.../* @__PURE__ */ new Set([...discoverOuterlayerPaths(repoRoot), ...bundled.keys()])].sort();
55
+ const discovered = [.../* @__PURE__ */ new Set([...discoverOuterlayerPaths(repoRoot).filter((p) => !excluded.has(p)), ...bundled.keys()])].sort();
55
56
  const { entries } = classifyTree(discovered);
56
57
  const classified = new Set(entries.map((e) => e.path));
57
58
  const contents = /* @__PURE__ */ new Map();
@@ -80,12 +81,13 @@ function writeOutputs(repoRoot, result, maintained) {
80
81
  }
81
82
  return { files, copies, maintained: [...changedMaintained.created, ...changedMaintained.rewritten] };
82
83
  }
83
- function renderSuccess(targets, files, copies, maintained) {
84
+ function renderSuccess(targets, files, copies, maintained, retired) {
84
85
  const copySuffix = copies.length > 0 ? `, ${copies.length} ${copies.length === 1 ? "copy" : "copies"}` : "";
85
86
  const lines = [
86
87
  `${C.green}\u2713${C.reset} emitted ${files.length} file${files.length === 1 ? "" : "s"}${copySuffix} for ${C.dim}${targets.join(", ")}${C.reset}`
87
88
  ];
88
89
  for (const path of maintained) lines.push(` ${C.dim}updated ${path} to this CLI's version${C.reset}`);
90
+ for (const path of retired) lines.push(` ${C.dim}removed ${path}: this skill has a new name${C.reset}`);
89
91
  return lines.join("\n");
90
92
  }
91
93
  function renderFailure(errors) {
@@ -179,21 +181,24 @@ function runEmit(opts = {}) {
179
181
  if (!existsSync(cwd)) throw new EmitError(`no such directory: ${cwd}`);
180
182
  const targets = resolveTargetsFromConfig(cwd);
181
183
  const maintained = maintainedFilesForExistingSkills(cwd, loadMaintainedFiles());
182
- const built = buildEmitInput(cwd, targets, maintained);
184
+ const retired = retiredSkillPaths(cwd);
185
+ const built = buildEmitInput(cwd, targets, maintained, new Set(retired));
183
186
  const emitted = emitTree(built.input);
184
187
  const { result, kept } = withholdKept(cwd, emitted, opts.keep);
185
188
  const hasErrors = result.errors.length > 0;
186
- if (opts.check) return runCheck(cwd, opts, targets, result, hasErrors, built.sourcePaths, maintained, kept);
189
+ if (opts.check) return runCheck(cwd, opts, targets, result, hasErrors, built.sourcePaths, maintained, kept, retired);
187
190
  const written = hasErrors ? { files: [], copies: [], maintained: [] } : writeOutputs(cwd, result, maintained);
191
+ const removed = hasErrors ? [] : removeRetiredSkills(cwd);
188
192
  const exitCode = hasErrors ? 1 : 0;
189
193
  const output = opts.json ? JSON.stringify({
190
194
  targets,
191
195
  files: written.files,
192
196
  copies: written.copies,
193
197
  maintained: written.maintained,
198
+ retired: removed,
194
199
  warnings: result.warnings,
195
200
  errors: result.errors
196
- }) : hasErrors ? renderFailure(result.errors) : renderSuccess(targets, written.files, written.copies, written.maintained);
201
+ }) : hasErrors ? renderFailure(result.errors) : renderSuccess(targets, written.files, written.copies, written.maintained, removed);
197
202
  if (!opts.quiet) {
198
203
  if (opts.json) {
199
204
  process.stdout.write(output + "\n");
@@ -211,15 +216,17 @@ function runEmit(opts = {}) {
211
216
  copies: written.copies,
212
217
  maintained: written.maintained,
213
218
  kept,
219
+ retired: removed,
214
220
  warnings: result.warnings,
215
221
  errors: result.errors,
216
222
  output,
217
223
  exitCode
218
224
  };
219
225
  }
220
- function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths, maintained, kept) {
226
+ function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths, maintained, kept, retired) {
221
227
  const drift = [];
222
228
  if (!hasErrors) {
229
+ for (const path of retired) drift.push({ kind: "retired", path });
223
230
  for (const file of maintained) {
224
231
  const d = compareFile(cwd, file.path, file.content);
225
232
  if (d) drift.push(d);
@@ -233,7 +240,8 @@ function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths, maintained
233
240
  const d = compareCopy(cwd, copy);
234
241
  if (d) drift.push(d);
235
242
  }
236
- drift.push(...findOrphans(cwd, computedFiles, sourcePaths));
243
+ const retiredPaths = new Set(retired);
244
+ drift.push(...findOrphans(cwd, computedFiles, sourcePaths).filter((d) => !retiredPaths.has(d.path)));
237
245
  drift.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));
238
246
  }
239
247
  const exitCode = hasErrors || drift.length > 0 ? 1 : 0;
@@ -249,7 +257,7 @@ function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths, maintained
249
257
  `);
250
258
  }
251
259
  }
252
- return { targets, files: [], copies: [], maintained: [], kept, warnings: result.warnings, errors: result.errors, drift, output, exitCode };
260
+ return { targets, files: [], copies: [], maintained: [], kept, retired: [], warnings: result.warnings, errors: result.errors, drift, output, exitCode };
253
261
  }
254
262
 
255
263
  export { EmitError, runEmit };
@@ -78,6 +78,7 @@ var REASON_TEXT = {
78
78
  flag: "you passed --factory",
79
79
  saved: "already connected",
80
80
  linked: "the repository is linked to it",
81
+ owner: "it owns this repository",
81
82
  only: "the only factory you can use",
82
83
  prompt: "you chose it"
83
84
  };
@@ -128,18 +129,19 @@ function parseFactories(body) {
128
129
  name: sanitizeTerminal(org.name),
129
130
  displayName: sanitizeTerminal(typeof org.displayName === "string" ? org.displayName : org.name)
130
131
  },
131
- linked: typeof row.linked === "boolean" ? row.linked : null
132
+ linked: typeof row.linked === "boolean" ? row.linked : null,
133
+ owner: row.owner === true
132
134
  });
133
135
  }
134
136
  return out;
135
137
  }
136
138
  function listText(factories, repository) {
137
- return factories.map((f) => ` ${slug(f)} ${f.displayName}${f.linked === true ? ` (linked to ${repository})` : ""}`).join("\n");
139
+ return factories.map((f) => ` ${slug(f)} ${f.displayName}${f.linked === true && repository !== void 0 ? ` (linked to ${repository})` : ""}`).join("\n");
138
140
  }
139
141
  async function fetchFactories(fetchImpl, url, token, repository) {
140
142
  let res;
141
143
  try {
142
- res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/me/factories?${new URLSearchParams({ repository }).toString()}`, {
144
+ res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/me/factories${repository === void 0 ? "" : `?${new URLSearchParams({ repository }).toString()}`}`, {
143
145
  method: "GET",
144
146
  headers: { authorization: `Bearer ${token}` },
145
147
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
@@ -168,6 +170,8 @@ function selectFactory(factories, saved, repository) {
168
170
  if (remembered) return { factory: remembered, reason: "saved" };
169
171
  const linked = factories.filter((f) => f.linked === true);
170
172
  if (linked.length === 1) return { factory: linked[0], reason: "linked" };
173
+ const owners = factories.filter((f) => f.owner);
174
+ if (owners.length === 1) return { factory: owners[0], reason: "owner" };
171
175
  if (factories.length === 1) return { factory: factories[0], reason: "only" };
172
176
  return { factory: void 0, values: factories.map(slug), text: listText(factories, repository) };
173
177
  }
@@ -310,4 +314,4 @@ ${COMMIT_LINE}
310
314
  return r.code;
311
315
  }
312
316
 
313
- export { COMMIT_LINE, autoConnectedLine, fetchFactories, isLinkedRepository, promptOnTerminal, realConnectDeps, runConnect, runConnectCommand, saveFactoryConnection, selectFactory, workPageUrl };
317
+ export { COMMIT_LINE, autoConnectedLine, dashboardBase, fetchFactories, isLinkedRepository, listText, promptOnTerminal, realConnectDeps, runConnect, runConnectCommand, saveFactoryConnection, selectFactory, slug, workPageUrl };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { C } from './chunk-YRYMNBGQ.js';
4
- import { resolvePackRoot, loadTemplateFiles, loadMaintainedFiles, TEMPLATE_SKILLS, skillDir, writeMaintainedFiles, MAINTAINED_SKILLS } from './chunk-ZA5CHAFY.js';
4
+ import { resolvePackRoot, loadTemplateFiles, loadMaintainedFiles, TEMPLATE_SKILLS, skillDir, writeMaintainedFiles, MAINTAINED_SKILLS } from './chunk-POJMEEJJ.js';
5
5
  import { resolvePackageVersion } from './chunk-RA3O54FC.js';
6
6
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
7
7
  import { join, dirname } from 'path';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { fetchFactories, selectFactory, saveFactoryConnection, autoConnectedLine } from './chunk-YDSMROTB.js';
3
+ import { fetchFactories, selectFactory, saveFactoryConnection, autoConnectedLine } from './chunk-3NSNIIBE.js';
4
4
  import { resolveRepoIdentity } from './chunk-JXYVA45C.js';
5
5
  import { resolveGatewayCredentials } from './chunk-PLMTLGYH.js';
6
6
  import { findGitDirs, readRepoConnection } from './chunk-BJ3KTMDH.js';
@@ -634,7 +634,7 @@ async function runHookFast(event, home = homeFromEnv(), read = readStdin, env =
634
634
  recordHookErrorsSeen(home, hookErrorsBytes);
635
635
  if (contextPossiblyGoverned(sessionCwd, home)) {
636
636
  try {
637
- const { materializeContext: materialize } = await import('./context-materialize-FDHYKNDT.js');
637
+ const { materializeContext: materialize } = await import('./context-materialize-IV2TUUFO.js');
638
638
  const sessionId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
639
639
  const result = await materialize({ cwd: sessionCwd, sessionId, home });
640
640
  materializeMessage = result.message;
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { skillDir, FORMER_MAINTAINED_NAMES, isMaintainedCopy } from './chunk-POJMEEJJ.js';
4
+ import { emitTree, ALL_TARGET_IDS, classifyTree } from './chunk-OHVJA253.js';
5
+ import { readFileSync, existsSync, unlinkSync, rmdirSync, readdirSync } from 'fs';
6
+ import { join, dirname, relative, sep } from 'path';
7
+
8
+ createRequire(import.meta.url);
9
+ function filesUnder(repoRoot, rel) {
10
+ try {
11
+ return readdirSync(join(repoRoot, rel), { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => relative(repoRoot, join(entry.parentPath, entry.name)).split(sep).join("/"));
12
+ } catch {
13
+ return [];
14
+ }
15
+ }
16
+ function retiredSkillsPresent(repoRoot) {
17
+ return Object.keys(FORMER_MAINTAINED_NAMES).filter((former) => isMaintainedCopy(repoRoot, former));
18
+ }
19
+ function retiredSkillPaths(repoRoot, retired = retiredSkillsPresent(repoRoot)) {
20
+ const paths = /* @__PURE__ */ new Set();
21
+ for (const former of retired) {
22
+ const sources = filesUnder(repoRoot, skillDir(former));
23
+ const compiled = emitTree({
24
+ entries: classifyTree(sources).entries,
25
+ contents: new Map(sources.map((path) => [path, readFileSync(join(repoRoot, path), "utf8")])),
26
+ // A superset is safe: targets ignore a path that is not a skill asset.
27
+ assetPaths: sources,
28
+ targets: [...ALL_TARGET_IDS]
29
+ });
30
+ for (const path of [...sources, ...compiled.files.map((f) => f.path), ...compiled.copies.map((c) => c.toPath)]) paths.add(path);
31
+ }
32
+ return [...paths].filter((path) => existsSync(join(repoRoot, path))).sort();
33
+ }
34
+ function removeRetiredSkills(repoRoot, unsafe) {
35
+ const removed = retiredSkillPaths(repoRoot).filter((path) => !unsafe?.(path));
36
+ for (const path of removed) {
37
+ unlinkSync(join(repoRoot, path));
38
+ for (let dir = dirname(path); dir !== "."; dir = dirname(dir)) {
39
+ try {
40
+ rmdirSync(join(repoRoot, dir));
41
+ } catch {
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ return removed;
47
+ }
48
+ function filesNamingFormerSkills(repoRoot) {
49
+ const patterns = [
50
+ ["emitting-evidence", /\bemitting-evidence\b/],
51
+ ["reporting-findings", /\breporting-findings\b/],
52
+ ["amend", /`amend` skill|(^|[\s"'`(])\/amend(?![\w-])/m],
53
+ ["outerlayer", /`outerlayer` skill|skills\/outerlayer\//]
54
+ ];
55
+ const retired = retiredSkillsPresent(repoRoot).map((former) => `${skillDir(former)}/`);
56
+ const hits = [];
57
+ for (const path of filesUnder(repoRoot, ".outerlayer/skills").sort()) {
58
+ if (retired.some((dir) => path.startsWith(dir))) continue;
59
+ const text = readFileSync(join(repoRoot, path), "utf8");
60
+ const formers = patterns.filter(([, pattern]) => pattern.test(text)).map(([former]) => former);
61
+ if (formers.length > 0) hits.push({ path, formers });
62
+ }
63
+ return hits;
64
+ }
65
+ function checkFormerSkillNames(repoRoot) {
66
+ const checks = [];
67
+ const retired = retiredSkillsPresent(repoRoot);
68
+ if (retired.length > 0) {
69
+ checks.push({
70
+ name: "Renamed skills",
71
+ status: "warn",
72
+ detail: `copies under earlier names: ${retired.map(skillDir).join(", ")}`,
73
+ fix: "Run `outerlayer context emit` to replace them with the current names, then commit."
74
+ });
75
+ }
76
+ const hits = filesNamingFormerSkills(repoRoot);
77
+ if (hits.length > 0) {
78
+ checks.push({
79
+ name: "Skill names",
80
+ status: "warn",
81
+ detail: `${hits.map((h) => `${h.path} (${h.formers.join(", ")})`).join("; ")} still ${hits.length === 1 ? "names" : "name"} a skill by an earlier name`,
82
+ fix: `Replace each earlier name with the current one: ${Object.entries(FORMER_MAINTAINED_NAMES).map(([former, current]) => `${former} \u2192 ${current}`).join(", ")}. \`outerlayer init --update\` does this for a file you have not edited.`
83
+ });
84
+ }
85
+ return checks;
86
+ }
87
+
88
+ export { checkFormerSkillNames, removeRetiredSkills, retiredSkillPaths };
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { writeJobFile, readJobFile } from './chunk-RMWLBUCJ.js';
4
+ import { senderPidfile } from './chunk-NNRTPG7X.js';
4
5
  import { DOCKERD_LOG } from './chunk-HYZRY4KZ.js';
5
- import { senderPidfile } from './chunk-KFRM23WD.js';
6
- import { installScrubContext } from './chunk-MSASO5O4.js';
7
- import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, formatDuration, retrySend, formatClock, containerSenderCommand, syncInContainer, copyTranscriptsFromContainer, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, startCheckContainer, lifecycleSteps, lifecycleFailureReason, writeEvalInput, EVAL_INPUT_NAME, copyTranscripts, senderSignalScript } from './chunk-UUDDPNRJ.js';
6
+ import { installScrubContext } from './chunk-NWQTTSBB.js';
7
+ import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, formatDuration, retrySend, formatClock, containerSenderCommand, syncInContainer, copyTranscriptsFromContainer, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, startCheckContainer, lifecycleSteps, lifecycleFailureReason, writeEvalInput, EVAL_INPUT_NAME, copyTranscripts, senderSignalScript } from './chunk-MAK5LYRF.js';
8
8
  import { childEnvironment } from './chunk-KMDLFI4P.js';
9
9
  import { waitForAgent, vmExecArgs } from './chunk-QKFB4K7X.js';
10
10
  import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-RSNSZ2LJ.js';
11
- import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-JR6ERP3M.js';
11
+ import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-S3NYLI2P.js';
12
12
  import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-22RMZBGW.js';
13
13
  import { readBranchHead, diffBetween, fetchCommit } from './chunk-R7PF5OOU.js';
14
14
  import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
@@ -18,7 +18,7 @@ import { CLAUDE_CODE_VERSION } from './chunk-CX22S5BA.js';
18
18
  import { planArtifactUpload } from './chunk-WUYTWNMA.js';
19
19
  import { scrubText } from './chunk-JXYVA45C.js';
20
20
  import { withHooksPath, authorEnvironment, commitIdentityRecord } from './chunk-BYT5O5RV.js';
21
- import { parsePolicy } from './chunk-2M63GG56.js';
21
+ import { parsePolicy } from './chunk-OHVJA253.js';
22
22
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
23
23
  import { resolveVersion } from './chunk-RA3O54FC.js';
24
24
  import { signerFor } from './chunk-V7NIVOFD.js';
@@ -2062,6 +2062,8 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
2062
2062
  // src/runner/sender.ts
2063
2063
  var STOP_WAIT_MS = 3e4;
2064
2064
  var POLL_MS = 200;
2065
+ var EXIT_REPORT_WAIT_MS = 2e3;
2066
+ var exitsLogged = /* @__PURE__ */ new Map();
2065
2067
  var BUILD_HOME = "/build/home";
2066
2068
  async function startSender(input) {
2067
2069
  const { config, job, jobDir, deps } = input;
@@ -2072,10 +2074,16 @@ async function startSender(input) {
2072
2074
  const launch = launchFor(config, job, jobDir, deps);
2073
2075
  const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
2074
2076
  let pid;
2077
+ let exitLogged;
2075
2078
  try {
2076
2079
  const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
2077
2080
  child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
2078
- child.on("exit", (code, signal) => deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`));
2081
+ exitLogged = new Promise((resolve) => {
2082
+ child.on("exit", (code, signal) => {
2083
+ deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`);
2084
+ resolve();
2085
+ });
2086
+ });
2079
2087
  child.unref?.();
2080
2088
  pid = child.pid;
2081
2089
  } finally {
@@ -2085,6 +2093,7 @@ async function startSender(input) {
2085
2093
  deps.log.line(`${tag} session sender did not start: no pid`);
2086
2094
  return void 0;
2087
2095
  }
2096
+ exitsLogged.set(pid, exitLogged);
2088
2097
  deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
2089
2098
  return { pid, start: startTimeOf(pid) ?? void 0 };
2090
2099
  } catch (err) {
@@ -2137,6 +2146,17 @@ async function signalInGuest(runtime, state, pidfile, signal) {
2137
2146
  env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
2138
2147
  });
2139
2148
  }
2149
+ async function exitReported(pid) {
2150
+ const logged = exitsLogged.get(pid);
2151
+ let timer;
2152
+ const bound = new Promise((resolve) => {
2153
+ timer = setTimeout(resolve, EXIT_REPORT_WAIT_MS);
2154
+ timer.unref?.();
2155
+ });
2156
+ await Promise.race([logged, bound]);
2157
+ clearTimeout(timer);
2158
+ exitsLogged.delete(pid);
2159
+ }
2140
2160
  async function stopSender(input) {
2141
2161
  const { job, deps } = input;
2142
2162
  const sender = job.sender;
@@ -2159,6 +2179,7 @@ async function stopSender(input) {
2159
2179
  await signalInside("KILL");
2160
2180
  signalHost(sender, "SIGKILL");
2161
2181
  }
2182
+ await exitReported(sender.pid);
2162
2183
  } catch (err) {
2163
2184
  deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
2164
2185
  }