@mindstudio-ai/remy 0.1.266 → 0.1.268
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/README.md +2 -1
- package/dist/automatedActions/publish.md +2 -1
- package/dist/headless.d.ts +7 -0
- package/dist/headless.js +1258 -992
- package/dist/index.js +1292 -1023
- package/dist/prompt/compiled/methods.md +1 -1
- package/dist/prompt/skills/taskAgents.md +1 -1
- package/dist/prompt/skills/voiceInterfaces.md +2 -3
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/prompt/static/team.md +3 -1
- package/dist/subagents/browserAutomation/prompt.md +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -403,7 +403,6 @@ var init_readSpec = __esm({
|
|
|
403
403
|
DEFAULT_MAX_LINES = 500;
|
|
404
404
|
readSpecTool = {
|
|
405
405
|
definition: {
|
|
406
|
-
clearable: true,
|
|
407
406
|
name: "readSpec",
|
|
408
407
|
description: "Read a spec file from src/ with line numbers. Always read a spec file before editing it. Paths are relative to the project root and must start with src/ (e.g., src/app.md, src/interfaces/web.md).",
|
|
409
408
|
inputSchema: {
|
|
@@ -536,7 +535,6 @@ var init_writeSpec = __esm({
|
|
|
536
535
|
init_fileLock();
|
|
537
536
|
writeSpecTool = {
|
|
538
537
|
definition: {
|
|
539
|
-
clearable: true,
|
|
540
538
|
name: "writeSpec",
|
|
541
539
|
description: "Create a new spec file or completely overwrite an existing one in src/. Parent directories are created automatically. Use this for new spec files or full rewrites. For targeted changes to existing specs, use editSpec instead.",
|
|
542
540
|
inputSchema: {
|
|
@@ -687,7 +685,6 @@ var init_editSpec = __esm({
|
|
|
687
685
|
init_fileLock();
|
|
688
686
|
editSpecTool = {
|
|
689
687
|
definition: {
|
|
690
|
-
clearable: true,
|
|
691
688
|
name: "editSpec",
|
|
692
689
|
description: "Make a targeted find/replace edit to a spec file (src/*.md). old_string must appear exactly once (minor indentation differences are handled automatically); set replace_all to true to replace every occurrence. Read the file with readSpec first so you match the exact text, and include the full enclosing structure rather than an inner fragment. The file's YAML frontmatter (the leading --- \u2026 --- block, which holds required fields like name) is protected \u2014 an edit that would remove or malform it is refused. For a full rewrite, use writeSpec.",
|
|
693
690
|
inputSchema: {
|
|
@@ -821,7 +818,6 @@ var init_listSpecFiles = __esm({
|
|
|
821
818
|
"use strict";
|
|
822
819
|
listSpecFilesTool = {
|
|
823
820
|
definition: {
|
|
824
|
-
clearable: false,
|
|
825
821
|
name: "listSpecFiles",
|
|
826
822
|
description: "List all files in the src/ directory (spec files, brand guidelines, interface specs, references). Use this to understand what spec files exist before reading or editing them.",
|
|
827
823
|
inputSchema: {
|
|
@@ -855,7 +851,6 @@ var init_presentPublishPlan = __esm({
|
|
|
855
851
|
"use strict";
|
|
856
852
|
presentPublishPlanTool = {
|
|
857
853
|
definition: {
|
|
858
|
-
clearable: false,
|
|
859
854
|
name: "presentPublishPlan",
|
|
860
855
|
description: "Present a publish changelog to the user for approval. Write a clear markdown summary of what changed since the last deploy. The user will see this in a full-screen view and can approve or dismiss. Call this BEFORE committing or pushing.",
|
|
861
856
|
inputSchema: {
|
|
@@ -886,7 +881,6 @@ var init_writePlan = __esm({
|
|
|
886
881
|
PLAN_FILE = ".remy-plan.md";
|
|
887
882
|
writePlanTool = {
|
|
888
883
|
definition: {
|
|
889
|
-
clearable: false,
|
|
890
884
|
name: "writePlan",
|
|
891
885
|
description: "Write an implementation plan for user approval before making changes. Use this only for large, multi-step changes like new features, new interface types, or when the user explicitly asks to see a plan. Most work should be done autonomously without a plan. Write a clear markdown summary of what you intend to do in plain language \u2014 describe the changes from the user's perspective, not as a list of files and code paths. The plan is displayed standalone in the UI with approve/reject buttons attached. The plan body ends with its last substantive section \u2014 do not write any closing line addressed to the reader. No 'If this lands, I'll start building,' no 'Let me know what to adjust,' no 'Approve when ready,' no trailing horizontal rule with a sign-off. Wrap-up text belongs in your chat message alongside the plan, not in the plan file. If the user asks for revisions, call this tool again with updated content to overwrite the plan.",
|
|
892
886
|
inputSchema: {
|
|
@@ -923,7 +917,6 @@ var init_updatePlanStatus = __esm({
|
|
|
923
917
|
PLAN_FILE2 = ".remy-plan.md";
|
|
924
918
|
updatePlanStatusTool = {
|
|
925
919
|
definition: {
|
|
926
|
-
clearable: false,
|
|
927
920
|
name: "updatePlanStatus",
|
|
928
921
|
description: 'Update the status of the current implementation plan. Use when the user approves or rejects the plan via chat (e.g. "looks good, go ahead" or "scrap it"). Approving sets the plan to active so you can begin implementation. Rejecting deletes the plan.',
|
|
929
922
|
inputSchema: {
|
|
@@ -971,7 +964,6 @@ var init_setProjectOnboardingState = __esm({
|
|
|
971
964
|
"use strict";
|
|
972
965
|
setProjectOnboardingStateTool = {
|
|
973
966
|
definition: {
|
|
974
|
-
clearable: false,
|
|
975
967
|
name: "setProjectOnboardingState",
|
|
976
968
|
description: "Advance the project onboarding state. Forward-only: building \u2192 buildComplete \u2192 onboardingFinished. Normally driven by automated actions \u2014 don't call this out of order during a normal build, or you'll skip stages the user hasn't experienced. Exception: if the project has been in `building` for a while, the build is clearly done (the user is iterating on a working app, deploying, etc.), and the user reports the editor seems stuck \u2014 disabled Preview/Spec/Code tabs, no reveal, etc. \u2014 call `setProjectOnboardingState({ state: 'buildComplete' })` to unstick them. `onboardingFinished` is always set by the frontend after the user dismisses the reveal; never call it yourself.",
|
|
977
969
|
inputSchema: {
|
|
@@ -1000,7 +992,6 @@ var init_promptUser = __esm({
|
|
|
1000
992
|
"use strict";
|
|
1001
993
|
promptUserTool = {
|
|
1002
994
|
definition: {
|
|
1003
|
-
clearable: false,
|
|
1004
995
|
name: "promptUser",
|
|
1005
996
|
description: 'Ask the user structured questions. Choose type first: "form" for structured intake (5+ questions, takes over screen), "inline" for quick clarifications or confirmations. Blocks until the user responds. Result contains `_dismissed: true` if the user dismisses without answering.',
|
|
1006
997
|
inputSchema: {
|
|
@@ -1138,7 +1129,6 @@ var init_confirmDestructiveAction = __esm({
|
|
|
1138
1129
|
"use strict";
|
|
1139
1130
|
confirmDestructiveActionTool = {
|
|
1140
1131
|
definition: {
|
|
1141
|
-
clearable: false,
|
|
1142
1132
|
name: "confirmDestructiveAction",
|
|
1143
1133
|
description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentPublishPlan or writePlan (those already include approval). Do not use before onboarding state transitions.",
|
|
1144
1134
|
inputSchema: {
|
|
@@ -1325,7 +1315,6 @@ var init_sdkConsultant = __esm({
|
|
|
1325
1315
|
init_runCli();
|
|
1326
1316
|
askMindStudioSdkTool = {
|
|
1327
1317
|
definition: {
|
|
1328
|
-
clearable: false,
|
|
1329
1318
|
name: "askMindStudioSdk",
|
|
1330
1319
|
description: "@mindstudio-ai/agent backend SDK expert. Knows every backend action, AI model, connector, and configuration option. Returns architectural guidance and working code. Only covers the backend SDK (@mindstudio-ai/agent) \u2014 do NOT use for frontend/interface SDK questions (@mindstudio-ai/interface) like file uploads, auth, or client-side APIs. Describe what you want to build, not just what API method you need. Batch related questions into a single query.",
|
|
1331
1320
|
inputSchema: {
|
|
@@ -1487,7 +1476,6 @@ var init_searchGoogle = __esm({
|
|
|
1487
1476
|
init_runCli();
|
|
1488
1477
|
searchGoogleTool = {
|
|
1489
1478
|
definition: {
|
|
1490
|
-
clearable: false,
|
|
1491
1479
|
name: "searchGoogle",
|
|
1492
1480
|
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
1493
1481
|
inputSchema: {
|
|
@@ -1524,7 +1512,6 @@ var init_setProjectMetadata = __esm({
|
|
|
1524
1512
|
"use strict";
|
|
1525
1513
|
setProjectMetadataTool = {
|
|
1526
1514
|
definition: {
|
|
1527
|
-
clearable: false,
|
|
1528
1515
|
name: "setProjectMetadata",
|
|
1529
1516
|
description: "Set project metadata. Can update any combination of: display name, short description, app icon, and Open Graph share image. Provide only the fields you want to change.",
|
|
1530
1517
|
inputSchema: {
|
|
@@ -1759,7 +1746,11 @@ function getConversationMessagesForSummary(messages, endIndex) {
|
|
|
1759
1746
|
break;
|
|
1760
1747
|
}
|
|
1761
1748
|
}
|
|
1762
|
-
return messages.slice(startIdx, endIndex)
|
|
1749
|
+
return messages.slice(startIdx, endIndex).filter(
|
|
1750
|
+
(msg) => !(Array.isArray(msg.content) && msg.content.some(
|
|
1751
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
1752
|
+
))
|
|
1753
|
+
);
|
|
1763
1754
|
}
|
|
1764
1755
|
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
1765
1756
|
let checkpointIdx = -1;
|
|
@@ -2289,7 +2280,12 @@ ${summaryBlock.text}
|
|
|
2289
2280
|
}
|
|
2290
2281
|
startIdx = checkpointIdx + 1;
|
|
2291
2282
|
}
|
|
2292
|
-
const
|
|
2283
|
+
const apiMessages = messages.slice(startIdx).filter(
|
|
2284
|
+
(msg) => !(msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some(
|
|
2285
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
2286
|
+
))
|
|
2287
|
+
);
|
|
2288
|
+
const messagesToProcess = fixOrphanedToolCalls(apiMessages);
|
|
2293
2289
|
const toolUseIds = /* @__PURE__ */ new Set();
|
|
2294
2290
|
for (const msg of messagesToProcess) {
|
|
2295
2291
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
@@ -2393,6 +2389,11 @@ var init_toolResultCap = __esm({
|
|
|
2393
2389
|
// src/session.ts
|
|
2394
2390
|
import fs10 from "fs";
|
|
2395
2391
|
import path4 from "path";
|
|
2392
|
+
function writeFileAtomicSync(file, data) {
|
|
2393
|
+
const tmp = `${file}.tmp`;
|
|
2394
|
+
fs10.writeFileSync(tmp, data, "utf-8");
|
|
2395
|
+
fs10.renameSync(tmp, file);
|
|
2396
|
+
}
|
|
2396
2397
|
function loadSession(state) {
|
|
2397
2398
|
pruneArchives();
|
|
2398
2399
|
try {
|
|
@@ -2481,7 +2482,7 @@ function archiveMessages(messages, label, models) {
|
|
|
2481
2482
|
if (models && Object.keys(models).length > 0) {
|
|
2482
2483
|
payload.models = models;
|
|
2483
2484
|
}
|
|
2484
|
-
|
|
2485
|
+
writeFileAtomicSync(dest, JSON.stringify(payload));
|
|
2485
2486
|
archiveCountCache.set(path4.basename(dest), count);
|
|
2486
2487
|
log3.info("Session archived", { label, dest, messageCount: count });
|
|
2487
2488
|
pruneArchives();
|
|
@@ -2686,7 +2687,7 @@ function saveSession(state) {
|
|
|
2686
2687
|
if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
|
|
2687
2688
|
serialized = JSON.stringify(buildPayload(state));
|
|
2688
2689
|
}
|
|
2689
|
-
|
|
2690
|
+
writeFileAtomicSync(SESSION_FILE, serialized);
|
|
2690
2691
|
log3.info("Session saved", { messageCount: state.messages.length });
|
|
2691
2692
|
} catch (err) {
|
|
2692
2693
|
log3.warn("Session save failed", { error: err.message });
|
|
@@ -2737,6 +2738,28 @@ var init_session = __esm({
|
|
|
2737
2738
|
});
|
|
2738
2739
|
|
|
2739
2740
|
// src/compaction/trigger.ts
|
|
2741
|
+
function getInflightCompaction() {
|
|
2742
|
+
return inflightCompaction;
|
|
2743
|
+
}
|
|
2744
|
+
function summariesOf(result) {
|
|
2745
|
+
const out = [];
|
|
2746
|
+
for (const msg of result.checkpoints) {
|
|
2747
|
+
if (!Array.isArray(msg.content)) {
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
for (const block of msg.content) {
|
|
2751
|
+
if (block.type === "summary") {
|
|
2752
|
+
out.push({
|
|
2753
|
+
name: block.name,
|
|
2754
|
+
text: block.text,
|
|
2755
|
+
...block.recent && { recent: block.recent },
|
|
2756
|
+
startedAt: block.startedAt
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
return out;
|
|
2762
|
+
}
|
|
2740
2763
|
function applyPendingSummaries(state) {
|
|
2741
2764
|
const drained = pending;
|
|
2742
2765
|
pending = null;
|
|
@@ -2757,6 +2780,20 @@ function applyPendingSummaries(state) {
|
|
|
2757
2780
|
});
|
|
2758
2781
|
saveSession(state);
|
|
2759
2782
|
}
|
|
2783
|
+
function formatSummariesResult(summaries) {
|
|
2784
|
+
if (summaries === null) {
|
|
2785
|
+
return "A compaction checkpoint was already pending \u2014 no new compaction was needed.";
|
|
2786
|
+
}
|
|
2787
|
+
if (summaries.length === 0) {
|
|
2788
|
+
return "Nothing to compact \u2014 the conversation is already fully summarized.";
|
|
2789
|
+
}
|
|
2790
|
+
if (summaries.length === 1 && summaries[0].name === "conversation") {
|
|
2791
|
+
return summaries[0].text;
|
|
2792
|
+
}
|
|
2793
|
+
return summaries.map((s) => `## ${SUMMARY_SECTION_LABELS[s.name] ?? s.name}
|
|
2794
|
+
|
|
2795
|
+
${s.text}`).join("\n\n");
|
|
2796
|
+
}
|
|
2760
2797
|
function setCompactionListener(l) {
|
|
2761
2798
|
listener = l;
|
|
2762
2799
|
}
|
|
@@ -2766,18 +2803,24 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2766
2803
|
}
|
|
2767
2804
|
if (pending) {
|
|
2768
2805
|
log4.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
2769
|
-
return Promise.resolve();
|
|
2806
|
+
return Promise.resolve(null);
|
|
2770
2807
|
}
|
|
2771
|
-
const { blocking = false, requestId, model } = opts;
|
|
2772
|
-
listener?.({ type: "started", blocking, requestId });
|
|
2808
|
+
const { blocking = false, requestId, model, origin, toolCallId } = opts;
|
|
2809
|
+
listener?.({ type: "started", blocking, requestId, origin, toolCallId });
|
|
2773
2810
|
inflightCompaction = compactConversation(
|
|
2774
2811
|
state.messages,
|
|
2775
2812
|
apiConfig,
|
|
2776
2813
|
resolveModel("conversationSummarizer", state.models, model)
|
|
2777
2814
|
).then((result) => {
|
|
2778
2815
|
pending = result;
|
|
2779
|
-
|
|
2816
|
+
const summaries = summariesOf(result);
|
|
2817
|
+
listener?.({
|
|
2818
|
+
type: "complete",
|
|
2819
|
+
requestId,
|
|
2820
|
+
...summaries.length > 0 && { summaries }
|
|
2821
|
+
});
|
|
2780
2822
|
log4.info("Compaction complete");
|
|
2823
|
+
return summaries;
|
|
2781
2824
|
}).catch((err) => {
|
|
2782
2825
|
const message = err.message || "Compaction failed";
|
|
2783
2826
|
listener?.({ type: "complete", error: message, requestId });
|
|
@@ -2788,7 +2831,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2788
2831
|
});
|
|
2789
2832
|
return inflightCompaction;
|
|
2790
2833
|
}
|
|
2791
|
-
var log4, pending, inflightCompaction, listener;
|
|
2834
|
+
var log4, pending, inflightCompaction, SUMMARY_SECTION_LABELS, listener;
|
|
2792
2835
|
var init_trigger = __esm({
|
|
2793
2836
|
"src/compaction/trigger.ts"() {
|
|
2794
2837
|
"use strict";
|
|
@@ -2799,6 +2842,11 @@ var init_trigger = __esm({
|
|
|
2799
2842
|
log4 = createLogger("compaction:trigger");
|
|
2800
2843
|
pending = null;
|
|
2801
2844
|
inflightCompaction = null;
|
|
2845
|
+
SUMMARY_SECTION_LABELS = {
|
|
2846
|
+
conversation: "Conversation",
|
|
2847
|
+
visualDesignExpert: "Design Agent Thread",
|
|
2848
|
+
productVision: "Roadmap Agent Thread"
|
|
2849
|
+
};
|
|
2802
2850
|
listener = null;
|
|
2803
2851
|
}
|
|
2804
2852
|
});
|
|
@@ -2810,8 +2858,13 @@ var init_compactConversation = __esm({
|
|
|
2810
2858
|
"use strict";
|
|
2811
2859
|
init_trigger();
|
|
2812
2860
|
compactConversationTool = {
|
|
2861
|
+
backgroundOnly: true,
|
|
2862
|
+
// Silent: the completed summary is written onto the tool block for the UI
|
|
2863
|
+
// detail view only. The model must never receive it as a message — it gets
|
|
2864
|
+
// the summary the correct way, as the checkpoint prefix in every later API
|
|
2865
|
+
// call (cleanMessagesForApi).
|
|
2866
|
+
backgroundNotify: "silent",
|
|
2813
2867
|
definition: {
|
|
2814
|
-
clearable: false,
|
|
2815
2868
|
name: "compactConversation",
|
|
2816
2869
|
description: "Compact the conversation history by summarizing older messages into a checkpoint. The summary preserves key decisions, what was built, and the current state of the project, but drops the verbose tool results, diffs, and intermediate steps that are no longer useful. Runs in the background.",
|
|
2817
2870
|
inputSchema: {
|
|
@@ -2823,11 +2876,32 @@ var init_compactConversation = __esm({
|
|
|
2823
2876
|
if (!context?.conversationMessages || !context.apiConfig) {
|
|
2824
2877
|
return "Error: compaction requires execution context.";
|
|
2825
2878
|
}
|
|
2879
|
+
const { toolCallId, onBackgroundComplete, toolRegistry } = context;
|
|
2826
2880
|
triggerCompaction(
|
|
2827
2881
|
{ messages: context.conversationMessages },
|
|
2828
2882
|
context.apiConfig,
|
|
2829
|
-
|
|
2830
|
-
|
|
2883
|
+
// origin/toolCallId tell the headless listener a real block exists —
|
|
2884
|
+
// it must not synthesize a UI-only one for this compaction.
|
|
2885
|
+
{
|
|
2886
|
+
blocking: false,
|
|
2887
|
+
requestId: context.requestId,
|
|
2888
|
+
origin: "tool",
|
|
2889
|
+
toolCallId
|
|
2890
|
+
}
|
|
2891
|
+
).then((summaries) => {
|
|
2892
|
+
onBackgroundComplete?.(
|
|
2893
|
+
toolCallId,
|
|
2894
|
+
"compactConversation",
|
|
2895
|
+
formatSummariesResult(summaries)
|
|
2896
|
+
);
|
|
2897
|
+
}).catch((err) => {
|
|
2898
|
+
onBackgroundComplete?.(
|
|
2899
|
+
toolCallId,
|
|
2900
|
+
"compactConversation",
|
|
2901
|
+
`Error: ${err.message || "Compaction failed"}`
|
|
2902
|
+
);
|
|
2903
|
+
}).finally(() => {
|
|
2904
|
+
toolRegistry?.unregister(toolCallId);
|
|
2831
2905
|
});
|
|
2832
2906
|
return "Compaction started in the background.";
|
|
2833
2907
|
}
|
|
@@ -2925,7 +2999,6 @@ var init_loadSkill = __esm({
|
|
|
2925
2999
|
init_catalog();
|
|
2926
3000
|
loadSkillTool = {
|
|
2927
3001
|
definition: {
|
|
2928
|
-
clearable: true,
|
|
2929
3002
|
name: "loadSkill",
|
|
2930
3003
|
description: "Load the full reference for a platform capability that isn't in your system prompt \u2014 task agents, agent interfaces, voice interfaces, MCP interfaces, data sources. The available skills and the trigger for each are listed in <available_skills>. Load one before writing code in its area, not after: these are APIs where a plausible-looking guess is usually wrong. Calling this is cheap and expected \u2014 if you're unsure whether you need it, load it. Only covers the capabilities listed in the catalog; for backend SDK actions and model IDs use askMindStudioSdk.",
|
|
2931
3004
|
inputSchema: {
|
|
@@ -2982,7 +3055,6 @@ var init_readFile = __esm({
|
|
|
2982
3055
|
MAX_BYTES = 64 * 1024;
|
|
2983
3056
|
readFileTool = {
|
|
2984
3057
|
definition: {
|
|
2985
|
-
clearable: true,
|
|
2986
3058
|
name: "readFile",
|
|
2987
3059
|
description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines, and at most 64KB \u2014 a file with very wide lines (a CSV, a minified bundle) comes back short of 500 lines, so read a narrower range or grep rather than paging through it. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
|
|
2988
3060
|
inputSchema: {
|
|
@@ -3094,7 +3166,6 @@ var init_writeFile = __esm({
|
|
|
3094
3166
|
init_fileLock();
|
|
3095
3167
|
writeFileTool = {
|
|
3096
3168
|
definition: {
|
|
3097
|
-
clearable: true,
|
|
3098
3169
|
name: "writeFile",
|
|
3099
3170
|
description: "Create a new file or completely overwrite an existing one. Parent directories are created automatically. Use this for new files or full rewrites. For targeted changes to existing files, use editFile instead \u2014 it preserves the parts you don't want to change and avoids errors from forgetting to include unchanged code.",
|
|
3100
3171
|
inputSchema: {
|
|
@@ -3169,7 +3240,6 @@ var init_editFile = __esm({
|
|
|
3169
3240
|
init_helpers2();
|
|
3170
3241
|
editFileTool = {
|
|
3171
3242
|
definition: {
|
|
3172
|
-
clearable: true,
|
|
3173
3243
|
name: "editFile",
|
|
3174
3244
|
description: "Replace a string in a file. old_string must appear exactly once (minor indentation differences are handled automatically). Set replace_all to true to replace every occurrence at once. For bulk mechanical substitutions (renaming a variable, swapping colors), prefer replace_all. Always read the file first so you know the exact text to match. When editing nested structures (objects, function bodies, arrays, template literals), always include the full enclosing structure in old_string rather than just an inner fragment. Replacing a partial slice from the middle of nested code is the most common source of syntax errors.",
|
|
3175
3245
|
inputSchema: {
|
|
@@ -3278,7 +3348,6 @@ var init_bash = __esm({
|
|
|
3278
3348
|
MAX_OUTPUT_BYTES = 3e4;
|
|
3279
3349
|
bashTool = {
|
|
3280
3350
|
definition: {
|
|
3281
|
-
clearable: true,
|
|
3282
3351
|
name: "bash",
|
|
3283
3352
|
description: "Run a shell command and return stdout + stderr. 120-second timeout by default (configurable). Use for: npm install/build/test, git operations, tsc --noEmit, or any CLI tool. Prefer dedicated tools over bash when available (use grep instead of bash + rg, readFile instead of bash + cat). Output is truncated to 500 lines or 30KB, whichever comes first. If a command would emit a lot of data, narrow it down (grep, head/tail, --short flags) rather than reading everything.",
|
|
3284
3353
|
inputSchema: {
|
|
@@ -3406,7 +3475,6 @@ var init_grep = __esm({
|
|
|
3406
3475
|
DEFAULT_MAX = 50;
|
|
3407
3476
|
grepTool = {
|
|
3408
3477
|
definition: {
|
|
3409
|
-
clearable: true,
|
|
3410
3478
|
name: "grep",
|
|
3411
3479
|
description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. Set outputMode to 'count' for per-file match counts (like grep -c) or 'filesWithMatches' for just the file paths (like grep -l). Add context (like grep -C), or contextBefore/contextAfter (like grep -B/-A), to include surrounding lines. Set caseInsensitive (like grep -i) for a case-insensitive search. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
|
|
3412
3480
|
inputSchema: {
|
|
@@ -3524,7 +3592,6 @@ var init_glob = __esm({
|
|
|
3524
3592
|
DEFAULT_MAX2 = 200;
|
|
3525
3593
|
globTool = {
|
|
3526
3594
|
definition: {
|
|
3527
|
-
clearable: true,
|
|
3528
3595
|
name: "glob",
|
|
3529
3596
|
description: 'Find files matching a glob pattern. Returns matching file paths sorted alphabetically (default 200 results). Use this to discover project structure, find files by name or extension, or check if a file exists. Common patterns: "**/*.ts" (all TypeScript files), "src/**/*.tsx" (React components in src), "*.json" (root-level JSON files). Automatically excludes node_modules and .git.',
|
|
3530
3597
|
inputSchema: {
|
|
@@ -3628,7 +3695,6 @@ var init_listDir = __esm({
|
|
|
3628
3695
|
MAX_CHILDREN = 15;
|
|
3629
3696
|
listDirTool = {
|
|
3630
3697
|
definition: {
|
|
3631
|
-
clearable: true,
|
|
3632
3698
|
name: "listDir",
|
|
3633
3699
|
description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
|
|
3634
3700
|
inputSchema: {
|
|
@@ -3711,7 +3777,6 @@ var init_editsFinished = __esm({
|
|
|
3711
3777
|
"use strict";
|
|
3712
3778
|
editsFinishedTool = {
|
|
3713
3779
|
definition: {
|
|
3714
|
-
clearable: false,
|
|
3715
3780
|
name: "editsFinished",
|
|
3716
3781
|
description: "Signal that file edits are complete. Call this after you finish writing/editing files so the live preview updates cleanly. The preview is paused while you edit to avoid showing broken intermediate states \u2014 this unpauses it. If you forget to call this, the preview updates when your turn ends.",
|
|
3717
3782
|
inputSchema: {
|
|
@@ -3799,7 +3864,6 @@ var init_lspDiagnostics = __esm({
|
|
|
3799
3864
|
init_lsp();
|
|
3800
3865
|
lspDiagnosticsTool = {
|
|
3801
3866
|
definition: {
|
|
3802
|
-
clearable: true,
|
|
3803
3867
|
name: "lspDiagnostics",
|
|
3804
3868
|
description: "Get TypeScript diagnostics (type errors, warnings) for a file, with suggested fixes when available. Use this after editing a file to check for errors.",
|
|
3805
3869
|
inputSchema: {
|
|
@@ -3855,7 +3919,6 @@ var init_restartProcess = __esm({
|
|
|
3855
3919
|
init_lsp();
|
|
3856
3920
|
restartProcessTool = {
|
|
3857
3921
|
definition: {
|
|
3858
|
-
clearable: false,
|
|
3859
3922
|
name: "restartProcess",
|
|
3860
3923
|
description: "Restart a managed sandbox process. Use this after running npm install or changing package.json to restart the dev server so it picks up new dependencies.",
|
|
3861
3924
|
inputSchema: {
|
|
@@ -3888,7 +3951,6 @@ var init_runScenario = __esm({
|
|
|
3888
3951
|
"use strict";
|
|
3889
3952
|
runScenarioTool = {
|
|
3890
3953
|
definition: {
|
|
3891
|
-
clearable: true,
|
|
3892
3954
|
name: "runScenario",
|
|
3893
3955
|
description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and impersonates the scenario roles. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
|
|
3894
3956
|
inputSchema: {
|
|
@@ -3920,7 +3982,6 @@ var init_runMethod = __esm({
|
|
|
3920
3982
|
"use strict";
|
|
3921
3983
|
runMethodTool = {
|
|
3922
3984
|
definition: {
|
|
3923
|
-
clearable: true,
|
|
3924
3985
|
name: "runMethod",
|
|
3925
3986
|
description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup.',
|
|
3926
3987
|
inputSchema: {
|
|
@@ -3961,7 +4022,6 @@ var init_queryDatabase = __esm({
|
|
|
3961
4022
|
"use strict";
|
|
3962
4023
|
queryDatabaseTool = {
|
|
3963
4024
|
definition: {
|
|
3964
|
-
clearable: true,
|
|
3965
4025
|
name: "queryDatabase",
|
|
3966
4026
|
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
3967
4027
|
inputSchema: {
|
|
@@ -4337,16 +4397,48 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
4337
4397
|
});
|
|
4338
4398
|
|
|
4339
4399
|
// src/statusWatcher.ts
|
|
4400
|
+
function sanitizeStatusText(text) {
|
|
4401
|
+
if (!text) {
|
|
4402
|
+
return "";
|
|
4403
|
+
}
|
|
4404
|
+
for (const marker of INTERNAL_PAYLOAD_MARKERS) {
|
|
4405
|
+
if (text.includes(marker)) {
|
|
4406
|
+
return "";
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
const truncIdx = text.indexOf("(tool result truncated at");
|
|
4410
|
+
return truncIdx === -1 ? text : text.slice(0, truncIdx);
|
|
4411
|
+
}
|
|
4340
4412
|
function startStatusWatcher(config) {
|
|
4341
4413
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
4342
4414
|
let inflight2 = false;
|
|
4343
4415
|
let stopped = false;
|
|
4344
4416
|
let pauseCount = 0;
|
|
4345
4417
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
4418
|
+
let consecutiveFailures = 0;
|
|
4419
|
+
let backoffMs = 0;
|
|
4420
|
+
let nextAllowedAt = 0;
|
|
4421
|
+
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
4422
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
4423
|
+
function recordFailure() {
|
|
4424
|
+
consecutiveFailures++;
|
|
4425
|
+
backoffMs = Math.min(
|
|
4426
|
+
backoffMs === 0 ? interval : backoffMs * 2,
|
|
4427
|
+
MAX_BACKOFF_MS
|
|
4428
|
+
);
|
|
4429
|
+
nextAllowedAt = Date.now() + backoffMs;
|
|
4430
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !stopped) {
|
|
4431
|
+
stopped = true;
|
|
4432
|
+
clearInterval(timer);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4346
4435
|
async function tick() {
|
|
4347
4436
|
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
4348
4437
|
return;
|
|
4349
4438
|
}
|
|
4439
|
+
if (Date.now() < nextAllowedAt) {
|
|
4440
|
+
return;
|
|
4441
|
+
}
|
|
4350
4442
|
inflight2 = true;
|
|
4351
4443
|
try {
|
|
4352
4444
|
const context = getContext();
|
|
@@ -4362,18 +4454,29 @@ function startStatusWatcher(config) {
|
|
|
4362
4454
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
4363
4455
|
signal
|
|
4364
4456
|
});
|
|
4365
|
-
if (
|
|
4457
|
+
if (stopped) {
|
|
4458
|
+
return;
|
|
4459
|
+
}
|
|
4460
|
+
if (!res.ok) {
|
|
4461
|
+
recordFailure();
|
|
4366
4462
|
return;
|
|
4367
4463
|
}
|
|
4368
4464
|
const data = await res.json();
|
|
4369
4465
|
if (!data.label) {
|
|
4466
|
+
recordFailure();
|
|
4370
4467
|
return;
|
|
4371
4468
|
}
|
|
4469
|
+
consecutiveFailures = 0;
|
|
4470
|
+
backoffMs = 0;
|
|
4471
|
+
nextAllowedAt = 0;
|
|
4372
4472
|
if (pauseCount > 0 || stopped || signal?.aborted) {
|
|
4373
4473
|
return;
|
|
4374
4474
|
}
|
|
4375
4475
|
onStatus(data.label);
|
|
4376
4476
|
} catch {
|
|
4477
|
+
if (!stopped && !signal?.aborted) {
|
|
4478
|
+
recordFailure();
|
|
4479
|
+
}
|
|
4377
4480
|
} finally {
|
|
4378
4481
|
inflight2 = false;
|
|
4379
4482
|
}
|
|
@@ -4394,9 +4497,17 @@ function startStatusWatcher(config) {
|
|
|
4394
4497
|
}
|
|
4395
4498
|
};
|
|
4396
4499
|
}
|
|
4500
|
+
var INTERNAL_PAYLOAD_MARKERS;
|
|
4397
4501
|
var init_statusWatcher = __esm({
|
|
4398
4502
|
"src/statusWatcher.ts"() {
|
|
4399
4503
|
"use strict";
|
|
4504
|
+
INTERNAL_PAYLOAD_MARKERS = [
|
|
4505
|
+
"[USER CANCELLED]",
|
|
4506
|
+
"[INTERRUPTED]",
|
|
4507
|
+
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
4508
|
+
"<background_results>",
|
|
4509
|
+
"<tool_result"
|
|
4510
|
+
];
|
|
4400
4511
|
}
|
|
4401
4512
|
});
|
|
4402
4513
|
|
|
@@ -4465,329 +4576,341 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4465
4576
|
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
4466
4577
|
}
|
|
4467
4578
|
let lastToolResult = "";
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
}
|
|
4579
|
+
let watchedBlocks = [];
|
|
4580
|
+
let watchedToolNames = "";
|
|
4581
|
+
const statusWatcher = startStatusWatcher({
|
|
4582
|
+
apiConfig,
|
|
4583
|
+
getContext: () => {
|
|
4584
|
+
const parts = [];
|
|
4585
|
+
if (task) {
|
|
4586
|
+
parts.push(`Task: ${sanitizeStatusText(task).slice(-200)}`);
|
|
4587
|
+
}
|
|
4588
|
+
const text = getPartialText(watchedBlocks);
|
|
4589
|
+
if (text) {
|
|
4590
|
+
parts.push(`Assistant text: ${text.slice(-500)}`);
|
|
4591
|
+
}
|
|
4592
|
+
if (watchedToolNames) {
|
|
4593
|
+
parts.push(`Tool: ${watchedToolNames}`);
|
|
4594
|
+
}
|
|
4595
|
+
const toolResult = sanitizeStatusText(lastToolResult);
|
|
4596
|
+
if (toolResult) {
|
|
4597
|
+
parts.push(`Tool result: ${toolResult.slice(-200)}`);
|
|
4598
|
+
}
|
|
4599
|
+
return parts.join("\n");
|
|
4600
|
+
},
|
|
4601
|
+
onStatus: (label) => emit({ type: "status", message: label }),
|
|
4602
|
+
signal
|
|
4603
|
+
});
|
|
4604
|
+
try {
|
|
4605
|
+
while (true) {
|
|
4606
|
+
turns++;
|
|
4607
|
+
if (signal?.aborted) {
|
|
4608
|
+
return abortResult([]);
|
|
4609
|
+
}
|
|
4610
|
+
const iterStart = Date.now();
|
|
4611
|
+
const contentBlocks = [];
|
|
4612
|
+
let thinkingStartedAt = 0;
|
|
4613
|
+
let lastThinkingRelatedStartedAt;
|
|
4614
|
+
let stopReason = "end_turn";
|
|
4615
|
+
let lastUsage;
|
|
4616
|
+
let lastProviderMetadata;
|
|
4617
|
+
let lastModelId;
|
|
4618
|
+
watchedBlocks = contentBlocks;
|
|
4619
|
+
watchedToolNames = "";
|
|
4620
|
+
try {
|
|
4621
|
+
for await (const event of streamChatWithRetry(
|
|
4622
|
+
{
|
|
4623
|
+
...apiConfig,
|
|
4624
|
+
model,
|
|
4625
|
+
requestId,
|
|
4626
|
+
subAgentId,
|
|
4627
|
+
system: fullSystem,
|
|
4628
|
+
messages: cleanMessagesForApi(messages),
|
|
4629
|
+
tools: tools2,
|
|
4630
|
+
signal
|
|
4631
|
+
},
|
|
4632
|
+
{
|
|
4633
|
+
onRetry: (attempt) => emit({
|
|
4634
|
+
type: "status",
|
|
4635
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
4636
|
+
})
|
|
4637
|
+
}
|
|
4638
|
+
)) {
|
|
4639
|
+
if (signal?.aborted) {
|
|
4640
|
+
break;
|
|
4641
|
+
}
|
|
4642
|
+
switch (event.type) {
|
|
4643
|
+
case "text": {
|
|
4644
|
+
const lastBlock = contentBlocks.at(-1);
|
|
4645
|
+
if (lastBlock?.type === "text") {
|
|
4646
|
+
lastBlock.text += event.text;
|
|
4647
|
+
} else {
|
|
4648
|
+
contentBlocks.push({
|
|
4649
|
+
type: "text",
|
|
4650
|
+
text: event.text,
|
|
4651
|
+
startedAt: event.ts
|
|
4652
|
+
});
|
|
4653
|
+
}
|
|
4654
|
+
emit({ type: "text", text: event.text });
|
|
4655
|
+
break;
|
|
4656
|
+
}
|
|
4657
|
+
case "thinking":
|
|
4658
|
+
if (!thinkingStartedAt) {
|
|
4659
|
+
thinkingStartedAt = event.ts;
|
|
4660
|
+
}
|
|
4661
|
+
emit({ type: "thinking", text: event.text });
|
|
4662
|
+
break;
|
|
4663
|
+
case "thinking_complete":
|
|
4664
|
+
contentBlocks.push({
|
|
4665
|
+
type: "thinking",
|
|
4666
|
+
thinking: event.thinking,
|
|
4667
|
+
signature: event.signature,
|
|
4668
|
+
startedAt: thinkingStartedAt,
|
|
4669
|
+
completedAt: event.ts
|
|
4670
|
+
});
|
|
4671
|
+
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
4672
|
+
thinkingStartedAt = 0;
|
|
4673
|
+
break;
|
|
4674
|
+
case "redacted_thinking_complete": {
|
|
4675
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
4676
|
+
contentBlocks.push({
|
|
4677
|
+
type: "redacted_thinking",
|
|
4678
|
+
data: event.data,
|
|
4679
|
+
startedAt,
|
|
4680
|
+
completedAt: event.ts
|
|
4681
|
+
});
|
|
4682
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
4683
|
+
break;
|
|
4684
|
+
}
|
|
4685
|
+
case "tool_use":
|
|
4686
|
+
contentBlocks.push({
|
|
4687
|
+
type: "tool",
|
|
4688
|
+
id: event.id,
|
|
4689
|
+
name: event.name,
|
|
4690
|
+
input: event.input,
|
|
4691
|
+
startedAt: Date.now()
|
|
4692
|
+
});
|
|
4693
|
+
emit({
|
|
4694
|
+
type: "tool_start",
|
|
4695
|
+
id: event.id,
|
|
4696
|
+
name: event.name,
|
|
4697
|
+
input: event.input
|
|
4698
|
+
});
|
|
4699
|
+
break;
|
|
4700
|
+
case "done":
|
|
4701
|
+
stopReason = event.stopReason;
|
|
4702
|
+
lastUsage = {
|
|
4703
|
+
inputTokens: event.usage.inputTokens,
|
|
4704
|
+
outputTokens: event.usage.outputTokens,
|
|
4705
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4706
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4707
|
+
llmCalls: 1
|
|
4708
|
+
};
|
|
4709
|
+
lastProviderMetadata = event.providerMetadata;
|
|
4710
|
+
lastModelId = event.modelId;
|
|
4711
|
+
recordUsage({
|
|
4712
|
+
ts: Date.now(),
|
|
4713
|
+
requestId,
|
|
4714
|
+
agentName: subAgentId || "sub-agent",
|
|
4715
|
+
parentToolId,
|
|
4716
|
+
modelId: event.modelId,
|
|
4717
|
+
inputTokens: event.usage.inputTokens,
|
|
4718
|
+
outputTokens: event.usage.outputTokens,
|
|
4719
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4720
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4721
|
+
cost: nanoToDollars(event.cost),
|
|
4722
|
+
billingEvents: event.billingEvents,
|
|
4723
|
+
durationMs: Date.now() - iterStart,
|
|
4724
|
+
toolNames: contentBlocks.filter(
|
|
4725
|
+
(b) => b.type === "tool"
|
|
4726
|
+
).map((b) => b.name)
|
|
4727
|
+
});
|
|
4728
|
+
break;
|
|
4729
|
+
case "error":
|
|
4730
|
+
statusWatcher.stop();
|
|
4731
|
+
return {
|
|
4732
|
+
text: `Error: ${event.error}`,
|
|
4733
|
+
messages: thisInvocation()
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4521
4736
|
}
|
|
4522
|
-
)
|
|
4523
|
-
if (signal?.aborted) {
|
|
4524
|
-
|
|
4737
|
+
} catch (err) {
|
|
4738
|
+
if (!signal?.aborted) {
|
|
4739
|
+
throw err;
|
|
4525
4740
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4741
|
+
}
|
|
4742
|
+
if (signal?.aborted) {
|
|
4743
|
+
statusWatcher.stop();
|
|
4744
|
+
return abortResult(contentBlocks);
|
|
4745
|
+
}
|
|
4746
|
+
messages.push({
|
|
4747
|
+
role: "assistant",
|
|
4748
|
+
content: contentBlocks,
|
|
4749
|
+
...lastUsage ? { usage: lastUsage } : {},
|
|
4750
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
4751
|
+
model: lastModelId ?? model
|
|
4752
|
+
});
|
|
4753
|
+
const toolCalls = contentBlocks.filter(
|
|
4754
|
+
(b) => b.type === "tool"
|
|
4755
|
+
);
|
|
4756
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4757
|
+
statusWatcher.stop();
|
|
4758
|
+
const text = getPartialText(contentBlocks);
|
|
4759
|
+
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
4760
|
+
return {
|
|
4761
|
+
text,
|
|
4762
|
+
messages: thisInvocation(),
|
|
4763
|
+
...hasArtifacts ? { artifacts } : {}
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
4766
|
+
log8.info("Tools executing", {
|
|
4767
|
+
requestId,
|
|
4768
|
+
parentToolId,
|
|
4769
|
+
count: toolCalls.length,
|
|
4770
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
4771
|
+
});
|
|
4772
|
+
watchedToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
4773
|
+
const results = await Promise.all(
|
|
4774
|
+
toolCalls.map(async (tc) => {
|
|
4775
|
+
if (signal?.aborted) {
|
|
4776
|
+
return {
|
|
4777
|
+
id: tc.id,
|
|
4778
|
+
result: USER_CANCELLED_RESULT,
|
|
4779
|
+
isError: true
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
let settle;
|
|
4783
|
+
const resultPromise = new Promise((res) => {
|
|
4784
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4785
|
+
});
|
|
4786
|
+
let toolAbort = new AbortController();
|
|
4787
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
4788
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4789
|
+
let settled = false;
|
|
4790
|
+
const safeSettle = (result, isError) => {
|
|
4791
|
+
if (settled) {
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
settled = true;
|
|
4795
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
4796
|
+
settle(result, isError);
|
|
4797
|
+
};
|
|
4798
|
+
const run2 = async (input) => {
|
|
4799
|
+
try {
|
|
4800
|
+
let result;
|
|
4801
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
4802
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4803
|
+
} else {
|
|
4804
|
+
const onLog = (line) => emit({
|
|
4805
|
+
type: "tool_input_delta",
|
|
4806
|
+
id: tc.id,
|
|
4807
|
+
name: tc.name,
|
|
4808
|
+
result: line
|
|
4809
|
+
});
|
|
4810
|
+
result = await executeTool2(
|
|
4811
|
+
tc.name,
|
|
4812
|
+
input,
|
|
4813
|
+
tc.id,
|
|
4814
|
+
onLog,
|
|
4815
|
+
subAgentMessages
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
4818
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
4819
|
+
} catch (err) {
|
|
4820
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
4821
|
+
}
|
|
4822
|
+
};
|
|
4823
|
+
const entry = {
|
|
4824
|
+
id: tc.id,
|
|
4825
|
+
name: tc.name,
|
|
4826
|
+
input: tc.input,
|
|
4827
|
+
parentToolId,
|
|
4828
|
+
abortController: toolAbort,
|
|
4829
|
+
startedAt: Date.now(),
|
|
4830
|
+
settle: safeSettle,
|
|
4831
|
+
rerun: (newInput) => {
|
|
4832
|
+
settled = false;
|
|
4833
|
+
toolAbort = new AbortController();
|
|
4834
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4835
|
+
once: true
|
|
4536
4836
|
});
|
|
4837
|
+
entry.abortController = toolAbort;
|
|
4838
|
+
entry.input = newInput;
|
|
4839
|
+
run2(newInput);
|
|
4537
4840
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4841
|
+
};
|
|
4842
|
+
toolRegistry?.register(entry);
|
|
4843
|
+
const toolStart = Date.now();
|
|
4844
|
+
run2(tc.input);
|
|
4845
|
+
const r = await resultPromise;
|
|
4846
|
+
toolRegistry?.unregister(tc.id);
|
|
4847
|
+
log8.info("Tool completed", {
|
|
4848
|
+
requestId,
|
|
4849
|
+
parentToolId,
|
|
4850
|
+
toolCallId: tc.id,
|
|
4851
|
+
name: tc.name,
|
|
4852
|
+
durationMs: Date.now() - toolStart,
|
|
4853
|
+
isError: r.isError
|
|
4854
|
+
});
|
|
4855
|
+
emit({
|
|
4856
|
+
type: "tool_done",
|
|
4857
|
+
id: tc.id,
|
|
4858
|
+
name: tc.name,
|
|
4859
|
+
result: r.result,
|
|
4860
|
+
isError: r.isError
|
|
4861
|
+
});
|
|
4862
|
+
return r;
|
|
4863
|
+
})
|
|
4864
|
+
);
|
|
4865
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
4866
|
+
for (const r of results) {
|
|
4867
|
+
const block = contentBlocks.find(
|
|
4868
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
4869
|
+
);
|
|
4870
|
+
if (block?.type === "tool") {
|
|
4871
|
+
block.result = r.result;
|
|
4872
|
+
block.isError = r.isError;
|
|
4873
|
+
block.completedAt = Date.now();
|
|
4874
|
+
const innerMsgs = subAgentMessages.get(r.id);
|
|
4875
|
+
if (innerMsgs) {
|
|
4876
|
+
block.subAgentMessages = innerMsgs;
|
|
4540
4877
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4878
|
+
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4879
|
+
try {
|
|
4880
|
+
artifacts[block.name] = JSON.parse(r.result);
|
|
4881
|
+
} catch {
|
|
4544
4882
|
}
|
|
4545
|
-
emit({ type: "thinking", text: event.text });
|
|
4546
|
-
break;
|
|
4547
|
-
case "thinking_complete":
|
|
4548
|
-
contentBlocks.push({
|
|
4549
|
-
type: "thinking",
|
|
4550
|
-
thinking: event.thinking,
|
|
4551
|
-
signature: event.signature,
|
|
4552
|
-
startedAt: thinkingStartedAt,
|
|
4553
|
-
completedAt: event.ts
|
|
4554
|
-
});
|
|
4555
|
-
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
4556
|
-
thinkingStartedAt = 0;
|
|
4557
|
-
break;
|
|
4558
|
-
case "redacted_thinking_complete": {
|
|
4559
|
-
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
4560
|
-
contentBlocks.push({
|
|
4561
|
-
type: "redacted_thinking",
|
|
4562
|
-
data: event.data,
|
|
4563
|
-
startedAt,
|
|
4564
|
-
completedAt: event.ts
|
|
4565
|
-
});
|
|
4566
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
4567
|
-
break;
|
|
4568
4883
|
}
|
|
4569
|
-
case "tool_use":
|
|
4570
|
-
contentBlocks.push({
|
|
4571
|
-
type: "tool",
|
|
4572
|
-
id: event.id,
|
|
4573
|
-
name: event.name,
|
|
4574
|
-
input: event.input,
|
|
4575
|
-
startedAt: Date.now()
|
|
4576
|
-
});
|
|
4577
|
-
emit({
|
|
4578
|
-
type: "tool_start",
|
|
4579
|
-
id: event.id,
|
|
4580
|
-
name: event.name,
|
|
4581
|
-
input: event.input
|
|
4582
|
-
});
|
|
4583
|
-
break;
|
|
4584
|
-
case "done":
|
|
4585
|
-
stopReason = event.stopReason;
|
|
4586
|
-
lastUsage = {
|
|
4587
|
-
inputTokens: event.usage.inputTokens,
|
|
4588
|
-
outputTokens: event.usage.outputTokens,
|
|
4589
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4590
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4591
|
-
llmCalls: 1
|
|
4592
|
-
};
|
|
4593
|
-
lastProviderMetadata = event.providerMetadata;
|
|
4594
|
-
lastModelId = event.modelId;
|
|
4595
|
-
recordUsage({
|
|
4596
|
-
ts: Date.now(),
|
|
4597
|
-
requestId,
|
|
4598
|
-
agentName: subAgentId || "sub-agent",
|
|
4599
|
-
parentToolId,
|
|
4600
|
-
modelId: event.modelId,
|
|
4601
|
-
inputTokens: event.usage.inputTokens,
|
|
4602
|
-
outputTokens: event.usage.outputTokens,
|
|
4603
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4604
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4605
|
-
cost: nanoToDollars(event.cost),
|
|
4606
|
-
billingEvents: event.billingEvents,
|
|
4607
|
-
durationMs: Date.now() - iterStart,
|
|
4608
|
-
toolNames: contentBlocks.filter(
|
|
4609
|
-
(b) => b.type === "tool"
|
|
4610
|
-
).map((b) => b.name)
|
|
4611
|
-
});
|
|
4612
|
-
break;
|
|
4613
|
-
case "error":
|
|
4614
|
-
return {
|
|
4615
|
-
text: `Error: ${event.error}`,
|
|
4616
|
-
messages: thisInvocation()
|
|
4617
|
-
};
|
|
4618
4884
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4885
|
+
messages.push({
|
|
4886
|
+
role: "user",
|
|
4887
|
+
content: r.result,
|
|
4888
|
+
toolCallId: r.id,
|
|
4889
|
+
isToolError: r.isError
|
|
4890
|
+
});
|
|
4623
4891
|
}
|
|
4624
4892
|
}
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4893
|
+
} finally {
|
|
4894
|
+
statusWatcher.stop();
|
|
4895
|
+
}
|
|
4896
|
+
};
|
|
4897
|
+
const wrapRun = async () => {
|
|
4898
|
+
try {
|
|
4899
|
+
const result = await run();
|
|
4900
|
+
log8.info("Sub-agent complete", {
|
|
4901
|
+
requestId,
|
|
4902
|
+
parentToolId,
|
|
4903
|
+
agentName,
|
|
4904
|
+
durationMs: Date.now() - runStart,
|
|
4905
|
+
turns
|
|
4635
4906
|
});
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4640
|
-
statusWatcher.stop();
|
|
4641
|
-
const text = getPartialText(contentBlocks);
|
|
4642
|
-
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
4643
|
-
return {
|
|
4644
|
-
text,
|
|
4645
|
-
messages: thisInvocation(),
|
|
4646
|
-
...hasArtifacts ? { artifacts } : {}
|
|
4647
|
-
};
|
|
4648
|
-
}
|
|
4649
|
-
log8.info("Tools executing", {
|
|
4907
|
+
return result;
|
|
4908
|
+
} catch (err) {
|
|
4909
|
+
log8.warn("Sub-agent error", {
|
|
4650
4910
|
requestId,
|
|
4651
4911
|
parentToolId,
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
});
|
|
4655
|
-
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
4656
|
-
const results = await Promise.all(
|
|
4657
|
-
toolCalls.map(async (tc) => {
|
|
4658
|
-
if (signal?.aborted) {
|
|
4659
|
-
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
4660
|
-
}
|
|
4661
|
-
let settle;
|
|
4662
|
-
const resultPromise = new Promise((res) => {
|
|
4663
|
-
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4664
|
-
});
|
|
4665
|
-
let toolAbort = new AbortController();
|
|
4666
|
-
const cascadeAbort = () => toolAbort.abort();
|
|
4667
|
-
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4668
|
-
let settled = false;
|
|
4669
|
-
const safeSettle = (result, isError) => {
|
|
4670
|
-
if (settled) {
|
|
4671
|
-
return;
|
|
4672
|
-
}
|
|
4673
|
-
settled = true;
|
|
4674
|
-
signal?.removeEventListener("abort", cascadeAbort);
|
|
4675
|
-
settle(result, isError);
|
|
4676
|
-
};
|
|
4677
|
-
const run2 = async (input) => {
|
|
4678
|
-
try {
|
|
4679
|
-
let result;
|
|
4680
|
-
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
4681
|
-
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4682
|
-
} else {
|
|
4683
|
-
const onLog = (line) => emit({
|
|
4684
|
-
type: "tool_input_delta",
|
|
4685
|
-
id: tc.id,
|
|
4686
|
-
name: tc.name,
|
|
4687
|
-
result: line
|
|
4688
|
-
});
|
|
4689
|
-
result = await executeTool2(
|
|
4690
|
-
tc.name,
|
|
4691
|
-
input,
|
|
4692
|
-
tc.id,
|
|
4693
|
-
onLog,
|
|
4694
|
-
subAgentMessages
|
|
4695
|
-
);
|
|
4696
|
-
}
|
|
4697
|
-
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
4698
|
-
} catch (err) {
|
|
4699
|
-
safeSettle(`Error: ${err.message}`, true);
|
|
4700
|
-
}
|
|
4701
|
-
};
|
|
4702
|
-
const entry = {
|
|
4703
|
-
id: tc.id,
|
|
4704
|
-
name: tc.name,
|
|
4705
|
-
input: tc.input,
|
|
4706
|
-
parentToolId,
|
|
4707
|
-
abortController: toolAbort,
|
|
4708
|
-
startedAt: Date.now(),
|
|
4709
|
-
settle: safeSettle,
|
|
4710
|
-
rerun: (newInput) => {
|
|
4711
|
-
settled = false;
|
|
4712
|
-
toolAbort = new AbortController();
|
|
4713
|
-
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4714
|
-
once: true
|
|
4715
|
-
});
|
|
4716
|
-
entry.abortController = toolAbort;
|
|
4717
|
-
entry.input = newInput;
|
|
4718
|
-
run2(newInput);
|
|
4719
|
-
}
|
|
4720
|
-
};
|
|
4721
|
-
toolRegistry?.register(entry);
|
|
4722
|
-
const toolStart = Date.now();
|
|
4723
|
-
run2(tc.input);
|
|
4724
|
-
const r = await resultPromise;
|
|
4725
|
-
toolRegistry?.unregister(tc.id);
|
|
4726
|
-
log8.info("Tool completed", {
|
|
4727
|
-
requestId,
|
|
4728
|
-
parentToolId,
|
|
4729
|
-
toolCallId: tc.id,
|
|
4730
|
-
name: tc.name,
|
|
4731
|
-
durationMs: Date.now() - toolStart,
|
|
4732
|
-
isError: r.isError
|
|
4733
|
-
});
|
|
4734
|
-
emit({
|
|
4735
|
-
type: "tool_done",
|
|
4736
|
-
id: tc.id,
|
|
4737
|
-
name: tc.name,
|
|
4738
|
-
result: r.result,
|
|
4739
|
-
isError: r.isError
|
|
4740
|
-
});
|
|
4741
|
-
return r;
|
|
4742
|
-
})
|
|
4743
|
-
);
|
|
4744
|
-
statusWatcher.stop();
|
|
4745
|
-
lastToolResult = results.at(-1)?.result ?? "";
|
|
4746
|
-
for (const r of results) {
|
|
4747
|
-
const block = contentBlocks.find(
|
|
4748
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
4749
|
-
);
|
|
4750
|
-
if (block?.type === "tool") {
|
|
4751
|
-
block.result = r.result;
|
|
4752
|
-
block.isError = r.isError;
|
|
4753
|
-
block.completedAt = Date.now();
|
|
4754
|
-
const innerMsgs = subAgentMessages.get(r.id);
|
|
4755
|
-
if (innerMsgs) {
|
|
4756
|
-
block.subAgentMessages = innerMsgs;
|
|
4757
|
-
}
|
|
4758
|
-
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4759
|
-
try {
|
|
4760
|
-
artifacts[block.name] = JSON.parse(r.result);
|
|
4761
|
-
} catch {
|
|
4762
|
-
}
|
|
4763
|
-
}
|
|
4764
|
-
}
|
|
4765
|
-
messages.push({
|
|
4766
|
-
role: "user",
|
|
4767
|
-
content: r.result,
|
|
4768
|
-
toolCallId: r.id,
|
|
4769
|
-
isToolError: r.isError
|
|
4770
|
-
});
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
4773
|
-
};
|
|
4774
|
-
const wrapRun = async () => {
|
|
4775
|
-
try {
|
|
4776
|
-
const result = await run();
|
|
4777
|
-
log8.info("Sub-agent complete", {
|
|
4778
|
-
requestId,
|
|
4779
|
-
parentToolId,
|
|
4780
|
-
agentName,
|
|
4781
|
-
durationMs: Date.now() - runStart,
|
|
4782
|
-
turns
|
|
4783
|
-
});
|
|
4784
|
-
return result;
|
|
4785
|
-
} catch (err) {
|
|
4786
|
-
log8.warn("Sub-agent error", {
|
|
4787
|
-
requestId,
|
|
4788
|
-
parentToolId,
|
|
4789
|
-
agentName,
|
|
4790
|
-
error: err.message
|
|
4912
|
+
agentName,
|
|
4913
|
+
error: err.message
|
|
4791
4914
|
});
|
|
4792
4915
|
throw err;
|
|
4793
4916
|
}
|
|
@@ -4875,7 +4998,6 @@ var init_tools2 = __esm({
|
|
|
4875
4998
|
init_readSpec();
|
|
4876
4999
|
BROWSER_TOOLS = [
|
|
4877
5000
|
{
|
|
4878
|
-
clearable: false,
|
|
4879
5001
|
name: "setupBrowser",
|
|
4880
5002
|
description: "Pre-authenticate the browser and optionally navigate to a starting page. Call this before interacting with authenticated content instead of manually logging in. Auth is optional \u2014 omit to just navigate without authenticating.",
|
|
4881
5003
|
inputSchema: {
|
|
@@ -4908,7 +5030,6 @@ var init_tools2 = __esm({
|
|
|
4908
5030
|
}
|
|
4909
5031
|
},
|
|
4910
5032
|
{
|
|
4911
|
-
clearable: true,
|
|
4912
5033
|
name: "browserCommand",
|
|
4913
5034
|
description: "Interact with the app's live preview by sending browser commands. Commands execute sequentially with an animated cursor. Always start with a snapshot to see the current state and get ref identifiers. The result includes a snapshot field with the final page state after all steps complete. On error, the failing step has an error field and execution stops. Batches that contain an interactive step (click, type, select) also return a `recording` object \u2014 one chunk of a continuous per-session rrweb recording that the viewer stitches into a single replay (not a standalone per-call clip). Timeout: 120s.",
|
|
4914
5035
|
inputSchema: {
|
|
@@ -5356,7 +5477,6 @@ var init_browserAutomation = __esm({
|
|
|
5356
5477
|
CAPTURE_COMMANDS = /* @__PURE__ */ new Set(["screenshotViewport", "screenshotFullPage"]);
|
|
5357
5478
|
browserAutomationTool = {
|
|
5358
5479
|
definition: {
|
|
5359
|
-
clearable: true,
|
|
5360
5480
|
name: "runAutomatedBrowserTest",
|
|
5361
5481
|
description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes to frontend code, to reproduce user-reported issues, or to test end-to-end flows. Never give it explicit values to use when filling out forms or creating accounts \u2014 it will use its own judgement (often it needs specific values to trigger dev-mode bypasses of things like login verification codes).",
|
|
5362
5482
|
inputSchema: {
|
|
@@ -5447,7 +5567,6 @@ var init_screenshot2 = __esm({
|
|
|
5447
5567
|
init_browserAutomation();
|
|
5448
5568
|
init_surfaces();
|
|
5449
5569
|
screenshotDefinition = {
|
|
5450
|
-
clearable: true,
|
|
5451
5570
|
name: "screenshot",
|
|
5452
5571
|
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. The analysis is not precise about every detail \u2014 for example it cannot reliably identify specific fonts by name, only describe what the letterforms look like. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as `imageUrl` to skip recapture; `imageUrl` also accepts the disk path of an image file (a user upload, a saved asset) to analyze that instead of the preview. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
5453
5572
|
inputSchema: {
|
|
@@ -5521,7 +5640,6 @@ var init_searchGoogle2 = __esm({
|
|
|
5521
5640
|
init_runMindstudioCli();
|
|
5522
5641
|
init_runCli();
|
|
5523
5642
|
definition = {
|
|
5524
|
-
clearable: false,
|
|
5525
5643
|
name: "searchGoogle",
|
|
5526
5644
|
description: 'Search Google for web results. Reserch modern design trends in industries or verticals, "best [domain] apps 2026", ui patterns, or find something specific if the the user has an explicit reference. Searching for and reading case studies is a great way to get information and context about a project\'s domain. Prioritize authoritative sources like Figma and other design leaders, avoid random blog spam. Pick one or more URLs from the results and then use `scrapeWebUrl` to get their text content.',
|
|
5527
5645
|
inputSchema: {
|
|
@@ -5564,7 +5682,6 @@ var init_scrapeWebUrl = __esm({
|
|
|
5564
5682
|
init_runMindstudioCli();
|
|
5565
5683
|
init_runCli();
|
|
5566
5684
|
definition2 = {
|
|
5567
|
-
clearable: false,
|
|
5568
5685
|
name: "scrapeWebUrl",
|
|
5569
5686
|
description: "Fetch the content of a web page as markdown. Use when reading sites from search results or specific things the user wants to incorporate.",
|
|
5570
5687
|
inputSchema: {
|
|
@@ -5657,7 +5774,6 @@ Identify the specific design moves that make this page interesting and unique, d
|
|
|
5657
5774
|
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
|
|
5658
5775
|
`;
|
|
5659
5776
|
definition3 = {
|
|
5660
|
-
clearable: false,
|
|
5661
5777
|
name: "analyzeDesign",
|
|
5662
5778
|
description: "Analyze the visual design of a website, an image URL, or an image file on disk. Websites are automatically screenshotted first. Provides static image analysis only, will not capture animations or video. If no prompt is provided, performs a full design reference analysis (mood, color, typography, layout, distinctiveness). Provide a custom prompt to ask a specific design question instead. Use a bulleted list to ask many questions at once.",
|
|
5663
5779
|
inputSchema: {
|
|
@@ -5705,7 +5821,6 @@ var init_analyzeImage2 = __esm({
|
|
|
5705
5821
|
init_screenshot();
|
|
5706
5822
|
init_surfaces();
|
|
5707
5823
|
definition4 = {
|
|
5708
|
-
clearable: true,
|
|
5709
5824
|
name: "analyzeImage",
|
|
5710
5825
|
description: "Analyze an image using a vision model. Provides static image analysis only, will not capture animations or video. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. If you are analyzing a screenshot of the app preview, you can reuse the same screenshot URL multiple times to ask multiple questions.",
|
|
5711
5826
|
inputSchema: {
|
|
@@ -5977,7 +6092,6 @@ var init_generateImages = __esm({
|
|
|
5977
6092
|
init_imageGenerator();
|
|
5978
6093
|
init_surfaces();
|
|
5979
6094
|
definition5 = {
|
|
5980
|
-
clearable: false,
|
|
5981
6095
|
name: "generateImages",
|
|
5982
6096
|
description: "Generate images. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
|
|
5983
6097
|
inputSchema: {
|
|
@@ -6053,7 +6167,6 @@ var init_editImages = __esm({
|
|
|
6053
6167
|
init_imageGenerator();
|
|
6054
6168
|
init_surfaces();
|
|
6055
6169
|
definition6 = {
|
|
6056
|
-
clearable: false,
|
|
6057
6170
|
name: "editImages",
|
|
6058
6171
|
description: "Edit or transform existing images. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more references into something new. Returns CDN URLs with analysis.",
|
|
6059
6172
|
inputSchema: {
|
|
@@ -6116,7 +6229,6 @@ var init_copyEditor = __esm({
|
|
|
6116
6229
|
BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
|
|
6117
6230
|
copyEditorTool = {
|
|
6118
6231
|
definition: {
|
|
6119
|
-
clearable: false,
|
|
6120
6232
|
name: "copyEditor",
|
|
6121
6233
|
description: "Hand it user-facing copy and it hands back a sharper version \u2014 better structured for its audience and free of the overused words, telltale constructions, and rhythms that make text read as AI-generated. Think of it as a design expert for words: it elevates how the copy communicates and strips the AI fingerprints, but it never invents facts or claims you didn't give it. Use it on anything users will read: in-app strings, empty states, errors, the Build Overview, deck copy, launch posts, Slack announcements. Readonly.",
|
|
6122
6234
|
inputSchema: {
|
|
@@ -6179,7 +6291,6 @@ var init_polishCopy = __esm({
|
|
|
6179
6291
|
"use strict";
|
|
6180
6292
|
init_copyEditor();
|
|
6181
6293
|
definition7 = {
|
|
6182
|
-
clearable: false,
|
|
6183
6294
|
name: "polishCopy",
|
|
6184
6295
|
description: "Hand off any user-facing copy you've written \u2014 headlines, captions, labels, body text \u2014 and get back a sharper version: better built for its audience and free of the fingerprints that make writing read as AI. It elevates how the copy communicates without inventing facts or claims you didn't give it. Give it the text plus what it's for (where it appears, the audience).",
|
|
6185
6296
|
inputSchema: {
|
|
@@ -6673,7 +6784,6 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
|
|
|
6673
6784
|
];
|
|
6674
6785
|
designExpertTool = {
|
|
6675
6786
|
definition: {
|
|
6676
|
-
clearable: false,
|
|
6677
6787
|
name: "visualDesignExpert",
|
|
6678
6788
|
description: DESCRIPTION,
|
|
6679
6789
|
inputSchema: {
|
|
@@ -6916,7 +7026,6 @@ var init_productVision = __esm({
|
|
|
6916
7026
|
init_surfaces();
|
|
6917
7027
|
productVisionTool = {
|
|
6918
7028
|
definition: {
|
|
6919
|
-
clearable: false,
|
|
6920
7029
|
name: "productVision",
|
|
6921
7030
|
description: "Owns the product roadmap. Reads spec and roadmap files automatically. Creates, updates, and deletes roadmap items in src/roadmap/. Describe the situation and what needs to happen.",
|
|
6922
7031
|
inputSchema: {
|
|
@@ -7048,7 +7157,6 @@ var init_codeSanityCheck = __esm({
|
|
|
7048
7157
|
BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
7049
7158
|
codeSanityCheckTool = {
|
|
7050
7159
|
definition: {
|
|
7051
|
-
clearable: false,
|
|
7052
7160
|
name: "codeSanityCheck",
|
|
7053
7161
|
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
7054
7162
|
inputSchema: {
|
|
@@ -7096,6 +7204,128 @@ var init_codeSanityCheck = __esm({
|
|
|
7096
7204
|
}
|
|
7097
7205
|
});
|
|
7098
7206
|
|
|
7207
|
+
// src/tools/spec/writeBuildOverview.ts
|
|
7208
|
+
import fs19 from "fs";
|
|
7209
|
+
function initialDelivery() {
|
|
7210
|
+
return `### Your deliverable
|
|
7211
|
+
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
7212
|
+
|
|
7213
|
+
<overview_shell>
|
|
7214
|
+
${OVERVIEW_SHELL}
|
|
7215
|
+
</overview_shell>
|
|
7216
|
+
|
|
7217
|
+
Then reply with a one-line summary of what you wrote.`;
|
|
7218
|
+
}
|
|
7219
|
+
function refreshDelivery() {
|
|
7220
|
+
return `### Your deliverable
|
|
7221
|
+
The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
|
|
7222
|
+
|
|
7223
|
+
Then reply with a one-line summary of what you changed.`;
|
|
7224
|
+
}
|
|
7225
|
+
async function renderBuildOverview(content, context, opts) {
|
|
7226
|
+
if (!content) {
|
|
7227
|
+
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7228
|
+
}
|
|
7229
|
+
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7230
|
+
const task = `<overview_copy>${content}</overview_copy>
|
|
7231
|
+
|
|
7232
|
+
${DESIGN_BRIEF}
|
|
7233
|
+
|
|
7234
|
+
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
7235
|
+
try {
|
|
7236
|
+
if (opts.background) {
|
|
7237
|
+
const result2 = await runDesignExpertRender(
|
|
7238
|
+
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
7239
|
+
context
|
|
7240
|
+
);
|
|
7241
|
+
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
7242
|
+
return result2.text;
|
|
7243
|
+
}
|
|
7244
|
+
const result = await runDesignExpertRender({ task }, context);
|
|
7245
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7246
|
+
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7247
|
+
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7248
|
+
${result.text}`;
|
|
7249
|
+
}
|
|
7250
|
+
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
7251
|
+
} catch (err) {
|
|
7252
|
+
return `Error generating build overview: ${err.message}`;
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
7255
|
+
var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
|
|
7256
|
+
var init_writeBuildOverview = __esm({
|
|
7257
|
+
"src/tools/spec/writeBuildOverview.ts"() {
|
|
7258
|
+
"use strict";
|
|
7259
|
+
init_designExpert();
|
|
7260
|
+
OVERVIEW_FILE = "src/overview.html";
|
|
7261
|
+
DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
|
|
7262
|
+
|
|
7263
|
+
Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
|
|
7264
|
+
|
|
7265
|
+
### The single hard rule
|
|
7266
|
+
The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
|
|
7267
|
+
|
|
7268
|
+
### What it is (and is not)
|
|
7269
|
+
- A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
|
|
7270
|
+
- Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
|
|
7271
|
+
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
7272
|
+
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
7273
|
+
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
7274
|
+
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
7275
|
+
|
|
7276
|
+
### Constraints
|
|
7277
|
+
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
7278
|
+
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
7279
|
+
OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
7280
|
+
<html lang="en">
|
|
7281
|
+
<head>
|
|
7282
|
+
<meta charset="utf-8">
|
|
7283
|
+
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
7284
|
+
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
7285
|
+
The user-scalable=no viewport is intentional; leave it. -->
|
|
7286
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
7287
|
+
<title>Build Overview</title>
|
|
7288
|
+
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
7289
|
+
<style>
|
|
7290
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7291
|
+
</style>
|
|
7292
|
+
</head>
|
|
7293
|
+
<body>
|
|
7294
|
+
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
7295
|
+
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
7296
|
+
app's spec. -->
|
|
7297
|
+
</body>
|
|
7298
|
+
</html>`;
|
|
7299
|
+
buildOverviewTool = {
|
|
7300
|
+
definition: {
|
|
7301
|
+
name: "writeBuildOverview",
|
|
7302
|
+
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build; routine refreshes ride specSync's `refreshBuildOverview` flag instead, so call this directly only for the initial generation or when the user explicitly asks.",
|
|
7303
|
+
inputSchema: {
|
|
7304
|
+
type: "object",
|
|
7305
|
+
properties: {
|
|
7306
|
+
content: {
|
|
7307
|
+
type: "string",
|
|
7308
|
+
description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
|
|
7309
|
+
}
|
|
7310
|
+
},
|
|
7311
|
+
required: ["content"]
|
|
7312
|
+
}
|
|
7313
|
+
},
|
|
7314
|
+
async execute(input, context) {
|
|
7315
|
+
if (!context) {
|
|
7316
|
+
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
7317
|
+
}
|
|
7318
|
+
const content = (input.content ?? "").trim();
|
|
7319
|
+
const background = Boolean(content) && fs19.existsSync(OVERVIEW_FILE);
|
|
7320
|
+
if (background) {
|
|
7321
|
+
input.background = true;
|
|
7322
|
+
}
|
|
7323
|
+
return renderBuildOverview(content, context, { background });
|
|
7324
|
+
}
|
|
7325
|
+
};
|
|
7326
|
+
}
|
|
7327
|
+
});
|
|
7328
|
+
|
|
7099
7329
|
// src/subagents/specSync/tools.ts
|
|
7100
7330
|
var SPEC_SYNC_TOOLS;
|
|
7101
7331
|
var init_tools7 = __esm({
|
|
@@ -7141,6 +7371,7 @@ var init_specSync = __esm({
|
|
|
7141
7371
|
init_runner();
|
|
7142
7372
|
init_context();
|
|
7143
7373
|
init_tools8();
|
|
7374
|
+
init_writeBuildOverview();
|
|
7144
7375
|
init_tools7();
|
|
7145
7376
|
init_lock();
|
|
7146
7377
|
init_surfaces();
|
|
@@ -7157,15 +7388,18 @@ ${readAsset(
|
|
|
7157
7388
|
// next real turn as a hidden background_results note instead.
|
|
7158
7389
|
backgroundNotify: "passive",
|
|
7159
7390
|
definition: {
|
|
7160
|
-
clearable: false,
|
|
7161
7391
|
name: "specSync",
|
|
7162
|
-
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background and completes silently \u2014 do not wait for it; its outcome appears as an automated note at the start of a later turn.",
|
|
7392
|
+
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Set `refreshBuildOverview` after a deploy or a large milestone to also re-author the Build Overview from the updated spec. Always runs in the background and completes silently \u2014 do not wait for it; its outcome appears as an automated note at the start of a later turn.",
|
|
7163
7393
|
inputSchema: {
|
|
7164
7394
|
type: "object",
|
|
7165
7395
|
properties: {
|
|
7166
7396
|
task: {
|
|
7167
7397
|
type: "string",
|
|
7168
7398
|
description: "What you changed in the code and why, in plain language \u2014 the way you would explain it to a teammate. The specialist reads the spec itself and decides which sections to update; you do not need to name files or spec locations."
|
|
7399
|
+
},
|
|
7400
|
+
refreshBuildOverview: {
|
|
7401
|
+
type: "boolean",
|
|
7402
|
+
description: "Also refresh the Build Overview (src/overview.html) after reconciling \u2014 the specialist authors fresh copy from the updated spec and re-renders the page. Set this after a deploy or a large milestone; leave it off for routine syncs."
|
|
7169
7403
|
}
|
|
7170
7404
|
},
|
|
7171
7405
|
required: ["task"]
|
|
@@ -7189,12 +7423,27 @@ ${readAsset(
|
|
|
7189
7423
|
parts.push(specIndex);
|
|
7190
7424
|
}
|
|
7191
7425
|
const system = parts.join("\n\n");
|
|
7426
|
+
const refreshOverview = input.refreshBuildOverview === true;
|
|
7427
|
+
const tools2 = refreshOverview ? [...SPEC_SYNC_TOOLS, buildOverviewTool.definition] : SPEC_SYNC_TOOLS;
|
|
7428
|
+
const task = refreshOverview ? `${input.task}
|
|
7429
|
+
|
|
7430
|
+
After reconciling the spec, refresh the Build Overview: author the complete updated copy from the freshly-reconciled spec (follow the writeBuildOverview tool description) and call \`writeBuildOverview\` once with the final copy.` : input.task;
|
|
7192
7431
|
const result = await runSubAgent({
|
|
7193
7432
|
system,
|
|
7194
|
-
task
|
|
7195
|
-
tools:
|
|
7433
|
+
task,
|
|
7434
|
+
tools: tools2,
|
|
7196
7435
|
externalTools: /* @__PURE__ */ new Set(),
|
|
7197
|
-
executeTool: (name, toolInput
|
|
7436
|
+
executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
|
|
7437
|
+
if (name === "writeBuildOverview") {
|
|
7438
|
+
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
7439
|
+
return renderBuildOverview(
|
|
7440
|
+
String(toolInput.content ?? "").trim(),
|
|
7441
|
+
childCtx,
|
|
7442
|
+
{ background: false }
|
|
7443
|
+
);
|
|
7444
|
+
}
|
|
7445
|
+
return executeTool(name, toolInput, context);
|
|
7446
|
+
},
|
|
7198
7447
|
apiConfig: context.apiConfig,
|
|
7199
7448
|
model: resolveModel("specSync", context.models, context.model),
|
|
7200
7449
|
subAgentId: "specSync",
|
|
@@ -7233,7 +7482,6 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7233
7482
|
init_runCli();
|
|
7234
7483
|
scrapeWebUrlTool = {
|
|
7235
7484
|
definition: {
|
|
7236
|
-
clearable: false,
|
|
7237
7485
|
name: "scrapeWebUrl",
|
|
7238
7486
|
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
7239
7487
|
inputSchema: {
|
|
@@ -7277,143 +7525,26 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7277
7525
|
}
|
|
7278
7526
|
});
|
|
7279
7527
|
|
|
7280
|
-
// src/tools/
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
return `### Your deliverable
|
|
7284
|
-
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
7285
|
-
|
|
7286
|
-
<overview_shell>
|
|
7287
|
-
${OVERVIEW_SHELL}
|
|
7288
|
-
</overview_shell>
|
|
7289
|
-
|
|
7290
|
-
Then reply with a one-line summary of what you wrote.`;
|
|
7528
|
+
// src/tools/index.ts
|
|
7529
|
+
function deriveContext(parent, toolCallId) {
|
|
7530
|
+
return { ...parent, toolCallId };
|
|
7291
7531
|
}
|
|
7292
|
-
function
|
|
7293
|
-
return
|
|
7294
|
-
The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
|
|
7295
|
-
|
|
7296
|
-
Then reply with a one-line summary of what you changed.`;
|
|
7532
|
+
function getToolDefinitions(_onboardingState) {
|
|
7533
|
+
return ALL_TOOLS.map((t) => t.definition);
|
|
7297
7534
|
}
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
### What it is (and is not)
|
|
7312
|
-
- A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
|
|
7313
|
-
- Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
|
|
7314
|
-
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
7315
|
-
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
7316
|
-
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
7317
|
-
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
7318
|
-
|
|
7319
|
-
### Constraints
|
|
7320
|
-
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
7321
|
-
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
7322
|
-
OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
7323
|
-
<html lang="en">
|
|
7324
|
-
<head>
|
|
7325
|
-
<meta charset="utf-8">
|
|
7326
|
-
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
7327
|
-
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
7328
|
-
The user-scalable=no viewport is intentional; leave it. -->
|
|
7329
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
7330
|
-
<title>Build Overview</title>
|
|
7331
|
-
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
7332
|
-
<style>
|
|
7333
|
-
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7334
|
-
</style>
|
|
7335
|
-
</head>
|
|
7336
|
-
<body>
|
|
7337
|
-
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
7338
|
-
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
7339
|
-
app's spec. -->
|
|
7340
|
-
</body>
|
|
7341
|
-
</html>`;
|
|
7342
|
-
buildOverviewTool = {
|
|
7343
|
-
definition: {
|
|
7344
|
-
clearable: false,
|
|
7345
|
-
name: "writeBuildOverview",
|
|
7346
|
-
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
|
|
7347
|
-
inputSchema: {
|
|
7348
|
-
type: "object",
|
|
7349
|
-
properties: {
|
|
7350
|
-
content: {
|
|
7351
|
-
type: "string",
|
|
7352
|
-
description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
|
|
7353
|
-
}
|
|
7354
|
-
},
|
|
7355
|
-
required: ["content"]
|
|
7356
|
-
}
|
|
7357
|
-
},
|
|
7358
|
-
async execute(input, context) {
|
|
7359
|
-
if (!context) {
|
|
7360
|
-
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
7361
|
-
}
|
|
7362
|
-
const content = (input.content ?? "").trim();
|
|
7363
|
-
if (!content) {
|
|
7364
|
-
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7365
|
-
}
|
|
7366
|
-
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7367
|
-
const task = `<overview_copy>${content}</overview_copy>
|
|
7368
|
-
|
|
7369
|
-
${DESIGN_BRIEF}
|
|
7370
|
-
|
|
7371
|
-
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
7372
|
-
try {
|
|
7373
|
-
if (exists) {
|
|
7374
|
-
input.background = true;
|
|
7375
|
-
const result2 = await runDesignExpertRender(
|
|
7376
|
-
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
7377
|
-
context
|
|
7378
|
-
);
|
|
7379
|
-
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
7380
|
-
return result2.text;
|
|
7381
|
-
}
|
|
7382
|
-
const result = await runDesignExpertRender({ task }, context);
|
|
7383
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7384
|
-
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7385
|
-
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7386
|
-
${result.text}`;
|
|
7387
|
-
}
|
|
7388
|
-
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
7389
|
-
} catch (err) {
|
|
7390
|
-
return `Error generating build overview: ${err.message}`;
|
|
7391
|
-
}
|
|
7392
|
-
}
|
|
7393
|
-
};
|
|
7394
|
-
}
|
|
7395
|
-
});
|
|
7396
|
-
|
|
7397
|
-
// src/tools/index.ts
|
|
7398
|
-
function deriveContext(parent, toolCallId) {
|
|
7399
|
-
return { ...parent, toolCallId };
|
|
7400
|
-
}
|
|
7401
|
-
function getToolDefinitions(_onboardingState) {
|
|
7402
|
-
return ALL_TOOLS.map((t) => t.definition);
|
|
7403
|
-
}
|
|
7404
|
-
function getToolByName(name) {
|
|
7405
|
-
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
7406
|
-
}
|
|
7407
|
-
function executeTool(name, input, context) {
|
|
7408
|
-
const tool = getToolByName(name);
|
|
7409
|
-
if (!tool) {
|
|
7410
|
-
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
7411
|
-
}
|
|
7412
|
-
return tool.execute(input, context);
|
|
7413
|
-
}
|
|
7414
|
-
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7415
|
-
var init_tools8 = __esm({
|
|
7416
|
-
"src/tools/index.ts"() {
|
|
7535
|
+
function getToolByName(name) {
|
|
7536
|
+
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
7537
|
+
}
|
|
7538
|
+
function executeTool(name, input, context) {
|
|
7539
|
+
const tool = getToolByName(name);
|
|
7540
|
+
if (!tool) {
|
|
7541
|
+
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
7542
|
+
}
|
|
7543
|
+
return tool.execute(input, context);
|
|
7544
|
+
}
|
|
7545
|
+
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7546
|
+
var init_tools8 = __esm({
|
|
7547
|
+
"src/tools/index.ts"() {
|
|
7417
7548
|
"use strict";
|
|
7418
7549
|
init_readSpec();
|
|
7419
7550
|
init_writeSpec();
|
|
@@ -7680,6 +7811,70 @@ var init_parsePartialJson = __esm({
|
|
|
7680
7811
|
}
|
|
7681
7812
|
});
|
|
7682
7813
|
|
|
7814
|
+
// src/automatedActions/resolve.ts
|
|
7815
|
+
function resolveAction(text) {
|
|
7816
|
+
const parsed = parseSentinel(text);
|
|
7817
|
+
if (!parsed) {
|
|
7818
|
+
return null;
|
|
7819
|
+
}
|
|
7820
|
+
const { name: triggerName, remainder } = parsed;
|
|
7821
|
+
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
7822
|
+
return null;
|
|
7823
|
+
}
|
|
7824
|
+
let params = {};
|
|
7825
|
+
if (remainder) {
|
|
7826
|
+
try {
|
|
7827
|
+
params = JSON.parse(remainder.split("\n")[0]);
|
|
7828
|
+
} catch {
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
7832
|
+
let next;
|
|
7833
|
+
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7834
|
+
if (fmMatch) {
|
|
7835
|
+
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
7836
|
+
if (nextMatch) {
|
|
7837
|
+
next = nextMatch[1];
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
7841
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7842
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
7843
|
+
body = body.replaceAll(`{{${key}}}`, str);
|
|
7844
|
+
}
|
|
7845
|
+
return {
|
|
7846
|
+
message: automatedMessage(triggerName, body),
|
|
7847
|
+
next
|
|
7848
|
+
};
|
|
7849
|
+
}
|
|
7850
|
+
function getActionChain(startName) {
|
|
7851
|
+
const chain = [];
|
|
7852
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7853
|
+
let name = startName;
|
|
7854
|
+
while (name && !seen.has(name)) {
|
|
7855
|
+
seen.add(name);
|
|
7856
|
+
chain.push(name);
|
|
7857
|
+
let body;
|
|
7858
|
+
try {
|
|
7859
|
+
body = readAsset("automatedActions", `${name}.md`);
|
|
7860
|
+
} catch {
|
|
7861
|
+
break;
|
|
7862
|
+
}
|
|
7863
|
+
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7864
|
+
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
7865
|
+
}
|
|
7866
|
+
return chain;
|
|
7867
|
+
}
|
|
7868
|
+
var NON_ACTION_SENTINELS;
|
|
7869
|
+
var init_resolve = __esm({
|
|
7870
|
+
"src/automatedActions/resolve.ts"() {
|
|
7871
|
+
"use strict";
|
|
7872
|
+
init_assets();
|
|
7873
|
+
init_sentinel();
|
|
7874
|
+
NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
7875
|
+
}
|
|
7876
|
+
});
|
|
7877
|
+
|
|
7683
7878
|
// src/errors.ts
|
|
7684
7879
|
function friendlyError(raw) {
|
|
7685
7880
|
for (const [pattern, message] of patterns) {
|
|
@@ -8182,6 +8377,9 @@ async function runTurn(params) {
|
|
|
8182
8377
|
let lastCompletedTools = "";
|
|
8183
8378
|
let lastCompletedInput = "";
|
|
8184
8379
|
let lastCompletedResult = "";
|
|
8380
|
+
let subAgentText = "";
|
|
8381
|
+
let currentToolNames = "";
|
|
8382
|
+
let contentBlocksRef = [];
|
|
8185
8383
|
let turnInputTokens = 0;
|
|
8186
8384
|
let turnOutputTokens = 0;
|
|
8187
8385
|
let turnCacheCreation = 0;
|
|
@@ -8190,281 +8388,328 @@ async function runTurn(params) {
|
|
|
8190
8388
|
let lastCallInputTokens = 0;
|
|
8191
8389
|
let lastCallCacheCreation = 0;
|
|
8192
8390
|
let lastCallCacheRead = 0;
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
} } : startStatusWatcher({
|
|
8224
|
-
apiConfig,
|
|
8225
|
-
getContext: () => {
|
|
8226
|
-
const parts = [];
|
|
8227
|
-
const toolName = currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
8228
|
-
if (toolName) {
|
|
8229
|
-
parts.push(`Tool: ${toolName}`);
|
|
8230
|
-
}
|
|
8231
|
-
if (lastCompletedInput) {
|
|
8232
|
-
parts.push(`Tool input: ${lastCompletedInput.slice(-1500)}`);
|
|
8233
|
-
}
|
|
8234
|
-
if (lastCompletedResult) {
|
|
8235
|
-
parts.push(`Tool result: ${lastCompletedResult.slice(-1500)}`);
|
|
8236
|
-
}
|
|
8237
|
-
const text = subAgentText || getTextContent(contentBlocks).slice(-2e3);
|
|
8238
|
-
if (text) {
|
|
8239
|
-
parts.push(`Assistant text: ${text}`);
|
|
8240
|
-
}
|
|
8241
|
-
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
8242
|
-
parts.push(`Build phase: ${onboardingState}`);
|
|
8391
|
+
const statusWatcher = isFirstMessage ? { stop() {
|
|
8392
|
+
}, pause() {
|
|
8393
|
+
}, resume() {
|
|
8394
|
+
} } : startStatusWatcher({
|
|
8395
|
+
apiConfig,
|
|
8396
|
+
getContext: () => {
|
|
8397
|
+
const parts = [];
|
|
8398
|
+
const toolName = currentToolNames || getToolCalls(contentBlocksRef).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
8399
|
+
if (toolName) {
|
|
8400
|
+
parts.push(`Tool: ${toolName}`);
|
|
8401
|
+
}
|
|
8402
|
+
const toolInput = sanitizeStatusText(lastCompletedInput);
|
|
8403
|
+
if (toolInput) {
|
|
8404
|
+
parts.push(`Tool input: ${toolInput.slice(-1500)}`);
|
|
8405
|
+
}
|
|
8406
|
+
const toolResult = sanitizeStatusText(lastCompletedResult);
|
|
8407
|
+
if (toolResult) {
|
|
8408
|
+
parts.push(`Tool result: ${toolResult.slice(-1500)}`);
|
|
8409
|
+
}
|
|
8410
|
+
const text = subAgentText || getTextContent(contentBlocksRef).slice(-2e3);
|
|
8411
|
+
if (text) {
|
|
8412
|
+
parts.push(`Assistant text: ${text}`);
|
|
8413
|
+
}
|
|
8414
|
+
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
8415
|
+
parts.push(`Build phase: ${onboardingState}`);
|
|
8416
|
+
}
|
|
8417
|
+
let hasUserSignal = false;
|
|
8418
|
+
for (const entry of keptEntries) {
|
|
8419
|
+
if (entry.hidden) {
|
|
8420
|
+
continue;
|
|
8243
8421
|
}
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
if (automated) {
|
|
8422
|
+
const automated = parseSentinel(entry.text);
|
|
8423
|
+
if (automated) {
|
|
8424
|
+
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
8247
8425
|
parts.push(`Automated action: ${automated.name}`);
|
|
8248
|
-
|
|
8249
|
-
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
8426
|
+
hasUserSignal = true;
|
|
8250
8427
|
}
|
|
8428
|
+
} else if (entry.text) {
|
|
8429
|
+
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
8430
|
+
hasUserSignal = true;
|
|
8251
8431
|
}
|
|
8252
|
-
return parts.join("\n");
|
|
8253
|
-
},
|
|
8254
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
8255
|
-
signal
|
|
8256
|
-
});
|
|
8257
|
-
async function handlePartialInput(acc, id, name, partial) {
|
|
8258
|
-
const tool = getToolByName(name);
|
|
8259
|
-
if (!tool?.streaming) {
|
|
8260
|
-
return;
|
|
8261
8432
|
}
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
transform,
|
|
8265
|
-
partialInput
|
|
8266
|
-
} = tool.streaming;
|
|
8267
|
-
if (partialInput) {
|
|
8268
|
-
const result = partialInput(partial, acc.lastEmittedCount);
|
|
8269
|
-
if (!result) {
|
|
8270
|
-
return;
|
|
8271
|
-
}
|
|
8272
|
-
acc.lastEmittedCount = result.emittedCount;
|
|
8273
|
-
acc.started = true;
|
|
8274
|
-
onEvent({
|
|
8275
|
-
type: "tool_start",
|
|
8276
|
-
id,
|
|
8277
|
-
name,
|
|
8278
|
-
input: result.input,
|
|
8279
|
-
partial: true
|
|
8280
|
-
});
|
|
8281
|
-
return;
|
|
8433
|
+
if (!hasUserSignal && !toolName && !text) {
|
|
8434
|
+
return "";
|
|
8282
8435
|
}
|
|
8283
|
-
|
|
8284
|
-
|
|
8436
|
+
return parts.join("\n");
|
|
8437
|
+
},
|
|
8438
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
8439
|
+
signal
|
|
8440
|
+
});
|
|
8441
|
+
try {
|
|
8442
|
+
while (true) {
|
|
8443
|
+
let getOrCreateAccumulator2 = function(id, name) {
|
|
8444
|
+
let acc = toolInputAccumulators.get(id);
|
|
8445
|
+
if (!acc) {
|
|
8446
|
+
acc = { name, json: "", started: false, lastEmittedCount: 0 };
|
|
8447
|
+
toolInputAccumulators.set(id, acc);
|
|
8448
|
+
}
|
|
8449
|
+
return acc;
|
|
8450
|
+
};
|
|
8451
|
+
var getOrCreateAccumulator = getOrCreateAccumulator2;
|
|
8452
|
+
if (signal?.aborted) {
|
|
8453
|
+
onEvent({ type: "turn_cancelled" });
|
|
8454
|
+
saveSession(state);
|
|
8285
8455
|
return;
|
|
8286
8456
|
}
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8457
|
+
const iterStart = Date.now();
|
|
8458
|
+
const contentBlocks = [];
|
|
8459
|
+
const thinkingBlockStartTimes = [];
|
|
8460
|
+
let thinkingCompleteCount = 0;
|
|
8461
|
+
let lastThinkingRelatedStartedAt;
|
|
8462
|
+
let textBlockOpen = false;
|
|
8463
|
+
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
8464
|
+
let stopReason = "end_turn";
|
|
8465
|
+
let turnProviderMetadata;
|
|
8466
|
+
let turnModelId;
|
|
8467
|
+
contentBlocksRef = contentBlocks;
|
|
8468
|
+
subAgentText = "";
|
|
8469
|
+
currentToolNames = "";
|
|
8470
|
+
async function handlePartialInput(acc, id, name, partial) {
|
|
8471
|
+
const tool = getToolByName(name);
|
|
8472
|
+
if (!tool?.streaming) {
|
|
8294
8473
|
return;
|
|
8295
8474
|
}
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
model: parentModel,
|
|
8306
|
-
requestId,
|
|
8307
|
-
system,
|
|
8308
|
-
messages: cleanMessagesForApi(state.messages),
|
|
8309
|
-
tools: tools2,
|
|
8310
|
-
signal
|
|
8311
|
-
},
|
|
8312
|
-
{
|
|
8313
|
-
onRetry: (attempt) => {
|
|
8314
|
-
onEvent({
|
|
8315
|
-
type: "status",
|
|
8316
|
-
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8317
|
-
});
|
|
8475
|
+
const {
|
|
8476
|
+
contentField = "content",
|
|
8477
|
+
transform,
|
|
8478
|
+
partialInput
|
|
8479
|
+
} = tool.streaming;
|
|
8480
|
+
if (partialInput) {
|
|
8481
|
+
const result = partialInput(partial, acc.lastEmittedCount);
|
|
8482
|
+
if (!result) {
|
|
8483
|
+
return;
|
|
8318
8484
|
}
|
|
8485
|
+
acc.lastEmittedCount = result.emittedCount;
|
|
8486
|
+
acc.started = true;
|
|
8487
|
+
onEvent({
|
|
8488
|
+
type: "tool_start",
|
|
8489
|
+
id,
|
|
8490
|
+
name,
|
|
8491
|
+
input: result.input,
|
|
8492
|
+
partial: true
|
|
8493
|
+
});
|
|
8494
|
+
return;
|
|
8319
8495
|
}
|
|
8320
|
-
|
|
8321
|
-
if (
|
|
8322
|
-
|
|
8496
|
+
const content = partial[contentField];
|
|
8497
|
+
if (typeof content !== "string") {
|
|
8498
|
+
return;
|
|
8323
8499
|
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
text: event.text,
|
|
8333
|
-
startedAt: event.ts
|
|
8334
|
-
});
|
|
8335
|
-
}
|
|
8336
|
-
textBlockOpen = true;
|
|
8337
|
-
onEvent({ type: "text", text: event.text });
|
|
8338
|
-
break;
|
|
8500
|
+
if (!acc.started) {
|
|
8501
|
+
acc.started = true;
|
|
8502
|
+
onEvent({ type: "tool_start", id, name, input: partial });
|
|
8503
|
+
}
|
|
8504
|
+
if (transform) {
|
|
8505
|
+
const result = await transform(partial);
|
|
8506
|
+
if (result === null) {
|
|
8507
|
+
return;
|
|
8339
8508
|
}
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8509
|
+
onEvent({ type: "tool_input_delta", id, name, result });
|
|
8510
|
+
} else {
|
|
8511
|
+
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8514
|
+
try {
|
|
8515
|
+
for await (const event of streamChatWithRetry(
|
|
8516
|
+
{
|
|
8517
|
+
...apiConfig,
|
|
8518
|
+
model: parentModel,
|
|
8519
|
+
requestId,
|
|
8520
|
+
system,
|
|
8521
|
+
messages: cleanMessagesForApi(state.messages),
|
|
8522
|
+
tools: tools2,
|
|
8523
|
+
signal
|
|
8524
|
+
},
|
|
8525
|
+
{
|
|
8526
|
+
onRetry: (attempt) => {
|
|
8527
|
+
onEvent({
|
|
8528
|
+
type: "status",
|
|
8529
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8530
|
+
});
|
|
8344
8531
|
}
|
|
8345
|
-
onEvent({ type: "thinking", text: event.text });
|
|
8346
|
-
break;
|
|
8347
|
-
case "thinking_complete": {
|
|
8348
|
-
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8349
|
-
contentBlocks.push({
|
|
8350
|
-
type: "thinking",
|
|
8351
|
-
thinking: event.thinking,
|
|
8352
|
-
signature: event.signature,
|
|
8353
|
-
startedAt,
|
|
8354
|
-
completedAt: event.ts
|
|
8355
|
-
});
|
|
8356
|
-
thinkingCompleteCount++;
|
|
8357
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8358
|
-
break;
|
|
8359
8532
|
}
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
contentBlocks.push({
|
|
8363
|
-
type: "redacted_thinking",
|
|
8364
|
-
data: event.data,
|
|
8365
|
-
startedAt,
|
|
8366
|
-
completedAt: event.ts
|
|
8367
|
-
});
|
|
8368
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8533
|
+
)) {
|
|
8534
|
+
if (signal?.aborted) {
|
|
8369
8535
|
break;
|
|
8370
8536
|
}
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8537
|
+
switch (event.type) {
|
|
8538
|
+
case "text": {
|
|
8539
|
+
const lastBlock = contentBlocks.at(-1);
|
|
8540
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
8541
|
+
lastBlock.text += event.text;
|
|
8542
|
+
} else {
|
|
8543
|
+
contentBlocks.push({
|
|
8544
|
+
type: "text",
|
|
8545
|
+
text: event.text,
|
|
8546
|
+
startedAt: event.ts
|
|
8547
|
+
});
|
|
8548
|
+
}
|
|
8549
|
+
textBlockOpen = true;
|
|
8550
|
+
onEvent({ type: "text", text: event.text });
|
|
8551
|
+
break;
|
|
8378
8552
|
}
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8384
|
-
break;
|
|
8385
|
-
}
|
|
8386
|
-
case "tool_use": {
|
|
8387
|
-
const tool = getToolByName(event.name);
|
|
8388
|
-
contentBlocks.push({
|
|
8389
|
-
type: "tool",
|
|
8390
|
-
id: event.id,
|
|
8391
|
-
name: event.name,
|
|
8392
|
-
input: event.input,
|
|
8393
|
-
startedAt: event.ts,
|
|
8394
|
-
...(event.input.background || tool?.backgroundOnly) && {
|
|
8395
|
-
background: true
|
|
8553
|
+
case "thinking":
|
|
8554
|
+
if (event.text === "") {
|
|
8555
|
+
thinkingBlockStartTimes.push(event.ts);
|
|
8556
|
+
textBlockOpen = false;
|
|
8396
8557
|
}
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8558
|
+
onEvent({ type: "thinking", text: event.text });
|
|
8559
|
+
break;
|
|
8560
|
+
case "thinking_complete": {
|
|
8561
|
+
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8562
|
+
contentBlocks.push({
|
|
8563
|
+
type: "thinking",
|
|
8564
|
+
thinking: event.thinking,
|
|
8565
|
+
signature: event.signature,
|
|
8566
|
+
startedAt,
|
|
8567
|
+
completedAt: event.ts
|
|
8568
|
+
});
|
|
8569
|
+
thinkingCompleteCount++;
|
|
8570
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8571
|
+
break;
|
|
8572
|
+
}
|
|
8573
|
+
case "redacted_thinking_complete": {
|
|
8574
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
8575
|
+
contentBlocks.push({
|
|
8576
|
+
type: "redacted_thinking",
|
|
8577
|
+
data: event.data,
|
|
8578
|
+
startedAt,
|
|
8579
|
+
completedAt: event.ts
|
|
8580
|
+
});
|
|
8581
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8582
|
+
break;
|
|
8583
|
+
}
|
|
8584
|
+
case "tool_input_delta": {
|
|
8585
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8586
|
+
acc.json += event.delta;
|
|
8587
|
+
try {
|
|
8588
|
+
const partial = parsePartialJson(acc.json);
|
|
8589
|
+
await handlePartialInput(acc, event.id, event.name, partial);
|
|
8590
|
+
} catch {
|
|
8591
|
+
}
|
|
8592
|
+
break;
|
|
8593
|
+
}
|
|
8594
|
+
case "tool_input_args": {
|
|
8595
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8596
|
+
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8597
|
+
break;
|
|
8598
|
+
}
|
|
8599
|
+
case "tool_use": {
|
|
8600
|
+
const tool = getToolByName(event.name);
|
|
8601
|
+
contentBlocks.push({
|
|
8602
|
+
type: "tool",
|
|
8409
8603
|
id: event.id,
|
|
8410
8604
|
name: event.name,
|
|
8411
|
-
input: event.input
|
|
8605
|
+
input: event.input,
|
|
8606
|
+
startedAt: event.ts,
|
|
8607
|
+
...(event.input.background || tool?.backgroundOnly) && {
|
|
8608
|
+
background: true
|
|
8609
|
+
}
|
|
8610
|
+
});
|
|
8611
|
+
const acc = toolInputAccumulators.get(event.id);
|
|
8612
|
+
const wasStreamed = acc?.started ?? false;
|
|
8613
|
+
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8614
|
+
log13.info("Tool received", {
|
|
8615
|
+
requestId,
|
|
8616
|
+
toolCallId: event.id,
|
|
8617
|
+
name: event.name
|
|
8412
8618
|
});
|
|
8619
|
+
if (!wasStreamed || isInputStreaming) {
|
|
8620
|
+
onEvent({
|
|
8621
|
+
type: "tool_start",
|
|
8622
|
+
id: event.id,
|
|
8623
|
+
name: event.name,
|
|
8624
|
+
input: event.input,
|
|
8625
|
+
// Mirror the content-block stamp above so the live event
|
|
8626
|
+
// matches what a history reload shows — without it the
|
|
8627
|
+
// frontend renders background rows as already complete.
|
|
8628
|
+
...isBackgroundCall({
|
|
8629
|
+
name: event.name,
|
|
8630
|
+
input: event.input
|
|
8631
|
+
}) && { background: true }
|
|
8632
|
+
});
|
|
8633
|
+
}
|
|
8634
|
+
break;
|
|
8413
8635
|
}
|
|
8414
|
-
|
|
8636
|
+
case "done":
|
|
8637
|
+
stopReason = event.stopReason;
|
|
8638
|
+
turnProviderMetadata = event.providerMetadata;
|
|
8639
|
+
turnModelId = event.modelId;
|
|
8640
|
+
turnLlmCalls++;
|
|
8641
|
+
lastCallInputTokens = event.usage.inputTokens;
|
|
8642
|
+
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8643
|
+
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8644
|
+
turnInputTokens += lastCallInputTokens;
|
|
8645
|
+
turnOutputTokens += event.usage.outputTokens;
|
|
8646
|
+
turnCacheCreation += lastCallCacheCreation;
|
|
8647
|
+
turnCacheRead += lastCallCacheRead;
|
|
8648
|
+
recordUsage({
|
|
8649
|
+
ts: Date.now(),
|
|
8650
|
+
requestId,
|
|
8651
|
+
agentName: "parent",
|
|
8652
|
+
modelId: event.modelId,
|
|
8653
|
+
inputTokens: event.usage.inputTokens,
|
|
8654
|
+
outputTokens: event.usage.outputTokens,
|
|
8655
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8656
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8657
|
+
cost: nanoToDollars(event.cost),
|
|
8658
|
+
billingEvents: event.billingEvents,
|
|
8659
|
+
durationMs: Date.now() - iterStart,
|
|
8660
|
+
toolNames: contentBlocks.filter(
|
|
8661
|
+
(b) => b.type === "tool"
|
|
8662
|
+
).map((b) => b.name)
|
|
8663
|
+
});
|
|
8664
|
+
break;
|
|
8665
|
+
case "error":
|
|
8666
|
+
statusWatcher.stop();
|
|
8667
|
+
onEvent({
|
|
8668
|
+
type: "error",
|
|
8669
|
+
error: friendlyError(event.error),
|
|
8670
|
+
...event.code ? { code: event.code } : {}
|
|
8671
|
+
});
|
|
8672
|
+
return;
|
|
8415
8673
|
}
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
lastCallInputTokens = event.usage.inputTokens;
|
|
8422
|
-
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8423
|
-
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8424
|
-
turnInputTokens += lastCallInputTokens;
|
|
8425
|
-
turnOutputTokens += event.usage.outputTokens;
|
|
8426
|
-
turnCacheCreation += lastCallCacheCreation;
|
|
8427
|
-
turnCacheRead += lastCallCacheRead;
|
|
8428
|
-
recordUsage({
|
|
8429
|
-
ts: Date.now(),
|
|
8430
|
-
requestId,
|
|
8431
|
-
agentName: "parent",
|
|
8432
|
-
modelId: event.modelId,
|
|
8433
|
-
inputTokens: event.usage.inputTokens,
|
|
8434
|
-
outputTokens: event.usage.outputTokens,
|
|
8435
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8436
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8437
|
-
cost: nanoToDollars(event.cost),
|
|
8438
|
-
billingEvents: event.billingEvents,
|
|
8439
|
-
durationMs: Date.now() - iterStart,
|
|
8440
|
-
toolNames: contentBlocks.filter(
|
|
8441
|
-
(b) => b.type === "tool"
|
|
8442
|
-
).map((b) => b.name)
|
|
8443
|
-
});
|
|
8444
|
-
break;
|
|
8445
|
-
case "error":
|
|
8446
|
-
onEvent({
|
|
8447
|
-
type: "error",
|
|
8448
|
-
error: friendlyError(event.error),
|
|
8449
|
-
...event.code ? { code: event.code } : {}
|
|
8450
|
-
});
|
|
8451
|
-
return;
|
|
8674
|
+
}
|
|
8675
|
+
} catch (err) {
|
|
8676
|
+
if (signal?.aborted) {
|
|
8677
|
+
} else {
|
|
8678
|
+
throw err;
|
|
8452
8679
|
}
|
|
8453
8680
|
}
|
|
8454
|
-
} catch (err) {
|
|
8455
8681
|
if (signal?.aborted) {
|
|
8456
|
-
|
|
8457
|
-
|
|
8682
|
+
statusWatcher.stop();
|
|
8683
|
+
if (contentBlocks.length > 0) {
|
|
8684
|
+
contentBlocks.push({
|
|
8685
|
+
type: "text",
|
|
8686
|
+
text: "\n\n(cancelled)",
|
|
8687
|
+
startedAt: Date.now()
|
|
8688
|
+
});
|
|
8689
|
+
state.messages.push({
|
|
8690
|
+
role: "assistant",
|
|
8691
|
+
content: [...contentBlocks].sort(
|
|
8692
|
+
(a, b) => a.startedAt - b.startedAt
|
|
8693
|
+
),
|
|
8694
|
+
usage: {
|
|
8695
|
+
inputTokens: turnInputTokens,
|
|
8696
|
+
outputTokens: turnOutputTokens,
|
|
8697
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8698
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8699
|
+
llmCalls: turnLlmCalls
|
|
8700
|
+
},
|
|
8701
|
+
...turnProviderMetadata && {
|
|
8702
|
+
providerMetadata: turnProviderMetadata
|
|
8703
|
+
},
|
|
8704
|
+
model: turnModelId ?? parentModel,
|
|
8705
|
+
...modelOverride && { modelOverride }
|
|
8706
|
+
});
|
|
8707
|
+
}
|
|
8708
|
+
onEvent({ type: "turn_cancelled" });
|
|
8709
|
+
saveSession(state);
|
|
8710
|
+
return;
|
|
8458
8711
|
}
|
|
8459
|
-
}
|
|
8460
|
-
if (signal?.aborted) {
|
|
8461
|
-
statusWatcher.stop();
|
|
8462
8712
|
if (contentBlocks.length > 0) {
|
|
8463
|
-
contentBlocks.push({
|
|
8464
|
-
type: "text",
|
|
8465
|
-
text: "\n\n(cancelled)",
|
|
8466
|
-
startedAt: Date.now()
|
|
8467
|
-
});
|
|
8468
8713
|
state.messages.push({
|
|
8469
8714
|
role: "assistant",
|
|
8470
8715
|
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
@@ -8482,229 +8727,212 @@ async function runTurn(params) {
|
|
|
8482
8727
|
...modelOverride && { modelOverride }
|
|
8483
8728
|
});
|
|
8484
8729
|
}
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
});
|
|
8504
|
-
}
|
|
8505
|
-
const toolCalls = getToolCalls(contentBlocks);
|
|
8506
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8507
|
-
statusWatcher.stop();
|
|
8508
|
-
saveSession(state);
|
|
8509
|
-
onEvent({
|
|
8510
|
-
type: "turn_done",
|
|
8511
|
-
stats: {
|
|
8512
|
-
inputTokens: turnInputTokens,
|
|
8513
|
-
outputTokens: turnOutputTokens,
|
|
8514
|
-
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8515
|
-
cacheReadTokens: turnCacheRead || void 0,
|
|
8516
|
-
llmCalls: turnLlmCalls,
|
|
8517
|
-
lastCallInputTokens,
|
|
8518
|
-
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8519
|
-
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8520
|
-
}
|
|
8521
|
-
});
|
|
8522
|
-
return;
|
|
8523
|
-
}
|
|
8524
|
-
log13.info("Tools executing", {
|
|
8525
|
-
requestId,
|
|
8526
|
-
count: toolCalls.length,
|
|
8527
|
-
tools: toolCalls.map((tc) => tc.name)
|
|
8528
|
-
});
|
|
8529
|
-
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8530
|
-
const wrappedOnEvent = (e) => {
|
|
8531
|
-
if ("parentToolId" in e && e.parentToolId) {
|
|
8532
|
-
if (e.type === "text") {
|
|
8533
|
-
subAgentText = e.text;
|
|
8534
|
-
} else if (e.type === "tool_start") {
|
|
8535
|
-
subAgentText = `Using ${e.name}`;
|
|
8536
|
-
}
|
|
8730
|
+
const toolCalls = getToolCalls(contentBlocks);
|
|
8731
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8732
|
+
statusWatcher.stop();
|
|
8733
|
+
saveSession(state);
|
|
8734
|
+
onEvent({
|
|
8735
|
+
type: "turn_done",
|
|
8736
|
+
stats: {
|
|
8737
|
+
inputTokens: turnInputTokens,
|
|
8738
|
+
outputTokens: turnOutputTokens,
|
|
8739
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8740
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8741
|
+
llmCalls: turnLlmCalls,
|
|
8742
|
+
lastCallInputTokens,
|
|
8743
|
+
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8744
|
+
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8745
|
+
}
|
|
8746
|
+
});
|
|
8747
|
+
return;
|
|
8537
8748
|
}
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8749
|
+
log13.info("Tools executing", {
|
|
8750
|
+
requestId,
|
|
8751
|
+
count: toolCalls.length,
|
|
8752
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
8753
|
+
});
|
|
8754
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8755
|
+
const wrappedOnEvent = (e) => {
|
|
8756
|
+
if ("parentToolId" in e && e.parentToolId) {
|
|
8757
|
+
if (e.type === "text") {
|
|
8758
|
+
subAgentText = e.text;
|
|
8759
|
+
} else if (e.type === "tool_start") {
|
|
8760
|
+
subAgentText = `Using ${e.name}`;
|
|
8761
|
+
}
|
|
8545
8762
|
}
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
const safeSettle = (result, isError) => {
|
|
8554
|
-
if (settled) {
|
|
8555
|
-
return;
|
|
8763
|
+
onEvent(e);
|
|
8764
|
+
};
|
|
8765
|
+
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
8766
|
+
const results = await Promise.all(
|
|
8767
|
+
toolCalls.map(async (tc) => {
|
|
8768
|
+
if (signal?.aborted) {
|
|
8769
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
8556
8770
|
}
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
toolAbort
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8771
|
+
const toolStart = Date.now();
|
|
8772
|
+
let settle;
|
|
8773
|
+
const resultPromise = new Promise((res) => {
|
|
8774
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
8775
|
+
});
|
|
8776
|
+
let toolAbort = new AbortController();
|
|
8777
|
+
let settled = false;
|
|
8778
|
+
const safeSettle = (result, isError) => {
|
|
8779
|
+
if (settled) {
|
|
8780
|
+
return;
|
|
8781
|
+
}
|
|
8782
|
+
settled = true;
|
|
8783
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
8784
|
+
settle(result, isError);
|
|
8785
|
+
};
|
|
8786
|
+
const cascadeAbort = () => {
|
|
8787
|
+
toolAbort.abort();
|
|
8788
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
8789
|
+
};
|
|
8790
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
8791
|
+
const run = async (input) => {
|
|
8792
|
+
try {
|
|
8793
|
+
let result;
|
|
8794
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8795
|
+
saveSession(state);
|
|
8796
|
+
log13.info("Waiting for external tool result", {
|
|
8797
|
+
requestId,
|
|
8798
|
+
toolCallId: tc.id,
|
|
8799
|
+
name: tc.name
|
|
8800
|
+
});
|
|
8801
|
+
const blocksUser = USER_BLOCKING_EXTERNAL_TOOLS.has(tc.name);
|
|
8583
8802
|
if (blocksUser) {
|
|
8584
|
-
statusWatcher.
|
|
8803
|
+
statusWatcher.pause();
|
|
8585
8804
|
}
|
|
8805
|
+
try {
|
|
8806
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
8807
|
+
} finally {
|
|
8808
|
+
if (blocksUser) {
|
|
8809
|
+
statusWatcher.resume();
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
} else {
|
|
8813
|
+
result = await executeTool(tc.name, input, {
|
|
8814
|
+
apiConfig,
|
|
8815
|
+
model,
|
|
8816
|
+
models: state.models,
|
|
8817
|
+
signal: toolAbort.signal,
|
|
8818
|
+
onEvent: wrappedOnEvent,
|
|
8819
|
+
resolveExternalTool,
|
|
8820
|
+
toolCallId: tc.id,
|
|
8821
|
+
requestId,
|
|
8822
|
+
onboardingState,
|
|
8823
|
+
subAgentMessages,
|
|
8824
|
+
conversationMessages: state.messages,
|
|
8825
|
+
toolRegistry,
|
|
8826
|
+
onBackgroundComplete,
|
|
8827
|
+
onLog: (line) => wrappedOnEvent({
|
|
8828
|
+
type: "tool_input_delta",
|
|
8829
|
+
id: tc.id,
|
|
8830
|
+
name: tc.name,
|
|
8831
|
+
result: line
|
|
8832
|
+
})
|
|
8833
|
+
});
|
|
8586
8834
|
}
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
id: tc.id,
|
|
8605
|
-
name: tc.name,
|
|
8606
|
-
result: line
|
|
8607
|
-
})
|
|
8835
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
8836
|
+
} catch (err) {
|
|
8837
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
8838
|
+
}
|
|
8839
|
+
};
|
|
8840
|
+
const entry = {
|
|
8841
|
+
id: tc.id,
|
|
8842
|
+
name: tc.name,
|
|
8843
|
+
input: tc.input,
|
|
8844
|
+
abortController: toolAbort,
|
|
8845
|
+
startedAt: toolStart,
|
|
8846
|
+
settle: safeSettle,
|
|
8847
|
+
rerun: (newInput) => {
|
|
8848
|
+
settled = false;
|
|
8849
|
+
toolAbort = new AbortController();
|
|
8850
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
8851
|
+
once: true
|
|
8608
8852
|
});
|
|
8853
|
+
entry.abortController = toolAbort;
|
|
8854
|
+
entry.input = newInput;
|
|
8855
|
+
run(newInput);
|
|
8609
8856
|
}
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8857
|
+
};
|
|
8858
|
+
toolRegistry?.register(entry);
|
|
8859
|
+
run(tc.input);
|
|
8860
|
+
const r = await resultPromise;
|
|
8861
|
+
if (!isBackgroundCall(tc)) {
|
|
8862
|
+
toolRegistry?.unregister(tc.id);
|
|
8613
8863
|
}
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8864
|
+
log13.info("Tool completed", {
|
|
8865
|
+
requestId,
|
|
8866
|
+
toolCallId: tc.id,
|
|
8867
|
+
name: tc.name,
|
|
8868
|
+
durationMs: Date.now() - toolStart,
|
|
8869
|
+
isError: r.isError
|
|
8870
|
+
});
|
|
8871
|
+
onEvent({
|
|
8872
|
+
type: "tool_done",
|
|
8873
|
+
id: tc.id,
|
|
8874
|
+
name: tc.name,
|
|
8875
|
+
result: r.result,
|
|
8876
|
+
isError: r.isError
|
|
8877
|
+
});
|
|
8878
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8879
|
+
triggerBrandExtraction(
|
|
8880
|
+
apiConfig,
|
|
8881
|
+
resolveModel("brandExtractor", state.models, model)
|
|
8882
|
+
);
|
|
8631
8883
|
}
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
run(tc.input);
|
|
8635
|
-
const r = await resultPromise;
|
|
8636
|
-
if (!isBackgroundCall(tc)) {
|
|
8637
|
-
toolRegistry?.unregister(tc.id);
|
|
8638
|
-
}
|
|
8639
|
-
log13.info("Tool completed", {
|
|
8640
|
-
requestId,
|
|
8641
|
-
toolCallId: tc.id,
|
|
8642
|
-
name: tc.name,
|
|
8643
|
-
durationMs: Date.now() - toolStart,
|
|
8644
|
-
isError: r.isError
|
|
8645
|
-
});
|
|
8646
|
-
onEvent({
|
|
8647
|
-
type: "tool_done",
|
|
8648
|
-
id: tc.id,
|
|
8649
|
-
name: tc.name,
|
|
8650
|
-
result: r.result,
|
|
8651
|
-
isError: r.isError
|
|
8652
|
-
});
|
|
8653
|
-
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8654
|
-
triggerBrandExtraction(
|
|
8655
|
-
apiConfig,
|
|
8656
|
-
resolveModel("brandExtractor", state.models, model)
|
|
8657
|
-
);
|
|
8658
|
-
}
|
|
8659
|
-
return r;
|
|
8660
|
-
})
|
|
8661
|
-
);
|
|
8662
|
-
statusWatcher.stop();
|
|
8663
|
-
for (const r of results) {
|
|
8664
|
-
const block = contentBlocks.find(
|
|
8665
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
8884
|
+
return r;
|
|
8885
|
+
})
|
|
8666
8886
|
);
|
|
8667
|
-
|
|
8668
|
-
block
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
block.
|
|
8887
|
+
for (const r of results) {
|
|
8888
|
+
const block = contentBlocks.find(
|
|
8889
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
8890
|
+
);
|
|
8891
|
+
if (block?.type === "tool") {
|
|
8892
|
+
block.result = r.result;
|
|
8893
|
+
block.isError = r.isError;
|
|
8894
|
+
block.completedAt = Date.now();
|
|
8895
|
+
const msgs = subAgentMessages.get(r.id);
|
|
8896
|
+
if (msgs) {
|
|
8897
|
+
block.subAgentMessages = msgs;
|
|
8898
|
+
}
|
|
8674
8899
|
}
|
|
8675
8900
|
}
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8679
|
-
);
|
|
8680
|
-
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8681
|
-
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8682
|
-
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8683
|
-
for (const r of results) {
|
|
8684
|
-
state.messages.push({
|
|
8685
|
-
role: "user",
|
|
8686
|
-
content: r.result,
|
|
8687
|
-
toolCallId: r.id,
|
|
8688
|
-
isToolError: r.isError
|
|
8689
|
-
});
|
|
8690
|
-
}
|
|
8691
|
-
if (takeSteering && !signal?.aborted) {
|
|
8692
|
-
const injected = (await takeSteering()).filter(
|
|
8693
|
-
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8901
|
+
const lastNonExcluded = toolCalls.filter(
|
|
8902
|
+
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8694
8903
|
);
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8904
|
+
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8905
|
+
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8906
|
+
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8907
|
+
for (const r of results) {
|
|
8908
|
+
state.messages.push({
|
|
8909
|
+
role: "user",
|
|
8910
|
+
content: r.result,
|
|
8911
|
+
toolCallId: r.id,
|
|
8912
|
+
isToolError: r.isError
|
|
8913
|
+
});
|
|
8914
|
+
}
|
|
8915
|
+
if (takeSteering && !signal?.aborted) {
|
|
8916
|
+
const injected = (await takeSteering()).filter(
|
|
8917
|
+
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8918
|
+
);
|
|
8919
|
+
if (injected.length > 0) {
|
|
8920
|
+
for (const entry of injected) {
|
|
8921
|
+
appendEntry(entry);
|
|
8922
|
+
keptEntries.push(entry);
|
|
8923
|
+
}
|
|
8924
|
+
saveSession(state);
|
|
8699
8925
|
}
|
|
8926
|
+
}
|
|
8927
|
+
if (signal?.aborted) {
|
|
8928
|
+
statusWatcher.stop();
|
|
8929
|
+
onEvent({ type: "turn_cancelled" });
|
|
8700
8930
|
saveSession(state);
|
|
8931
|
+
return;
|
|
8701
8932
|
}
|
|
8702
8933
|
}
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
saveSession(state);
|
|
8706
|
-
return;
|
|
8707
|
-
}
|
|
8934
|
+
} finally {
|
|
8935
|
+
statusWatcher.stop();
|
|
8708
8936
|
}
|
|
8709
8937
|
}
|
|
8710
8938
|
var log13, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
@@ -8718,6 +8946,7 @@ var init_agent = __esm({
|
|
|
8718
8946
|
init_usageLedger();
|
|
8719
8947
|
init_parsePartialJson();
|
|
8720
8948
|
init_statusWatcher();
|
|
8949
|
+
init_resolve();
|
|
8721
8950
|
init_errors();
|
|
8722
8951
|
init_cleanMessages();
|
|
8723
8952
|
init_sentinel();
|
|
@@ -9393,70 +9622,6 @@ var init_messageQueue = __esm({
|
|
|
9393
9622
|
}
|
|
9394
9623
|
});
|
|
9395
9624
|
|
|
9396
|
-
// src/automatedActions/resolve.ts
|
|
9397
|
-
function resolveAction(text) {
|
|
9398
|
-
const parsed = parseSentinel(text);
|
|
9399
|
-
if (!parsed) {
|
|
9400
|
-
return null;
|
|
9401
|
-
}
|
|
9402
|
-
const { name: triggerName, remainder } = parsed;
|
|
9403
|
-
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
9404
|
-
return null;
|
|
9405
|
-
}
|
|
9406
|
-
let params = {};
|
|
9407
|
-
if (remainder) {
|
|
9408
|
-
try {
|
|
9409
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
9410
|
-
} catch {
|
|
9411
|
-
}
|
|
9412
|
-
}
|
|
9413
|
-
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
9414
|
-
let next;
|
|
9415
|
-
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9416
|
-
if (fmMatch) {
|
|
9417
|
-
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
9418
|
-
if (nextMatch) {
|
|
9419
|
-
next = nextMatch[1];
|
|
9420
|
-
}
|
|
9421
|
-
}
|
|
9422
|
-
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
9423
|
-
for (const [key, value] of Object.entries(params)) {
|
|
9424
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
9425
|
-
body = body.replaceAll(`{{${key}}}`, str);
|
|
9426
|
-
}
|
|
9427
|
-
return {
|
|
9428
|
-
message: automatedMessage(triggerName, body),
|
|
9429
|
-
next
|
|
9430
|
-
};
|
|
9431
|
-
}
|
|
9432
|
-
function getActionChain(startName) {
|
|
9433
|
-
const chain = [];
|
|
9434
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9435
|
-
let name = startName;
|
|
9436
|
-
while (name && !seen.has(name)) {
|
|
9437
|
-
seen.add(name);
|
|
9438
|
-
chain.push(name);
|
|
9439
|
-
let body;
|
|
9440
|
-
try {
|
|
9441
|
-
body = readAsset("automatedActions", `${name}.md`);
|
|
9442
|
-
} catch {
|
|
9443
|
-
break;
|
|
9444
|
-
}
|
|
9445
|
-
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9446
|
-
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
9447
|
-
}
|
|
9448
|
-
return chain;
|
|
9449
|
-
}
|
|
9450
|
-
var NON_ACTION_SENTINELS;
|
|
9451
|
-
var init_resolve = __esm({
|
|
9452
|
-
"src/automatedActions/resolve.ts"() {
|
|
9453
|
-
"use strict";
|
|
9454
|
-
init_assets();
|
|
9455
|
-
init_sentinel();
|
|
9456
|
-
NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
9457
|
-
}
|
|
9458
|
-
});
|
|
9459
|
-
|
|
9460
9625
|
// src/headless/index.ts
|
|
9461
9626
|
var headless_exports = {};
|
|
9462
9627
|
__export(headless_exports, {
|
|
@@ -9538,6 +9703,13 @@ var init_headless = __esm({
|
|
|
9538
9703
|
earlyResults = /* @__PURE__ */ new Map();
|
|
9539
9704
|
// Tool block updates from background completions (separate from the message queue)
|
|
9540
9705
|
pendingBlockUpdates = [];
|
|
9706
|
+
/**
|
|
9707
|
+
* Id of the UI-only tool block synthesized for the in-flight user/gate
|
|
9708
|
+
* compaction (see the compaction listener). Model-invoked compactions have
|
|
9709
|
+
* a real block and never set this. Single slot — triggerCompaction is
|
|
9710
|
+
* single-flight.
|
|
9711
|
+
*/
|
|
9712
|
+
syntheticCompactionId = null;
|
|
9541
9713
|
// Tool lifecycle management — shared across all nesting depths
|
|
9542
9714
|
toolRegistry = new ToolRegistry();
|
|
9543
9715
|
// IO — accumulates stdin bytes between newline boundaries
|
|
@@ -9569,6 +9741,7 @@ var init_headless = __esm({
|
|
|
9569
9741
|
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
9570
9742
|
});
|
|
9571
9743
|
this.passivePen = loadPassiveResults();
|
|
9744
|
+
this.persistStats();
|
|
9572
9745
|
if (resumed) {
|
|
9573
9746
|
this.emit("session_restored", {
|
|
9574
9747
|
messageCount: this.state.messages.length,
|
|
@@ -9591,15 +9764,61 @@ var init_headless = __esm({
|
|
|
9591
9764
|
);
|
|
9592
9765
|
this.sessionStats.compactionInProgress = true;
|
|
9593
9766
|
this.persistStats();
|
|
9767
|
+
if (!event.toolCallId && (event.origin === "user" || event.origin === "gate")) {
|
|
9768
|
+
const id = `compact_${Date.now()}`;
|
|
9769
|
+
this.syntheticCompactionId = id;
|
|
9770
|
+
this.state.messages.push({
|
|
9771
|
+
role: "assistant",
|
|
9772
|
+
content: [
|
|
9773
|
+
{
|
|
9774
|
+
type: "tool",
|
|
9775
|
+
id,
|
|
9776
|
+
name: "compactConversation",
|
|
9777
|
+
input: {},
|
|
9778
|
+
startedAt: Date.now(),
|
|
9779
|
+
background: true,
|
|
9780
|
+
uiOnly: true,
|
|
9781
|
+
result: "Compaction started in the background."
|
|
9782
|
+
}
|
|
9783
|
+
]
|
|
9784
|
+
});
|
|
9785
|
+
saveSession(this.state);
|
|
9786
|
+
this.onEvent({
|
|
9787
|
+
type: "tool_start",
|
|
9788
|
+
id,
|
|
9789
|
+
name: "compactConversation",
|
|
9790
|
+
input: {},
|
|
9791
|
+
background: true
|
|
9792
|
+
});
|
|
9793
|
+
this.onEvent({
|
|
9794
|
+
type: "tool_done",
|
|
9795
|
+
id,
|
|
9796
|
+
name: "compactConversation",
|
|
9797
|
+
result: "Compaction started in the background.",
|
|
9798
|
+
isError: false
|
|
9799
|
+
});
|
|
9800
|
+
}
|
|
9594
9801
|
} else {
|
|
9595
9802
|
const data = event.error ? { error: event.error } : {};
|
|
9596
9803
|
this.emit("compaction_complete", data, event.requestId);
|
|
9804
|
+
if (this.syntheticCompactionId) {
|
|
9805
|
+
const id = this.syntheticCompactionId;
|
|
9806
|
+
this.syntheticCompactionId = null;
|
|
9807
|
+
this.onBackgroundComplete(
|
|
9808
|
+
id,
|
|
9809
|
+
"compactConversation",
|
|
9810
|
+
event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
|
|
9811
|
+
);
|
|
9812
|
+
}
|
|
9597
9813
|
this.sessionStats.compactionInProgress = false;
|
|
9598
9814
|
if (!event.error) {
|
|
9599
9815
|
this.sessionStats.lastContextSize = 0;
|
|
9600
9816
|
}
|
|
9601
9817
|
this.sessionStats.messageCount = this.state.messages.length;
|
|
9602
9818
|
this.persistStats();
|
|
9819
|
+
if (!this.running) {
|
|
9820
|
+
this.kickDrain();
|
|
9821
|
+
}
|
|
9603
9822
|
}
|
|
9604
9823
|
});
|
|
9605
9824
|
process.stdin.setEncoding("utf-8");
|
|
@@ -9738,7 +9957,8 @@ var init_headless = __esm({
|
|
|
9738
9957
|
await triggerCompaction(this.state, this.config, {
|
|
9739
9958
|
blocking: true,
|
|
9740
9959
|
requestId,
|
|
9741
|
-
model: this.opts.model
|
|
9960
|
+
model: this.opts.model,
|
|
9961
|
+
origin: "gate"
|
|
9742
9962
|
});
|
|
9743
9963
|
applyPendingSummaries(this.state);
|
|
9744
9964
|
} catch {
|
|
@@ -9767,6 +9987,12 @@ var init_headless = __esm({
|
|
|
9767
9987
|
}
|
|
9768
9988
|
return;
|
|
9769
9989
|
}
|
|
9990
|
+
if (notify === "silent") {
|
|
9991
|
+
if (!this.running) {
|
|
9992
|
+
this.applyPendingBlockUpdates();
|
|
9993
|
+
}
|
|
9994
|
+
return;
|
|
9995
|
+
}
|
|
9770
9996
|
this.queue.push({
|
|
9771
9997
|
command: {
|
|
9772
9998
|
action: "message",
|
|
@@ -10148,6 +10374,12 @@ var init_headless = __esm({
|
|
|
10148
10374
|
this.completedEmitted = false;
|
|
10149
10375
|
this.lastCompleted = null;
|
|
10150
10376
|
this.turnStart = Date.now();
|
|
10377
|
+
const inflight2 = getInflightCompaction();
|
|
10378
|
+
if (inflight2) {
|
|
10379
|
+
await inflight2.catch(() => {
|
|
10380
|
+
});
|
|
10381
|
+
}
|
|
10382
|
+
applyPendingSummaries(this.state);
|
|
10151
10383
|
if (this.passivePen.length > 0) {
|
|
10152
10384
|
const swept = this.passivePen.splice(0);
|
|
10153
10385
|
this.persistStats();
|
|
@@ -10237,7 +10469,7 @@ var init_headless = __esm({
|
|
|
10237
10469
|
this.applyPendingBlockUpdates();
|
|
10238
10470
|
}
|
|
10239
10471
|
async handleMessage(parsed, requestId) {
|
|
10240
|
-
if (this.running) {
|
|
10472
|
+
if (this.running || getInflightCompaction()) {
|
|
10241
10473
|
const command = { ...parsed };
|
|
10242
10474
|
if (requestId && command.requestId === void 0) {
|
|
10243
10475
|
command.requestId = requestId;
|
|
@@ -10247,6 +10479,9 @@ var init_headless = __esm({
|
|
|
10247
10479
|
source: "user",
|
|
10248
10480
|
enqueuedAt: Date.now()
|
|
10249
10481
|
});
|
|
10482
|
+
if (!this.running && !getInflightCompaction()) {
|
|
10483
|
+
this.kickDrain();
|
|
10484
|
+
}
|
|
10250
10485
|
return;
|
|
10251
10486
|
}
|
|
10252
10487
|
this.running = true;
|
|
@@ -10254,6 +10489,7 @@ var init_headless = __esm({
|
|
|
10254
10489
|
await this.runSingleTurn(parsed, requestId);
|
|
10255
10490
|
await this.drainQueueLoop();
|
|
10256
10491
|
} finally {
|
|
10492
|
+
this.currentAbort?.abort();
|
|
10257
10493
|
this.currentAbort = null;
|
|
10258
10494
|
this.currentRequestId = void 0;
|
|
10259
10495
|
this.running = false;
|
|
@@ -10285,6 +10521,18 @@ var init_headless = __esm({
|
|
|
10285
10521
|
async drainQueueLoop() {
|
|
10286
10522
|
while (this.queue.length > 0) {
|
|
10287
10523
|
const head = this.queue.peek();
|
|
10524
|
+
if (head.command.action === "compact") {
|
|
10525
|
+
this.queue.shift();
|
|
10526
|
+
await triggerCompaction(this.state, this.config, {
|
|
10527
|
+
blocking: true,
|
|
10528
|
+
requestId: head.command.requestId,
|
|
10529
|
+
model: this.opts.model,
|
|
10530
|
+
origin: "user"
|
|
10531
|
+
}).catch(() => {
|
|
10532
|
+
});
|
|
10533
|
+
applyPendingSummaries(this.state);
|
|
10534
|
+
continue;
|
|
10535
|
+
}
|
|
10288
10536
|
if (head.source === "chain") {
|
|
10289
10537
|
const item = this.queue.shift();
|
|
10290
10538
|
const rid = item.command.requestId ?? `chain-${Date.now()}`;
|
|
@@ -10329,6 +10577,7 @@ var init_headless = __esm({
|
|
|
10329
10577
|
try {
|
|
10330
10578
|
await this.drainQueueLoop();
|
|
10331
10579
|
} finally {
|
|
10580
|
+
this.currentAbort?.abort();
|
|
10332
10581
|
this.currentAbort = null;
|
|
10333
10582
|
this.currentRequestId = void 0;
|
|
10334
10583
|
this.running = false;
|
|
@@ -10575,11 +10824,31 @@ var init_headless = __esm({
|
|
|
10575
10824
|
return;
|
|
10576
10825
|
}
|
|
10577
10826
|
if (action === "compact") {
|
|
10827
|
+
if (this.running) {
|
|
10828
|
+
const alreadyQueued = this.queue.snapshot().some((it) => it.command.action === "compact");
|
|
10829
|
+
if (!alreadyQueued) {
|
|
10830
|
+
this.queue.push({
|
|
10831
|
+
command: {
|
|
10832
|
+
action: "compact",
|
|
10833
|
+
// Display label for the queue card (matches the automated
|
|
10834
|
+
// action idiom); never resolved as an action — drainQueueLoop
|
|
10835
|
+
// routes on command.action before any sentinel handling.
|
|
10836
|
+
text: sentinel("compact"),
|
|
10837
|
+
requestId
|
|
10838
|
+
},
|
|
10839
|
+
source: "user",
|
|
10840
|
+
enqueuedAt: Date.now()
|
|
10841
|
+
});
|
|
10842
|
+
}
|
|
10843
|
+
this.emit("completed", { success: true, queued: true }, requestId);
|
|
10844
|
+
return;
|
|
10845
|
+
}
|
|
10578
10846
|
try {
|
|
10579
10847
|
await triggerCompaction(this.state, this.config, {
|
|
10580
10848
|
blocking: false,
|
|
10581
10849
|
requestId,
|
|
10582
|
-
model: this.opts.model
|
|
10850
|
+
model: this.opts.model,
|
|
10851
|
+
origin: "user"
|
|
10583
10852
|
});
|
|
10584
10853
|
if (!this.running) {
|
|
10585
10854
|
applyPendingSummaries(this.state);
|