pi-ptc-subagents 6.0.0 → 6.0.2
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/README.md +6 -3
- package/dist/index.js +43 -40
- package/docs/routes/create-a-task.md +15 -0
- package/docs/routes/manage-a-task.md +9 -0
- package/docs/routes/reach-the-web.md +15 -0
- package/docs/routes/report-what-you-did.md +9 -0
- package/docs/routes/run-a-phased-workflow.md +12 -0
- package/docs/routes/run-one-program.md +11 -0
- package/docs/routes/use-the-built-in-tools.md +7 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -70,9 +70,12 @@ Which one you get depends on whether `codemode` is loaded and callable. Nothing
|
|
|
70
70
|
|
|
71
71
|
## PTC mode
|
|
72
72
|
|
|
73
|
-
On a `full_ptc` session, PTC mode
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
On a `full_ptc` session, PTC mode adds a routing lead to the system prompt: batch your
|
|
74
|
+
tool calls into one program. `/ptc mode off` drops the lead.
|
|
75
|
+
|
|
76
|
+
It does not hide the built-in tools from the model. pi keeps a tool callable from a
|
|
77
|
+
program only while it is active, so narrowing the loadout would take `tools.read` away
|
|
78
|
+
from the program too — the two cannot both hold on pi 1.4.0.
|
|
76
79
|
|
|
77
80
|
## Configuration
|
|
78
81
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -4890,7 +4890,10 @@ function createPtcRunCodeTool(options) {
|
|
|
4890
4890
|
description: TOOLS$1.ptc_run_code.description(options.surface ?? "full_ptc"),
|
|
4891
4891
|
parameters: RUN_CODE_PARAMETERS,
|
|
4892
4892
|
renderResult: renderPtcResult("run_code"),
|
|
4893
|
-
execute: async (
|
|
4893
|
+
execute: async (...rawArgs) => {
|
|
4894
|
+
const args = rawArgs[1];
|
|
4895
|
+
const ctx = rawArgs.find((value) => typeof value?.executeTool === "function");
|
|
4896
|
+
options.onToolContext?.(ctx);
|
|
4894
4897
|
const outcome = await runPtcProgram({
|
|
4895
4898
|
code: args.code,
|
|
4896
4899
|
surface: "run_code",
|
|
@@ -4936,7 +4939,10 @@ function createPtcWorkflowTool(options) {
|
|
|
4936
4939
|
description: TOOLS$1.ptc_workflow.description(options.surface ?? "full_ptc"),
|
|
4937
4940
|
parameters: WORKFLOW_PARAMETERS,
|
|
4938
4941
|
renderResult: renderPtcResult("workflow"),
|
|
4939
|
-
execute: async (
|
|
4942
|
+
execute: async (...rawArgs) => {
|
|
4943
|
+
const params = rawArgs[1];
|
|
4944
|
+
const ctx = rawArgs.find((value) => typeof value?.executeTool === "function");
|
|
4945
|
+
options.onToolContext?.(ctx);
|
|
4940
4946
|
try {
|
|
4941
4947
|
validateWorkflowArgs(params.args);
|
|
4942
4948
|
} catch (error) {
|
|
@@ -5116,11 +5122,11 @@ const ROUTE_INDEX_LEAD = {
|
|
|
5116
5122
|
full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。",
|
|
5117
5123
|
codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。"
|
|
5118
5124
|
};
|
|
5119
|
-
function renderRouteIndex(surface) {
|
|
5120
|
-
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${route.detail}`);
|
|
5125
|
+
function renderRouteIndex(surface, detailBase, nudge = true) {
|
|
5126
|
+
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${detailBase ? join(detailBase, route.detail.split("/").pop()) : route.detail}`);
|
|
5121
5127
|
let index = [
|
|
5122
5128
|
`<ptc>`,
|
|
5123
|
-
ROUTE_INDEX_LEAD[surface],
|
|
5129
|
+
...nudge ? [ROUTE_INDEX_LEAD[surface]] : [],
|
|
5124
5130
|
"路由表(一条一行,详情按需 read):",
|
|
5125
5131
|
...lines,
|
|
5126
5132
|
`</ptc>`
|
|
@@ -5221,6 +5227,9 @@ const textResult = (value) => ({
|
|
|
5221
5227
|
}],
|
|
5222
5228
|
details: void 0
|
|
5223
5229
|
});
|
|
5230
|
+
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
5231
|
+
const publishedDetailDir = join(packageRoot, "docs", "routes");
|
|
5232
|
+
const detailDir = existsSync(publishedDetailDir) ? publishedDetailDir : join(packageRoot, "..", "routes", "docs", "routes");
|
|
5224
5233
|
function piPtcEntry(pi) {
|
|
5225
5234
|
pi.on("session_start", (_event, ctx) => {
|
|
5226
5235
|
if (Number(process.env.PI_PTC_DEPTH ?? 0) > 0) {
|
|
@@ -5230,37 +5239,15 @@ function piPtcEntry(pi) {
|
|
|
5230
5239
|
const surface = pi.getActiveTools().includes("codemode") ? "codemode_ptc" : FALLBACK_SURFACE_MODE;
|
|
5231
5240
|
let modeEnabled = readDefaultModeConfig(getAgentDir());
|
|
5232
5241
|
let modeActive = false;
|
|
5233
|
-
let baseLoadout = null;
|
|
5234
5242
|
const applyMode = () => {
|
|
5235
|
-
|
|
5236
|
-
const shouldEnter = surface === "full_ptc" && modeEnabled && active.includes("ptc_run_code");
|
|
5237
|
-
if (shouldEnter && !modeActive) {
|
|
5238
|
-
baseLoadout ??= [...active];
|
|
5239
|
-
pi.setActiveTools(active.filter((name) => !KNOWN_BINDING_NAMES.includes(name)));
|
|
5240
|
-
modeActive = true;
|
|
5241
|
-
} else if (!shouldEnter && modeActive && baseLoadout) {
|
|
5242
|
-
pi.setActiveTools(baseLoadout);
|
|
5243
|
-
baseLoadout = null;
|
|
5244
|
-
modeActive = false;
|
|
5245
|
-
}
|
|
5243
|
+
modeActive = surface === "full_ptc" && modeEnabled;
|
|
5246
5244
|
};
|
|
5247
5245
|
applyMode();
|
|
5248
5246
|
pi.on("before_agent_start", (event) => {
|
|
5249
5247
|
applyMode();
|
|
5250
5248
|
applyTaskFaceVisibility(pi, session);
|
|
5251
5249
|
const sections = event.systemPromptOptions?.sections;
|
|
5252
|
-
if (sections) sections["ptc"] = renderRouteIndex(surface);
|
|
5253
|
-
if (!modeActive) return;
|
|
5254
|
-
const active = pi.getActiveTools();
|
|
5255
|
-
if (active.includes("read") || active.includes("bash")) return;
|
|
5256
|
-
const loaded = loadSkills({
|
|
5257
|
-
cwd: process.cwd(),
|
|
5258
|
-
agentDir: getAgentDir(),
|
|
5259
|
-
skillPaths: [],
|
|
5260
|
-
includeDefaults: true
|
|
5261
|
-
});
|
|
5262
|
-
if (loaded.skills.length === 0) return;
|
|
5263
|
-
if (sections) sections["available_skills"] = formatSkillsForPrompt(loaded.skills, "indirect");
|
|
5250
|
+
if (sections) sections["ptc"] = renderRouteIndex(surface, detailDir, modeActive);
|
|
5264
5251
|
});
|
|
5265
5252
|
const pool = new WorkerPool(spawnPtcWorker);
|
|
5266
5253
|
const notifications = new NotificationCenter();
|
|
@@ -5305,7 +5292,7 @@ function piPtcEntry(pi) {
|
|
|
5305
5292
|
]
|
|
5306
5293
|
});
|
|
5307
5294
|
pi.registerCommand("ptc", {
|
|
5308
|
-
description: "/ptc off|on:派发开关 · /ptc why on|off:理由开关 · /ptc mode on|off:PTC
|
|
5295
|
+
description: "/ptc off|on:派发开关 · /ptc why on|off:理由开关 · /ptc mode on|off:PTC 模式(程序优先的引导层)",
|
|
5309
5296
|
handler: async (args, cmdCtx) => {
|
|
5310
5297
|
const [sub, rest] = args.trim().split(/\s+/);
|
|
5311
5298
|
if (sub === "off") {
|
|
@@ -5358,12 +5345,16 @@ function piPtcEntry(pi) {
|
|
|
5358
5345
|
};
|
|
5359
5346
|
registerTaskFace(registerTracked, session, surface);
|
|
5360
5347
|
applyTaskFaceVisibility(pi, session);
|
|
5361
|
-
|
|
5348
|
+
let toolContext;
|
|
5349
|
+
const getBindings = () => createBindingTable(pi, session, () => toolContext);
|
|
5362
5350
|
registerPtcTools(registerTracked, {
|
|
5363
|
-
getBindings
|
|
5351
|
+
getBindings,
|
|
5364
5352
|
cwd: process.cwd(),
|
|
5365
5353
|
surface,
|
|
5366
|
-
getPool: () => pool
|
|
5354
|
+
getPool: () => pool,
|
|
5355
|
+
onToolContext: (ctx) => {
|
|
5356
|
+
toolContext = ctx;
|
|
5357
|
+
}
|
|
5367
5358
|
}, surface);
|
|
5368
5359
|
if (surface === "codemode_ptc") registerTaskDoor(registerTracked, session, surface);
|
|
5369
5360
|
pi.on("turn_end", () => pool.drain());
|
|
@@ -5463,18 +5454,30 @@ function registerChildToolsOnly(pi) {
|
|
|
5463
5454
|
})
|
|
5464
5455
|
});
|
|
5465
5456
|
}
|
|
5466
|
-
function
|
|
5467
|
-
|
|
5468
|
-
|
|
5457
|
+
function normalizeToolValue(value) {
|
|
5458
|
+
if (typeof value === "string") return value;
|
|
5459
|
+
if (Array.isArray(value)) return value.map((block) => typeof block === "string" ? block : block?.text ?? JSON.stringify(block)).join("\n");
|
|
5460
|
+
if (value && typeof value === "object") {
|
|
5461
|
+
const obj = value;
|
|
5462
|
+
if (typeof obj.content === "string") return obj.content;
|
|
5463
|
+
if (typeof obj.text === "string") return obj.text;
|
|
5464
|
+
}
|
|
5465
|
+
return value;
|
|
5466
|
+
}
|
|
5467
|
+
function createBindingTable(pi, session, getToolContext) {
|
|
5468
|
+
const active = pi.getActiveTools();
|
|
5469
|
+
const known = new Set(KNOWN_BINDING_NAMES);
|
|
5470
|
+
const names = resolveBindingNames(active.some((name) => known.has(name)) ? active : pi.getAllTools().map((tool) => tool.name));
|
|
5469
5471
|
const table = Object.fromEntries(names.map((name) => [name, async (args, callCtx) => {
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
+
const ctx = getToolContext();
|
|
5473
|
+
if (!ctx?.executeTool) throw new Error(`此 pi 版本没有程序内调用工具的通道(需 pi ≥ 0.99,binding: ${name})`);
|
|
5474
|
+
const outcome = await ctx.executeTool(name, args, { signal: callCtx.signal });
|
|
5472
5475
|
for (const block of outcome.result.content) if (block.type === "image") callCtx.emitImage(block);
|
|
5473
5476
|
if (outcome.isError) {
|
|
5474
5477
|
const text = outcome.result.content.map((block) => block.type === "text" ? block.text : `[${block.type}]`).join("\n");
|
|
5475
5478
|
throw new Error(text || `工具 ${name} 执行失败`);
|
|
5476
5479
|
}
|
|
5477
|
-
return outcome.result.structuredContent ?? outcome.result.content;
|
|
5480
|
+
return normalizeToolValue(outcome.result.structuredContent ?? outcome.result.content);
|
|
5478
5481
|
}]));
|
|
5479
5482
|
const taskBinding = createTaskBinding(session);
|
|
5480
5483
|
table[TASK_BINDING_NAME] = (args) => taskBinding.execute(args);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 派一个子代理
|
|
2
|
+
|
|
3
|
+
入口两扇门面(ADR-0006),同参同形(名字随所在平面),只是所在平面不同:
|
|
4
|
+
|
|
5
|
+
- `pi_task_ptc`:PTC 程序内的 binding,程序里 `tools.pi_task_ptc({...})`
|
|
6
|
+
- `pi_task_codemode`:注册的工具(exposure: codemode,模型不可见),codemode 脚本里 `tools.pi_task_codemode({...})`
|
|
7
|
+
|
|
8
|
+
参数:`task`(必填,孩子要干的活)、`label`、`wait`、`why`、`agent`、`agentScope`、`model`、`thinkingLevel`、`readOnly` 均已生效(`cwd` 待接);
|
|
9
|
+
agent 定义里的 `tools / model / thinkingLevel / childReport` 由 frontmatter 钉住。
|
|
10
|
+
|
|
11
|
+
返回 handle:`{ taskId, label?, status: "queued" }`。默认不等;撞闸(随闸门落地)返回
|
|
12
|
+
`{ status: "rejected", errorMessage }`,不抛异常。
|
|
13
|
+
|
|
14
|
+
任务建成后在任务记录里排队(满员排队不拒绝,ADR-0010),经
|
|
15
|
+
`ptc_task_list / ptc_task_output / ptc_task_stop` 查/停。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# 管一个任务
|
|
2
|
+
|
|
3
|
+
三个工具,有任务记录才出现在模型面前(ADR-0007:可见性 = 有无记录,不是独立开关):
|
|
4
|
+
|
|
5
|
+
- `ptc_task_list()`:在飞的全部 + 最近完成的(默认 10 条)
|
|
6
|
+
- `ptc_task_output({ task_id })`:读一条记录(状态/产出/报告)
|
|
7
|
+
- `ptc_task_stop({ task_id, reason? })`:排队中的抽掉(canceled);在跑的转 stopping
|
|
8
|
+
|
|
9
|
+
任务记录是派发的唯一痕迹:每次建任务都建记录,前台任务也一样。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 上网查资料(web_ds_search,ADR-0001–0049)
|
|
2
|
+
|
|
3
|
+
搜索能力**只活在被派出的子代理里**(父会话不注册这个工具)——「这条来源靠不靠谱」的判断力在干活的子代理侧。
|
|
4
|
+
|
|
5
|
+
三动作:
|
|
6
|
+
|
|
7
|
+
- `search({ query, count? })`:概览(title/url/snippet,默认 10 条)。snippet 是 DeepSeek 自算的短摘要——便宜且不丢事实;长 summary 不经概览给
|
|
8
|
+
- `open({ url })`:抓整页(记忆命中最近结果则免费)
|
|
9
|
+
- `find({ url, pattern })`:页内按正则匹配行(先查记忆)
|
|
10
|
+
|
|
11
|
+
预算与配额:
|
|
12
|
+
|
|
13
|
+
- 模型可见文本预算 12,000 字符;超了按排名**整条丢弃** + 告知丢了哪些(要哪个用 open 补抓)——不按字符截断
|
|
14
|
+
- 配额每进程计(`searchMaxCalls`,默认 30 次/进程,0 = 不限;只计成功调用)
|
|
15
|
+
- 开关 `webDsSearch`(`~/.pi/agent/ptc.json`)是布尔,**默认关**:开关关 = 工具不存在;开关开但缺凭据 = 注册但调用返回可读错误
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# 交报告(child report,旧 ADR-0032)
|
|
2
|
+
|
|
3
|
+
子代理收工前交一份结构化报告,四样:`summary`(一句话结论)、`findings`(每条 what +
|
|
4
|
+
独立 evidence)、`files_touched`、`usage`(input/output/cost/turns)。散文保留不替换。
|
|
5
|
+
|
|
6
|
+
- 契约写在孩子的追加 system prompt(唯一处),形式:最终消息**末尾的 fenced JSON 块**
|
|
7
|
+
- 宿主自动解析;`reportChannel` 四值:`tool` = 经 `ptc_child_report` 工具交的(首选通道,已落地)、`prompt-json` = 末尾 fenced JSON 解析到了、`none` = 没交(散文照交但不标可信)、`opted-out` = agent 定义 `childReport:false` 声明免交
|
|
8
|
+
- 报告落 TaskRecord,经 `ptc_task_output` 读得到;没交报告的孩子永远明说,不静默
|
|
9
|
+
- 工具通道(`ptc_child_report`)在子代理进程注册,已是首选;prompt 通道兜底
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 分阶段 workflow(ptc_workflow)
|
|
2
|
+
|
|
3
|
+
`ptc_workflow({ meta: { name, description, phases? }, script, args?, timeoutMs? })`。
|
|
4
|
+
|
|
5
|
+
script 内额外可用(仅 workflow 面注入):
|
|
6
|
+
|
|
7
|
+
- `workflow.log(...)`:阶段内日志
|
|
8
|
+
- `workflow.phase(name)`:阶段标记
|
|
9
|
+
- `workflow.parallel([fn, ...])`:并行执行
|
|
10
|
+
- `workflow.pipeline(items, step)`:顺序流水线
|
|
11
|
+
|
|
12
|
+
args 必须是可 JSON 序列化的值(进 worker 前校验)。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 批量调工具的一段程序(ptc_run_code)
|
|
2
|
+
|
|
3
|
+
`ptc_run_code({ code, description, timeoutMs? })`:code 是 TypeScript 程序体,
|
|
4
|
+
支持顶层 await 与 return。程序内 `tools.<name>(args)` 调工具:
|
|
5
|
+
|
|
6
|
+
- 绑定表 = 内置名单 ∩ 会话活跃工具(`--tools` 收窄的会话不会拿到被滤掉的绑定)
|
|
7
|
+
- `bash` 不在默认表里,需 opt-in 授权(旧 ADR-0040)
|
|
8
|
+
- 程序返回值是归约后的结果;undefined 返回标记为「无完成值」
|
|
9
|
+
|
|
10
|
+
默认 2 分钟超时(可经 timeoutMs 下调,上限 10 分钟)。输出超 50 KB 按 pi 截断契约切尾,
|
|
11
|
+
全文写在临时文件、路径进尾注。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ptc-subagents",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.2",
|
|
4
4
|
"description": "Programmable tool calling (PTC) and subagent fan-out for pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist/**/*.js",
|
|
23
|
+
"docs/routes/**/*.md",
|
|
23
24
|
"README.md",
|
|
24
25
|
"LICENSE"
|
|
25
26
|
],
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
33
|
"build": "pnpm -r run build",
|
|
33
|
-
"prepack": "pnpm run build && rm -rf dist && cp -R packages/entry/dist dist",
|
|
34
|
+
"prepack": "pnpm run build && rm -rf dist docs/routes && cp -R packages/entry/dist dist && cp -R packages/routes/docs/routes docs/routes",
|
|
34
35
|
"check": "pnpm -r run check",
|
|
35
36
|
"lint": "oxlint",
|
|
36
37
|
"fmt": "oxfmt",
|