pi-ptc-subagents 7.2.0 → 7.4.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.
@@ -0,0 +1,197 @@
1
+ import { stripTypeScriptTypes } from "node:module";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import vm from "node:vm";
4
+ // src/compile.ts
5
+ function compileProgram(source) {
6
+ return `"use strict";\n${stripTypeScriptTypes(`async function __ptcProgram() {\n${source}\n}`, { mode: "strip" })}\n;globalThis.__ptcProgram = __ptcProgram;`;
7
+ }
8
+ // src/worker-main.ts
9
+ const CELL_PREAMBLE = `"use strict";
10
+ (function () {
11
+ var __bridge = globalThis.__ptcCall;
12
+ var __emit = globalThis.__ptcConsole;
13
+ var __args = JSON.parse(globalThis.__ptcArgsJson);
14
+ // contextified 全局属性不可 delete(vm 限制)但可写——置 undefined 即脱手,桥只剩序言闭包可达
15
+ globalThis.__ptcCall = undefined;
16
+ globalThis.__ptcConsole = undefined;
17
+ globalThis.__ptcArgsJson = undefined;
18
+ function __ptcFmt(v) {
19
+ if (typeof v === "string") return v;
20
+ try { var s = JSON.stringify(v); return s === undefined ? String(v) : s; } catch (e) { return String(v); }
21
+ }
22
+ function __ptcJoin(values) {
23
+ var out = [];
24
+ for (var i = 0; i < values.length; i++) out.push(__ptcFmt(values[i]));
25
+ return out.join(" ");
26
+ }
27
+ var __ptcTools = {};
28
+ __PTC_NAMES__.forEach(function (name) {
29
+ __ptcTools[name] = function (args) {
30
+ return new Promise(function (resolve, reject) {
31
+ __bridge(name, JSON.stringify(args === undefined ? null : args)).then(function (json) {
32
+ var value;
33
+ try { value = JSON.parse(json); } catch (e) { reject(e); return; }
34
+ resolve(value);
35
+ }, reject);
36
+ });
37
+ };
38
+ });
39
+ globalThis.tools = Object.freeze(__ptcTools);
40
+ globalThis.console = Object.freeze({
41
+ log: function () { __emit("log", __ptcJoin(arguments)); },
42
+ info: function () { __emit("info", __ptcJoin(arguments)); },
43
+ warn: function () { __emit("warn", __ptcJoin(arguments)); },
44
+ error: function () { __emit("error", __ptcJoin(arguments)); },
45
+ debug: function () { __emit("debug", __ptcJoin(arguments)); }
46
+ });
47
+ globalThis.args = Object.freeze(__args);
48
+ if (__PTC_WORKFLOW__) {
49
+ globalThis.workflow = Object.freeze({
50
+ log: function () { __emit("log", __ptcJoin(arguments)); },
51
+ phase: function (name) { __emit("log", "— phase: " + name + " —"); },
52
+ parallel: function (items) { return Promise.allSettled(items.map(function (run) { return run(); })); },
53
+ pipeline: function (items, step) {
54
+ var results = [];
55
+ return items.reduce(function (chain, item, index) {
56
+ return chain.then(function () { return Promise.resolve(step(item, index)).then(function (r) { results.push(r); }); });
57
+ }, Promise.resolve()).then(function () { return results; });
58
+ }
59
+ });
60
+ }
61
+ })();
62
+ `;
63
+ function workerMain(deps) {
64
+ let bindingNames = [];
65
+ let surface = "run_code";
66
+ let pending = /* @__PURE__ */ new Map();
67
+ let nextCallId = 1;
68
+ let currentRunId = null;
69
+ let logs = [];
70
+ deps.onFrame((frame) => {
71
+ if (frame.kind === "init") {
72
+ bindingNames = frame.bindingNames;
73
+ surface = frame.surface;
74
+ deps.post({ kind: "ready" });
75
+ return;
76
+ }
77
+ if (frame.kind === "reset") {
78
+ currentRunId = null;
79
+ for (const { reject } of pending.values()) reject(/* @__PURE__ */ new Error("run reset:worker 被复用"));
80
+ pending = /* @__PURE__ */ new Map();
81
+ deps.post({ kind: "ready" });
82
+ return;
83
+ }
84
+ if (frame.kind === "result") {
85
+ const entry = pending.get(frame.callId);
86
+ if (!entry) return;
87
+ pending.delete(frame.callId);
88
+ if (frame.ok) {
89
+ let json;
90
+ try {
91
+ json = JSON.stringify(frame.value ?? null);
92
+ } catch {
93
+ json = void 0;
94
+ }
95
+ if (json === void 0) entry.reject(/* @__PURE__ */ new Error("binding 返回值不可 JSON 序列化(如循环引用)"));
96
+ else entry.resolve(json);
97
+ } else entry.reject(new Error(frame.error));
98
+ return;
99
+ }
100
+ if (frame.kind === "cancel") return;
101
+ run(frame.code, frame.id, frame.args);
102
+ });
103
+ function preamble() {
104
+ return CELL_PREAMBLE.replace(/__PTC_NAMES__/g, () => JSON.stringify(bindingNames)).replace(/__PTC_WORKFLOW__/g, () => surface === "workflow" ? "true" : "false");
105
+ }
106
+ async function run(code, runId, args) {
107
+ currentRunId = runId;
108
+ logs = [];
109
+ const postLog = (level, text) => {
110
+ logs.push(text);
111
+ deps.post({
112
+ kind: "log",
113
+ runId,
114
+ level,
115
+ text
116
+ });
117
+ };
118
+ const outcome = await execute(code, vm.createContext({
119
+ __ptcArgsJson: JSON.stringify(args ?? {}),
120
+ __ptcCall(name, argsJson) {
121
+ if (currentRunId === null) return Promise.reject(/* @__PURE__ */ new Error("run 已结束:reset 后不再接受工具调用"));
122
+ let parsed = null;
123
+ try {
124
+ parsed = JSON.parse(argsJson);
125
+ } catch {
126
+ return Promise.reject(/* @__PURE__ */ new Error("工具参数不是合法 JSON"));
127
+ }
128
+ const call = new Promise((resolve, reject) => {
129
+ const callId = nextCallId++;
130
+ pending.set(callId, {
131
+ resolve,
132
+ reject
133
+ });
134
+ deps.post({
135
+ kind: "call",
136
+ runId: currentRunId ?? "",
137
+ callId,
138
+ name,
139
+ args: parsed
140
+ });
141
+ });
142
+ call.catch(() => {});
143
+ return call;
144
+ },
145
+ __ptcConsole(level, text) {
146
+ postLog(level, String(text));
147
+ }
148
+ }));
149
+ deps.post({
150
+ kind: "settled",
151
+ runId,
152
+ outcome
153
+ });
154
+ }
155
+ function errorMessage(error) {
156
+ if (error instanceof Error) return error.message;
157
+ if (error && typeof error === "object" && typeof error.message === "string") return error.message;
158
+ return String(error);
159
+ }
160
+ async function execute(code, context) {
161
+ try {
162
+ vm.runInContext(preamble(), context, { filename: "ptc_preamble.js" });
163
+ vm.runInContext(compileProgram(code), context, { filename: "run_code.js" });
164
+ const value = await context.__ptcProgram.call(context);
165
+ if (value === void 0) return {
166
+ status: "fulfilled",
167
+ value: { $void: true },
168
+ logs
169
+ };
170
+ const json = JSON.stringify(value);
171
+ if (json === void 0) return {
172
+ status: "rejected",
173
+ error: "程序返回值不可序列化",
174
+ logs
175
+ };
176
+ return {
177
+ status: "fulfilled",
178
+ value: JSON.parse(json),
179
+ logs
180
+ };
181
+ } catch (error) {
182
+ return {
183
+ status: "rejected",
184
+ error: errorMessage(error),
185
+ logs
186
+ };
187
+ }
188
+ }
189
+ }
190
+ // src/worker-entry.ts
191
+ if (!parentPort) throw new Error("worker-entry 只能在 worker 线程里加载");
192
+ Object.freeze(workerData);
193
+ workerMain({
194
+ post: (frame) => parentPort.postMessage(frame),
195
+ onFrame: (handler) => parentPort.on("message", (frame) => handler(frame))
196
+ });
197
+ export {};
@@ -15,6 +15,9 @@ agent 定义里的 `tools / model / thinkingLevel / childReport` 由 frontmatter
15
15
  任务建成后在任务记录里排队(满员排队不拒绝,ADR-0010),经
