Skip to content

Commit 1c1a37b

Browse files
feat: daemon SDK MVP — connectDaemon, DaemonConnection, DaemonSession (#48)
* feat: daemon SDK MVP — connectDaemon, DaemonConnection, DaemonSession Adds daemon mode as a sibling to the existing exec-based API. The daemon SDK connects to a running droid daemon over WebSocket and supports both interactive streaming and fire-and-forget headless delegation. New public API: - connectDaemon() — resolve SDKMachineConfig to WebSocket URL, authenticate - DaemonConnection — createSession, resumeSession, interruptSession, close - DaemonSession — stream() for interactive use, send() for fire-and-forget - WebSocketTransport — DroidClientTransport over WebSocket (ws package) - MachineType enum, SDKMachineConfig type Key design: - SharedTransportMultiplexer broadcasts messages to multiple DroidClient instances sharing one WebSocket connection - Same DroidStreamEvent/MessageBridge/StreamStateTracker stack as exec mode - URL resolution: MachineType.Ephemeral → e2b sandbox, Computer → relay Includes API design doc (docs/daemon-sdk-api-design.md) with consumer migration breakdown for Slack, Linear, automations, and REST API. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: daemon protocol compatibility — method prefix remapping, session routing, token injection Three critical fixes to make the daemon SDK work with the actual daemon protocol: 1. Method prefix remapping: ProtocolEngine now supports a methodPrefix option that remaps droid.* to daemon.* on outgoing requests and daemon.* to droid.* on incoming messages. DroidClient passes this through to ProtocolEngine. 2. Session-aware multiplexer: SharedTransportMultiplexer now routes responses by matching request IDs, and notifications/server-requests by sessionId. This enables correct concurrent multi-session support. 3. Token/sessionId injection: DaemonConnection injects the auth token into initialize_session and load_session params (daemon requires it). DroidClient injects sessionId into all session-scoped requests when in daemon mode. Verified against live daemon with 16/16 stress tests passing: - Basic connect + auth, create session + stream, multi-turn context, interrupt, concurrent sessions, error handling, notifications. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * chore: update daemon exports, protocol version, and test fixtures - Export ensureLocalDaemon and resolveLocalAuthToken from daemon barrel - Update FACTORY_PROTOCOL_VERSION test to match 1.51.0 - Add local daemon URL resolution tests - Add local daemon export tests Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * refactor: replace methodPrefix hack with standalone DaemonClient Replace the brittle methodPrefix string remapping in ProtocolEngine/DroidClient with a standalone DaemonClient class that sends daemon.* methods natively. - New src/daemon/client.ts: DaemonClient sends daemon.initialize_session, daemon.add_user_message, etc. directly. Includes sessionId in all session-scoped params and token in init/load params by design — no Object.assign hacks or conditional injection. - ProtocolEngine: Replace methodPrefix with serverRequestMethodMap — a Record<string, 'permission' | 'askUser'> that maps incoming server request methods to handler types. Defaults to droid.* for exec mode. DaemonClient passes daemon.* entries. No more method string remapping. - DroidClient: Reverted to its pre-hack state. No methodPrefix, no conditional sessionId injection. Exec mode is completely unaffected. - DaemonSession: Type changed from DroidClient to DaemonClient. Body unchanged since both expose the same method signatures. - DaemonConnection: Creates DaemonClient instead of DroidClient. Token passed via constructor, not Object.assign. Handler setup done inline. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: remap daemon notification method prefix for stream compatibility The daemon sends notifications with method 'daemon.session_notification' but MessageBridge/StreamStateTracker validate against the exec-mode SessionNotificationSchema which expects 'droid.session_notification'. This caused stream() to silently drop all notifications and hang forever. Fix: DaemonClient normalizes the method prefix before dispatching to notification listeners. Also adds comprehensive daemon SDK tests: - client.test.ts: 27 tests for DaemonClient (RPC, handlers, lifecycle) - multiplexer.test.ts: 6 tests for SharedTransportMultiplexer routing - session-advanced.test.ts: 16 tests (abort, partial, multi-turn, concurrent) - connection-lifecycle.test.ts: 18 tests (create/resume/interrupt/close) - authenticate.test.ts: 3 tests for auth envelope format - stress-test.ts: 16-point live daemon end-to-end stress test - diagnostic.ts: raw notification inspection utility Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat: add local daemon spawn, credential resolution, docs, and enum fixes * fix: daemon discovery — probe well-known ports, cache target, deduplicate concurrent calls Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: resolve lint, typecheck, and formatting issues in daemon test files Fix import ordering, unused variables, TypeScript union type narrowing, and apply prettier formatting across daemon test and source files. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: resolve FACTORY_DROID_BINARY via PATH and detach spawned daemon - resolveExecPath: bare command names (e.g. droid-dev) are returned as-is and resolved by spawn() via PATH. Absolute/relative paths are still validated with fs.accessSync before use. - detached: true so the daemon gets its own session (setsid) and won't be killed by the parent's terminal signals (SIGINT/SIGHUP). - child.unref() so the SDK consumer's Node process can exit without waiting for the long-lived daemon. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * chore: remove redundant ad-hoc MCP test scripts These 11 _test-* files were iterative debugging scripts created during MCP development. All scenarios are now covered by the comprehensive stress-test-suite.ts (43 tests across 8 groups). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * chore: clean up junk files, add stress test suite and daemon example Delete: - docs/daemon-sdk-api-design.md (superseded by implementation + usage guide) - tests/daemon/debug-stream.ts (ad-hoc debug script) - tests/daemon/diagnostic.ts (ad-hoc diagnostic script) - tests/daemon/stress-test.ts (superseded by stress-test-suite.ts) Add: - tests/daemon/stress-test-suite.ts (43 tests across 8 groups: exec core, exec lifecycle, exec settings, daemon core, daemon lifecycle, daemon concurrency, error handling, MCP edge cases) - examples/daemon-multi-session.ts (concurrent sessions over WebSocket) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: resolve lint, typecheck, and formatting issues in stress test suite Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs: cross-link exec and daemon usage guides Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: plug MCP server leak, forward sessionSource, remove dead title field - DaemonSession: add cleanup callback mechanism matching exec-mode DroidSession. Wire sdkMcpServers.cleanup on the success path of createSession/resumeSession so SDK-started MCP HTTP servers are stopped when the session closes. - buildInitParams: forward sessionSource to InitializeSessionRequestParams. Previously the field was accepted by the Zod schema but silently dropped by the manual object construction in helpers.ts. - DaemonSessionOptions: remove title (not in init schema, no renameSession in DaemonClient) and tighten sessionSource type from Record<string, unknown> to SessionSource. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat!: require explicit apiKey for all SDK entry points BREAKING CHANGE: apiKey is now a required parameter on run(), createSession(), resumeSession(), and connectDaemon(). The SDK no longer auto-resolves credentials from env vars or stored login. - Add apiKey to SessionInitOptions, TransportCreationOptions, ResumeSessionOptions, and ConnectDaemonOptions - Exec mode injects apiKey into subprocess env as FACTORY_API_KEY - Remove token field from ConnectDaemonOptions and DaemonClientOptions - Remove resolveLocalAuthToken and all credential file code (~190 lines) - Remove WorkOS token refresh, AES-256-GCM encryption, JWT expiry checks - Update all examples, tests, and docs Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: send apiKey as 'token' on daemon session init/load wire protocol The daemon server schema requires the credential field to be named 'token' in initialize_session and load_session RPC params. The SDK stores it internally as _apiKey but must send it as 'token' on the wire. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs: fix code snippet accuracy in usage guides - Add apiKey and sessionSource to CreateSessionOptions table - Pass FACTORY_API_KEY env to ProcessTransport in Rewind example - Add non-null assertions to 3 apiKey references in daemon guide - Add type annotations to collectResult in concurrent sessions example Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * refactor: address PR review feedback — remove dev mode, scope SDK files, add enums, type envelope - Remove dev-mode branching: always use production port (37643) and ~/.factory/ directory. Delete DEFAULT_DEV_DAEMON_PORT export. - Scope SDK-written files under ~/.factory/sdk/ (port file, logs) to avoid collisions with CLI artifacts. - Create ServerRequestHandlerType enum to replace 'permission'/'askUser' string literals in protocol and daemon client. - Type authenticate() envelope as JsonRpcRequest instead of Record<string, unknown>. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * refactor: extract shared streamFromClient() to deduplicate stream() DroidSession.stream() and DaemonSession.stream() had ~45 lines of identical async generator logic. Extract into a single streamFromClient() utility in helpers.ts. Both session classes now delegate via yield*. Also consolidates duplicated throwIfAborted/getAbortError into a single export and moves MessageOptions to helpers.ts to avoid circular imports. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent c2c92f9 commit 1c1a37b

56 files changed

Lines changed: 7372 additions & 205 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/daemon-usage-guide.md

Lines changed: 544 additions & 0 deletions
Large diffs are not rendered by default.

docs/sdk-usage-guide.md

Lines changed: 103 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
npm install @factory/droid-sdk
77
```
88

9-
Requires Node.js 18+ and the `droid` CLI on your PATH.
9+
Requires Node.js 18+ and the `droid` CLI on your PATH. This guide covers **exec mode** (`run()`, `createSession()`), which spawns a subprocess per session. For WebSocket-based daemon mode with concurrent sessions, see the [Daemon Usage Guide](./daemon-usage-guide.md).
1010

1111
```ts
1212
import { run } from '@factory/droid-sdk';
1313

1414
const result = await run('What files are in this directory?', {
15+
apiKey: process.env.FACTORY_API_KEY!,
1516
cwd: process.cwd(),
1617
});
1718
console.log(result.text);
@@ -26,7 +27,10 @@ Send a prompt, get a result, done. The session is created and closed automatical
2627
```ts
2728
import { run } from '@factory/droid-sdk';
2829

29-
const result = await run('What is 2 + 2?', { cwd: process.cwd() });
30+
const result = await run('What is 2 + 2?', {
31+
apiKey: process.env.FACTORY_API_KEY!,
32+
cwd: process.cwd(),
33+
});
3034
console.log(result.text);
3135
```
3236

@@ -38,6 +42,7 @@ Force the response to match a JSON schema. The validated object is available on
3842
import { OutputFormatType, run } from '@factory/droid-sdk';
3943

4044
const result = await run('Pick a number between 1 and 42.', {
45+
apiKey: process.env.FACTORY_API_KEY!,
4146
cwd: process.cwd(),
4247
outputFormat: {
4348
type: OutputFormatType.JsonSchema,
@@ -57,16 +62,19 @@ console.log((result.structuredOutput as { number: number }).number);
5762
Create a session once, then call `stream()` multiple times. Context is preserved across turns.
5863

5964
```ts
60-
import { createSession } from '@factory/droid-sdk';
65+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
6166

62-
const session = await createSession({ cwd: process.cwd() });
67+
const session = await createSession({
68+
apiKey: process.env.FACTORY_API_KEY!,
69+
cwd: process.cwd(),
70+
});
6371

6472
for await (const msg of session.stream('Remember the word "mango".')) {
6573
// consume first turn
6674
}
6775

6876
for await (const msg of session.stream('What word did I say?')) {
69-
if (msg.type === 'assistant') console.log(msg.text);
77+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
7078
}
7179

7280
await session.close();
@@ -77,12 +85,14 @@ await session.close();
7785
Reconnect to a previously created session by its ID.
7886

7987
```ts
80-
import { resumeSession } from '@factory/droid-sdk';
88+
import { resumeSession, DroidMessageType } from '@factory/droid-sdk';
8189

82-
const session = await resumeSession('existing-session-id');
90+
const session = await resumeSession('existing-session-id', {
91+
apiKey: process.env.FACTORY_API_KEY!,
92+
});
8393

8494
for await (const msg of session.stream('Continue where we left off.')) {
85-
if (msg.type === 'assistant') console.log(msg.text);
95+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
8696
}
8797

8898
await session.close();
@@ -95,7 +105,10 @@ await session.close();
95105
```ts
96106
import { createSession, DroidMessageType } from '@factory/droid-sdk';
97107

98-
const session = await createSession({ cwd: process.cwd() });
108+
const session = await createSession({
109+
apiKey: process.env.FACTORY_API_KEY!,
110+
cwd: process.cwd(),
111+
});
99112

100113
for await (const msg of session.stream(
101114
'List files in the current directory.'
@@ -126,7 +139,10 @@ Enable `includePartialMessages` to get token-by-token deltas, thinking blocks, a
126139
```ts
127140
import { createSession, DroidMessageType } from '@factory/droid-sdk';
128141

129-
const session = await createSession({ cwd: process.cwd() });
142+
const session = await createSession({
143+
apiKey: process.env.FACTORY_API_KEY!,
144+
cwd: process.cwd(),
145+
});
130146

131147
for await (const msg of session.stream('Explain recursion.', {
132148
includePartialMessages: true,
@@ -144,19 +160,25 @@ await session.close();
144160
Use `session.interrupt()` to stop the current turn server-side, or pass an `AbortSignal` to cancel from the client.
145161

146162
```ts
147-
import { createSession } from '@factory/droid-sdk';
163+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
148164

149165
// Interrupt after receiving some output
150-
const session = await createSession({ cwd: process.cwd() });
166+
const session = await createSession({
167+
apiKey: process.env.FACTORY_API_KEY!,
168+
cwd: process.cwd(),
169+
});
151170
for await (const msg of session.stream('Write a long essay.')) {
152-
if (msg.type === 'assistant') {
171+
if (msg.type === DroidMessageType.Assistant) {
153172
await session.interrupt();
154173
}
155174
}
156175
await session.close();
157176

158177
// Or cancel with AbortSignal
159-
const session2 = await createSession({ cwd: process.cwd() });
178+
const session2 = await createSession({
179+
apiKey: process.env.FACTORY_API_KEY!,
180+
cwd: process.cwd(),
181+
});
160182
const controller = new AbortController();
161183
setTimeout(() => controller.abort(), 2000);
162184

@@ -179,6 +201,7 @@ Define custom tools that Droid can call during a session. Tools are served via a
179201
import {
180202
createSession,
181203
createSdkMcpServer,
204+
DroidMessageType,
182205
tool,
183206
ToolConfirmationOutcome,
184207
} from '@factory/droid-sdk';
@@ -199,13 +222,14 @@ const server = createSdkMcpServer({
199222
});
200223

201224
const session = await createSession({
225+
apiKey: process.env.FACTORY_API_KEY!,
202226
cwd: process.cwd(),
203227
mcpServers: [server],
204228
permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
205229
});
206230

207231
for await (const msg of session.stream('Look up Alice.')) {
208-
if (msg.type === 'assistant') console.log(msg.text);
232+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
209233
}
210234

211235
await session.close();
@@ -219,6 +243,7 @@ Control what Droid can do without asking for permission. Set at session creation
219243
import { createSession, AutonomyLevel } from '@factory/droid-sdk';
220244

221245
const session = await createSession({
246+
apiKey: process.env.FACTORY_API_KEY!,
222247
cwd: process.cwd(),
223248
autonomyLevel: AutonomyLevel.High, // Off | Low | Medium | High
224249
});
@@ -236,6 +261,7 @@ Restrict which tools Droid can use. Accepts tool IDs like `'Read'`, `'Execute'`,
236261
import { createSession } from '@factory/droid-sdk';
237262

238263
const session = await createSession({
264+
apiKey: process.env.FACTORY_API_KEY!,
239265
cwd: process.cwd(),
240266
enabledToolIds: ['Read', 'Grep'],
241267
disabledToolIds: ['Execute'],
@@ -258,6 +284,7 @@ import {
258284
} from '@factory/droid-sdk';
259285

260286
await run('Create hello.txt with "Hello, World!"', {
287+
apiKey: process.env.FACTORY_API_KEY!,
261288
cwd: process.cwd(),
262289
permissionHandler(params) {
263290
const safe = params.toolUses.every(
@@ -283,6 +310,7 @@ import {
283310
} from '@factory/droid-sdk';
284311

285312
const session = await createSession({
313+
apiKey: process.env.FACTORY_API_KEY!,
286314
cwd: process.cwd(),
287315
interactionMode: DroidInteractionMode.Spec,
288316
permissionHandler(params) {
@@ -306,9 +334,12 @@ Send images or documents alongside your prompt. Images must be base64-encoded.
306334

307335
```ts
308336
import { readFileSync } from 'node:fs';
309-
import { createSession } from '@factory/droid-sdk';
337+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
310338

311-
const session = await createSession({ cwd: process.cwd() });
339+
const session = await createSession({
340+
apiKey: process.env.FACTORY_API_KEY!,
341+
cwd: process.cwd(),
342+
});
312343

313344
for await (const msg of session.stream('Describe this image.', {
314345
images: [
@@ -319,7 +350,7 @@ for await (const msg of session.stream('Describe this image.', {
319350
},
320351
],
321352
})) {
322-
if (msg.type === 'assistant') console.log(msg.text);
353+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
323354
}
324355

325356
await session.close();
@@ -330,17 +361,26 @@ await session.close();
330361
Create a copy of the current session with all context preserved. Useful for branching a conversation.
331362

332363
```ts
333-
import { createSession, resumeSession } from '@factory/droid-sdk';
364+
import {
365+
createSession,
366+
DroidMessageType,
367+
resumeSession,
368+
} from '@factory/droid-sdk';
334369

335-
const session = await createSession({ cwd: process.cwd() });
370+
const session = await createSession({
371+
apiKey: process.env.FACTORY_API_KEY!,
372+
cwd: process.cwd(),
373+
});
336374
for await (const msg of session.stream('Remember: the password is "banana".')) {
337375
}
338376

339377
const { newSessionId } = await session.forkSession();
340-
const fork = await resumeSession(newSessionId);
378+
const fork = await resumeSession(newSessionId, {
379+
apiKey: process.env.FACTORY_API_KEY!,
380+
});
341381

342382
for await (const msg of fork.stream('What is the password?')) {
343-
if (msg.type === 'assistant') console.log(msg.text);
383+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
344384
}
345385

346386
await fork.close();
@@ -354,7 +394,10 @@ Summarize and remove old messages to free up context window space.
354394
```ts
355395
import { createSession } from '@factory/droid-sdk';
356396

357-
const session = await createSession({ cwd: process.cwd() });
397+
const session = await createSession({
398+
apiKey: process.env.FACTORY_API_KEY!,
399+
cwd: process.cwd(),
400+
});
358401

359402
// ... after many turns ...
360403
const result = await session.compactSession();
@@ -376,7 +419,10 @@ import {
376419
AutonomyLevel,
377420
} from '@factory/droid-sdk';
378421

379-
const transport = new ProcessTransport({ cwd: process.cwd() });
422+
const transport = new ProcessTransport({
423+
cwd: process.cwd(),
424+
env: { FACTORY_API_KEY: process.env.FACTORY_API_KEY! },
425+
});
380426
await transport.connect();
381427
const client = new DroidClient({ transport });
382428

@@ -423,6 +469,7 @@ Choose which model to use and how much reasoning effort to apply. Configurable a
423469
import { createSession, ReasoningEffort } from '@factory/droid-sdk';
424470

425471
const session = await createSession({
472+
apiKey: process.env.FACTORY_API_KEY!,
426473
cwd: process.cwd(),
427474
modelId: 'claude-sonnet-4-20250514',
428475
reasoningEffort: ReasoningEffort.High,
@@ -443,7 +490,10 @@ Observe file hooks (pre/post tool execution hooks) as they run during a session.
443490
```ts
444491
import { createSession, DroidMessageType } from '@factory/droid-sdk';
445492

446-
const session = await createSession({ cwd: process.cwd() });
493+
const session = await createSession({
494+
apiKey: process.env.FACTORY_API_KEY!,
495+
cwd: process.cwd(),
496+
});
447497

448498
for await (const msg of session.stream('Create a new file.')) {
449499
if (msg.type === DroidMessageType.Hook) {
@@ -463,9 +513,10 @@ await session.close();
463513
Programmatically answer questions that Droid asks the user during execution.
464514

465515
```ts
466-
import { createSession } from '@factory/droid-sdk';
516+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
467517

468518
const session = await createSession({
519+
apiKey: process.env.FACTORY_API_KEY!,
469520
cwd: process.cwd(),
470521
askUserHandler(params) {
471522
return {
@@ -480,7 +531,7 @@ const session = await createSession({
480531
});
481532

482533
for await (const msg of session.stream('Help me set up this project.')) {
483-
if (msg.type === 'assistant') console.log(msg.text);
534+
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
484535
}
485536

486537
await session.close();
@@ -493,7 +544,10 @@ Add, remove, toggle, and list MCP servers at runtime within an active session.
493544
```ts
494545
import { createSession, McpServerType } from '@factory/droid-sdk';
495546

496-
const session = await createSession({ cwd: process.cwd() });
547+
const session = await createSession({
548+
apiKey: process.env.FACTORY_API_KEY!,
549+
cwd: process.cwd(),
550+
});
497551

498552
await session.addMcpServer({
499553
name: 'my-server',
@@ -514,7 +568,10 @@ Query current context window usage to understand how much capacity remains.
514568
```ts
515569
import { createSession } from '@factory/droid-sdk';
516570

517-
const session = await createSession({ cwd: process.cwd() });
571+
const session = await createSession({
572+
apiKey: process.env.FACTORY_API_KEY!,
573+
cwd: process.cwd(),
574+
});
518575
for await (const msg of session.stream('Hello')) {
519576
}
520577

@@ -533,7 +590,10 @@ Monitor token consumption in real-time via stream events, or read the final tota
533590
```ts
534591
import { createSession, DroidMessageType } from '@factory/droid-sdk';
535592

536-
const session = await createSession({ cwd: process.cwd() });
593+
const session = await createSession({
594+
apiKey: process.env.FACTORY_API_KEY!,
595+
cwd: process.cwd(),
596+
});
537597

538598
for await (const msg of session.stream('Summarize this project.', {
539599
includePartialMessages: true,
@@ -558,7 +618,10 @@ List all available skills in the current session.
558618
```ts
559619
import { createSession } from '@factory/droid-sdk';
560620

561-
const session = await createSession({ cwd: process.cwd() });
621+
const session = await createSession({
622+
apiKey: process.env.FACTORY_API_KEY!,
623+
cwd: process.cwd(),
624+
});
562625
const { skills } = await session.listSkills();
563626

564627
for (const skill of skills) {
@@ -575,7 +638,10 @@ Subscribe to raw protocol notifications for custom event handling beyond the str
575638
```ts
576639
import { createSession, SessionNotificationType } from '@factory/droid-sdk';
577640

578-
const session = await createSession({ cwd: process.cwd() });
641+
const session = await createSession({
642+
apiKey: process.env.FACTORY_API_KEY!,
643+
cwd: process.cwd(),
644+
});
579645

580646
const unsubscribe = session.onNotification(
581647
(notification) => {
@@ -604,7 +670,9 @@ import {
604670
} from '@factory/droid-sdk';
605671

606672
try {
607-
const session = await resumeSession('nonexistent-id');
673+
const session = await resumeSession('nonexistent-id', {
674+
apiKey: process.env.FACTORY_API_KEY!,
675+
});
608676
} catch (error) {
609677
if (error instanceof SessionNotFoundError) {
610678
console.log(`Session not found: ${error.sessionId}`);
@@ -634,6 +702,7 @@ The package also exports its full Zod schema surface from `src/schemas/index.ts`
634702

635703
| Field | Type | Description |
636704
| :------------------------ | :----------------------- | :---------------------------------------------------------- |
705+
| `apiKey` | `string` | **Required.** Factory API key for authentication |
637706
| `cwd` | `string` | Working directory for the session |
638707
| `machineId` | `string` | Machine identifier for initialization |
639708
| `modelId` | `string` | LLM model identifier |
@@ -646,6 +715,7 @@ The package also exports its full Zod schema surface from `src/schemas/index.ts`
646715
| `enabledToolIds` | `string[]` | Tool allowlist |
647716
| `disabledToolIds` | `string[]` | Tool denylist |
648717
| `tags` | `SessionTag[]` | Session tags for categorization |
718+
| `sessionSource` | `SessionSource` | Attribution metadata (e.g., integration origin) |
649719
| `permissionHandler` | `PermissionHandler` | Tool confirmation callback |
650720
| `askUserHandler` | `AskUserHandler` | Structured user-input callback |
651721
| `execPath` | `string` | Path to `droid` executable (default: `"droid"`) |

0 commit comments

Comments
 (0)