@factory/droid-sdk 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @factory/droid-sdk
2
2
 
3
- TypeScript SDK for the [Factory](https://factory.ai) Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with streaming message support, multi-turn sessions, and tool permission handling.
3
+ TypeScript SDK for the [Factory](https://factory.ai) Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with one-shot prompts, streaming messages, multi-turn sessions, structured output, SDK-backed MCP tools, spec mode, tool controls, initialization metadata, session forking, session discovery, and tool permission handling.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -15,80 +15,338 @@ npm install @factory/droid-sdk
15
15
 
16
16
  ## Quick Start
17
17
 
18
- Send a one-shot prompt and stream the response:
18
+ Send a one-shot prompt and get the aggregated result:
19
19
 
20
20
  ```ts
21
- import { query } from "@factory/droid-sdk";
21
+ import { run } from '@factory/droid-sdk';
22
22
 
23
- const stream = query({
24
- prompt: "What files are in the current directory?",
25
- cwd: "/my/project",
23
+ const result = await run('What files are in the current directory?', {
24
+ cwd: '/my/project',
26
25
  });
27
26
 
28
- for await (const msg of stream) {
29
- if (msg.type === "assistant_text_delta") {
30
- process.stdout.write(msg.text);
31
- }
32
- if (msg.type === "turn_complete") {
33
- console.log("\nDone!");
27
+ console.log(result.text);
28
+ ```
29
+
30
+ Create a session when you want to stream one or more turns:
31
+
32
+ ```ts
33
+ import { DroidMessageType, createSession } from '@factory/droid-sdk';
34
+
35
+ const session = await createSession({ cwd: '/my/project' });
36
+
37
+ try {
38
+ for await (const msg of session.stream(
39
+ 'What files are in the current directory?'
40
+ )) {
41
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
42
+ process.stdout.write(msg.text);
43
+ }
44
+ if (msg.type === DroidMessageType.Result) {
45
+ console.log('\nDone!');
46
+ }
34
47
  }
48
+ } finally {
49
+ await session.close();
35
50
  }
36
51
  ```
37
52
 
53
+ ## Structured Output
54
+
55
+ Request a JSON object that matches a JSON Schema:
56
+
57
+ ```ts
58
+ import { OutputFormatType, run } from '@factory/droid-sdk';
59
+
60
+ const result = await run('Pick a favorite number between 1 and 42.', {
61
+ cwd: '/my/project',
62
+ outputFormat: {
63
+ type: OutputFormatType.JsonSchema,
64
+ schema: {
65
+ type: 'object',
66
+ properties: {
67
+ favoriteNumber: {
68
+ type: 'number',
69
+ minimum: 1,
70
+ maximum: 42,
71
+ },
72
+ },
73
+ required: ['favoriteNumber'],
74
+ },
75
+ },
76
+ });
77
+
78
+ console.log(result.structuredOutput?.favoriteNumber);
79
+ ```
80
+
81
+ Structured output is available on `run()` and `session.stream(prompt, options)` through the `outputFormat` message option. `run()` parses the final object into `result.structuredOutput`; streaming callers can read and parse the final assistant message themselves.
82
+
38
83
  ## Multi-Turn Sessions
39
84
 
40
85
  Use `createSession()` for persistent conversations with multiple turns:
41
86
 
42
87
  ```ts
43
- import { createSession } from "@factory/droid-sdk";
88
+ import { createSession, DroidMessageType } from '@factory/droid-sdk';
44
89
 
45
- const session = await createSession({ cwd: "/my/project" });
90
+ const session = await createSession({ cwd: '/my/project' });
91
+ console.log(session.sessionId);
46
92
 
47
93
  // Streaming turn
48
- for await (const msg of session.stream("List all TypeScript files")) {
49
- if (msg.type === "assistant_text_delta") {
94
+ for await (const msg of session.stream('List all TypeScript files')) {
95
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
50
96
  process.stdout.write(msg.text);
51
97
  }
52
98
  }
53
99
 
54
- // Non-streaming turn
55
- const result = await session.send("Summarize the project");
56
- console.log(result.text);
100
+ // Later turns use the same streaming API
101
+ for await (const msg of session.stream('Summarize the project')) {
102
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
103
+ process.stdout.write(msg.text);
104
+ }
105
+ }
57
106
 
58
107
  await session.close();
59
108
  ```
60
109
 
61
- Resume an existing session by ID:
110
+ Use `session.sessionId` to persist the session ID, then resume it later:
62
111
 
63
112
  ```ts
64
- import { resumeSession } from "@factory/droid-sdk";
113
+ import { resumeSession } from '@factory/droid-sdk';
65
114
 
66
- const session = await resumeSession("session-id-here");
67
- const result = await session.send("Continue where we left off");
68
- console.log(result.text);
115
+ const session = await resumeSession(savedSessionId);
116
+ for await (const msg of session.stream('Continue where we left off')) {
117
+ // Handle streamed DroidMessage events.
118
+ }
119
+ await session.close();
120
+ ```
121
+
122
+ The returned `DroidSession` also exposes `session.initResult`, which contains the raw `initialize_session` or `load_session` result returned by the JSON-RPC server.
123
+
124
+ ## SDK-backed MCP Tools
125
+
126
+ Expose in-process TypeScript tools to Droid through MCP:
127
+
128
+ ```ts
129
+ import { z } from 'zod';
130
+
131
+ import {
132
+ DroidMessageType,
133
+ ToolConfirmationOutcome,
134
+ createSession,
135
+ createSdkMcpServer,
136
+ tool,
137
+ } from '@factory/droid-sdk';
138
+
139
+ const sdkTools = createSdkMcpServer({
140
+ name: 'sdk-tools',
141
+ tools: [
142
+ tool(
143
+ 'favorite_number',
144
+ 'Returns a favorite number for a person',
145
+ { name: z.string() },
146
+ ({ name }) => `${name}'s favorite number is 42.`
147
+ ),
148
+ ],
149
+ });
150
+
151
+ const session = await createSession({
152
+ cwd: '/my/project',
153
+ mcpServers: [sdkTools],
154
+ permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
155
+ });
156
+
157
+ for await (const msg of session.stream(
158
+ 'Use the favorite_number tool for Ada and tell me the answer.'
159
+ )) {
160
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
161
+ process.stdout.write(msg.text);
162
+ }
163
+ }
164
+
165
+ await session.close();
166
+ ```
167
+
168
+ `createSdkMcpServer()` is managed by the SDK session lifecycle. Use `tool()` with a Zod object shape for typed tool input validation.
169
+
170
+ ## Initialization Metadata
171
+
172
+ Inspect the raw initialization metadata from `createSession()` and `resumeSession()`:
173
+
174
+ ```ts
175
+ import { createSession, resumeSession } from '@factory/droid-sdk';
176
+
177
+ const session = await createSession({ cwd: '/my/project' });
178
+ console.log(session.sessionId);
179
+ console.log(session.initResult.settings.modelId);
180
+
181
+ const resumed = await resumeSession(session.sessionId);
182
+ console.log(resumed.initResult.cwd);
183
+
184
+ await resumed.close();
185
+ await session.close();
186
+ ```
187
+
188
+ ## Spec Mode
189
+
190
+ Start a session directly in spec mode, or enter spec mode later on an existing session:
191
+
192
+ ```ts
193
+ import {
194
+ DroidMessageType,
195
+ createSession,
196
+ DroidInteractionMode,
197
+ ReasoningEffort,
198
+ } from '@factory/droid-sdk';
199
+
200
+ const session = await createSession({
201
+ cwd: '/my/project',
202
+ interactionMode: DroidInteractionMode.Spec,
203
+ specModeReasoningEffort: ReasoningEffort.High,
204
+ specModeModelId: 'claude-sonnet-4-20250514',
205
+ });
206
+
207
+ for await (const msg of session.stream(
208
+ 'Draft a plan for adding integration tests'
209
+ )) {
210
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
211
+ process.stdout.write(msg.text);
212
+ }
213
+ }
214
+
215
+ await session.enterSpecMode({
216
+ specModeReasoningEffort: ReasoningEffort.High,
217
+ });
218
+
219
+ await session.close();
220
+ ```
221
+
222
+ When handling spec-mode approval, you can approve implementation in the same session with `ToolConfirmationOutcome.ProceedOnce`, or hand off to a fresh session with `ToolConfirmationOutcome.ProceedNewSessionHigh`.
223
+
224
+ ## Tool Controls
225
+
226
+ Control which exec tools are available at session start, inspect the current tool catalog, and update tool overrides later:
227
+
228
+ ```ts
229
+ import { createSession } from '@factory/droid-sdk';
230
+
231
+ const session = await createSession({
232
+ cwd: '/my/project',
233
+ enabledToolIds: ['Read'],
234
+ disabledToolIds: ['Execute'],
235
+ });
236
+
237
+ const { tools } = await session.listTools();
238
+ console.log(
239
+ tools.map((tool) => ({
240
+ id: tool.llmId,
241
+ allowed: tool.currentlyAllowed,
242
+ }))
243
+ );
244
+
245
+ await session.updateSettings({
246
+ disabledToolIds: ['Read', 'Execute'],
247
+ });
248
+
249
+ await session.close();
250
+ ```
251
+
252
+ ## Forking Sessions
253
+
254
+ Fork the current server-side session and continue from the new session ID:
255
+
256
+ ```ts
257
+ import {
258
+ DroidMessageType,
259
+ createSession,
260
+ resumeSession,
261
+ } from '@factory/droid-sdk';
262
+
263
+ const session = await createSession({ cwd: '/my/project' });
264
+
265
+ for await (const _msg of session.stream(
266
+ 'Remember this phrase: mango sunrise'
267
+ )) {
268
+ // Consume the turn.
269
+ }
270
+
271
+ const { newSessionId } = await session.forkSession();
272
+ const fork = await resumeSession(newSessionId);
273
+
274
+ for await (const msg of fork.stream('What phrase did I ask you to remember?')) {
275
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
276
+ process.stdout.write(msg.text);
277
+ }
278
+ }
279
+
280
+ await fork.close();
69
281
  await session.close();
