@usine/agents 0.3.0 → 0.5.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 (114) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +100 -30
  3. package/dist/child-host/child-host.d.ts +90 -0
  4. package/dist/child-host/child-host.d.ts.map +1 -0
  5. package/dist/child-host/child-host.js +119 -0
  6. package/dist/child-host/child-host.js.map +1 -0
  7. package/dist/child-host/command.d.ts +22 -0
  8. package/dist/child-host/command.d.ts.map +1 -0
  9. package/dist/child-host/command.js +43 -0
  10. package/dist/child-host/command.js.map +1 -0
  11. package/dist/openai-prices.d.ts +8 -0
  12. package/dist/openai-prices.d.ts.map +1 -0
  13. package/dist/openai-prices.js +28 -0
  14. package/dist/openai-prices.js.map +1 -0
  15. package/dist/pi/authentication.d.ts +11 -2
  16. package/dist/pi/authentication.d.ts.map +1 -1
  17. package/dist/pi/authentication.js +15 -2
  18. package/dist/pi/authentication.js.map +1 -1
  19. package/dist/pi/children.d.ts +1 -4
  20. package/dist/pi/children.d.ts.map +1 -1
  21. package/dist/pi/children.js +1 -4
  22. package/dist/pi/children.js.map +1 -1
  23. package/dist/pi/claude/claude.d.ts +10 -0
  24. package/dist/pi/claude/claude.d.ts.map +1 -0
  25. package/dist/pi/claude/claude.js +610 -0
  26. package/dist/pi/claude/claude.js.map +1 -0
  27. package/dist/pi/claude/codec.d.ts +67 -0
  28. package/dist/pi/claude/codec.d.ts.map +1 -0
  29. package/dist/pi/claude/codec.js +140 -0
  30. package/dist/pi/claude/codec.js.map +1 -0
  31. package/dist/pi/{claude.d.ts → claude/sdk.d.ts} +11 -10
  32. package/dist/pi/claude/sdk.d.ts.map +1 -0
  33. package/dist/pi/claude/sdk.js +63 -0
  34. package/dist/pi/claude/sdk.js.map +1 -0
  35. package/dist/pi/codex/account.d.ts +11 -5
  36. package/dist/pi/codex/account.d.ts.map +1 -1
  37. package/dist/pi/codex/account.js +23 -6
  38. package/dist/pi/codex/account.js.map +1 -1
  39. package/dist/pi/codex/app-server.d.ts +19 -7
  40. package/dist/pi/codex/app-server.d.ts.map +1 -1
  41. package/dist/pi/codex/app-server.js +41 -36
  42. package/dist/pi/codex/app-server.js.map +1 -1
  43. package/dist/pi/codex/codec.d.ts +10 -84
  44. package/dist/pi/codex/codec.d.ts.map +1 -1
  45. package/dist/pi/codex/codec.js +18 -80
  46. package/dist/pi/codex/codec.js.map +1 -1
  47. package/dist/pi/codex/codex.d.ts +0 -1
  48. package/dist/pi/codex/codex.d.ts.map +1 -1
  49. package/dist/pi/codex/codex.js +89 -18
  50. package/dist/pi/codex/codex.js.map +1 -1
  51. package/dist/pi/conversation.d.ts +33 -12
  52. package/dist/pi/conversation.d.ts.map +1 -1
  53. package/dist/pi/conversation.js +58 -18
  54. package/dist/pi/conversation.js.map +1 -1
  55. package/dist/pi/pi.d.ts +1 -4
  56. package/dist/pi/pi.d.ts.map +1 -1
  57. package/dist/pi/pi.js +29 -175
  58. package/dist/pi/pi.js.map +1 -1
  59. package/dist/pi/sdk.d.ts +18 -15
  60. package/dist/pi/sdk.d.ts.map +1 -1
  61. package/dist/pi/sdk.js +45 -55
  62. package/dist/pi/sdk.js.map +1 -1
  63. package/dist/pi/session.d.ts +13 -14
  64. package/dist/pi/session.d.ts.map +1 -1
  65. package/dist/pi/session.js +63 -24
  66. package/dist/pi/session.js.map +1 -1
  67. package/dist/pi/turns.d.ts +7 -7
  68. package/dist/pi/turns.d.ts.map +1 -1
  69. package/dist/pi/turns.js +22 -9
  70. package/dist/pi/turns.js.map +1 -1
  71. package/dist/pi/usage.d.ts +36 -22
  72. package/dist/pi/usage.d.ts.map +1 -1
  73. package/dist/pi/usage.js +79 -33
  74. package/dist/pi/usage.js.map +1 -1
  75. package/dist/preflight/command.d.ts +16 -4
  76. package/dist/preflight/command.d.ts.map +1 -1
  77. package/dist/preflight/command.js +29 -27
  78. package/dist/preflight/command.js.map +1 -1
  79. package/dist/preflight/preflight.d.ts +19 -7
  80. package/dist/preflight/preflight.d.ts.map +1 -1
  81. package/dist/preflight/preflight.js +73 -32
  82. package/dist/preflight/preflight.js.map +1 -1
  83. package/dist/prompt.d.ts +12 -9
  84. package/dist/prompt.d.ts.map +1 -1
  85. package/dist/prompt.js +33 -4
  86. package/dist/prompt.js.map +1 -1
  87. package/dist/protocol/codex.d.ts +122 -7
  88. package/dist/protocol/codex.d.ts.map +1 -1
  89. package/dist/protocol/codex.js +109 -1
  90. package/dist/protocol/codex.js.map +1 -1
  91. package/dist/protocol/count.d.ts +4 -0
  92. package/dist/protocol/count.d.ts.map +1 -0
  93. package/dist/protocol/count.js +4 -0
  94. package/dist/protocol/count.js.map +1 -0
  95. package/dist/runtime.d.ts +110 -55
  96. package/dist/runtime.d.ts.map +1 -1
  97. package/dist/runtime.js +114 -27
  98. package/dist/runtime.js.map +1 -1
  99. package/package.json +35 -19
  100. package/dist/pi/claude.d.ts.map +0 -1
  101. package/dist/pi/claude.js +0 -302
  102. package/dist/pi/claude.js.map +0 -1
  103. package/dist/pi/live.d.ts +0 -4
  104. package/dist/pi/live.d.ts.map +0 -1
  105. package/dist/pi/live.js +0 -11
  106. package/dist/pi/live.js.map +0 -1
  107. package/dist/pi/subagents.d.ts +0 -29
  108. package/dist/pi/subagents.d.ts.map +0 -1
  109. package/dist/pi/subagents.js +0 -120
  110. package/dist/pi/subagents.js.map +0 -1
  111. package/dist/pi/thinking.d.ts +0 -8
  112. package/dist/pi/thinking.d.ts.map +0 -1
  113. package/dist/pi/thinking.js +0 -15
  114. package/dist/pi/thinking.js.map +0 -1
package/README.md CHANGED
@@ -1,57 +1,127 @@
1
1
  # @usine/agents
2
2
 
