@maxiaochao/pi-toolkit 0.9.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/package.json +5 -5
  4. package/packages/codex-edit/CHANGELOG.md +15 -0
  5. package/packages/codex-edit/README.md +10 -3
  6. package/packages/codex-edit/TESTING.md +40 -9
  7. package/packages/codex-edit/benchmark/explicit-edit-100.json +2824 -0
  8. package/packages/codex-edit/benchmark/export-evidence.mjs +77 -0
  9. package/packages/codex-edit/benchmark/incident-cases.mjs +37 -0
  10. package/packages/codex-edit/benchmark/prepare.mjs +181 -0
  11. package/packages/codex-edit/benchmark/results/explicit-edit-100-sol-low-v3.json +4562 -0
  12. package/packages/codex-edit/benchmark/run-with-budget.mjs +77 -0
  13. package/packages/codex-edit/benchmark/schedule.mjs +19 -0
  14. package/packages/codex-edit/benchmark/select-tasks.mjs +167 -0
  15. package/packages/codex-edit/codex-edit-explainer.html +4 -4
  16. package/packages/codex-edit/docs/test-report.html +181 -0
  17. package/packages/codex-edit/docs/test-report.md +242 -0
  18. package/packages/codex-edit/docs/test-summary.md +9 -0
  19. package/packages/codex-edit/extensions/codex-edit.ts +4 -2
  20. package/packages/codex-edit/package.json +8 -2
  21. package/packages/codex-edit/src/parser.ts +26 -3
  22. package/packages/codex-edit/tests/benchmark.test.mjs +81 -0
  23. package/packages/codex-edit/tests/comparison-cases.mjs +396 -0
  24. package/packages/codex-edit/tests/comparison-harness.mjs +44 -0
  25. package/packages/codex-edit/tests/generate-test-report.mjs +589 -0
  26. package/packages/codex-edit/tests/legacy-parser-v0.1.7.mjs +128 -0
  27. package/packages/codex-edit/tests/native-vs-codex.ts +109 -0
  28. package/packages/codex-edit/tests/package.test.mjs +8 -0
  29. package/packages/codex-edit/tests/parser.test.mjs +123 -1
  30. package/packages/codex-edit/tests/released-vs-local.test.mjs +31 -0
  31. package/packages/herdr-subagents/CHANGELOG.md +41 -0
  32. package/packages/herdr-subagents/README.md +155 -0
  33. package/packages/herdr-subagents/TESTING.md +24 -0
  34. package/packages/herdr-subagents/agents/explorer.md +8 -0
  35. package/packages/herdr-subagents/agents/reviewer.md +8 -0
  36. package/packages/{tiny-subagent → herdr-subagents}/agents/worker.md +1 -0
  37. package/packages/herdr-subagents/docs/demo.gif +0 -0
  38. package/packages/herdr-subagents/docs/screenshot.png +0 -0
  39. package/packages/herdr-subagents/extensions/herdr-subagents.ts +258 -0
  40. package/packages/herdr-subagents/extensions/subagent-report.ts +65 -0
  41. package/packages/{tiny-subagent → herdr-subagents}/package.json +7 -6
  42. package/packages/herdr-subagents/src/batch-runner.ts +219 -0
  43. package/packages/herdr-subagents/src/blocking.ts +175 -0
  44. package/packages/herdr-subagents/src/config.ts +125 -0
  45. package/packages/herdr-subagents/src/continue-run.ts +87 -0
  46. package/packages/herdr-subagents/src/dispatcher.ts +73 -0
  47. package/packages/herdr-subagents/src/errors.ts +3 -0
  48. package/packages/herdr-subagents/src/fleet-controller.ts +82 -0
  49. package/packages/herdr-subagents/src/fleet.ts +56 -0
  50. package/packages/herdr-subagents/src/herdr.ts +224 -0
  51. package/packages/herdr-subagents/src/history.ts +210 -0
  52. package/packages/herdr-subagents/src/live-agent.ts +87 -0
  53. package/packages/herdr-subagents/src/monitor.ts +18 -0
  54. package/packages/herdr-subagents/src/ownership.ts +17 -0
  55. package/packages/herdr-subagents/src/parameters.ts +21 -0
  56. package/packages/{tiny-subagent → herdr-subagents}/src/personas.ts +13 -2
  57. package/packages/herdr-subagents/src/protocol.ts +23 -0
  58. package/packages/herdr-subagents/src/queued-runs.ts +70 -0
  59. package/packages/herdr-subagents/src/reconcile.ts +167 -0
  60. package/packages/herdr-subagents/src/records.ts +213 -0
  61. package/packages/herdr-subagents/src/result-path.ts +7 -0
  62. package/packages/herdr-subagents/src/run-locator.ts +19 -0
  63. package/packages/herdr-subagents/src/run-pane-allocator.ts +78 -0
  64. package/packages/herdr-subagents/src/run-status.ts +23 -0
  65. package/packages/herdr-subagents/src/runner.ts +250 -0
  66. package/packages/herdr-subagents/src/state.ts +11 -0
  67. package/packages/herdr-subagents/src/tab-cleanup.ts +68 -0
  68. package/packages/herdr-subagents/src/tool.ts +217 -0
  69. package/packages/herdr-subagents/src/validation.ts +38 -0
  70. package/packages/herdr-subagents/tests/extension-contract.ts +225 -0
  71. package/packages/herdr-subagents/tests/run-tests.mjs +889 -0
  72. package/packages/todo/extensions/todo.ts +1 -1
  73. package/packages/tiny-subagent/CHANGELOG.md +0 -12
  74. package/packages/tiny-subagent/README.md +0 -128
  75. package/packages/tiny-subagent/TESTING.md +0 -20
  76. package/packages/tiny-subagent/docs/demo.gif +0 -0
  77. package/packages/tiny-subagent/docs/screenshot.png +0 -0
  78. package/packages/tiny-subagent/extensions/tiny-subagent.ts +0 -228
  79. package/packages/tiny-subagent/src/runner.ts +0 -319
  80. package/packages/tiny-subagent/tests/run-tests.mjs +0 -111
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { basename, resolve } from "node:path";
5
+
6
+ const [runDirectoryArg, outputArg] = process.argv.slice(2);
7
+ if (!runDirectoryArg || !outputArg) {
8
+ console.error("Usage: export-evidence.mjs <benchmark-run-directory> <output.json>");
9
+ process.exit(2);
10
+ }
11
+
12
+ const runDirectory = resolve(runDirectoryArg);
13
+ const output = resolve(outputArg);
14
+ const packageDirectory = resolve(new URL("..", import.meta.url).pathname);
15
+ const [progress, runManifest, taskManifestText] = await Promise.all([
16
+ readFile(resolve(runDirectory, "progress.json"), "utf8").then(JSON.parse),
17
+ readFile(resolve(runDirectory, "manifest.json"), "utf8").then(JSON.parse),
18
+ readFile(resolve(packageDirectory, "benchmark/explicit-edit-100.json"), "utf8"),
19
+ ]);
20
+ const taskManifest = JSON.parse(taskManifestText);
21
+ const taskById = new Map(taskManifest.included.map((task) => [task.id, task]));
22
+ const sumAttempts = (result, field) => (result.recovery?.attempts ?? []).reduce(
23
+ (sum, attempt) => sum + Number(attempt.execution?.[field] ?? 0), 0,
24
+ );
25
+
26
+ const evidence = {
27
+ schemaVersion: 1,
28
+ runId: basename(runDirectory),
29
+ completed: progress.completed,
30
+ total: progress.total,
31
+ identities: {
32
+ taskManifestSha256: createHash("sha256").update(taskManifestText).digest("hex"),
33
+ focusedSelectionSha256: runManifest.focusedSelectionSha256,
34
+ configSha256: runManifest.configSha256,
35
+ verifierSha256: runManifest.verifierSha256,
36
+ },
37
+ source: taskManifest.source,
38
+ selection: {
39
+ seed: taskManifest.selection.seed,
40
+ scheduling: taskManifest.selection.scheduling,
41
+ neutral: taskManifest.included.filter((task) => task.cohort === "neutral").length,
42
+ challenge: taskManifest.included.filter((task) => task.cohort === "challenge").length,
43
+ },
44
+ run: {
45
+ contract: runManifest.contract,
46
+ attempts: runManifest.attempts,
47
+ oracleRecoveries: runManifest.oracleRecoveries,
48
+ concurrency: runManifest.concurrency,
49
+ timeoutMs: runManifest.timeoutMs,
50
+ },
51
+ harnesses: Object.fromEntries(Object.entries(runManifest.harnesses).map(([profile, harness]) => [profile, {
52
+ agentVersion: harness.agentVersion,
53
+ model: harness.model,
54
+ thinking: harness.thinking,
55
+ harnessVersion: harness.harnessVersion,
56
+ configurationId: harness.configurationId,
57
+ extensions: harness.configuration?.extensions ?? [],
58
+ }])),
59
+ results: progress.results.map((result) => ({
60
+ taskId: result.taskId,
61
+ profile: result.profile,
62
+ category: result.category,
63
+ cohort: taskById.get(result.taskId)?.cohort,
64
+ firstAttemptPassed: Boolean(result.recovery?.firstAttemptPassed),
65
+ eventuallyPassed: Boolean(result.recovery?.eventuallyPassed),
66
+ recoveriesUsed: Number(result.recovery?.recoveriesUsed ?? 0),
67
+ costUsd: sumAttempts(result, "costUsd"),
68
+ totalTokens: sumAttempts(result, "totalTokens"),
69
+ seconds: Number(result.seconds ?? 0),
70
+ toolCalls: sumAttempts(result, "toolCalls"),
71
+ failedToolCalls: sumAttempts(result, "failedToolCalls"),
72
+ providerFailures: (result.recovery?.attempts ?? []).filter((attempt) => attempt.execution?.providerFailure).length,
73
+ })),
74
+ };
75
+
76
+ await writeFile(output, `${JSON.stringify(evidence, null, 2)}\n`, "utf8");
77
+ console.log(`Exported ${evidence.results.length} sanitized results to ${output}`);
@@ -0,0 +1,37 @@
1
+ export const incidentCases = [
2
+ {
3
+ id: "incident-repeated-markdown-context",
4
+ protocolCaseId: "repeated-markdown-separator",
5
+ tier: "agent-and-protocol",
6
+ issue: "重复 Markdown 分隔符曾触发 ambiguous match",
7
+ prompt: "更新 SKILL.md 的 frontmatter 描述,并在 frontmatter 结束分隔符后插入指定说明;不要改动正文和页脚。",
8
+ },
9
+ {
10
+ id: "incident-repeated-update-same-path",
11
+ protocolCaseId: "repeated-update-same-path",
12
+ tier: "protocol-only",
13
+ issue: "同一个 raw path 的多个 Update File 块在发布版中冲突",
14
+ prompt: "验证同一次 apply_patch 调用中,同一路径的多个普通 Update File 块可以按顺序以上一块结果为输入。",
15
+ },
16
+ {
17
+ id: "incident-runner-type-rename",
18
+ protocolCaseId: "ordered-runner-type-renames",
19
+ tier: "agent-and-protocol",
20
+ issue: "runner.ts 中重复类型名的大范围重命名曾触发 ambiguous match",
21
+ prompt: "将 packages/herdr-subagents/src/runner.ts 中 TinySubagent 系列公开类型和引用完整重命名为 HerdrSubagents,保持其余代码逐字不变。",
22
+ },
23
+ {
24
+ id: "incident-multi-file-structural-edit",
25
+ protocolCaseId: "multi-file-add-delete-move",
26
+ tier: "agent-and-protocol",
27
+ issue: "一次 patch 中的 add/delete/move 需要原子预校验和执行",
28
+ prompt: "按要求移动并更新 old.ts,删除 remove.txt,再创建 added.txt;不要留下额外文件。",
29
+ },
30
+ {
31
+ id: "incident-bom-crlf",
32
+ protocolCaseId: "bom-and-crlf",
33
+ tier: "agent-and-protocol",
34
+ issue: "UTF-8 BOM 与 CRLF 曾导致原生精确文本匹配失败",
35
+ prompt: "只修改 app.ts 中的目标标识符,同时逐字保留 UTF-8 BOM、CRLF 和未指定行。",
36
+ },
37
+ ];
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { execFileSync } from "node:child_process";
5
+ import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join, resolve } from "node:path";
8
+
9
+ const PI_VERSION = "0.85.1";
10
+ const MODEL = "openai-codex/gpt-5.6-sol";
11
+ const THINKING = "low";
12
+ const PACKAGE = "@maxiaochao/pi-codex-edit";
13
+ const nodeBinary = realpathSync(execFileSync("which", ["node"], { encoding: "utf8" }).trim());
14
+ const nodeRuntime = dirname(dirname(nodeBinary));
15
+ const root = resolve(".cache/codex-edit-benchmark");
16
+ const authFile = join(homedir(), ".pi/agent/auth.json");
17
+ const modelFile = join(homedir(), ".pi/agent/models.json");
18
+ for (const file of [authFile, modelFile]) {
19
+ if (!existsSync(file)) throw new Error(`Required Pi state is missing: ${file}`);
20
+ }
21
+
22
+ const npmJson = (...args) =>
23
+ JSON.parse(execFileSync("npm", args, { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 }));
24
+ const latest = npmJson("view", `${PACKAGE}@latest`, "version", "dist.integrity", "--json");
25
+ latest.integrity ??= latest["dist.integrity"];
26
+ if (typeof latest.version !== "string" || typeof latest.integrity !== "string") {
27
+ throw new Error("npm latest did not resolve to an exact version and integrity");
28
+ }
29
+
30
+ rmSync(root, { recursive: true, force: true });
31
+ mkdirSync(root, { recursive: true });
32
+ const tarballDirectory = join(root, "tarballs");
33
+ mkdirSync(tarballDirectory);
34
+ const [packed] = npmJson(
35
+ "pack",
36
+ resolve("packages/codex-edit"),
37
+ "--pack-destination",
38
+ tarballDirectory,
39
+ "--json",
40
+ );
41
+ if (!packed?.filename || !packed?.integrity) throw new Error("Local npm pack returned no integrity");
42
+ const localTarball = join(tarballDirectory, packed.filename);
43
+
44
+ function installRuntime(name, extensionSpec) {
45
+ const directory = join(root, "runtimes", name);
46
+ mkdirSync(directory, { recursive: true });
47
+ writeFileSync(join(directory, "package.json"), `${JSON.stringify({ private: true })}\n`);
48
+ const dependencies = [`@earendil-works/pi-coding-agent@${PI_VERSION}`];
49
+ if (extensionSpec) dependencies.push(extensionSpec);
50
+ execFileSync(
51
+ "npm",
52
+ ["install", "--no-audit", "--no-fund", "--save-exact", ...dependencies],
53
+ { cwd: directory, stdio: "inherit" },
54
+ );
55
+ const piManifest = JSON.parse(
56
+ readFileSync(join(directory, "node_modules/@earendil-works/pi-coding-agent/package.json")),
57
+ );
58
+ if (piManifest.version !== PI_VERSION) throw new Error(`${name}: Pi version drifted`);
59
+ if (extensionSpec) {
60
+ const extensionManifest = JSON.parse(
61
+ readFileSync(join(directory, "node_modules/@maxiaochao/pi-codex-edit/package.json")),
62
+ );
63
+ if (!extensionManifest.pi?.extensions?.includes("./extensions/codex-edit.ts")) {
64
+ throw new Error(`${name}: installed package does not declare Codex Edit`);
65
+ }
66
+ }
67
+ return directory;
68
+ }
69
+
70
+ const nativeRuntime = installRuntime("native-edit");
71
+ const releasedRuntime = installRuntime("released-codex-edit", `${PACKAGE}@${latest.version}`);
72
+ const localRuntime = installRuntime("local-codex-edit", localTarball);
73
+ const lock = JSON.parse(readFileSync(join(releasedRuntime, "package-lock.json")));
74
+ const releasedLock = lock.packages?.["node_modules/@maxiaochao/pi-codex-edit"];
75
+ if (releasedLock?.version !== latest.version || releasedLock?.integrity !== latest.integrity) {
76
+ throw new Error("Released Codex Edit lockfile does not match the npm version and integrity pin");
77
+ }
78
+
79
+ const sourceDigest = createHash("sha256")
80
+ .update(readFileSync(resolve("packages/codex-edit/src/parser.ts")))
81
+ .update(readFileSync(resolve("packages/codex-edit/extensions/codex-edit.ts")))
82
+ .digest("hex");
83
+ const commonArgs = [
84
+ "--model",
85
+ MODEL,
86
+ "--thinking",
87
+ THINKING,
88
+ "--no-extensions",
89
+ "--no-skills",
90
+ "--no-prompt-templates",
91
+ "--no-themes",
92
+ "--no-context-files",
93
+ "--tools",
94
+ "read,bash,edit,write",
95
+ "--session-dir",
96
+ "/state/pi/sessions",
97
+ "--mode",
98
+ "json",
99
+ "-p",
100
+ ];
101
+ function adapter(name, runtime, extension) {
102
+ const extensionEntry = extension
103
+ ? join(runtime, "node_modules/@maxiaochao/pi-codex-edit/extensions/codex-edit.ts")
104
+ : null;
105
+ const extensionIdentity =
106
+ name === "released-codex-edit"
107
+ ? `${PACKAGE}@${latest.version} (${latest.integrity})`
108
+ : name === "local-codex-edit"
109
+ ? `${PACKAGE}@${packed.version}+local (${packed.integrity})`
110
+ : null;
111
+ return {
112
+ kind: "pi-default",
113
+ command: join(runtime, "node_modules/.bin/pi"),
114
+ args: [
115
+ ...commonArgs,
116
+ ...(extensionEntry ? ["--extension", extensionEntry] : []),
117
+ "--",
118
+ "{prompt}",
119
+ ],
120
+ version: PI_VERSION,
121
+ model: MODEL,
122
+ thinking: THINKING,
123
+ transport: "harness-native",
124
+ ready: false,
125
+ readOnly: [nodeRuntime, join(runtime, "node_modules")],
126
+ seedFiles: { "pi/auth.json": authFile, "pi/models.json": modelFile },
127
+ env: {
128
+ PATH: `${dirname(nodeBinary)}:/usr/local/bin:/usr/bin:/bin`,
129
+ PI_CODING_AGENT_DIR: "/state/pi",
130
+ PI_TELEMETRY: "0",
131
+ },
132
+ agentFamily: "pi",
133
+ agentVersion: PI_VERSION,
134
+ modelFamily: "gpt-5.6-sol",
135
+ modelVersion: "gpt-5.6-sol",
136
+ provider: "openai-codex",
137
+ harnessFamily: name,
138
+ harnessVersion:
139
+ name === "native-edit"
140
+ ? PI_VERSION
141
+ : name === "released-codex-edit"
142
+ ? latest.version
143
+ : `${packed.version}+local.${sourceDigest.slice(0, 12)}`,
144
+ adapterVersion: "1",
145
+ configurationLabels: [`harness/${name}`, extension ? "editor/apply-patch" : "editor/edit"],
146
+ configurationId: `${name}/explicit-edit-v1`,
147
+ configuration: {
148
+ tools: extension ? ["read", "bash", "write", "apply_patch"] : ["read", "bash", "write", "edit"],
149
+ extensions: extensionIdentity ? [extensionIdentity] : [],
150
+ rules: extension ? ["Codex patch grammar", "native write retained"] : [],
151
+ runtimeFlags: [`thinking=${THINKING}`, "clean-pi-resources", "tool-parity-v1"],
152
+ environment: [],
153
+ },
154
+ };
155
+ }
156
+
157
+ const config = {
158
+ harnesses: {
159
+ "native-edit": adapter("native-edit", nativeRuntime, false),
160
+ "released-codex-edit": adapter("released-codex-edit", releasedRuntime, true),
161
+ "local-codex-edit": adapter("local-codex-edit", localRuntime, true),
162
+ },
163
+ };
164
+ const configPath = join(root, "config.json");
165
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
166
+ const metadata = {
167
+ preparedAt: new Date().toISOString(),
168
+ piVersion: PI_VERSION,
169
+ model: MODEL,
170
+ thinking: THINKING,
171
+ released: { package: PACKAGE, version: latest.version, integrity: latest.integrity },
172
+ local: {
173
+ version: packed.version,
174
+ integrity: packed.integrity,
175
+ shasum: packed.shasum,
176
+ sourceDigest,
177
+ },
178
+ config: configPath,
179
+ };
180
+ writeFileSync(join(root, "metadata.json"), `${JSON.stringify(metadata, null, 2)}\n`);
181
+ console.log(JSON.stringify(metadata, null, 2));