@factory/droid-sdk 0.6.0 → 0.7.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 +35 -482
- package/dist/chunk-5UXINOXG.mjs +24483 -0
- package/dist/chunk-5UXINOXG.mjs.map +1 -0
- package/dist/chunk-C3KHERVH.js +24483 -0
- package/dist/chunk-C3KHERVH.js.map +1 -0
- package/dist/{index-CwXX5IUH.d.cts → index-D_SzTnFR.d.mts} +83826 -81547
- package/dist/{index-CwXX5IUH.d.ts → index-D_SzTnFR.d.ts} +83826 -81547
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -93103
- package/dist/index.js +821 -11745
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +822 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node.d.mts +736 -0
- package/dist/node.d.ts +736 -0
- package/dist/node.js +4601 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +4601 -0
- package/dist/node.mjs.map +1 -0
- package/docs/typescript-sdk-reference.md +1260 -0
- package/examples/browser/daemon-lifecycle.ts +95 -0
- package/examples/browser/daemon-multi-session.ts +66 -0
- package/examples/browser/daemon-session.ts +73 -0
- package/examples/browser/index.html +68 -0
- package/examples/browser/list-sessions.ts +52 -0
- package/examples/{abort-session-stream.ts → node/abort-session-stream.ts} +8 -10
- package/examples/node/ask-user-elicitation.ts +35 -0
- package/examples/node/compact-session.ts +48 -0
- package/examples/node/enter-spec-mode.ts +66 -0
- package/examples/node/fork-session.ts +45 -0
- package/examples/node/hook-execution.ts +94 -0
- package/examples/node/image-attachment.ts +35 -0
- package/examples/node/init-metadata.ts +24 -0
- package/examples/{interrupt-session.ts → node/interrupt-session.ts} +7 -6
- package/examples/node/list-sessions.ts +20 -0
- package/examples/node/multi-turn-session.ts +33 -0
- package/examples/node/observability.ts +88 -0
- package/examples/node/permission-handler.ts +55 -0
- package/examples/node/rename-session.ts +29 -0
- package/examples/node/result-metadata.ts +34 -0
- package/examples/node/rewind-session.ts +69 -0
- package/examples/node/run.ts +25 -0
- package/examples/{sdk-mcp-tool.ts → node/sdk-mcp-tool.ts} +22 -12
- package/examples/node/session-settings.ts +35 -0
- package/examples/node/session-stream.ts +44 -0
- package/examples/node/spec-mode-new-session.ts +92 -0
- package/examples/node/spec-mode-same-session.ts +85 -0
- package/examples/node/structured-output.ts +73 -0
- package/examples/node/tool-controls.ts +26 -0
- package/examples/package.json +3 -0
- package/package.json +20 -42
- package/dist/index.cjs +0 -12135
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -93103
- package/dist/protocol/index.cjs +0 -6466
- package/dist/protocol/index.cjs.map +0 -1
- package/dist/protocol/index.d.cts +0 -2
- package/dist/protocol/index.d.ts +0 -2
- package/dist/protocol/index.js +0 -6078
- package/dist/protocol/index.js.map +0 -1
- package/docs/daemon-usage-guide.md +0 -544
- package/docs/sdk-usage-guide.md +0 -748
- package/examples/daemon-multi-session.ts +0 -67
- package/examples/droid-dev-structured-output.ts +0 -44
- package/examples/fork-session.ts +0 -68
- package/examples/hook-execution.ts +0 -68
- package/examples/init-metadata.ts +0 -24
- package/examples/list-sessions.ts +0 -19
- package/examples/multi-turn-session.ts +0 -40
- package/examples/permission-handler.ts +0 -41
- package/examples/readme-structured-output.ts +0 -26
- package/examples/result-metadata.ts +0 -63
- package/examples/run.ts +0 -39
- package/examples/session-stream.ts +0 -57
- package/examples/spec-mode-new-session.ts +0 -61
- package/examples/spec-mode-same-session.ts +0 -72
- package/examples/structured-output.ts +0 -60
- package/examples/test-compact.ts +0 -65
- package/examples/test-rewind.ts +0 -87
- package/examples/tool-controls.ts +0 -37
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
# Factory TypeScript SDK
|
|
2
|
+
|
|
3
|
+
The Factory TypeScript SDK lets applications run Droid, continue conversations,
|
|
4
|
+
stream work, control tools, and connect to a running Droid daemon.
|
|
5
|
+
|
|
6
|
+
The public npm package is `@factory/droid-sdk`.
|
|
7
|
+
|
|
8
|
+
This page is the single user-facing SDK guide. The package's exported
|
|
9
|
+
TypeScript declarations remain authoritative for its compile-time API surface.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The SDK has two main runtimes.
|
|
14
|
+
|
|
15
|
+
| Runtime | Import | Use it for |
|
|
16
|
+
| ------- | ------------------------- | --------------------------------------------------------------- |
|
|
17
|
+
| Node | `@factory/droid-sdk/node` | Scripts, CI, local tools, and apps that can start the Droid CLI |
|
|
18
|
+
| Daemon | `@factory/droid-sdk` | Browser or Node apps that connect to an existing Droid daemon |
|
|
19
|
+
|
|
20
|
+
The Node runtime starts a Droid subprocess for you. The daemon runtime connects
|
|
21
|
+
to a daemon over WebSocket and can manage several sessions through one
|
|
22
|
+
connection.
|
|
23
|
+
|
|
24
|
+
The root import is browser-safe. The `/node` import adds subprocess,
|
|
25
|
+
filesystem, local session discovery, and SDK MCP tool APIs.
|
|
26
|
+
|
|
27
|
+
## Requirements and installation
|
|
28
|
+
|
|
29
|
+
Install the package:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @factory/droid-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The SDK requires Node.js 18 or later.
|
|
36
|
+
|
|
37
|
+
By default, Node session APIs start the `droid` CLI from `PATH`. You can provide
|
|
38
|
+
a different executable with `execPath`. Supplying a custom `transport` bypasses
|
|
39
|
+
subprocess creation and this requirement.
|
|
40
|
+
|
|
41
|
+
Set an API key:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export FACTORY_API_KEY="your-key"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also pass `apiKey` directly when creating a Node session.
|
|
48
|
+
|
|
49
|
+
### Browser security
|
|
50
|
+
|
|
51
|
+
The daemon client currently authenticates with a Factory API key. Use browser
|
|
52
|
+
examples only in trusted local tools. Do not ship a Factory API key in a public
|
|
53
|
+
website, commit it to source control, or put it in a URL.
|
|
54
|
+
|
|
55
|
+
An HTTPS page must connect with `wss://`. Local pages served over HTTP can use
|
|
56
|
+
`ws://`.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
### Run one prompt
|
|
61
|
+
|
|
62
|
+
**Runtime: Node**
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { run } from '@factory/droid-sdk/node';
|
|
66
|
+
|
|
67
|
+
const result = await run('Summarize this repository.');
|
|
68
|
+
|
|
69
|
+
if (!result.success) {
|
|
70
|
+
throw new Error(result.error?.message ?? 'The run did not complete.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(result.text);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`run()` creates a session, waits for its final result, and closes the session.
|
|
77
|
+
Use it for one-shot work.
|
|
78
|
+
|
|
79
|
+
### Continue a conversation
|
|
80
|
+
|
|
81
|
+
**Runtime: Node**
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
85
|
+
|
|
86
|
+
const session = await createSession();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
for await (const message of session.stream('What does this project do?')) {
|
|
90
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
91
|
+
console.log(message.text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for await (const message of session.stream('What should I test first?')) {
|
|
96
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
97
|
+
console.log(message.text);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
await session.close();
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use a session when later prompts need context from earlier turns.
|
|
106
|
+
|
|
107
|
+
### Connect to a daemon
|
|
108
|
+
|
|
109
|
+
**Runtime: Node or trusted local browser**
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { connectToDaemon, DroidMessageType } from '@factory/droid-sdk';
|
|
113
|
+
|
|
114
|
+
async function runWithDaemon(apiKey: string, cwd: string) {
|
|
115
|
+
const droid = await connectToDaemon({
|
|
116
|
+
url: 'ws://127.0.0.1:37643',
|
|
117
|
+
auth: { apiKey },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const session = await droid.sessions.create({ cwd });
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
for await (const message of session.stream('Summarize this repository.')) {
|
|
124
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
125
|
+
console.log(message.text);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
await Promise.allSettled([session.close()]);
|
|
130
|
+
droid.disconnect();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
In browser code, obtain both arguments from trusted local configuration. This
|
|
136
|
+
does not make an API key secret from browser JavaScript, so keep this pattern
|
|
137
|
+
restricted to trusted local pages.
|
|
138
|
+
|
|
139
|
+
## Core concepts
|
|
140
|
+
|
|
141
|
+
### Session
|
|
142
|
+
|
|
143
|
+
A session holds conversation history, settings, and a working directory.
|
|
144
|
+
|
|
145
|
+
Node sessions are created with `createSession()` or loaded with
|
|
146
|
+
`resumeSession()`. Daemon sessions are created or resumed through
|
|
147
|
+
`droid.sessions`.
|
|
148
|
+
|
|
149
|
+
### Turn
|
|
150
|
+
|
|
151
|
+
A turn starts when you send one prompt with `session.stream()`. A session can
|
|
152
|
+
run one turn at a time. Different daemon sessions can run turns concurrently.
|
|
153
|
+
|
|
154
|
+
### Stream message
|
|
155
|
+
|
|
156
|
+
`session.stream()` yields a discriminated message union. The default stream
|
|
157
|
+
contains complete user, assistant, tool, hook, error, and result messages.
|
|
158
|
+
|
|
159
|
+
### Result
|
|
160
|
+
|
|
161
|
+
Every normally completed stream ends with a result message. A result reports
|
|
162
|
+
success, interruption, or failure and includes final text, messages, duration,
|
|
163
|
+
turn count, and token usage when available.
|
|
164
|
+
|
|
165
|
+
### Node and daemon lifecycle behavior
|
|
166
|
+
|
|
167
|
+
Node and daemon replacement operations intentionally behave differently.
|
|
168
|
+
|
|
169
|
+
| Behavior | Node session | Daemon session |
|
|
170
|
+
| ------------------------------- | ---------------------------------------------------------- | ------------------------------ |
|
|
171
|
+
| Successor response | `fork`: handle; `compact`/`rewind`: outcome with `session` | Outcome with `newSessionId` |
|
|
172
|
+
| Source handle after replacement | Retired | Still usable |
|
|
173
|
+
| Load successor | Automatic | Call `droid.sessions.resume()` |
|
|
174
|
+
|
|
175
|
+
See [Session lifecycle](#session-lifecycle) for examples.
|
|
176
|
+
|
|
177
|
+
## One-shot runs
|
|
178
|
+
|
|
179
|
+
**Runtime: Node**
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
function run(prompt: string, options?: RunOptions): Promise<DroidResult>;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`run()` supports the same creation and message options as a Node session,
|
|
186
|
+
except partial stream events.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const result = await run('Review the authentication middleware.', {
|
|
190
|
+
cwd: '/path/to/repository',
|
|
191
|
+
modelId: 'model-id',
|
|
192
|
+
reasoningEffort: ReasoningEffort.High,
|
|
193
|
+
disabledToolIds: ['Execute'],
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Common options:
|
|
198
|
+
|
|
199
|
+
| Option | Purpose |
|
|
200
|
+
| ------------------------------ | ---------------------------------------------- |
|
|
201
|
+
| `cwd` | Working directory. Defaults to `process.cwd()` |
|
|
202
|
+
| `apiKey` | Factory API key. Defaults to `FACTORY_API_KEY` |
|
|
203
|
+
| `modelId` | Model selection |
|
|
204
|
+
| `reasoningEffort` | Model reasoning level |
|
|
205
|
+
| `autonomyLevel` | Permission policy level |
|
|
206
|
+
| `interactionMode` | Auto, spec, or mission mode. AGI is deprecated |
|
|
207
|
+
| `specModeModelId` | Model used in spec mode |
|
|
208
|
+
| `specModeReasoningEffort` | Reasoning level used in spec mode |
|
|
209
|
+
| `disabledToolIds` | Tools that Droid cannot use |
|
|
210
|
+
| `autoRejectPermissionRequests` | Reject all permission requests automatically |
|
|
211
|
+
| `disableBuiltinSkills` | Hide Factory-provided built-in skills |
|
|
212
|
+
| `permissionHandler` | Handles permission requests |
|
|
213
|
+
| `askUserHandler` | Answers AskUser requests |
|
|
214
|
+
| `tags` | Session attribution tags |
|
|
215
|
+
| `sessionSource` | Session source attribution |
|
|
216
|
+
| `images` | Base64 image attachments |
|
|
217
|
+
| `files` | Text or PDF attachments |
|
|
218
|
+
| `outputFormat` | JSON schema for structured output |
|
|
219
|
+
| `abortSignal` | Cancels the run |
|
|
220
|
+
| `mcpServers` | MCP servers available to the session |
|
|
221
|
+
| `observability` | Logging, metrics, and trace integration |
|
|
222
|
+
| `execPath` | Droid executable path |
|
|
223
|
+
| `execArgs` | Additional Droid process arguments |
|
|
224
|
+
| `env` | Additional Droid process environment variables |
|
|
225
|
+
| `transport` | Custom string-framed transport |
|
|
226
|
+
|
|
227
|
+
## Sessions
|
|
228
|
+
|
|
229
|
+
### Create a Node session
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { createSession } from '@factory/droid-sdk/node';
|
|
233
|
+
|
|
234
|
+
const session = await createSession({
|
|
235
|
+
cwd: process.cwd(),
|
|
236
|
+
modelId: 'model-id',
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
When omitted, `cwd` defaults to `process.cwd()`. Model and reasoning defaults
|
|
241
|
+
come from Droid settings.
|
|
242
|
+
|
|
243
|
+
### Resume a Node session
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { resumeSession } from '@factory/droid-sdk/node';
|
|
247
|
+
|
|
248
|
+
const session = await resumeSession('session-id');
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The saved session owns its working directory and settings. Resume options can
|
|
252
|
+
change handlers, transports, disabled tools, MCP servers, and observability,
|
|
253
|
+
but cannot replace saved model or working-directory settings.
|
|
254
|
+
|
|
255
|
+
### Session state
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
console.log(session.id);
|
|
259
|
+
console.log(session.cwd);
|
|
260
|
+
console.log(session.settings.modelId);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`settings` and `cwd` update when the running session reports changes.
|
|
264
|
+
|
|
265
|
+
### Update settings
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
await session.updateSettings({
|
|
269
|
+
modelId: 'model-id',
|
|
270
|
+
reasoningEffort: ReasoningEffort.High,
|
|
271
|
+
autonomyLevel: AutonomyLevel.Low,
|
|
272
|
+
disabledToolIds: ['Execute'],
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Public settings include model, reasoning, interaction mode, autonomy, spec
|
|
277
|
+
mode settings, tags, mission settings, compaction settings, and disabled tools.
|
|
278
|
+
|
|
279
|
+
### Rename a session
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
await session.rename({ title: 'Authentication review' });
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Discover saved Node sessions
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { listSessions } from '@factory/droid-sdk/node';
|
|
289
|
+
|
|
290
|
+
const sessions = await listSessions({
|
|
291
|
+
cwd: process.cwd(),
|
|
292
|
+
limit: 10,
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`listSessions()` reads local session storage. It does not call the Factory REST
|
|
297
|
+
API or a daemon.
|
|
298
|
+
|
|
299
|
+
### Close a session
|
|
300
|
+
|
|
301
|
+
Call `close()` in `finally` when your code owns a Node session.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const session = await createSession();
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Use the session.
|
|
308
|
+
} finally {
|
|
309
|
+
await session.close();
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Streaming
|
|
314
|
+
|
|
315
|
+
### Complete messages
|
|
316
|
+
|
|
317
|
+
Complete messages are the default.
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
for await (const message of session.stream('Find the failing test.')) {
|
|
321
|
+
switch (message.type) {
|
|
322
|
+
case DroidMessageType.Assistant:
|
|
323
|
+
console.log(message.text);
|
|
324
|
+
break;
|
|
325
|
+
case DroidMessageType.ToolCall:
|
|
326
|
+
console.log(`Tool: ${message.name}`);
|
|
327
|
+
break;
|
|
328
|
+
case DroidMessageType.Result:
|
|
329
|
+
console.log(message.subtype);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Default message types:
|
|
336
|
+
|
|
337
|
+
| Type | Purpose |
|
|
338
|
+
| ------------- | ------------------------------------ |
|
|
339
|
+
| `assistant` | Complete assistant message |
|
|
340
|
+
| `user` | User message recorded by the session |
|
|
341
|
+
| `tool_call` | Complete tool request |
|
|
342
|
+
| `tool_result` | Complete tool result |
|
|
343
|
+
| `hook` | Hook execution |
|
|
344
|
+
| `error` | Runtime error event |
|
|
345
|
+
| `result` | Final turn result |
|
|
346
|
+
|
|
347
|
+
### Partial events
|
|
348
|
+
|
|
349
|
+
Use partial events only when the application needs live text, thinking, tool
|
|
350
|
+
progress, usage, or state updates.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
for await (const event of session.stream('Explain the test failure.', {
|
|
354
|
+
includePartialMessages: true,
|
|
355
|
+
})) {
|
|
356
|
+
if (event.type === DroidMessageType.AssistantTextDelta) {
|
|
357
|
+
process.stdout.write(event.text);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Partial streams can also include thinking deltas, tool-call deltas, tool
|
|
363
|
+
progress, token updates, permission results, settings changes, working-state
|
|
364
|
+
changes, MCP status, and mission events.
|
|
365
|
+
|
|
366
|
+
### Stream concurrency
|
|
367
|
+
|
|
368
|
+
One session handle can have one active stream. Starting another active stream
|
|
369
|
+
throws `ConcurrentStreamError`.
|
|
370
|
+
|
|
371
|
+
Daemon sessions on the same connection may stream concurrently.
|
|
372
|
+
|
|
373
|
+
### Stop consuming a stream
|
|
374
|
+
|
|
375
|
+
Breaking out of a stream early interrupts the active server turn.
|
|
376
|
+
|
|
377
|
+
## Inputs and outputs
|
|
378
|
+
|
|
379
|
+
### Images
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
const result = await run('Describe this image.', {
|
|
383
|
+
images: [
|
|
384
|
+
{
|
|
385
|
+
type: 'base64',
|
|
386
|
+
data: base64Png,
|
|
387
|
+
mediaType: 'image/png',
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Supported image types are JPEG, PNG, GIF, and WebP.
|
|
394
|
+
|
|
395
|
+
### Documents
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
for await (const _message of session.stream('Summarize this document.', {
|
|
399
|
+
files: [
|
|
400
|
+
{
|
|
401
|
+
type: 'text',
|
|
402
|
+
mediaType: 'text/plain',
|
|
403
|
+
data: report,
|
|
404
|
+
name: 'report.txt',
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
})) {
|
|
408
|
+
// Consume messages.
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Document inputs support plain text and base64 PDF data.
|
|
413
|
+
|
|
414
|
+
### Structured output
|
|
415
|
+
|
|
416
|
+
Use structured output when another system needs a predictable result.
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import { OutputFormatType, run } from '@factory/droid-sdk/node';
|
|
420
|
+
|
|
421
|
+
const result = await run('Review this function for correctness issues.', {
|
|
422
|
+
outputFormat: {
|
|
423
|
+
type: OutputFormatType.JsonSchema,
|
|
424
|
+
schema: {
|
|
425
|
+
type: 'object',
|
|
426
|
+
properties: {
|
|
427
|
+
summary: { type: 'string' },
|
|
428
|
+
findings: {
|
|
429
|
+
type: 'array',
|
|
430
|
+
items: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
line: { type: 'number' },
|
|
434
|
+
severity: {
|
|
435
|
+
type: 'string',
|
|
436
|
+
enum: ['low', 'medium', 'high'],
|
|
437
|
+
},
|
|
438
|
+
message: { type: 'string' },
|
|
439
|
+
},
|
|
440
|
+
required: ['line', 'severity', 'message'],
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
required: ['summary', 'findings'],
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (!result.success) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
result.structuredOutputError?.message ??
|
|
452
|
+
result.error?.message ??
|
|
453
|
+
'Structured output failed.'
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
console.log(result.structuredOutput);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`structuredOutput` is typed as `unknown`. Validate or narrow it before passing
|
|
461
|
+
it to another system. A successful result can omit structured output.
|
|
462
|
+
|
|
463
|
+
### Result metadata
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
console.log(result.sessionId);
|
|
467
|
+
console.log(result.durationMs);
|
|
468
|
+
console.log(result.turnCount);
|
|
469
|
+
console.log(result.tokenUsage);
|
|
470
|
+
console.log(result.messages);
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`tokenUsage` is `null` when usage is unavailable.
|
|
474
|
+
|
|
475
|
+
## Cancellation and interruption
|
|
476
|
+
|
|
477
|
+
### Abort one turn
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const controller = new AbortController();
|
|
481
|
+
|
|
482
|
+
setTimeout(() => controller.abort(), 5_000);
|
|
483
|
+
|
|
484
|
+
for await (const message of session.stream('Perform a long review.', {
|
|
485
|
+
abortSignal: controller.signal,
|
|
486
|
+
})) {
|
|
487
|
+
// Handle messages.
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
The stream throws the abort reason after interrupting the active turn.
|
|
492
|
+
|
|
493
|
+
### Interrupt and keep the session
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
await session.interrupt();
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`interrupt()` stops the active turn on the server. The stream continues to its
|
|
500
|
+
terminal result, normally with subtype `interrupted`. The session remains
|
|
501
|
+
usable for later prompts.
|
|
502
|
+
|
|
503
|
+
A session-level abort signal passed to `createSession()` closes the whole
|
|
504
|
+
session when aborted.
|
|
505
|
+
|
|
506
|
+
## Permissions and user input
|
|
507
|
+
|
|
508
|
+
### Autonomy
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
import { AutonomyLevel } from '@factory/droid-sdk/node';
|
|
512
|
+
|
|
513
|
+
const session = await createSession({
|
|
514
|
+
autonomyLevel: AutonomyLevel.Off,
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Levels are `Off`, `Low`, `Medium`, and `High`. When omitted, Droid settings
|
|
519
|
+
choose the default.
|
|
520
|
+
|
|
521
|
+
### Permission handler
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
import {
|
|
525
|
+
ToolConfirmationOutcome,
|
|
526
|
+
ToolConfirmationType,
|
|
527
|
+
} from '@factory/droid-sdk/node';
|
|
528
|
+
|
|
529
|
+
const session = await createSession({
|
|
530
|
+
autonomyLevel: AutonomyLevel.Off,
|
|
531
|
+
permissionHandler(request) {
|
|
532
|
+
const safe =
|
|
533
|
+
request.toolUses.length > 0 &&
|
|
534
|
+
request.toolUses.every(
|
|
535
|
+
(use) => use.details.type === ToolConfirmationType.Create
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
return safe
|
|
539
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
540
|
+
: ToolConfirmationOutcome.Cancel;
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
The handler must return an outcome supplied by the request. Invalid results and
|
|
546
|
+
handler errors cancel the request.
|
|
547
|
+
|
|
548
|
+
Without a handler, permission requests are cancelled.
|
|
549
|
+
|
|
550
|
+
### AskUser handler
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
const result = await run('Ask me which environment to deploy.', {
|
|
554
|
+
askUserHandler(request) {
|
|
555
|
+
return {
|
|
556
|
+
answers: request.questions.map((question) => ({
|
|
557
|
+
index: question.index,
|
|
558
|
+
question: question.question,
|
|
559
|
+
answer: question.options[0] ?? 'none',
|
|
560
|
+
})),
|
|
561
|
+
};
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
To decline:
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
return { cancelled: true, answers: [] };
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Without a handler, AskUser requests are declined.
|
|
573
|
+
|
|
574
|
+
### Disable tools
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
const session = await createSession({
|
|
578
|
+
disabledToolIds: ['Execute'],
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Tool IDs are strings. Use `listTools()` to inspect the tools available to the
|
|
583
|
+
current session.
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
const tools = await session.listTools();
|
|
587
|
+
|
|
588
|
+
for (const tool of tools) {
|
|
589
|
+
console.log(tool.id, tool.allowed);
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
The public SDK provides subtractive `disabledToolIds`. It does not expose a
|
|
594
|
+
restrictive allowlist.
|
|
595
|
+
|
|
596
|
+
## Tools and extensions
|
|
597
|
+
|
|
598
|
+
### Skills
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
const { skills } = await session.listSkills();
|
|
602
|
+
|
|
603
|
+
await session.setSkillDisabled({
|
|
604
|
+
skillName: 'skill-name',
|
|
605
|
+
disabled: true,
|
|
606
|
+
settingsLevel: 'project',
|
|
607
|
+
});
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Skills may come from project, personal, built-in, or automation settings.
|
|
611
|
+
|
|
612
|
+
### SDK MCP tools
|
|
613
|
+
|
|
614
|
+
**Runtime: Node**
|
|
615
|
+
|
|
616
|
+
Use an SDK MCP server to define an in-process tool with a typed Zod input.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
import { z } from 'zod';
|
|
620
|
+
import {
|
|
621
|
+
createSdkMcpServer,
|
|
622
|
+
createSession,
|
|
623
|
+
tool,
|
|
624
|
+
} from '@factory/droid-sdk/node';
|
|
625
|
+
|
|
626
|
+
const tools = createSdkMcpServer({
|
|
627
|
+
name: 'review-tools',
|
|
628
|
+
tools: [
|
|
629
|
+
tool(
|
|
630
|
+
'lookup_owner',
|
|
631
|
+
'Returns the owner of a file',
|
|
632
|
+
{ path: z.string() },
|
|
633
|
+
({ path }) => `Owner for ${path}: platform-team`
|
|
634
|
+
),
|
|
635
|
+
],
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const session = await createSession({
|
|
639
|
+
mcpServers: [tools],
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
The SDK starts an authenticated loopback MCP server and closes it with the
|
|
644
|
+
session.
|
|
645
|
+
|
|
646
|
+
### External MCP servers
|
|
647
|
+
|
|
648
|
+
Node sessions can add, remove, toggle, list, and authenticate MCP servers.
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
await session.addMcpServer({
|
|
652
|
+
name: 'docs',
|
|
653
|
+
type: 'http',
|
|
654
|
+
url: 'https://example.com/mcp',
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const servers = await session.listMcpServers();
|
|
658
|
+
const tools = await session.listMcpTools();
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
The daemon client exposes the complete MCP management API through `droid.mcp`.
|
|
662
|
+
|
|
663
|
+
### Hooks
|
|
664
|
+
|
|
665
|
+
Hooks are configured in `.factory/hooks.json`. They can run before or after
|
|
666
|
+
tools, when prompts are submitted, when notifications arrive, during
|
|
667
|
+
compaction, and when sessions or subagents stop.
|
|
668
|
+
|
|
669
|
+
Hook events are available in the stream:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
for await (const message of session.stream('Run the tests.')) {
|
|
673
|
+
if (message.type === DroidMessageType.Hook) {
|
|
674
|
+
console.log(message.status, message.command);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
The SDK exposes hook schemas and types, but does not provide a programmatic
|
|
680
|
+
hook-registration API.
|
|
681
|
+
|
|
682
|
+
### Raw notifications
|
|
683
|
+
|
|
684
|
+
**Runtime: Node**
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
const unsubscribe = session.onNotification(
|
|
688
|
+
(notification) => console.log(notification),
|
|
689
|
+
{ type: 'settings_updated' }
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
unsubscribe();
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Use raw notifications only when the typed stream does not expose the event you
|
|
696
|
+
need.
|
|
697
|
+
|
|
698
|
+
## Session lifecycle
|
|
699
|
+
|
|
700
|
+
### Node replacement handles
|
|
701
|
+
|
|
702
|
+
Node `fork()` returns a ready successor handle. `compact()` and `rewind()`
|
|
703
|
+
return outcome objects containing a ready successor in `session`. Each
|
|
704
|
+
operation retires its source wrapper.
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
const forked = await session.fork();
|
|
708
|
+
|
|
709
|
+
const { session: compacted, removedCount } = await forked.compact();
|
|
710
|
+
|
|
711
|
+
const {
|
|
712
|
+
session: rewound,
|
|
713
|
+
restoredCount,
|
|
714
|
+
deletedCount,
|
|
715
|
+
} = await compacted.rewind({
|
|
716
|
+
messageId,
|
|
717
|
+
filesToRestore,
|
|
718
|
+
filesToDelete,
|
|
719
|
+
forkTitle: 'Before the failed change',
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
After a successful replacement, active operations on the source wrapper throw
|
|
724
|
+
`SessionReplacedError`. Its `id`, `settings`, and `cwd` remain readable, and
|
|
725
|
+
`close()` is a no-op. The persisted source session can still be loaded later
|
|
726
|
+
with `resumeSession(sourceId)`.
|
|
727
|
+
|
|
728
|
+
Do not replace a session while it has an active stream.
|
|
729
|
+
|
|
730
|
+
### Daemon replacement IDs
|
|
731
|
+
|
|
732
|
+
Daemon operations return a new session ID. The source handle remains usable.
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
const { newSessionId } = await source.compact();
|
|
736
|
+
const compacted = await droid.sessions.resume(newSessionId);
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
The same rule applies to `fork()` and `rewind()`.
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
const forkResult = await source.fork();
|
|
743
|
+
const fork = await droid.sessions.resume(forkResult.newSessionId);
|
|
744
|
+
|
|
745
|
+
const rewindResult = await source.rewind({
|
|
746
|
+
messageId,
|
|
747
|
+
filesToRestore,
|
|
748
|
+
filesToDelete,
|
|
749
|
+
forkTitle: 'Before the failed change',
|
|
750
|
+
});
|
|
751
|
+
const rewound = await droid.sessions.resume(rewindResult.newSessionId);
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Daemon replacement locking is local to one client handle. Avoid replacing or
|
|
755
|
+
streaming the same session through several daemon clients at once.
|
|
756
|
+
|
|
757
|
+
## Spec mode
|
|
758
|
+
|
|
759
|
+
Create a session in spec mode:
|
|
760
|
+
|
|
761
|
+
```ts
|
|
762
|
+
import {
|
|
763
|
+
createSession,
|
|
764
|
+
DroidInteractionMode,
|
|
765
|
+
ReasoningEffort,
|
|
766
|
+
} from '@factory/droid-sdk/node';
|
|
767
|
+
|
|
768
|
+
const session = await createSession({
|
|
769
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
770
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
771
|
+
});
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Enter spec mode later:
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
await session.enterSpecMode({
|
|
778
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
779
|
+
});
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
When Droid asks to leave spec mode, a permission handler can approve
|
|
783
|
+
implementation in the same session or hand it to a new session.
|
|
784
|
+
|
|
785
|
+
| Outcome | Behavior |
|
|
786
|
+
| ----------------------- | --------------------------------------------------- |
|
|
787
|
+
| `ProceedOnce` | Continue in the same session |
|
|
788
|
+
| `ProceedNewSessionHigh` | Start implementation in a new high-autonomy session |
|
|
789
|
+
|
|
790
|
+
New-session handoff IDs currently require inspecting raw notifications.
|
|
791
|
+
|
|
792
|
+
## Daemon and browser applications
|
|
793
|
+
|
|
794
|
+
### Connect
|
|
795
|
+
|
|
796
|
+
```ts
|
|
797
|
+
const droid = await connectToDaemon({
|
|
798
|
+
url: 'ws://127.0.0.1:37643',
|
|
799
|
+
auth: { apiKey },
|
|
800
|
+
permissionHandler,
|
|
801
|
+
askUserHandler,
|
|
802
|
+
onError(error) {
|
|
803
|
+
console.error(error);
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
Connection-level handlers become defaults for created and resumed sessions.
|
|
809
|
+
Per-session handlers override them.
|
|
810
|
+
|
|
811
|
+
### Create and resume
|
|
812
|
+
|
|
813
|
+
```ts
|
|
814
|
+
const created = await droid.sessions.create({
|
|
815
|
+
cwd: '/path/to/repository',
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const resumed = await droid.sessions.resume('session-id');
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
One `ConnectedDroid` can have one attached handle for each session ID.
|
|
822
|
+
|
|
823
|
+
### List and inspect history
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
const sessions = await droid.sessions.list({ limit: 20 });
|
|
827
|
+
|
|
828
|
+
const messages = await droid.sessions.getMessages(sessions[0].id, {
|
|
829
|
+
limit: 50,
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const matches = await droid.sessions.search({
|
|
833
|
+
query: 'authentication failure',
|
|
834
|
+
});
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
The daemon session resource also supports opened-session listing,
|
|
838
|
+
archive/unarchive, rename, settings, context information, and queued-message
|
|
839
|
+
operations.
|
|
840
|
+
|
|
841
|
+
### Concurrent sessions
|
|
842
|
+
|
|
843
|
+
Different daemon sessions can stream over one connection at the same time.
|
|
844
|
+
|
|
845
|
+
```ts
|
|
846
|
+
await Promise.all([
|
|
847
|
+
consume(first.stream('Review the API.')),
|
|
848
|
+
consume(second.stream('Review the tests.')),
|
|
849
|
+
]);
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### Cleanup
|
|
853
|
+
|
|
854
|
+
| Operation | Daemon session | Local handle | Connection |
|
|
855
|
+
| -------------------- | -------------- | ---------------------- | ---------- |
|
|
856
|
+
| `session.detach()` | Keeps running | Detached | Retained |
|
|
857
|
+
| `session.close()` | Ends | Detached after success | Retained |
|
|
858
|
+
| `droid.disconnect()` | Keeps running | All detached | Destroyed |
|
|
859
|
+
|
|
860
|
+
Detached handles cannot be reused. Resume the session to obtain a new handle.
|
|
861
|
+
|
|
862
|
+
### Stable daemon resources
|
|
863
|
+
|
|
864
|
+
| Namespace | Main methods |
|
|
865
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
866
|
+
| `sessions` | `list`, `listOpened`, `create`, `resume`, `getMessages`, `search`, `archive`, `unarchive`, `rename`, `updateSettings`, `resolveQueuedMessage`, `killWorker`, `getRewindInfo`, `rewind`, `compact`, `fork`, `getContextBreakdown` |
|
|
867
|
+
| `workspace` | `changeDirectory`, `validateDirectory`, `checkTrust`, `trust`, `listFiles`, `searchFiles`, `getFileContent` |
|
|
868
|
+
| `settings` | `getDefaults`, `updateDefaults` |
|
|
869
|
+
| `customModels` | `list`, `upsert`, `delete` |
|
|
870
|
+
| `ssh` | `installKey` |
|
|
871
|
+
| `updates` | `trigger` |
|
|
872
|
+
| `relay` | `start`, `stop`, `status` |
|
|
873
|
+
| `terminals` | `create`, `write`, `resize`, `close`, `list` |
|
|
874
|
+
| `mcp` | `getConfig`, `updateConfig`, `addServer`, `removeServer`, `toggleServer`, authentication, registry, and tool methods |
|
|
875
|
+
| `skills` | `list`, `setDisabled` |
|
|
876
|
+
| `commands` | `list` |
|
|
877
|
+
| `plugins` | `listAvailable`, `listInstalled`, `install`, `uninstall`, `setEnabled`, `update` |
|
|
878
|
+
| `marketplaces` | `list`, `add`, `remove`, `update` |
|
|
879
|
+
| `automations` | `list`, `run`, `pause`, `resume`, history, visual, create, update, rename, delete, fork, and config methods |
|
|
880
|
+
| `git` | `getDiff`, `listBranches`, `checkoutBranch`, `push`, `commit`, `createPullRequest`, `resolvePullRequestStatuses` |
|
|
881
|
+
| `feedback` | `submitBugReport` |
|
|
882
|
+
|
|
883
|
+
### Facade return shapes
|
|
884
|
+
|
|
885
|
+
The namespaced daemon facade normalizes common collection results:
|
|
886
|
+
|
|
887
|
+
- `sessions.list()` returns session summaries with `id`, `messageCount`, and
|
|
888
|
+
`modifiedTime`.
|
|
889
|
+
- `sessions.getMessages()` returns the message array directly.
|
|
890
|
+
- Collection methods such as `customModels.list()`, `terminals.list()`,
|
|
891
|
+
`mcp.listRegistry()`, `mcp.listTools()`, `commands.list()`, and the plugin,
|
|
892
|
+
marketplace, automation, and Software Factory list methods return arrays.
|
|
893
|
+
- Methods whose protocol result includes additional metadata retain the result
|
|
894
|
+
object. This includes `sessions.search()`, `mcp.listServers()`, and
|
|
895
|
+
`skills.list()`.
|
|
896
|
+
|
|
897
|
+
The low-level `DaemonClient` returns canonical protocol response types instead
|
|
898
|
+
of these normalized facade shapes.
|
|
899
|
+
|
|
900
|
+
## Observability
|
|
901
|
+
|
|
902
|
+
**Runtime: Node**
|
|
903
|
+
|
|
904
|
+
```ts
|
|
905
|
+
const session = await createSession({
|
|
906
|
+
observability: {
|
|
907
|
+
logger: {
|
|
908
|
+
log(event) {
|
|
909
|
+
console.log(event.level, event.message);
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
metrics: {
|
|
913
|
+
record(event) {
|
|
914
|
+
console.log(event.name, event.value);
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
tracing: {
|
|
918
|
+
inject(carrier) {
|
|
919
|
+
carrier.traceparent = currentTraceparent;
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
});
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
Observability sinks must not throw into SDK operations. The SDK avoids sending
|
|
927
|
+
prompt text, message content, tool inputs, raw output, and stack traces through
|
|
928
|
+
its observability contracts.
|
|
929
|
+
|
|
930
|
+
## Advanced resources
|
|
931
|
+
|
|
932
|
+
### Low-level daemon integration
|
|
933
|
+
|
|
934
|
+
Most applications should use `connectToDaemon()`. The root entrypoint also
|
|
935
|
+
exports `DaemonClient`, `DaemonSessionController`, `MultiSessionStateManager`,
|
|
936
|
+
`MultiMissionStateManager`, `WebSocketDaemonTransport`, daemon events, and
|
|
937
|
+
canonical protocol types for hosts that need custom connection or state
|
|
938
|
+
ownership.
|
|
939
|
+
|
|
940
|
+
Node-only IPC and in-process transports come from the `/node` entrypoint:
|
|
941
|
+
|
|
942
|
+
```ts
|
|
943
|
+
import { DaemonClient, MachineType } from '@factory/droid-sdk';
|
|
944
|
+
import {
|
|
945
|
+
InProcessDaemonClientTransport,
|
|
946
|
+
type InProcessDaemonClientTransportOptions,
|
|
947
|
+
type InProcessMessageHandler,
|
|
948
|
+
} from '@factory/droid-sdk/node';
|
|
949
|
+
|
|
950
|
+
function createInProcessClient(
|
|
951
|
+
sendMessage: InProcessMessageHandler,
|
|
952
|
+
onMessage: NonNullable<InProcessDaemonClientTransportOptions['onMessage']>
|
|
953
|
+
) {
|
|
954
|
+
const transport = new InProcessDaemonClientTransport({
|
|
955
|
+
sendMessage,
|
|
956
|
+
onMessage,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
return new DaemonClient({
|
|
960
|
+
machineType: MachineType.Local,
|
|
961
|
+
transport,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
The root also exports `WebSocketDaemonTransport`; the `/node` entrypoint exports
|
|
967
|
+
`IpcDaemonClientTransport`.
|
|
968
|
+
|
|
969
|
+
Hosts can inject daemon-core logging, metrics, tracing, and domain helpers once
|
|
970
|
+
at startup:
|
|
971
|
+
|
|
972
|
+
```ts
|
|
973
|
+
import { configureDaemonCoreDeps } from '@factory/droid-sdk';
|
|
974
|
+
|
|
975
|
+
configureDaemonCoreDeps({
|
|
976
|
+
logger: {
|
|
977
|
+
info: (message, metadata) => logger.info(message, metadata),
|
|
978
|
+
error: (message, metadata) => logger.error(message, metadata),
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
Unspecified dependencies keep their standalone defaults.
|
|
984
|
+
|
|
985
|
+
### Low-level Node integration
|
|
986
|
+
|
|
987
|
+
Most Node applications should use `run()` or `DroidSession`. The `/node`
|
|
988
|
+
entrypoint also exports `ProcessTransport` and `DroidClient` for direct
|
|
989
|
+
JSON-RPC integrations.
|
|
990
|
+
|
|
991
|
+
Low-level RPC methods return complete response envelopes. Read successful
|
|
992
|
+
payloads from `response.result`. Direct permission and AskUser handlers receive
|
|
993
|
+
complete request events, while high-level session handlers receive request
|
|
994
|
+
parameters.
|
|
995
|
+
|
|
996
|
+
`ProcessTransport` uses `droidExecPath` and `droidExecExtraArgs` for executable
|
|
997
|
+
configuration. A custom `StringFramedDroidClientTransport` exchanges
|
|
998
|
+
newline-delimited JSON-RPC strings and can replace subprocess transport
|
|
999
|
+
entirely.
|
|
1000
|
+
|
|
1001
|
+
### Context and token usage
|
|
1002
|
+
|
|
1003
|
+
Read the current context-window estimate:
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
const stats = await session.getContextStats();
|
|
1007
|
+
|
|
1008
|
+
console.log(stats.used, stats.remaining, stats.limit, stats.accuracy);
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
Enable partial messages to receive live token updates:
|
|
1012
|
+
|
|
1013
|
+
```ts
|
|
1014
|
+
for await (const message of session.stream('Summarize this repository.', {
|
|
1015
|
+
includePartialMessages: true,
|
|
1016
|
+
})) {
|
|
1017
|
+
if (message.type === DroidMessageType.TokenUsageUpdate) {
|
|
1018
|
+
console.log(message.inputTokens, message.outputTokens);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
The final result's `tokenUsage` contains the last observed totals or `null`.
|
|
1024
|
+
|
|
1025
|
+
### Factory REST API
|
|
1026
|
+
|
|
1027
|
+
The browser-safe root exports REST helpers for Factory computers, machine
|
|
1028
|
+
templates, metrics, dependency installation, and remote-session listing.
|
|
1029
|
+
|
|
1030
|
+
```ts
|
|
1031
|
+
import { listComputers, listRemoteSessions } from '@factory/droid-sdk';
|
|
1032
|
+
|
|
1033
|
+
const options = { apiKey: process.env.FACTORY_API_KEY! };
|
|
1034
|
+
|
|
1035
|
+
const computers = await listComputers(options);
|
|
1036
|
+
const sessions = await listRemoteSessions(options);
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
REST calls use `https://api.factory.ai` by default. Pass `baseUrl` to target a
|
|
1040
|
+
different Factory API host.
|
|
1041
|
+
|
|
1042
|
+
| Resource | Functions |
|
|
1043
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1044
|
+
| Machine templates | `listMachineTemplates`, `getMachineTemplate` |
|
|
1045
|
+
| Computers | `listComputers`, `getComputer`, `getComputerByName`, `createComputer`, `updateComputer`, `deleteComputer`, `restartComputer`, `refreshComputer` |
|
|
1046
|
+
| Computer operations | `getComputerMetrics`, `retryInstallDeps` |
|
|
1047
|
+
| Remote sessions | `listRemoteSessions` |
|
|
1048
|
+
|
|
1049
|
+
### Unstable daemon resources
|
|
1050
|
+
|
|
1051
|
+
APIs under `droid.unstable` may change between SDK releases.
|
|
1052
|
+
|
|
1053
|
+
| Namespace | Main methods or resources |
|
|
1054
|
+
| ----------------- | ------------------------------------------------------------------------- |
|
|
1055
|
+
| `crons` | `list`, `create`, `update`, `delete`, `holdSession`, `resumeSession` |
|
|
1056
|
+
| `missions` | `inspectReadiness`, `acknowledgeReadinessWarning` |
|
|
1057
|
+
| `semanticDiff` | `getCache`, `saveCache`, `generate` |
|
|
1058
|
+
| `fileTransfers` | `push`, `pull` |
|
|
1059
|
+
| `proxy` | `getToken` |
|
|
1060
|
+
| `softwareFactory` | `workstreams`, `signals`, `changes`, `activities`, and `events` resources |
|
|
1061
|
+
|
|
1062
|
+
Keep unstable API use behind an application-owned adapter.
|
|
1063
|
+
|
|
1064
|
+
For example, create and manage a scheduled prompt through the unstable cron
|
|
1065
|
+
resource:
|
|
1066
|
+
|
|
1067
|
+
```ts
|
|
1068
|
+
const cron = await droid.unstable.crons.create({
|
|
1069
|
+
kind: 'root_prompt',
|
|
1070
|
+
scope: { type: 'root' },
|
|
1071
|
+
schedule: { expression: '0 * * * *', recurring: true },
|
|
1072
|
+
payload: {
|
|
1073
|
+
type: 'prompt',
|
|
1074
|
+
prompt: 'Check repository health',
|
|
1075
|
+
target: { type: 'new_session', cwd: '/workspace' },
|
|
1076
|
+
},
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
await droid.unstable.crons.update(cron.id, { status: 'paused' });
|
|
1080
|
+
await droid.unstable.crons.delete(cron.id);
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
## API reference
|
|
1084
|
+
|
|
1085
|
+
### Node functions
|
|
1086
|
+
|
|
1087
|
+
| API | Returns |
|
|
1088
|
+
| ------------------------------------ | ---------------------------- |
|
|
1089
|
+
| `run(prompt, options?)` | `Promise<DroidResult>` |
|
|
1090
|
+
| `createSession(options?)` | `Promise<DroidSession>` |
|
|
1091
|
+
| `resumeSession(sessionId, options?)` | `Promise<DroidSession>` |
|
|
1092
|
+
| `listSessions(options?)` | `Promise<SessionMetadata[]>` |
|
|
1093
|
+
| `createSdkMcpServer(options)` | `SdkMcpServer` |
|
|
1094
|
+
| `tool(...)` | `DroidTool` |
|
|
1095
|
+
|
|
1096
|
+
### `DroidSession`
|
|
1097
|
+
|
|
1098
|
+
| Member | Purpose |
|
|
1099
|
+
| ------------------------- | ------------------------------------- |
|
|
1100
|
+
| `id` | Session ID |
|
|
1101
|
+
| `cwd` | Live working directory |
|
|
1102
|
+
| `settings` | Live read-only settings |
|
|
1103
|
+
| `stream()` | Run one turn |
|
|
1104
|
+
| `interrupt()` | Stop the active turn |
|
|
1105
|
+
| `updateSettings()` | Update session settings |
|
|
1106
|
+
| `enterSpecMode()` | Change to spec mode |
|
|
1107
|
+
| `listTools()` | List normalized tools |
|
|
1108
|
+
| `listSkills()` | List skills |
|
|
1109
|
+
| `setSkillDisabled()` | Enable or disable a skill |
|
|
1110
|
+
| `addMcpServer()` | Add an MCP server |
|
|
1111
|
+
| `removeMcpServer()` | Remove an MCP server |
|
|
1112
|
+
| `toggleMcpServer()` | Enable or disable an MCP server |
|
|
1113
|
+
| `listMcpServers()` | List MCP servers |
|
|
1114
|
+
| `listMcpTools()` | List MCP tools |
|
|
1115
|
+
| `authenticateMcpServer()` | Start MCP authentication |
|
|
1116
|
+
| `getContextStats()` | Read context usage |
|
|
1117
|
+
| `getRewindInfo()` | Inspect rewindable file changes |
|
|
1118
|
+
| `rewind()` | Create a rewound successor |
|
|
1119
|
+
| `compact()` | Create a compacted successor |
|
|
1120
|
+
| `fork()` | Create a forked successor |
|
|
1121
|
+
| `rename()` | Change the session title |
|
|
1122
|
+
| `onNotification()` | Subscribe to raw notifications |
|
|
1123
|
+
| `close()` | Close the session and owned resources |
|
|
1124
|
+
|
|
1125
|
+
### Result states
|
|
1126
|
+
|
|
1127
|
+
| Subtype | `success` | `interrupted` | Meaning |
|
|
1128
|
+
| ------------------------- | --------- | ------------- | ------------------------- |
|
|
1129
|
+
| `success` | `true` | `false` | Turn completed |
|
|
1130
|
+
| `interrupted` | `false` | `true` | Turn was stopped |
|
|
1131
|
+
| `error_during_execution` | `false` | `false` | Runtime failure |
|
|
1132
|
+
| `error_structured_output` | `false` | `false` | Structured-output failure |
|
|
1133
|
+
|
|
1134
|
+
Common result fields:
|
|
1135
|
+
|
|
1136
|
+
```ts
|
|
1137
|
+
{
|
|
1138
|
+
type: 'result';
|
|
1139
|
+
subtype:
|
|
1140
|
+
| 'success'
|
|
1141
|
+
| 'interrupted'
|
|
1142
|
+
| 'error_during_execution'
|
|
1143
|
+
| 'error_structured_output';
|
|
1144
|
+
sessionId: string;
|
|
1145
|
+
durationMs: number;
|
|
1146
|
+
tokenUsage: TokenUsage | null;
|
|
1147
|
+
messages: DroidStreamEvent[];
|
|
1148
|
+
text: string;
|
|
1149
|
+
turnCount: number;
|
|
1150
|
+
success: boolean;
|
|
1151
|
+
interrupted: boolean;
|
|
1152
|
+
structuredOutput?: unknown;
|
|
1153
|
+
structuredOutputError?: StructuredOutputError | null;
|
|
1154
|
+
error: ErrorEvent | null;
|
|
1155
|
+
}
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
### Main enums
|
|
1159
|
+
|
|
1160
|
+
| Enum | Values |
|
|
1161
|
+
| ---------------------- | -------------------------------------------------------------------------------- |
|
|
1162
|
+
| `AutonomyLevel` | `Off`, `Low`, `Medium`, `High` |
|
|
1163
|
+
| `DroidInteractionMode` | `Auto`, `Spec`, `Mission`; `AGI` is deprecated |
|
|
1164
|
+
| `ReasoningEffort` | `None`, `Dynamic`, `Off`, `Minimal`, `Low`, `Medium`, `High`, `ExtraHigh`, `Max` |
|
|
1165
|
+
| `OutputFormatType` | `JsonSchema` |
|
|
1166
|
+
|
|
1167
|
+
Model IDs and tool IDs are strings. Discover available models and tools at
|
|
1168
|
+
runtime instead of treating them as closed enums.
|
|
1169
|
+
|
|
1170
|
+
### Main errors
|
|
1171
|
+
|
|
1172
|
+
Universal errors:
|
|
1173
|
+
|
|
1174
|
+
| Error | Meaning |
|
|
1175
|
+
| -------------------------- | --------------------------------------------- |
|
|
1176
|
+
| `ConcurrentStreamError` | A session handle already has an active stream |
|
|
1177
|
+
| `SessionReplacedError` | A Node source wrapper was replaced |
|
|
1178
|
+
| `SessionReplacementError` | Successor loading or rollback failed |
|
|
1179
|
+
| `AbortError` | A daemon operation was aborted |
|
|
1180
|
+
| `ConnectionClosedError` | Daemon connection closed |
|
|
1181
|
+
| `JsonRpcRequestError` | Daemon JSON-RPC request failed |
|
|
1182
|
+
| `RelayConnectionError` | Relay connection failed |
|
|
1183
|
+
| `WebSocketConnectionError` | WebSocket connection failed |
|
|
1184
|
+
|
|
1185
|
+
Node-only errors:
|
|
1186
|
+
|
|
1187
|
+
| Error | Meaning |
|
|
1188
|
+
| ------------------------ | -------------------------------------------- |
|
|
1189
|
+
| `DroidClientError` | Base Node client error |
|
|
1190
|
+
| `ConnectionError` | Droid process or transport connection failed |
|
|
1191
|
+
| `TimeoutError` | A request timed out |
|
|
1192
|
+
| `ProtocolError` | Protocol or API response failed |
|
|
1193
|
+
| `SessionError` | Base session error |
|
|
1194
|
+
| `SessionNotFoundError` | Saved session was not found |
|
|
1195
|
+
| `InvalidSessionCwdError` | Saved working directory is invalid |
|
|
1196
|
+
| `ProcessExitError` | Droid subprocess exited unexpectedly |
|
|
1197
|
+
|
|
1198
|
+
### Custom transports
|
|
1199
|
+
|
|
1200
|
+
**Runtime: Node, advanced**
|
|
1201
|
+
|
|
1202
|
+
```ts
|
|
1203
|
+
interface StringFramedDroidClientTransport {
|
|
1204
|
+
isConnected: boolean;
|
|
1205
|
+
send(message: string): Promise<void>;
|
|
1206
|
+
onMessage(handler: (message: string) => void): void;
|
|
1207
|
+
onError(handler: (error: Error) => void): void;
|
|
1208
|
+
close(): Promise<void>;
|
|
1209
|
+
}
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
Pass a ready transport through `run()`, `createSession()`, or
|
|
1213
|
+
`resumeSession()`. A custom transport bypasses subprocess creation and the
|
|
1214
|
+
Node API-key requirement.
|
|
1215
|
+
|
|
1216
|
+
## Errors and limitations
|
|
1217
|
+
|
|
1218
|
+
### Handle results explicitly
|
|
1219
|
+
|
|
1220
|
+
```ts
|
|
1221
|
+
const result = await run('Run the test suite.');
|
|
1222
|
+
|
|
1223
|
+
switch (result.subtype) {
|
|
1224
|
+
case 'success':
|
|
1225
|
+
console.log(result.text);
|
|
1226
|
+
break;
|
|
1227
|
+
case 'interrupted':
|
|
1228
|
+
console.log('The run was interrupted.');
|
|
1229
|
+
break;
|
|
1230
|
+
case 'error_during_execution':
|
|
1231
|
+
case 'error_structured_output':
|
|
1232
|
+
console.error(
|
|
1233
|
+
result.structuredOutputError?.message ??
|
|
1234
|
+
result.error?.message ??
|
|
1235
|
+
'Unknown error'
|
|
1236
|
+
);
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
### Known limitations
|
|
1242
|
+
|
|
1243
|
+
- Daemon replacement locking does not coordinate separate clients.
|
|
1244
|
+
- Browser daemon authentication currently requires an API key.
|
|
1245
|
+
- New-session spec handoff IDs require raw notification inspection.
|
|
1246
|
+
- REST remote-session support currently provides listing only.
|
|
1247
|
+
|
|
1248
|
+
## Runnable examples
|
|
1249
|
+
|
|
1250
|
+
The package includes runnable examples for one-shot runs, streaming, sessions,
|
|
1251
|
+
permissions, AskUser, attachments, structured output, MCP tools, hooks,
|
|
1252
|
+
observability, spec mode, replacement lifecycle, daemon sessions, concurrent
|
|
1253
|
+
browser sessions, and daemon lifecycle operations.
|
|
1254
|
+
|
|
1255
|
+
All examples are under:
|
|
1256
|
+
|
|
1257
|
+
```text
|
|
1258
|
+
examples/node/
|
|
1259
|
+
examples/browser/
|
|
1260
|
+
```
|