@lmzhen/dsh-evolution-commands 0.18.0 → 0.19.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/README.md +1 -0
- package/lib/index.js +265 -49
- package/lib/types/index.d.ts +5 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ The full `/evolution` subcommand table is single-sourced in the family README's
|
|
|
17
17
|
## Known limitations
|
|
18
18
|
|
|
19
19
|
- No known durable consumer gaps at this time. Runtime contracts are covered by package and boundary tests.
|
|
20
|
+
- **`/evolution maintain` runs in the BACKGROUND (0.19.0).** The command answers with a run id and the scan keeps working: read it with `/evolution maintain report <id>` (a run that produced nothing answers "no result" instead of an empty plan) or `/evolution maintain status [<id>]` (in flight plus the recent terminal states). The run's lifetime belongs to this plugin through `evolution-core`'s run registry, **not** to the request that started it: the desktop shell ends a request at ~305 s (measured four times) while the same scan completes in 949.7 s on the web plane, and `maintainTimeoutMs` (default 600000) bounds the run itself. **Cancelling the request no longer stops the scan** — use `/evolution maintain cancel <id>`, or unmount the row. The plan is written once at settle to `<evolutionHome>/reports/maintain-<runId>.{json,md}`, under the same per-home reports window the curator's reports use.
|
|
20
21
|
- **① variant form: the `/evolution …` command face is visible in every session** (accepted, not fixed). Commands are registered in scope layers, and the family registers once on the host plane, so a session on a platform original preset still lists the management-only subcommands even though no automatic family behaviour reaches it. The difference and its reasoning are single-sourced in `INSTALL.md` ("Known difference").
|
|
21
22
|
|
|
22
23
|
**Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ 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, evolutionRoot, isDeprecatedParamId, isMissingPath, mergePresetRow, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, resolveBasePresetPatch, resolveParamId, resolveRootConfig, scopedProbeReport } from "@lmzhen/dsh-evolution-core";
|
|
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, isMissingPath, isPresent, isUnknown, 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
7
|
import { copyFileSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
@@ -1332,7 +1332,19 @@ const COMMAND_ENTRIES = [
|
|
|
1332
1332
|
},
|
|
1333
1333
|
{
|
|
1334
1334
|
usage: "maintain [--timeout=<ms> | --facts]",
|
|
1335
|
-
summary: "
|
|
1335
|
+
summary: "起一次维护扫描并立即回 run id(后台跑;--facts 为 0 token 预览)"
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
usage: "maintain status [<id>]",
|
|
1339
|
+
summary: "看维护扫描:在飞的那次(含已跑多久)或某个 run 的终态"
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
usage: "maintain report <id>",
|
|
1343
|
+
summary: "读某个 run 的结果正文(没有结果就明说,不装成空结果)"
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
usage: "maintain cancel <id>",
|
|
1347
|
+
summary: "停掉在飞的那次维护扫描"
|
|
1336
1348
|
},
|
|
1337
1349
|
{
|
|
1338
1350
|
usage: "migrate",
|
|
@@ -1386,13 +1398,119 @@ const Config = z.object({
|
|
|
1386
1398
|
maintainTimeoutMs: z.number().min(1).default(6e5),
|
|
1387
1399
|
threatExemptLabels: z.array(z.string()).default([])
|
|
1388
1400
|
});
|
|
1401
|
+
/** How one run reads in `maintain status` output. */
|
|
1402
|
+
function renderRunRecord(record) {
|
|
1403
|
+
const seconds = Math.max(1, Math.round(((record.endedAt ?? Date.now()) - record.startedAt) / 1e3));
|
|
1404
|
+
const parts = [`${record.id} ${record.state} ${seconds}s`];
|
|
1405
|
+
if (record.failure !== void 0) parts.push(`— ${record.failure}`);
|
|
1406
|
+
if (record.resultRef !== void 0) parts.push(`→ ${record.resultRef}`);
|
|
1407
|
+
return parts.join(" ");
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Write one run's result to its ONE home: `<evolutionHome>/reports/maintain-<id>.json`
|
|
1411
|
+
* plus the `.md` digest, then sweep that window.
|
|
1412
|
+
*
|
|
1413
|
+
* A write failure does not fail the run — because the run has no result then, and
|
|
1414
|
+
* `maintain report <id>` says exactly that instead of pretending otherwise.
|
|
1415
|
+
* @param io - the evolution io seam.
|
|
1416
|
+
* @param record - the run's outcome, as persisted.
|
|
1417
|
+
* @param warn - warn channel for retention notes.
|
|
1418
|
+
* @returns the report path, or `undefined` when nothing could be written.
|
|
1419
|
+
*/
|
|
1420
|
+
/**
|
|
1421
|
+
* Re-read the run index before answering a command (review P1-1).
|
|
1422
|
+
*
|
|
1423
|
+
* One DSH_HOME is shared by both planes, so a scan the OTHER process started is
|
|
1424
|
+
* only visible after a refresh — without it the in-flight guard, the cooldown and
|
|
1425
|
+
* `status <id>` would all describe this process's memory alone. A read FAILURE is
|
|
1426
|
+
* not "no runs": the caller decides what to say.
|
|
1427
|
+
* @param registry - this row's registry.
|
|
1428
|
+
* @returns whether the history could be read, and the note to surface when there is one.
|
|
1429
|
+
*/
|
|
1430
|
+
async function syncRuns(registry) {
|
|
1431
|
+
try {
|
|
1432
|
+
return await registry.load();
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
return {
|
|
1435
|
+
ok: false,
|
|
1436
|
+
note: `the run index could not be read (${error instanceof Error ? error.message : String(error)})`
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
async function writeMaintainReport(io, record, warn, evolutionHomePath) {
|
|
1441
|
+
const dir = join(evolutionHomePath, "reports");
|
|
1442
|
+
const path = join(dir, `maintain-${record.runId}.json`);
|
|
1443
|
+
let written = true;
|
|
1444
|
+
try {
|
|
1445
|
+
await io.writeText(path, JSON.stringify(record, null, 2));
|
|
1446
|
+
await io.writeText(join(dir, `maintain-${record.runId}.md`), record.text.trim() === "" ? `Maintenance run ${record.runId}: ${record.failure ?? "no plan"}\n` : record.text);
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
written = false;
|
|
1449
|
+
warn(`evolution-commands: could not write the report for run ${record.runId} (${error instanceof Error ? error.message : String(error)}) — that run will answer "no result"`);
|
|
1450
|
+
}
|
|
1451
|
+
await transactIo(io, reportsSweepLockTarget(), async () => {
|
|
1452
|
+
await sweepReports({
|
|
1453
|
+
io,
|
|
1454
|
+
dir,
|
|
1455
|
+
buckets: [{
|
|
1456
|
+
prefix: "maintain-",
|
|
1457
|
+
keep: 20
|
|
1458
|
+
}],
|
|
1459
|
+
owner: "evolution-commands",
|
|
1460
|
+
warn
|
|
1461
|
+
});
|
|
1462
|
+
return null;
|
|
1463
|
+
});
|
|
1464
|
+
return written ? path : void 0;
|
|
1465
|
+
}
|
|
1466
|
+
/** Three-state read of a run's result: present, missing, or unreadable (never collapsed). */
|
|
1467
|
+
async function readRunReport(io, record) {
|
|
1468
|
+
const probed = await probeText(io, record.resultRef ?? join(evolutionHome(), "reports", `maintain-${record.id}.json`));
|
|
1469
|
+
if (isUnknown(probed)) return {
|
|
1470
|
+
kind: "unknown",
|
|
1471
|
+
reason: probed.reason
|
|
1472
|
+
};
|
|
1473
|
+
if (!isPresent(probed)) return { kind: "missing" };
|
|
1474
|
+
try {
|
|
1475
|
+
const parsed = JSON.parse(probed.value);
|
|
1476
|
+
return {
|
|
1477
|
+
kind: "present",
|
|
1478
|
+
text: `${`Maintenance run ${record.id} — ${record.state}${record.failure === void 0 ? "" : ` (${record.failure})`}`}\n${typeof parsed.text === "string" && parsed.text.trim() !== "" ? parsed.text : renderRunRecord(record)}`
|
|
1479
|
+
};
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
return {
|
|
1482
|
+
kind: "unknown",
|
|
1483
|
+
reason: `the report is not JSON (${error instanceof Error ? error.message : String(error)})`
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1389
1487
|
function apply(ctx, rawConfig = {}) {
|
|
1390
1488
|
const config = rawConfig;
|
|
1391
1489
|
assertSkillsRootAliasRetired(rawConfig);
|
|
1392
1490
|
const skillsRootValue = resolveRootConfig(rawConfig).root;
|
|
1393
|
-
let
|
|
1394
|
-
|
|
1395
|
-
|
|
1491
|
+
let runs;
|
|
1492
|
+
ctx.inject(["evolutionIo"], (ioCtx) => {
|
|
1493
|
+
const ioRegistry = ioCtx.get("evolutionIo");
|
|
1494
|
+
if (!ioRegistry) return;
|
|
1495
|
+
const registry = newRunRegistry({
|
|
1496
|
+
io: ioRegistry.provider(),
|
|
1497
|
+
home: evolutionHome(),
|
|
1498
|
+
warn: (message) => {
|
|
1499
|
+
ctx.logger.warn(message);
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
runs = registry;
|
|
1503
|
+
registry.load().then((loaded) => {
|
|
1504
|
+
if (!loaded.ok) ctx.logger.warn(`evolution-commands: the run index is not usable — ${loaded.note ?? "unknown reason"}`);
|
|
1505
|
+
else if (loaded.note !== void 0) ctx.logger.warn(`evolution-commands: ${loaded.note}`);
|
|
1506
|
+
}).catch((error) => {
|
|
1507
|
+
ctx.logger.warn(`evolution-commands: could not read the run index (${error instanceof Error ? error.message : String(error)})`);
|
|
1508
|
+
});
|
|
1509
|
+
ioCtx.effect(() => () => {
|
|
1510
|
+
registry.cancelAll();
|
|
1511
|
+
runs = void 0;
|
|
1512
|
+
}, "evolution-commands.run-registry");
|
|
1513
|
+
});
|
|
1396
1514
|
ctx.inject(["commands"], (commandCtx) => {
|
|
1397
1515
|
const commands = commandCtx.commands;
|
|
1398
1516
|
const commandBody = { run: async (invocation) => {
|
|
@@ -1670,57 +1788,155 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1670
1788
|
if (maintainArgs) {
|
|
1671
1789
|
const runTimeoutMs = maintainArgs[1] ? Number(maintainArgs[1]) : config.maintainTimeoutMs ?? 6e5;
|
|
1672
1790
|
if (!Number.isSafeInteger(runTimeoutMs) || runTimeoutMs <= 0 || runTimeoutMs > MAX_TIMER_DELAY_MS) return err(maintainArgs[1] ? `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.`);
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const
|
|
1676
|
-
if (
|
|
1791
|
+
const registry = runs;
|
|
1792
|
+
if (!registry) return err("Evolution IO registry not mounted — maintenance scan unavailable.");
|
|
1793
|
+
const synced = await syncRuns(registry);
|
|
1794
|
+
if (!synced.ok) ctx.logger.warn(`evolution-commands: ${synced.note ?? "run index unreadable"} — the in-flight guard cannot see other processes' runs`);
|
|
1795
|
+
else if (synced.note !== void 0) ctx.logger.warn(`evolution-commands: ${synced.note}`);
|
|
1677
1796
|
const ioRegistry = ctx.get("evolutionIo");
|
|
1678
1797
|
const subagents = ctx.get("subagents");
|
|
1679
1798
|
if (!ioRegistry) return err("Evolution IO registry not mounted — maintenance scan unavailable.");
|
|
1680
1799
|
if (!subagents) return err("Subagents service not mounted — maintenance scan unavailable.");
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
const
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1800
|
+
const running = registry.inFlight();
|
|
1801
|
+
if (running) {
|
|
1802
|
+
const elapsed = Math.max(1, Math.round((Date.now() - running.startedAt) / 1e3));
|
|
1803
|
+
return err(`Maintenance run ${running.id} is already running (started ~${elapsed}s ago) — re-submitting now would start a second scan. Status: /evolution maintain status ${running.id}; stop it: /evolution maintain cancel ${running.id}.`);
|
|
1804
|
+
}
|
|
1805
|
+
const cooldownMs = clampedNumber(config.maintainCooldownMs, 3e4, { min: 0 });
|
|
1806
|
+
const lastSettled = registry.lastSettledAt();
|
|
1807
|
+
const sinceLast = lastSettled === void 0 ? Number.POSITIVE_INFINITY : Date.now() - lastSettled;
|
|
1808
|
+
if (cooldownMs > 0 && sinceLast < cooldownMs) {
|
|
1809
|
+
const remaining = Math.ceil((cooldownMs - sinceLast) / 1e3);
|
|
1810
|
+
const latest = registry.runs().find((record) => record.state !== "running");
|
|
1811
|
+
return err(`Maintenance cooldown active (${remaining}s)${latest === void 0 ? "" : ` — latest run ${latest.id}`}; re-running now would spend another model call.`);
|
|
1812
|
+
}
|
|
1813
|
+
const runHome = evolutionHome();
|
|
1814
|
+
const runRoot = evolutionRoot();
|
|
1815
|
+
const handle = registry.begin("maintain");
|
|
1816
|
+
(async () => {
|
|
1817
|
+
try {
|
|
1818
|
+
const library = newSkillLibrary({
|
|
1819
|
+
config: { root: skillsRootValue },
|
|
1820
|
+
io: ioRegistry.provider()
|
|
1821
|
+
});
|
|
1822
|
+
const enrichment = await buildEnrichment(ctx, library);
|
|
1823
|
+
const outcome = await runMaintain({
|
|
1824
|
+
library,
|
|
1825
|
+
subagents,
|
|
1826
|
+
parent: invocation.agent,
|
|
1827
|
+
evolutionPolicy: { get: () => ctx.get("evolutionPolicy")?.get() },
|
|
1828
|
+
tools: { get: (name) => ctx.get("tools")?.get(name) },
|
|
1829
|
+
logger: { warn: (message) => {
|
|
1830
|
+
ctx.logger.warn(message);
|
|
1831
|
+
} },
|
|
1832
|
+
rootExists: skillsRootValue === "" ? void 0 : () => ioRegistry.provider().exists(skillsRootValue),
|
|
1833
|
+
skillRoot: skillsRootValue
|
|
1834
|
+
}, {
|
|
1835
|
+
timeoutMs: runTimeoutMs,
|
|
1836
|
+
signal: handle.signal,
|
|
1837
|
+
runId: handle.id,
|
|
1838
|
+
enrichment: () => enrichment
|
|
1839
|
+
});
|
|
1840
|
+
const endedAt = Date.now();
|
|
1841
|
+
const resultRef = await writeMaintainReport(ioRegistry.provider(), {
|
|
1842
|
+
runId: handle.id,
|
|
1843
|
+
...outcome.ok ? {
|
|
1844
|
+
verdict: outcome.verdict,
|
|
1845
|
+
recommendationCount: outcome.recommendationCount
|
|
1846
|
+
} : {},
|
|
1847
|
+
...outcome.ok ? {} : { failure: handle.signal.aborted ? "cancelled: the scan was stopped before it settled" : outcome.error ?? "Maintenance scan failed." },
|
|
1848
|
+
startedAt: handle.startedAt,
|
|
1849
|
+
endedAt,
|
|
1850
|
+
text: outcome.text ?? ""
|
|
1851
|
+
}, (message) => {
|
|
1695
1852
|
ctx.logger.warn(message);
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1853
|
+
}, runHome);
|
|
1854
|
+
const state = outcome.ok ? "succeeded" : handle.signal.aborted ? "cancelled" : "failed";
|
|
1855
|
+
await registry.settle(handle.id, {
|
|
1856
|
+
state,
|
|
1857
|
+
...resultRef === void 0 ? {} : { resultRef },
|
|
1858
|
+
...outcome.ok ? {} : { failure: handle.signal.aborted ? "cancelled: the scan was stopped before it settled" : outcome.error ?? "Maintenance scan failed." }
|
|
1859
|
+
});
|
|
1860
|
+
if (outcome.ok) appendEvolutionEvent(ioRegistry.provider(), eventsFile(runRoot), {
|
|
1861
|
+
type: "maintain",
|
|
1862
|
+
source: "manual",
|
|
1863
|
+
runId: handle.id,
|
|
1864
|
+
verdict: outcome.verdict,
|
|
1865
|
+
recommendations: outcome.recommendationCount
|
|
1866
|
+
}).catch((error) => {
|
|
1867
|
+
ctx.logger.warn(`evolution-commands: failed to record maintain event: ${String(error)}`);
|
|
1868
|
+
});
|
|
1869
|
+
else ctx.logger.warn(`evolution-commands: maintenance run ${handle.id} produced no plan (${outcome.error ?? "unknown reason"})`);
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
const aborted = handle.signal.aborted;
|
|
1872
|
+
const failure = aborted ? "cancelled: the scan was stopped before it settled" : `Maintenance scan failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1873
|
+
await registry.settle(handle.id, {
|
|
1874
|
+
state: aborted ? "cancelled" : "failed",
|
|
1875
|
+
failure
|
|
1876
|
+
});
|
|
1877
|
+
ctx.logger.warn(`evolution-commands: maintenance run ${handle.id} threw (${failure})`);
|
|
1878
|
+
}
|
|
1879
|
+
})();
|
|
1880
|
+
return ok([
|
|
1881
|
+
`Maintenance run ${handle.id} started — the scan keeps running after this reply.`,
|
|
1882
|
+
`- status: /evolution maintain status ${handle.id}`,
|
|
1883
|
+
`- result: /evolution maintain report ${handle.id}`,
|
|
1884
|
+
`- stop: /evolution maintain cancel ${handle.id}`
|
|
1885
|
+
].join("\n"));
|
|
1886
|
+
}
|
|
1887
|
+
const maintainStatus = /^maintain\s+status(?:\s+(\S+))?\s*$/.exec(input);
|
|
1888
|
+
if (maintainStatus) {
|
|
1889
|
+
const registry = runs;
|
|
1890
|
+
if (!registry) return err("Evolution IO registry not mounted — run status unavailable.");
|
|
1891
|
+
const synced = await syncRuns(registry);
|
|
1892
|
+
const wanted = maintainStatus[1];
|
|
1893
|
+
if (wanted !== void 0) {
|
|
1894
|
+
const record = registry.find(wanted);
|
|
1895
|
+
if (!record) {
|
|
1896
|
+
if (!synced.ok) return err(`Run ${wanted} is not in this process's history, and the home's run index is not readable (${synced.note ?? "unknown reason"}) — that is NOT proof the run never happened.`);
|
|
1897
|
+
return err(`No run ${wanted} in this home — /evolution maintain status lists the recent ones.`);
|
|
1898
|
+
}
|
|
1899
|
+
return ok(synced.ok ? renderRunRecord(record) : `${renderRunRecord(record)}\n(index not readable: ${synced.note ?? "unknown reason"} — only this process's view is shown)`);
|
|
1900
|
+
}
|
|
1901
|
+
if (!synced.ok) return err(`The run history of this home is not readable (${synced.note ?? "unknown reason"}) — this is NOT "no runs". Fix or remove <evolutionHome>/runs.json to start a clean history.`);
|
|
1902
|
+
const all = registry.runs();
|
|
1903
|
+
if (all.length === 0) return ok("No maintenance run recorded in this home yet — /evolution maintain starts one.");
|
|
1904
|
+
const live = all.filter((record) => record.state === "running");
|
|
1905
|
+
const recent = all.filter((record) => record.state !== "running").slice(0, 5);
|
|
1906
|
+
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"));
|
|
1907
|
+
}
|
|
1908
|
+
const maintainReport = /^maintain\s+report\s+(\S+)\s*$/.exec(input);
|
|
1909
|
+
if (maintainReport) {
|
|
1910
|
+
const registry = runs;
|
|
1911
|
+
if (!registry) return err("Evolution IO registry not mounted — run results unavailable.");
|
|
1912
|
+
await syncRuns(registry);
|
|
1913
|
+
const wanted = maintainReport[1];
|
|
1914
|
+
if (wanted === void 0) return err("maintain report needs a run id: /evolution maintain report <id>.");
|
|
1915
|
+
const record = registry.find(wanted);
|
|
1916
|
+
if (!record) return err(`No run ${wanted} in this home — /evolution maintain status lists the recent ones.`);
|
|
1917
|
+
const ioRegistry = ctx.get("evolutionIo");
|
|
1918
|
+
if (!ioRegistry) return err("Evolution IO registry not mounted — run results unavailable.");
|
|
1919
|
+
const probed = await readRunReport(ioRegistry.provider(), record);
|
|
1920
|
+
if (probed.kind === "missing") return err(`Run ${record.id} (${record.state}) has NO result: no report was written${record.failure === void 0 ? "" : ` (${record.failure})`}.`);
|
|
1921
|
+
if (probed.kind === "unknown") return err(`Run ${record.id} has a report that could not be read (${probed.reason}) — this is not "no result".`);
|
|
1922
|
+
return ok(probed.text);
|
|
1923
|
+
}
|
|
1924
|
+
const maintainCancel = /^maintain\s+cancel\s+(\S+)\s*$/.exec(input);
|
|
1925
|
+
if (maintainCancel) {
|
|
1926
|
+
const registry = runs;
|
|
1927
|
+
if (!registry) return err("Evolution IO registry not mounted — run cancellation unavailable.");
|
|
1928
|
+
await syncRuns(registry);
|
|
1929
|
+
const wanted = maintainCancel[1];
|
|
1930
|
+
if (wanted === void 0) return err("maintain cancel needs a run id: /evolution maintain cancel <id>.");
|
|
1931
|
+
const record = registry.find(wanted);
|
|
1932
|
+
if (!record) return err(`No run ${wanted} in this home — /evolution maintain status lists the recent ones.`);
|
|
1933
|
+
if (!registry.cancel(record.id)) {
|
|
1934
|
+
if (record.state === "running") return err(`Run ${record.id} is running in ANOTHER process sharing this home — it can only be cancelled there.`);
|
|
1935
|
+
return err(`Run ${record.id} is already ${record.state} — nothing to cancel.`);
|
|
1721
1936
|
}
|
|
1937
|
+
return ok(`Maintenance run ${record.id} cancelled — the scan stops at its next checkpoint. /evolution maintain status ${record.id} shows the terminal state.`);
|
|
1722
1938
|
}
|
|
1723
|
-
if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected
|
|
1939
|
+
if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected `maintain`, `maintain --timeout <ms>` / `maintain --timeout=<ms>`, `maintain status [<id>]`, `maintain report <id>` or `maintain cancel <id>`. Got: " + input);
|
|
1724
1940
|
const presetInstall = /^preset install(?: --base (\S+))?$/.exec(input);
|
|
1725
1941
|
if (presetInstall !== null || /^preset\b/.test(input)) {
|
|
1726
1942
|
if (presetInstall === null) return err("Unknown preset arguments: expected `preset install` or `preset install --base <name>[,<name>...]` (the names live in the agent package's bases.json). Got: " + input);
|
package/lib/types/index.d.ts
CHANGED
|
@@ -24,10 +24,11 @@ export interface Config {
|
|
|
24
24
|
* of dropping it silently; the plugin never reads it as a root. */
|
|
25
25
|
skillsRoot?: string | undefined;
|
|
26
26
|
/** Cooldown window for scan commands (ms) — misclick/rapid-trigger guard;
|
|
27
|
-
* secondary calls inside the window
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* in-flight runs
|
|
27
|
+
* secondary calls inside the window are refused instead of spending another
|
|
28
|
+
* model call. Default 30s (0.3.5). NOTE: the window starts AFTER a run settles
|
|
29
|
+
* (0.19.0/S2: the run registry's newest terminal time) — it does NOT dedupe
|
|
30
|
+
* in-flight runs; that refusal belongs to the registry's in-flight query, not
|
|
31
|
+
* to this window (the old 130s ">= timeout" rationale was a comment bug).
|
|
31
32
|
* Transient (per-process). */
|
|
32
33
|
maintainCooldownMs?: number | undefined;
|
|
33
34
|
/** Subagent deadline for one maintenance scan (ms). Default 600s (0.3.10):
|
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.19.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,12 +27,12 @@
|
|
|
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.19.0",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.19.0",
|
|
32
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.19.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.
|
|
35
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.19.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|