@factory/droid-sdk 0.2.0 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +214 -82
  2. package/dist/index.cjs +2345 -692
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +2570 -232
  5. package/dist/index.d.ts +2570 -232
  6. package/dist/index.js +2298 -697
  7. package/dist/index.js.map +1 -1
  8. package/docs/daemon-usage-guide.md +544 -0
  9. package/docs/sdk-usage-guide.md +748 -0
  10. package/examples/abort-session-stream.ts +23 -56
  11. package/examples/daemon-multi-session.ts +67 -0
  12. package/examples/droid-dev-structured-output.ts +5 -5
  13. package/examples/fork-session.ts +33 -7
  14. package/examples/hook-execution.ts +68 -0
  15. package/examples/init-metadata.ts +14 -66
  16. package/examples/interrupt-session.ts +23 -55
  17. package/examples/list-sessions.ts +9 -55
  18. package/examples/multi-turn-session.ts +27 -54
  19. package/examples/permission-handler.ts +27 -84
  20. package/examples/readme-structured-output.ts +26 -0
  21. package/examples/result-metadata.ts +2 -1
  22. package/examples/run.ts +2 -1
  23. package/examples/sdk-mcp-tool.ts +14 -8
  24. package/examples/session-stream.ts +57 -0
  25. package/examples/spec-mode-new-session.ts +39 -99
  26. package/examples/spec-mode-same-session.ts +50 -111
  27. package/examples/structured-output.ts +17 -10
  28. package/examples/test-compact.ts +9 -4
  29. package/examples/test-rewind.ts +49 -117
  30. package/examples/tool-controls.ts +23 -55
  31. package/package.json +3 -1
  32. package/docs/examples/init-metadata.md +0 -79
  33. package/docs/examples/list-sessions.md +0 -64
  34. package/docs/examples/multi-turn-session.md +0 -77
  35. package/docs/examples/one-shot-query.md +0 -73
  36. package/docs/examples/permission-handler.md +0 -102
  37. package/docs/examples/run.md +0 -52
  38. package/docs/examples/spec-mode.md +0 -101
  39. package/docs/examples/tool-controls.md +0 -90
  40. package/docs/typescript-sdk-reference.md +0 -372
  41. package/examples/simple-query.ts +0 -53
@@ -1,98 +1,41 @@
1
1
  /**
2
2
  * Permission handler example.
3
3
  *
4
- * Demonstrates using `query()` with a custom `permissionHandler` that
5
- * logs tool confirmation details, approves only one expected file creation,
6
- * and cancels any unexpected tool request.
7
- *
8
4
  * Usage:
9
5
  * npx tsx examples/permission-handler.ts
10
6
  */
11
7
 
12
- import { mkdtemp, rm } from 'node:fs/promises';
8
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
13
9
  import { tmpdir } from 'node:os';
14
10
  import { join } from 'node:path';
15
11
 
