Commit 1c1a37b
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
File tree
- docs
- examples
- src
- daemon
- schemas
- tests
- daemon
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
29 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
30 | 34 | | |
31 | 35 | | |
32 | 36 | | |
| |||
38 | 42 | | |
39 | 43 | | |
40 | 44 | | |
| 45 | + | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
| |||
57 | 62 | | |
58 | 63 | | |
59 | 64 | | |
60 | | - | |
| 65 | + | |
61 | 66 | | |
62 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
63 | 71 | | |
64 | 72 | | |
65 | 73 | | |
66 | 74 | | |
67 | 75 | | |
68 | 76 | | |
69 | | - | |
| 77 | + | |
70 | 78 | | |
71 | 79 | | |
72 | 80 | | |
| |||
77 | 85 | | |
78 | 86 | | |
79 | 87 | | |
80 | | - | |
| 88 | + | |
81 | 89 | | |
82 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
83 | 93 | | |
84 | 94 | | |
85 | | - | |
| 95 | + | |
86 | 96 | | |
87 | 97 | | |
88 | 98 | | |
| |||
95 | 105 | | |
96 | 106 | | |
97 | 107 | | |
98 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
99 | 112 | | |
100 | 113 | | |
101 | 114 | | |
| |||
126 | 139 | | |
127 | 140 | | |
128 | 141 | | |
129 | | - | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
130 | 146 | | |
131 | 147 | | |
132 | 148 | | |
| |||
144 | 160 | | |
145 | 161 | | |
146 | 162 | | |
147 | | - | |
| 163 | + | |
148 | 164 | | |
149 | 165 | | |
150 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
151 | 170 | | |
152 | | - | |
| 171 | + | |
153 | 172 | | |
154 | 173 | | |
155 | 174 | | |
156 | 175 | | |
157 | 176 | | |
158 | 177 | | |
159 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
160 | 182 | | |
161 | 183 | | |
162 | 184 | | |
| |||
179 | 201 | | |
180 | 202 | | |
181 | 203 | | |
| 204 | + | |
182 | 205 | | |
183 | 206 | | |
184 | 207 | | |
| |||
199 | 222 | | |
200 | 223 | | |
201 | 224 | | |
| 225 | + | |
202 | 226 | | |
203 | 227 | | |
204 | 228 | | |
205 | 229 | | |
206 | 230 | | |
207 | 231 | | |
208 | | - | |
| 232 | + | |
209 | 233 | | |
210 | 234 | | |
211 | 235 | | |
| |||
219 | 243 | | |
220 | 244 | | |
221 | 245 | | |
| 246 | + | |
222 | 247 | | |
223 | 248 | | |
224 | 249 | | |
| |||
236 | 261 | | |
237 | 262 | | |
238 | 263 | | |
| 264 | + | |
239 | 265 | | |
240 | 266 | | |
241 | 267 | | |
| |||
258 | 284 | | |
259 | 285 | | |
260 | 286 | | |
| 287 | + | |
261 | 288 | | |
262 | 289 | | |
263 | 290 | | |
| |||
283 | 310 | | |
284 | 311 | | |
285 | 312 | | |
| 313 | + | |
286 | 314 | | |
287 | 315 | | |
288 | 316 | | |
| |||
306 | 334 | | |
307 | 335 | | |
308 | 336 | | |
309 | | - | |
| 337 | + | |
310 | 338 | | |
311 | | - | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
312 | 343 | | |
313 | 344 | | |
314 | 345 | | |
| |||
319 | 350 | | |
320 | 351 | | |
321 | 352 | | |
322 | | - | |
| 353 | + | |
323 | 354 | | |
324 | 355 | | |
325 | 356 | | |
| |||
330 | 361 | | |
331 | 362 | | |
332 | 363 | | |
333 | | - | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
334 | 369 | | |
335 | | - | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
336 | 374 | | |
337 | 375 | | |
338 | 376 | | |
339 | 377 | | |
340 | | - | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
341 | 381 | | |
342 | 382 | | |
343 | | - | |
| 383 | + | |
344 | 384 | | |
345 | 385 | | |
346 | 386 | | |
| |||
354 | 394 | | |
355 | 395 | | |
356 | 396 | | |
357 | | - | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
358 | 401 | | |
359 | 402 | | |
360 | 403 | | |
| |||
376 | 419 | | |
377 | 420 | | |
378 | 421 | | |
379 | | - | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
380 | 426 | | |
381 | 427 | | |
382 | 428 | | |
| |||
423 | 469 | | |
424 | 470 | | |
425 | 471 | | |
| 472 | + | |
426 | 473 | | |
427 | 474 | | |
428 | 475 | | |
| |||
443 | 490 | | |
444 | 491 | | |
445 | 492 | | |
446 | | - | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
447 | 497 | | |
448 | 498 | | |
449 | 499 | | |
| |||
463 | 513 | | |
464 | 514 | | |
465 | 515 | | |
466 | | - | |
| 516 | + | |
467 | 517 | | |
468 | 518 | | |
| 519 | + | |
469 | 520 | | |
470 | 521 | | |
471 | 522 | | |
| |||
480 | 531 | | |
481 | 532 | | |
482 | 533 | | |
483 | | - | |
| 534 | + | |
484 | 535 | | |
485 | 536 | | |
486 | 537 | | |
| |||
493 | 544 | | |
494 | 545 | | |
495 | 546 | | |
496 | | - | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
497 | 551 | | |
498 | 552 | | |
499 | 553 | | |
| |||
514 | 568 | | |
515 | 569 | | |
516 | 570 | | |
517 | | - | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
518 | 575 | | |
519 | 576 | | |
520 | 577 | | |
| |||
533 | 590 | | |
534 | 591 | | |
535 | 592 | | |
536 | | - | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
537 | 597 | | |
538 | 598 | | |
539 | 599 | | |
| |||
558 | 618 | | |
559 | 619 | | |
560 | 620 | | |
561 | | - | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
562 | 625 | | |
563 | 626 | | |
564 | 627 | | |
| |||
575 | 638 | | |
576 | 639 | | |
577 | 640 | | |
578 | | - | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
579 | 645 | | |
580 | 646 | | |
581 | 647 | | |
| |||
604 | 670 | | |
605 | 671 | | |
606 | 672 | | |
607 | | - | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
608 | 676 | | |
609 | 677 | | |
610 | 678 | | |
| |||
634 | 702 | | |
635 | 703 | | |
636 | 704 | | |
| 705 | + | |
637 | 706 | | |
638 | 707 | | |
639 | 708 | | |
| |||
646 | 715 | | |
647 | 716 | | |
648 | 717 | | |
| 718 | + | |
649 | 719 | | |
650 | 720 | | |
651 | 721 | | |
| |||
0 commit comments