@zhongqian97-code/ecode 0.5.13 → 0.5.15
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/index.js +518 -152
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -552,6 +552,77 @@ function executeBash(cmd, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
552
552
|
});
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
// src/prompts.ts
|
|
556
|
+
var DEFAULT_SYSTEM_PROMPT = `You are ecode, a terminal-based coding assistant focused on software engineering work.
|
|
557
|
+
|
|
558
|
+
Your job is to help the user complete coding tasks accurately, efficiently, and safely. Prefer doing useful work over discussing work abstractly, but do not pretend to have done things you did not do.
|
|
559
|
+
|
|
560
|
+
# Core behavior
|
|
561
|
+
|
|
562
|
+
- Treat the user's request as a real software engineering task unless they clearly ask for pure explanation.
|
|
563
|
+
- Read relevant code before proposing or making changes.
|
|
564
|
+
- Understand the local context before editing. Do not guess how the codebase works when you can inspect it.
|
|
565
|
+
- Prefer the simplest approach that fully solves the requested problem.
|
|
566
|
+
- Do not add features, refactors, configurability, abstractions, or cleanups that were not requested unless they are necessary to complete the task correctly.
|
|
567
|
+
- Do not make speculative improvements for hypothetical future needs.
|
|
568
|
+
- If an approach fails, diagnose the reason before switching tactics. Do not blindly retry the same failing action.
|
|
569
|
+
|
|
570
|
+
# Editing discipline
|
|
571
|
+
|
|
572
|
+
- Prefer dedicated file tools over shell commands when available.
|
|
573
|
+
- Use read-like tools to inspect files, edit/patch tools to modify files, and write tools only when replacing full file content is the right choice.
|
|
574
|
+
- Do not use shell tricks as a substitute for structured file operations when a dedicated tool exists.
|
|
575
|
+
- Make the smallest coherent change that solves the problem.
|
|
576
|
+
- Do not create new files unless they are actually needed.
|
|
577
|
+
- Do not rewrite large files when a targeted edit is enough.
|
|
578
|
+
- Preserve existing code style unless the task explicitly requires style changes.
|
|
579
|
+
- Do not add comments unless they help explain a non-obvious constraint or decision.
|
|
580
|
+
|
|
581
|
+
# Tool usage
|
|
582
|
+
|
|
583
|
+
- Prefer dedicated tools over bash for file reading, file editing, search, patching, and task tracking.
|
|
584
|
+
- Use bash for commands that are genuinely shell-oriented: tests, builds, git inspection, package manager commands, environment inspection, and other terminal operations.
|
|
585
|
+
- When there are multiple independent lookups, do them efficiently. When steps depend on each other, do them in order.
|
|
586
|
+
- Treat tool output as evidence. Base conclusions on what you actually observed.
|
|
587
|
+
- If tool output is noisy, extract and carry forward only the important facts.
|
|
588
|
+
|
|
589
|
+
# Safety and confirmation
|
|
590
|
+
|
|
591
|
+
- Be careful with destructive, irreversible, or externally visible actions.
|
|
592
|
+
- Ask before actions like deleting important files, overwriting significant uncommitted work, force-pushing, changing remote state, sending messages, or performing other hard-to-reverse operations.
|
|
593
|
+
- Do not treat a previous approval as blanket approval for future risky actions.
|
|
594
|
+
- If the user denies a risky action, do not immediately retry the same action. Adjust your plan.
|
|
595
|
+
- Safety checks are a convenience layer, not a guarantee. Act cautiously even when a command appears allowed.
|
|
596
|
+
|
|
597
|
+
# Verification and honesty
|
|
598
|
+
|
|
599
|
+
- When you change code, verify the result when practical: run tests, type checks, linters, builds, or the narrowest useful validation step.
|
|
600
|
+
- Prefer the smallest meaningful verification rather than expensive blanket verification when the task is local and narrow.
|
|
601
|
+
- If you could not verify something, say so plainly.
|
|
602
|
+
- Never claim success, passing tests, or completed work unless the evidence supports it.
|
|
603
|
+
- If a command failed, a test failed, or verification is incomplete, report that accurately.
|
|
604
|
+
|
|
605
|
+
# Communication style
|
|
606
|
+
|
|
607
|
+
- Be concise, direct, and useful.
|
|
608
|
+
- Give short progress updates when starting multi-step work, when you discover something important, when changing direction, or when blocked.
|
|
609
|
+
- Do not narrate every obvious tool call.
|
|
610
|
+
- Do not pad responses with filler, hype, or unnecessary repetition.
|
|
611
|
+
- When reporting completion, lead with what changed, then include relevant verification or blockers.
|
|
612
|
+
- When you need user input, ask for the single most important missing decision.
|
|
613
|
+
|
|
614
|
+
# Scope control
|
|
615
|
+
|
|
616
|
+
- Solve the requested problem completely, but do not quietly widen scope.
|
|
617
|
+
- If you notice an adjacent issue that matters, mention it briefly and separate it from the requested task.
|
|
618
|
+
- Favor correctness and clarity over cleverness.
|
|
619
|
+
|
|
620
|
+
# Failure mode handling
|
|
621
|
+
|
|
622
|
+
- If paths, commands, or assumptions are wrong, correct course based on inspection rather than defending the original plan.
|
|
623
|
+
- If you are blocked by missing information, say exactly what is missing.
|
|
624
|
+
- If you are blocked by tool limits, choose the next best bounded action instead of stalling.`;
|
|
625
|
+
|
|
555
626
|
// src/tools/read.ts
|
|
556
627
|
import * as fs from "fs/promises";
|
|
557
628
|
var READ_TOOL = {
|
|
@@ -1205,8 +1276,160 @@ function todo(params) {
|
|
|
1205
1276
|
}
|
|
1206
1277
|
}
|
|
1207
1278
|
|
|
1208
|
-
// src/
|
|
1209
|
-
|
|
1279
|
+
// src/sessions/metadata.ts
|
|
1280
|
+
import * as crypto from "crypto";
|
|
1281
|
+
import * as fs7 from "fs";
|
|
1282
|
+
import * as path5 from "path";
|
|
1283
|
+
function metadataPathFromLogFile(logFilePath2) {
|
|
1284
|
+
const base = path5.basename(logFilePath2, ".jsonl");
|
|
1285
|
+
const dir = path5.dirname(logFilePath2);
|
|
1286
|
+
return path5.join(dir, `${base}-session.json`);
|
|
1287
|
+
}
|
|
1288
|
+
function createSessionMetadata(logFilePath2, model) {
|
|
1289
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1290
|
+
return {
|
|
1291
|
+
id: crypto.randomUUID(),
|
|
1292
|
+
startTime: now,
|
|
1293
|
+
lastActivity: now,
|
|
1294
|
+
cwd: process.cwd(),
|
|
1295
|
+
model,
|
|
1296
|
+
title: "",
|
|
1297
|
+
turnCount: 0,
|
|
1298
|
+
totalTokens: 0,
|
|
1299
|
+
logFile: path5.basename(logFilePath2)
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function writeSessionMetadata(logFilePath2, metadata) {
|
|
1303
|
+
const metaPath = metadataPathFromLogFile(logFilePath2);
|
|
1304
|
+
try {
|
|
1305
|
+
fs7.writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + "\n");
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
process.stderr.write(`[sessions] Failed to write metadata: ${err}
|
|
1308
|
+
`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function readSessionMetadata(metaFilePath) {
|
|
1312
|
+
try {
|
|
1313
|
+
const raw = fs7.readFileSync(metaFilePath, "utf-8");
|
|
1314
|
+
return JSON.parse(raw);
|
|
1315
|
+
} catch {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function updateSessionMetadata(logFilePath2, partial) {
|
|
1320
|
+
const metaPath = metadataPathFromLogFile(logFilePath2);
|
|
1321
|
+
let existing = null;
|
|
1322
|
+
try {
|
|
1323
|
+
const raw = fs7.readFileSync(metaPath, "utf-8");
|
|
1324
|
+
existing = JSON.parse(raw);
|
|
1325
|
+
} catch {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
writeSessionMetadata(logFilePath2, { ...existing, ...partial });
|
|
1329
|
+
}
|
|
1330
|
+
function listSessions(logDir) {
|
|
1331
|
+
try {
|
|
1332
|
+
const files = fs7.readdirSync(logDir);
|
|
1333
|
+
const metaFiles = files.filter((f) => f.endsWith("-session.json"));
|
|
1334
|
+
const sessions = [];
|
|
1335
|
+
for (const file of metaFiles) {
|
|
1336
|
+
const meta = readSessionMetadata(path5.join(logDir, file));
|
|
1337
|
+
if (meta) sessions.push(meta);
|
|
1338
|
+
}
|
|
1339
|
+
return sessions.sort(
|
|
1340
|
+
(a, b) => b.lastActivity.localeCompare(a.lastActivity)
|
|
1341
|
+
);
|
|
1342
|
+
} catch {
|
|
1343
|
+
return [];
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function findSession(logDir, idOrPrefix) {
|
|
1347
|
+
const sessions = listSessions(logDir);
|
|
1348
|
+
return sessions.find(
|
|
1349
|
+
(s) => s.id === idOrPrefix || s.id.startsWith(idOrPrefix)
|
|
1350
|
+
) ?? null;
|
|
1351
|
+
}
|
|
1352
|
+
function generateTitle(firstUserMessage) {
|
|
1353
|
+
const oneLine = firstUserMessage.replace(/\n+/g, " ").trim();
|
|
1354
|
+
return oneLine.length > 50 ? oneLine.slice(0, 47) + "..." : oneLine;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/logger.ts
|
|
1358
|
+
import * as fs8 from "fs";
|
|
1359
|
+
import * as path6 from "path";
|
|
1360
|
+
function createLogger(logDir, sessionStart) {
|
|
1361
|
+
fs8.mkdirSync(logDir, { recursive: true });
|
|
1362
|
+
const filename = sessionStart.toISOString().replace(/:/g, "-").replace(/\..+/, "") + ".jsonl";
|
|
1363
|
+
const filePath = path6.join(logDir, filename);
|
|
1364
|
+
return {
|
|
1365
|
+
filePath,
|
|
1366
|
+
/**
|
|
1367
|
+
* 将单条日志条目序列化为 JSON 并同步追加到文件(末尾加换行符)。
|
|
1368
|
+
*
|
|
1369
|
+
* 使用 appendFileSync 而非 appendFile(异步版本)的原因:
|
|
1370
|
+
* 进程崩溃或 Ctrl-C 退出时,异步写操作可能尚未完成,导致最后几条记录丢失。
|
|
1371
|
+
* 同步写入虽然阻塞事件循环,但日志条目通常很小(< 1KB),延迟可忽略。
|
|
1372
|
+
*
|
|
1373
|
+
* 写入失败时输出到 stderr 而非抛出异常,防止日志错误中断正常业务流程。
|
|
1374
|
+
*/
|
|
1375
|
+
append(entry) {
|
|
1376
|
+
try {
|
|
1377
|
+
fs8.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
process.stderr.write(`[logger] Failed to write log entry: ${err}
|
|
1380
|
+
`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/tools/task.ts
|
|
1387
|
+
var TASK_TOOL = {
|
|
1388
|
+
type: "function",
|
|
1389
|
+
function: {
|
|
1390
|
+
name: "task",
|
|
1391
|
+
description: "\u628A\u4E00\u4E2A\u5B50\u4EFB\u52A1\u59D4\u6258\u7ED9\u9694\u79BB\u7684\u5B50 agent\u3002\u9002\u5408\u4EE3\u7801\u8C03\u7814\u3001\u5C40\u90E8\u5B9E\u73B0\u3001\u6587\u4EF6\u4FEE\u6539\u6216\u7ED3\u679C\u6C47\u603B\u3002\u8FD4\u56DE\u5355\u6761\u6700\u7EC8\u7ED3\u679C\u3002",
|
|
1392
|
+
parameters: {
|
|
1393
|
+
type: "object",
|
|
1394
|
+
properties: {
|
|
1395
|
+
description: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
description: "3-8 \u4E2A\u8BCD\u7684\u77ED\u63CF\u8FF0\uFF0C\u7528\u4E8E\u6807\u8BC6\u8FD9\u4E2A\u5B50\u4EFB\u52A1\u3002"
|
|
1398
|
+
},
|
|
1399
|
+
prompt: {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
description: "\u4EA4\u7ED9\u5B50 agent \u7684\u5B8C\u6574\u4EFB\u52A1\u8BF4\u660E\u3002"
|
|
1402
|
+
},
|
|
1403
|
+
context: {
|
|
1404
|
+
type: "string",
|
|
1405
|
+
description: "\u53EF\u9009\u3002\u989D\u5916\u8865\u5145\u7ED9\u5B50 agent \u7684\u4E0A\u4E0B\u6587\u8BF4\u660E\u3002"
|
|
1406
|
+
},
|
|
1407
|
+
model: {
|
|
1408
|
+
type: "string",
|
|
1409
|
+
description: "\u53EF\u9009\u3002\u8986\u76D6\u5B50 agent \u4F7F\u7528\u7684\u6A21\u578B\u3002"
|
|
1410
|
+
},
|
|
1411
|
+
cwd: {
|
|
1412
|
+
type: "string",
|
|
1413
|
+
description: "\u53EF\u9009\u3002\u5B50 agent \u7684\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u9ED8\u8BA4\u7EE7\u627F\u5F53\u524D process.cwd()\u3002"
|
|
1414
|
+
},
|
|
1415
|
+
max_turns: {
|
|
1416
|
+
type: "number",
|
|
1417
|
+
description: "\u53EF\u9009\u3002\u5B50 agent \u6700\u591A\u53EF\u6267\u884C\u591A\u5C11\u8F6E\u5DE5\u5177\u5FAA\u73AF\uFF0C\u9ED8\u8BA4 8\u3002"
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
required: ["description", "prompt"]
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
var SUBAGENT_SYSTEM_PROMPT = [
|
|
1425
|
+
DEFAULT_SYSTEM_PROMPT,
|
|
1426
|
+
"",
|
|
1427
|
+
"You are a focused subagent working for another ecode session.",
|
|
1428
|
+
"Finish the delegated task independently when possible.",
|
|
1429
|
+
"Use tools directly instead of asking the human for routine lookups.",
|
|
1430
|
+
"Return a concise final result for the parent agent.",
|
|
1431
|
+
"If blocked, clearly state the blocker and the smallest next step."
|
|
1432
|
+
].join("\n");
|
|
1210
1433
|
var BASH_TOOL = {
|
|
1211
1434
|
type: "function",
|
|
1212
1435
|
function: {
|
|
@@ -1221,6 +1444,249 @@ var BASH_TOOL = {
|
|
|
1221
1444
|
}
|
|
1222
1445
|
}
|
|
1223
1446
|
};
|
|
1447
|
+
var SUBAGENT_TOOLS = [
|
|
1448
|
+
{
|
|
1449
|
+
type: BASH_TOOL.type,
|
|
1450
|
+
function: BASH_TOOL.function
|
|
1451
|
+
},
|
|
1452
|
+
READ_TOOL,
|
|
1453
|
+
WRITE_TOOL,
|
|
1454
|
+
EDIT_TOOL,
|
|
1455
|
+
GLOB_TOOL,
|
|
1456
|
+
GREP_TOOL,
|
|
1457
|
+
APPLY_PATCH_TOOL,
|
|
1458
|
+
TODO_TOOL
|
|
1459
|
+
];
|
|
1460
|
+
function clip(text, maxChars) {
|
|
1461
|
+
return text.length <= maxChars ? text : `${text.slice(0, maxChars - 3)}...`;
|
|
1462
|
+
}
|
|
1463
|
+
function messageToContextLine(msg) {
|
|
1464
|
+
if (msg.role === "system") return null;
|
|
1465
|
+
if (msg.role === "user") return `user: ${clip(msg.content, 500)}`;
|
|
1466
|
+
if (msg.role === "tool") return `tool(${msg.tool_call_id}): ${clip(msg.content, 700)}`;
|
|
1467
|
+
const parts = [];
|
|
1468
|
+
if (msg.content) parts.push(clip(msg.content, 700));
|
|
1469
|
+
if (msg.tool_calls?.length) {
|
|
1470
|
+
parts.push(
|
|
1471
|
+
`[tool_calls: ${msg.tool_calls.map((tc) => tc.function.name).join(", ")}]`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return `assistant: ${parts.join(" ")}`.trim();
|
|
1475
|
+
}
|
|
1476
|
+
function buildParentContext(parentMessages, extraContext) {
|
|
1477
|
+
const lines = [];
|
|
1478
|
+
if (extraContext?.trim()) {
|
|
1479
|
+
lines.push("## Caller-provided context");
|
|
1480
|
+
lines.push(extraContext.trim());
|
|
1481
|
+
}
|
|
1482
|
+
const tail = (parentMessages ?? []).slice(-8).map(messageToContextLine).filter((line) => Boolean(line));
|
|
1483
|
+
if (tail.length > 0) {
|
|
1484
|
+
if (lines.length > 0) lines.push("");
|
|
1485
|
+
lines.push("## Recent parent-session context");
|
|
1486
|
+
lines.push(...tail);
|
|
1487
|
+
}
|
|
1488
|
+
if (lines.length === 0) return void 0;
|
|
1489
|
+
return clip(lines.join("\n"), 4e3);
|
|
1490
|
+
}
|
|
1491
|
+
function appendLog(logger, entry) {
|
|
1492
|
+
if (!logger) return;
|
|
1493
|
+
logger.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
1494
|
+
}
|
|
1495
|
+
var SKIP_MESSAGE = "Command skipped by user.";
|
|
1496
|
+
async function runNonInteractiveBash(command, autoApproveNormal) {
|
|
1497
|
+
const cls = classifyCommand(command);
|
|
1498
|
+
if (cls === "danger") return SKIP_MESSAGE;
|
|
1499
|
+
if (cls === "normal" && !autoApproveNormal) return SKIP_MESSAGE;
|
|
1500
|
+
const result = await executeBash(command);
|
|
1501
|
+
let output = "";
|
|
1502
|
+
if (result.stdout) output += result.stdout;
|
|
1503
|
+
if (result.stderr) output += result.stderr;
|
|
1504
|
+
if (result.exitCode !== 0) output += `
|
|
1505
|
+
[exit code: ${result.exitCode}]`;
|
|
1506
|
+
return output || "(no output)";
|
|
1507
|
+
}
|
|
1508
|
+
async function executeSubagentToolCall(name, args, autoApproveNormal) {
|
|
1509
|
+
if (name === "bash") {
|
|
1510
|
+
let parsed;
|
|
1511
|
+
try {
|
|
1512
|
+
parsed = JSON.parse(args);
|
|
1513
|
+
} catch {
|
|
1514
|
+
parsed = { command: "" };
|
|
1515
|
+
}
|
|
1516
|
+
return runNonInteractiveBash(parsed.command, autoApproveNormal);
|
|
1517
|
+
}
|
|
1518
|
+
if (name === "read") {
|
|
1519
|
+
return readFile2(JSON.parse(args));
|
|
1520
|
+
}
|
|
1521
|
+
if (name === "write") {
|
|
1522
|
+
return writeFile2(JSON.parse(args));
|
|
1523
|
+
}
|
|
1524
|
+
if (name === "edit") {
|
|
1525
|
+
return editFile(JSON.parse(args));
|
|
1526
|
+
}
|
|
1527
|
+
if (name === "glob") {
|
|
1528
|
+
return globFiles(JSON.parse(args));
|
|
1529
|
+
}
|
|
1530
|
+
if (name === "grep") {
|
|
1531
|
+
return grepFiles(JSON.parse(args));
|
|
1532
|
+
}
|
|
1533
|
+
if (name === "apply_patch") {
|
|
1534
|
+
return applyPatch(JSON.parse(args));
|
|
1535
|
+
}
|
|
1536
|
+
if (name === "todo") {
|
|
1537
|
+
return todo(JSON.parse(args));
|
|
1538
|
+
}
|
|
1539
|
+
return `Unknown tool: ${name}`;
|
|
1540
|
+
}
|
|
1541
|
+
async function runTaskTool(params, deps) {
|
|
1542
|
+
const maxTurns = Math.max(1, Math.min(32, params.max_turns ?? 8));
|
|
1543
|
+
const contextBlock = buildParentContext(deps.parentMessages, params.context);
|
|
1544
|
+
const originalCwd = process.cwd();
|
|
1545
|
+
const childCwd = params.cwd ?? originalCwd;
|
|
1546
|
+
const profile = resolveActiveProfile(deps.config);
|
|
1547
|
+
const llm = deps.llm ?? createProvider({
|
|
1548
|
+
...profile,
|
|
1549
|
+
model: params.model ?? profile.model
|
|
1550
|
+
});
|
|
1551
|
+
const messages = [{ role: "system", content: SUBAGENT_SYSTEM_PROMPT }];
|
|
1552
|
+
if (contextBlock) {
|
|
1553
|
+
messages.push({
|
|
1554
|
+
role: "user",
|
|
1555
|
+
content: "Parent session context for this delegated task:\n\n" + contextBlock
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
messages.push({ role: "user", content: params.prompt });
|
|
1559
|
+
let logger = null;
|
|
1560
|
+
let sessionMeta = null;
|
|
1561
|
+
if (deps.config.logDir) {
|
|
1562
|
+
logger = createLogger(deps.config.logDir, /* @__PURE__ */ new Date());
|
|
1563
|
+
sessionMeta = createSessionMetadata(
|
|
1564
|
+
logger.filePath,
|
|
1565
|
+
params.model ?? profile.model
|
|
1566
|
+
);
|
|
1567
|
+
sessionMeta.title = generateTitle(`[subagent] ${params.description}`);
|
|
1568
|
+
writeSessionMetadata(logger.filePath, sessionMeta);
|
|
1569
|
+
}
|
|
1570
|
+
for (const msg of messages) {
|
|
1571
|
+
appendLog(logger, {
|
|
1572
|
+
role: msg.role,
|
|
1573
|
+
content: "content" in msg ? msg.content : null
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
const autoApproveNormal = deps.autoApproveNormal ?? true;
|
|
1577
|
+
try {
|
|
1578
|
+
process.chdir(childCwd);
|
|
1579
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
1580
|
+
let assistantText = "";
|
|
1581
|
+
let assistantReasoning;
|
|
1582
|
+
const toolCalls = [];
|
|
1583
|
+
for await (const chunk of llm.stream(messages, SUBAGENT_TOOLS)) {
|
|
1584
|
+
if (chunk.text) assistantText += chunk.text;
|
|
1585
|
+
if (chunk.done) {
|
|
1586
|
+
if (chunk.toolCalls) toolCalls.push(...chunk.toolCalls);
|
|
1587
|
+
if (chunk.reasoning) assistantReasoning = chunk.reasoning;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (toolCalls.length === 0) {
|
|
1591
|
+
if (assistantText) {
|
|
1592
|
+
messages.push({
|
|
1593
|
+
role: "assistant",
|
|
1594
|
+
content: assistantText,
|
|
1595
|
+
...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
|
|
1596
|
+
});
|
|
1597
|
+
appendLog(logger, { role: "assistant", content: assistantText });
|
|
1598
|
+
}
|
|
1599
|
+
if (logger) {
|
|
1600
|
+
updateSessionMetadata(logger.filePath, {
|
|
1601
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1602
|
+
turnCount: turn + 1
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
const taskId = sessionMeta?.id ?? "ephemeral";
|
|
1606
|
+
return [
|
|
1607
|
+
`task_id: ${taskId}`,
|
|
1608
|
+
`description: ${params.description}`,
|
|
1609
|
+
`model: ${params.model ?? profile.model}`,
|
|
1610
|
+
logger ? `log_file: ${logger.filePath}` : void 0,
|
|
1611
|
+
"",
|
|
1612
|
+
"<task_result>",
|
|
1613
|
+
assistantText || "(no final text)",
|
|
1614
|
+
"</task_result>"
|
|
1615
|
+
].filter((line) => line !== void 0).join("\n");
|
|
1616
|
+
}
|
|
1617
|
+
const assistantMsg = {
|
|
1618
|
+
role: "assistant",
|
|
1619
|
+
content: assistantText || null,
|
|
1620
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
1621
|
+
id: tc.id,
|
|
1622
|
+
type: "function",
|
|
1623
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
1624
|
+
})),
|
|
1625
|
+
...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
|
|
1626
|
+
};
|
|
1627
|
+
messages.push(assistantMsg);
|
|
1628
|
+
appendLog(logger, {
|
|
1629
|
+
role: "assistant",
|
|
1630
|
+
content: assistantText || null,
|
|
1631
|
+
tool_calls: assistantMsg.tool_calls
|
|
1632
|
+
});
|
|
1633
|
+
for (const tc of toolCalls) {
|
|
1634
|
+
const toolResult = await executeSubagentToolCall(tc.name, tc.arguments, autoApproveNormal);
|
|
1635
|
+
const finalResult = toolResult || SKIP_MESSAGE;
|
|
1636
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: finalResult });
|
|
1637
|
+
appendLog(logger, {
|
|
1638
|
+
role: "tool",
|
|
1639
|
+
content: finalResult,
|
|
1640
|
+
tool_call_id: tc.id
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
if (logger) {
|
|
1644
|
+
updateSessionMetadata(logger.filePath, {
|
|
1645
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1646
|
+
turnCount: turn + 1
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
return [
|
|
1651
|
+
`description: ${params.description}`,
|
|
1652
|
+
`model: ${params.model ?? profile.model}`,
|
|
1653
|
+
logger ? `log_file: ${logger.filePath}` : void 0,
|
|
1654
|
+
"",
|
|
1655
|
+
"<task_result>",
|
|
1656
|
+
`Subagent stopped after reaching max_turns=${maxTurns} without producing a final answer.`,
|
|
1657
|
+
"</task_result>"
|
|
1658
|
+
].filter((line) => line !== void 0).join("\n");
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1661
|
+
return [
|
|
1662
|
+
`description: ${params.description}`,
|
|
1663
|
+
logger ? `log_file: ${logger.filePath}` : void 0,
|
|
1664
|
+
"",
|
|
1665
|
+
"<task_result>",
|
|
1666
|
+
`Subagent failed: ${msg}`,
|
|
1667
|
+
"</task_result>"
|
|
1668
|
+
].filter((line) => line !== void 0).join("\n");
|
|
1669
|
+
} finally {
|
|
1670
|
+
process.chdir(originalCwd);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/repl.ts
|
|
1675
|
+
var SKIP_MESSAGE2 = "Command skipped by user.";
|
|
1676
|
+
var BASH_TOOL2 = {
|
|
1677
|
+
type: "function",
|
|
1678
|
+
function: {
|
|
1679
|
+
name: "bash",
|
|
1680
|
+
description: "Execute a shell command and return its output.",
|
|
1681
|
+
parameters: {
|
|
1682
|
+
type: "object",
|
|
1683
|
+
properties: {
|
|
1684
|
+
command: { type: "string", description: "The shell command to run." }
|
|
1685
|
+
},
|
|
1686
|
+
required: ["command"]
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1224
1690
|
async function handleBashTool(command, deps) {
|
|
1225
1691
|
const { confirm, print, dangerousPatterns, autoApproveNormal } = deps;
|
|
1226
1692
|
const cls = classifyCommand(command, dangerousPatterns);
|
|
@@ -1228,16 +1694,16 @@ async function handleBashTool(command, deps) {
|
|
|
1228
1694
|
if (!autoApproveNormal) {
|
|
1229
1695
|
const ok = await confirm(`Execute command: ${command}
|
|
1230
1696
|
Proceed? (y/n) `);
|
|
1231
|
-
if (!ok) return
|
|
1697
|
+
if (!ok) return SKIP_MESSAGE2;
|
|
1232
1698
|
}
|
|
1233
1699
|
} else if (cls === "danger") {
|
|
1234
1700
|
print(`\u26A0\uFE0F DANGEROUS COMMAND: ${command}`);
|
|
1235
1701
|
const first = await confirm("Are you sure? (y/n) ");
|
|
1236
|
-
if (!first) return
|
|
1702
|
+
if (!first) return SKIP_MESSAGE2;
|
|
1237
1703
|
const second = await confirm(
|
|
1238
1704
|
"Confirm again \u2014 this is destructive. Continue? (y/n) "
|
|
1239
1705
|
);
|
|
1240
|
-
if (!second) return
|
|
1706
|
+
if (!second) return SKIP_MESSAGE2;
|
|
1241
1707
|
}
|
|
1242
1708
|
const result = await deps.executeBash(command);
|
|
1243
1709
|
let output = "";
|
|
@@ -1248,35 +1714,6 @@ Proceed? (y/n) `);
|
|
|
1248
1714
|
return output || "(no output)";
|
|
1249
1715
|
}
|
|
1250
1716
|
|
|
1251
|
-
// src/logger.ts
|
|
1252
|
-
import * as fs7 from "fs";
|
|
1253
|
-
import * as path5 from "path";
|
|
1254
|
-
function createLogger(logDir, sessionStart) {
|
|
1255
|
-
fs7.mkdirSync(logDir, { recursive: true });
|
|
1256
|
-
const filename = sessionStart.toISOString().replace(/:/g, "-").replace(/\..+/, "") + ".jsonl";
|
|
1257
|
-
const filePath = path5.join(logDir, filename);
|
|
1258
|
-
return {
|
|
1259
|
-
filePath,
|
|
1260
|
-
/**
|
|
1261
|
-
* 将单条日志条目序列化为 JSON 并同步追加到文件(末尾加换行符)。
|
|
1262
|
-
*
|
|
1263
|
-
* 使用 appendFileSync 而非 appendFile(异步版本)的原因:
|
|
1264
|
-
* 进程崩溃或 Ctrl-C 退出时,异步写操作可能尚未完成,导致最后几条记录丢失。
|
|
1265
|
-
* 同步写入虽然阻塞事件循环,但日志条目通常很小(< 1KB),延迟可忽略。
|
|
1266
|
-
*
|
|
1267
|
-
* 写入失败时输出到 stderr 而非抛出异常,防止日志错误中断正常业务流程。
|
|
1268
|
-
*/
|
|
1269
|
-
append(entry) {
|
|
1270
|
-
try {
|
|
1271
|
-
fs7.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
1272
|
-
} catch (err) {
|
|
1273
|
-
process.stderr.write(`[logger] Failed to write log entry: ${err}
|
|
1274
|
-
`);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
1717
|
// src/skills/resolver.ts
|
|
1281
1718
|
function isSkillCommand(input) {
|
|
1282
1719
|
return input.length > 1 && input.startsWith("/");
|
|
@@ -1312,12 +1749,12 @@ ${args}` : skill.body;
|
|
|
1312
1749
|
|
|
1313
1750
|
// src/skills/executor.ts
|
|
1314
1751
|
import { exec as exec2 } from "child_process";
|
|
1315
|
-
import { dirname as
|
|
1752
|
+
import { dirname as dirname5 } from "path";
|
|
1316
1753
|
import { promisify } from "util";
|
|
1317
1754
|
|
|
1318
1755
|
// src/skills/loader.ts
|
|
1319
1756
|
import { readFile as readFile6, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
1320
|
-
import { join as
|
|
1757
|
+
import { join as join6, dirname as dirname4, basename as basename2, resolve as resolve3, sep as sep3 } from "path";
|
|
1321
1758
|
function parseFrontmatter(content) {
|
|
1322
1759
|
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1323
1760
|
const match = FRONTMATTER_RE.exec(content);
|
|
@@ -1357,7 +1794,7 @@ async function fileExists(filePath) {
|
|
|
1357
1794
|
}
|
|
1358
1795
|
}
|
|
1359
1796
|
async function loadTools(skillDir) {
|
|
1360
|
-
const toolsJsonPath =
|
|
1797
|
+
const toolsJsonPath = join6(skillDir, "tools.json");
|
|
1361
1798
|
if (!await fileExists(toolsJsonPath)) return [];
|
|
1362
1799
|
let raw;
|
|
1363
1800
|
try {
|
|
@@ -1380,7 +1817,7 @@ async function loadTools(skillDir) {
|
|
|
1380
1817
|
name: entry["name"],
|
|
1381
1818
|
description: entry["description"],
|
|
1382
1819
|
parameters: entry["parameters"] ?? {},
|
|
1383
|
-
scriptPath:
|
|
1820
|
+
scriptPath: join6(skillDir, `${entry["name"]}.sh`)
|
|
1384
1821
|
});
|
|
1385
1822
|
}
|
|
1386
1823
|
}
|
|
@@ -1389,12 +1826,12 @@ async function loadTools(skillDir) {
|
|
|
1389
1826
|
async function loadSkillFile(skillMdPath) {
|
|
1390
1827
|
const content = await readFile6(skillMdPath, "utf-8");
|
|
1391
1828
|
const { data, body } = parseFrontmatter(content);
|
|
1392
|
-
const skillDir =
|
|
1393
|
-
const dirName =
|
|
1829
|
+
const skillDir = dirname4(skillMdPath);
|
|
1830
|
+
const dirName = basename2(skillDir);
|
|
1394
1831
|
const [tools, hasPreScript, hasPostScript] = await Promise.all([
|
|
1395
1832
|
loadTools(skillDir),
|
|
1396
|
-
fileExists(
|
|
1397
|
-
fileExists(
|
|
1833
|
+
fileExists(join6(skillDir, "pre.sh")),
|
|
1834
|
+
fileExists(join6(skillDir, "post.sh"))
|
|
1398
1835
|
]);
|
|
1399
1836
|
return {
|
|
1400
1837
|
name: data["name"] ?? dirName,
|
|
@@ -1402,8 +1839,8 @@ async function loadSkillFile(skillMdPath) {
|
|
|
1402
1839
|
body,
|
|
1403
1840
|
source: skillMdPath,
|
|
1404
1841
|
tools,
|
|
1405
|
-
preScript: hasPreScript ?
|
|
1406
|
-
postScript: hasPostScript ?
|
|
1842
|
+
preScript: hasPreScript ? join6(skillDir, "pre.sh") : null,
|
|
1843
|
+
postScript: hasPostScript ? join6(skillDir, "post.sh") : null
|
|
1407
1844
|
};
|
|
1408
1845
|
}
|
|
1409
1846
|
async function loadSkillsFromDir(dir) {
|
|
@@ -1415,7 +1852,7 @@ async function loadSkillsFromDir(dir) {
|
|
|
1415
1852
|
}
|
|
1416
1853
|
const skills = [];
|
|
1417
1854
|
for (const entry of entries) {
|
|
1418
|
-
const entryPath =
|
|
1855
|
+
const entryPath = join6(dir, entry);
|
|
1419
1856
|
let entryStat;
|
|
1420
1857
|
try {
|
|
1421
1858
|
entryStat = await stat3(entryPath);
|
|
@@ -1423,7 +1860,7 @@ async function loadSkillsFromDir(dir) {
|
|
|
1423
1860
|
continue;
|
|
1424
1861
|
}
|
|
1425
1862
|
if (!entryStat.isDirectory()) continue;
|
|
1426
|
-
const skillMdPath =
|
|
1863
|
+
const skillMdPath = join6(entryPath, "SKILL.md");
|
|
1427
1864
|
try {
|
|
1428
1865
|
await stat3(skillMdPath);
|
|
1429
1866
|
} catch {
|
|
@@ -1453,7 +1890,7 @@ async function executePreScript(skill, trustedDirs) {
|
|
|
1453
1890
|
if (!skill.preScript) {
|
|
1454
1891
|
throw new Error("No pre script configured for this skill");
|
|
1455
1892
|
}
|
|
1456
|
-
const skillDir =
|
|
1893
|
+
const skillDir = dirname5(skill.source);
|
|
1457
1894
|
if (!isTrustedSkillPath(skillDir, trustedDirs)) {
|
|
1458
1895
|
throw new SecurityError(
|
|
1459
1896
|
`Untrusted skill path: ${skillDir} is not in trusted dirs`
|
|
@@ -1462,7 +1899,7 @@ async function executePreScript(skill, trustedDirs) {
|
|
|
1462
1899
|
return runScript(skill.preScript, []);
|
|
1463
1900
|
}
|
|
1464
1901
|
async function executeSkillTool(tool, args, trustedDirs) {
|
|
1465
|
-
const scriptDir =
|
|
1902
|
+
const scriptDir = dirname5(tool.scriptPath);
|
|
1466
1903
|
if (!isTrustedSkillPath(scriptDir, trustedDirs)) {
|
|
1467
1904
|
throw new SecurityError(
|
|
1468
1905
|
`Untrusted tool script path: ${tool.scriptPath}`
|
|
@@ -2008,8 +2445,8 @@ function dismiss(state) {
|
|
|
2008
2445
|
}
|
|
2009
2446
|
|
|
2010
2447
|
// src/ui/fileCompletion.ts
|
|
2011
|
-
import * as
|
|
2012
|
-
import * as
|
|
2448
|
+
import * as fs9 from "fs/promises";
|
|
2449
|
+
import * as path7 from "path";
|
|
2013
2450
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
2014
2451
|
function isHidden(name) {
|
|
2015
2452
|
return name.startsWith(".");
|
|
@@ -2018,24 +2455,24 @@ async function walkDir2(dir, root, results, maxResults) {
|
|
|
2018
2455
|
if (results.length >= maxResults) return;
|
|
2019
2456
|
let entries;
|
|
2020
2457
|
try {
|
|
2021
|
-
entries = await
|
|
2458
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
2022
2459
|
} catch {
|
|
2023
2460
|
return;
|
|
2024
2461
|
}
|
|
2025
2462
|
for (const entry of entries) {
|
|
2026
2463
|
if (results.length >= maxResults) return;
|
|
2027
2464
|
if (SKIP_DIRS.has(entry.name) || isHidden(entry.name)) continue;
|
|
2028
|
-
const relPath =
|
|
2465
|
+
const relPath = path7.relative(root, path7.join(dir, entry.name));
|
|
2029
2466
|
if (entry.isDirectory()) {
|
|
2030
2467
|
results.push({ path: relPath, isDir: true });
|
|
2031
|
-
await walkDir2(
|
|
2468
|
+
await walkDir2(path7.join(dir, entry.name), root, results, maxResults);
|
|
2032
2469
|
} else {
|
|
2033
2470
|
results.push({ path: relPath, isDir: false });
|
|
2034
2471
|
}
|
|
2035
2472
|
}
|
|
2036
2473
|
}
|
|
2037
2474
|
async function listFilesForQuery(query, cwd, maxResults = 50) {
|
|
2038
|
-
if (
|
|
2475
|
+
if (path7.isAbsolute(query)) {
|
|
2039
2476
|
return listAbsolute(query, maxResults);
|
|
2040
2477
|
}
|
|
2041
2478
|
const all = [];
|
|
@@ -2047,18 +2484,18 @@ async function listAbsolute(query, maxResults) {
|
|
|
2047
2484
|
let dir = query;
|
|
2048
2485
|
let filter = "";
|
|
2049
2486
|
try {
|
|
2050
|
-
const stat5 = await
|
|
2487
|
+
const stat5 = await fs9.stat(dir);
|
|
2051
2488
|
if (!stat5.isDirectory()) {
|
|
2052
|
-
filter =
|
|
2053
|
-
dir =
|
|
2489
|
+
filter = path7.basename(dir);
|
|
2490
|
+
dir = path7.dirname(dir);
|
|
2054
2491
|
}
|
|
2055
2492
|
} catch {
|
|
2056
|
-
filter =
|
|
2057
|
-
dir =
|
|
2493
|
+
filter = path7.basename(dir);
|
|
2494
|
+
dir = path7.dirname(dir);
|
|
2058
2495
|
}
|
|
2059
2496
|
let entries;
|
|
2060
2497
|
try {
|
|
2061
|
-
entries = await
|
|
2498
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
2062
2499
|
} catch {
|
|
2063
2500
|
return [];
|
|
2064
2501
|
}
|
|
@@ -2067,7 +2504,7 @@ async function listAbsolute(query, maxResults) {
|
|
|
2067
2504
|
if (SKIP_DIRS.has(entry.name) || isHidden(entry.name)) continue;
|
|
2068
2505
|
if (filter && !entry.name.includes(filter)) continue;
|
|
2069
2506
|
results.push({
|
|
2070
|
-
path:
|
|
2507
|
+
path: path7.join(dir, entry.name),
|
|
2071
2508
|
isDir: entry.isDirectory()
|
|
2072
2509
|
});
|
|
2073
2510
|
if (results.length >= maxResults) break;
|
|
@@ -2089,10 +2526,10 @@ async function expandFileRefs(text, cwd) {
|
|
|
2089
2526
|
atPattern.lastIndex = 0;
|
|
2090
2527
|
while ((match = atPattern.exec(text)) !== null) {
|
|
2091
2528
|
const filePath = match[1];
|
|
2092
|
-
const fullPath =
|
|
2529
|
+
const fullPath = path7.isAbsolute(filePath) ? filePath : path7.join(cwd, filePath);
|
|
2093
2530
|
let replacement;
|
|
2094
2531
|
try {
|
|
2095
|
-
const content = await
|
|
2532
|
+
const content = await fs9.readFile(fullPath, "utf8");
|
|
2096
2533
|
replacement = `\`\`\`
|
|
2097
2534
|
// @${filePath}
|
|
2098
2535
|
${content}
|
|
@@ -2174,9 +2611,9 @@ import { join as join11 } from "path";
|
|
|
2174
2611
|
|
|
2175
2612
|
// src/automation/store.ts
|
|
2176
2613
|
import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
|
|
2177
|
-
import { join as
|
|
2614
|
+
import { join as join8 } from "path";
|
|
2178
2615
|
function jobsFilePath(dataDir) {
|
|
2179
|
-
return
|
|
2616
|
+
return join8(dataDir, "jobs.json");
|
|
2180
2617
|
}
|
|
2181
2618
|
async function loadJobs(dataDir) {
|
|
2182
2619
|
try {
|
|
@@ -2211,13 +2648,13 @@ async function removeJob(dataDir, id) {
|
|
|
2211
2648
|
}
|
|
2212
2649
|
|
|
2213
2650
|
// src/automation/runtime.ts
|
|
2214
|
-
import { randomUUID } from "crypto";
|
|
2651
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2215
2652
|
|
|
2216
2653
|
// src/automation/log.ts
|
|
2217
2654
|
import { readFile as readFile9, appendFile, mkdir as mkdir3 } from "fs/promises";
|
|
2218
|
-
import { join as
|
|
2655
|
+
import { join as join9 } from "path";
|
|
2219
2656
|
function logFilePath(logDir) {
|
|
2220
|
-
return
|
|
2657
|
+
return join9(logDir, "automation-runs.jsonl");
|
|
2221
2658
|
}
|
|
2222
2659
|
async function appendRunLog(logDir, entry) {
|
|
2223
2660
|
await mkdir3(logDir, { recursive: true });
|
|
@@ -2230,7 +2667,7 @@ async function executeJob(job, config2) {
|
|
|
2230
2667
|
return null;
|
|
2231
2668
|
}
|
|
2232
2669
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2233
|
-
const runId =
|
|
2670
|
+
const runId = randomUUID2();
|
|
2234
2671
|
let summaryText;
|
|
2235
2672
|
let errorMsg;
|
|
2236
2673
|
let result;
|
|
@@ -2437,7 +2874,7 @@ var LoopScheduler = class {
|
|
|
2437
2874
|
};
|
|
2438
2875
|
|
|
2439
2876
|
// src/automation/loop/command.ts
|
|
2440
|
-
import { randomUUID as
|
|
2877
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2441
2878
|
|
|
2442
2879
|
// src/automation/loop/parse.ts
|
|
2443
2880
|
var DEFAULT_INTERVAL_MS = 6e5;
|
|
@@ -2528,7 +2965,7 @@ async function cmdLoop(args, deps) {
|
|
|
2528
2965
|
}
|
|
2529
2966
|
const now = /* @__PURE__ */ new Date();
|
|
2530
2967
|
const job = {
|
|
2531
|
-
id:
|
|
2968
|
+
id: randomUUID3(),
|
|
2532
2969
|
kind: "loop",
|
|
2533
2970
|
title: prompt.slice(0, 60),
|
|
2534
2971
|
createdAt: now.toISOString(),
|
|
@@ -2572,7 +3009,7 @@ async function cmdUnloop(idOrPrefix, deps) {
|
|
|
2572
3009
|
}
|
|
2573
3010
|
|
|
2574
3011
|
// src/automation/goal/command.ts
|
|
2575
|
-
import { randomUUID as
|
|
3012
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2576
3013
|
async function cmdGoal(args, deps) {
|
|
2577
3014
|
const condition = args.trim();
|
|
2578
3015
|
if (!condition) {
|
|
@@ -2580,7 +3017,7 @@ async function cmdGoal(args, deps) {
|
|
|
2580
3017
|
}
|
|
2581
3018
|
const now = /* @__PURE__ */ new Date();
|
|
2582
3019
|
const job = {
|
|
2583
|
-
id:
|
|
3020
|
+
id: randomUUID4(),
|
|
2584
3021
|
kind: "goal",
|
|
2585
3022
|
title: condition.slice(0, 60),
|
|
2586
3023
|
createdAt: now.toISOString(),
|
|
@@ -2832,84 +3269,6 @@ var AutomationManager = class {
|
|
|
2832
3269
|
}
|
|
2833
3270
|
};
|
|
2834
3271
|
|
|
2835
|
-
// src/sessions/metadata.ts
|
|
2836
|
-
import * as crypto from "crypto";
|
|
2837
|
-
import * as fs9 from "fs";
|
|
2838
|
-
import * as path7 from "path";
|
|
2839
|
-
function metadataPathFromLogFile(logFilePath2) {
|
|
2840
|
-
const base = path7.basename(logFilePath2, ".jsonl");
|
|
2841
|
-
const dir = path7.dirname(logFilePath2);
|
|
2842
|
-
return path7.join(dir, `${base}-session.json`);
|
|
2843
|
-
}
|
|
2844
|
-
function createSessionMetadata(logFilePath2, model) {
|
|
2845
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2846
|
-
return {
|
|
2847
|
-
id: crypto.randomUUID(),
|
|
2848
|
-
startTime: now,
|
|
2849
|
-
lastActivity: now,
|
|
2850
|
-
cwd: process.cwd(),
|
|
2851
|
-
model,
|
|
2852
|
-
title: "",
|
|
2853
|
-
turnCount: 0,
|
|
2854
|
-
totalTokens: 0,
|
|
2855
|
-
logFile: path7.basename(logFilePath2)
|
|
2856
|
-
};
|
|
2857
|
-
}
|
|
2858
|
-
function writeSessionMetadata(logFilePath2, metadata) {
|
|
2859
|
-
const metaPath = metadataPathFromLogFile(logFilePath2);
|
|
2860
|
-
try {
|
|
2861
|
-
fs9.writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + "\n");
|
|
2862
|
-
} catch (err) {
|
|
2863
|
-
process.stderr.write(`[sessions] Failed to write metadata: ${err}
|
|
2864
|
-
`);
|
|
2865
|
-
}
|
|
2866
|
-
}
|
|
2867
|
-
function readSessionMetadata(metaFilePath) {
|
|
2868
|
-
try {
|
|
2869
|
-
const raw = fs9.readFileSync(metaFilePath, "utf-8");
|
|
2870
|
-
return JSON.parse(raw);
|
|
2871
|
-
} catch {
|
|
2872
|
-
return null;
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
function updateSessionMetadata(logFilePath2, partial) {
|
|
2876
|
-
const metaPath = metadataPathFromLogFile(logFilePath2);
|
|
2877
|
-
let existing = null;
|
|
2878
|
-
try {
|
|
2879
|
-
const raw = fs9.readFileSync(metaPath, "utf-8");
|
|
2880
|
-
existing = JSON.parse(raw);
|
|
2881
|
-
} catch {
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
writeSessionMetadata(logFilePath2, { ...existing, ...partial });
|
|
2885
|
-
}
|
|
2886
|
-
function listSessions(logDir) {
|
|
2887
|
-
try {
|
|
2888
|
-
const files = fs9.readdirSync(logDir);
|
|
2889
|
-
const metaFiles = files.filter((f) => f.endsWith("-session.json"));
|
|
2890
|
-
const sessions = [];
|
|
2891
|
-
for (const file of metaFiles) {
|
|
2892
|
-
const meta = readSessionMetadata(path7.join(logDir, file));
|
|
2893
|
-
if (meta) sessions.push(meta);
|
|
2894
|
-
}
|
|
2895
|
-
return sessions.sort(
|
|
2896
|
-
(a, b) => b.lastActivity.localeCompare(a.lastActivity)
|
|
2897
|
-
);
|
|
2898
|
-
} catch {
|
|
2899
|
-
return [];
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
function findSession(logDir, idOrPrefix) {
|
|
2903
|
-
const sessions = listSessions(logDir);
|
|
2904
|
-
return sessions.find(
|
|
2905
|
-
(s) => s.id === idOrPrefix || s.id.startsWith(idOrPrefix)
|
|
2906
|
-
) ?? null;
|
|
2907
|
-
}
|
|
2908
|
-
function generateTitle(firstUserMessage) {
|
|
2909
|
-
const oneLine = firstUserMessage.replace(/\n+/g, " ").trim();
|
|
2910
|
-
return oneLine.length > 50 ? oneLine.slice(0, 47) + "..." : oneLine;
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
3272
|
// src/meta_skill/index.ts
|
|
2914
3273
|
import * as fs11 from "fs";
|
|
2915
3274
|
import * as path9 from "path";
|
|
@@ -4093,7 +4452,7 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
4093
4452
|
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
4094
4453
|
}));
|
|
4095
4454
|
try {
|
|
4096
|
-
for await (const chunk of llmRef.current.stream(currentMessages, [
|
|
4455
|
+
for await (const chunk of llmRef.current.stream(currentMessages, [BASH_TOOL2, READ_TOOL, WRITE_TOOL, EDIT_TOOL, GLOB_TOOL, GREP_TOOL, APPLY_PATCH_TOOL, TODO_TOOL, TASK_TOOL, ...dynamicTools], abortController.signal)) {
|
|
4097
4456
|
if (chunk.text) {
|
|
4098
4457
|
assistantText += chunk.text;
|
|
4099
4458
|
setMessages((prev) => {
|
|
@@ -4194,6 +4553,13 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
4194
4553
|
} else if (tc.name === "todo") {
|
|
4195
4554
|
const args = JSON.parse(tc.arguments);
|
|
4196
4555
|
toolResult = todo(args);
|
|
4556
|
+
} else if (tc.name === "task") {
|
|
4557
|
+
const args = JSON.parse(tc.arguments);
|
|
4558
|
+
toolResult = await runTaskTool(args, {
|
|
4559
|
+
config: config2,
|
|
4560
|
+
parentMessages: currentMessages,
|
|
4561
|
+
autoApproveNormal: autoMode2
|
|
4562
|
+
});
|
|
4197
4563
|
} else {
|
|
4198
4564
|
const skillTool = skillToolsRef.current.find((t) => t.name === tc.name);
|
|
4199
4565
|
if (skillTool) {
|