@raindrop-ai/wizard 0.0.1

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 (135) hide show
  1. package/LICENSE +47 -0
  2. package/dist/bin.d.ts +2 -0
  3. package/dist/bin.js +117 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/src/docs/browser.md +105 -0
  6. package/dist/src/docs/python.md +618 -0
  7. package/dist/src/docs/typescript.md +584 -0
  8. package/dist/src/docs/vercel-ai-sdk.md +304 -0
  9. package/dist/src/lib/agent-interface.d.ts +46 -0
  10. package/dist/src/lib/agent-interface.js +292 -0
  11. package/dist/src/lib/agent-interface.js.map +1 -0
  12. package/dist/src/lib/agent-prompts.d.ts +10 -0
  13. package/dist/src/lib/agent-prompts.js +49 -0
  14. package/dist/src/lib/agent-prompts.js.map +1 -0
  15. package/dist/src/lib/config.d.ts +39 -0
  16. package/dist/src/lib/config.js +549 -0
  17. package/dist/src/lib/config.js.map +1 -0
  18. package/dist/src/lib/constants.d.ts +27 -0
  19. package/dist/src/lib/constants.js +165 -0
  20. package/dist/src/lib/constants.js.map +1 -0
  21. package/dist/src/lib/handlers.d.ts +68 -0
  22. package/dist/src/lib/handlers.js +420 -0
  23. package/dist/src/lib/handlers.js.map +1 -0
  24. package/dist/src/lib/integration-testing.d.ts +44 -0
  25. package/dist/src/lib/integration-testing.js +123 -0
  26. package/dist/src/lib/integration-testing.js.map +1 -0
  27. package/dist/src/lib/mcp.d.ts +14 -0
  28. package/dist/src/lib/mcp.js +134 -0
  29. package/dist/src/lib/mcp.js.map +1 -0
  30. package/dist/src/lib/sdk-messages.d.ts +17 -0
  31. package/dist/src/lib/sdk-messages.js +278 -0
  32. package/dist/src/lib/sdk-messages.js.map +1 -0
  33. package/dist/src/lib/wizard.d.ts +6 -0
  34. package/dist/src/lib/wizard.js +131 -0
  35. package/dist/src/lib/wizard.js.map +1 -0
  36. package/dist/src/run.d.ts +8 -0
  37. package/dist/src/run.js +53 -0
  38. package/dist/src/run.js.map +1 -0
  39. package/dist/src/ui/App.d.ts +15 -0
  40. package/dist/src/ui/App.js +27 -0
  41. package/dist/src/ui/App.js.map +1 -0
  42. package/dist/src/ui/cancellation.d.ts +14 -0
  43. package/dist/src/ui/cancellation.js +17 -0
  44. package/dist/src/ui/cancellation.js.map +1 -0
  45. package/dist/src/ui/components/ClarifyingQuestionsPrompt.d.ts +17 -0
  46. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +359 -0
  47. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -0
  48. package/dist/src/ui/components/ContinuePrompt.d.ts +14 -0
  49. package/dist/src/ui/components/ContinuePrompt.js +23 -0
  50. package/dist/src/ui/components/ContinuePrompt.js.map +1 -0
  51. package/dist/src/ui/components/DiffDisplay.d.ts +18 -0
  52. package/dist/src/ui/components/DiffDisplay.js +110 -0
  53. package/dist/src/ui/components/DiffDisplay.js.map +1 -0
  54. package/dist/src/ui/components/FeedbackSelectPrompt.d.ts +20 -0
  55. package/dist/src/ui/components/FeedbackSelectPrompt.js +132 -0
  56. package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -0
  57. package/dist/src/ui/components/HistoryItemDisplay.d.ts +14 -0
  58. package/dist/src/ui/components/HistoryItemDisplay.js +140 -0
  59. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -0
  60. package/dist/src/ui/components/Logo.d.ts +10 -0
  61. package/dist/src/ui/components/Logo.js +47 -0
  62. package/dist/src/ui/components/Logo.js.map +1 -0
  63. package/dist/src/ui/components/OrgInfoBox.d.ts +11 -0
  64. package/dist/src/ui/components/OrgInfoBox.js +16 -0
  65. package/dist/src/ui/components/OrgInfoBox.js.map +1 -0
  66. package/dist/src/ui/components/PendingPrompt.d.ts +18 -0
  67. package/dist/src/ui/components/PendingPrompt.js +57 -0
  68. package/dist/src/ui/components/PendingPrompt.js.map +1 -0
  69. package/dist/src/ui/components/PersistentTextInput.d.ts +21 -0
  70. package/dist/src/ui/components/PersistentTextInput.js +117 -0
  71. package/dist/src/ui/components/PersistentTextInput.js.map +1 -0
  72. package/dist/src/ui/components/PlanApprovalPrompt.d.ts +19 -0
  73. package/dist/src/ui/components/PlanApprovalPrompt.js +62 -0
  74. package/dist/src/ui/components/PlanApprovalPrompt.js.map +1 -0
  75. package/dist/src/ui/components/PromptContainer.d.ts +14 -0
  76. package/dist/src/ui/components/PromptContainer.js +18 -0
  77. package/dist/src/ui/components/PromptContainer.js.map +1 -0
  78. package/dist/src/ui/components/SelectPrompt.d.ts +14 -0
  79. package/dist/src/ui/components/SelectPrompt.js +62 -0
  80. package/dist/src/ui/components/SelectPrompt.js.map +1 -0
  81. package/dist/src/ui/components/SpinnerDisplay.d.ts +13 -0
  82. package/dist/src/ui/components/SpinnerDisplay.js +11 -0
  83. package/dist/src/ui/components/SpinnerDisplay.js.map +1 -0
  84. package/dist/src/ui/components/ToolApprovalPrompt.d.ts +14 -0
  85. package/dist/src/ui/components/ToolApprovalPrompt.js +142 -0
  86. package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -0
  87. package/dist/src/ui/components/ToolCallDisplay.d.ts +14 -0
  88. package/dist/src/ui/components/ToolCallDisplay.js +83 -0
  89. package/dist/src/ui/components/ToolCallDisplay.js.map +1 -0
  90. package/dist/src/ui/components/WriteKeyDisplay.d.ts +15 -0
  91. package/dist/src/ui/components/WriteKeyDisplay.js +13 -0
  92. package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -0
  93. package/dist/src/ui/contexts/WizardContext.d.ts +210 -0
  94. package/dist/src/ui/contexts/WizardContext.js +362 -0
  95. package/dist/src/ui/contexts/WizardContext.js.map +1 -0
  96. package/dist/src/ui/hooks/useCancellation.d.ts +15 -0
  97. package/dist/src/ui/hooks/useCancellation.js +25 -0
  98. package/dist/src/ui/hooks/useCancellation.js.map +1 -0
  99. package/dist/src/ui/render.d.ts +34 -0
  100. package/dist/src/ui/render.js +94 -0
  101. package/dist/src/ui/render.js.map +1 -0
  102. package/dist/src/ui/types.d.ts +184 -0
  103. package/dist/src/ui/types.js +6 -0
  104. package/dist/src/ui/types.js.map +1 -0
  105. package/dist/src/utils/clack-utils.d.ts +13 -0
  106. package/dist/src/utils/clack-utils.js +131 -0
  107. package/dist/src/utils/clack-utils.js.map +1 -0
  108. package/dist/src/utils/debug.d.ts +13 -0
  109. package/dist/src/utils/debug.js +47 -0
  110. package/dist/src/utils/debug.js.map +1 -0
  111. package/dist/src/utils/environment.d.ts +5 -0
  112. package/dist/src/utils/environment.js +131 -0
  113. package/dist/src/utils/environment.js.map +1 -0
  114. package/dist/src/utils/logging.d.ts +9 -0
  115. package/dist/src/utils/logging.js +38 -0
  116. package/dist/src/utils/logging.js.map +1 -0
  117. package/dist/src/utils/oauth.d.ts +12 -0
  118. package/dist/src/utils/oauth.js +497 -0
  119. package/dist/src/utils/oauth.js.map +1 -0
  120. package/dist/src/utils/package-json-types.d.ts +44 -0
  121. package/dist/src/utils/package-json-types.js +6 -0
  122. package/dist/src/utils/package-json-types.js.map +1 -0
  123. package/dist/src/utils/package-json.d.ts +19 -0
  124. package/dist/src/utils/package-json.js +22 -0
  125. package/dist/src/utils/package-json.js.map +1 -0
  126. package/dist/src/utils/session.d.ts +2 -0
  127. package/dist/src/utils/session.js +87 -0
  128. package/dist/src/utils/session.js.map +1 -0
  129. package/dist/src/utils/types.d.ts +61 -0
  130. package/dist/src/utils/types.js +2 -0
  131. package/dist/src/utils/types.js.map +1 -0
  132. package/dist/src/utils/ui.d.ts +120 -0
  133. package/dist/src/utils/ui.js +164 -0
  134. package/dist/src/utils/ui.js.map +1 -0
  135. package/package.json +140 -0
