@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/headless.js
CHANGED
|
@@ -1043,7 +1043,6 @@ function extractFrontmatter(content) {
|
|
|
1043
1043
|
var DEFAULT_MAX_LINES = 500;
|
|
1044
1044
|
var readSpecTool = {
|
|
1045
1045
|
definition: {
|
|
1046
|
-
clearable: true,
|
|
1047
1046
|
name: "readSpec",
|
|
1048
1047
|
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).",
|
|
1049
1048
|
inputSchema: {
|
|
@@ -1157,7 +1156,6 @@ function acquireFileLock(filePath) {
|
|
|
1157
1156
|
// src/tools/spec/writeSpec.ts
|
|
1158
1157
|
var writeSpecTool = {
|
|
1159
1158
|
definition: {
|
|
1160
|
-
clearable: true,
|
|
1161
1159
|
name: "writeSpec",
|
|
1162
1160
|
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.",
|
|
1163
1161
|
inputSchema: {
|
|
@@ -1295,7 +1293,6 @@ function formatOccurrenceError(count, lines, filePath) {
|
|
|
1295
1293
|
// src/tools/spec/editSpec.ts
|
|
1296
1294
|
var editSpecTool = {
|
|
1297
1295
|
definition: {
|
|
1298
|
-
clearable: true,
|
|
1299
1296
|
name: "editSpec",
|
|
1300
1297
|
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.",
|
|
1301
1298
|
inputSchema: {
|
|
@@ -1400,7 +1397,6 @@ import fs9 from "fs/promises";
|
|
|
1400
1397
|
import path6 from "path";
|
|
1401
1398
|
var listSpecFilesTool = {
|
|
1402
1399
|
definition: {
|
|
1403
|
-
clearable: false,
|
|
1404
1400
|
name: "listSpecFiles",
|
|
1405
1401
|
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.",
|
|
1406
1402
|
inputSchema: {
|
|
@@ -1451,7 +1447,6 @@ async function listRecursive(dir) {
|
|
|
1451
1447
|
// src/tools/spec/presentPublishPlan.ts
|
|
1452
1448
|
var presentPublishPlanTool = {
|
|
1453
1449
|
definition: {
|
|
1454
|
-
clearable: false,
|
|
1455
1450
|
name: "presentPublishPlan",
|
|
1456
1451
|
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.",
|
|
1457
1452
|
inputSchema: {
|
|
@@ -1476,7 +1471,6 @@ import fs10 from "fs/promises";
|
|
|
1476
1471
|
var PLAN_FILE = ".remy-plan.md";
|
|
1477
1472
|
var writePlanTool = {
|
|
1478
1473
|
definition: {
|
|
1479
|
-
clearable: false,
|
|
1480
1474
|
name: "writePlan",
|
|
1481
1475
|
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.",
|
|
1482
1476
|
inputSchema: {
|
|
@@ -1507,7 +1501,6 @@ import fs11 from "fs/promises";
|
|
|
1507
1501
|
var PLAN_FILE2 = ".remy-plan.md";
|
|
1508
1502
|
var updatePlanStatusTool = {
|
|
1509
1503
|
definition: {
|
|
1510
|
-
clearable: false,
|
|
1511
1504
|
name: "updatePlanStatus",
|
|
1512
1505
|
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.',
|
|
1513
1506
|
inputSchema: {
|
|
@@ -1549,7 +1542,6 @@ var updatePlanStatusTool = {
|
|
|
1549
1542
|
// src/tools/common/setProjectOnboardingState.ts
|
|
1550
1543
|
var setProjectOnboardingStateTool = {
|
|
1551
1544
|
definition: {
|
|
1552
|
-
clearable: false,
|
|
1553
1545
|
name: "setProjectOnboardingState",
|
|
1554
1546
|
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.",
|
|
1555
1547
|
inputSchema: {
|
|
@@ -1572,7 +1564,6 @@ var setProjectOnboardingStateTool = {
|
|
|
1572
1564
|
// src/tools/common/promptUser.ts
|
|
1573
1565
|
var promptUserTool = {
|
|
1574
1566
|
definition: {
|
|
1575
|
-
clearable: false,
|
|
1576
1567
|
name: "promptUser",
|
|
1577
1568
|
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.',
|
|
1578
1569
|
inputSchema: {
|
|
@@ -1704,7 +1695,6 @@ ${lines.join("\n")}`;
|
|
|
1704
1695
|
// src/tools/common/confirmDestructiveAction.ts
|
|
1705
1696
|
var confirmDestructiveActionTool = {
|
|
1706
1697
|
definition: {
|
|
1707
|
-
clearable: false,
|
|
1708
1698
|
name: "confirmDestructiveAction",
|
|
1709
1699
|
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.",
|
|
1710
1700
|
inputSchema: {
|
|
@@ -1878,7 +1868,6 @@ function runCli(command, args, options) {
|
|
|
1878
1868
|
// src/subagents/sdkConsultant/index.ts
|
|
1879
1869
|
var askMindStudioSdkTool = {
|
|
1880
1870
|
definition: {
|
|
1881
|
-
clearable: false,
|
|
1882
1871
|
name: "askMindStudioSdk",
|
|
1883
1872
|
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.",
|
|
1884
1873
|
inputSchema: {
|
|
@@ -2019,7 +2008,6 @@ function stripDollarKeys(envelope) {
|
|
|
2019
2008
|
// src/tools/common/searchGoogle.ts
|
|
2020
2009
|
var searchGoogleTool = {
|
|
2021
2010
|
definition: {
|
|
2022
|
-
clearable: false,
|
|
2023
2011
|
name: "searchGoogle",
|
|
2024
2012
|
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
2025
2013
|
inputSchema: {
|
|
@@ -2050,7 +2038,6 @@ var searchGoogleTool = {
|
|
|
2050
2038
|
// src/tools/common/setProjectMetadata.ts
|
|
2051
2039
|
var setProjectMetadataTool = {
|
|
2052
2040
|
definition: {
|
|
2053
|
-
clearable: false,
|
|
2054
2041
|
name: "setProjectMetadata",
|
|
2055
2042
|
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.",
|
|
2056
2043
|
inputSchema: {
|
|
@@ -2082,8 +2069,13 @@ var setProjectMetadataTool = {
|
|
|
2082
2069
|
|
|
2083
2070
|
// src/tools/common/compactConversation.ts
|
|
2084
2071
|
var compactConversationTool = {
|
|
2072
|
+
backgroundOnly: true,
|
|
2073
|
+
// Silent: the completed summary is written onto the tool block for the UI
|
|
2074
|
+
// detail view only. The model must never receive it as a message — it gets
|
|
2075
|
+
// the summary the correct way, as the checkpoint prefix in every later API
|
|
2076
|
+
// call (cleanMessagesForApi).
|
|
2077
|
+
backgroundNotify: "silent",
|
|
2085
2078
|
definition: {
|
|
2086
|
-
clearable: false,
|
|
2087
2079
|
name: "compactConversation",
|
|
2088
2080
|
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.",
|
|
2089
2081
|
inputSchema: {
|
|
@@ -2095,11 +2087,32 @@ var compactConversationTool = {
|
|
|
2095
2087
|
if (!context?.conversationMessages || !context.apiConfig) {
|
|
2096
2088
|
return "Error: compaction requires execution context.";
|
|
2097
2089
|
}
|
|
2090
|
+
const { toolCallId, onBackgroundComplete, toolRegistry } = context;
|
|
2098
2091
|
triggerCompaction(
|
|
2099
2092
|
{ messages: context.conversationMessages },
|
|
2100
2093
|
context.apiConfig,
|
|
2101
|
-
|
|
2102
|
-
|
|
2094
|
+
// origin/toolCallId tell the headless listener a real block exists —
|
|
2095
|
+
// it must not synthesize a UI-only one for this compaction.
|
|
2096
|
+
{
|
|
2097
|
+
blocking: false,
|
|
2098
|
+
requestId: context.requestId,
|
|
2099
|
+
origin: "tool",
|
|
2100
|
+
toolCallId
|
|
2101
|
+
}
|
|
2102
|
+
).then((summaries) => {
|
|
2103
|
+
onBackgroundComplete?.(
|
|
2104
|
+
toolCallId,
|
|
2105
|
+
"compactConversation",
|
|
2106
|
+
formatSummariesResult(summaries)
|
|
2107
|
+
);
|
|
2108
|
+
}).catch((err) => {
|
|
2109
|
+
onBackgroundComplete?.(
|
|
2110
|
+
toolCallId,
|
|
2111
|
+
"compactConversation",
|
|
2112
|
+
`Error: ${err.message || "Compaction failed"}`
|
|
2113
|
+
);
|
|
2114
|
+
}).finally(() => {
|
|
2115
|
+
toolRegistry?.unregister(toolCallId);
|
|
2103
2116
|
});
|
|
2104
2117
|
return "Compaction started in the background.";
|
|
2105
2118
|
}
|
|
@@ -2108,7 +2121,6 @@ var compactConversationTool = {
|
|
|
2108
2121
|
// src/tools/common/loadSkill.ts
|
|
2109
2122
|
var loadSkillTool = {
|
|
2110
2123
|
definition: {
|
|
2111
|
-
clearable: true,
|
|
2112
2124
|
name: "loadSkill",
|
|
2113
2125
|
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.",
|
|
2114
2126
|
inputSchema: {
|
|
@@ -2159,7 +2171,6 @@ function isBinary(buffer) {
|
|
|
2159
2171
|
}
|
|
2160
2172
|
var readFileTool = {
|
|
2161
2173
|
definition: {
|
|
2162
|
-
clearable: true,
|
|
2163
2174
|
name: "readFile",
|
|
2164
2175
|
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.",
|
|
2165
2176
|
inputSchema: {
|
|
@@ -2263,7 +2274,6 @@ import fs14 from "fs/promises";
|
|
|
2263
2274
|
import path7 from "path";
|
|
2264
2275
|
var writeFileTool = {
|
|
2265
2276
|
definition: {
|
|
2266
|
-
clearable: true,
|
|
2267
2277
|
name: "writeFile",
|
|
2268
2278
|
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.",
|
|
2269
2279
|
inputSchema: {
|
|
@@ -2329,7 +2339,6 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
2329
2339
|
import fs15 from "fs/promises";
|
|
2330
2340
|
var editFileTool = {
|
|
2331
2341
|
definition: {
|
|
2332
|
-
clearable: true,
|
|
2333
2342
|
name: "editFile",
|
|
2334
2343
|
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.",
|
|
2335
2344
|
inputSchema: {
|
|
@@ -2422,7 +2431,6 @@ var DEFAULT_MAX_LINES2 = 500;
|
|
|
2422
2431
|
var MAX_OUTPUT_BYTES = 3e4;
|
|
2423
2432
|
var bashTool = {
|
|
2424
2433
|
definition: {
|
|
2425
|
-
clearable: true,
|
|
2426
2434
|
name: "bash",
|
|
2427
2435
|
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.",
|
|
2428
2436
|
inputSchema: {
|
|
@@ -2544,7 +2552,6 @@ function formatResults(stdout, max, mode) {
|
|
|
2544
2552
|
}
|
|
2545
2553
|
var grepTool = {
|
|
2546
2554
|
definition: {
|
|
2547
|
-
clearable: true,
|
|
2548
2555
|
name: "grep",
|
|
2549
2556
|
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.",
|
|
2550
2557
|
inputSchema: {
|
|
@@ -2656,7 +2663,6 @@ import fg from "fast-glob";
|
|
|
2656
2663
|
var DEFAULT_MAX2 = 200;
|
|
2657
2664
|
var globTool = {
|
|
2658
2665
|
definition: {
|
|
2659
|
-
clearable: true,
|
|
2660
2666
|
name: "glob",
|
|
2661
2667
|
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.',
|
|
2662
2668
|
inputSchema: {
|
|
@@ -2754,7 +2760,6 @@ async function formatFile(dirPath, name, indent) {
|
|
|
2754
2760
|
}
|
|
2755
2761
|
var listDirTool = {
|
|
2756
2762
|
definition: {
|
|
2757
|
-
clearable: true,
|
|
2758
2763
|
name: "listDir",
|
|
2759
2764
|
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.",
|
|
2760
2765
|
inputSchema: {
|
|
@@ -2831,7 +2836,6 @@ var listDirTool = {
|
|
|
2831
2836
|
// src/tools/code/editsFinished.ts
|
|
2832
2837
|
var editsFinishedTool = {
|
|
2833
2838
|
definition: {
|
|
2834
|
-
clearable: false,
|
|
2835
2839
|
name: "editsFinished",
|
|
2836
2840
|
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.",
|
|
2837
2841
|
inputSchema: {
|
|
@@ -2898,7 +2902,6 @@ async function lspRequest(endpoint, body) {
|
|
|
2898
2902
|
// src/tools/code/lspDiagnostics.ts
|
|
2899
2903
|
var lspDiagnosticsTool = {
|
|
2900
2904
|
definition: {
|
|
2901
|
-
clearable: true,
|
|
2902
2905
|
name: "lspDiagnostics",
|
|
2903
2906
|
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.",
|
|
2904
2907
|
inputSchema: {
|
|
@@ -2947,7 +2950,6 @@ var lspDiagnosticsTool = {
|
|
|
2947
2950
|
// src/tools/code/restartProcess.ts
|
|
2948
2951
|
var restartProcessTool = {
|
|
2949
2952
|
definition: {
|
|
2950
|
-
clearable: false,
|
|
2951
2953
|
name: "restartProcess",
|
|
2952
2954
|
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.",
|
|
2953
2955
|
inputSchema: {
|
|
@@ -2974,7 +2976,6 @@ var restartProcessTool = {
|
|
|
2974
2976
|
// src/tools/code/runScenario.ts
|
|
2975
2977
|
var runScenarioTool = {
|
|
2976
2978
|
definition: {
|
|
2977
|
-
clearable: true,
|
|
2978
2979
|
name: "runScenario",
|
|
2979
2980
|
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.",
|
|
2980
2981
|
inputSchema: {
|
|
@@ -3000,7 +3001,6 @@ var runScenarioTool = {
|
|
|
3000
3001
|
// src/tools/code/runMethod.ts
|
|
3001
3002
|
var runMethodTool = {
|
|
3002
3003
|
definition: {
|
|
3003
|
-
clearable: true,
|
|
3004
3004
|
name: "runMethod",
|
|
3005
3005
|
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.',
|
|
3006
3006
|
inputSchema: {
|
|
@@ -3035,7 +3035,6 @@ var runMethodTool = {
|
|
|
3035
3035
|
// src/tools/code/queryDatabase.ts
|
|
3036
3036
|
var queryDatabaseTool = {
|
|
3037
3037
|
definition: {
|
|
3038
|
-
clearable: true,
|
|
3039
3038
|
name: "queryDatabase",
|
|
3040
3039
|
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
3041
3040
|
inputSchema: {
|
|
@@ -3385,16 +3384,55 @@ function capToolResult(result) {
|
|
|
3385
3384
|
}
|
|
3386
3385
|
|
|
3387
3386
|
// src/statusWatcher.ts
|
|
3387
|
+
var INTERNAL_PAYLOAD_MARKERS = [
|
|
3388
|
+
"[USER CANCELLED]",
|
|
3389
|
+
"[INTERRUPTED]",
|
|
3390
|
+
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
3391
|
+
"<background_results>",
|
|
3392
|
+
"<tool_result"
|
|
3393
|
+
];
|
|
3394
|
+
function sanitizeStatusText(text) {
|
|
3395
|
+
if (!text) {
|
|
3396
|
+
return "";
|
|
3397
|
+
}
|
|
3398
|
+
for (const marker of INTERNAL_PAYLOAD_MARKERS) {
|
|
3399
|
+
if (text.includes(marker)) {
|
|
3400
|
+
return "";
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
const truncIdx = text.indexOf("(tool result truncated at");
|
|
3404
|
+
return truncIdx === -1 ? text : text.slice(0, truncIdx);
|
|
3405
|
+
}
|
|
3388
3406
|
function startStatusWatcher(config) {
|
|
3389
3407
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
3390
3408
|
let inflight2 = false;
|
|
3391
3409
|
let stopped = false;
|
|
3392
3410
|
let pauseCount = 0;
|
|
3393
3411
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3412
|
+
let consecutiveFailures = 0;
|
|
3413
|
+
let backoffMs = 0;
|
|
3414
|
+
let nextAllowedAt = 0;
|
|
3415
|
+
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
3416
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
3417
|
+
function recordFailure() {
|
|
3418
|
+
consecutiveFailures++;
|
|
3419
|
+
backoffMs = Math.min(
|
|
3420
|
+
backoffMs === 0 ? interval : backoffMs * 2,
|
|
3421
|
+
MAX_BACKOFF_MS
|
|
3422
|
+
);
|
|
3423
|
+
nextAllowedAt = Date.now() + backoffMs;
|
|
3424
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !stopped) {
|
|
3425
|
+
stopped = true;
|
|
3426
|
+
clearInterval(timer);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3394
3429
|
async function tick() {
|
|
3395
3430
|
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
3396
3431
|
return;
|
|
3397
3432
|
}
|
|
3433
|
+
if (Date.now() < nextAllowedAt) {
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3398
3436
|
inflight2 = true;
|
|
3399
3437
|
try {
|
|
3400
3438
|
const context = getContext();
|
|
@@ -3410,18 +3448,29 @@ function startStatusWatcher(config) {
|
|
|
3410
3448
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
3411
3449
|
signal
|
|
3412
3450
|
});
|
|
3413
|
-
if (
|
|
3451
|
+
if (stopped) {
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
if (!res.ok) {
|
|
3455
|
+
recordFailure();
|
|
3414
3456
|
return;
|
|
3415
3457
|
}
|
|
3416
3458
|
const data = await res.json();
|
|
3417
3459
|
if (!data.label) {
|
|
3460
|
+
recordFailure();
|
|
3418
3461
|
return;
|
|
3419
3462
|
}
|
|
3463
|
+
consecutiveFailures = 0;
|
|
3464
|
+
backoffMs = 0;
|
|
3465
|
+
nextAllowedAt = 0;
|
|
3420
3466
|
if (pauseCount > 0 || stopped || signal?.aborted) {
|
|
3421
3467
|
return;
|
|
3422
3468
|
}
|
|
3423
3469
|
onStatus(data.label);
|
|
3424
3470
|
} catch {
|
|
3471
|
+
if (!stopped && !signal?.aborted) {
|
|
3472
|
+
recordFailure();
|
|
3473
|
+
}
|
|
3425
3474
|
} finally {
|
|
3426
3475
|
inflight2 = false;
|
|
3427
3476
|
}
|
|
@@ -3518,7 +3567,12 @@ ${summaryBlock.text}
|
|
|
3518
3567
|
}
|
|
3519
3568
|
startIdx = checkpointIdx + 1;
|
|
3520
3569
|
}
|
|
3521
|
-
const
|
|
3570
|
+
const apiMessages = messages.slice(startIdx).filter(
|
|
3571
|
+
(msg) => !(msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some(
|
|
3572
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
3573
|
+
))
|
|
3574
|
+
);
|
|
3575
|
+
const messagesToProcess = fixOrphanedToolCalls(apiMessages);
|
|
3522
3576
|
const toolUseIds = /* @__PURE__ */ new Set();
|
|
3523
3577
|
for (const msg of messagesToProcess) {
|
|
3524
3578
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
@@ -3660,329 +3714,341 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3660
3714
|
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
3661
3715
|
}
|
|
3662
3716
|
let lastToolResult = "";
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3717
|
+
let watchedBlocks = [];
|
|
3718
|
+
let watchedToolNames = "";
|
|
3719
|
+
const statusWatcher = startStatusWatcher({
|
|
3720
|
+
apiConfig,
|
|
3721
|
+
getContext: () => {
|
|
3722
|
+
const parts = [];
|
|
3723
|
+
if (task) {
|
|
3724
|
+
parts.push(`Task: ${sanitizeStatusText(task).slice(-200)}`);
|
|
3725
|
+
}
|
|
3726
|
+
const text = getPartialText(watchedBlocks);
|
|
3727
|
+
if (text) {
|
|
3728
|
+
parts.push(`Assistant text: ${text.slice(-500)}`);
|
|
3729
|
+
}
|
|
3730
|
+
if (watchedToolNames) {
|
|
3731
|
+
parts.push(`Tool: ${watchedToolNames}`);
|
|
3732
|
+
}
|
|
3733
|
+
const toolResult = sanitizeStatusText(lastToolResult);
|
|
3734
|
+
if (toolResult) {
|
|
3735
|
+
parts.push(`Tool result: ${toolResult.slice(-200)}`);
|
|
3736
|
+
}
|
|
3737
|
+
return parts.join("\n");
|
|
3738
|
+
},
|
|
3739
|
+
onStatus: (label) => emit({ type: "status", message: label }),
|
|
3740
|
+
signal
|
|
3741
|
+
});
|
|
3742
|
+
try {
|
|
3743
|
+
while (true) {
|
|
3744
|
+
turns++;
|
|
3745
|
+
if (signal?.aborted) {
|
|
3746
|
+
return abortResult([]);
|
|
3747
|
+
}
|
|
3748
|
+
const iterStart = Date.now();
|
|
3749
|
+
const contentBlocks = [];
|
|
3750
|
+
let thinkingStartedAt = 0;
|
|
3751
|
+
let lastThinkingRelatedStartedAt;
|
|
3752
|
+
let stopReason = "end_turn";
|
|
3753
|
+
let lastUsage;
|
|
3754
|
+
let lastProviderMetadata;
|
|
3755
|
+
let lastModelId;
|
|
3756
|
+
watchedBlocks = contentBlocks;
|
|
3757
|
+
watchedToolNames = "";
|
|
3758
|
+
try {
|
|
3759
|
+
for await (const event of streamChatWithRetry(
|
|
3760
|
+
{
|
|
3761
|
+
...apiConfig,
|
|
3762
|
+
model,
|
|
3763
|
+
requestId,
|
|
3764
|
+
subAgentId,
|
|
3765
|
+
system: fullSystem,
|
|
3766
|
+
messages: cleanMessagesForApi(messages),
|
|
3767
|
+
tools: tools2,
|
|
3768
|
+
signal
|
|
3769
|
+
},
|
|
3770
|
+
{
|
|
3771
|
+
onRetry: (attempt) => emit({
|
|
3772
|
+
type: "status",
|
|
3773
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
3774
|
+
})
|
|
3775
|
+
}
|
|
3776
|
+
)) {
|
|
3777
|
+
if (signal?.aborted) {
|
|
3778
|
+
break;
|
|
3779
|
+
}
|
|
3780
|
+
switch (event.type) {
|
|
3781
|
+
case "text": {
|
|
3782
|
+
const lastBlock = contentBlocks.at(-1);
|
|
3783
|
+
if (lastBlock?.type === "text") {
|
|
3784
|
+
lastBlock.text += event.text;
|
|
3785
|
+
} else {
|
|
3786
|
+
contentBlocks.push({
|
|
3787
|
+
type: "text",
|
|
3788
|
+
text: event.text,
|
|
3789
|
+
startedAt: event.ts
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
emit({ type: "text", text: event.text });
|
|
3793
|
+
break;
|
|
3794
|
+
}
|
|
3795
|
+
case "thinking":
|
|
3796
|
+
if (!thinkingStartedAt) {
|
|
3797
|
+
thinkingStartedAt = event.ts;
|
|
3798
|
+
}
|
|
3799
|
+
emit({ type: "thinking", text: event.text });
|
|
3800
|
+
break;
|
|
3801
|
+
case "thinking_complete":
|
|
3802
|
+
contentBlocks.push({
|
|
3803
|
+
type: "thinking",
|
|
3804
|
+
thinking: event.thinking,
|
|
3805
|
+
signature: event.signature,
|
|
3806
|
+
startedAt: thinkingStartedAt,
|
|
3807
|
+
completedAt: event.ts
|
|
3808
|
+
});
|
|
3809
|
+
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
3810
|
+
thinkingStartedAt = 0;
|
|
3811
|
+
break;
|
|
3812
|
+
case "redacted_thinking_complete": {
|
|
3813
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
3814
|
+
contentBlocks.push({
|
|
3815
|
+
type: "redacted_thinking",
|
|
3816
|
+
data: event.data,
|
|
3817
|
+
startedAt,
|
|
3818
|
+
completedAt: event.ts
|
|
3819
|
+
});
|
|
3820
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
3821
|
+
break;
|
|
3822
|
+
}
|
|
3823
|
+
case "tool_use":
|
|
3824
|
+
contentBlocks.push({
|
|
3825
|
+
type: "tool",
|
|
3826
|
+
id: event.id,
|
|
3827
|
+
name: event.name,
|
|
3828
|
+
input: event.input,
|
|
3829
|
+
startedAt: Date.now()
|
|
3830
|
+
});
|
|
3831
|
+
emit({
|
|
3832
|
+
type: "tool_start",
|
|
3833
|
+
id: event.id,
|
|
3834
|
+
name: event.name,
|
|
3835
|
+
input: event.input
|
|
3836
|
+
});
|
|
3837
|
+
break;
|
|
3838
|
+
case "done":
|
|
3839
|
+
stopReason = event.stopReason;
|
|
3840
|
+
lastUsage = {
|
|
3841
|
+
inputTokens: event.usage.inputTokens,
|
|
3842
|
+
outputTokens: event.usage.outputTokens,
|
|
3843
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3844
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3845
|
+
llmCalls: 1
|
|
3846
|
+
};
|
|
3847
|
+
lastProviderMetadata = event.providerMetadata;
|
|
3848
|
+
lastModelId = event.modelId;
|
|
3849
|
+
recordUsage({
|
|
3850
|
+
ts: Date.now(),
|
|
3851
|
+
requestId,
|
|
3852
|
+
agentName: subAgentId || "sub-agent",
|
|
3853
|
+
parentToolId,
|
|
3854
|
+
modelId: event.modelId,
|
|
3855
|
+
inputTokens: event.usage.inputTokens,
|
|
3856
|
+
outputTokens: event.usage.outputTokens,
|
|
3857
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3858
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3859
|
+
cost: nanoToDollars(event.cost),
|
|
3860
|
+
billingEvents: event.billingEvents,
|
|
3861
|
+
durationMs: Date.now() - iterStart,
|
|
3862
|
+
toolNames: contentBlocks.filter(
|
|
3863
|
+
(b) => b.type === "tool"
|
|
3864
|
+
).map((b) => b.name)
|
|
3865
|
+
});
|
|
3866
|
+
break;
|
|
3867
|
+
case "error":
|
|
3868
|
+
statusWatcher.stop();
|
|
3869
|
+
return {
|
|
3870
|
+
text: `Error: ${event.error}`,
|
|
3871
|
+
messages: thisInvocation()
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3716
3874
|
}
|
|
3717
|
-
)
|
|
3718
|
-
if (signal?.aborted) {
|
|
3719
|
-
|
|
3875
|
+
} catch (err) {
|
|
3876
|
+
if (!signal?.aborted) {
|
|
3877
|
+
throw err;
|
|
3720
3878
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3879
|
+
}
|
|
3880
|
+
if (signal?.aborted) {
|
|
3881
|
+
statusWatcher.stop();
|
|
3882
|
+
return abortResult(contentBlocks);
|
|
3883
|
+
}
|
|
3884
|
+
messages.push({
|
|
3885
|
+
role: "assistant",
|
|
3886
|
+
content: contentBlocks,
|
|
3887
|
+
...lastUsage ? { usage: lastUsage } : {},
|
|
3888
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
3889
|
+
model: lastModelId ?? model
|
|
3890
|
+
});
|
|
3891
|
+
const toolCalls = contentBlocks.filter(
|
|
3892
|
+
(b) => b.type === "tool"
|
|
3893
|
+
);
|
|
3894
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3895
|
+
statusWatcher.stop();
|
|
3896
|
+
const text = getPartialText(contentBlocks);
|
|
3897
|
+
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
3898
|
+
return {
|
|
3899
|
+
text,
|
|
3900
|
+
messages: thisInvocation(),
|
|
3901
|
+
...hasArtifacts ? { artifacts } : {}
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
log7.info("Tools executing", {
|
|
3905
|
+
requestId,
|
|
3906
|
+
parentToolId,
|
|
3907
|
+
count: toolCalls.length,
|
|
3908
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
3909
|
+
});
|
|
3910
|
+
watchedToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
3911
|
+
const results = await Promise.all(
|
|
3912
|
+
toolCalls.map(async (tc) => {
|
|
3913
|
+
if (signal?.aborted) {
|
|
3914
|
+
return {
|
|
3915
|
+
id: tc.id,
|
|
3916
|
+
result: USER_CANCELLED_RESULT,
|
|
3917
|
+
isError: true
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
let settle;
|
|
3921
|
+
const resultPromise = new Promise((res) => {
|
|
3922
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
3923
|
+
});
|
|
3924
|
+
let toolAbort = new AbortController();
|
|
3925
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
3926
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
3927
|
+
let settled = false;
|
|
3928
|
+
const safeSettle = (result, isError) => {
|
|
3929
|
+
if (settled) {
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
settled = true;
|
|
3933
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
3934
|
+
settle(result, isError);
|
|
3935
|
+
};
|
|
3936
|
+
const run2 = async (input) => {
|
|
3937
|
+
try {
|
|
3938
|
+
let result;
|
|
3939
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
3940
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
3941
|
+
} else {
|
|
3942
|
+
const onLog = (line) => emit({
|
|
3943
|
+
type: "tool_input_delta",
|
|
3944
|
+
id: tc.id,
|
|
3945
|
+
name: tc.name,
|
|
3946
|
+
result: line
|
|
3947
|
+
});
|
|
3948
|
+
result = await executeTool2(
|
|
3949
|
+
tc.name,
|
|
3950
|
+
input,
|
|
3951
|
+
tc.id,
|
|
3952
|
+
onLog,
|
|
3953
|
+
subAgentMessages
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
3959
|
+
}
|
|
3960
|
+
};
|
|
3961
|
+
const entry = {
|
|
3962
|
+
id: tc.id,
|
|
3963
|
+
name: tc.name,
|
|
3964
|
+
input: tc.input,
|
|
3965
|
+
parentToolId,
|
|
3966
|
+
abortController: toolAbort,
|
|
3967
|
+
startedAt: Date.now(),
|
|
3968
|
+
settle: safeSettle,
|
|
3969
|
+
rerun: (newInput) => {
|
|
3970
|
+
settled = false;
|
|
3971
|
+
toolAbort = new AbortController();
|
|
3972
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
3973
|
+
once: true
|
|
3731
3974
|
});
|
|
3975
|
+
entry.abortController = toolAbort;
|
|
3976
|
+
entry.input = newInput;
|
|
3977
|
+
run2(newInput);
|
|
3732
3978
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3979
|
+
};
|
|
3980
|
+
toolRegistry?.register(entry);
|
|
3981
|
+
const toolStart = Date.now();
|
|
3982
|
+
run2(tc.input);
|
|
3983
|
+
const r = await resultPromise;
|
|
3984
|
+
toolRegistry?.unregister(tc.id);
|
|
3985
|
+
log7.info("Tool completed", {
|
|
3986
|
+
requestId,
|
|
3987
|
+
parentToolId,
|
|
3988
|
+
toolCallId: tc.id,
|
|
3989
|
+
name: tc.name,
|
|
3990
|
+
durationMs: Date.now() - toolStart,
|
|
3991
|
+
isError: r.isError
|
|
3992
|
+
});
|
|
3993
|
+
emit({
|
|
3994
|
+
type: "tool_done",
|
|
3995
|
+
id: tc.id,
|
|
3996
|
+
name: tc.name,
|
|
3997
|
+
result: r.result,
|
|
3998
|
+
isError: r.isError
|
|
3999
|
+
});
|
|
4000
|
+
return r;
|
|
4001
|
+
})
|
|
4002
|
+
);
|
|
4003
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
4004
|
+
for (const r of results) {
|
|
4005
|
+
const block = contentBlocks.find(
|
|
4006
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
4007
|
+
);
|
|
4008
|
+
if (block?.type === "tool") {
|
|
4009
|
+
block.result = r.result;
|
|
4010
|
+
block.isError = r.isError;
|
|
4011
|
+
block.completedAt = Date.now();
|
|
4012
|
+
const innerMsgs = subAgentMessages.get(r.id);
|
|
4013
|
+
if (innerMsgs) {
|
|
4014
|
+
block.subAgentMessages = innerMsgs;
|
|
3735
4015
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
4016
|
+
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4017
|
+
try {
|
|
4018
|
+
artifacts[block.name] = JSON.parse(r.result);
|
|
4019
|
+
} catch {
|
|
3739
4020
|
}
|
|
3740
|
-
emit({ type: "thinking", text: event.text });
|
|
3741
|
-
break;
|
|
3742
|
-
case "thinking_complete":
|
|
3743
|
-
contentBlocks.push({
|
|
3744
|
-
type: "thinking",
|
|
3745
|
-
thinking: event.thinking,
|
|
3746
|
-
signature: event.signature,
|
|
3747
|
-
startedAt: thinkingStartedAt,
|
|
3748
|
-
completedAt: event.ts
|
|
3749
|
-
});
|
|
3750
|
-
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
3751
|
-
thinkingStartedAt = 0;
|
|
3752
|
-
break;
|
|
3753
|
-
case "redacted_thinking_complete": {
|
|
3754
|
-
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
3755
|
-
contentBlocks.push({
|
|
3756
|
-
type: "redacted_thinking",
|
|
3757
|
-
data: event.data,
|
|
3758
|
-
startedAt,
|
|
3759
|
-
completedAt: event.ts
|
|
3760
|
-
});
|
|
3761
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
3762
|
-
break;
|
|
3763
4021
|
}
|
|
3764
|
-
case "tool_use":
|
|
3765
|
-
contentBlocks.push({
|
|
3766
|
-
type: "tool",
|
|
3767
|
-
id: event.id,
|
|
3768
|
-
name: event.name,
|
|
3769
|
-
input: event.input,
|
|
3770
|
-
startedAt: Date.now()
|
|
3771
|
-
});
|
|
3772
|
-
emit({
|
|
3773
|
-
type: "tool_start",
|
|
3774
|
-
id: event.id,
|
|
3775
|
-
name: event.name,
|
|
3776
|
-
input: event.input
|
|
3777
|
-
});
|
|
3778
|
-
break;
|
|
3779
|
-
case "done":
|
|
3780
|
-
stopReason = event.stopReason;
|
|
3781
|
-
lastUsage = {
|
|
3782
|
-
inputTokens: event.usage.inputTokens,
|
|
3783
|
-
outputTokens: event.usage.outputTokens,
|
|
3784
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3785
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3786
|
-
llmCalls: 1
|
|
3787
|
-
};
|
|
3788
|
-
lastProviderMetadata = event.providerMetadata;
|
|
3789
|
-
lastModelId = event.modelId;
|
|
3790
|
-
recordUsage({
|
|
3791
|
-
ts: Date.now(),
|
|
3792
|
-
requestId,
|
|
3793
|
-
agentName: subAgentId || "sub-agent",
|
|
3794
|
-
parentToolId,
|
|
3795
|
-
modelId: event.modelId,
|
|
3796
|
-
inputTokens: event.usage.inputTokens,
|
|
3797
|
-
outputTokens: event.usage.outputTokens,
|
|
3798
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3799
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3800
|
-
cost: nanoToDollars(event.cost),
|
|
3801
|
-
billingEvents: event.billingEvents,
|
|
3802
|
-
durationMs: Date.now() - iterStart,
|
|
3803
|
-
toolNames: contentBlocks.filter(
|
|
3804
|
-
(b) => b.type === "tool"
|
|
3805
|
-
).map((b) => b.name)
|
|
3806
|
-
});
|
|
3807
|
-
break;
|
|
3808
|
-
case "error":
|
|
3809
|
-
return {
|
|
3810
|
-
text: `Error: ${event.error}`,
|
|
3811
|
-
messages: thisInvocation()
|
|
3812
|
-
};
|
|
3813
4022
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
4023
|
+
messages.push({
|
|
4024
|
+
role: "user",
|
|
4025
|
+
content: r.result,
|
|
4026
|
+
toolCallId: r.id,
|
|
4027
|
+
isToolError: r.isError
|
|
4028
|
+
});
|
|
3818
4029
|
}
|
|
3819
4030
|
}
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4031
|
+
} finally {
|
|
4032
|
+
statusWatcher.stop();
|
|
4033
|
+
}
|
|
4034
|
+
};
|
|
4035
|
+
const wrapRun = async () => {
|
|
4036
|
+
try {
|
|
4037
|
+
const result = await run();
|
|
4038
|
+
log7.info("Sub-agent complete", {
|
|
4039
|
+
requestId,
|
|
4040
|
+
parentToolId,
|
|
4041
|
+
agentName,
|
|
4042
|
+
durationMs: Date.now() - runStart,
|
|
4043
|
+
turns
|
|
3830
4044
|
});
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3835
|
-
statusWatcher.stop();
|
|
3836
|
-
const text = getPartialText(contentBlocks);
|
|
3837
|
-
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
3838
|
-
return {
|
|
3839
|
-
text,
|
|
3840
|
-
messages: thisInvocation(),
|
|
3841
|
-
...hasArtifacts ? { artifacts } : {}
|
|
3842
|
-
};
|
|
3843
|
-
}
|
|
3844
|
-
log7.info("Tools executing", {
|
|
4045
|
+
return result;
|
|
4046
|
+
} catch (err) {
|
|
4047
|
+
log7.warn("Sub-agent error", {
|
|
3845
4048
|
requestId,
|
|
3846
4049
|
parentToolId,
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
});
|
|
3850
|
-
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
3851
|
-
const results = await Promise.all(
|
|
3852
|
-
toolCalls.map(async (tc) => {
|
|
3853
|
-
if (signal?.aborted) {
|
|
3854
|
-
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
3855
|
-
}
|
|
3856
|
-
let settle;
|
|
3857
|
-
const resultPromise = new Promise((res) => {
|
|
3858
|
-
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
3859
|
-
});
|
|
3860
|
-
let toolAbort = new AbortController();
|
|
3861
|
-
const cascadeAbort = () => toolAbort.abort();
|
|
3862
|
-
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
3863
|
-
let settled = false;
|
|
3864
|
-
const safeSettle = (result, isError) => {
|
|
3865
|
-
if (settled) {
|
|
3866
|
-
return;
|
|
3867
|
-
}
|
|
3868
|
-
settled = true;
|
|
3869
|
-
signal?.removeEventListener("abort", cascadeAbort);
|
|
3870
|
-
settle(result, isError);
|
|
3871
|
-
};
|
|
3872
|
-
const run2 = async (input) => {
|
|
3873
|
-
try {
|
|
3874
|
-
let result;
|
|
3875
|
-
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
3876
|
-
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
3877
|
-
} else {
|
|
3878
|
-
const onLog = (line) => emit({
|
|
3879
|
-
type: "tool_input_delta",
|
|
3880
|
-
id: tc.id,
|
|
3881
|
-
name: tc.name,
|
|
3882
|
-
result: line
|
|
3883
|
-
});
|
|
3884
|
-
result = await executeTool2(
|
|
3885
|
-
tc.name,
|
|
3886
|
-
input,
|
|
3887
|
-
tc.id,
|
|
3888
|
-
onLog,
|
|
3889
|
-
subAgentMessages
|
|
3890
|
-
);
|
|
3891
|
-
}
|
|
3892
|
-
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
3893
|
-
} catch (err) {
|
|
3894
|
-
safeSettle(`Error: ${err.message}`, true);
|
|
3895
|
-
}
|
|
3896
|
-
};
|
|
3897
|
-
const entry = {
|
|
3898
|
-
id: tc.id,
|
|
3899
|
-
name: tc.name,
|
|
3900
|
-
input: tc.input,
|
|
3901
|
-
parentToolId,
|
|
3902
|
-
abortController: toolAbort,
|
|
3903
|
-
startedAt: Date.now(),
|
|
3904
|
-
settle: safeSettle,
|
|
3905
|
-
rerun: (newInput) => {
|
|
3906
|
-
settled = false;
|
|
3907
|
-
toolAbort = new AbortController();
|
|
3908
|
-
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
3909
|
-
once: true
|
|
3910
|
-
});
|
|
3911
|
-
entry.abortController = toolAbort;
|
|
3912
|
-
entry.input = newInput;
|
|
3913
|
-
run2(newInput);
|
|
3914
|
-
}
|
|
3915
|
-
};
|
|
3916
|
-
toolRegistry?.register(entry);
|
|
3917
|
-
const toolStart = Date.now();
|
|
3918
|
-
run2(tc.input);
|
|
3919
|
-
const r = await resultPromise;
|
|
3920
|
-
toolRegistry?.unregister(tc.id);
|
|
3921
|
-
log7.info("Tool completed", {
|
|
3922
|
-
requestId,
|
|
3923
|
-
parentToolId,
|
|
3924
|
-
toolCallId: tc.id,
|
|
3925
|
-
name: tc.name,
|
|
3926
|
-
durationMs: Date.now() - toolStart,
|
|
3927
|
-
isError: r.isError
|
|
3928
|
-
});
|
|
3929
|
-
emit({
|
|
3930
|
-
type: "tool_done",
|
|
3931
|
-
id: tc.id,
|
|
3932
|
-
name: tc.name,
|
|
3933
|
-
result: r.result,
|
|
3934
|
-
isError: r.isError
|
|
3935
|
-
});
|
|
3936
|
-
return r;
|
|
3937
|
-
})
|
|
3938
|
-
);
|
|
3939
|
-
statusWatcher.stop();
|
|
3940
|
-
lastToolResult = results.at(-1)?.result ?? "";
|
|
3941
|
-
for (const r of results) {
|
|
3942
|
-
const block = contentBlocks.find(
|
|
3943
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
3944
|
-
);
|
|
3945
|
-
if (block?.type === "tool") {
|
|
3946
|
-
block.result = r.result;
|
|
3947
|
-
block.isError = r.isError;
|
|
3948
|
-
block.completedAt = Date.now();
|
|
3949
|
-
const innerMsgs = subAgentMessages.get(r.id);
|
|
3950
|
-
if (innerMsgs) {
|
|
3951
|
-
block.subAgentMessages = innerMsgs;
|
|
3952
|
-
}
|
|
3953
|
-
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
3954
|
-
try {
|
|
3955
|
-
artifacts[block.name] = JSON.parse(r.result);
|
|
3956
|
-
} catch {
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
messages.push({
|
|
3961
|
-
role: "user",
|
|
3962
|
-
content: r.result,
|
|
3963
|
-
toolCallId: r.id,
|
|
3964
|
-
isToolError: r.isError
|
|
3965
|
-
});
|
|
3966
|
-
}
|
|
3967
|
-
}
|
|
3968
|
-
};
|
|
3969
|
-
const wrapRun = async () => {
|
|
3970
|
-
try {
|
|
3971
|
-
const result = await run();
|
|
3972
|
-
log7.info("Sub-agent complete", {
|
|
3973
|
-
requestId,
|
|
3974
|
-
parentToolId,
|
|
3975
|
-
agentName,
|
|
3976
|
-
durationMs: Date.now() - runStart,
|
|
3977
|
-
turns
|
|
3978
|
-
});
|
|
3979
|
-
return result;
|
|
3980
|
-
} catch (err) {
|
|
3981
|
-
log7.warn("Sub-agent error", {
|
|
3982
|
-
requestId,
|
|
3983
|
-
parentToolId,
|
|
3984
|
-
agentName,
|
|
3985
|
-
error: err.message
|
|
4050
|
+
agentName,
|
|
4051
|
+
error: err.message
|
|
3986
4052
|
});
|
|
3987
4053
|
throw err;
|
|
3988
4054
|
}
|
|
@@ -4040,7 +4106,6 @@ var COMMON_READ_TOOL_NAMES = new Set(
|
|
|
4040
4106
|
// src/subagents/browserAutomation/tools.ts
|
|
4041
4107
|
var BROWSER_TOOLS = [
|
|
4042
4108
|
{
|
|
4043
|
-
clearable: false,
|
|
4044
4109
|
name: "setupBrowser",
|
|
4045
4110
|
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.",
|
|
4046
4111
|
inputSchema: {
|
|
@@ -4073,7 +4138,6 @@ var BROWSER_TOOLS = [
|
|
|
4073
4138
|
}
|
|
4074
4139
|
},
|
|
4075
4140
|
{
|
|
4076
|
-
clearable: true,
|
|
4077
4141
|
name: "browserCommand",
|
|
4078
4142
|
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.",
|
|
4079
4143
|
inputSchema: {
|
|
@@ -4490,7 +4554,6 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4490
4554
|
}
|
|
4491
4555
|
var browserAutomationTool = {
|
|
4492
4556
|
definition: {
|
|
4493
|
-
clearable: true,
|
|
4494
4557
|
name: "runAutomatedBrowserTest",
|
|
4495
4558
|
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).",
|
|
4496
4559
|
inputSchema: {
|
|
@@ -4520,7 +4583,6 @@ var browserAutomationTool = {
|
|
|
4520
4583
|
|
|
4521
4584
|
// src/tools/code/screenshot.ts
|
|
4522
4585
|
var screenshotDefinition = {
|
|
4523
|
-
clearable: true,
|
|
4524
4586
|
name: "screenshot",
|
|
4525
4587
|
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.",
|
|
4526
4588
|
inputSchema: {
|
|
@@ -4626,7 +4688,6 @@ __export(searchGoogle_exports, {
|
|
|
4626
4688
|
execute: () => execute
|
|
4627
4689
|
});
|
|
4628
4690
|
var definition = {
|
|
4629
|
-
clearable: false,
|
|
4630
4691
|
name: "searchGoogle",
|
|
4631
4692
|
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.',
|
|
4632
4693
|
inputSchema: {
|
|
@@ -4659,7 +4720,6 @@ __export(scrapeWebUrl_exports, {
|
|
|
4659
4720
|
execute: () => execute2
|
|
4660
4721
|
});
|
|
4661
4722
|
var definition2 = {
|
|
4662
|
-
clearable: false,
|
|
4663
4723
|
name: "scrapeWebUrl",
|
|
4664
4724
|
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.",
|
|
4665
4725
|
inputSchema: {
|
|
@@ -4717,7 +4777,6 @@ Identify the specific design moves that make this page interesting and unique, d
|
|
|
4717
4777
|
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.
|
|
4718
4778
|
`;
|
|
4719
4779
|
var definition3 = {
|
|
4720
|
-
clearable: false,
|
|
4721
4780
|
name: "analyzeDesign",
|
|
4722
4781
|
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.",
|
|
4723
4782
|
inputSchema: {
|
|
@@ -4782,7 +4841,6 @@ __export(analyzeImage_exports, {
|
|
|
4782
4841
|
execute: () => execute4
|
|
4783
4842
|
});
|
|
4784
4843
|
var definition4 = {
|
|
4785
|
-
clearable: true,
|
|
4786
4844
|
name: "analyzeImage",
|
|
4787
4845
|
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.",
|
|
4788
4846
|
inputSchema: {
|
|
@@ -5016,7 +5074,6 @@ async function generateImageAssets(opts) {
|
|
|
5016
5074
|
|
|
5017
5075
|
// src/subagents/designExpert/tools/images/generateImages.ts
|
|
5018
5076
|
var definition5 = {
|
|
5019
|
-
clearable: false,
|
|
5020
5077
|
name: "generateImages",
|
|
5021
5078
|
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.",
|
|
5022
5079
|
inputSchema: {
|
|
@@ -5084,7 +5141,6 @@ __export(editImages_exports, {
|
|
|
5084
5141
|
execute: () => execute6
|
|
5085
5142
|
});
|
|
5086
5143
|
var definition6 = {
|
|
5087
|
-
clearable: false,
|
|
5088
5144
|
name: "editImages",
|
|
5089
5145
|
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.",
|
|
5090
5146
|
inputSchema: {
|
|
@@ -5162,7 +5218,6 @@ var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
|
|
|
5162
5218
|
var BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
|
|
5163
5219
|
var copyEditorTool = {
|
|
5164
5220
|
definition: {
|
|
5165
|
-
clearable: false,
|
|
5166
5221
|
name: "copyEditor",
|
|
5167
5222
|
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.",
|
|
5168
5223
|
inputSchema: {
|
|
@@ -5210,7 +5265,6 @@ var copyEditorTool = {
|
|
|
5210
5265
|
|
|
5211
5266
|
// src/subagents/designExpert/tools/polishCopy.ts
|
|
5212
5267
|
var definition7 = {
|
|
5213
|
-
clearable: false,
|
|
5214
5268
|
name: "polishCopy",
|
|
5215
5269
|
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).",
|
|
5216
5270
|
inputSchema: {
|
|
@@ -5562,7 +5616,6 @@ async function runDesignExpert(opts, context) {
|
|
|
5562
5616
|
}
|
|
5563
5617
|
var designExpertTool = {
|
|
5564
5618
|
definition: {
|
|
5565
|
-
clearable: false,
|
|
5566
5619
|
name: "visualDesignExpert",
|
|
5567
5620
|
description: DESCRIPTION,
|
|
5568
5621
|
inputSchema: {
|
|
@@ -5770,7 +5823,6 @@ function getProductVisionPrompt() {
|
|
|
5770
5823
|
// src/subagents/productVision/index.ts
|
|
5771
5824
|
var productVisionTool = {
|
|
5772
5825
|
definition: {
|
|
5773
|
-
clearable: false,
|
|
5774
5826
|
name: "productVision",
|
|
5775
5827
|
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.",
|
|
5776
5828
|
inputSchema: {
|
|
@@ -5883,7 +5935,6 @@ var SANITY_CHECK_TOOLS = [
|
|
|
5883
5935
|
var BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
5884
5936
|
var codeSanityCheckTool = {
|
|
5885
5937
|
definition: {
|
|
5886
|
-
clearable: false,
|
|
5887
5938
|
name: "codeSanityCheck",
|
|
5888
5939
|
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.',
|
|
5889
5940
|
inputSchema: {
|
|
@@ -5929,6 +5980,121 @@ var codeSanityCheckTool = {
|
|
|
5929
5980
|
}
|
|
5930
5981
|
};
|
|
5931
5982
|
|
|
5983
|
+
// src/tools/spec/writeBuildOverview.ts
|
|
5984
|
+
import fs20 from "fs";
|
|
5985
|
+
var OVERVIEW_FILE = "src/overview.html";
|
|
5986
|
+
var 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).
|
|
5987
|
+
|
|
5988
|
+
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.
|
|
5989
|
+
|
|
5990
|
+
### The single hard rule
|
|
5991
|
+
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.
|
|
5992
|
+
|
|
5993
|
+
### What it is (and is not)
|
|
5994
|
+
- 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."
|
|
5995
|
+
- 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.
|
|
5996
|
+
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
5997
|
+
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
5998
|
+
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
5999
|
+
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
6000
|
+
|
|
6001
|
+
### Constraints
|
|
6002
|
+
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
6003
|
+
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
6004
|
+
var OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
6005
|
+
<html lang="en">
|
|
6006
|
+
<head>
|
|
6007
|
+
<meta charset="utf-8">
|
|
6008
|
+
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
6009
|
+
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
6010
|
+
The user-scalable=no viewport is intentional; leave it. -->
|
|
6011
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
6012
|
+
<title>Build Overview</title>
|
|
6013
|
+
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
6014
|
+
<style>
|
|
6015
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
6016
|
+
</style>
|
|
6017
|
+
</head>
|
|
6018
|
+
<body>
|
|
6019
|
+
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
6020
|
+
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
6021
|
+
app's spec. -->
|
|
6022
|
+
</body>
|
|
6023
|
+
</html>`;
|
|
6024
|
+
function initialDelivery() {
|
|
6025
|
+
return `### Your deliverable
|
|
6026
|
+
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.
|
|
6027
|
+
|
|
6028
|
+
<overview_shell>
|
|
6029
|
+
${OVERVIEW_SHELL}
|
|
6030
|
+
</overview_shell>
|
|
6031
|
+
|
|
6032
|
+
Then reply with a one-line summary of what you wrote.`;
|
|
6033
|
+
}
|
|
6034
|
+
function refreshDelivery() {
|
|
6035
|
+
return `### Your deliverable
|
|
6036
|
+
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.
|
|
6037
|
+
|
|
6038
|
+
Then reply with a one-line summary of what you changed.`;
|
|
6039
|
+
}
|
|
6040
|
+
async function renderBuildOverview(content, context, opts) {
|
|
6041
|
+
if (!content) {
|
|
6042
|
+
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
6043
|
+
}
|
|
6044
|
+
const exists = fs20.existsSync(OVERVIEW_FILE);
|
|
6045
|
+
const task = `<overview_copy>${content}</overview_copy>
|
|
6046
|
+
|
|
6047
|
+
${DESIGN_BRIEF}
|
|
6048
|
+
|
|
6049
|
+
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
6050
|
+
try {
|
|
6051
|
+
if (opts.background) {
|
|
6052
|
+
const result2 = await runDesignExpertRender(
|
|
6053
|
+
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
6054
|
+
context
|
|
6055
|
+
);
|
|
6056
|
+
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
6057
|
+
return result2.text;
|
|
6058
|
+
}
|
|
6059
|
+
const result = await runDesignExpertRender({ task }, context);
|
|
6060
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6061
|
+
if (!fs20.existsSync(OVERVIEW_FILE)) {
|
|
6062
|
+
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
6063
|
+
${result.text}`;
|
|
6064
|
+
}
|
|
6065
|
+
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
6066
|
+
} catch (err) {
|
|
6067
|
+
return `Error generating build overview: ${err.message}`;
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
var buildOverviewTool = {
|
|
6071
|
+
definition: {
|
|
6072
|
+
name: "writeBuildOverview",
|
|
6073
|
+
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.",
|
|
6074
|
+
inputSchema: {
|
|
6075
|
+
type: "object",
|
|
6076
|
+
properties: {
|
|
6077
|
+
content: {
|
|
6078
|
+
type: "string",
|
|
6079
|
+
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."
|
|
6080
|
+
}
|
|
6081
|
+
},
|
|
6082
|
+
required: ["content"]
|
|
6083
|
+
}
|
|
6084
|
+
},
|
|
6085
|
+
async execute(input, context) {
|
|
6086
|
+
if (!context) {
|
|
6087
|
+
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
6088
|
+
}
|
|
6089
|
+
const content = (input.content ?? "").trim();
|
|
6090
|
+
const background = Boolean(content) && fs20.existsSync(OVERVIEW_FILE);
|
|
6091
|
+
if (background) {
|
|
6092
|
+
input.background = true;
|
|
6093
|
+
}
|
|
6094
|
+
return renderBuildOverview(content, context, { background });
|
|
6095
|
+
}
|
|
6096
|
+
};
|
|
6097
|
+
|
|
5932
6098
|
// src/subagents/specSync/tools.ts
|
|
5933
6099
|
var SPEC_SYNC_TOOLS = [
|
|
5934
6100
|
...COMMON_READ_TOOLS,
|
|
@@ -5963,15 +6129,18 @@ var specSyncTool = {
|
|
|
5963
6129
|
// next real turn as a hidden background_results note instead.
|
|
5964
6130
|
backgroundNotify: "passive",
|
|
5965
6131
|
definition: {
|
|
5966
|
-
clearable: false,
|
|
5967
6132
|
name: "specSync",
|
|
5968
|
-
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.",
|
|
6133
|
+
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.",
|
|
5969
6134
|
inputSchema: {
|
|
5970
6135
|
type: "object",
|
|
5971
6136
|
properties: {
|
|
5972
6137
|
task: {
|
|
5973
6138
|
type: "string",
|
|
5974
6139
|
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."
|
|
6140
|
+
},
|
|
6141
|
+
refreshBuildOverview: {
|
|
6142
|
+
type: "boolean",
|
|
6143
|
+
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."
|
|
5975
6144
|
}
|
|
5976
6145
|
},
|
|
5977
6146
|
required: ["task"]
|
|
@@ -5995,12 +6164,27 @@ var specSyncTool = {
|
|
|
5995
6164
|
parts.push(specIndex);
|
|
5996
6165
|
}
|
|
5997
6166
|
const system = parts.join("\n\n");
|
|
6167
|
+
const refreshOverview = input.refreshBuildOverview === true;
|
|
6168
|
+
const tools2 = refreshOverview ? [...SPEC_SYNC_TOOLS, buildOverviewTool.definition] : SPEC_SYNC_TOOLS;
|
|
6169
|
+
const task = refreshOverview ? `${input.task}
|
|
6170
|
+
|
|
6171
|
+
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;
|
|
5998
6172
|
const result = await runSubAgent({
|
|
5999
6173
|
system,
|
|
6000
|
-
task
|
|
6001
|
-
tools:
|
|
6174
|
+
task,
|
|
6175
|
+
tools: tools2,
|
|
6002
6176
|
externalTools: /* @__PURE__ */ new Set(),
|
|
6003
|
-
executeTool: (name, toolInput
|
|
6177
|
+
executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
|
|
6178
|
+
if (name === "writeBuildOverview") {
|
|
6179
|
+
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
6180
|
+
return renderBuildOverview(
|
|
6181
|
+
String(toolInput.content ?? "").trim(),
|
|
6182
|
+
childCtx,
|
|
6183
|
+
{ background: false }
|
|
6184
|
+
);
|
|
6185
|
+
}
|
|
6186
|
+
return executeTool(name, toolInput, context);
|
|
6187
|
+
},
|
|
6004
6188
|
apiConfig: context.apiConfig,
|
|
6005
6189
|
model: resolveModel("specSync", context.models, context.model),
|
|
6006
6190
|
subAgentId: "specSync",
|
|
@@ -6031,7 +6215,6 @@ var specSyncTool = {
|
|
|
6031
6215
|
// src/tools/common/scrapeWebUrl.ts
|
|
6032
6216
|
var scrapeWebUrlTool = {
|
|
6033
6217
|
definition: {
|
|
6034
|
-
clearable: false,
|
|
6035
6218
|
name: "scrapeWebUrl",
|
|
6036
6219
|
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",
|
|
6037
6220
|
inputSchema: {
|
|
@@ -6073,119 +6256,9 @@ var scrapeWebUrlTool = {
|
|
|
6073
6256
|
}
|
|
6074
6257
|
};
|
|
6075
6258
|
|
|
6076
|
-
// src/tools/
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
var 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).
|
|
6080
|
-
|
|
6081
|
-
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.
|
|
6082
|
-
|
|
6083
|
-
### The single hard rule
|
|
6084
|
-
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.
|
|
6085
|
-
|
|
6086
|
-
### What it is (and is not)
|
|
6087
|
-
- 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."
|
|
6088
|
-
- 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.
|
|
6089
|
-
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
6090
|
-
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
6091
|
-
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
6092
|
-
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
6093
|
-
|
|
6094
|
-
### Constraints
|
|
6095
|
-
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
6096
|
-
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
6097
|
-
var OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
6098
|
-
<html lang="en">
|
|
6099
|
-
<head>
|
|
6100
|
-
<meta charset="utf-8">
|
|
6101
|
-
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
6102
|
-
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
6103
|
-
The user-scalable=no viewport is intentional; leave it. -->
|
|
6104
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
6105
|
-
<title>Build Overview</title>
|
|
6106
|
-
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
6107
|
-
<style>
|
|
6108
|
-
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
6109
|
-
</style>
|
|
6110
|
-
</head>
|
|
6111
|
-
<body>
|
|
6112
|
-
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
6113
|
-
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
6114
|
-
app's spec. -->
|
|
6115
|
-
</body>
|
|
6116
|
-
</html>`;
|
|
6117
|
-
function initialDelivery() {
|
|
6118
|
-
return `### Your deliverable
|
|
6119
|
-
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.
|
|
6120
|
-
|
|
6121
|
-
<overview_shell>
|
|
6122
|
-
${OVERVIEW_SHELL}
|
|
6123
|
-
</overview_shell>
|
|
6124
|
-
|
|
6125
|
-
Then reply with a one-line summary of what you wrote.`;
|
|
6126
|
-
}
|
|
6127
|
-
function refreshDelivery() {
|
|
6128
|
-
return `### Your deliverable
|
|
6129
|
-
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.
|
|
6130
|
-
|
|
6131
|
-
Then reply with a one-line summary of what you changed.`;
|
|
6132
|
-
}
|
|
6133
|
-
var buildOverviewTool = {
|
|
6134
|
-
definition: {
|
|
6135
|
-
clearable: false,
|
|
6136
|
-
name: "writeBuildOverview",
|
|
6137
|
-
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.",
|
|
6138
|
-
inputSchema: {
|
|
6139
|
-
type: "object",
|
|
6140
|
-
properties: {
|
|
6141
|
-
content: {
|
|
6142
|
-
type: "string",
|
|
6143
|
-
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."
|
|
6144
|
-
}
|
|
6145
|
-
},
|
|
6146
|
-
required: ["content"]
|
|
6147
|
-
}
|
|
6148
|
-
},
|
|
6149
|
-
async execute(input, context) {
|
|
6150
|
-
if (!context) {
|
|
6151
|
-
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
6152
|
-
}
|
|
6153
|
-
const content = (input.content ?? "").trim();
|
|
6154
|
-
if (!content) {
|
|
6155
|
-
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
6156
|
-
}
|
|
6157
|
-
const exists = fs20.existsSync(OVERVIEW_FILE);
|
|
6158
|
-
const task = `<overview_copy>${content}</overview_copy>
|
|
6159
|
-
|
|
6160
|
-
${DESIGN_BRIEF}
|
|
6161
|
-
|
|
6162
|
-
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
6163
|
-
try {
|
|
6164
|
-
if (exists) {
|
|
6165
|
-
input.background = true;
|
|
6166
|
-
const result2 = await runDesignExpertRender(
|
|
6167
|
-
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
6168
|
-
context
|
|
6169
|
-
);
|
|
6170
|
-
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
6171
|
-
return result2.text;
|
|
6172
|
-
}
|
|
6173
|
-
const result = await runDesignExpertRender({ task }, context);
|
|
6174
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6175
|
-
if (!fs20.existsSync(OVERVIEW_FILE)) {
|
|
6176
|
-
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
6177
|
-
${result.text}`;
|
|
6178
|
-
}
|
|
6179
|
-
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
6180
|
-
} catch (err) {
|
|
6181
|
-
return `Error generating build overview: ${err.message}`;
|
|
6182
|
-
}
|
|
6183
|
-
}
|
|
6184
|
-
};
|
|
6185
|
-
|
|
6186
|
-
// src/tools/index.ts
|
|
6187
|
-
function deriveContext(parent, toolCallId) {
|
|
6188
|
-
return { ...parent, toolCallId };
|
|
6259
|
+
// src/tools/index.ts
|
|
6260
|
+
function deriveContext(parent, toolCallId) {
|
|
6261
|
+
return { ...parent, toolCallId };
|
|
6189
6262
|
}
|
|
6190
6263
|
var ALL_TOOLS = [
|
|
6191
6264
|
// Common
|
|
@@ -6381,7 +6454,11 @@ function getConversationMessagesForSummary(messages, endIndex) {
|
|
|
6381
6454
|
break;
|
|
6382
6455
|
}
|
|
6383
6456
|
}
|
|
6384
|
-
return messages.slice(startIdx, endIndex)
|
|
6457
|
+
return messages.slice(startIdx, endIndex).filter(
|
|
6458
|
+
(msg) => !(Array.isArray(msg.content) && msg.content.some(
|
|
6459
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
6460
|
+
))
|
|
6461
|
+
);
|
|
6385
6462
|
}
|
|
6386
6463
|
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
6387
6464
|
let checkpointIdx = -1;
|
|
@@ -6659,6 +6736,11 @@ import path12 from "path";
|
|
|
6659
6736
|
var log10 = createLogger("session");
|
|
6660
6737
|
var SESSION_FILE = ".remy-session.json";
|
|
6661
6738
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
6739
|
+
function writeFileAtomicSync(file, data) {
|
|
6740
|
+
const tmp = `${file}.tmp`;
|
|
6741
|
+
fs21.writeFileSync(tmp, data, "utf-8");
|
|
6742
|
+
fs21.renameSync(tmp, file);
|
|
6743
|
+
}
|
|
6662
6744
|
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
6663
6745
|
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
6664
6746
|
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
@@ -6758,7 +6840,7 @@ function archiveMessages(messages, label, models) {
|
|
|
6758
6840
|
if (models && Object.keys(models).length > 0) {
|
|
6759
6841
|
payload.models = models;
|
|
6760
6842
|
}
|
|
6761
|
-
|
|
6843
|
+
writeFileAtomicSync(dest, JSON.stringify(payload));
|
|
6762
6844
|
archiveCountCache.set(path12.basename(dest), count);
|
|
6763
6845
|
log10.info("Session archived", { label, dest, messageCount: count });
|
|
6764
6846
|
pruneArchives();
|
|
@@ -6963,7 +7045,7 @@ function saveSession(state) {
|
|
|
6963
7045
|
if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
|
|
6964
7046
|
serialized = JSON.stringify(buildPayload(state));
|
|
6965
7047
|
}
|
|
6966
|
-
|
|
7048
|
+
writeFileAtomicSync(SESSION_FILE, serialized);
|
|
6967
7049
|
log10.info("Session saved", { messageCount: state.messages.length });
|
|
6968
7050
|
} catch (err) {
|
|
6969
7051
|
log10.warn("Session save failed", { error: err.message });
|
|
@@ -6993,6 +7075,28 @@ function clearSession(state) {
|
|
|
6993
7075
|
var log11 = createLogger("compaction:trigger");
|
|
6994
7076
|
var pending = null;
|
|
6995
7077
|
var inflightCompaction = null;
|
|
7078
|
+
function getInflightCompaction() {
|
|
7079
|
+
return inflightCompaction;
|
|
7080
|
+
}
|
|
7081
|
+
function summariesOf(result) {
|
|
7082
|
+
const out = [];
|
|
7083
|
+
for (const msg of result.checkpoints) {
|
|
7084
|
+
if (!Array.isArray(msg.content)) {
|
|
7085
|
+
continue;
|
|
7086
|
+
}
|
|
7087
|
+
for (const block of msg.content) {
|
|
7088
|
+
if (block.type === "summary") {
|
|
7089
|
+
out.push({
|
|
7090
|
+
name: block.name,
|
|
7091
|
+
text: block.text,
|
|
7092
|
+
...block.recent && { recent: block.recent },
|
|
7093
|
+
startedAt: block.startedAt
|
|
7094
|
+
});
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
return out;
|
|
7099
|
+
}
|
|
6996
7100
|
function applyPendingSummaries(state) {
|
|
6997
7101
|
const drained = pending;
|
|
6998
7102
|
pending = null;
|
|
@@ -7013,6 +7117,25 @@ function applyPendingSummaries(state) {
|
|
|
7013
7117
|
});
|
|
7014
7118
|
saveSession(state);
|
|
7015
7119
|
}
|
|
7120
|
+
var SUMMARY_SECTION_LABELS = {
|
|
7121
|
+
conversation: "Conversation",
|
|
7122
|
+
visualDesignExpert: "Design Agent Thread",
|
|
7123
|
+
productVision: "Roadmap Agent Thread"
|
|
7124
|
+
};
|
|
7125
|
+
function formatSummariesResult(summaries) {
|
|
7126
|
+
if (summaries === null) {
|
|
7127
|
+
return "A compaction checkpoint was already pending \u2014 no new compaction was needed.";
|
|
7128
|
+
}
|
|
7129
|
+
if (summaries.length === 0) {
|
|
7130
|
+
return "Nothing to compact \u2014 the conversation is already fully summarized.";
|
|
7131
|
+
}
|
|
7132
|
+
if (summaries.length === 1 && summaries[0].name === "conversation") {
|
|
7133
|
+
return summaries[0].text;
|
|
7134
|
+
}
|
|
7135
|
+
return summaries.map((s) => `## ${SUMMARY_SECTION_LABELS[s.name] ?? s.name}
|
|
7136
|
+
|
|
7137
|
+
${s.text}`).join("\n\n");
|
|
7138
|
+
}
|
|
7016
7139
|
var listener = null;
|
|
7017
7140
|
function setCompactionListener(l) {
|
|
7018
7141
|
listener = l;
|
|
@@ -7023,18 +7146,24 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
7023
7146
|
}
|
|
7024
7147
|
if (pending) {
|
|
7025
7148
|
log11.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
7026
|
-
return Promise.resolve();
|
|
7149
|
+
return Promise.resolve(null);
|
|
7027
7150
|
}
|
|
7028
|
-
const { blocking = false, requestId, model } = opts;
|
|
7029
|
-
listener?.({ type: "started", blocking, requestId });
|
|
7151
|
+
const { blocking = false, requestId, model, origin, toolCallId } = opts;
|
|
7152
|
+
listener?.({ type: "started", blocking, requestId, origin, toolCallId });
|
|
7030
7153
|
inflightCompaction = compactConversation(
|
|
7031
7154
|
state.messages,
|
|
7032
7155
|
apiConfig,
|
|
7033
7156
|
resolveModel("conversationSummarizer", state.models, model)
|
|
7034
7157
|
).then((result) => {
|
|
7035
7158
|
pending = result;
|
|
7036
|
-
|
|
7159
|
+
const summaries = summariesOf(result);
|
|
7160
|
+
listener?.({
|
|
7161
|
+
type: "complete",
|
|
7162
|
+
requestId,
|
|
7163
|
+
...summaries.length > 0 && { summaries }
|
|
7164
|
+
});
|
|
7037
7165
|
log11.info("Compaction complete");
|
|
7166
|
+
return summaries;
|
|
7038
7167
|
}).catch((err) => {
|
|
7039
7168
|
const message = err.message || "Compaction failed";
|
|
7040
7169
|
listener?.({ type: "complete", error: message, requestId });
|
|
@@ -7554,6 +7683,62 @@ function parsePartialJson(jsonString) {
|
|
|
7554
7683
|
return parseAny();
|
|
7555
7684
|
}
|
|
7556
7685
|
|
|
7686
|
+
// src/automatedActions/resolve.ts
|
|
7687
|
+
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
7688
|
+
function resolveAction(text) {
|
|
7689
|
+
const parsed = parseSentinel(text);
|
|
7690
|
+
if (!parsed) {
|
|
7691
|
+
return null;
|
|
7692
|
+
}
|
|
7693
|
+
const { name: triggerName, remainder } = parsed;
|
|
7694
|
+
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
7695
|
+
return null;
|
|
7696
|
+
}
|
|
7697
|
+
let params = {};
|
|
7698
|
+
if (remainder) {
|
|
7699
|
+
try {
|
|
7700
|
+
params = JSON.parse(remainder.split("\n")[0]);
|
|
7701
|
+
} catch {
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
7705
|
+
let next;
|
|
7706
|
+
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7707
|
+
if (fmMatch) {
|
|
7708
|
+
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
7709
|
+
if (nextMatch) {
|
|
7710
|
+
next = nextMatch[1];
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7713
|
+
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
7714
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7715
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
7716
|
+
body = body.replaceAll(`{{${key}}}`, str);
|
|
7717
|
+
}
|
|
7718
|
+
return {
|
|
7719
|
+
message: automatedMessage(triggerName, body),
|
|
7720
|
+
next
|
|
7721
|
+
};
|
|
7722
|
+
}
|
|
7723
|
+
function getActionChain(startName) {
|
|
7724
|
+
const chain = [];
|
|
7725
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7726
|
+
let name = startName;
|
|
7727
|
+
while (name && !seen.has(name)) {
|
|
7728
|
+
seen.add(name);
|
|
7729
|
+
chain.push(name);
|
|
7730
|
+
let body;
|
|
7731
|
+
try {
|
|
7732
|
+
body = readAsset("automatedActions", `${name}.md`);
|
|
7733
|
+
} catch {
|
|
7734
|
+
break;
|
|
7735
|
+
}
|
|
7736
|
+
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7737
|
+
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
7738
|
+
}
|
|
7739
|
+
return chain;
|
|
7740
|
+
}
|
|
7741
|
+
|
|
7557
7742
|
// src/errors.ts
|
|
7558
7743
|
var patterns = [
|
|
7559
7744
|
[
|
|
@@ -7705,6 +7890,9 @@ async function runTurn(params) {
|
|
|
7705
7890
|
let lastCompletedTools = "";
|
|
7706
7891
|
let lastCompletedInput = "";
|
|
7707
7892
|
let lastCompletedResult = "";
|
|
7893
|
+
let subAgentText = "";
|
|
7894
|
+
let currentToolNames = "";
|
|
7895
|
+
let contentBlocksRef = [];
|
|
7708
7896
|
let turnInputTokens = 0;
|
|
7709
7897
|
let turnOutputTokens = 0;
|
|
7710
7898
|
let turnCacheCreation = 0;
|
|
@@ -7713,281 +7901,328 @@ async function runTurn(params) {
|
|
|
7713
7901
|
let lastCallInputTokens = 0;
|
|
7714
7902
|
let lastCallCacheCreation = 0;
|
|
7715
7903
|
let lastCallCacheRead = 0;
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
} } : startStatusWatcher({
|
|
7747
|
-
apiConfig,
|
|
7748
|
-
getContext: () => {
|
|
7749
|
-
const parts = [];
|
|
7750
|
-
const toolName = currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
7751
|
-
if (toolName) {
|
|
7752
|
-
parts.push(`Tool: ${toolName}`);
|
|
7753
|
-
}
|
|
7754
|
-
if (lastCompletedInput) {
|
|
7755
|
-
parts.push(`Tool input: ${lastCompletedInput.slice(-1500)}`);
|
|
7756
|
-
}
|
|
7757
|
-
if (lastCompletedResult) {
|
|
7758
|
-
parts.push(`Tool result: ${lastCompletedResult.slice(-1500)}`);
|
|
7759
|
-
}
|
|
7760
|
-
const text = subAgentText || getTextContent(contentBlocks).slice(-2e3);
|
|
7761
|
-
if (text) {
|
|
7762
|
-
parts.push(`Assistant text: ${text}`);
|
|
7763
|
-
}
|
|
7764
|
-
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
7765
|
-
parts.push(`Build phase: ${onboardingState}`);
|
|
7904
|
+
const statusWatcher = isFirstMessage ? { stop() {
|
|
7905
|
+
}, pause() {
|
|
7906
|
+
}, resume() {
|
|
7907
|
+
} } : startStatusWatcher({
|
|
7908
|
+
apiConfig,
|
|
7909
|
+
getContext: () => {
|
|
7910
|
+
const parts = [];
|
|
7911
|
+
const toolName = currentToolNames || getToolCalls(contentBlocksRef).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
7912
|
+
if (toolName) {
|
|
7913
|
+
parts.push(`Tool: ${toolName}`);
|
|
7914
|
+
}
|
|
7915
|
+
const toolInput = sanitizeStatusText(lastCompletedInput);
|
|
7916
|
+
if (toolInput) {
|
|
7917
|
+
parts.push(`Tool input: ${toolInput.slice(-1500)}`);
|
|
7918
|
+
}
|
|
7919
|
+
const toolResult = sanitizeStatusText(lastCompletedResult);
|
|
7920
|
+
if (toolResult) {
|
|
7921
|
+
parts.push(`Tool result: ${toolResult.slice(-1500)}`);
|
|
7922
|
+
}
|
|
7923
|
+
const text = subAgentText || getTextContent(contentBlocksRef).slice(-2e3);
|
|
7924
|
+
if (text) {
|
|
7925
|
+
parts.push(`Assistant text: ${text}`);
|
|
7926
|
+
}
|
|
7927
|
+
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
7928
|
+
parts.push(`Build phase: ${onboardingState}`);
|
|
7929
|
+
}
|
|
7930
|
+
let hasUserSignal = false;
|
|
7931
|
+
for (const entry of keptEntries) {
|
|
7932
|
+
if (entry.hidden) {
|
|
7933
|
+
continue;
|
|
7766
7934
|
}
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
if (automated) {
|
|
7935
|
+
const automated = parseSentinel(entry.text);
|
|
7936
|
+
if (automated) {
|
|
7937
|
+
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
7770
7938
|
parts.push(`Automated action: ${automated.name}`);
|
|
7771
|
-
|
|
7772
|
-
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
7939
|
+
hasUserSignal = true;
|
|
7773
7940
|
}
|
|
7941
|
+
} else if (entry.text) {
|
|
7942
|
+
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
7943
|
+
hasUserSignal = true;
|
|
7774
7944
|
}
|
|
7775
|
-
return parts.join("\n");
|
|
7776
|
-
},
|
|
7777
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
7778
|
-
signal
|
|
7779
|
-
});
|
|
7780
|
-
async function handlePartialInput(acc, id, name, partial) {
|
|
7781
|
-
const tool = getToolByName(name);
|
|
7782
|
-
if (!tool?.streaming) {
|
|
7783
|
-
return;
|
|
7784
7945
|
}
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
transform,
|
|
7788
|
-
partialInput
|
|
7789
|
-
} = tool.streaming;
|
|
7790
|
-
if (partialInput) {
|
|
7791
|
-
const result = partialInput(partial, acc.lastEmittedCount);
|
|
7792
|
-
if (!result) {
|
|
7793
|
-
return;
|
|
7794
|
-
}
|
|
7795
|
-
acc.lastEmittedCount = result.emittedCount;
|
|
7796
|
-
acc.started = true;
|
|
7797
|
-
onEvent({
|
|
7798
|
-
type: "tool_start",
|
|
7799
|
-
id,
|
|
7800
|
-
name,
|
|
7801
|
-
input: result.input,
|
|
7802
|
-
partial: true
|
|
7803
|
-
});
|
|
7804
|
-
return;
|
|
7946
|
+
if (!hasUserSignal && !toolName && !text) {
|
|
7947
|
+
return "";
|
|
7805
7948
|
}
|
|
7806
|
-
|
|
7807
|
-
|
|
7949
|
+
return parts.join("\n");
|
|
7950
|
+
},
|
|
7951
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
7952
|
+
signal
|
|
7953
|
+
});
|
|
7954
|
+
try {
|
|
7955
|
+
while (true) {
|
|
7956
|
+
let getOrCreateAccumulator2 = function(id, name) {
|
|
7957
|
+
let acc = toolInputAccumulators.get(id);
|
|
7958
|
+
if (!acc) {
|
|
7959
|
+
acc = { name, json: "", started: false, lastEmittedCount: 0 };
|
|
7960
|
+
toolInputAccumulators.set(id, acc);
|
|
7961
|
+
}
|
|
7962
|
+
return acc;
|
|
7963
|
+
};
|
|
7964
|
+
var getOrCreateAccumulator = getOrCreateAccumulator2;
|
|
7965
|
+
if (signal?.aborted) {
|
|
7966
|
+
onEvent({ type: "turn_cancelled" });
|
|
7967
|
+
saveSession(state);
|
|
7808
7968
|
return;
|
|
7809
7969
|
}
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7970
|
+
const iterStart = Date.now();
|
|
7971
|
+
const contentBlocks = [];
|
|
7972
|
+
const thinkingBlockStartTimes = [];
|
|
7973
|
+
let thinkingCompleteCount = 0;
|
|
7974
|
+
let lastThinkingRelatedStartedAt;
|
|
7975
|
+
let textBlockOpen = false;
|
|
7976
|
+
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
7977
|
+
let stopReason = "end_turn";
|
|
7978
|
+
let turnProviderMetadata;
|
|
7979
|
+
let turnModelId;
|
|
7980
|
+
contentBlocksRef = contentBlocks;
|
|
7981
|
+
subAgentText = "";
|
|
7982
|
+
currentToolNames = "";
|
|
7983
|
+
async function handlePartialInput(acc, id, name, partial) {
|
|
7984
|
+
const tool = getToolByName(name);
|
|
7985
|
+
if (!tool?.streaming) {
|
|
7817
7986
|
return;
|
|
7818
7987
|
}
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
model: parentModel,
|
|
7829
|
-
requestId,
|
|
7830
|
-
system,
|
|
7831
|
-
messages: cleanMessagesForApi(state.messages),
|
|
7832
|
-
tools: tools2,
|
|
7833
|
-
signal
|
|
7834
|
-
},
|
|
7835
|
-
{
|
|
7836
|
-
onRetry: (attempt) => {
|
|
7837
|
-
onEvent({
|
|
7838
|
-
type: "status",
|
|
7839
|
-
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
7840
|
-
});
|
|
7988
|
+
const {
|
|
7989
|
+
contentField = "content",
|
|
7990
|
+
transform,
|
|
7991
|
+
partialInput
|
|
7992
|
+
} = tool.streaming;
|
|
7993
|
+
if (partialInput) {
|
|
7994
|
+
const result = partialInput(partial, acc.lastEmittedCount);
|
|
7995
|
+
if (!result) {
|
|
7996
|
+
return;
|
|
7841
7997
|
}
|
|
7998
|
+
acc.lastEmittedCount = result.emittedCount;
|
|
7999
|
+
acc.started = true;
|
|
8000
|
+
onEvent({
|
|
8001
|
+
type: "tool_start",
|
|
8002
|
+
id,
|
|
8003
|
+
name,
|
|
8004
|
+
input: result.input,
|
|
8005
|
+
partial: true
|
|
8006
|
+
});
|
|
8007
|
+
return;
|
|
7842
8008
|
}
|
|
7843
|
-
|
|
7844
|
-
if (
|
|
7845
|
-
|
|
8009
|
+
const content = partial[contentField];
|
|
8010
|
+
if (typeof content !== "string") {
|
|
8011
|
+
return;
|
|
7846
8012
|
}
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
text: event.text,
|
|
7856
|
-
startedAt: event.ts
|
|
7857
|
-
});
|
|
7858
|
-
}
|
|
7859
|
-
textBlockOpen = true;
|
|
7860
|
-
onEvent({ type: "text", text: event.text });
|
|
7861
|
-
break;
|
|
8013
|
+
if (!acc.started) {
|
|
8014
|
+
acc.started = true;
|
|
8015
|
+
onEvent({ type: "tool_start", id, name, input: partial });
|
|
8016
|
+
}
|
|
8017
|
+
if (transform) {
|
|
8018
|
+
const result = await transform(partial);
|
|
8019
|
+
if (result === null) {
|
|
8020
|
+
return;
|
|
7862
8021
|
}
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
8022
|
+
onEvent({ type: "tool_input_delta", id, name, result });
|
|
8023
|
+
} else {
|
|
8024
|
+
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
try {
|
|
8028
|
+
for await (const event of streamChatWithRetry(
|
|
8029
|
+
{
|
|
8030
|
+
...apiConfig,
|
|
8031
|
+
model: parentModel,
|
|
8032
|
+
requestId,
|
|
8033
|
+
system,
|
|
8034
|
+
messages: cleanMessagesForApi(state.messages),
|
|
8035
|
+
tools: tools2,
|
|
8036
|
+
signal
|
|
8037
|
+
},
|
|
8038
|
+
{
|
|
8039
|
+
onRetry: (attempt) => {
|
|
8040
|
+
onEvent({
|
|
8041
|
+
type: "status",
|
|
8042
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8043
|
+
});
|
|
7867
8044
|
}
|
|
7868
|
-
onEvent({ type: "thinking", text: event.text });
|
|
7869
|
-
break;
|
|
7870
|
-
case "thinking_complete": {
|
|
7871
|
-
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
7872
|
-
contentBlocks.push({
|
|
7873
|
-
type: "thinking",
|
|
7874
|
-
thinking: event.thinking,
|
|
7875
|
-
signature: event.signature,
|
|
7876
|
-
startedAt,
|
|
7877
|
-
completedAt: event.ts
|
|
7878
|
-
});
|
|
7879
|
-
thinkingCompleteCount++;
|
|
7880
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
7881
|
-
break;
|
|
7882
8045
|
}
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
contentBlocks.push({
|
|
7886
|
-
type: "redacted_thinking",
|
|
7887
|
-
data: event.data,
|
|
7888
|
-
startedAt,
|
|
7889
|
-
completedAt: event.ts
|
|
7890
|
-
});
|
|
7891
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8046
|
+
)) {
|
|
8047
|
+
if (signal?.aborted) {
|
|
7892
8048
|
break;
|
|
7893
8049
|
}
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
8050
|
+
switch (event.type) {
|
|
8051
|
+
case "text": {
|
|
8052
|
+
const lastBlock = contentBlocks.at(-1);
|
|
8053
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
8054
|
+
lastBlock.text += event.text;
|
|
8055
|
+
} else {
|
|
8056
|
+
contentBlocks.push({
|
|
8057
|
+
type: "text",
|
|
8058
|
+
text: event.text,
|
|
8059
|
+
startedAt: event.ts
|
|
8060
|
+
});
|
|
8061
|
+
}
|
|
8062
|
+
textBlockOpen = true;
|
|
8063
|
+
onEvent({ type: "text", text: event.text });
|
|
8064
|
+
break;
|
|
7901
8065
|
}
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
7907
|
-
break;
|
|
7908
|
-
}
|
|
7909
|
-
case "tool_use": {
|
|
7910
|
-
const tool = getToolByName(event.name);
|
|
7911
|
-
contentBlocks.push({
|
|
7912
|
-
type: "tool",
|
|
7913
|
-
id: event.id,
|
|
7914
|
-
name: event.name,
|
|
7915
|
-
input: event.input,
|
|
7916
|
-
startedAt: event.ts,
|
|
7917
|
-
...(event.input.background || tool?.backgroundOnly) && {
|
|
7918
|
-
background: true
|
|
8066
|
+
case "thinking":
|
|
8067
|
+
if (event.text === "") {
|
|
8068
|
+
thinkingBlockStartTimes.push(event.ts);
|
|
8069
|
+
textBlockOpen = false;
|
|
7919
8070
|
}
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8071
|
+
onEvent({ type: "thinking", text: event.text });
|
|
8072
|
+
break;
|
|
8073
|
+
case "thinking_complete": {
|
|
8074
|
+
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8075
|
+
contentBlocks.push({
|
|
8076
|
+
type: "thinking",
|
|
8077
|
+
thinking: event.thinking,
|
|
8078
|
+
signature: event.signature,
|
|
8079
|
+
startedAt,
|
|
8080
|
+
completedAt: event.ts
|
|
8081
|
+
});
|
|
8082
|
+
thinkingCompleteCount++;
|
|
8083
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8084
|
+
break;
|
|
8085
|
+
}
|
|
8086
|
+
case "redacted_thinking_complete": {
|
|
8087
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
8088
|
+
contentBlocks.push({
|
|
8089
|
+
type: "redacted_thinking",
|
|
8090
|
+
data: event.data,
|
|
8091
|
+
startedAt,
|
|
8092
|
+
completedAt: event.ts
|
|
8093
|
+
});
|
|
8094
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8095
|
+
break;
|
|
8096
|
+
}
|
|
8097
|
+
case "tool_input_delta": {
|
|
8098
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8099
|
+
acc.json += event.delta;
|
|
8100
|
+
try {
|
|
8101
|
+
const partial = parsePartialJson(acc.json);
|
|
8102
|
+
await handlePartialInput(acc, event.id, event.name, partial);
|
|
8103
|
+
} catch {
|
|
8104
|
+
}
|
|
8105
|
+
break;
|
|
8106
|
+
}
|
|
8107
|
+
case "tool_input_args": {
|
|
8108
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8109
|
+
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8110
|
+
break;
|
|
8111
|
+
}
|
|
8112
|
+
case "tool_use": {
|
|
8113
|
+
const tool = getToolByName(event.name);
|
|
8114
|
+
contentBlocks.push({
|
|
8115
|
+
type: "tool",
|
|
7932
8116
|
id: event.id,
|
|
7933
8117
|
name: event.name,
|
|
7934
|
-
input: event.input
|
|
8118
|
+
input: event.input,
|
|
8119
|
+
startedAt: event.ts,
|
|
8120
|
+
...(event.input.background || tool?.backgroundOnly) && {
|
|
8121
|
+
background: true
|
|
8122
|
+
}
|
|
8123
|
+
});
|
|
8124
|
+
const acc = toolInputAccumulators.get(event.id);
|
|
8125
|
+
const wasStreamed = acc?.started ?? false;
|
|
8126
|
+
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8127
|
+
log14.info("Tool received", {
|
|
8128
|
+
requestId,
|
|
8129
|
+
toolCallId: event.id,
|
|
8130
|
+
name: event.name
|
|
7935
8131
|
});
|
|
8132
|
+
if (!wasStreamed || isInputStreaming) {
|
|
8133
|
+
onEvent({
|
|
8134
|
+
type: "tool_start",
|
|
8135
|
+
id: event.id,
|
|
8136
|
+
name: event.name,
|
|
8137
|
+
input: event.input,
|
|
8138
|
+
// Mirror the content-block stamp above so the live event
|
|
8139
|
+
// matches what a history reload shows — without it the
|
|
8140
|
+
// frontend renders background rows as already complete.
|
|
8141
|
+
...isBackgroundCall({
|
|
8142
|
+
name: event.name,
|
|
8143
|
+
input: event.input
|
|
8144
|
+
}) && { background: true }
|
|
8145
|
+
});
|
|
8146
|
+
}
|
|
8147
|
+
break;
|
|
7936
8148
|
}
|
|
7937
|
-
|
|
8149
|
+
case "done":
|
|
8150
|
+
stopReason = event.stopReason;
|
|
8151
|
+
turnProviderMetadata = event.providerMetadata;
|
|
8152
|
+
turnModelId = event.modelId;
|
|
8153
|
+
turnLlmCalls++;
|
|
8154
|
+
lastCallInputTokens = event.usage.inputTokens;
|
|
8155
|
+
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8156
|
+
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8157
|
+
turnInputTokens += lastCallInputTokens;
|
|
8158
|
+
turnOutputTokens += event.usage.outputTokens;
|
|
8159
|
+
turnCacheCreation += lastCallCacheCreation;
|
|
8160
|
+
turnCacheRead += lastCallCacheRead;
|
|
8161
|
+
recordUsage({
|
|
8162
|
+
ts: Date.now(),
|
|
8163
|
+
requestId,
|
|
8164
|
+
agentName: "parent",
|
|
8165
|
+
modelId: event.modelId,
|
|
8166
|
+
inputTokens: event.usage.inputTokens,
|
|
8167
|
+
outputTokens: event.usage.outputTokens,
|
|
8168
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8169
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8170
|
+
cost: nanoToDollars(event.cost),
|
|
8171
|
+
billingEvents: event.billingEvents,
|
|
8172
|
+
durationMs: Date.now() - iterStart,
|
|
8173
|
+
toolNames: contentBlocks.filter(
|
|
8174
|
+
(b) => b.type === "tool"
|
|
8175
|
+
).map((b) => b.name)
|
|
8176
|
+
});
|
|
8177
|
+
break;
|
|
8178
|
+
case "error":
|
|
8179
|
+
statusWatcher.stop();
|
|
8180
|
+
onEvent({
|
|
8181
|
+
type: "error",
|
|
8182
|
+
error: friendlyError(event.error),
|
|
8183
|
+
...event.code ? { code: event.code } : {}
|
|
8184
|
+
});
|
|
8185
|
+
return;
|
|
7938
8186
|
}
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
lastCallInputTokens = event.usage.inputTokens;
|
|
7945
|
-
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
7946
|
-
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
7947
|
-
turnInputTokens += lastCallInputTokens;
|
|
7948
|
-
turnOutputTokens += event.usage.outputTokens;
|
|
7949
|
-
turnCacheCreation += lastCallCacheCreation;
|
|
7950
|
-
turnCacheRead += lastCallCacheRead;
|
|
7951
|
-
recordUsage({
|
|
7952
|
-
ts: Date.now(),
|
|
7953
|
-
requestId,
|
|
7954
|
-
agentName: "parent",
|
|
7955
|
-
modelId: event.modelId,
|
|
7956
|
-
inputTokens: event.usage.inputTokens,
|
|
7957
|
-
outputTokens: event.usage.outputTokens,
|
|
7958
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
7959
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
7960
|
-
cost: nanoToDollars(event.cost),
|
|
7961
|
-
billingEvents: event.billingEvents,
|
|
7962
|
-
durationMs: Date.now() - iterStart,
|
|
7963
|
-
toolNames: contentBlocks.filter(
|
|
7964
|
-
(b) => b.type === "tool"
|
|
7965
|
-
).map((b) => b.name)
|
|
7966
|
-
});
|
|
7967
|
-
break;
|
|
7968
|
-
case "error":
|
|
7969
|
-
onEvent({
|
|
7970
|
-
type: "error",
|
|
7971
|
-
error: friendlyError(event.error),
|
|
7972
|
-
...event.code ? { code: event.code } : {}
|
|
7973
|
-
});
|
|
7974
|
-
return;
|
|
8187
|
+
}
|
|
8188
|
+
} catch (err) {
|
|
8189
|
+
if (signal?.aborted) {
|
|
8190
|
+
} else {
|
|
8191
|
+
throw err;
|
|
7975
8192
|
}
|
|
7976
8193
|
}
|
|
7977
|
-
} catch (err) {
|
|
7978
8194
|
if (signal?.aborted) {
|
|
7979
|
-
|
|
7980
|
-
|
|
8195
|
+
statusWatcher.stop();
|
|
8196
|
+
if (contentBlocks.length > 0) {
|
|
8197
|
+
contentBlocks.push({
|
|
8198
|
+
type: "text",
|
|
8199
|
+
text: "\n\n(cancelled)",
|
|
8200
|
+
startedAt: Date.now()
|
|
8201
|
+
});
|
|
8202
|
+
state.messages.push({
|
|
8203
|
+
role: "assistant",
|
|
8204
|
+
content: [...contentBlocks].sort(
|
|
8205
|
+
(a, b) => a.startedAt - b.startedAt
|
|
8206
|
+
),
|
|
8207
|
+
usage: {
|
|
8208
|
+
inputTokens: turnInputTokens,
|
|
8209
|
+
outputTokens: turnOutputTokens,
|
|
8210
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8211
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8212
|
+
llmCalls: turnLlmCalls
|
|
8213
|
+
},
|
|
8214
|
+
...turnProviderMetadata && {
|
|
8215
|
+
providerMetadata: turnProviderMetadata
|
|
8216
|
+
},
|
|
8217
|
+
model: turnModelId ?? parentModel,
|
|
8218
|
+
...modelOverride && { modelOverride }
|
|
8219
|
+
});
|
|
8220
|
+
}
|
|
8221
|
+
onEvent({ type: "turn_cancelled" });
|
|
8222
|
+
saveSession(state);
|
|
8223
|
+
return;
|
|
7981
8224
|
}
|
|
7982
|
-
}
|
|
7983
|
-
if (signal?.aborted) {
|
|
7984
|
-
statusWatcher.stop();
|
|
7985
8225
|
if (contentBlocks.length > 0) {
|
|
7986
|
-
contentBlocks.push({
|
|
7987
|
-
type: "text",
|
|
7988
|
-
text: "\n\n(cancelled)",
|
|
7989
|
-
startedAt: Date.now()
|
|
7990
|
-
});
|
|
7991
8226
|
state.messages.push({
|
|
7992
8227
|
role: "assistant",
|
|
7993
8228
|
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
@@ -8005,229 +8240,212 @@ async function runTurn(params) {
|
|
|
8005
8240
|
...modelOverride && { modelOverride }
|
|
8006
8241
|
});
|
|
8007
8242
|
}
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
});
|
|
8027
|
-
}
|
|
8028
|
-
const toolCalls = getToolCalls(contentBlocks);
|
|
8029
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8030
|
-
statusWatcher.stop();
|
|
8031
|
-
saveSession(state);
|
|
8032
|
-
onEvent({
|
|
8033
|
-
type: "turn_done",
|
|
8034
|
-
stats: {
|
|
8035
|
-
inputTokens: turnInputTokens,
|
|
8036
|
-
outputTokens: turnOutputTokens,
|
|
8037
|
-
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8038
|
-
cacheReadTokens: turnCacheRead || void 0,
|
|
8039
|
-
llmCalls: turnLlmCalls,
|
|
8040
|
-
lastCallInputTokens,
|
|
8041
|
-
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8042
|
-
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8043
|
-
}
|
|
8044
|
-
});
|
|
8045
|
-
return;
|
|
8046
|
-
}
|
|
8047
|
-
log14.info("Tools executing", {
|
|
8048
|
-
requestId,
|
|
8049
|
-
count: toolCalls.length,
|
|
8050
|
-
tools: toolCalls.map((tc) => tc.name)
|
|
8051
|
-
});
|
|
8052
|
-
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8053
|
-
const wrappedOnEvent = (e) => {
|
|
8054
|
-
if ("parentToolId" in e && e.parentToolId) {
|
|
8055
|
-
if (e.type === "text") {
|
|
8056
|
-
subAgentText = e.text;
|
|
8057
|
-
} else if (e.type === "tool_start") {
|
|
8058
|
-
subAgentText = `Using ${e.name}`;
|
|
8059
|
-
}
|
|
8243
|
+
const toolCalls = getToolCalls(contentBlocks);
|
|
8244
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8245
|
+
statusWatcher.stop();
|
|
8246
|
+
saveSession(state);
|
|
8247
|
+
onEvent({
|
|
8248
|
+
type: "turn_done",
|
|
8249
|
+
stats: {
|
|
8250
|
+
inputTokens: turnInputTokens,
|
|
8251
|
+
outputTokens: turnOutputTokens,
|
|
8252
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8253
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8254
|
+
llmCalls: turnLlmCalls,
|
|
8255
|
+
lastCallInputTokens,
|
|
8256
|
+
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8257
|
+
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8258
|
+
}
|
|
8259
|
+
});
|
|
8260
|
+
return;
|
|
8060
8261
|
}
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8262
|
+
log14.info("Tools executing", {
|
|
8263
|
+
requestId,
|
|
8264
|
+
count: toolCalls.length,
|
|
8265
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
8266
|
+
});
|
|
8267
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8268
|
+
const wrappedOnEvent = (e) => {
|
|
8269
|
+
if ("parentToolId" in e && e.parentToolId) {
|
|
8270
|
+
if (e.type === "text") {
|
|
8271
|
+
subAgentText = e.text;
|
|
8272
|
+
} else if (e.type === "tool_start") {
|
|
8273
|
+
subAgentText = `Using ${e.name}`;
|
|
8274
|
+
}
|
|
8068
8275
|
}
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
const safeSettle = (result, isError) => {
|
|
8077
|
-
if (settled) {
|
|
8078
|
-
return;
|
|
8276
|
+
onEvent(e);
|
|
8277
|
+
};
|
|
8278
|
+
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
8279
|
+
const results = await Promise.all(
|
|
8280
|
+
toolCalls.map(async (tc) => {
|
|
8281
|
+
if (signal?.aborted) {
|
|
8282
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
8079
8283
|
}
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
toolAbort
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8284
|
+
const toolStart = Date.now();
|
|
8285
|
+
let settle;
|
|
8286
|
+
const resultPromise = new Promise((res) => {
|
|
8287
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
8288
|
+
});
|
|
8289
|
+
let toolAbort = new AbortController();
|
|
8290
|
+
let settled = false;
|
|
8291
|
+
const safeSettle = (result, isError) => {
|
|
8292
|
+
if (settled) {
|
|
8293
|
+
return;
|
|
8294
|
+
}
|
|
8295
|
+
settled = true;
|
|
8296
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
8297
|
+
settle(result, isError);
|
|
8298
|
+
};
|
|
8299
|
+
const cascadeAbort = () => {
|
|
8300
|
+
toolAbort.abort();
|
|
8301
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
8302
|
+
};
|
|
8303
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
8304
|
+
const run = async (input) => {
|
|
8305
|
+
try {
|
|
8306
|
+
let result;
|
|
8307
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8308
|
+
saveSession(state);
|
|
8309
|
+
log14.info("Waiting for external tool result", {
|
|
8310
|
+
requestId,
|
|
8311
|
+
toolCallId: tc.id,
|
|
8312
|
+
name: tc.name
|
|
8313
|
+
});
|
|
8314
|
+
const blocksUser = USER_BLOCKING_EXTERNAL_TOOLS.has(tc.name);
|
|
8106
8315
|
if (blocksUser) {
|
|
8107
|
-
statusWatcher.
|
|
8316
|
+
statusWatcher.pause();
|
|
8108
8317
|
}
|
|
8318
|
+
try {
|
|
8319
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
8320
|
+
} finally {
|
|
8321
|
+
if (blocksUser) {
|
|
8322
|
+
statusWatcher.resume();
|
|
8323
|
+
}
|
|
8324
|
+
}
|
|
8325
|
+
} else {
|
|
8326
|
+
result = await executeTool(tc.name, input, {
|
|
8327
|
+
apiConfig,
|
|
8328
|
+
model,
|
|
8329
|
+
models: state.models,
|
|
8330
|
+
signal: toolAbort.signal,
|
|
8331
|
+
onEvent: wrappedOnEvent,
|
|
8332
|
+
resolveExternalTool,
|
|
8333
|
+
toolCallId: tc.id,
|
|
8334
|
+
requestId,
|
|
8335
|
+
onboardingState,
|
|
8336
|
+
subAgentMessages,
|
|
8337
|
+
conversationMessages: state.messages,
|
|
8338
|
+
toolRegistry,
|
|
8339
|
+
onBackgroundComplete,
|
|
8340
|
+
onLog: (line) => wrappedOnEvent({
|
|
8341
|
+
type: "tool_input_delta",
|
|
8342
|
+
id: tc.id,
|
|
8343
|
+
name: tc.name,
|
|
8344
|
+
result: line
|
|
8345
|
+
})
|
|
8346
|
+
});
|
|
8109
8347
|
}
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
id: tc.id,
|
|
8128
|
-
name: tc.name,
|
|
8129
|
-
result: line
|
|
8130
|
-
})
|
|
8348
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
8349
|
+
} catch (err) {
|
|
8350
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
8351
|
+
}
|
|
8352
|
+
};
|
|
8353
|
+
const entry = {
|
|
8354
|
+
id: tc.id,
|
|
8355
|
+
name: tc.name,
|
|
8356
|
+
input: tc.input,
|
|
8357
|
+
abortController: toolAbort,
|
|
8358
|
+
startedAt: toolStart,
|
|
8359
|
+
settle: safeSettle,
|
|
8360
|
+
rerun: (newInput) => {
|
|
8361
|
+
settled = false;
|
|
8362
|
+
toolAbort = new AbortController();
|
|
8363
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
8364
|
+
once: true
|
|
8131
8365
|
});
|
|
8366
|
+
entry.abortController = toolAbort;
|
|
8367
|
+
entry.input = newInput;
|
|
8368
|
+
run(newInput);
|
|
8132
8369
|
}
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8370
|
+
};
|
|
8371
|
+
toolRegistry?.register(entry);
|
|
8372
|
+
run(tc.input);
|
|
8373
|
+
const r = await resultPromise;
|
|
8374
|
+
if (!isBackgroundCall(tc)) {
|
|
8375
|
+
toolRegistry?.unregister(tc.id);
|
|
8136
8376
|
}
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8377
|
+
log14.info("Tool completed", {
|
|
8378
|
+
requestId,
|
|
8379
|
+
toolCallId: tc.id,
|
|
8380
|
+
name: tc.name,
|
|
8381
|
+
durationMs: Date.now() - toolStart,
|
|
8382
|
+
isError: r.isError
|
|
8383
|
+
});
|
|
8384
|
+
onEvent({
|
|
8385
|
+
type: "tool_done",
|
|
8386
|
+
id: tc.id,
|
|
8387
|
+
name: tc.name,
|
|
8388
|
+
result: r.result,
|
|
8389
|
+
isError: r.isError
|
|
8390
|
+
});
|
|
8391
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8392
|
+
triggerBrandExtraction(
|
|
8393
|
+
apiConfig,
|
|
8394
|
+
resolveModel("brandExtractor", state.models, model)
|
|
8395
|
+
);
|
|
8154
8396
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
run(tc.input);
|
|
8158
|
-
const r = await resultPromise;
|
|
8159
|
-
if (!isBackgroundCall(tc)) {
|
|
8160
|
-
toolRegistry?.unregister(tc.id);
|
|
8161
|
-
}
|
|
8162
|
-
log14.info("Tool completed", {
|
|
8163
|
-
requestId,
|
|
8164
|
-
toolCallId: tc.id,
|
|
8165
|
-
name: tc.name,
|
|
8166
|
-
durationMs: Date.now() - toolStart,
|
|
8167
|
-
isError: r.isError
|
|
8168
|
-
});
|
|
8169
|
-
onEvent({
|
|
8170
|
-
type: "tool_done",
|
|
8171
|
-
id: tc.id,
|
|
8172
|
-
name: tc.name,
|
|
8173
|
-
result: r.result,
|
|
8174
|
-
isError: r.isError
|
|
8175
|
-
});
|
|
8176
|
-
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8177
|
-
triggerBrandExtraction(
|
|
8178
|
-
apiConfig,
|
|
8179
|
-
resolveModel("brandExtractor", state.models, model)
|
|
8180
|
-
);
|
|
8181
|
-
}
|
|
8182
|
-
return r;
|
|
8183
|
-
})
|
|
8184
|
-
);
|
|
8185
|
-
statusWatcher.stop();
|
|
8186
|
-
for (const r of results) {
|
|
8187
|
-
const block = contentBlocks.find(
|
|
8188
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
8397
|
+
return r;
|
|
8398
|
+
})
|
|
8189
8399
|
);
|
|
8190
|
-
|
|
8191
|
-
block
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
block.
|
|
8400
|
+
for (const r of results) {
|
|
8401
|
+
const block = contentBlocks.find(
|
|
8402
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
8403
|
+
);
|
|
8404
|
+
if (block?.type === "tool") {
|
|
8405
|
+
block.result = r.result;
|
|
8406
|
+
block.isError = r.isError;
|
|
8407
|
+
block.completedAt = Date.now();
|
|
8408
|
+
const msgs = subAgentMessages.get(r.id);
|
|
8409
|
+
if (msgs) {
|
|
8410
|
+
block.subAgentMessages = msgs;
|
|
8411
|
+
}
|
|
8197
8412
|
}
|
|
8198
8413
|
}
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8202
|
-
);
|
|
8203
|
-
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8204
|
-
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8205
|
-
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8206
|
-
for (const r of results) {
|
|
8207
|
-
state.messages.push({
|
|
8208
|
-
role: "user",
|
|
8209
|
-
content: r.result,
|
|
8210
|
-
toolCallId: r.id,
|
|
8211
|
-
isToolError: r.isError
|
|
8212
|
-
});
|
|
8213
|
-
}
|
|
8214
|
-
if (takeSteering && !signal?.aborted) {
|
|
8215
|
-
const injected = (await takeSteering()).filter(
|
|
8216
|
-
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8414
|
+
const lastNonExcluded = toolCalls.filter(
|
|
8415
|
+
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8217
8416
|
);
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8417
|
+
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8418
|
+
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8419
|
+
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8420
|
+
for (const r of results) {
|
|
8421
|
+
state.messages.push({
|
|
8422
|
+
role: "user",
|
|
8423
|
+
content: r.result,
|
|
8424
|
+
toolCallId: r.id,
|
|
8425
|
+
isToolError: r.isError
|
|
8426
|
+
});
|
|
8427
|
+
}
|
|
8428
|
+
if (takeSteering && !signal?.aborted) {
|
|
8429
|
+
const injected = (await takeSteering()).filter(
|
|
8430
|
+
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8431
|
+
);
|
|
8432
|
+
if (injected.length > 0) {
|
|
8433
|
+
for (const entry of injected) {
|
|
8434
|
+
appendEntry(entry);
|
|
8435
|
+
keptEntries.push(entry);
|
|
8436
|
+
}
|
|
8437
|
+
saveSession(state);
|
|
8222
8438
|
}
|
|
8439
|
+
}
|
|
8440
|
+
if (signal?.aborted) {
|
|
8441
|
+
statusWatcher.stop();
|
|
8442
|
+
onEvent({ type: "turn_cancelled" });
|
|
8223
8443
|
saveSession(state);
|
|
8444
|
+
return;
|
|
8224
8445
|
}
|
|
8225
8446
|
}
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
saveSession(state);
|
|
8229
|
-
return;
|
|
8230
|
-
}
|
|
8447
|
+
} finally {
|
|
8448
|
+
statusWatcher.stop();
|
|
8231
8449
|
}
|
|
8232
8450
|
}
|
|
8233
8451
|
|
|
@@ -8511,62 +8729,6 @@ var MessageQueue = class {
|
|
|
8511
8729
|
}
|
|
8512
8730
|
};
|
|
8513
8731
|
|
|
8514
|
-
// src/automatedActions/resolve.ts
|
|
8515
|
-
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
8516
|
-
function resolveAction(text) {
|
|
8517
|
-
const parsed = parseSentinel(text);
|
|
8518
|
-
if (!parsed) {
|
|
8519
|
-
return null;
|
|
8520
|
-
}
|
|
8521
|
-
const { name: triggerName, remainder } = parsed;
|
|
8522
|
-
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
8523
|
-
return null;
|
|
8524
|
-
}
|
|
8525
|
-
let params = {};
|
|
8526
|
-
if (remainder) {
|
|
8527
|
-
try {
|
|
8528
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
8529
|
-
} catch {
|
|
8530
|
-
}
|
|
8531
|
-
}
|
|
8532
|
-
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
8533
|
-
let next;
|
|
8534
|
-
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
8535
|
-
if (fmMatch) {
|
|
8536
|
-
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
8537
|
-
if (nextMatch) {
|
|
8538
|
-
next = nextMatch[1];
|
|
8539
|
-
}
|
|
8540
|
-
}
|
|
8541
|
-
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
8542
|
-
for (const [key, value] of Object.entries(params)) {
|
|
8543
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
8544
|
-
body = body.replaceAll(`{{${key}}}`, str);
|
|
8545
|
-
}
|
|
8546
|
-
return {
|
|
8547
|
-
message: automatedMessage(triggerName, body),
|
|
8548
|
-
next
|
|
8549
|
-
};
|
|
8550
|
-
}
|
|
8551
|
-
function getActionChain(startName) {
|
|
8552
|
-
const chain = [];
|
|
8553
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8554
|
-
let name = startName;
|
|
8555
|
-
while (name && !seen.has(name)) {
|
|
8556
|
-
seen.add(name);
|
|
8557
|
-
chain.push(name);
|
|
8558
|
-
let body;
|
|
8559
|
-
try {
|
|
8560
|
-
body = readAsset("automatedActions", `${name}.md`);
|
|
8561
|
-
} catch {
|
|
8562
|
-
break;
|
|
8563
|
-
}
|
|
8564
|
-
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
8565
|
-
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
8566
|
-
}
|
|
8567
|
-
return chain;
|
|
8568
|
-
}
|
|
8569
|
-
|
|
8570
8732
|
// src/headless/index.ts
|
|
8571
8733
|
var log16 = createLogger("headless");
|
|
8572
8734
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
@@ -8622,6 +8784,13 @@ var HeadlessSession = class {
|
|
|
8622
8784
|
earlyResults = /* @__PURE__ */ new Map();
|
|
8623
8785
|
// Tool block updates from background completions (separate from the message queue)
|
|
8624
8786
|
pendingBlockUpdates = [];
|
|
8787
|
+
/**
|
|
8788
|
+
* Id of the UI-only tool block synthesized for the in-flight user/gate
|
|
8789
|
+
* compaction (see the compaction listener). Model-invoked compactions have
|
|
8790
|
+
* a real block and never set this. Single slot — triggerCompaction is
|
|
8791
|
+
* single-flight.
|
|
8792
|
+
*/
|
|
8793
|
+
syntheticCompactionId = null;
|
|
8625
8794
|
// Tool lifecycle management — shared across all nesting depths
|
|
8626
8795
|
toolRegistry = new ToolRegistry();
|
|
8627
8796
|
// IO — accumulates stdin bytes between newline boundaries
|
|
@@ -8653,6 +8822,7 @@ var HeadlessSession = class {
|
|
|
8653
8822
|
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
8654
8823
|
});
|
|
8655
8824
|
this.passivePen = loadPassiveResults();
|
|
8825
|
+
this.persistStats();
|
|
8656
8826
|
if (resumed) {
|
|
8657
8827
|
this.emit("session_restored", {
|
|
8658
8828
|
messageCount: this.state.messages.length,
|
|
@@ -8675,15 +8845,61 @@ var HeadlessSession = class {
|
|
|
8675
8845
|
);
|
|
8676
8846
|
this.sessionStats.compactionInProgress = true;
|
|
8677
8847
|
this.persistStats();
|
|
8848
|
+
if (!event.toolCallId && (event.origin === "user" || event.origin === "gate")) {
|
|
8849
|
+
const id = `compact_${Date.now()}`;
|
|
8850
|
+
this.syntheticCompactionId = id;
|
|
8851
|
+
this.state.messages.push({
|
|
8852
|
+
role: "assistant",
|
|
8853
|
+
content: [
|
|
8854
|
+
{
|
|
8855
|
+
type: "tool",
|
|
8856
|
+
id,
|
|
8857
|
+
name: "compactConversation",
|
|
8858
|
+
input: {},
|
|
8859
|
+
startedAt: Date.now(),
|
|
8860
|
+
background: true,
|
|
8861
|
+
uiOnly: true,
|
|
8862
|
+
result: "Compaction started in the background."
|
|
8863
|
+
}
|
|
8864
|
+
]
|
|
8865
|
+
});
|
|
8866
|
+
saveSession(this.state);
|
|
8867
|
+
this.onEvent({
|
|
8868
|
+
type: "tool_start",
|
|
8869
|
+
id,
|
|
8870
|
+
name: "compactConversation",
|
|
8871
|
+
input: {},
|
|
8872
|
+
background: true
|
|
8873
|
+
});
|
|
8874
|
+
this.onEvent({
|
|
8875
|
+
type: "tool_done",
|
|
8876
|
+
id,
|
|
8877
|
+
name: "compactConversation",
|
|
8878
|
+
result: "Compaction started in the background.",
|
|
8879
|
+
isError: false
|
|
8880
|
+
});
|
|
8881
|
+
}
|
|
8678
8882
|
} else {
|
|
8679
8883
|
const data = event.error ? { error: event.error } : {};
|
|
8680
8884
|
this.emit("compaction_complete", data, event.requestId);
|
|
8885
|
+
if (this.syntheticCompactionId) {
|
|
8886
|
+
const id = this.syntheticCompactionId;
|
|
8887
|
+
this.syntheticCompactionId = null;
|
|
8888
|
+
this.onBackgroundComplete(
|
|
8889
|
+
id,
|
|
8890
|
+
"compactConversation",
|
|
8891
|
+
event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
|
|
8892
|
+
);
|
|
8893
|
+
}
|
|
8681
8894
|
this.sessionStats.compactionInProgress = false;
|
|
8682
8895
|
if (!event.error) {
|
|
8683
8896
|
this.sessionStats.lastContextSize = 0;
|
|
8684
8897
|
}
|
|
8685
8898
|
this.sessionStats.messageCount = this.state.messages.length;
|
|
8686
8899
|
this.persistStats();
|
|
8900
|
+
if (!this.running) {
|
|
8901
|
+
this.kickDrain();
|
|
8902
|
+
}
|
|
8687
8903
|
}
|
|
8688
8904
|
});
|
|
8689
8905
|
process.stdin.setEncoding("utf-8");
|
|
@@ -8822,7 +9038,8 @@ var HeadlessSession = class {
|
|
|
8822
9038
|
await triggerCompaction(this.state, this.config, {
|
|
8823
9039
|
blocking: true,
|
|
8824
9040
|
requestId,
|
|
8825
|
-
model: this.opts.model
|
|
9041
|
+
model: this.opts.model,
|
|
9042
|
+
origin: "gate"
|
|
8826
9043
|
});
|
|
8827
9044
|
applyPendingSummaries(this.state);
|
|
8828
9045
|
} catch {
|
|
@@ -8851,6 +9068,12 @@ var HeadlessSession = class {
|
|
|
8851
9068
|
}
|
|
8852
9069
|
return;
|
|
8853
9070
|
}
|
|
9071
|
+
if (notify === "silent") {
|
|
9072
|
+
if (!this.running) {
|
|
9073
|
+
this.applyPendingBlockUpdates();
|
|
9074
|
+
}
|
|
9075
|
+
return;
|
|
9076
|
+
}
|
|
8854
9077
|
this.queue.push({
|
|
8855
9078
|
command: {
|
|
8856
9079
|
action: "message",
|
|
@@ -9232,6 +9455,12 @@ var HeadlessSession = class {
|
|
|
9232
9455
|
this.completedEmitted = false;
|
|
9233
9456
|
this.lastCompleted = null;
|
|
9234
9457
|
this.turnStart = Date.now();
|
|
9458
|
+
const inflight2 = getInflightCompaction();
|
|
9459
|
+
if (inflight2) {
|
|
9460
|
+
await inflight2.catch(() => {
|
|
9461
|
+
});
|
|
9462
|
+
}
|
|
9463
|
+
applyPendingSummaries(this.state);
|
|
9235
9464
|
if (this.passivePen.length > 0) {
|
|
9236
9465
|
const swept = this.passivePen.splice(0);
|
|
9237
9466
|
this.persistStats();
|
|
@@ -9321,7 +9550,7 @@ var HeadlessSession = class {
|
|
|
9321
9550
|
this.applyPendingBlockUpdates();
|
|
9322
9551
|
}
|
|
9323
9552
|
async handleMessage(parsed, requestId) {
|
|
9324
|
-
if (this.running) {
|
|
9553
|
+
if (this.running || getInflightCompaction()) {
|
|
9325
9554
|
const command = { ...parsed };
|
|
9326
9555
|
if (requestId && command.requestId === void 0) {
|
|
9327
9556
|
command.requestId = requestId;
|
|
@@ -9331,6 +9560,9 @@ var HeadlessSession = class {
|
|
|
9331
9560
|
source: "user",
|
|
9332
9561
|
enqueuedAt: Date.now()
|
|
9333
9562
|
});
|
|
9563
|
+
if (!this.running && !getInflightCompaction()) {
|
|
9564
|
+
this.kickDrain();
|
|
9565
|
+
}
|
|
9334
9566
|
return;
|
|
9335
9567
|
}
|
|
9336
9568
|
this.running = true;
|
|
@@ -9338,6 +9570,7 @@ var HeadlessSession = class {
|
|
|
9338
9570
|
await this.runSingleTurn(parsed, requestId);
|
|
9339
9571
|
await this.drainQueueLoop();
|
|
9340
9572
|
} finally {
|
|
9573
|
+
this.currentAbort?.abort();
|
|
9341
9574
|
this.currentAbort = null;
|
|
9342
9575
|
this.currentRequestId = void 0;
|
|
9343
9576
|
this.running = false;
|
|
@@ -9369,6 +9602,18 @@ var HeadlessSession = class {
|
|
|
9369
9602
|
async drainQueueLoop() {
|
|
9370
9603
|
while (this.queue.length > 0) {
|
|
9371
9604
|
const head = this.queue.peek();
|
|
9605
|
+
if (head.command.action === "compact") {
|
|
9606
|
+
this.queue.shift();
|
|
9607
|
+
await triggerCompaction(this.state, this.config, {
|
|
9608
|
+
blocking: true,
|
|
9609
|
+
requestId: head.command.requestId,
|
|
9610
|
+
model: this.opts.model,
|
|
9611
|
+
origin: "user"
|
|
9612
|
+
}).catch(() => {
|
|
9613
|
+
});
|
|
9614
|
+
applyPendingSummaries(this.state);
|
|
9615
|
+
continue;
|
|
9616
|
+
}
|
|
9372
9617
|
if (head.source === "chain") {
|
|
9373
9618
|
const item = this.queue.shift();
|
|
9374
9619
|
const rid = item.command.requestId ?? `chain-${Date.now()}`;
|
|
@@ -9413,6 +9658,7 @@ var HeadlessSession = class {
|
|
|
9413
9658
|
try {
|
|
9414
9659
|
await this.drainQueueLoop();
|
|
9415
9660
|
} finally {
|
|
9661
|
+
this.currentAbort?.abort();
|
|
9416
9662
|
this.currentAbort = null;
|
|
9417
9663
|
this.currentRequestId = void 0;
|
|
9418
9664
|
this.running = false;
|
|
@@ -9659,11 +9905,31 @@ var HeadlessSession = class {
|
|
|
9659
9905
|
return;
|
|
9660
9906
|
}
|
|
9661
9907
|
if (action === "compact") {
|
|
9908
|
+
if (this.running) {
|
|
9909
|
+
const alreadyQueued = this.queue.snapshot().some((it) => it.command.action === "compact");
|
|
9910
|
+
if (!alreadyQueued) {
|
|
9911
|
+
this.queue.push({
|
|
9912
|
+
command: {
|
|
9913
|
+
action: "compact",
|
|
9914
|
+
// Display label for the queue card (matches the automated
|
|
9915
|
+
// action idiom); never resolved as an action — drainQueueLoop
|
|
9916
|
+
// routes on command.action before any sentinel handling.
|
|
9917
|
+
text: sentinel("compact"),
|
|
9918
|
+
requestId
|
|
9919
|
+
},
|
|
9920
|
+
source: "user",
|
|
9921
|
+
enqueuedAt: Date.now()
|
|
9922
|
+
});
|
|
9923
|
+
}
|
|
9924
|
+
this.emit("completed", { success: true, queued: true }, requestId);
|
|
9925
|
+
return;
|
|
9926
|
+
}
|
|
9662
9927
|
try {
|
|
9663
9928
|
await triggerCompaction(this.state, this.config, {
|
|
9664
9929
|
blocking: false,
|
|
9665
9930
|
requestId,
|
|
9666
|
-
model: this.opts.model
|
|
9931
|
+
model: this.opts.model,
|
|
9932
|
+
origin: "user"
|
|
9667
9933
|
});
|
|
9668
9934
|
if (!this.running) {
|
|
9669
9935
|
applyPendingSummaries(this.state);
|