@mindstudio-ai/remy 0.1.35 → 0.1.36
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/headless.js +436 -209
- package/dist/index.js +450 -215
- package/dist/prompt/compiled/media-cdn.md +1 -1
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -2109,7 +2109,7 @@ var runMethodTool = {
|
|
|
2109
2109
|
};
|
|
2110
2110
|
|
|
2111
2111
|
// src/tools/_helpers/screenshot.ts
|
|
2112
|
-
var SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).";
|
|
2112
|
+
var SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
2113
2113
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2114
2114
|
let prompt;
|
|
2115
2115
|
let onLog;
|
|
@@ -2167,6 +2167,85 @@ var screenshotTool = {
|
|
|
2167
2167
|
}
|
|
2168
2168
|
};
|
|
2169
2169
|
|
|
2170
|
+
// src/statusWatcher.ts
|
|
2171
|
+
function startStatusWatcher(config) {
|
|
2172
|
+
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
2173
|
+
let lastLabel = "";
|
|
2174
|
+
let inflight = false;
|
|
2175
|
+
let stopped = false;
|
|
2176
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
2177
|
+
async function tick() {
|
|
2178
|
+
if (stopped || signal?.aborted || inflight) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
inflight = true;
|
|
2182
|
+
try {
|
|
2183
|
+
const ctx = getContext();
|
|
2184
|
+
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
2185
|
+
log.debug("Status watcher: no context, skipping");
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
log.debug("Status watcher: requesting label", {
|
|
2189
|
+
textLength: ctx.assistantText.length,
|
|
2190
|
+
lastToolName: ctx.lastToolName
|
|
2191
|
+
});
|
|
2192
|
+
const res = await fetch(url, {
|
|
2193
|
+
method: "POST",
|
|
2194
|
+
headers: {
|
|
2195
|
+
"Content-Type": "application/json",
|
|
2196
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
2197
|
+
},
|
|
2198
|
+
body: JSON.stringify({
|
|
2199
|
+
assistantText: ctx.assistantText.slice(-500),
|
|
2200
|
+
lastToolName: ctx.lastToolName,
|
|
2201
|
+
lastToolResult: ctx.lastToolResult?.slice(-200),
|
|
2202
|
+
onboardingState: ctx.onboardingState,
|
|
2203
|
+
userMessage: ctx.userMessage?.slice(-200)
|
|
2204
|
+
}),
|
|
2205
|
+
signal
|
|
2206
|
+
});
|
|
2207
|
+
if (!res.ok) {
|
|
2208
|
+
log.debug("Status watcher: endpoint returned non-ok", {
|
|
2209
|
+
status: res.status
|
|
2210
|
+
});
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const data = await res.json();
|
|
2214
|
+
if (!data.label) {
|
|
2215
|
+
log.debug("Status watcher: no label in response");
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (data.label === lastLabel) {
|
|
2219
|
+
log.debug("Status watcher: duplicate label, skipping", {
|
|
2220
|
+
label: data.label
|
|
2221
|
+
});
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
lastLabel = data.label;
|
|
2225
|
+
if (stopped) {
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
log.debug("Status watcher: emitting", { label: data.label });
|
|
2229
|
+
onStatus(data.label);
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
2232
|
+
} finally {
|
|
2233
|
+
inflight = false;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
const timer = setInterval(tick, interval);
|
|
2237
|
+
tick().catch(() => {
|
|
2238
|
+
});
|
|
2239
|
+
log.debug("Status watcher started", { interval });
|
|
2240
|
+
return {
|
|
2241
|
+
stop() {
|
|
2242
|
+
stopped = true;
|
|
2243
|
+
clearInterval(timer);
|
|
2244
|
+
log.debug("Status watcher stopped");
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2170
2249
|
// src/subagents/common/cleanMessages.ts
|
|
2171
2250
|
function cleanMessagesForApi(messages) {
|
|
2172
2251
|
return messages.map((msg) => {
|
|
@@ -2210,19 +2289,47 @@ async function runSubAgent(config) {
|
|
|
2210
2289
|
signal,
|
|
2211
2290
|
parentToolId,
|
|
2212
2291
|
onEvent,
|
|
2213
|
-
resolveExternalTool
|
|
2292
|
+
resolveExternalTool,
|
|
2293
|
+
toolRegistry
|
|
2214
2294
|
} = config;
|
|
2215
2295
|
const emit2 = (e) => {
|
|
2216
2296
|
onEvent({ ...e, parentToolId });
|
|
2217
2297
|
};
|
|
2218
2298
|
const messages = [{ role: "user", content: task }];
|
|
2299
|
+
function getPartialText(blocks) {
|
|
2300
|
+
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2301
|
+
}
|
|
2302
|
+
function abortResult(blocks) {
|
|
2303
|
+
if (signal?.reason === "graceful") {
|
|
2304
|
+
const partial = getPartialText(blocks);
|
|
2305
|
+
return {
|
|
2306
|
+
text: partial ? `[INTERRUPTED]
|
|
2307
|
+
|
|
2308
|
+
${partial}` : "[INTERRUPTED] Sub-agent was interrupted before producing output.",
|
|
2309
|
+
messages
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
return { text: "Error: cancelled", messages };
|
|
2313
|
+
}
|
|
2314
|
+
let lastToolResult = "";
|
|
2219
2315
|
while (true) {
|
|
2220
2316
|
if (signal?.aborted) {
|
|
2221
|
-
return
|
|
2317
|
+
return abortResult([]);
|
|
2222
2318
|
}
|
|
2223
2319
|
const contentBlocks = [];
|
|
2224
2320
|
let thinkingStartedAt = 0;
|
|
2225
2321
|
let stopReason = "end_turn";
|
|
2322
|
+
let currentToolNames = "";
|
|
2323
|
+
const statusWatcher = startStatusWatcher({
|
|
2324
|
+
apiConfig,
|
|
2325
|
+
getContext: () => ({
|
|
2326
|
+
assistantText: getPartialText(contentBlocks),
|
|
2327
|
+
lastToolName: currentToolNames || void 0,
|
|
2328
|
+
lastToolResult: lastToolResult || void 0
|
|
2329
|
+
}),
|
|
2330
|
+
onStatus: (label) => emit2({ type: "status", message: label }),
|
|
2331
|
+
signal
|
|
2332
|
+
});
|
|
2226
2333
|
const fullSystem = `${system}
|
|
2227
2334
|
|
|
2228
2335
|
Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
|
|
@@ -2298,7 +2405,8 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2298
2405
|
}
|
|
2299
2406
|
}
|
|
2300
2407
|
if (signal?.aborted) {
|
|
2301
|
-
|
|
2408
|
+
statusWatcher.stop();
|
|
2409
|
+
return abortResult(contentBlocks);
|
|
2302
2410
|
}
|
|
2303
2411
|
messages.push({
|
|
2304
2412
|
role: "assistant",
|
|
@@ -2308,6 +2416,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2308
2416
|
(b) => b.type === "tool"
|
|
2309
2417
|
);
|
|
2310
2418
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
2419
|
+
statusWatcher.stop();
|
|
2311
2420
|
const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2312
2421
|
return { text, messages };
|
|
2313
2422
|
}
|
|
@@ -2316,46 +2425,82 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2316
2425
|
count: toolCalls.length,
|
|
2317
2426
|
tools: toolCalls.map((tc) => tc.name)
|
|
2318
2427
|
});
|
|
2428
|
+
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
2319
2429
|
const results = await Promise.all(
|
|
2320
2430
|
toolCalls.map(async (tc) => {
|
|
2321
2431
|
if (signal?.aborted) {
|
|
2322
2432
|
return { id: tc.id, result: "Error: cancelled", isError: true };
|
|
2323
2433
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2434
|
+
let settle;
|
|
2435
|
+
const resultPromise = new Promise((res) => {
|
|
2436
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
2437
|
+
});
|
|
2438
|
+
let toolAbort = new AbortController();
|
|
2439
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
2440
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
2441
|
+
let settled = false;
|
|
2442
|
+
const safeSettle = (result, isError) => {
|
|
2443
|
+
if (settled) {
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
settled = true;
|
|
2447
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
2448
|
+
settle(result, isError);
|
|
2449
|
+
};
|
|
2450
|
+
const run = async (input) => {
|
|
2451
|
+
try {
|
|
2452
|
+
let result;
|
|
2453
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
2454
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
2455
|
+
} else {
|
|
2456
|
+
const onLog = (line) => emit2({
|
|
2457
|
+
type: "tool_input_delta",
|
|
2458
|
+
id: tc.id,
|
|
2459
|
+
name: tc.name,
|
|
2460
|
+
result: line
|
|
2461
|
+
});
|
|
2462
|
+
result = await executeTool2(tc.name, input, tc.id, onLog);
|
|
2463
|
+
}
|
|
2464
|
+
safeSettle(result, result.startsWith("Error"));
|
|
2465
|
+
} catch (err) {
|
|
2466
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2469
|
+
const entry = {
|
|
2470
|
+
id: tc.id,
|
|
2471
|
+
name: tc.name,
|
|
2472
|
+
input: tc.input,
|
|
2473
|
+
parentToolId,
|
|
2474
|
+
abortController: toolAbort,
|
|
2475
|
+
startedAt: Date.now(),
|
|
2476
|
+
settle: safeSettle,
|
|
2477
|
+
rerun: (newInput) => {
|
|
2478
|
+
settled = false;
|
|
2479
|
+
toolAbort = new AbortController();
|
|
2480
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
2481
|
+
once: true
|
|
2334
2482
|
});
|
|
2335
|
-
|
|
2483
|
+
entry.abortController = toolAbort;
|
|
2484
|
+
entry.input = newInput;
|
|
2485
|
+
run(newInput);
|
|
2336
2486
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
id: tc.id,
|
|
2351
|
-
name: tc.name,
|
|
2352
|
-
result: errorMsg,
|
|
2353
|
-
isError: true
|
|
2354
|
-
});
|
|
2355
|
-
return { id: tc.id, result: errorMsg, isError: true };
|
|
2356
|
-
}
|
|
2487
|
+
};
|
|
2488
|
+
toolRegistry?.register(entry);
|
|
2489
|
+
run(tc.input);
|
|
2490
|
+
const r = await resultPromise;
|
|
2491
|
+
toolRegistry?.unregister(tc.id);
|
|
2492
|
+
emit2({
|
|
2493
|
+
type: "tool_done",
|
|
2494
|
+
id: tc.id,
|
|
2495
|
+
name: tc.name,
|
|
2496
|
+
result: r.result,
|
|
2497
|
+
isError: r.isError
|
|
2498
|
+
});
|
|
2499
|
+
return r;
|
|
2357
2500
|
})
|
|
2358
2501
|
);
|
|
2502
|
+
statusWatcher.stop();
|
|
2503
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
2359
2504
|
for (const r of results) {
|
|
2360
2505
|
const block = contentBlocks.find(
|
|
2361
2506
|
(b) => b.type === "tool" && b.id === r.id
|
|
@@ -2363,6 +2508,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
|
|
|
2363
2508
|
if (block?.type === "tool") {
|
|
2364
2509
|
block.result = r.result;
|
|
2365
2510
|
block.isError = r.isError;
|
|
2511
|
+
block.completedAt = Date.now();
|
|
2366
2512
|
}
|
|
2367
2513
|
messages.push({
|
|
2368
2514
|
role: "user",
|
|
@@ -2594,7 +2740,8 @@ var browserAutomationTool = {
|
|
|
2594
2740
|
}
|
|
2595
2741
|
}
|
|
2596
2742
|
return result2;
|
|
2597
|
-
}
|
|
2743
|
+
},
|
|
2744
|
+
toolRegistry: context.toolRegistry
|
|
2598
2745
|
});
|
|
2599
2746
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
2600
2747
|
return result.text;
|
|
@@ -2684,7 +2831,7 @@ Brief description of the types used on the page. If you can identify the actual
|
|
|
2684
2831
|
## Techniques
|
|
2685
2832
|
Identify the specific design moves that make this page interesting and unique, described in terms of how a designer with a technical background would write them down as notes in their notebook for inspiration. Focus only on the non-obvious, hard-to-think-of techniques \u2014 the things that make this page gallery-worthy. Skip basics like "high contrast CTA" or "generous whitespace" that any competent designer already knows.
|
|
2686
2833
|
|
|
2687
|
-
Respond only with
|
|
2834
|
+
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
|
|
2688
2835
|
`;
|
|
2689
2836
|
var definition3 = {
|
|
2690
2837
|
name: "analyzeDesign",
|
|
@@ -2734,7 +2881,7 @@ __export(analyzeImage_exports, {
|
|
|
2734
2881
|
definition: () => definition4,
|
|
2735
2882
|
execute: () => execute4
|
|
2736
2883
|
});
|
|
2737
|
-
var DEFAULT_PROMPT = "Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).";
|
|
2884
|
+
var DEFAULT_PROMPT = "Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprhensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
2738
2885
|
var definition4 = {
|
|
2739
2886
|
name: "analyzeImage",
|
|
2740
2887
|
description: "Analyze an image by URL. Returns a detailed description of everything visible. Provide a custom prompt to ask a specific question instead of the default full description.",
|
|
@@ -2801,7 +2948,7 @@ __export(generateImages_exports, {
|
|
|
2801
2948
|
});
|
|
2802
2949
|
|
|
2803
2950
|
// src/subagents/designExpert/tools/_seedream.ts
|
|
2804
|
-
var ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical.";
|
|
2951
|
+
var ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical. Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
|
|
2805
2952
|
async function seedreamGenerate(opts) {
|
|
2806
2953
|
const { prompts, sourceImages, transparentBackground, onLog } = opts;
|
|
2807
2954
|
const width = opts.width || 2048;
|
|
@@ -2841,7 +2988,7 @@ async function seedreamGenerate(opts) {
|
|
|
2841
2988
|
);
|
|
2842
2989
|
try {
|
|
2843
2990
|
const parsed = JSON.parse(batchResult);
|
|
2844
|
-
imageUrls = parsed.
|
|
2991
|
+
imageUrls = parsed.map(
|
|
2845
2992
|
(r) => r.output?.imageUrl ?? `Error: ${r.error}`
|
|
2846
2993
|
);
|
|
2847
2994
|
} catch {
|
|
@@ -3232,7 +3379,8 @@ var designExpertTool = {
|
|
|
3232
3379
|
signal: context.signal,
|
|
3233
3380
|
parentToolId: context.toolCallId,
|
|
3234
3381
|
onEvent: context.onEvent,
|
|
3235
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3382
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3383
|
+
toolRegistry: context.toolRegistry
|
|
3236
3384
|
});
|
|
3237
3385
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3238
3386
|
return result.text;
|
|
@@ -3514,7 +3662,8 @@ var productVisionTool = {
|
|
|
3514
3662
|
signal: context.signal,
|
|
3515
3663
|
parentToolId: context.toolCallId,
|
|
3516
3664
|
onEvent: context.onEvent,
|
|
3517
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3665
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3666
|
+
toolRegistry: context.toolRegistry
|
|
3518
3667
|
});
|
|
3519
3668
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3520
3669
|
return result.text;
|
|
@@ -3651,7 +3800,8 @@ var codeSanityCheckTool = {
|
|
|
3651
3800
|
signal: context.signal,
|
|
3652
3801
|
parentToolId: context.toolCallId,
|
|
3653
3802
|
onEvent: context.onEvent,
|
|
3654
|
-
resolveExternalTool: context.resolveExternalTool
|
|
3803
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
3804
|
+
toolRegistry: context.toolRegistry
|
|
3655
3805
|
});
|
|
3656
3806
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3657
3807
|
return result.text;
|
|
@@ -3971,85 +4121,6 @@ function parsePartialJson(jsonString) {
|
|
|
3971
4121
|
return parseAny();
|
|
3972
4122
|
}
|
|
3973
4123
|
|
|
3974
|
-
// src/statusWatcher.ts
|
|
3975
|
-
function startStatusWatcher(config) {
|
|
3976
|
-
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
3977
|
-
let lastLabel = "";
|
|
3978
|
-
let inflight = false;
|
|
3979
|
-
let stopped = false;
|
|
3980
|
-
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3981
|
-
async function tick() {
|
|
3982
|
-
if (stopped || signal?.aborted || inflight) {
|
|
3983
|
-
return;
|
|
3984
|
-
}
|
|
3985
|
-
inflight = true;
|
|
3986
|
-
try {
|
|
3987
|
-
const ctx = getContext();
|
|
3988
|
-
if (!ctx.assistantText && !ctx.lastToolName) {
|
|
3989
|
-
log.debug("Status watcher: no context, skipping");
|
|
3990
|
-
return;
|
|
3991
|
-
}
|
|
3992
|
-
log.debug("Status watcher: requesting label", {
|
|
3993
|
-
textLength: ctx.assistantText.length,
|
|
3994
|
-
lastToolName: ctx.lastToolName
|
|
3995
|
-
});
|
|
3996
|
-
const res = await fetch(url, {
|
|
3997
|
-
method: "POST",
|
|
3998
|
-
headers: {
|
|
3999
|
-
"Content-Type": "application/json",
|
|
4000
|
-
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
4001
|
-
},
|
|
4002
|
-
body: JSON.stringify({
|
|
4003
|
-
assistantText: ctx.assistantText.slice(-500),
|
|
4004
|
-
lastToolName: ctx.lastToolName,
|
|
4005
|
-
lastToolResult: ctx.lastToolResult?.slice(-200),
|
|
4006
|
-
onboardingState: ctx.onboardingState,
|
|
4007
|
-
userMessage: ctx.userMessage?.slice(-200)
|
|
4008
|
-
}),
|
|
4009
|
-
signal
|
|
4010
|
-
});
|
|
4011
|
-
if (!res.ok) {
|
|
4012
|
-
log.debug("Status watcher: endpoint returned non-ok", {
|
|
4013
|
-
status: res.status
|
|
4014
|
-
});
|
|
4015
|
-
return;
|
|
4016
|
-
}
|
|
4017
|
-
const data = await res.json();
|
|
4018
|
-
if (!data.label) {
|
|
4019
|
-
log.debug("Status watcher: no label in response");
|
|
4020
|
-
return;
|
|
4021
|
-
}
|
|
4022
|
-
if (data.label === lastLabel) {
|
|
4023
|
-
log.debug("Status watcher: duplicate label, skipping", {
|
|
4024
|
-
label: data.label
|
|
4025
|
-
});
|
|
4026
|
-
return;
|
|
4027
|
-
}
|
|
4028
|
-
lastLabel = data.label;
|
|
4029
|
-
if (stopped) {
|
|
4030
|
-
return;
|
|
4031
|
-
}
|
|
4032
|
-
log.debug("Status watcher: emitting", { label: data.label });
|
|
4033
|
-
onStatus(data.label);
|
|
4034
|
-
} catch (err) {
|
|
4035
|
-
log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
|
|
4036
|
-
} finally {
|
|
4037
|
-
inflight = false;
|
|
4038
|
-
}
|
|
4039
|
-
}
|
|
4040
|
-
const timer = setInterval(tick, interval);
|
|
4041
|
-
tick().catch(() => {
|
|
4042
|
-
});
|
|
4043
|
-
log.debug("Status watcher started", { interval });
|
|
4044
|
-
return {
|
|
4045
|
-
stop() {
|
|
4046
|
-
stopped = true;
|
|
4047
|
-
clearInterval(timer);
|
|
4048
|
-
log.debug("Status watcher stopped");
|
|
4049
|
-
}
|
|
4050
|
-
};
|
|
4051
|
-
}
|
|
4052
|
-
|
|
4053
4124
|
// src/errors.ts
|
|
4054
4125
|
var patterns = [
|
|
4055
4126
|
[
|
|
@@ -4113,7 +4184,8 @@ async function runTurn(params) {
|
|
|
4113
4184
|
signal,
|
|
4114
4185
|
onEvent,
|
|
4115
4186
|
resolveExternalTool,
|
|
4116
|
-
hidden
|
|
4187
|
+
hidden,
|
|
4188
|
+
toolRegistry
|
|
4117
4189
|
} = params;
|
|
4118
4190
|
const tools2 = getToolDefinitions(onboardingState);
|
|
4119
4191
|
log.info("Turn started", {
|
|
@@ -4166,6 +4238,20 @@ async function runTurn(params) {
|
|
|
4166
4238
|
let thinkingStartedAt = 0;
|
|
4167
4239
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
4168
4240
|
let stopReason = "end_turn";
|
|
4241
|
+
let subAgentText = "";
|
|
4242
|
+
let currentToolNames = "";
|
|
4243
|
+
const statusWatcher = startStatusWatcher({
|
|
4244
|
+
apiConfig,
|
|
4245
|
+
getContext: () => ({
|
|
4246
|
+
assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
|
|
4247
|
+
lastToolName: currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
|
|
4248
|
+
lastToolResult: lastCompletedResult || void 0,
|
|
4249
|
+
onboardingState,
|
|
4250
|
+
userMessage
|
|
4251
|
+
}),
|
|
4252
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
4253
|
+
signal
|
|
4254
|
+
});
|
|
4169
4255
|
async function handlePartialInput(acc, id, name, partial) {
|
|
4170
4256
|
const tool = getToolByName(name);
|
|
4171
4257
|
if (!tool?.streaming) {
|
|
@@ -4229,18 +4315,6 @@ async function runTurn(params) {
|
|
|
4229
4315
|
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
4230
4316
|
}
|
|
4231
4317
|
}
|
|
4232
|
-
const statusWatcher = startStatusWatcher({
|
|
4233
|
-
apiConfig,
|
|
4234
|
-
getContext: () => ({
|
|
4235
|
-
assistantText: getTextContent(contentBlocks).slice(-500),
|
|
4236
|
-
lastToolName: getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools || void 0,
|
|
4237
|
-
lastToolResult: lastCompletedResult || void 0,
|
|
4238
|
-
onboardingState,
|
|
4239
|
-
userMessage
|
|
4240
|
-
}),
|
|
4241
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
4242
|
-
signal
|
|
4243
|
-
});
|
|
4244
4318
|
try {
|
|
4245
4319
|
for await (const event of streamChatWithRetry(
|
|
4246
4320
|
{
|
|
@@ -4361,10 +4435,9 @@ async function runTurn(params) {
|
|
|
4361
4435
|
} else {
|
|
4362
4436
|
throw err;
|
|
4363
4437
|
}
|
|
4364
|
-
} finally {
|
|
4365
|
-
statusWatcher.stop();
|
|
4366
4438
|
}
|
|
4367
4439
|
if (signal?.aborted) {
|
|
4440
|
+
statusWatcher.stop();
|
|
4368
4441
|
if (contentBlocks.length > 0) {
|
|
4369
4442
|
contentBlocks.push({
|
|
4370
4443
|
type: "text",
|
|
@@ -4386,6 +4459,7 @@ async function runTurn(params) {
|
|
|
4386
4459
|
});
|
|
4387
4460
|
const toolCalls = getToolCalls(contentBlocks);
|
|
4388
4461
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4462
|
+
statusWatcher.stop();
|
|
4389
4463
|
saveSession(state);
|
|
4390
4464
|
onEvent({ type: "turn_done" });
|
|
4391
4465
|
return;
|
|
@@ -4394,8 +4468,7 @@ async function runTurn(params) {
|
|
|
4394
4468
|
count: toolCalls.length,
|
|
4395
4469
|
tools: toolCalls.map((tc) => tc.name)
|
|
4396
4470
|
});
|
|
4397
|
-
|
|
4398
|
-
const origOnEvent = onEvent;
|
|
4471
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
4399
4472
|
const wrappedOnEvent = (e) => {
|
|
4400
4473
|
if ("parentToolId" in e && e.parentToolId) {
|
|
4401
4474
|
if (e.type === "text") {
|
|
@@ -4404,86 +4477,103 @@ async function runTurn(params) {
|
|
|
4404
4477
|
subAgentText = `Using ${e.name}`;
|
|
4405
4478
|
}
|
|
4406
4479
|
}
|
|
4407
|
-
|
|
4480
|
+
onEvent(e);
|
|
4408
4481
|
};
|
|
4409
|
-
const toolStatusWatcher = startStatusWatcher({
|
|
4410
|
-
apiConfig,
|
|
4411
|
-
getContext: () => ({
|
|
4412
|
-
assistantText: subAgentText || getTextContent(contentBlocks).slice(-500),
|
|
4413
|
-
lastToolName: toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ") || void 0,
|
|
4414
|
-
lastToolResult: lastCompletedResult || void 0,
|
|
4415
|
-
onboardingState,
|
|
4416
|
-
userMessage
|
|
4417
|
-
}),
|
|
4418
|
-
onStatus: (label) => origOnEvent({ type: "status", message: label }),
|
|
4419
|
-
signal
|
|
4420
|
-
});
|
|
4421
4482
|
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
4422
4483
|
const results = await Promise.all(
|
|
4423
4484
|
toolCalls.map(async (tc) => {
|
|
4424
4485
|
if (signal?.aborted) {
|
|
4425
|
-
return {
|
|
4426
|
-
id: tc.id,
|
|
4427
|
-
result: "Error: cancelled",
|
|
4428
|
-
isError: true
|
|
4429
|
-
};
|
|
4486
|
+
return { id: tc.id, result: "Error: cancelled", isError: true };
|
|
4430
4487
|
}
|
|
4431
4488
|
const toolStart = Date.now();
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4489
|
+
let settle;
|
|
4490
|
+
const resultPromise = new Promise((res) => {
|
|
4491
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4492
|
+
});
|
|
4493
|
+
let toolAbort = new AbortController();
|
|
4494
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
4495
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4496
|
+
let settled = false;
|
|
4497
|
+
const safeSettle = (result, isError) => {
|
|
4498
|
+
if (settled) {
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
settled = true;
|
|
4502
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
4503
|
+
settle(result, isError);
|
|
4504
|
+
};
|
|
4505
|
+
const run = async (input) => {
|
|
4506
|
+
try {
|
|
4507
|
+
let result;
|
|
4508
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
4509
|
+
saveSession(state);
|
|
4510
|
+
log.info("Waiting for external tool result", {
|
|
4453
4511
|
name: tc.name,
|
|
4454
|
-
|
|
4455
|
-
})
|
|
4512
|
+
id: tc.id
|
|
4513
|
+
});
|
|
4514
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4515
|
+
} else {
|
|
4516
|
+
result = await executeTool(tc.name, input, {
|
|
4517
|
+
apiConfig,
|
|
4518
|
+
model,
|
|
4519
|
+
signal: toolAbort.signal,
|
|
4520
|
+
onEvent: wrappedOnEvent,
|
|
4521
|
+
resolveExternalTool,
|
|
4522
|
+
toolCallId: tc.id,
|
|
4523
|
+
subAgentMessages,
|
|
4524
|
+
toolRegistry,
|
|
4525
|
+
onLog: (line) => wrappedOnEvent({
|
|
4526
|
+
type: "tool_input_delta",
|
|
4527
|
+
id: tc.id,
|
|
4528
|
+
name: tc.name,
|
|
4529
|
+
result: line
|
|
4530
|
+
})
|
|
4531
|
+
});
|
|
4532
|
+
}
|
|
4533
|
+
safeSettle(result, result.startsWith("Error"));
|
|
4534
|
+
} catch (err) {
|
|
4535
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
const entry = {
|
|
4539
|
+
id: tc.id,
|
|
4540
|
+
name: tc.name,
|
|
4541
|
+
input: tc.input,
|
|
4542
|
+
abortController: toolAbort,
|
|
4543
|
+
startedAt: toolStart,
|
|
4544
|
+
settle: safeSettle,
|
|
4545
|
+
rerun: (newInput) => {
|
|
4546
|
+
settled = false;
|
|
4547
|
+
toolAbort = new AbortController();
|
|
4548
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4549
|
+
once: true
|
|
4456
4550
|
});
|
|
4551
|
+
entry.abortController = toolAbort;
|
|
4552
|
+
entry.input = newInput;
|
|
4553
|
+
run(newInput);
|
|
4457
4554
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
id: tc.id,
|
|
4478
|
-
name: tc.name,
|
|
4479
|
-
result: errorMsg,
|
|
4480
|
-
isError: true
|
|
4481
|
-
});
|
|
4482
|
-
return { id: tc.id, result: errorMsg, isError: true };
|
|
4483
|
-
}
|
|
4555
|
+
};
|
|
4556
|
+
toolRegistry?.register(entry);
|
|
4557
|
+
run(tc.input);
|
|
4558
|
+
const r = await resultPromise;
|
|
4559
|
+
toolRegistry?.unregister(tc.id);
|
|
4560
|
+
log.info("Tool completed", {
|
|
4561
|
+
name: tc.name,
|
|
4562
|
+
elapsed: `${Date.now() - toolStart}ms`,
|
|
4563
|
+
isError: r.isError,
|
|
4564
|
+
resultLength: r.result.length
|
|
4565
|
+
});
|
|
4566
|
+
onEvent({
|
|
4567
|
+
type: "tool_done",
|
|
4568
|
+
id: tc.id,
|
|
4569
|
+
name: tc.name,
|
|
4570
|
+
result: r.result,
|
|
4571
|
+
isError: r.isError
|
|
4572
|
+
});
|
|
4573
|
+
return r;
|
|
4484
4574
|
})
|
|
4485
4575
|
);
|
|
4486
|
-
|
|
4576
|
+
statusWatcher.stop();
|
|
4487
4577
|
for (const r of results) {
|
|
4488
4578
|
const block = contentBlocks.find(
|
|
4489
4579
|
(b) => b.type === "tool" && b.id === r.id
|
|
@@ -4491,6 +4581,7 @@ async function runTurn(params) {
|
|
|
4491
4581
|
if (block?.type === "tool") {
|
|
4492
4582
|
block.result = r.result;
|
|
4493
4583
|
block.isError = r.isError;
|
|
4584
|
+
block.completedAt = Date.now();
|
|
4494
4585
|
const msgs = subAgentMessages.get(r.id);
|
|
4495
4586
|
if (msgs) {
|
|
4496
4587
|
block.subAgentMessages = msgs;
|
|
@@ -4515,6 +4606,78 @@ async function runTurn(params) {
|
|
|
4515
4606
|
}
|
|
4516
4607
|
}
|
|
4517
4608
|
|
|
4609
|
+
// src/toolRegistry.ts
|
|
4610
|
+
var ToolRegistry = class {
|
|
4611
|
+
entries = /* @__PURE__ */ new Map();
|
|
4612
|
+
onEvent;
|
|
4613
|
+
register(entry) {
|
|
4614
|
+
this.entries.set(entry.id, entry);
|
|
4615
|
+
}
|
|
4616
|
+
unregister(id) {
|
|
4617
|
+
this.entries.delete(id);
|
|
4618
|
+
}
|
|
4619
|
+
get(id) {
|
|
4620
|
+
return this.entries.get(id);
|
|
4621
|
+
}
|
|
4622
|
+
/**
|
|
4623
|
+
* Stop a running tool.
|
|
4624
|
+
*
|
|
4625
|
+
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
4626
|
+
* - hard: abort and settle with a generic error
|
|
4627
|
+
*
|
|
4628
|
+
* Returns true if the tool was found and stopped.
|
|
4629
|
+
*/
|
|
4630
|
+
stop(id, mode) {
|
|
4631
|
+
const entry = this.entries.get(id);
|
|
4632
|
+
if (!entry) {
|
|
4633
|
+
return false;
|
|
4634
|
+
}
|
|
4635
|
+
entry.abortController.abort(mode);
|
|
4636
|
+
if (mode === "graceful") {
|
|
4637
|
+
const partial = entry.getPartialResult?.() ?? "";
|
|
4638
|
+
const result = partial ? `[INTERRUPTED]
|
|
4639
|
+
|
|
4640
|
+
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
4641
|
+
entry.settle(result, false);
|
|
4642
|
+
} else {
|
|
4643
|
+
entry.settle("Error: tool was cancelled", true);
|
|
4644
|
+
}
|
|
4645
|
+
this.onEvent?.({
|
|
4646
|
+
type: "tool_stopped",
|
|
4647
|
+
id: entry.id,
|
|
4648
|
+
name: entry.name,
|
|
4649
|
+
mode,
|
|
4650
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
4651
|
+
});
|
|
4652
|
+
this.entries.delete(id);
|
|
4653
|
+
return true;
|
|
4654
|
+
}
|
|
4655
|
+
/**
|
|
4656
|
+
* Restart a running tool with the same or patched input.
|
|
4657
|
+
* The original controllable promise stays pending and settles
|
|
4658
|
+
* when the new execution finishes.
|
|
4659
|
+
*
|
|
4660
|
+
* Returns true if the tool was found and restarted.
|
|
4661
|
+
*/
|
|
4662
|
+
restart(id, patchedInput) {
|
|
4663
|
+
const entry = this.entries.get(id);
|
|
4664
|
+
if (!entry) {
|
|
4665
|
+
return false;
|
|
4666
|
+
}
|
|
4667
|
+
entry.abortController.abort("restart");
|
|
4668
|
+
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
4669
|
+
this.onEvent?.({
|
|
4670
|
+
type: "tool_restarted",
|
|
4671
|
+
id: entry.id,
|
|
4672
|
+
name: entry.name,
|
|
4673
|
+
input: newInput,
|
|
4674
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
4675
|
+
});
|
|
4676
|
+
entry.rerun(newInput);
|
|
4677
|
+
return true;
|
|
4678
|
+
}
|
|
4679
|
+
};
|
|
4680
|
+
|
|
4518
4681
|
// src/headless.ts
|
|
4519
4682
|
function loadActionPrompt(name) {
|
|
4520
4683
|
return readAsset("prompt", "actions", `${name}.md`);
|
|
@@ -4581,6 +4744,7 @@ async function startHeadless(opts = {}) {
|
|
|
4581
4744
|
const EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
4582
4745
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
4583
4746
|
const earlyResults = /* @__PURE__ */ new Map();
|
|
4747
|
+
const toolRegistry = new ToolRegistry();
|
|
4584
4748
|
const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
4585
4749
|
"promptUser",
|
|
4586
4750
|
"confirmDestructiveAction",
|
|
@@ -4685,14 +4849,46 @@ async function startHeadless(opts = {}) {
|
|
|
4685
4849
|
rid
|
|
4686
4850
|
);
|
|
4687
4851
|
return;
|
|
4852
|
+
case "tool_stopped":
|
|
4853
|
+
emit(
|
|
4854
|
+
"tool_stopped",
|
|
4855
|
+
{
|
|
4856
|
+
id: e.id,
|
|
4857
|
+
name: e.name,
|
|
4858
|
+
mode: e.mode,
|
|
4859
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4860
|
+
},
|
|
4861
|
+
rid
|
|
4862
|
+
);
|
|
4863
|
+
return;
|
|
4864
|
+
case "tool_restarted":
|
|
4865
|
+
emit(
|
|
4866
|
+
"tool_restarted",
|
|
4867
|
+
{
|
|
4868
|
+
id: e.id,
|
|
4869
|
+
name: e.name,
|
|
4870
|
+
input: e.input,
|
|
4871
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4872
|
+
},
|
|
4873
|
+
rid
|
|
4874
|
+
);
|
|
4875
|
+
return;
|
|
4688
4876
|
case "status":
|
|
4689
|
-
emit(
|
|
4877
|
+
emit(
|
|
4878
|
+
"status",
|
|
4879
|
+
{
|
|
4880
|
+
message: e.message,
|
|
4881
|
+
...e.parentToolId && { parentToolId: e.parentToolId }
|
|
4882
|
+
},
|
|
4883
|
+
rid
|
|
4884
|
+
);
|
|
4690
4885
|
return;
|
|
4691
4886
|
case "error":
|
|
4692
4887
|
emit("error", { error: e.error }, rid);
|
|
4693
4888
|
return;
|
|
4694
4889
|
}
|
|
4695
4890
|
}
|
|
4891
|
+
toolRegistry.onEvent = onEvent;
|
|
4696
4892
|
async function handleMessage(parsed, requestId) {
|
|
4697
4893
|
if (running) {
|
|
4698
4894
|
emit(
|
|
@@ -4744,7 +4940,8 @@ async function startHeadless(opts = {}) {
|
|
|
4744
4940
|
signal: currentAbort.signal,
|
|
4745
4941
|
onEvent,
|
|
4746
4942
|
resolveExternalTool,
|
|
4747
|
-
hidden: isCommand
|
|
4943
|
+
hidden: isCommand,
|
|
4944
|
+
toolRegistry
|
|
4748
4945
|
});
|
|
4749
4946
|
if (!completedEmitted) {
|
|
4750
4947
|
emit(
|
|
@@ -4798,6 +4995,36 @@ async function startHeadless(opts = {}) {
|
|
|
4798
4995
|
emit("completed", { success: true }, requestId);
|
|
4799
4996
|
return;
|
|
4800
4997
|
}
|
|
4998
|
+
if (action === "stop_tool") {
|
|
4999
|
+
const id = parsed.id;
|
|
5000
|
+
const mode = parsed.mode ?? "hard";
|
|
5001
|
+
const found = toolRegistry.stop(id, mode);
|
|
5002
|
+
if (found) {
|
|
5003
|
+
emit("completed", { success: true }, requestId);
|
|
5004
|
+
} else {
|
|
5005
|
+
emit(
|
|
5006
|
+
"completed",
|
|
5007
|
+
{ success: false, error: "Tool not found" },
|
|
5008
|
+
requestId
|
|
5009
|
+
);
|
|
5010
|
+
}
|
|
5011
|
+
return;
|
|
5012
|
+
}
|
|
5013
|
+
if (action === "restart_tool") {
|
|
5014
|
+
const id = parsed.id;
|
|
5015
|
+
const patchedInput = parsed.input;
|
|
5016
|
+
const found = toolRegistry.restart(id, patchedInput);
|
|
5017
|
+
if (found) {
|
|
5018
|
+
emit("completed", { success: true }, requestId);
|
|
5019
|
+
} else {
|
|
5020
|
+
emit(
|
|
5021
|
+
"completed",
|
|
5022
|
+
{ success: false, error: "Tool not found" },
|
|
5023
|
+
requestId
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
4801
5028
|
if (action === "message") {
|
|
4802
5029
|
await handleMessage(parsed, requestId);
|
|
4803
5030
|
return;
|