subharness 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -9
- package/dist/adapters/approval-context.d.ts +2 -0
- package/dist/adapters/approval-context.js +9 -0
- package/dist/adapters/approval-context.js.map +1 -0
- package/dist/adapters/approval-lifetime.d.ts +8 -0
- package/dist/adapters/approval-lifetime.js +50 -0
- package/dist/adapters/approval-lifetime.js.map +1 -0
- package/dist/adapters/claude-approvals.d.ts +4 -0
- package/dist/adapters/claude-approvals.js +32 -0
- package/dist/adapters/claude-approvals.js.map +1 -0
- package/dist/adapters/claude-permissions.js +1 -1
- package/dist/adapters/claude-permissions.js.map +1 -1
- package/dist/adapters/claude-process.d.ts +3 -0
- package/dist/adapters/claude-process.js +11 -1
- package/dist/adapters/claude-process.js.map +1 -1
- package/dist/adapters/claude-result.d.ts +2 -0
- package/dist/adapters/claude-result.js +22 -0
- package/dist/adapters/claude-result.js.map +1 -0
- package/dist/adapters/claude.js +60 -12
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex-approvals.d.ts +6 -0
- package/dist/adapters/codex-approvals.js +111 -0
- package/dist/adapters/codex-approvals.js.map +1 -0
- package/dist/adapters/codex.js +36 -3
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/copilot-permissions.d.ts +26 -0
- package/dist/adapters/copilot-permissions.js +121 -0
- package/dist/adapters/copilot-permissions.js.map +1 -0
- package/dist/adapters/copilot-tools.d.ts +12 -0
- package/dist/adapters/copilot-tools.js +61 -0
- package/dist/adapters/copilot-tools.js.map +1 -0
- package/dist/adapters/copilot.d.ts +108 -0
- package/dist/adapters/copilot.js +819 -0
- package/dist/adapters/copilot.js.map +1 -0
- package/dist/adapters/cursor-cli-approvals.d.ts +2 -0
- package/dist/adapters/cursor-cli-approvals.js +83 -0
- package/dist/adapters/cursor-cli-approvals.js.map +1 -0
- package/dist/adapters/cursor-cli-model.d.ts +4 -0
- package/dist/adapters/cursor-cli-model.js +64 -0
- package/dist/adapters/cursor-cli-model.js.map +1 -0
- package/dist/adapters/cursor-cli-session.d.ts +32 -0
- package/dist/adapters/cursor-cli-session.js +316 -0
- package/dist/adapters/cursor-cli-session.js.map +1 -0
- package/dist/adapters/cursor-cli-tools.d.ts +20 -0
- package/dist/adapters/cursor-cli-tools.js +181 -0
- package/dist/adapters/cursor-cli-tools.js.map +1 -0
- package/dist/adapters/cursor-cli.d.ts +2 -0
- package/dist/adapters/cursor-cli.js +306 -0
- package/dist/adapters/cursor-cli.js.map +1 -0
- package/dist/adapters/cursor-model.d.ts +7 -0
- package/dist/adapters/cursor-model.js +67 -0
- package/dist/adapters/cursor-model.js.map +1 -0
- package/dist/adapters/cursor-rpc.d.ts +42 -0
- package/dist/adapters/cursor-rpc.js +271 -0
- package/dist/adapters/cursor-rpc.js.map +1 -0
- package/dist/adapters/cursor-session.d.ts +4 -0
- package/dist/adapters/cursor-session.js +327 -0
- package/dist/adapters/cursor-session.js.map +1 -0
- package/dist/adapters/cursor-startup.d.ts +16 -0
- package/dist/adapters/cursor-startup.js +74 -0
- package/dist/adapters/cursor-startup.js.map +1 -0
- package/dist/adapters/cursor-tools.d.ts +11 -0
- package/dist/adapters/cursor-tools.js +49 -0
- package/dist/adapters/cursor-tools.js.map +1 -0
- package/dist/adapters/cursor.d.ts +6 -0
- package/dist/adapters/cursor.js +17 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/fx-approvals.d.ts +2 -0
- package/dist/adapters/fx-approvals.js +14 -0
- package/dist/adapters/fx-approvals.js.map +1 -0
- package/dist/adapters/fx-auth.d.ts +12 -2
- package/dist/adapters/fx-auth.js +51 -61
- package/dist/adapters/fx-auth.js.map +1 -1
- package/dist/adapters/fx-profile.d.ts +8 -0
- package/dist/adapters/fx-profile.js +101 -0
- package/dist/adapters/fx-profile.js.map +1 -0
- package/dist/adapters/fx-rpc.d.ts +2 -0
- package/dist/adapters/fx-rpc.js +30 -4
- package/dist/adapters/fx-rpc.js.map +1 -1
- package/dist/adapters/fx-status.d.ts +2 -0
- package/dist/adapters/fx-status.js +96 -0
- package/dist/adapters/fx-status.js.map +1 -0
- package/dist/adapters/fx.js +77 -30
- package/dist/adapters/fx.js.map +1 -1
- package/dist/adapters/opencode-access.d.ts +13 -0
- package/dist/adapters/opencode-access.js +76 -0
- package/dist/adapters/opencode-access.js.map +1 -0
- package/dist/adapters/opencode-config.d.ts +11 -0
- package/dist/adapters/opencode-config.js +238 -0
- package/dist/adapters/opencode-config.js.map +1 -0
- package/dist/adapters/opencode-http.d.ts +28 -0
- package/dist/adapters/opencode-http.js +297 -0
- package/dist/adapters/opencode-http.js.map +1 -0
- package/dist/adapters/opencode-tools.d.ts +19 -0
- package/dist/adapters/opencode-tools.js +127 -0
- package/dist/adapters/opencode-tools.js.map +1 -0
- package/dist/adapters/opencode.d.ts +2 -0
- package/dist/adapters/opencode.js +569 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/rpc.d.ts +3 -0
- package/dist/adapters/rpc.js +45 -4
- package/dist/adapters/rpc.js.map +1 -1
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.js.map +1 -1
- package/dist/approvals/schema.d.ts +4 -0
- package/dist/approvals/schema.js +70 -0
- package/dist/approvals/schema.js.map +1 -0
- package/dist/approvals/types.d.ts +47 -0
- package/dist/approvals/types.js +2 -0
- package/dist/approvals/types.js.map +1 -0
- package/dist/cli/args.d.ts +3 -2
- package/dist/cli/args.js +24 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/dashboard-client.d.ts +5 -1
- package/dist/cli/dashboard-client.js +162 -15
- package/dist/cli/dashboard-client.js.map +1 -1
- package/dist/cli/dashboard-controller.d.ts +7 -0
- package/dist/cli/dashboard-controller.js +27 -0
- package/dist/cli/dashboard-controller.js.map +1 -0
- package/dist/cli/dashboard-detail-view.d.ts +16 -0
- package/dist/cli/dashboard-detail-view.js +162 -0
- package/dist/cli/dashboard-detail-view.js.map +1 -0
- package/dist/cli/dashboard-history-view.d.ts +29 -0
- package/dist/cli/dashboard-history-view.js +211 -0
- package/dist/cli/dashboard-history-view.js.map +1 -0
- package/dist/cli/dashboard-input.d.ts +14 -0
- package/dist/cli/dashboard-input.js +128 -0
- package/dist/cli/dashboard-input.js.map +1 -0
- package/dist/cli/dashboard-layout.d.ts +12 -1
- package/dist/cli/dashboard-layout.js +32 -30
- package/dist/cli/dashboard-layout.js.map +1 -1
- package/dist/cli/dashboard-renderer.d.ts +30 -2
- package/dist/cli/dashboard-renderer.js +125 -7
- package/dist/cli/dashboard-renderer.js.map +1 -1
- package/dist/cli/dashboard-style.d.ts +9 -0
- package/dist/cli/dashboard-style.js +36 -0
- package/dist/cli/dashboard-style.js.map +1 -0
- package/dist/cli/dashboard.d.ts +5 -3
- package/dist/cli/dashboard.js +102 -12
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +37 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/input.d.ts +1 -0
- package/dist/cli/input.js +23 -17
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/main.js +6 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output.js +17 -1
- package/dist/cli/output.js.map +1 -1
- package/dist/config/access-provenance.d.ts +10 -0
- package/dist/config/access-provenance.js +31 -0
- package/dist/config/access-provenance.js.map +1 -0
- package/dist/config/access.d.ts +12 -2
- package/dist/config/access.js +125 -27
- package/dist/config/access.js.map +1 -1
- package/dist/config/loader.js +1 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/oidc.js +4 -2
- package/dist/config/oidc.js.map +1 -1
- package/dist/config/project.js +2 -1
- package/dist/config/project.js.map +1 -1
- package/dist/config/resolve-access.js +24 -10
- package/dist/config/resolve-access.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/process-diagnostics.d.ts +2 -0
- package/dist/process-diagnostics.js +11 -0
- package/dist/process-diagnostics.js.map +1 -0
- package/dist/runtime/access-errors.d.ts +4 -0
- package/dist/runtime/access-errors.js +25 -0
- package/dist/runtime/access-errors.js.map +1 -0
- package/dist/runtime/approval-registry.d.ts +27 -0
- package/dist/runtime/approval-registry.js +126 -0
- package/dist/runtime/approval-registry.js.map +1 -0
- package/dist/runtime/client.js +54 -14
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +17 -1
- package/dist/runtime/coordinator.js +124 -6
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/dashboard-sanitize.d.ts +2 -0
- package/dist/runtime/dashboard-sanitize.js +7 -0
- package/dist/runtime/dashboard-sanitize.js.map +1 -0
- package/dist/runtime/dashboard.d.ts +43 -1
- package/dist/runtime/dashboard.js +2 -3
- package/dist/runtime/dashboard.js.map +1 -1
- package/dist/runtime/definition.js +11 -1
- package/dist/runtime/definition.js.map +1 -1
- package/dist/runtime/select-native.d.ts +2 -1
- package/dist/runtime/select-native.js +28 -10
- package/dist/runtime/select-native.js.map +1 -1
- package/dist/runtime/service.js +24 -1
- package/dist/runtime/service.js.map +1 -1
- package/dist/runtime/types.d.ts +9 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/runtime/worker-approvals.d.ts +21 -0
- package/dist/runtime/worker-approvals.js +51 -0
- package/dist/runtime/worker-approvals.js.map +1 -0
- package/dist/runtime/worker-client.js +73 -20
- package/dist/runtime/worker-client.js.map +1 -1
- package/dist/runtime/worker-server.d.ts +2 -0
- package/dist/runtime/worker-server.js +39 -10
- package/dist/runtime/worker-server.js.map +1 -1
- package/dist/sdk/definitions.d.ts +4 -1
- package/dist/sdk/definitions.js +16 -2
- package/dist/sdk/definitions.js.map +1 -1
- package/dist/sdk/permission-validation.js +10 -1
- package/dist/sdk/permission-validation.js.map +1 -1
- package/dist/sdk/types.d.ts +29 -1
- package/dist/sdk/types.js.map +1 -1
- package/package.json +4 -1
- package/sdk/access-config.md +86 -4
- package/sdk/adapter-contract.md +7 -13
- package/sdk/additional-harnesses.md +43 -0
- package/sdk/agent-skill.md +7 -4
- package/sdk/agent.md +5 -3
- package/sdk/approvals.md +120 -0
- package/sdk/authentication.md +1 -1
- package/sdk/cli/dashboard-design.md +44 -4
- package/sdk/cli/dashboard-inspection.md +25 -0
- package/sdk/cli/index.md +20 -11
- package/sdk/cli/output.md +10 -4
- package/sdk/completion-notifications.md +2 -0
- package/sdk/config.md +1 -1
- package/sdk/copilot.md +53 -0
- package/sdk/cursor.md +68 -0
- package/sdk/diagnostics.md +29 -0
- package/sdk/distribution.md +53 -1
- package/sdk/evals.md +10 -4
- package/sdk/fx.md +51 -6
- package/sdk/harnesses.md +13 -13
- package/sdk/index.md +4 -2
- package/sdk/message-delivery.md +1 -1
- package/sdk/opencode.md +57 -0
- package/sdk/permissions.md +7 -5
- package/sdk/pr-integration.md +1 -1
- package/sdk/project-team.md +2 -0
- package/sdk/sessions.md +4 -1
- package/sdk/tools.md +1 -1
- package/sdk/v1-runtime.md +6 -6
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<a href="https://github.com/vercel-labs/subharness/blob/main/LICENSE"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache--2.0-blue.svg?style=for-the-badge&labelColor=000000" height="28"></a>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
Stay with your favorite coding agent. Ask it to delegate tasks across Codex, Claude Code, and
|
|
9
|
+
Stay with your favorite coding agent. Ask it to delegate tasks across Codex, Claude Code, fx, OpenCode, GitHub Copilot, and Cursor, and bring results back to one conversation. The main agent uses the local `subharness` CLI to start independent native sessions, check their status, read complete responses, and report the results.
|
|
10
10
|
|
|
11
|
-
TypeScript definitions are optional. Add them when your coding agent needs reusable specialists with shared instructions, tools, or harness alternatives. Codex and Claude Code use eligible native subscription logins or explicitly configured API access
|
|
11
|
+
TypeScript definitions are optional. Add them when your coding agent needs reusable specialists with shared instructions, tools, or harness alternatives. Codex and Claude Code use eligible native subscription logins or explicitly configured API access. fx, OpenCode, and GitHub Copilot accept explicitly configured native logins, direct API keys, or Vercel AI Gateway access. Cursor uses an explicitly configured native CLI login or Cursor API key.
|
|
12
12
|
|
|
13
13
|
The library coordinates sessions, queues, and subagents. Harnesses own model execution, native tools, and context. You supply the working directory and any sandbox or worktree isolation.
|
|
14
14
|
|
|
@@ -33,7 +33,7 @@ npx skills add vercel-labs/subharness
|
|
|
33
33
|
|
|
34
34
|
The installer requires access to the subharness repository while the repository is internal. See the [agent-skill reference](sdk/agent-skill.md) for installation behavior and boundaries.
|
|
35
35
|
|
|
36
|
-
See [package distribution](sdk/distribution.md) for development checkouts, local tarballs, and package boundaries. subharness uses the [Apache License 2.0](LICENSE).
|
|
36
|
+
See [package distribution](sdk/distribution.md) for development checkouts, local tarballs, and package boundaries. Maintainers publish through the [GitHub release procedure](sdk/distribution.md#github-release-publishing). subharness uses the [Apache License 2.0](LICENSE).
|
|
37
37
|
|
|
38
38
|
## Delegate from your coding agent
|
|
39
39
|
|
|
@@ -56,10 +56,13 @@ You can also invoke a harness manually:
|
|
|
56
56
|
subharness run claude "Review the current diff."
|
|
57
57
|
subharness run codex --cwd /path/to/worktree "Implement the documented feature."
|
|
58
58
|
subharness run fx --model "provider/model" "Compare the proposed implementations."
|
|
59
|
+
subharness run opencode --model "creator/model" "Inspect the current implementation."
|
|
60
|
+
subharness run copilot --model "creator/model" "Review the current diff."
|
|
61
|
+
subharness run cursor --model "CURSOR_MODEL_ID" "Implement the documented change."
|
|
59
62
|
subharness run claude --help
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
Replace `provider/model` with an available Gateway model identifier. fx
|
|
65
|
+
Replace `provider/model` or `creator/model` with an available Gateway model identifier. fx, OpenCode, and Copilot require an explicit compatible connection as described under Access below. Non-Gateway routes use their documented native model identifiers. Cursor requires explicit subscription or Cursor API-key access. OpenCode, Copilot, and Cursor require `--model` and reject `--effort`. Codex, Claude Code, and fx use a verifiable native default when possible and accept their documented effort values. These options do not override a TypeScript specialist.
|
|
63
66
|
|
|
64
67
|
Prompts can be one quoted argument, `--prompt`, or a UTF-8 file. `--prompt-file -` reads piped stdin explicitly. Exactly one prompt source is accepted, with a 1 MiB limit.
|
|
65
68
|
|
|
@@ -68,7 +71,7 @@ subharness run claude --prompt-file ./review-task.txt
|
|
|
68
71
|
subharness run codex --prompt-file - < ./task.txt
|
|
69
72
|
```
|
|
70
73
|
|
|
71
|
-
Use `subharness dashboard` in a terminal to watch agents managed by the local coordinator. The live view groups agents by repository/worktree, pins the checkout containing the launch directory first, and orders other groups by their latest agent update. Each group shows current agents followed by finished runs, with harness, task title, elapsed time, and status. Finished history is retained for the coordinator’s lifetime.
|
|
74
|
+
Use `subharness dashboard` in a terminal to watch agents managed by the local coordinator. The live view groups agents by repository/worktree, pins the checkout containing the launch directory first, and orders other groups by their latest agent update. Each group shows current agents followed by finished runs, with harness, task title, elapsed time, and status. Finished history is retained for the coordinator’s lifetime. Use Up/Down to select a run and Enter to open its agent session’s request history. Queued requests appear first, followed by remaining requests newest first. Enter expands a request’s prompt and response; Escape returns to the overview. Completed statuses are green, and a compact Sub-agents footer summarizes direct child agents. The overview has no application banner or footer. Ctrl+C closes the view without stopping agents.
|
|
72
75
|
|
|
73
76
|
## Define a reusable specialist
|
|
74
77
|
|
|
@@ -94,7 +97,7 @@ export default agent({
|
|
|
94
97
|
});
|
|
95
98
|
```
|
|
96
99
|
|
|
97
|
-
Replace the model placeholders with identifiers available through your native harnesses. Each file default-exports one definition. Global definitions live in `~/.subharness/agents/`; repository and global names never silently override one another. The bare targets `codex`, `claude`, and `
|
|
100
|
+
Replace the model placeholders with identifiers available through your native harnesses. Each file default-exports one definition. Global definitions live in `~/.subharness/agents/`; repository and global names never silently override one another. The bare targets `codex`, `claude`, `fx`, `opencode`, `copilot`, and `cursor` are reserved for harnesses; a specialist with one of those names requires a scope qualifier such as `repo:claude`.
|
|
98
101
|
|
|
99
102
|
## Run and follow up
|
|
100
103
|
|
|
@@ -114,7 +117,7 @@ Long prompts can use `--prompt-file`. Output is compact text by default; `--form
|
|
|
114
117
|
|
|
115
118
|
## Access
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
Codex and Claude Code need no personal access configuration when an eligible native subscription login is available. Other harnesses require an explicit connection. Login remains with the native harness. Ambient API keys do not silently enable paid fallback.
|
|
118
121
|
|
|
119
122
|
Personal access preferences can be placed in the ignored `.subharness/agents.local.json`. Linked worktrees read that file from the main checkout, while their agent definitions come from the selected worktree.
|
|
120
123
|
|
|
@@ -129,7 +132,7 @@ Personal access preferences can be placed in the ignored `.subharness/agents.loc
|
|
|
129
132
|
|
|
130
133
|
Only credential references belong in that file. [Access configuration](sdk/access-config.md) also describes direct API keys, project OIDC, explicit connection fallback, and token-lifetime limits.
|
|
131
134
|
|
|
132
|
-
For [fx](sdk/fx.md), select a
|
|
135
|
+
For [fx](sdk/fx.md), select a connection explicitly. This example uses Gateway:
|
|
133
136
|
|
|
134
137
|
```json
|
|
135
138
|
{
|
|
@@ -141,6 +144,33 @@ For [fx](sdk/fx.md), select a Gateway connection explicitly:
|
|
|
141
144
|
|
|
142
145
|
Use `fx({ model: "google/gemini-3.8-flash" })` in the agent's `harness` field. Native fx must select the supplied environment credential instead of a saved login. Gateway model and provider restrictions remain effective; catalog availability alone does not establish access for a particular team.
|
|
143
146
|
|
|
147
|
+
OpenCode and Copilot use the same explicit Gateway connection types under their own access keys. Cursor accepts an explicit `subscription` connection for the current `cursor-agent login`, or an `api-key` connection that defaults to `CURSOR_API_KEY`. This example selects the local login:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"access": {
|
|
152
|
+
"opencode": [{ "type": "vercel-api-key" }],
|
|
153
|
+
"copilot": [{ "type": "vercel-oidc", "project": ".", "envFile": ".env.local" }],
|
|
154
|
+
"cursor": [{ "type": "subscription" }]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Gateway is optional for OpenCode and Copilot. For example, select direct Anthropic access in OpenCode and a native GitHub login in Copilot:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"access": {
|
|
164
|
+
"opencode": [{ "type": "api-key", "provider": "anthropic", "env": "ANTHROPIC_API_KEY" }],
|
|
165
|
+
"copilot": [{ "type": "subscription" }]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Copilot also supports explicit GitHub tokens and OpenAI, Anthropic, or Azure BYOK keys. fx supports its own saved Vercel, Codex, or Grok login and keys for native named Chat Completions connections. Model identifiers and required setup depend on the selected route.
|
|
171
|
+
|
|
172
|
+
See the [OpenCode](sdk/opencode.md), [Copilot](sdk/copilot.md), and [Cursor](sdk/cursor.md) adapter contracts for native behavior.
|
|
173
|
+
|
|
144
174
|
## Tools and delegation
|
|
145
175
|
|
|
146
176
|
Agents can expose validated [custom tools](sdk/tools.md) and declare [subagents](sdk/plugins/sub-agents.md). A managed parent invokes its child with the same CLI:
|
|
@@ -155,7 +185,7 @@ This checkout includes six [repository roles](sdk/project-team.md): architecture
|
|
|
155
185
|
|
|
156
186
|
## Boundaries
|
|
157
187
|
|
|
158
|
-
The local coordinator retains sessions and responses across command exits, but does not restore queues after coordinator or environment loss. External assistant reactivation depends on that assistant's host.
|
|
188
|
+
The local coordinator retains sessions and responses across command exits, but does not restore queues after coordinator or environment loss. External assistant reactivation depends on that assistant's host. Supported native permission requests return structured schemas and keep the original operation pending. The caller answers with `subharness respond <request-id> --content <json>` and observes the same task. See [permission request examples](sdk/approvals.md). Unsupported interactive input still fails with actionable `INPUT_REQUIRED`; no request is automatically approved. CLI delegation requires the native shell to reach the coordinator over loopback HTTP; a sandbox that blocks local networking must be configured by the caller.
|
|
159
189
|
|
|
160
190
|
`resume` is available as a command, but v1 adapters report `RECOVERY_UNSUPPORTED` when prompt-free native recovery is unavailable. Failed work is not automatically replayed or migrated.
|
|
161
191
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Native envelopes can include private configuration unrelated to the requested action. */
|
|
2
|
+
export function approvalContext(source, fields) {
|
|
3
|
+
const result = {};
|
|
4
|
+
for (const key of fields)
|
|
5
|
+
if (Object.hasOwn(source, key) && source[key] !== undefined)
|
|
6
|
+
result[key] = structuredClone(source[key]);
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=approval-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-context.js","sourceRoot":"","sources":["../../src/adapters/approval-context.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,MAA+B,EAAE,MAAyB;IACxF,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM;QAAE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjI,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/** Native envelopes can include private configuration unrelated to the requested action. */\nexport function approvalContext(source: Record<string, unknown>, fields: readonly string[]): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of fields) if (Object.hasOwn(source,key) && source[key] !== undefined) result[key] = structuredClone(source[key]);\n return result;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ApprovalContent, ApprovalHandler, NativeApprovalRequest } from "../approvals/types.js";
|
|
2
|
+
/** Owns callback lifetimes independently of an outstanding native turn. */
|
|
3
|
+
export declare class NativeApprovals {
|
|
4
|
+
private readonly pending;
|
|
5
|
+
request(id: string | number, request: NativeApprovalRequest, handler: ApprovalHandler, parent?: AbortSignal): Promise<ApprovalContent>;
|
|
6
|
+
withdraw(id: string | number): void;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
import { validateApprovalContent, validateApprovalSchema } from "../approvals/schema.js";
|
|
3
|
+
/** Owns callback lifetimes independently of an outstanding native turn. */
|
|
4
|
+
export class NativeApprovals {
|
|
5
|
+
pending = new Map();
|
|
6
|
+
async request(id, request, handler, parent) {
|
|
7
|
+
if (this.pending.has(id) || this.pending.size >= 32)
|
|
8
|
+
throw new AgentError("INPUT_REQUIRED", "The native permission request cannot be admitted.");
|
|
9
|
+
let snapshot;
|
|
10
|
+
try {
|
|
11
|
+
const serialized = JSON.stringify(request);
|
|
12
|
+
if (Buffer.byteLength(serialized) > 64 * 1024)
|
|
13
|
+
throw new Error();
|
|
14
|
+
snapshot = JSON.parse(serialized);
|
|
15
|
+
validateApprovalSchema(snapshot.requestedSchema);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new AgentError("INPUT_REQUIRED", "The native permission request is unsupported or exceeds the supported size.");
|
|
19
|
+
}
|
|
20
|
+
const schema = structuredClone(snapshot.requestedSchema);
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
this.pending.set(id, controller);
|
|
23
|
+
const abort = () => controller.abort();
|
|
24
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
25
|
+
if (parent?.aborted)
|
|
26
|
+
abort();
|
|
27
|
+
let rejectAbort;
|
|
28
|
+
const withdrawn = new Promise((_resolve, reject) => {
|
|
29
|
+
rejectAbort = () => reject(new AgentError("REQUEST_EXPIRED", "The native permission request is no longer pending."));
|
|
30
|
+
controller.signal.addEventListener("abort", rejectAbort, { once: true });
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
if (controller.signal.aborted)
|
|
34
|
+
throw new AgentError("REQUEST_EXPIRED", "The native permission request is no longer pending.");
|
|
35
|
+
const content = await Promise.race([handler(snapshot, controller.signal), withdrawn]);
|
|
36
|
+
if (controller.signal.aborted)
|
|
37
|
+
throw new AgentError("REQUEST_EXPIRED", "The native permission request is no longer pending.");
|
|
38
|
+
return validateApprovalContent(schema, content);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
parent?.removeEventListener("abort", abort);
|
|
42
|
+
controller.signal.removeEventListener("abort", rejectAbort);
|
|
43
|
+
this.pending.delete(id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
withdraw(id) { this.pending.get(id)?.abort(); }
|
|
47
|
+
clear() { for (const controller of this.pending.values())
|
|
48
|
+
controller.abort(); }
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=approval-lifetime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-lifetime.js","sourceRoot":"","sources":["../../src/adapters/approval-lifetime.ts"],"names":[],"mappings":"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","sourcesContent":["import { AgentError } from \"../errors.js\";\nimport { validateApprovalContent, validateApprovalSchema } from \"../approvals/schema.js\";\nimport type { ApprovalContent, ApprovalHandler, NativeApprovalRequest } from \"../approvals/types.js\";\n\n/** Owns callback lifetimes independently of an outstanding native turn. */\nexport class NativeApprovals {\n private readonly pending = new Map<string | number, AbortController>();\n\n async request(id: string | number, request: NativeApprovalRequest, handler: ApprovalHandler, parent?: AbortSignal): Promise<ApprovalContent> {\n if (this.pending.has(id) || this.pending.size >= 32) throw new AgentError(\"INPUT_REQUIRED\", \"The native permission request cannot be admitted.\");\n let snapshot: NativeApprovalRequest;\n try {\n const serialized = JSON.stringify(request);\n if (Buffer.byteLength(serialized) > 64 * 1024) throw new Error();\n snapshot = JSON.parse(serialized);\n validateApprovalSchema(snapshot.requestedSchema);\n } catch { throw new AgentError(\"INPUT_REQUIRED\", \"The native permission request is unsupported or exceeds the supported size.\"); }\n const schema = structuredClone(snapshot.requestedSchema);\n const controller = new AbortController();\n this.pending.set(id, controller);\n const abort = () => controller.abort();\n parent?.addEventListener(\"abort\", abort, { once: true });\n if (parent?.aborted) abort();\n let rejectAbort!: () => void;\n const withdrawn = new Promise<never>((_resolve, reject) => {\n rejectAbort = () => reject(new AgentError(\"REQUEST_EXPIRED\", \"The native permission request is no longer pending.\"));\n controller.signal.addEventListener(\"abort\", rejectAbort, { once: true });\n });\n try {\n if (controller.signal.aborted) throw new AgentError(\"REQUEST_EXPIRED\", \"The native permission request is no longer pending.\");\n const content = await Promise.race([handler(snapshot, controller.signal), withdrawn]);\n if (controller.signal.aborted) throw new AgentError(\"REQUEST_EXPIRED\", \"The native permission request is no longer pending.\");\n return validateApprovalContent(schema, content);\n } finally {\n parent?.removeEventListener(\"abort\", abort);\n controller.signal.removeEventListener(\"abort\", rejectAbort);\n this.pending.delete(id);\n }\n }\n\n withdraw(id: string | number): void { this.pending.get(id)?.abort(); }\n clear(): void { for (const controller of this.pending.values()) controller.abort(); }\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CanUseTool, PermissionResult } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { ApprovalHandler } from "../approvals/types.js";
|
|
3
|
+
import { NativeApprovals } from "./approval-lifetime.js";
|
|
4
|
+
export declare function claudeApproval(approvals: NativeApprovals, handler: ApprovalHandler, toolName: string, input: Record<string, unknown>, metadata: Parameters<CanUseTool>[2]): Promise<PermissionResult>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { approvalContext } from "./approval-context.js";
|
|
2
|
+
/** Only native session suggestions that add grants can be reused without changing policy. */
|
|
3
|
+
function reusable(suggestions) {
|
|
4
|
+
return !!suggestions?.length && suggestions.every(suggestion => suggestion.destination === "session"
|
|
5
|
+
&& ((suggestion.type === "addRules" && suggestion.behavior === "allow" && suggestion.rules.length > 0)
|
|
6
|
+
|| (suggestion.type === "addDirectories" && suggestion.directories.length > 0)));
|
|
7
|
+
}
|
|
8
|
+
export async function claudeApproval(approvals, handler, toolName, input, metadata) {
|
|
9
|
+
const originalInput = structuredClone(input);
|
|
10
|
+
const suggestions = metadata.suggestions && structuredClone(metadata.suggestions);
|
|
11
|
+
const session = !metadata.suppressAlwaysAllowRule && reusable(suggestions);
|
|
12
|
+
const request = {
|
|
13
|
+
kind: "permission", harness: "claudeCode", message: metadata.title ?? `Allow native tool ${toolName}?`,
|
|
14
|
+
requestedSchema: { type: "object", properties: { decision: { type: "string", oneOf: [
|
|
15
|
+
{ const: "allow_once", title: "Allow once" },
|
|
16
|
+
...(session ? [{ const: "allow_session", title: "Allow for this session using the suggested permissions" }] : []),
|
|
17
|
+
{ const: "deny", title: "Deny" },
|
|
18
|
+
] } }, required: ["decision"] },
|
|
19
|
+
context: { toolName, input: originalInput, ...approvalContext(metadata, ["blockedPath", "decisionReason", "title", "displayName", "description", "defaultToNo", "suppressAlwaysAllowRule", "toolUseID", "agentID", "requestId"]),
|
|
20
|
+
...(metadata.matchedAskRule ? { matchedAskRule: approvalContext(metadata.matchedAskRule, ["source", "toolName", "ruleContent"]) } : {}),
|
|
21
|
+
...(metadata.mcpServer ? { mcpServer: approvalContext(metadata.mcpServer, ["name", "source"]) } : {}),
|
|
22
|
+
...(suggestions ? { suggestions: suggestions.map(suggestion => ({ ...approvalContext(suggestion, ["type", "destination", "behavior", "mode", "directories"]), ...("rules" in suggestion ? { rules: suggestion.rules.map(rule => approvalContext(rule, ["toolName", "ruleContent"])) } : {}) })) } : {}),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const content = await approvals.request(metadata.requestId, request, handler, metadata.signal);
|
|
26
|
+
if (metadata.signal.aborted)
|
|
27
|
+
return { behavior: "deny", message: "The permission request is no longer pending." };
|
|
28
|
+
if (content.decision === "deny")
|
|
29
|
+
return { behavior: "deny", message: "The caller denied permission for this tool." };
|
|
30
|
+
return { behavior: "allow", updatedInput: originalInput, ...(content.decision === "allow_session" ? { updatedPermissions: suggestions } : {}) };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=claude-approvals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-approvals.js","sourceRoot":"","sources":["../../src/adapters/claude-approvals.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CanUseTool, PermissionResult, PermissionUpdate } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { ApprovalHandler, NativeApprovalRequest } from \"../approvals/types.js\";\nimport { approvalContext } from \"./approval-context.js\";\nimport { NativeApprovals } from \"./approval-lifetime.js\";\n\n/** Only native session suggestions that add grants can be reused without changing policy. */\nfunction reusable(suggestions: PermissionUpdate[] | undefined): suggestions is PermissionUpdate[] {\n return !!suggestions?.length && suggestions.every(suggestion => suggestion.destination === \"session\"\n && ((suggestion.type === \"addRules\" && suggestion.behavior === \"allow\" && suggestion.rules.length > 0)\n || (suggestion.type === \"addDirectories\" && suggestion.directories.length > 0)));\n}\nexport async function claudeApproval(\n approvals: NativeApprovals, handler: ApprovalHandler, toolName: string,\n input: Record<string, unknown>, metadata: Parameters<CanUseTool>[2],\n): Promise<PermissionResult> {\n const originalInput = structuredClone(input);\n const suggestions = metadata.suggestions && structuredClone(metadata.suggestions);\n const session = !metadata.suppressAlwaysAllowRule && reusable(suggestions);\n const request: NativeApprovalRequest = {\n kind: \"permission\", harness: \"claudeCode\", message: metadata.title ?? `Allow native tool ${toolName}?`,\n requestedSchema: {type:\"object\",properties:{decision:{type:\"string\",oneOf:[\n {const:\"allow_once\",title:\"Allow once\"},\n ...(session ? [{const:\"allow_session\",title:\"Allow for this session using the suggested permissions\"}] : []),\n {const:\"deny\",title:\"Deny\"},\n ]}},required:[\"decision\"]},\n context: {toolName,input:originalInput,...approvalContext(metadata,[\"blockedPath\",\"decisionReason\",\"title\",\"displayName\",\"description\",\"defaultToNo\",\"suppressAlwaysAllowRule\",\"toolUseID\",\"agentID\",\"requestId\"]),\n ...(metadata.matchedAskRule ? {matchedAskRule:approvalContext(metadata.matchedAskRule,[\"source\",\"toolName\",\"ruleContent\"])} : {}),\n ...(metadata.mcpServer ? {mcpServer:approvalContext(metadata.mcpServer,[\"name\",\"source\"])} : {}),\n ...(suggestions ? {suggestions:suggestions.map(suggestion=>({...approvalContext(suggestion,[\"type\",\"destination\",\"behavior\",\"mode\",\"directories\"]),...(\"rules\" in suggestion ? {rules:suggestion.rules.map(rule=>approvalContext(rule,[\"toolName\",\"ruleContent\"]))} : {})}))} : {}),\n },\n };\n const content = await approvals.request(metadata.requestId, request, handler, metadata.signal);\n if (metadata.signal.aborted) return {behavior:\"deny\",message:\"The permission request is no longer pending.\"};\n if (content.decision === \"deny\") return {behavior:\"deny\",message:\"The caller denied permission for this tool.\"};\n return {behavior:\"allow\",updatedInput:originalInput,...(content.decision === \"allow_session\" ? {updatedPermissions:suggestions} : {})};\n}\n"]}
|
|
@@ -17,7 +17,7 @@ export function claudeDelegationPermissions(options) {
|
|
|
17
17
|
launchers.push(path, `"${path}"`);
|
|
18
18
|
const commands = [
|
|
19
19
|
...names.map(name => `run subagent:${name}`),
|
|
20
|
-
"list", "--help", "send", "wait", "status", "queue", "cancel", "resume",
|
|
20
|
+
"list", "--help", "send", "wait", "status", "queue", "cancel", "resume", "respond",
|
|
21
21
|
];
|
|
22
22
|
return commands.flatMap(command => launchers.map(launcher => `Bash(${launcher} ${command} *)`));
|
|
23
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-permissions.js","sourceRoot":"","sources":["../../src/adapters/claude-permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,oFAAoF;AACpF,MAAM,UAAU,2BAA2B,CAAC,OAAuB;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACxC,8EAA8E;IAC9E,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,kHAAkH,CAAC,CAAC;IAC7J,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,gFAAgF;IAChF,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG;QACf,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC;QAC5C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ;
|
|
1
|
+
{"version":3,"file":"claude-permissions.js","sourceRoot":"","sources":["../../src/adapters/claude-permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,oFAAoF;AACpF,MAAM,UAAU,2BAA2B,CAAC,OAAuB;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACxC,8EAA8E;IAC9E,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,kHAAkH,CAAC,CAAC;IAC7J,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,gFAAgF;IAChF,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG;QACf,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC;QAC5C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS;KACnF,CAAC;IACF,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,QAAQ,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC;AAClG,CAAC","sourcesContent":["import type { AdapterOptions } from \"./types.js\";\nimport { shellQuote } from \"../shell.js\";\nimport { isAbsolute } from \"node:path\";\nimport { AgentError } from \"../errors.js\";\n\n/** Only the native permission evaluator decides whether a shell call is allowed. */\nexport function claudeDelegationPermissions(options: AdapterOptions): string[] {\n const names = Object.keys(options.agent.subagents ?? {});\n if (names.length === 0) return [];\n const path = options.env.AGENT_CLI_PATH;\n // Bash rules treat '*' as a wildcard even when it occurs inside shell quotes.\n if (!path || !isAbsolute(path) || /[\\x00-\\x1f\\x7f*]/u.test(path)) {\n throw new AgentError(\"INVALID_CONFIG\", \"The session launcher needs an absolute path without wildcards or control characters for Claude Code permissions.\");\n }\n const launchers = [shellQuote(path)];\n // Native matching is textual: harmless quote changes need exact separate rules.\n if (/^[A-Za-z0-9_./-]+$/u.test(path)) launchers.push(path, `\"${path}\"`);\n const commands = [\n ...names.map(name => `run subagent:${name}`),\n \"list\", \"--help\", \"send\", \"wait\", \"status\", \"queue\", \"cancel\", \"resume\", \"respond\",\n ];\n return commands.flatMap(command => launchers.map(launcher => `Bash(${launcher} ${command} *)`));\n}\n"]}
|
|
@@ -4,7 +4,10 @@ export declare class ClaudeProcessOwner {
|
|
|
4
4
|
private child?;
|
|
5
5
|
private exited?;
|
|
6
6
|
private closing?;
|
|
7
|
+
private exitCode;
|
|
8
|
+
private exitSignal;
|
|
7
9
|
readonly spawn: (options: SpawnOptions) => SpawnedProcess;
|
|
10
|
+
exitDescription(): string;
|
|
8
11
|
close(): Promise<void>;
|
|
9
12
|
private stop;
|
|
10
13
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { AgentError } from '../errors.js';
|
|
3
|
+
import { processExitDescription } from '../process-diagnostics.js';
|
|
3
4
|
const gracefulExitMs = 2_000;
|
|
4
5
|
const terminatedExitMs = 5_000;
|
|
5
6
|
const killedExitMs = 1_000;
|
|
@@ -14,6 +15,8 @@ export class ClaudeProcessOwner {
|
|
|
14
15
|
child;
|
|
15
16
|
exited;
|
|
16
17
|
closing;
|
|
18
|
+
exitCode;
|
|
19
|
+
exitSignal;
|
|
17
20
|
spawn = (options) => {
|
|
18
21
|
if (this.child)
|
|
19
22
|
throw new AgentError('HARNESS_FAILED', 'Claude Code attempted to start more than one native process.');
|
|
@@ -26,12 +29,19 @@ export class ClaudeProcessOwner {
|
|
|
26
29
|
});
|
|
27
30
|
this.child = child;
|
|
28
31
|
this.exited = new Promise(resolve => {
|
|
29
|
-
child.once('exit', () =>
|
|
32
|
+
child.once('exit', (code, signal) => {
|
|
33
|
+
this.exitCode = code;
|
|
34
|
+
this.exitSignal = signal;
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
30
37
|
child.once('error', () => { if (child.pid === undefined)
|
|
31
38
|
resolve(); });
|
|
32
39
|
});
|
|
33
40
|
return child;
|
|
34
41
|
};
|
|
42
|
+
exitDescription() {
|
|
43
|
+
return processExitDescription(this.exitCode, this.exitSignal);
|
|
44
|
+
}
|
|
35
45
|
close() {
|
|
36
46
|
return this.closing ??= this.stop();
|
|
37
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-process.js","sourceRoot":"","sources":["../../src/adapters/claude-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA4B,MAAM,oBAAoB,CAAC;AAGrE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-process.js","sourceRoot":"","sources":["../../src/adapters/claude-process.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcessByStdio } from 'node:child_process';\nimport type { Readable, Writable } from 'node:stream';\nimport type { SpawnedProcess, SpawnOptions } from '@anthropic-ai/claude-agent-sdk';\nimport { AgentError } from '../errors.js';\nimport { processExitDescription } from '../process-diagnostics.js';\n\nconst gracefulExitMs = 2_000;\nconst terminatedExitMs = 5_000;\nconst killedExitMs = 1_000;\n\nfunction exitsWithin(exited: Promise<void>, timeout: number): Promise<boolean> {\n return new Promise(resolve => {\n const timer = setTimeout(() => resolve(false),timeout);\n exited.then(() => { clearTimeout(timer); resolve(true); });\n });\n}\n\n/** Retains the concrete SDK subprocess until its termination is confirmed. */\nexport class ClaudeProcessOwner {\n private child?: ChildProcessByStdio<Writable,Readable,null>;\n private exited?: Promise<void>;\n private closing?: Promise<void>;\n private exitCode: unknown;\n private exitSignal: unknown;\n\n readonly spawn = (options: SpawnOptions): SpawnedProcess => {\n if (this.child) throw new AgentError('HARNESS_FAILED','Claude Code attempted to start more than one native process.');\n const child = spawn(options.command,options.args,{\n cwd:options.cwd,\n env:options.env,\n signal:options.signal,\n stdio:['pipe','pipe','ignore'],\n windowsHide:true,\n });\n this.child = child;\n this.exited = new Promise(resolve => {\n child.once('exit',(code,signal) => {\n this.exitCode = code;\n this.exitSignal = signal;\n resolve();\n });\n child.once('error',() => { if (child.pid === undefined) resolve(); });\n });\n return child;\n };\n\n exitDescription(): string {\n return processExitDescription(this.exitCode,this.exitSignal);\n }\n\n close(): Promise<void> {\n return this.closing ??= this.stop();\n }\n\n private async stop(): Promise<void> {\n const child = this.child;\n const exited = this.exited;\n if (!child || !exited) return;\n try { child.stdin.end(); } catch { /* Continue to bounded termination. */ }\n if (await exitsWithin(exited,gracefulExitMs)) return;\n child.kill('SIGTERM');\n if (await exitsWithin(exited,terminatedExitMs)) return;\n child.kill('SIGKILL');\n if (await exitsWithin(exited,killedExitMs)) return;\n throw new AgentError('HARNESS_FAILED','Claude Code process termination could not be confirmed.');\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
export function claudeResultFailure(subtype) {
|
|
3
|
+
let message;
|
|
4
|
+
switch (subtype) {
|
|
5
|
+
case "error_max_turns":
|
|
6
|
+
message = "Claude Code reached its native turn limit.";
|
|
7
|
+
break;
|
|
8
|
+
case "error_max_budget_usd":
|
|
9
|
+
message = "Claude Code reached its native budget limit.";
|
|
10
|
+
break;
|
|
11
|
+
case "error_max_structured_output_retries":
|
|
12
|
+
message = "Claude Code exhausted its native structured-output retries.";
|
|
13
|
+
break;
|
|
14
|
+
case "error_during_execution":
|
|
15
|
+
message = "Claude Code reported an error during execution.";
|
|
16
|
+
break;
|
|
17
|
+
default:
|
|
18
|
+
message = "Claude Code could not complete the turn.";
|
|
19
|
+
}
|
|
20
|
+
return new AgentError("HARNESS_ERROR", message);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=claude-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-result.js","sourceRoot":"","sources":["../../src/adapters/claude-result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,IAAI,OAAe,CAAC;IACpB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,iBAAiB;YACpB,OAAO,GAAG,4CAA4C,CAAC;YACvD,MAAM;QACR,KAAK,sBAAsB;YACzB,OAAO,GAAG,8CAA8C,CAAC;YACzD,MAAM;QACR,KAAK,qCAAqC;YACxC,OAAO,GAAG,6DAA6D,CAAC;YACxE,MAAM;QACR,KAAK,wBAAwB;YAC3B,OAAO,GAAG,iDAAiD,CAAC;YAC5D,MAAM;QACR;YACE,OAAO,GAAG,0CAA0C,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC","sourcesContent":["import { AgentError } from \"../errors.js\";\n\nexport function claudeResultFailure(subtype: unknown): AgentError {\n let message: string;\n switch (subtype) {\n case \"error_max_turns\":\n message = \"Claude Code reached its native turn limit.\";\n break;\n case \"error_max_budget_usd\":\n message = \"Claude Code reached its native budget limit.\";\n break;\n case \"error_max_structured_output_retries\":\n message = \"Claude Code exhausted its native structured-output retries.\";\n break;\n case \"error_during_execution\":\n message = \"Claude Code reported an error during execution.\";\n break;\n default:\n message = \"Claude Code could not complete the turn.\";\n }\n return new AgentError(\"HARNESS_ERROR\", message);\n}\n"]}
|