@maxiaochao/pi-toolkit 0.10.4 → 0.10.6

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +26 -4
  3. package/package.json +3 -5
  4. package/packages/herdr-subagents/CHANGELOG.md +11 -0
  5. package/packages/herdr-subagents/README.md +13 -2
  6. package/packages/herdr-subagents/TESTING.md +5 -4
  7. package/packages/herdr-subagents/agents/analyst.md +8 -0
  8. package/packages/herdr-subagents/extensions/herdr-subagents.ts +1 -1
  9. package/packages/herdr-subagents/extensions/subagent-report.ts +26 -0
  10. package/packages/herdr-subagents/package.json +1 -1
  11. package/packages/herdr-subagents/src/history.ts +2 -2
  12. package/packages/herdr-subagents/src/live-agent.ts +11 -0
  13. package/packages/herdr-subagents/src/parameters.ts +2 -2
  14. package/packages/herdr-subagents/src/run-pane-allocator.ts +2 -1
  15. package/packages/herdr-subagents/src/runner.ts +2 -8
  16. package/packages/herdr-subagents/src/status.ts +95 -0
  17. package/packages/herdr-subagents/src/tool.ts +13 -2
  18. package/packages/herdr-subagents/src/validation.ts +2 -1
  19. package/packages/herdr-subagents/tests/extension-contract.ts +4 -0
  20. package/packages/herdr-subagents/tests/run-tests.mjs +61 -4
  21. package/skills/agent-team/SKILL.md +33 -57
  22. package/skills/agent-team/models.json +19 -12
  23. package/packages/todo/CHANGELOG.md +0 -9
  24. package/packages/todo/README.md +0 -73
  25. package/packages/todo/TESTING.md +0 -22
  26. package/packages/todo/docs/demo.gif +0 -0
  27. package/packages/todo/docs/screenshot.png +0 -0
  28. package/packages/todo/extensions/todo.ts +0 -279
  29. package/packages/todo/package.json +0 -25
  30. package/packages/todo/src/todo-state.ts +0 -159
  31. package/packages/todo/tests/todo-extension.mjs +0 -134
  32. package/packages/todo/tests/todo-state.mjs +0 -108
