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 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 moves the built-in tools behind the program, so writing
74
- one becomes the shortest path. `/ptc mode off` restores them. `/ptc off` stops new
75
- dispatches and withdraws the orchestration tools; running tasks keep running.
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 { formatSkillsForPrompt, getAgentDir, loadSkills } from "@earendil-works/pi-coding-agent";
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 (_id, args) => {
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 (_id, params) => {
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
- const active = pi.getActiveTools();
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 模式(收窄/恢复 loadout)",
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
- const bindings = createBindingTable(pi, ctx, session);
5348
+ let toolContext;
5349
+ const getBindings = () => createBindingTable(pi, session, () => toolContext);
5362
5350
  registerPtcTools(registerTracked, {
5363
- getBindings: () => bindings,
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 createBindingTable(pi, ctx, session) {
5467
- const executeTool = ctx.executeTool;
5468
- const names = resolveBindingNames(pi.getActiveTools());
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
- if (!executeTool) throw new Error(`此 pi 版本不支持程序内工具调用(executeTool 不可用,binding: ${name})`);
5471
- const outcome = await executeTool(`ptc/${name}`, name, args);
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
+ 全文写在临时文件、路径进尾注。
@@ -0,0 +1,7 @@
1
+ # 直接用内置工具
2
+
3
+ 单次调用、或每步都需要模型判断的场景(写操作、审批敏感),不要写程序,直接调。
4
+
5
+ - PTC 程序内:`tools.read / grep / find / ls / edit / write`(绑定表收窄规则见 run-one-program)
6
+ - `bash` 需 opt-in:embedder 的 optIns 参数或 `~/.pi/agent/ptc.json` 的 optInBindings,处处 fail-closed
7
+ - 程序外的逐个直调走 pi 自己的工具调用管线
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ptc-subagents",
3
- "version": "6.0.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",