16
12
  import {
17
- DroidMessageType,
18
- query,
19
13
  ToolConfirmationOutcome,
20
14
  ToolConfirmationType,
21
- type RequestPermissionRequestParams,
22
- } from '../src/index.js';
23
-
24
- function permissionHandler(
25
- allowedFilePath: string,
26
- params: RequestPermissionRequestParams
27
- ): ToolConfirmationOutcome {
28
- for (const item of params.toolUses) {
29
- console.log(`\n[Permission] Tool: ${item.toolUse.name}`);
30
- console.log(` Type: ${item.confirmationType}`);
31
- console.log(` Input: ${JSON.stringify(item.toolUse.input, null, 2)}`);
32
- }
33
-
34
- const onlyAllowedCreate = params.toolUses.every(
35
- (item) =>
36
- item.details.type === ToolConfirmationType.Create &&
37
- item.details.filePath === allowedFilePath
38
- );
39
-
40
- if (!onlyAllowedCreate) {
41
- console.log(' → Cancelling unexpected tool request\n');
42
- return ToolConfirmationOutcome.Cancel;
43
- }
44
-
45
- console.log(' → Approving expected file creation with ProceedOnce\n');
46
- return ToolConfirmationOutcome.ProceedOnce;
47
- }
48
-
49
- async function main(): Promise<void> {
50
- const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-permission-'));
51
- const outputPath = join(tempDir, 'hello.txt');
52
- const prompt =
53
- process.argv[2] ??
54
- `Create a file called ${outputPath} with the text 'Hello, World!'`;
55
-
56
- try {
57
- console.log(`Sending prompt: "${prompt}"\n`);
58
-
59
- const stream = query({
60
- prompt,
61
- cwd: process.cwd(),
62
- permissionHandler: (params) => permissionHandler(outputPath, params),
63
- });
64
-
65
- for await (const msg of stream) {
66
- switch (msg.type) {
67
- case DroidMessageType.AssistantTextDelta:
68
- process.stdout.write(msg.text);
69
- break;
70
-
71
- case DroidMessageType.ToolUse:
72
- console.log(`\n[Tool Use] ${msg.toolName}`);
73
- break;
74
-
75
- case DroidMessageType.ToolResult:
76
- console.log(`[Tool Result] ${msg.isError ? 'Error' : 'Success'}`);
77
- break;
78
-
79
- case DroidMessageType.TurnComplete:
80
- console.log('\n\n--- Turn complete ---');
81
- if (msg.tokenUsage) {
82
- console.log(
83
- `Tokens — input: ${msg.tokenUsage.inputTokens}, ` +
84
- `output: ${msg.tokenUsage.outputTokens}`
85
- );
86
- }
87
- break;
88
- }
89
- }
90
- } finally {
91
- await rm(tempDir, { recursive: true, force: true });
92
- }
15
+ run,
16
+ } from '@factory/droid-sdk';
17
+
18
+ const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-permission-'));
19
+ const outputPath = join(tempDir, 'hello.txt');
20
+
21
+ try {
22
+ await run(`Create ${outputPath} with the text "Hello, World!"`, {
23
+ apiKey: process.env.FACTORY_API_KEY!,
24
+ cwd: process.cwd(),
25
+ permissionHandler(params) {
26
+ const onlyCreatesExpectedFile = params.toolUses.every(
27
+ (item) =>
28
+ item.details.type === ToolConfirmationType.Create &&
29
+ item.details.filePath === outputPath
30
+ );
31
+
32
+ return onlyCreatesExpectedFile
33
+ ? ToolConfirmationOutcome.ProceedOnce
34
+ : ToolConfirmationOutcome.Cancel;
35
+ },
36
+ });
37
+
38
+ console.log(await readFile(outputPath, 'utf8'));
39
+ } finally {
40
+ await rm(tempDir, { recursive: true, force: true });
93
41
  }
94
-
95
- main().catch((err: unknown) => {
96
- console.error('Error:', err);
97
- process.exit(1);
98
- });
@@ -0,0 +1,26 @@
1
+ import { OutputFormatType, run } from '@factory/droid-sdk';
2
+
3
+ type FavoriteNumber = { favoriteNumber: number };
4
+
5
+ const result = await run('Pick a favorite number between 1 and 42.', {
6
+ apiKey: process.env.FACTORY_API_KEY!,
7
+ cwd: process.cwd(),
8
+ outputFormat: {
9
+ type: OutputFormatType.JsonSchema,
10
+ schema: {
11
+ type: 'object',
12
+ properties: {
13
+ favoriteNumber: {
14
+ type: 'number',
15
+ minimum: 1,
16
+ maximum: 42,
17
+ },
18
+ },
19
+ required: ['favoriteNumber'],
20
+ },
21
+ },
22
+ });
23
+
24
+ console.log(
25
+ (result.structuredOutput as FavoriteNumber | undefined)?.favoriteNumber
26
+ );
@@ -12,7 +12,7 @@
12
12
 
13
13
  import assert from 'node:assert/strict';
14
14
 
15
- import { run } from '../src/index.js';
15
+ import { run } from '@factory/droid-sdk';
16
16
 
