@minonl/dia 0.0.9 → 0.0.11

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 CHANGED
@@ -4392,7 +4392,7 @@ function migrateLegacy(opts = {}) {
4392
4392
 
4393
4393
  // src/claims.ts
4394
4394
  init_paths();
4395
- import { appendFileSync as appendFileSync5, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync13 } from "node:fs";
4395
+ import { appendFileSync as appendFileSync5, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync7, writeFileSync as writeFileSync11 } from "node:fs";
4396
4396
  import { execFileSync as execFileSync4 } from "node:child_process";
4397
4397
  import { randomBytes as randomBytes3 } from "node:crypto";
4398
4398
  import { basename as basename7, join as join16, relative as relative5, resolve as resolve9, sep as sep7 } from "node:path";
@@ -4510,6 +4510,58 @@ function unclaim(target, opts = {}) {
4510
4510
  return { ...hit, released_at: new Date().toISOString() };
4511
4511
  });
4512
4512
  }
4513
+ function compact(opts = {}) {
4514
+ const days = Math.max(0, Number(opts.days ?? 7));
4515
+ const now = opts.now || new Date;
4516
+ const cutoff = now.getTime() - days * 86400000;
4517
+ return withLock("claims", () => {
4518
+ const f = file();
4519
+ if (!existsSync14(f))
4520
+ return { before: 0, after: 0, dropped: 0 };
4521
+ const raw = readFileSync13(f, "utf8");
4522
+ const lines = raw.split(`
4523
+ `).filter((l) => l.trim());
4524
+ const keepIds = new Set;
4525
+ for (const line of lines) {
4526
+ let e;
4527
+ try {
4528
+ e = JSON.parse(line);
4529
+ } catch {
4530
+ continue;
4531
+ }
4532
+ if (e.event !== "claim" || !e.id)
4533
+ continue;
4534
+ const created = new Date(e.created_at || e.ts).getTime();
4535
+ if (isActive({ id: e.id, path: e.path || "", scope: e.scope || "", holder: e.holder || "", created_at: e.created_at || e.ts, expires_at: e.expires_at || e.ts }, now.getTime()) || created > cutoff) {
4536
+ keepIds.add(e.id);
4537
+ }
4538
+ }
4539
+ const kept = [];
4540
+ let dropped = 0;
4541
+ for (const line of lines) {
4542
+ let e;
4543
+ try {
4544
+ e = JSON.parse(line);
4545
+ } catch {
4546
+ dropped++;
4547
+ continue;
4548
+ }
4549
+ if (e.id && keepIds.has(e.id))
4550
+ kept.push(line);
4551
+ else
4552
+ dropped++;
4553
+ }
4554
+ if (dropped) {
4555
+ const tmp = `${f}.tmp.${process.pid}`;
4556
+ writeFileSync11(tmp, kept.join(`
4557
+ `) + (kept.length ? `
4558
+ ` : ""), { mode: 384 });
4559
+ renameSync7(tmp, f);
4560
+ }
4561
+ const after = existsSync14(f) ? readFileSync13(f, "utf8").length : 0;
4562
+ return { before: raw.length, after, dropped };
4563
+ });
4564
+ }
4513
4565
 
4514
4566
  // src/task.ts
4515
4567
  init_paths();
@@ -4786,7 +4838,7 @@ init_paths();
4786
4838
 
4787
4839
  // src/agents.ts
4788
4840
  init_paths();
4789
- import { existsSync as existsSync16, mkdirSync as mkdirSync15, readdirSync as readdirSync9, readFileSync as readFileSync15, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync11 } from "node:fs";
4841
+ import { existsSync as existsSync16, mkdirSync as mkdirSync15, readdirSync as readdirSync9, readFileSync as readFileSync15, renameSync as renameSync8, rmSync as rmSync6, writeFileSync as writeFileSync12 } from "node:fs";
4790
4842
  import { basename as basename8, join as join18, resolve as resolve10 } from "node:path";
4791
4843
  import { execFileSync as execFileSync5 } from "node:child_process";
4792
4844
  var agentsRoot2 = () => agentsRoot();
@@ -4794,8 +4846,8 @@ var fileOf = (agent) => join18(agentsRoot2(), `${agent}.json`);
4794
4846
  function atomicWrite2(path, data) {
4795
4847
  mkdirSync15(join18(path, ".."), { recursive: true });
4796
4848
  const tmp = `${path}.tmp.${process.pid}`;
4797
- writeFileSync11(tmp, data);
4798
- renameSync7(tmp, path);
4849
+ writeFileSync12(tmp, data);
4850
+ renameSync8(tmp, path);
4799
4851
  }
