@mindstudio-ai/remy 0.1.256 → 0.1.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +514 -315
- package/dist/index.js +517 -288
- package/dist/prompt/compiled/files.md +17 -3
- package/dist/prompt/compiled/interfaces.md +26 -472
- package/dist/prompt/compiled/methods.md +10 -15
- package/dist/prompt/compiled/scenarios.md +16 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -2
- package/dist/prompt/{compiled/agent-interfaces.md → skills/agentInterfaces.md} +118 -10
- package/dist/prompt/skills/dataSources.md +131 -0
- package/dist/prompt/skills/inboundEmail.md +116 -0
- package/dist/prompt/skills/mcpInterfaces.md +280 -0
- package/dist/prompt/skills/restApi.md +149 -0
- package/dist/prompt/skills/scheduledJobs.md +51 -0
- package/dist/prompt/{compiled/task-agents.md → skills/taskAgents.md} +10 -8
- package/dist/prompt/skills/webhooks.md +108 -0
- package/dist/prompt/static/authoring.md +1 -1
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +1 -1
- package/package.json +2 -2
- package/dist/prompt/.notes.md +0 -194
- package/dist/prompt/compiled/README.md +0 -100
- package/dist/prompt/compiled/mcp-interfaces.md +0 -34
- package/dist/prompt/compiled/media-cdn.md +0 -51
- package/dist/prompt/sources/llms.txt +0 -1618
- package/dist/subagents/.notes-background-agents.md +0 -64
- package/dist/subagents/codeSanityCheck/.notes.md +0 -44
- package/dist/subagents/designExpert/.notes.md +0 -265
- package/dist/subagents/productVision/.notes.md +0 -79
package/dist/headless.js
CHANGED
|
@@ -285,7 +285,7 @@ function isRetryableError(error) {
|
|
|
285
285
|
return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error);
|
|
286
286
|
}
|
|
287
287
|
function sleep(ms) {
|
|
288
|
-
return new Promise((
|
|
288
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
289
289
|
}
|
|
290
290
|
async function* streamChatWithRetry(params, options) {
|
|
291
291
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
@@ -591,20 +591,7 @@ function renderOrgContextBlock() {
|
|
|
591
591
|
// src/assets.ts
|
|
592
592
|
import fs3 from "fs";
|
|
593
593
|
import path2 from "path";
|
|
594
|
-
var
|
|
595
|
-
import.meta.dirname ?? path2.dirname(new URL(import.meta.url).pathname)
|
|
596
|
-
);
|
|
597
|
-
function findRoot(start) {
|
|
598
|
-
let dir = start;
|
|
599
|
-
while (dir !== path2.dirname(dir)) {
|
|
600
|
-
if (fs3.existsSync(path2.join(dir, "package.json"))) {
|
|
601
|
-
return dir;
|
|
602
|
-
}
|
|
603
|
-
dir = path2.dirname(dir);
|
|
604
|
-
}
|
|
605
|
-
return start;
|
|
606
|
-
}
|
|
607
|
-
var ASSETS_BASE = fs3.existsSync(path2.join(ROOT, "dist", "prompt")) ? path2.join(ROOT, "dist") : path2.join(ROOT, "src");
|
|
594
|
+
var ASSETS_BASE = import.meta.dirname ?? path2.dirname(new URL(import.meta.url).pathname);
|
|
608
595
|
function assetPath(...segments) {
|
|
609
596
|
return path2.join(ASSETS_BASE, ...segments);
|
|
610
597
|
}
|
|
@@ -762,6 +749,81 @@ ${listing}
|
|
|
762
749
|
}
|
|
763
750
|
}
|
|
764
751
|
|
|
752
|
+
// src/prompt/skills/catalog.ts
|
|
753
|
+
import fs5 from "fs";
|
|
754
|
+
import path4 from "path";
|
|
755
|
+
var SKILLS_DIR = assetPath("prompt", "skills");
|
|
756
|
+
function parseFrontmatter2(content) {
|
|
757
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
758
|
+
if (!match) {
|
|
759
|
+
return {};
|
|
760
|
+
}
|
|
761
|
+
const fields = {};
|
|
762
|
+
for (const line of match[1].split("\n")) {
|
|
763
|
+
const sep = line.indexOf(":");
|
|
764
|
+
if (sep > 0) {
|
|
765
|
+
fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return fields;
|
|
769
|
+
}
|
|
770
|
+
function loadCatalog() {
|
|
771
|
+
let files;
|
|
772
|
+
try {
|
|
773
|
+
files = fs5.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
|
|
774
|
+
} catch {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
const skills = [];
|
|
778
|
+
for (const file of files.sort()) {
|
|
779
|
+
const full = path4.join(SKILLS_DIR, file);
|
|
780
|
+
const id = file.replace(/\.md$/, "");
|
|
781
|
+
const fields = parseFrontmatter2(fs5.readFileSync(full, "utf-8"));
|
|
782
|
+
if (!fields.name || !fields.what || !fields.when) {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
skills.push({
|
|
786
|
+
id,
|
|
787
|
+
name: fields.name,
|
|
788
|
+
what: fields.what,
|
|
789
|
+
when: fields.when,
|
|
790
|
+
path: full
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return skills;
|
|
794
|
+
}
|
|
795
|
+
var SKILLS = loadCatalog();
|
|
796
|
+
var SKILL_IDS = SKILLS.map((s) => s.id);
|
|
797
|
+
function getSkill(id) {
|
|
798
|
+
return SKILLS.find((s) => s.id === id);
|
|
799
|
+
}
|
|
800
|
+
function readSkillBody(skill) {
|
|
801
|
+
return fs5.readFileSync(skill.path, "utf-8").replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
802
|
+
}
|
|
803
|
+
function loadSkillsCatalog() {
|
|
804
|
+
if (SKILLS.length === 0) {
|
|
805
|
+
return "";
|
|
806
|
+
}
|
|
807
|
+
const entries = SKILLS.map(
|
|
808
|
+
(s) => [
|
|
809
|
+
`### ${s.name} (\`${s.id}\`)`,
|
|
810
|
+
s.what,
|
|
811
|
+
"",
|
|
812
|
+
`When to load: ${s.when}`,
|
|
813
|
+
`Reference: ${s.path}`
|
|
814
|
+
].join("\n")
|
|
815
|
+
);
|
|
816
|
+
return `<available_skills>
|
|
817
|
+
Platform capabilities most apps don't use, so their references are kept out of this prompt rather than competing for your attention on every task \u2014 not because they're marginal.
|
|
818
|
+
|
|
819
|
+
Read what follows as part of what the platform can do, not as a lookup table. Recognising that one of these fits a feature is your job, and proposing one is fair game \u2014 several of them are the difference between an app that works and an app worth showing off. When a trigger fires, load the reference with loadSkill before writing the code rather than after. Loading is cheap and expected; guessing at one of these APIs is not.
|
|
820
|
+
|
|
821
|
+
A loaded reference drops out of the conversation once it ages out. Re-read it at the path listed with readFile whenever you need it again.
|
|
822
|
+
|
|
823
|
+
${entries.join("\n\n")}
|
|
824
|
+
</available_skills>`;
|
|
825
|
+
}
|
|
826
|
+
|
|
765
827
|
// src/prompt/index.ts
|
|
766
828
|
function resolveIncludes(template) {
|
|
767
829
|
const result = template.replace(
|
|
@@ -816,18 +878,6 @@ Current date: ${now}
|
|
|
816
878
|
{{compiled/design.md}}
|
|
817
879
|
</design>
|
|
818
880
|
|
|
819
|
-
<building_agent_interfaces>
|
|
820
|
-
{{compiled/agent-interfaces.md}}
|
|
821
|
-
</building_agent_interfaces>
|
|
822
|
-
|
|
823
|
-
<building_mcp_interfaces>
|
|
824
|
-
{{compiled/mcp-interfaces.md}}
|
|
825
|
-
</building_mcp_interfaces>
|
|
826
|
-
|
|
827
|
-
<media_cdn>
|
|
828
|
-
{{compiled/media-cdn.md}}
|
|
829
|
-
</media_cdn>
|
|
830
|
-
|
|
831
881
|
<app_files>
|
|
832
882
|
{{compiled/files.md}}
|
|
833
883
|
</app_files>
|
|
@@ -845,10 +895,10 @@ Current date: ${now}
|
|
|
845
895
|
</secrets>
|
|
846
896
|
</platform_docs>
|
|
847
897
|
|
|
898
|
+
${loadSkillsCatalog()}
|
|
899
|
+
|
|
848
900
|
<mindstudio_agent_sdk_docs>
|
|
849
901
|
{{compiled/sdk-actions.md}}
|
|
850
|
-
|
|
851
|
-
{{compiled/task-agents.md}}
|
|
852
902
|
</mindstudio_agent_sdk_docs>
|
|
853
903
|
|
|
854
904
|
<mindstudio_flavored_markdown_spec_docs>
|
|
@@ -963,7 +1013,7 @@ function mergeBackgroundResultsMessages(messages) {
|
|
|
963
1013
|
}
|
|
964
1014
|
|
|
965
1015
|
// src/tools/spec/readSpec.ts
|
|
966
|
-
import
|
|
1016
|
+
import fs6 from "fs/promises";
|
|
967
1017
|
|
|
968
1018
|
// src/tools/spec/_helpers.ts
|
|
969
1019
|
function validateSpecPath(filePath) {
|
|
@@ -988,8 +1038,8 @@ function extractFrontmatter(content) {
|
|
|
988
1038
|
// src/tools/spec/readSpec.ts
|
|
989
1039
|
var DEFAULT_MAX_LINES = 500;
|
|
990
1040
|
var readSpecTool = {
|
|
991
|
-
clearable: true,
|
|
992
1041
|
definition: {
|
|
1042
|
+
clearable: true,
|
|
993
1043
|
name: "readSpec",
|
|
994
1044
|
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).",
|
|
995
1045
|
inputSchema: {
|
|
@@ -1018,7 +1068,7 @@ var readSpecTool = {
|
|
|
1018
1068
|
return `Error: ${err.message}`;
|
|
1019
1069
|
}
|
|
1020
1070
|
try {
|
|
1021
|
-
const content = await
|
|
1071
|
+
const content = await fs6.readFile(input.path, "utf-8");
|
|
1022
1072
|
const allLines = content.split("\n");
|
|
1023
1073
|
const totalLines = allLines.length;
|
|
1024
1074
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES;
|
|
@@ -1046,8 +1096,8 @@ var readSpecTool = {
|
|
|
1046
1096
|
};
|
|
1047
1097
|
|
|
1048
1098
|
// src/tools/spec/writeSpec.ts
|
|
1049
|
-
import
|
|
1050
|
-
import
|
|
1099
|
+
import fs7 from "fs/promises";
|
|
1100
|
+
import path5 from "path";
|
|
1051
1101
|
|
|
1052
1102
|
// src/tools/_helpers/diff.ts
|
|
1053
1103
|
var CONTEXT_LINES = 3;
|
|
@@ -1102,8 +1152,8 @@ function acquireFileLock(filePath) {
|
|
|
1102
1152
|
|
|
1103
1153
|
// src/tools/spec/writeSpec.ts
|
|
1104
1154
|
var writeSpecTool = {
|
|
1105
|
-
clearable: true,
|
|
1106
1155
|
definition: {
|
|
1156
|
+
clearable: true,
|
|
1107
1157
|
name: "writeSpec",
|
|
1108
1158
|
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.",
|
|
1109
1159
|
inputSchema: {
|
|
@@ -1123,7 +1173,7 @@ var writeSpecTool = {
|
|
|
1123
1173
|
},
|
|
1124
1174
|
streaming: {
|
|
1125
1175
|
transform: async (partial) => {
|
|
1126
|
-
const oldContent = await
|
|
1176
|
+
const oldContent = await fs7.readFile(partial.path, "utf-8").catch(() => "");
|
|
1127
1177
|
const lineCount = partial.content.split("\n").length;
|
|
1128
1178
|
return `Writing ${partial.path} (${lineCount} lines)
|
|
1129
1179
|
${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
@@ -1137,13 +1187,13 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
1137
1187
|
}
|
|
1138
1188
|
const release = await acquireFileLock(input.path);
|
|
1139
1189
|
try {
|
|
1140
|
-
await
|
|
1190
|
+
await fs7.mkdir(path5.dirname(input.path), { recursive: true });
|
|
1141
1191
|
let oldContent = null;
|
|
1142
1192
|
try {
|
|
1143
|
-
oldContent = await
|
|
1193
|
+
oldContent = await fs7.readFile(input.path, "utf-8");
|
|
1144
1194
|
} catch {
|
|
1145
1195
|
}
|
|
1146
|
-
await
|
|
1196
|
+
await fs7.writeFile(input.path, input.content, "utf-8");
|
|
1147
1197
|
const lineCount = input.content.split("\n").length;
|
|
1148
1198
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
1149
1199
|
return `${label} ${input.path} (${lineCount} lines)
|
|
@@ -1157,7 +1207,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
1157
1207
|
};
|
|
1158
1208
|
|
|
1159
1209
|
// src/tools/spec/editSpec.ts
|
|
1160
|
-
import
|
|
1210
|
+
import fs8 from "fs/promises";
|
|
1161
1211
|
|
|
1162
1212
|
// src/tools/code/editFile/_helpers.ts
|
|
1163
1213
|
function buildLineOffsets(content) {
|
|
@@ -1240,8 +1290,8 @@ function formatOccurrenceError(count, lines, filePath) {
|
|
|
1240
1290
|
|
|
1241
1291
|
// src/tools/spec/editSpec.ts
|
|
1242
1292
|
var editSpecTool = {
|
|
1243
|
-
clearable: true,
|
|
1244
1293
|
definition: {
|
|
1294
|
+
clearable: true,
|
|
1245
1295
|
name: "editSpec",
|
|
1246
1296
|
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.",
|
|
1247
1297
|
inputSchema: {
|
|
@@ -1277,7 +1327,7 @@ var editSpecTool = {
|
|
|
1277
1327
|
try {
|
|
1278
1328
|
let content;
|
|
1279
1329
|
try {
|
|
1280
|
-
content = await
|
|
1330
|
+
content = await fs8.readFile(input.path, "utf-8");
|
|
1281
1331
|
} catch (err) {
|
|
1282
1332
|
return `Error reading file: ${err.message}`;
|
|
1283
1333
|
}
|
|
@@ -1330,7 +1380,7 @@ var editSpecTool = {
|
|
|
1330
1380
|
return `Error: that edit would remove or malform the spec's YAML frontmatter (the leading \`--- \u2026 ---\` block, which holds required fields like \`name\`). Narrow old_string to the body content you meant to change and leave the frontmatter block intact.`;
|
|
1331
1381
|
}
|
|
1332
1382
|
try {
|
|
1333
|
-
await
|
|
1383
|
+
await fs8.writeFile(input.path, updated, "utf-8");
|
|
1334
1384
|
} catch (err) {
|
|
1335
1385
|
return `Error writing file: ${err.message}`;
|
|
1336
1386
|
}
|
|
@@ -1342,11 +1392,11 @@ var editSpecTool = {
|
|
|
1342
1392
|
};
|
|
1343
1393
|
|
|
1344
1394
|
// src/tools/spec/listSpecFiles.ts
|
|
1345
|
-
import
|
|
1346
|
-
import
|
|
1395
|
+
import fs9 from "fs/promises";
|
|
1396
|
+
import path6 from "path";
|
|
1347
1397
|
var listSpecFilesTool = {
|
|
1348
|
-
clearable: false,
|
|
1349
1398
|
definition: {
|
|
1399
|
+
clearable: false,
|
|
1350
1400
|
name: "listSpecFiles",
|
|
1351
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.",
|
|
1352
1402
|
inputSchema: {
|
|
@@ -1372,7 +1422,7 @@ var listSpecFilesTool = {
|
|
|
1372
1422
|
};
|
|
1373
1423
|
async function listRecursive(dir) {
|
|
1374
1424
|
const results = [];
|
|
1375
|
-
const entries = await
|
|
1425
|
+
const entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
1376
1426
|
entries.sort((a, b) => {
|
|
1377
1427
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
1378
1428
|
return -1;
|
|
@@ -1383,7 +1433,7 @@ async function listRecursive(dir) {
|
|
|
1383
1433
|
return a.name.localeCompare(b.name);
|
|
1384
1434
|
});
|
|
1385
1435
|
for (const entry of entries) {
|
|
1386
|
-
const fullPath =
|
|
1436
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1387
1437
|
if (entry.isDirectory()) {
|
|
1388
1438
|
results.push(`${fullPath}/`);
|
|
1389
1439
|
results.push(...await listRecursive(fullPath));
|
|
@@ -1396,8 +1446,8 @@ async function listRecursive(dir) {
|
|
|
1396
1446
|
|
|
1397
1447
|
// src/tools/spec/presentPublishPlan.ts
|
|
1398
1448
|
var presentPublishPlanTool = {
|
|
1399
|
-
clearable: false,
|
|
1400
1449
|
definition: {
|
|
1450
|
+
clearable: false,
|
|
1401
1451
|
name: "presentPublishPlan",
|
|
1402
1452
|
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.",
|
|
1403
1453
|
inputSchema: {
|
|
@@ -1418,11 +1468,11 @@ var presentPublishPlanTool = {
|
|
|
1418
1468
|
};
|
|
1419
1469
|
|
|
1420
1470
|
// src/tools/spec/writePlan.ts
|
|
1421
|
-
import
|
|
1471
|
+
import fs10 from "fs/promises";
|
|
1422
1472
|
var PLAN_FILE = ".remy-plan.md";
|
|
1423
1473
|
var writePlanTool = {
|
|
1424
|
-
clearable: false,
|
|
1425
1474
|
definition: {
|
|
1475
|
+
clearable: false,
|
|
1426
1476
|
name: "writePlan",
|
|
1427
1477
|
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.",
|
|
1428
1478
|
inputSchema: {
|
|
@@ -1443,17 +1493,17 @@ status: pending
|
|
|
1443
1493
|
---
|
|
1444
1494
|
|
|
1445
1495
|
${content}`;
|
|
1446
|
-
await
|
|
1496
|
+
await fs10.writeFile(PLAN_FILE, file, "utf-8");
|
|
1447
1497
|
return "Plan written to .remy-plan.md. Waiting for user approval.";
|
|
1448
1498
|
}
|
|
1449
1499
|
};
|
|
1450
1500
|
|
|
1451
1501
|
// src/tools/spec/updatePlanStatus.ts
|
|
1452
|
-
import
|
|
1502
|
+
import fs11 from "fs/promises";
|
|
1453
1503
|
var PLAN_FILE2 = ".remy-plan.md";
|
|
1454
1504
|
var updatePlanStatusTool = {
|
|
1455
|
-
clearable: false,
|
|
1456
1505
|
definition: {
|
|
1506
|
+
clearable: false,
|
|
1457
1507
|
name: "updatePlanStatus",
|
|
1458
1508
|
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.',
|
|
1459
1509
|
inputSchema: {
|
|
@@ -1475,15 +1525,15 @@ var updatePlanStatusTool = {
|
|
|
1475
1525
|
}
|
|
1476
1526
|
let content;
|
|
1477
1527
|
try {
|
|
1478
|
-
content = await
|
|
1528
|
+
content = await fs11.readFile(PLAN_FILE2, "utf-8");
|
|
1479
1529
|
} catch {
|
|
1480
1530
|
return "No plan file found.";
|
|
1481
1531
|
}
|
|
1482
1532
|
if (status === "rejected") {
|
|
1483
|
-
await
|
|
1533
|
+
await fs11.unlink(PLAN_FILE2);
|
|
1484
1534
|
return "Plan rejected and removed.";
|
|
1485
1535
|
}
|
|
1486
|
-
await
|
|
1536
|
+
await fs11.writeFile(
|
|
1487
1537
|
PLAN_FILE2,
|
|
1488
1538
|
content.replace(/^status:\s*\w+/m, `status: ${status}`),
|
|
1489
1539
|
"utf-8"
|
|
@@ -1494,8 +1544,8 @@ var updatePlanStatusTool = {
|
|
|
1494
1544
|
|
|
1495
1545
|
// src/tools/common/setProjectOnboardingState.ts
|
|
1496
1546
|
var setProjectOnboardingStateTool = {
|
|
1497
|
-
clearable: false,
|
|
1498
1547
|
definition: {
|
|
1548
|
+
clearable: false,
|
|
1499
1549
|
name: "setProjectOnboardingState",
|
|
1500
1550
|
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.",
|
|
1501
1551
|
inputSchema: {
|
|
@@ -1517,8 +1567,8 @@ var setProjectOnboardingStateTool = {
|
|
|
1517
1567
|
|
|
1518
1568
|
// src/tools/common/promptUser.ts
|
|
1519
1569
|
var promptUserTool = {
|
|
1520
|
-
clearable: false,
|
|
1521
1570
|
definition: {
|
|
1571
|
+
clearable: false,
|
|
1522
1572
|
name: "promptUser",
|
|
1523
1573
|
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.',
|
|
1524
1574
|
inputSchema: {
|
|
@@ -1649,8 +1699,8 @@ ${lines.join("\n")}`;
|
|
|
1649
1699
|
|
|
1650
1700
|
// src/tools/common/confirmDestructiveAction.ts
|
|
1651
1701
|
var confirmDestructiveActionTool = {
|
|
1652
|
-
clearable: false,
|
|
1653
1702
|
definition: {
|
|
1703
|
+
clearable: false,
|
|
1654
1704
|
name: "confirmDestructiveAction",
|
|
1655
1705
|
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.",
|
|
1656
1706
|
inputSchema: {
|
|
@@ -1689,7 +1739,7 @@ function formatCliResult(r) {
|
|
|
1689
1739
|
return logBlock + body + truncNote;
|
|
1690
1740
|
}
|
|
1691
1741
|
function runCli(command, args, options) {
|
|
1692
|
-
return new Promise((
|
|
1742
|
+
return new Promise((resolve3) => {
|
|
1693
1743
|
const timeout = options?.timeout ?? 6e4;
|
|
1694
1744
|
const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
|
|
1695
1745
|
let finalArgs = args;
|
|
@@ -1718,7 +1768,7 @@ function runCli(command, args, options) {
|
|
|
1718
1768
|
if (killTimer) {
|
|
1719
1769
|
clearTimeout(killTimer);
|
|
1720
1770
|
}
|
|
1721
|
-
|
|
1771
|
+
resolve3(result);
|
|
1722
1772
|
};
|
|
1723
1773
|
const child = spawn(command, finalArgs, {
|
|
1724
1774
|
stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -1823,8 +1873,8 @@ function runCli(command, args, options) {
|
|
|
1823
1873
|
|
|
1824
1874
|
// src/subagents/sdkConsultant/index.ts
|
|
1825
1875
|
var askMindStudioSdkTool = {
|
|
1826
|
-
clearable: false,
|
|
1827
1876
|
definition: {
|
|
1877
|
+
clearable: false,
|
|
1828
1878
|
name: "askMindStudioSdk",
|
|
1829
1879
|
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.",
|
|
1830
1880
|
inputSchema: {
|
|
@@ -1850,7 +1900,7 @@ var askMindStudioSdkTool = {
|
|
|
1850
1900
|
};
|
|
1851
1901
|
|
|
1852
1902
|
// src/usageLedger.ts
|
|
1853
|
-
import
|
|
1903
|
+
import fs12 from "fs";
|
|
1854
1904
|
var LEDGER_FILE = ".logs/usage.ndjson";
|
|
1855
1905
|
var fd = null;
|
|
1856
1906
|
function nanoToDollars(nano) {
|
|
@@ -1859,10 +1909,10 @@ function nanoToDollars(nano) {
|
|
|
1859
1909
|
function recordUsage(entry) {
|
|
1860
1910
|
try {
|
|
1861
1911
|
if (fd === null) {
|
|
1862
|
-
|
|
1863
|
-
fd =
|
|
1912
|
+
fs12.mkdirSync(".logs", { recursive: true });
|
|
1913
|
+
fd = fs12.openSync(LEDGER_FILE, "a");
|
|
1864
1914
|
}
|
|
1865
|
-
|
|
1915
|
+
fs12.writeSync(fd, JSON.stringify(entry) + "\n");
|
|
1866
1916
|
} catch {
|
|
1867
1917
|
}
|
|
1868
1918
|
}
|
|
@@ -1964,8 +2014,8 @@ function stripDollarKeys(envelope) {
|
|
|
1964
2014
|
|
|
1965
2015
|
// src/tools/common/searchGoogle.ts
|
|
1966
2016
|
var searchGoogleTool = {
|
|
1967
|
-
clearable: false,
|
|
1968
2017
|
definition: {
|
|
2018
|
+
clearable: false,
|
|
1969
2019
|
name: "searchGoogle",
|
|
1970
2020
|
description: "Search Google and return results. Use for research, finding documentation, looking up APIs, or any task where web search would help.",
|
|
1971
2021
|
inputSchema: {
|
|
@@ -1995,8 +2045,8 @@ var searchGoogleTool = {
|
|
|
1995
2045
|
|
|
1996
2046
|
// src/tools/common/setProjectMetadata.ts
|
|
1997
2047
|
var setProjectMetadataTool = {
|
|
1998
|
-
clearable: false,
|
|
1999
2048
|
definition: {
|
|
2049
|
+
clearable: false,
|
|
2000
2050
|
name: "setProjectMetadata",
|
|
2001
2051
|
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.",
|
|
2002
2052
|
inputSchema: {
|
|
@@ -2028,8 +2078,8 @@ var setProjectMetadataTool = {
|
|
|
2028
2078
|
|
|
2029
2079
|
// src/tools/common/compactConversation.ts
|
|
2030
2080
|
var compactConversationTool = {
|
|
2031
|
-
clearable: false,
|
|
2032
2081
|
definition: {
|
|
2082
|
+
clearable: false,
|
|
2033
2083
|
name: "compactConversation",
|
|
2034
2084
|
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.",
|
|
2035
2085
|
inputSchema: {
|
|
@@ -2051,8 +2101,47 @@ var compactConversationTool = {
|
|
|
2051
2101
|
}
|
|
2052
2102
|
};
|
|
2053
2103
|
|
|
2104
|
+
// src/tools/common/loadSkill.ts
|
|
2105
|
+
var loadSkillTool = {
|
|
2106
|
+
definition: {
|
|
2107
|
+
clearable: true,
|
|
2108
|
+
name: "loadSkill",
|
|
2109
|
+
description: "Load the full reference for a platform capability that isn't in your system prompt \u2014 task agents, agent 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.",
|
|
2110
|
+
inputSchema: {
|
|
2111
|
+
type: "object",
|
|
2112
|
+
properties: {
|
|
2113
|
+
skill: {
|
|
2114
|
+
type: "string",
|
|
2115
|
+
// Omitted when the catalog is empty: an empty enum is a schema no
|
|
2116
|
+
// provider accepts, and failing one tool call beats failing every
|
|
2117
|
+
// request if the docs ever go missing from a build.
|
|
2118
|
+
...SKILL_IDS.length > 0 ? { enum: SKILL_IDS } : {},
|
|
2119
|
+
description: "The skill id, as listed in <available_skills>."
|
|
2120
|
+
}
|
|
2121
|
+
},
|
|
2122
|
+
required: ["skill"]
|
|
2123
|
+
}
|
|
2124
|
+
},
|
|
2125
|
+
async execute(input) {
|
|
2126
|
+
const id = String(input.skill ?? "");
|
|
2127
|
+
const skill = getSkill(id);
|
|
2128
|
+
if (!skill) {
|
|
2129
|
+
return `Error: unknown skill "${id}". Available: ${SKILL_IDS.join(", ") || "(none)"}`;
|
|
2130
|
+
}
|
|
2131
|
+
try {
|
|
2132
|
+
const body = readSkillBody(skill);
|
|
2133
|
+
return `${body}
|
|
2134
|
+
|
|
2135
|
+
---
|
|
2136
|
+
This reference lives at ${skill.path}. Re-read it with readFile if you need it again later \u2014 it won't stay in the conversation.`;
|
|
2137
|
+
} catch (err) {
|
|
2138
|
+
return `Error loading skill "${id}": ${err.message}`;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
|
|
2054
2143
|
// src/tools/code/readFile.ts
|
|
2055
|
-
import
|
|
2144
|
+
import fs13 from "fs/promises";
|
|
2056
2145
|
var DEFAULT_WINDOW = 500;
|
|
2057
2146
|
var MAX_BYTES = 64 * 1024;
|
|
2058
2147
|
function isBinary(buffer) {
|
|
@@ -2065,8 +2154,8 @@ function isBinary(buffer) {
|
|
|
2065
2154
|
return false;
|
|
2066
2155
|
}
|
|
2067
2156
|
var readFileTool = {
|
|
2068
|
-
clearable: true,
|
|
2069
2157
|
definition: {
|
|
2158
|
+
clearable: true,
|
|
2070
2159
|
name: "readFile",
|
|
2071
2160
|
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.",
|
|
2072
2161
|
inputSchema: {
|
|
@@ -2074,7 +2163,7 @@ var readFileTool = {
|
|
|
2074
2163
|
properties: {
|
|
2075
2164
|
path: {
|
|
2076
2165
|
type: "string",
|
|
2077
|
-
description: "The file path to read
|
|
2166
|
+
description: "The file path to read. Relative paths resolve from the project root; an absolute path is also accepted, which is how you re-read a reference doc from the path a loadSkill result gave you."
|
|
2078
2167
|
},
|
|
2079
2168
|
startLine: {
|
|
2080
2169
|
type: "number",
|
|
@@ -2094,7 +2183,7 @@ var readFileTool = {
|
|
|
2094
2183
|
},
|
|
2095
2184
|
async execute(input) {
|
|
2096
2185
|
try {
|
|
2097
|
-
const buffer = await
|
|
2186
|
+
const buffer = await fs13.readFile(input.path);
|
|
2098
2187
|
if (isBinary(buffer)) {
|
|
2099
2188
|
const size = buffer.length;
|
|
2100
2189
|
const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
|
|
@@ -2166,11 +2255,11 @@ var readFileTool = {
|
|
|
2166
2255
|
};
|
|
2167
2256
|
|
|
2168
2257
|
// src/tools/code/writeFile.ts
|
|
2169
|
-
import
|
|
2170
|
-
import
|
|
2258
|
+
import fs14 from "fs/promises";
|
|
2259
|
+
import path7 from "path";
|
|
2171
2260
|
var writeFileTool = {
|
|
2172
|
-
clearable: true,
|
|
2173
2261
|
definition: {
|
|
2262
|
+
clearable: true,
|
|
2174
2263
|
name: "writeFile",
|
|
2175
2264
|
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.",
|
|
2176
2265
|
inputSchema: {
|
|
@@ -2204,7 +2293,7 @@ var writeFileTool = {
|
|
|
2204
2293
|
lastNewlineCount = newlineCount;
|
|
2205
2294
|
const lastNewline = partial.content.lastIndexOf("\n");
|
|
2206
2295
|
const completeContent = partial.content.substring(0, lastNewline + 1);
|
|
2207
|
-
const oldContent = await
|
|
2296
|
+
const oldContent = await fs14.readFile(partial.path, "utf-8").catch(() => "");
|
|
2208
2297
|
return `Writing ${partial.path} (${newlineCount} lines)
|
|
2209
2298
|
${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
2210
2299
|
}
|
|
@@ -2213,13 +2302,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
2213
2302
|
async execute(input) {
|
|
2214
2303
|
const release = await acquireFileLock(input.path);
|
|
2215
2304
|
try {
|
|
2216
|
-
await
|
|
2305
|
+
await fs14.mkdir(path7.dirname(input.path), { recursive: true });
|
|
2217
2306
|
let oldContent = null;
|
|
2218
2307
|
try {
|
|
2219
|
-
oldContent = await
|
|
2308
|
+
oldContent = await fs14.readFile(input.path, "utf-8");
|
|
2220
2309
|
} catch {
|
|
2221
2310
|
}
|
|
2222
|
-
await
|
|
2311
|
+
await fs14.writeFile(input.path, input.content, "utf-8");
|
|
2223
2312
|
const lineCount = input.content.split("\n").length;
|
|
2224
2313
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
2225
2314
|
return `${label} ${input.path} (${lineCount} lines)
|
|
@@ -2233,10 +2322,10 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
2233
2322
|
};
|
|
2234
2323
|
|
|
2235
2324
|
// src/tools/code/editFile/index.ts
|
|
2236
|
-
import
|
|
2325
|
+
import fs15 from "fs/promises";
|
|
2237
2326
|
var editFileTool = {
|
|
2238
|
-
clearable: true,
|
|
2239
2327
|
definition: {
|
|
2328
|
+
clearable: true,
|
|
2240
2329
|
name: "editFile",
|
|
2241
2330
|
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.",
|
|
2242
2331
|
inputSchema: {
|
|
@@ -2265,7 +2354,7 @@ var editFileTool = {
|
|
|
2265
2354
|
async execute(input) {
|
|
2266
2355
|
const release = await acquireFileLock(input.path);
|
|
2267
2356
|
try {
|
|
2268
|
-
const content = await
|
|
2357
|
+
const content = await fs15.readFile(input.path, "utf-8");
|
|
2269
2358
|
const { old_string, new_string, replace_all } = input;
|
|
2270
2359
|
const occurrences = findOccurrences(content, old_string);
|
|
2271
2360
|
if (replace_all) {
|
|
@@ -2281,7 +2370,7 @@ var editFileTool = {
|
|
|
2281
2370
|
new_string
|
|
2282
2371
|
);
|
|
2283
2372
|
}
|
|
2284
|
-
await
|
|
2373
|
+
await fs15.writeFile(input.path, updated, "utf-8");
|
|
2285
2374
|
return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
2286
2375
|
${unifiedDiff(input.path, content, updated)}`;
|
|
2287
2376
|
}
|
|
@@ -2292,7 +2381,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
2292
2381
|
old_string.length,
|
|
2293
2382
|
new_string
|
|
2294
2383
|
);
|
|
2295
|
-
await
|
|
2384
|
+
await fs15.writeFile(input.path, updated, "utf-8");
|
|
2296
2385
|
return `Updated ${input.path}
|
|
2297
2386
|
${unifiedDiff(input.path, content, updated)}`;
|
|
2298
2387
|
}
|
|
@@ -2308,7 +2397,7 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
2308
2397
|
flex.matchedText.length,
|
|
2309
2398
|
new_string
|
|
2310
2399
|
);
|
|
2311
|
-
await
|
|
2400
|
+
await fs15.writeFile(input.path, updated, "utf-8");
|
|
2312
2401
|
return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
2313
2402
|
${unifiedDiff(input.path, content, updated)}`;
|
|
2314
2403
|
}
|
|
@@ -2323,13 +2412,13 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
2323
2412
|
|
|
2324
2413
|
// src/tools/code/bash.ts
|
|
2325
2414
|
import { spawn as spawn2 } from "child_process";
|
|
2326
|
-
import
|
|
2415
|
+
import path8 from "path";
|
|
2327
2416
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2328
2417
|
var DEFAULT_MAX_LINES2 = 500;
|
|
2329
2418
|
var MAX_OUTPUT_BYTES = 3e4;
|
|
2330
2419
|
var bashTool = {
|
|
2331
|
-
clearable: true,
|
|
2332
2420
|
definition: {
|
|
2421
|
+
clearable: true,
|
|
2333
2422
|
name: "bash",
|
|
2334
2423
|
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.",
|
|
2335
2424
|
inputSchema: {
|
|
@@ -2358,11 +2447,11 @@ var bashTool = {
|
|
|
2358
2447
|
async execute(input, context) {
|
|
2359
2448
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
2360
2449
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
2361
|
-
return new Promise((
|
|
2450
|
+
return new Promise((resolve3) => {
|
|
2362
2451
|
const child = spawn2("sh", ["-c", input.command], {
|
|
2363
2452
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
2364
2453
|
// process happens to be", which is the project root only by luck.
|
|
2365
|
-
cwd: input.cwd ?
|
|
2454
|
+
cwd: input.cwd ? path8.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
|
|
2366
2455
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
2367
2456
|
});
|
|
2368
2457
|
let output = "";
|
|
@@ -2383,9 +2472,9 @@ var bashTool = {
|
|
|
2383
2472
|
clearTimeout(timer);
|
|
2384
2473
|
if (!output) {
|
|
2385
2474
|
if (code && code !== 0) {
|
|
2386
|
-
|
|
2475
|
+
resolve3(`Error: process exited with code ${code}`);
|
|
2387
2476
|
} else {
|
|
2388
|
-
|
|
2477
|
+
resolve3("(no output)");
|
|
2389
2478
|
}
|
|
2390
2479
|
return;
|
|
2391
2480
|
}
|
|
@@ -2411,18 +2500,18 @@ var bashTool = {
|
|
|
2411
2500
|
`${(MAX_OUTPUT_BYTES / 1024).toFixed(0)}KB of ${(totalBytes / 1024).toFixed(0)}KB`
|
|
2412
2501
|
);
|
|
2413
2502
|
}
|
|
2414
|
-
|
|
2503
|
+
resolve3(
|
|
2415
2504
|
truncated + `
|
|
2416
2505
|
|
|
2417
2506
|
(truncated at ${reasons.join(" / ")} \u2014 narrow the command (grep, head/tail, smaller paths) instead of increasing limits)`
|
|
2418
2507
|
);
|
|
2419
2508
|
} else {
|
|
2420
|
-
|
|
2509
|
+
resolve3(output);
|
|
2421
2510
|
}
|
|
2422
2511
|
});
|
|
2423
2512
|
child.on("error", (err) => {
|
|
2424
2513
|
clearTimeout(timer);
|
|
2425
|
-
|
|
2514
|
+
resolve3(`Error: ${err.message}`);
|
|
2426
2515
|
});
|
|
2427
2516
|
});
|
|
2428
2517
|
}
|
|
@@ -2450,8 +2539,8 @@ function formatResults(stdout, max, mode) {
|
|
|
2450
2539
|
return result;
|
|
2451
2540
|
}
|
|
2452
2541
|
var grepTool = {
|
|
2453
|
-
clearable: true,
|
|
2454
2542
|
definition: {
|
|
2543
|
+
clearable: true,
|
|
2455
2544
|
name: "grep",
|
|
2456
2545
|
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.",
|
|
2457
2546
|
inputSchema: {
|
|
@@ -2540,17 +2629,17 @@ var grepTool = {
|
|
|
2540
2629
|
}
|
|
2541
2630
|
const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
|
|
2542
2631
|
const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
2543
|
-
return new Promise((
|
|
2632
|
+
return new Promise((resolve3) => {
|
|
2544
2633
|
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
2545
2634
|
if (stdout?.trim()) {
|
|
2546
|
-
|
|
2635
|
+
resolve3(formatResults(stdout, max, mode));
|
|
2547
2636
|
return;
|
|
2548
2637
|
}
|
|
2549
2638
|
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
2550
2639
|
if (grepStdout?.trim()) {
|
|
2551
|
-
|
|
2640
|
+
resolve3(formatResults(grepStdout, max, mode));
|
|
2552
2641
|
} else {
|
|
2553
|
-
|
|
2642
|
+
resolve3("No matches found.");
|
|
2554
2643
|
}
|
|
2555
2644
|
});
|
|
2556
2645
|
});
|
|
@@ -2562,8 +2651,8 @@ var grepTool = {
|
|
|
2562
2651
|
import fg from "fast-glob";
|
|
2563
2652
|
var DEFAULT_MAX2 = 200;
|
|
2564
2653
|
var globTool = {
|
|
2565
|
-
clearable: true,
|
|
2566
2654
|
definition: {
|
|
2655
|
+
clearable: true,
|
|
2567
2656
|
name: "glob",
|
|
2568
2657
|
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.',
|
|
2569
2658
|
inputSchema: {
|
|
@@ -2607,12 +2696,12 @@ var globTool = {
|
|
|
2607
2696
|
};
|
|
2608
2697
|
|
|
2609
2698
|
// src/tools/code/listDir.ts
|
|
2610
|
-
import
|
|
2611
|
-
import
|
|
2699
|
+
import fs16 from "fs/promises";
|
|
2700
|
+
import path9 from "path";
|
|
2612
2701
|
var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2613
2702
|
var MAX_CHILDREN = 15;
|
|
2614
2703
|
async function readAndSort(dirPath) {
|
|
2615
|
-
const entries = await
|
|
2704
|
+
const entries = await fs16.readdir(dirPath, { withFileTypes: true });
|
|
2616
2705
|
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
2617
2706
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
2618
2707
|
return -1;
|
|
@@ -2625,7 +2714,7 @@ async function readAndSort(dirPath) {
|
|
|
2625
2714
|
}
|
|
2626
2715
|
async function collapsePath(basePath, name) {
|
|
2627
2716
|
let display = name;
|
|
2628
|
-
let current =
|
|
2717
|
+
let current = path9.join(basePath, name);
|
|
2629
2718
|
for (; ; ) {
|
|
2630
2719
|
let children;
|
|
2631
2720
|
try {
|
|
@@ -2635,7 +2724,7 @@ async function collapsePath(basePath, name) {
|
|
|
2635
2724
|
}
|
|
2636
2725
|
if (children.length === 1 && children[0].isDirectory()) {
|
|
2637
2726
|
display += "/" + children[0].name;
|
|
2638
|
-
current =
|
|
2727
|
+
current = path9.join(current, children[0].name);
|
|
2639
2728
|
} else {
|
|
2640
2729
|
break;
|
|
2641
2730
|
}
|
|
@@ -2653,15 +2742,15 @@ function formatSize(bytes) {
|
|
|
2653
2742
|
}
|
|
2654
2743
|
async function formatFile(dirPath, name, indent) {
|
|
2655
2744
|
try {
|
|
2656
|
-
const
|
|
2657
|
-
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(
|
|
2745
|
+
const stat2 = await fs16.stat(path9.join(dirPath, name));
|
|
2746
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
|
|
2658
2747
|
} catch {
|
|
2659
2748
|
return `${indent}${name}`;
|
|
2660
2749
|
}
|
|
2661
2750
|
}
|
|
2662
2751
|
var listDirTool = {
|
|
2663
|
-
clearable: true,
|
|
2664
2752
|
definition: {
|
|
2753
|
+
clearable: true,
|
|
2665
2754
|
name: "listDir",
|
|
2666
2755
|
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.",
|
|
2667
2756
|
inputSchema: {
|
|
@@ -2737,8 +2826,8 @@ var listDirTool = {
|
|
|
2737
2826
|
|
|
2738
2827
|
// src/tools/code/editsFinished.ts
|
|
2739
2828
|
var editsFinishedTool = {
|
|
2740
|
-
clearable: false,
|
|
2741
2829
|
definition: {
|
|
2830
|
+
clearable: false,
|
|
2742
2831
|
name: "editsFinished",
|
|
2743
2832
|
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.",
|
|
2744
2833
|
inputSchema: {
|
|
@@ -2804,8 +2893,8 @@ async function lspRequest(endpoint, body) {
|
|
|
2804
2893
|
|
|
2805
2894
|
// src/tools/code/lspDiagnostics.ts
|
|
2806
2895
|
var lspDiagnosticsTool = {
|
|
2807
|
-
clearable: true,
|
|
2808
2896
|
definition: {
|
|
2897
|
+
clearable: true,
|
|
2809
2898
|
name: "lspDiagnostics",
|
|
2810
2899
|
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.",
|
|
2811
2900
|
inputSchema: {
|
|
@@ -2853,8 +2942,8 @@ var lspDiagnosticsTool = {
|
|
|
2853
2942
|
|
|
2854
2943
|
// src/tools/code/restartProcess.ts
|
|
2855
2944
|
var restartProcessTool = {
|
|
2856
|
-
clearable: false,
|
|
2857
2945
|
definition: {
|
|
2946
|
+
clearable: false,
|
|
2858
2947
|
name: "restartProcess",
|
|
2859
2948
|
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.",
|
|
2860
2949
|
inputSchema: {
|
|
@@ -2871,7 +2960,7 @@ var restartProcessTool = {
|
|
|
2871
2960
|
async execute(input) {
|
|
2872
2961
|
const data = await lspRequest("/restart-process", { name: input.name });
|
|
2873
2962
|
if (data.ok) {
|
|
2874
|
-
await new Promise((
|
|
2963
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5e3));
|
|
2875
2964
|
return `Restarted ${input.name}.`;
|
|
2876
2965
|
}
|
|
2877
2966
|
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
@@ -2880,8 +2969,8 @@ var restartProcessTool = {
|
|
|
2880
2969
|
|
|
2881
2970
|
// src/tools/code/runScenario.ts
|
|
2882
2971
|
var runScenarioTool = {
|
|
2883
|
-
clearable: true,
|
|
2884
2972
|
definition: {
|
|
2973
|
+
clearable: true,
|
|
2885
2974
|
name: "runScenario",
|
|
2886
2975
|
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.",
|
|
2887
2976
|
inputSchema: {
|
|
@@ -2906,8 +2995,8 @@ var runScenarioTool = {
|
|
|
2906
2995
|
|
|
2907
2996
|
// src/tools/code/runMethod.ts
|
|
2908
2997
|
var runMethodTool = {
|
|
2909
|
-
clearable: true,
|
|
2910
2998
|
definition: {
|
|
2999
|
+
clearable: true,
|
|
2911
3000
|
name: "runMethod",
|
|
2912
3001
|
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.',
|
|
2913
3002
|
inputSchema: {
|
|
@@ -2941,8 +3030,8 @@ var runMethodTool = {
|
|
|
2941
3030
|
|
|
2942
3031
|
// src/tools/code/queryDatabase.ts
|
|
2943
3032
|
var queryDatabaseTool = {
|
|
2944
|
-
clearable: true,
|
|
2945
3033
|
definition: {
|
|
3034
|
+
clearable: true,
|
|
2946
3035
|
name: "queryDatabase",
|
|
2947
3036
|
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
2948
3037
|
inputSchema: {
|
|
@@ -2961,21 +3050,122 @@ var queryDatabaseTool = {
|
|
|
2961
3050
|
}
|
|
2962
3051
|
};
|
|
2963
3052
|
|
|
3053
|
+
// src/tools/_helpers/uploadImage.ts
|
|
3054
|
+
import { readFile, stat } from "fs/promises";
|
|
3055
|
+
import { basename, extname, resolve } from "path";
|
|
3056
|
+
var log5 = createLogger("uploadImage");
|
|
3057
|
+
var UPLOAD_TIMEOUT_MS = 6e4;
|
|
3058
|
+
var CONTENT_TYPES = {
|
|
3059
|
+
".png": "image/png",
|
|
3060
|
+
".jpg": "image/jpeg",
|
|
3061
|
+
".jpeg": "image/jpeg",
|
|
3062
|
+
".gif": "image/gif",
|
|
3063
|
+
".webp": "image/webp"
|
|
3064
|
+
};
|
|
3065
|
+
var hosted = /* @__PURE__ */ new Map();
|
|
3066
|
+
function isFetchableUrl(ref) {
|
|
3067
|
+
return /^(?:https?:|data:)/i.test(ref);
|
|
3068
|
+
}
|
|
3069
|
+
async function resolveImageRef(ref, apiConfig) {
|
|
3070
|
+
const trimmed = ref.trim();
|
|
3071
|
+
return isFetchableUrl(trimmed) ? trimmed : uploadLocalImage(trimmed, apiConfig);
|
|
3072
|
+
}
|
|
3073
|
+
function resolveImageRefs(refs, apiConfig) {
|
|
3074
|
+
return Promise.all(refs.map((ref) => resolveImageRef(ref, apiConfig)));
|
|
3075
|
+
}
|
|
3076
|
+
async function uploadLocalImage(localPath, apiConfig) {
|
|
3077
|
+
const absolute = resolve(PROJECT_ROOT, localPath);
|
|
3078
|
+
const ext = extname(absolute).toLowerCase();
|
|
3079
|
+
const contentType = CONTENT_TYPES[ext];
|
|
3080
|
+
if (!contentType) {
|
|
3081
|
+
throw new Error(
|
|
3082
|
+
`Cannot use "${localPath}" as an image \u2014 vision models read ${Object.keys(
|
|
3083
|
+
CONTENT_TYPES
|
|
3084
|
+
).join(", ")}. Convert it first, or pass an image URL.`
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
let stats;
|
|
3088
|
+
try {
|
|
3089
|
+
stats = await stat(absolute);
|
|
3090
|
+
} catch {
|
|
3091
|
+
throw new Error(
|
|
3092
|
+
`No file at "${localPath}". Paths resolve from the project root; list the directory to check the name, or pass an image URL.`
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
const cacheKey = `${absolute}:${stats.mtimeMs}:${stats.size}`;
|
|
3096
|
+
const cached3 = hosted.get(cacheKey);
|
|
3097
|
+
if (cached3) {
|
|
3098
|
+
return cached3;
|
|
3099
|
+
}
|
|
3100
|
+
if (!apiConfig?.appId) {
|
|
3101
|
+
throw new Error(
|
|
3102
|
+
`Cannot host "${localPath}" for analysis \u2014 this session has no app id. Pass an image URL instead.`
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
3105
|
+
const bytes = await readFile(absolute);
|
|
3106
|
+
const target = await requestUploadTarget(apiConfig, ext.slice(1));
|
|
3107
|
+
const form = new FormData();
|
|
3108
|
+
for (const [field, value] of Object.entries(target.uploadFields)) {
|
|
3109
|
+
form.append(field, value);
|
|
3110
|
+
}
|
|
3111
|
+
form.append(
|
|
3112
|
+
"file",
|
|
3113
|
+
new Blob([bytes], { type: contentType }),
|
|
3114
|
+
basename(absolute)
|
|
3115
|
+
);
|
|
3116
|
+
const res = await fetch(target.uploadUrl, {
|
|
3117
|
+
method: "POST",
|
|
3118
|
+
body: form,
|
|
3119
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
3120
|
+
});
|
|
3121
|
+
if (!res.ok) {
|
|
3122
|
+
throw new Error(`Upload of "${localPath}" failed: HTTP ${res.status}`);
|
|
3123
|
+
}
|
|
3124
|
+
log5.info("Local image hosted", {
|
|
3125
|
+
path: localPath,
|
|
3126
|
+
bytes: stats.size,
|
|
3127
|
+
url: target.publicUrl
|
|
3128
|
+
});
|
|
3129
|
+
hosted.set(cacheKey, target.publicUrl);
|
|
3130
|
+
return target.publicUrl;
|
|
3131
|
+
}
|
|
3132
|
+
async function requestUploadTarget(apiConfig, extension) {
|
|
3133
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/apps/${apiConfig.appId}/dev/manage/upload`;
|
|
3134
|
+
const res = await fetch(url, {
|
|
3135
|
+
method: "POST",
|
|
3136
|
+
headers: {
|
|
3137
|
+
"Content-Type": "application/json",
|
|
3138
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
3139
|
+
},
|
|
3140
|
+
body: JSON.stringify({ extension }),
|
|
3141
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
3142
|
+
});
|
|
3143
|
+
if (!res.ok) {
|
|
3144
|
+
throw new Error(`Could not get an upload URL: HTTP ${res.status}`);
|
|
3145
|
+
}
|
|
3146
|
+
return await res.json();
|
|
3147
|
+
}
|
|
3148
|
+
|
|
2964
3149
|
// src/subagents/common/analyzeImage.ts
|
|
2965
3150
|
async function analyzeImage(params) {
|
|
2966
|
-
const { prompt,
|
|
2967
|
-
|
|
3151
|
+
const { prompt, image, apiConfig, model, timeout = 2e5, onLog } = params;
|
|
3152
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
3153
|
+
const result = await runMindstudioCliResult(
|
|
2968
3154
|
[
|
|
2969
3155
|
"analyze-image",
|
|
2970
3156
|
"--prompt",
|
|
2971
3157
|
prompt,
|
|
2972
3158
|
"--image-url",
|
|
2973
|
-
|
|
3159
|
+
url,
|
|
2974
3160
|
"--vision-model-override",
|
|
2975
3161
|
JSON.stringify({ model })
|
|
2976
3162
|
],
|
|
2977
3163
|
{ outputKey: "analysis", timeout, onLog }
|
|
2978
3164
|
);
|
|
3165
|
+
if (!result.ok) {
|
|
3166
|
+
throw new Error(`Could not analyze ${url}: ${result.value}`);
|
|
3167
|
+
}
|
|
3168
|
+
return { url, analysis: result.value };
|
|
2979
3169
|
}
|
|
2980
3170
|
|
|
2981
3171
|
// src/tools/_helpers/screenshot.ts
|
|
@@ -3000,13 +3190,14 @@ ${ANALYSIS_RESPONSE_FORMAT}`;
|
|
|
3000
3190
|
return p;
|
|
3001
3191
|
}
|
|
3002
3192
|
async function streamScreenshotAnalysis(opts) {
|
|
3003
|
-
const {
|
|
3193
|
+
const { image, prompt, styleMap, onLog, model, apiConfig } = opts;
|
|
3194
|
+
const url = await resolveImageRef(image, apiConfig);
|
|
3004
3195
|
onLog?.(JSON.stringify({ url, analysis: null }));
|
|
3005
3196
|
const analysisPrompt = buildScreenshotAnalysisPrompt({ prompt, styleMap });
|
|
3006
3197
|
let accumulated = "";
|
|
3007
|
-
const analysis = await analyzeImage({
|
|
3198
|
+
const { analysis } = await analyzeImage({
|
|
3008
3199
|
prompt: analysisPrompt,
|
|
3009
|
-
|
|
3200
|
+
image: url,
|
|
3010
3201
|
model,
|
|
3011
3202
|
onLog: (chunk) => {
|
|
3012
3203
|
accumulated += chunk;
|
|
@@ -3017,18 +3208,19 @@ async function streamScreenshotAnalysis(opts) {
|
|
|
3017
3208
|
}
|
|
3018
3209
|
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
3019
3210
|
let prompt;
|
|
3020
|
-
let
|
|
3211
|
+
let existingImage;
|
|
3021
3212
|
let onLog;
|
|
3022
3213
|
let model;
|
|
3023
|
-
let
|
|
3214
|
+
let apiConfig;
|
|
3215
|
+
let path14;
|
|
3024
3216
|
let fullPage = true;
|
|
3025
3217
|
let width;
|
|
3026
3218
|
let height;
|
|
3027
3219
|
let format;
|
|
3028
3220
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
3029
3221
|
prompt = promptOrOptions.prompt;
|
|
3030
|
-
|
|
3031
|
-
|
|
3222
|
+
existingImage = promptOrOptions.image;
|
|
3223
|
+
path14 = promptOrOptions.path;
|
|
3032
3224
|
if (promptOrOptions.fullPage !== void 0) {
|
|
3033
3225
|
fullPage = promptOrOptions.fullPage;
|
|
3034
3226
|
}
|
|
@@ -3037,6 +3229,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3037
3229
|
format = promptOrOptions.format;
|
|
3038
3230
|
onLog = promptOrOptions.onLog;
|
|
3039
3231
|
model = promptOrOptions.model;
|
|
3232
|
+
apiConfig = promptOrOptions.apiConfig;
|
|
3040
3233
|
} else {
|
|
3041
3234
|
prompt = promptOrOptions;
|
|
3042
3235
|
}
|
|
@@ -3045,13 +3238,13 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3045
3238
|
}
|
|
3046
3239
|
let url;
|
|
3047
3240
|
let styleMap;
|
|
3048
|
-
if (
|
|
3049
|
-
url =
|
|
3241
|
+
if (existingImage) {
|
|
3242
|
+
url = existingImage;
|
|
3050
3243
|
} else {
|
|
3051
3244
|
const ssResult = await sidecarRequest(
|
|
3052
3245
|
fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
|
|
3053
3246
|
{
|
|
3054
|
-
...
|
|
3247
|
+
...path14 ? { path: path14 } : {},
|
|
3055
3248
|
...width != null ? { width } : {},
|
|
3056
3249
|
...height != null ? { height } : {},
|
|
3057
3250
|
...format ? { format } : {}
|
|
@@ -3069,7 +3262,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3069
3262
|
styleMap = ssResult?.styleMap;
|
|
3070
3263
|
}
|
|
3071
3264
|
if (prompt === false) {
|
|
3072
|
-
return url;
|
|
3265
|
+
return resolveImageRef(url, apiConfig);
|
|
3073
3266
|
}
|
|
3074
3267
|
if (!model) {
|
|
3075
3268
|
throw new Error(
|
|
@@ -3077,7 +3270,8 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3077
3270
|
);
|
|
3078
3271
|
}
|
|
3079
3272
|
return streamScreenshotAnalysis({
|
|
3080
|
-
url,
|
|
3273
|
+
image: url,
|
|
3274
|
+
apiConfig,
|
|
3081
3275
|
prompt: prompt || void 0,
|
|
3082
3276
|
styleMap,
|
|
3083
3277
|
onLog,
|
|
@@ -3098,7 +3292,7 @@ function acquireBrowserLock() {
|
|
|
3098
3292
|
}
|
|
3099
3293
|
|
|
3100
3294
|
// src/toolRegistry.ts
|
|
3101
|
-
var
|
|
3295
|
+
var log6 = createLogger("tool-registry");
|
|
3102
3296
|
var USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
3103
3297
|
var ToolRegistry = class {
|
|
3104
3298
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -3125,7 +3319,7 @@ var ToolRegistry = class {
|
|
|
3125
3319
|
if (!entry) {
|
|
3126
3320
|
return false;
|
|
3127
3321
|
}
|
|
3128
|
-
|
|
3322
|
+
log6.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
3129
3323
|
entry.abortController.abort(mode);
|
|
3130
3324
|
if (mode === "graceful") {
|
|
3131
3325
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -3158,7 +3352,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
3158
3352
|
if (!entry) {
|
|
3159
3353
|
return false;
|
|
3160
3354
|
}
|
|
3161
|
-
|
|
3355
|
+
log6.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
3162
3356
|
entry.abortController.abort("restart");
|
|
3163
3357
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
3164
3358
|
this.onEvent?.({
|
|
@@ -3397,7 +3591,7 @@ ${content}` : attachmentHeader;
|
|
|
3397
3591
|
}
|
|
3398
3592
|
|
|
3399
3593
|
// src/subagents/runner.ts
|
|
3400
|
-
var
|
|
3594
|
+
var log7 = createLogger("sub-agent");
|
|
3401
3595
|
async function runSubAgent(config) {
|
|
3402
3596
|
const {
|
|
3403
3597
|
system,
|
|
@@ -3425,7 +3619,7 @@ async function runSubAgent(config) {
|
|
|
3425
3619
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
3426
3620
|
const agentName = subAgentId || "sub-agent";
|
|
3427
3621
|
const runStart = Date.now();
|
|
3428
|
-
|
|
3622
|
+
log7.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
3429
3623
|
const emit = (e) => {
|
|
3430
3624
|
onEvent({ ...e, parentToolId });
|
|
3431
3625
|
};
|
|
@@ -3437,7 +3631,6 @@ async function runSubAgent(config) {
|
|
|
3437
3631
|
const fullSystem = `${system}
|
|
3438
3632
|
|
|
3439
3633
|
Current date: ${dateStr}`;
|
|
3440
|
-
const excludeToolsFromClearing = tools2.filter((t) => t.clearable === false).map((t) => t.name);
|
|
3441
3634
|
let turns = 0;
|
|
3442
3635
|
const run = async () => {
|
|
3443
3636
|
const historyLen = (history ?? []).length;
|
|
@@ -3509,7 +3702,6 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3509
3702
|
system: fullSystem,
|
|
3510
3703
|
messages: cleanMessagesForApi(messages),
|
|
3511
3704
|
tools: tools2,
|
|
3512
|
-
excludeToolsFromClearing,
|
|
3513
3705
|
signal
|
|
3514
3706
|
},
|
|
3515
3707
|
{
|
|
@@ -3645,7 +3837,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3645
3837
|
...hasArtifacts ? { artifacts } : {}
|
|
3646
3838
|
};
|
|
3647
3839
|
}
|
|
3648
|
-
|
|
3840
|
+
log7.info("Tools executing", {
|
|
3649
3841
|
requestId,
|
|
3650
3842
|
parentToolId,
|
|
3651
3843
|
count: toolCalls.length,
|
|
@@ -3722,7 +3914,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3722
3914
|
run2(tc.input);
|
|
3723
3915
|
const r = await resultPromise;
|
|
3724
3916
|
toolRegistry?.unregister(tc.id);
|
|
3725
|
-
|
|
3917
|
+
log7.info("Tool completed", {
|
|
3726
3918
|
requestId,
|
|
3727
3919
|
parentToolId,
|
|
3728
3920
|
toolCallId: tc.id,
|
|
@@ -3773,7 +3965,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3773
3965
|
const wrapRun = async () => {
|
|
3774
3966
|
try {
|
|
3775
3967
|
const result = await run();
|
|
3776
|
-
|
|
3968
|
+
log7.info("Sub-agent complete", {
|
|
3777
3969
|
requestId,
|
|
3778
3970
|
parentToolId,
|
|
3779
3971
|
agentName,
|
|
@@ -3782,7 +3974,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3782
3974
|
});
|
|
3783
3975
|
return result;
|
|
3784
3976
|
} catch (err) {
|
|
3785
|
-
|
|
3977
|
+
log7.warn("Sub-agent error", {
|
|
3786
3978
|
requestId,
|
|
3787
3979
|
parentToolId,
|
|
3788
3980
|
agentName,
|
|
@@ -3794,7 +3986,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3794
3986
|
if (!background) {
|
|
3795
3987
|
return wrapRun();
|
|
3796
3988
|
}
|
|
3797
|
-
|
|
3989
|
+
log7.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
3798
3990
|
toolRegistry?.register({
|
|
3799
3991
|
id: parentToolId,
|
|
3800
3992
|
name: agentName,
|
|
@@ -3985,13 +4177,13 @@ var BROWSER_TOOLS = [
|
|
|
3985
4177
|
var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
|
|
3986
4178
|
|
|
3987
4179
|
// src/subagents/common/context.ts
|
|
3988
|
-
import
|
|
3989
|
-
import
|
|
4180
|
+
import fs17 from "fs";
|
|
4181
|
+
import path10 from "path";
|
|
3990
4182
|
function walkMdFiles2(dir, skip) {
|
|
3991
4183
|
const files = [];
|
|
3992
4184
|
try {
|
|
3993
|
-
for (const entry of
|
|
3994
|
-
const full =
|
|
4185
|
+
for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
|
|
4186
|
+
const full = path10.join(dir, entry.name);
|
|
3995
4187
|
if (entry.isDirectory()) {
|
|
3996
4188
|
if (!skip?.has(entry.name)) {
|
|
3997
4189
|
files.push(...walkMdFiles2(full, skip));
|
|
@@ -4004,9 +4196,9 @@ function walkMdFiles2(dir, skip) {
|
|
|
4004
4196
|
}
|
|
4005
4197
|
return files.sort();
|
|
4006
4198
|
}
|
|
4007
|
-
function
|
|
4199
|
+
function parseFrontmatter3(filePath) {
|
|
4008
4200
|
try {
|
|
4009
|
-
const content =
|
|
4201
|
+
const content = fs17.readFileSync(filePath, "utf-8");
|
|
4010
4202
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4011
4203
|
if (!match) {
|
|
4012
4204
|
return {};
|
|
@@ -4031,7 +4223,7 @@ function loadSpecIndex() {
|
|
|
4031
4223
|
return "";
|
|
4032
4224
|
}
|
|
4033
4225
|
const lines = files.map((f) => {
|
|
4034
|
-
const fm =
|
|
4226
|
+
const fm = parseFrontmatter3(f);
|
|
4035
4227
|
let line = `- ${f}`;
|
|
4036
4228
|
if (fm.name) {
|
|
4037
4229
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4052,7 +4244,7 @@ function loadRoadmapIndex() {
|
|
|
4052
4244
|
const parts = [];
|
|
4053
4245
|
try {
|
|
4054
4246
|
const indexJson = JSON.parse(
|
|
4055
|
-
|
|
4247
|
+
fs17.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4056
4248
|
);
|
|
4057
4249
|
if (indexJson.lanes?.length > 0) {
|
|
4058
4250
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4072,7 +4264,7 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
|
|
|
4072
4264
|
const files = walkMdFiles2("src/roadmap");
|
|
4073
4265
|
if (files.length > 0) {
|
|
4074
4266
|
const lines = files.map((f) => {
|
|
4075
|
-
const fm =
|
|
4267
|
+
const fm = parseFrontmatter3(f);
|
|
4076
4268
|
let line = `- ${f}`;
|
|
4077
4269
|
if (fm.name) {
|
|
4078
4270
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4168,7 +4360,7 @@ function getBrowserAutomationPrompt() {
|
|
|
4168
4360
|
}
|
|
4169
4361
|
|
|
4170
4362
|
// src/subagents/browserAutomation/index.ts
|
|
4171
|
-
var
|
|
4363
|
+
var log8 = createLogger("browser-automation");
|
|
4172
4364
|
var CAPTURE_COMMANDS = /* @__PURE__ */ new Set(["screenshotViewport", "screenshotFullPage"]);
|
|
4173
4365
|
async function runBrowserAutomation(task, context, opts) {
|
|
4174
4366
|
const release = await acquireBrowserLock();
|
|
@@ -4264,7 +4456,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4264
4456
|
step.result.analysis = analyses[i]?.output?.analysis || analyses[i]?.output || "";
|
|
4265
4457
|
});
|
|
4266
4458
|
} catch {
|
|
4267
|
-
|
|
4459
|
+
log8.debug("Failed to parse batch analysis result", {
|
|
4268
4460
|
batchResult
|
|
4269
4461
|
});
|
|
4270
4462
|
}
|
|
@@ -4288,8 +4480,8 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4288
4480
|
}
|
|
4289
4481
|
}
|
|
4290
4482
|
var browserAutomationTool = {
|
|
4291
|
-
clearable: true,
|
|
4292
4483
|
definition: {
|
|
4484
|
+
clearable: true,
|
|
4293
4485
|
name: "runAutomatedBrowserTest",
|
|
4294
4486
|
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).",
|
|
4295
4487
|
inputSchema: {
|
|
@@ -4321,7 +4513,7 @@ var browserAutomationTool = {
|
|
|
4321
4513
|
var screenshotDefinition = {
|
|
4322
4514
|
clearable: true,
|
|
4323
4515
|
name: "screenshot",
|
|
4324
|
-
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. 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.",
|
|
4516
|
+
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.",
|
|
4325
4517
|
inputSchema: {
|
|
4326
4518
|
type: "object",
|
|
4327
4519
|
properties: {
|
|
@@ -4335,7 +4527,7 @@ var screenshotDefinition = {
|
|
|
4335
4527
|
},
|
|
4336
4528
|
imageUrl: {
|
|
4337
4529
|
type: "string",
|
|
4338
|
-
description: "
|
|
4530
|
+
description: "An existing image to analyze instead of capturing a new one \u2014 the URL of a previous screenshot (for follow-up questions about it), or the path of an image file on disk such as a user upload. Local files are hosted automatically and the returned URL can be reused."
|
|
4339
4531
|
},
|
|
4340
4532
|
path: {
|
|
4341
4533
|
type: "string",
|
|
@@ -4369,9 +4561,10 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
4369
4561
|
if (input.imageUrl) {
|
|
4370
4562
|
return await captureAndAnalyzeScreenshot({
|
|
4371
4563
|
prompt: input.prompt,
|
|
4372
|
-
|
|
4564
|
+
image: input.imageUrl,
|
|
4373
4565
|
onLog,
|
|
4374
|
-
model
|
|
4566
|
+
model,
|
|
4567
|
+
apiConfig: context?.apiConfig
|
|
4375
4568
|
});
|
|
4376
4569
|
}
|
|
4377
4570
|
if (input.instructions && context) {
|
|
@@ -4384,11 +4577,12 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
4384
4577
|
return result.text;
|
|
4385
4578
|
}
|
|
4386
4579
|
return await streamScreenshotAnalysis({
|
|
4387
|
-
|
|
4580
|
+
image: result.screenshot.url,
|
|
4388
4581
|
prompt: input.prompt,
|
|
4389
4582
|
styleMap: result.screenshot.styleMap,
|
|
4390
4583
|
onLog,
|
|
4391
|
-
model
|
|
4584
|
+
model,
|
|
4585
|
+
apiConfig: context?.apiConfig
|
|
4392
4586
|
});
|
|
4393
4587
|
}
|
|
4394
4588
|
const release = await acquireBrowserLock();
|
|
@@ -4401,7 +4595,8 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
4401
4595
|
height: input.height,
|
|
4402
4596
|
format: input.format,
|
|
4403
4597
|
onLog,
|
|
4404
|
-
model
|
|
4598
|
+
model,
|
|
4599
|
+
apiConfig: context?.apiConfig
|
|
4405
4600
|
});
|
|
4406
4601
|
} finally {
|
|
4407
4602
|
release();
|
|
@@ -4411,7 +4606,6 @@ async function executeScreenshot(input, onLog, context) {
|
|
|
4411
4606
|
}
|
|
4412
4607
|
}
|
|
4413
4608
|
var screenshotTool = {
|
|
4414
|
-
clearable: true,
|
|
4415
4609
|
definition: screenshotDefinition,
|
|
4416
4610
|
execute: (input, context) => executeScreenshot(input, context?.onLog, context)
|
|
4417
4611
|
};
|
|
@@ -4516,13 +4710,13 @@ Respond only with your analysis as Markdown and absolutely no other text. Do not
|
|
|
4516
4710
|
var definition3 = {
|
|
4517
4711
|
clearable: false,
|
|
4518
4712
|
name: "analyzeDesign",
|
|
4519
|
-
description: "Analyze the visual design of a website or image
|
|
4713
|
+
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.",
|
|
4520
4714
|
inputSchema: {
|
|
4521
4715
|
type: "object",
|
|
4522
4716
|
properties: {
|
|
4523
4717
|
url: {
|
|
4524
4718
|
type: "string",
|
|
4525
|
-
description: "
|
|
4719
|
+
description: "What to analyze: a website URL (will be screenshotted), an image URL, or the path of an image file on disk (e.g. a reference the user uploaded, under src/.user-uploads/). Local files are hosted automatically and their URL comes back in the result."
|
|
4526
4720
|
},
|
|
4527
4721
|
prompt: {
|
|
4528
4722
|
type: "string",
|
|
@@ -4535,9 +4729,9 @@ var definition3 = {
|
|
|
4535
4729
|
async function execute3(input, onLog, context) {
|
|
4536
4730
|
const url = input.url;
|
|
4537
4731
|
const analysisPrompt = input.prompt || DESIGN_REFERENCE_PROMPT;
|
|
4538
|
-
const
|
|
4539
|
-
let
|
|
4540
|
-
if (!
|
|
4732
|
+
const isImage = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
|
|
4733
|
+
let image = url;
|
|
4734
|
+
if (!isImage) {
|
|
4541
4735
|
const ss = await runMindstudioCliResult(
|
|
4542
4736
|
[
|
|
4543
4737
|
"screenshot-url",
|
|
@@ -4560,15 +4754,16 @@ async function execute3(input, onLog, context) {
|
|
|
4560
4754
|
if (!ss.ok) {
|
|
4561
4755
|
return `Could not screenshot ${url}: ${ss.value}`;
|
|
4562
4756
|
}
|
|
4563
|
-
|
|
4757
|
+
image = ss.value;
|
|
4564
4758
|
}
|
|
4565
|
-
const
|
|
4759
|
+
const analyzed = await analyzeImage({
|
|
4566
4760
|
prompt: analysisPrompt,
|
|
4567
|
-
|
|
4761
|
+
image,
|
|
4762
|
+
apiConfig: context?.apiConfig,
|
|
4568
4763
|
onLog,
|
|
4569
4764
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
4570
4765
|
});
|
|
4571
|
-
return JSON.stringify({ url:
|
|
4766
|
+
return JSON.stringify({ url: analyzed.url, analysis: analyzed.analysis });
|
|
4572
4767
|
}
|
|
4573
4768
|
|
|
4574
4769
|
// src/subagents/designExpert/tools/analyzeImage.ts
|
|
@@ -4580,13 +4775,16 @@ __export(analyzeImage_exports, {
|
|
|
4580
4775
|
var definition4 = {
|
|
4581
4776
|
clearable: true,
|
|
4582
4777
|
name: "analyzeImage",
|
|
4583
|
-
description: "Analyze an image
|
|
4778
|
+
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.",
|
|
4584
4779
|
inputSchema: {
|
|
4585
4780
|
type: "object",
|
|
4586
4781
|
properties: {
|
|
4782
|
+
// Still `imageUrl` even though a path works: the builder renders this
|
|
4783
|
+
// tool's thumbnail from `input.imageUrl` and deploys separately, so
|
|
4784
|
+
// renaming the property would blank the view under version skew.
|
|
4587
4785
|
imageUrl: {
|
|
4588
4786
|
type: "string",
|
|
4589
|
-
description: "The image URL
|
|
4787
|
+
description: "The image to analyze: either a URL, or the path of an image file on disk (e.g. a reference screenshot the user uploaded, under src/.user-uploads/). Local files are hosted automatically; the URL comes back in the result and can be reused for follow-up questions or embedded in a spec."
|
|
4590
4788
|
},
|
|
4591
4789
|
prompt: {
|
|
4592
4790
|
type: "string",
|
|
@@ -4597,17 +4795,17 @@ var definition4 = {
|
|
|
4597
4795
|
}
|
|
4598
4796
|
};
|
|
4599
4797
|
async function execute4(input, onLog, context) {
|
|
4600
|
-
const imageUrl = input.imageUrl;
|
|
4601
4798
|
const prompt = buildScreenshotAnalysisPrompt({
|
|
4602
4799
|
prompt: input.prompt
|
|
4603
4800
|
});
|
|
4604
|
-
const analysis = await analyzeImage({
|
|
4801
|
+
const { url, analysis } = await analyzeImage({
|
|
4605
4802
|
prompt,
|
|
4606
|
-
imageUrl,
|
|
4803
|
+
image: input.imageUrl,
|
|
4804
|
+
apiConfig: context?.apiConfig,
|
|
4607
4805
|
onLog,
|
|
4608
4806
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
4609
4807
|
});
|
|
4610
|
-
return JSON.stringify({ url
|
|
4808
|
+
return JSON.stringify({ url, analysis });
|
|
4611
4809
|
}
|
|
4612
4810
|
|
|
4613
4811
|
// src/subagents/designExpert/tools/images/generateImages.ts
|
|
@@ -4676,14 +4874,15 @@ var ANALYZE_PROMPT = 'You are reviewing this image for a visual designer sourcin
|
|
|
4676
4874
|
async function generateImageAssets(opts) {
|
|
4677
4875
|
const {
|
|
4678
4876
|
prompts,
|
|
4679
|
-
sourceImages,
|
|
4680
4877
|
transparentBackground,
|
|
4681
4878
|
enhancePrompts,
|
|
4682
4879
|
onLog,
|
|
4880
|
+
apiConfig,
|
|
4683
4881
|
imageGenerationModel: genModel,
|
|
4684
4882
|
imageAnalysisModel,
|
|
4685
4883
|
imagePromptEnhancerModel
|
|
4686
4884
|
} = opts;
|
|
4885
|
+
const sourceImages = opts.sourceImages?.length ? await resolveImageRefs(opts.sourceImages, apiConfig) : void 0;
|
|
4687
4886
|
const width = opts.width || 2048;
|
|
4688
4887
|
const height = opts.height || 2048;
|
|
4689
4888
|
const config = { width, height };
|
|
@@ -4789,10 +4988,10 @@ async function generateImageAssets(opts) {
|
|
|
4789
4988
|
}
|
|
4790
4989
|
const analysis = await analyzeImage({
|
|
4791
4990
|
prompt: ANALYZE_PROMPT,
|
|
4792
|
-
|
|
4991
|
+
image: url,
|
|
4793
4992
|
onLog,
|
|
4794
4993
|
model: imageAnalysisModel
|
|
4795
|
-
});
|
|
4994
|
+
}).then((r) => r.analysis).catch((err) => `Could not review this image: ${err.message}`);
|
|
4796
4995
|
return {
|
|
4797
4996
|
url,
|
|
4798
4997
|
prompt: prompts[i],
|
|
@@ -4823,7 +5022,7 @@ var definition5 = {
|
|
|
4823
5022
|
},
|
|
4824
5023
|
referenceImage: {
|
|
4825
5024
|
type: "string",
|
|
4826
|
-
description: "Optional
|
|
5025
|
+
description: "Optional single reference image to guide the generation \u2014 for style, subject, character consistency, or composition. Either a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). Your prompt still describes the desired result; the reference conditions it. Applies to every prompt in the batch."
|
|
4827
5026
|
},
|
|
4828
5027
|
width: {
|
|
4829
5028
|
type: "number",
|
|
@@ -4850,6 +5049,7 @@ async function execute5(input, onLog, context) {
|
|
|
4850
5049
|
sourceImages: input.referenceImage ? [input.referenceImage] : void 0,
|
|
4851
5050
|
enhancePrompts: true,
|
|
4852
5051
|
onLog,
|
|
5052
|
+
apiConfig: context?.apiConfig,
|
|
4853
5053
|
imageGenerationModel: resolveModel(
|
|
4854
5054
|
"imageGeneration",
|
|
4855
5055
|
context?.models,
|
|
@@ -4893,7 +5093,7 @@ var definition6 = {
|
|
|
4893
5093
|
items: {
|
|
4894
5094
|
type: "string"
|
|
4895
5095
|
},
|
|
4896
|
-
description: "One or more source/reference image
|
|
5096
|
+
description: "One or more source/reference images, each a URL or the path of an image file on disk (e.g. a user upload under src/.user-uploads/). These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
|
|
4897
5097
|
},
|
|
4898
5098
|
width: {
|
|
4899
5099
|
type: "number",
|
|
@@ -4920,6 +5120,7 @@ async function execute6(input, onLog, context) {
|
|
|
4920
5120
|
transparentBackground: input.transparentBackground,
|
|
4921
5121
|
enhancePrompts: false,
|
|
4922
5122
|
onLog,
|
|
5123
|
+
apiConfig: context?.apiConfig,
|
|
4923
5124
|
imageGenerationModel: resolveModel(
|
|
4924
5125
|
"imageGeneration",
|
|
4925
5126
|
context?.models,
|
|
@@ -4951,8 +5152,8 @@ var COPY_EDITOR_TOOLS = [...COMMON_READ_TOOLS];
|
|
|
4951
5152
|
// src/subagents/copyEditor/index.ts
|
|
4952
5153
|
var BASE_PROMPT2 = readAsset("subagents/copyEditor", "prompt.md");
|
|
4953
5154
|
var copyEditorTool = {
|
|
4954
|
-
clearable: false,
|
|
4955
5155
|
definition: {
|
|
5156
|
+
clearable: false,
|
|
4956
5157
|
name: "copyEditor",
|
|
4957
5158
|
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.",
|
|
4958
5159
|
inputSchema: {
|
|
@@ -5046,7 +5247,7 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
|
|
|
5046
5247
|
}
|
|
5047
5248
|
|
|
5048
5249
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
5049
|
-
import
|
|
5250
|
+
import fs18 from "fs";
|
|
5050
5251
|
var SAMPLE_FILE = ".remy-design-sample.json";
|
|
5051
5252
|
var cached2 = null;
|
|
5052
5253
|
function generateIndices(poolSize, sampleSize) {
|
|
@@ -5060,14 +5261,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
5060
5261
|
}
|
|
5061
5262
|
function load() {
|
|
5062
5263
|
try {
|
|
5063
|
-
return JSON.parse(
|
|
5264
|
+
return JSON.parse(fs18.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
5064
5265
|
} catch {
|
|
5065
5266
|
return null;
|
|
5066
5267
|
}
|
|
5067
5268
|
}
|
|
5068
5269
|
function save(indices) {
|
|
5069
5270
|
try {
|
|
5070
|
-
|
|
5271
|
+
fs18.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
5071
5272
|
} catch {
|
|
5072
5273
|
}
|
|
5073
5274
|
}
|
|
@@ -5351,8 +5552,8 @@ async function runDesignExpert(opts, context) {
|
|
|
5351
5552
|
});
|
|
5352
5553
|
}
|
|
5353
5554
|
var designExpertTool = {
|
|
5354
|
-
clearable: false,
|
|
5355
5555
|
definition: {
|
|
5556
|
+
clearable: false,
|
|
5356
5557
|
name: "visualDesignExpert",
|
|
5357
5558
|
description: DESCRIPTION,
|
|
5358
5559
|
inputSchema: {
|
|
@@ -5441,28 +5642,28 @@ var VISION_TOOLS = [
|
|
|
5441
5642
|
];
|
|
5442
5643
|
|
|
5443
5644
|
// src/subagents/productVision/executor.ts
|
|
5444
|
-
import
|
|
5445
|
-
import
|
|
5645
|
+
import fs19 from "fs";
|
|
5646
|
+
import path11 from "path";
|
|
5446
5647
|
var ROADMAP_DIR = "src/roadmap";
|
|
5447
5648
|
var PITCH_DECK_SHELL = readAsset(
|
|
5448
5649
|
"subagents/productVision",
|
|
5449
5650
|
"pitch-deck-shell.html"
|
|
5450
5651
|
);
|
|
5451
|
-
function
|
|
5452
|
-
return
|
|
5652
|
+
function resolve2(filePath) {
|
|
5653
|
+
return path11.join(ROADMAP_DIR, filePath);
|
|
5453
5654
|
}
|
|
5454
5655
|
async function executeVisionTool(name, input, context) {
|
|
5455
5656
|
switch (name) {
|
|
5456
5657
|
case "writeFile": {
|
|
5457
|
-
const filePath =
|
|
5658
|
+
const filePath = resolve2(input.path);
|
|
5458
5659
|
try {
|
|
5459
|
-
|
|
5660
|
+
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5460
5661
|
let oldContent = null;
|
|
5461
5662
|
try {
|
|
5462
|
-
oldContent =
|
|
5663
|
+
oldContent = fs19.readFileSync(filePath, "utf-8");
|
|
5463
5664
|
} catch {
|
|
5464
5665
|
}
|
|
5465
|
-
|
|
5666
|
+
fs19.writeFileSync(filePath, input.content, "utf-8");
|
|
5466
5667
|
const lineCount = input.content.split("\n").length;
|
|
5467
5668
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
5468
5669
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -5472,13 +5673,13 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
5472
5673
|
}
|
|
5473
5674
|
}
|
|
5474
5675
|
case "deleteFile": {
|
|
5475
|
-
const filePath =
|
|
5676
|
+
const filePath = resolve2(input.path);
|
|
5476
5677
|
try {
|
|
5477
|
-
if (!
|
|
5678
|
+
if (!fs19.existsSync(filePath)) {
|
|
5478
5679
|
return `Error: ${filePath} does not exist`;
|
|
5479
5680
|
}
|
|
5480
|
-
const oldContent =
|
|
5481
|
-
|
|
5681
|
+
const oldContent = fs19.readFileSync(filePath, "utf-8");
|
|
5682
|
+
fs19.unlinkSync(filePath);
|
|
5482
5683
|
return `Deleted ${filePath}
|
|
5483
5684
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
5484
5685
|
} catch (err) {
|
|
@@ -5489,11 +5690,11 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
5489
5690
|
if (!context) {
|
|
5490
5691
|
return "Error: writePitchDeck requires execution context for design expert delegation";
|
|
5491
5692
|
}
|
|
5492
|
-
const filePath =
|
|
5693
|
+
const filePath = resolve2("pitch.html");
|
|
5493
5694
|
try {
|
|
5494
|
-
|
|
5495
|
-
const exists =
|
|
5496
|
-
const before = exists ?
|
|
5695
|
+
fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5696
|
+
const exists = fs19.existsSync(filePath);
|
|
5697
|
+
const before = exists ? fs19.statSync(filePath).mtimeMs : null;
|
|
5497
5698
|
const delivery = exists ? `### Your deliverable
|
|
5498
5699
|
The pitch deck already exists at \`${filePath}\`. Read it, then update it for the new <pitch_content>, keeping the presentation scaffolding intact \u2014 change only what needs to change.
|
|
5499
5700
|
|
|
@@ -5523,11 +5724,11 @@ Maintain the bones of the presentation scaffolding. Always keep the progress bar
|
|
|
5523
5724
|
${delivery}`;
|
|
5524
5725
|
const result = await runDesignExpertRender({ task }, context);
|
|
5525
5726
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
5526
|
-
if (!
|
|
5727
|
+
if (!fs19.existsSync(filePath)) {
|
|
5527
5728
|
return `Error: the design expert did not write ${filePath}. Its reply was:
|
|
5528
5729
|
${result.text}`;
|
|
5529
5730
|
}
|
|
5530
|
-
if (before !== null &&
|
|
5731
|
+
if (before !== null && fs19.statSync(filePath).mtimeMs === before) {
|
|
5531
5732
|
return `Error: the pitch deck at ${filePath} was not modified. The design expert's reply was:
|
|
5532
5733
|
${result.text}`;
|
|
5533
5734
|
}
|
|
@@ -5559,8 +5760,8 @@ function getProductVisionPrompt() {
|
|
|
5559
5760
|
|
|
5560
5761
|
// src/subagents/productVision/index.ts
|
|
5561
5762
|
var productVisionTool = {
|
|
5562
|
-
clearable: false,
|
|
5563
5763
|
definition: {
|
|
5764
|
+
clearable: false,
|
|
5564
5765
|
name: "productVision",
|
|
5565
5766
|
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.",
|
|
5566
5767
|
inputSchema: {
|
|
@@ -5672,8 +5873,8 @@ var SANITY_CHECK_TOOLS = [
|
|
|
5672
5873
|
// src/subagents/codeSanityCheck/index.ts
|
|
5673
5874
|
var BASE_PROMPT4 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
5674
5875
|
var codeSanityCheckTool = {
|
|
5675
|
-
clearable: false,
|
|
5676
5876
|
definition: {
|
|
5877
|
+
clearable: false,
|
|
5677
5878
|
name: "codeSanityCheck",
|
|
5678
5879
|
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.',
|
|
5679
5880
|
inputSchema: {
|
|
@@ -5748,9 +5949,9 @@ ${readAsset(
|
|
|
5748
5949
|
)}
|
|
5749
5950
|
</mindstudio_flavored_markdown_spec_docs>`;
|
|
5750
5951
|
var specSyncTool = {
|
|
5751
|
-
clearable: false,
|
|
5752
5952
|
backgroundOnly: true,
|
|
5753
5953
|
definition: {
|
|
5954
|
+
clearable: false,
|
|
5754
5955
|
name: "specSync",
|
|
5755
5956
|
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 \u2014 it returns immediately and reports back when done.",
|
|
5756
5957
|
inputSchema: {
|
|
@@ -5817,8 +6018,8 @@ var specSyncTool = {
|
|
|
5817
6018
|
|
|
5818
6019
|
// src/tools/common/scrapeWebUrl.ts
|
|
5819
6020
|
var scrapeWebUrlTool = {
|
|
5820
|
-
clearable: false,
|
|
5821
6021
|
definition: {
|
|
6022
|
+
clearable: false,
|
|
5822
6023
|
name: "scrapeWebUrl",
|
|
5823
6024
|
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",
|
|
5824
6025
|
inputSchema: {
|
|
@@ -5861,7 +6062,7 @@ var scrapeWebUrlTool = {
|
|
|
5861
6062
|
};
|
|
5862
6063
|
|
|
5863
6064
|
// src/tools/spec/writeBuildOverview.ts
|
|
5864
|
-
import
|
|
6065
|
+
import fs20 from "fs";
|
|
5865
6066
|
var OVERVIEW_FILE = "src/overview.html";
|
|
5866
6067
|
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).
|
|
5867
6068
|
|
|
@@ -5918,8 +6119,8 @@ The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update
|
|
|
5918
6119
|
Then reply with a one-line summary of what you changed.`;
|
|
5919
6120
|
}
|
|
5920
6121
|
var buildOverviewTool = {
|
|
5921
|
-
clearable: false,
|
|
5922
6122
|
definition: {
|
|
6123
|
+
clearable: false,
|
|
5923
6124
|
name: "writeBuildOverview",
|
|
5924
6125
|
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.",
|
|
5925
6126
|
inputSchema: {
|
|
@@ -5941,7 +6142,7 @@ var buildOverviewTool = {
|
|
|
5941
6142
|
if (!content) {
|
|
5942
6143
|
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
5943
6144
|
}
|
|
5944
|
-
const exists =
|
|
6145
|
+
const exists = fs20.existsSync(OVERVIEW_FILE);
|
|
5945
6146
|
const task = `<overview_copy>${content}</overview_copy>
|
|
5946
6147
|
|
|
5947
6148
|
${DESIGN_BRIEF}
|
|
@@ -5959,7 +6160,7 @@ ${exists ? refreshDelivery() : initialDelivery()}`;
|
|
|
5959
6160
|
}
|
|
5960
6161
|
const result = await runDesignExpertRender({ task }, context);
|
|
5961
6162
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
5962
|
-
if (!
|
|
6163
|
+
if (!fs20.existsSync(OVERVIEW_FILE)) {
|
|
5963
6164
|
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
5964
6165
|
${result.text}`;
|
|
5965
6166
|
}
|
|
@@ -6016,11 +6217,11 @@ var ALL_TOOLS = [
|
|
|
6016
6217
|
browserAutomationTool,
|
|
6017
6218
|
// LSP
|
|
6018
6219
|
lspDiagnosticsTool,
|
|
6019
|
-
restartProcessTool
|
|
6220
|
+
restartProcessTool,
|
|
6221
|
+
// Appended rather than grouped: position is part of the cache prefix, so a
|
|
6222
|
+
// new tool goes at the end to leave every existing session's prefix intact.
|
|
6223
|
+
loadSkillTool
|
|
6020
6224
|
];
|
|
6021
|
-
var CLEARABLE_TOOLS = new Set(
|
|
6022
|
-
ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
|
|
6023
|
-
);
|
|
6024
6225
|
var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
6025
6226
|
"visualDesignExpert",
|
|
6026
6227
|
"productVision",
|
|
@@ -6045,7 +6246,7 @@ function executeTool(name, input, context) {
|
|
|
6045
6246
|
}
|
|
6046
6247
|
|
|
6047
6248
|
// src/compaction/index.ts
|
|
6048
|
-
var
|
|
6249
|
+
var log9 = createLogger("compaction");
|
|
6049
6250
|
var CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
|
|
6050
6251
|
var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
6051
6252
|
var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
@@ -6094,7 +6295,7 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
6094
6295
|
if (text) {
|
|
6095
6296
|
summaries.push({ name, text });
|
|
6096
6297
|
} else {
|
|
6097
|
-
|
|
6298
|
+
log9.warn("Subagent summary unusable \u2014 leaving its history intact", {
|
|
6098
6299
|
name
|
|
6099
6300
|
});
|
|
6100
6301
|
}
|
|
@@ -6122,7 +6323,7 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
6122
6323
|
}
|
|
6123
6324
|
]
|
|
6124
6325
|
}));
|
|
6125
|
-
|
|
6326
|
+
log9.info("Compaction complete", {
|
|
6126
6327
|
summaries: summaries.length,
|
|
6127
6328
|
recentNarrativeChars: recent.length
|
|
6128
6329
|
});
|
|
@@ -6324,7 +6525,7 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
6324
6525
|
messagesToSummarize.slice(0, mid),
|
|
6325
6526
|
messagesToSummarize.slice(mid)
|
|
6326
6527
|
];
|
|
6327
|
-
|
|
6528
|
+
log9.info("Chunking summary", {
|
|
6328
6529
|
name,
|
|
6329
6530
|
messageCount: messagesToSummarize.length,
|
|
6330
6531
|
serializedLength: serialized.length,
|
|
@@ -6354,7 +6555,7 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
6354
6555
|
const parts = results.filter((p) => p !== null);
|
|
6355
6556
|
return parts.length > 0 ? parts.join("\n\n---\n\n") : null;
|
|
6356
6557
|
}
|
|
6357
|
-
|
|
6558
|
+
log9.info("Generating summary", {
|
|
6358
6559
|
name,
|
|
6359
6560
|
messageCount: messagesToSummarize.length,
|
|
6360
6561
|
serializedLength: serialized.length
|
|
@@ -6370,10 +6571,10 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
6370
6571
|
return null;
|
|
6371
6572
|
}
|
|
6372
6573
|
if (summaryText.length >= MIN_SUMMARY_CHARS) {
|
|
6373
|
-
|
|
6574
|
+
log9.info("Summary generated", { name, summaryLength: summaryText.length });
|
|
6374
6575
|
return summaryText;
|
|
6375
6576
|
}
|
|
6376
|
-
|
|
6577
|
+
log9.warn("Summary too short to be real", {
|
|
6377
6578
|
name,
|
|
6378
6579
|
summaryLength: summaryText.length,
|
|
6379
6580
|
minimum: MIN_SUMMARY_CHARS,
|
|
@@ -6429,21 +6630,21 @@ Write the summary of the conversation above, following your instructions.`;
|
|
|
6429
6630
|
toolNames: []
|
|
6430
6631
|
});
|
|
6431
6632
|
} else if (event.type === "error") {
|
|
6432
|
-
|
|
6633
|
+
log9.error("Summary generation failed", { name, error: event.error });
|
|
6433
6634
|
return null;
|
|
6434
6635
|
}
|
|
6435
6636
|
}
|
|
6436
6637
|
if (!summaryText.trim()) {
|
|
6437
|
-
|
|
6638
|
+
log9.warn("Empty summary generated", { name });
|
|
6438
6639
|
return null;
|
|
6439
6640
|
}
|
|
6440
6641
|
return summaryText.trim();
|
|
6441
6642
|
}
|
|
6442
6643
|
|
|
6443
6644
|
// src/session.ts
|
|
6444
|
-
import
|
|
6445
|
-
import
|
|
6446
|
-
var
|
|
6645
|
+
import fs21 from "fs";
|
|
6646
|
+
import path12 from "path";
|
|
6647
|
+
var log10 = createLogger("session");
|
|
6447
6648
|
var SESSION_FILE = ".remy-session.json";
|
|
6448
6649
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
6449
6650
|
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
@@ -6460,14 +6661,14 @@ var ARCHIVE_MSG_CACHE_MAX = 3;
|
|
|
6460
6661
|
function loadSession(state) {
|
|
6461
6662
|
pruneArchives();
|
|
6462
6663
|
try {
|
|
6463
|
-
const raw =
|
|
6664
|
+
const raw = fs21.readFileSync(SESSION_FILE, "utf-8");
|
|
6464
6665
|
const data = JSON.parse(raw);
|
|
6465
6666
|
if (data.models && typeof data.models === "object") {
|
|
6466
6667
|
state.models = data.models;
|
|
6467
6668
|
}
|
|
6468
6669
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
6469
6670
|
state.messages = sanitizeMessages(data.messages);
|
|
6470
|
-
|
|
6671
|
+
log10.info("Session loaded", {
|
|
6471
6672
|
messageCount: state.messages.length,
|
|
6472
6673
|
...state.models && { models: state.models }
|
|
6473
6674
|
});
|
|
@@ -6533,33 +6734,33 @@ function buildPayload(state) {
|
|
|
6533
6734
|
return payload;
|
|
6534
6735
|
}
|
|
6535
6736
|
function archiveMessages(messages, label, models) {
|
|
6536
|
-
|
|
6737
|
+
fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
6537
6738
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6538
6739
|
const count = messages.length;
|
|
6539
|
-
let dest =
|
|
6740
|
+
let dest = path12.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
|
|
6540
6741
|
let n = 1;
|
|
6541
|
-
while (
|
|
6542
|
-
dest =
|
|
6742
|
+
while (fs21.existsSync(dest)) {
|
|
6743
|
+
dest = path12.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
|
|
6543
6744
|
}
|
|
6544
6745
|
const payload = { messages };
|
|
6545
6746
|
if (models && Object.keys(models).length > 0) {
|
|
6546
6747
|
payload.models = models;
|
|
6547
6748
|
}
|
|
6548
|
-
|
|
6549
|
-
archiveCountCache.set(
|
|
6550
|
-
|
|
6749
|
+
fs21.writeFileSync(dest, JSON.stringify(payload), "utf-8");
|
|
6750
|
+
archiveCountCache.set(path12.basename(dest), count);
|
|
6751
|
+
log10.info("Session archived", { label, dest, messageCount: count });
|
|
6551
6752
|
pruneArchives();
|
|
6552
6753
|
return dest;
|
|
6553
6754
|
}
|
|
6554
6755
|
function pruneArchives() {
|
|
6555
6756
|
try {
|
|
6556
|
-
const entries =
|
|
6757
|
+
const entries = fs21.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
|
|
6557
6758
|
if (entries.length <= 1) {
|
|
6558
6759
|
return;
|
|
6559
6760
|
}
|
|
6560
6761
|
const archives = entries.map((name) => ({
|
|
6561
6762
|
name,
|
|
6562
|
-
size:
|
|
6763
|
+
size: fs21.statSync(path12.join(ARCHIVE_DIR, name)).size
|
|
6563
6764
|
})).sort(
|
|
6564
6765
|
(a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
|
|
6565
6766
|
);
|
|
@@ -6577,14 +6778,14 @@ function pruneArchives() {
|
|
|
6577
6778
|
let freed = 0;
|
|
6578
6779
|
for (let i = cut; i < archives.length; i++) {
|
|
6579
6780
|
try {
|
|
6580
|
-
|
|
6781
|
+
fs21.unlinkSync(path12.join(ARCHIVE_DIR, archives[i].name));
|
|
6581
6782
|
freed += archives[i].size;
|
|
6582
6783
|
removed++;
|
|
6583
6784
|
} catch {
|
|
6584
6785
|
}
|
|
6585
6786
|
}
|
|
6586
6787
|
if (removed > 0) {
|
|
6587
|
-
|
|
6788
|
+
log10.info("Session archives pruned", {
|
|
6588
6789
|
removed,
|
|
6589
6790
|
freedBytes: freed,
|
|
6590
6791
|
keptBytes: kept
|
|
@@ -6601,7 +6802,7 @@ function parseArchive(name) {
|
|
|
6601
6802
|
return cached3;
|
|
6602
6803
|
}
|
|
6603
6804
|
try {
|
|
6604
|
-
const raw =
|
|
6805
|
+
const raw = fs21.readFileSync(path12.join(ARCHIVE_DIR, name), "utf-8");
|
|
6605
6806
|
const data = JSON.parse(raw);
|
|
6606
6807
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
6607
6808
|
archiveCountCache.set(name, messages.length);
|
|
@@ -6615,7 +6816,7 @@ function parseArchive(name) {
|
|
|
6615
6816
|
}
|
|
6616
6817
|
return messages;
|
|
6617
6818
|
} catch (err) {
|
|
6618
|
-
|
|
6819
|
+
log10.warn("Session archive unreadable", { name, error: err?.message });
|
|
6619
6820
|
return null;
|
|
6620
6821
|
}
|
|
6621
6822
|
}
|
|
@@ -6639,7 +6840,7 @@ function readArchiveMessages(name) {
|
|
|
6639
6840
|
function listConversationArchives() {
|
|
6640
6841
|
let names;
|
|
6641
6842
|
try {
|
|
6642
|
-
names =
|
|
6843
|
+
names = fs21.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
|
|
6643
6844
|
} catch {
|
|
6644
6845
|
return { slots: [], archivedCount: 0 };
|
|
6645
6846
|
}
|
|
@@ -6738,7 +6939,7 @@ function rotate(state) {
|
|
|
6738
6939
|
}
|
|
6739
6940
|
archiveMessages(messages.slice(0, cut), "rotated", state.models);
|
|
6740
6941
|
state.messages = messages.slice(cut);
|
|
6741
|
-
|
|
6942
|
+
log10.info("Session rotated", {
|
|
6742
6943
|
archived: cut,
|
|
6743
6944
|
retained: state.messages.length
|
|
6744
6945
|
});
|
|
@@ -6750,10 +6951,10 @@ function saveSession(state) {
|
|
|
6750
6951
|
if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
|
|
6751
6952
|
serialized = JSON.stringify(buildPayload(state));
|
|
6752
6953
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6954
|
+
fs21.writeFileSync(SESSION_FILE, serialized, "utf-8");
|
|
6955
|
+
log10.info("Session saved", { messageCount: state.messages.length });
|
|
6755
6956
|
} catch (err) {
|
|
6756
|
-
|
|
6957
|
+
log10.warn("Session save failed", { error: err.message });
|
|
6757
6958
|
}
|
|
6758
6959
|
}
|
|
6759
6960
|
function clearSession(state) {
|
|
@@ -6762,22 +6963,22 @@ function clearSession(state) {
|
|
|
6762
6963
|
archiveMessages(state.messages, "cleared", state.models);
|
|
6763
6964
|
}
|
|
6764
6965
|
} catch (err) {
|
|
6765
|
-
|
|
6966
|
+
log10.warn("Session archive on clear failed", { error: err.message });
|
|
6766
6967
|
}
|
|
6767
6968
|
state.messages = [];
|
|
6768
6969
|
try {
|
|
6769
|
-
if (
|
|
6770
|
-
|
|
6970
|
+
if (fs21.existsSync(SESSION_FILE)) {
|
|
6971
|
+
fs21.unlinkSync(SESSION_FILE);
|
|
6771
6972
|
}
|
|
6772
6973
|
} catch (err) {
|
|
6773
|
-
|
|
6974
|
+
log10.warn("Session clear: could not remove live file", {
|
|
6774
6975
|
error: err.message
|
|
6775
6976
|
});
|
|
6776
6977
|
}
|
|
6777
6978
|
}
|
|
6778
6979
|
|
|
6779
6980
|
// src/compaction/trigger.ts
|
|
6780
|
-
var
|
|
6981
|
+
var log11 = createLogger("compaction:trigger");
|
|
6781
6982
|
var pending = null;
|
|
6782
6983
|
var inflightCompaction = null;
|
|
6783
6984
|
function applyPendingSummaries(state) {
|
|
@@ -6794,7 +6995,7 @@ function applyPendingSummaries(state) {
|
|
|
6794
6995
|
idx = at === -1 ? 0 : at + 1;
|
|
6795
6996
|
}
|
|
6796
6997
|
state.messages.splice(idx, 0, ...drained.checkpoints);
|
|
6797
|
-
|
|
6998
|
+
log11.info("Checkpoint applied", {
|
|
6798
6999
|
index: idx,
|
|
6799
7000
|
messageCount: state.messages.length
|
|
6800
7001
|
});
|
|
@@ -6809,7 +7010,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
6809
7010
|
return inflightCompaction;
|
|
6810
7011
|
}
|
|
6811
7012
|
if (pending) {
|
|
6812
|
-
|
|
7013
|
+
log11.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
6813
7014
|
return Promise.resolve();
|
|
6814
7015
|
}
|
|
6815
7016
|
const { blocking = false, requestId, model } = opts;
|
|
@@ -6821,11 +7022,11 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
6821
7022
|
).then((result) => {
|
|
6822
7023
|
pending = result;
|
|
6823
7024
|
listener?.({ type: "complete", requestId });
|
|
6824
|
-
|
|
7025
|
+
log11.info("Compaction complete");
|
|
6825
7026
|
}).catch((err) => {
|
|
6826
7027
|
const message = err.message || "Compaction failed";
|
|
6827
7028
|
listener?.({ type: "complete", error: message, requestId });
|
|
6828
|
-
|
|
7029
|
+
log11.error("Compaction failed", { error: message });
|
|
6829
7030
|
throw err;
|
|
6830
7031
|
}).finally(() => {
|
|
6831
7032
|
inflightCompaction = null;
|
|
@@ -6834,10 +7035,10 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
6834
7035
|
}
|
|
6835
7036
|
|
|
6836
7037
|
// src/brandExtraction/index.ts
|
|
6837
|
-
import
|
|
6838
|
-
import
|
|
7038
|
+
import fs22 from "fs";
|
|
7039
|
+
import path13 from "path";
|
|
6839
7040
|
import { createHash } from "crypto";
|
|
6840
|
-
var
|
|
7041
|
+
var log12 = createLogger("brandExtraction");
|
|
6841
7042
|
var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
6842
7043
|
var BRAND_FILE = ".remy-brand.json";
|
|
6843
7044
|
var CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -6845,28 +7046,28 @@ async function runExtraction(apiConfig, model) {
|
|
|
6845
7046
|
const inputHash = computeInputHash();
|
|
6846
7047
|
const cached3 = readCache();
|
|
6847
7048
|
if (cached3 && cached3.inputHash === inputHash) {
|
|
6848
|
-
|
|
7049
|
+
log12.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
6849
7050
|
return null;
|
|
6850
7051
|
}
|
|
6851
|
-
|
|
7052
|
+
log12.info("Extracting brand", { inputHash });
|
|
6852
7053
|
const brand = await extractBrand(apiConfig, model);
|
|
6853
7054
|
if (!brand) {
|
|
6854
|
-
|
|
7055
|
+
log12.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
6855
7056
|
return null;
|
|
6856
7057
|
}
|
|
6857
7058
|
persistBrand(brand, inputHash);
|
|
6858
|
-
|
|
7059
|
+
log12.info("Brand persisted", { inputHash });
|
|
6859
7060
|
return brand;
|
|
6860
7061
|
}
|
|
6861
7062
|
function isDedicatedBrandFile(filePath) {
|
|
6862
|
-
if (filePath.split(
|
|
7063
|
+
if (filePath.split(path13.sep).includes("@brand")) {
|
|
6863
7064
|
return true;
|
|
6864
7065
|
}
|
|
6865
|
-
const { type } =
|
|
7066
|
+
const { type } = parseFrontmatter4(filePath);
|
|
6866
7067
|
return type.startsWith("design/color") || type.startsWith("design/typography");
|
|
6867
7068
|
}
|
|
6868
7069
|
function isBrandRelevant(filePath) {
|
|
6869
|
-
return filePath ===
|
|
7070
|
+
return filePath === path13.join("src", "app.md") || isDedicatedBrandFile(filePath);
|
|
6870
7071
|
}
|
|
6871
7072
|
function computeInputHash() {
|
|
6872
7073
|
const entries = [];
|
|
@@ -6888,7 +7089,7 @@ function sha256(input) {
|
|
|
6888
7089
|
}
|
|
6889
7090
|
function readSafe(filePath) {
|
|
6890
7091
|
try {
|
|
6891
|
-
return
|
|
7092
|
+
return fs22.readFileSync(filePath, "utf-8");
|
|
6892
7093
|
} catch {
|
|
6893
7094
|
return "";
|
|
6894
7095
|
}
|
|
@@ -6914,9 +7115,9 @@ function readBrandManifest() {
|
|
|
6914
7115
|
function walkMdFiles3(dir) {
|
|
6915
7116
|
const results = [];
|
|
6916
7117
|
try {
|
|
6917
|
-
const entries =
|
|
7118
|
+
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
6918
7119
|
for (const entry of entries) {
|
|
6919
|
-
const full =
|
|
7120
|
+
const full = path13.join(dir, entry.name);
|
|
6920
7121
|
if (entry.isDirectory()) {
|
|
6921
7122
|
results.push(...walkMdFiles3(full));
|
|
6922
7123
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -6927,9 +7128,9 @@ function walkMdFiles3(dir) {
|
|
|
6927
7128
|
}
|
|
6928
7129
|
return results.sort();
|
|
6929
7130
|
}
|
|
6930
|
-
function
|
|
7131
|
+
function parseFrontmatter4(filePath) {
|
|
6931
7132
|
try {
|
|
6932
|
-
const content =
|
|
7133
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
6933
7134
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
6934
7135
|
if (!match) {
|
|
6935
7136
|
return { type: "" };
|
|
@@ -6944,7 +7145,7 @@ function parseFrontmatter3(filePath) {
|
|
|
6944
7145
|
async function extractBrand(apiConfig, model) {
|
|
6945
7146
|
const corpus = buildCorpus();
|
|
6946
7147
|
if (!corpus.trim()) {
|
|
6947
|
-
|
|
7148
|
+
log12.debug("No spec corpus \u2014 emitting empty brand");
|
|
6948
7149
|
return { version: 1 };
|
|
6949
7150
|
}
|
|
6950
7151
|
let responseText = "";
|
|
@@ -6975,17 +7176,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
6975
7176
|
toolNames: []
|
|
6976
7177
|
});
|
|
6977
7178
|
} else if (event.type === "error") {
|
|
6978
|
-
|
|
7179
|
+
log12.error("Brand extraction stream error", { error: event.error });
|
|
6979
7180
|
return null;
|
|
6980
7181
|
}
|
|
6981
7182
|
}
|
|
6982
7183
|
} catch (err) {
|
|
6983
|
-
|
|
7184
|
+
log12.error("Brand extraction threw", { error: err?.message });
|
|
6984
7185
|
return null;
|
|
6985
7186
|
}
|
|
6986
7187
|
const parsed = parseJsonResponse(responseText);
|
|
6987
7188
|
if (!parsed) {
|
|
6988
|
-
|
|
7189
|
+
log12.warn("Brand extraction returned unparseable JSON", {
|
|
6989
7190
|
preview: responseText.slice(0, 200)
|
|
6990
7191
|
});
|
|
6991
7192
|
return null;
|
|
@@ -7139,14 +7340,14 @@ function pickFont(raw) {
|
|
|
7139
7340
|
}
|
|
7140
7341
|
function persistBrand(brand, inputHash) {
|
|
7141
7342
|
const tmp = `${BRAND_FILE}.tmp`;
|
|
7142
|
-
|
|
7143
|
-
|
|
7343
|
+
fs22.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
7344
|
+
fs22.renameSync(tmp, BRAND_FILE);
|
|
7144
7345
|
const cache = { inputHash, generatedAt: Date.now() };
|
|
7145
|
-
|
|
7346
|
+
fs22.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7146
7347
|
}
|
|
7147
7348
|
function readCache() {
|
|
7148
7349
|
try {
|
|
7149
|
-
const raw =
|
|
7350
|
+
const raw = fs22.readFileSync(CACHE_FILE, "utf-8");
|
|
7150
7351
|
const parsed = JSON.parse(raw);
|
|
7151
7352
|
if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
|
|
7152
7353
|
return parsed;
|
|
@@ -7158,7 +7359,7 @@ function readCache() {
|
|
|
7158
7359
|
}
|
|
7159
7360
|
|
|
7160
7361
|
// src/brandExtraction/trigger.ts
|
|
7161
|
-
var
|
|
7362
|
+
var log13 = createLogger("brandExtraction:trigger");
|
|
7162
7363
|
var inflight = false;
|
|
7163
7364
|
var dirty = false;
|
|
7164
7365
|
function triggerBrandExtraction(apiConfig, model) {
|
|
@@ -7168,7 +7369,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
7168
7369
|
}
|
|
7169
7370
|
inflight = true;
|
|
7170
7371
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
7171
|
-
|
|
7372
|
+
log13.error("Brand extraction failed", { error: err?.message });
|
|
7172
7373
|
}).finally(() => {
|
|
7173
7374
|
inflight = false;
|
|
7174
7375
|
if (dirty) {
|
|
@@ -7376,7 +7577,7 @@ function friendlyError(raw) {
|
|
|
7376
7577
|
}
|
|
7377
7578
|
|
|
7378
7579
|
// src/agent.ts
|
|
7379
|
-
var
|
|
7580
|
+
var log14 = createLogger("agent");
|
|
7380
7581
|
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
7381
7582
|
function getTextContent(blocks) {
|
|
7382
7583
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
@@ -7430,12 +7631,11 @@ async function runTurn(params) {
|
|
|
7430
7631
|
onBackgroundComplete
|
|
7431
7632
|
} = params;
|
|
7432
7633
|
const tools2 = getToolDefinitions(onboardingState);
|
|
7433
|
-
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
7434
7634
|
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7435
7635
|
const baseline = resolveModel("parent", state.models, model);
|
|
7436
7636
|
const parentModel = buildModelOverride ?? baseline;
|
|
7437
7637
|
const modelOverride = buildModelOverride && buildModelOverride !== baseline ? { from: baseline } : void 0;
|
|
7438
|
-
|
|
7638
|
+
log14.info("Turn started", {
|
|
7439
7639
|
requestId,
|
|
7440
7640
|
model,
|
|
7441
7641
|
buildModel: buildModelOverride,
|
|
@@ -7606,7 +7806,6 @@ async function runTurn(params) {
|
|
|
7606
7806
|
system,
|
|
7607
7807
|
messages: cleanMessagesForApi(state.messages),
|
|
7608
7808
|
tools: tools2,
|
|
7609
|
-
excludeToolsFromClearing,
|
|
7610
7809
|
signal
|
|
7611
7810
|
},
|
|
7612
7811
|
{
|
|
@@ -7698,7 +7897,7 @@ async function runTurn(params) {
|
|
|
7698
7897
|
const acc = toolInputAccumulators.get(event.id);
|
|
7699
7898
|
const wasStreamed = acc?.started ?? false;
|
|
7700
7899
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
7701
|
-
|
|
7900
|
+
log14.info("Tool received", {
|
|
7702
7901
|
requestId,
|
|
7703
7902
|
toolCallId: event.id,
|
|
7704
7903
|
name: event.name
|
|
@@ -7821,7 +8020,7 @@ async function runTurn(params) {
|
|
|
7821
8020
|
});
|
|
7822
8021
|
return;
|
|
7823
8022
|
}
|
|
7824
|
-
|
|
8023
|
+
log14.info("Tools executing", {
|
|
7825
8024
|
requestId,
|
|
7826
8025
|
count: toolCalls.length,
|
|
7827
8026
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -7868,7 +8067,7 @@ async function runTurn(params) {
|
|
|
7868
8067
|
let result;
|
|
7869
8068
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
7870
8069
|
saveSession(state);
|
|
7871
|
-
|
|
8070
|
+
log14.info("Waiting for external tool result", {
|
|
7872
8071
|
requestId,
|
|
7873
8072
|
toolCallId: tc.id,
|
|
7874
8073
|
name: tc.name
|
|
@@ -7936,7 +8135,7 @@ async function runTurn(params) {
|
|
|
7936
8135
|
if (!isBackgroundCall(tc)) {
|
|
7937
8136
|
toolRegistry?.unregister(tc.id);
|
|
7938
8137
|
}
|
|
7939
|
-
|
|
8138
|
+
log14.info("Tool completed", {
|
|
7940
8139
|
requestId,
|
|
7941
8140
|
toolCallId: tc.id,
|
|
7942
8141
|
name: tc.name,
|
|
@@ -7999,13 +8198,13 @@ async function runTurn(params) {
|
|
|
7999
8198
|
// src/headless/attachments.ts
|
|
8000
8199
|
import { mkdirSync, existsSync } from "fs";
|
|
8001
8200
|
import { writeFile } from "fs/promises";
|
|
8002
|
-
import { basename, join, extname } from "path";
|
|
8003
|
-
var
|
|
8201
|
+
import { basename as basename2, join, extname as extname2 } from "path";
|
|
8202
|
+
var log15 = createLogger("headless:attachments");
|
|
8004
8203
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
8005
8204
|
function filenameFromUrl(url) {
|
|
8006
8205
|
try {
|
|
8007
8206
|
const pathname = new URL(url).pathname;
|
|
8008
|
-
const name =
|
|
8207
|
+
const name = basename2(pathname);
|
|
8009
8208
|
return name && name !== "/" ? decodeURIComponent(name) : `upload-${Date.now()}`;
|
|
8010
8209
|
} catch {
|
|
8011
8210
|
return `upload-${Date.now()}`;
|
|
@@ -8016,7 +8215,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8016
8215
|
if (isFree(name)) {
|
|
8017
8216
|
return name;
|
|
8018
8217
|
}
|
|
8019
|
-
const ext =
|
|
8218
|
+
const ext = extname2(name);
|
|
8020
8219
|
const base = name.slice(0, name.length - ext.length);
|
|
8021
8220
|
let counter = 1;
|
|
8022
8221
|
while (!isFree(`${base}-${counter}${ext}`)) {
|
|
@@ -8027,7 +8226,7 @@ function resolveUniqueFilename(name, claimed) {
|
|
|
8027
8226
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
8028
8227
|
function isImageAttachment(att) {
|
|
8029
8228
|
const name = att.filename || filenameFromUrl(att.url);
|
|
8030
|
-
return IMAGE_EXTENSIONS.has(
|
|
8229
|
+
return IMAGE_EXTENSIONS.has(extname2(name).toLowerCase());
|
|
8031
8230
|
}
|
|
8032
8231
|
async function persistAttachments(attachments) {
|
|
8033
8232
|
const nonVoice = attachments.filter((a) => !a.isVoice);
|
|
@@ -8056,7 +8255,7 @@ async function persistAttachments(attachments) {
|
|
|
8056
8255
|
}
|
|
8057
8256
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
8058
8257
|
await writeFile(localPath, buffer);
|
|
8059
|
-
|
|
8258
|
+
log15.info("Attachment saved", {
|
|
8060
8259
|
filename: name,
|
|
8061
8260
|
path: localPath,
|
|
8062
8261
|
bytes: buffer.length
|
|
@@ -8070,7 +8269,7 @@ async function persistAttachments(attachments) {
|
|
|
8070
8269
|
if (textRes.ok) {
|
|
8071
8270
|
extractedTextPath = `${localPath}.txt`;
|
|
8072
8271
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
8073
|
-
|
|
8272
|
+
log15.info("Extracted text saved", { path: extractedTextPath });
|
|
8074
8273
|
}
|
|
8075
8274
|
} catch {
|
|
8076
8275
|
}
|
|
@@ -8293,7 +8492,7 @@ function getActionChain(startName) {
|
|
|
8293
8492
|
}
|
|
8294
8493
|
|
|
8295
8494
|
// src/headless/index.ts
|
|
8296
|
-
var
|
|
8495
|
+
var log16 = createLogger("headless");
|
|
8297
8496
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
8298
8497
|
var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
8299
8498
|
"promptUser",
|
|
@@ -8438,7 +8637,7 @@ var HeadlessSession = class {
|
|
|
8438
8637
|
}
|
|
8439
8638
|
const line = JSON.stringify(payload) + "\n";
|
|
8440
8639
|
if (event === "history") {
|
|
8441
|
-
|
|
8640
|
+
log16.info("Wrote history event to stdout", {
|
|
8442
8641
|
requestId,
|
|
8443
8642
|
bytes: line.length
|
|
8444
8643
|
});
|
|
@@ -8515,7 +8714,7 @@ var HeadlessSession = class {
|
|
|
8515
8714
|
if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
|
|
8516
8715
|
return;
|
|
8517
8716
|
}
|
|
8518
|
-
|
|
8717
|
+
log16.info("Forced compaction gate triggered", {
|
|
8519
8718
|
contextSize: this.sessionStats.lastContextSize,
|
|
8520
8719
|
threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
|
|
8521
8720
|
requestId
|
|
@@ -8532,7 +8731,7 @@ var HeadlessSession = class {
|
|
|
8532
8731
|
}
|
|
8533
8732
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
8534
8733
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
8535
|
-
|
|
8734
|
+
log16.info("Background complete", {
|
|
8536
8735
|
toolCallId,
|
|
8537
8736
|
name,
|
|
8538
8737
|
requestId: this.currentRequestId
|
|
@@ -8569,17 +8768,17 @@ var HeadlessSession = class {
|
|
|
8569
8768
|
return Promise.resolve(early);
|
|
8570
8769
|
}
|
|
8571
8770
|
const shouldTimeout = !USER_FACING_TOOLS.has(name);
|
|
8572
|
-
return new Promise((
|
|
8771
|
+
return new Promise((resolve3) => {
|
|
8573
8772
|
const timeout = shouldTimeout ? setTimeout(() => {
|
|
8574
8773
|
this.pendingTools.delete(id);
|
|
8575
|
-
|
|
8774
|
+
resolve3(
|
|
8576
8775
|
"Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
|
|
8577
8776
|
);
|
|
8578
8777
|
}, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
|
|
8579
8778
|
this.pendingTools.set(id, {
|
|
8580
8779
|
resolve: (result) => {
|
|
8581
8780
|
clearTimeout(timeout);
|
|
8582
|
-
|
|
8781
|
+
resolve3(result);
|
|
8583
8782
|
},
|
|
8584
8783
|
timeout
|
|
8585
8784
|
});
|
|
@@ -8770,7 +8969,7 @@ var HeadlessSession = class {
|
|
|
8770
8969
|
await this.runForcedCompactionIfNeeded(requestId);
|
|
8771
8970
|
const attachments = parsed.attachments;
|
|
8772
8971
|
if (attachments?.length) {
|
|
8773
|
-
|
|
8972
|
+
log16.info("Message has attachments", {
|
|
8774
8973
|
count: attachments.length,
|
|
8775
8974
|
urls: attachments.map((a) => a.url)
|
|
8776
8975
|
});
|
|
@@ -8786,7 +8985,7 @@ var HeadlessSession = class {
|
|
|
8786
8985
|
attachmentHeader = header;
|
|
8787
8986
|
}
|
|
8788
8987
|
} catch (err) {
|
|
8789
|
-
|
|
8988
|
+
log16.warn("Attachment persistence failed", { error: err.message });
|
|
8790
8989
|
}
|
|
8791
8990
|
}
|
|
8792
8991
|
let resolved = null;
|
|
@@ -8850,7 +9049,7 @@ var HeadlessSession = class {
|
|
|
8850
9049
|
error: "Turn ended unexpectedly"
|
|
8851
9050
|
});
|
|
8852
9051
|
}
|
|
8853
|
-
|
|
9052
|
+
log16.info("Turn complete", {
|
|
8854
9053
|
requestId,
|
|
8855
9054
|
durationMs: Date.now() - this.turnStart
|
|
8856
9055
|
});
|
|
@@ -8862,7 +9061,7 @@ var HeadlessSession = class {
|
|
|
8862
9061
|
error: err.message
|
|
8863
9062
|
});
|
|
8864
9063
|
}
|
|
8865
|
-
|
|
9064
|
+
log16.warn("Command failed", {
|
|
8866
9065
|
action: "message",
|
|
8867
9066
|
requestId,
|
|
8868
9067
|
error: err.message
|
|
@@ -9019,7 +9218,7 @@ var HeadlessSession = class {
|
|
|
9019
9218
|
try {
|
|
9020
9219
|
parsed = JSON.parse(line);
|
|
9021
9220
|
} catch (err) {
|
|
9022
|
-
|
|
9221
|
+
log16.warn("Invalid JSON on stdin", {
|
|
9023
9222
|
error: err.message,
|
|
9024
9223
|
lineLength: line.length,
|
|
9025
9224
|
preview: line.slice(0, 200)
|
|
@@ -9028,7 +9227,7 @@ var HeadlessSession = class {
|
|
|
9028
9227
|
return;
|
|
9029
9228
|
}
|
|
9030
9229
|
const { action, requestId } = parsed;
|
|
9031
|
-
|
|
9230
|
+
log16.info("Command received", { action, requestId });
|
|
9032
9231
|
if (action === "tool_result" && parsed.id) {
|
|
9033
9232
|
const id = parsed.id;
|
|
9034
9233
|
const result = parsed.result ?? "";
|
|
@@ -9037,7 +9236,7 @@ var HeadlessSession = class {
|
|
|
9037
9236
|
this.pendingTools.delete(id);
|
|
9038
9237
|
pending2.resolve(result);
|
|
9039
9238
|
} else if (!this.running) {
|
|
9040
|
-
|
|
9239
|
+
log16.info("Late tool_result while idle, dismissing", { id });
|
|
9041
9240
|
this.emit("completed", { success: true }, requestId);
|
|
9042
9241
|
} else {
|
|
9043
9242
|
this.earlyResults.set(id, result);
|
|
@@ -9050,7 +9249,7 @@ var HeadlessSession = class {
|
|
|
9050
9249
|
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
9051
9250
|
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
9052
9251
|
});
|
|
9053
|
-
|
|
9252
|
+
log16.info("History response", {
|
|
9054
9253
|
requestId,
|
|
9055
9254
|
startIndex: page.startIndex,
|
|
9056
9255
|
endIndex: page.endIndex,
|