70
282
  ```
71
283
 
284
+ ## Listing Sessions
285
+
286
+ Discover droid sessions saved on disk (mirrors the CLI's `/sessions` command). Reads `~/.factory/sessions/` directly — no droid process is spawned, so this works even when no session is running:
287
+
288
+ ```ts
289
+ import { listSessions } from '@factory/droid-sdk';
290
+
291
+ // Sessions for the current project (cwd defaults to process.cwd())
292
+ const current = await listSessions();
293
+
294
+ // 10 most recent sessions in the current project
295
+ const recent = await listSessions({ numSessions: 10 });
296
+
297
+ // Every session on disk, most recent first
298
+ const all = await listSessions({ fetchOutsideCWD: true });
299
+
300
+ // 10 most recent sessions across all projects
301
+ const recentAcrossProjects = await listSessions({
302
+ fetchOutsideCWD: true,
303
+ numSessions: 10,
304
+ });
305
+
306
+ // Sessions for a specific other project
307
+ const other = await listSessions({ cwd: '/Users/me/other-repo' });
308
+
309
+ for (const s of current) {
310
+ console.log(`[${s.id}] ${s.title} (${s.messageCount} msgs)`);
311
+ }
312
+ ```
313
+
314
+ Each `SessionMetadata` record includes `id`, `title`, `sessionTitle`, `owner`, `messageCount`, `modifiedTime`, `createdTime`, `isFavorite`, `cwd`, `decompSessionType`, and `decompMissionId`. Archived sessions (those with an `archivedAt` in their settings file) are excluded automatically. Results are sorted by `modifiedTime` descending.
315
+
316
+ `ListSessionsOptions`:
317
+
318
+ - **`cwd`** — working directory to scope the listing to (default `process.cwd()`). Ignored when `fetchOutsideCWD` is `true`.
319
+ - **`fetchOutsideCWD`** — return sessions from every working directory on disk (default `false`)
320
+ - **`numSessions`** — cap on total sessions returned
321
+ - **`sessionsDir`** — override the sessions root (default `~/.factory/sessions/`)
322
+
72
323
  ## Permission Handling
73
324
 
74
325
  Handle tool confirmation requests with a custom permission handler:
75
326
 
76
327
  ```ts