16
16
  `ptc_task_list / ptc_task_output / ptc_task_stop` 查/停。
17
17
 
18
+ 任务记录带宿主会话 id(`sessionId`)——按会话筛任务用 `ptc_task_list({ session: "current" })`,
19
+ 跨会话的旧记录用 `scope=all` 找。压缩归档(`scope=all`)里的条目也带 `archived: true`。
20
+
18
21
  ## 什么时候值得派(判据)
19
22
 
20
23
  - **值得**:主题多、彼此独立、每个都要读一堆材料——父一个人扛会算错,一份材料一个孩子最划算。
@@ -2,15 +2,69 @@
2
2
 
3
3
  三个工具,有任务记录才出现在模型面前(ADR-0007:可见性 = 有无记录,不是独立开关):
4
4
 
5
- - `ptc_task_list()`:在飞的全部 + 最近完成的(默认 10 条)
6
- - `ptc_task_output({ task_id })`:读一条记录(状态/产出/报告)
5
+ - `ptc_task_list()`:在飞的全部 + 最近完成的任务。响应是对象。
6
+ - `ptc_task_output({ task_id })`:读一条记录。响应是对象。
7
7
  - `ptc_task_stop({ task_id, reason? })`:排队中的抽掉(canceled);在跑的转 stopping
