@minonl/dia 0.0.7 → 0.0.9
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/cli.js +241 -44
- package/docs/cli.md +2 -2
- package/docs/msg-collab-m1.md +14 -0
- package/docs/service-noun.md +76 -0
- package/docs/status.md +1 -0
- package/package.json +1 -1
- package/packages/wt/bin/dia-wt +158 -101
- package/packages/wt/tests/adopt.test.sh +67 -143
package/dist/cli.js
CHANGED
|
@@ -2098,6 +2098,9 @@ function install(from, opts = {}) {
|
|
|
2098
2098
|
const kitRoot = resolve5(target, kitRootRel);
|
|
2099
2099
|
if (!kitRoot.startsWith(target + sep4) && kitRoot !== target)
|
|
2100
2100
|
throw new Error("E_MANIFEST: root escapes kit");
|
|
2101
|
+
const hasContent = ["files", "skills", "bin", "dirs", "knowledge", "commands", "config"].some((k) => (m[k]?.length ?? 0) > 0);
|
|
2102
|
+
if (!hasContent)
|
|
2103
|
+
throw new Error("E_MANIFEST: kit 未声明任何内容(files/skills/bin/dirs/knowledge/commands/config)");
|
|
2101
2104
|
const planned = renderContent(src.dir, target, m, { dryRun: true }) ?? [];
|
|
2102
2105
|
if (opts.dryRun)
|
|
2103
2106
|
return { id: m.id, version: m.version, dir: target, root: kitRoot, rendered: planned, hash: null, changed: false };
|
|
@@ -2110,6 +2113,10 @@ function install(from, opts = {}) {
|
|
|
2110
2113
|
const rendered = renderContent(src.dir, target, m) ?? planned;
|
|
2111
2114
|
const appliedConfig = apply(m.config ?? []);
|
|
2112
2115
|
const hash = hashTree(target);
|
|
2116
|
+
if (hash === "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") {
|
|
2117
|
+
rmSync3(target, { recursive: true, force: true });
|
|
2118
|
+
throw new Error(`E_KIT_EMPTY: kit ${m.id}@${m.version} 内容为空(检查 source 与清单路径)`);
|
|
2119
|
+
}
|
|
2113
2120
|
writeFileSync5(join10(target, "kit.lock"), JSON.stringify({
|
|
2114
2121
|
id: m.id,
|
|
2115
2122
|
version: m.version,
|
|
@@ -4713,6 +4720,67 @@ function build(opts = {}) {
|
|
|
4713
4720
|
return { schema: 1, nodes: filteredNodes, edges: filteredEdges, cycles: findCycles(tasks) };
|
|
4714
4721
|
}
|
|
4715
4722
|
|
|
4723
|
+
// src/graph-tui.ts
|
|
4724
|
+
var ORDER = ["doing", "blocked", "review", "todo", "awaiting_acceptance", "accepted", "done", "dropped", "rejected", "escalated"];
|
|
4725
|
+
var color = (s, code, on) => on ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
4726
|
+
function render2(g, opts = {}) {
|
|
4727
|
+
const on = opts.color ?? false;
|
|
4728
|
+
const tasks = g.nodes.filter((n) => n.kind === "task");
|
|
4729
|
+
const taskById = new Map(tasks.map((t) => [t.id, t]));
|
|
4730
|
+
const edgesFrom = (id, kind) => g.edges.filter((e) => e.from === id && e.kind === kind);
|
|
4731
|
+
const lines = [];
|
|
4732
|
+
const groups = new Map;
|
|
4733
|
+
for (const t of tasks) {
|
|
4734
|
+
const st = t.status || "todo";
|
|
4735
|
+
groups.set(st, [...groups.get(st) || [], t]);
|
|
4736
|
+
}
|
|
4737
|
+
for (const st of [...ORDER.filter((s) => groups.has(s)), ...[...groups.keys()].filter((s) => !ORDER.includes(s))]) {
|
|
4738
|
+
lines.push(color(`[${st}]`, "1;36", on));
|
|
4739
|
+
for (const t of groups.get(st).sort((a, b) => a.label.localeCompare(b.label))) {
|
|
4740
|
+
lines.push(` ${color(t.label, "1", on)} ${color(t.id.replace(/^task:/, ""), "2", on)}${t.owner ? ` owner=${t.owner}` : ""}`);
|
|
4741
|
+
for (const e of edgesFrom(t.id, "depends_on")) {
|
|
4742
|
+
const dep = taskById.get(e.to);
|
|
4743
|
+
lines.push(` ${color("↳ depends", "34", on)} ${dep?.label || e.to}${dep ? ` [${dep.status}]` : ""}`);
|
|
4744
|
+
}
|
|
4745
|
+
for (const e of edgesFrom(t.id, "touches"))
|
|
4746
|
+
lines.push(` ${color("↳ touches", "35", on)} ${e.to.replace(/^path:/, "")}`);
|
|
4747
|
+
if (t.blocked_by?.length)
|
|
4748
|
+
lines.push(` ${color(`⚠ blocked_by ${t.blocked_by.join(", ")}`, "31", on)}`);
|
|
4749
|
+
}
|
|
4750
|
+
lines.push("");
|
|
4751
|
+
}
|
|
4752
|
+
if (!tasks.length)
|
|
4753
|
+
lines.push("(no tasks)");
|
|
4754
|
+
const claimEdges = g.edges.filter((e) => e.kind === "claims");
|
|
4755
|
+
if (claimEdges.length) {
|
|
4756
|
+
lines.push(color("claims", "1;36", on));
|
|
4757
|
+
for (const e of claimEdges)
|
|
4758
|
+
lines.push(` ${e.from.replace(/^session:/, "")} → ${e.to.replace(/^path:/, "")}`);
|
|
4759
|
+
lines.push("");
|
|
4760
|
+
}
|
|
4761
|
+
for (const c of g.cycles)
|
|
4762
|
+
lines.push(color(`⚠ cycle: ${c.join(" -> ")}`, "31", on));
|
|
4763
|
+
return lines.join(`
|
|
4764
|
+
`);
|
|
4765
|
+
}
|
|
4766
|
+
function watch(build, opts = {}) {
|
|
4767
|
+
const paint = () => process.stdout.write("\x1B[2J\x1B[H" + render2(build(), { color: opts.color ?? process.stdout.isTTY }) + `
|
|
4768
|
+
`);
|
|
4769
|
+
paint();
|
|
4770
|
+
const timer = setInterval(() => {
|
|
4771
|
+
try {
|
|
4772
|
+
paint();
|
|
4773
|
+
} catch {}
|
|
4774
|
+
}, opts.intervalMs ?? 2000);
|
|
4775
|
+
const stop = () => {
|
|
4776
|
+
clearInterval(timer);
|
|
4777
|
+
process.exit(0);
|
|
4778
|
+
};
|
|
4779
|
+
process.on("SIGINT", stop);
|
|
4780
|
+
process.on("SIGTERM", stop);
|
|
4781
|
+
return new Promise(() => {});
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4716
4784
|
// packages/cli/src/index.ts
|
|
4717
4785
|
init_paths();
|
|
4718
4786
|
|
|
@@ -5137,7 +5205,7 @@ function scopeEnv(s, extra = {}) {
|
|
|
5137
5205
|
}
|
|
5138
5206
|
|
|
5139
5207
|
// src/doctor.ts
|
|
5140
|
-
import { existsSync as existsSync22 } from "node:fs";
|
|
5208
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "node:fs";
|
|
5141
5209
|
import { homedir as homedir12 } from "node:os";
|
|
5142
5210
|
import { join as join25, resolve as resolve13 } from "node:path";
|
|
5143
5211
|
|
|
@@ -5484,6 +5552,25 @@ function jobRootResolved() {
|
|
|
5484
5552
|
const legacy = join24(homedir11(), ".local", "share", "dia-job");
|
|
5485
5553
|
return !existsSync21(next) && existsSync21(legacy) ? legacy : next;
|
|
5486
5554
|
}
|
|
5555
|
+
function runJobCapture(argv) {
|
|
5556
|
+
const { bin, error } = jobBin();
|
|
5557
|
+
if (!bin) {
|
|
5558
|
+
process.stderr.write(`dia job: ${error || "未找到 dia-job;先跑 packages/job/install.sh 或设置 DIA_JOB_BIN"}
|
|
5559
|
+
`);
|
|
5560
|
+
return { status: 2, stdout: "", stderr: "" };
|
|
5561
|
+
}
|
|
5562
|
+
const r = spawnSync2(bin, argv, {
|
|
5563
|
+
encoding: "utf8",
|
|
5564
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
5565
|
+
env: { ...process.env, DIA_JOB_ROOT: jobRootResolved() }
|
|
5566
|
+
});
|
|
5567
|
+
if (r.error) {
|
|
5568
|
+
process.stderr.write(`dia job: ${r.error.message}
|
|
5569
|
+
`);
|
|
5570
|
+
return { status: 1, stdout: "", stderr: "" };
|
|
5571
|
+
}
|
|
5572
|
+
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
5573
|
+
}
|
|
5487
5574
|
function runJob(argv) {
|
|
5488
5575
|
const { bin, error } = jobBin();
|
|
5489
5576
|
if (!bin) {
|
|
@@ -5579,17 +5666,20 @@ async function personaChecks(id, prefix, shared) {
|
|
|
5579
5666
|
} catch (e) {
|
|
5580
5667
|
checks.push({ name: `${prefix}persona.render`, status: "ok", detail: `跳过(${e?.message || String(e)})` });
|
|
5581
5668
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5669
|
+
let refs = [];
|
|
5670
|
+
try {
|
|
5671
|
+
refs = JSON.parse(readFileSync19(join25(sc.dir, "dia.lock"), "utf8")).kits ?? [];
|
|
5672
|
+
} catch {
|
|
5673
|
+
refs = [];
|
|
5674
|
+
}
|
|
5675
|
+
if (!refs.length)
|
|
5584
5676
|
checks.push({ name: `${prefix}kits`, status: "ok", detail: "无独立 kit(内容由 persona 自带)" });
|
|
5585
|
-
for (const k of
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
}
|
|
5591
|
-
checks.push({ name: `${prefix}kit:${k.id}@${v}`, status: "fail", detail: e?.message });
|
|
5592
|
-
}
|
|
5677
|
+
for (const k of refs) {
|
|
5678
|
+
try {
|
|
5679
|
+
const r = verify2(k.id, k.version);
|
|
5680
|
+
checks.push({ name: `${prefix}kit:${k.id}@${k.version}`, status: r.ok ? "ok" : "fail", detail: r.ok ? "一致" : r.drift.join("; ") });
|
|
5681
|
+
} catch (e) {
|
|
5682
|
+
checks.push({ name: `${prefix}kit:${k.id}@${k.version}`, status: "fail", detail: e?.message });
|
|
5593
5683
|
}
|
|
5594
5684
|
}
|
|
5595
5685
|
if (sc.knowledgeRoot) {
|
|
@@ -5672,7 +5762,7 @@ async function run(opts = {}) {
|
|
|
5672
5762
|
}
|
|
5673
5763
|
|
|
5674
5764
|
// src/env.ts
|
|
5675
|
-
import { existsSync as existsSync23, readFileSync as
|
|
5765
|
+
import { existsSync as existsSync23, readFileSync as readFileSync20 } from "node:fs";
|
|
5676
5766
|
import { homedir as homedir13 } from "node:os";
|
|
5677
5767
|
import { join as join26 } from "node:path";
|
|
5678
5768
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -5687,7 +5777,7 @@ function parseFile(path) {
|
|
|
5687
5777
|
const out = {};
|
|
5688
5778
|
let text = "";
|
|
5689
5779
|
try {
|
|
5690
|
-
text =
|
|
5780
|
+
text = readFileSync20(path, "utf8");
|
|
5691
5781
|
} catch {
|
|
5692
5782
|
return out;
|
|
5693
5783
|
}
|
|
@@ -5757,7 +5847,7 @@ function run2(cfg, cmd, opts = {}) {
|
|
|
5757
5847
|
}
|
|
5758
5848
|
|
|
5759
5849
|
// src/bw.ts
|
|
5760
|
-
import { chmodSync as chmodSync4, existsSync as existsSync24, mkdirSync as mkdirSync17, readFileSync as
|
|
5850
|
+
import { chmodSync as chmodSync4, existsSync as existsSync24, mkdirSync as mkdirSync17, readFileSync as readFileSync21, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "node:fs";
|
|
5761
5851
|
init_paths();
|
|
5762
5852
|
import { join as join27 } from "node:path";
|
|
5763
5853
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
@@ -5770,7 +5860,7 @@ function sessionFile2() {
|
|
|
5770
5860
|
}
|
|
5771
5861
|
function readSession2() {
|
|
5772
5862
|
try {
|
|
5773
|
-
const s =
|
|
5863
|
+
const s = readFileSync21(sessionFile2(), "utf8").trim();
|
|
5774
5864
|
return s || null;
|
|
5775
5865
|
} catch {
|
|
5776
5866
|
return null;
|
|
@@ -6013,17 +6103,17 @@ function askBroker(req, timeoutMs = 5000) {
|
|
|
6013
6103
|
import { execFileSync as execFileSync12, spawn as spawn2, spawnSync as spawnSync5 } from "node:child_process";
|
|
6014
6104
|
import { mkdirSync as mkdirSync20, openSync as openSync3 } from "node:fs";
|
|
6015
6105
|
import { basename as basename10, join as join32 } from "node:path";
|
|
6016
|
-
import { existsSync as existsSync29, readFileSync as
|
|
6106
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26 } from "node:fs";
|
|
6017
6107
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6018
6108
|
import { resolve as resolvePath, sep as pathSep } from "node:path";
|
|
6019
6109
|
|
|
6020
6110
|
// src/team.ts
|
|
6021
|
-
import { appendFileSync as appendFileSync8, cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync18, readFileSync as
|
|
6111
|
+
import { appendFileSync as appendFileSync8, cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync18, readFileSync as readFileSync23, readdirSync as readdirSync11, rmSync as rmSync8, writeFileSync as writeFileSync15 } from "node:fs";
|
|
6022
6112
|
import { homedir as homedir15, tmpdir as tmpdir3 } from "node:os";
|
|
6023
6113
|
import { join as join29, resolve as resolve14 } from "node:path";
|
|
6024
6114
|
|
|
6025
6115
|
// src/pricing.ts
|
|
6026
|
-
import { existsSync as existsSync25, readFileSync as
|
|
6116
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
|
|
6027
6117
|
import { homedir as homedir14 } from "node:os";
|
|
6028
6118
|
import { join as join28 } from "node:path";
|
|
6029
6119
|
var DEFAULT = {
|
|
@@ -6038,7 +6128,7 @@ function loadPrices() {
|
|
|
6038
6128
|
if (!existsSync25(pricesFile()))
|
|
6039
6129
|
return { ...DEFAULT };
|
|
6040
6130
|
try {
|
|
6041
|
-
const user = JSON.parse(
|
|
6131
|
+
const user = JSON.parse(readFileSync22(pricesFile(), "utf8"));
|
|
6042
6132
|
return { ...DEFAULT, ...user };
|
|
6043
6133
|
} catch {
|
|
6044
6134
|
return { ...DEFAULT };
|
|
@@ -6111,7 +6201,7 @@ function writeUsage(dir, id, acc) {
|
|
|
6111
6201
|
}
|
|
6112
6202
|
function judgeWorkspace(dir, fallback) {
|
|
6113
6203
|
try {
|
|
6114
|
-
const run = JSON.parse(
|
|
6204
|
+
const run = JSON.parse(readFileSync23(join29(dir, "run.json"), "utf8"));
|
|
6115
6205
|
const ws = String(run?.workspace || "");
|
|
6116
6206
|
if (ws)
|
|
6117
6207
|
return ws;
|
|
@@ -6126,17 +6216,17 @@ function gate(id) {
|
|
|
6126
6216
|
return { ok: false, reason: "missing: " + missing.join(",") };
|
|
6127
6217
|
let verify, redteam, run;
|
|
6128
6218
|
try {
|
|
6129
|
-
verify = JSON.parse(
|
|
6219
|
+
verify = JSON.parse(readFileSync23(join29(dir, "verify.json"), "utf8"));
|
|
6130
6220
|
} catch {
|
|
6131
6221
|
return { ok: false, reason: "bad verify.json" };
|
|
6132
6222
|
}
|
|
6133
6223
|
try {
|
|
6134
|
-
redteam = JSON.parse(
|
|
6224
|
+
redteam = JSON.parse(readFileSync23(join29(dir, "redteam.json"), "utf8"));
|
|
6135
6225
|
} catch {
|
|
6136
6226
|
return { ok: false, reason: "bad redteam.json" };
|
|
6137
6227
|
}
|
|
6138
6228
|
try {
|
|
6139
|
-
run = JSON.parse(
|
|
6229
|
+
run = JSON.parse(readFileSync23(join29(dir, "run.json"), "utf8"));
|
|
6140
6230
|
} catch {
|
|
6141
6231
|
return { ok: false, reason: "bad run.json" };
|
|
6142
6232
|
}
|
|
@@ -6282,7 +6372,7 @@ function snapPath(id, dir, tag) {
|
|
|
6282
6372
|
const f = join29(dir, "snapshots.json");
|
|
6283
6373
|
let list = [];
|
|
6284
6374
|
try {
|
|
6285
|
-
list = JSON.parse(
|
|
6375
|
+
list = JSON.parse(readFileSync23(f, "utf8"));
|
|
6286
6376
|
} catch {
|
|
6287
6377
|
list = [];
|
|
6288
6378
|
}
|
|
@@ -6305,7 +6395,7 @@ function cleanupSnapshots(runDir) {
|
|
|
6305
6395
|
if (process.env.DIA_KEEP_SNAPSHOTS === "1")
|
|
6306
6396
|
return;
|
|
6307
6397
|
try {
|
|
6308
|
-
for (const p of JSON.parse(
|
|
6398
|
+
for (const p of JSON.parse(readFileSync23(join29(runDir, "snapshots.json"), "utf8"))) {
|
|
6309
6399
|
if (resolve14(p).startsWith(resolve14(tmpdir3()) + "/"))
|
|
6310
6400
|
rmSync8(p, { recursive: true, force: true });
|
|
6311
6401
|
}
|
|
@@ -6392,7 +6482,7 @@ function gitDiff(ws) {
|
|
|
6392
6482
|
const untracked = execFileSync11("git", ["-C", ws, "ls-files", "--others", "--exclude-standard"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split(`
|
|
6393
6483
|
`).filter(Boolean);
|
|
6394
6484
|
for (const f of untracked) {
|
|
6395
|
-
const body =
|
|
6485
|
+
const body = readFileSync23(join29(ws, f), "utf8");
|
|
6396
6486
|
out += `
|
|
6397
6487
|
--- /dev/null
|
|
6398
6488
|
+++ b/${f}
|
|
@@ -6585,7 +6675,7 @@ function dshSessionFile(sessionId) {
|
|
|
6585
6675
|
return null;
|
|
6586
6676
|
}
|
|
6587
6677
|
function dshUsages(file) {
|
|
6588
|
-
const raw = zstdDecompressSync2(
|
|
6678
|
+
const raw = zstdDecompressSync2(readFileSync23(file)).toString("utf8");
|
|
6589
6679
|
const out = [];
|
|
6590
6680
|
for (const line of raw.split(`
|
|
6591
6681
|
`)) {
|
|
@@ -6869,7 +6959,7 @@ function reject(id, reason) {
|
|
|
6869
6959
|
var DEFAULT_ACP_TIMEOUT = 600000;
|
|
6870
6960
|
|
|
6871
6961
|
// src/proposal.ts
|
|
6872
|
-
import { existsSync as existsSync27, readFileSync as
|
|
6962
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync12 } from "node:fs";
|
|
6873
6963
|
import { join as join30 } from "node:path";
|
|
6874
6964
|
import { createHash as createHash10 } from "node:crypto";
|
|
6875
6965
|
init_paths();
|
|
@@ -6885,7 +6975,7 @@ function push(out, p) {
|
|
|
6885
6975
|
}
|
|
6886
6976
|
function readJson3(path) {
|
|
6887
6977
|
try {
|
|
6888
|
-
return JSON.parse(
|
|
6978
|
+
return JSON.parse(readFileSync24(path, "utf8"));
|
|
6889
6979
|
} catch {
|
|
6890
6980
|
return null;
|
|
6891
6981
|
}
|
|
@@ -6971,7 +7061,7 @@ function expandIds(cell) {
|
|
|
6971
7061
|
function collectHypotheses(out, path) {
|
|
6972
7062
|
if (!existsSync27(path))
|
|
6973
7063
|
return;
|
|
6974
|
-
const lines =
|
|
7064
|
+
const lines = readFileSync24(path, "utf8").split(`
|
|
6975
7065
|
`);
|
|
6976
7066
|
const titles = new Map;
|
|
6977
7067
|
for (const line of lines) {
|
|
@@ -7014,7 +7104,7 @@ import {
|
|
|
7014
7104
|
existsSync as existsSync28,
|
|
7015
7105
|
lstatSync as lstatSync3,
|
|
7016
7106
|
mkdirSync as mkdirSync19,
|
|
7017
|
-
readFileSync as
|
|
7107
|
+
readFileSync as readFileSync25,
|
|
7018
7108
|
readdirSync as readdirSync13,
|
|
7019
7109
|
readlinkSync as readlinkSync2,
|
|
7020
7110
|
rmSync as rmSync9,
|
|
@@ -7153,7 +7243,7 @@ function readServiceManifest(dir) {
|
|
|
7153
7243
|
throw new Error(`E_NO_MANIFEST: dia.json not found in ${dir}(adopted 服务需先补 dia.json)`);
|
|
7154
7244
|
let raw;
|
|
7155
7245
|
try {
|
|
7156
|
-
raw = JSON.parse(
|
|
7246
|
+
raw = JSON.parse(readFileSync25(p, "utf8"));
|
|
7157
7247
|
} catch {
|
|
7158
7248
|
throw new Error("E_MANIFEST: invalid json");
|
|
7159
7249
|
}
|
|
@@ -7194,7 +7284,7 @@ function isSymlink2(p) {
|
|
|
7194
7284
|
return false;
|
|
7195
7285
|
}
|
|
7196
7286
|
}
|
|
7197
|
-
var shaFile3 = (p) => createHash11("sha256").update(
|
|
7287
|
+
var shaFile3 = (p) => createHash11("sha256").update(readFileSync25(p)).digest("hex");
|
|
7198
7288
|
var UNKNOWN = "unknown";
|
|
7199
7289
|
var SIGNALS = { 6: "SIGABRT", 9: "SIGKILL", 11: "SIGSEGV", 13: "SIGPIPE", 15: "SIGTERM" };
|
|
7200
7290
|
function judgeHealth(exit, opts = {}) {
|
|
@@ -7227,7 +7317,18 @@ var STALE_FACTOR = 1.5;
|
|
|
7227
7317
|
var MIN_HEALTH_INTERVAL = 60;
|
|
7228
7318
|
var healthInterval = (hc) => hc?.interval ?? DEFAULT_HEALTH_INTERVAL;
|
|
7229
7319
|
var staleAfter = (hc) => Math.floor(healthInterval(hc) * STALE_FACTOR);
|
|
7230
|
-
var isRecord = (v) => !!v && typeof v === "object" && v.kind === "health" &&
|
|
7320
|
+
var isRecord = (v) => !!v && typeof v === "object" && v.kind === "health" && (v.state === "ok" || v.state === "degraded" || v.state === "fail");
|
|
7321
|
+
var TS_RE = /"ts"\s*:\s*(-?\d+)/;
|
|
7322
|
+
var KIND_RE = /"kind"\s*:\s*"([A-Za-z0-9_-]+)"/;
|
|
7323
|
+
function parseStateLine(line) {
|
|
7324
|
+
const head = line.slice(0, 256), tail = line.slice(-256);
|
|
7325
|
+
const ts = TS_RE.exec(head)?.[1] ?? TS_RE.exec(tail)?.[1];
|
|
7326
|
+
const kind = KIND_RE.exec(head)?.[1] ?? KIND_RE.exec(tail)?.[1];
|
|
7327
|
+
if (!ts || !kind)
|
|
7328
|
+
return null;
|
|
7329
|
+
const n = Number(ts);
|
|
7330
|
+
return Number.isFinite(n) ? { ts: n, kind } : null;
|
|
7331
|
+
}
|
|
7231
7332
|
function findLastHealth(lines, service, gen) {
|
|
7232
7333
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
7233
7334
|
const r = lines[i];
|
|
@@ -7412,6 +7513,24 @@ function diaCmd() {
|
|
|
7412
7513
|
const entry = entries.find((p) => existsSync28(p));
|
|
7413
7514
|
return entry ? `${q(process.execPath)} ${q(entry)}` : "dia";
|
|
7414
7515
|
}
|
|
7516
|
+
function decorateStatus(text, view) {
|
|
7517
|
+
const lines = String(text ?? "").split(`
|
|
7518
|
+
`);
|
|
7519
|
+
const outLines = [];
|
|
7520
|
+
for (const line of lines) {
|
|
7521
|
+
outLines.push(line);
|
|
7522
|
+
const m = line.match(/local\.dia-job-svc-([A-Za-z0-9._-]+)/) || line.match(/(?:^|\s)svc-([A-Za-z0-9._-]+)\s/);
|
|
7523
|
+
if (!m)
|
|
7524
|
+
continue;
|
|
7525
|
+
try {
|
|
7526
|
+
const v = view(m[1]);
|
|
7527
|
+
if (v?.label)
|
|
7528
|
+
outLines.push(` health=${v.label}`);
|
|
7529
|
+
} catch {}
|
|
7530
|
+
}
|
|
7531
|
+
return outLines.join(`
|
|
7532
|
+
`);
|
|
7533
|
+
}
|
|
7415
7534
|
function servicesRoot() {
|
|
7416
7535
|
return process.env.DIA_SERVICE_HOME || join31(diaHome(), "services");
|
|
7417
7536
|
}
|
|
@@ -7425,7 +7544,7 @@ function readLockFile(id) {
|
|
|
7425
7544
|
if (!existsSync28(p))
|
|
7426
7545
|
throw new Error(`E_NOT_INSTALLED: service ${id}(缺 lock)`);
|
|
7427
7546
|
try {
|
|
7428
|
-
return JSON.parse(
|
|
7547
|
+
return JSON.parse(readFileSync25(p, "utf8"));
|
|
7429
7548
|
} catch {
|
|
7430
7549
|
throw new Error(`E_DRIFT: service.lock.json 不可解析(${p})`);
|
|
7431
7550
|
}
|
|
@@ -7446,15 +7565,34 @@ function readHealthLines(file) {
|
|
|
7446
7565
|
if (!existsSync28(file))
|
|
7447
7566
|
return [];
|
|
7448
7567
|
const rows = [];
|
|
7449
|
-
|
|
7450
|
-
`)
|
|
7568
|
+
const lines = readFileSync25(file, "utf8").split(`
|
|
7569
|
+
`);
|
|
7570
|
+
for (const line of lines) {
|
|
7451
7571
|
if (!line.trim())
|
|
7452
7572
|
continue;
|
|
7453
7573
|
try {
|
|
7454
7574
|
const d = JSON.parse(line);
|
|
7455
7575
|
if (isRecord(d))
|
|
7456
7576
|
rows.push(d);
|
|
7577
|
+
continue;
|
|
7457
7578
|
} catch {}
|
|
7579
|
+
const head = parseStateLine(line);
|
|
7580
|
+
const state = head ? /"state"\s*:\s*"(ok|degraded|fail)"/.exec(line)?.[1] : undefined;
|
|
7581
|
+
if (head && state) {
|
|
7582
|
+
rows.push({
|
|
7583
|
+
...head,
|
|
7584
|
+
state,
|
|
7585
|
+
service: "",
|
|
7586
|
+
gen: "",
|
|
7587
|
+
source: "schedule",
|
|
7588
|
+
candidate: state,
|
|
7589
|
+
streak: 0,
|
|
7590
|
+
raw: state,
|
|
7591
|
+
exit: null,
|
|
7592
|
+
timeout: false,
|
|
7593
|
+
detail: ""
|
|
7594
|
+
});
|
|
7595
|
+
}
|
|
7458
7596
|
}
|
|
7459
7597
|
return rows;
|
|
7460
7598
|
}
|
|
@@ -7463,6 +7601,41 @@ function appendHealthRecord(file, rec) {
|
|
|
7463
7601
|
appendFileSync9(file, JSON.stringify(rec) + `
|
|
7464
7602
|
`);
|
|
7465
7603
|
}
|
|
7604
|
+
function readServiceHealth(id, opts = {}) {
|
|
7605
|
+
const { healthFile } = opts;
|
|
7606
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
7607
|
+
const jr = opts.jobRoot ?? jobRoot2();
|
|
7608
|
+
const dir = opts.servicesRoot ?? (process.env.DIA_SERVICE_HOME || join31(jr, "services"));
|
|
7609
|
+
const installDir = join31(dir, id);
|
|
7610
|
+
let lock = null;
|
|
7611
|
+
try {
|
|
7612
|
+
lock = readLockFile(id);
|
|
7613
|
+
} catch {}
|
|
7614
|
+
let svc = null;
|
|
7615
|
+
try {
|
|
7616
|
+
svc = readServiceManifest(lock?.dir ?? installDir);
|
|
7617
|
+
} catch {}
|
|
7618
|
+
if (!svc)
|
|
7619
|
+
return healthView({ adopted: true, hasHealthcheck: false, now });
|
|
7620
|
+
const hc = svc?.healthcheck;
|
|
7621
|
+
if (!hc)
|
|
7622
|
+
return healthView({ hasHealthcheck: false, now });
|
|
7623
|
+
const ledger = healthFile ?? healthStatePath(id);
|
|
7624
|
+
const recs = readHealthLines(ledger).filter(isRecord);
|
|
7625
|
+
let rec = lock ? findLastHealth(recs, id, lock.gen) : null;
|
|
7626
|
+
if (!rec) {
|
|
7627
|
+
for (let i = recs.length - 1;i >= 0; i--) {
|
|
7628
|
+
const r = recs[i];
|
|
7629
|
+
if (r.state !== "ok" && r.state !== "degraded" && r.state !== "fail")
|
|
7630
|
+
continue;
|
|
7631
|
+
if (r.service && r.service !== id)
|
|
7632
|
+
continue;
|
|
7633
|
+
if (!rec || r.ts > rec.ts)
|
|
7634
|
+
rec = r;
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
return healthView({ record: rec, hasHealthcheck: true, hc, now });
|
|
7638
|
+
}
|
|
7466
7639
|
function listServices(opts = {}) {
|
|
7467
7640
|
const root = opts.servicesRoot ?? servicesRoot();
|
|
7468
7641
|
const jr = opts.jobRoot ?? jobRoot2();
|
|
@@ -7643,7 +7816,7 @@ function readRegistryTasks(file = registryPath2()) {
|
|
|
7643
7816
|
if (!existsSync28(file))
|
|
7644
7817
|
return null;
|
|
7645
7818
|
try {
|
|
7646
|
-
const d = JSON.parse(
|
|
7819
|
+
const d = JSON.parse(readFileSync25(file, "utf8"));
|
|
7647
7820
|
return new Set(Object.keys(d?.tasks ?? {}));
|
|
7648
7821
|
} catch {
|
|
7649
7822
|
return null;
|
|
@@ -8009,7 +8182,7 @@ function cliVersion() {
|
|
|
8009
8182
|
];
|
|
8010
8183
|
for (const u of candidates) {
|
|
8011
8184
|
try {
|
|
8012
|
-
return String(JSON.parse(
|
|
8185
|
+
return String(JSON.parse(readFileSync26(u, "utf8")).version || "0.0.0");
|
|
8013
8186
|
} catch {}
|
|
8014
8187
|
}
|
|
8015
8188
|
return "0.0.0";
|
|
@@ -8155,7 +8328,7 @@ usage:
|
|
|
8155
8328
|
dia agents list|register|unregister|discover
|
|
8156
8329
|
dia msg deliver <agent> <id> [--transport tmux|acp]
|
|
8157
8330
|
dia task add|list|link|status|done [--owner S] [--depends a,b] [--touches p1,p2]
|
|
8158
|
-
dia graph [--json] [--task t-...]
|
|
8331
|
+
dia graph [--json] [--task t-...] [--tui [--watch] [--no-color]]
|
|
8159
8332
|
dia claim <path> [--reason R] [--ttl 4h] [--session S] | dia claims [--all] [--mine] | dia unclaim <path|id> [--force]
|
|
8160
8333
|
dia team run <id> --mock | --acp|--dsh --workspace DIR
|
|
8161
8334
|
dia team gate|accept|reject|next
|
|
@@ -8548,6 +8721,19 @@ async function taskCli(argv) {
|
|
|
8548
8721
|
}
|
|
8549
8722
|
async function graphCli(argv) {
|
|
8550
8723
|
const asJson = has(argv, "--json");
|
|
8724
|
+
if (has(argv, "--tui") || has(argv, "--watch")) {
|
|
8725
|
+
const paint = (g) => out(render2(g, { color: !!process.stdout.isTTY && !has(argv, "--no-color") }));
|
|
8726
|
+
try {
|
|
8727
|
+
if (has(argv, "--watch"))
|
|
8728
|
+
return await watch(() => build({ task: flag(argv, "--task") }), { color: !!process.stdout.isTTY && !has(argv, "--no-color") });
|
|
8729
|
+
const g = build({ task: flag(argv, "--task") });
|
|
8730
|
+
paint(g);
|
|
8731
|
+
return g.cycles.length ? 3 : 0;
|
|
8732
|
+
} catch (e) {
|
|
8733
|
+
err(e?.message || String(e));
|
|
8734
|
+
return String(e?.message || e).startsWith("E_GRAPH") ? 2 : 31;
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8551
8737
|
try {
|
|
8552
8738
|
const g = build({ task: flag(argv, "--task") });
|
|
8553
8739
|
if (asJson)
|
|
@@ -9049,7 +9235,7 @@ async function collabCli(argv) {
|
|
|
9049
9235
|
}
|
|
9050
9236
|
if (sub === "reply") {
|
|
9051
9237
|
const id = positional(args)[0];
|
|
9052
|
-
const body = flag(args, "--body") ?? (flag(args, "--body-file") ?
|
|
9238
|
+
const body = flag(args, "--body") ?? (flag(args, "--body-file") ? readFileSync26(flag(args, "--body-file"), "utf8") : undefined);
|
|
9053
9239
|
if (!id || body === undefined) {
|
|
9054
9240
|
err("usage: dia reply <id> --body B|--body-file F [--session S]");
|
|
9055
9241
|
return 2;
|
|
@@ -9766,7 +9952,7 @@ async function main(rawArgv) {
|
|
|
9766
9952
|
if (!npmManaged) {
|
|
9767
9953
|
let source = "";
|
|
9768
9954
|
try {
|
|
9769
|
-
source = String(JSON.parse(
|
|
9955
|
+
source = String(JSON.parse(readFileSync26(join32(install, "install.json"), "utf8")).source || "");
|
|
9770
9956
|
} catch {}
|
|
9771
9957
|
out(`这是脚本安装,不走 npm 升级。升级方式:`);
|
|
9772
9958
|
out(` cd ${source || "<dia 仓库>"} && git pull && bun run build && bash install.sh`);
|
|
@@ -10076,8 +10262,19 @@ async function main(rawArgv) {
|
|
|
10076
10262
|
return agentsCli(argv.slice(1));
|
|
10077
10263
|
if (cmd === "team")
|
|
10078
10264
|
return teamCli(argv.slice(1));
|
|
10079
|
-
if (cmd === "job")
|
|
10080
|
-
|
|
10265
|
+
if (cmd === "job") {
|
|
10266
|
+
const rest = argv.slice(1);
|
|
10267
|
+
if (rest[0] === "status" || rest[0] === "list") {
|
|
10268
|
+
const r = runJobCapture(rest);
|
|
10269
|
+
if (r.stderr)
|
|
10270
|
+
process.stderr.write(r.stderr);
|
|
10271
|
+
const text = decorateStatus(r.stdout, (id) => readServiceHealth(id));
|
|
10272
|
+
if (text)
|
|
10273
|
+
out(text.replace(/\n$/, ""));
|
|
10274
|
+
return r.status;
|
|
10275
|
+
}
|
|
10276
|
+
return runJob(rest);
|
|
10277
|
+
}
|
|
10081
10278
|
if (cmd === "price") {
|
|
10082
10279
|
const table = loadPrices();
|
|
10083
10280
|
if (has(argv.slice(1), "--json"))
|
package/docs/cli.md
CHANGED
|
@@ -95,11 +95,11 @@ dia persona remove dabo
|
|
|
95
95
|
| `persona update <id>` | **已实现** | 从 lock 记录的 source+ref 重装,并把 active 指向新版本;本地源亦可;`--force` 覆盖宿主/内联内容冲突(先备份) |
|
|
96
96
|
| `persona default <id>` | **已实现** | 改宿主 `default_agent`:备份原文件 + 记原值,`--unset` 精确还原;非严格 JSON 直接拒绝 |
|
|
97
97
|
| `persona create / render` | **已实现** | `create` 出骨架;`render --check` 只比对不写盘(跳过宿主自写文件,如 dsh `cordis.yml` / `package.json` / 锁文件);`render` 只覆盖 persona 产物,不整树重建宿主 profile |
|
|
98
|
-
| `kit add / list / verify / remove` | **已实现** | 复制/软链/记账;冲突写盘前全量检查(`E_CONFLICT`);`src/kit.ts` + `tests/kit.test.ts` |
|
|
98
|
+
| `kit add / list / verify / remove` | **已实现** | 复制/软链/记账;冲突写盘前全量检查(`E_CONFLICT`);**空清单/空内容拒绝**(`E_MANIFEST`/`E_KIT_EMPTY`);`src/kit.ts` + `tests/kit.test.ts` |
|
|
99
99
|
| `env list / check / run` | **已实现** | 白名单注入、不回显值、`--redact`、密钥不进 argv;清单由 persona 声明(`src/env.ts` + `tests/env.test.ts`) |
|
|
100
100
|
| `update [--check]` | **已实现** | 查 npm `dist-tags`;`--check` 有新版 exit 1、领先/最新 exit 0;**按安装方式分流**:npm 全局包 → `npm i -g`;脚本安装 → 提示 `git pull && bun run build && bash install.sh`(避免装到不在 PATH 的 npm prefix) |
|
|
101
101
|
| `deps list / record` | **已实现** | `list` = 登记 + 实测派生视图(required 缺失 exit 2);`record` = 只追加一行(`--id/--kind/--version` 必填);`src/deps.ts` + `tests/deps*.test.ts` |
|
|
102
|
-
| `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit
|
|
102
|
+
| `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit(**只查该 persona 引用的**,孤儿 kit 不拖红)/ knowledge(**索引新鲜度三态**,过期给重建命令)/ dsh preset / **deps 登记校验** / **platform 版本(读 `docs/platforms/registry.json`)** / wt;exit 0/1/2。不指定 persona 且装了多个 → **逐个自检**(条目加 `<id>.` 前缀),不再整体 `E_SCOPE` |
|
|
103
103
|
| `wt …` | **已实现(需安装包)** | 转发 `packages/wt/bin/dia-wt`(create/adopt/doctor/gate/list/status/check/clean/prune-merged;详见 [worktree.md](worktree.md));未装时 `E_WT`(exit 33),`install.sh` 会分发 |
|
|
104
104
|
| `kb index/search/query/get/lint/path/review` | **已实现** | persona 作用域下索引过期自动重建;显式 `--root` 保持「缺索引即报错」 |
|
|
105
105
|
| `kb <域命令>` | **已实现(委派)** | 未命中内置 → persona `commands` → 委派;argv 透传、exit code 原样 |
|
package/docs/msg-collab-m1.md
CHANGED
|
@@ -64,3 +64,17 @@ dia graph --json --task t-xxx
|
|
|
64
64
|
- `blocked_by` 只在 task 节点里做派生提示,不是边;
|
|
65
65
|
- 任务依赖成环:`cycles` 非空,退出码 **3**(参数错仍是 2);
|
|
66
66
|
- `--task` 只输出该任务的直接依赖/touches/claim 邻居,不做全图闭包。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# 只读 TUI 图(M2 第一步)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dia graph --tui # 打印一次(有环 exit 3)
|
|
74
|
+
dia graph --watch # 每 2s 刷新,Ctrl-C 退出
|
|
75
|
+
dia graph --tui --no-color # 关 ANSI 颜色
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- 按状态分组显示 task → deps / touches / blocked_by;claims 单列;
|
|
79
|
+
- 不启 HTTP/daemon,遵守框架“无网络模块”约束;
|
|
80
|
+
- `--task t-...` 只显示该任务的直接邻居。
|
package/docs/service-noun.md
CHANGED
|
@@ -352,3 +352,79 @@ unknown(stale)**不单独返回**——`health` 是实时执行,不存在
|
|
|
352
352
|
|
|
353
353
|
1. 判据写成**可判定的检查**:`node scripts/docs-index.mjs --check && node scripts/docs-link-check.mjs`(+ 可选 checklist 计数);
|
|
354
354
|
2. 或者把它当**评审工具**用:接受 `escalated` 是预期结果,只取 `verify.json` 的 findings 作为 grill 产出。
|
|
355
|
+
|
|
356
|
+
## 14. P2:agent 型服务(entry = persona 会话)
|
|
357
|
+
|
|
358
|
+
> 状态:**草案(待实现)**。§0 已定「本版只做脚本型」;本节把 P2 的形状先定下来,避免实现时二次发明语义。
|
|
359
|
+
|
|
360
|
+
### 14.1 与脚本型的关系
|
|
361
|
+
|
|
362
|
+
```mermaid
|
|
363
|
+
flowchart LR
|
|
364
|
+
S["service"] --> A["脚本型(已实现)<br/>entry = bin/*"]
|
|
365
|
+
S --> B["agent 型(P2)<br/>entry = ACP 会话"]
|
|
366
|
+
B --> J["job:把 ACP 端点拉起并保持<br/>(dsh --profile dia-acp / opencode acp)"]
|
|
367
|
+
B --> H["healthcheck:端点可应答 + 最近一轮成功<br/>(不判断“想得对不对”)"]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**不变的**:`lifecycle`(job 托管)、`healthcheck` 契约(专用码域)、台账(`kind:"health"`)、`gen` 代际。
|
|
371
|
+
**不同的**:没有 `stdin` 事件契约 —— 输入是 ACP 的 `prompt`,输出是会话事件;所以 `contract` 换成 ACP 语义。
|
|
372
|
+
|
|
373
|
+
### 14.2 清单
|
|
374
|
+
|
|
375
|
+
```jsonc
|
|
376
|
+
{
|
|
377
|
+
"kind": "service",
|
|
378
|
+
"id": "example-agent",
|
|
379
|
+
"entry": {
|
|
380
|
+
"agent": {
|
|
381
|
+
"host": "dsh", // dsh | opencode
|
|
382
|
+
"profile": "dia-acp", // 由 persona/宿主提供;缺省用宿主默认
|
|
383
|
+
"persona": "yan" // 可选:仅做归属校验,不注入内容
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
"lifecycle": { "type": "service", "target": "local" },
|
|
387
|
+
"healthcheck": { "kind": "acp", "interval": 1800, "timeout": 60 },
|
|
388
|
+
"kits": []
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
| 字段 | 语义 |
|
|
393
|
+
|---|---|
|
|
394
|
+
| `entry.agent.host` | 用哪套 ACP 实现(`dsh --profile <p>` 或 `opencode acp`) |
|
|
395
|
+
| `entry.agent.profile` | 宿主的 profile 名(不复制 persona 内容,只引用) |
|
|
396
|
+
| `entry.agent.persona` | 可选;`verify` 时校验「该 profile 是否属于此 persona」,防止挂错人 |
|
|
397
|
+
| `healthcheck.kind: "acp"` | 走 ACP 健康检查(见 §14.3),**不再要求 `cmd`** |
|
|
398
|
+
|
|
399
|
+
### 14.3 健康检查(ACP 版)
|
|
400
|
+
|
|
401
|
+
| 检查 | 判据 | 失败归类 |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| 端点存活 | `initialize` 能返回 `protocolVersion`(JSON-RPC 1.0) | 连不上/超时 → `degraded` |
|
|
404
|
+
| 会话可用 | `session/new` 成功返回 `sessionId` | 返回错误 → `fail` |
|
|
405
|
+
| 最近一轮 | 台账里最近一次 `prompt` 的成功/失败(由调用方写事件) | 连续失败 `debounce` 次 → `degraded` |
|
|
406
|
+
|
|
407
|
+
**明确不做**:不判断「回答质量」、不重放 prompt、不在 healthcheck 里跑真实业务(成本 + 副作用)。
|
|
408
|
+
|
|
409
|
+
### 14.4 与脚本型的复用
|
|
410
|
+
|
|
411
|
+
- `healthView` / `transition` / `gen` / 台账格式**完全复用**(`state.jsonl` + `kind:"health"`)
|
|
412
|
+
- 差异只在「怎么产生一次 judgement」:`runHealth()` 换成 `runAcpHealth()`(`initialize` + `session/new`)
|
|
413
|
+
- `dia service health <id>` 对 agent 型走 `runAcpHealth`,退出码沿用 §5
|
|
414
|
+
|
|
415
|
+
### 14.5 边界
|
|
416
|
+
|
|
417
|
+
| 场景 | 行为 |
|
|
418
|
+
|---|---|
|
|
419
|
+
| 宿主未安装 / profile 不存在 | `add` 期 `E_BACKEND`(不装半套) |
|
|
420
|
+
| 端点活着但 `session/new` 失败 | `fail`(配置/鉴权问题,不是 infra 抖动) |
|
|
421
|
+
| 长任务(一轮 prompt 很久) | **不算失败**;只有「端点不可应答」才算 |
|
|
422
|
+
| 多实例 | `lifecycle.target` 声明归属;ACP 端点同机只允许一个 |
|
|
423
|
+
| 权限/凭据 | 由宿主 profile 自己管;`dia` 不读取、不注入 |
|
|
424
|
+
|
|
425
|
+
### 14.6 验收(P2)
|
|
426
|
+
|
|
427
|
+
- [ ] 单测:清单校验(`entry.agent` 与 `entry.cmd` **互斥**,同时给 → `E_MANIFEST`)
|
|
428
|
+
- [ ] 单测:`runAcpHealth` 用 `tests/fixtures/fake-acp.mjs` 覆盖 `initialize` 失败 / `session/new` 失败 / 正常三态
|
|
429
|
+
- [ ] 单测:`healthcheck.kind` 缺省时按脚本型(向后兼容)
|
|
430
|
+
- [ ] e2e:装一个 agent 型 demo(fake ACP)→ `dia job status` 显示 `health=ok…`,杀掉端点后变 `degraded(stale)`
|