@@ -23,6 +23,7 @@ import { applyTaskStatus, isSettledRunStatus, projectTaskActivity, readTaskRecor
23
23
  import { RunPaneAllocator } from "../src/run-pane-allocator.ts";
24
24
  import { aggregateTaskStatus } from "../src/run-status.ts";
25
25
  import { isAbortError } from "../src/errors.ts";
26
+ import registerSubagentReport from "../extensions/subagent-report.ts";
26
27
 
27
28
  let passed = 0;
28
29
  let failed = 0;
@@ -140,14 +141,16 @@ function latestRunDirectory() {
140
141
  try {
141
142
  check("run validation accepts one or more tasks", validateRunParams({ tasks: [{ name: "one", prompt: "do it" }], concurrency: 2, background: true }) === undefined);
142
143
  check("control validation accepts supported calls", [
143
- { action: "list" }, { action: "history" }, { action: "cancel", runId: "run" }, { action: "cleanup", runId: "run" },
144
+ { action: "list" }, { action: "history" }, { action: "status", runId: "run" }, { action: "cancel", runId: "run" }, { action: "cleanup", runId: "run" },
144
145
  { action: "respond", runId: "run", answer: "yes", task: 1 },
145
146
  { action: "resume", runId: "run", task: 1, prompt: "continue" },
146
147
  ].every((params) => validateControlParams(params) === undefined));
147
- check("all supported actions have shared validation and rendering metadata", ["list", "history", "cancel", "cleanup", "respond", "resume"].every((action) => action in HERDR_ACTIONS));
148
+ check("all supported actions have shared validation and rendering metadata", ["list", "history", "status", "cancel", "cleanup", "respond", "resume"].every((action) => action in HERDR_ACTIONS));
148
149
  check("split validation rejects missing tasks and invalid control fields", [
149
150
  validateRunParams({ tasks: [] }),
150
151
  validateControlParams({ action: "list", prompt: "not allowed" }),
152
+ validateControlParams({ action: "status" }),
153
+ validateControlParams({ action: "history", runId: "not-allowed" }),
151
154
  validateControlParams({ action: "respond", runId: "run" }),
152
155
  validateControlParams({ action: "resume", runId: "run", task: 1 }),
153
156
  ].every((message) => typeof message === "string" && message.length > 0));
@@ -313,7 +316,25 @@ try {
313
316
  const outside = await runHerdrSubagents("hello", { cwd: root, herdr: unavailableHerdr });
314
317
  check("Herdr is required", !outside.ok && outside.errorMessage?.includes("inside Herdr") && unavailableHerdr.calls.length === 0);
315
318
 
316
- setEnv({ HERDR_ENV: "1", HERDR_WORKSPACE_ID: "w1", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
319
+ setEnv({ HERDR_ENV: "1", HERDR_WORKSPACE_ID: "w1", HERDR_PANE_ID: "w1:p-parent", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
320
+ const childCommands = new Map();
321
+ const childShortcuts = new Map();
322
+ const childEvents = new Map();
323
+ const childExecCalls = [];
324
+ const childStatuses = [];
325
+ registerSubagentReport({
326
+ registerTool() {},
327
+ registerCommand(name, options) { childCommands.set(name, options); },
328
+ registerShortcut(key, options) { childShortcuts.set(key, options); },
329
+ on(name, handler) { childEvents.set(name, handler); },
330
+ async exec(command, args) { childExecCalls.push([command, args]); return { stdout: "", stderr: "", code: 0, killed: false }; },
331
+ });
332
+ process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID = "w1:p-parent";
333
+ const childContext = { ui: { notify() {}, setStatus(key, value) { childStatuses.push([key, value]); } } };
334
+ await childEvents.get("session_start")?.({}, childContext);
335
+ await childCommands.get("parent")?.handler("", childContext);
336
+ await childShortcuts.get("alt+p")?.handler(childContext);
337
+ check("child command and shortcut return to the exact parent Agent pane", childExecCalls.length === 2 && childExecCalls.every(([command, args]) => command === "herdr" && args.join(",") === "agent,focus,w1:p-parent") && childStatuses.some(([, value]) => value.includes("alt+p")));
317
338
  const herdr = fakeHerdr();
318
339
  const success = await runHerdrSubagents("Review authentication", {
319
340
  cwd: root,
@@ -330,7 +351,7 @@ try {
330
351
  const startRequest = herdr.calls.find(([name]) => name === "startAgent")[1];
331
352
  check("named unfocused tab in parent workspace", createRequest.workspaceId === "w1" && createRequest.cwd === root && createRequest.label === "SA · auth-review" && createRequest.focus === false);
332
353
  check("ordinary execution never steals pane focus", !herdr.calls.some(([name]) => name === "focusPane"));
333
- check("child environment points at durable task", createRequest.env.PI_HERDR_SUBAGENTS_CHILD === "1" && createRequest.env.PI_HERDR_SUBAGENTS_TASK_DIR.includes(join(".pi", "herdr-subagents", "runs")));
354
+ check("child environment points at durable task and parent pane", createRequest.env.PI_HERDR_SUBAGENTS_CHILD === "1" && createRequest.env.PI_HERDR_SUBAGENTS_TASK_DIR.includes(join(".pi", "herdr-subagents", "runs")) && createRequest.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID === "w1:p-parent");
334
355
  check("persistent isolated child Pi", startRequest.kind === "pi" && startRequest.paneId === "w1:p2" && startRequest.args.includes("--session") && startRequest.args.includes("--no-extensions") && startRequest.args.includes("--extension") && startRequest.args.includes("--no-skills") && startRequest.args.includes("--model") && startRequest.args.includes("fake/model") && startRequest.args.includes("--tools") && startRequest.args.includes("read,grep,subagent_report"));
335
356
  const promptRequest = herdr.calls.find(([name]) => name === "promptAgent")[1];
336
357
  check("prompt submitted through Herdr", promptRequest.prompt === "Review authentication" && promptRequest.target === startRequest.name);
@@ -385,6 +406,9 @@ try {
385
406
  writeFileSync(join(agentsDir, "broken.md"), "not frontmatter");
386
407
  const discovery = discoverPackageAgents(agentsDir);
387
408
  check("persona discovery and diagnostics", discovery.agents.length === 1 && discovery.agents[0].name === "worker" && discovery.diagnostics.length === 1);
409
+ const packageDiscovery = discoverPackageAgents(join(process.cwd(), "agents"));
410
+ const analystPersona = packageDiscovery.agents.find((persona) => persona.name === "analyst");
411
+ check("built-in analyst is neutral and read-only", packageDiscovery.diagnostics.length === 0 && analystPersona?.access === "read" && analystPersona.model === undefined && analystPersona.tools?.join(",") === "read,bash,grep,find,ls" && /independent analyst/.test(analystPersona.systemPrompt) && /direct evidence, inference, and unknowns/.test(analystPersona.systemPrompt) && !/auditor|replicator|adversary/.test(analystPersona.systemPrompt));
388
412
 
389
413
  const configRoot = join(root, "configuration");
390
414
  const packageAgents = join(configRoot, "package-agents");
@@ -424,6 +448,18 @@ try {
424
448
  const configuredStart = configuredHerdr.calls.find(([name]) => name === "startAgent")[1];
425
449
  check("configured persona uses displayed effective values", !configuredRun.isError && configuredStart.args.includes("task/model") && configuredStart.args.includes("high") && configuredStart.args.includes("--skill") && configuredStart.args.includes("project-skill"));
426
450
 
451
+ const analystHerdr = fakeHerdr();
452
+ const analystBatch = await executeHerdrSubagents({ label: "independent-analysis", concurrency: 3, tasks: [
453
+ { name: "sol-audit", prompt: "slow audit", agent: "analyst", model: "vendor/sol" },
454
+ { name: "grok-replicate", prompt: "slow replicate", agent: "analyst", model: "vendor/grok" },
455
+ { name: "opus-challenge", prompt: "slow challenge", agent: "analyst", model: "vendor/opus" },
456
+ ] }, undefined, undefined, { cwd: root, model: { provider: "parent", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: analystHerdr });
457
+ const analystStarts = analystHerdr.calls.filter(([name]) => name === "startAgent").map(([, request]) => request);
458
+ const analystModels = analystStarts.map((request) => request.args[request.args.indexOf("--model") + 1]);
459
+ check("three read-only analyst tasks run concurrently", !analystBatch.isError && analystBatch.details.effectiveConcurrency === 3 && analystHerdr.maxActive === 3);
460
+ check("analyst tasks preserve independent model overrides", analystModels.join(",") === "vendor/sol,vendor/grok,vendor/opus");
461
+ check("completed analyst batch closes its run tab", analystHerdr.calls.at(-1)?.[0] === "closeTab");
462
+
427
463
  const batchHerdr = fakeHerdr();
428
464
  const batchUpdates = [];
429
465
  const batch = await executeHerdrSubagents({ label: "reviews", concurrency: 2, tasks: [
@@ -584,10 +620,31 @@ try {
584
620
  const stableHerdr = fakeHerdr();
585
621
  const stable = await executeHerdrSubagents({ tasks: [{ name: "stable", prompt: "stable", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: stableHerdr, runId: backgroundId });
586
622
  check("preallocated background run id stays stable", stable.details.runId === backgroundId && readFileSync(join(stable.details.recordDirectory, "run.json"), "utf8").includes(backgroundId));
623
+ const stableStatus = await executeHerdrSubagents({ action: "status", runId: backgroundId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents") });
624
+ check("status returns only one durable run with task details", !stableStatus.isError && stableStatus.details.runId === backgroundId && stableStatus.details.status === "completed" && stableStatus.details.settled === true && stableStatus.details.source === "run" && stableStatus.details.tasks[0].name === "stable" && !stableStatus.content[0].text.includes("run-one"));
625
+ await persistQueuedRun(root, backgroundId, { label: "stale-queue", tasks: [{ name: "wrong-source", prompt: "queued" }], background: true });
626
+ const durableFirstStatus = await executeHerdrSubagents({ action: "status", runId: backgroundId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents") });
627
+ check("status prefers durable state over a stale queue record", durableFirstStatus.details.source === "run" && durableFirstStatus.details.label !== "stale-queue" && durableFirstStatus.details.tasks[0].name === "stable");
628
+ const queuedStatusId = "queued-status-id";
629
+ await persistQueuedRun(root, queuedStatusId, { label: "queued-status", tasks: [{ name: "waiting", prompt: "wait", agent: "reviewer" }], background: true });
630
+ const queuedStatus = await executeHerdrSubagents({ action: "status", runId: queuedStatusId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents") });
631
+ check("status projects a queued run before durable records exist", !queuedStatus.isError && queuedStatus.details.source === "queue" && queuedStatus.details.status === "queued" && queuedStatus.details.settled === false && queuedStatus.details.tasks[0].status === "queued" && queuedStatus.details.tasks[0].agent === "reviewer");
632
+ const unknownStatus = await executeHerdrSubagents({ action: "status", runId: "unknown-status-id" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents") });
633
+ check("status rejects unknown run IDs without falling back to history", unknownStatus.isError && unknownStatus.content[0].text === "Unknown Subagent run 'unknown-status-id'.");
634
+ const staleStatusRoot = join(root, "stale-status-report");
635
+ const staleStatusTaskDirectory = join(staleStatusRoot, ".pi", "herdr-subagents", "runs", "stale", "tasks", "01-task");
636
+ mkdirSync(staleStatusTaskDirectory, { recursive: true });
637
+ writeFileSync(join(staleStatusTaskDirectory, "..", "..", "run.json"), JSON.stringify({ id: "stale-status", label: "stale-status", status: "running", startedAt: "2026-10-08T00:00:00.000Z" }));
638
+ writeFileSync(join(staleStatusTaskDirectory, "task.json"), JSON.stringify({ id: "stale-task", runId: "stale-status", order: 1, name: "stale-task", agent: "reviewer", status: "running" }));
639
+ writeFileSync(join(staleStatusTaskDirectory, "report.json"), JSON.stringify({ status: "needs-input", summary: "Old blocked report.", question: "Old question?", documents: [] }));
640
+ const runningStatus = await executeHerdrSubagents({ action: "status", runId: "stale-status" }, undefined, undefined, { cwd: staleStatusRoot }, { agentsDirectory: join(process.cwd(), "agents") });
641
+ check("running status does not expose a stale report from an earlier blocked turn", runningStatus.details.tasks[0].status === "running" && !runningStatus.details.tasks[0].question && !runningStatus.details.tasks[0].summary && !runningStatus.content[0].text.includes("Old question"));
587
642
 
588
643
  const blockedHerdr = fakeHerdr("blocked");
589
644
  const blocked = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "question", prompt: "inspect branch", agent: "explorer" }, { name: "after-answer", prompt: "continue queued", agent: "reviewer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: blockedHerdr });
590
645
  check("needs-input returns foreground control and preserves tab", blocked.details.status === "blocked" && blocked.details.tasks[0].question === "Which branch should I inspect?" && !blockedHerdr.calls.some(([name]) => name === "closeTab"));
646
+ const blockedStatus = await executeHerdrSubagents({ action: "status", runId: blocked.details.runId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents") });
647
+ check("status exposes blocked task questions for respond", blockedStatus.details.status === "blocked" && blockedStatus.details.settled === false && blockedStatus.details.tasks[0].question === "Which branch should I inspect?" && blockedStatus.content[0].text.includes("Which branch should I inspect?"));
591
648
  const activeCleanup = await executeHerdrSubagents({ action: "cleanup", runId: blocked.details.runId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: blockedHerdr });
592
649
  check("cleanup refuses to delete an active blocked run", activeCleanup.isError && activeCleanup.content[0].text.includes("still active") && existsSync(blocked.details.recordDirectory));
593
650
  const resumeUpdates = [];
@@ -4,86 +4,62 @@ description: 以对抗评审的方式让不同厂商的模型交叉验证同一
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- 当前会话是**管理者**,不是转述人:自己也承担一份实证工作,并对每一轮的裁决负责。队员是长驻的 herdr pane,不是一次性 subagent —— 它们要能被反复追问、被点名互相反驳。
7
+ 当前会话是**管理者**,不是转述人:自己也承担一份实证工作,并对每一轮的裁决负责。每轮并发启动三个相互独立的子代理,统一收集结果后由管理者裁决。子代理不需要常驻;跨轮连续性来自持久调查档案和逻辑成员身份,而不是聊天记忆。
8
8
 
9
9
  ## 何时值得开
10
10
 
11
- 只在用户要求组队时开,它会在用户的终端里真的分屏。值得开的条件:结论会驱动一个代价高的动作(改内核、下线设备、向客户定责),或者证据链已经长到单模型会自我确认。查一个事实、读一份文件用不上。
11
+ 只在用户要求组队时开。值得开的条件:结论会驱动一个代价高的动作(改内核、下线设备、向客户定责),或者证据链已经长到单模型容易自我确认。查一个事实、读一份文件用不上。
12
12
 
13
- ## 1. 建队
13
+ ## 1. 建立调查档案
14
14
 
15
- **必须跨厂商。** 同厂同代模型共享盲区,三个一起点头等于一个点头。
15
+ 为当前问题建立 `.pi/agent-team/<case>/`:
16
16
 
17
- 名单在同目录的 `models.json`,先读它,照里面的 `model` 字段写命令;用户指定了别的模型就用用户的。每个成员有 `name` / `model` / `role` / `charter`,`defaults` 里是共用的 `kind` 与 `thinking`。
18
-
19
- ```bash
20
- herdr pane split --current --direction right --cwd "$PWD" --no-focus
21
- herdr agent start <name> --kind pi --pane <pane> -- --model <model> --thinking <thinking>
22
- echo <pane> > /tmp/pane_<name>
17
+ ```text
18
+ .pi/agent-team/<case>/
19
+ ├── BRIEF.md # 问题、范围、术语、输入和排除项
20
+ ├── FINDINGS-stageN.md # 当前唯一事实源;新阶段整体取代旧阶段
21
+ ├── LEDGER.md # 只增不改的 N/X 记录
22
+ └── rounds/
23
+ └── N.md # 本轮角色映射和可证伪攻击点
23
24
  ```
24
25
 
25
- pane id 落盘,后面所有广播脚本都从这里取。开完把该成员的 `charter` 作为第一条 prompt 发过去,立常设职责。`herdr agent list` 确认都是 idle 再开工。
26
+ `BRIEF.md` 只记录稳定背景。`FINDINGS-stageN.md` 必含机制链、编号发现、已推翻清单和未闭合点。`LEDGER.md` 中发现按 `N1, N2…` 追加,被推翻的结论按 `X1, X2…` 追加并写明推翻者。只有管理者能更新这三个权威文件;子代理只提交候选证据。
26
27
 
27
- ## 2. 分角色:互不重叠的攻击面
28
+ ## 2. 配置成员与角色
28
29
 
29
- 每个 pane 领一个**方法论上互斥**的职责:`auditor` 审源码、`replicator` 复算数据、`adversary` 攻方法学。具体措辞是 `models.json` 里各自的 `charter`,改职责改那里。
30
+ 名单在同目录的 `models.json`。三个逻辑成员必须来自不同模型厂商,并显式使用各自的 `model`;不能依赖父会话或环境默认模型。启动后核对并在本轮记录中写下每个任务实际生效的模型;无法确认、与名单不符或三个成员落到同一厂商时,本轮无效。
30
31
 
31
- ## 3. 交底与通信
32
+ 三个成员使用相同的中性只读执行约束,方法角色由每轮任务动态赋予:
32
33
 
33
- herdr 没有邮箱,只有 `agent prompt`(往别人输入框塞文字)和 `agent read`(把别人屏幕刮下来)。拓扑是星型,管理者是唯一的路由:跨 pane 的话全部由管理者转述,把 A 的原话引进 B 的下一条 prompt(「opus 说你这个计数可能一对多,用数据回应」)。
34
+ - `auditor`:审源码、规范和提交,只接受可定位的直接证据。
35
+ - `replicator`:绕开管理者的脚本和口径,从原始数据独立复算。
36
+ - `adversary`:攻击前提和方法学,查样本敏感性、一对多计数和阴性对照。
34
37
 
35
- ```
36
- 管理者(本会话)
37
- 唯一写 FINDINGS 的人
38
- │ │ │
39
- 下行:消息 │ │ │ herdr agent prompt
40
- (短指针) ▼ ▼ ▼
41
- ┌────┐ ┌──────┐ ┌──────┐
42
- │ sol│ │ grok │ │ opus │ 彼此不相连
43
- └──┬──┘ └──┬───┘ └──┬───┘
44
- │ │ │
45
- 上行:文件 └───────┴───────┘
46
- ▼
47
- /tmp/<轮次>-<name>.md ← 管理者从磁盘读
48
- ```
38
+ 模型与角色不得永久绑定。首轮可以按 `models.json` 顺序分配三个角色;后续根据争议轮换或交叉复核,使模型差异和角色差异能够被区分。每轮在 `rounds/N.md` 写明成员、模型、角色、输入文件和攻击点。
49
39
 
50
- 技术上任何 pane 都能 prompt 任何 pane,让它们直连有四个代价:管理者看不见这次交换,ledger 就缺一环;对正在 working 的 agent 发 prompt 会被拒(`agent_blocked`)或插进它当前那一轮;没有投递回执;而且两两串供会让它们彼此收敛,恰好毁掉跨厂商独立性这唯一的价值来源。
40
+ ## 3. 交底与通信
51
41
 
52
- 下行只发短指针,正文留在文件里:长文本经终端会被折断或触发意外快捷键。上行走文件:队员把结论写 `/tmp/<轮次>-<name>.md`,终端只回一行路径,管理者从磁盘读。`agent read` 是终端截图,会截断、会按宽度折行、会混进 TUI 边框,拿它收长输出必丢内容;只用它确认回执和状态。
42
+ 拓扑是星型,管理者是唯一消息路由。成员在同一轮中独立作答,在提交前不得看到其他成员本轮答案。需要逻辑成员 A 反驳逻辑成员 B 的上一轮结论时,由管理者在新任务中引用 B 的原话和证据位置;不能假定新实例记得上一轮聊天。
53
43
 
54
- 基准文档 `FINDINGS-stageN.md` 是**唯一事实源**,只有管理者写,新版本整体取代旧版本而不是打补丁。必含四块:机制链、编号发现、已推翻清单、未闭合点。
55
-
56
- ```bash
57
- MSG='【N17】读 /tmp/FINDINGS-stage4.md 全文。本轮你的攻击点:……。结论写 /tmp/N17-<你的名字>.md,终端只回一行路径。'
58
- for a in sol grok opus; do herdr agent prompt "$a" "$MSG"; done
59
- for a in sol grok opus; do herdr agent wait "$a" --until idle --timeout 900000; done
60
- ```
44
+ 下行只给短任务和文件指针:每个成员阅读 `BRIEF.md`、最新 `FINDINGS-stageN.md`、`LEDGER.md` 和本轮文件。不得要求它重新梳理已经确认的全部背景。上行结果必须能归属到轮次和成员,并包含证据位置;长结果写入持久文件,不能依赖截断的终端文本。
61
45
 
62
46
  ## 4. 回合
63
47
 
64
- 一轮 = 广播 → 各自独立干 → 管理者从磁盘收文件 → 裁决 → 更新文档 → 再广播。
48
+ 一轮 = 记录受保护文件和工作树初始状态 → 写本轮任务 → 三个成员并发独立调查 → 管理者收齐结果 → 检查子代理未修改项目或权威文件 → 核验证据并裁决 → 更新 FINDINGS 和 LEDGER → 决定下一轮。
65
49
 
66
- 每轮广播里给每个 pane 一个**具体到能证伪的攻击点**(「查 ART 的 adbconnection.cc,确认这条错误日志与被销毁的 fd 是不是 1:1」),而不是「你看看有没有问题」。
50
+ 每个成员必须收到具体到能证伪的攻击点,例如“查 `adbconnection.cc`,确认该错误日志与被销毁的 fd 是否 1:1”,而不是“看看有没有问题”。子代理结束后即可释放;下一轮通过调查档案恢复事实状态,并重新创建独立任务。
67
51
 
68
52
  ## 5. 纪律
69
53
 
70
- - **ledger**:发现按 `N1, N2…` 只增不改;每一条被推翻的结论进 `X1, X2…` 清单,写明是谁推翻的。这份 X 清单是最终交付里最有价值的部分。
71
- - **retraction 要公开**:管理者被驳倒时,在下一次广播开头显式写「我错了,更正为……」。管理者一旦开始维护自己的结论,pane 就会停止攻击,整个队伍塌成回音室。
72
- - **replication**:任何承载结论的数字至少两方独立算。对不上先查口径再查代码。
73
- - **control**:任何「异常」指标必须配阴性样本才进文档。拿不出对照的指标只是一个数。
74
- - **grading**:每条结论标注 `源码读取 / 实测 / 推断` 和置信度。推断不能和实测并列陈述。
75
- - **裁决**:两个 pane 冲突时判出谁对,并说明依据。把冲突的结论平均掉会同时丢掉两边的信息。
54
+ - **独立性**:同轮答案不得互相可见;成员之间不直接通信。
55
+ - **只读完整性**:只读是协议约束,不假定执行环境构成沙箱。每轮结束后比较初始状态;发现子代理修改项目或权威文件时,该成员结果无效并显式报告污染范围。
56
+ - **retraction**:管理者被驳倒时,在下一轮开头明确写“我错了,更正为……”,并追加对应 X。
57
+ - **replication**:任何承载结论的数字至少由两个不同模型独立计算;对不上先查口径再查代码。
58
+ - **control**:任何“异常”指标必须配阴性样本才进入 FINDINGS。
59
+ - **grading**:每条结论标注 `源码读取 / 实测 / 推断` 和置信度;推断不能与实测并列陈述。
60
+ - **裁决**:成员冲突时管理者必须判定谁对并说明证据,不能多数投票或折中平均。
61
+ - **缺席视角**:成员失败或额度耗尽时,明确记录缺失的模型和方法角色,不能把不完整轮次当成完整评审。
76
62
 
77
63
  ## 6. 收尾
78
64
 
79
- 收敛判据:完整一轮广播后没有任何 pane 能产出新的 X。
80
-
81
- 最终文档必须单列**未闭合点**,以及**每一个需要人或设备批准才能执行的动作**。
82
-
83
- 用完 `herdr pane close <pane_id>` 关掉,pane 不会自己退出。
84
-
85
- ## 故障
86
-
87
- - pane 额度耗尽:降级为剩余 pane 继续,并在文档里记下缺失的是哪个视角。
88
- - agent 卡在 blocked:`herdr agent read <name> --source visible` 看它在等什么,多半是在等一个确认。
89
- - 广播后无人响应:`herdr agent list` 核对 pane 是否还活着,pane id 会随布局变化。
65
+ 收尾前必须进行一轮**开放攻击**:三个方法角色各自选择其认为最强的攻击点,不能只回答管理者预设的窄问题。只有完整开放攻击轮没有产生任何被管理者接受并记入 X 的新反证时才算收敛。最终文档单列未闭合点、缺失视角,以及每一个需要人或设备批准才能执行的动作。
@@ -1,25 +1,32 @@
1
1
  {
2
- "defaults": {
3
- "kind": "pi",
4
- "thinking": "high"
5
- },
6
2
  "members": [
7
3
  {
8
4
  "name": "sol",
9
- "model": "pudu/gpt-5.6-sol",
10
- "role": "auditor",
11
- "charter": "只接受能逐行引用的源码、规范、commit,输出必须带 文件:行号。负责把「机制上可能」变成「代码里确实如此」;查不到就说查不到,不要用常识补。"
5
+ "vendor": "OpenAI",
6
+ "model": "pudu/gpt-5.6-sol"
12
7
  },
13
8
  {
14
9
  "name": "grok",
15
- "model": "pudu/grok-4.6",
16
- "role": "replicator",
17
- "charter": "拿原始数据和口径,不看管理者的脚本,独立重算同一个指标,只报自己的数和差异。数对不上就是一次发现,先查口径再查代码。"
10
+ "vendor": "xAI",
11
+ "model": "pudu/grok-4.6"
18
12
  },
19
13
  {
20
14
  "name": "opus",
21
- "model": "pudu/claude-opus-5",
22
- "role": "adversary",
15
+ "vendor": "Anthropic",
16
+ "model": "pudu/claude-opus-5"
17
+ }
18
+ ],
19
+ "roles": [
20
+ {
21
+ "name": "auditor",
22
+ "charter": "只接受能逐行引用的源码、规范、commit,输出必须带文件和行号。负责把“机制上可能”变成“代码里确实如此”;查不到就说查不到,不要用常识补。"
23
+ },
24
+ {
25
+ "name": "replicator",
26
+ "charter": "拿原始数据和口径,不看管理者的脚本,独立重算同一个指标,只报自己的数和差异。数对不上就是一次发现,先查口径再查代码。"
27
+ },
28
+ {
29
+ "name": "adversary",
23
30
  "charter": "攻前提和方法学,不攻结论。指出指标对样本组成的敏感性、计数的一对多、缺失的阴性对照。附议没有价值,找不到问题就说明理由。"
24
31
  }
25
32
  ]
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## Unreleased
4
-
5
- - replace generic Todo CRUD with serialized execution-plan lifecycle commands, guarded single-step advancement, preserved replan history, canonical current-plan results, branch reconstruction tests, and a live rerendering current-plan widget
6
-
7
- ## 2026-09-30 - v0.1.0
8
-
9
- - 从 `@maxiaochao/pi-toolkit` 提取 todo 权威源码,支持主包捆绑和独立安装。
@@ -1,73 +0,0 @@
1
- # @maxiaochao/pi-todo
2
-
3
- Pi 的分支本地执行计划扩展,提供 `todo` 生命周期工具、只显示当前计划的 widget 和 `/todos` 完整历史视图。
4
-
5
- ![Todo 真实状态机运行截图](docs/screenshot.png)
6
-
7
- ## 为什么有这个包
8
-
9
- 这是 pi-toolkit 自研的计划执行模块,用明确状态替代只存在于对话文本里的计划。Todo 是用户可见的后续工作顺序,不是隐藏推理、完成报告或通用任务管理器。它解决改向后历史丢失、并发调用误完成、多个步骤同时 doing、完成后不推进,以及 UI 序号与持久 ID 混用的问题。
10
-
11
- ## 演示
12
-
13
- ![Todo 生命周期演示](docs/demo.gif)
14
-
15
- ```text
16
- todo({ action: "plan", items: ["Inspect callers", "Implement", "Verify"] })
17
- todo({ action: "complete_current", expected_current_id: 1 })
18
- /todos
19
- ```
20
-
21
- 第一项实际完成后,下一项自动进入 `doing`。工具结果和 widget 只展示 `CURRENT plan`;`/todos` 展示当前分支的全部完成、取消和未完成历史。
22
-
23
- ## 安装
24
-
25
- 完整工具包已经包含本扩展:
26
-
27
- ```bash
28
- pi install npm:@maxiaochao/pi-toolkit
29
- ```
30
-
31
- 只需要 todo 时单独安装:
32
-
33
- ```bash
34
- pi install npm:@maxiaochao/pi-todo
35
- ```
36
-
37
- 两种方式二选一,避免重复注册。
38
-
39
- ## 更新
40
-
41
- ```bash
42
- pi update npm:@maxiaochao/pi-toolkit # 更新主包内置版本
43
- pi update npm:@maxiaochao/pi-todo # 更新独立子包
44
- ```
45
-
46
- 两个 npm 包版本独立。Todo 源码变更只有在发布新主包后才会到达主包用户,发布子包本身不会更新主包。
47
-
48
- ## 行为
49
-
50
- - `plan(items)` 创建或重新规划。非空计划取消并保留全部旧 `doing`/`todo` 历史,保留已关闭历史,使用新的单调递增 ID,并启动第一步。
51
- - `plan([])` 取消全部未完成工作,不留下当前计划;ID 不重置。
52
- - `add_step(text)` 只追加一步。`complete_current` 和 `cancel_current` 必须携带匹配唯一 `doing` 步骤的 `expected_current_id`,否则原子失败;每次调用只推进一步。
53
- - 调用共享可变状态并串行执行。同轮多个 sibling completion 不是支持的顺序机制。
54
- - `list` 与每个变更都向模型返回简洁、规范的 `CURRENT plan`,包含稳定内部 ID 和状态;只有 `/todos` 显示完整历史。
55
- - widget 从实时状态读取当前计划,使用连续显示位置、隐藏内部 ID 和关闭历史,并在计划结束时消失。
56
-
57
- ## 使用准则
58
-
59
- - 只为明确的多步工作、确实分阶段的工作或跨模型交接创建计划;不要为简单单步任务创建计划。
60
- - 仅在工作实际完成后调用 `complete_current`。仍然需要但被阻塞的步骤保持当前状态。
61
- - `cancel_current` 表示当前步骤不再需要且后续计划仍有效;方向改变时使用 `plan`。
62
- - 保留用户给出的步骤意图和顺序,只在可执行性要求下拆分步骤。
63
- - 协议不提供任意更新、删除、清空、撤销、批量完成或 marker 文本解析。
64
-
65
- ## 开发与测试
66
-
67
- 权威源码位于 `extensions/` 和 `src/`。测试流程、用例和最近验证结果见 [TESTING.md](TESTING.md)。
68
-
69
- ```bash
70
- npm --prefix packages/todo test
71
- npm --prefix packages/todo run load-test
72
- npm pack ./packages/todo --dry-run
73
- ```
@@ -1,22 +0,0 @@
1
- # Todo 测试方案
2
-
3
- ## 流程
4
-
5
- 1. `npm --prefix packages/todo test`:运行状态机确定性测试和扩展/widget 集成测试。
6
- 2. `npm --prefix packages/todo run load-test`:让 Pi 加载真实扩展入口。
7
- 3. `npm pack ./packages/todo --dry-run`:确认发布文件完整。
8
- 4. 确认由真实状态机输出生成的 `docs/screenshot.png` 和 `docs/demo.gif` 可打开且进入 tarball。
9
-
10
- ## 用例数据
11
-
12
- - 恢复旧格式记录、修复非法生命周期,并只从当前分支快照重建。
13
- - 非空 `plan` 保留关闭历史、取消旧未完成步骤并使用单调新 ID;`plan([])` 留下空当前计划。
14
- - `expected_current_id` 的 stale、重复和缺失调用原子失败;顺序完成与取消每次只推进一步。
15
- - `add_step` 只追加,步骤文本不作为 marker 或唯一键解释。
16
- - 并发发起的调用按提交顺序串行化,同轮重复 completion 不会跨越两个步骤。
17
- - 已挂载 widget 从实时状态重渲染、主动请求 TUI render、隐藏内部 ID 和关闭历史,并在计划结束时消失。
18
- - `/todos` 展示完整当前分支历史,而所有工具结果只返回规范 `CURRENT plan`。
19
-
20
- ## 最近验证结果
21
-
22
- 迁移基线:状态机和扩展集成测试全部通过,扩展可由 Pi 独立加载。发布前必须重新运行上述三步;跳过的检查不算通过。
Binary file
Binary file