3
- The agent runtime for [usine](https://github.com/basaltbytes/usine). Each Attempt starts an in-process Pi parent, which can delegate focused work through the usine-owned `subagent` tool. An agent's brain spec selects Claude Code, Codex, or Pi for the child.
3
+ The agent layer for [usine](https://github.com/basaltbytes/usine): the Claude Code, Codex, and Pi child backends that an attempt's Pi parent delegates to, their prompts, and harness preflight. The parent runs in the orchestrator as a durable task (`@usine/durable`); its `subagent` tool starts the child that the agent's brain spec selects. [Attempt execution](https://docs.usine.dev/architecture/execution/) describes each harness.
4
4
 
5
- `@usine/agents/prompt` and `@usine/agents/runtime` are platform-free: prompt assembly, typed runtime errors, and the `AgentRuntime` and `ChildBackend` service contracts. The Node implementation lives at `@usine/agents/pi/live`:
5
+ 0.x APIs can change between minor releases. Supply `effect` at the workspace's pinned version. Harness SDKs and protocols are exact-pinned.
6
6
 
7
- ```ts
8
- import { layer as agentLayer } from "@usine/agents/pi/live";
9
- ```
7
+ ## Compose the child backends
10
8
 
11
- Use `@usine/agents/pi` to compose the Pi parent with your own child backend:
9
+ `/prompt` and `/runtime` are platform-free: prompt assembly, typed runtime errors, and the `ChildBackend` contracts. `childDispatcherLayer` (`/children`) picks a backend from the agent's `harness`. Provide its three backends from `/claude`, `/codex`, and `/pi`, which also export their SDK contracts and production SDK layers. `@usine/durable/live` composes them:
12
10
 
13
11
  ```ts
14
12
  import { Layer } from "effect";
15
- import { piParentLayer, piSdkLive } from "@usine/agents/pi";
16
-
17
- // childBackendLayer supplies ChildBackend from @usine/agents/runtime.
18
- const agentLayer = piParentLayer.pipe(
19
- Layer.provide(piSdkLive),
20
- Layer.provide(childBackendLayer),
13
+ import { childDispatcherLayer } from "@usine/agents/children";
14
+ import { claudeChildBackendLayer, claudeSdkLive } from "@usine/agents/claude";
15
+ import { codexAppServerLive, codexChildBackendLayer } from "@usine/agents/codex";
16
+ import { piChildBackendLayer, piSdkLive } from "@usine/agents/pi";
17
+
18
+ const childBackendLayer = childDispatcherLayer.pipe(
19
+ Layer.provide(
20
+ Layer.mergeAll(
21
+ claudeChildBackendLayer.pipe(Layer.provide(claudeSdkLive)),
22
+ codexChildBackendLayer.pipe(Layer.provide(codexAppServerLive)),
23
+ piChildBackendLayer.pipe(Layer.provide(piSdkLive)),
24
+ ),
25
+ ),
21
26
  );
22
27
  ```
23
28
 
24
- `@usine/agents/children` provides `childDispatcherLayer`, which selects a child backend by harness. Compose its `ClaudeChildBackend`, `CodexChildBackend`, and `PiChildBackend` requirements with the adapter layers from `/claude`, `/codex`, and `/pi`. These Node entry points also provide their SDK service contracts and production SDK layers for caller-controlled composition. `/pi/live` assembles the parent and all three child backends.
29
+ The Claude backend also needs `ClaudeLoginLock` (`/runtime`), which every Claude command that can refresh a Claude login takes: one lock per worker, or per machine of a provisioned worker, whose machines each have their own login. Give the same instance (`claudeLoginLockLayer` from `@usine/executor`) to the backends and the executor running preflight. Like Codex's app server, it also needs a `ChildProcessSpawner`, to stop a child on its worker before it resumes the child.
25
30
 
26
- The production layer resolves the Pi parent model from the Run spec, installs the subagents extension, and dispatches each child from the rostered `harness` field. The Run spec also defines the per-Attempt child-session budget. Every accepted spawn logs its cumulative count and a 160-character task excerpt; an excess spawn stops the parent with `ChildSessionBudgetExceededError`. The parent accepts API-key-backed Pi models and ChatGPT subscription models from the `openai-codex` provider. Claude Code runs the worker’s `claude` executable through the Agent SDK with `bypassPermissions` and reads only worker-user settings. Codex uses app-server JSON-RPC over standard input and output with `approvalPolicy: never`; it verifies ChatGPT authentication with `account/read` before creating an ephemeral thread and records ChatGPT plan rate-window updates as `PoolRateLimitsUpdated` session events. Pi children remain API-key-only.
31
+ ## Harnesses and credentials
27
32
 
28
- The Attempt worktree counts as repository-controlled input. Pi ignores project `.pi/` settings, extensions, prompt templates, skills, and themes. Claude omits project setting sources, while Codex marks the worktree untrusted before starting its thread. AGENTS instructions remain available to the coding harnesses. Worker isolation remains the executor's responsibility.
33
+ - **Parent:** a Pi model with an API key, or an `openai-codex` ChatGPT model through Plus or Pro OAuth. The start log records `apiKeys` or `chatgptPlan`. A child past the run spec's child-session budget stops the parent with `ChildSessionBudgetExceededError`.
34
+ - **Claude Code:** the worker's `claude` through the Agent SDK, with `bypassPermissions`, worker-user settings only, and the CLI login.
35
+ - **Codex:** app-server JSON-RPC over stdio, with `approvalPolicy: never` and the CLI login. It checks the login with `account/read` before creating a thread, and records plan rate windows as `PoolRateLimitsUpdated`. An API-key login, no login, or an unsupported account fails with `PoolUnavailableError`, which needs the operator. A login change ends the session before more rate windows are recorded. If `ChildSession.gatewayCredential` is set, the gateway holds the OpenAI credential and Codex has no login: the child skips the login check and ignores login changes, and its pool follows the credential kind, `chatgptPlan` for a ChatGPT login and `apiKeys` for an API key.
36
+ - **Pi children:** API keys only.
29
37
 
30
- Parent and child output becomes typed session events: `SessionStarted`, `TextOutput`, tool calls, pool observations, and `SessionFinished`. Completed assistant text produces `TextOutput` events; streaming deltas don't enter the Attempt transcript. Parent and child controls support steer and interrupt. A child session always names one parent session, so recursive child events cannot enter the public model.
38
+ Logs record the verified authentication method and pool. On completion or interrupt, usine closes the input of the child's Codex app server and waits for it to exit before publishing, releasing background commands. Scope cleanup does the same, bounded, and logs before force-stopping a stalled process.
31
39
 
32
- Model identifiers stay opaque until the selected harness checks its registry. Agent-reported failures use `AgentResultError`; SDK, process, and protocol failures use `SessionOperationError`. `toAttemptFailure` maps only `SessionOperationError` to retryable infrastructure failure, while `ChildSessionBudgetExceededError` becomes a non-retryable `ChildSessionBudgetFailure`.
40
+ `/pi/authentication` exports the parent's rules for other runtimes: `splitPiModel` (parses `provider/model`), `piAuthentication` (a provider's credential check), `requiredPiPool` (a provider's pool), and `resolvePiPool` (its credential's pool).
33
41
 
34
- Configure Pi with either a provider API key or ChatGPT Plus/Pro OAuth for the parent. Pi children require API keys; Claude and Codex children use their CLI login state. The parent-start log records `apiKeys` or `chatgptPlan` according to the resolved credential. The harness SDK and protocol dependencies use exact versions.
42
+ `/openai-prices` exports `openAiPrice`, which prices the token breakdown of a Codex session's `SessionTokensUpdated` at OpenAI's API rates. It returns `None` for a model that the bundled table lacks. The table covers the listed models of each supported Codex CLI version.
35
43
 
36
- `@usine/agents/preflight` provides the Node `preflight(brain)` operation. Supply a `ChildProcessSpawner` layer to check a worker without running a prompt. It accepts Codex CLI **0.144.0 or 0.153.4**, verifies the Codex ChatGPT account, checks `claude auth status --json` for a Claude subscription, and checks Pi’s model and API-key authentication through the installed SDK. `HarnessUnavailableError` identifies a binary, version, or login gap; `SessionOperationError` reports an incomplete check, including a timeout or failed SDK connection. Install and log in as the worker user before starting usine.
44
+ ## Child hosts
37
45
 
38
- A Codex API-key login, absent login, or unsupported account response produces `PoolUnavailableError`, which requires operator action. The verified authentication method and pool appear in structured logs. An authentication change during a running Codex session ends it before further rate windows are recorded.
46
+ `ChildRequest.host` (`/child-host`) sets where a child runs, and `ChildRequest.worker` names that machine's worker:
39
47
 
40
- The opt-in live contracts run in disposable directories. They steer a child during a waiting command, assert the changed response and event framing, and interrupt another active command. The Codex contract also requires a real rate-window snapshot. Use a supported Codex CLI with a ChatGPT login; the Pi contract requires API-key authentication for the specified provider.
48
+ - `LocalProcess`: on this machine, with `ChildRequest.environment` as the whole environment.
49
+ - `RemoteShell`: one `ssh <args> <destination> -- <remote command>`, which enters the worktree, exports the request's environment over the machine's own, writes its pid to `<worktree>.child-<n>.pid`, and runs the harness. The SSH client gets only `RemoteShell.sshEnvironment`. With `container`, the remote command is `<runtime> exec -i <name> sh -c <command line>`, so the directory, variables, and pid file are inside that container.
41
50
 
42
- Run them with explicit models:
51
+ Executors run `CHILD_KILL_SCRIPT` remotely to stop every child in those pid files before keeping or removing a worktree. Claude Code runs remotely through `spawnClaudeCodeProcess`, Codex over the same SSH command. Pi children run tools in this process and fail on `RemoteShell` with `AgentResultError`. They get only Pi's default tools, `read`, `bash`, `edit`, and `write`, whatever the global Pi `defaultTools` setting adds, and `bash` runs with exactly `ChildRequest.environment`.
43
52
 
44
- ```sh
45
- USINE_CODEX_INTEGRATION_MODEL=gpt-5.6-terra pnpm --filter @usine/agents test:integration:codex
46
- USINE_PI_INTEGRATION_MODEL=anthropic/claude-sonnet-4-6 pnpm --filter @usine/agents test:integration:pi
53
+ The worktree is repo-controlled input: Pi ignores the project's `.pi/` settings, extensions, prompt templates, skills, and themes; Claude omits project setting sources; Codex marks the worktree untrusted. AGENTS instructions still apply. The executor isolates the worker.
54
+
55
+ ## Session events and operator actions
56
+
57
+ Child output becomes typed events: `SessionStarted`, `TextOutput` for completed assistant text (streaming deltas stay out of the transcript), tool calls, pool observations, and `SessionFinished`. Each child session has one parent session; recursive child events can't enter the public model.
58
+
59
+ `RunningSession.intervene(action, onDelivery)` applies steer, interrupt, and holds. A message to an idle or paused child starts a turn and emits `SessionResumed`. A message to a working child joins the turn and returns `InterventionState.queued`; `onDelivery` runs once, with the child's state when the harness reads it, or with none if the child stops first. Pi reads it when the queued steering message starts, Codex when it records a `userMessage` item, and Claude Code with a result whose `queued_turn_count` no longer counts it. A finished turn has read all its queued messages.
60
+
61
+ ## Claude login lock
62
+
63
+ A Claude child holds the `ClaudeLoginLock` of its worker, or of its machine (`ChildRequest.machine`) on a provisioned worker, from process start until its first messages prove the login (a rate-limit or thinking update, an API retry, a model answer, or the turn result), for one minute at most. `launch` returns then.
64
+
65
+ If the first turn ends with `Failed to refresh OAuth token: another Claude Code process ...` and no tokens spent, another process had the refresh lock. The backend stops the process and relaunches after 15, 30, then 60 seconds, logging each retry at info. Only the running launch is transcribed. If the last launch fails the same way, the parent gets Claude's error as the child's report.
66
+
67
+ ## Background work
68
+
69
+ Claude children use the CLI's background-task snapshots and session-idle signal to wait for background work and its follow-up answer, without an operator hold. Queued user turns delay completion; ambient watchers don't. An output stream that closes without a completed answer fails with `SessionOperationError`.
70
+
71
+ The prompts tell parents and children, before returning, to run gates, tests, and CI watches in the foreground, wait for background results and pending checks, finish dependent steps, record real blockers on the issue or pull request, and push commits, since cleanup removes the worktree. This also covers older CLIs without background-task or session-state messages.
72
+
73
+ ## Lost connections
74
+
75
+ If the SSH connection to a remote Claude child breaks, for example while the orchestrator's machine sleeps, the SSH client exits with `SSH_FAILURE` (255) and the output stream fails. The backend then resumes the child instead of failing it: it stops the Claude process left on the worker with `CHILD_STOP_SCRIPT` and the child's pid file, and starts Claude with the saved session in the same child session, so the parent spends no child budget. A working child gets a message that asks it to check the worktree and continue, followed by the operator messages that it hadn't read. A paused or idle child waits for the operator. Open tool calls end as errors, and token usage and cost carry over.
76
+
77
+ The backend tries after 5 s, 15 s, 30 s, 1 min, 2 min, and 5 min; a message from Claude resets the count. After the last try, on a local host, or if Claude itself exited, the child fails with `SessionOperationError` as before. A Codex child whose SSH connection breaks fails.
78
+
79
+ ## Outcome markers
80
+
81
+ An unresolved blocker starts the final answer with `[usine:blocked]`, then details and incomplete work on that line or the next:
82
+
83
+ ```text
84
+ [usine:blocked]
85
+ Repository permission denied; publishing remains incomplete.
86
+ ```
87
+
88
+ An operator-only decision starts it with `[usine:question]` and the question, which the prompt tells the agent to post on the item first:
89
+
90
+ ```text
91
+ [usine:question]
92
+ Should egress use HTTP CONNECT or a SOCKS proxy? Both fit the gateway.
47
93
  ```
48
94
 
49
- The protocol conformance gate retains 0.144.0 as its baseline. Codex 0.153.4 was also checked against its generated protocol types and the real steering/interruption contracts, including Astra. Other CLI builds require verification before they are accepted.
95
+ `reportedOutcome` reads markers as `AgentBlockedError` and `AgentQuestionError`, and `reportText` writes them. A parent's blocker becomes a non-retryable `BlockedFailure` and its question a non-retryable `QuestionFailure`: the run fails, its loop suppresses the item, and the run waits for the operator. A child's harness failure (`AgentResultError`) goes to the parent as a `[usine:failed]` report that `reportedOutcome` reads back, so the attempt ends with `AgentFailure` and suppresses nothing: the harness posted nothing.
96
+
97
+ A child's report returns to the parent's `subagent` call as text opening with its marker, to return verbatim. The parent keeps the first: a later ordinary answer fails with it, and the paraphrase stays in the transcript. Successful siblings don't clear it. Held children can take guidance and replace their answer before release. A marker without details still fails.
98
+
99
+ Model ids stay opaque until the harness checks its registry. Harness failures use `AgentResultError`. `toAttemptFailure` maps only `SessionOperationError` (SDK, process, and protocol failures) to a retryable infrastructure failure; `ChildSessionBudgetExceededError` becomes a non-retryable `ChildSessionBudgetFailure`.
100
+
101
+ ## GitHub identity and reviews
102
+
103
+ Agents share the installation's GitHub App identity. The instructions require comment reviews at the reviewed head and the loop's outcome labels. A success label doesn't satisfy GitHub's required review: an independent reviewer approves and the operator merges. See [Write review and fix loops](https://docs.usine.dev/guides/review-fix-loops/).
104
+
105
+ ## Resume a harness session
50
106
 
51
- Each Codex child owns one app-server process. On completion or interruption, usine closes its input and waits for graceful shutdown before publishing the result, so background commands are released. Scope cleanup uses the same bounded shutdown and logs before forcing a stalled process to stop.
107
+ Claude children save sessions, and Codex threads aren't ephemeral. `SessionStarted.harnessSession` and launch logs (`harnessSessionId`, `sessionId`, `cwd`) give the id and directory. While the worktree and session files exist, run `claude --resume <id>` or `codex resume <id>` there, on the same worker and account. A Codex child whose environment sets `CODEX_HOME` saves its sessions there, and `HarnessSession.home` and the launch log's `home` record that directory: run `CODEX_HOME=<home> codex resume <id>`. An attempt behind a gateway gets its own `CODEX_HOME`, and its model credential isn't in your shell.
52
108
 
53
- Everything here is 0.x, so APIs can change between minor releases. Supply `effect` at the version pinned by the usine workspace.
109
+ ## Preflight
54
110
 
55
- Agents share the installation’s GitHub App identity. Runtime instructions require comment reviews at the reviewed head commit and the outcome labels defined by the Loop. An agent success label does not satisfy GitHub’s required-review rule; an independent reviewer supplies required approval and the operator merges. See the [review/fix guide](../../docs/guides/review-fix-loops.md).
111
+ `preflight(brain, host, environment)` (`/preflight`, Node) checks a worker without a prompt, using a `ChildProcessSpawner` layer. Each check sees `environment` as a child does. On a `RemoteShell` host, checks run over SSH, an SSH failure (exit 255) is an incomplete check rather than a missing harness, and a Pi brain is unavailable.
112
+
113
+ It accepts Codex CLI **0.153.4 or 0.159.0 through 0.159.3** (`supportedCodexVersions`; `unsupportedCodexVersion` returns the error for other `codex --version` output) with a verified ChatGPT account, a Claude subscription in `claude auth status --json`, and Pi's model and API key through the SDK. `HarnessUnavailableError` reports a missing binary, version, or login; `SessionOperationError` an incomplete check, such as a timeout or failed SDK connection. Install and log in as the worker user before starting usine.
114
+
115
+ ## Live contract tests
116
+
117
+ The opt-in live contracts, in disposable directories, steer a child during a waiting command, checking the response and event framing, and interrupt another; Codex also needs a real rate-window snapshot. Claude and Codex cases also run through `test/fixtures/ssh-shim.sh`, an SSH stand-in that runs the remote command locally, to check it, its environment, and its pid file with the real CLIs.
118
+
119
+ They need a supported Codex CLI with a ChatGPT login, a Claude CLI with a subscription login, an API key for the Pi provider, and explicit models:
120
+
121
+ ```sh
122
+ USINE_CLAUDE_INTEGRATION_MODEL=claude-sonnet-5-5 pnpm --filter @usine/agents test:integration:claude
123
+ USINE_CODEX_INTEGRATION_MODEL=gpt-5.6-terra pnpm --filter @usine/agents test:integration:codex
124
+ USINE_PI_INTEGRATION_MODEL=anthropic/claude-sonnet-4-6 pnpm --filter @usine/agents test:integration:pi
125
+ ```
56
126
 
57
- Claude children persist their sessions; Codex children start non-ephemeral threads. Their `SessionStarted.harnessSession` records the native session id and worker working directory, and structured launch logs include `harnessSessionId`, `sessionId`, and `cwd`. Resume with `claude --resume <id>` or `codex resume <id>` from that directory on the same worker and account, while its worktree and saved session files still exist. Attempt cleanup removes the worktree.
127
+ The protocol conformance baseline is 0.159.2. Codex 0.159.0, 0.159.1, and 0.159.3 have the same protocol crates. Codex 0.153.4 passed the real steering and interruption contracts, including Astra, and its generated types have the same shape as 0.159.2's for every field the adapter uses. Other CLI builds need verification first.
@@ -0,0 +1,90 @@
1
+ import type { AgentSessionId } from "@usine/core/agent/session";
2
+ import { Schema } from "effect";
3
+ declare const LocalProcess_base: Schema.Class<LocalProcess, Schema.TaggedStruct<"LocalProcess", {}>, {}>;
4
+ /** A host that runs children as local processes. */
5
+ export declare class LocalProcess extends LocalProcess_base {
6
+ }
7
+ /** The local SSH executable and the options before the destination. */
8
+ export declare const SshCommand: Schema.Struct<{
9
+ readonly command: Schema.NonEmptyString;
10
+ readonly args: Schema.$Array<Schema.String>;
11
+ }>;
12
+ export type SshCommand = typeof SshCommand.Type;
13
+ /** A running container on the remote machine, entered with `<runtime> exec`. */
14
+ export declare const ShellContainer: Schema.Struct<{
15
+ /** The container CLI on the machine, such as `docker`. */
16
+ readonly runtime: Schema.NonEmptyString;
17
+ readonly name: Schema.NonEmptyString;
18
+ }>;
19
+ export type ShellContainer = typeof ShellContainer.Type;
20
+ declare const RemoteShell_base: Schema.Class<RemoteShell, Schema.TaggedStruct<"RemoteShell", {
21
+ readonly ssh: Schema.Struct<{
22
+ readonly command: Schema.NonEmptyString;
23
+ readonly args: Schema.$Array<Schema.String>;
24
+ }>;
25
+ /**
26
+ * The SSH client's process environment: what SSH needs to connect, never the orchestrator's
27
+ * secrets (ADR 0011), plus the variables that `-o SendEnv=` options in `ssh` forward to the
28
+ * child. Use it for values that must not appear on the command line, such as a Gateway caller
29
+ * token. Never log this field.
30
+ */
31
+ readonly sshEnvironment: Schema.$Record<Schema.String, Schema.String>;
32
+ /** The SSH destination, passed as one argument. */
33
+ readonly destination: Schema.NonEmptyString;
34
+ /**
35
+ * The container that runs each command, with the working directory, variables, and PID file
36
+ * inside it. If absent, the SSH user's shell runs the command.
37
+ */
38
+ readonly container: Schema.optionalKey<Schema.Struct<{
39
+ /** The container CLI on the machine, such as `docker`. */
40
+ readonly runtime: Schema.NonEmptyString;
41
+ readonly name: Schema.NonEmptyString;
42
+ }>>;
43
+ }>, {}>;
44
+ /** A host that runs each child on another machine as one SSH command, through its standard streams. */
45
+ export declare class RemoteShell extends RemoteShell_base {
46
+ }
47
+ /** The host that runs an Attempt's children, chosen by the Worker that prepared its checkout. */
48
+ export declare const ChildHost: Schema.Union<readonly [typeof LocalProcess, typeof RemoteShell]>;
49
+ export type ChildHost = typeof ChildHost.Type;
50
+ /** A command for a host to run. */
51
+ export interface HostInvocation {
52
+ /** The working directory; defaults to the host user's home directory. */
53
+ readonly cwd?: string | undefined;
54
+ /**
55
+ * The command's complete environment on a local host, or the variables exported on top of a remote
56
+ * shell's environment; `undefined` values are skipped.
57
+ */
58
+ readonly environment?: Readonly<Record<string, string | undefined>> | undefined;
59
+ /** On a remote shell, the file that contains the command's PID while it runs, so cleanup can stop it after the SSH client exits. */
60
+ readonly pidFile?: string | undefined;
61
+ readonly command: string;
62
+ readonly args: ReadonlyArray<string>;
63
+ }
64
+ /**
65
+ * Returns the SSH client arguments that run `invocation`: the options, the destination, and one
66
+ * remote command line. In a container, the line runs `sh -c` with `invocation`'s command line
67
+ * through `<runtime> exec -i`, so its standard streams pass through.
68
+ */
69
+ export declare const sshArguments: (host: RemoteShell, invocation: HostInvocation) => Array<string>;
70
+ /** Returns the PID file path of a remote child: next to its worktree, named by the child number in its session ID. */
71
+ export declare const childPidFile: (worktree: string, session: AgentSessionId) => string;
72
+ /**
73
+ * The exit code of the OpenSSH client when it can't connect, loses the connection, or can't run the
74
+ * remote command. Any other code is the remote command's.
75
+ */
76
+ export declare const SSH_FAILURE = 255;
77
+ /**
78
+ * A shell script that stops an Attempt's remaining remote children before its worktree is kept or
79
+ * removed: for each `<worktree>.child-*.pid`, it sends TERM, waits, then sends KILL. Run it with
80
+ * `sh -c` and the worktree as `$1`; it prints one outcome per PID file. An exited SSH client does
81
+ * not mean that its remote command stopped.
82
+ */
83
+ export declare const CHILD_KILL_SCRIPT: string;
84
+ /**
85
+ * A shell script that stops one remote child, whose PID file (`childPidFile`) is `$1`, in the way
86
+ * `CHILD_KILL_SCRIPT` stops each child. Run it with `sh -c`; it prints one outcome.
87
+ */
88
+ export declare const CHILD_STOP_SCRIPT = "pidfile=$1\n pid=$(cat \"$pidfile\" 2>/dev/null)\n if [ -n \"$pid\" ] && kill -TERM \"$pid\" 2>/dev/null; then\n n=0\n while kill -0 \"$pid\" 2>/dev/null && [ \"$n\" -lt 10 ]; do sleep 0.5; n=$((n+1)); done\n if kill -0 \"$pid\" 2>/dev/null; then kill -KILL \"$pid\" 2>/dev/null; echo \"$pidfile killed\"; else echo \"$pidfile stopped\"; fi\n else\n echo \"$pidfile absent\"\n fi\n rm -f \"$pidfile\"";
89
+ export {};
90
+ //# sourceMappingURL=child-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-host.d.ts","sourceRoot":"","sources":["../../src/child-host/child-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;AAEhC,oDAAoD;AACpD,qBAAa,YAAa,SAAQ,iBAGjC;CAAG;AAEJ,uEAAuE;AACvE,eAAO,MAAM,UAAU;;;EAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAEhD,gFAAgF;AAChF,eAAO,MAAM,cAAc;IACzB,0DAA0D;;;EAG1D,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;;;;;;IAOpD;;;;;OAKG;;IAEH,mDAAmD;;IAEnD;;;OAGG;;QAvBL,0DAA0D;;;;;AAM5D,uGAAuG;AACvG,qBAAa,WAAY,SAAQ,gBAmBhC;CAAG;AAEJ,iGAAiG;AACjG,eAAO,MAAM,SAAS,kEAA4C,CAAC;AACnE,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC;AAE9C,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IAChF,oIAAoI;IACpI,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtC;AA8BD;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,WAAW,cAAc,cAAc,KAAG,KAAK,CAAC,MAAM,CAUxF,CAAC;AAEF,sHAAsH;AACtH,eAAO,MAAM,YAAY,aAAc,MAAM,WAAW,cAAc,KAAG,MACD,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,WAAW,MAAM,CAAC;AAiB/B;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAOtB,CAAC;AAET;;;GAGG;AACH,eAAO,MAAM,iBAAiB,saAA+B,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { Schema } from "effect";
2
+ /** A host that runs children as local processes. */
3
+ export class LocalProcess extends Schema.TaggedClass("@usine/agents/LocalProcess")("LocalProcess", {}) {
4
+ }
5
+ /** The local SSH executable and the options before the destination. */
6
+ export const SshCommand = Schema.Struct({
7
+ command: Schema.NonEmptyString,
8
+ args: Schema.Array(Schema.String),
9
+ });
10
+ /** A running container on the remote machine, entered with `<runtime> exec`. */
11
+ export const ShellContainer = Schema.Struct({
12
+ /** The container CLI on the machine, such as `docker`. */
13
+ runtime: Schema.NonEmptyString,
14
+ name: Schema.NonEmptyString,
15
+ });
16
+ /** A host that runs each child on another machine as one SSH command, through its standard streams. */
17
+ export class RemoteShell extends Schema.TaggedClass("@usine/agents/RemoteShell")("RemoteShell", {
18
+ ssh: SshCommand,
19
+ /**
20
+ * The SSH client's process environment: what SSH needs to connect, never the orchestrator's
21
+ * secrets (ADR 0011), plus the variables that `-o SendEnv=` options in `ssh` forward to the
22
+ * child. Use it for values that must not appear on the command line, such as a Gateway caller
23
+ * token. Never log this field.
24
+ */
25
+ sshEnvironment: Schema.Record(Schema.String, Schema.String),
26
+ /** The SSH destination, passed as one argument. */
27
+ destination: Schema.NonEmptyString,
28
+ /**
29
+ * The container that runs each command, with the working directory, variables, and PID file
30
+ * inside it. If absent, the SSH user's shell runs the command.
31
+ */
32
+ container: Schema.optionalKey(ShellContainer),
33
+ }) {
34
+ }
35
+ /** The host that runs an Attempt's children, chosen by the Worker that prepared its checkout. */
36
+ export const ChildHost = Schema.Union([LocalProcess, RemoteShell]);
37
+ const VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
38
+ const quote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
39
+ const quoted = (words) => words.map(quote).join(" ");
40
+ /**
41
+ * Returns the remote POSIX shell command line for `invocation`, with every word quoted so it passes
42
+ * through OpenSSH unchanged. Throws if a variable name is not a shell identifier.
43
+ */
44
+ const remoteShellCommand = (invocation) => {
45
+ const exports = Object.entries(invocation.environment ?? {}).flatMap(([name, value]) => {
46
+ if (!VARIABLE_NAME.test(name))
47
+ throw new Error(`Cannot export ${name} in a remote shell`);
48
+ return value === undefined ? [] : [`${name}=${quote(value)}`];
49
+ });
50
+ const setup = [
51
+ ...(invocation.cwd === undefined ? [] : [`cd ${quote(invocation.cwd)}`]),
52
+ ...(exports.length === 0 ? [] : [`export ${exports.join(" ")}`]),
53
+ ];
54
+ const command = [invocation.command, ...invocation.args];
55
+ if (invocation.pidFile === undefined)
56
+ return [...setup, `exec ${quoted(command)}`].join(" && ");
57
+ // The inner shell writes its PID, then execs the command. The file exists only while the command
58
+ // runs, so cleanup never signals a reused PID.
59
+ const pidFile = quote(invocation.pidFile);
60
+ const run = `sh -c 'printf %s "$$" > "$0" && exec "$@"' ${pidFile} ${quoted(command)}`;
61
+ return `${[...setup, run].join(" && ")}; code=$?; rm -f ${pidFile}; exit "$code"`;
62
+ };
63
+ /**
64
+ * Returns the SSH client arguments that run `invocation`: the options, the destination, and one
65
+ * remote command line. In a container, the line runs `sh -c` with `invocation`'s command line
66
+ * through `<runtime> exec -i`, so its standard streams pass through.
67
+ */
68
+ export const sshArguments = (host, invocation) => {
69
+ const line = remoteShellCommand(invocation);
70
+ return [
71
+ ...host.ssh.args,
72
+ host.destination,
73
+ "--",
74
+ host.container === undefined
75
+ ? line
76
+ : `exec ${quoted([host.container.runtime, "exec", "-i", host.container.name, "sh", "-c", line])}`,
77
+ ];
78
+ };
79
+ /** Returns the PID file path of a remote child: next to its worktree, named by the child number in its session ID. */
80
+ export const childPidFile = (worktree, session) => `${worktree}.child-${session.slice(session.lastIndexOf(":") + 1)}.pid`;
81
+ /**
82
+ * The exit code of the OpenSSH client when it can't connect, loses the connection, or can't run the
83
+ * remote command. Any other code is the remote command's.
84
+ */
85
+ export const SSH_FAILURE = 255;
86
+ /**
87
+ * Stops the process in `$pidfile`: TERM, up to 5 seconds of waiting, then KILL. Prints
88
+ * `<pidfile> stopped`, `killed`, or `absent`, and removes the file.
89
+ */
90
+ const STOP_PID_FILE = `
91
+ pid=$(cat "$pidfile" 2>/dev/null)
92
+ if [ -n "$pid" ] && kill -TERM "$pid" 2>/dev/null; then
93
+ n=0
94
+ while kill -0 "$pid" 2>/dev/null && [ "$n" -lt 10 ]; do sleep 0.5; n=$((n+1)); done
95
+ if kill -0 "$pid" 2>/dev/null; then kill -KILL "$pid" 2>/dev/null; echo "$pidfile killed"; else echo "$pidfile stopped"; fi
96
+ else
97
+ echo "$pidfile absent"
98
+ fi
99
+ rm -f "$pidfile"`;
100
+ /**
101
+ * A shell script that stops an Attempt's remaining remote children before its worktree is kept or
102
+ * removed: for each `<worktree>.child-*.pid`, it sends TERM, waits, then sends KILL. Run it with
103
+ * `sh -c` and the worktree as `$1`; it prints one outcome per PID file. An exited SSH client does
104
+ * not mean that its remote command stopped.
105
+ */
106
+ export const CHILD_KILL_SCRIPT = `
107
+ found=0
108
+ for pidfile in "$1".child-*.pid; do
109
+ test -f "$pidfile" || continue
110
+ found=1${STOP_PID_FILE}
111
+ done
112
+ test "$found" = 1 || echo no-children
113
+ `.trim();
114
+ /**
115
+ * A shell script that stops one remote child, whose PID file (`childPidFile`) is `$1`, in the way
116
+ * `CHILD_KILL_SCRIPT` stops each child. Run it with `sh -c`; it prints one outcome.
117
+ */
118
+ export const CHILD_STOP_SCRIPT = `pidfile=$1${STOP_PID_FILE}`;
119
+ //# sourceMappingURL=child-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-host.js","sourceRoot":"","sources":["../../src/child-host/child-host.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,oDAAoD;AACpD,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,WAAW,CAAe,4BAA4B,CAAC,CAC9F,cAAc,EACd,EAAE,CACH;CAAG;AAEJ,uEAAuE;AACvE,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;CAClC,CAAC,CAAC;AAGH,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC,cAAc;CAC5B,CAAC,CAAC;AAGH,uGAAuG;AACvG,MAAM,OAAO,WAAY,SAAQ,MAAM,CAAC,WAAW,CAAc,2BAA2B,CAAC,CAC3F,aAAa,EACb;IACE,GAAG,EAAE,UAAU;IACf;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;IAC3D,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC,cAAc;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC;CAC9C,CACF;CAAG;AAEJ,iGAAiG;AACjG,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;AAkBnE,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAElD,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAE/E,MAAM,MAAM,GAAG,CAAC,KAA4B,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,kBAAkB,GAAG,CAAC,UAA0B,EAAU,EAAE;IAChE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACrF,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,oBAAoB,CAAC,CAAC;QAC1F,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,UAAU,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;KACjE,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,KAAK,EAAE,QAAQ,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChG,iGAAiG;IACjG,+CAA+C;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,8CAA8C,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACvF,OAAO,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,oBAAoB,OAAO,gBAAgB,CAAC;AACpF,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAiB,EAAE,UAA0B,EAAiB,EAAE;IAC3F,MAAM,IAAI,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC5C,OAAO;QACL,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI;QAChB,IAAI,CAAC,WAAW;QAChB,IAAI;QACJ,IAAI,CAAC,SAAS,KAAK,SAAS;YAC1B,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE;KACpG,CAAC;AACJ,CAAC,CAAC;AAEF,sHAAsH;AACtH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAE,OAAuB,EAAU,EAAE,CAChF,GAAG,QAAQ,UAAU,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAE/B;;;GAGG;AACH,MAAM,aAAa,GAAG;;;;;;;;;mBASH,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;WAItB,aAAa;;;CAGvB,CAAC,IAAI,EAAE,CAAC;AAET;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,aAAa,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { Effect } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
3
+ import { type ChildHost, type HostInvocation } from "./child-host.ts";
4
+ /**
5
+ * Returns the local process command that runs `invocation` on `host`: the command itself with
6
+ * exactly `environment` on a local host, or one SSH command whose client gets only `sshEnvironment`
7
+ * on a remote host.
8
+ */
9
+ export declare const hostCommand: (host: ChildHost, invocation: HostInvocation & {
10
+ readonly environment: Readonly<Record<string, string>>;
11
+ }, options: ChildProcess.CommandOptions) => ChildProcess.Command;
12
+ /**
13
+ * Runs `invocation` on `host` and returns its trimmed stdout and exit code; stderr is discarded. On
14
+ * a remote host, `SSH_FAILURE` means that SSH failed, not the command.
15
+ */
16
+ export declare const hostOutput: (host: import("./child-host.ts").LocalProcess | import("./child-host.ts").RemoteShell, invocation: HostInvocation & {
17
+ readonly environment: Readonly<Record<string, string>>;
18
+ }) => Effect.Effect<{
19
+ stdout: string;
20
+ exitCode: ChildProcessSpawner.ExitCode;
21
+ }, import("effect/PlatformError").PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
22
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/child-host/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAgB,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,eAAO,MAAM,WAAW,SAChB,SAAS,cACH,cAAc,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,WAC9E,YAAY,CAAC,cAAc,KACnC,YAAY,CAAC,OAgBf,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,UAAU;0BAEgC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;;;;yFAmBrF,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
3
+ import { homedir } from "node:os";
4
+ import { sshArguments } from "./child-host.js";
5
+ /**
6
+ * Returns the local process command that runs `invocation` on `host`: the command itself with
7
+ * exactly `environment` on a local host, or one SSH command whose client gets only `sshEnvironment`
8
+ * on a remote host.
9
+ */
10
+ export const hostCommand = (host, invocation, options) => {
11
+ switch (host._tag) {
12
+ case "LocalProcess":
13
+ return ChildProcess.make(invocation.command, invocation.args, {
14
+ ...options,
15
+ cwd: invocation.cwd ?? homedir(),
16
+ env: { ...invocation.environment },
17
+ extendEnv: false,
18
+ });
19
+ case "RemoteShell":
20
+ return ChildProcess.make(host.ssh.command, sshArguments(host, invocation), {
21
+ ...options,
22
+ env: { ...host.sshEnvironment },
23
+ extendEnv: false,
24
+ });
25
+ }
26
+ };
27
+ /**
28
+ * Runs `invocation` on `host` and returns its trimmed stdout and exit code; stderr is discarded. On
29
+ * a remote host, `SSH_FAILURE` means that SSH failed, not the command.
30
+ */
31
+ export const hostOutput = Effect.fn("ChildHost.output")(function* (host, invocation) {
32
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
33
+ return yield* Effect.scoped(Effect.gen(function* () {
34
+ const handle = yield* spawner.spawn(hostCommand(host, invocation, { detached: false, forceKillAfter: "2 seconds" }));
35
+ const [stdout, , exitCode] = yield* Effect.all([
36
+ handle.stdout.pipe(Stream.decodeText(), Stream.mkString),
37
+ handle.stderr.pipe(Stream.runDrain),
38
+ handle.exitCode,
39
+ ], { concurrency: "unbounded" });
40
+ return { stdout: stdout.trim(), exitCode };
41
+ }));
42
+ });
43
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/child-host/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAuC,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,IAAe,EACf,UAAuF,EACvF,OAAoC,EACd,EAAE;IACxB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;gBAC5D,GAAG,OAAO;gBACV,GAAG,EAAE,UAAU,CAAC,GAAG,IAAI,OAAO,EAAE;gBAChC,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC,WAAW,EAAE;gBAClC,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,KAAK,aAAa;YAChB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE;gBACzE,GAAG,OAAO;gBACV,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE;gBAC/B,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;IACP,CAAC;AACH,CAAC,CAAC;AAQF;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,EAC/D,IAAe,EACf,UAAuF;IAEvF,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAC;IAC/D,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CACjC,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAChF,CAAC;QACF,MAAM,CAAC,MAAM,EAAE,AAAD,EAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAC5C;YACE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YACnC,MAAM,CAAC,QAAQ;SAChB,EACD,EAAE,WAAW,EAAE,WAAW,EAAE,CAC7B,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAuB,CAAC;IAClE,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type TokenBreakdown, UsdAmount } from "@usine/core/agent/session";
2
+ import { Option } from "effect";
3
+ /**
4
+ * Returns the price of `tokens` at OpenAI's rates for `model`, a model ID as the Codex app-server
5
+ * reports it, or `None` if the table has no rates for the model.
6
+ */
7
+ export declare const openAiPrice: (model: string, tokens: TokenBreakdown) => Option.Option<UsdAmount>;
8
+ //# sourceMappingURL=openai-prices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-prices.d.ts","sourceRoot":"","sources":["../src/openai-prices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AA4BhC;;;GAGG;AACH,eAAO,MAAM,WAAW,UAAW,MAAM,UAAU,cAAc,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CASxF,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { UsdAmount } from "@usine/core/agent/session";
2
+ import { Option } from "effect";
3
+ // Standard tier, short context (up to 272K input tokens, the Codex context window), read from
4
+ // https://developers.openai.com/api/docs/pricing on 2026-10-26. `cacheWrite` is the 30-minute write
5
+ // rate, which applies when a request sets no cache retention, as Codex does.
6
+ const RATES = new Map([
7
+ ["gpt-6-astra", { input: 10, cachedInput: 1, cacheWrite: 12.5, output: 50 }],
8
+ ["gpt-6.1-sol", { input: 2, cachedInput: 0.1, cacheWrite: 2.5, output: 10 }],
9
+ ["gpt-6-sol", { input: 2, cachedInput: 0.2, cacheWrite: 2.5, output: 10 }],
10
+ ["gpt-6-luna", { input: 0.1, cachedInput: 0.01, cacheWrite: 0.125, output: 0.5 }],
11
+ ["gpt-5.6-sol", { input: 4, cachedInput: 0.4, cacheWrite: 5, output: 20 }],
12
+ ["gpt-5.6-terra", { input: 2, cachedInput: 0.2, cacheWrite: 2.5, output: 12 }],
13
+ ["gpt-5.6-luna", { input: 0.2, cachedInput: 0.02, cacheWrite: 0.25, output: 1.2 }],
14
+ ["gpt-5.5", { input: 5, cachedInput: 0.5, output: 30 }],
15
+ ["gpt-5.4", { input: 2.5, cachedInput: 0.25, output: 15 }],
16
+ ["gpt-5.4-mini", { input: 0.75, cachedInput: 0.075, output: 4.5 }],
17
+ ["gpt-5.2", { input: 1.75, cachedInput: 0.175, output: 14 }],
18
+ ]);
19
+ /**
20
+ * Returns the price of `tokens` at OpenAI's rates for `model`, a model ID as the Codex app-server
21
+ * reports it, or `None` if the table has no rates for the model.
22
+ */
23
+ export const openAiPrice = (model, tokens) => Option.map(Option.fromNullishOr(RATES.get(model)), (rates) => UsdAmount.make((tokens.input * rates.input +
24
+ tokens.cachedInput * rates.cachedInput +
25
+ tokens.cacheWrite * (rates.cacheWrite ?? rates.input) +
26
+ tokens.output * rates.output) /
27
+ 1_000_000));
28
+ //# sourceMappingURL=openai-prices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-prices.js","sourceRoot":"","sources":["../src/openai-prices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWhC,8FAA8F;AAC9F,oGAAoG;AACpG,6EAA6E;AAC7E,MAAM,KAAK,GAAqC,IAAI,GAAG,CAAC;IACtD,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC1E,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACjF,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC1E,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC9E,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAClF,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACvD,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC1D,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAClE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;CAC7D,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,MAAsB,EAA4B,EAAE,CAC7F,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAC3D,SAAS,CAAC,IAAI,CACZ,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;IACzB,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW;IACtC,MAAM,CAAC,UAAU,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC;IACrD,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7B,SAAS,CACZ,CACF,CAAC"}
@@ -6,8 +6,17 @@ export type PiAuthentication = {
6
6
  readonly subscription: boolean;
7
7
  };
8
8
  export type PiPool = Extract<BillingPool, "apiKeys" | "chatgptPlan">;
9
- /** The billing pool the chosen Pi provider spends from, checked before launch. */
9
+ /** Splits `provider/model` into provider and model ID; `undefined` unless both parts are present. */
10
+ export declare const splitPiModel: (model: string) => {
11
+ readonly provider: string;
12
+ readonly modelId: string;
13
+ } | undefined;
14
+ /** Converts Pi's credential check, with `subscription` set if the OAuth login is a subscription; returns `undefined` if Pi found no credential. */
15
+ export declare const piAuthentication: (check: {
16
+ readonly type: "api_key" | "oauth";
17
+ } | undefined, subscription: boolean) => PiAuthentication | undefined;
18
+ /** Returns the billing pool that a Pi provider requires; checked before launch. */
10
19
  export declare const requiredPiPool: (provider: string) => PiPool;
11
- /** Returns the billing pool of the credential that pays for the model request. */
20
+ /** Returns the billing pool of the credential used for the model request, or `undefined` if it is neither an API key nor a subscription login to `openai-codex`. */
12
21
  export declare const resolvePiPool: (provider: string, authentication: PiAuthentication | undefined) => PiPool | undefined;
13
22
  //# sourceMappingURL=authentication.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authentication.d.ts","sourceRoot":"","sources":["../../src/pi/authentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,SAAS,GAAG,aAAa,CAAC,CAAC;AAErE,kFAAkF;AAClF,eAAO,MAAM,cAAc,aAAc,MAAM,KAAG,MACO,CAAC;AAE1D,kFAAkF;AAClF,eAAO,MAAM,aAAa,aACd,MAAM,kBACA,gBAAgB,GAAG,SAAS,KAC3C,MAAM,GAAG,SAUX,CAAC"}
1
+ {"version":3,"file":"authentication.d.ts","sourceRoot":"","sources":["../../src/pi/authentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,SAAS,GAAG,aAAa,CAAC,CAAC;AAErE,qGAAqG;AACrG,eAAO,MAAM,YAAY,UAChB,MAAM,KACZ;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAK5D,CAAC;AAEF,mJAAmJ;AACnJ,eAAO,MAAM,gBAAgB,UACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,GAAG,SAAS,gBAC3C,OAAO,KACpB,gBAAgB,GAAG,SAKL,CAAC;AAElB,mFAAmF;AACnF,eAAO,MAAM,cAAc,aAAc,MAAM,KAAG,MACO,CAAC;AAE1D,oKAAoK;AACpK,eAAO,MAAM,aAAa,aACd,MAAM,kBACA,gBAAgB,GAAG,SAAS,KAC3C,MAAM,GAAG,SAUX,CAAC"}
@@ -1,6 +1,19 @@
1
- /** The billing pool the chosen Pi provider spends from, checked before launch. */
1
+ /** Splits `provider/model` into provider and model ID; `undefined` unless both parts are present. */
2
+ export const splitPiModel = (model) => {
3
+ const separator = model.indexOf("/");
4
+ return separator <= 0 || separator === model.length - 1
5
+ ? undefined
6
+ : { provider: model.slice(0, separator), modelId: model.slice(separator + 1) };
7
+ };
8
+ /** Converts Pi's credential check, with `subscription` set if the OAuth login is a subscription; returns `undefined` if Pi found no credential. */
9
+ export const piAuthentication = (check, subscription) => check?.type === "api_key"
10
+ ? { _tag: "ApiKey" }
11
+ : check?.type === "oauth"
12
+ ? { _tag: "OAuth", subscription }
13
+ : undefined;
14
+ /** Returns the billing pool that a Pi provider requires; checked before launch. */
2
15
  export const requiredPiPool = (provider) => provider === "openai-codex" ? "chatgptPlan" : "apiKeys";
3
- /** Returns the billing pool of the credential that pays for the model request. */
16
+ /** Returns the billing pool of the credential used for the model request, or `undefined` if it is neither an API key nor a subscription login to `openai-codex`. */
4
17
  export const resolvePiPool = (provider, authentication) => {
5
18
  if (authentication?._tag === "ApiKey")
6
19
  return "apiKeys";
@@ -1 +1 @@
1
- {"version":3,"file":"authentication.js","sourceRoot":"","sources":["../../src/pi/authentication.ts"],"names":[],"mappings":"AAOA,kFAAkF;AAClF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAU,EAAE,CACzD,QAAQ,KAAK,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AAE1D,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,QAAgB,EAChB,cAA4C,EACxB,EAAE;IACtB,IAAI,cAAc,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,IACE,cAAc,EAAE,IAAI,KAAK,OAAO;QAChC,cAAc,CAAC,YAAY;QAC3B,QAAQ,KAAK,cAAc,EAC3B,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC"}
1
+ {"version":3,"file":"authentication.js","sourceRoot":"","sources":["../../src/pi/authentication.ts"],"names":[],"mappings":"AAOA,qGAAqG;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,KAAa,EACwD,EAAE;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,SAAS,IAAI,CAAC,IAAI,SAAS,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;QACrD,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;AACnF,CAAC,CAAC;AAEF,mJAAmJ;AACnJ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,KAAyD,EACzD,YAAqB,EACS,EAAE,CAChC,KAAK,EAAE,IAAI,KAAK,SAAS;IACvB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;IACpB,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO;QACvB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE;QACjC,CAAC,CAAC,SAAS,CAAC;AAElB,mFAAmF;AACnF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAU,EAAE,CACzD,QAAQ,KAAK,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AAE1D,oKAAoK;AACpK,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,QAAgB,EAChB,cAA4C,EACxB,EAAE;IACtB,IAAI,cAAc,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,IACE,cAAc,EAAE,IAAI,KAAK,OAAO;QAChC,cAAc,CAAC,YAAY;QAC3B,QAAQ,KAAK,cAAc,EAC3B,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC"}
@@ -1,8 +1,5 @@
1
1
  import { Layer } from "effect";
2
2
  import { ChildBackend, ClaudeChildBackend, CodexChildBackend, PiChildBackend } from "../runtime.ts";
3
- /**
4
- * Selects Claude Code, Codex, or Pi for each delegated task from the Agent's harness.
5
- * Supply the three backend layers here, or use a concrete backend directly.
6
- */
3
+ /** Dispatches each delegated task to the Claude Code, Codex, or Pi backend according to the Agent's harness; requires all three backend layers. */
7
4
  export declare const childDispatcherLayer: Layer.Layer<ChildBackend, never, ClaudeChildBackend | CodexChildBackend | PiChildBackend>;
8
5
  //# sourceMappingURL=children.d.ts.map