@@ -0,0 +1,304 @@
1
+ ## Vercel AI SDK Integration
2
+
3
+ Two-step setup:
4
+
5
+ 1. Configure OpenTelemetry trace exporter (platform-specific)
6
+ 2. Instrument AI SDK calls with Raindrop metadata
7
+
8
+ ---
9
+
10
+ ## Step 1: Configure OTEL Trace Exporter
11
+
12
+ > Choose the section matching the project setup:
13
+ >
14
+ > - **Next.js + Sentry** → [With Sentry (Next.js)](#with-sentry-nextjs)
15
+ > - **Next.js (no Sentry)** → [Next.js](#nextjs)
16
+ > - **Node.js** → [Node.js](#nodejs)
17
+ > - **Cloudflare Workers** → [Cloudflare Workers](#cloudflare-workers)
18
+
19
+ ### With Sentry (Next.js)
20
+
21
+ If already using Sentry, add Raindrop's exporter to Sentry's OTEL config to
22
+ avoid duplicate registration issues.
23
+
24
+ **Install dependencies:**
25
+
26
+ ```bash
27
+ npm install @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-base
28
+ ```
29
+
30
+ **Configure:**
31
+
32
+ ```ts
33
+ // sentry.server.config.ts
34
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
35
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
36
+ import * as Sentry from '@sentry/nextjs';
37
+
38
+ Sentry.init({
39
+ dsn: process.env.SENTRY_DSN,
40
+ tracesSampleRate: 1,
41
+ openTelemetrySpanProcessors: [
42
+ new BatchSpanProcessor(
43
+ new OTLPTraceExporter({
44
+ url: 'https://api.raindrop.ai/v1/traces',
45
+ headers: {
46
+ Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
47
+ },
48
+ }),
49
+ ),
50
+ ],
51
+ });
52
+ ```
53
+
54
+ ### Next.js
55
+
56
+ **Install dependencies:**
57
+
58
+ ```bash
59
+ npm install raindrop-ai @opentelemetry/api @opentelemetry/sdk-trace-base @vercel/otel
60
+ ```
61
+
62
+ **Configure:**
63
+
64
+ ```ts
65
+ // instrumentation.ts
66
+ import { registerOTel, OTLPHttpProtoTraceExporter } from '@vercel/otel';
67
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
68
+
69
+ export function register() {
70
+ registerOTel({
71
+ serviceName: projectName, // Replace with your app/service name
72
+ spanProcessors: [
73
+ new BatchSpanProcessor(
74
+ new OTLPHttpProtoTraceExporter({
75
+ url: 'https://api.raindrop.ai/v1/traces',
76
+ headers: {
77
+ Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
78
+ },
79
+ }),
80
+ ),
81
+ ],
82
+ });
83
+ }
84
+ ```
85
+
86
+ > Error: "Cannot execute the operation on ended Span" → Use `runtime = 'nodejs'`
87
+ > instead of `'edge'`.
88
+
89
+ ### Node.js
90
+
91
+ **Install dependencies:**
92
+
93
+ ```bash
94
+ npm install raindrop-ai @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-node
95
+ ```
96
+
97
+ **Configure:**
98
+
99
+ ```ts
100
+ import { NodeSDK } from '@opentelemetry/sdk-node';
101
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
102
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
103
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
104
+ import { resourceFromAttributes } from '@opentelemetry/resources';
105
+
106
+ const sdk = new NodeSDK({
107
+ resource: resourceFromAttributes({
108
+ [ATTR_SERVICE_NAME]: projectName, // Replace with your app/service name
109
+ }),
110
+ spanProcessors: [
111
+ new BatchSpanProcessor(
112
+ new OTLPTraceExporter({
113
+ url: 'https://api.raindrop.ai/v1/traces',
114
+ headers: {
115
+ Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
116
+ },
117
+ }),
118
+ ),
119
+ ],
120
+ });
121
+
122
+ sdk.start();
123
+ ```
124
+
125
+ ### Cloudflare Workers
126
+
127
+ Cloudflare's native tracing does not support custom spans. Use
128
+ `@microlabs/otel-cf-workers`.
129
+
130
+ **Install dependencies:**
131
+
132
+ ```bash
133
+ npm install raindrop-ai @opentelemetry/api @microlabs/otel-cf-workers
134
+ ```
135
+
136
+ **Wrangler config:**
137
+
138
+ ```toml
139
+ compatibility_flags = ["nodejs_compat"]
140
+ ```
141
+
142
+ **OTEL config:**
143
+
144
+ ```ts
145
+ // src/otel.ts
146
+ import { instrument, type ResolveConfigFn } from '@microlabs/otel-cf-workers';
147
+
148
+ export interface Env {
149
+ RAINDROP_WRITE_KEY: string;
150
+ [key: string]: unknown;
151
+ }
152
+
153
+ export const otelConfig: ResolveConfigFn<Env> = (env, _trigger) => ({
154
+ exporter: {
155
+ url: 'https://api.raindrop.ai/v1/traces',
156
+ headers: {
157
+ Authorization: `Bearer ${env.RAINDROP_WRITE_KEY}`,
158
+ },
159
+ },
160
+ service: {
161
+ name: workerName, // Replace with your worker/service name
162
+ },
163
+ });
164
+
165
+ export { instrument };
166
+ ```
167
+
168
+ **Instrument handler:**
169
+
170
+ ```ts
171
+ // src/index.ts — adapt AI provider imports to your project
172
+ import { streamText } from 'ai';
173
+ import { createOpenAI } from '@ai-sdk/openai'; // Adapt to your AI provider (e.g. @ai-sdk/anthropic)
174
+ import raindrop from 'raindrop-ai/otel';
175
+ import { instrument, otelConfig, Env } from './otel';
176
+
177
+ const handler = {
178
+ async fetch(
179
+ request: Request,
180
+ env: Env,
181
+ ctx: ExecutionContext,
182
+ ): Promise<Response> {
183
+ const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY }); // Adapt to your AI provider
184
+ const { prompt } = await request.json();
185
+
186
+ const result = streamText({
187
+ model: model, // Your app's AI model instance (e.g. 'gpt-4o','claude-3-opus')
188
+ prompt,
189
+ experimental_telemetry: {
190
+ isEnabled: true,
191
+ functionId: 'chat',
192
+ metadata: {
193
+ ...raindrop.metadata({ userId: userId }), // The authenticated user's ID from your app
194
+ },
195
+ },
196
+ });
197
+
198
+ ctx.waitUntil(result.text);
199
+ return result.toTextStreamResponse();
200
+ },
201
+ };
202
+
203
+ export default instrument(handler, otelConfig);
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Step 2: Instrumentation
209
+
210
+ ### Requirements
211
+
212
+ 1. Enable telemetry at **all** AI SDK call sites:
213
+ `experimental_telemetry: { isEnabled: true }`
214
+ 2. Add Raindrop metadata at top-level call using `raindrop.metadata()`
215
+
216
+ ### Usage
217
+
218
+ ```typescript
219
+ // Adapt imports to your project's AI provider (e.g. @ai-sdk/openai, @ai-sdk/anthropic, etc.)
220
+ import { generateText, tool } from 'ai';
221
+ import { z } from 'zod';
222
+ import raindrop from 'raindrop-ai/otel';
223
+
224
+ // Nested call - only needs isEnabled: true
225
+ const enhanceStory = tool({
226
+ description: 'Enhance story',
227
+ parameters: z.object({ story: z.string() }),
228
+ execute: async ({ story }) => {
229
+ const enhanced = await generateText({
230
+ model: model, // Your app's AI model instance (e.g. 'gpt-4o','claude-3-opus')
231
+ prompt: `Enhance: ${story}`,
232
+ experimental_telemetry: {
233
+ isEnabled: true, // Required at all call sites
234
+ functionId: 'enhance-story',
235
+ },
236
+ });
237
+ return { enhancedStory: enhanced.text };
238
+ },
239
+ });
240
+
241
+ // Top-level call - add raindrop.metadata()
242
+ const result = await generateText({
243
+ model: model, // Your app's AI model instance
244
+ prompt: userPrompt,
245
+ tools: { enhanceStory },
246
+ experimental_telemetry: {
247
+ isEnabled: true, // Required
248
+ functionId: 'generate-text',
249
+ metadata: {
250
+ ...raindrop.metadata({
251
+ userId: userId, // Required — the authenticated user's ID from your app
252
+ eventName: eventName, // A descriptive name for this AI action (e.g. 'chat_message', 'code_generation')
253
+ convoId: conversationId, // Your app's conversation/thread ID (if applicable)
254
+ wizardSession: '__WIZARD_SESSION_UUID__', // Required
255
+ }),
256
+ },
257
+ },
258
+ });
259
+ ```
260
+
261
+ ### Key Points
262
+
263
+ - **All call sites** need `experimental_telemetry: { isEnabled: true }`
264
+ - **Top-level call** adds `raindrop.metadata()` to capture user/event info
265
+ - **Nested calls** only need `isEnabled: true`
266
+
267
+ ## Troubleshooting
268
+
269
+ ### Debug Logging
270
+
271
+ ```bash
272
+ OTEL_LOG_LEVEL=debug npm run dev
273
+ ```
274
+
275
+ ### Missing Traces
276
+
277
+ Ensure `experimental_telemetry: { isEnabled: true }` at **every** AI SDK call:
278
+
279
+ ```typescript
280
+ // ❌ Not traced
281
+ const result = await generateText({ model: openai('gpt-4o'), prompt: 'Hello' });
282
+
283
+ // ✅ Traced
284
+ const result = await generateText({
285
+ model: openai('gpt-4o'),
286
+ prompt: 'Hello',
287
+ experimental_telemetry: { isEnabled: true },
288
+ });
289
+ ```
290
+
291
+ ### Cloudflare Workers: Incomplete Spans
292
+
293
+ Streaming responses need `waitUntil()` to prevent premature flush:
294
+
295
+ ```typescript
296
+ // ❌ Incomplete spans (1ms duration)
297
+ const result = streamText({ ... });
298
+ return result.toTextStreamResponse();
299
+
300
+ // ✅ Complete spans
301
+ const result = streamText({ ... });
302
+ ctx.waitUntil(result.text); // Delays flush until stream completes
303
+ return result.toTextStreamResponse();
304
+ ```
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared agent interface for wizards
3
+ * Uses Claude Agent SDK directly with streaming input support
4
+ */
5
+ import type { AgentQueryHandle } from '../ui/types.js';
6
+ import type { WizardOptions } from '../utils/types.js';
7
+ export type { AgentQueryHandle };
8
+ export type AgentConfig = {
9
+ workingDirectory: string;
10
+ };
11
+ /**
12
+ * Result from runAgentLoop including session ID and query handle
13
+ */
14
+ export interface AgentRunResult {
15
+ sessionId?: string;
16
+ handle: AgentQueryHandle;
17
+ }
18
+ /**
19
+ * Internal configuration object returned by initializeAgent
20
+ */
21
+ export type AgentRunConfig = {
22
+ workingDirectory: string;
23
+ model: string;
24
+ };
25
+ /**
26
+ * Initialize agent configuration for the Claude agent
27
+ */
28
+ export declare function initializeAgent(config: AgentConfig, options: WizardOptions): AgentRunConfig;
29
+ /**
30
+ * Configuration for runAgentLoop
31
+ */
32
+ export interface RunAgentConfig {
33
+ spinnerMessage?: string;
34
+ successMessage?: string;
35
+ resume?: string;
36
+ accessToken: string;
37
+ orgId: string;
38
+ }
39
+ /**
40
+ * Execute an agent with the provided prompt and options.
41
+ * Supports streaming input for user interruption and follow-up messages.
42
+ * Uses a while loop to handle user interactions instead of recursion.
43
+ *
44
+ * @returns Session ID and query handle for controlling the agent
45
+ */
46
+ export declare function runAgentLoop(agentConfig: AgentRunConfig, prompt: string, options: WizardOptions, config?: RunAgentConfig): Promise<AgentRunResult>;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Shared agent interface for wizards
3
+ * Uses Claude Agent SDK directly with streaming input support
4
+ */
5
+ import path from 'path';
6
+ import { createRequire } from 'module';
7
+ import ui from '../utils/ui.js';
8
+ import { debug, logToFile, initLogFile, LOG_FILE_PATH, } from '../utils/debug.js';
9
+ import { createCanUseToolHandler, createAgentQueryHandle, } from './handlers.js';
10
+ import { processSDKMessage } from './sdk-messages.js';
11
+ import { createCompletionMcpServer } from './mcp.js';
12
+ import { query } from '@anthropic-ai/claude-agent-sdk';
13
+ // Create a require function for ESM compatibility
14
+ const require = createRequire(import.meta.url);
15
+ /**
16
+ * Get the path to the bundled Claude Code CLI from the SDK package.
17
+ * This ensures we use the SDK's bundled version rather than the user's installed Claude Code.
18
+ */
19
+ function getClaudeCodeExecutablePath() {
20
+ // require.resolve finds the package's main entry, then we get cli.js from same dir
21
+ const sdkPackagePath = require.resolve('@anthropic-ai/claude-agent-sdk');
22
+ return path.join(path.dirname(sdkPackagePath), 'cli.js');
23
+ }
24
+ /**
25
+ * Initialize agent configuration for the Claude agent
26
+ */
27
+ export function initializeAgent(config, options) {
28
+ // Initialize log file for this run
29
+ initLogFile();
30
+ logToFile('Agent initialization starting');
31
+ logToFile('Install directory:', options.installDir);
32
+ try {
33
+ process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
34
+ // Set default subagent model to Sonnet
35
+ process.env.CLAUDE_CODE_SUBAGENT_MODEL = 'claude-sonnet-4-5-20250929';
36
+ const agentRunConfig = {
37
+ workingDirectory: config.workingDirectory,
38
+ model: 'opus',
39
+ };
40
+ logToFile('Agent config:', {
41
+ workingDirectory: agentRunConfig.workingDirectory,
42
+ });
43
+ if (options.debug) {
44
+ debug('Agent config:', {
45
+ workingDirectory: agentRunConfig.workingDirectory,
46
+ });
47
+ }
48
+ ui.addItem({
49
+ type: 'step',
50
+ text: `I'll keep verbose logs for this session at: ${LOG_FILE_PATH}`,
51
+ });
52
+ return agentRunConfig;
53
+ }
54
+ catch (error) {
55
+ ui.addItem({
56
+ type: 'error',
57
+ text: `Failed to initialize agent: ${error.message}`,
58
+ });
59
+ logToFile('Agent initialization error:', error);
60
+ debug('Agent initialization error:', error);
61
+ throw error;
62
+ }
63
+ }
64
+ /**
65
+ * Execute an agent with the provided prompt and options.
66
+ * Supports streaming input for user interruption and follow-up messages.
67
+ * Uses a while loop to handle user interactions instead of recursion.
68
+ *
69
+ * @returns Session ID and query handle for controlling the agent
70
+ */
71
+ export async function runAgentLoop(agentConfig, prompt, options, config) {
72
+ const { spinnerMessage = 'Raindrop wizard is working...', successMessage = 'Raindrop integration complete', resume, accessToken, } = config ?? {};
73
+ // Add header to indicate start of interactive agent phase
74
+ ui.addItem({ type: 'phase', text: '─── Agent ───' });
75
+ const cliPath = getClaudeCodeExecutablePath();
76
+ logToFile('Starting agent run');
77
+ logToFile('Claude Code executable:', cliPath);
78
+ // Loop-persistent state
79
+ let currentPrompt = prompt;
80
+ let currentSessionId = resume;
81
+ let handle;
82
+ // Cache for approved files (persists across all iterations)
83
+ const approvedFilesCache = new Set();
84
+ // eslint-disable-next-line no-constant-condition
85
+ while (true) {
86
+ const spinner = ui.spinner();
87
+ logToFile('Prompt:', currentPrompt);
88
+ if (currentSessionId) {
89
+ logToFile('Resuming session:', currentSessionId);
90
+ }
91
+ // Timing and session state
92
+ const startTime = Date.now();
93
+ let sessionId = currentSessionId;
94
+ let queryObject = null;
95
+ // Message and tool call collectors
96
+ const collectedText = [];
97
+ const pendingToolCalls = new Map();
98
+ // Shared ref objects for cross-boundary state
99
+ const hasCompletedWorkRef = { value: false };
100
+ const isInterruptingRef = { value: false };
101
+ const waitingForUserInputRef = { value: false };
102
+ const pendingUserMessageRef = { value: null };
103
+ const exitHintShownRef = { value: false };
104
+ // Promise resolver for waiting on user input after interrupt
105
+ let resolveUserMessage = null;
106
+ // Query handle for external control (interrupt, etc.)
107
+ handle = createAgentQueryHandle({
108
+ isInterruptingRef,
109
+ waitingForUserInputRef,
110
+ pendingToolCalls,
111
+ getQueryObject: () => queryObject,
112
+ });
113
+ // Create MCP server with CompleteIntegration tool
114
+ const completionMcpServer = createCompletionMcpServer(hasCompletedWorkRef, {
115
+ sessionId: options.sessionId,
116
+ accessToken,
117
+ orgId: config?.orgId ?? '',
118
+ installDir: agentConfig.workingDirectory,
119
+ });
120
+ // Define callbacks for persistent input
121
+ const handlePersistentSubmit = (message) => {
122
+ // Reset exit hint flag when user submits a message
123
+ exitHintShownRef.value = false;
124
+ if (isInterruptingRef.value) {
125
+ // Already interrupted - resolve the waiting promise with this message
126
+ logToFile('User submitted message after interrupt:', message);
127
+ pendingUserMessageRef.value = message;
128
+ if (resolveUserMessage) {
129
+ resolveUserMessage(message);
130
+ resolveUserMessage = null;
131
+ }
132
+ }
133
+ else {
134
+ // Not yet interrupted - store message and trigger interrupt
135
+ pendingUserMessageRef.value = message;
136
+ logToFile('User submitted message while agent running - triggering interrupt');
137
+ void handle.interrupt();
138
+ }
139
+ };
140
+ const handlePersistentInterrupt = () => {
141
+ logToFile('User requested interrupt (Esc)');
142
+ // Stop spinner immediately - persistent input stays visible
143
+ spinner.stop();
144
+ void handle.interrupt();
145
+ };
146
+ const handlePersistentCtrlC = () => {
147
+ logToFile('User pressed Ctrl+C');
148
+ // If already interrupted and exit hint was shown, exit immediately
149
+ if ((isInterruptingRef.value || waitingForUserInputRef.value) &&
150
+ exitHintShownRef.value) {
151
+ logToFile('Second Ctrl+C - exiting');
152
+ ui.stopPersistentInput();
153
+ ui.exit();
154
+ // Small delay to allow UI to clean up before exit
155
+ setTimeout(() => process.exit(130), 100);
156
+ return;
157
+ }
158
+ // If already interrupted but hint not shown yet, show hint and clear input
159
+ if (isInterruptingRef.value || waitingForUserInputRef.value) {
160
+ logToFile('First Ctrl+C while interrupted - showing exit hint');
161
+ exitHintShownRef.value = true;
162
+ // Clear the input and update placeholder with hint
163
+ ui.stopPersistentInput();
164
+ ui.startPersistentInput({
165
+ onSubmit: handlePersistentSubmit,
166
+ onInterrupt: handlePersistentInterrupt,
167
+ onCtrlC: handlePersistentCtrlC,
168
+ placeholder: 'Press Ctrl+C again to exit',
169
+ });
170
+ return;
171
+ }
172
+ // Not interrupted yet - treat as normal interrupt
173
+ logToFile('First Ctrl+C - triggering interrupt');
174
+ spinner.stop();
175
+ void handle.interrupt();
176
+ };
177
+ spinner.start(spinnerMessage);
178
+ ui.startPersistentInput({
179
+ onSubmit: handlePersistentSubmit,
180
+ onInterrupt: handlePersistentInterrupt,
181
+ onCtrlC: handlePersistentCtrlC,
182
+ });
183
+ // Session info for notifications
184
+ const sessionInfo = {
185
+ sessionId: options.sessionId,
186
+ accessToken,
187
+ orgId: config?.orgId ?? '',
188
+ };
189
+ queryObject = query({
190
+ prompt: currentPrompt,
191
+ options: {
192
+ model: agentConfig.model,
193
+ cwd: agentConfig.workingDirectory,
194
+ permissionMode: 'default',
195
+ mcpServers: {
196
+ 'raindrop-wizard': completionMcpServer,
197
+ },
198
+ systemPrompt: '{WIZARD_SYSTEM_PROMPT}',
199
+ env: { ...process.env },
200
+ resume: currentSessionId,
201
+ canUseTool: createCanUseToolHandler(sessionInfo, approvedFilesCache),
202
+ stderr: (data) => {
203
+ logToFile('CLI stderr:', data);
204
+ if (options.debug) {
205
+ debug('CLI stderr:', data);
206
+ }
207
+ },
208
+ },
209
+ });
210
+ // Update agent state
211
+ ui.setAgentState({
212
+ isRunning: true,
213
+ queryHandle: handle,
214
+ });
215
+ // Process the query stream
216
+ for await (const message of queryObject) {
217
+ // FIX: Check interrupt flag at start of each iteration
218
+ // This handles the case where interrupt() was called before SDK initialized
219
+ if (isInterruptingRef.value) {
220
+ logToFile('Breaking out of for-await loop - interrupt flag set before SDK init');
221
+ // Capture session_id from init message if available before breaking
222
+ if (message.session_id && !sessionId) {
223
+ sessionId = message.session_id;
224
+ ui.setAgentState({ sessionId });
225
+ }
226
+ break;
227
+ }
228
+ // Capture session_id from any message
229
+ if (message.session_id && !sessionId) {
230
+ sessionId = message.session_id;
231
+ ui.setAgentState({ sessionId });
232
+ }
233
+ processSDKMessage(message, options, collectedText, pendingToolCalls, isInterruptingRef.value);
234
+ }
235
+ const durationMs = Date.now() - startTime;
236
+ logToFile(`Agent run completed in ${Math.round(durationMs / 1000)}s`);
237
+ logToFile('Session ID for resuming:', sessionId);
238
+ logToFile('Completion status:', hasCompletedWorkRef.value);
239
+ // Check if we need user input to continue (either stream ended without completion or interrupted)
240
+ const needsUserInput = !hasCompletedWorkRef.value && !waitingForUserInputRef.value && sessionId;
241
+ const wasInterrupted = waitingForUserInputRef.value && sessionId;
242
+ if (needsUserInput || wasInterrupted) {
243
+ if (needsUserInput) {
244
+ logToFile('Stream ended but agent has not called CompleteIntegration - waiting for user response');
245
+ }
246
+ else {
247
+ logToFile('Stream ended after interrupt, waiting for user input');
248
+ }
249
+ spinner.stop();
250
+ ui.setAgentState({ isRunning: false });
251
+ // Get the resume message - either from pending submit or wait for user to submit
252
+ let userMessage;
253
+ if (pendingUserMessageRef.value) {
254
+ // Message was submitted during execution - use it directly
255
+ userMessage = pendingUserMessageRef.value;
256
+ pendingUserMessageRef.value = null; // Clear for next iteration
257
+ logToFile('Using pending user message:', userMessage);
258
+ }
259
+ else {
260
+ // No pending message - wait for user to submit via persistent input
261
+ logToFile('Waiting for user to submit message via persistent input...');
262
+ // Persistent input is already visible, spinner already stopped - just wait for user
263
+ userMessage = await new Promise((resolve) => {
264
+ resolveUserMessage = resolve;
265
+ });
266
+ // Check if user cancelled (empty message from Esc)
267
+ if (!userMessage) {
268
+ logToFile('User cancelled input, ending session');
269
+ ui.stopPersistentInput();
270
+ return { sessionId, handle };
271
+ }
272
+ logToFile('Received user message from persistent input:', userMessage);
273
+ }
274
+ // Show user message in UI
275
+ ui.addItem({
276
+ type: 'user-message',
277
+ text: userMessage,
278
+ });
279
+ // Update state for next iteration
280
+ logToFile('Resuming agent with user message:', userMessage);
281
+ currentPrompt = userMessage;
282
+ currentSessionId = sessionId;
283
+ continue;
284
+ }
285
+ // Normal completion - clean up and return
286
+ ui.stopPersistentInput();
287
+ ui.setAgentState({ isRunning: false });
288
+ spinner.stop(successMessage);
289
+ return { sessionId, handle };
290
+ }
291
+ }
292
+ //# sourceMappingURL=agent-interface.js.map