@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.
- package/dist/src/docs/claude-agent-sdk.mdx +382 -0
- package/dist/src/docs/{typescript.md → typescript.mdx} +8 -4
- package/dist/src/docs/vercel-ai-sdk.mdx +769 -0
- package/dist/src/lib/agent-interface.d.ts +4 -3
- package/dist/src/lib/agent-interface.js +290 -197
- package/dist/src/lib/agent-interface.js.map +1 -1
- package/dist/src/lib/constants.d.ts +1 -0
- package/dist/src/lib/constants.js +1 -0
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/handlers.d.ts +16 -8
- package/dist/src/lib/handlers.js +232 -118
- package/dist/src/lib/handlers.js.map +1 -1
- package/dist/src/lib/integration-testing.d.ts +5 -5
- package/dist/src/lib/integration-testing.js +28 -12
- package/dist/src/lib/integration-testing.js.map +1 -1
- package/dist/src/lib/mcp.d.ts +1 -1
- package/dist/src/lib/mcp.js +88 -49
- package/dist/src/lib/mcp.js.map +1 -1
- package/dist/src/lib/sdk-messages.d.ts +8 -1
- package/dist/src/lib/sdk-messages.js +83 -27
- package/dist/src/lib/sdk-messages.js.map +1 -1
- package/dist/src/lib/wizard.js +16 -20
- package/dist/src/lib/wizard.js.map +1 -1
- package/dist/src/ui/App.d.ts +5 -4
- package/dist/src/ui/App.js +12 -12
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +4 -2
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -1
- package/dist/src/ui/components/ContinuePrompt.d.ts +3 -2
- package/dist/src/ui/components/ContinuePrompt.js +4 -4
- package/dist/src/ui/components/ContinuePrompt.js.map +1 -1
- package/dist/src/ui/components/DiffDisplay.js +16 -6
- package/dist/src/ui/components/DiffDisplay.js.map +1 -1
- package/dist/src/ui/components/FeedbackSelectPrompt.js +6 -3
- package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.d.ts +5 -3
- package/dist/src/ui/components/HistoryItemDisplay.js +10 -9
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/Logo.js +19 -34
- package/dist/src/ui/components/Logo.js.map +1 -1
- package/dist/src/ui/components/OrgInfoBox.d.ts +2 -1
- package/dist/src/ui/components/OrgInfoBox.js +2 -4
- package/dist/src/ui/components/OrgInfoBox.js.map +1 -1
- package/dist/src/ui/components/PersistentTextInput.js +13 -11
- package/dist/src/ui/components/PersistentTextInput.js.map +1 -1
- package/dist/src/ui/components/PromptContainer.js +4 -8
- package/dist/src/ui/components/PromptContainer.js.map +1 -1
- package/dist/src/ui/components/ToolApprovalPrompt.js +4 -4
- package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.d.ts +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.js +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -1
- package/dist/src/ui/contexts/WizardContext.d.ts +13 -5
- package/dist/src/ui/contexts/WizardContext.js +60 -20
- package/dist/src/ui/contexts/WizardContext.js.map +1 -1
- package/dist/src/ui/render.js +49 -5
- package/dist/src/ui/render.js.map +1 -1
- package/dist/src/ui/types.d.ts +4 -2
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/utils/oauth.js +0 -4
- package/dist/src/utils/oauth.js.map +1 -1
- package/dist/src/utils/session.d.ts +1 -0
- package/dist/src/utils/session.js +40 -1
- package/dist/src/utils/session.js.map +1 -1
- package/dist/src/utils/ui.d.ts +7 -2
- package/dist/src/utils/ui.js +9 -2
- package/dist/src/utils/ui.js.map +1 -1
- package/package.json +2 -1
- package/dist/src/docs/vercel-ai-sdk.md +0 -304
- /package/dist/src/docs/{browser.md → browser.mdx} +0 -0
- /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
|
|
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
|
|
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
|
|
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 {
|
|
9
|
-
import {
|
|
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: '
|
|
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
|
|
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',
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
247
|
-
|
|
317
|
+
processSDKMessage(message, options, collectedText, pendingToolCalls, isInterruptingRef.value, { updateSpinner, baseSpinnerMessage: spinnerMessage });
|
|
318
|
+
if (message.type !== 'result') {
|
|
319
|
+
continue;
|
|
248
320
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|