@raindrop-ai/wizard 0.0.1 → 0.0.2

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 (71) hide show
  1. package/dist/src/docs/claude-agent-sdk.mdx +382 -0
  2. package/dist/src/docs/{typescript.md → typescript.mdx} +8 -4
  3. package/dist/src/docs/vercel-ai-sdk.mdx +769 -0
  4. package/dist/src/lib/agent-interface.d.ts +4 -3
  5. package/dist/src/lib/agent-interface.js +290 -197
  6. package/dist/src/lib/agent-interface.js.map +1 -1
  7. package/dist/src/lib/constants.d.ts +1 -0
  8. package/dist/src/lib/constants.js +1 -0
  9. package/dist/src/lib/constants.js.map +1 -1
  10. package/dist/src/lib/handlers.d.ts +16 -8
  11. package/dist/src/lib/handlers.js +232 -118
  12. package/dist/src/lib/handlers.js.map +1 -1
  13. package/dist/src/lib/integration-testing.d.ts +5 -5
  14. package/dist/src/lib/integration-testing.js +28 -12
  15. package/dist/src/lib/integration-testing.js.map +1 -1
  16. package/dist/src/lib/mcp.d.ts +1 -1
  17. package/dist/src/lib/mcp.js +88 -49
  18. package/dist/src/lib/mcp.js.map +1 -1
  19. package/dist/src/lib/sdk-messages.d.ts +8 -1
  20. package/dist/src/lib/sdk-messages.js +83 -27
  21. package/dist/src/lib/sdk-messages.js.map +1 -1
  22. package/dist/src/lib/wizard.js +16 -20
  23. package/dist/src/lib/wizard.js.map +1 -1
  24. package/dist/src/ui/App.d.ts +5 -4
  25. package/dist/src/ui/App.js +12 -12
  26. package/dist/src/ui/App.js.map +1 -1
  27. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +4 -2
  28. package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -1
  29. package/dist/src/ui/components/ContinuePrompt.d.ts +3 -2
  30. package/dist/src/ui/components/ContinuePrompt.js +4 -4
  31. package/dist/src/ui/components/ContinuePrompt.js.map +1 -1
  32. package/dist/src/ui/components/DiffDisplay.js +16 -6
  33. package/dist/src/ui/components/DiffDisplay.js.map +1 -1
  34. package/dist/src/ui/components/FeedbackSelectPrompt.js +6 -3
  35. package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -1
  36. package/dist/src/ui/components/HistoryItemDisplay.d.ts +5 -3
  37. package/dist/src/ui/components/HistoryItemDisplay.js +10 -9
  38. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  39. package/dist/src/ui/components/Logo.js +19 -34
  40. package/dist/src/ui/components/Logo.js.map +1 -1
  41. package/dist/src/ui/components/OrgInfoBox.d.ts +2 -1
  42. package/dist/src/ui/components/OrgInfoBox.js +2 -4
  43. package/dist/src/ui/components/OrgInfoBox.js.map +1 -1
  44. package/dist/src/ui/components/PersistentTextInput.js +13 -11
  45. package/dist/src/ui/components/PersistentTextInput.js.map +1 -1
  46. package/dist/src/ui/components/PromptContainer.js +4 -8
  47. package/dist/src/ui/components/PromptContainer.js.map +1 -1
  48. package/dist/src/ui/components/ToolApprovalPrompt.js +4 -4
  49. package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -1
  50. package/dist/src/ui/components/WriteKeyDisplay.d.ts +1 -1
  51. package/dist/src/ui/components/WriteKeyDisplay.js +1 -1
  52. package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -1
  53. package/dist/src/ui/contexts/WizardContext.d.ts +13 -5
  54. package/dist/src/ui/contexts/WizardContext.js +60 -20
  55. package/dist/src/ui/contexts/WizardContext.js.map +1 -1
  56. package/dist/src/ui/render.js +49 -5
  57. package/dist/src/ui/render.js.map +1 -1
  58. package/dist/src/ui/types.d.ts +4 -2
  59. package/dist/src/ui/types.js.map +1 -1
  60. package/dist/src/utils/oauth.js +0 -4
  61. package/dist/src/utils/oauth.js.map +1 -1
  62. package/dist/src/utils/session.d.ts +1 -0
  63. package/dist/src/utils/session.js +40 -1
  64. package/dist/src/utils/session.js.map +1 -1
  65. package/dist/src/utils/ui.d.ts +7 -2
  66. package/dist/src/utils/ui.js +9 -2
  67. package/dist/src/utils/ui.js.map +1 -1
  68. package/package.json +2 -1
  69. package/dist/src/docs/vercel-ai-sdk.md +0 -304
  70. /package/dist/src/docs/{browser.md → browser.mdx} +0 -0
  71. /package/dist/src/docs/{python.md → python.mdx} +0 -0
