claude-agent-sdk 0.32.0 → 0.33.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +3 -2
- data/docs/client.md +4 -0
- data/docs/configuration.md +33 -0
- data/docs/hooks-and-permissions.md +66 -2
- data/docs/sessions.md +28 -0
- data/docs/subagents.md +198 -0
- data/docs/types.md +34 -4
- data/lib/claude_agent_sdk/cancellation_signal.rb +28 -0
- data/lib/claude_agent_sdk/message_parser.rb +3 -1
- data/lib/claude_agent_sdk/query.rb +113 -24
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +10 -0
- data/lib/claude_agent_sdk/session_mutations.rb +2 -4
- data/lib/claude_agent_sdk/sessions.rb +37 -0
- data/lib/claude_agent_sdk/types.rb +224 -12
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +43 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6994a2674fc098001857cff72a21c3d804744c6fa25cb866f2a133ee1a2ec876
|
|
4
|
+
data.tar.gz: 349b7ce2314eac4c315e3c3d03ef0e466926fe3abb0f8823d1780f1486a8ce0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0442feb3be095e8be7903e55e51f5620607b682cb0ed57f1ded989f01f6bf04bf22cee36556b52fd8cb4072d2e3bf4a2400c459b8cad231a83590d75b1010403
|
|
7
|
+
data.tar.gz: e287cd5825a5e16f1c8d7ce8295d0f3fba07ee15c5e4502be2dbfefcd223cf061c6e20afe5e5e6349e7ee2729c8c70e77aea152c6b95dc997978bc5ef358f68e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.33.1] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
Compatibility with `json` 3.x and `mcp` 1.x. Upgrade if your bundle resolves `json` 3.x — `rename_session` / `tag_session` raise on 0.33.0.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `mcp` dependency is now `>= 0.20, < 2` (was `>= 0.6, < 1`). The floor moves to 0.20 because `mcp` 0.19 and older validate through the `json-schema` gem, which breaks under `json` 3.x and fails every SDK MCP `tools/call`; with `json` 2.x those versions still pass, so this only forces an `mcp` upgrade on bundles pinned below 0.20. The suite passes against every 1.x release through 1.6.0, and `initialize` / `tools/list` / `tools/call` / `resources/*` / `prompts/*` wire output is byte-identical to 0.2x.
|
|
16
|
+
- SDK MCP tool handler exceptions now reach the model as the bare exception message (matching Python's `str(e)` and `SdkMcpServer#call_tool`) instead of the gem's `Internal error calling tool X: msg`. The exception is rescued inside the SDK's tool class, so the text no longer depends on the `mcp` gem version — `mcp` 1.2+ redacts the message from its own wrapper, which would otherwise have left the model with no error text to self-correct from. Still in-band `isError: true`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `rename_session` / `tag_session` raised `ArgumentError: unknown keyword: space_size` under `json` 3.x, which takes generator options as strict keywords. The option was a no-op on `json` 2.x (output unchanged), so it is simply gone. A fresh end-user bundle resolves `json` 3.x through `async → console → json`; CI missed it only because the dev-only RuboCop pin holds `json` at 2.x.
|
|
20
|
+
|
|
21
|
+
## [0.33.0] - 2026-09-21
|
|
22
|
+
|
|
23
|
+
Subagent capabilities for UI builders: metadata reads, background snapshots, cooperative callback cancellation, and the task/background/permission signals the CLI already emits — all raw data and controls, no status model. Ruby-ahead of the Python SDK (0.2.153).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `get_subagent_metadata` / `get_subagent_metadata_from_store`: read optional subagent metadata with original string keys, including type, spawning tool ID, parent agent, depth, and future CLI fields. Disk reads reuse transcript scoping without parsing the transcript; store reads select the latest metadata entry even before messages arrive. These are historical reads, not live-status queries.
|
|
27
|
+
- `background_tasks` and `session_crons` on `StopHookInput` / `SubagentStopHookInput`. Raw snapshots preserve unavailable (`nil`) versus explicitly empty (`[]`) and describe the parent session, not all foreground/background agents.
|
|
28
|
+
- Cooperative permission cancellation through `ToolPermissionContext#signal` (`CancellationSignal#cancelled?` / `#wait`) and the associated `request_id`. CLI cancellation, disconnect, EOF, and failed dispatch invalidate pending requests, including callbacks running on worker threads; normal decisions do not. User threads are not forcibly stopped, and late decisions after observed cancellation cannot become allow responses.
|
|
29
|
+
- `HookContext#signal` / `#request_id` use the same per-invocation cancellation contract, including hook timeouts. Thread callbacks can cooperate with cancellation; late hook output is discarded.
|
|
30
|
+
- A minimal subagent event subscription example and capability reference covering lifecycle events, metadata, background snapshots, and permission cancellation. UI and application status aggregation remain outside the SDK.
|
|
31
|
+
- Opt-in real-CLI subagent contract tests for ID/metadata/text correlation, permission cancellation on interrupt, and background completion/stop after a parent result. They self-skip without CLI credentials.
|
|
32
|
+
- Subagent UI signals the CLI already emits, read from the schema embedded in Claude Code CLI 2.1.278 (the Python SDK has none of these as of Python SDK 0.2.153; only partly verified live — a smoke run against CLI 2.1.278 confirmed the `task_started` fields and the targeted-miss `background_tasks` response; the rest is schema-derived). The SDK exposes raw data and controls only — no status aggregation.
|
|
33
|
+
- `TaskStartedMessage#subagent_type` / `#is_backgrounded` / `#spawn_depth`, `TaskProgressMessage#subagent_type`, `TaskNotificationMessage#reason` / `#resource_links` (raw Array, symbol keys with the wire spelling), the `#skip_transcript` / `#ambient` display flags on both `TaskStartedMessage` and `TaskNotificationMessage` (hints for the host — the SDK never filters frames or computes activity), and `TaskUpdatedMessage#is_backgrounded` / `#error` / `#end_time` / `#total_paused_ms` / `#description` derived from `patch` like `status`. `is_backgrounded` keeps `nil` (not reported) distinct from an explicit `false` (foreground, spawning tool call blocking). `TaskUpdatedMessage` now also reads a string-keyed `patch` on hand-built messages.
|
|
34
|
+
- `BackgroundTasksChangedMessage` (`background_tasks_changed`): the full set of live background tasks, a level signal with REPLACE semantics. The SDK's own stdin-close bookkeeping still deliberately ignores this frame.
|
|
35
|
+
- `PermissionDeniedMessage` (`permission_denied`): a tool call auto-denied without an interactive prompt, with `agent_id` for subagent routing (not a permission `request_id`). A best-effort advisory, not a complete denial feed — `ResultMessage#permission_denials` stays authoritative.
|
|
36
|
+
- `Client#background_tasks(tool_use_id: nil)` / `Query#background_tasks`: send in-flight foreground tasks to the background (Ctrl+B). Keyed by the spawning `tool_use_id`, not `task_id` / `agent_id`. The targeted form returns `{ backgrounded: true }` or `{ backgrounded: false }` — a definitive miss, after which no event is coming; `nil` is the explicit all-tasks form and returns `{}`. Because the CLI treats `''` as "all tasks", anything other than `nil` or a non-empty String raises `ArgumentError` before a request is written. `TaskUpdatedMessage#is_backgrounded` / `BackgroundTasksChangedMessage` report the lifecycle state that follows.
|
|
37
|
+
- `ClaudeAgentOptions#agent_progress_summaries`: request model-generated progress summaries for subagents; `TaskProgressMessage#summary` may then be present, and stays optional. Tri-state; `nil` omits `agentProgressSummaries` from the `initialize` request and `true` / `false` are forwarded verbatim. An enable switch, not a live toggle: CLI 2.1.278 only acts on a truthy value, so `false` is equivalent to unset.
|
|
38
|
+
- **Compatibility:** `background_tasks_changed` and `permission_denied` frames previously parsed as a generic `SystemMessage`. Both new classes subclass it, so `when SystemMessage` and `#data` consumers are unaffected; code matching on `message.class == SystemMessage` will no longer see them.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- Best-effort control error/cancellation replies no longer leak a secondary connection error when the CLI has already exited. Original transport read errors still propagate to the consumer.
|
|
42
|
+
- Control-request tracking is identity-guarded: if the CLI ever reused an in-flight request ID, the first handler finishing no longer untracks the later request's cancellation signal or task, so EOF, disconnect, and `control_cancel_request` still invalidate it and its late decision cannot be sent as a success.
|
|
43
|
+
- `ClaudeAgentSDK.configure` defaults now merge by option, not by literal key spelling. `ClaudeAgentOptions` accepts symbol/string and snake_case/camelCase names, but the defaults merge compared raw keys, so a differently spelled per-call key rode along as a second entry: `'permissionMode' => nil` wiped a configured `permission_mode:` instead of inheriting it, and a Hash option such as `'env' => {...}` replaced the configured `env:` instead of merging into it. Unknown option names are still reported with the caller's own spelling.
|
|
44
|
+
|
|
10
45
|
## [0.32.0] - 2026-09-17
|
|
11
46
|
|
|
12
47
|
Syncs the gem with Python SDK **0.2.153** (previously 0.2.147). The intervening Python releases 0.2.148–0.2.152 only bump the CLI binary Python bundles; this gem does not vendor a CLI, so they carry no Ruby-side change.
|
data/README.md
CHANGED
|
@@ -27,7 +27,7 @@ A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-ove
|
|
|
27
27
|
|
|
28
28
|
```ruby
|
|
29
29
|
# Gemfile
|
|
30
|
-
gem 'claude-agent-sdk', '~> 0.
|
|
30
|
+
gem 'claude-agent-sdk', '~> 0.33.1'
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Then `bundle install`, or install directly with `gem install claude-agent-sdk`. To track unreleased changes, point the Gemfile at GitHub: `gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'`.
|
|
@@ -146,6 +146,7 @@ See [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md) for the full
|
|
|
146
146
|
| All hook events, typed inputs, permission callbacks | [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md) |
|
|
147
147
|
| Structured output, thinking, budget, fallback and advisor models, sandbox, bare mode, checkpointing | [docs/configuration.md](docs/configuration.md) |
|
|
148
148
|
| Session listing, reading, renaming, tagging, forking, resume-at-message | [docs/sessions.md](docs/sessions.md) |
|
|
149
|
+
| Subagent capabilities, event contracts, and minimal example | [docs/subagents.md](docs/subagents.md) |
|
|
149
150
|
| OpenTelemetry tracing, Langfuse, custom observers | [docs/observability.md](docs/observability.md) |
|
|
150
151
|
| Rails: fiber safety, solid_queue fiber workers, ActionCable, jobs, initializer | [docs/rails.md](docs/rails.md) |
|
|
151
152
|
| Vendoring a pinned CLI binary and CLI discovery order | [docs/cli-installer.md](docs/cli-installer.md) |
|
|
@@ -180,7 +181,7 @@ All three SDKs drive the same CLI over the same protocol, so capabilities line u
|
|
|
180
181
|
| Hooks (all 27 events) | ✅ | ✅ | ✅ |
|
|
181
182
|
| Permission callbacks | ✅ | ✅ | ✅ |
|
|
182
183
|
| Structured output | ✅ | ✅ | ✅ |
|
|
183
|
-
| All
|
|
184
|
+
| All 28 message types | ✅ | partial | ✅ |
|
|
184
185
|
| [Sandbox](https://github.com/anthropic-experimental/sandbox-runtime) settings | ✅ | partial | ✅ |
|
|
185
186
|
| Bare mode (`--bare`) | ✅ | ✅ | ✅ |
|
|
186
187
|
| File checkpointing & rewind | ✅ | ✅ | ✅ |
|
data/docs/client.md
CHANGED
|
@@ -44,6 +44,10 @@ Async do
|
|
|
44
44
|
client.reconnect_mcp_server('my-server') # Reconnect a failed MCP server
|
|
45
45
|
client.toggle_mcp_server('my-server', false) # Enable/disable an MCP server
|
|
46
46
|
client.stop_task('task_abc123') # Stop a running background task
|
|
47
|
+
client.background_tasks # Background every foreground task (Ctrl+B) => {}
|
|
48
|
+
client.background_tasks(tool_use_id: 'toolu_01') # Only the task spawned by that tool_use block
|
|
49
|
+
# => { backgrounded: true } | { backgrounded: false } (definitive miss)
|
|
50
|
+
# '' or a non-String raises ArgumentError; nil is the all-tasks form
|
|
47
51
|
|
|
48
52
|
client.disconnect
|
|
49
53
|
end.wait
|
data/docs/configuration.md
CHANGED
|
@@ -262,6 +262,39 @@ Matches the TypeScript SDK's `forwardSubagentText`. The capability is
|
|
|
262
262
|
negotiated on the control-protocol handshake, so it applies to both
|
|
263
263
|
`ClaudeAgentSDK.query` and `Client`.
|
|
264
264
|
|
|
265
|
+
## Subagent Progress Summaries
|
|
266
|
+
|
|
267
|
+
Set `agent_progress_summaries` to **request** model-generated one-line progress
|
|
268
|
+
summaries for subagent (`local_agent`) tasks. While the CLI has generation
|
|
269
|
+
enabled, a subagent's `TaskProgressMessage#summary` **may** be present; the
|
|
270
|
+
field stays optional on the wire, so not every progress frame carries one —
|
|
271
|
+
read it nil-safely:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
options = ClaudeAgentSDK::ClaudeAgentOptions.new(agent_progress_summaries: true)
|
|
275
|
+
|
|
276
|
+
# ...
|
|
277
|
+
when ClaudeAgentSDK::TaskProgressMessage
|
|
278
|
+
puts "#{message.task_id}: #{message.summary}" if message.summary
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`false` and `nil` do not enable generation. They do not promise suppression
|
|
282
|
+
either: a process that already enabled summaries keeps producing them, and a
|
|
283
|
+
backgrounded `mcp_task` reports its own status in `summary` regardless of this
|
|
284
|
+
option.
|
|
285
|
+
|
|
286
|
+
The option is tri-state: `nil` (the default) omits the key from the `initialize`
|
|
287
|
+
control request, while `true` and `false` are forwarded verbatim as
|
|
288
|
+
`agentProgressSummaries`. It is an **enable switch, not a live toggle**: CLI
|
|
289
|
+
2.1.278 only acts on a truthy value, so `false` is schema-valid but equivalent
|
|
290
|
+
to leaving the option unset — it does not switch summaries off on a process that
|
|
291
|
+
already enabled them. With `ClaudeAgentSDK.configure` defaults, a per-call
|
|
292
|
+
`false` overrides a global `true`, and an unset per-call value inherits the
|
|
293
|
+
global one. It applies to both `ClaudeAgentSDK.query` and `Client`. The key was
|
|
294
|
+
read from the schema embedded in Claude Code CLI 2.1.278 and has not been
|
|
295
|
+
verified against a live run. See
|
|
296
|
+
[subagent capabilities](subagents.md).
|
|
297
|
+
|
|
265
298
|
## File Checkpointing & Rewind
|
|
266
299
|
|
|
267
300
|
Enable file checkpointing to revert file changes to a previous state:
|
|
@@ -12,8 +12,8 @@ All hook input objects include common fields like `session_id`, `transcript_path
|
|
|
12
12
|
- `PostToolUse` → `PostToolUseHookInput` (`tool_name`, `tool_input`, `tool_response`, `tool_use_id`)
|
|
13
13
|
- `PostToolUseFailure` → `PostToolUseFailureHookInput` (`tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`)
|
|
14
14
|
- `UserPromptSubmit` → `UserPromptSubmitHookInput` (`prompt`)
|
|
15
|
-
- `Stop` → `StopHookInput` (`stop_hook_active`)
|
|
16
|
-
- `SubagentStop` → `SubagentStopHookInput` (`stop_hook_active`, `agent_id`, `agent_transcript_path`, `agent_type`)
|
|
15
|
+
- `Stop` → `StopHookInput` (`stop_hook_active`, `last_assistant_message`, `background_tasks`, `session_crons`)
|
|
16
|
+
- `SubagentStop` → `SubagentStopHookInput` (`stop_hook_active`, `agent_id`, `agent_transcript_path`, `agent_type`, `last_assistant_message`, `background_tasks`, `session_crons`)
|
|
17
17
|
- `PreCompact` → `PreCompactHookInput` (`trigger`, `custom_instructions`)
|
|
18
18
|
- `Notification` → `NotificationHookInput` (`message`, `title`, `notification_type`)
|
|
19
19
|
- `SubagentStart` → `SubagentStartHookInput` (`agent_id`, `agent_type`)
|
|
@@ -21,6 +21,14 @@ All hook input objects include common fields like `session_id`, `transcript_path
|
|
|
21
21
|
|
|
22
22
|
All 27 hook events have typed input classes. See [`ClaudeAgentSDK::HOOK_EVENTS`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/types.rb) and [examples/lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb).
|
|
23
23
|
|
|
24
|
+
`background_tasks` and `session_crons` are optional arrays of raw CLI hashes.
|
|
25
|
+
`nil` means the CLI did not provide a snapshot; `[]` means it provided an empty
|
|
26
|
+
one. Nested keys are passed through unchanged (Symbols on the live transport).
|
|
27
|
+
Both snapshots belong to the **parent session**, even on `SubagentStop`; they
|
|
28
|
+
are not a complete list of foreground and background agents. A task disappearing
|
|
29
|
+
from a background snapshot does not establish its terminal status, and a
|
|
30
|
+
SubagentStop hook can itself keep the subagent running. See [subagent capabilities](subagents.md).
|
|
31
|
+
|
|
24
32
|
### Example: Blocking Dangerous Commands
|
|
25
33
|
|
|
26
34
|
```ruby
|
|
@@ -69,6 +77,21 @@ end.wait
|
|
|
69
77
|
|
|
70
78
|
See [examples/hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb), [examples/advanced_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb), and [examples/lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb).
|
|
71
79
|
|
|
80
|
+
### Hook cancellation
|
|
81
|
+
|
|
82
|
+
Dispatched hooks receive `HookContext#request_id` and `HookContext#signal`, using
|
|
83
|
+
the same [cooperative cancellation API](#permission-request-cancellation) as
|
|
84
|
+
permission callbacks. The request ID identifies this invocation, not the hook's
|
|
85
|
+
registered callback ID or the tool invocation.
|
|
86
|
+
|
|
87
|
+
CLI cancellation, EOF, disconnect, callback failure, and `HookMatcher#timeout`
|
|
88
|
+
invalidate the signal. Successful hook completion does not. A default `:thread`
|
|
89
|
+
hook waiting on external work must poll `signal.cancelled?` or use a bounded
|
|
90
|
+
wait; timeout does not forcibly stop its thread. Late hook output is discarded.
|
|
91
|
+
Inline hooks may unwind before the signal is marked cancelled on timeout, so
|
|
92
|
+
always clean up in `ensure`, regardless of the signal's value there. Do not
|
|
93
|
+
swallow cancellation exceptions. Observe the signal; `cancel` is SDK-internal.
|
|
94
|
+
|
|
72
95
|
## Permission Callbacks
|
|
73
96
|
|
|
74
97
|
A **permission callback** is a Ruby proc/lambda that allows you to programmatically control tool execution. This gives you fine-grained control over what tools Claude can use and with what inputs.
|
|
@@ -108,6 +131,47 @@ end.wait
|
|
|
108
131
|
|
|
109
132
|
See [examples/permission_callback_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb).
|
|
110
133
|
|
|
134
|
+
### Permission request cancellation
|
|
135
|
+
|
|
136
|
+
Dispatched `can_use_tool` callbacks receive `context.request_id` (the control
|
|
137
|
+
request ID, distinct from `tool_use_id`) and `context.signal`, a
|
|
138
|
+
`ClaudeAgentSDK::CancellationSignal`:
|
|
139
|
+
|
|
140
|
+
- `signal.cancelled?` checks whether the request is no longer actionable.
|
|
141
|
+
- `signal.wait(timeout: seconds)` waits for cancellation, returning `true` on
|
|
142
|
+
cancellation or `false` on timeout. Omit the timeout to wait indefinitely.
|
|
143
|
+
Multiple waiters and callers arriving after cancellation all observe it.
|
|
144
|
+
- The SDK signals cancellation on a CLI `control_cancel_request`, disconnect,
|
|
145
|
+
EOF/transport failure, or an unsuccessful callback dispatch. Normal allow/deny
|
|
146
|
+
completion does not cancel the signal. Stop waiting once the callback returns.
|
|
147
|
+
|
|
148
|
+
The signal is safe on worker threads and Async fibers. Cancellation is
|
|
149
|
+
cooperative: default `:thread` callbacks are **not** forcibly terminated. Poll
|
|
150
|
+
the signal while waiting for an application's decision and remove the pending
|
|
151
|
+
request in `ensure`. Inline callbacks may instead unwind via `Async::Stop`;
|
|
152
|
+
let it propagate. The SDK never sends a late callback decision as an allow
|
|
153
|
+
response after it has observed cancellation.
|
|
154
|
+
|
|
155
|
+
For example, if the host has registered a separate `Thread::Queue` for this
|
|
156
|
+
request and its decision handler pushes a `PermissionResultAllow` or
|
|
157
|
+
`PermissionResultDeny` into it:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
begin
|
|
161
|
+
loop do
|
|
162
|
+
break ClaudeAgentSDK::PermissionResultDeny.new(message: 'Request cancelled') if context.signal.cancelled?
|
|
163
|
+
decision = decision_queue.pop(timeout: 0.1)
|
|
164
|
+
break decision if decision
|
|
165
|
+
end
|
|
166
|
+
ensure
|
|
167
|
+
# Unregister context.request_id from the host's pending decisions here.
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Do not use an unbounded blocking `gets`/queue wait for a human decision without
|
|
172
|
+
observing cancellation, or auto-approve when the decision service disconnects.
|
|
173
|
+
Hooks have the same signal API; see [hook cancellation](#hook-cancellation).
|
|
174
|
+
|
|
111
175
|
### Shadowing: when `can_use_tool` never runs
|
|
112
176
|
|
|
113
177
|
`can_use_tool` is only consulted when the CLI's permission ladder lands on
|
data/docs/sessions.md
CHANGED
|
@@ -51,6 +51,34 @@ With `directory:` given, only that project and its git worktrees are searched (n
|
|
|
51
51
|
|
|
52
52
|
> Each returned `SessionMessage` carries `parent_tool_use_id` — the id of the Agent `tool_use` block in the parent session that spawned this subagent — and `parent_agent_id`, the spawning subagent's id for nested subagents. Both are read from the `agent-<id>.meta.json` sidecar beside the transcript (or the `agent_metadata` entry in a `SessionStore`), and are `nil` when it is missing or unusable.
|
|
53
53
|
|
|
54
|
+
### Reading Subagent Metadata
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
meta = ClaudeAgentSDK.get_subagent_metadata(session_id: session_id, agent_id: agent_id, directory: project)
|
|
58
|
+
meta = ClaudeAgentSDK.get_subagent_metadata_from_store(
|
|
59
|
+
session_store: store, session_id: session_id, agent_id: agent_id, directory: project
|
|
60
|
+
)
|
|
61
|
+
meta&.dig('toolUseId') # spawning Agent tool call; not task_id
|
|
62
|
+
meta&.dig('parentAgentId')
|
|
63
|
+
meta&.dig('agentType')
|
|
64
|
+
meta&.dig('spawnDepth')
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Returns a **string-keyed Hash with the original CLI field spelling**, preserving
|
|
68
|
+
unknown fields. All fields are optional. `nil` means unavailable; `{}` is a valid
|
|
69
|
+
empty sidecar. The disk reader uses the same project/worktree scope and sorted
|
|
70
|
+
first-match rule as `get_subagent_messages`, but does not parse the transcript.
|
|
71
|
+
It locates the sidecar beside `agent-<id>.jsonl`, so it returns `nil` until that
|
|
72
|
+
transcript file exists, even if the sidecar has already been written.
|
|
73
|
+
Missing, unreadable, non-regular, corrupt, or invalid-UTF-8 sidecars return `nil`.
|
|
74
|
+
|
|
75
|
+
The store reader resolves nested subpaths with `list_subkeys` when available,
|
|
76
|
+
otherwise tries the direct path. It returns the **last** `agent_metadata` entry
|
|
77
|
+
without the synthetic `type` marker, even before any conversation messages have
|
|
78
|
+
arrived. Adapter errors propagate, like other store reads. These APIs do not
|
|
79
|
+
return live status, and reading metadata does not resume an agent. See
|
|
80
|
+
[subagent capabilities](subagents.md) for correlating metadata with events.
|
|
81
|
+
|
|
54
82
|
## Renaming a Session
|
|
55
83
|
|
|
56
84
|
```ruby
|
data/docs/subagents.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Subagent capabilities
|
|
2
|
+
|
|
3
|
+
This repository provides SDK capabilities and minimal usage examples. UI,
|
|
4
|
+
application status aggregation, event persistence, and recovery policy belong
|
|
5
|
+
to the consuming application, not this SDK.
|
|
6
|
+
|
|
7
|
+
## Available data and controls
|
|
8
|
+
|
|
9
|
+
| Capability | SDK surface | Contract |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Define subagents | `ClaudeAgentOptions#agents`, `AgentDefinition` | Configure description, prompt, tools, model, and other agent options |
|
|
12
|
+
| Task lifecycle | `TaskStartedMessage`, `TaskProgressMessage`, `TaskUpdatedMessage`, `TaskNotificationMessage` | Task IDs identify tasks, including non-agent tasks; `task_type: 'local_agent'` identifies subagents |
|
|
13
|
+
| Activity and usage | Task progress / notification fields | Usage is cumulative, not a delta; optional fields depend on the CLI |
|
|
14
|
+
| Card fields | `TaskStartedMessage#subagent_type` / `#is_backgrounded` / `#spawn_depth`, `TaskProgressMessage#subagent_type` | All optional. `is_backgrounded` is tri-state: `true` background, `false` foreground (spawning tool call blocking), `nil` not reported |
|
|
15
|
+
| Display flags | `#skip_transcript` / `#ambient` on `TaskStartedMessage` and `TaskNotificationMessage` | Optional booleans (`nil` absent, `false` preserved). Hints for the host; the SDK never filters frames or computes activity |
|
|
16
|
+
| Progress summaries | `agent_progress_summaries: true`, `TaskProgressMessage#summary` | `true` requests model-generated one-line statuses for `local_agent` tasks; `summary` may then be present but stays optional. An enable switch, not a live toggle. Unset omits the key from `initialize` |
|
|
17
|
+
| Patch fields | `TaskUpdatedMessage#is_backgrounded` / `#error` / `#end_time` / `#total_paused_ms` / `#description` | Derived from `patch`; `nil` means "not in this patch", not a value. `is_backgrounded == true` is a move to the background |
|
|
18
|
+
| Settle details | `TaskNotificationMessage#reason` / `#resource_links` | `reason` is `'worker_restart'` or `nil`; `resource_links` is a raw Array for completed `mcp_task` tasks |
|
|
19
|
+
| Live background set | `BackgroundTasksChangedMessage#tasks` | Level signal with REPLACE semantics: swap your set for each payload. Background tasks only |
|
|
20
|
+
| Auto-denied tool calls | `PermissionDeniedMessage` | Advisory; `agent_id` routes a denial to a subagent. `ResultMessage#permission_denials` stays authoritative |
|
|
21
|
+
| Child output | `forward_subagent_text: true`, `AssistantMessage#parent_tool_use_id` | Forward child text/thinking; the parent tool ID identifies the spawning tool call |
|
|
22
|
+
| Agent identity | `SubagentStartHookInput`, `SubagentStopHookInput`, `get_subagent_metadata[_from_store]` | Metadata can link `agent_id` to `toolUseId`, `agentType`, `parentAgentId`, and `spawnDepth` |
|
|
23
|
+
| Historical transcripts | `list_subagents[_from_store]`, `get_subagent_messages[_from_store]` | Historical reads, not proof that an agent is currently alive |
|
|
24
|
+
| Background snapshots | `StopHookInput` / `SubagentStopHookInput` | Optional `background_tasks` and `session_crons`; `nil` means absent, `[]` means explicitly empty |
|
|
25
|
+
| Permissions | `can_use_tool`, `ToolPermissionContext` | `request_id`, `agent_id`, `tool_use_id`, and cooperative cancellation via `signal.cancelled?` / `signal.wait` |
|
|
26
|
+
| Hook cancellation | `HookContext#signal`, `HookContext#request_id` | Per-invocation cancellation on CLI cancel, disconnect, failure, or hook timeout; successful completion does not cancel |
|
|
27
|
+
| Stop a task | `Client#stop_task(task_id)` | Acknowledgement is not completion; observe a terminal event |
|
|
28
|
+
| Send to background | `Client#background_tasks(tool_use_id: nil)` | Keyed by the spawning `tool_use_id`, not `task_id` / `agent_id`. Targeted: returns `{ backgrounded: true }` or `{ backgrounded: false }` (a definitive miss). `nil`: every foreground task, returns `{}`. `''` / non-String raises `ArgumentError` |
|
|
29
|
+
|
|
30
|
+
Task updates expose raw statuses such as `pending`, `running`, `paused`,
|
|
31
|
+
`completed`, `failed`, and `killed`. Notifications use `completed`, `failed`,
|
|
32
|
+
and `stopped`. A terminal state can arrive through **either** event type;
|
|
33
|
+
`TERMINAL_TASK_STATUSES` covers both vocabularies. Receiving `paused` does not
|
|
34
|
+
imply a public per-agent pause/resume control exists.
|
|
35
|
+
|
|
36
|
+
### Foreground, background, and the live set
|
|
37
|
+
|
|
38
|
+
A subagent registered in the **foreground** reports `TaskStartedMessage#is_backgrounded`
|
|
39
|
+
as `false`: the spawning Agent tool call blocks the turn. Test `== false`, not
|
|
40
|
+
falsiness — `nil` means the CLI did not report the field (it is set only for
|
|
41
|
+
`local_agent` and `local_bash` tasks). A later move to the background does not
|
|
42
|
+
re-emit `task_started`; it arrives as a `TaskUpdatedMessage` whose
|
|
43
|
+
`is_backgrounded` is `true`. The patch readers (`is_backgrounded`, `error`,
|
|
44
|
+
`end_time`, `total_paused_ms`, `description`) mirror how `status` is derived: a
|
|
45
|
+
patch carries only what changed, so merge patches into your own task map instead
|
|
46
|
+
of reading one as the task's full state.
|
|
47
|
+
|
|
48
|
+
`Client#background_tasks` is the control-request equivalent of pressing Ctrl+B:
|
|
49
|
+
each targeted blocking tool call returns a "running in the background"
|
|
50
|
+
tool_result, the turn continues, and the task still emits a
|
|
51
|
+
`TaskNotificationMessage` when it settles.
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
client.background_tasks # every foreground task => {}
|
|
55
|
+
client.background_tasks(tool_use_id: 'toolu_01') # one task => { backgrounded: true } or { backgrounded: false }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`tool_use_id` is the id of the `tool_use` block that **spawned** the task
|
|
59
|
+
(`TaskStartedMessage#tool_use_id`, or `AssistantMessage#parent_tool_use_id` on
|
|
60
|
+
forwarded child output) — not a `task_id` or `agent_id`.
|
|
61
|
+
|
|
62
|
+
- **Targeted** (`tool_use_id:` given): the reply is the outcome.
|
|
63
|
+
`{ backgrounded: true }` means the matching foreground task was backgrounded.
|
|
64
|
+
`{ backgrounded: false }` is a **definitive miss** — the CLI found no
|
|
65
|
+
matching foreground task. Do not wait for an event after a miss.
|
|
66
|
+
- **All tasks** (`nil`, the explicit all-tasks form): the reply is `{}`, which
|
|
67
|
+
says nothing about whether any foreground task existed.
|
|
68
|
+
|
|
69
|
+
Event observation — `TaskUpdatedMessage#is_backgrounded` and
|
|
70
|
+
`BackgroundTasksChangedMessage` — is for the lifecycle state that follows, not a
|
|
71
|
+
substitute for reading the targeted reply.
|
|
72
|
+
|
|
73
|
+
`tool_use_id` must be `nil` or a non-empty String; anything else raises
|
|
74
|
+
`ArgumentError` before a request is written. The CLI itself treats `''` as "all
|
|
75
|
+
tasks", so a selector built from a missing id (`started.tool_use_id.to_s`) would
|
|
76
|
+
release every blocking tool call — and `TaskStartedMessage#tool_use_id` is
|
|
77
|
+
optional on the wire. Never substitute `nil` or `''` for a per-card id you do
|
|
78
|
+
not have yet: disable the per-task control until a real id arrives.
|
|
79
|
+
|
|
80
|
+
`skip_transcript` and `ambient` (on `TaskStartedMessage` and
|
|
81
|
+
`TaskNotificationMessage`) are the CLI's display hints. `skip_transcript` marks
|
|
82
|
+
an ambient/housekeeping task: hide it from the inline transcript, though it may
|
|
83
|
+
still appear in a tasks panel. `ambient` marks tasks that are not activity —
|
|
84
|
+
every `skip_transcript` task plus every live-update watcher, requested or
|
|
85
|
+
auto-started — which hosts should exclude from activity indicators. Both are
|
|
86
|
+
`nil` when absent and keep an explicit `false`. The SDK surfaces every frame
|
|
87
|
+
regardless and computes no activity state from them.
|
|
88
|
+
|
|
89
|
+
`BackgroundTasksChangedMessage#tasks` is the full set of live **background**
|
|
90
|
+
tasks after a membership change, as raw symbol-keyed Hashes
|
|
91
|
+
(`{ task_id:, task_type:, description:, ambient: }`, `ambient` optional — `true`
|
|
92
|
+
marks housekeeping tasks to exclude from activity indicators). It is a level
|
|
93
|
+
signal with **REPLACE semantics**: replace your set with each payload rather
|
|
94
|
+
than pairing start/notification edges, so a missed edge cannot leave a stale
|
|
95
|
+
"running" badge. A frame is emitted on membership changes and also when an
|
|
96
|
+
entry's `ambient` flag flips. REPLACE alone does not prevent a stale badge; the
|
|
97
|
+
CLI's contract also says:
|
|
98
|
+
|
|
99
|
+
- Ordering relative to the edge frames is unspecified, so do not correlate the
|
|
100
|
+
two streams.
|
|
101
|
+
- The level is per CLI process. Nothing is emitted at startup, so reset to the
|
|
102
|
+
empty set whenever the process (re)starts — otherwise the previous process's
|
|
103
|
+
last payload shows running work forever while the new one sits idle.
|
|
104
|
+
- `tasks: []` is an authoritative empty snapshot for that process.
|
|
105
|
+
- A repeated `initialize` on an already-running process is answered with a
|
|
106
|
+
snapshot (even an empty one) right behind its success response; older CLIs
|
|
107
|
+
may send nothing there. This SDK initializes once per connection.
|
|
108
|
+
- A foreground subagent is absent from the set until it is backgrounded.
|
|
109
|
+
|
|
110
|
+
The SDK does not consume this frame for its own bookkeeping, and does not
|
|
111
|
+
aggregate it into a status model.
|
|
112
|
+
|
|
113
|
+
`PermissionDeniedMessage` reports a tool call that was auto-denied without an
|
|
114
|
+
interactive prompt (`tool_name`, `tool_use_id`, `message`, and optionally
|
|
115
|
+
`agent_id`, `decision_reason_type`, `decision_reason`). It is a **best-effort
|
|
116
|
+
advisory, not a complete denial feed**: `ResultMessage#permission_denials` is
|
|
117
|
+
the authoritative record, and in rare races a booked denial has no frame or a
|
|
118
|
+
frame has no booked denial — do not derive badge counts or permission state
|
|
119
|
+
from it. Not covered at all: PreToolUse hook denies, deny-rule overrides of a
|
|
120
|
+
hook's allow/ask decision, Read/Edit/Write calls refused by a path-scoped deny
|
|
121
|
+
rule, and the MCP `--permission-prompt-tool` surface. `decision_reason_type` is
|
|
122
|
+
an open string (`'classifier'`, `'asyncAgent'`, `'mode'`, `'rule'` are
|
|
123
|
+
examples, not an enum), and `agent_id` is a subagent id for routing — not a
|
|
124
|
+
permission `request_id`. With a `can_use_tool` callback an "ask"
|
|
125
|
+
decision goes to the callback instead; without one nobody can answer it, so that
|
|
126
|
+
implicit denial is reported here too.
|
|
127
|
+
|
|
128
|
+
`TaskNotificationMessage#resource_links` passes the CLI's array through
|
|
129
|
+
untouched: symbol-keyed Hashes with the wire spelling preserved — `:uri` and
|
|
130
|
+
`:name` always, plus optional `:title`, `:description`, `:mimeType`, `:size` (a
|
|
131
|
+
number, not necessarily an integer) and `:annotations`. Elements carry no
|
|
132
|
+
`type: 'resource_link'` discriminator. The CLI describes its output as at most
|
|
133
|
+
50 links / 64 KiB; that is a producer-side note the SDK does not enforce.
|
|
134
|
+
|
|
135
|
+
Other frames the CLI marks internal (`agents_killed`, `task_summary`, and
|
|
136
|
+
`permission_denied`'s `decision_reason_code`) arrive as a generic
|
|
137
|
+
`SystemMessage` / through `#data` with no stability promise.
|
|
138
|
+
|
|
139
|
+
**Provenance.** The fields, messages, control request, and option in this
|
|
140
|
+
section were read from the schema embedded in Claude Code CLI 2.1.278. They are
|
|
141
|
+
covered by protocol unit tests, but only partly verified against a live CLI: a
|
|
142
|
+
smoke run against 2.1.278 confirmed `task_started`'s `subagent_type` /
|
|
143
|
+
`is_backgrounded` / `spawn_depth` and the `{ backgrounded: false }` answer to a
|
|
144
|
+
targeted miss. Actually backgrounding a task, `background_tasks_changed` and
|
|
145
|
+
`permission_denied` frames, and progress summaries are schema-derived only.
|
|
146
|
+
Every new reader is `nil` whenever the CLI does not send its field. The
|
|
147
|
+
Python SDK exposes none of them as of Python SDK 0.2.153.
|
|
148
|
+
|
|
149
|
+
`task_id`, `agent_id`, and `tool_use_id` are different identifiers. The metadata
|
|
150
|
+
field `toolUseId` links an agent to its spawning tool call, not to an inner tool
|
|
151
|
+
call awaiting permission. Metadata may be unavailable at the start hook; retry
|
|
152
|
+
on later events or history reads. See [metadata APIs](sessions.md#reading-subagent-metadata).
|
|
153
|
+
|
|
154
|
+
Both Stop snapshots describe the **parent session's background work**, even on
|
|
155
|
+
SubagentStop. They are not a complete agent registry, and absence from a snapshot
|
|
156
|
+
does not establish completion. A SubagentStop hook can itself prevent stopping.
|
|
157
|
+
See [hook fields and permission cancellation](hooks-and-permissions.md).
|
|
158
|
+
|
|
159
|
+
## Minimal example
|
|
160
|
+
|
|
161
|
+
[`examples/subagent_status_example.rb`](../examples/subagent_status_example.rb)
|
|
162
|
+
defines a tool-free reviewer, registers lifecycle hooks, reads metadata, and
|
|
163
|
+
prints task events and forwarded child text without building a status model:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
bundle exec ruby examples/subagent_status_example.rb
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It requires an authenticated Claude Code CLI and makes a real model request.
|
|
170
|
+
The example limits the parent to the Agent tool and gives the reviewer no tools.
|
|
171
|
+
Events and optional metadata depend on the CLI version and actual execution;
|
|
172
|
+
not every run emits every event type. Output can contain prompts and transcripts.
|
|
173
|
+
|
|
174
|
+
The example reads with `Client#receive_messages` for a bounded 60-second window,
|
|
175
|
+
then disconnects. `receive_response` stops at the next parent result, which is
|
|
176
|
+
not the end of background work. The demo deadline is not a task status. Use the
|
|
177
|
+
store-backed metadata API when the CLI filesystem is remote; see
|
|
178
|
+
[permission cancellation](hooks-and-permissions.md#permission-request-cancellation)
|
|
179
|
+
for a minimal cancellation-aware callback wait.
|
|
180
|
+
|
|
181
|
+
The SDK does not provide an authoritative live-agent registry, a UI status
|
|
182
|
+
machine, or direct per-agent pause/resume controls. Reading metadata or restoring
|
|
183
|
+
transcripts does not reconnect to a running subagent.
|
|
184
|
+
|
|
185
|
+
## CLI contract checks
|
|
186
|
+
|
|
187
|
+
The real-CLI suite includes subagent ID/metadata/text correlation, permission
|
|
188
|
+
cancellation on interrupt, and background completion/stop after a parent result:
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
RUN_INTEGRATION=1 bundle exec rspec spec/integration/real_cli_integration_spec.rb --example 'subagent contracts'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
These checks use disposable settings/transcripts and cap each run at $0.50 and
|
|
195
|
+
120 seconds. They require `claude` on PATH and `ANTHROPIC_API_KEY`; without either
|
|
196
|
+
they are **skipped**, not verified. They exercise the installed CLI, not every
|
|
197
|
+
supported CLI version. Protocol unit tests cover both callback scheduling modes
|
|
198
|
+
and terminal event variants deterministically.
|
data/docs/types.md
CHANGED
|
@@ -50,26 +50,56 @@ end
|
|
|
50
50
|
|
|
51
51
|
# Typed subclasses (all inherit from SystemMessage, so is_a?(SystemMessage) still works)
|
|
52
52
|
class TaskStartedMessage < SystemMessage
|
|
53
|
-
attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type, :workflow_name, :prompt
|
|
53
|
+
attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type, :workflow_name, :prompt,
|
|
54
|
+
:subagent_type, # String | nil
|
|
55
|
+
:is_backgrounded, # true (background) | false (foreground, tool call blocking) | nil (not reported)
|
|
56
|
+
:spawn_depth, # Integer | nil (1 = top-level subagent)
|
|
57
|
+
:skip_transcript, # true | false | nil — hide from the inline transcript (a tasks panel may still show it)
|
|
58
|
+
:ambient # true | false | nil — not activity; exclude from activity indicators
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
class TaskProgressMessage < SystemMessage
|
|
57
|
-
attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name, :summary
|
|
62
|
+
attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name, :summary,
|
|
63
|
+
:subagent_type # String | nil
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
class TaskNotificationMessage < SystemMessage
|
|
61
|
-
attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage
|
|
67
|
+
attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage,
|
|
68
|
+
:reason, # 'worker_restart' | nil
|
|
69
|
+
:resource_links, # Array<Hash> | nil — raw, symbol keys with wire spelling (:uri, :name, :mimeType, ...)
|
|
70
|
+
:skip_transcript, # true | false | nil — same meaning as on TaskStartedMessage
|
|
71
|
+
:ambient # true | false | nil — the SDK never filters on either flag
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
# Background task lifecycle state change. `status` is derived from patch["status"].
|
|
65
75
|
# A terminal task can arrive *only* as a TaskUpdatedMessage (no TaskNotificationMessage) —
|
|
66
76
|
# e.g. a TaskStop-killed task reports status "killed" here. Clear tracked task IDs on a
|
|
67
77
|
# terminal status (see TERMINAL_TASK_STATUSES) from *either* message.
|
|
78
|
+
# The other patch readers are derived the same way; nil means "not in this patch".
|
|
68
79
|
class TaskUpdatedMessage < SystemMessage
|
|
69
|
-
attr_accessor :task_id, :patch, :status, :uuid, :session_id
|
|
80
|
+
attr_accessor :task_id, :patch, :status, :uuid, :session_id,
|
|
81
|
+
:description, :error, :end_time, :total_paused_ms,
|
|
82
|
+
:is_backgrounded # true = moved to the background | false | nil (patch does not mention it)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Full set of live background tasks; REPLACE semantics (swap your set for each payload).
|
|
86
|
+
class BackgroundTasksChangedMessage < SystemMessage
|
|
87
|
+
attr_accessor :tasks, # Array<Hash> — raw { task_id:, task_type:, description:, ambient: }
|
|
88
|
+
:uuid, :session_id
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# A tool call auto-denied without an interactive prompt. Best-effort advisory, not a
|
|
92
|
+
# complete denial feed; ResultMessage#permission_denials is the authoritative record.
|
|
93
|
+
class PermissionDeniedMessage < SystemMessage
|
|
94
|
+
attr_accessor :tool_name, :tool_use_id, :message, :uuid, :session_id,
|
|
95
|
+
:agent_id, # String | nil — subagent id for routing (NOT a permission request_id)
|
|
96
|
+
:decision_reason_type, # String | nil — open string ('classifier', 'asyncAgent', 'mode', 'rule', ...)
|
|
97
|
+
:decision_reason # String | nil
|
|
70
98
|
end
|
|
71
99
|
```
|
|
72
100
|
|
|
101
|
+
See [subagent capabilities](subagents.md) for the contracts behind these fields.
|
|
102
|
+
|
|
73
103
|
### ResultMessage
|
|
74
104
|
|
|
75
105
|
Final result message with cost and usage information.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgentSDK
|
|
4
|
+
# Cooperative cancellation of a permission or hook request. Safe to observe from
|
|
5
|
+
# worker threads and Async fibers. Cancellation does not kill user threads.
|
|
6
|
+
class CancellationSignal
|
|
7
|
+
def initialize
|
|
8
|
+
@queue = Thread::Queue.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def cancelled?
|
|
12
|
+
@queue.closed?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Wait for cancellation, returning true when cancelled, false on timeout.
|
|
16
|
+
# Level-triggered: late callers and multiple waiters all observe cancellation.
|
|
17
|
+
# @param timeout [Numeric, nil] Seconds; nil waits without a deadline
|
|
18
|
+
def wait(timeout: nil) # rubocop:disable Naming/PredicateMethod -- blocking wait, not a state predicate
|
|
19
|
+
@queue.pop(timeout: timeout)
|
|
20
|
+
cancelled?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @api private Called by the SDK when the request is no longer actionable.
|
|
24
|
+
def cancel
|
|
25
|
+
@queue.close
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -133,7 +133,9 @@ module ClaudeAgentSDK
|
|
|
133
133
|
# like every other system subtype. `data` is always symbol-keyed here:
|
|
134
134
|
# `parse` rejects any message lacking a `:type` symbol key, so a
|
|
135
135
|
# string-keyed hash never reaches these classes.
|
|
136
|
-
'task_updated' => TaskUpdatedMessage
|
|
136
|
+
'task_updated' => TaskUpdatedMessage,
|
|
137
|
+
'background_tasks_changed' => BackgroundTasksChangedMessage,
|
|
138
|
+
'permission_denied' => PermissionDeniedMessage
|
|
137
139
|
}.freeze
|
|
138
140
|
|
|
139
141
|
def self.parse_system_message(data)
|