@lmzhen/dsh-evolution-commands 0.22.0 → 0.23.0
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/lib/index.js +296 -198
- package/lib/types/index.d.ts +2 -42
- package/lib/types/parse.d.ts +65 -0
- package/lib/types/registry.d.ts +8 -0
- package/package.json +7 -6
package/lib/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
|
|
4
4
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
|
-
import { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, assertSkillsRootAliasRetired, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, composePresetEntry, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, isDeprecatedParamId,
|
|
5
|
+
import { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, asRecord, assertSkillsRootAliasRetired, atomicWriteFiles, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, composePresetEntry, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, isDeprecatedParamId, isPresent, isRecord, isUnknown, liveBool, mergePresetRow, newRunRegistry, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeText, reportsSweepLockTarget, resolveBasePresetPatch, resolveParamId, resolveRootConfig, scopedProbeReport, sweepReports, transactIo } from "@lmzhen/dsh-evolution-core";
|
|
6
6
|
import { buildEnrichment, buildMaintainFacts, enrichmentSnapshotOptions, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
|
|
7
|
-
import {
|
|
7
|
+
import { preferProvider } from "@lmzhen/dsh-evolution-io";
|
|
8
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
9
|
import { dirname, join } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
//#region lib/types/migration.js
|
|
@@ -194,7 +195,7 @@ async function objectOf(read) {
|
|
|
194
195
|
if (read === void 0) return void 0;
|
|
195
196
|
try {
|
|
196
197
|
const value = await read();
|
|
197
|
-
return
|
|
198
|
+
return isRecord(value) ? value : void 0;
|
|
198
199
|
} catch {
|
|
199
200
|
return;
|
|
200
201
|
}
|
|
@@ -579,7 +580,7 @@ function liveRowTails(plugins) {
|
|
|
579
580
|
* @returns the mapping, or undefined for anything else.
|
|
580
581
|
*/
|
|
581
582
|
function asMapping(value) {
|
|
582
|
-
return
|
|
583
|
+
return asRecord(value);
|
|
583
584
|
}
|
|
584
585
|
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
585
586
|
*
|
|
@@ -1288,9 +1289,24 @@ const SOURCE_LABELS = Object.freeze({
|
|
|
1288
1289
|
});
|
|
1289
1290
|
/** What the E-tiers mean, so the count line reads without the docs. */
|
|
1290
1291
|
const TIER_LEGEND = "档位:E0 固定/E1 只读/E2 安装时定/E3 你可改/E4 装完固化。";
|
|
1292
|
+
/**
|
|
1293
|
+
* The source column's word for one row (F-1).
|
|
1294
|
+
*
|
|
1295
|
+
* `unregistered` covers TWO different facts: the owner publishes no settings namespace (the value
|
|
1296
|
+
* really cannot be written — E-315/E-316) and the settings service itself is missing (then whether
|
|
1297
|
+
* the value could be written is UNKNOWN). Without a provider the column must not claim "不可改", or it
|
|
1298
|
+
* contradicts the legend and the footer of the very same output.
|
|
1299
|
+
* @param row - the row's surface facts.
|
|
1300
|
+
* @param providerMounted - whether a settings provider answered.
|
|
1301
|
+
* @returns the label for the source column.
|
|
1302
|
+
*/
|
|
1303
|
+
function sourceLabel(row, providerMounted) {
|
|
1304
|
+
const label = SOURCE_LABELS[row.source] ?? row.source;
|
|
1305
|
+
return providerMounted || row.source !== "unregistered" ? label : "未知";
|
|
1306
|
+
}
|
|
1291
1307
|
function renderParamRows(rows, options) {
|
|
1292
1308
|
const header = pad("参数", 36) + pad("分组", 12) + pad("档位", 6) + pad("生效", 8) + pad("来源", 10) + "当前值";
|
|
1293
|
-
const body = rows.map((row) => pad(row.id, 36) + pad(PARAM_GROUP_LABELS[row.group], 12) + pad(row.tier, 6) + pad(APPLIES_LABELS[row.applies] ?? row.applies, 8) + pad(
|
|
1309
|
+
const body = rows.map((row) => pad(row.id, 36) + pad(PARAM_GROUP_LABELS[row.group], 12) + pad(row.tier, 6) + pad(APPLIES_LABELS[row.applies] ?? row.applies, 8) + pad(sourceLabel(row, options.providerMounted), 10) + renderValue(row.value));
|
|
1294
1310
|
const tiers = [
|
|
1295
1311
|
"E0",
|
|
1296
1312
|
"E1",
|
|
@@ -1347,116 +1363,160 @@ function renderParamJson(rows) {
|
|
|
1347
1363
|
}
|
|
1348
1364
|
//#endregion
|
|
1349
1365
|
//#region lib/types/registry.js
|
|
1366
|
+
/** The flags the TABLE declares for one subcommand (name → `--flag`), DERIVED from its usage. */
|
|
1367
|
+
function flagsOf(name) {
|
|
1368
|
+
const entry = COMMAND_ENTRIES.find((candidate) => entryName(candidate) === name);
|
|
1369
|
+
return entry === void 0 ? [] : entryFlags(entry);
|
|
1370
|
+
}
|
|
1371
|
+
/** C7 shape, DERIVED: the subcommand name is the first token of `usage` (variants use `|`). */
|
|
1372
|
+
function entryName(entry) {
|
|
1373
|
+
const [head = ""] = entry.usage.split(" ");
|
|
1374
|
+
return head.split("|")[0] ?? "";
|
|
1375
|
+
}
|
|
1376
|
+
/** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
|
|
1377
|
+
function entryFlags(entry) {
|
|
1378
|
+
const found = [];
|
|
1379
|
+
for (const match of entry.usage.matchAll(/--[a-z][a-z-]*/g)) if (!found.includes(match[0])) found.push(match[0]);
|
|
1380
|
+
return found;
|
|
1381
|
+
}
|
|
1350
1382
|
const COMMAND_ENTRIES = [
|
|
1351
1383
|
{
|
|
1384
|
+
readonly: true,
|
|
1352
1385
|
usage: "pending [--detail]",
|
|
1353
1386
|
summary: "列出暂存的写入(--detail 显示参数)"
|
|
1354
1387
|
},
|
|
1355
1388
|
{
|
|
1389
|
+
readonly: false,
|
|
1356
1390
|
usage: "approve <id>",
|
|
1357
1391
|
summary: "执行一条已批准的写入"
|
|
1358
1392
|
},
|
|
1359
1393
|
{
|
|
1394
|
+
readonly: false,
|
|
1360
1395
|
usage: "reject <id>",
|
|
1361
1396
|
summary: "丢弃一条暂存写入,不执行"
|
|
1362
1397
|
},
|
|
1363
1398
|
{
|
|
1399
|
+
readonly: false,
|
|
1364
1400
|
usage: "release <id>",
|
|
1365
1401
|
summary: "把卡在执行中的写入放回待批窗口(先确认效果再批准 or reject)"
|
|
1366
1402
|
},
|
|
1367
1403
|
{
|
|
1404
|
+
readonly: true,
|
|
1368
1405
|
usage: "doctor [--json]",
|
|
1369
1406
|
summary: "只读自检:安装形态、冲突、环境、服务(--json 供脚本用)"
|
|
1370
1407
|
},
|
|
1371
1408
|
{
|
|
1409
|
+
readonly: false,
|
|
1372
1410
|
usage: "curator run|pause|resume|status|report|scope",
|
|
1373
1411
|
summary: "立即运行一次自动整理,或暂停/恢复/查看状态"
|
|
1374
1412
|
},
|
|
1375
1413
|
{
|
|
1414
|
+
readonly: true,
|
|
1376
1415
|
usage: "mutations",
|
|
1377
1416
|
summary: "列出技能改动记录"
|
|
1378
1417
|
},
|
|
1379
1418
|
{
|
|
1419
|
+
readonly: false,
|
|
1380
1420
|
usage: "restore",
|
|
1381
1421
|
summary: "从最近的快照恢复技能"
|
|
1382
1422
|
},
|
|
1383
1423
|
{
|
|
1424
|
+
readonly: false,
|
|
1384
1425
|
usage: "consolidate <target> <sources...> [--plan <runId>]",
|
|
1385
1426
|
summary: "把若干技能合并进一个总括技能"
|
|
1386
1427
|
},
|
|
1387
1428
|
{
|
|
1429
|
+
readonly: false,
|
|
1388
1430
|
usage: "skill restore <name>",
|
|
1389
1431
|
summary: "按名字恢复一个已归档的技能"
|
|
1390
1432
|
},
|
|
1391
1433
|
{
|
|
1434
|
+
readonly: true,
|
|
1392
1435
|
usage: "skill history <name>",
|
|
1393
1436
|
summary: "列出某个技能的内容版本(含时间、动作、字符数、哈希前缀)"
|
|
1394
1437
|
},
|
|
1395
1438
|
{
|
|
1439
|
+
readonly: false,
|
|
1396
1440
|
usage: "skill undo <name> [--to v<N>]",
|
|
1397
1441
|
summary: "把某个技能的内容回退到某一版(默认上一版;只动内容,撤销本身也记为一版)"
|
|
1398
1442
|
},
|
|
1399
1443
|
{
|
|
1444
|
+
readonly: false,
|
|
1400
1445
|
usage: "skill pin <name>",
|
|
1401
1446
|
summary: "钉住一个技能:免于删除、后台审查与生命周期整理(立即生效、随时可解钉;与 skill_manage 的 pin 动作同一语义,不走审批)"
|
|
1402
1447
|
},
|
|
1403
1448
|
{
|
|
1449
|
+
readonly: false,
|
|
1404
1450
|
usage: "skill unpin <name>",
|
|
1405
1451
|
summary: "解钉一个技能:撤销上面的冻结(立即生效、不走审批)"
|
|
1406
1452
|
},
|
|
1407
1453
|
{
|
|
1454
|
+
readonly: true,
|
|
1408
1455
|
usage: "skills health",
|
|
1409
1456
|
summary: "技能库结构体检"
|
|
1410
1457
|
},
|
|
1411
1458
|
{
|
|
1459
|
+
readonly: false,
|
|
1412
1460
|
usage: "skills refresh",
|
|
1413
1461
|
summary: "丢弃目录缓存并重新读取技能树"
|
|
1414
1462
|
},
|
|
1415
1463
|
{
|
|
1464
|
+
readonly: false,
|
|
1416
1465
|
usage: "learn [request]",
|
|
1417
1466
|
summary: "向本会话发一条学习请求"
|
|
1418
1467
|
},
|
|
1419
1468
|
{
|
|
1469
|
+
readonly: false,
|
|
1420
1470
|
usage: "maintain [--timeout=<ms> | --facts]",
|
|
1421
1471
|
summary: "起一次维护扫描并立即回 run id(后台跑;--facts 为 0 token 预览)"
|
|
1422
1472
|
},
|
|
1423
1473
|
{
|
|
1474
|
+
readonly: true,
|
|
1424
1475
|
usage: "maintain status [<id>]",
|
|
1425
1476
|
summary: "看维护扫描:在飞的那次(含已跑多久)或某个 run 的终态"
|
|
1426
1477
|
},
|
|
1427
1478
|
{
|
|
1479
|
+
readonly: true,
|
|
1428
1480
|
usage: "maintain report [<id>]",
|
|
1429
1481
|
summary: "读某个 run 的结果正文(省略 id 取本 home 最新一条;没有结果就明说,不装成空结果)"
|
|
1430
1482
|
},
|
|
1431
1483
|
{
|
|
1484
|
+
readonly: false,
|
|
1432
1485
|
usage: "maintain handoff [<id>]",
|
|
1433
1486
|
summary: "把某次维护报告交给模型:注入指针与汇报协议,模型只汇报、等你确认后再动手"
|
|
1434
1487
|
},
|
|
1435
1488
|
{
|
|
1489
|
+
readonly: false,
|
|
1436
1490
|
usage: "maintain cancel [<id>]",
|
|
1437
1491
|
summary: "停掉在飞的那次维护扫描(省略 id 仅当最新一条是本进程在跑的那次)"
|
|
1438
1492
|
},
|
|
1439
1493
|
{
|
|
1494
|
+
readonly: false,
|
|
1440
1495
|
usage: "migrate",
|
|
1441
1496
|
summary: "把旧设置文档里家族的旧分区迁进现在的行(幂等;已迁移过就报「已一致」)"
|
|
1442
1497
|
},
|
|
1443
1498
|
{
|
|
1499
|
+
readonly: false,
|
|
1444
1500
|
usage: "policy set <id> <value> [--expect <revision>]",
|
|
1445
1501
|
summary: "通过设置服务写入一个你可改的参数 (E3 only; E1/E2 stay in cordis.yml)"
|
|
1446
1502
|
},
|
|
1447
1503
|
{
|
|
1504
|
+
readonly: true,
|
|
1448
1505
|
usage: "params [--group <name>] [--json]",
|
|
1449
1506
|
summary: "列出全部参数:分组、档位、生效时机、来源与当前值(--json feeds scripts)"
|
|
1450
1507
|
},
|
|
1451
1508
|
{
|
|
1509
|
+
readonly: false,
|
|
1452
1510
|
usage: "preset install [--base <name>[,<name>...]]",
|
|
1453
1511
|
summary: "把自进化 Agent 预设**行**写进当前 profile 的 patch 层(`profiles/<p>/cordis.patch.yml`;bases 来自 agent 包的 bases.json)"
|
|
1454
1512
|
},
|
|
1455
1513
|
{
|
|
1514
|
+
readonly: false,
|
|
1456
1515
|
usage: "restructure <name> \"<heading>\" <to_file> [--plan <runId>]",
|
|
1457
1516
|
summary: "把技能正文的一节移到 references 文件"
|
|
1458
1517
|
},
|
|
1459
1518
|
{
|
|
1519
|
+
readonly: true,
|
|
1460
1520
|
usage: "replay",
|
|
1461
1521
|
summary: "比较不同会话与重启之间的计划结果(从活动记录回填)"
|
|
1462
1522
|
}
|
|
@@ -1474,6 +1534,137 @@ function renderHelpText() {
|
|
|
1474
1534
|
].join("\n");
|
|
1475
1535
|
}
|
|
1476
1536
|
//#endregion
|
|
1537
|
+
//#region lib/types/parse.js
|
|
1538
|
+
/**
|
|
1539
|
+
* F-4/G7.4 (C7): the ONE parser for the /evolution command surface.
|
|
1540
|
+
*
|
|
1541
|
+
* The surface used to answer each subcommand with its own regex (`^params(?: --group ([a-z-]+))?(?: --json)?$`
|
|
1542
|
+
* and nine siblings), so `params --json --group review` and `params --group=review` were refused as
|
|
1543
|
+
* "not a subcommand", while a sibling regex accepted `=` — and a mistyped group name degraded into the
|
|
1544
|
+
* generic error instead of the one that lists the known groups.
|
|
1545
|
+
*
|
|
1546
|
+
* This parser is order- and `=`-tolerant, matches the subcommand name case-insensitively, keeps a flag
|
|
1547
|
+
* VALUE's case intact (the domain layer is the one that validates it), and answers a SYNTAX failure when a
|
|
1548
|
+
* declared value flag is left without its value (F-3: `policy set a.b --expect 3` must not read
|
|
1549
|
+
* `--expect 3` as the value).
|
|
1550
|
+
*
|
|
1551
|
+
* @module @lmzhen/dsh-evolution-commands/parse
|
|
1552
|
+
*/
|
|
1553
|
+
/** The flags whose declared form takes a value (`--group <name>`, `--expect <revision>`, …). */
|
|
1554
|
+
/**
|
|
1555
|
+
* Exported so the command-table gate can tie it to the table's usage lines (P2: two declarations of
|
|
1556
|
+
* "which flags take a value" would otherwise be free to drift apart).
|
|
1557
|
+
*/
|
|
1558
|
+
const VALUE_FLAGS = [
|
|
1559
|
+
"group",
|
|
1560
|
+
"expect",
|
|
1561
|
+
"base",
|
|
1562
|
+
"to",
|
|
1563
|
+
"timeout",
|
|
1564
|
+
"plan"
|
|
1565
|
+
];
|
|
1566
|
+
/**
|
|
1567
|
+
* Narrow a parse result: true when it is the syntax failure rather than a command.
|
|
1568
|
+
* @param result - what {@link parseSubcommand} answered.
|
|
1569
|
+
* @returns whether the line was refused.
|
|
1570
|
+
*/
|
|
1571
|
+
function isParseFailure(result) {
|
|
1572
|
+
return result.kind !== void 0;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Split a command line into tokens: whitespace separates, and a double-quoted run is ONE token with its
|
|
1576
|
+
* quotes removed (the `restructure <name> "<heading>" <to_file>` form needs that; the old regexes each
|
|
1577
|
+
* carried their own quoting rule).
|
|
1578
|
+
* @param input - the text after `/evolution`.
|
|
1579
|
+
* @returns the tokens, in order.
|
|
1580
|
+
*/
|
|
1581
|
+
function tokenize(input) {
|
|
1582
|
+
const tokens = [];
|
|
1583
|
+
let current = "";
|
|
1584
|
+
let quoted = false;
|
|
1585
|
+
for (const char of input.trim()) {
|
|
1586
|
+
if (char === String.fromCharCode(34)) {
|
|
1587
|
+
quoted = !quoted;
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (!quoted && /\s/.test(char)) {
|
|
1591
|
+
if (current !== "") {
|
|
1592
|
+
tokens.push(current);
|
|
1593
|
+
current = "";
|
|
1594
|
+
}
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
current += char;
|
|
1598
|
+
}
|
|
1599
|
+
if (current !== "") tokens.push(current);
|
|
1600
|
+
return tokens;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Read one SUB-FORM line: `maintain status`, `maintain report`, … — the shape the regexes used to own.
|
|
1604
|
+
*
|
|
1605
|
+
* A sub-form matches only when: the line parses, the name matches, the first positional is the sub-form,
|
|
1606
|
+
* and every flag the line carries is DECLARED by the table (an undeclared flag keeps the old
|
|
1607
|
+
* "unknown arguments" answer instead of silently becoming a bare flag).
|
|
1608
|
+
* @param input - the text after `/evolution`.
|
|
1609
|
+
* @param name - the subcommand name the table declares (e.g. `maintain`).
|
|
1610
|
+
* @param sub - the sub-form word (e.g. `status`).
|
|
1611
|
+
* @param declaredFlags - the flags the table declares for `name` (`flagsOf(name)`).
|
|
1612
|
+
* @returns the parsed command, or null when this line is not that sub-form.
|
|
1613
|
+
*/
|
|
1614
|
+
function subcommandOf(input, name, sub, declaredFlags) {
|
|
1615
|
+
const parsed = parseSubcommand(input);
|
|
1616
|
+
if (isParseFailure(parsed) || parsed.name !== name) return null;
|
|
1617
|
+
if (parsed.positionals[0] !== sub) return null;
|
|
1618
|
+
for (const flag of parsed.flags.keys()) if (!declaredFlags.includes("--" + flag)) return null;
|
|
1619
|
+
return parsed;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Parse one command line into its name, flags and positionals.
|
|
1623
|
+
* @param input - the text after `/evolution`.
|
|
1624
|
+
* @returns the parsed command, or the syntax failure that refused it.
|
|
1625
|
+
*/
|
|
1626
|
+
function parseSubcommand(input) {
|
|
1627
|
+
const [first, ...rest] = tokenize(input);
|
|
1628
|
+
if (first === void 0) return {
|
|
1629
|
+
kind: "unknown-subcommand",
|
|
1630
|
+
name: ""
|
|
1631
|
+
};
|
|
1632
|
+
const name = first.toLowerCase();
|
|
1633
|
+
const flags = /* @__PURE__ */ new Map();
|
|
1634
|
+
const positionals = [];
|
|
1635
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1636
|
+
const token = rest[index];
|
|
1637
|
+
if (token === void 0) continue;
|
|
1638
|
+
if (!token.startsWith("--")) {
|
|
1639
|
+
positionals.push(token);
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
const equals = token.indexOf("=");
|
|
1643
|
+
const flag = (equals < 0 ? token : token.slice(0, equals)).slice(2);
|
|
1644
|
+
if (equals >= 0) {
|
|
1645
|
+
flags.set(flag, token.slice(equals + 1));
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (!VALUE_FLAGS.includes(flag)) {
|
|
1649
|
+
flags.set(flag, true);
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
const next = rest[index + 1];
|
|
1653
|
+
if (next === void 0 || next.startsWith("--")) return {
|
|
1654
|
+
kind: "missing-value",
|
|
1655
|
+
name,
|
|
1656
|
+
flag
|
|
1657
|
+
};
|
|
1658
|
+
flags.set(flag, next);
|
|
1659
|
+
index += 1;
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
name,
|
|
1663
|
+
flags,
|
|
1664
|
+
positionals
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
//#endregion
|
|
1477
1668
|
//#region lib/types/index.js
|
|
1478
1669
|
/**
|
|
1479
1670
|
* Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate|skills refresh.
|
|
@@ -1650,7 +1841,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1650
1841
|
const ioRegistry = ioCtx.get("evolutionIo");
|
|
1651
1842
|
if (!ioRegistry) return;
|
|
1652
1843
|
const registry = newRunRegistry({
|
|
1653
|
-
io: ioRegistry
|
|
1844
|
+
io: preferProvider(ioRegistry, ["fs"]),
|
|
1654
1845
|
home: evolutionHome(),
|
|
1655
1846
|
warn: (message) => {
|
|
1656
1847
|
ctx.logger.warn(message);
|
|
@@ -1691,15 +1882,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1691
1882
|
return err(errorText("e-306-this-deployment-stages-foreground", { a1: what }));
|
|
1692
1883
|
};
|
|
1693
1884
|
const approval = ctx.get("evolutionApproval");
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1885
|
+
const pendingParsed = parseSubcommand(input);
|
|
1886
|
+
if (!isParseFailure(pendingParsed) && pendingParsed.name === "pending") {
|
|
1696
1887
|
if (!approval) return err(errorText("e-301-approval-service-not-mounted"));
|
|
1697
1888
|
const listed = [...await approval.list("pending"), ...await approval.list("executing")];
|
|
1698
1889
|
const pending = [...new Map(listed.map((row) => [row.id, row])).values()];
|
|
1699
1890
|
const ttlMs = typeof approval.ttlMs === "number" && Number.isFinite(approval.ttlMs) ? approval.ttlMs : 0;
|
|
1700
1891
|
const ttlNote = ttlMs > 0 ? `\n(staged writes expire after ${ttlMs} ms: an expired write is closed as REJECTED and is never executed — stage it again if it is still wanted)` : "\n(staged writes never expire — the window waits for a decision; set the evolution-approval row pendingTtlMs to bound it)";
|
|
1701
1892
|
if (pending.length === 0) return ok("No pending evolution writes." + ttlNote);
|
|
1702
|
-
const detailed =
|
|
1893
|
+
const detailed = pendingParsed.flags.get("detail") === true;
|
|
1703
1894
|
const nowMs = Date.now();
|
|
1704
1895
|
const attribution = (p) => {
|
|
1705
1896
|
const age = elapsedSince(p.createdAt, nowMs);
|
|
@@ -1882,7 +2073,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1882
2073
|
if (!ioRegistry) return err("Evolution IO registry not mounted — skill pinning unavailable.");
|
|
1883
2074
|
const result = await newSkillLibrary({
|
|
1884
2075
|
config: { root: skillsRootValue },
|
|
1885
|
-
io: ioRegistry
|
|
2076
|
+
io: preferProvider(ioRegistry, ["fs"]),
|
|
1886
2077
|
ctx,
|
|
1887
2078
|
limits: {
|
|
1888
2079
|
...DEFAULT_SKILL_LIMITS,
|
|
@@ -1936,12 +2127,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1936
2127
|
});
|
|
1937
2128
|
return woke ? ok("Learning request sent to this session. Follow it now.") : ok("Learning request queued for this session — this host exposes no wake-up channel, so it is read on your next message.");
|
|
1938
2129
|
}
|
|
1939
|
-
|
|
2130
|
+
const factsParsed = parseSubcommand(input);
|
|
2131
|
+
if (!isParseFailure(factsParsed) && factsParsed.name === "maintain" && factsParsed.flags.get("facts") === true && factsParsed.positionals.length === 0) {
|
|
1940
2132
|
const ioRegistry = ctx.get("evolutionIo");
|
|
1941
2133
|
if (!ioRegistry) return err("Evolution IO registry not mounted — maintenance facts unavailable.");
|
|
1942
2134
|
const library = newSkillLibrary({
|
|
1943
2135
|
config: { root: skillsRootValue },
|
|
1944
|
-
io: ioRegistry
|
|
2136
|
+
io: preferProvider(ioRegistry, ["fs"])
|
|
1945
2137
|
});
|
|
1946
2138
|
const enrichment = await buildEnrichment(ctx, library);
|
|
1947
2139
|
const readFailures = [];
|
|
@@ -1953,15 +2145,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1953
2145
|
}
|
|
1954
2146
|
});
|
|
1955
2147
|
if (snapshots.length === 0 && readFailures.length > 0) return err(`maintenance scan could not read ${readFailures.length} listed skill(s) (${readFailures.slice(0, 10).join(", ")}${readFailures.length > 10 ? ", …" : ""}) and read no skill at all — the library was NOT audited; fix the unreadable skills (or their directory names) and run the scan again`);
|
|
1956
|
-
if (snapshots.length === 0 && skillsRootValue !== "" && !await ioRegistry
|
|
2148
|
+
if (snapshots.length === 0 && skillsRootValue !== "" && !await preferProvider(ioRegistry, ["fs"]).exists(skillsRootValue)) return err(`maintenance scan found an empty skill library, but the configured skill root does not exist (${skillsRootValue}) — point the skillsRoot config at a real directory (a literal \`~\` is not expanded) and run the scan again`);
|
|
1957
2149
|
if (snapshots.length === 0) return ok("Maintenance scan: empty skill library. Nothing to do.");
|
|
1958
2150
|
const { facts } = buildMaintainFacts(snapshots, enrichment.usageObservedValue, void 0);
|
|
1959
2151
|
return ok(`Maintenance facts (0-token preview):\n${facts}`);
|
|
1960
2152
|
}
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2153
|
+
const maintainParsed = parseSubcommand(input);
|
|
2154
|
+
const maintainFlagsDeclared = [...isParseFailure(maintainParsed) ? [] : maintainParsed.flags.keys()].every((flag) => flagsOf("maintain").includes("--" + flag));
|
|
2155
|
+
if (!isParseFailure(maintainParsed) && maintainParsed.name === "maintain" && maintainParsed.positionals.length === 0 && maintainFlagsDeclared) {
|
|
2156
|
+
const timeoutFlag = maintainParsed.flags.get("timeout");
|
|
2157
|
+
const runTimeoutMs = typeof timeoutFlag === "string" ? Number(timeoutFlag) : config.maintainTimeoutMs ?? 6e5;
|
|
2158
|
+
if (!Number.isSafeInteger(runTimeoutMs) || runTimeoutMs <= 0 || runTimeoutMs > MAX_TIMER_DELAY_MS) return err(timeoutFlag !== void 0 ? `Invalid --timeout value: expected a positive integer number of milliseconds up to ${MAX_TIMER_DELAY_MS} (e.g. /evolution maintain --timeout 600000).` : `The maintainTimeoutMs config is invalid: expected a positive integer number of milliseconds up to ${MAX_TIMER_DELAY_MS}. Fix the evolution-commands row config (maintainTimeoutMs), then retry.`);
|
|
1965
2159
|
const registry = runs;
|
|
1966
2160
|
if (!registry) return err("Evolution IO registry not mounted — maintenance scan unavailable.");
|
|
1967
2161
|
const synced = await syncRuns(registry);
|
|
@@ -1992,7 +2186,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1992
2186
|
try {
|
|
1993
2187
|
const library = newSkillLibrary({
|
|
1994
2188
|
config: { root: skillsRootValue },
|
|
1995
|
-
io: ioRegistry
|
|
2189
|
+
io: preferProvider(ioRegistry, ["fs"])
|
|
1996
2190
|
});
|
|
1997
2191
|
const enrichment = await buildEnrichment(ctx, library);
|
|
1998
2192
|
const outcome = await runMaintain({
|
|
@@ -2004,7 +2198,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2004
2198
|
logger: { warn: (message) => {
|
|
2005
2199
|
ctx.logger.warn(message);
|
|
2006
2200
|
} },
|
|
2007
|
-
rootExists: skillsRootValue === "" ? void 0 : () => ioRegistry
|
|
2201
|
+
rootExists: skillsRootValue === "" ? void 0 : () => preferProvider(ioRegistry, ["fs"]).exists(skillsRootValue),
|
|
2008
2202
|
skillRoot: skillsRootValue
|
|
2009
2203
|
}, {
|
|
2010
2204
|
timeoutMs: runTimeoutMs,
|
|
@@ -2013,7 +2207,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2013
2207
|
enrichment: () => enrichment
|
|
2014
2208
|
});
|
|
2015
2209
|
const endedAt = Date.now();
|
|
2016
|
-
const resultRef = await writeMaintainReport(ioRegistry
|
|
2210
|
+
const resultRef = await writeMaintainReport(preferProvider(ioRegistry, ["fs"]), {
|
|
2017
2211
|
runId: handle.id,
|
|
2018
2212
|
...outcome.ok ? {
|
|
2019
2213
|
verdict: outcome.verdict,
|
|
@@ -2037,7 +2231,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2037
2231
|
...outcome.ok ? {} : { failure: handle.signal.aborted ? "cancelled: the scan was stopped before it settled" : outcome.error ?? "Maintenance scan failed." }
|
|
2038
2232
|
});
|
|
2039
2233
|
if (outcome.ok) {
|
|
2040
|
-
appendEvolutionEvent(ioRegistry
|
|
2234
|
+
appendEvolutionEvent(preferProvider(ioRegistry, ["fs"]), eventsFile(runRoot), {
|
|
2041
2235
|
type: "maintain",
|
|
2042
2236
|
source: "manual",
|
|
2043
2237
|
runId: handle.id,
|
|
@@ -2091,12 +2285,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2091
2285
|
`- stop: /evolution maintain cancel ${handle.id}`
|
|
2092
2286
|
].join("\n"));
|
|
2093
2287
|
}
|
|
2094
|
-
const maintainStatus =
|
|
2095
|
-
if (maintainStatus) {
|
|
2288
|
+
const maintainStatus = subcommandOf(input, "maintain", "status", flagsOf("maintain"));
|
|
2289
|
+
if (maintainStatus !== null) {
|
|
2096
2290
|
const registry = runs;
|
|
2097
2291
|
if (!registry) return err("Evolution IO registry not mounted — run status unavailable.");
|
|
2098
2292
|
const synced = await syncRuns(registry);
|
|
2099
|
-
const wanted = maintainStatus[1];
|
|
2293
|
+
const wanted = maintainStatus.positionals[1];
|
|
2100
2294
|
if (wanted !== void 0) {
|
|
2101
2295
|
const record = registry.find(wanted);
|
|
2102
2296
|
if (!record) {
|
|
@@ -2112,12 +2306,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2112
2306
|
const recent = all.filter((record) => record.state !== "running").slice(0, 5);
|
|
2113
2307
|
return ok([live.length === 0 ? "No maintenance run in flight." : `In flight:\n${live.map(renderRunRecord).join("\n")}`, recent.length === 0 ? "" : `Recent:\n${recent.map(renderRunRecord).join("\n")}`].filter((part) => part !== "").join("\n"));
|
|
2114
2308
|
}
|
|
2115
|
-
const maintainReport =
|
|
2116
|
-
if (maintainReport) {
|
|
2309
|
+
const maintainReport = subcommandOf(input, "maintain", "report", flagsOf("maintain"));
|
|
2310
|
+
if (maintainReport !== null) {
|
|
2117
2311
|
const registry = runs;
|
|
2118
2312
|
if (!registry) return err("Evolution IO registry not mounted — run results unavailable.");
|
|
2119
2313
|
const synced = await syncRuns(registry);
|
|
2120
|
-
const wanted = maintainReport[1];
|
|
2314
|
+
const wanted = maintainReport.positionals[1];
|
|
2121
2315
|
if (wanted === void 0 && !synced.ok) return err(unreadableIndex(synced.note));
|
|
2122
2316
|
const record = wanted === void 0 ? newestRun(registry) : registry.find(wanted);
|
|
2123
2317
|
if (!record) {
|
|
@@ -2130,17 +2324,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2130
2324
|
if (wanted === void 0 && record.state === "running") return err(`The newest run ${record.id} is still running — its report appears when it settles; /evolution maintain status ${record.id} shows where it is.`);
|
|
2131
2325
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2132
2326
|
if (!ioRegistry) return err("Evolution IO registry not mounted — run results unavailable.");
|
|
2133
|
-
const probed = await readRunReport(ioRegistry
|
|
2327
|
+
const probed = await readRunReport(preferProvider(ioRegistry, ["fs"]), record);
|
|
2134
2328
|
if (probed.kind === "missing") return err(`Run ${record.id} (${record.state}) has NO result: no report was written${record.failure === void 0 ? "" : ` (${record.failure})`}.`);
|
|
2135
2329
|
if (probed.kind === "unknown") return err(`Run ${record.id} has a report that could not be read (${probed.reason}) — this is not "no result".`);
|
|
2136
2330
|
return ok(probed.text);
|
|
2137
2331
|
}
|
|
2138
|
-
const maintainCancel =
|
|
2139
|
-
if (maintainCancel) {
|
|
2332
|
+
const maintainCancel = subcommandOf(input, "maintain", "cancel", flagsOf("maintain"));
|
|
2333
|
+
if (maintainCancel !== null) {
|
|
2140
2334
|
const registry = runs;
|
|
2141
2335
|
if (!registry) return err("Evolution IO registry not mounted — run cancellation unavailable.");
|
|
2142
2336
|
const synced = await syncRuns(registry);
|
|
2143
|
-
const wanted = maintainCancel[1];
|
|
2337
|
+
const wanted = maintainCancel.positionals[1];
|
|
2144
2338
|
if (wanted === void 0 && !synced.ok) return err(unreadableIndex(synced.note));
|
|
2145
2339
|
const record = wanted === void 0 ? newestRun(registry) : registry.find(wanted);
|
|
2146
2340
|
if (!record) {
|
|
@@ -2157,12 +2351,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2157
2351
|
}
|
|
2158
2352
|
return ok(`Maintenance run ${record.id} cancelled — the scan stops at its next checkpoint. /evolution maintain status ${record.id} shows the terminal state.`);
|
|
2159
2353
|
}
|
|
2160
|
-
const maintainHandoff =
|
|
2161
|
-
if (maintainHandoff) {
|
|
2354
|
+
const maintainHandoff = subcommandOf(input, "maintain", "handoff", flagsOf("maintain"));
|
|
2355
|
+
if (maintainHandoff !== null) {
|
|
2162
2356
|
const registry = runs;
|
|
2163
2357
|
if (!registry) return err("Evolution IO registry not mounted — the report cannot be handed over.");
|
|
2164
2358
|
const synced = await syncRuns(registry);
|
|
2165
|
-
const wanted = maintainHandoff[1];
|
|
2359
|
+
const wanted = maintainHandoff.positionals[1];
|
|
2166
2360
|
if (wanted === void 0 && !synced.ok) return err(unreadableIndex(synced.note));
|
|
2167
2361
|
const record = wanted === void 0 ? newestRun(registry) : registry.find(wanted);
|
|
2168
2362
|
if (!record) {
|
|
@@ -2175,7 +2369,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2175
2369
|
if (wanted === void 0 && record.state === "running") return err(`The newest run ${record.id} is still running — its report appears when it settles; /evolution maintain status ${record.id} shows where it is.`);
|
|
2176
2370
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2177
2371
|
if (!ioRegistry) return err("Evolution IO registry not mounted — the report cannot be handed over.");
|
|
2178
|
-
const probed = await readRunReport(ioRegistry
|
|
2372
|
+
const probed = await readRunReport(preferProvider(ioRegistry, ["fs"]), record);
|
|
2179
2373
|
if (probed.kind === "missing") return err(`Run ${record.id} (${record.state}) has NO result: no report was written${record.failure === void 0 ? "" : ` (${record.failure})`}.`);
|
|
2180
2374
|
if (probed.kind === "unknown") return err(`Run ${record.id} has a report that could not be read (${probed.reason}) — this is not "no result".`);
|
|
2181
2375
|
const missingAgent = agentMissing("handoff");
|
|
@@ -2201,9 +2395,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2201
2395
|
return ok(woke ? `Handed run ${record.id} to the model — report: ${reportDigestPath(record)}. It will read the file and follow the action lines.` : `Handed run ${record.id} to the model, queued — this host exposes no wake-up channel, so it is read on your next message.`);
|
|
2202
2396
|
}
|
|
2203
2397
|
if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected `maintain`, `maintain --timeout <ms>` / `maintain --timeout=<ms>`, `maintain status [<id>]`, `maintain report [<id>]`, `maintain handoff [<id>]` or `maintain cancel [<id>]`. Got: " + input);
|
|
2204
|
-
const
|
|
2205
|
-
|
|
2206
|
-
|
|
2398
|
+
const presetParsed = parseSubcommand(input);
|
|
2399
|
+
const presetInstall = isParseFailure(presetParsed) || presetParsed.name !== "preset" ? null : presetParsed.positionals[0] === "install" && presetParsed.positionals.length === 1 && [...presetParsed.flags.keys()].every((flag) => flagsOf("preset").includes("--" + flag)) ? presetParsed : false;
|
|
2400
|
+
if (presetInstall !== null && presetInstall !== false) {
|
|
2207
2401
|
{
|
|
2208
2402
|
const mounted = presetProfileTarget(ctx);
|
|
2209
2403
|
const exclusion = installExclusionTarget(mounted === void 0 ? void 0 : {
|
|
@@ -2220,7 +2414,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2220
2414
|
try {
|
|
2221
2415
|
const source = resolveAgentPresetDir(import.meta.url);
|
|
2222
2416
|
const table = readAgentPresetBases(source);
|
|
2223
|
-
const
|
|
2417
|
+
const baseFlag = presetInstall.flags.get("base");
|
|
2418
|
+
const requestedRaw = typeof baseFlag === "string" ? baseFlag : table.defaultBase;
|
|
2224
2419
|
const requested = [...new Set(requestedRaw.split(",").map((part) => part.trim()).filter((part) => part !== ""))];
|
|
2225
2420
|
if (requested.length === 0) return err(`No agent-preset base named — ${join(source, "bases.json")} lists ${table.entries.map((entry) => entry.name).join(", ")}`);
|
|
2226
2421
|
const selected = [];
|
|
@@ -2234,45 +2429,51 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2234
2429
|
const profile = presetProfileTarget(ctx);
|
|
2235
2430
|
if (profile === void 0) return err("No profile is mounted in this process, so there is no profile patch to write the preset row into — run the command in a session started by `dsh --profile <name>`.");
|
|
2236
2431
|
const delta = readFileSync(deltaPath, "utf8");
|
|
2237
|
-
const
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
rowId
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2432
|
+
const hmr = ctx.get("hmr");
|
|
2433
|
+
const commit = () => {
|
|
2434
|
+
const current = existsSync(profile.patchPath) ? readFileSync(profile.patchPath, "utf8") : "";
|
|
2435
|
+
let merged = current;
|
|
2436
|
+
const written = [];
|
|
2437
|
+
for (const base of selected) {
|
|
2438
|
+
const refusal = baseRefusalReason(base, (name) => ctx.get(name) !== void 0);
|
|
2439
|
+
if (refusal !== void 0) return err(refusal);
|
|
2440
|
+
const basePatch = resolvePresetBasePatch(base.name, profile);
|
|
2441
|
+
const rowId = presetRowId(base.id);
|
|
2442
|
+
const entryText = composePresetEntry(readFileSync(basePatch, "utf8"), delta, {
|
|
2443
|
+
rowId,
|
|
2444
|
+
id: base.id,
|
|
2445
|
+
name: base.display.name,
|
|
2446
|
+
description: base.display.description,
|
|
2447
|
+
order: base.display.order
|
|
2448
|
+
});
|
|
2449
|
+
const next = mergePresetRow(merged, entryText, rowId);
|
|
2450
|
+
written.push(next === merged ? `${base.name} → ${profile.patchPath} (already current)` : `${base.name} → ${rowId} in ${profile.patchPath}`);
|
|
2451
|
+
merged = next;
|
|
2452
|
+
}
|
|
2453
|
+
if (merged !== current) atomicWriteFiles(dirname(profile.patchPath), [{
|
|
2454
|
+
name: PROFILE_PATCH_FILENAME$1,
|
|
2455
|
+
content: presetPatchText(merged)
|
|
2456
|
+
}], void 0, (message) => {
|
|
2457
|
+
ctx.logger.warn(message);
|
|
2251
2458
|
});
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
name: PROFILE_PATCH_FILENAME$1,
|
|
2258
|
-
content: presetPatchText(merged)
|
|
2259
|
-
}], void 0, (message) => {
|
|
2260
|
-
ctx.logger.warn(message);
|
|
2261
|
-
});
|
|
2262
|
-
return ok(`Evolution agent preset row installed (${written.join("; ")}). Restart the session switcher to select it.`);
|
|
2459
|
+
return ok(`Evolution agent preset row installed (${written.join("; ")}). Restart the session switcher to select it.`);
|
|
2460
|
+
};
|
|
2461
|
+
const runExclusive = hmr?.runExclusive;
|
|
2462
|
+
if (runExclusive === void 0) return commit();
|
|
2463
|
+
return await runExclusive.call(hmr, commit);
|
|
2263
2464
|
} catch (error) {
|
|
2264
2465
|
return err(`Preset install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2265
2466
|
}
|
|
2266
2467
|
}
|
|
2267
2468
|
if (input.startsWith("restructure ")) {
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
const
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
const
|
|
2275
|
-
const
|
|
2469
|
+
const restParsed = parseSubcommand(rawInputTrimmed);
|
|
2470
|
+
if (isParseFailure(restParsed) || restParsed.name !== "restructure") return err("Usage: /evolution restructure <name> \"<## heading>\" <to_file>");
|
|
2471
|
+
const name = restParsed.positionals[0] ?? "";
|
|
2472
|
+
const heading = restParsed.positionals[1] ?? "";
|
|
2473
|
+
const toFile = restParsed.positionals[2] ?? "";
|
|
2474
|
+
if (name === "" || heading === "" || toFile === "") return err("Usage: /evolution restructure <name> \"<## heading>\" <to_file>");
|
|
2475
|
+
const planFlag = restParsed.flags.get("plan");
|
|
2476
|
+
const planRunId = typeof planFlag === "string" ? planFlag : void 0;
|
|
2276
2477
|
if (!toFile.startsWith("references/")) return err("to_file must live under references/ (log/detail destination).");
|
|
2277
2478
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2278
2479
|
if (!ioRegistry) return err("Evolution IO registry not mounted — restructure unavailable.");
|
|
@@ -2308,7 +2509,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2308
2509
|
}
|
|
2309
2510
|
const result = await newSkillLibrary({
|
|
2310
2511
|
config: { root: skillsRootValue },
|
|
2311
|
-
io: ioRegistry
|
|
2512
|
+
io: preferProvider(ioRegistry, ["fs"]),
|
|
2312
2513
|
ctx,
|
|
2313
2514
|
threatExemptLabels: config.threatExemptLabels,
|
|
2314
2515
|
limits: {
|
|
@@ -2328,8 +2529,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2328
2529
|
if (!replay) return err(errorText("e-303-replay-service-not-mounted"));
|
|
2329
2530
|
return ok(replay.compare().report);
|
|
2330
2531
|
}
|
|
2331
|
-
const
|
|
2332
|
-
if (
|
|
2532
|
+
const paramsParsed = parseSubcommand(input);
|
|
2533
|
+
if (!isParseFailure(paramsParsed) && paramsParsed.name === "params") {
|
|
2333
2534
|
const provider = ctx.get("settings");
|
|
2334
2535
|
const sections = /* @__PURE__ */ new Map();
|
|
2335
2536
|
if (provider?.describe !== void 0) try {
|
|
@@ -2344,21 +2545,23 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2344
2545
|
return err(errorText("e-307-the-settings-service-could", { a1: error instanceof Error ? error.message : String(error) }));
|
|
2345
2546
|
}
|
|
2346
2547
|
const rows = paramSurfaceRows(sections, PARAM_EXPOSURE);
|
|
2347
|
-
const
|
|
2548
|
+
const groupFlag = paramsParsed.flags.get("group");
|
|
2549
|
+
const group = typeof groupFlag === "string" ? groupFlag : void 0;
|
|
2348
2550
|
const selected = group === void 0 ? rows : rows.filter((row) => row.group === group);
|
|
2349
2551
|
if (group !== void 0 && selected.length === 0) return err(errorText("e-308-unknown-parameter-group-group", {
|
|
2350
2552
|
a1: group,
|
|
2351
2553
|
a2: paramGroups(PARAM_EXPOSURE).join(", ")
|
|
2352
2554
|
}));
|
|
2353
|
-
if (
|
|
2555
|
+
if (paramsParsed.flags.get("json") === true) return ok(renderParamJson(selected));
|
|
2354
2556
|
return ok(renderParamRows(selected, { providerMounted: provider !== void 0 }));
|
|
2355
2557
|
}
|
|
2356
|
-
const
|
|
2357
|
-
if (
|
|
2558
|
+
const policyParsed = parseSubcommand(input);
|
|
2559
|
+
if (isParseFailure(policyParsed) && policyParsed.name === "policy" && policyParsed.kind === "missing-value") return err("/evolution policy set needs a value after --" + (policyParsed.flag ?? "") + "; usage: policy set <id> <value> [--expect <revision>]");
|
|
2560
|
+
if (!isParseFailure(policyParsed) && policyParsed.name === "policy" && policyParsed.positionals[0] === "set") {
|
|
2358
2561
|
const provider = ctx.get("settings");
|
|
2359
2562
|
if (provider?.update === void 0 || provider.describe === void 0) return err(errorText("e-311-no-settings-service-is"));
|
|
2360
|
-
const rawId =
|
|
2361
|
-
const rawValue =
|
|
2563
|
+
const rawId = policyParsed.positionals[1] ?? "";
|
|
2564
|
+
const rawValue = policyParsed.positionals[2] ?? "";
|
|
2362
2565
|
let id;
|
|
2363
2566
|
try {
|
|
2364
2567
|
id = canonicalWriteId(rawId);
|
|
@@ -2389,13 +2592,24 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2389
2592
|
}));
|
|
2390
2593
|
const current = descriptor.value;
|
|
2391
2594
|
const resolved = typeof current === "object" && current !== null ? current : void 0;
|
|
2392
|
-
const
|
|
2595
|
+
const expectFlag = policyParsed.flags.get("expect");
|
|
2596
|
+
const expectedRaw = typeof expectFlag === "string" ? expectFlag : void 0;
|
|
2393
2597
|
const expected = expectedRaw === void 0 ? descriptor.revision : Number(expectedRaw);
|
|
2394
2598
|
if (expectedRaw !== void 0 && descriptor.revision !== void 0 && Number(expectedRaw) !== descriptor.revision) return err(errorText("e-309-revision-conflict-you-sent", {
|
|
2395
2599
|
a1: expectedRaw,
|
|
2396
2600
|
a2: descriptor.revision
|
|
2397
2601
|
}));
|
|
2398
2602
|
const value = parseParamValue(rawValue);
|
|
2603
|
+
const floorId = entry.relation?.atLeast;
|
|
2604
|
+
if (floorId !== void 0 && typeof value === "number") {
|
|
2605
|
+
const sibling = resolved?.[floorId];
|
|
2606
|
+
const floor = typeof sibling === "number" ? sibling : NaN;
|
|
2607
|
+
if (Number.isFinite(floor) && value < floor) return err(errorText("e-322-a-parameter-would-break-a", {
|
|
2608
|
+
a1: id,
|
|
2609
|
+
a2: String(floor),
|
|
2610
|
+
a3: String(value)
|
|
2611
|
+
}));
|
|
2612
|
+
}
|
|
2399
2613
|
try {
|
|
2400
2614
|
await provider.update(namespace, { [id]: value }, expected);
|
|
2401
2615
|
} catch (error) {
|
|
@@ -2443,7 +2657,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2443
2657
|
const evolutionCommand = {
|
|
2444
2658
|
name: "evolution",
|
|
2445
2659
|
description: "自进化:状态与待批准写入",
|
|
2446
|
-
recordInput: false,
|
|
2447
2660
|
input: { hint: renderHint() },
|
|
2448
2661
|
handler: (invocation) => commandBody.run(invocation).catch((error) => ({
|
|
2449
2662
|
kind: "error",
|
|
@@ -2558,121 +2771,6 @@ function resolveAgentPresetDir(importMetaUrl) {
|
|
|
2558
2771
|
return overlayCandidate;
|
|
2559
2772
|
}
|
|
2560
2773
|
}
|
|
2561
|
-
const defaultFs = {
|
|
2562
|
-
writeFileSync: (path, data) => {
|
|
2563
|
-
writeFileSync(path, data);
|
|
2564
|
-
},
|
|
2565
|
-
existsSync: (path) => existsSync(path),
|
|
2566
|
-
copyFileSync: (from, to) => {
|
|
2567
|
-
copyFileSync(from, to);
|
|
2568
|
-
},
|
|
2569
|
-
renameSync: (from, to) => {
|
|
2570
|
-
renameSync(from, to);
|
|
2571
|
-
},
|
|
2572
|
-
rmSync: (path, options) => {
|
|
2573
|
-
rmSync(path, options);
|
|
2574
|
-
},
|
|
2575
|
-
mtime: (path) => {
|
|
2576
|
-
try {
|
|
2577
|
-
return statSync(path).mtimeMs;
|
|
2578
|
-
} catch (error) {
|
|
2579
|
-
if (isMissingPath(error)) return null;
|
|
2580
|
-
throw error;
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
};
|
|
2584
|
-
/**
|
|
2585
|
-
* S6.3 (E-40): atomically replace a set of files within one directory.
|
|
2586
|
-
* Each file is staged to a sibling `<name>.tmp`, then renamed into place
|
|
2587
|
-
* (atomic on the same filesystem); a pre-existing file keeps a single
|
|
2588
|
-
* `<name>.bak`. A failure at ANY staging step removes the staged temps and
|
|
2589
|
-
* leaves the previous files untouched — a half-written target is impossible
|
|
2590
|
-
* up to the staging and backup phases.
|
|
2591
|
-
*
|
|
2592
|
-
* F-211: the rename COMMIT phase does not carry the same guarantee. When a
|
|
2593
|
-
* filesystem refuses to overwrite the destination, the code removes it and
|
|
2594
|
-
* retries the rename; if the retry ALSO fails the destination is gone. The
|
|
2595
|
-
* single `.bak` created above still holds the previous content, so the target
|
|
2596
|
-
* is restored from it (best-effort) before the original rename error is
|
|
2597
|
-
* rethrown, so a failed commit never leaves the file missing.
|
|
2598
|
-
*
|
|
2599
|
-
* V4-17: the `.bak` is refreshed to the committed content after every
|
|
2600
|
-
* successful commit, so a later failed commit always recovers to the most
|
|
2601
|
-
* recent good generation (a once-at-first-install `.bak` could otherwise
|
|
2602
|
-
* restore content several installs old). The recovery error names the `.bak`
|
|
2603
|
-
* source (and mtime when available) so the caller knows the generation it
|
|
2604
|
-
* restored from.
|
|
2605
|
-
*/
|
|
2606
|
-
function atomicWriteFiles(targetDir, writes, fs = defaultFs, logger = console.warn) {
|
|
2607
|
-
const stage = (name) => join(targetDir, `${name}.tmp`);
|
|
2608
|
-
const dupes = [...new Set(writes.map((write) => write.name).filter((name, index, all) => all.indexOf(name) !== index))];
|
|
2609
|
-
if (dupes.length > 0) throw new Error(`atomicWriteFiles: duplicate input names: ${dupes.join(", ")}`);
|
|
2610
|
-
try {
|
|
2611
|
-
for (const { name, content } of writes) fs.writeFileSync(stage(name), content);
|
|
2612
|
-
for (const { name } of writes) {
|
|
2613
|
-
const finalPath = join(targetDir, name);
|
|
2614
|
-
const bakPath = join(targetDir, `${name}.bak`);
|
|
2615
|
-
if (fs.existsSync(finalPath) && !fs.existsSync(bakPath)) fs.copyFileSync(finalPath, bakPath);
|
|
2616
|
-
}
|
|
2617
|
-
const committed = [];
|
|
2618
|
-
for (const { name } of writes) {
|
|
2619
|
-
const finalPath = join(targetDir, name);
|
|
2620
|
-
const bakPath = join(targetDir, `${name}.bak`);
|
|
2621
|
-
const tmp = stage(name);
|
|
2622
|
-
try {
|
|
2623
|
-
fs.renameSync(tmp, finalPath);
|
|
2624
|
-
committed.push(name);
|
|
2625
|
-
} catch {
|
|
2626
|
-
try {
|
|
2627
|
-
fs.rmSync(finalPath, { force: true });
|
|
2628
|
-
} catch (removeError) {
|
|
2629
|
-
const already = committed.length > 0 ? `; already committed: ${committed.join(", ")}` : "";
|
|
2630
|
-
throw new Error(`atomicWriteFiles: could not remove "${name}" to replace it (${removeError instanceof Error ? removeError.message : String(removeError)})${already}`);
|
|
2631
|
-
}
|
|
2632
|
-
try {
|
|
2633
|
-
fs.renameSync(tmp, finalPath);
|
|
2634
|
-
committed.push(name);
|
|
2635
|
-
} catch (renameError) {
|
|
2636
|
-
const already = committed.length > 0 ? `; already committed: ${committed.join(", ")}` : "";
|
|
2637
|
-
if (fs.existsSync(bakPath)) {
|
|
2638
|
-
try {
|
|
2639
|
-
fs.copyFileSync(bakPath, finalPath);
|
|
2640
|
-
} catch {
|
|
2641
|
-
throw new Error(`atomicWriteFiles: "${name}" was removed but recovery from ${bakPath} failed (${renameError instanceof Error ? renameError.message : String(renameError)}); verify the file manually${already}`);
|
|
2642
|
-
}
|
|
2643
|
-
const bakMtime = fs.mtime?.(bakPath);
|
|
2644
|
-
const when = bakMtime !== null && bakMtime !== void 0 && Number.isFinite(bakMtime) ? ` (bak mtime ${new Date(bakMtime).toISOString()})` : "";
|
|
2645
|
-
throw new Error(`atomicWriteFiles: "${name}" was removed during commit and restored from the last good generation ${bakPath}${when}; original error: ${renameError instanceof Error ? renameError.message : String(renameError)}${already}`);
|
|
2646
|
-
}
|
|
2647
|
-
throw new Error(`${renameError instanceof Error ? renameError.message : String(renameError)}${already}`);
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
for (const { name } of writes) {
|
|
2652
|
-
const finalPath = join(targetDir, name);
|
|
2653
|
-
try {
|
|
2654
|
-
if (fs.existsSync(finalPath)) fs.copyFileSync(finalPath, join(targetDir, `${name}.bak`));
|
|
2655
|
-
} catch (refreshError) {
|
|
2656
|
-
logger(`atomicWriteFiles: committed "${name}" but failed to refresh its .bak (${refreshError instanceof Error ? refreshError.message : String(refreshError)}); a later failed commit will recover to an OLDER generation`);
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
} catch (error) {
|
|
2660
|
-
const cleanupFailures = [];
|
|
2661
|
-
for (const { name } of writes) try {
|
|
2662
|
-
fs.rmSync(stage(name), {
|
|
2663
|
-
force: true,
|
|
2664
|
-
recursive: true
|
|
2665
|
-
});
|
|
2666
|
-
} catch (cleanupError) {
|
|
2667
|
-
cleanupFailures.push(`${name}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
2668
|
-
}
|
|
2669
|
-
if (cleanupFailures.length > 0) {
|
|
2670
|
-
const original = error instanceof Error ? error.message : String(error);
|
|
2671
|
-
throw new Error(`${original} (staged-temp cleanup also failed: ${cleanupFailures.join("; ")})`);
|
|
2672
|
-
}
|
|
2673
|
-
throw error;
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
2774
|
/** F-328: render staged args for `pending --detail`, truncated to 500 chars.
|
|
2677
2775
|
* args may be non-JSON (a tool produced garbage), so the render is fail-safe.
|
|
2678
2776
|
* V4-19: a truncation is marked with `…(truncated N chars)` so the operator is
|
package/lib/types/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
12
12
|
} & ContextFormed;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
import { atomicWriteFiles } from '@lmzhen/dsh-evolution-core';
|
|
16
|
+
export { atomicWriteFiles };
|
|
15
17
|
export declare const name = "evolution-commands";
|
|
16
18
|
export interface Config {
|
|
17
19
|
/** Skill-tree root for maintain/restructure; empty uses the default tree.
|
|
@@ -71,46 +73,4 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
71
73
|
threatExemptLabels: z<string[], string[], "defined">;
|
|
72
74
|
}>>, "plain">;
|
|
73
75
|
export declare function apply(ctx: Context, rawConfig?: Config): void;
|
|
74
|
-
/** F-211: the fs surface atomicWriteFiles uses — injectable so the commit-phase
|
|
75
|
-
* rename-failure path is deterministically testable. Defaults to node:fs.
|
|
76
|
-
* `mtime` (optional) probes a path's last-write time so the failed-commit
|
|
77
|
-
* recovery message can name the generation it restored from. */
|
|
78
|
-
interface FsOps {
|
|
79
|
-
writeFileSync(path: string, data: string | Uint8Array): void;
|
|
80
|
-
existsSync(path: string): boolean;
|
|
81
|
-
copyFileSync(from: string, to: string): void;
|
|
82
|
-
renameSync(from: string, to: string): void;
|
|
83
|
-
rmSync(path: string, options?: {
|
|
84
|
-
force?: boolean;
|
|
85
|
-
recursive?: boolean;
|
|
86
|
-
}): void;
|
|
87
|
-
mtime?(path: string): number | null;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* S6.3 (E-40): atomically replace a set of files within one directory.
|
|
91
|
-
* Each file is staged to a sibling `<name>.tmp`, then renamed into place
|
|
92
|
-
* (atomic on the same filesystem); a pre-existing file keeps a single
|
|
93
|
-
* `<name>.bak`. A failure at ANY staging step removes the staged temps and
|
|
94
|
-
* leaves the previous files untouched — a half-written target is impossible
|
|
95
|
-
* up to the staging and backup phases.
|
|
96
|
-
*
|
|
97
|
-
* F-211: the rename COMMIT phase does not carry the same guarantee. When a
|
|
98
|
-
* filesystem refuses to overwrite the destination, the code removes it and
|
|
99
|
-
* retries the rename; if the retry ALSO fails the destination is gone. The
|
|
100
|
-
* single `.bak` created above still holds the previous content, so the target
|
|
101
|
-
* is restored from it (best-effort) before the original rename error is
|
|
102
|
-
* rethrown, so a failed commit never leaves the file missing.
|
|
103
|
-
*
|
|
104
|
-
* V4-17: the `.bak` is refreshed to the committed content after every
|
|
105
|
-
* successful commit, so a later failed commit always recovers to the most
|
|
106
|
-
* recent good generation (a once-at-first-install `.bak` could otherwise
|
|
107
|
-
* restore content several installs old). The recovery error names the `.bak`
|
|
108
|
-
* source (and mtime when available) so the caller knows the generation it
|
|
109
|
-
* restored from.
|
|
110
|
-
*/
|
|
111
|
-
export declare function atomicWriteFiles(targetDir: string, writes: Array<{
|
|
112
|
-
name: string;
|
|
113
|
-
content: string | Uint8Array;
|
|
114
|
-
}>, fs?: FsOps, logger?: (message: string) => void): void;
|
|
115
|
-
export {};
|
|
116
76
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F-4/G7.4 (C7): the ONE parser for the /evolution command surface.
|
|
3
|
+
*
|
|
4
|
+
* The surface used to answer each subcommand with its own regex (`^params(?: --group ([a-z-]+))?(?: --json)?$`
|
|
5
|
+
* and nine siblings), so `params --json --group review` and `params --group=review` were refused as
|
|
6
|
+
* "not a subcommand", while a sibling regex accepted `=` — and a mistyped group name degraded into the
|
|
7
|
+
* generic error instead of the one that lists the known groups.
|
|
8
|
+
*
|
|
9
|
+
* This parser is order- and `=`-tolerant, matches the subcommand name case-insensitively, keeps a flag
|
|
10
|
+
* VALUE's case intact (the domain layer is the one that validates it), and answers a SYNTAX failure when a
|
|
11
|
+
* declared value flag is left without its value (F-3: `policy set a.b --expect 3` must not read
|
|
12
|
+
* `--expect 3` as the value).
|
|
13
|
+
*
|
|
14
|
+
* @module @lmzhen/dsh-evolution-commands/parse
|
|
15
|
+
*/
|
|
16
|
+
/** The flags whose declared form takes a value (`--group <name>`, `--expect <revision>`, …). */
|
|
17
|
+
/**
|
|
18
|
+
* Exported so the command-table gate can tie it to the table's usage lines (P2: two declarations of
|
|
19
|
+
* "which flags take a value" would otherwise be free to drift apart).
|
|
20
|
+
*/
|
|
21
|
+
export declare const VALUE_FLAGS: readonly string[];
|
|
22
|
+
/** One parsed command line. */
|
|
23
|
+
export interface ParsedCommand {
|
|
24
|
+
/** The subcommand name, lower-cased (the table stores it lower-case too). */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** Declared flags: a value when the flag takes one, `true` when it is bare. */
|
|
27
|
+
readonly flags: ReadonlyMap<string, string | true>;
|
|
28
|
+
/** Everything that was neither the name nor a flag, in order. */
|
|
29
|
+
readonly positionals: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
/** A SYNTAX failure — the caller answers it as a usage error, never as a domain error. */
|
|
32
|
+
export interface ParseFailure {
|
|
33
|
+
/** Which syntax rule was broken. */
|
|
34
|
+
readonly kind: 'unknown-subcommand' | 'unknown-flag' | 'missing-value';
|
|
35
|
+
/** The subcommand name the caller saw (empty when the line was empty). */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
/** The flag involved, for the two flag failures. */
|
|
38
|
+
readonly flag?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Narrow a parse result: true when it is the syntax failure rather than a command.
|
|
42
|
+
* @param result - what {@link parseSubcommand} answered.
|
|
43
|
+
* @returns whether the line was refused.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isParseFailure(result: ParsedCommand | ParseFailure): result is ParseFailure;
|
|
46
|
+
/**
|
|
47
|
+
* Read one SUB-FORM line: `maintain status`, `maintain report`, … — the shape the regexes used to own.
|
|
48
|
+
*
|
|
49
|
+
* A sub-form matches only when: the line parses, the name matches, the first positional is the sub-form,
|
|
50
|
+
* and every flag the line carries is DECLARED by the table (an undeclared flag keeps the old
|
|
51
|
+
* "unknown arguments" answer instead of silently becoming a bare flag).
|
|
52
|
+
* @param input - the text after `/evolution`.
|
|
53
|
+
* @param name - the subcommand name the table declares (e.g. `maintain`).
|
|
54
|
+
* @param sub - the sub-form word (e.g. `status`).
|
|
55
|
+
* @param declaredFlags - the flags the table declares for `name` (`flagsOf(name)`).
|
|
56
|
+
* @returns the parsed command, or null when this line is not that sub-form.
|
|
57
|
+
*/
|
|
58
|
+
export declare function subcommandOf(input: string, name: string, sub: string, declaredFlags: readonly string[]): ParsedCommand | null;
|
|
59
|
+
/**
|
|
60
|
+
* Parse one command line into its name, flags and positionals.
|
|
61
|
+
* @param input - the text after `/evolution`.
|
|
62
|
+
* @returns the parsed command, or the syntax failure that refused it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseSubcommand(input: string): ParsedCommand | ParseFailure;
|
|
65
|
+
//# sourceMappingURL=parse.d.ts.map
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -12,9 +12,17 @@
|
|
|
12
12
|
export interface CommandEntry {
|
|
13
13
|
/** The subcommand as typed after `/evolution`. Variants use `|` (curator run|pause). */
|
|
14
14
|
usage: string;
|
|
15
|
+
/** C7: true when the subcommand only READS (no skill/param write). Stored: not derivable. */
|
|
16
|
+
readonly: boolean;
|
|
15
17
|
/** One-line purpose rendered in the help output. */
|
|
16
18
|
summary: string;
|
|
17
19
|
}
|
|
20
|
+
/** The flags the TABLE declares for one subcommand (name → `--flag`), DERIVED from its usage. */
|
|
21
|
+
export declare function flagsOf(name: string): string[];
|
|
22
|
+
/** C7 shape, DERIVED: the subcommand name is the first token of `usage` (variants use `|`). */
|
|
23
|
+
export declare function entryName(entry: CommandEntry): string;
|
|
24
|
+
/** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
|
|
25
|
+
export declare function entryFlags(entry: CommandEntry): string[];
|
|
18
26
|
export declare const COMMAND_ENTRIES: CommandEntry[];
|
|
19
27
|
/** The input-declaration hint (single line, ` | `-separated). */
|
|
20
28
|
export declare function renderHint(): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-commands",
|
|
3
3
|
"description": "Human commands for the evolution family (/evolution pending|curator|maintain|doctor) (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,17 +27,18 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
30
|
-
"@lmzhen/dsh-evolution-approval": "^0.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
32
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.23.0",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.23.0",
|
|
32
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.23.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.
|
|
35
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.23.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
39
|
"@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
|
|
40
|
-
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1"
|
|
40
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
41
|
+
"@lmzhen/dsh-evolution-io": "^0.23.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@deepseek-ai/dsh-commands": "^0.2.0-rc.1"
|