@@ -14,6 +14,7 @@ export type AgentConfig = {
14
14
  export interface AgentRunResult {
15
15
  sessionId?: string;
16
16
  handle: AgentQueryHandle;
17
+ completed: boolean;
17
18
  }
18
19
  /**
19
20
  * Internal configuration object returned by initializeAgent
@@ -32,15 +33,15 @@ export declare function initializeAgent(config: AgentConfig, options: WizardOpti
32
33
  export interface RunAgentConfig {
33
34
  spinnerMessage?: string;
34
35
  successMessage?: string;
35
- resume?: string;
36
36
  accessToken: string;
37
37
  orgId: string;
38
+ onCompleteIntegration?: () => Promise<boolean | string>;
38
39
  }
39
40
  /**
40
41
  * Execute an agent with the provided prompt and options.
41
42
  * Supports streaming input for user interruption and follow-up messages.
42
- * Uses a while loop to handle user interactions instead of recursion.
43
+ * Uses a single long-lived SDK query fed by an async message queue.
43
44
  *
44
45
  * @returns Session ID and query handle for controlling the agent
45
46
  */
46
- export declare function runAgentLoop(agentConfig: AgentRunConfig, prompt: string, options: WizardOptions, config?: RunAgentConfig): Promise<AgentRunResult>;
47
+ export declare function runAgentLoop(agentConfig: AgentRunConfig, prompt: string, options: WizardOptions, config: RunAgentConfig): Promise<AgentRunResult>;
@@ -2,14 +2,14 @@
2
2
  * Shared agent interface for wizards
3
3
  * Uses Claude Agent SDK directly with streaming input support
4
4
  */
5
- import path from 'path';
5
+ import { query } from '@anthropic-ai/claude-agent-sdk';
6
6
  import { createRequire } from 'module';
7
+ import path from 'path';
8
+ import { debug, initLogFile, LOG_FILE_PATH, logToFile, } from '../utils/debug.js';
7
9
  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 { createAgentQueryHandle, createCanUseToolHandler, createPreToolUseHook, } from './handlers.js';
11
+ import { createMcpServer } from './mcp.js';
10
12
  import { processSDKMessage } from './sdk-messages.js';
11
- import { createCompletionMcpServer } from './mcp.js';
12
- import { query } from '@anthropic-ai/claude-agent-sdk';
13
13
  // Create a require function for ESM compatibility
14
14
  const require = createRequire(import.meta.url);
15
15
  /**
@@ -21,6 +21,61 @@ function getClaudeCodeExecutablePath() {
21
21
  const sdkPackagePath = require.resolve('@anthropic-ai/claude-agent-sdk');
22
22
  return path.join(path.dirname(sdkPackagePath), 'cli.js');
23
23
  }
24
+ /**
25
+ * Async queue for user messages.
26
+ * Messages are pushed by UI callbacks and consumed by the SDK as an async iterable.
27
+ */
28
+ class UserMessageQueue {
29
+ messages = [];
30
+ waitingResolver = null;
31
+ closed = false;
32
+ push(content) {
33
+ if (this.closed) {
34
+ return;
35
+ }
36
+ const message = {
37
+ type: 'user',
38
+ message: {
39
+ role: 'user',
40
+ content,
41
+ },
42
+ };
43
+ if (this.waitingResolver) {
44
+ this.waitingResolver(message);
45
+ this.waitingResolver = null;
46
+ return;
47
+ }
48
+ this.messages.push(message);
49
+ }
50
+ hasPending() {
51
+ return this.messages.length > 0;
52
+ }
53
+ close() {
54
+ this.closed = true;
55
+ if (this.waitingResolver) {
56
+ this.waitingResolver(null);
57
+ this.waitingResolver = null;
58
+ }
59
+ }
60
+ async *[Symbol.asyncIterator]() {
61
+ while (!this.closed) {
62
+ if (this.messages.length > 0) {
63
+ const nextMessage = this.messages.shift();
64
+ if (nextMessage) {
65
+ yield nextMessage;
66
+ }
67
+ continue;
68
+ }
69
+ const nextMessage = await new Promise((resolve) => {
70
+ this.waitingResolver = resolve;
71
+ });
72
+ if (!nextMessage) {
73
+ return;
74
+ }
75
+ yield nextMessage;
76
+ }
77
+ }
78
+ }
24
79
  /**
25
80
  * Initialize agent configuration for the Claude agent
26
81
  */
@@ -30,12 +85,13 @@ export function initializeAgent(config, options) {
30
85
  logToFile('Agent initialization starting');
31
86
  logToFile('Install directory:', options.installDir);
32
87
  try {
88
+ process.env.MAX_THINKING_TOKENS = '10000';
33
89
  process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
34
90
  // Set default subagent model to Sonnet
35
91
  process.env.CLAUDE_CODE_SUBAGENT_MODEL = 'claude-sonnet-4-5-20250929';
36
92
  const agentRunConfig = {
37
93
  workingDirectory: config.workingDirectory,
38
- model: 'opus',
94
+ model: 'sonnet',
39
95
  };
40
96
  logToFile('Agent config:', {
41
97
  workingDirectory: agentRunConfig.workingDirectory,
@@ -64,229 +120,266 @@ export function initializeAgent(config, options) {
64
120
  /**
65
121
  * Execute an agent with the provided prompt and options.
66
122
  * Supports streaming input for user interruption and follow-up messages.
67
- * Uses a while loop to handle user interactions instead of recursion.
123
+ * Uses a single long-lived SDK query fed by an async message queue.
68
124
  *
69
125
  * @returns Session ID and query handle for controlling the agent
70
126
  */
71
127
  export async function runAgentLoop(agentConfig, prompt, options, config) {
72
- const { spinnerMessage = 'Raindrop wizard is working...', successMessage = 'Raindrop integration complete', resume, accessToken, } = config ?? {};
128
+ const { spinnerMessage = 'Raindrop wizard is working...', successMessage = 'Raindrop integration complete', accessToken, orgId, onCompleteIntegration, } = config;
73
129
  // Add header to indicate start of interactive agent phase
74
130
  ui.addItem({ type: 'phase', text: '─── Agent ───' });
75
131
  const cliPath = getClaudeCodeExecutablePath();
76
132
  logToFile('Starting agent run');
77
133
  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)
134
+ const startTime = Date.now();
135
+ const inputQueue = new UserMessageQueue();
136
+ inputQueue.push(prompt);
83
137
  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);
138
+ const collectedText = [];
139
+ const pendingToolCalls = new Map();
140
+ const hasCompletedWorkRef = { value: false };
141
+ const isInterruptingRef = { value: false };
142
+ const waitingForUserInputRef = { value: false };
143
+ const exitHintShownRef = { value: false };
144
+ let sessionId;
145
+ let queryObject = null;
146
+ let isSpinnerRunning = false;
147
+ let completed = false;
148
+ let shouldEndSession = false;
149
+ const spinner = ui.spinner();
150
+ // Track current spinner base message and when it last changed.
151
+ let currentSpinnerMsg = spinnerMessage;
152
+ let lastActivityTime = Date.now();
153
+ const updateSpinner = (msg) => {
154
+ if (!isSpinnerRunning)
155
+ return;
156
+ currentSpinnerMsg = msg;
157
+ lastActivityTime = Date.now();
158
+ spinner.message(msg);
159
+ };
160
+ // Every second, if the agent has been silent for >= 10s, append a counting thinking indicator.
161
+ // Pure JS interval — Ink only re-renders when the string actually changes.
162
+ const thinkingTimer = setInterval(() => {
163
+ if (!isSpinnerRunning)
164
+ return;
165
+ const idleS = Math.floor((Date.now() - lastActivityTime) / 1000);
166
+ if (idleS >= 20) {
167
+ const mins = Math.floor(idleS / 60);
168
+ const secs = idleS % 60;
169
+ const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
170
+ spinner.message(`${currentSpinnerMsg} (thinking · ${timeStr})`);
90
171
  }
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;
172
+ }, 1000);
173
+ const setAgentRunning = (isRunning) => {
174
+ if (isRunning) {
175
+ if (!isSpinnerRunning) {
176
+ spinner.start(spinnerMessage);
177
+ isSpinnerRunning = true;
157
178
  }
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');
179
+ ui.setAgentState({ isRunning: true });
180
+ return;
181
+ }
182
+ if (isSpinnerRunning) {
174
183
  spinner.stop();
175
- void handle.interrupt();
176
- };
177
- spinner.start(spinnerMessage);
184
+ isSpinnerRunning = false;
185
+ }
186
+ ui.setAgentState({ isRunning: false });
187
+ };
188
+ // Create MCP server with CompleteIntegration tool
189
+ const toolsMcpServer = createMcpServer(hasCompletedWorkRef, {
190
+ sessionId: options.sessionId,
191
+ accessToken,
192
+ orgId,
193
+ installDir: agentConfig.workingDirectory,
194
+ });
195
+ // Session info for notifications
196
+ const sessionInfo = {
197
+ sessionId: options.sessionId,
198
+ accessToken,
199
+ orgId,
200
+ };
201
+ const handle = createAgentQueryHandle({
202
+ isInterruptingRef,
203
+ waitingForUserInputRef,
204
+ pendingToolCalls,
205
+ getQueryObject: () => queryObject,
206
+ sendMessage: (message) => {
207
+ inputQueue.push(message);
208
+ },
209
+ });
210
+ const startPersistentInput = (placeholder) => {
178
211
  ui.startPersistentInput({
179
212
  onSubmit: handlePersistentSubmit,
180
213
  onInterrupt: handlePersistentInterrupt,
181
214
  onCtrlC: handlePersistentCtrlC,
215
+ placeholder,
182
216
  });
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,
217
+ };
218
+ // Define callbacks for persistent input
219
+ const handlePersistentSubmit = (message) => {
220
+ const trimmedMessage = message.trim();
221
+ if (!trimmedMessage) {
222
+ return;
223
+ }
224
+ exitHintShownRef.value = false;
225
+ ui.addItem({
226
+ type: 'user-message',
227
+ text: trimmedMessage,
214
228
  });
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 });
229
+ handle.sendMessage(trimmedMessage);
230
+ if (waitingForUserInputRef.value) {
231
+ waitingForUserInputRef.value = false;
232
+ isInterruptingRef.value = false;
233
+ setAgentRunning(true);
234
+ return;
235
+ }
236
+ if (!isInterruptingRef.value) {
237
+ logToFile('User submitted message while agent is running - interrupting current turn');
238
+ void handle.interrupt(true);
239
+ }
240
+ };
241
+ const handlePersistentInterrupt = () => {
242
+ logToFile('User requested interrupt (Esc)');
243
+ setAgentRunning(false);
244
+ void handle.interrupt();
245
+ };
246
+ const handlePersistentCtrlC = () => {
247
+ logToFile('User pressed Ctrl+C');
248
+ // If already interrupted and exit hint was shown, exit immediately
249
+ if ((isInterruptingRef.value || waitingForUserInputRef.value) &&
250
+ exitHintShownRef.value) {
251
+ logToFile('Second Ctrl+C - exiting');
252
+ ui.stopPersistentInput();
253
+ ui.exit();
254
+ // Small delay to allow UI to clean up before exit
255
+ setTimeout(() => process.exit(130), 100);
256
+ return;
257
+ }
258
+ // If already interrupted but hint not shown yet, show hint and clear input
259
+ if (isInterruptingRef.value || waitingForUserInputRef.value) {
260
+ logToFile('First Ctrl+C while interrupted - showing exit hint');
261
+ exitHintShownRef.value = true;
262
+ ui.stopPersistentInput();
263
+ startPersistentInput('Press Ctrl+C again to exit');
264
+ return;
265
+ }
266
+ // Not interrupted yet - treat as normal interrupt
267
+ logToFile('First Ctrl+C - triggering interrupt');
268
+ setAgentRunning(false);
269
+ void handle.interrupt();
270
+ };
271
+ startPersistentInput();
272
+ setAgentRunning(true);
273
+ queryObject = query({
274
+ prompt: inputQueue,
275
+ options: {
276
+ model: agentConfig.model,
277
+ cwd: agentConfig.workingDirectory,
278
+ permissionMode: 'default',
279
+ mcpServers: {
280
+ 'raindrop-wizard': toolsMcpServer,
281
+ },
282
+ systemPrompt: '{WIZARD_SYSTEM_PROMPT}',
283
+ env: { ...process.env },
284
+ canUseTool: createCanUseToolHandler(approvedFilesCache),
285
+ hooks: {
286
+ PreToolUse: [{ hooks: [createPreToolUseHook(sessionInfo)] }],
287
+ },
288
+ stderr: (data) => {
289
+ logToFile('CLI stderr:', data);
290
+ if (options.debug) {
291
+ debug('CLI stderr:', data);
225
292
  }
226
- break;
227
- }
293
+ },
294
+ },
295
+ });
296
+ // Update agent state
297
+ ui.setAgentState({
298
+ isRunning: true,
299
+ queryHandle: handle,
300
+ });
301
+ try {
302
+ for await (const message of queryObject) {
228
303
  // Capture session_id from any message
229
304
  if (message.session_id && !sessionId) {
230
305
  sessionId = message.session_id;
231
306
  ui.setAgentState({ sessionId });
232
307
  }
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');
308
+ // If we were waiting for user input but got a new message,
309
+ // the queued message has started processing.
310
+ if (waitingForUserInputRef.value &&
311
+ !isInterruptingRef.value &&
312
+ message.type !== 'result') {
313
+ waitingForUserInputRef.value = false;
314
+ isInterruptingRef.value = false;
315
+ setAgentRunning(true);
245
316
  }
246
- else {
247
- logToFile('Stream ended after interrupt, waiting for user input');
317
+ processSDKMessage(message, options, collectedText, pendingToolCalls, isInterruptingRef.value, { updateSpinner, baseSpinnerMessage: spinnerMessage });
318
+ if (message.type !== 'result') {
319
+ continue;
248
320
  }
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);
321
+ if (hasCompletedWorkRef.value) {
322
+ setAgentRunning(false);
323
+ ui.stopPersistentInput();
324
+ const completionDecision = onCompleteIntegration
325
+ ? await onCompleteIntegration()
326
+ : true;
327
+ if (typeof completionDecision === 'string') {
328
+ const feedbackPrompt = completionDecision.trim();
329
+ if (!feedbackPrompt) {
330
+ logToFile('Received empty feedback prompt - ending session');
331
+ completed = false;
332
+ shouldEndSession = true;
333
+ inputQueue.close();
334
+ break;
335
+ }
336
+ logToFile('Received testing feedback - queueing feedback prompt');
337
+ hasCompletedWorkRef.value = false;
338
+ waitingForUserInputRef.value = false;
339
+ isInterruptingRef.value = false;
340
+ handle.sendMessage(feedbackPrompt);
341
+ startPersistentInput();
342
+ setAgentRunning(true);
343
+ continue;
344
+ }
345
+ completed = completionDecision;
346
+ shouldEndSession = true;
347
+ inputQueue.close();
348
+ break;
349
+ }
350
+ // Turn ended without completion. If there are no queued messages,
351
+ // we are waiting for the user to submit the next one.
352
+ isInterruptingRef.value = false;
353
+ if (!inputQueue.hasPending()) {
354
+ waitingForUserInputRef.value = true;
355
+ setAgentRunning(false);
258
356
  }
259
357
  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);
358
+ waitingForUserInputRef.value = false;
359
+ setAgentRunning(true);
273
360
  }
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
361
  }
285
- // Normal completion - clean up and return
286
- ui.stopPersistentInput();
287
- ui.setAgentState({ isRunning: false });
362
+ }
363
+ finally {
364
+ inputQueue.close();
365
+ clearInterval(thinkingTimer);
366
+ }
367
+ const durationMs = Date.now() - startTime;
368
+ logToFile(`Agent session completed in ${Math.round(durationMs / 1000)}s`);
369
+ logToFile('Session ID:', sessionId);
370
+ logToFile('Completion status:', hasCompletedWorkRef.value);
371
+ if (!shouldEndSession && hasCompletedWorkRef.value) {
372
+ // Fallback: if stream ended after completion, mark as completed.
373
+ completed = true;
374
+ }
375
+ ui.stopPersistentInput();
376
+ ui.setAgentState({ isRunning: false });
377
+ if (completed) {
288
378
  spinner.stop(successMessage);
289
- return { sessionId, handle };
290
379
  }
380
+ else if (isSpinnerRunning) {
381
+ spinner.stop();
382
+ }
383
+ return { sessionId, handle, completed };
291
384
  }
292
385
  //# sourceMappingURL=agent-interface.js.map