8
8
 
9
- 任务记录是派发的唯一痕迹:每次建任务都建记录,前台任务也一样。
9
+ 任务记录是派发的唯一痕迹:每次建任务都建记录,等不等也一样建。
10
10
 
11
11
  终态记录热存留最近 500 条(决策 29),被挤出的进压缩存档(默认留 30 天,
12
12
  `archiveEnabled: false` 则直接删),凭精确 task id 经 `ptc_task_output` 仍读得到。
13
13
 
14
+ ## ptc_task_list
15
+
16
+ 参数:
17
+
18
+ - `scope`:`recent`(默认,只热存)/ `all`(连压缩归档一起列,更全也更慢)
19
+ - `limit`:取多少条(默认 50,上限 200)
20
+ - `status`:只看某个状态(七个状态之一)
21
+ - `session`:`current` = 本会话,或给一个会话 id
22
+
23
+ 响应:
24
+
25
+ ```json
26
+ {
27
+ "scope": "recent",
28
+ "total": 520,
29
+ "tasks": [
30
+ {
31
+ "id": "5d90e136…",
32
+ "status": "succeeded",
33
+ "label": "口径汇总",
34
+ "finishedAt": 1758000123456,
35
+ "task": "读 AGENTS.md 并汇总六个包的对外口径",
36
+ "summary": "六个包口径一致"
37
+ }
38
+ ]
39
+ }
40
+ ```
41
+
42
+ - `total` = 候选总数。它比 `tasks.length` 大,说明还有更早的记录没列出来。
43
+ - 每条字段各自封顶(task 80 字、summary 100 字、label 120 字)。响应按字节预算逐条裁剪,超预算就停——返回的永远是合法 JSON(不会砍成半截 JSON)。
44
+ - `scope=all` 时归档条目带 `archived: true`。
45
+ - `session` 筛选时条目回显 `sessionId`。老记录没有这个字段,不匹配任何会话。
46
+
47
+ ## ptc_task_output
48
+
49
+ 参数:`task_id`(一条任务记录 id)。
50
+
51
+ 响应 = 那条记录 + 归约层:
52
+
53
+ - `text`:孩子的散文答案。**交付物是这段正文**,报告只是摘要。正文从事件流现算,不另存一份。
54
+ - 没有正文时给 `output`(原始事件流尾部,50KB 截断,砍头留尾)+ `guidance`(分层引导:先看报告,完整原文在 `outputPath`)。
55
+ - `report`:孩子交的报告(`summary` + `findings`)。孩子没交报告时,这个键整个不存在。
56
+ - `outputPath`:原始事件流的路径。
57
+
58
+ registry 里记不住的 id 会回落压缩归档(TTL 窗口内仍可解,标 `archived: true`)。
59
+
60
+ ## 程序内检索
61
+
62
+ `ptc_run_code` 的程序内可调 `tools.task_records({ scope: "all" })`:拿全部任务记录(含压缩归档)。
63
+
64
+ 机械扫描在宿主做,筛选与归约在程序里做。程序只 `return` 要的几条,其余不进上下文。
65
+
66
+ 正则跑在运行格里,运行格有 terminate 兜底;宿主本身没有隔离——病态正则要在程序里跑,别在宿主里跑。
67
+
14
68
  ## 交付核对(delivery)
