@mindstudio-ai/remy 0.1.136 → 0.1.138
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/automatedActions/buildFromInitialSpec.md +1 -1
- package/dist/automatedActions/buildFromRoadmap.md +1 -1
- package/dist/automatedActions/debugRequest.md +9 -0
- package/dist/headless.js +98 -47
- package/dist/index.js +1980 -1911
- package/dist/prompt/static/team.md +2 -2
- package/dist/subagents/codeSanityCheck/prompt.md +14 -5
- package/dist/subagents/designExpert/prompts/instructions.md +1 -0
- package/dist/subagents/designExpert/prompts/ui-patterns.md +45 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1278,1276 +1278,1958 @@ var init_setProjectMetadata = __esm({
|
|
|
1278
1278
|
}
|
|
1279
1279
|
});
|
|
1280
1280
|
|
|
1281
|
-
// src/
|
|
1282
|
-
import fs6 from "fs
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1281
|
+
// src/assets.ts
|
|
1282
|
+
import fs6 from "fs";
|
|
1283
|
+
import path3 from "path";
|
|
1284
|
+
function findRoot(start) {
|
|
1285
|
+
let dir = start;
|
|
1286
|
+
while (dir !== path3.dirname(dir)) {
|
|
1287
|
+
if (fs6.existsSync(path3.join(dir, "package.json"))) {
|
|
1288
|
+
return dir;
|
|
1288
1289
|
}
|
|
1290
|
+
dir = path3.dirname(dir);
|
|
1289
1291
|
}
|
|
1290
|
-
return
|
|
1292
|
+
return start;
|
|
1291
1293
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1294
|
+
function assetPath(...segments) {
|
|
1295
|
+
return path3.join(ASSETS_BASE, ...segments);
|
|
1296
|
+
}
|
|
1297
|
+
function readAsset(...segments) {
|
|
1298
|
+
const full = assetPath(...segments);
|
|
1299
|
+
try {
|
|
1300
|
+
return fs6.readFileSync(full, "utf-8").trim();
|
|
1301
|
+
} catch {
|
|
1302
|
+
throw new Error(`Required asset missing: ${full}`);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
function readJsonAsset(fallback, ...segments) {
|
|
1306
|
+
const full = assetPath(...segments);
|
|
1307
|
+
try {
|
|
1308
|
+
return JSON.parse(fs6.readFileSync(full, "utf-8"));
|
|
1309
|
+
} catch {
|
|
1310
|
+
return fallback;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
var ROOT, ASSETS_BASE;
|
|
1314
|
+
var init_assets = __esm({
|
|
1315
|
+
"src/assets.ts"() {
|
|
1295
1316
|
"use strict";
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
name: "readFile",
|
|
1301
|
-
description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
|
|
1302
|
-
inputSchema: {
|
|
1303
|
-
type: "object",
|
|
1304
|
-
properties: {
|
|
1305
|
-
path: {
|
|
1306
|
-
type: "string",
|
|
1307
|
-
description: "The file path to read, relative to the project root."
|
|
1308
|
-
},
|
|
1309
|
-
offset: {
|
|
1310
|
-
type: "number",
|
|
1311
|
-
description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
|
|
1312
|
-
},
|
|
1313
|
-
maxLines: {
|
|
1314
|
-
type: "number",
|
|
1315
|
-
description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
|
|
1316
|
-
}
|
|
1317
|
-
},
|
|
1318
|
-
required: ["path"]
|
|
1319
|
-
}
|
|
1320
|
-
},
|
|
1321
|
-
async execute(input) {
|
|
1322
|
-
try {
|
|
1323
|
-
const buffer = await fs6.readFile(input.path);
|
|
1324
|
-
if (isBinary(buffer)) {
|
|
1325
|
-
const size = buffer.length;
|
|
1326
|
-
const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
|
|
1327
|
-
return `Error: ${input.path} appears to be a binary file (${unit}). Use bash to inspect it if needed.`;
|
|
1328
|
-
}
|
|
1329
|
-
const content = buffer.toString("utf-8");
|
|
1330
|
-
const allLines = content.split("\n");
|
|
1331
|
-
const totalLines = allLines.length;
|
|
1332
|
-
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
1333
|
-
let startIdx;
|
|
1334
|
-
if (input.offset && input.offset < 0) {
|
|
1335
|
-
startIdx = Math.max(0, totalLines + input.offset);
|
|
1336
|
-
} else {
|
|
1337
|
-
startIdx = Math.max(0, (input.offset || 1) - 1);
|
|
1338
|
-
}
|
|
1339
|
-
const sliced = allLines.slice(startIdx, startIdx + maxLines);
|
|
1340
|
-
const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
|
|
1341
|
-
let result = numbered;
|
|
1342
|
-
const endLine = startIdx + sliced.length;
|
|
1343
|
-
const displayStart = startIdx + 1;
|
|
1344
|
-
if (endLine < totalLines) {
|
|
1345
|
-
result += `
|
|
1346
|
-
|
|
1347
|
-
(showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
|
|
1348
|
-
}
|
|
1349
|
-
return result;
|
|
1350
|
-
} catch (err) {
|
|
1351
|
-
return `Error reading file: ${err.message}`;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
};
|
|
1317
|
+
ROOT = findRoot(
|
|
1318
|
+
import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
|
|
1319
|
+
);
|
|
1320
|
+
ASSETS_BASE = fs6.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
|
|
1355
1321
|
}
|
|
1356
1322
|
});
|
|
1357
1323
|
|
|
1358
|
-
// src/
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
content: {
|
|
1380
|
-
type: "string",
|
|
1381
|
-
description: "The full content to write to the file."
|
|
1382
|
-
}
|
|
1383
|
-
},
|
|
1384
|
-
required: ["path", "content"]
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
streaming: /* @__PURE__ */ (() => {
|
|
1388
|
-
let lastNewlineCount = 0;
|
|
1389
|
-
let lastPath = "";
|
|
1390
|
-
return {
|
|
1391
|
-
transform: async (partial) => {
|
|
1392
|
-
const newlineCount = partial.content.split("\n").length - 1;
|
|
1393
|
-
if (partial.path !== lastPath || newlineCount < lastNewlineCount) {
|
|
1394
|
-
lastNewlineCount = 0;
|
|
1395
|
-
lastPath = partial.path;
|
|
1396
|
-
}
|
|
1397
|
-
if (newlineCount <= lastNewlineCount) {
|
|
1398
|
-
return null;
|
|
1399
|
-
}
|
|
1400
|
-
lastNewlineCount = newlineCount;
|
|
1401
|
-
const lastNewline = partial.content.lastIndexOf("\n");
|
|
1402
|
-
const completeContent = partial.content.substring(0, lastNewline + 1);
|
|
1403
|
-
const oldContent = await fs7.readFile(partial.path, "utf-8").catch(() => "");
|
|
1404
|
-
return `Writing ${partial.path} (${newlineCount} lines)
|
|
1405
|
-
${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
1406
|
-
}
|
|
1407
|
-
};
|
|
1408
|
-
})(),
|
|
1409
|
-
async execute(input) {
|
|
1410
|
-
const release = await acquireFileLock(input.path);
|
|
1411
|
-
try {
|
|
1412
|
-
await fs7.mkdir(path3.dirname(input.path), { recursive: true });
|
|
1413
|
-
let oldContent = null;
|
|
1414
|
-
try {
|
|
1415
|
-
oldContent = await fs7.readFile(input.path, "utf-8");
|
|
1416
|
-
} catch {
|
|
1417
|
-
}
|
|
1418
|
-
await fs7.writeFile(input.path, input.content, "utf-8");
|
|
1419
|
-
const lineCount = input.content.split("\n").length;
|
|
1420
|
-
const label = oldContent !== null ? "Wrote" : "Created";
|
|
1421
|
-
return `${label} ${input.path} (${lineCount} lines)
|
|
1422
|
-
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
1423
|
-
} catch (err) {
|
|
1424
|
-
return `Error writing file: ${err.message}`;
|
|
1425
|
-
} finally {
|
|
1426
|
-
release();
|
|
1324
|
+
// src/compaction/index.ts
|
|
1325
|
+
async function compactConversation(state, apiConfig, system, tools2) {
|
|
1326
|
+
const insertionIndex = findSafeInsertionPoint(state.messages);
|
|
1327
|
+
const summaries = [];
|
|
1328
|
+
const tasks = [];
|
|
1329
|
+
const conversationMessages = getConversationMessagesForSummary(
|
|
1330
|
+
state.messages,
|
|
1331
|
+
insertionIndex
|
|
1332
|
+
);
|
|
1333
|
+
if (conversationMessages.length > 0) {
|
|
1334
|
+
tasks.push(
|
|
1335
|
+
generateSummary(
|
|
1336
|
+
apiConfig,
|
|
1337
|
+
"conversation",
|
|
1338
|
+
CONVERSATION_SUMMARY_PROMPT,
|
|
1339
|
+
conversationMessages,
|
|
1340
|
+
system,
|
|
1341
|
+
tools2
|
|
1342
|
+
).then((text) => {
|
|
1343
|
+
if (text) {
|
|
1344
|
+
summaries.push({ name: "conversation", text });
|
|
1427
1345
|
}
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1346
|
+
})
|
|
1347
|
+
);
|
|
1430
1348
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
if (
|
|
1438
|
-
|
|
1349
|
+
for (const name of SUMMARIZABLE_SUBAGENTS) {
|
|
1350
|
+
const subagentMessages = getSubAgentMessagesForSummary(
|
|
1351
|
+
state.messages,
|
|
1352
|
+
name,
|
|
1353
|
+
insertionIndex
|
|
1354
|
+
);
|
|
1355
|
+
if (subagentMessages.length > 0) {
|
|
1356
|
+
tasks.push(
|
|
1357
|
+
generateSummary(
|
|
1358
|
+
apiConfig,
|
|
1359
|
+
name,
|
|
1360
|
+
SUBAGENT_SUMMARY_PROMPT,
|
|
1361
|
+
subagentMessages,
|
|
1362
|
+
system,
|
|
1363
|
+
tools2
|
|
1364
|
+
).then((text) => {
|
|
1365
|
+
if (text) {
|
|
1366
|
+
summaries.push({ name, text });
|
|
1367
|
+
}
|
|
1368
|
+
})
|
|
1369
|
+
);
|
|
1439
1370
|
}
|
|
1440
1371
|
}
|
|
1441
|
-
|
|
1372
|
+
await Promise.all(tasks);
|
|
1373
|
+
const checkpointMessages = summaries.map((s) => ({
|
|
1374
|
+
role: "user",
|
|
1375
|
+
hidden: true,
|
|
1376
|
+
content: [
|
|
1377
|
+
{
|
|
1378
|
+
type: "summary",
|
|
1379
|
+
name: s.name,
|
|
1380
|
+
text: s.text,
|
|
1381
|
+
startedAt: Date.now()
|
|
1382
|
+
}
|
|
1383
|
+
]
|
|
1384
|
+
}));
|
|
1385
|
+
if (checkpointMessages.length > 0) {
|
|
1386
|
+
state.messages.splice(insertionIndex, 0, ...checkpointMessages);
|
|
1387
|
+
}
|
|
1388
|
+
log2.info("Compaction complete", {
|
|
1389
|
+
summaries: summaries.length,
|
|
1390
|
+
insertionIndex,
|
|
1391
|
+
messagesAfter: state.messages.length - insertionIndex - checkpointMessages.length
|
|
1392
|
+
});
|
|
1442
1393
|
}
|
|
1443
|
-
function
|
|
1444
|
-
let
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
lo = mid;
|
|
1394
|
+
function findSafeInsertionPoint(messages) {
|
|
1395
|
+
let idx = messages.length;
|
|
1396
|
+
while (idx > 0) {
|
|
1397
|
+
const msg = messages[idx - 1];
|
|
1398
|
+
if (msg.role === "user" && msg.toolCallId) {
|
|
1399
|
+
idx--;
|
|
1450
1400
|
} else {
|
|
1451
|
-
|
|
1401
|
+
break;
|
|
1452
1402
|
}
|
|
1453
1403
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
while (pos <= content.length - searchString.length) {
|
|
1464
|
-
const idx = content.indexOf(searchString, pos);
|
|
1465
|
-
if (idx === -1) {
|
|
1466
|
-
break;
|
|
1404
|
+
if (idx < messages.length && idx > 0) {
|
|
1405
|
+
const msg = messages[idx - 1];
|
|
1406
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
1407
|
+
const hasToolUse = msg.content.some(
|
|
1408
|
+
(b) => b.type === "tool"
|
|
1409
|
+
);
|
|
1410
|
+
if (hasToolUse) {
|
|
1411
|
+
idx--;
|
|
1412
|
+
}
|
|
1467
1413
|
}
|
|
1468
|
-
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
1469
|
-
pos = idx + 1;
|
|
1470
1414
|
}
|
|
1471
|
-
return
|
|
1415
|
+
return idx;
|
|
1472
1416
|
}
|
|
1473
|
-
function
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1417
|
+
function getConversationMessagesForSummary(messages, endIndex) {
|
|
1418
|
+
let startIdx = 0;
|
|
1419
|
+
for (let i = endIndex - 1; i >= 0; i--) {
|
|
1420
|
+
const msg = messages[i];
|
|
1421
|
+
if (!Array.isArray(msg.content)) {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
for (const block of msg.content) {
|
|
1425
|
+
if (block.type === "summary" && block.name === "conversation") {
|
|
1426
|
+
startIdx = i + 1;
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (startIdx > 0) {
|
|
1431
|
+
break;
|
|
1432
|
+
}
|
|
1478
1433
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1434
|
+
return messages.slice(startIdx, endIndex);
|
|
1435
|
+
}
|
|
1436
|
+
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
1437
|
+
let checkpointIdx = -1;
|
|
1438
|
+
for (let i = endIndex - 1; i >= 0; i--) {
|
|
1439
|
+
const msg = messages[i];
|
|
1440
|
+
if (!Array.isArray(msg.content)) {
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
for (const block of msg.content) {
|
|
1444
|
+
if (block.type === "summary" && block.name === subAgentName) {
|
|
1445
|
+
checkpointIdx = i;
|
|
1485
1446
|
break;
|
|
1486
1447
|
}
|
|
1487
1448
|
}
|
|
1488
|
-
if (
|
|
1489
|
-
|
|
1449
|
+
if (checkpointIdx !== -1) {
|
|
1450
|
+
break;
|
|
1490
1451
|
}
|
|
1491
1452
|
}
|
|
1492
|
-
|
|
1453
|
+
const startIdx = checkpointIdx !== -1 ? checkpointIdx + 1 : 0;
|
|
1454
|
+
const collected = [];
|
|
1455
|
+
for (let i = startIdx; i < endIndex; i++) {
|
|
1456
|
+
const msg = messages[i];
|
|
1457
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
for (const block of msg.content) {
|
|
1461
|
+
if (block.type === "tool" && block.name === subAgentName && block.subAgentMessages?.length) {
|
|
1462
|
+
collected.push(...block.subAgentMessages);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return collected;
|
|
1467
|
+
}
|
|
1468
|
+
function serializeForSummary(messages) {
|
|
1469
|
+
return messages.map((msg) => {
|
|
1470
|
+
if (typeof msg.content === "string") {
|
|
1471
|
+
return `[${msg.role}]: ${msg.content}`;
|
|
1472
|
+
}
|
|
1473
|
+
if (!Array.isArray(msg.content)) {
|
|
1474
|
+
return `[${msg.role}]: (empty)`;
|
|
1475
|
+
}
|
|
1476
|
+
const blocks = msg.content;
|
|
1477
|
+
const parts = [];
|
|
1478
|
+
for (const block of blocks) {
|
|
1479
|
+
if (block.type === "text") {
|
|
1480
|
+
parts.push(block.text);
|
|
1481
|
+
} else if (block.type === "tool") {
|
|
1482
|
+
parts.push(
|
|
1483
|
+
`[tool: ${block.name}(${JSON.stringify(block.input).slice(0, 200)})] \u2192 ${(block.result ?? "").slice(0, 500)}`
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return `[${msg.role}]: ${parts.join("\n")}`;
|
|
1488
|
+
}).join("\n\n");
|
|
1489
|
+
}
|
|
1490
|
+
async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, mainSystem, mainTools) {
|
|
1491
|
+
const serialized = serializeForSummary(messagesToSummarize);
|
|
1492
|
+
if (!serialized.trim()) {
|
|
1493
1493
|
return null;
|
|
1494
1494
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1495
|
+
log2.info("Generating summary", {
|
|
1496
|
+
name,
|
|
1497
|
+
messageCount: messagesToSummarize.length,
|
|
1498
|
+
cacheReuse: !!mainSystem
|
|
1499
|
+
});
|
|
1500
|
+
let summaryText = "";
|
|
1501
|
+
const useMainCache = !!mainSystem;
|
|
1502
|
+
const system = useMainCache ? mainSystem : compactionPrompt;
|
|
1503
|
+
const tools2 = useMainCache ? mainTools ?? [] : [];
|
|
1504
|
+
const userContent = useMainCache ? `${compactionPrompt}
|
|
1505
|
+
|
|
1506
|
+
---
|
|
1507
|
+
|
|
1508
|
+
Conversation to summarize:
|
|
1509
|
+
|
|
1510
|
+
${serialized}` : serialized;
|
|
1511
|
+
for await (const event of streamChat({
|
|
1512
|
+
...apiConfig,
|
|
1513
|
+
subAgentId: "conversationSummarizer",
|
|
1514
|
+
system,
|
|
1515
|
+
messages: [{ role: "user", content: userContent }],
|
|
1516
|
+
tools: tools2
|
|
1517
|
+
})) {
|
|
1518
|
+
if (event.type === "text") {
|
|
1519
|
+
summaryText += event.text;
|
|
1520
|
+
} else if (event.type === "error") {
|
|
1521
|
+
log2.error("Summary generation failed", { name, error: event.error });
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
if (!summaryText.trim()) {
|
|
1526
|
+
log2.warn("Empty summary generated", { name });
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1529
|
+
log2.info("Summary generated", { name, summaryLength: summaryText.length });
|
|
1530
|
+
return summaryText.trim();
|
|
1504
1531
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1532
|
+
var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS;
|
|
1533
|
+
var init_compaction = __esm({
|
|
1534
|
+
"src/compaction/index.ts"() {
|
|
1535
|
+
"use strict";
|
|
1536
|
+
init_api();
|
|
1537
|
+
init_assets();
|
|
1538
|
+
init_logger();
|
|
1539
|
+
log2 = createLogger("compaction");
|
|
1540
|
+
CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
|
|
1541
|
+
SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
1542
|
+
SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
// src/tools/_helpers/sidecar.ts
|
|
1547
|
+
function setSidecarBaseUrl(url) {
|
|
1548
|
+
baseUrl = url;
|
|
1549
|
+
log3.info("Configured", { url });
|
|
1507
1550
|
}
|
|
1508
|
-
function
|
|
1509
|
-
return
|
|
1551
|
+
function isSidecarConfigured() {
|
|
1552
|
+
return baseUrl !== null;
|
|
1510
1553
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1554
|
+
async function sidecarRequest(endpoint, body = {}, options) {
|
|
1555
|
+
if (!baseUrl) {
|
|
1556
|
+
throw new Error("Sidecar not available");
|
|
1557
|
+
}
|
|
1558
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1559
|
+
try {
|
|
1560
|
+
const res = await fetch(url, {
|
|
1561
|
+
method: "POST",
|
|
1562
|
+
headers: { "Content-Type": "application/json" },
|
|
1563
|
+
body: JSON.stringify(body),
|
|
1564
|
+
signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0
|
|
1565
|
+
});
|
|
1566
|
+
if (!res.ok) {
|
|
1567
|
+
log3.error("Sidecar error", { endpoint, status: res.status });
|
|
1568
|
+
throw new Error(`Sidecar error: ${res.status}`);
|
|
1569
|
+
}
|
|
1570
|
+
const data = await res.json();
|
|
1571
|
+
if (data?.success === false) {
|
|
1572
|
+
const code = data.errorCode ? ` [${data.errorCode}]` : "";
|
|
1573
|
+
throw new Error(`${data.error || "Unknown error"}${code}`);
|
|
1574
|
+
}
|
|
1575
|
+
return data;
|
|
1576
|
+
} catch (err) {
|
|
1577
|
+
if (err.message.startsWith("Sidecar error")) {
|
|
1578
|
+
throw err;
|
|
1579
|
+
}
|
|
1580
|
+
log3.error("Sidecar connection error", { endpoint, error: err.message });
|
|
1581
|
+
throw new Error(`Sidecar connection error: ${err.message}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
var log3, baseUrl;
|
|
1585
|
+
var init_sidecar = __esm({
|
|
1586
|
+
"src/tools/_helpers/sidecar.ts"() {
|
|
1513
1587
|
"use strict";
|
|
1588
|
+
init_logger();
|
|
1589
|
+
log3 = createLogger("sidecar");
|
|
1590
|
+
baseUrl = null;
|
|
1514
1591
|
}
|
|
1515
1592
|
});
|
|
1516
1593
|
|
|
1517
|
-
// src/tools/
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1594
|
+
// src/tools/_helpers/lsp.ts
|
|
1595
|
+
async function lspRequest(endpoint, body) {
|
|
1596
|
+
return sidecarRequest(endpoint, body);
|
|
1597
|
+
}
|
|
1598
|
+
var setLspBaseUrl, isLspConfigured;
|
|
1599
|
+
var init_lsp = __esm({
|
|
1600
|
+
"src/tools/_helpers/lsp.ts"() {
|
|
1522
1601
|
"use strict";
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
editFileTool = {
|
|
1527
|
-
clearable: true,
|
|
1528
|
-
definition: {
|
|
1529
|
-
name: "editFile",
|
|
1530
|
-
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.",
|
|
1531
|
-
inputSchema: {
|
|
1532
|
-
type: "object",
|
|
1533
|
-
properties: {
|
|
1534
|
-
path: {
|
|
1535
|
-
type: "string",
|
|
1536
|
-
description: "The file path to edit, relative to the project root."
|
|
1537
|
-
},
|
|
1538
|
-
old_string: {
|
|
1539
|
-
type: "string",
|
|
1540
|
-
description: "The exact string to find and replace. Must be unique in the file unless replace_all is true."
|
|
1541
|
-
},
|
|
1542
|
-
new_string: {
|
|
1543
|
-
type: "string",
|
|
1544
|
-
description: "The replacement string."
|
|
1545
|
-
},
|
|
1546
|
-
replace_all: {
|
|
1547
|
-
type: "boolean",
|
|
1548
|
-
description: "If true, replace every occurrence of old_string in the file. Defaults to false."
|
|
1549
|
-
}
|
|
1550
|
-
},
|
|
1551
|
-
required: ["path", "old_string", "new_string"]
|
|
1552
|
-
}
|
|
1553
|
-
},
|
|
1554
|
-
async execute(input) {
|
|
1555
|
-
const release = await acquireFileLock(input.path);
|
|
1556
|
-
try {
|
|
1557
|
-
const content = await fs8.readFile(input.path, "utf-8");
|
|
1558
|
-
const { old_string, new_string, replace_all } = input;
|
|
1559
|
-
const occurrences = findOccurrences(content, old_string);
|
|
1560
|
-
if (replace_all) {
|
|
1561
|
-
if (occurrences.length === 0) {
|
|
1562
|
-
return `Error: old_string not found in ${input.path}.`;
|
|
1563
|
-
}
|
|
1564
|
-
let updated = content;
|
|
1565
|
-
for (let i = occurrences.length - 1; i >= 0; i--) {
|
|
1566
|
-
updated = replaceAt(
|
|
1567
|
-
updated,
|
|
1568
|
-
occurrences[i].index,
|
|
1569
|
-
old_string.length,
|
|
1570
|
-
new_string
|
|
1571
|
-
);
|
|
1572
|
-
}
|
|
1573
|
-
await fs8.writeFile(input.path, updated, "utf-8");
|
|
1574
|
-
return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
1575
|
-
${unifiedDiff(input.path, content, updated)}`;
|
|
1576
|
-
}
|
|
1577
|
-
if (occurrences.length === 1) {
|
|
1578
|
-
const updated = replaceAt(
|
|
1579
|
-
content,
|
|
1580
|
-
occurrences[0].index,
|
|
1581
|
-
old_string.length,
|
|
1582
|
-
new_string
|
|
1583
|
-
);
|
|
1584
|
-
await fs8.writeFile(input.path, updated, "utf-8");
|
|
1585
|
-
return `Updated ${input.path}
|
|
1586
|
-
${unifiedDiff(input.path, content, updated)}`;
|
|
1587
|
-
}
|
|
1588
|
-
if (occurrences.length > 1) {
|
|
1589
|
-
const lines = occurrences.map((o) => o.line);
|
|
1590
|
-
return `Error: ${formatOccurrenceError(occurrences.length, lines, input.path)}`;
|
|
1591
|
-
}
|
|
1592
|
-
const flex = flexibleMatch(content, old_string);
|
|
1593
|
-
if (flex) {
|
|
1594
|
-
const updated = replaceAt(
|
|
1595
|
-
content,
|
|
1596
|
-
flex.index,
|
|
1597
|
-
flex.matchedText.length,
|
|
1598
|
-
new_string
|
|
1599
|
-
);
|
|
1600
|
-
await fs8.writeFile(input.path, updated, "utf-8");
|
|
1601
|
-
return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
1602
|
-
${unifiedDiff(input.path, content, updated)}`;
|
|
1603
|
-
}
|
|
1604
|
-
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
1605
|
-
} catch (err) {
|
|
1606
|
-
return `Error editing file: ${err.message}`;
|
|
1607
|
-
} finally {
|
|
1608
|
-
release();
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
};
|
|
1602
|
+
init_sidecar();
|
|
1603
|
+
setLspBaseUrl = setSidecarBaseUrl;
|
|
1604
|
+
isLspConfigured = isSidecarConfigured;
|
|
1612
1605
|
}
|
|
1613
1606
|
});
|
|
1614
1607
|
|
|
1615
|
-
// src/
|
|
1616
|
-
import
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
name: "bash",
|
|
1627
|
-
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 by default.",
|
|
1628
|
-
inputSchema: {
|
|
1629
|
-
type: "object",
|
|
1630
|
-
properties: {
|
|
1631
|
-
command: {
|
|
1632
|
-
type: "string",
|
|
1633
|
-
description: "The shell command to execute."
|
|
1634
|
-
},
|
|
1635
|
-
cwd: {
|
|
1636
|
-
type: "string",
|
|
1637
|
-
description: "Working directory to run the command in. Defaults to the project root."
|
|
1638
|
-
},
|
|
1639
|
-
timeout: {
|
|
1640
|
-
type: "number",
|
|
1641
|
-
description: "Timeout in seconds. Defaults to 120. Use higher values for long-running commands like builds or test suites."
|
|
1642
|
-
},
|
|
1643
|
-
maxLines: {
|
|
1644
|
-
type: "number",
|
|
1645
|
-
description: "Maximum number of output lines to return. Defaults to 500. Set to 0 for no limit."
|
|
1646
|
-
}
|
|
1647
|
-
},
|
|
1648
|
-
required: ["command"]
|
|
1649
|
-
}
|
|
1650
|
-
},
|
|
1651
|
-
async execute(input, context) {
|
|
1652
|
-
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
1653
|
-
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
1654
|
-
return new Promise((resolve2) => {
|
|
1655
|
-
const child = spawn2("sh", ["-c", input.command], {
|
|
1656
|
-
cwd: input.cwd || void 0,
|
|
1657
|
-
env: { ...process.env, FORCE_COLOR: "1" }
|
|
1658
|
-
});
|
|
1659
|
-
let output = "";
|
|
1660
|
-
child.stdout.on("data", (chunk) => {
|
|
1661
|
-
const text = chunk.toString();
|
|
1662
|
-
output += text;
|
|
1663
|
-
context?.onLog?.(text);
|
|
1664
|
-
});
|
|
1665
|
-
child.stderr.on("data", (chunk) => {
|
|
1666
|
-
const text = chunk.toString();
|
|
1667
|
-
output += text;
|
|
1668
|
-
context?.onLog?.(text);
|
|
1669
|
-
});
|
|
1670
|
-
const timer = setTimeout(() => {
|
|
1671
|
-
child.kill("SIGTERM");
|
|
1672
|
-
}, timeoutMs);
|
|
1673
|
-
child.on("close", (code) => {
|
|
1674
|
-
clearTimeout(timer);
|
|
1675
|
-
if (!output) {
|
|
1676
|
-
if (code && code !== 0) {
|
|
1677
|
-
resolve2(`Error: process exited with code ${code}`);
|
|
1678
|
-
} else {
|
|
1679
|
-
resolve2("(no output)");
|
|
1680
|
-
}
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
const lines = output.split("\n");
|
|
1684
|
-
if (lines.length > maxLines) {
|
|
1685
|
-
resolve2(
|
|
1686
|
-
lines.slice(0, maxLines).join("\n") + `
|
|
1687
|
-
|
|
1688
|
-
(truncated at ${maxLines} lines of ${lines.length} total \u2014 increase maxLines to see more)`
|
|
1689
|
-
);
|
|
1690
|
-
} else {
|
|
1691
|
-
resolve2(output);
|
|
1692
|
-
}
|
|
1693
|
-
});
|
|
1694
|
-
child.on("error", (err) => {
|
|
1695
|
-
clearTimeout(timer);
|
|
1696
|
-
resolve2(`Error: ${err.message}`);
|
|
1697
|
-
});
|
|
1698
|
-
});
|
|
1608
|
+
// src/prompt/static/projectContext.ts
|
|
1609
|
+
import fs7 from "fs";
|
|
1610
|
+
import path4 from "path";
|
|
1611
|
+
function loadProjectInstructions() {
|
|
1612
|
+
for (const file of AGENT_INSTRUCTION_FILES) {
|
|
1613
|
+
try {
|
|
1614
|
+
const content = fs7.readFileSync(file, "utf-8").trim();
|
|
1615
|
+
if (content) {
|
|
1616
|
+
return `
|
|
1617
|
+
## Project Instructions (${file})
|
|
1618
|
+
${content}`;
|
|
1699
1619
|
}
|
|
1700
|
-
}
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1701
1622
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1623
|
+
return "";
|
|
1624
|
+
}
|
|
1625
|
+
function loadProjectManifest() {
|
|
1626
|
+
try {
|
|
1627
|
+
const manifest = fs7.readFileSync("mindstudio.json", "utf-8");
|
|
1628
|
+
return `
|
|
1629
|
+
## Project Manifest (mindstudio.json)
|
|
1630
|
+
\`\`\`json
|
|
1631
|
+
${manifest}
|
|
1632
|
+
\`\`\``;
|
|
1633
|
+
} catch {
|
|
1634
|
+
return "";
|
|
1713
1635
|
}
|
|
1714
|
-
return result;
|
|
1715
1636
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
properties: {
|
|
1729
|
-
pattern: {
|
|
1730
|
-
type: "string",
|
|
1731
|
-
description: "The search pattern (regex supported)."
|
|
1732
|
-
},
|
|
1733
|
-
path: {
|
|
1734
|
-
type: "string",
|
|
1735
|
-
description: "Directory or file to search in. Defaults to current directory."
|
|
1736
|
-
},
|
|
1737
|
-
glob: {
|
|
1738
|
-
type: "string",
|
|
1739
|
-
description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
|
|
1740
|
-
},
|
|
1741
|
-
maxResults: {
|
|
1742
|
-
type: "number",
|
|
1743
|
-
description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
|
|
1744
|
-
}
|
|
1745
|
-
},
|
|
1746
|
-
required: ["pattern"]
|
|
1747
|
-
}
|
|
1748
|
-
},
|
|
1749
|
-
async execute(input) {
|
|
1750
|
-
const searchPath = input.path || ".";
|
|
1751
|
-
const max = input.maxResults || DEFAULT_MAX;
|
|
1752
|
-
const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
|
|
1753
|
-
const escaped = input.pattern.replace(/'/g, "'\\''");
|
|
1754
|
-
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
1755
|
-
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
1756
|
-
return new Promise((resolve2) => {
|
|
1757
|
-
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1758
|
-
if (stdout?.trim()) {
|
|
1759
|
-
resolve2(formatResults(stdout, max));
|
|
1760
|
-
return;
|
|
1761
|
-
}
|
|
1762
|
-
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
1763
|
-
if (grepStdout?.trim()) {
|
|
1764
|
-
resolve2(formatResults(grepStdout, max));
|
|
1765
|
-
} else {
|
|
1766
|
-
resolve2("No matches found.");
|
|
1767
|
-
}
|
|
1768
|
-
});
|
|
1769
|
-
});
|
|
1770
|
-
});
|
|
1637
|
+
function loadSpecFileMetadata() {
|
|
1638
|
+
try {
|
|
1639
|
+
const files = walkMdFiles("src");
|
|
1640
|
+
if (files.length === 0) {
|
|
1641
|
+
return "";
|
|
1642
|
+
}
|
|
1643
|
+
const entries = [];
|
|
1644
|
+
for (const filePath of files) {
|
|
1645
|
+
const { name, description, type } = parseFrontmatter(filePath);
|
|
1646
|
+
let line = `- ${filePath}`;
|
|
1647
|
+
if (name) {
|
|
1648
|
+
line += ` \u2014 "${name}"`;
|
|
1771
1649
|
}
|
|
1772
|
-
|
|
1650
|
+
if (type) {
|
|
1651
|
+
line += ` (${type})`;
|
|
1652
|
+
}
|
|
1653
|
+
if (description) {
|
|
1654
|
+
line += ` \u2014 ${description}`;
|
|
1655
|
+
}
|
|
1656
|
+
entries.push(line);
|
|
1657
|
+
}
|
|
1658
|
+
return `
|
|
1659
|
+
## Spec Files
|
|
1660
|
+
${entries.join("\n")}`;
|
|
1661
|
+
} catch {
|
|
1662
|
+
return "";
|
|
1773
1663
|
}
|
|
1774
|
-
});
|
|
1775
|
-
|
|
1776
|
-
// src/tools/code/glob.ts
|
|
1777
|
-
import fg from "fast-glob";
|
|
1778
|
-
var DEFAULT_MAX2, globTool;
|
|
1779
|
-
var init_glob = __esm({
|
|
1780
|
-
"src/tools/code/glob.ts"() {
|
|
1781
|
-
"use strict";
|
|
1782
|
-
DEFAULT_MAX2 = 200;
|
|
1783
|
-
globTool = {
|
|
1784
|
-
clearable: true,
|
|
1785
|
-
definition: {
|
|
1786
|
-
name: "glob",
|
|
1787
|
-
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.',
|
|
1788
|
-
inputSchema: {
|
|
1789
|
-
type: "object",
|
|
1790
|
-
properties: {
|
|
1791
|
-
pattern: {
|
|
1792
|
-
type: "string",
|
|
1793
|
-
description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
|
|
1794
|
-
},
|
|
1795
|
-
maxResults: {
|
|
1796
|
-
type: "number",
|
|
1797
|
-
description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
|
|
1798
|
-
}
|
|
1799
|
-
},
|
|
1800
|
-
required: ["pattern"]
|
|
1801
|
-
}
|
|
1802
|
-
},
|
|
1803
|
-
async execute(input) {
|
|
1804
|
-
try {
|
|
1805
|
-
const max = input.maxResults || DEFAULT_MAX2;
|
|
1806
|
-
const files = await fg(input.pattern, {
|
|
1807
|
-
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
1808
|
-
dot: false
|
|
1809
|
-
});
|
|
1810
|
-
if (files.length === 0) {
|
|
1811
|
-
return "No files found.";
|
|
1812
|
-
}
|
|
1813
|
-
const sorted = files.sort();
|
|
1814
|
-
const truncated = sorted.slice(0, max);
|
|
1815
|
-
let result = truncated.join("\n");
|
|
1816
|
-
if (sorted.length > max) {
|
|
1817
|
-
result += `
|
|
1818
|
-
|
|
1819
|
-
(showing ${max} of ${sorted.length} matches \u2014 increase maxResults to see all)`;
|
|
1820
|
-
}
|
|
1821
|
-
return result;
|
|
1822
|
-
} catch (err) {
|
|
1823
|
-
return `Error: ${err.message}`;
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
|
-
});
|
|
1829
|
-
|
|
1830
|
-
// src/tools/code/listDir.ts
|
|
1831
|
-
import fs9 from "fs/promises";
|
|
1832
|
-
import path4 from "path";
|
|
1833
|
-
async function readAndSort(dirPath) {
|
|
1834
|
-
const entries = await fs9.readdir(dirPath, { withFileTypes: true });
|
|
1835
|
-
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
1836
|
-
if (a.isDirectory() && !b.isDirectory()) {
|
|
1837
|
-
return -1;
|
|
1838
|
-
}
|
|
1839
|
-
if (!a.isDirectory() && b.isDirectory()) {
|
|
1840
|
-
return 1;
|
|
1841
|
-
}
|
|
1842
|
-
return a.name.localeCompare(b.name);
|
|
1843
|
-
});
|
|
1844
1664
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
display += "/" + children[0].name;
|
|
1857
|
-
current = path4.join(current, children[0].name);
|
|
1858
|
-
} else {
|
|
1859
|
-
break;
|
|
1665
|
+
function walkMdFiles(dir) {
|
|
1666
|
+
const results = [];
|
|
1667
|
+
try {
|
|
1668
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1669
|
+
for (const entry of entries) {
|
|
1670
|
+
const full = path4.join(dir, entry.name);
|
|
1671
|
+
if (entry.isDirectory()) {
|
|
1672
|
+
results.push(...walkMdFiles(full));
|
|
1673
|
+
} else if (entry.name.endsWith(".md")) {
|
|
1674
|
+
results.push(full);
|
|
1675
|
+
}
|
|
1860
1676
|
}
|
|
1677
|
+
} catch {
|
|
1861
1678
|
}
|
|
1862
|
-
return
|
|
1863
|
-
}
|
|
1864
|
-
function formatSize(bytes) {
|
|
1865
|
-
if (bytes < 1e3) {
|
|
1866
|
-
return `${bytes} B`;
|
|
1867
|
-
}
|
|
1868
|
-
if (bytes < 1e6) {
|
|
1869
|
-
return `${(bytes / 1e3).toFixed(1)} kB`;
|
|
1870
|
-
}
|
|
1871
|
-
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
1679
|
+
return results.sort();
|
|
1872
1680
|
}
|
|
1873
|
-
|
|
1681
|
+
function parseFrontmatter(filePath) {
|
|
1874
1682
|
try {
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1683
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1684
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1685
|
+
if (!match) {
|
|
1686
|
+
return { name: "", description: "", type: "" };
|
|
1687
|
+
}
|
|
1688
|
+
const fm = match[1];
|
|
1689
|
+
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
1690
|
+
const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
1691
|
+
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
1692
|
+
return { name, description, type };
|
|
1877
1693
|
} catch {
|
|
1878
|
-
return
|
|
1694
|
+
return { name: "", description: "", type: "" };
|
|
1879
1695
|
}
|
|
1880
1696
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
"
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
listDirTool = {
|
|
1888
|
-
clearable: true,
|
|
1889
|
-
definition: {
|
|
1890
|
-
name: "listDir",
|
|
1891
|
-
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.",
|
|
1892
|
-
inputSchema: {
|
|
1893
|
-
type: "object",
|
|
1894
|
-
properties: {
|
|
1895
|
-
path: {
|
|
1896
|
-
type: "string",
|
|
1897
|
-
description: 'Directory path to list, relative to project root. Defaults to ".".'
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
},
|
|
1902
|
-
async execute(input) {
|
|
1903
|
-
const dirPath = input.path || ".";
|
|
1904
|
-
try {
|
|
1905
|
-
const entries = await readAndSort(dirPath);
|
|
1906
|
-
const lines = [];
|
|
1907
|
-
for (const entry of entries) {
|
|
1908
|
-
if (entry.isDirectory()) {
|
|
1909
|
-
const [displayName, finalPath] = await collapsePath(
|
|
1910
|
-
dirPath,
|
|
1911
|
-
entry.name
|
|
1912
|
-
);
|
|
1913
|
-
lines.push(`${displayName}/`);
|
|
1914
|
-
try {
|
|
1915
|
-
const children = await readAndSort(finalPath);
|
|
1916
|
-
const capped = children.slice(0, MAX_CHILDREN);
|
|
1917
|
-
for (const child of capped) {
|
|
1918
|
-
if (child.isDirectory()) {
|
|
1919
|
-
lines.push(` ${child.name}/`);
|
|
1920
|
-
} else {
|
|
1921
|
-
lines.push(await formatFile(finalPath, child.name, " "));
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
if (children.length > MAX_CHILDREN) {
|
|
1925
|
-
lines.push(` ... and ${children.length - MAX_CHILDREN} more`);
|
|
1926
|
-
}
|
|
1927
|
-
} catch {
|
|
1928
|
-
}
|
|
1929
|
-
} else {
|
|
1930
|
-
lines.push(await formatFile(dirPath, entry.name, ""));
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
return lines.join("\n") || "(empty directory)";
|
|
1934
|
-
} catch (err) {
|
|
1935
|
-
return `Error listing directory: ${err.message}`;
|
|
1936
|
-
}
|
|
1697
|
+
function loadProjectFileListing() {
|
|
1698
|
+
try {
|
|
1699
|
+
const entries = fs7.readdirSync(".", { withFileTypes: true });
|
|
1700
|
+
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
1701
|
+
if (a.isDirectory() && !b.isDirectory()) {
|
|
1702
|
+
return -1;
|
|
1937
1703
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
});
|
|
1941
|
-
|
|
1942
|
-
// src/tools/code/editsFinished.ts
|
|
1943
|
-
var editsFinishedTool;
|
|
1944
|
-
var init_editsFinished = __esm({
|
|
1945
|
-
"src/tools/code/editsFinished.ts"() {
|
|
1946
|
-
"use strict";
|
|
1947
|
-
editsFinishedTool = {
|
|
1948
|
-
clearable: false,
|
|
1949
|
-
definition: {
|
|
1950
|
-
name: "editsFinished",
|
|
1951
|
-
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.",
|
|
1952
|
-
inputSchema: {
|
|
1953
|
-
type: "object",
|
|
1954
|
-
properties: {},
|
|
1955
|
-
required: []
|
|
1956
|
-
}
|
|
1957
|
-
},
|
|
1958
|
-
async execute() {
|
|
1959
|
-
return "Preview updated.";
|
|
1704
|
+
if (!a.isDirectory() && b.isDirectory()) {
|
|
1705
|
+
return 1;
|
|
1960
1706
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
function isSidecarConfigured() {
|
|
1971
|
-
return baseUrl !== null;
|
|
1972
|
-
}
|
|
1973
|
-
async function sidecarRequest(endpoint, body = {}, options) {
|
|
1974
|
-
if (!baseUrl) {
|
|
1975
|
-
throw new Error("Sidecar not available");
|
|
1976
|
-
}
|
|
1977
|
-
const url = `${baseUrl}${endpoint}`;
|
|
1978
|
-
try {
|
|
1979
|
-
const res = await fetch(url, {
|
|
1980
|
-
method: "POST",
|
|
1981
|
-
headers: { "Content-Type": "application/json" },
|
|
1982
|
-
body: JSON.stringify(body),
|
|
1983
|
-
signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0
|
|
1984
|
-
});
|
|
1985
|
-
if (!res.ok) {
|
|
1986
|
-
log2.error("Sidecar error", { endpoint, status: res.status });
|
|
1987
|
-
throw new Error(`Sidecar error: ${res.status}`);
|
|
1988
|
-
}
|
|
1989
|
-
const data = await res.json();
|
|
1990
|
-
if (data?.success === false) {
|
|
1991
|
-
const code = data.errorCode ? ` [${data.errorCode}]` : "";
|
|
1992
|
-
throw new Error(`${data.error || "Unknown error"}${code}`);
|
|
1993
|
-
}
|
|
1994
|
-
return data;
|
|
1995
|
-
} catch (err) {
|
|
1996
|
-
if (err.message.startsWith("Sidecar error")) {
|
|
1997
|
-
throw err;
|
|
1998
|
-
}
|
|
1999
|
-
log2.error("Sidecar connection error", { endpoint, error: err.message });
|
|
2000
|
-
throw new Error(`Sidecar connection error: ${err.message}`);
|
|
1707
|
+
return a.name.localeCompare(b.name);
|
|
1708
|
+
}).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
1709
|
+
return `
|
|
1710
|
+
## Project Files
|
|
1711
|
+
\`\`\`
|
|
1712
|
+
${listing}
|
|
1713
|
+
\`\`\``;
|
|
1714
|
+
} catch {
|
|
1715
|
+
return "";
|
|
2001
1716
|
}
|
|
2002
1717
|
}
|
|
2003
|
-
var
|
|
2004
|
-
var
|
|
2005
|
-
"src/
|
|
1718
|
+
var AGENT_INSTRUCTION_FILES;
|
|
1719
|
+
var init_projectContext = __esm({
|
|
1720
|
+
"src/prompt/static/projectContext.ts"() {
|
|
2006
1721
|
"use strict";
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
1722
|
+
AGENT_INSTRUCTION_FILES = [
|
|
1723
|
+
"CLAUDE.md",
|
|
1724
|
+
"claude.md",
|
|
1725
|
+
".claude/instructions.md",
|
|
1726
|
+
"AGENTS.md",
|
|
1727
|
+
"agents.md",
|
|
1728
|
+
".agents.md",
|
|
1729
|
+
"COPILOT.md",
|
|
1730
|
+
"copilot.md",
|
|
1731
|
+
".copilot-instructions.md",
|
|
1732
|
+
".github/copilot-instructions.md",
|
|
1733
|
+
"REMY.md",
|
|
1734
|
+
"remy.md",
|
|
1735
|
+
".cursorrules",
|
|
1736
|
+
".cursorules"
|
|
1737
|
+
];
|
|
2010
1738
|
}
|
|
2011
1739
|
});
|
|
2012
1740
|
|
|
2013
|
-
// src/
|
|
2014
|
-
|
|
2015
|
-
|
|
1741
|
+
// src/prompt/index.ts
|
|
1742
|
+
function resolveIncludes(template) {
|
|
1743
|
+
const result = template.replace(
|
|
1744
|
+
/\{\{([^}]+)\}\}/g,
|
|
1745
|
+
(_, filePath) => readAsset("prompt", filePath.trim())
|
|
1746
|
+
);
|
|
1747
|
+
return result.replace(/\n{3,}/g, "\n\n").trim();
|
|
2016
1748
|
}
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
1749
|
+
function buildSystemPrompt(onboardingState, viewContext) {
|
|
1750
|
+
const projectContext = [
|
|
1751
|
+
loadProjectInstructions(),
|
|
1752
|
+
loadProjectManifest(),
|
|
1753
|
+
loadSpecFileMetadata(),
|
|
1754
|
+
loadProjectFileListing()
|
|
1755
|
+
].filter(Boolean).join("\n");
|
|
1756
|
+
const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
1757
|
+
month: "long",
|
|
1758
|
+
day: "numeric",
|
|
1759
|
+
year: "numeric"
|
|
1760
|
+
});
|
|
1761
|
+
const template = `
|
|
1762
|
+
{{static/identity.md}}
|
|
1763
|
+
|
|
1764
|
+
Current date: ${now}
|
|
1765
|
+
|
|
1766
|
+
<platform_docs>
|
|
1767
|
+
<platform>
|
|
1768
|
+
{{compiled/platform.md}}
|
|
1769
|
+
</platform>
|
|
1770
|
+
|
|
1771
|
+
<manifest>
|
|
1772
|
+
{{compiled/manifest.md}}
|
|
1773
|
+
</manifest>
|
|
1774
|
+
|
|
1775
|
+
<tables>
|
|
1776
|
+
{{compiled/tables.md}}
|
|
1777
|
+
</tables>
|
|
1778
|
+
|
|
1779
|
+
<methods>
|
|
1780
|
+
{{compiled/methods.md}}
|
|
1781
|
+
</methods>
|
|
1782
|
+
|
|
1783
|
+
<auth>
|
|
1784
|
+
{{compiled/auth.md}}
|
|
1785
|
+
</auth>
|
|
1786
|
+
|
|
1787
|
+
<dev_and_deploy>
|
|
1788
|
+
{{compiled/dev-and-deploy.md}}
|
|
1789
|
+
</dev_and_deploy>
|
|
1790
|
+
|
|
1791
|
+
<design>
|
|
1792
|
+
{{compiled/design.md}}
|
|
1793
|
+
</design>
|
|
1794
|
+
|
|
1795
|
+
<building_agent_interfaces>
|
|
1796
|
+
{{compiled/agent-interfaces.md}}
|
|
1797
|
+
</building_agent_interfaces>
|
|
1798
|
+
|
|
1799
|
+
<media_cdn>
|
|
1800
|
+
{{compiled/media-cdn.md}}
|
|
1801
|
+
</media_cdn>
|
|
1802
|
+
|
|
1803
|
+
<interfaces>
|
|
1804
|
+
{{compiled/interfaces.md}}
|
|
1805
|
+
</interfaces>
|
|
1806
|
+
|
|
1807
|
+
<scenarios>
|
|
1808
|
+
{{compiled/scenarios.md}}
|
|
1809
|
+
</scenarios>
|
|
1810
|
+
|
|
1811
|
+
<secrets>
|
|
1812
|
+
{{compiled/secrets.md}}
|
|
1813
|
+
</secrets>
|
|
1814
|
+
</platform_docs>
|
|
1815
|
+
|
|
1816
|
+
<mindstudio_agent_sdk_docs>
|
|
1817
|
+
{{compiled/sdk-actions.md}}
|
|
1818
|
+
|
|
1819
|
+
{{compiled/task-agents.md}}
|
|
1820
|
+
</mindstudio_agent_sdk_docs>
|
|
1821
|
+
|
|
1822
|
+
<mindstudio_flavored_markdown_spec_docs>
|
|
1823
|
+
{{compiled/msfm.md}}
|
|
1824
|
+
</mindstudio_flavored_markdown_spec_docs>
|
|
1825
|
+
|
|
1826
|
+
<project_context>
|
|
1827
|
+
${projectContext}
|
|
1828
|
+
</project_context>
|
|
1829
|
+
|
|
1830
|
+
<intake_mode_instructions>
|
|
1831
|
+
{{static/intake.md}}
|
|
1832
|
+
</intake_mode_instructions>
|
|
1833
|
+
|
|
1834
|
+
<spec_authoring_instructions>
|
|
1835
|
+
{{static/authoring.md}}
|
|
1836
|
+
</spec_authoring_instructions>
|
|
1837
|
+
|
|
1838
|
+
{{static/team.md}}
|
|
1839
|
+
|
|
1840
|
+
<code_authoring_instructions>
|
|
1841
|
+
{{static/coding.md}}
|
|
1842
|
+
${isLspConfigured() ? `<typescript_lsp>
|
|
1843
|
+
{{static/lsp.md}}
|
|
1844
|
+
</typescript_lsp>` : ""}
|
|
1845
|
+
</code_authoring_instructions>
|
|
1846
|
+
|
|
1847
|
+
{{static/instructions.md}}
|
|
1848
|
+
|
|
1849
|
+
<conversation_summaries>
|
|
1850
|
+
Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
|
|
1851
|
+
|
|
1852
|
+
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
|
|
1853
|
+
</conversation_summaries>
|
|
1854
|
+
|
|
1855
|
+
<project_onboarding>
|
|
1856
|
+
New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
|
|
1857
|
+
|
|
1858
|
+
- **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code.
|
|
1859
|
+
- **initialSpecAuthoring**: Writing and refining the first spec. The user can see it in the editor as it streams in and can give feedback to iterate on it. This phase covers both the initial draft and any back-and-forth refinement before code generation.
|
|
1860
|
+
- **initialCodegen**: First code generation from the spec. The agent is generating methods, tables, interfaces, manifest updates, and scenarios. This can take a while and involves heavy tool use. The user sees a full-screen build progress view.
|
|
1861
|
+
- **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
|
|
1862
|
+
|
|
1863
|
+
<!-- cache_breakpoint -->
|
|
1864
|
+
|
|
1865
|
+
<current_project_onboarding_state>
|
|
1866
|
+
${onboardingState ?? "onboardingFinished"}
|
|
1867
|
+
</current_project_onboarding_state>
|
|
1868
|
+
</project_onboarding>
|
|
1869
|
+
|
|
1870
|
+
<view_context>
|
|
1871
|
+
The user is currently in ${viewContext?.mode ?? "code"} mode.
|
|
1872
|
+
${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
|
|
1873
|
+
</view_context>
|
|
1874
|
+
`;
|
|
1875
|
+
return resolveIncludes(template);
|
|
1876
|
+
}
|
|
1877
|
+
var init_prompt = __esm({
|
|
1878
|
+
"src/prompt/index.ts"() {
|
|
2020
1879
|
"use strict";
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1880
|
+
init_assets();
|
|
1881
|
+
init_lsp();
|
|
1882
|
+
init_projectContext();
|
|
2024
1883
|
}
|
|
2025
1884
|
});
|
|
2026
1885
|
|
|
2027
|
-
// src/
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
1886
|
+
// src/session.ts
|
|
1887
|
+
import fs8 from "fs";
|
|
1888
|
+
function loadSession(state) {
|
|
1889
|
+
try {
|
|
1890
|
+
const raw = fs8.readFileSync(SESSION_FILE, "utf-8");
|
|
1891
|
+
const data = JSON.parse(raw);
|
|
1892
|
+
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
1893
|
+
state.messages = sanitizeMessages(data.messages);
|
|
1894
|
+
log4.info("Session loaded", { messageCount: state.messages.length });
|
|
1895
|
+
return true;
|
|
1896
|
+
}
|
|
1897
|
+
} catch {
|
|
1898
|
+
}
|
|
1899
|
+
return false;
|
|
1900
|
+
}
|
|
1901
|
+
function sanitizeMessages(messages) {
|
|
1902
|
+
const result = [];
|
|
1903
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1904
|
+
result.push(messages[i]);
|
|
1905
|
+
const msg = messages[i];
|
|
1906
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
const toolBlocks = msg.content.filter(
|
|
1910
|
+
(b) => b.type === "tool"
|
|
1911
|
+
);
|
|
1912
|
+
if (toolBlocks.length === 0) {
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
const resultIds = /* @__PURE__ */ new Set();
|
|
1916
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
1917
|
+
const next = messages[j];
|
|
1918
|
+
if (next.role === "user" && next.toolCallId) {
|
|
1919
|
+
resultIds.add(next.toolCallId);
|
|
1920
|
+
} else {
|
|
1921
|
+
break;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
for (const tc of toolBlocks) {
|
|
1925
|
+
if (!resultIds.has(tc.id)) {
|
|
1926
|
+
result.push({
|
|
1927
|
+
role: "user",
|
|
1928
|
+
content: "Error: tool result lost (session recovered)",
|
|
1929
|
+
toolCallId: tc.id,
|
|
1930
|
+
isToolError: true
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return result;
|
|
1936
|
+
}
|
|
1937
|
+
function saveSession(state) {
|
|
1938
|
+
try {
|
|
1939
|
+
fs8.writeFileSync(
|
|
1940
|
+
SESSION_FILE,
|
|
1941
|
+
JSON.stringify({ messages: state.messages }, null, 2),
|
|
1942
|
+
"utf-8"
|
|
1943
|
+
);
|
|
1944
|
+
log4.info("Session saved", { messageCount: state.messages.length });
|
|
1945
|
+
} catch (err) {
|
|
1946
|
+
log4.warn("Session save failed", { error: err.message });
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function clearSession(state) {
|
|
1950
|
+
state.messages = [];
|
|
1951
|
+
try {
|
|
1952
|
+
fs8.unlinkSync(SESSION_FILE);
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
var log4, SESSION_FILE;
|
|
1957
|
+
var init_session = __esm({
|
|
1958
|
+
"src/session.ts"() {
|
|
2031
1959
|
"use strict";
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1960
|
+
init_logger();
|
|
1961
|
+
log4 = createLogger("session");
|
|
1962
|
+
SESSION_FILE = ".remy-session.json";
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
// src/compaction/trigger.ts
|
|
1967
|
+
function triggerCompaction(state, apiConfig, callbacks) {
|
|
1968
|
+
callbacks?.onStart?.();
|
|
1969
|
+
const system = buildSystemPrompt("onboardingFinished");
|
|
1970
|
+
const tools2 = getToolDefinitions("onboardingFinished");
|
|
1971
|
+
compactConversation(state, apiConfig, system, tools2).then(() => {
|
|
1972
|
+
saveSession(state);
|
|
1973
|
+
callbacks?.onComplete?.();
|
|
1974
|
+
log5.info("Compaction complete");
|
|
1975
|
+
}).catch((err) => {
|
|
1976
|
+
callbacks?.onError?.(err.message || "Compaction failed");
|
|
1977
|
+
log5.error("Compaction failed", { error: err.message });
|
|
1978
|
+
}).finally(() => {
|
|
1979
|
+
callbacks?.onFinally?.();
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
var log5;
|
|
1983
|
+
var init_trigger = __esm({
|
|
1984
|
+
"src/compaction/trigger.ts"() {
|
|
1985
|
+
"use strict";
|
|
1986
|
+
init_compaction();
|
|
1987
|
+
init_prompt();
|
|
1988
|
+
init_tools6();
|
|
1989
|
+
init_session();
|
|
1990
|
+
init_logger();
|
|
1991
|
+
log5 = createLogger("compaction:trigger");
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1995
|
+
// src/tools/common/compactConversation.ts
|
|
1996
|
+
var compactConversationTool;
|
|
1997
|
+
var init_compactConversation = __esm({
|
|
1998
|
+
"src/tools/common/compactConversation.ts"() {
|
|
1999
|
+
"use strict";
|
|
2000
|
+
init_trigger();
|
|
2001
|
+
compactConversationTool = {
|
|
2002
|
+
clearable: false,
|
|
2035
2003
|
definition: {
|
|
2036
|
-
name: "
|
|
2037
|
-
description: "
|
|
2004
|
+
name: "compactConversation",
|
|
2005
|
+
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. Use this when you have just finished a large block of mechanical work (building, refactoring, debugging) and are about to shift back into conversational mode with the user. Runs in the background. Do not use after small changes like fixing a bug or editing copy.",
|
|
2038
2006
|
inputSchema: {
|
|
2039
2007
|
type: "object",
|
|
2040
|
-
properties: {
|
|
2041
|
-
file: {
|
|
2042
|
-
type: "string",
|
|
2043
|
-
description: "File path relative to workspace root."
|
|
2044
|
-
}
|
|
2045
|
-
},
|
|
2046
|
-
required: ["file"]
|
|
2008
|
+
properties: {}
|
|
2047
2009
|
}
|
|
2048
2010
|
},
|
|
2049
|
-
async execute(
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
if (diags.length === 0) {
|
|
2053
|
-
return "No diagnostics \u2014 file is clean.";
|
|
2011
|
+
async execute(_input, context) {
|
|
2012
|
+
if (!context?.conversationMessages || !context.apiConfig) {
|
|
2013
|
+
return "Error: compaction requires execution context.";
|
|
2054
2014
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
file: d.file,
|
|
2061
|
-
startLine: d.line,
|
|
2062
|
-
startColumn: d.column,
|
|
2063
|
-
endLine: d.endLine ?? d.line,
|
|
2064
|
-
endColumn: d.endColumn ?? d.column,
|
|
2065
|
-
diagnostics: [d]
|
|
2066
|
-
});
|
|
2067
|
-
const actions = actionsData.actions || [];
|
|
2068
|
-
if (actions.length > 0) {
|
|
2069
|
-
const fixes = actions.map((a) => a.title).join("; ");
|
|
2070
|
-
line += `
|
|
2071
|
-
Quick fixes: ${fixes}`;
|
|
2072
|
-
}
|
|
2073
|
-
} catch {
|
|
2074
|
-
}
|
|
2075
|
-
lines.push(line);
|
|
2076
|
-
}
|
|
2077
|
-
return lines.join("\n");
|
|
2015
|
+
triggerCompaction(
|
|
2016
|
+
{ messages: context.conversationMessages },
|
|
2017
|
+
context.apiConfig
|
|
2018
|
+
);
|
|
2019
|
+
return "Compaction started in the background.";
|
|
2078
2020
|
}
|
|
2079
2021
|
};
|
|
2080
2022
|
}
|
|
2081
2023
|
});
|
|
2082
2024
|
|
|
2083
|
-
// src/tools/code/
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
definition: {
|
|
2092
|
-
name: "restartProcess",
|
|
2093
|
-
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.",
|
|
2094
|
-
inputSchema: {
|
|
2095
|
-
type: "object",
|
|
2096
|
-
properties: {
|
|
2097
|
-
name: {
|
|
2098
|
-
type: "string",
|
|
2099
|
-
description: 'Process name to restart. Currently supported: "devServer".'
|
|
2100
|
-
}
|
|
2101
|
-
},
|
|
2102
|
-
required: ["name"]
|
|
2103
|
-
}
|
|
2104
|
-
},
|
|
2105
|
-
async execute(input) {
|
|
2106
|
-
const data = await lspRequest("/restart-process", { name: input.name });
|
|
2107
|
-
if (data.ok) {
|
|
2108
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5e3));
|
|
2109
|
-
return `Restarted ${input.name}.`;
|
|
2110
|
-
}
|
|
2111
|
-
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
2112
|
-
}
|
|
2113
|
-
};
|
|
2025
|
+
// src/tools/code/readFile.ts
|
|
2026
|
+
import fs9 from "fs/promises";
|
|
2027
|
+
function isBinary(buffer) {
|
|
2028
|
+
const sample = buffer.subarray(0, 8192);
|
|
2029
|
+
for (let i = 0; i < sample.length; i++) {
|
|
2030
|
+
if (sample[i] === 0) {
|
|
2031
|
+
return true;
|
|
2032
|
+
}
|
|
2114
2033
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
var
|
|
2119
|
-
|
|
2120
|
-
"src/tools/code/runScenario.ts"() {
|
|
2034
|
+
return false;
|
|
2035
|
+
}
|
|
2036
|
+
var DEFAULT_MAX_LINES2, readFileTool;
|
|
2037
|
+
var init_readFile = __esm({
|
|
2038
|
+
"src/tools/code/readFile.ts"() {
|
|
2121
2039
|
"use strict";
|
|
2122
|
-
|
|
2040
|
+
DEFAULT_MAX_LINES2 = 500;
|
|
2041
|
+
readFileTool = {
|
|
2123
2042
|
clearable: true,
|
|
2124
2043
|
definition: {
|
|
2125
|
-
name: "
|
|
2126
|
-
description: "
|
|
2044
|
+
name: "readFile",
|
|
2045
|
+
description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
|
|
2127
2046
|
inputSchema: {
|
|
2128
2047
|
type: "object",
|
|
2129
2048
|
properties: {
|
|
2130
|
-
|
|
2049
|
+
path: {
|
|
2131
2050
|
type: "string",
|
|
2132
|
-
description: "The
|
|
2051
|
+
description: "The file path to read, relative to the project root."
|
|
2133
2052
|
},
|
|
2134
|
-
|
|
2135
|
-
type: "
|
|
2136
|
-
description: "
|
|
2053
|
+
offset: {
|
|
2054
|
+
type: "number",
|
|
2055
|
+
description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
|
|
2056
|
+
},
|
|
2057
|
+
maxLines: {
|
|
2058
|
+
type: "number",
|
|
2059
|
+
description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
|
|
2137
2060
|
}
|
|
2138
2061
|
},
|
|
2139
|
-
required: ["
|
|
2062
|
+
required: ["path"]
|
|
2140
2063
|
}
|
|
2141
2064
|
},
|
|
2142
|
-
async execute() {
|
|
2143
|
-
|
|
2065
|
+
async execute(input) {
|
|
2066
|
+
try {
|
|
2067
|
+
const buffer = await fs9.readFile(input.path);
|
|
2068
|
+
if (isBinary(buffer)) {
|
|
2069
|
+
const size = buffer.length;
|
|
2070
|
+
const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
|
|
2071
|
+
return `Error: ${input.path} appears to be a binary file (${unit}). Use bash to inspect it if needed.`;
|
|
2072
|
+
}
|
|
2073
|
+
const content = buffer.toString("utf-8");
|
|
2074
|
+
const allLines = content.split("\n");
|
|
2075
|
+
const totalLines = allLines.length;
|
|
2076
|
+
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
2077
|
+
let startIdx;
|
|
2078
|
+
if (input.offset && input.offset < 0) {
|
|
2079
|
+
startIdx = Math.max(0, totalLines + input.offset);
|
|
2080
|
+
} else {
|
|
2081
|
+
startIdx = Math.max(0, (input.offset || 1) - 1);
|
|
2082
|
+
}
|
|
2083
|
+
const sliced = allLines.slice(startIdx, startIdx + maxLines);
|
|
2084
|
+
const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
|
|
2085
|
+
let result = numbered;
|
|
2086
|
+
const endLine = startIdx + sliced.length;
|
|
2087
|
+
const displayStart = startIdx + 1;
|
|
2088
|
+
if (endLine < totalLines) {
|
|
2089
|
+
result += `
|
|
2090
|
+
|
|
2091
|
+
(showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
|
|
2092
|
+
}
|
|
2093
|
+
return result;
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
return `Error reading file: ${err.message}`;
|
|
2096
|
+
}
|
|
2144
2097
|
}
|
|
2145
2098
|
};
|
|
2146
2099
|
}
|
|
2147
2100
|
});
|
|
2148
2101
|
|
|
2149
|
-
// src/tools/code/
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2102
|
+
// src/tools/code/writeFile.ts
|
|
2103
|
+
import fs10 from "fs/promises";
|
|
2104
|
+
import path5 from "path";
|
|
2105
|
+
var writeFileTool;
|
|
2106
|
+
var init_writeFile = __esm({
|
|
2107
|
+
"src/tools/code/writeFile.ts"() {
|
|
2153
2108
|
"use strict";
|
|
2154
|
-
|
|
2109
|
+
init_diff();
|
|
2110
|
+
init_fileLock();
|
|
2111
|
+
writeFileTool = {
|
|
2155
2112
|
clearable: true,
|
|
2156
2113
|
definition: {
|
|
2157
|
-
name: "
|
|
2158
|
-
description: "
|
|
2114
|
+
name: "writeFile",
|
|
2115
|
+
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.",
|
|
2159
2116
|
inputSchema: {
|
|
2160
2117
|
type: "object",
|
|
2161
2118
|
properties: {
|
|
2162
|
-
|
|
2119
|
+
path: {
|
|
2163
2120
|
type: "string",
|
|
2164
|
-
description:
|
|
2165
|
-
},
|
|
2166
|
-
input: {
|
|
2167
|
-
type: "object",
|
|
2168
|
-
description: "The input payload to pass to the method. Omit for methods that take no input."
|
|
2169
|
-
},
|
|
2170
|
-
roles: {
|
|
2171
|
-
type: "array",
|
|
2172
|
-
items: { type: "string" },
|
|
2173
|
-
description: 'Optional. Role names for this request (e.g. ["admin"]). Can be used without userId to test role-gated logic. Overrides session-level impersonation for this call only.'
|
|
2121
|
+
description: "The file path to write, relative to the project root."
|
|
2174
2122
|
},
|
|
2175
|
-
|
|
2123
|
+
content: {
|
|
2176
2124
|
type: "string",
|
|
2177
|
-
description: "
|
|
2125
|
+
description: "The full content to write to the file."
|
|
2178
2126
|
}
|
|
2179
2127
|
},
|
|
2180
|
-
required: ["
|
|
2128
|
+
required: ["path", "content"]
|
|
2181
2129
|
}
|
|
2182
2130
|
},
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
var init_queryDatabase = __esm({
|
|
2193
|
-
"src/tools/code/queryDatabase.ts"() {
|
|
2194
|
-
"use strict";
|
|
2195
|
-
queryDatabaseTool = {
|
|
2196
|
-
clearable: true,
|
|
2197
|
-
definition: {
|
|
2198
|
-
name: "queryDatabase",
|
|
2199
|
-
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
2200
|
-
inputSchema: {
|
|
2201
|
-
type: "object",
|
|
2202
|
-
properties: {
|
|
2203
|
-
sql: {
|
|
2204
|
-
type: "string",
|
|
2205
|
-
description: "The SQL query to execute."
|
|
2131
|
+
streaming: /* @__PURE__ */ (() => {
|
|
2132
|
+
let lastNewlineCount = 0;
|
|
2133
|
+
let lastPath = "";
|
|
2134
|
+
return {
|
|
2135
|
+
transform: async (partial) => {
|
|
2136
|
+
const newlineCount = partial.content.split("\n").length - 1;
|
|
2137
|
+
if (partial.path !== lastPath || newlineCount < lastNewlineCount) {
|
|
2138
|
+
lastNewlineCount = 0;
|
|
2139
|
+
lastPath = partial.path;
|
|
2206
2140
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2141
|
+
if (newlineCount <= lastNewlineCount) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
lastNewlineCount = newlineCount;
|
|
2145
|
+
const lastNewline = partial.content.lastIndexOf("\n");
|
|
2146
|
+
const completeContent = partial.content.substring(0, lastNewline + 1);
|
|
2147
|
+
const oldContent = await fs10.readFile(partial.path, "utf-8").catch(() => "");
|
|
2148
|
+
return `Writing ${partial.path} (${newlineCount} lines)
|
|
2149
|
+
${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
})(),
|
|
2153
|
+
async execute(input) {
|
|
2154
|
+
const release = await acquireFileLock(input.path);
|
|
2155
|
+
try {
|
|
2156
|
+
await fs10.mkdir(path5.dirname(input.path), { recursive: true });
|
|
2157
|
+
let oldContent = null;
|
|
2158
|
+
try {
|
|
2159
|
+
oldContent = await fs10.readFile(input.path, "utf-8");
|
|
2160
|
+
} catch {
|
|
2161
|
+
}
|
|
2162
|
+
await fs10.writeFile(input.path, input.content, "utf-8");
|
|
2163
|
+
const lineCount = input.content.split("\n").length;
|
|
2164
|
+
const label = oldContent !== null ? "Wrote" : "Created";
|
|
2165
|
+
return `${label} ${input.path} (${lineCount} lines)
|
|
2166
|
+
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
return `Error writing file: ${err.message}`;
|
|
2169
|
+
} finally {
|
|
2170
|
+
release();
|
|
2209
2171
|
}
|
|
2210
|
-
},
|
|
2211
|
-
async execute() {
|
|
2212
|
-
return "ok";
|
|
2213
2172
|
}
|
|
2214
2173
|
};
|
|
2215
2174
|
}
|
|
2216
2175
|
});
|
|
2217
2176
|
|
|
2218
|
-
// src/
|
|
2219
|
-
|
|
2220
|
-
const
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
}
|
|
2226
|
-
var VISION_MODEL, VISION_MODEL_OVERRIDE;
|
|
2227
|
-
var init_analyzeImage = __esm({
|
|
2228
|
-
"src/subagents/common/analyzeImage.ts"() {
|
|
2229
|
-
"use strict";
|
|
2230
|
-
init_runCli();
|
|
2231
|
-
VISION_MODEL = "claude-4-6-sonnet";
|
|
2232
|
-
VISION_MODEL_OVERRIDE = JSON.stringify({
|
|
2233
|
-
model: VISION_MODEL,
|
|
2234
|
-
config: { thinkingBudget: "off" }
|
|
2235
|
-
});
|
|
2177
|
+
// src/tools/code/editFile/_helpers.ts
|
|
2178
|
+
function buildLineOffsets(content) {
|
|
2179
|
+
const offsets = [0];
|
|
2180
|
+
for (let i = 0; i < content.length; i++) {
|
|
2181
|
+
if (content[i] === "\n") {
|
|
2182
|
+
offsets.push(i + 1);
|
|
2183
|
+
}
|
|
2236
2184
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
// src/tools/_helpers/screenshot.ts
|
|
2240
|
-
function buildScreenshotAnalysisPrompt(opts) {
|
|
2241
|
-
let p = opts?.prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2242
|
-
if (opts?.styleMap) {
|
|
2243
|
-
p += `
|
|
2244
|
-
|
|
2245
|
-
The following styleMap describes the computed layout state at the moment of capture. Use it to verify typography, spacing, overflow, and element dimensions \u2014 it is more accurate than visual estimation from the image.
|
|
2246
|
-
|
|
2247
|
-
<style_map>
|
|
2248
|
-
${opts.styleMap}
|
|
2249
|
-
</style_map>`;
|
|
2250
|
-
}
|
|
2251
|
-
p += `
|
|
2252
|
-
|
|
2253
|
-
${TEXT_WRAP_DISCLAIMER}`;
|
|
2254
|
-
return p;
|
|
2185
|
+
return offsets;
|
|
2255
2186
|
}
|
|
2256
|
-
|
|
2257
|
-
let
|
|
2258
|
-
let
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
onLog = promptOrOptions.onLog;
|
|
2266
|
-
} else {
|
|
2267
|
-
prompt = promptOrOptions;
|
|
2268
|
-
}
|
|
2269
|
-
let url;
|
|
2270
|
-
let styleMap;
|
|
2271
|
-
if (existingUrl) {
|
|
2272
|
-
url = existingUrl;
|
|
2273
|
-
} else {
|
|
2274
|
-
const ssResult = await sidecarRequest(
|
|
2275
|
-
"/screenshot-full-page",
|
|
2276
|
-
path11 ? { path: path11 } : void 0,
|
|
2277
|
-
{ timeout: 12e4 }
|
|
2278
|
-
);
|
|
2279
|
-
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
2280
|
-
if (!url) {
|
|
2281
|
-
throw new Error(
|
|
2282
|
-
`No URL in sidecar response. The browser may not be ready yet. Response: ${JSON.stringify(ssResult)}`
|
|
2283
|
-
);
|
|
2187
|
+
function lineAtOffset(offsets, charIndex) {
|
|
2188
|
+
let lo = 0;
|
|
2189
|
+
let hi = offsets.length - 1;
|
|
2190
|
+
while (lo < hi) {
|
|
2191
|
+
const mid = lo + hi + 1 >> 1;
|
|
2192
|
+
if (offsets[mid] <= charIndex) {
|
|
2193
|
+
lo = mid;
|
|
2194
|
+
} else {
|
|
2195
|
+
hi = mid - 1;
|
|
2284
2196
|
}
|
|
2285
|
-
styleMap = ssResult?.styleMap;
|
|
2286
|
-
}
|
|
2287
|
-
if (prompt === false) {
|
|
2288
|
-
return url;
|
|
2289
2197
|
}
|
|
2290
|
-
|
|
2291
|
-
prompt: prompt || void 0,
|
|
2292
|
-
styleMap
|
|
2293
|
-
});
|
|
2294
|
-
const analysis = await analyzeImage({
|
|
2295
|
-
prompt: analysisPrompt,
|
|
2296
|
-
imageUrl: url,
|
|
2297
|
-
onLog
|
|
2298
|
-
});
|
|
2299
|
-
return JSON.stringify({ url, analysis, ...styleMap ? { styleMap } : {} });
|
|
2198
|
+
return lo + 1;
|
|
2300
2199
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
"use strict";
|
|
2305
|
-
init_sidecar();
|
|
2306
|
-
init_analyzeImage();
|
|
2307
|
-
SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
2308
|
-
|
|
2309
|
-
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2310
|
-
TEXT_WRAP_DISCLAIMER = `Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.`;
|
|
2200
|
+
function findOccurrences(content, searchString) {
|
|
2201
|
+
if (!searchString) {
|
|
2202
|
+
return [];
|
|
2311
2203
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
});
|
|
2320
|
-
const wait = lockQueue;
|
|
2321
|
-
lockQueue = next;
|
|
2322
|
-
return wait.then(() => release);
|
|
2323
|
-
}
|
|
2324
|
-
async function checkBrowserConnected() {
|
|
2325
|
-
try {
|
|
2326
|
-
const status = await sidecarRequest(
|
|
2327
|
-
"/browser-status",
|
|
2328
|
-
{},
|
|
2329
|
-
{ timeout: 5e3 }
|
|
2330
|
-
);
|
|
2331
|
-
if (!status.connected) {
|
|
2332
|
-
return {
|
|
2333
|
-
connected: false,
|
|
2334
|
-
error: "The browser preview is not connected. The user needs to open the preview."
|
|
2335
|
-
};
|
|
2204
|
+
const offsets = buildLineOffsets(content);
|
|
2205
|
+
const results = [];
|
|
2206
|
+
let pos = 0;
|
|
2207
|
+
while (pos <= content.length - searchString.length) {
|
|
2208
|
+
const idx = content.indexOf(searchString, pos);
|
|
2209
|
+
if (idx === -1) {
|
|
2210
|
+
break;
|
|
2336
2211
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
return {
|
|
2340
|
-
connected: false,
|
|
2341
|
-
error: err?.message || "Could not check browser status. The dev environment may not be running."
|
|
2342
|
-
};
|
|
2212
|
+
results.push({ index: idx, line: lineAtOffset(offsets, idx) });
|
|
2213
|
+
pos = idx + 1;
|
|
2343
2214
|
}
|
|
2215
|
+
return results;
|
|
2344
2216
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
"
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
lockQueue = Promise.resolve();
|
|
2217
|
+
function flexibleMatch(content, searchString) {
|
|
2218
|
+
const contentLines = content.split("\n");
|
|
2219
|
+
const searchLines = searchString.split("\n").map((l) => l.trimStart());
|
|
2220
|
+
if (searchLines.length === 0) {
|
|
2221
|
+
return null;
|
|
2351
2222
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
let inflight = false;
|
|
2360
|
-
let stopped = false;
|
|
2361
|
-
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
2362
|
-
async function tick() {
|
|
2363
|
-
if (stopped || signal?.aborted || inflight) {
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
inflight = true;
|
|
2367
|
-
try {
|
|
2368
|
-
const context = getContext();
|
|
2369
|
-
if (!context || context === lastContext) {
|
|
2370
|
-
return;
|
|
2371
|
-
}
|
|
2372
|
-
lastContext = context;
|
|
2373
|
-
const res = await fetch(url, {
|
|
2374
|
-
method: "POST",
|
|
2375
|
-
headers: {
|
|
2376
|
-
"Content-Type": "application/json",
|
|
2377
|
-
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
2378
|
-
},
|
|
2379
|
-
body: JSON.stringify({ context }),
|
|
2380
|
-
signal
|
|
2381
|
-
});
|
|
2382
|
-
if (!res.ok) {
|
|
2383
|
-
return;
|
|
2384
|
-
}
|
|
2385
|
-
const data = await res.json();
|
|
2386
|
-
if (!data.label || data.label === lastLabel) {
|
|
2387
|
-
return;
|
|
2388
|
-
}
|
|
2389
|
-
lastLabel = data.label;
|
|
2390
|
-
if (stopped) {
|
|
2391
|
-
return;
|
|
2223
|
+
const matches = [];
|
|
2224
|
+
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
2225
|
+
let allMatch = true;
|
|
2226
|
+
for (let j = 0; j < searchLines.length; j++) {
|
|
2227
|
+
if (contentLines[i + j].trimStart() !== searchLines[j]) {
|
|
2228
|
+
allMatch = false;
|
|
2229
|
+
break;
|
|
2392
2230
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
inflight = false;
|
|
2231
|
+
}
|
|
2232
|
+
if (allMatch) {
|
|
2233
|
+
matches.push(i);
|
|
2397
2234
|
}
|
|
2398
2235
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
}
|
|
2236
|
+
if (matches.length !== 1) {
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
const startIdx = matches[0];
|
|
2240
|
+
const matchedText = contentLines.slice(startIdx, startIdx + searchLines.length).join("\n");
|
|
2241
|
+
const offsets = buildLineOffsets(content);
|
|
2402
2242
|
return {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2243
|
+
matchedText,
|
|
2244
|
+
index: offsets[startIdx],
|
|
2245
|
+
line: startIdx + 1
|
|
2246
|
+
// 1-based
|
|
2407
2247
|
};
|
|
2408
2248
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2249
|
+
function replaceAt(content, index, oldLength, newString) {
|
|
2250
|
+
return content.slice(0, index) + newString + content.slice(index + oldLength);
|
|
2251
|
+
}
|
|
2252
|
+
function formatOccurrenceError(count, lines, filePath) {
|
|
2253
|
+
return `old_string found ${count} times in ${filePath} (at lines ${lines.join(", ")}) \u2014 must be unique. Include more surrounding context to disambiguate, or use replace_all to replace every occurrence.`;
|
|
2254
|
+
}
|
|
2255
|
+
var init_helpers2 = __esm({
|
|
2256
|
+
"src/tools/code/editFile/_helpers.ts"() {
|
|
2411
2257
|
"use strict";
|
|
2412
2258
|
}
|
|
2413
2259
|
});
|
|
2414
2260
|
|
|
2415
|
-
// src/
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2261
|
+
// src/tools/code/editFile/index.ts
|
|
2262
|
+
import fs11 from "fs/promises";
|
|
2263
|
+
var editFileTool;
|
|
2264
|
+
var init_editFile = __esm({
|
|
2265
|
+
"src/tools/code/editFile/index.ts"() {
|
|
2266
|
+
"use strict";
|
|
2267
|
+
init_diff();
|
|
2268
|
+
init_fileLock();
|
|
2269
|
+
init_helpers2();
|
|
2270
|
+
editFileTool = {
|
|
2271
|
+
clearable: true,
|
|
2272
|
+
definition: {
|
|
2273
|
+
name: "editFile",
|
|
2274
|
+
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.",
|
|
2275
|
+
inputSchema: {
|
|
2276
|
+
type: "object",
|
|
2277
|
+
properties: {
|
|
2278
|
+
path: {
|
|
2279
|
+
type: "string",
|
|
2280
|
+
description: "The file path to edit, relative to the project root."
|
|
2281
|
+
},
|
|
2282
|
+
old_string: {
|
|
2283
|
+
type: "string",
|
|
2284
|
+
description: "The exact string to find and replace. Must be unique in the file unless replace_all is true."
|
|
2285
|
+
},
|
|
2286
|
+
new_string: {
|
|
2287
|
+
type: "string",
|
|
2288
|
+
description: "The replacement string."
|
|
2289
|
+
},
|
|
2290
|
+
replace_all: {
|
|
2291
|
+
type: "boolean",
|
|
2292
|
+
description: "If true, replace every occurrence of old_string in the file. Defaults to false."
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
required: ["path", "old_string", "new_string"]
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
async execute(input) {
|
|
2299
|
+
const release = await acquireFileLock(input.path);
|
|
2300
|
+
try {
|
|
2301
|
+
const content = await fs11.readFile(input.path, "utf-8");
|
|
2302
|
+
const { old_string, new_string, replace_all } = input;
|
|
2303
|
+
const occurrences = findOccurrences(content, old_string);
|
|
2304
|
+
if (replace_all) {
|
|
2305
|
+
if (occurrences.length === 0) {
|
|
2306
|
+
return `Error: old_string not found in ${input.path}.`;
|
|
2307
|
+
}
|
|
2308
|
+
let updated = content;
|
|
2309
|
+
for (let i = occurrences.length - 1; i >= 0; i--) {
|
|
2310
|
+
updated = replaceAt(
|
|
2311
|
+
updated,
|
|
2312
|
+
occurrences[i].index,
|
|
2313
|
+
old_string.length,
|
|
2314
|
+
new_string
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
await fs11.writeFile(input.path, updated, "utf-8");
|
|
2318
|
+
return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
|
|
2319
|
+
${unifiedDiff(input.path, content, updated)}`;
|
|
2320
|
+
}
|
|
2321
|
+
if (occurrences.length === 1) {
|
|
2322
|
+
const updated = replaceAt(
|
|
2323
|
+
content,
|
|
2324
|
+
occurrences[0].index,
|
|
2325
|
+
old_string.length,
|
|
2326
|
+
new_string
|
|
2327
|
+
);
|
|
2328
|
+
await fs11.writeFile(input.path, updated, "utf-8");
|
|
2329
|
+
return `Updated ${input.path}
|
|
2330
|
+
${unifiedDiff(input.path, content, updated)}`;
|
|
2331
|
+
}
|
|
2332
|
+
if (occurrences.length > 1) {
|
|
2333
|
+
const lines = occurrences.map((o) => o.line);
|
|
2334
|
+
return `Error: ${formatOccurrenceError(occurrences.length, lines, input.path)}`;
|
|
2335
|
+
}
|
|
2336
|
+
const flex = flexibleMatch(content, old_string);
|
|
2337
|
+
if (flex) {
|
|
2338
|
+
const updated = replaceAt(
|
|
2339
|
+
content,
|
|
2340
|
+
flex.index,
|
|
2341
|
+
flex.matchedText.length,
|
|
2342
|
+
new_string
|
|
2343
|
+
);
|
|
2344
|
+
await fs11.writeFile(input.path, updated, "utf-8");
|
|
2345
|
+
return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
|
|
2346
|
+
${unifiedDiff(input.path, content, updated)}`;
|
|
2347
|
+
}
|
|
2348
|
+
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
2349
|
+
} catch (err) {
|
|
2350
|
+
return `Error editing file: ${err.message}`;
|
|
2351
|
+
} finally {
|
|
2352
|
+
release();
|
|
2489
2353
|
}
|
|
2490
2354
|
}
|
|
2491
|
-
}
|
|
2355
|
+
};
|
|
2492
2356
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2357
|
+
});
|
|
2358
|
+
|
|
2359
|
+
// src/tools/code/bash.ts
|
|
2360
|
+
import { spawn as spawn2 } from "child_process";
|
|
2361
|
+
var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES3, bashTool;
|
|
2362
|
+
var init_bash = __esm({
|
|
2363
|
+
"src/tools/code/bash.ts"() {
|
|
2364
|
+
"use strict";
|
|
2365
|
+
DEFAULT_TIMEOUT_MS = 12e4;
|
|
2366
|
+
DEFAULT_MAX_LINES3 = 500;
|
|
2367
|
+
bashTool = {
|
|
2368
|
+
clearable: true,
|
|
2369
|
+
definition: {
|
|
2370
|
+
name: "bash",
|
|
2371
|
+
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 by default.",
|
|
2372
|
+
inputSchema: {
|
|
2373
|
+
type: "object",
|
|
2374
|
+
properties: {
|
|
2375
|
+
command: {
|
|
2376
|
+
type: "string",
|
|
2377
|
+
description: "The shell command to execute."
|
|
2378
|
+
},
|
|
2379
|
+
cwd: {
|
|
2380
|
+
type: "string",
|
|
2381
|
+
description: "Working directory to run the command in. Defaults to the project root."
|
|
2382
|
+
},
|
|
2383
|
+
timeout: {
|
|
2384
|
+
type: "number",
|
|
2385
|
+
description: "Timeout in seconds. Defaults to 120. Use higher values for long-running commands like builds or test suites."
|
|
2386
|
+
},
|
|
2387
|
+
maxLines: {
|
|
2388
|
+
type: "number",
|
|
2389
|
+
description: "Maximum number of output lines to return. Defaults to 500. Set to 0 for no limit."
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
required: ["command"]
|
|
2393
|
+
}
|
|
2394
|
+
},
|
|
2395
|
+
async execute(input, context) {
|
|
2396
|
+
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
|
|
2397
|
+
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
2398
|
+
return new Promise((resolve2) => {
|
|
2399
|
+
const child = spawn2("sh", ["-c", input.command], {
|
|
2400
|
+
cwd: input.cwd || void 0,
|
|
2401
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
2402
|
+
});
|
|
2403
|
+
let output = "";
|
|
2404
|
+
child.stdout.on("data", (chunk) => {
|
|
2405
|
+
const text = chunk.toString();
|
|
2406
|
+
output += text;
|
|
2407
|
+
context?.onLog?.(text);
|
|
2408
|
+
});
|
|
2409
|
+
child.stderr.on("data", (chunk) => {
|
|
2410
|
+
const text = chunk.toString();
|
|
2411
|
+
output += text;
|
|
2412
|
+
context?.onLog?.(text);
|
|
2413
|
+
});
|
|
2414
|
+
const timer = setTimeout(() => {
|
|
2415
|
+
child.kill("SIGTERM");
|
|
2416
|
+
}, timeoutMs);
|
|
2417
|
+
child.on("close", (code) => {
|
|
2418
|
+
clearTimeout(timer);
|
|
2419
|
+
if (!output) {
|
|
2420
|
+
if (code && code !== 0) {
|
|
2421
|
+
resolve2(`Error: process exited with code ${code}`);
|
|
2422
|
+
} else {
|
|
2423
|
+
resolve2("(no output)");
|
|
2424
|
+
}
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
const lines = output.split("\n");
|
|
2428
|
+
if (lines.length > maxLines) {
|
|
2429
|
+
resolve2(
|
|
2430
|
+
lines.slice(0, maxLines).join("\n") + `
|
|
2431
|
+
|
|
2432
|
+
(truncated at ${maxLines} lines of ${lines.length} total \u2014 increase maxLines to see more)`
|
|
2433
|
+
);
|
|
2434
|
+
} else {
|
|
2435
|
+
resolve2(output);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
child.on("error", (err) => {
|
|
2439
|
+
clearTimeout(timer);
|
|
2440
|
+
resolve2(`Error: ${err.message}`);
|
|
2441
|
+
});
|
|
2442
|
+
});
|
|
2498
2443
|
}
|
|
2499
|
-
}
|
|
2500
|
-
if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
|
|
2501
|
-
return false;
|
|
2502
|
-
}
|
|
2503
|
-
if (msg.role === "user" && msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
|
|
2504
|
-
return false;
|
|
2505
|
-
}
|
|
2506
|
-
return true;
|
|
2507
|
-
}).map((msg) => {
|
|
2508
|
-
if (msg.role === "user" && typeof msg.content === "string" && msg.content.startsWith("@@automated::")) {
|
|
2509
|
-
return {
|
|
2510
|
-
...msg,
|
|
2511
|
-
content: msg.content.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "")
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
if (!Array.isArray(msg.content)) {
|
|
2515
|
-
return msg;
|
|
2516
|
-
}
|
|
2517
|
-
const blocks = msg.content;
|
|
2518
|
-
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
2519
|
-
const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
2520
|
-
const thinking = blocks.filter(
|
|
2521
|
-
(b) => b.type === "thinking"
|
|
2522
|
-
).map((b) => ({ thinking: b.thinking, signature: b.signature }));
|
|
2523
|
-
const cleaned2 = {
|
|
2524
|
-
role: msg.role,
|
|
2525
|
-
content: text
|
|
2526
2444
|
};
|
|
2527
|
-
if (toolCalls.length > 0) {
|
|
2528
|
-
cleaned2.toolCalls = toolCalls;
|
|
2529
|
-
}
|
|
2530
|
-
if (thinking.length > 0) {
|
|
2531
|
-
cleaned2.thinking = thinking;
|
|
2532
|
-
}
|
|
2533
|
-
if (msg.hidden) {
|
|
2534
|
-
cleaned2.hidden = true;
|
|
2535
|
-
}
|
|
2536
|
-
return cleaned2;
|
|
2537
|
-
});
|
|
2538
|
-
return [...prefix, ...cleaned];
|
|
2539
|
-
}
|
|
2540
|
-
var init_cleanMessages = __esm({
|
|
2541
|
-
"src/subagents/common/cleanMessages.ts"() {
|
|
2542
|
-
"use strict";
|
|
2543
2445
|
}
|
|
2544
2446
|
});
|
|
2545
2447
|
|
|
2546
|
-
// src/
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2448
|
+
// src/tools/code/grep.ts
|
|
2449
|
+
import { exec } from "child_process";
|
|
2450
|
+
function formatResults(stdout, max) {
|
|
2451
|
+
const lines = stdout.trim().split("\n");
|
|
2452
|
+
let result = lines.join("\n");
|
|
2453
|
+
if (lines.length >= max) {
|
|
2454
|
+
result += `
|
|
2455
|
+
|
|
2456
|
+
(truncated at ${max} results \u2014 increase maxResults to see more)`;
|
|
2457
|
+
}
|
|
2458
|
+
return result;
|
|
2459
|
+
}
|
|
2460
|
+
var DEFAULT_MAX, grepTool;
|
|
2461
|
+
var init_grep = __esm({
|
|
2462
|
+
"src/tools/code/grep.ts"() {
|
|
2463
|
+
"use strict";
|
|
2464
|
+
DEFAULT_MAX = 50;
|
|
2465
|
+
grepTool = {
|
|
2466
|
+
clearable: true,
|
|
2467
|
+
definition: {
|
|
2468
|
+
name: "grep",
|
|
2469
|
+
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. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
|
|
2470
|
+
inputSchema: {
|
|
2471
|
+
type: "object",
|
|
2472
|
+
properties: {
|
|
2473
|
+
pattern: {
|
|
2474
|
+
type: "string",
|
|
2475
|
+
description: "The search pattern (regex supported)."
|
|
2476
|
+
},
|
|
2477
|
+
path: {
|
|
2478
|
+
type: "string",
|
|
2479
|
+
description: "Directory or file to search in. Defaults to current directory."
|
|
2480
|
+
},
|
|
2481
|
+
glob: {
|
|
2482
|
+
type: "string",
|
|
2483
|
+
description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
|
|
2484
|
+
},
|
|
2485
|
+
maxResults: {
|
|
2486
|
+
type: "number",
|
|
2487
|
+
description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
required: ["pattern"]
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
async execute(input) {
|
|
2494
|
+
const searchPath = input.path || ".";
|
|
2495
|
+
const max = input.maxResults || DEFAULT_MAX;
|
|
2496
|
+
const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
|
|
2497
|
+
const escaped = input.pattern.replace(/'/g, "'\\''");
|
|
2498
|
+
const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
|
|
2499
|
+
const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
|
|
2500
|
+
return new Promise((resolve2) => {
|
|
2501
|
+
exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
2502
|
+
if (stdout?.trim()) {
|
|
2503
|
+
resolve2(formatResults(stdout, max));
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
|
|
2507
|
+
if (grepStdout?.trim()) {
|
|
2508
|
+
resolve2(formatResults(grepStdout, max));
|
|
2509
|
+
} else {
|
|
2510
|
+
resolve2("No matches found.");
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2513
|
+
});
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
// src/tools/code/glob.ts
|
|
2521
|
+
import fg from "fast-glob";
|
|
2522
|
+
var DEFAULT_MAX2, globTool;
|
|
2523
|
+
var init_glob = __esm({
|
|
2524
|
+
"src/tools/code/glob.ts"() {
|
|
2525
|
+
"use strict";
|
|
2526
|
+
DEFAULT_MAX2 = 200;
|
|
2527
|
+
globTool = {
|
|
2528
|
+
clearable: true,
|
|
2529
|
+
definition: {
|
|
2530
|
+
name: "glob",
|
|
2531
|
+
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.',
|
|
2532
|
+
inputSchema: {
|
|
2533
|
+
type: "object",
|
|
2534
|
+
properties: {
|
|
2535
|
+
pattern: {
|
|
2536
|
+
type: "string",
|
|
2537
|
+
description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
|
|
2538
|
+
},
|
|
2539
|
+
maxResults: {
|
|
2540
|
+
type: "number",
|
|
2541
|
+
description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
|
|
2542
|
+
}
|
|
2543
|
+
},
|
|
2544
|
+
required: ["pattern"]
|
|
2545
|
+
}
|
|
2546
|
+
},
|
|
2547
|
+
async execute(input) {
|
|
2548
|
+
try {
|
|
2549
|
+
const max = input.maxResults || DEFAULT_MAX2;
|
|
2550
|
+
const files = await fg(input.pattern, {
|
|
2551
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
2552
|
+
dot: false
|
|
2553
|
+
});
|
|
2554
|
+
if (files.length === 0) {
|
|
2555
|
+
return "No files found.";
|
|
2556
|
+
}
|
|
2557
|
+
const sorted = files.sort();
|
|
2558
|
+
const truncated = sorted.slice(0, max);
|
|
2559
|
+
let result = truncated.join("\n");
|
|
2560
|
+
if (sorted.length > max) {
|
|
2561
|
+
result += `
|
|
2562
|
+
|
|
2563
|
+
(showing ${max} of ${sorted.length} matches \u2014 increase maxResults to see all)`;
|
|
2564
|
+
}
|
|
2565
|
+
return result;
|
|
2566
|
+
} catch (err) {
|
|
2567
|
+
return `Error: ${err.message}`;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
// src/tools/code/listDir.ts
|
|
2575
|
+
import fs12 from "fs/promises";
|
|
2576
|
+
import path6 from "path";
|
|
2577
|
+
async function readAndSort(dirPath) {
|
|
2578
|
+
const entries = await fs12.readdir(dirPath, { withFileTypes: true });
|
|
2579
|
+
return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
|
|
2580
|
+
if (a.isDirectory() && !b.isDirectory()) {
|
|
2581
|
+
return -1;
|
|
2582
|
+
}
|
|
2583
|
+
if (!a.isDirectory() && b.isDirectory()) {
|
|
2584
|
+
return 1;
|
|
2585
|
+
}
|
|
2586
|
+
return a.name.localeCompare(b.name);
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
async function collapsePath(basePath, name) {
|
|
2590
|
+
let display = name;
|
|
2591
|
+
let current = path6.join(basePath, name);
|
|
2592
|
+
for (; ; ) {
|
|
2593
|
+
let children;
|
|
2594
|
+
try {
|
|
2595
|
+
children = await readAndSort(current);
|
|
2596
|
+
} catch {
|
|
2597
|
+
break;
|
|
2598
|
+
}
|
|
2599
|
+
if (children.length === 1 && children[0].isDirectory()) {
|
|
2600
|
+
display += "/" + children[0].name;
|
|
2601
|
+
current = path6.join(current, children[0].name);
|
|
2602
|
+
} else {
|
|
2603
|
+
break;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return [display, current];
|
|
2607
|
+
}
|
|
2608
|
+
function formatSize(bytes) {
|
|
2609
|
+
if (bytes < 1e3) {
|
|
2610
|
+
return `${bytes} B`;
|
|
2611
|
+
}
|
|
2612
|
+
if (bytes < 1e6) {
|
|
2613
|
+
return `${(bytes / 1e3).toFixed(1)} kB`;
|
|
2614
|
+
}
|
|
2615
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
2616
|
+
}
|
|
2617
|
+
async function formatFile(dirPath, name, indent) {
|
|
2618
|
+
try {
|
|
2619
|
+
const stat = await fs12.stat(path6.join(dirPath, name));
|
|
2620
|
+
return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
|
|
2621
|
+
} catch {
|
|
2622
|
+
return `${indent}${name}`;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
var EXCLUDE, MAX_CHILDREN, listDirTool;
|
|
2626
|
+
var init_listDir = __esm({
|
|
2627
|
+
"src/tools/code/listDir.ts"() {
|
|
2628
|
+
"use strict";
|
|
2629
|
+
EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2630
|
+
MAX_CHILDREN = 15;
|
|
2631
|
+
listDirTool = {
|
|
2632
|
+
clearable: true,
|
|
2633
|
+
definition: {
|
|
2634
|
+
name: "listDir",
|
|
2635
|
+
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.",
|
|
2636
|
+
inputSchema: {
|
|
2637
|
+
type: "object",
|
|
2638
|
+
properties: {
|
|
2639
|
+
path: {
|
|
2640
|
+
type: "string",
|
|
2641
|
+
description: 'Directory path to list, relative to project root. Defaults to ".".'
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
},
|
|
2646
|
+
async execute(input) {
|
|
2647
|
+
const dirPath = input.path || ".";
|
|
2648
|
+
try {
|
|
2649
|
+
const entries = await readAndSort(dirPath);
|
|
2650
|
+
const lines = [];
|
|
2651
|
+
for (const entry of entries) {
|
|
2652
|
+
if (entry.isDirectory()) {
|
|
2653
|
+
const [displayName, finalPath] = await collapsePath(
|
|
2654
|
+
dirPath,
|
|
2655
|
+
entry.name
|
|
2656
|
+
);
|
|
2657
|
+
lines.push(`${displayName}/`);
|
|
2658
|
+
try {
|
|
2659
|
+
const children = await readAndSort(finalPath);
|
|
2660
|
+
const capped = children.slice(0, MAX_CHILDREN);
|
|
2661
|
+
for (const child of capped) {
|
|
2662
|
+
if (child.isDirectory()) {
|
|
2663
|
+
lines.push(` ${child.name}/`);
|
|
2664
|
+
} else {
|
|
2665
|
+
lines.push(await formatFile(finalPath, child.name, " "));
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (children.length > MAX_CHILDREN) {
|
|
2669
|
+
lines.push(` ... and ${children.length - MAX_CHILDREN} more`);
|
|
2670
|
+
}
|
|
2671
|
+
} catch {
|
|
2672
|
+
}
|
|
2673
|
+
} else {
|
|
2674
|
+
lines.push(await formatFile(dirPath, entry.name, ""));
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return lines.join("\n") || "(empty directory)";
|
|
2678
|
+
} catch (err) {
|
|
2679
|
+
return `Error listing directory: ${err.message}`;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
// src/tools/code/editsFinished.ts
|
|
2687
|
+
var editsFinishedTool;
|
|
2688
|
+
var init_editsFinished = __esm({
|
|
2689
|
+
"src/tools/code/editsFinished.ts"() {
|
|
2690
|
+
"use strict";
|
|
2691
|
+
editsFinishedTool = {
|
|
2692
|
+
clearable: false,
|
|
2693
|
+
definition: {
|
|
2694
|
+
name: "editsFinished",
|
|
2695
|
+
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.",
|
|
2696
|
+
inputSchema: {
|
|
2697
|
+
type: "object",
|
|
2698
|
+
properties: {},
|
|
2699
|
+
required: []
|
|
2700
|
+
}
|
|
2701
|
+
},
|
|
2702
|
+
async execute() {
|
|
2703
|
+
return "Preview updated.";
|
|
2704
|
+
}
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
|
|
2709
|
+
// src/tools/code/lspDiagnostics.ts
|
|
2710
|
+
var lspDiagnosticsTool;
|
|
2711
|
+
var init_lspDiagnostics = __esm({
|
|
2712
|
+
"src/tools/code/lspDiagnostics.ts"() {
|
|
2713
|
+
"use strict";
|
|
2714
|
+
init_lsp();
|
|
2715
|
+
lspDiagnosticsTool = {
|
|
2716
|
+
clearable: true,
|
|
2717
|
+
definition: {
|
|
2718
|
+
name: "lspDiagnostics",
|
|
2719
|
+
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.",
|
|
2720
|
+
inputSchema: {
|
|
2721
|
+
type: "object",
|
|
2722
|
+
properties: {
|
|
2723
|
+
file: {
|
|
2724
|
+
type: "string",
|
|
2725
|
+
description: "File path relative to workspace root."
|
|
2726
|
+
}
|
|
2727
|
+
},
|
|
2728
|
+
required: ["file"]
|
|
2729
|
+
}
|
|
2730
|
+
},
|
|
2731
|
+
async execute(input) {
|
|
2732
|
+
const data = await lspRequest("/diagnostics", { file: input.file });
|
|
2733
|
+
const diags = data.diagnostics || [];
|
|
2734
|
+
if (diags.length === 0) {
|
|
2735
|
+
return "No diagnostics \u2014 file is clean.";
|
|
2736
|
+
}
|
|
2737
|
+
const lines = [];
|
|
2738
|
+
for (const d of diags) {
|
|
2739
|
+
let line = `${d.severity}: ${d.file}:${d.line}:${d.column} \u2014 ${d.message}`;
|
|
2740
|
+
try {
|
|
2741
|
+
const actionsData = await lspRequest("/code-actions", {
|
|
2742
|
+
file: d.file,
|
|
2743
|
+
startLine: d.line,
|
|
2744
|
+
startColumn: d.column,
|
|
2745
|
+
endLine: d.endLine ?? d.line,
|
|
2746
|
+
endColumn: d.endColumn ?? d.column,
|
|
2747
|
+
diagnostics: [d]
|
|
2748
|
+
});
|
|
2749
|
+
const actions = actionsData.actions || [];
|
|
2750
|
+
if (actions.length > 0) {
|
|
2751
|
+
const fixes = actions.map((a) => a.title).join("; ");
|
|
2752
|
+
line += `
|
|
2753
|
+
Quick fixes: ${fixes}`;
|
|
2754
|
+
}
|
|
2755
|
+
} catch {
|
|
2756
|
+
}
|
|
2757
|
+
lines.push(line);
|
|
2758
|
+
}
|
|
2759
|
+
return lines.join("\n");
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2764
|
+
|
|
2765
|
+
// src/tools/code/restartProcess.ts
|
|
2766
|
+
var restartProcessTool;
|
|
2767
|
+
var init_restartProcess = __esm({
|
|
2768
|
+
"src/tools/code/restartProcess.ts"() {
|
|
2769
|
+
"use strict";
|
|
2770
|
+
init_lsp();
|
|
2771
|
+
restartProcessTool = {
|
|
2772
|
+
clearable: false,
|
|
2773
|
+
definition: {
|
|
2774
|
+
name: "restartProcess",
|
|
2775
|
+
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.",
|
|
2776
|
+
inputSchema: {
|
|
2777
|
+
type: "object",
|
|
2778
|
+
properties: {
|
|
2779
|
+
name: {
|
|
2780
|
+
type: "string",
|
|
2781
|
+
description: 'Process name to restart. Currently supported: "devServer".'
|
|
2782
|
+
}
|
|
2783
|
+
},
|
|
2784
|
+
required: ["name"]
|
|
2785
|
+
}
|
|
2786
|
+
},
|
|
2787
|
+
async execute(input) {
|
|
2788
|
+
const data = await lspRequest("/restart-process", { name: input.name });
|
|
2789
|
+
if (data.ok) {
|
|
2790
|
+
await new Promise((resolve2) => setTimeout(resolve2, 5e3));
|
|
2791
|
+
return `Restarted ${input.name}.`;
|
|
2792
|
+
}
|
|
2793
|
+
return `Error: unexpected response: ${JSON.stringify(data)}`;
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
// src/tools/code/runScenario.ts
|
|
2800
|
+
var runScenarioTool;
|
|
2801
|
+
var init_runScenario = __esm({
|
|
2802
|
+
"src/tools/code/runScenario.ts"() {
|
|
2803
|
+
"use strict";
|
|
2804
|
+
runScenarioTool = {
|
|
2805
|
+
clearable: true,
|
|
2806
|
+
definition: {
|
|
2807
|
+
name: "runScenario",
|
|
2808
|
+
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.",
|
|
2809
|
+
inputSchema: {
|
|
2810
|
+
type: "object",
|
|
2811
|
+
properties: {
|
|
2812
|
+
scenarioId: {
|
|
2813
|
+
type: "string",
|
|
2814
|
+
description: "The scenario ID from mindstudio.json."
|
|
2815
|
+
},
|
|
2816
|
+
skipTruncate: {
|
|
2817
|
+
type: "boolean",
|
|
2818
|
+
description: "When true, skip the database reset step and run the seed function against existing data. Defaults to false (clean-slate)."
|
|
2819
|
+
}
|
|
2820
|
+
},
|
|
2821
|
+
required: ["scenarioId"]
|
|
2822
|
+
}
|
|
2823
|
+
},
|
|
2824
|
+
async execute() {
|
|
2825
|
+
return "ok";
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
|
|
2831
|
+
// src/tools/code/runMethod.ts
|
|
2832
|
+
var runMethodTool;
|
|
2833
|
+
var init_runMethod = __esm({
|
|
2834
|
+
"src/tools/code/runMethod.ts"() {
|
|
2835
|
+
"use strict";
|
|
2836
|
+
runMethodTool = {
|
|
2837
|
+
clearable: true,
|
|
2838
|
+
definition: {
|
|
2839
|
+
name: "runMethod",
|
|
2840
|
+
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. Return synchronously - no need to sleep before checking results.",
|
|
2841
|
+
inputSchema: {
|
|
2842
|
+
type: "object",
|
|
2843
|
+
properties: {
|
|
2844
|
+
method: {
|
|
2845
|
+
type: "string",
|
|
2846
|
+
description: 'The method export name (camelCase, e.g. "listHaikus").'
|
|
2847
|
+
},
|
|
2848
|
+
input: {
|
|
2849
|
+
type: "object",
|
|
2850
|
+
description: "The input payload to pass to the method. Omit for methods that take no input."
|
|
2851
|
+
},
|
|
2852
|
+
roles: {
|
|
2853
|
+
type: "array",
|
|
2854
|
+
items: { type: "string" },
|
|
2855
|
+
description: 'Optional. Role names for this request (e.g. ["admin"]). Can be used without userId to test role-gated logic. Overrides session-level impersonation for this call only.'
|
|
2856
|
+
},
|
|
2857
|
+
userId: {
|
|
2858
|
+
type: "string",
|
|
2859
|
+
description: "Optional. User ID for this request \u2014 use a managed user's ID to simulate their identity. Overrides session-level impersonation for this call only."
|
|
2860
|
+
}
|
|
2861
|
+
},
|
|
2862
|
+
required: ["method"]
|
|
2863
|
+
}
|
|
2864
|
+
},
|
|
2865
|
+
async execute() {
|
|
2866
|
+
return "ok";
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
});
|
|
2871
|
+
|
|
2872
|
+
// src/tools/code/queryDatabase.ts
|
|
2873
|
+
var queryDatabaseTool;
|
|
2874
|
+
var init_queryDatabase = __esm({
|
|
2875
|
+
"src/tools/code/queryDatabase.ts"() {
|
|
2876
|
+
"use strict";
|
|
2877
|
+
queryDatabaseTool = {
|
|
2878
|
+
clearable: true,
|
|
2879
|
+
definition: {
|
|
2880
|
+
name: "queryDatabase",
|
|
2881
|
+
description: "Execute a raw SQL query against the dev database and return the results. Use for inspecting data and debugging issues.",
|
|
2882
|
+
inputSchema: {
|
|
2883
|
+
type: "object",
|
|
2884
|
+
properties: {
|
|
2885
|
+
sql: {
|
|
2886
|
+
type: "string",
|
|
2887
|
+
description: "The SQL query to execute."
|
|
2888
|
+
}
|
|
2889
|
+
},
|
|
2890
|
+
required: ["sql"]
|
|
2891
|
+
}
|
|
2892
|
+
},
|
|
2893
|
+
async execute() {
|
|
2894
|
+
return "ok";
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
|
|
2900
|
+
// src/subagents/common/analyzeImage.ts
|
|
2901
|
+
async function analyzeImage(params) {
|
|
2902
|
+
const { prompt, imageUrl, timeout = 2e5, onLog } = params;
|
|
2903
|
+
return runCli(
|
|
2904
|
+
`mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(imageUrl)} --vision-model-override ${JSON.stringify(VISION_MODEL_OVERRIDE)} --output-key analysis --no-meta`,
|
|
2905
|
+
{ timeout, onLog }
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
var VISION_MODEL, VISION_MODEL_OVERRIDE;
|
|
2909
|
+
var init_analyzeImage = __esm({
|
|
2910
|
+
"src/subagents/common/analyzeImage.ts"() {
|
|
2911
|
+
"use strict";
|
|
2912
|
+
init_runCli();
|
|
2913
|
+
VISION_MODEL = "claude-4-6-sonnet";
|
|
2914
|
+
VISION_MODEL_OVERRIDE = JSON.stringify({
|
|
2915
|
+
model: VISION_MODEL,
|
|
2916
|
+
config: { thinkingBudget: "off" }
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
});
|
|
2920
|
+
|
|
2921
|
+
// src/tools/_helpers/screenshot.ts
|
|
2922
|
+
function buildScreenshotAnalysisPrompt(opts) {
|
|
2923
|
+
let p = opts?.prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2924
|
+
if (opts?.styleMap) {
|
|
2925
|
+
p += `
|
|
2926
|
+
|
|
2927
|
+
The following styleMap describes the computed layout state at the moment of capture. Use it to verify typography, spacing, overflow, and element dimensions \u2014 it is more accurate than visual estimation from the image.
|
|
2928
|
+
|
|
2929
|
+
<style_map>
|
|
2930
|
+
${opts.styleMap}
|
|
2931
|
+
</style_map>`;
|
|
2932
|
+
}
|
|
2933
|
+
p += `
|
|
2934
|
+
|
|
2935
|
+
${TEXT_WRAP_DISCLAIMER}`;
|
|
2936
|
+
return p;
|
|
2937
|
+
}
|
|
2938
|
+
async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
2939
|
+
let prompt;
|
|
2940
|
+
let existingUrl;
|
|
2941
|
+
let onLog;
|
|
2942
|
+
let path11;
|
|
2943
|
+
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2944
|
+
prompt = promptOrOptions.prompt;
|
|
2945
|
+
existingUrl = promptOrOptions.imageUrl;
|
|
2946
|
+
path11 = promptOrOptions.path;
|
|
2947
|
+
onLog = promptOrOptions.onLog;
|
|
2948
|
+
} else {
|
|
2949
|
+
prompt = promptOrOptions;
|
|
2950
|
+
}
|
|
2951
|
+
let url;
|
|
2952
|
+
let styleMap;
|
|
2953
|
+
if (existingUrl) {
|
|
2954
|
+
url = existingUrl;
|
|
2955
|
+
} else {
|
|
2956
|
+
const ssResult = await sidecarRequest(
|
|
2957
|
+
"/screenshot-full-page",
|
|
2958
|
+
path11 ? { path: path11 } : void 0,
|
|
2959
|
+
{ timeout: 12e4 }
|
|
2960
|
+
);
|
|
2961
|
+
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
2962
|
+
if (!url) {
|
|
2963
|
+
throw new Error(
|
|
2964
|
+
`No URL in sidecar response. The browser may not be ready yet. Response: ${JSON.stringify(ssResult)}`
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
styleMap = ssResult?.styleMap;
|
|
2968
|
+
}
|
|
2969
|
+
if (prompt === false) {
|
|
2970
|
+
return url;
|
|
2971
|
+
}
|
|
2972
|
+
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
2973
|
+
prompt: prompt || void 0,
|
|
2974
|
+
styleMap
|
|
2975
|
+
});
|
|
2976
|
+
const analysis = await analyzeImage({
|
|
2977
|
+
prompt: analysisPrompt,
|
|
2978
|
+
imageUrl: url,
|
|
2979
|
+
onLog
|
|
2980
|
+
});
|
|
2981
|
+
return JSON.stringify({ url, analysis, ...styleMap ? { styleMap } : {} });
|
|
2982
|
+
}
|
|
2983
|
+
var SCREENSHOT_ANALYSIS_PROMPT, TEXT_WRAP_DISCLAIMER;
|
|
2984
|
+
var init_screenshot = __esm({
|
|
2985
|
+
"src/tools/_helpers/screenshot.ts"() {
|
|
2986
|
+
"use strict";
|
|
2987
|
+
init_sidecar();
|
|
2988
|
+
init_analyzeImage();
|
|
2989
|
+
SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).
|
|
2990
|
+
|
|
2991
|
+
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2992
|
+
TEXT_WRAP_DISCLAIMER = `Note: ignore text wrapping issues. Screenshots occasionally show text wrapping onto an extra line compared to the live page \u2014 most noticeable in buttons, badges, and headings. This is a known limitation of SVG foreignObject rendering used the DOM-to-image capture library that took the screenshot. The browser's SVG renderer computes slightly wider text metrics than the HTML layout engine, so text that fits on one line in the live DOM can overflow by a fraction of a pixel in the capture - this is not a real issue.`;
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
// src/tools/_helpers/browserLock.ts
|
|
2997
|
+
function acquireBrowserLock() {
|
|
2998
|
+
let release;
|
|
2999
|
+
const next = new Promise((res) => {
|
|
3000
|
+
release = res;
|
|
3001
|
+
});
|
|
3002
|
+
const wait = lockQueue;
|
|
3003
|
+
lockQueue = next;
|
|
3004
|
+
return wait.then(() => release);
|
|
3005
|
+
}
|
|
3006
|
+
async function checkBrowserConnected() {
|
|
3007
|
+
try {
|
|
3008
|
+
const status = await sidecarRequest(
|
|
3009
|
+
"/browser-status",
|
|
3010
|
+
{},
|
|
3011
|
+
{ timeout: 5e3 }
|
|
3012
|
+
);
|
|
3013
|
+
if (!status.connected) {
|
|
3014
|
+
return {
|
|
3015
|
+
connected: false,
|
|
3016
|
+
error: "The browser preview is not connected. The user needs to open the preview."
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
return { connected: true };
|
|
3020
|
+
} catch (err) {
|
|
3021
|
+
return {
|
|
3022
|
+
connected: false,
|
|
3023
|
+
error: err?.message || "Could not check browser status. The dev environment may not be running."
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
var lockQueue;
|
|
3028
|
+
var init_browserLock = __esm({
|
|
3029
|
+
"src/tools/_helpers/browserLock.ts"() {
|
|
3030
|
+
"use strict";
|
|
3031
|
+
init_sidecar();
|
|
3032
|
+
lockQueue = Promise.resolve();
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
|
|
3036
|
+
// src/statusWatcher.ts
|
|
3037
|
+
function startStatusWatcher(config) {
|
|
3038
|
+
const { apiConfig, getContext, onStatus, interval = 3e3, signal } = config;
|
|
3039
|
+
let lastLabel = "";
|
|
3040
|
+
let lastContext = "";
|
|
3041
|
+
let inflight = false;
|
|
3042
|
+
let stopped = false;
|
|
3043
|
+
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3044
|
+
async function tick() {
|
|
3045
|
+
if (stopped || signal?.aborted || inflight) {
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
inflight = true;
|
|
3049
|
+
try {
|
|
3050
|
+
const context = getContext();
|
|
3051
|
+
if (!context || context === lastContext) {
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
lastContext = context;
|
|
3055
|
+
const res = await fetch(url, {
|
|
3056
|
+
method: "POST",
|
|
3057
|
+
headers: {
|
|
3058
|
+
"Content-Type": "application/json",
|
|
3059
|
+
Authorization: `Bearer ${apiConfig.apiKey}`
|
|
3060
|
+
},
|
|
3061
|
+
body: JSON.stringify({ context }),
|
|
3062
|
+
signal
|
|
3063
|
+
});
|
|
3064
|
+
if (!res.ok) {
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
const data = await res.json();
|
|
3068
|
+
if (!data.label || data.label === lastLabel) {
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
lastLabel = data.label;
|
|
3072
|
+
if (stopped) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
onStatus(data.label);
|
|
3076
|
+
} catch {
|
|
3077
|
+
} finally {
|
|
3078
|
+
inflight = false;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
const timer = setInterval(tick, interval);
|
|
3082
|
+
tick().catch(() => {
|
|
3083
|
+
});
|
|
3084
|
+
return {
|
|
3085
|
+
stop() {
|
|
3086
|
+
stopped = true;
|
|
3087
|
+
clearInterval(timer);
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
var init_statusWatcher = __esm({
|
|
3092
|
+
"src/statusWatcher.ts"() {
|
|
3093
|
+
"use strict";
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
|
|
3097
|
+
// src/subagents/common/cleanMessages.ts
|
|
3098
|
+
function findLastSummaryCheckpoint(messages, name) {
|
|
3099
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3100
|
+
const msg = messages[i];
|
|
3101
|
+
if (!Array.isArray(msg.content)) {
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
for (const block of msg.content) {
|
|
3105
|
+
if (block.type === "summary" && block.name === name) {
|
|
3106
|
+
return i;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
return -1;
|
|
3111
|
+
}
|
|
3112
|
+
function fixOrphanedToolCalls(messages) {
|
|
3113
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
3114
|
+
for (const msg of messages) {
|
|
3115
|
+
if (msg.role === "user" && msg.toolCallId) {
|
|
3116
|
+
toolResultIds.add(msg.toolCallId);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const result = [...messages];
|
|
3120
|
+
for (let i = result.length - 1; i >= 0; i--) {
|
|
3121
|
+
const msg = result[i];
|
|
3122
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
const toolBlocks = msg.content.filter(
|
|
3126
|
+
(b) => b.type === "tool"
|
|
3127
|
+
);
|
|
3128
|
+
const orphans = toolBlocks.filter((tc) => !toolResultIds.has(tc.id));
|
|
3129
|
+
if (orphans.length === 0) {
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
const synthetics = orphans.map((tc) => ({
|
|
3133
|
+
role: "user",
|
|
3134
|
+
content: "Error: tool result lost (session recovered)",
|
|
3135
|
+
toolCallId: tc.id,
|
|
3136
|
+
isToolError: true
|
|
3137
|
+
}));
|
|
3138
|
+
result.splice(i + 1, 0, ...synthetics);
|
|
3139
|
+
break;
|
|
3140
|
+
}
|
|
3141
|
+
return result;
|
|
3142
|
+
}
|
|
3143
|
+
function cleanMessagesForApi(messages) {
|
|
3144
|
+
const checkpointIdx = findLastSummaryCheckpoint(messages, "conversation");
|
|
3145
|
+
let startIdx = 0;
|
|
3146
|
+
const prefix = [];
|
|
3147
|
+
if (checkpointIdx !== -1) {
|
|
3148
|
+
const checkpointMsg = messages[checkpointIdx];
|
|
3149
|
+
const blocks = checkpointMsg.content;
|
|
3150
|
+
const summaryBlock = blocks.find(
|
|
3151
|
+
(b) => b.type === "summary" && b.name === "conversation"
|
|
3152
|
+
);
|
|
3153
|
+
if (summaryBlock && summaryBlock.type === "summary") {
|
|
3154
|
+
prefix.push({
|
|
3155
|
+
role: "user",
|
|
3156
|
+
content: `<conversation_summary>
|
|
3157
|
+
${summaryBlock.text}
|
|
3158
|
+
</conversation_summary>`,
|
|
3159
|
+
hidden: true
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
startIdx = checkpointIdx + 1;
|
|
3163
|
+
}
|
|
3164
|
+
const messagesToProcess = fixOrphanedToolCalls(messages.slice(startIdx));
|
|
3165
|
+
const toolUseIds = /* @__PURE__ */ new Set();
|
|
3166
|
+
for (const msg of messagesToProcess) {
|
|
3167
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
3168
|
+
for (const block of msg.content) {
|
|
3169
|
+
if (block.type === "tool") {
|
|
3170
|
+
toolUseIds.add(block.id);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
const cleaned = messagesToProcess.filter((msg) => {
|
|
3176
|
+
if (Array.isArray(msg.content)) {
|
|
3177
|
+
const blocks = msg.content;
|
|
3178
|
+
if (blocks.some((b) => b.type === "summary")) {
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
|
|
3183
|
+
return false;
|
|
3184
|
+
}
|
|
3185
|
+
if (msg.role === "user" && msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
|
|
3186
|
+
return false;
|
|
3187
|
+
}
|
|
3188
|
+
return true;
|
|
3189
|
+
}).map((msg) => {
|
|
3190
|
+
if (msg.role === "user" && typeof msg.content === "string" && msg.content.startsWith("@@automated::")) {
|
|
3191
|
+
return {
|
|
3192
|
+
...msg,
|
|
3193
|
+
content: msg.content.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "")
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
if (!Array.isArray(msg.content)) {
|
|
3197
|
+
return msg;
|
|
3198
|
+
}
|
|
3199
|
+
const blocks = msg.content;
|
|
3200
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
3201
|
+
const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
3202
|
+
const thinking = blocks.filter(
|
|
3203
|
+
(b) => b.type === "thinking"
|
|
3204
|
+
).map((b) => ({ thinking: b.thinking, signature: b.signature }));
|
|
3205
|
+
const cleaned2 = {
|
|
3206
|
+
role: msg.role,
|
|
3207
|
+
content: text
|
|
3208
|
+
};
|
|
3209
|
+
if (toolCalls.length > 0) {
|
|
3210
|
+
cleaned2.toolCalls = toolCalls;
|
|
3211
|
+
}
|
|
3212
|
+
if (thinking.length > 0) {
|
|
3213
|
+
cleaned2.thinking = thinking;
|
|
3214
|
+
}
|
|
3215
|
+
if (msg.hidden) {
|
|
3216
|
+
cleaned2.hidden = true;
|
|
3217
|
+
}
|
|
3218
|
+
return cleaned2;
|
|
3219
|
+
});
|
|
3220
|
+
return [...prefix, ...cleaned];
|
|
3221
|
+
}
|
|
3222
|
+
var init_cleanMessages = __esm({
|
|
3223
|
+
"src/subagents/common/cleanMessages.ts"() {
|
|
3224
|
+
"use strict";
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
|
|
3228
|
+
// src/subagents/runner.ts
|
|
3229
|
+
async function runSubAgent(config) {
|
|
3230
|
+
const {
|
|
3231
|
+
system,
|
|
3232
|
+
task,
|
|
2551
3233
|
tools: tools2,
|
|
2552
3234
|
externalTools,
|
|
2553
3235
|
executeTool: executeTool2,
|
|
@@ -2568,7 +3250,7 @@ async function runSubAgent(config) {
|
|
|
2568
3250
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
2569
3251
|
const agentName = subAgentId || "sub-agent";
|
|
2570
3252
|
const runStart = Date.now();
|
|
2571
|
-
|
|
3253
|
+
log6.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
2572
3254
|
const emit2 = (e) => {
|
|
2573
3255
|
onEvent({ ...e, parentToolId });
|
|
2574
3256
|
};
|
|
@@ -2737,7 +3419,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2737
3419
|
const text = getPartialText(contentBlocks);
|
|
2738
3420
|
return { text, messages: thisInvocation() };
|
|
2739
3421
|
}
|
|
2740
|
-
|
|
3422
|
+
log6.info("Tools executing", {
|
|
2741
3423
|
requestId,
|
|
2742
3424
|
parentToolId,
|
|
2743
3425
|
count: toolCalls.length,
|
|
@@ -2814,7 +3496,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2814
3496
|
run2(tc.input);
|
|
2815
3497
|
const r = await resultPromise;
|
|
2816
3498
|
toolRegistry?.unregister(tc.id);
|
|
2817
|
-
|
|
3499
|
+
log6.info("Tool completed", {
|
|
2818
3500
|
requestId,
|
|
2819
3501
|
parentToolId,
|
|
2820
3502
|
toolCallId: tc.id,
|
|
@@ -2859,7 +3541,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2859
3541
|
const wrapRun = async () => {
|
|
2860
3542
|
try {
|
|
2861
3543
|
const result = await run();
|
|
2862
|
-
|
|
3544
|
+
log6.info("Sub-agent complete", {
|
|
2863
3545
|
requestId,
|
|
2864
3546
|
parentToolId,
|
|
2865
3547
|
agentName,
|
|
@@ -2868,7 +3550,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2868
3550
|
});
|
|
2869
3551
|
return result;
|
|
2870
3552
|
} catch (err) {
|
|
2871
|
-
|
|
3553
|
+
log6.warn("Sub-agent error", {
|
|
2872
3554
|
requestId,
|
|
2873
3555
|
parentToolId,
|
|
2874
3556
|
agentName,
|
|
@@ -2880,7 +3562,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2880
3562
|
if (!background) {
|
|
2881
3563
|
return wrapRun();
|
|
2882
3564
|
}
|
|
2883
|
-
|
|
3565
|
+
log6.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
2884
3566
|
toolRegistry?.register({
|
|
2885
3567
|
id: parentToolId,
|
|
2886
3568
|
name: agentName,
|
|
@@ -2907,7 +3589,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
2907
3589
|
});
|
|
2908
3590
|
return { text: ack, messages: [], backgrounded: true };
|
|
2909
3591
|
}
|
|
2910
|
-
var
|
|
3592
|
+
var log6;
|
|
2911
3593
|
var init_runner = __esm({
|
|
2912
3594
|
"src/subagents/runner.ts"() {
|
|
2913
3595
|
"use strict";
|
|
@@ -2915,7 +3597,7 @@ var init_runner = __esm({
|
|
|
2915
3597
|
init_logger();
|
|
2916
3598
|
init_statusWatcher();
|
|
2917
3599
|
init_cleanMessages();
|
|
2918
|
-
|
|
3600
|
+
log6 = createLogger("sub-agent");
|
|
2919
3601
|
}
|
|
2920
3602
|
});
|
|
2921
3603
|
|
|
@@ -3058,54 +3740,11 @@ var init_tools = __esm({
|
|
|
3058
3740
|
}
|
|
3059
3741
|
});
|
|
3060
3742
|
|
|
3061
|
-
// src/assets.ts
|
|
3062
|
-
import fs10 from "fs";
|
|
3063
|
-
import path5 from "path";
|
|
3064
|
-
function findRoot(start) {
|
|
3065
|
-
let dir = start;
|
|
3066
|
-
while (dir !== path5.dirname(dir)) {
|
|
3067
|
-
if (fs10.existsSync(path5.join(dir, "package.json"))) {
|
|
3068
|
-
return dir;
|
|
3069
|
-
}
|
|
3070
|
-
dir = path5.dirname(dir);
|
|
3071
|
-
}
|
|
3072
|
-
return start;
|
|
3073
|
-
}
|
|
3074
|
-
function assetPath(...segments) {
|
|
3075
|
-
return path5.join(ASSETS_BASE, ...segments);
|
|
3076
|
-
}
|
|
3077
|
-
function readAsset(...segments) {
|
|
3078
|
-
const full = assetPath(...segments);
|
|
3079
|
-
try {
|
|
3080
|
-
return fs10.readFileSync(full, "utf-8").trim();
|
|
3081
|
-
} catch {
|
|
3082
|
-
throw new Error(`Required asset missing: ${full}`);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
function readJsonAsset(fallback, ...segments) {
|
|
3086
|
-
const full = assetPath(...segments);
|
|
3087
|
-
try {
|
|
3088
|
-
return JSON.parse(fs10.readFileSync(full, "utf-8"));
|
|
3089
|
-
} catch {
|
|
3090
|
-
return fallback;
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
var ROOT, ASSETS_BASE;
|
|
3094
|
-
var init_assets = __esm({
|
|
3095
|
-
"src/assets.ts"() {
|
|
3096
|
-
"use strict";
|
|
3097
|
-
ROOT = findRoot(
|
|
3098
|
-
import.meta.dirname ?? path5.dirname(new URL(import.meta.url).pathname)
|
|
3099
|
-
);
|
|
3100
|
-
ASSETS_BASE = fs10.existsSync(path5.join(ROOT, "dist", "prompt")) ? path5.join(ROOT, "dist") : path5.join(ROOT, "src");
|
|
3101
|
-
}
|
|
3102
|
-
});
|
|
3103
|
-
|
|
3104
3743
|
// src/subagents/browserAutomation/prompt.ts
|
|
3105
|
-
import
|
|
3744
|
+
import fs13 from "fs";
|
|
3106
3745
|
function getBrowserAutomationPrompt() {
|
|
3107
3746
|
try {
|
|
3108
|
-
const appSpec =
|
|
3747
|
+
const appSpec = fs13.readFileSync("src/app.md", "utf-8").trim();
|
|
3109
3748
|
return `${BASE_PROMPT}
|
|
3110
3749
|
|
|
3111
3750
|
<!-- cache_breakpoint -->
|
|
@@ -3118,7 +3757,7 @@ ${appSpec}
|
|
|
3118
3757
|
}
|
|
3119
3758
|
}
|
|
3120
3759
|
var BASE_PROMPT;
|
|
3121
|
-
var
|
|
3760
|
+
var init_prompt2 = __esm({
|
|
3122
3761
|
"src/subagents/browserAutomation/prompt.ts"() {
|
|
3123
3762
|
"use strict";
|
|
3124
3763
|
init_assets();
|
|
@@ -3127,19 +3766,19 @@ var init_prompt = __esm({
|
|
|
3127
3766
|
});
|
|
3128
3767
|
|
|
3129
3768
|
// src/subagents/browserAutomation/index.ts
|
|
3130
|
-
var
|
|
3769
|
+
var log7, browserAutomationTool;
|
|
3131
3770
|
var init_browserAutomation = __esm({
|
|
3132
3771
|
"src/subagents/browserAutomation/index.ts"() {
|
|
3133
3772
|
"use strict";
|
|
3134
3773
|
init_runner();
|
|
3135
3774
|
init_tools();
|
|
3136
|
-
|
|
3775
|
+
init_prompt2();
|
|
3137
3776
|
init_sidecar();
|
|
3138
3777
|
init_browserLock();
|
|
3139
3778
|
init_screenshot();
|
|
3140
3779
|
init_runCli();
|
|
3141
3780
|
init_logger();
|
|
3142
|
-
|
|
3781
|
+
log7 = createLogger("browser-automation");
|
|
3143
3782
|
browserAutomationTool = {
|
|
3144
3783
|
clearable: true,
|
|
3145
3784
|
definition: {
|
|
@@ -3245,7 +3884,7 @@ var init_browserAutomation = __esm({
|
|
|
3245
3884
|
}
|
|
3246
3885
|
}
|
|
3247
3886
|
} catch {
|
|
3248
|
-
|
|
3887
|
+
log7.debug("Failed to parse batch analysis result", {
|
|
3249
3888
|
batchResult
|
|
3250
3889
|
});
|
|
3251
3890
|
}
|
|
@@ -4062,16 +4701,16 @@ var init_tools3 = __esm({
|
|
|
4062
4701
|
});
|
|
4063
4702
|
|
|
4064
4703
|
// src/subagents/common/context.ts
|
|
4065
|
-
import
|
|
4066
|
-
import
|
|
4067
|
-
function
|
|
4704
|
+
import fs14 from "fs";
|
|
4705
|
+
import path7 from "path";
|
|
4706
|
+
function walkMdFiles2(dir, skip) {
|
|
4068
4707
|
const files = [];
|
|
4069
4708
|
try {
|
|
4070
|
-
for (const entry of
|
|
4071
|
-
const full =
|
|
4709
|
+
for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
|
|
4710
|
+
const full = path7.join(dir, entry.name);
|
|
4072
4711
|
if (entry.isDirectory()) {
|
|
4073
4712
|
if (!skip?.has(entry.name)) {
|
|
4074
|
-
files.push(...
|
|
4713
|
+
files.push(...walkMdFiles2(full, skip));
|
|
4075
4714
|
}
|
|
4076
4715
|
} else if (entry.name.endsWith(".md")) {
|
|
4077
4716
|
files.push(full);
|
|
@@ -4081,9 +4720,9 @@ function walkMdFiles(dir, skip) {
|
|
|
4081
4720
|
}
|
|
4082
4721
|
return files.sort();
|
|
4083
4722
|
}
|
|
4084
|
-
function
|
|
4723
|
+
function parseFrontmatter2(filePath) {
|
|
4085
4724
|
try {
|
|
4086
|
-
const content =
|
|
4725
|
+
const content = fs14.readFileSync(filePath, "utf-8");
|
|
4087
4726
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4088
4727
|
if (!match) {
|
|
4089
4728
|
return {};
|
|
@@ -4103,12 +4742,12 @@ function parseFrontmatter(filePath) {
|
|
|
4103
4742
|
}
|
|
4104
4743
|
}
|
|
4105
4744
|
function loadSpecIndex() {
|
|
4106
|
-
const files =
|
|
4745
|
+
const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
|
|
4107
4746
|
if (files.length === 0) {
|
|
4108
4747
|
return "";
|
|
4109
4748
|
}
|
|
4110
4749
|
const lines = files.map((f) => {
|
|
4111
|
-
const fm =
|
|
4750
|
+
const fm = parseFrontmatter2(f);
|
|
4112
4751
|
let line = `- ${f}`;
|
|
4113
4752
|
if (fm.name) {
|
|
4114
4753
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4129,7 +4768,7 @@ function loadRoadmapIndex() {
|
|
|
4129
4768
|
const parts = [];
|
|
4130
4769
|
try {
|
|
4131
4770
|
const indexJson = JSON.parse(
|
|
4132
|
-
|
|
4771
|
+
fs14.readFileSync("src/roadmap/index.json", "utf-8")
|
|
4133
4772
|
);
|
|
4134
4773
|
if (indexJson.lanes?.length > 0) {
|
|
4135
4774
|
const laneLines = indexJson.lanes.map(
|
|
@@ -4146,10 +4785,10 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
|
|
|
4146
4785
|
}
|
|
4147
4786
|
} catch {
|
|
4148
4787
|
}
|
|
4149
|
-
const files =
|
|
4788
|
+
const files = walkMdFiles2("src/roadmap");
|
|
4150
4789
|
if (files.length > 0) {
|
|
4151
4790
|
const lines = files.map((f) => {
|
|
4152
|
-
const fm =
|
|
4791
|
+
const fm = parseFrontmatter2(f);
|
|
4153
4792
|
let line = `- ${f}`;
|
|
4154
4793
|
if (fm.name) {
|
|
4155
4794
|
line += ` \u2014 "${fm.name}"`;
|
|
@@ -4237,7 +4876,7 @@ var init_context = __esm({
|
|
|
4237
4876
|
});
|
|
4238
4877
|
|
|
4239
4878
|
// src/subagents/designExpert/data/sampleCache.ts
|
|
4240
|
-
import
|
|
4879
|
+
import fs15 from "fs";
|
|
4241
4880
|
function generateIndices(poolSize, sampleSize) {
|
|
4242
4881
|
const n = Math.min(sampleSize, poolSize);
|
|
4243
4882
|
const indices = Array.from({ length: poolSize }, (_, i) => i);
|
|
@@ -4249,14 +4888,14 @@ function generateIndices(poolSize, sampleSize) {
|
|
|
4249
4888
|
}
|
|
4250
4889
|
function load() {
|
|
4251
4890
|
try {
|
|
4252
|
-
return JSON.parse(
|
|
4891
|
+
return JSON.parse(fs15.readFileSync(SAMPLE_FILE, "utf-8"));
|
|
4253
4892
|
} catch {
|
|
4254
4893
|
return null;
|
|
4255
4894
|
}
|
|
4256
4895
|
}
|
|
4257
4896
|
function save(indices) {
|
|
4258
4897
|
try {
|
|
4259
|
-
|
|
4898
|
+
fs15.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
4260
4899
|
} catch {
|
|
4261
4900
|
}
|
|
4262
4901
|
}
|
|
@@ -4439,7 +5078,7 @@ This project is in the "${state}" phase. The codebase is a placeholder scaffold
|
|
|
4439
5078
|
return prompt;
|
|
4440
5079
|
}
|
|
4441
5080
|
var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE;
|
|
4442
|
-
var
|
|
5081
|
+
var init_prompt3 = __esm({
|
|
4443
5082
|
"src/subagents/designExpert/prompt.ts"() {
|
|
4444
5083
|
"use strict";
|
|
4445
5084
|
init_assets();
|
|
@@ -4519,7 +5158,7 @@ var init_designExpert = __esm({
|
|
|
4519
5158
|
init_runner();
|
|
4520
5159
|
init_tools3();
|
|
4521
5160
|
init_tools2();
|
|
4522
|
-
|
|
5161
|
+
init_prompt3();
|
|
4523
5162
|
init_history();
|
|
4524
5163
|
DESCRIPTION = `
|
|
4525
5164
|
Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job. Do not suggest implementation details or ideas - only relay what is needed.
|
|
@@ -4653,23 +5292,23 @@ var init_tools4 = __esm({
|
|
|
4653
5292
|
});
|
|
4654
5293
|
|
|
4655
5294
|
// src/subagents/productVision/executor.ts
|
|
4656
|
-
import
|
|
4657
|
-
import
|
|
5295
|
+
import fs16 from "fs";
|
|
5296
|
+
import path8 from "path";
|
|
4658
5297
|
function resolve(filePath) {
|
|
4659
|
-
return
|
|
5298
|
+
return path8.join(ROADMAP_DIR, filePath);
|
|
4660
5299
|
}
|
|
4661
5300
|
async function executeVisionTool(name, input, context) {
|
|
4662
5301
|
switch (name) {
|
|
4663
5302
|
case "writeFile": {
|
|
4664
5303
|
const filePath = resolve(input.path);
|
|
4665
5304
|
try {
|
|
4666
|
-
|
|
5305
|
+
fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
4667
5306
|
let oldContent = null;
|
|
4668
5307
|
try {
|
|
4669
|
-
oldContent =
|
|
5308
|
+
oldContent = fs16.readFileSync(filePath, "utf-8");
|
|
4670
5309
|
} catch {
|
|
4671
5310
|
}
|
|
4672
|
-
|
|
5311
|
+
fs16.writeFileSync(filePath, input.content, "utf-8");
|
|
4673
5312
|
const lineCount = input.content.split("\n").length;
|
|
4674
5313
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
4675
5314
|
return `${label} ${filePath} (${lineCount} lines)
|
|
@@ -4681,11 +5320,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
|
|
|
4681
5320
|
case "deleteFile": {
|
|
4682
5321
|
const filePath = resolve(input.path);
|
|
4683
5322
|
try {
|
|
4684
|
-
if (!
|
|
5323
|
+
if (!fs16.existsSync(filePath)) {
|
|
4685
5324
|
return `Error: ${filePath} does not exist`;
|
|
4686
5325
|
}
|
|
4687
|
-
const oldContent =
|
|
4688
|
-
|
|
5326
|
+
const oldContent = fs16.readFileSync(filePath, "utf-8");
|
|
5327
|
+
fs16.unlinkSync(filePath);
|
|
4689
5328
|
return `Deleted ${filePath}
|
|
4690
5329
|
${unifiedDiff(filePath, oldContent, "")}`;
|
|
4691
5330
|
} catch (err) {
|
|
@@ -4698,8 +5337,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
|
|
|
4698
5337
|
}
|
|
4699
5338
|
const filePath = resolve("pitch.html");
|
|
4700
5339
|
try {
|
|
4701
|
-
|
|
4702
|
-
const existing =
|
|
5340
|
+
fs16.mkdirSync(ROADMAP_DIR, { recursive: true });
|
|
5341
|
+
const existing = fs16.existsSync(filePath) ? fs16.readFileSync(filePath, "utf-8").trim() : "";
|
|
4703
5342
|
const currentDeck = existing || PITCH_DECK_SHELL;
|
|
4704
5343
|
const task = `
|
|
4705
5344
|
<pitch_content>${input.task}</pitch_content>
|
|
@@ -4724,7 +5363,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
|
|
|
4724
5363
|
/```(?:html|wireframe)\n([\s\S]*?)```/
|
|
4725
5364
|
);
|
|
4726
5365
|
const html = htmlMatch ? htmlMatch[1].trim() : result;
|
|
4727
|
-
|
|
5366
|
+
fs16.writeFileSync(filePath, html, "utf-8");
|
|
4728
5367
|
return `Pitch deck written successfully.`;
|
|
4729
5368
|
} catch (err) {
|
|
4730
5369
|
return `Error generating pitch deck: ${err.message}`;
|
|
@@ -4765,7 +5404,7 @@ function getProductVisionPrompt() {
|
|
|
4765
5404
|
return parts.join("\n\n");
|
|
4766
5405
|
}
|
|
4767
5406
|
var BASE_PROMPT2;
|
|
4768
|
-
var
|
|
5407
|
+
var init_prompt4 = __esm({
|
|
4769
5408
|
"src/subagents/productVision/prompt.ts"() {
|
|
4770
5409
|
"use strict";
|
|
4771
5410
|
init_assets();
|
|
@@ -4785,7 +5424,7 @@ var init_productVision = __esm({
|
|
|
4785
5424
|
init_tools4();
|
|
4786
5425
|
init_tools2();
|
|
4787
5426
|
init_executor();
|
|
4788
|
-
|
|
5427
|
+
init_prompt4();
|
|
4789
5428
|
init_history();
|
|
4790
5429
|
productVisionTool = {
|
|
4791
5430
|
clearable: false,
|
|
@@ -5045,6 +5684,7 @@ var init_tools6 = __esm({
|
|
|
5045
5684
|
init_sdkConsultant();
|
|
5046
5685
|
init_searchGoogle();
|
|
5047
5686
|
init_setProjectMetadata();
|
|
5687
|
+
init_compactConversation();
|
|
5048
5688
|
init_readFile();
|
|
5049
5689
|
init_writeFile();
|
|
5050
5690
|
init_editFile();
|
|
@@ -5076,6 +5716,7 @@ var init_tools6 = __esm({
|
|
|
5076
5716
|
designExpertTool,
|
|
5077
5717
|
productVisionTool,
|
|
5078
5718
|
codeSanityCheckTool,
|
|
5719
|
+
compactConversationTool,
|
|
5079
5720
|
// Post-onboarding
|
|
5080
5721
|
clearSyncStatusTool,
|
|
5081
5722
|
presentSyncPlanTool,
|
|
@@ -5110,86 +5751,6 @@ var init_tools6 = __esm({
|
|
|
5110
5751
|
}
|
|
5111
5752
|
});
|
|
5112
5753
|
|
|
5113
|
-
// src/session.ts
|
|
5114
|
-
import fs15 from "fs";
|
|
5115
|
-
function loadSession(state) {
|
|
5116
|
-
try {
|
|
5117
|
-
const raw = fs15.readFileSync(SESSION_FILE, "utf-8");
|
|
5118
|
-
const data = JSON.parse(raw);
|
|
5119
|
-
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
5120
|
-
state.messages = sanitizeMessages(data.messages);
|
|
5121
|
-
log5.info("Session loaded", { messageCount: state.messages.length });
|
|
5122
|
-
return true;
|
|
5123
|
-
}
|
|
5124
|
-
} catch {
|
|
5125
|
-
}
|
|
5126
|
-
return false;
|
|
5127
|
-
}
|
|
5128
|
-
function sanitizeMessages(messages) {
|
|
5129
|
-
const result = [];
|
|
5130
|
-
for (let i = 0; i < messages.length; i++) {
|
|
5131
|
-
result.push(messages[i]);
|
|
5132
|
-
const msg = messages[i];
|
|
5133
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
5134
|
-
continue;
|
|
5135
|
-
}
|
|
5136
|
-
const toolBlocks = msg.content.filter(
|
|
5137
|
-
(b) => b.type === "tool"
|
|
5138
|
-
);
|
|
5139
|
-
if (toolBlocks.length === 0) {
|
|
5140
|
-
continue;
|
|
5141
|
-
}
|
|
5142
|
-
const resultIds = /* @__PURE__ */ new Set();
|
|
5143
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
5144
|
-
const next = messages[j];
|
|
5145
|
-
if (next.role === "user" && next.toolCallId) {
|
|
5146
|
-
resultIds.add(next.toolCallId);
|
|
5147
|
-
} else {
|
|
5148
|
-
break;
|
|
5149
|
-
}
|
|
5150
|
-
}
|
|
5151
|
-
for (const tc of toolBlocks) {
|
|
5152
|
-
if (!resultIds.has(tc.id)) {
|
|
5153
|
-
result.push({
|
|
5154
|
-
role: "user",
|
|
5155
|
-
content: "Error: tool result lost (session recovered)",
|
|
5156
|
-
toolCallId: tc.id,
|
|
5157
|
-
isToolError: true
|
|
5158
|
-
});
|
|
5159
|
-
}
|
|
5160
|
-
}
|
|
5161
|
-
}
|
|
5162
|
-
return result;
|
|
5163
|
-
}
|
|
5164
|
-
function saveSession(state) {
|
|
5165
|
-
try {
|
|
5166
|
-
fs15.writeFileSync(
|
|
5167
|
-
SESSION_FILE,
|
|
5168
|
-
JSON.stringify({ messages: state.messages }, null, 2),
|
|
5169
|
-
"utf-8"
|
|
5170
|
-
);
|
|
5171
|
-
log5.info("Session saved", { messageCount: state.messages.length });
|
|
5172
|
-
} catch (err) {
|
|
5173
|
-
log5.warn("Session save failed", { error: err.message });
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
function clearSession(state) {
|
|
5177
|
-
state.messages = [];
|
|
5178
|
-
try {
|
|
5179
|
-
fs15.unlinkSync(SESSION_FILE);
|
|
5180
|
-
} catch {
|
|
5181
|
-
}
|
|
5182
|
-
}
|
|
5183
|
-
var log5, SESSION_FILE;
|
|
5184
|
-
var init_session = __esm({
|
|
5185
|
-
"src/session.ts"() {
|
|
5186
|
-
"use strict";
|
|
5187
|
-
init_logger();
|
|
5188
|
-
log5 = createLogger("session");
|
|
5189
|
-
SESSION_FILE = ".remy-session.json";
|
|
5190
|
-
}
|
|
5191
|
-
});
|
|
5192
|
-
|
|
5193
5754
|
// src/parsePartialJson.ts
|
|
5194
5755
|
function parsePartialJson(jsonString) {
|
|
5195
5756
|
const length = jsonString.length;
|
|
@@ -5422,7 +5983,7 @@ async function runTurn(params) {
|
|
|
5422
5983
|
} = params;
|
|
5423
5984
|
const tools2 = getToolDefinitions(onboardingState);
|
|
5424
5985
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
5425
|
-
|
|
5986
|
+
log8.info("Turn started", {
|
|
5426
5987
|
requestId,
|
|
5427
5988
|
model,
|
|
5428
5989
|
toolCount: tools2.length,
|
|
@@ -5646,7 +6207,7 @@ async function runTurn(params) {
|
|
|
5646
6207
|
const tool = getToolByName(event.name);
|
|
5647
6208
|
const wasStreamed = acc?.started ?? false;
|
|
5648
6209
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
5649
|
-
|
|
6210
|
+
log8.info("Tool received", {
|
|
5650
6211
|
requestId,
|
|
5651
6212
|
toolCallId: event.id,
|
|
5652
6213
|
name: event.name
|
|
@@ -5693,7 +6254,14 @@ async function runTurn(params) {
|
|
|
5693
6254
|
});
|
|
5694
6255
|
state.messages.push({
|
|
5695
6256
|
role: "assistant",
|
|
5696
|
-
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
6257
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
6258
|
+
usage: {
|
|
6259
|
+
inputTokens: turnInputTokens,
|
|
6260
|
+
outputTokens: turnOutputTokens,
|
|
6261
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
6262
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
6263
|
+
llmCalls: turnLlmCalls
|
|
6264
|
+
}
|
|
5697
6265
|
});
|
|
5698
6266
|
}
|
|
5699
6267
|
onEvent({ type: "turn_cancelled" });
|
|
@@ -5703,7 +6271,14 @@ async function runTurn(params) {
|
|
|
5703
6271
|
if (contentBlocks.length > 0) {
|
|
5704
6272
|
state.messages.push({
|
|
5705
6273
|
role: "assistant",
|
|
5706
|
-
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
6274
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
6275
|
+
usage: {
|
|
6276
|
+
inputTokens: turnInputTokens,
|
|
6277
|
+
outputTokens: turnOutputTokens,
|
|
6278
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
6279
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
6280
|
+
llmCalls: turnLlmCalls
|
|
6281
|
+
}
|
|
5707
6282
|
});
|
|
5708
6283
|
}
|
|
5709
6284
|
const toolCalls = getToolCalls(contentBlocks);
|
|
@@ -5725,7 +6300,7 @@ async function runTurn(params) {
|
|
|
5725
6300
|
});
|
|
5726
6301
|
return;
|
|
5727
6302
|
}
|
|
5728
|
-
|
|
6303
|
+
log8.info("Tools executing", {
|
|
5729
6304
|
requestId,
|
|
5730
6305
|
count: toolCalls.length,
|
|
5731
6306
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -5772,7 +6347,7 @@ async function runTurn(params) {
|
|
|
5772
6347
|
let result;
|
|
5773
6348
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
5774
6349
|
saveSession(state);
|
|
5775
|
-
|
|
6350
|
+
log8.info("Waiting for external tool result", {
|
|
5776
6351
|
requestId,
|
|
5777
6352
|
toolCallId: tc.id,
|
|
5778
6353
|
name: tc.name
|
|
@@ -5829,7 +6404,7 @@ async function runTurn(params) {
|
|
|
5829
6404
|
if (!tc.input.background) {
|
|
5830
6405
|
toolRegistry?.unregister(tc.id);
|
|
5831
6406
|
}
|
|
5832
|
-
|
|
6407
|
+
log8.info("Tool completed", {
|
|
5833
6408
|
requestId,
|
|
5834
6409
|
toolCallId: tc.id,
|
|
5835
6410
|
name: tc.name,
|
|
@@ -5848,346 +6423,68 @@ async function runTurn(params) {
|
|
|
5848
6423
|
);
|
|
5849
6424
|
statusWatcher.stop();
|
|
5850
6425
|
for (const r of results) {
|
|
5851
|
-
const block = contentBlocks.find(
|
|
5852
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
5853
|
-
);
|
|
5854
|
-
if (block?.type === "tool") {
|
|
5855
|
-
block.result = r.result;
|
|
5856
|
-
block.isError = r.isError;
|
|
5857
|
-
block.completedAt = Date.now();
|
|
5858
|
-
const msgs = subAgentMessages.get(r.id);
|
|
5859
|
-
if (msgs) {
|
|
5860
|
-
block.subAgentMessages = msgs;
|
|
5861
|
-
}
|
|
5862
|
-
}
|
|
5863
|
-
}
|
|
5864
|
-
const lastNonExcluded = toolCalls.filter(
|
|
5865
|
-
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
5866
|
-
);
|
|
5867
|
-
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
5868
|
-
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
5869
|
-
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
5870
|
-
for (const r of results) {
|
|
5871
|
-
state.messages.push({
|
|
5872
|
-
role: "user",
|
|
5873
|
-
content: r.result,
|
|
5874
|
-
toolCallId: r.id,
|
|
5875
|
-
isToolError: r.isError
|
|
5876
|
-
});
|
|
5877
|
-
}
|
|
5878
|
-
if (signal?.aborted) {
|
|
5879
|
-
onEvent({ type: "turn_cancelled" });
|
|
5880
|
-
saveSession(state);
|
|
5881
|
-
return;
|
|
5882
|
-
}
|
|
5883
|
-
}
|
|
5884
|
-
}
|
|
5885
|
-
var log6, EXTERNAL_TOOLS;
|
|
5886
|
-
var init_agent = __esm({
|
|
5887
|
-
"src/agent.ts"() {
|
|
5888
|
-
"use strict";
|
|
5889
|
-
init_api();
|
|
5890
|
-
init_tools6();
|
|
5891
|
-
init_session();
|
|
5892
|
-
init_logger();
|
|
5893
|
-
init_parsePartialJson();
|
|
5894
|
-
init_statusWatcher();
|
|
5895
|
-
init_errors();
|
|
5896
|
-
init_cleanMessages();
|
|
5897
|
-
init_tools6();
|
|
5898
|
-
log6 = createLogger("agent");
|
|
5899
|
-
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
5900
|
-
"promptUser",
|
|
5901
|
-
"setProjectOnboardingState",
|
|
5902
|
-
"clearSyncStatus",
|
|
5903
|
-
"presentSyncPlan",
|
|
5904
|
-
"presentPublishPlan",
|
|
5905
|
-
"presentPlan",
|
|
5906
|
-
"confirmDestructiveAction",
|
|
5907
|
-
"runScenario",
|
|
5908
|
-
"runMethod",
|
|
5909
|
-
"queryDatabase",
|
|
5910
|
-
"browserCommand",
|
|
5911
|
-
"setProjectMetadata"
|
|
5912
|
-
]);
|
|
5913
|
-
}
|
|
5914
|
-
});
|
|
5915
|
-
|
|
5916
|
-
// src/prompt/static/projectContext.ts
|
|
5917
|
-
import fs16 from "fs";
|
|
5918
|
-
import path8 from "path";
|
|
5919
|
-
function loadProjectInstructions() {
|
|
5920
|
-
for (const file of AGENT_INSTRUCTION_FILES) {
|
|
5921
|
-
try {
|
|
5922
|
-
const content = fs16.readFileSync(file, "utf-8").trim();
|
|
5923
|
-
if (content) {
|
|
5924
|
-
return `
|
|
5925
|
-
## Project Instructions (${file})
|
|
5926
|
-
${content}`;
|
|
5927
|
-
}
|
|
5928
|
-
} catch {
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
5931
|
-
return "";
|
|
5932
|
-
}
|
|
5933
|
-
function loadProjectManifest() {
|
|
5934
|
-
try {
|
|
5935
|
-
const manifest = fs16.readFileSync("mindstudio.json", "utf-8");
|
|
5936
|
-
return `
|
|
5937
|
-
## Project Manifest (mindstudio.json)
|
|
5938
|
-
\`\`\`json
|
|
5939
|
-
${manifest}
|
|
5940
|
-
\`\`\``;
|
|
5941
|
-
} catch {
|
|
5942
|
-
return "";
|
|
5943
|
-
}
|
|
5944
|
-
}
|
|
5945
|
-
function loadSpecFileMetadata() {
|
|
5946
|
-
try {
|
|
5947
|
-
const files = walkMdFiles2("src");
|
|
5948
|
-
if (files.length === 0) {
|
|
5949
|
-
return "";
|
|
5950
|
-
}
|
|
5951
|
-
const entries = [];
|
|
5952
|
-
for (const filePath of files) {
|
|
5953
|
-
const { name, description, type } = parseFrontmatter2(filePath);
|
|
5954
|
-
let line = `- ${filePath}`;
|
|
5955
|
-
if (name) {
|
|
5956
|
-
line += ` \u2014 "${name}"`;
|
|
5957
|
-
}
|
|
5958
|
-
if (type) {
|
|
5959
|
-
line += ` (${type})`;
|
|
5960
|
-
}
|
|
5961
|
-
if (description) {
|
|
5962
|
-
line += ` \u2014 ${description}`;
|
|
6426
|
+
const block = contentBlocks.find(
|
|
6427
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
6428
|
+
);
|
|
6429
|
+
if (block?.type === "tool") {
|
|
6430
|
+
block.result = r.result;
|
|
6431
|
+
block.isError = r.isError;
|
|
6432
|
+
block.completedAt = Date.now();
|
|
6433
|
+
const msgs = subAgentMessages.get(r.id);
|
|
6434
|
+
if (msgs) {
|
|
6435
|
+
block.subAgentMessages = msgs;
|
|
6436
|
+
}
|
|
5963
6437
|
}
|
|
5964
|
-
entries.push(line);
|
|
5965
6438
|
}
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
if (entry.isDirectory()) {
|
|
5980
|
-
results.push(...walkMdFiles2(full));
|
|
5981
|
-
} else if (entry.name.endsWith(".md")) {
|
|
5982
|
-
results.push(full);
|
|
5983
|
-
}
|
|
6439
|
+
const lastNonExcluded = toolCalls.filter(
|
|
6440
|
+
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
6441
|
+
);
|
|
6442
|
+
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
6443
|
+
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
6444
|
+
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
6445
|
+
for (const r of results) {
|
|
6446
|
+
state.messages.push({
|
|
6447
|
+
role: "user",
|
|
6448
|
+
content: r.result,
|
|
6449
|
+
toolCallId: r.id,
|
|
6450
|
+
isToolError: r.isError
|
|
6451
|
+
});
|
|
5984
6452
|
}
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
function parseFrontmatter2(filePath) {
|
|
5990
|
-
try {
|
|
5991
|
-
const content = fs16.readFileSync(filePath, "utf-8");
|
|
5992
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5993
|
-
if (!match) {
|
|
5994
|
-
return { name: "", description: "", type: "" };
|
|
6453
|
+
if (signal?.aborted) {
|
|
6454
|
+
onEvent({ type: "turn_cancelled" });
|
|
6455
|
+
saveSession(state);
|
|
6456
|
+
return;
|
|
5995
6457
|
}
|
|
5996
|
-
const fm = match[1];
|
|
5997
|
-
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
5998
|
-
const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
5999
|
-
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
6000
|
-
return { name, description, type };
|
|
6001
|
-
} catch {
|
|
6002
|
-
return { name: "", description: "", type: "" };
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
function loadProjectFileListing() {
|
|
6006
|
-
try {
|
|
6007
|
-
const entries = fs16.readdirSync(".", { withFileTypes: true });
|
|
6008
|
-
const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
|
|
6009
|
-
if (a.isDirectory() && !b.isDirectory()) {
|
|
6010
|
-
return -1;
|
|
6011
|
-
}
|
|
6012
|
-
if (!a.isDirectory() && b.isDirectory()) {
|
|
6013
|
-
return 1;
|
|
6014
|
-
}
|
|
6015
|
-
return a.name.localeCompare(b.name);
|
|
6016
|
-
}).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
6017
|
-
return `
|
|
6018
|
-
## Project Files
|
|
6019
|
-
\`\`\`
|
|
6020
|
-
${listing}
|
|
6021
|
-
\`\`\``;
|
|
6022
|
-
} catch {
|
|
6023
|
-
return "";
|
|
6024
|
-
}
|
|
6025
|
-
}
|
|
6026
|
-
var AGENT_INSTRUCTION_FILES;
|
|
6027
|
-
var init_projectContext = __esm({
|
|
6028
|
-
"src/prompt/static/projectContext.ts"() {
|
|
6029
|
-
"use strict";
|
|
6030
|
-
AGENT_INSTRUCTION_FILES = [
|
|
6031
|
-
"CLAUDE.md",
|
|
6032
|
-
"claude.md",
|
|
6033
|
-
".claude/instructions.md",
|
|
6034
|
-
"AGENTS.md",
|
|
6035
|
-
"agents.md",
|
|
6036
|
-
".agents.md",
|
|
6037
|
-
"COPILOT.md",
|
|
6038
|
-
"copilot.md",
|
|
6039
|
-
".copilot-instructions.md",
|
|
6040
|
-
".github/copilot-instructions.md",
|
|
6041
|
-
"REMY.md",
|
|
6042
|
-
"remy.md",
|
|
6043
|
-
".cursorrules",
|
|
6044
|
-
".cursorules"
|
|
6045
|
-
];
|
|
6046
6458
|
}
|
|
6047
|
-
});
|
|
6048
|
-
|
|
6049
|
-
// src/prompt/index.ts
|
|
6050
|
-
function resolveIncludes(template) {
|
|
6051
|
-
const result = template.replace(
|
|
6052
|
-
/\{\{([^}]+)\}\}/g,
|
|
6053
|
-
(_, filePath) => readAsset("prompt", filePath.trim())
|
|
6054
|
-
);
|
|
6055
|
-
return result.replace(/\n{3,}/g, "\n\n").trim();
|
|
6056
|
-
}
|
|
6057
|
-
function buildSystemPrompt(onboardingState, viewContext) {
|
|
6058
|
-
const projectContext = [
|
|
6059
|
-
loadProjectInstructions(),
|
|
6060
|
-
loadProjectManifest(),
|
|
6061
|
-
loadSpecFileMetadata(),
|
|
6062
|
-
loadProjectFileListing()
|
|
6063
|
-
].filter(Boolean).join("\n");
|
|
6064
|
-
const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
6065
|
-
month: "long",
|
|
6066
|
-
day: "numeric",
|
|
6067
|
-
year: "numeric"
|
|
6068
|
-
});
|
|
6069
|
-
const template = `
|
|
6070
|
-
{{static/identity.md}}
|
|
6071
|
-
|
|
6072
|
-
Current date: ${now}
|
|
6073
|
-
|
|
6074
|
-
<platform_docs>
|
|
6075
|
-
<platform>
|
|
6076
|
-
{{compiled/platform.md}}
|
|
6077
|
-
</platform>
|
|
6078
|
-
|
|
6079
|
-
<manifest>
|
|
6080
|
-
{{compiled/manifest.md}}
|
|
6081
|
-
</manifest>
|
|
6082
|
-
|
|
6083
|
-
<tables>
|
|
6084
|
-
{{compiled/tables.md}}
|
|
6085
|
-
</tables>
|
|
6086
|
-
|
|
6087
|
-
<methods>
|
|
6088
|
-
{{compiled/methods.md}}
|
|
6089
|
-
</methods>
|
|
6090
|
-
|
|
6091
|
-
<auth>
|
|
6092
|
-
{{compiled/auth.md}}
|
|
6093
|
-
</auth>
|
|
6094
|
-
|
|
6095
|
-
<dev_and_deploy>
|
|
6096
|
-
{{compiled/dev-and-deploy.md}}
|
|
6097
|
-
</dev_and_deploy>
|
|
6098
|
-
|
|
6099
|
-
<design>
|
|
6100
|
-
{{compiled/design.md}}
|
|
6101
|
-
</design>
|
|
6102
|
-
|
|
6103
|
-
<building_agent_interfaces>
|
|
6104
|
-
{{compiled/agent-interfaces.md}}
|
|
6105
|
-
</building_agent_interfaces>
|
|
6106
|
-
|
|
6107
|
-
<media_cdn>
|
|
6108
|
-
{{compiled/media-cdn.md}}
|
|
6109
|
-
</media_cdn>
|
|
6110
|
-
|
|
6111
|
-
<interfaces>
|
|
6112
|
-
{{compiled/interfaces.md}}
|
|
6113
|
-
</interfaces>
|
|
6114
|
-
|
|
6115
|
-
<scenarios>
|
|
6116
|
-
{{compiled/scenarios.md}}
|
|
6117
|
-
</scenarios>
|
|
6118
|
-
|
|
6119
|
-
<secrets>
|
|
6120
|
-
{{compiled/secrets.md}}
|
|
6121
|
-
</secrets>
|
|
6122
|
-
</platform_docs>
|
|
6123
|
-
|
|
6124
|
-
<mindstudio_agent_sdk_docs>
|
|
6125
|
-
{{compiled/sdk-actions.md}}
|
|
6126
|
-
|
|
6127
|
-
{{compiled/task-agents.md}}
|
|
6128
|
-
</mindstudio_agent_sdk_docs>
|
|
6129
|
-
|
|
6130
|
-
<mindstudio_flavored_markdown_spec_docs>
|
|
6131
|
-
{{compiled/msfm.md}}
|
|
6132
|
-
</mindstudio_flavored_markdown_spec_docs>
|
|
6133
|
-
|
|
6134
|
-
<project_context>
|
|
6135
|
-
${projectContext}
|
|
6136
|
-
</project_context>
|
|
6137
|
-
|
|
6138
|
-
<intake_mode_instructions>
|
|
6139
|
-
{{static/intake.md}}
|
|
6140
|
-
</intake_mode_instructions>
|
|
6141
|
-
|
|
6142
|
-
<spec_authoring_instructions>
|
|
6143
|
-
{{static/authoring.md}}
|
|
6144
|
-
</spec_authoring_instructions>
|
|
6145
|
-
|
|
6146
|
-
{{static/team.md}}
|
|
6147
|
-
|
|
6148
|
-
<code_authoring_instructions>
|
|
6149
|
-
{{static/coding.md}}
|
|
6150
|
-
${isLspConfigured() ? `<typescript_lsp>
|
|
6151
|
-
{{static/lsp.md}}
|
|
6152
|
-
</typescript_lsp>` : ""}
|
|
6153
|
-
</code_authoring_instructions>
|
|
6154
|
-
|
|
6155
|
-
{{static/instructions.md}}
|
|
6156
|
-
|
|
6157
|
-
<conversation_summaries>
|
|
6158
|
-
Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
|
|
6159
|
-
|
|
6160
|
-
Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
|
|
6161
|
-
</conversation_summaries>
|
|
6162
|
-
|
|
6163
|
-
<project_onboarding>
|
|
6164
|
-
New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
|
|
6165
|
-
|
|
6166
|
-
- **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code.
|
|
6167
|
-
- **initialSpecAuthoring**: Writing and refining the first spec. The user can see it in the editor as it streams in and can give feedback to iterate on it. This phase covers both the initial draft and any back-and-forth refinement before code generation.
|
|
6168
|
-
- **initialCodegen**: First code generation from the spec. The agent is generating methods, tables, interfaces, manifest updates, and scenarios. This can take a while and involves heavy tool use. The user sees a full-screen build progress view.
|
|
6169
|
-
- **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
|
|
6170
|
-
|
|
6171
|
-
<!-- cache_breakpoint -->
|
|
6172
|
-
|
|
6173
|
-
<current_project_onboarding_state>
|
|
6174
|
-
${onboardingState ?? "onboardingFinished"}
|
|
6175
|
-
</current_project_onboarding_state>
|
|
6176
|
-
</project_onboarding>
|
|
6177
|
-
|
|
6178
|
-
<view_context>
|
|
6179
|
-
The user is currently in ${viewContext?.mode ?? "code"} mode.
|
|
6180
|
-
${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
|
|
6181
|
-
</view_context>
|
|
6182
|
-
`;
|
|
6183
|
-
return resolveIncludes(template);
|
|
6184
6459
|
}
|
|
6185
|
-
var
|
|
6186
|
-
|
|
6460
|
+
var log8, EXTERNAL_TOOLS;
|
|
6461
|
+
var init_agent = __esm({
|
|
6462
|
+
"src/agent.ts"() {
|
|
6187
6463
|
"use strict";
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6464
|
+
init_api();
|
|
6465
|
+
init_tools6();
|
|
6466
|
+
init_session();
|
|
6467
|
+
init_logger();
|
|
6468
|
+
init_parsePartialJson();
|
|
6469
|
+
init_statusWatcher();
|
|
6470
|
+
init_errors();
|
|
6471
|
+
init_cleanMessages();
|
|
6472
|
+
init_tools6();
|
|
6473
|
+
log8 = createLogger("agent");
|
|
6474
|
+
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
6475
|
+
"promptUser",
|
|
6476
|
+
"setProjectOnboardingState",
|
|
6477
|
+
"clearSyncStatus",
|
|
6478
|
+
"presentSyncPlan",
|
|
6479
|
+
"presentPublishPlan",
|
|
6480
|
+
"presentPlan",
|
|
6481
|
+
"confirmDestructiveAction",
|
|
6482
|
+
"runScenario",
|
|
6483
|
+
"runMethod",
|
|
6484
|
+
"queryDatabase",
|
|
6485
|
+
"browserCommand",
|
|
6486
|
+
"setProjectMetadata"
|
|
6487
|
+
]);
|
|
6191
6488
|
}
|
|
6192
6489
|
});
|
|
6193
6490
|
|
|
@@ -6198,10 +6495,10 @@ import os from "os";
|
|
|
6198
6495
|
function loadConfigFile() {
|
|
6199
6496
|
try {
|
|
6200
6497
|
const raw = fs17.readFileSync(CONFIG_PATH, "utf-8");
|
|
6201
|
-
|
|
6498
|
+
log9.debug("Loaded config file", { path: CONFIG_PATH });
|
|
6202
6499
|
return JSON.parse(raw);
|
|
6203
6500
|
} catch (err) {
|
|
6204
|
-
|
|
6501
|
+
log9.debug("No config file found", {
|
|
6205
6502
|
path: CONFIG_PATH,
|
|
6206
6503
|
error: err.message
|
|
6207
6504
|
});
|
|
@@ -6215,25 +6512,25 @@ function resolveConfig(flags2) {
|
|
|
6215
6512
|
const apiKey = flags2?.apiKey || process.env.MINDSTUDIO_API_KEY || env?.apiKey || "";
|
|
6216
6513
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
6217
6514
|
if (!apiKey) {
|
|
6218
|
-
|
|
6515
|
+
log9.error("No API key found");
|
|
6219
6516
|
throw new Error(
|
|
6220
6517
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
6221
6518
|
);
|
|
6222
6519
|
}
|
|
6223
6520
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
6224
|
-
|
|
6521
|
+
log9.info("Config resolved", {
|
|
6225
6522
|
baseUrl: baseUrl2,
|
|
6226
6523
|
keySource,
|
|
6227
6524
|
environment: activeEnv
|
|
6228
6525
|
});
|
|
6229
6526
|
return { apiKey, baseUrl: baseUrl2 };
|
|
6230
6527
|
}
|
|
6231
|
-
var
|
|
6528
|
+
var log9, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
6232
6529
|
var init_config = __esm({
|
|
6233
6530
|
"src/config.ts"() {
|
|
6234
6531
|
"use strict";
|
|
6235
6532
|
init_logger();
|
|
6236
|
-
|
|
6533
|
+
log9 = createLogger("config");
|
|
6237
6534
|
CONFIG_PATH = path9.join(
|
|
6238
6535
|
os.homedir(),
|
|
6239
6536
|
".mindstudio-local-tunnel",
|
|
@@ -6243,235 +6540,13 @@ var init_config = __esm({
|
|
|
6243
6540
|
}
|
|
6244
6541
|
});
|
|
6245
6542
|
|
|
6246
|
-
// src/compaction/index.ts
|
|
6247
|
-
async function compactConversation(state, apiConfig, system, tools2) {
|
|
6248
|
-
const insertionIndex = findSafeInsertionPoint(state.messages);
|
|
6249
|
-
const summaries = [];
|
|
6250
|
-
const tasks = [];
|
|
6251
|
-
const conversationMessages = getConversationMessagesForSummary(
|
|
6252
|
-
state.messages,
|
|
6253
|
-
insertionIndex
|
|
6254
|
-
);
|
|
6255
|
-
if (conversationMessages.length > 0) {
|
|
6256
|
-
tasks.push(
|
|
6257
|
-
generateSummary(
|
|
6258
|
-
apiConfig,
|
|
6259
|
-
"conversation",
|
|
6260
|
-
CONVERSATION_SUMMARY_PROMPT,
|
|
6261
|
-
conversationMessages,
|
|
6262
|
-
system,
|
|
6263
|
-
tools2
|
|
6264
|
-
).then((text) => {
|
|
6265
|
-
if (text) {
|
|
6266
|
-
summaries.push({ name: "conversation", text });
|
|
6267
|
-
}
|
|
6268
|
-
})
|
|
6269
|
-
);
|
|
6270
|
-
}
|
|
6271
|
-
for (const name of SUMMARIZABLE_SUBAGENTS) {
|
|
6272
|
-
const subagentMessages = getSubAgentMessagesForSummary(
|
|
6273
|
-
state.messages,
|
|
6274
|
-
name,
|
|
6275
|
-
insertionIndex
|
|
6276
|
-
);
|
|
6277
|
-
if (subagentMessages.length > 0) {
|
|
6278
|
-
tasks.push(
|
|
6279
|
-
generateSummary(
|
|
6280
|
-
apiConfig,
|
|
6281
|
-
name,
|
|
6282
|
-
SUBAGENT_SUMMARY_PROMPT,
|
|
6283
|
-
subagentMessages,
|
|
6284
|
-
system,
|
|
6285
|
-
tools2
|
|
6286
|
-
).then((text) => {
|
|
6287
|
-
if (text) {
|
|
6288
|
-
summaries.push({ name, text });
|
|
6289
|
-
}
|
|
6290
|
-
})
|
|
6291
|
-
);
|
|
6292
|
-
}
|
|
6293
|
-
}
|
|
6294
|
-
await Promise.all(tasks);
|
|
6295
|
-
const checkpointMessages = summaries.map((s) => ({
|
|
6296
|
-
role: "user",
|
|
6297
|
-
hidden: true,
|
|
6298
|
-
content: [
|
|
6299
|
-
{
|
|
6300
|
-
type: "summary",
|
|
6301
|
-
name: s.name,
|
|
6302
|
-
text: s.text,
|
|
6303
|
-
startedAt: Date.now()
|
|
6304
|
-
}
|
|
6305
|
-
]
|
|
6306
|
-
}));
|
|
6307
|
-
if (checkpointMessages.length > 0) {
|
|
6308
|
-
state.messages.splice(insertionIndex, 0, ...checkpointMessages);
|
|
6309
|
-
}
|
|
6310
|
-
log8.info("Compaction complete", {
|
|
6311
|
-
summaries: summaries.length,
|
|
6312
|
-
insertionIndex,
|
|
6313
|
-
messagesAfter: state.messages.length - insertionIndex - checkpointMessages.length
|
|
6314
|
-
});
|
|
6315
|
-
}
|
|
6316
|
-
function findSafeInsertionPoint(messages) {
|
|
6317
|
-
let idx = messages.length;
|
|
6318
|
-
while (idx > 0) {
|
|
6319
|
-
const msg = messages[idx - 1];
|
|
6320
|
-
if (msg.role === "user" && msg.toolCallId) {
|
|
6321
|
-
idx--;
|
|
6322
|
-
} else {
|
|
6323
|
-
break;
|
|
6324
|
-
}
|
|
6325
|
-
}
|
|
6326
|
-
if (idx < messages.length && idx > 0) {
|
|
6327
|
-
const msg = messages[idx - 1];
|
|
6328
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
6329
|
-
const hasToolUse = msg.content.some(
|
|
6330
|
-
(b) => b.type === "tool"
|
|
6331
|
-
);
|
|
6332
|
-
if (hasToolUse) {
|
|
6333
|
-
idx--;
|
|
6334
|
-
}
|
|
6335
|
-
}
|
|
6336
|
-
}
|
|
6337
|
-
return idx;
|
|
6338
|
-
}
|
|
6339
|
-
function getConversationMessagesForSummary(messages, endIndex) {
|
|
6340
|
-
let startIdx = 0;
|
|
6341
|
-
for (let i = endIndex - 1; i >= 0; i--) {
|
|
6342
|
-
const msg = messages[i];
|
|
6343
|
-
if (!Array.isArray(msg.content)) {
|
|
6344
|
-
continue;
|
|
6345
|
-
}
|
|
6346
|
-
for (const block of msg.content) {
|
|
6347
|
-
if (block.type === "summary" && block.name === "conversation") {
|
|
6348
|
-
startIdx = i + 1;
|
|
6349
|
-
break;
|
|
6350
|
-
}
|
|
6351
|
-
}
|
|
6352
|
-
if (startIdx > 0) {
|
|
6353
|
-
break;
|
|
6354
|
-
}
|
|
6355
|
-
}
|
|
6356
|
-
return messages.slice(startIdx, endIndex);
|
|
6357
|
-
}
|
|
6358
|
-
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
6359
|
-
let checkpointIdx = -1;
|
|
6360
|
-
for (let i = endIndex - 1; i >= 0; i--) {
|
|
6361
|
-
const msg = messages[i];
|
|
6362
|
-
if (!Array.isArray(msg.content)) {
|
|
6363
|
-
continue;
|
|
6364
|
-
}
|
|
6365
|
-
for (const block of msg.content) {
|
|
6366
|
-
if (block.type === "summary" && block.name === subAgentName) {
|
|
6367
|
-
checkpointIdx = i;
|
|
6368
|
-
break;
|
|
6369
|
-
}
|
|
6370
|
-
}
|
|
6371
|
-
if (checkpointIdx !== -1) {
|
|
6372
|
-
break;
|
|
6373
|
-
}
|
|
6374
|
-
}
|
|
6375
|
-
const startIdx = checkpointIdx !== -1 ? checkpointIdx + 1 : 0;
|
|
6376
|
-
const collected = [];
|
|
6377
|
-
for (let i = startIdx; i < endIndex; i++) {
|
|
6378
|
-
const msg = messages[i];
|
|
6379
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
6380
|
-
continue;
|
|
6381
|
-
}
|
|
6382
|
-
for (const block of msg.content) {
|
|
6383
|
-
if (block.type === "tool" && block.name === subAgentName && block.subAgentMessages?.length) {
|
|
6384
|
-
collected.push(...block.subAgentMessages);
|
|
6385
|
-
}
|
|
6386
|
-
}
|
|
6387
|
-
}
|
|
6388
|
-
return collected;
|
|
6389
|
-
}
|
|
6390
|
-
function serializeForSummary(messages) {
|
|
6391
|
-
return messages.map((msg) => {
|
|
6392
|
-
if (typeof msg.content === "string") {
|
|
6393
|
-
return `[${msg.role}]: ${msg.content}`;
|
|
6394
|
-
}
|
|
6395
|
-
if (!Array.isArray(msg.content)) {
|
|
6396
|
-
return `[${msg.role}]: (empty)`;
|
|
6397
|
-
}
|
|
6398
|
-
const blocks = msg.content;
|
|
6399
|
-
const parts = [];
|
|
6400
|
-
for (const block of blocks) {
|
|
6401
|
-
if (block.type === "text") {
|
|
6402
|
-
parts.push(block.text);
|
|
6403
|
-
} else if (block.type === "tool") {
|
|
6404
|
-
parts.push(
|
|
6405
|
-
`[tool: ${block.name}(${JSON.stringify(block.input).slice(0, 200)})] \u2192 ${(block.result ?? "").slice(0, 500)}`
|
|
6406
|
-
);
|
|
6407
|
-
}
|
|
6408
|
-
}
|
|
6409
|
-
return `[${msg.role}]: ${parts.join("\n")}`;
|
|
6410
|
-
}).join("\n\n");
|
|
6411
|
-
}
|
|
6412
|
-
async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, mainSystem, mainTools) {
|
|
6413
|
-
const serialized = serializeForSummary(messagesToSummarize);
|
|
6414
|
-
if (!serialized.trim()) {
|
|
6415
|
-
return null;
|
|
6416
|
-
}
|
|
6417
|
-
log8.info("Generating summary", {
|
|
6418
|
-
name,
|
|
6419
|
-
messageCount: messagesToSummarize.length,
|
|
6420
|
-
cacheReuse: !!mainSystem
|
|
6421
|
-
});
|
|
6422
|
-
let summaryText = "";
|
|
6423
|
-
const useMainCache = !!mainSystem;
|
|
6424
|
-
const system = useMainCache ? mainSystem : compactionPrompt;
|
|
6425
|
-
const tools2 = useMainCache ? mainTools ?? [] : [];
|
|
6426
|
-
const userContent = useMainCache ? `${compactionPrompt}
|
|
6427
|
-
|
|
6428
|
-
---
|
|
6429
|
-
|
|
6430
|
-
Conversation to summarize:
|
|
6431
|
-
|
|
6432
|
-
${serialized}` : serialized;
|
|
6433
|
-
for await (const event of streamChat({
|
|
6434
|
-
...apiConfig,
|
|
6435
|
-
subAgentId: "conversationSummarizer",
|
|
6436
|
-
system,
|
|
6437
|
-
messages: [{ role: "user", content: userContent }],
|
|
6438
|
-
tools: tools2
|
|
6439
|
-
})) {
|
|
6440
|
-
if (event.type === "text") {
|
|
6441
|
-
summaryText += event.text;
|
|
6442
|
-
} else if (event.type === "error") {
|
|
6443
|
-
log8.error("Summary generation failed", { name, error: event.error });
|
|
6444
|
-
return null;
|
|
6445
|
-
}
|
|
6446
|
-
}
|
|
6447
|
-
if (!summaryText.trim()) {
|
|
6448
|
-
log8.warn("Empty summary generated", { name });
|
|
6449
|
-
return null;
|
|
6450
|
-
}
|
|
6451
|
-
log8.info("Summary generated", { name, summaryLength: summaryText.length });
|
|
6452
|
-
return summaryText.trim();
|
|
6453
|
-
}
|
|
6454
|
-
var log8, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS;
|
|
6455
|
-
var init_compaction = __esm({
|
|
6456
|
-
"src/compaction/index.ts"() {
|
|
6457
|
-
"use strict";
|
|
6458
|
-
init_api();
|
|
6459
|
-
init_assets();
|
|
6460
|
-
init_logger();
|
|
6461
|
-
log8 = createLogger("compaction");
|
|
6462
|
-
CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
|
|
6463
|
-
SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
6464
|
-
SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
6465
|
-
}
|
|
6466
|
-
});
|
|
6467
|
-
|
|
6468
6543
|
// src/toolRegistry.ts
|
|
6469
|
-
var
|
|
6544
|
+
var log10, ToolRegistry;
|
|
6470
6545
|
var init_toolRegistry = __esm({
|
|
6471
6546
|
"src/toolRegistry.ts"() {
|
|
6472
6547
|
"use strict";
|
|
6473
6548
|
init_logger();
|
|
6474
|
-
|
|
6549
|
+
log10 = createLogger("tool-registry");
|
|
6475
6550
|
ToolRegistry = class {
|
|
6476
6551
|
entries = /* @__PURE__ */ new Map();
|
|
6477
6552
|
onEvent;
|
|
@@ -6497,7 +6572,7 @@ var init_toolRegistry = __esm({
|
|
|
6497
6572
|
if (!entry) {
|
|
6498
6573
|
return false;
|
|
6499
6574
|
}
|
|
6500
|
-
|
|
6575
|
+
log10.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6501
6576
|
entry.abortController.abort(mode);
|
|
6502
6577
|
if (mode === "graceful") {
|
|
6503
6578
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -6530,7 +6605,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6530
6605
|
if (!entry) {
|
|
6531
6606
|
return false;
|
|
6532
6607
|
}
|
|
6533
|
-
|
|
6608
|
+
log10.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6534
6609
|
entry.abortController.abort("restart");
|
|
6535
6610
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6536
6611
|
this.onEvent?.({
|
|
@@ -6705,7 +6780,7 @@ ${xmlParts}
|
|
|
6705
6780
|
}
|
|
6706
6781
|
function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
|
|
6707
6782
|
pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
6708
|
-
|
|
6783
|
+
log11.info("Background complete", {
|
|
6709
6784
|
toolCallId,
|
|
6710
6785
|
name,
|
|
6711
6786
|
requestId: currentRequestId
|
|
@@ -6977,7 +7052,7 @@ ${xmlParts}
|
|
|
6977
7052
|
requestId
|
|
6978
7053
|
);
|
|
6979
7054
|
}
|
|
6980
|
-
|
|
7055
|
+
log11.info("Turn complete", {
|
|
6981
7056
|
requestId,
|
|
6982
7057
|
durationMs: Date.now() - turnStart
|
|
6983
7058
|
});
|
|
@@ -6986,7 +7061,7 @@ ${xmlParts}
|
|
|
6986
7061
|
emit("error", { error: err.message }, requestId);
|
|
6987
7062
|
emit("completed", { success: false, error: err.message }, requestId);
|
|
6988
7063
|
}
|
|
6989
|
-
|
|
7064
|
+
log11.warn("Command failed", {
|
|
6990
7065
|
action: "message",
|
|
6991
7066
|
requestId,
|
|
6992
7067
|
error: err.message
|
|
@@ -7006,7 +7081,7 @@ ${xmlParts}
|
|
|
7006
7081
|
return;
|
|
7007
7082
|
}
|
|
7008
7083
|
const { action, requestId } = parsed;
|
|
7009
|
-
|
|
7084
|
+
log11.info("Command received", { action, requestId });
|
|
7010
7085
|
if (action === "tool_result" && parsed.id) {
|
|
7011
7086
|
const id = parsed.id;
|
|
7012
7087
|
const result = parsed.result ?? "";
|
|
@@ -7015,7 +7090,7 @@ ${xmlParts}
|
|
|
7015
7090
|
pendingTools.delete(id);
|
|
7016
7091
|
pending.resolve(result);
|
|
7017
7092
|
} else if (!running) {
|
|
7018
|
-
|
|
7093
|
+
log11.info("Late tool_result while idle, dismissing", { id });
|
|
7019
7094
|
emit("completed", { success: true }, requestId);
|
|
7020
7095
|
} else {
|
|
7021
7096
|
earlyResults.set(id, result);
|
|
@@ -7071,36 +7146,31 @@ ${xmlParts}
|
|
|
7071
7146
|
return;
|
|
7072
7147
|
}
|
|
7073
7148
|
if (action === "compact") {
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
"compaction_complete",
|
|
7089
|
-
{
|
|
7090
|
-
|
|
7091
|
-
)
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
sessionStats.messageCount = state.messages.length;
|
|
7100
|
-
sessionStats.updatedAt = Date.now();
|
|
7101
|
-
try {
|
|
7102
|
-
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
7103
|
-
} catch {
|
|
7149
|
+
triggerCompaction(state, config, {
|
|
7150
|
+
onStart: () => {
|
|
7151
|
+
sessionStats.compactionInProgress = true;
|
|
7152
|
+
sessionStats.updatedAt = Date.now();
|
|
7153
|
+
try {
|
|
7154
|
+
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
7155
|
+
} catch {
|
|
7156
|
+
}
|
|
7157
|
+
},
|
|
7158
|
+
onComplete: () => {
|
|
7159
|
+
emit("compaction_complete", {}, requestId);
|
|
7160
|
+
emit("completed", { success: true }, requestId);
|
|
7161
|
+
},
|
|
7162
|
+
onError: (error) => {
|
|
7163
|
+
emit("compaction_complete", { error }, requestId);
|
|
7164
|
+
emit("completed", { success: false, error }, requestId);
|
|
7165
|
+
},
|
|
7166
|
+
onFinally: () => {
|
|
7167
|
+
sessionStats.compactionInProgress = false;
|
|
7168
|
+
sessionStats.messageCount = state.messages.length;
|
|
7169
|
+
sessionStats.updatedAt = Date.now();
|
|
7170
|
+
try {
|
|
7171
|
+
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
7172
|
+
} catch {
|
|
7173
|
+
}
|
|
7104
7174
|
}
|
|
7105
7175
|
});
|
|
7106
7176
|
return;
|
|
@@ -7130,21 +7200,20 @@ ${xmlParts}
|
|
|
7130
7200
|
process.on("SIGINT", shutdown);
|
|
7131
7201
|
emit("ready");
|
|
7132
7202
|
}
|
|
7133
|
-
var
|
|
7203
|
+
var log11;
|
|
7134
7204
|
var init_headless = __esm({
|
|
7135
7205
|
"src/headless.ts"() {
|
|
7136
7206
|
"use strict";
|
|
7137
7207
|
init_logger();
|
|
7138
7208
|
init_config();
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
init_tools6();
|
|
7209
|
+
init_prompt();
|
|
7210
|
+
init_trigger();
|
|
7142
7211
|
init_lsp();
|
|
7143
7212
|
init_agent();
|
|
7144
7213
|
init_session();
|
|
7145
7214
|
init_toolRegistry();
|
|
7146
7215
|
init_resolve();
|
|
7147
|
-
|
|
7216
|
+
log11 = createLogger("headless");
|
|
7148
7217
|
}
|
|
7149
7218
|
});
|
|
7150
7219
|
|
|
@@ -7287,7 +7356,7 @@ function MessageList({ turns }) {
|
|
|
7287
7356
|
|
|
7288
7357
|
// src/tui/App.tsx
|
|
7289
7358
|
init_agent();
|
|
7290
|
-
|
|
7359
|
+
init_prompt();
|
|
7291
7360
|
init_session();
|
|
7292
7361
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7293
7362
|
function App({ apiConfig, model }) {
|