pi-ptc-subagents 6.1.0 → 7.0.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.
- package/dist/index.js +34 -1
- package/dist/worker.js +115 -76
- package/docs/routes/create-a-task.md +11 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { mkdir, open, readFile, rename, writeFile } from "node:fs/promises";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
7
|
import { gunzipSync, gzip } from "node:zlib";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -3062,6 +3062,7 @@ const REPORT_CONTRACT_BLOCK = [
|
|
|
3062
3062
|
"```json",
|
|
3063
3063
|
"{ \"summary\": \"one sentence conclusion\", \"findings\": [{ \"what\": \"claim\", \"evidence\": \"where it comes from\" }], \"files_touched\": [\"relative/path\"], \"usage\": { \"input\": 0, \"output\": 0, \"cost\": 0, \"turns\": 0 } }",
|
|
3064
3064
|
"```",
|
|
3065
|
+
"Rules: \"summary\" is your deliverable — answer the task asked, in one sentence. \"findings\" carries load-bearing evidence only; never recap content you merely read. Do NOT write report/summary .md files — your final message is what the parent reads.",
|
|
3065
3066
|
"findings/files_touched/usage may be omitted when empty. Your prose answer stays; the report is checked alongside it.",
|
|
3066
3067
|
"</child-report-contract>"
|
|
3067
3068
|
].join("\n");
|
|
@@ -3132,6 +3133,14 @@ function spawnChild(argv, env = process.env) {
|
|
|
3132
3133
|
function escapeXmlText(text) {
|
|
3133
3134
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3134
3135
|
}
|
|
3136
|
+
function supportsNoMcp(piVersion) {
|
|
3137
|
+
if (!piVersion) return false;
|
|
3138
|
+
const [major = "0", minor = "0"] = piVersion.trim().replace(/^v/, "").split(".");
|
|
3139
|
+
const M = Number.parseInt(major, 10);
|
|
3140
|
+
const m = Number.parseInt(minor, 10);
|
|
3141
|
+
if (Number.isNaN(M) || Number.isNaN(m)) return false;
|
|
3142
|
+
return M > 1 || M === 1 && m >= 1;
|
|
3143
|
+
}
|
|
3135
3144
|
function buildPiArgv(input, context) {
|
|
3136
3145
|
const argv = [
|
|
3137
3146
|
"pi",
|
|
@@ -3148,6 +3157,8 @@ function buildPiArgv(input, context) {
|
|
|
3148
3157
|
"</pi-ptc-context>"
|
|
3149
3158
|
].filter(Boolean).join("\n")
|
|
3150
3159
|
];
|
|
3160
|
+
argv.push("--no-extensions", "--no-skills");
|
|
3161
|
+
if (supportsNoMcp(context.piVersion)) argv.push("--no-mcp");
|
|
3151
3162
|
const model = input.model ?? input.agentModel ?? context.sessionModel;
|
|
3152
3163
|
const thinking = input.thinkingLevel ?? input.agentThinkingLevel;
|
|
3153
3164
|
if (model) argv.push("--model", thinking ? `${model}:${thinking}` : model);
|
|
@@ -3235,6 +3246,8 @@ function parseChildStream(stdout) {
|
|
|
3235
3246
|
observedUsage = {
|
|
3236
3247
|
input: message.usage.input,
|
|
3237
3248
|
output: message.usage.output,
|
|
3249
|
+
...message.usage.cacheRead !== void 0 ? { cacheRead: message.usage.cacheRead } : {},
|
|
3250
|
+
...message.usage.cacheWrite !== void 0 ? { cacheWrite: message.usage.cacheWrite } : {},
|
|
3238
3251
|
...cost !== void 0 ? { cost } : {}
|
|
3239
3252
|
};
|
|
3240
3253
|
}
|
|
@@ -4964,6 +4977,7 @@ function assemblePtcSession(options = {}) {
|
|
|
4964
4977
|
registry,
|
|
4965
4978
|
gates: options.gates,
|
|
4966
4979
|
whyEnabled: options.whyEnabled,
|
|
4980
|
+
context: options.context,
|
|
4967
4981
|
...options.engine
|
|
4968
4982
|
});
|
|
4969
4983
|
return {
|
|
@@ -4994,6 +5008,20 @@ const textResult = (value) => ({
|
|
|
4994
5008
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
4995
5009
|
const publishedDetailDir = join(packageRoot, "docs", "routes");
|
|
4996
5010
|
const detailDir = existsSync(publishedDetailDir) ? publishedDetailDir : join(packageRoot, "..", "routes", "docs", "routes");
|
|
5011
|
+
function resolvePiVersion() {
|
|
5012
|
+
try {
|
|
5013
|
+
return execSync("pi --version", {
|
|
5014
|
+
encoding: "utf8",
|
|
5015
|
+
stdio: [
|
|
5016
|
+
"ignore",
|
|
5017
|
+
"pipe",
|
|
5018
|
+
"ignore"
|
|
5019
|
+
]
|
|
5020
|
+
}).trim() || void 0;
|
|
5021
|
+
} catch {
|
|
5022
|
+
return;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
4997
5025
|
function piPtcEntry(pi) {
|
|
4998
5026
|
pi.on("session_start", (_event, ctx) => {
|
|
4999
5027
|
if (Number(process.env.PI_PTC_DEPTH ?? 0) > 0) {
|
|
@@ -5034,6 +5062,7 @@ function piPtcEntry(pi) {
|
|
|
5034
5062
|
userDir: join(getAgentDir(), "agents"),
|
|
5035
5063
|
projectDir: join(process.cwd(), ".pi", "agents")
|
|
5036
5064
|
};
|
|
5065
|
+
const piVersion = resolvePiVersion();
|
|
5037
5066
|
let session;
|
|
5038
5067
|
const faceAddedByUs = /* @__PURE__ */ new Set();
|
|
5039
5068
|
const applyTaskFaceVisibility = () => {
|
|
@@ -5055,6 +5084,10 @@ function piPtcEntry(pi) {
|
|
|
5055
5084
|
session = assemblePtcSession({
|
|
5056
5085
|
onTransition: () => applyTaskFaceVisibility(),
|
|
5057
5086
|
whyEnabled: () => whyEnabledOverride ?? whyEnabledPersisted,
|
|
5087
|
+
context: {
|
|
5088
|
+
depth: 0,
|
|
5089
|
+
piVersion
|
|
5090
|
+
},
|
|
5058
5091
|
engine: {
|
|
5059
5092
|
notifications,
|
|
5060
5093
|
resolveAgent: (name, scope) => findAgent(name, agentDirs, scope),
|
package/dist/worker.js
CHANGED
|
@@ -1,66 +1,76 @@
|
|
|
1
1
|
import { stripTypeScriptTypes } from "node:module";
|
|
2
|
-
import { inspect } from "node:util";
|
|
3
2
|
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
import vm from "node:vm";
|
|
4
4
|
// ../ptc/src/compile.ts
|
|
5
5
|
function compileProgram(source) {
|
|
6
|
-
|
|
7
|
-
return new Function(`"use strict";\n${stripped}\nreturn __ptcProgram;`)();
|
|
8
|
-
}
|
|
9
|
-
// ../ptc/src/workflow-helpers.ts
|
|
10
|
-
function createWorkflowHelpers(emit) {
|
|
11
|
-
return {
|
|
12
|
-
log: (...args) => emit(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")),
|
|
13
|
-
phase: (name) => emit(`— phase: ${name} —`),
|
|
14
|
-
parallel: (items) => Promise.allSettled(items.map((run) => run())),
|
|
15
|
-
pipeline: (items, step) => {
|
|
16
|
-
const results = [];
|
|
17
|
-
return items.reduce((chain, item, index) => chain.then(async () => {
|
|
18
|
-
results.push(await step(item, index));
|
|
19
|
-
}), Promise.resolve()).then(() => results);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
6
|
+
return `"use strict";\n${stripTypeScriptTypes(`async function __ptcProgram() {\n${source}\n}`, { mode: "strip" })}\n;globalThis.__ptcProgram = __ptcProgram;`;
|
|
22
7
|
}
|
|
23
8
|
// ../ptc/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
|
+
`;
|
|
24
63
|
function workerMain(deps) {
|
|
25
|
-
let
|
|
64
|
+
let bindingNames = [];
|
|
65
|
+
let surface = "run_code";
|
|
26
66
|
let pending = /* @__PURE__ */ new Map();
|
|
27
67
|
let nextCallId = 1;
|
|
28
68
|
let currentRunId = null;
|
|
29
69
|
let logs = [];
|
|
30
|
-
const globalBaseline = /* @__PURE__ */ new Set();
|
|
31
|
-
const consoleBaseline = { ...console };
|
|
32
70
|
deps.onFrame((frame) => {
|
|
33
71
|
if (frame.kind === "init") {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (currentRunId === null) return Promise.reject(/* @__PURE__ */ new Error("run 已结束:reset 后不再接受工具调用"));
|
|
37
|
-
const call = new Promise((resolve, reject) => {
|
|
38
|
-
const callId = nextCallId++;
|
|
39
|
-
pending.set(callId, {
|
|
40
|
-
resolve,
|
|
41
|
-
reject
|
|
42
|
-
});
|
|
43
|
-
deps.post({
|
|
44
|
-
kind: "call",
|
|
45
|
-
runId: currentRunId ?? "",
|
|
46
|
-
callId,
|
|
47
|
-
name,
|
|
48
|
-
args
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
call.catch(() => {});
|
|
52
|
-
return call;
|
|
53
|
-
}])));
|
|
54
|
-
defineGlobal("tools", tools);
|
|
55
|
-
if (frame.surface === "workflow") defineGlobal("workflow", createWorkflowHelpers((text) => {
|
|
56
|
-
logs.push(text);
|
|
57
|
-
deps.post({
|
|
58
|
-
kind: "log",
|
|
59
|
-
runId: currentRunId ?? "",
|
|
60
|
-
level: "log",
|
|
61
|
-
text
|
|
62
|
-
});
|
|
63
|
-
}));
|
|
72
|
+
bindingNames = frame.bindingNames;
|
|
73
|
+
surface = frame.surface;
|
|
64
74
|
deps.post({ kind: "ready" });
|
|
65
75
|
return;
|
|
66
76
|
}
|
|
@@ -68,8 +78,6 @@ function workerMain(deps) {
|
|
|
68
78
|
currentRunId = null;
|
|
69
79
|
for (const { reject } of pending.values()) reject(/* @__PURE__ */ new Error("run reset:worker 被复用"));
|
|
70
80
|
pending = /* @__PURE__ */ new Map();
|
|
71
|
-
for (const name of Object.getOwnPropertyNames(globalThis)) if (!globalBaseline.has(name)) delete globalThis[name];
|
|
72
|
-
Object.assign(console, consoleBaseline);
|
|
73
81
|
deps.post({ kind: "ready" });
|
|
74
82
|
return;
|
|
75
83
|
}
|
|
@@ -77,25 +85,28 @@ function workerMain(deps) {
|
|
|
77
85
|
const entry = pending.get(frame.callId);
|
|
78
86
|
if (!entry) return;
|
|
79
87
|
pending.delete(frame.callId);
|
|
80
|
-
if (frame.ok)
|
|
81
|
-
|
|
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));
|
|
82
98
|
return;
|
|
83
99
|
}
|
|
84
100
|
if (frame.kind === "cancel") return;
|
|
85
101
|
run(frame.code, frame.id, frame.args);
|
|
86
102
|
});
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
value,
|
|
90
|
-
writable: false,
|
|
91
|
-
configurable: true
|
|
92
|
-
});
|
|
103
|
+
function preamble() {
|
|
104
|
+
return CELL_PREAMBLE.replace(/__PTC_NAMES__/g, () => JSON.stringify(bindingNames)).replace(/__PTC_WORKFLOW__/g, () => surface === "workflow" ? "true" : "false");
|
|
93
105
|
}
|
|
94
106
|
async function run(code, runId, args) {
|
|
95
107
|
currentRunId = runId;
|
|
96
108
|
logs = [];
|
|
97
|
-
|
|
98
|
-
installConsole((level, text) => {
|
|
109
|
+
const postLog = (level, text) => {
|
|
99
110
|
logs.push(text);
|
|
100
111
|
deps.post({
|
|
101
112
|
kind: "log",
|
|
@@ -103,17 +114,54 @@ function workerMain(deps) {
|
|
|
103
114
|
level,
|
|
104
115
|
text
|
|
105
116
|
});
|
|
106
|
-
}
|
|
107
|
-
const outcome = await execute(code
|
|
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
|
+
}));
|
|
108
149
|
deps.post({
|
|
109
150
|
kind: "settled",
|
|
110
151
|
runId,
|
|
111
152
|
outcome
|
|
112
153
|
});
|
|
113
154
|
}
|
|
114
|
-
|
|
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) {
|
|
115
161
|
try {
|
|
116
|
-
|
|
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);
|
|
117
165
|
if (value === void 0) return {
|
|
118
166
|
status: "fulfilled",
|
|
119
167
|
value: { $void: true },
|
|
@@ -133,21 +181,12 @@ function workerMain(deps) {
|
|
|
133
181
|
} catch (error) {
|
|
134
182
|
return {
|
|
135
183
|
status: "rejected",
|
|
136
|
-
error:
|
|
184
|
+
error: errorMessage(error),
|
|
137
185
|
logs
|
|
138
186
|
};
|
|
139
187
|
}
|
|
140
188
|
}
|
|
141
189
|
}
|
|
142
|
-
function installConsole(emit) {
|
|
143
|
-
for (const level of [
|
|
144
|
-
"log",
|
|
145
|
-
"info",
|
|
146
|
-
"warn",
|
|
147
|
-
"error",
|
|
148
|
-
"debug"
|
|
149
|
-
]) console[level] = (...args) => emit(level, args.map((a) => typeof a === "string" ? a : inspect(a)).join(" "));
|
|
150
|
-
}
|
|
151
190
|
// ../ptc/src/worker-entry.ts
|
|
152
191
|
if (!parentPort) throw new Error("worker-entry 只能在 worker 线程里加载");
|
|
153
192
|
Object.freeze(workerData);
|
|
@@ -13,3 +13,14 @@ agent 定义里的 `tools / model / thinkingLevel / childReport` 由 frontmatter
|
|
|
13
13
|
|
|
14
14
|
任务建成后在任务记录里排队(满员排队不拒绝,ADR-0010),经
|
|
15
15
|
`ptc_task_list / ptc_task_output / ptc_task_stop` 查/停。
|
|
16
|
+
|
|
17
|
+
## Briefing 纪律(省 token,派发前自查)
|
|
18
|
+
|
|
19
|
+
- **给确切路径、命令、决策**:孩子看不到父会话。task 里写死一个路径,省孩子一轮探索
|
|
20
|
+
(每轮探索 = 一次工具调用结果 + 推理 token)。
|
|
21
|
+
- **写明最终消息要带回什么**:孩子只回一次。要什么格式、哪些字段,task 里说清。
|
|
22
|
+
- **不确定能否一次搜中才派**:目标明确(已知路径、已知行号)的活别派,主会话直接
|
|
23
|
+
`tools.read` 更省。
|
|
24
|
+
- **同域的活合并给一个串行孩子**:每个孩子都付一份固定 harness 税(系统提示词 +
|
|
25
|
+
项目上下文)。多个重叠的小活拆并行 = 同一份探索被付费多次。
|
|
26
|
+
- **孩子不写报告文件**:交付物就是最终消息(含报告 JSON 块),落盘 .md 是浪费。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ptc-subagents",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Programmable tool calling (PTC) and subagent fan-out for pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -30,10 +30,12 @@
|
|
|
30
30
|
".": "./dist/index.js"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "pnpm -r run build",
|
|
33
|
+
"build": "pnpm -r --filter=!@pi-ptc/website run build",
|
|
34
34
|
"prepack": "pnpm run build && rm -rf dist docs/routes && cp -R packages/entry/dist dist && cp -R packages/routes/docs/routes docs/routes",
|
|
35
|
-
"check": "pnpm -r run check",
|
|
35
|
+
"check": "pnpm -r --filter=!@pi-ptc/website run check",
|
|
36
36
|
"lint": "oxlint",
|
|
37
|
+
"test": "node --test tests/*.test.mjs",
|
|
38
|
+
"pretest": "pnpm run build",
|
|
37
39
|
"fmt": "oxfmt",
|
|
38
40
|
"fmt:check": "oxfmt --check"
|
|
39
41
|
},
|