4800
4852
  function sh(cmd, args) {
4801
4853
  try {
@@ -4893,7 +4945,7 @@ function discoverTmux() {
4893
4945
 
4894
4946
  // src/tmux.ts
4895
4947
  import { execFileSync as execFileSync6 } from "node:child_process";
4896
- import { mkdtempSync, writeFileSync as writeFileSync12 } from "node:fs";
4948
+ import { mkdtempSync, writeFileSync as writeFileSync13 } from "node:fs";
4897
4949
  import { tmpdir as tmpdir2 } from "node:os";
4898
4950
  import { join as join19 } from "node:path";
4899
4951
  function tmux(args) {
@@ -4939,7 +4991,7 @@ function deliver(agent, id, opts = {}) {
4939
4991
  `);
4940
4992
  const dir = mkdtempSync(join19(tmpdir2(), "dia-msg-"));
4941
4993
  const buf = join19(dir, "body.txt");
4942
- writeFileSync12(buf, text);
4994
+ writeFileSync13(buf, text);
4943
4995
  try {
4944
4996
  tmux(["load-buffer", "-b", `dia-${m.id}`, buf]);
4945
4997
  tmux(["paste-buffer", "-p", "-b", `dia-${m.id}`, "-t", pane]);
@@ -5404,7 +5456,7 @@ function checks(reg) {
5404
5456
 
5405
5457
  // src/deps.ts
5406
5458
  init_paths();
5407
- import { existsSync as existsSync20, readFileSync as readFileSync18, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13, chmodSync as chmodSync3 } from "node:fs";
5459
+ import { existsSync as existsSync20, readFileSync as readFileSync18, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14, chmodSync as chmodSync3 } from "node:fs";
5408
5460
  import { homedir as homedir10 } from "node:os";
5409
5461
  import { dirname as dirname11, join as join23 } from "node:path";
5410
5462
  import { execFileSync as execFileSync9 } from "node:child_process";
@@ -5511,7 +5563,7 @@ function append4(entry, p) {
5511
5563
  const line = JSON.stringify(entry) + `
5512
5564
  `;
5513
5565
  const prev = existsSync20(path) ? readFileSync18(path, "utf8") : "";
5514
- writeFileSync13(path, prev.endsWith(`
5566
+ writeFileSync14(path, prev.endsWith(`
5515
5567
  `) || !prev ? prev + line : prev + `
5516
5568
  ` + line);
5517
5569
  try {
@@ -5847,7 +5899,7 @@ function run2(cfg, cmd, opts = {}) {
5847
5899
  }
5848
5900
 
5849
5901
  // src/bw.ts
5850
- import { chmodSync as chmodSync4, existsSync as existsSync24, mkdirSync as mkdirSync17, readFileSync as readFileSync21, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "node:fs";
5902
+ import { chmodSync as chmodSync4, existsSync as existsSync24, mkdirSync as mkdirSync17, readFileSync as readFileSync21, rmSync as rmSync7, writeFileSync as writeFileSync15 } from "node:fs";
5851
5903
  init_paths();
5852
5904
  import { join as join27 } from "node:path";
5853
5905
  import { execFileSync as execFileSync10 } from "node:child_process";
@@ -5869,7 +5921,7 @@ function readSession2() {
5869
5921
  function writeSession(key) {
5870
5922
  const f = sessionFile2();
5871
5923
  mkdirSync17(join27(f, ".."), { recursive: true });
5872
- writeFileSync14(f, key + `
5924
+ writeFileSync15(f, key + `
5873
5925
  `, { mode: 384 });
5874
5926
  chmodSync4(f, 384);
5875
5927
  }
@@ -6100,7 +6152,7 @@ function askBroker(req, timeoutMs = 5000) {
6100
6152
  }
6101
6153
 
6102
6154
  // packages/cli/src/index.ts
6103
- import { execFileSync as execFileSync12, spawn as spawn2, spawnSync as spawnSync5 } from "node:child_process";
6155
+ import { execFileSync as execFileSync12, spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
6104
6156
  import { mkdirSync as mkdirSync20, openSync as openSync3 } from "node:fs";
6105
6157
  import { basename as basename10, join as join32 } from "node:path";
6106
6158
  import { existsSync as existsSync29, readFileSync as readFileSync26 } from "node:fs";
@@ -6108,7 +6160,7 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
6108
6160
  import { resolve as resolvePath, sep as pathSep } from "node:path";
6109
6161
 
6110
6162
  // src/team.ts
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";
6163
+ import { appendFileSync as appendFileSync8, cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync18, readFileSync as readFileSync23, readdirSync as readdirSync11, rmSync as rmSync8, writeFileSync as writeFileSync16 } from "node:fs";
6112
6164
  import { homedir as homedir15, tmpdir as tmpdir3 } from "node:os";
6113
6165
  import { join as join29, resolve as resolve14 } from "node:path";
6114
6166
 
@@ -6161,23 +6213,23 @@ function runMock(id) {
6161
6213
  const t = get2(id);
6162
6214
  const dir = runDir(id);
6163
6215
  mkdirSync18(dir, { recursive: true, mode: 448 });
6164
- writeFileSync15(join29(dir, "plan.md"), `# Plan: ${t.title}
6216
+ writeFileSync16(join29(dir, "plan.md"), `# Plan: ${t.title}
6165
6217
 
6166
6218
  - judge: ${t.judge || "(missing)"}
6167
6219
  - roles: planner → builder → verifier → redteam
6168
6220
  `);
6169
- writeFileSync15(join29(dir, "diff.patch"), `# mock diff
6221
+ writeFileSync16(join29(dir, "diff.patch"), `# mock diff
6170
6222
  `);
6171
- writeFileSync15(join29(dir, "test.log"), `mock tests: 0 failures
6223
+ writeFileSync16(join29(dir, "test.log"), `mock tests: 0 failures
6172
6224
  `);
6173
- writeFileSync15(join29(dir, "verify.json"), JSON.stringify({ conclusion: "通过", commit: "mock", evidence: ["mock test.log"] }, null, 1));
6174
- writeFileSync15(join29(dir, "redteam.json"), JSON.stringify({ findings: [] }, null, 1));
6175
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6225
+ writeFileSync16(join29(dir, "verify.json"), JSON.stringify({ conclusion: "通过", commit: "mock", evidence: ["mock test.log"] }, null, 1));
6226
+ writeFileSync16(join29(dir, "redteam.json"), JSON.stringify({ findings: [] }, null, 1));
6227
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6176
6228
 
6177
6229
  - 结论:通过(mock)
6178
6230
  - 风险:无
6179
6231
  `);
6180
- writeFileSync15(join29(dir, "run.json"), JSON.stringify({ mock: true, judge: t.judge || null, ts: new Date().toISOString() }, null, 1));
6232
+ writeFileSync16(join29(dir, "run.json"), JSON.stringify({ mock: true, judge: t.judge || null, ts: new Date().toISOString() }, null, 1));
6181
6233
  status3(id, "awaiting_acceptance", "mock run");
6182
6234
  return dir;
6183
6235
  }
@@ -6197,7 +6249,7 @@ function runJudgeSafe(workspace, judge) {
6197
6249
  }
6198
6250
  }
6199
6251
  function writeUsage(dir, id, acc) {
6200
- writeFileSync15(join29(dir, "usage.json"), JSON.stringify({ task_id: id, calls: acc.rows.length, input_tokens: acc.input, output_tokens: acc.output, total_tokens: acc.total, cost_usd: acc.cost, unpriced_calls: acc.unpriced, by_role: acc.rows }, null, 1));
6252
+ writeFileSync16(join29(dir, "usage.json"), JSON.stringify({ task_id: id, calls: acc.rows.length, input_tokens: acc.input, output_tokens: acc.output, total_tokens: acc.total, cost_usd: acc.cost, unpriced_calls: acc.unpriced, by_role: acc.rows }, null, 1));
6201
6253
  }
6202
6254
  function judgeWorkspace(dir, fallback) {
6203
6255
  try {
@@ -6269,8 +6321,8 @@ function gate(id) {
6269
6321
  exit = typeof e?.status === "number" ? e.status : 1;
6270
6322
  out = String(e?.stdout || e?.message || "");
6271
6323
  }
6272
- writeFileSync15(join29(dir, "judge.json"), JSON.stringify({ source: "gate", command: judgeCmd, exit, ms: Date.now() - started, ts: new Date().toISOString() }, null, 1));
6273
- writeFileSync15(join29(dir, "judge.log"), out.slice(0, 20000));
6324
+ writeFileSync16(join29(dir, "judge.json"), JSON.stringify({ source: "gate", command: judgeCmd, exit, ms: Date.now() - started, ts: new Date().toISOString() }, null, 1));
6325
+ writeFileSync16(join29(dir, "judge.log"), out.slice(0, 20000));
6274
6326
  if (exit !== 0)
6275
6327
  return { ok: false, reason: `judge exit: ${exit}(见 judge.log)` };
6276
6328
  return { ok: true };
@@ -6377,7 +6429,7 @@ function snapPath(id, dir, tag) {
6377
6429
  list = [];
6378
6430
  }
6379
6431
  list.push(p);
6380
- writeFileSync15(f, JSON.stringify(list, null, 1));
6432
+ writeFileSync16(f, JSON.stringify(list, null, 1));
6381
6433
  return p;
6382
6434
  }
6383
6435
  function snapshotWorkspace(src, dest) {
@@ -6415,17 +6467,17 @@ async function repairRedteamHighs(o) {
6415
6467
  await o.builderFix(high);
6416
6468
  const workspace = judgeWorkspace(dir, o.workspace);
6417
6469
  const jr = runJudgeSafe(workspace, judge);
6418
- writeFileSync15(join29(dir, "test.log"), `judge: ${judge}
6470
+ writeFileSync16(join29(dir, "test.log"), `judge: ${judge}
6419
6471
  exit=${jr.code}
6420
6472
  ${jr.out}`.slice(0, 20000));
6421
6473
  const diff2 = gitDiff(workspace);
6422
- writeFileSync15(join29(dir, "diff.patch"), diff2);
6474
+ writeFileSync16(join29(dir, "diff.patch"), diff2);
6423
6475
  const ws2 = snapshotWorkspace(workspace, snapPath(id, dir, "redteam-ws-2"));
6424
6476
  const p2 = await o.round2(ws2, diff2);
6425
6477
  redteam = { ...normalizeRedteam(p2.value), repaired: p2.repaired, round: 2 };
6426
- writeFileSync15(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6478
+ writeFileSync16(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6427
6479
  if (jr.code !== 0 || highFindings(redteam).length) {
6428
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6480
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6429
6481
 
6430
6482
  - 任务:${t.title}
6431
6483
  - 结论:**已升级**(redteam 修复后仍不达标)
@@ -6523,7 +6575,7 @@ Keep exploration bounded (at most 3 reads); return only the JSON object now.`);
6523
6575
  });
6524
6576
  plan = planned.value;
6525
6577
  } catch (e) {
6526
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6578
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6527
6579
 
6528
6580
  - 任务:${t.title}
6529
6581
  - 结论:**已升级**(planner 输出无法解析)
@@ -6533,7 +6585,7 @@ Keep exploration bounded (at most 3 reads); return only the JSON object now.`);
6533
6585
  status3(id, "escalated", "planner: " + e.message);
6534
6586
  return { dir, escalated: true, error: e.message };
6535
6587
  }
6536
- writeFileSync15(join29(dir, "plan.md"), `# Plan
6588
+ writeFileSync16(join29(dir, "plan.md"), `# Plan
6537
6589
 
6538
6590
  ${JSON.stringify(plan, null, 1)}
6539
6591
  `);
@@ -6551,7 +6603,7 @@ Plan: ${JSON.stringify(plan)}
6551
6603
  Judge: ${judge}${fix}
6552
6604
  Report briefly what you changed and test results.`);
6553
6605
  recordModelCall(id, `builder-${round}`, modelOf(buildNs), buildOut.usage, usageAcc, prices);
6554
- writeFileSync15(join29(dir, `builder-${round}.md`), buildOut.text || "(no output)");
6606
+ writeFileSync16(join29(dir, `builder-${round}.md`), buildOut.text || "(no output)");
6555
6607
  const vws = snapshotWorkspace(workspace, snapPath(id, dir, `verify-ws-${round}`));
6556
6608
  const verifyNs = await acp.newSession(vws);
6557
6609
  const sVerify = verifyNs.sessionId;
@@ -6566,8 +6618,8 @@ Return only the JSON object.`);
6566
6618
  return r.text;
6567
6619
  });
6568
6620
  verify = { ...normalizeVerify(fixed.value), repaired: fixed.repaired };
6569
- writeFileSync15(join29(dir, "verify.json"), JSON.stringify(verify, null, 1));
6570
- writeFileSync15(join29(dir, "test.log"), (verify.evidence || []).join(`
6621
+ writeFileSync16(join29(dir, "verify.json"), JSON.stringify(verify, null, 1));
6622
+ writeFileSync16(join29(dir, "test.log"), (verify.evidence || []).join(`
6571
6623
  `) || "(no evidence)");
6572
6624
  if (verify.conclusion === "通过")
6573
6625
  break;
@@ -6577,9 +6629,9 @@ Return only the JSON object.`);
6577
6629
  lastVerifyKey = key;
6578
6630
  }
6579
6631
  const diff = gitDiff(workspace);
6580
- writeFileSync15(join29(dir, "diff.patch"), diff);
6632
+ writeFileSync16(join29(dir, "diff.patch"), diff);
6581
6633
  if (verify?.conclusion !== "通过") {
6582
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6634
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6583
6635
 
6584
6636
  - 任务:${t.title}
6585
6637
  - 结论:**已升级**(验证未通过)
@@ -6594,7 +6646,7 @@ ${(verify?.evidence || []).map((e) => "- " + e).join(`
6594
6646
  status3(id, "escalated", "verify: " + verify?.conclusion);
6595
6647
  return { dir, verify, escalated: true };
6596
6648
  }
6597
- writeFileSync15(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "acp", workspace, judge, ts: new Date().toISOString() }, null, 1));
6649
+ writeFileSync16(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "acp", workspace, judge, ts: new Date().toISOString() }, null, 1));
6598
6650
  const rws = snapshotWorkspace(workspace, snapPath(id, dir, "redteam-ws"));
6599
6651
  const redNs = await acp.newSession(rws);
6600
6652
  const sRed = redNs.sessionId;
@@ -6610,7 +6662,7 @@ Return only the JSON object.`);
6610
6662
  return r.text;
6611
6663
  });
6612
6664
  let redteam = { ...normalizeRedteam(fixed.value), repaired: fixed.repaired, round: 1 };
6613
- writeFileSync15(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6665
+ writeFileSync16(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6614
6666
  const repaired = await repairRedteamHighs({
6615
6667
  id,
6616
6668
  dir,
@@ -6647,7 +6699,7 @@ Return only the JSON object.`);
6647
6699
  if (repaired.escalated)
6648
6700
  return { dir, verify, redteam, escalated: true };
6649
6701
  writeUsage(dir, id, usageAcc);
6650
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6702
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6651
6703
 
6652
6704
  - 任务:${t.title}
6653
6705
  - 判据:${judge}
@@ -6768,7 +6820,7 @@ Judge: ${t.judge}
6768
6820
  });
6769
6821
  plan = planned.value;
6770
6822
  } catch (e) {
6771
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6823
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6772
6824
 
6773
6825
  - 任务:${t.title}
6774
6826
  - 结论:**已升级**(planner 输出无法解析)
@@ -6778,7 +6830,7 @@ Judge: ${t.judge}
6778
6830
  status3(id, "escalated", "planner: " + e.message);
6779
6831
  return { dir, escalated: true, error: e.message };
6780
6832
  }
6781
- writeFileSync15(join29(dir, "plan.md"), `# Plan
6833
+ writeFileSync16(join29(dir, "plan.md"), `# Plan
6782
6834
 
6783
6835
  ${JSON.stringify(plan, null, 1)}
6784
6836
  `);
@@ -6793,7 +6845,7 @@ Plan: ${JSON.stringify(plan)}
6793
6845
  Judge: ${judge}${fix}
6794
6846
  Report briefly what you changed and test results.`, tracker);
6795
6847
  recordModelCall(id, `builder-${round}`, buildOut.model, buildOut.usage, usageAcc, prices);
6796
- writeFileSync15(join29(dir, `builder-${round}.md`), buildOut.text || "(no output)");
6848
+ writeFileSync16(join29(dir, `builder-${round}.md`), buildOut.text || "(no output)");
6797
6849
  const vws = snapshotWorkspace(workspace, snapPath(id, dir, `verify-ws-${round}`));
6798
6850
  const vOut = await callDsh("verifier", vws, `Repo: ${vws}
6799
6851
  Run this judge yourself: ${judge}
@@ -6807,8 +6859,8 @@ ${msg}`, tracker);
6807
6859
  return r.text;
6808
6860
  });
6809
6861
  verify = { ...normalizeVerify(fixed.value), repaired: fixed.repaired };
6810
- writeFileSync15(join29(dir, "verify.json"), JSON.stringify(verify, null, 1));
6811
- writeFileSync15(join29(dir, "test.log"), (verify.evidence || []).join(`
6862
+ writeFileSync16(join29(dir, "verify.json"), JSON.stringify(verify, null, 1));
6863
+ writeFileSync16(join29(dir, "test.log"), (verify.evidence || []).join(`
6812
6864
  `) || "(no evidence)");
6813
6865
  if (verify.conclusion === "通过")
6814
6866
  break;
@@ -6818,9 +6870,9 @@ ${msg}`, tracker);
6818
6870
  lastKey = key;
6819
6871
  }
6820
6872
  const diff = gitDiff(workspace);
6821
- writeFileSync15(join29(dir, "diff.patch"), diff);
6873
+ writeFileSync16(join29(dir, "diff.patch"), diff);
6822
6874
  if (verify?.conclusion !== "通过") {
6823
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6875
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6824
6876
 
6825
6877
  - 任务:${t.title}
6826
6878
  - 结论:**已升级**(验证未通过)
@@ -6835,7 +6887,7 @@ ${(verify?.evidence || []).map((e) => "- " + e).join(`
6835
6887
  status3(id, "escalated", "verify: " + verify?.conclusion);
6836
6888
  return { dir, verify, escalated: true };
6837
6889
  }
6838
- writeFileSync15(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "dsh", workspace, judge, ts: new Date().toISOString() }, null, 1));
6890
+ writeFileSync16(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "dsh", workspace, judge, ts: new Date().toISOString() }, null, 1));
6839
6891
  const rws = snapshotWorkspace(workspace, snapPath(id, dir, "redteam-ws"));
6840
6892
  const rOut = await callDsh("redteam", rws, `Repo: ${rws}
6841
6893
  Diff:
@@ -6851,7 +6903,7 @@ ${msg}`, tracker);
6851
6903
  return r.text;
6852
6904
  });
6853
6905
  let redteam = { ...normalizeRedteam(fixed.value), repaired: fixed.repaired, round: 1 };
6854
- writeFileSync15(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6906
+ writeFileSync16(join29(dir, "redteam.json"), JSON.stringify(redteam, null, 1));
6855
6907
  const repaired = await repairRedteamHighs({
6856
6908
  id,
6857
6909
  dir,
@@ -6888,7 +6940,7 @@ ${msg}`, tracker);
6888
6940
  if (repaired.escalated)
6889
6941
  return { dir, verify, redteam, escalated: true };
6890
6942
  writeUsage(dir, id, usageAcc);
6891
- writeFileSync15(join29(dir, "packet.md"), `# 验收包 ${id}
6943
+ writeFileSync16(join29(dir, "packet.md"), `# 验收包 ${id}
6892
6944
 
6893
6945
  - 任务:${t.title}
6894
6946
  - 判据:${judge}
@@ -6897,7 +6949,7 @@ ${msg}`, tracker);
6897
6949
  - 反驳:${(redteam.findings || []).length} 条
6898
6950
  - 模型调用:${usageAcc.rows.length} 次 / ${usageAcc.total} tokens
6899
6951
  `);
6900
- writeFileSync15(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "dsh", workspace, judge, ts: new Date().toISOString() }, null, 1));
6952
+ writeFileSync16(join29(dir, "run.json"), JSON.stringify({ mock: false, mode: "dsh", workspace, judge, ts: new Date().toISOString() }, null, 1));
6901
6953
  status3(id, "awaiting_acceptance", "dsh run");
6902
6954
  return { dir, verify, redteam };
6903
6955
  } finally {
@@ -7096,7 +7148,7 @@ function propose(opts = {}) {
7096
7148
  }
7097
7149
 
7098
7150
  // src/service.ts
7099
- import { spawnSync as spawnSync4 } from "node:child_process";
7151
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
7100
7152
  import { createHash as createHash11, randomBytes as randomBytes4 } from "node:crypto";
7101
7153
  import {
7102
7154
  appendFileSync as appendFileSync9,
@@ -7111,7 +7163,7 @@ import {
7111
7163
  statSync as statSync12,
7112
7164
  symlinkSync as symlinkSync3,
7113
7165
  unlinkSync as unlinkSync3,
7114
- writeFileSync as writeFileSync16
7166
+ writeFileSync as writeFileSync17
7115
7167
  } from "node:fs";
7116
7168
  import { hostname as osHostname } from "node:os";
7117
7169
  import { basename as basename9, dirname as dirname13, isAbsolute as isAbsolute4, join as join31, relative as relative6, resolve as resolve15, sep as sep8 } from "node:path";
@@ -7121,6 +7173,52 @@ var ID_RE2 = /^[a-z0-9][a-z0-9-]*$/;
7121
7173
  var VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
7122
7174
  var LIFECYCLE_TYPES = new Set(["periodic", "bounded", "service"]);
7123
7175
  var STDIN_TYPES = new Set(["json", "text", "none"]);
7176
+ var ACP_HOSTS = ["dsh", "opencode"];
7177
+ var HEALTHCHECK_KINDS = new Set(["script", "acp"]);
7178
+ var isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
7179
+ var isNonEmptyString = (v) => typeof v === "string" && v.trim() !== "";
7180
+ function acpAgentCommand(agent) {
7181
+ if (!ACP_HOSTS.includes(agent?.host))
7182
+ throw new Error(`E_MANIFEST: entry.agent.host must be ${ACP_HOSTS.join("|")}`);
7183
+ const profile = agent.profile?.trim();
7184
+ if (agent.host === "dsh")
7185
+ return ["dsh", ...profile ? ["--profile", profile] : [], "acp"];
7186
+ return ["opencode", "acp"];
7187
+ }
7188
+ var isAgentEntry = (e) => !!e && e.agent !== undefined && e.agent !== null;
7189
+ var healthcheckKind = (hc) => hc?.kind ?? "script";
7190
+ var entryCmdOf = (svc) => svc.entry.cmd ?? "";
7191
+ function checkAgentEntry(a) {
7192
+ if (!isPlainObject(a))
7193
+ throw new Error("E_MANIFEST: entry.agent must be an object");
7194
+ if (!ACP_HOSTS.includes(a.host))
7195
+ throw new Error(`E_MANIFEST: entry.agent.host must be ${ACP_HOSTS.join("|")}`);
7196
+ for (const k of ["profile", "persona"]) {
7197
+ if (a[k] !== undefined && !isNonEmptyString(a[k]))
7198
+ throw new Error(`E_MANIFEST: entry.agent.${k} must be a non-empty string`);
7199
+ }
7200
+ if (a.host !== "dsh" && a.profile !== undefined) {
7201
+ throw new Error(`E_MANIFEST: entry.agent.profile 只适用于 host=dsh(${a.host} 没有 profile)`);
7202
+ }
7203
+ return a;
7204
+ }
7205
+ function checkEntry(entry) {
7206
+ if (!isPlainObject(entry))
7207
+ throw new Error("E_MANIFEST: entry required");
7208
+ const hasCmd = entry.cmd !== undefined && entry.cmd !== null;
7209
+ const hasAgent = entry.agent !== undefined && entry.agent !== null;
7210
+ if (hasCmd && hasAgent)
7211
+ throw new Error("E_MANIFEST: entry.cmd 与 entry.agent 互斥,只能声明其一");
7212
+ if (!hasCmd && !hasAgent)
7213
+ throw new Error("E_MANIFEST: entry.cmd required(脚本型)或 entry.agent(agent 型)");
7214
+ if (hasCmd) {
7215
+ if (typeof entry.cmd !== "string" || !entry.cmd.trim())
7216
+ throw new Error("E_MANIFEST: entry.cmd required");
7217
+ } else {
7218
+ checkAgentEntry(entry.agent);
7219
+ }
7220
+ return entry;
7221
+ }
7124
7222
  function entryDir() {
7125
7223
  return process.env.DIA_JOB_ENTRY_DIR || join31(jobRoot2(), "bin");
7126
7224
  }
@@ -7163,8 +7261,14 @@ var LEVELS = ["ok", "degraded", "fail"];
7163
7261
  function checkHealthcheck(hc) {
7164
7262
  if (hc === null || typeof hc !== "object" || Array.isArray(hc))
7165
7263
  throw new Error("E_MANIFEST: healthcheck must be an object");
7166
- if (typeof hc.cmd !== "string" || !hc.cmd.trim())
7167
- throw new Error("E_MANIFEST: healthcheck.cmd required");
7264
+ if (hc.kind !== undefined && !HEALTHCHECK_KINDS.has(hc.kind))
7265
+ throw new Error("E_MANIFEST: healthcheck.kind must be script|acp");
7266
+ if (healthcheckKind(hc) === "script") {
7267
+ if (typeof hc.cmd !== "string" || !hc.cmd.trim())
7268
+ throw new Error("E_MANIFEST: healthcheck.cmd required");
7269
+ } else if (hc.cmd !== undefined) {
7270
+ throw new Error("E_MANIFEST: healthcheck.kind=acp 不得再声明 cmd(判据是 initialize + session/new,不是外部脚本)");
7271
+ }
7168
7272
  if (hc.interval !== undefined) {
7169
7273
  const why = checkCronInterval(hc.interval);
7170
7274
  if (why)
@@ -7199,10 +7303,14 @@ function validateServiceManifest(m) {
7199
7303
  throw new Error("E_MANIFEST: bad id");
7200
7304
  if (!m.version || !VERSION_RE.test(m.version))
7201
7305
  throw new Error("E_MANIFEST: bad version");
7202
- if (m.entry === null || typeof m.entry !== "object" || Array.isArray(m.entry))
7203
- throw new Error("E_MANIFEST: entry required");
7204
- if (typeof m.entry.cmd !== "string" || !m.entry.cmd.trim())
7205
- throw new Error("E_MANIFEST: entry.cmd required");
7306
+ checkEntry(m.entry);
7307
+ const agentEntry = isAgentEntry(m.entry);
7308
+ if (agentEntry && healthcheckKind(m.healthcheck) !== "acp") {
7309
+ throw new Error('E_MANIFEST: entry.agent 必须配 healthcheck.kind="acp"(脚本型 healthcheck 无 cmd 可执行)');
7310
+ }
7311
+ if (!agentEntry && m.healthcheck && healthcheckKind(m.healthcheck) === "acp") {
7312
+ throw new Error('E_MANIFEST: healthcheck.kind="acp" 只适用于 entry.agent 型服务');
7313
+ }
7206
7314
  if (m.lifecycle === null || typeof m.lifecycle !== "object" || Array.isArray(m.lifecycle))
7207
7315
  throw new Error("E_MANIFEST: lifecycle required");
7208
7316
  if (!LIFECYCLE_TYPES.has(m.lifecycle.type))
@@ -7804,6 +7912,134 @@ function runHealth(svc, opts) {
7804
7912
  }
7805
7913
  return { judgement, report, stdout, stderr, ms };
7806
7914
  }
7915
+ async function runAcpHealth(svc, opts) {
7916
+ const hc = svc.healthcheck;
7917
+ if (!hc || healthcheckKind(hc) !== "acp")
7918
+ throw new Error("E_MANIFEST: runAcpHealth 只处理 healthcheck.kind=acp");
7919
+ const agent = svc.entry.agent;
7920
+ if (!agent)
7921
+ throw new Error("E_MANIFEST: healthcheck.kind=acp 需要 entry.agent");
7922
+ const timeoutMs = (hc.timeout ?? DEFAULT_HEALTH_TIMEOUT) * 1000;
7923
+ const now = opts.now ?? Date.now;
7924
+ const t0 = now();
7925
+ const argv = acpAgentCommand(agent);
7926
+ const env = { ...process.env, ...opts.env ?? {} };
7927
+ const steps = [];
7928
+ const finish = (status, exit) => {
7929
+ const judgement = {
7930
+ ...judgeHealth(exit, {}),
7931
+ state: status,
7932
+ exit,
7933
+ timeout: false,
7934
+ detail: steps.filter((x) => x.state !== "ok").map((x) => `${x.step}: ${x.detail}`).join("; ") || `initialize + session/new ok(${agent.host}${agent.profile ? " --profile " + agent.profile : ""})`
7935
+ };
7936
+ return { judgement, report: { status, checks: steps.map((x) => ({ name: x.step, status: x.state, detail: x.detail })) }, ms: now() - t0, steps };
7937
+ };
7938
+ const child = spawn2(argv[0], argv.slice(1), { cwd: opts.dir, env, stdio: ["pipe", "pipe", "pipe"] });
7939
+ const pending = new Map;
7940
+ let buf = "";
7941
+ child.stdout.setEncoding("utf8");
7942
+ child.stdout.on("data", (chunk) => {
7943
+ buf += chunk;
7944
+ for (let i = buf.indexOf(`
7945
+ `);i >= 0; i = buf.indexOf(`
7946
+ `)) {
7947
+ const line = buf.slice(0, i).trim();
7948
+ buf = buf.slice(i + 1);
7949
+ if (!line)
7950
+ continue;
7951
+ let msg;
7952
+ try {
7953
+ msg = JSON.parse(line);
7954
+ } catch {
7955
+ continue;
7956
+ }
7957
+ const waiter = msg && msg.id !== undefined ? pending.get(msg.id) : undefined;
7958
+ if (waiter) {
7959
+ pending.delete(msg.id);
7960
+ waiter(msg);
7961
+ }
7962
+ }
7963
+ });
7964
+ const spawnError = await new Promise((resolve) => {
7965
+ let settled = false;
7966
+ const done = (v) => {
7967
+ if (!settled) {
7968
+ settled = true;
7969
+ resolve(v);
7970
+ }
7971
+ };
7972
+ child.once("error", (e) => done(String(e?.message || e)));
7973
+ child.once("spawn", () => done(null));
7974
+ setTimeout(() => done(null), 50);
7975
+ });
7976
+ if (spawnError)
7977
+ return steps.push({ step: "initialize", state: "degraded", detail: `端点起不来:${spawnError}` }), finish("degraded", 20);
7978
+ const call = (id, method, params) => new Promise((resolve) => {
7979
+ const timer = setTimeout(() => {
7980
+ pending.delete(id);
7981
+ resolve({ timeout: true });
7982
+ }, timeoutMs);
7983
+ pending.set(id, (m) => {
7984
+ clearTimeout(timer);
7985
+ resolve(m);
7986
+ });
7987
+ try {
7988
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + `
7989
+ `);
7990
+ } catch (e) {
7991
+ clearTimeout(timer);
7992
+ pending.delete(id);
7993
+ resolve({ died: String(e?.message || e) });
7994
+ }
7995
+ });
7996
+ try {
7997
+ const init = await call(1, "initialize", { protocolVersion: 1, clientCapabilities: {} });
7998
+ if (init.timeout)
7999
+ steps.push({ step: "initialize", state: "degraded", detail: "initialize 超时" });
8000
+ else if (init.died)
8001
+ steps.push({ step: "initialize", state: "degraded", detail: `端点已退出:${init.died}` });
8002
+ else if (init.error)
8003
+ steps.push({ step: "initialize", state: "degraded", detail: `initialize 报错:${init.error?.message ?? JSON.stringify(init.error)}` });
8004
+ else if (!init.result)
8005
+ steps.push({ step: "initialize", state: "degraded", detail: "initialize 无 result" });
8006
+ else
8007
+ steps.push({ step: "initialize", state: "ok", detail: "initialize ok" });
8008
+ if (steps[0].state !== "ok")
8009
+ return finish("degraded", 20);
8010
+ const ns = await call(2, "session/new", { cwd: opts.dir, mcpServers: [] });
8011
+ if (ns.timeout)
8012
+ steps.push({ step: "session/new", state: "fail", detail: "session/new 超时" });
8013
+ else if (ns.error)
8014
+ steps.push({ step: "session/new", state: "fail", detail: `session/new 报错:${ns.error?.message ?? JSON.stringify(ns.error)}` });
8015
+ else if (!ns.result?.sessionId)
8016
+ steps.push({ step: "session/new", state: "fail", detail: "session/new 未返回 sessionId" });
8017
+ else
8018
+ steps.push({ step: "session/new", state: "ok", detail: `session ${ns.result.sessionId}` });
8019
+ return steps[1].state === "ok" ? finish("ok", 0) : finish("fail", 21);
8020
+ } finally {
8021
+ try {
8022
+ child.kill("SIGKILL");
8023
+ } catch {}
8024
+ }
8025
+ }
8026
+ function recordHealth(svc, judgement, opts = {}) {
8027
+ const id = svc.id;
8028
+ const gen = opts.gen || generationOf(id);
8029
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
8030
+ const file = opts.stateFile || process.env.DIA_SERVICE_HEALTH_FILE || healthStatePath(id);
8031
+ const record = transition(findLastHealth(readHealthLines(file), id, gen), {
8032
+ service: id,
8033
+ gen,
8034
+ source: opts.source ?? "manual",
8035
+ ts: now,
8036
+ judgement,
8037
+ debounce: svc.healthcheck?.debounce,
8038
+ checks: opts.checks
8039
+ });
8040
+ appendHealthRecord(file, record);
8041
+ return { record, statePath: file };
8042
+ }
7807
8043
  function whichTimeout1() {
7808
8044
  const probe = spawnSync4("bash", ["-c", "command -v timeout || command -v gtimeout"], { encoding: "utf8" });
7809
8045
  const p = String(probe.stdout ?? "").trim().split(`
@@ -7862,7 +8098,7 @@ function collectManagedFiles(base, hc) {
7862
8098
  }
7863
8099
  function verify4(dir, opts = {}) {
7864
8100
  const svc = readServiceManifest(dir);
7865
- const entry = resolveEntry(svc.entry.cmd, dir);
8101
+ const entry = resolveEntry(entryCmdOf(svc), dir);
7866
8102
  const drift = [];
7867
8103
  if (!existsSync28(entry.path))
7868
8104
  drift.push({ code: "entry_missing", detail: `entry 不存在:${entry.path}` });
@@ -7965,7 +8201,7 @@ function add2(opts) {
7965
8201
  }
7966
8202
  if (svc.healthcheck)
7967
8203
  healthJobInterval(svc);
7968
- const entry = resolveEntry(svc.entry.cmd, dir);
8204
+ const entry = resolveEntry(entryCmdOf(svc), dir);
7969
8205
  if (!existsSync28(entry.path))
7970
8206
  throw new Error(`E_UNSTABLE_ENTRY: entry 不存在:${entry.path}`);
7971
8207
  if (!isExecutableFile(entry.path))
@@ -7979,7 +8215,7 @@ function add2(opts) {
7979
8215
  gen,
7980
8216
  dryRun: !!opts.dryRun,
7981
8217
  backend,
7982
- entry: { declared: svc.entry.cmd, kind: entry.kind, path: entry.path, link: null },
8218
+ entry: { declared: entryCmdOf(svc), kind: entry.kind, path: entry.path, link: null },
7983
8219
  jobs: { service: serviceJobName(svc.id), health: svc.healthcheck ? healthJobName(svc.id) : null },
7984
8220
  target_mismatch: mismatch
7985
8221
  };
@@ -7988,7 +8224,7 @@ function add2(opts) {
7988
8224
  rmSync9(dir, { recursive: true, force: true });
7989
8225
  return result;
7990
8226
  }
7991
- const backfill = ["--allow-unstable", "--desc", `dia service ${svc.id}: healthcheck 只读判定,稳定入口 ${entryPathOf(svc.entry.cmd, dir)}`];
8227
+ const backfill = ["--allow-unstable", "--desc", `dia service ${svc.id}: healthcheck 只读判定,稳定入口 ${entryPathOf(entryCmdOf(svc), dir)}`];
7992
8228
  const runner = opts.runner ?? defaultRunner({ backfill });
7993
8229
  const installed = [];
7994
8230
  const fail = (code, msg, rc) => {
@@ -8005,7 +8241,7 @@ function add2(opts) {
8005
8241
  entryPath = linkEntry(entry.path);
8006
8242
  result.entry.link = entryPath;
8007
8243
  result.entry.path = entryPath;
8008
- } else if (svc.entry.cmd.startsWith(entryDir())) {
8244
+ } else if (entryCmdOf(svc).startsWith(entryDir())) {
8009
8245
  result.entry.link = entry.path;
8010
8246
  }
8011
8247
  const jobArgs = (name, cmd) => {
@@ -8047,7 +8283,7 @@ function add2(opts) {
8047
8283
  rmSync9(copy, { recursive: true, force: true });
8048
8284
  copyTree(dir, copy);
8049
8285
  }
8050
- writeFileSync16(serviceLockPath(svc.id), JSON.stringify(lock, null, 1) + `
8286
+ writeFileSync17(serviceLockPath(svc.id), JSON.stringify(lock, null, 1) + `
8051
8287
  `);
8052
8288
  return result;
8053
8289
  }
@@ -8322,14 +8558,14 @@ usage:
8322
8558
  dia collab status|enable|disable [--persona ID] # dia-msg 默认能力开关(默认开)
8323
8559
  dia whoami [--json] [--name NAME] # 当前会话身份(session id)
8324
8560
  dia in [--unread] [--all] [--legacy] [--json] # 会话收件箱(正文内联)
8325
- dia out --to <session_id> --subject S (--body B|--body-file F) [--kind K] [--reply-to ID]
8561
+ dia out --to <session_id> --subject S (--body B|--body-file F) [--kind K] [--touch p1,p2] [--reply-to ID]
8326
8562
  dia ack <id> | dia reply <id> --body B | dia close <id> --reason R | dia show <id>
8327
8563
  dia msg send|show|inbox|ack|delivered|deliver [--wait]|reply|needs-info|status|stuck|thread|escalate|agents|migrate
8328
8564
  dia agents list|register|unregister|discover
8329
8565
  dia msg deliver <agent> <id> [--transport tmux|acp]
8330
8566
  dia task add|list|link|status|done [--owner S] [--depends a,b] [--touches p1,p2]
8331
8567
  dia graph [--json] [--task t-...] [--tui [--watch] [--no-color]]
8332
- dia claim <path> [--reason R] [--ttl 4h] [--session S] | dia claims [--all] [--mine] | dia unclaim <path|id> [--force]
8568
+ dia claim <path> [--reason R] [--ttl 4h] [--session S] | dia claims [--all] [--mine] [--gc [--days 7]] | dia unclaim <path|id> [--force]
8333
8569
  dia team run <id> --mock | --acp|--dsh --workspace DIR
8334
8570
  dia team gate|accept|reject|next
8335
8571
  dia team accept <id> [--note ...] [--pr N [--repo R] [--merge]] # --pr:先验 CI,绿才接受/合并
@@ -8820,7 +9056,7 @@ function spawnDetachedDelivery(args) {
8820
9056
  const log = join32(dir, `deliver-${id}.log`);
8821
9057
  const fd = openSync3(log, "a");
8822
9058
  const bin = process.argv[1];
8823
- const child = spawn2(process.execPath, [bin, "msg", "deliver", ...args, "--wait"], {
9059
+ const child = spawn3(process.execPath, [bin, "msg", "deliver", ...args, "--wait"], {
8824
9060
  detached: true,
8825
9061
  stdio: ["ignore", fd, fd],
8826
9062
  env: process.env
@@ -9067,6 +9303,11 @@ async function claimsCli(argv) {
9067
9303
  return 0;
9068
9304
  }
9069
9305
  if (sub === "claims") {
9306
+ if (has(args, "--gc")) {
9307
+ const r = compact({ days: Number(flag(args, "--days") || 7) });
9308
+ out(asJson ? JSON.stringify(r, null, 1) : `claims gc: before=${r.before} after=${r.after} dropped=${r.dropped}`);
9309
+ return 0;
9310
+ }
9070
9311
  const mine = has(args, "--mine") ? resolveSessionId(session) : undefined;
9071
9312
  const rows = list3({ all: has(args, "--all"), mine });
9072
9313
  out(asJson ? JSON.stringify(rows, null, 1) : rows.map((c) => `${c.path} ${c.holder} ${c.reason || ""} ${c.expires_at}${isActive(c) ? "" : "\t(expired)"}`).join(`
@@ -9110,6 +9351,13 @@ async function collabCli(argv) {
9110
9351
  i++;
9111
9352
  }
9112
9353
  }
9354
+ const touchPaths = [...flags(args, "--touch"), ...(flag(args, "--touches") || "").split(",")].map((x) => x.trim()).filter(Boolean);
9355
+ if (touchPaths.length)
9356
+ refs.touches = touchPaths.join(",");
9357
+ const refPaths = (m) => [
9358
+ ...(m.refs?.touches || "").split(","),
9359
+ ...m.refs?.path ? [m.refs.path] : []
9360
+ ].map((x) => x.trim()).filter(Boolean);
9113
9361
  const view = (m) => ({ ...m, body: bodyOf(m) });
9114
9362
  const print = (m) => {
9115
9363
  out(`${m.id} [${m.state}] ${m.subject}`);
@@ -9118,6 +9366,12 @@ async function collabCli(argv) {
9118
9366
  out(` kind: ${m.kind}${m.requires_reply ? " 需回复" : ""}${m.deadline ? ` deadline=${m.deadline}` : ""} thread=${m.thread_id}#${m.seq}`);
9119
9367
  if (m.refs && Object.keys(m.refs).length)
9120
9368
  out(` refs: ${Object.entries(m.refs).map(([k, v]) => `${k}=${v}`).join(" ")}`);
9369
+ const touches = refPaths(m);
9370
+ if (touches.length) {
9371
+ const held = list3({}).filter((c) => touches.includes(c.path) && c.holder !== m.to.session_id);
9372
+ for (const c of held)
9373
+ out(` ⚠ claim: ${c.path} 由 ${c.holder} 持有(${c.reason || "no reason"})`);
9374
+ }
9121
9375
  const body = bodyOf(m);
9122
9376
  if (body) {
9123
9377
  const max = 4000;
@@ -9220,7 +9474,14 @@ async function collabCli(argv) {
9220
9474
  idempotencyKey: flag(args, "--idempotency-key"),
9221
9475
  hops: flag(args, "--hops") ? Number(flag(args, "--hops")) : undefined
9222
9476
  });
9223
- out(asJson ? JSON.stringify(m, null, 1) : `${m.id} ${m.thread_id}#${m.seq} ${m.from.session_id} → ${m.to.session_id} ${m.kind} ${m.subject}`);
9477
+ const overlaps = list3({}).filter((c) => touchPaths.includes(c.path) && c.holder !== m.from.session_id);
9478
+ if (asJson)
9479
+ out(JSON.stringify(overlaps.length ? { ...m, claim_overlaps: overlaps } : m, null, 1));
9480
+ else {
9481
+ out(`${m.id} ${m.thread_id}#${m.seq} ${m.from.session_id} → ${m.to.session_id} ${m.kind} ${m.subject}`);
9482
+ for (const c of overlaps)
9483
+ out(` ⚠ claim overlap: ${c.path} 由 ${c.holder} 持有(${c.reason || "no reason"})`);
9484
+ }
9224
9485
  return 0;
9225
9486
  }
9226
9487
  if (sub === "ack") {
@@ -9814,6 +10075,18 @@ async function serviceCli(argv) {
9814
10075
  }
9815
10076
  const fromWrapper = has(args, "--from-wrapper");
9816
10077
  const ex = flag(args, "--exit");
10078
+ const dir = serviceDir(id);
10079
+ const svc = readServiceManifest(dir);
10080
+ if (healthcheckKind(svc.healthcheck) === "acp" && !fromWrapper) {
10081
+ const run = await runAcpHealth(svc, { dir });
10082
+ const rec = recordHealth(svc, run.judgement, { gen: flag(args, "--gen"), stateFile: flag(args, "--state-file") });
10083
+ const code = run.judgement.state === "ok" ? 0 : run.judgement.state === "degraded" ? 30 : 1;
10084
+ if (asJson)
10085
+ out(JSON.stringify({ id, verdict: run.judgement.state, shown: rec.record.state, exit: run.judgement.exit, detail: run.judgement.detail, steps: run.steps }, null, 1));
10086
+ else
10087
+ out(`${id}: ${run.judgement.state}(shown=${rec.record.state}${run.judgement.detail ? ", " + run.judgement.detail : ""})`);
10088
+ return code;
10089
+ }
9817
10090
  const r = runServiceHealth(id, {
9818
10091
  wrapperExit: fromWrapper ? Number(ex ?? "1") : undefined,
9819
10092
  gen: flag(args, "--gen"),