77
- import { query, ToolConfirmationOutcome } from "@factory/droid-sdk";
78
-
79
- const stream = query({
80
- prompt: "Create a hello.txt file",
81
- cwd: "/my/project",
328
+ import {
329
+ DroidMessageType,
330
+ createSession,
331
+ ToolConfirmationOutcome,
332
+ } from '@factory/droid-sdk';
333
+
334
+ const session = await createSession({
335
+ cwd: '/my/project',
82
336
  permissionHandler(params) {
83
- console.log("Tool permission requested:", params);
337
+ console.log('Tool permission requested:', params);
84
338
  return ToolConfirmationOutcome.ProceedOnce;
85
339
  },
86
340
  });
87
341
 
88
- for await (const msg of stream) {
89
- if (msg.type === "assistant_text_delta") {
90
- process.stdout.write(msg.text);
342
+ try {
343
+ for await (const msg of session.stream('Create a hello.txt file')) {
344
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
345
+ process.stdout.write(msg.text);
346
+ }
91
347
  }
348
+ } finally {
349
+ await session.close();
92
350
  }
93
351
  ```
94
352
 
@@ -96,93 +354,159 @@ for await (const msg of stream) {
96
354
 
97
355
  ### Top-Level Functions
98
356
 
99
- | Function | Description |
100
- |---|---|
101
- | `query(options)` | One-shot prompt → async generator of `DroidMessage` events |
102
- | `createSession(options?)` | Create a new multi-turn session → `DroidSession` |
103
- | `resumeSession(id, options?)` | Resume an existing session → `DroidSession` |
104
-
105
- ### `query(options): DroidQuery`
106
-
107
- Returns an async generator that yields `DroidMessage` events. The returned `DroidQuery` object also exposes:
108
-
109
- - **`interrupt()`** — gracefully interrupt the agent's current turn
110
- - **`abort()`** — forcefully kill the subprocess
111
- - **`sessionId`** — the session ID (available after initialization)
357
+ | Function | Description |
358
+ | ----------------------------- | ---------------------------------------------------------------- |
359
+ | `run(prompt, options?)` | One-shot prompt → aggregated `DroidResult` |
360
+ | `createSession(options?)` | Create a new multi-turn session → `DroidSession` |
361
+ | `resumeSession(id, options?)` | Resume an existing session → `DroidSession` |
362
+ | `listSessions(options?)` | List droid sessions saved on disk → `Promise<SessionMetadata[]>` |
363
+ | `createSdkMcpServer(options)` | Create an SDK-managed MCP server for in-process tools |
364
+ | `tool(...)` | Define a typed SDK-backed MCP tool |
112
365
 
113
366
  ### `DroidSession`
114
367
 
115
368
  Returned by `createSession()` and `resumeSession()`. Key methods:
116
369
 
117
- - **`stream(text, options?)`** — send a message, returns async generator of `DroidMessage`
118
- - **`send(text, options?)`** — send a message, returns aggregated `DroidResult`
370
+ - **`stream(prompt, options?)`** — stream `DroidMessage` events for one turn
119
371
  - **`interrupt()`** — interrupt the current turn
120
372
  - **`close()`** — close the session and release resources
121
373
  - **`updateSettings(params)`** — update model, autonomy level, etc.
122
- - **`addMcpServer(params)`** / **`removeMcpServer(params)`** — manage MCP servers
374
+ - **`enterSpecMode(params?)`** — switch the current session into spec mode
375
+ - **`forkSession()`** — create a forked server-side session and return its new session ID
376
+ - **`compactSession(params?)`** — compact session history and return the new session ID
377
+ - **`getContextStats()`** — read current context window utilization
378
+ - **`addMcpServer(params)`** / **`removeMcpServer(params)`** / **`toggleMcpServer(params)`** — manage MCP servers
379
+ - **`listMcpServers()`** / **`listMcpTools()`** / **`authenticateMcpServer(params)`** — inspect and authenticate MCP servers
380
+ - **`listTools(params?)`** — inspect the exec tool catalog and current allow/deny state
381
+ - **`renameSession(params)`** — rename the current session
382
+ - **`getRewindInfo(params)`** / **`executeRewind(params)`** — inspect and execute file rewind operations
123
383
  - **`sessionId`** — the session ID
384
+ - **`initResult`** — cached `initialize_session` or `load_session` result
124
385
 
125
386
  ### `DroidResult`
126
387
 
127
- Returned by `session.send()`:
388
+ Returned by `run()`:
128
389
 
390
+ - **`sessionId`** — session that produced the result
129
391
  - **`text`** — concatenated assistant response text
130
392
  - **`messages`** — all `DroidMessage` objects from the turn
131
393
  - **`tokenUsage`** — final token usage, or `null`
394
+ - **`durationMs`** — wall-clock time spent consuming the turn
395
+ - **`turnCount`** — number of completed turns observed while consuming the stream
396
+ - **`error`** — first Droid error event from the turn, or `null`
397
+ - **`structuredOutput`** — parsed structured JSON object, or `null`
398
+ - **`success`** — `true` when no Droid error event was emitted
132
399
 
133
400
  ### `DroidMessage` Types
134
401
 
135
402
  All messages have a discriminated `type` field:
136
403
 
137
- | Type | Description |
138
- |---|---|
139
- | `assistant_text_delta` | Streaming text token from the assistant |
140
- | `thinking_text_delta` | Streaming reasoning/thinking token |
141
- | `tool_use` | Tool invocation by the assistant |
142
- | `tool_result` | Result from a tool execution |
143
- | `tool_progress` | Progress update during tool execution |
144
- | `working_state_changed` | Agent working state transition |
145
- | `token_usage_update` | Updated token usage counters |
146
- | `create_message` | Full assistant message created |
147
- | `turn_complete` | Sentinel: agent turn finished |
148
- | `error` | Error event from the process |
404
+ ```ts
405
+ import { DroidMessageType } from '@factory/droid-sdk';
406
+
407
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
408
+ process.stdout.write(msg.text);
409
+ }
410
+ ```
411
+
412
+ | Type | Description |
413
+ | -------------------------- | ----------------------------------------------- |
414
+ | `assistant` | Complete assistant message |
415
+ | `user` | Complete user message |
416
+ | `assistant_text_delta` | Streaming text token from the assistant |
417
+ | `assistant_text_complete` | End of an assistant text block |
418
+ | `thinking_text_delta` | Streaming reasoning/thinking token |
419
+ | `thinking_text_complete` | End of a thinking block |
420
+ | `tool_call` | Tool invocation by the assistant |
421
+ | `tool_call_delta` | Streaming tool call input |
422
+ | `tool_result` | Result from a tool execution |
423
+ | `tool_progress` | Progress update during tool execution |
424
+ | `hook` | File hook execution event (started or finished) |
425
+ | `working_state_changed` | Agent working state transition |
426
+ | `token_usage_update` | Updated token usage counters |
427
+ | `result` | End-of-turn sentinel with aggregated metadata |
428
+ | `session_title_updated` | Session title changed |
429
+ | `settings_updated` | Session settings changed |
430
+ | `permission_resolved` | Tool permission request resolved |
431
+ | `mcp_status_changed` | MCP server status changed |
432
+ | `mcp_auth_required` | MCP authentication required |
433
+ | `mcp_auth_completed` | MCP authentication completed |
434
+ | `error` | Error event from the process |
435
+ | `mission_state_changed` | Mission state changed |
436
+ | `mission_features_changed` | Mission features changed |
437
+ | `mission_progress_entry` | Mission progress log changed |
438
+ | `mission_heartbeat` | Mission heartbeat |
439
+ | `mission_worker_started` | Mission worker started |
440
+ | `mission_worker_completed` | Mission worker completed |
149
441
 
150
442
  ### Options
151
443
 
152
- `QueryOptions` and `CreateSessionOptions` accept:
444
+ Session creation options used by `run()` and `createSession()` include:
153
445
 
154
- - **`prompt`** — the user prompt (query only)
155
446
  - **`cwd`** — working directory for the session
447
+ - **`execPath`** — path to `droid` executable (default: `"droid"`)
448
+ - **`execArgs`** — extra CLI arguments for the spawned droid process
449
+ - **`env`** — environment variables for the spawned process
450
+ - **`transport`** — provide a custom transport instead of spawning a process
156
451
  - **`modelId`** — LLM model identifier
157
452
  - **`autonomyLevel`** — `AutonomyLevel` enum value
453
+ - **`interactionMode`** — `DroidInteractionMode` enum value
158
454
  - **`reasoningEffort`** — `ReasoningEffort` enum value
455
+ - **`specModeModelId`** — override model used in spec mode
456
+ - **`specModeReasoningEffort`** — override reasoning level used in spec mode
457
+ - **`mcpServers`** — initial MCP server configurations, including SDK-backed MCP servers from `createSdkMcpServer()`
458
+ - **`enabledToolIds`** — explicit exec tool allowlist
459
+ - **`disabledToolIds`** — explicit exec tool denylist
159
460
  - **`permissionHandler`** — callback for tool confirmations
160
461
  - **`askUserHandler`** — callback for interactive questions
161
- - **`execPath`** — path to `droid` executable (default: `"droid"`)
162
- - **`transport`** — provide a custom transport instead of spawning a process
462
+ - **`abortSignal`** — standard `AbortSignal` for cancellation
463
+
464
+ `resumeSession()` accepts the process, transport, handler, `mcpServers`, and `abortSignal` options needed to reconnect to an existing session, but does not accept new-session-only options such as `modelId` or `interactionMode`. `cwd` is intentionally not accepted on resume: the persisted session's working directory is always used. To run in a different directory, create a new session or fork the existing one.
465
+
466
+ Message APIs (`run()` and `session.stream()`) also accept:
467
+
468
+ - **`images`** — base64 image attachments
469
+ - **`files`** — document/file attachments
470
+ - **`outputFormat`** — structured output request, currently `OutputFormatType.JsonSchema`
471
+ - **`abortSignal`** — standard `AbortSignal` for turn cancellation
163
472
 
164
473
  ### `DroidClient`
165
474
 
166
- Low-level JSON-RPC client for advanced use. Provides typed methods for all 19 protocol operations. Most users should prefer `query()` and `createSession()`.
475
+ Low-level JSON-RPC client for advanced use. Provides typed methods for the underlying protocol operations, including `listTools()`, `renameSession()`, `getRewindInfo()`, and `executeRewind()`. Most users should prefer `run()` and `createSession()`.
167
476
 
168
477
  ### Error Types
169
478
 
170
- | Error | Description |
171
- |---|---|
172
- | `ConnectionError` | Failed to connect to the droid process |
173
- | `ProtocolError` | JSON-RPC protocol error |
174
- | `SessionError` | Base session error |
175
- | `SessionNotFoundError` | Session ID not found |
176
- | `TimeoutError` | Request timed out |
177
- | `ProcessExitError` | Droid subprocess exited unexpectedly |
479
+ | Error | Description |
480
+ | ---------------------- | -------------------------------------- |
481
+ | `ConnectionError` | Failed to connect to the droid process |
482
+ | `ProtocolError` | JSON-RPC protocol error |
483
+ | `SessionError` | Base session error |
484
+ | `SessionNotFoundError` | Session ID not found |
485
+ | `TimeoutError` | Request timed out |
486
+ | `ProcessExitError` | Droid subprocess exited unexpectedly |
178
487
 
179
488
  ## Examples
180
489
 
181
490
  See the [`examples/`](./examples) directory for runnable examples:
182
491
 
183
- - **[`simple-query.ts`](./examples/simple-query.ts)** — one-shot query with streaming output
492
+ - **[`session-stream.ts`](./examples/session-stream.ts)** — session streaming output
493
+ - **[`run.ts`](./examples/run.ts)** — one-shot prompt with aggregated result
184
494
  - **[`multi-turn-session.ts`](./examples/multi-turn-session.ts)** — multi-turn session lifecycle
495
+ - **[`abort-session-stream.ts`](./examples/abort-session-stream.ts)** — cancel an in-flight streaming session turn with `AbortSignal`
496
+ - **[`interrupt-session.ts`](./examples/interrupt-session.ts)** — interrupt a running session turn
497
+ - **[`init-metadata.ts`](./examples/init-metadata.ts)** — read initialization and load metadata from session APIs
498
+ - **[`result-metadata.ts`](./examples/result-metadata.ts)** — inspect `DroidResult` metadata from `run()`
499
+ - **[`structured-output.ts`](./examples/structured-output.ts)** — request and parse structured output
500
+ - **[`droid-dev-structured-output.ts`](./examples/droid-dev-structured-output.ts)** — structured output smoke test with configurable Droid executable
185
501
  - **[`permission-handler.ts`](./examples/permission-handler.ts)** — custom permission handling
502
+ - **[`spec-mode-same-session.ts`](./examples/spec-mode-same-session.ts)** — approve a spec and continue in the same session
503
+ - **[`spec-mode-new-session.ts`](./examples/spec-mode-new-session.ts)** — approve a spec and hand off implementation to a new session
504
+ - **[`tool-controls.ts`](./examples/tool-controls.ts)** — configure allow/deny lists and inspect tool availability
505
+ - **[`sdk-mcp-tool.ts`](./examples/sdk-mcp-tool.ts)** — expose SDK-defined tools to Droid through MCP
506
+ - **[`fork-session.ts`](./examples/fork-session.ts)** — fork a session and continue from the new session ID
507
+ - **[`list-sessions.ts`](./examples/list-sessions.ts)** — discover droid sessions saved on disk
508
+ - **[`test-compact.ts`](./examples/test-compact.ts)** — compact session history
509
+ - **[`test-rewind.ts`](./examples/test-rewind.ts)** — use low-level rewind APIs
186
510
 
187
511
  ## License
188
512