15
69
 
16
70
  任务记录与产出里带 `delivery`:**引擎从孩子的进程事件流抽的实测轨迹**,不是孩子的自述。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ptc-subagents",
3
- "version": "7.2.0",
3
+ "version": "7.4.0",
4
4
  "description": "Programmable tool calling (PTC) and subagent fan-out for pi",
5
5
  "keywords": [
6
6
  "pi",
@@ -18,6 +18,9 @@
18
18
  "type": "git",
19
19
  "url": "git+https://github.com/a1121611810/pi-ptc-subagents.git"
20
20
  },
21
+ "bin": {
22
+ "ptc": "./dist/ptc/cli.js"
23
+ },
21
24
  "files": [
22
25
  "dist/**/*.js",
23
26
  "docs/routes/**/*.md",
@@ -27,18 +30,20 @@
27
30
  "type": "module",
28
31
  "main": "./dist/index.js",
29
32
  "exports": {
30
- ".": "./dist/index.js"
33
+ ".": "./dist/index.js",
34
+ "./ptc": "./dist/ptc/index.js"
31
35
  },
32
36
  "scripts": {
33
37
  "build": "pnpm -r --filter=!@pi-ptc/website run build",
34
38
  "build:kimi": "pnpm --filter @pi-ptc/kimi-mcp run build && node scripts/assemble-kimi.mjs",
35
- "prepack": "pnpm run build && rm -rf dist docs/routes && cp -R packages/entry/dist dist && cp -R packages/routes/docs/routes docs/routes",
39
+ "prepack": "pnpm run build && rm -rf dist docs/routes && cp -R packages/entry/dist dist && cp -R packages/ptc/dist dist/ptc && cp -R packages/routes/docs/routes docs/routes",
36
40
  "check": "pnpm -r --filter=!@pi-ptc/website run check",
37
41
  "lint": "oxlint",
38
- "test": "pnpm run test:pi && pnpm run test:output && pnpm run test:kimi",
42
+ "test": "pnpm run test:pi && pnpm run test:output && pnpm run test:kimi && pnpm run test:standalone",
39
43
  "test:pi": "node --test tests/pi/*.test.mjs",
40
44
  "test:output": "node --test tests/output/*.test.mjs",
41
45
  "test:kimi": "node --test tests/kimi/*.test.mjs",
46
+ "test:standalone": "node --test tests/standalone/*.test.mjs",
42
47
  "pretest": "pnpm run build",
43
48
  "fmt": "oxfmt",
44
49
  "fmt:check": "oxfmt --check"