17
17
  async function main(): Promise<void> {
18
18
  const prompt =
@@ -22,6 +22,7 @@ async function main(): Promise<void> {
22
22
  console.log(`Sending prompt: "${prompt}"\n`);
23
23
 
24
24
  const result = await run(prompt, {
25
+ apiKey: process.env.FACTORY_API_KEY!,
25
26
  cwd: process.cwd(),
26
27
  });
27
28
 
package/examples/run.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * npx tsx examples/run.ts "What is 2 + 2?"
10
10
  */
11
11
 
12
- import { run } from '../src/index.js';
12
+ import { run } from '@factory/droid-sdk';
13
13
 
14
14
  async function main(): Promise<void> {
15
15
  const text = process.argv.slice(2).join(' ') || 'What is 2 + 2?';
@@ -17,6 +17,7 @@ async function main(): Promise<void> {
17
17
  console.log(`Sending prompt: "${text}"\n`);
18
18
 
19
19
  const result = await run(text, {
20
+ apiKey: process.env.FACTORY_API_KEY!,
20
21
  cwd: process.cwd(),
21
22
  });
22
23
 
@@ -1,13 +1,13 @@
1
- import { z } from 'zod';
2
-
3
1
  import {
2
+ DroidMessageType,
4
3
  ToolConfirmationOutcome,
5
4
  createSession,
6
5
  createSdkMcpServer,
7
6
  tool,
8
- } from '../src/index.js';
7
+ } from '@factory/droid-sdk';
8
+ import { z } from 'zod';
9
9
 
10
- const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid';
10
+ const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid-dev';
11
11
 
12
12
  const sdkTools = createSdkMcpServer({
13
13
  name: 'sdk-tools',
@@ -22,6 +22,7 @@ const sdkTools = createSdkMcpServer({
22
22
  });
23
23
 
24
24
  const session = await createSession({
25
+ apiKey: process.env.FACTORY_API_KEY!,
25
26
  execPath,
26
27
  mcpServers: [sdkTools],
27
28
  cwd: process.cwd(),
@@ -29,10 +30,15 @@ const session = await createSession({
29
30
  });
30
31
 
31
32
  try {
32
- const result = await session.send(
33
- 'Use the favorite_number tool for Ada and tell me the answer.'
34
- );
35
- console.log(result.text);
33
+ for await (const msg of session.stream(
34
+ 'Use the favorite_number tool for Ada and tell me the answer.',
35
+ { includePartialMessages: true }
36
+ )) {
37
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
38
+ process.stdout.write(msg.text);
39
+ }
40
+ }
41
+ console.log();
36
42
  } finally {
37
43
  await session.close();
38
44
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Simple session streaming example.
3
+ *
4
+ * Demonstrates using `session.stream()` to send a prompt, streaming
5
+ * full assistant/tool messages, and handling the final `result`.
6
+ *
7
+ * Usage:
8
+ * npx tsx examples/session-stream.ts
9
+ */
10
+
11
+ import { DroidMessageType, createSession } from '@factory/droid-sdk';
12
+
13
+ async function main(): Promise<void> {
14
+ const prompt = process.argv[2] ?? 'What files are in the current directory?';
15
+
16
+ console.log(`Sending prompt: "${prompt}"\n`);
17
+
18
+ const session = await createSession({
19
+ apiKey: process.env.FACTORY_API_KEY!,
20
+ cwd: process.cwd(),
21
+ });
22
+
23
+ try {
24
+ for await (const msg of session.stream(prompt)) {
25
+ switch (msg.type) {
26
+ case DroidMessageType.Assistant:
27
+ process.stdout.write(msg.text);
28
+ break;
29
+
30
+ case DroidMessageType.ToolCall:
31
+ console.log(`\n[Tool] ${msg.toolUse.name}`);
32
+ break;
33
+
34
+ case DroidMessageType.ToolResult:
35
+ console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
36
+ break;
37
+
38
+ case DroidMessageType.Result:
39
+ console.log('\n\n--- Turn complete ---');
40
+ if (msg.tokenUsage) {
41
+ console.log(
42
+ `Tokens — input: ${msg.tokenUsage.inputTokens}, ` +
43
+ `output: ${msg.tokenUsage.outputTokens}`
44
+ );
45
+ }
46
+ break;
47
+ }
48
+ }
49
+ } finally {
50
+ await session.close();
51
+ }
52
+ }
53
+
54
+ main().catch((err: unknown) => {
55
+ console.error('Error:', err);
56
+ process.exit(1);
57
+ });
@@ -1,10 +1,5 @@
1
1
  /**
2
- * Spec mode: approve and hand off to a new session.
3
- *
4
- * The agent drafts a plan in spec mode. On approval with
5
- * ProceedNewSessionHigh, the server automatically creates a
6
- * fresh session and implements the plan. The results stream
7
- * back through the same query — no manual session creation needed.
2
+ * Spec mode: approve and hand off implementation to a new session.
8
3
  *
9
4
  * Usage:
10
5
  * npx tsx examples/spec-mode-new-session.ts
@@ -15,107 +10,52 @@ import { tmpdir } from 'node:os';
15
10
  import { join } from 'node:path';
16
11
 
17
12
  import {
18
- DroidMessageType,
19
13
  DroidInteractionMode,
20
- query,
21
14
  ReasoningEffort,
22
15
  ToolConfirmationOutcome,
23
16
  ToolConfirmationType,
24
- } from '../src/index.js';
25
-
26
- const EXPECTED_CONTENT = 'Hello from Droid';
27
-
28
- async function main(): Promise<void> {
29
- const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
30
- const outputPath = join(tempDir, 'hello-from-droid.txt');
31
- const prompt =
32
- `Plan how to create a small ${outputPath} file containing the text ` +
33
- '"Hello from Droid". Keep the plan short and concrete.';
34
-
35
- console.log('=== Spec Mode: New Session Handoff ===\n');
36
-
37
- try {
38
- let toolError: string | null = null;
39
-
40
- const stream = query({
41
- prompt,
42
- cwd: process.cwd(),
43
- interactionMode: DroidInteractionMode.Spec,
44
- specModeReasoningEffort: ReasoningEffort.High,
45
- permissionHandler(params) {
46
- const exitSpec = params.toolUses.find(
47
- (item) => item.details.type === ToolConfirmationType.ExitSpecMode
48
- );
49
-
50
- if (exitSpec?.details.type === ToolConfirmationType.ExitSpecMode) {
51
- console.log(
52
- '\n--- Plan received, approving with new session handoff ---'
53
- );
54
- console.log(exitSpec.details.plan);
55
- console.log('---\n');
56
-
57
- // The server creates a fresh session and implements the plan.
58
- // Results continue streaming through this same query.
59
- return ToolConfirmationOutcome.ProceedNewSessionHigh;
60
- }
61
-
62
- const onlyAllowedCreate = params.toolUses.every(
63
- (item) =>
64
- item.details.type === ToolConfirmationType.Create &&
65
- item.details.filePath === outputPath
66
- );
67
-
68
- return onlyAllowedCreate
69
- ? ToolConfirmationOutcome.ProceedOnce
70
- : ToolConfirmationOutcome.Cancel;
71
- },
72
- });
17
+ createSession,
18
+ } from '@factory/droid-sdk';
19
+
20
+ const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
21
+ const outputPath = join(tempDir, 'hello.txt');
22
+
23
+ try {
24
+ const session = await createSession({
25
+ apiKey: process.env.FACTORY_API_KEY!,
26
+ cwd: process.cwd(),
27
+ interactionMode: DroidInteractionMode.Spec,
28
+ specModeReasoningEffort: ReasoningEffort.High,
29
+ permissionHandler(params) {
30
+ const canExitSpec = params.toolUses.some(
31
+ (item) => item.details.type === ToolConfirmationType.ExitSpecMode
32
+ );
33
+ const onlyCreatesFile = params.toolUses.every(
34
+ (item) =>
35
+ item.details.type === ToolConfirmationType.Create &&
36
+ item.details.filePath === outputPath
37
+ );
73
38
 
74
- for await (const msg of stream) {
75
- switch (msg.type) {
76
- case DroidMessageType.AssistantTextDelta:
77
- process.stdout.write(msg.text);
78
- break;
79
- case DroidMessageType.ToolUse:
80
- console.log(`\n[Tool] ${msg.toolName}`);
81
- break;
82
- case DroidMessageType.ToolResult:
83
- if (msg.isError) {
84
- toolError = JSON.stringify(msg.content);
85
- }
86
- console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
87
- break;
88
- case DroidMessageType.TurnComplete:
89
- console.log('\n\n--- Turn complete ---');
90
- if (msg.tokenUsage) {
91
- console.log(
92
- `Tokens — input: ${msg.tokenUsage.inputTokens}, output: ${msg.tokenUsage.outputTokens}`
93
- );
94
- }
95
- break;
39
+ if (canExitSpec) {
40
+ return ToolConfirmationOutcome.ProceedNewSessionHigh;
96
41
  }
97
- }
98
-
99
- if (toolError) {
100
- throw new Error(
101
- `Tool failed during spec-mode implementation: ${toolError}`
102
- );
103
- }
42
+ return onlyCreatesFile
43
+ ? ToolConfirmationOutcome.ProceedOnce
44
+ : ToolConfirmationOutcome.Cancel;
45
+ },
46
+ });
104
47
 
105
- const content = await readFile(outputPath, 'utf8');
106
- if (content.trim() !== EXPECTED_CONTENT) {
107
- throw new Error(
108
- `Expected ${outputPath} to contain ${JSON.stringify(EXPECTED_CONTENT)}, got ${JSON.stringify(content)}`
109
- );
48
+ try {
49
+ for await (const _msg of session.stream(
50
+ `Plan then create ${outputPath} containing "Hello from Droid".`
51
+ )) {
52
+ // Consume the stream until the handoff implementation finishes.
110
53
  }
111
-
112
- console.log(`\nVerified ${outputPath}.`);
113
54
  } finally {
114
- await rm(tempDir, { recursive: true, force: true });
55
+ await session.close();
115
56
  }
116
- }
117
57
 
118
- main().catch((err: unknown) => {
119
- console.error('Error:', err);
120
- process.exit(1);
121
- });
58
+ console.log(await readFile(outputPath, 'utf8'));
59
+ } finally {
60
+ await rm(tempDir, { recursive: true, force: true });
61
+ }
@@ -1,133 +1,72 @@
1
1
  /**
2
2
  * Spec mode: approve and implement in the same session.
3
3
  *
4
- * The agent drafts a plan in spec mode, then implements it
5
- * in the same query stream after approval.
6
- *
7
4
  * Usage:
8
5
  * npx tsx examples/spec-mode-same-session.ts
9
6
  */
10
7
 
11
- import { mkdtemp, rm } from 'node:fs/promises';
12
- import { readFile } from 'node:fs/promises';
8
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
13
9
  import { tmpdir } from 'node:os';
14
10
  import { join } from 'node:path';
15
11
 
16
12
  import {
17
- DroidMessageType,
18
13
  DroidInteractionMode,
19
- query,
20
14
  ReasoningEffort,
21
15
  ToolConfirmationOutcome,
22
16
  ToolConfirmationType,
23
- } from '../src/index.js';
24
-
25
- const EXPECTED_CONTENT = 'Hello from Droid';
26
-
27
- async function main(): Promise<void> {
28
- const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
29
- const outputPath = join(tempDir, 'hello-from-droid.txt');
30
- const prompt =
31
- `Plan how to create a small ${outputPath} file containing the text ` +
32
- '"Hello from Droid". Keep the plan short and concrete.';
17
+ createSession,
18
+ } from '@factory/droid-sdk';
19
+
20
+ const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
21
+ const outputPath = join(tempDir, 'hello.txt');
22
+
23
+ try {
24
+ const session = await createSession({
25
+ apiKey: process.env.FACTORY_API_KEY!,
26
+ cwd: process.cwd(),
27
+ interactionMode: DroidInteractionMode.Spec,
28
+ specModeReasoningEffort: ReasoningEffort.High,
29
+ permissionHandler(params) {
30
+ const canExitSpec = params.toolUses.some(
31
+ (item) => item.details.type === ToolConfirmationType.ExitSpecMode
32
+ );
33
+ const onlyCreatesFile = params.toolUses.every(
34
+ (item) =>
35
+ item.details.type === ToolConfirmationType.Create &&
36
+ item.details.filePath === outputPath
37
+ );
38
+ const onlyEditsTempFile = params.toolUses.every(
39
+ (item) =>
40
+ item.details.type === ToolConfirmationType.ApplyPatch &&
41
+ item.details.filePath === outputPath
42
+ );
43
+ const onlyRunsTempCommand = params.toolUses.every(
44
+ (item) =>
45
+ item.details.type === ToolConfirmationType.Execute &&
46
+ item.details.fullCommand.includes(outputPath) &&
47
+ item.details.fullCommand.includes(tempDir)
48
+ );
33
49
 
34
- console.log('=== Spec Mode: Same Session ===\n');
50
+ return canExitSpec ||
51
+ onlyCreatesFile ||
52
+ onlyEditsTempFile ||
53
+ onlyRunsTempCommand
54
+ ? ToolConfirmationOutcome.ProceedOnce
55
+ : ToolConfirmationOutcome.Cancel;
56
+ },
57
+ });
35
58
 
36
59
  try {
37
- let toolError: string | null = null;
38
-
39
- const stream = query({
40
- prompt,
41
- cwd: process.cwd(),
42
- interactionMode: DroidInteractionMode.Spec,
43
- specModeReasoningEffort: ReasoningEffort.High,
44
- permissionHandler(params) {
45
- const exitSpec = params.toolUses.find(
46
- (item) => item.details.type === ToolConfirmationType.ExitSpecMode
47
- );
48
-
49
- if (exitSpec?.details.type === ToolConfirmationType.ExitSpecMode) {
50
- console.log('\n--- Plan received, approving in same session ---');
51
- console.log(exitSpec.details.plan);
52
- console.log('---\n');
53
- return ToolConfirmationOutcome.ProceedOnce;
54
- }
55
-
56
- const onlyAllowedCreate = params.toolUses.every(
57
- (item) =>
58
- item.details.type === ToolConfirmationType.Create &&
59
- item.details.filePath === outputPath
60
- );
61
- const onlyAllowedApplyPatch = params.toolUses.every(
62
- (item) =>
63
- item.details.type === ToolConfirmationType.ApplyPatch &&
64
- item.details.filePath === outputPath
65
- );
66
- const onlyAllowedExecute = params.toolUses.every((item) => {
67
- if (item.details.type !== ToolConfirmationType.Execute) {
68
- return false;
69
- }
70
-
71
- const command = item.details.fullCommand;
72
- return (
73
- command.includes(outputPath) &&
74
- command.includes(tempDir) &&
75
- !command.includes('sudo') &&
76
- !command.includes('rm -rf /')
77
- );
78
- });
79
-
80
- return onlyAllowedCreate || onlyAllowedApplyPatch || onlyAllowedExecute
81
- ? ToolConfirmationOutcome.ProceedOnce
82
- : ToolConfirmationOutcome.Cancel;
83
- },
84
- });
85
-
86
- for await (const msg of stream) {
87
- switch (msg.type) {
88
- case DroidMessageType.AssistantTextDelta:
89
- process.stdout.write(msg.text);
90
- break;
91
- case DroidMessageType.ToolUse:
92
- console.log(`\n[Tool] ${msg.toolName}`);
93
- break;
94
- case DroidMessageType.ToolResult:
95
- if (msg.isError) {
96
- toolError = JSON.stringify(msg.content);
97
- }
98
- console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
99
- break;
100
- case DroidMessageType.TurnComplete:
101
- console.log('\n\n--- Turn complete ---');
102
- if (msg.tokenUsage) {
103
- console.log(
104
- `Tokens — input: ${msg.tokenUsage.inputTokens}, output: ${msg.tokenUsage.outputTokens}`
105
- );
106
- }
107
- break;
108
- }
109
- }
110
-
111
- if (toolError) {
112
- throw new Error(
113
- `Tool failed during spec-mode implementation: ${toolError}`
114
- );
60
+ for await (const _msg of session.stream(
61
+ `Plan then create ${outputPath} containing "Hello from Droid".`
62
+ )) {
63
+ // Consume the stream until implementation finishes.
115
64
  }
116
-
117
- const content = await readFile(outputPath, 'utf8');
118
- if (content.trim() !== EXPECTED_CONTENT) {
119
- throw new Error(
120
- `Expected ${outputPath} to contain ${JSON.stringify(EXPECTED_CONTENT)}, got ${JSON.stringify(content)}`
121
- );
122
- }
123
-
124
- console.log(`\nVerified ${outputPath}.`);
125
65
  } finally {
126
- await rm(tempDir, { recursive: true, force: true });
66
+ await session.close();
127
67
  }
128
- }
129
68
 
130
- main().catch((err: unknown) => {
131
- console.error('Error:', err);
132
- process.exit(1);
133
- });
69
+ console.log(await readFile(outputPath, 'utf8'));
70
+ } finally {
71
+ await rm(tempDir, { recursive: true, force: true });
72
+ }