pi-ptc-subagents 7.1.0 → 7.2.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/README.md +71 -37
- package/dist/index.js +264 -173
- package/docs/routes/create-a-task.md +47 -3
- package/docs/routes/manage-a-task.md +18 -0
- package/docs/routes/reach-the-web.md +5 -2
- package/docs/routes/run-one-program.md +5 -3
- package/docs/routes/use-the-built-in-tools.md +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
# pi-ptc-subagents
|
|
2
2
|
|
|
3
|
-
Programmable tool calling and subagent dispatch for [pi](https://pi.dev) — and a
|
|
3
|
+
Programmable tool calling and subagent dispatch for [pi](https://pi.dev) — and a
|
|
4
|
+
[Kimi Code](https://www.kimi.com/code) plugin (`ptc-tools`, MCP) that brings the same
|
|
5
|
+
"write one program, batch-call your tools" to Kimi sessions. The npm package covers pi;
|
|
6
|
+
the Kimi Code plugin ships from this repo's GitHub Releases.
|
|
4
7
|
|
|
5
|
-
pi calls tools one at a time.
|
|
6
|
-
|
|
8
|
+
pi calls tools one at a time. On pi, the model writes a program that calls them in one
|
|
9
|
+
go — and can spawn pi subagents from inside it. On Kimi Code, the plugin gives the model
|
|
10
|
+
one program tool over a fixed file-tool set.
|
|
7
11
|
|
|
8
12
|
```
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
on pi
|
|
14
|
+
full_ptc model ──▶ ptc_run_code ──▶ program ──┬──▶ tools.read · grep · edit · … (session tools)
|
|
15
|
+
└──▶ pi_task_ptc ──▶ pi child ──▶ report
|
|
11
16
|
|
|
12
|
-
codemode_ptc model ──▶ codemode script ───────────┬──▶ tools.read · grep · edit · … (session tools)
|
|
13
|
-
|
|
17
|
+
codemode_ptc model ──▶ codemode script ───────────┬──▶ tools.read · grep · edit · … (session tools)
|
|
18
|
+
└──▶ pi_task_codemode ──▶ pi child ──▶ report
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
on kimi (MCP)
|
|
21
|
+
model ──▶ ptc_run_code ──▶ program ──────▶ tools.read · grep · glob · edit (fs 四件套, workdir-locked)
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
one call in, one result back — never the calls in between.
|
|
24
|
+
```
|
|
19
25
|
|
|
20
|
-
##
|
|
26
|
+
## On pi
|
|
21
27
|
|
|
22
28
|
- **One program, many tool calls.** `ptc_run_code` and `ptc_workflow` run TypeScript in a
|
|
23
29
|
worker. The program uses the session's tools as `tools.read(...)`, `tools.grep(...)`.
|
|
@@ -30,8 +36,6 @@ Both surfaces are detected from the session. There is no setting.
|
|
|
30
36
|
- **Search, where it belongs.** `web_ds_search` registers only inside dispatched children.
|
|
31
37
|
Off by default.
|
|
32
38
|
|
|
33
|
-
A call looks like this — the same shape on both surfaces:
|
|
34
|
-
|
|
35
39
|
```ts
|
|
36
40
|
// full_ptc, inside ptc_run_code
|
|
37
41
|
const r = await tools.pi_task_ptc({ task: "audit the auth code", wait: true });
|
|
@@ -42,7 +46,42 @@ const r = await tools.pi_task_codemode({ task: "audit the auth code", wait: true
|
|
|
42
46
|
return r.report?.summary ?? r.text;
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
### Surfaces
|
|
50
|
+
|
|
51
|
+
- **`full_ptc`** — the PTC pair is model-facing. Programs reach tasks with `pi_task_ptc`.
|
|
52
|
+
- **`codemode_ptc`** — pi's codemode orchestrates. Scripts reach tasks with
|
|
53
|
+
`pi_task_codemode`; the PTC pair is not registered at all.
|
|
54
|
+
|
|
55
|
+
Which one you get depends on whether `codemode` is loaded and callable. Nothing to configure.
|
|
56
|
+
|
|
57
|
+
### PTC mode
|
|
58
|
+
|
|
59
|
+
On a `full_ptc` session, PTC mode adds a routing lead to the system prompt: batch your
|
|
60
|
+
tool calls into one program. `/ptc off` drops the lead.
|
|
61
|
+
|
|
62
|
+
It does not hide the built-in tools from the model. pi keeps a tool callable from a
|
|
63
|
+
program only while it is active, so narrowing the loadout would take `tools.read` away
|
|
64
|
+
from the program too — the two cannot both hold on pi 1.4.0.
|
|
65
|
+
|
|
66
|
+
## The Kimi Code plugin (`ptc-tools`)
|
|
67
|
+
|
|
68
|
+
Two MCP tools; the host's own tools remain the orchestrator (no subagent door here —
|
|
69
|
+
Kimi Code ships its own Agent tool, so this plugin doesn't duplicate one):
|
|
70
|
+
|
|
71
|
+
- **`ptc_run_code({ code, description, workdir, timeoutMs? })`** — a TypeScript program in
|
|
72
|
+
the same vm cell as on pi, over a fixed tool set: `read` / `grep` / `glob` / `edit`.
|
|
73
|
+
Every path is realpath-checked and confined to `workdir`; `edit` lands atomically and
|
|
74
|
+
requires a unique `old_string`. No `bash`, no task dispatch inside the program.
|
|
75
|
+
- **`web_ds_search`** — search / open / find, callable directly (no child process needed).
|
|
76
|
+
Quota is per MCP server process (default 30 calls, `0` = unlimited); the DeepSeek key
|
|
77
|
+
resolves from env, then Kimi Code's `config.toml`.
|
|
78
|
+
|
|
79
|
+
Install with `/plugins install https://github.com/a1121611810/pi-ptc-subagents` — the bare repo
|
|
80
|
+
URL tracks the latest GitHub Release (pinned URLs get no update checks); run `/reload` to
|
|
81
|
+
activate. A bundled `ptc` skill teaches the model when to write a program instead of
|
|
82
|
+
fanning out tool calls one by one.
|
|
83
|
+
|
|
84
|
+
## Install (pi)
|
|
46
85
|
|
|
47
86
|
```bash
|
|
48
87
|
pi install npm:pi-ptc-subagents
|
|
@@ -60,24 +99,9 @@ To run one build without touching your settings:
|
|
|
60
99
|
pi -ne -e packages/entry/dist/index.js
|
|
61
100
|
```
|
|
62
101
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
- **`full_ptc`** — the PTC pair is model-facing. Programs reach tasks with `pi_task_ptc`.
|
|
66
|
-
- **`codemode_ptc`** — pi's codemode orchestrates. Scripts reach tasks with
|
|
67
|
-
`pi_task_codemode`; the PTC pair is not registered at all.
|
|
68
|
-
|
|
69
|
-
Which one you get depends on whether `codemode` is loaded and callable. Nothing to configure.
|
|
70
|
-
|
|
71
|
-
## PTC mode
|
|
102
|
+
Requires Node ≥ 22.19.
|
|
72
103
|
|
|
73
|
-
|
|
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.
|
|
79
|
-
|
|
80
|
-
## Configuration
|
|
104
|
+
## Configuration (pi)
|
|
81
105
|
|
|
82
106
|
`~/.pi/agent/ptc.json` — all optional:
|
|
83
107
|
|
|
@@ -92,19 +116,29 @@ from the program too — the two cannot both hold on pi 1.4.0.
|
|
|
92
116
|
|
|
93
117
|
## Trust posture
|
|
94
118
|
|
|
95
|
-
Programs run
|
|
96
|
-
`
|
|
119
|
+
Programs run in a vm cell on both editions (ADR-0019, v7.0.0): the only door out is
|
|
120
|
+
`tools.*`, which goes through the same tool pipeline as a direct call. Programs do not
|
|
121
|
+
see `process`, `fetch`, or dynamic `import` — code that used them broke in v7.0.0.
|
|
122
|
+
|
|
123
|
+
That is an alignment of power paths, not a security boundary. There is still no OS
|
|
124
|
+
sandbox: whatever the bound tools can do, a program can do, and a dispatched child is a
|
|
125
|
+
full pi process running as you. `bash` is not in the program binding table on either edition.
|
|
97
126
|
|
|
98
127
|
## Compatibility
|
|
99
128
|
|
|
100
|
-
pi `>= 0.86.0
|
|
101
|
-
|
|
102
|
-
|
|
129
|
+
pi `>= 0.86.0`, tracked against latest (currently 1.4.0). Two version facts matter:
|
|
130
|
+
|
|
131
|
+
- **In-program `tools.*` needs pi ≥ 0.99.** On 0.86/0.87 a program still evaluates and can
|
|
132
|
+
still dispatch a task, but session-tool calls fail with a clear version error. The
|
|
133
|
+
codemode surface itself also only exists from 0.99.
|
|
134
|
+
- Everything else in the 0.86–1.4 range is covered by real-machine smoke tests, including
|
|
135
|
+
a program that dispatches a real child process. Per-version details:
|
|
136
|
+
[docs/pi-version-matrix.md](./docs/pi-version-matrix.md).
|
|
103
137
|
|
|
104
138
|
## Documentation
|
|
105
139
|
|
|
106
140
|
- [AGENTS.md](./AGENTS.md) — how this repository is built and guarded
|
|
107
|
-
- [docs/adr/](./docs/adr/) — decisions, 0001–
|
|
141
|
+
- [docs/adr/](./docs/adr/) — decisions, 0001–0020
|
|
108
142
|
- [docs/prototypes/](./docs/prototypes/) — spec and status pages
|
|
109
143
|
- [docs/research/](./docs/research/) — evidence notes
|
|
110
144
|
|
package/dist/index.js
CHANGED
|
@@ -2848,7 +2848,8 @@ var TaskRegistry = class {
|
|
|
2848
2848
|
const next = {
|
|
2849
2849
|
...current,
|
|
2850
2850
|
report: command.report,
|
|
2851
|
-
reportChannel: command.channel
|
|
2851
|
+
reportChannel: command.channel,
|
|
2852
|
+
delivery: command.delivery ?? current.delivery
|
|
2852
2853
|
};
|
|
2853
2854
|
this.#records.set(command.id, next);
|
|
2854
2855
|
this.#emit(next, current);
|
|
@@ -3058,12 +3059,12 @@ var FileOutputStorage = class {
|
|
|
3058
3059
|
// ../dispatch/src/report.ts
|
|
3059
3060
|
const REPORT_CONTRACT_BLOCK = [
|
|
3060
3061
|
"<child-report-contract>",
|
|
3061
|
-
"Before finishing,
|
|
3062
|
+
"Before finishing, deliver a child report — preferably by calling the ptc_child_report tool (its schema fixes the shape); if that tool is not available, put a fenced JSON block at the very end of your final message:",
|
|
3062
3063
|
"```json",
|
|
3063
3064
|
"{ \"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
3065
|
"```",
|
|
3065
|
-
"Rules:
|
|
3066
|
-
"findings/files_touched/usage may be omitted when empty.
|
|
3066
|
+
"Rules: the report is a SUMMARY — it is NOT the deliverable. The deliverable is your prose answer, in exactly the format the task asked for; never hide the deliverable inside the JSON block. \"summary\" is one sentence of conclusion. \"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.",
|
|
3067
|
+
"findings/files_touched/usage may be omitted when empty.",
|
|
3067
3068
|
"</child-report-contract>"
|
|
3068
3069
|
].join("\n");
|
|
3069
3070
|
function extractReportFromText(text) {
|
|
@@ -3158,6 +3159,7 @@ function buildPiArgv(input, context) {
|
|
|
3158
3159
|
].filter(Boolean).join("\n")
|
|
3159
3160
|
];
|
|
3160
3161
|
argv.push("--no-extensions", "--no-skills");
|
|
3162
|
+
if (input.projectContext === "none") argv.push("--no-context-files");
|
|
3161
3163
|
if (supportsNoMcp(context.piVersion)) argv.push("--no-mcp");
|
|
3162
3164
|
const model = input.model ?? input.agentModel ?? context.sessionModel;
|
|
3163
3165
|
const thinking = input.thinkingLevel ?? input.agentThinkingLevel;
|
|
@@ -3220,12 +3222,16 @@ function findAgent(name, dirs, scope = "both") {
|
|
|
3220
3222
|
return null;
|
|
3221
3223
|
}
|
|
3222
3224
|
// ../dispatch/src/jsonl.ts
|
|
3225
|
+
const MAX_PATHS = 20;
|
|
3223
3226
|
function parseChildStream(stdout) {
|
|
3224
3227
|
let text = "";
|
|
3225
3228
|
let toolReport = null;
|
|
3226
3229
|
let observedUsage = null;
|
|
3227
3230
|
let observedModel = null;
|
|
3228
3231
|
let observedProvider = null;
|
|
3232
|
+
const tools = {};
|
|
3233
|
+
const paths = [];
|
|
3234
|
+
let errors = 0;
|
|
3229
3235
|
for (const line of stdout.split("\n")) {
|
|
3230
3236
|
const trimmed = line.trim();
|
|
3231
3237
|
if (!trimmed.startsWith("{")) continue;
|
|
@@ -3235,6 +3241,17 @@ function parseChildStream(stdout) {
|
|
|
3235
3241
|
} catch {
|
|
3236
3242
|
continue;
|
|
3237
3243
|
}
|
|
3244
|
+
if (event.type === "tool_execution_start") {
|
|
3245
|
+
const name = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
3246
|
+
tools[name] = (tools[name] ?? 0) + 1;
|
|
3247
|
+
const path = event.args?.path;
|
|
3248
|
+
if (typeof path === "string" && path && !paths.includes(path) && paths.length < MAX_PATHS) paths.push(path);
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
if (event.type === "tool_execution_end") {
|
|
3252
|
+
if (event.isError === true || event.result?.isError === true) errors++;
|
|
3253
|
+
continue;
|
|
3254
|
+
}
|
|
3238
3255
|
if (event.type !== "message_end" || !event.message) continue;
|
|
3239
3256
|
const message = event.message;
|
|
3240
3257
|
if (message.role === "assistant") {
|
|
@@ -3262,7 +3279,13 @@ function parseChildStream(stdout) {
|
|
|
3262
3279
|
toolReport,
|
|
3263
3280
|
observedUsage,
|
|
3264
3281
|
observedModel,
|
|
3265
|
-
observedProvider
|
|
3282
|
+
observedProvider,
|
|
3283
|
+
delivery: {
|
|
3284
|
+
tools,
|
|
3285
|
+
paths,
|
|
3286
|
+
errors,
|
|
3287
|
+
textChars: text.length
|
|
3288
|
+
}
|
|
3266
3289
|
};
|
|
3267
3290
|
}
|
|
3268
3291
|
var NotificationCenter = class {
|
|
@@ -3367,7 +3390,7 @@ var TaskEngine = class {
|
|
|
3367
3390
|
this.#registry = options.registry;
|
|
3368
3391
|
this.#slots = options.slots ?? new DispatchSlotCounter();
|
|
3369
3392
|
this.#output = options.output ?? new InMemoryOutputStorage();
|
|
3370
|
-
this.#spawn = options.spawnChild ?? ((argv) => spawnChild(argv));
|
|
3393
|
+
this.#spawn = options.spawnChild ?? ((argv, env) => spawnChild(argv, env));
|
|
3371
3394
|
this.#gates = options.gates ?? [];
|
|
3372
3395
|
this.#buildArgv = options.buildArgv ?? ((input, context) => buildPiArgv(input, context));
|
|
3373
3396
|
this.#resolveAgent = options.resolveAgent ?? (() => null);
|
|
@@ -3511,6 +3534,11 @@ var TaskEngine = class {
|
|
|
3511
3534
|
};
|
|
3512
3535
|
const child = this.#spawn(argv, env);
|
|
3513
3536
|
this.#children.set(taskId, child);
|
|
3537
|
+
const timeoutMs = typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? input.timeoutMs : void 0;
|
|
3538
|
+
const timer = timeoutMs ? setTimeout(() => {
|
|
3539
|
+
this.stop(taskId, `timeout after ${timeoutMs}ms`);
|
|
3540
|
+
}, timeoutMs) : void 0;
|
|
3541
|
+
timer?.unref();
|
|
3514
3542
|
let text = "";
|
|
3515
3543
|
let outputTruncated = false;
|
|
3516
3544
|
child.onOutput((chunk) => {
|
|
@@ -3523,6 +3551,7 @@ var TaskEngine = class {
|
|
|
3523
3551
|
const exitCode = await new Promise((resolveExit) => {
|
|
3524
3552
|
child.onExit((code) => resolveExit(code));
|
|
3525
3553
|
});
|
|
3554
|
+
if (timer) clearTimeout(timer);
|
|
3526
3555
|
this.#children.delete(taskId);
|
|
3527
3556
|
const outputPath = await this.#output.write(taskId, text);
|
|
3528
3557
|
const parsed = parseChildStream(text);
|
|
@@ -3538,7 +3567,9 @@ var TaskEngine = class {
|
|
|
3538
3567
|
report = extracted.report ?? void 0;
|
|
3539
3568
|
channel = extracted.channel;
|
|
3540
3569
|
}
|
|
3541
|
-
|
|
3570
|
+
const selfUsage = report?.usage;
|
|
3571
|
+
const selfUsageEmpty = !selfUsage || !selfUsage.input && !selfUsage.output && !selfUsage.cost;
|
|
3572
|
+
if (report && selfUsageEmpty && parsed.observedUsage) report = {
|
|
3542
3573
|
...report,
|
|
3543
3574
|
usage: parsed.observedUsage
|
|
3544
3575
|
};
|
|
@@ -3546,7 +3577,8 @@ var TaskEngine = class {
|
|
|
3546
3577
|
kind: "attach-report",
|
|
3547
3578
|
id: taskId,
|
|
3548
3579
|
report,
|
|
3549
|
-
channel
|
|
3580
|
+
channel,
|
|
3581
|
+
delivery: parsed.delivery
|
|
3550
3582
|
});
|
|
3551
3583
|
await this.#registry.submit({
|
|
3552
3584
|
kind: "attach-output",
|
|
@@ -3582,6 +3614,7 @@ var TaskEngine = class {
|
|
|
3582
3614
|
whySuppressed,
|
|
3583
3615
|
report: report ?? void 0,
|
|
3584
3616
|
reportChannel: channel,
|
|
3617
|
+
delivery: parsed.delivery,
|
|
3585
3618
|
outputTruncated
|
|
3586
3619
|
};
|
|
3587
3620
|
}
|
|
@@ -4246,25 +4279,209 @@ function createWebDsSearchTool(context) {
|
|
|
4246
4279
|
}
|
|
4247
4280
|
};
|
|
4248
4281
|
}
|
|
4282
|
+
// ../routes/src/surface.ts
|
|
4283
|
+
const FALLBACK_SURFACE_MODE = "full_ptc";
|
|
4284
|
+
// ../routes/src/registry.ts
|
|
4285
|
+
const TOOLS = {
|
|
4286
|
+
ptc_run_code: {
|
|
4287
|
+
name: "ptc_run_code",
|
|
4288
|
+
description: (s) => ["写一段 TypeScript 程序批量调用工具:tools.<name>(args)。支持顶层 await、Promise.all 并行、循环与条件;程序返回值是归约后的结果,中间产物不进上下文。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达,模型直走 codemode。" : [
|
|
4289
|
+
"什么时候写程序而不是逐个调工具,看系统提示词 <ptc> 节的路由表。",
|
|
4290
|
+
"程序内可调本会话已启用的同名工具(read / write / edit / grep / find / ls;bash 默认不可用),参数与直接调用该工具完全一致;每次调用返回 Promise,要 await,返回文本结果,失败抛异常。",
|
|
4291
|
+
"例:const files = await tools.find({ pattern: \"*.py\" }); for (const f of files) await tools.write({ path: f, content: body });"
|
|
4292
|
+
].join("")].join("")
|
|
4293
|
+
},
|
|
4294
|
+
ptc_workflow: {
|
|
4295
|
+
name: "ptc_workflow",
|
|
4296
|
+
description: (s) => ["结构化的多阶段程序:script 里可用 workflow.log / workflow.phase / workflow.parallel / workflow.pipeline。适合阶段明确的批处理。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达。" : "与 ptc_run_code 的取舍看 <ptc> 节的路由表。"].join("")
|
|
4297
|
+
},
|
|
4298
|
+
pi_task_codemode: {
|
|
4299
|
+
name: "pi_task_codemode",
|
|
4300
|
+
description: () => "建一条子代理任务,默认立刻返回 handle(taskId + label);wait:true 时阻塞到终态再返回结果。任务在任务记录里排队,由派发引擎起独立 pi 进程执行;完成后的报告与产出经 ptc_task_output 读取。"
|
|
4301
|
+
},
|
|
4302
|
+
ptc_task_list: {
|
|
4303
|
+
name: "ptc_task_list",
|
|
4304
|
+
description: () => "列出在飞的任务与最近完成的任务(最近 10 条已完成)。"
|
|
4305
|
+
},
|
|
4306
|
+
ptc_task_output: {
|
|
4307
|
+
name: "ptc_task_output",
|
|
4308
|
+
description: () => "按 task_id 读一条任务记录:状态、报告、产出。产出超过截断阈值时给尾部与完整产出位置(outputPath)。"
|
|
4309
|
+
},
|
|
4310
|
+
ptc_task_stop: {
|
|
4311
|
+
name: "ptc_task_stop",
|
|
4312
|
+
description: () => "按 task_id 叫停任务:排队中的直接抽掉(canceled),在跑的转 stopping。"
|
|
4313
|
+
}
|
|
4314
|
+
};
|
|
4315
|
+
const TASK_DOOR_PARAM_DESCRIPTIONS = {
|
|
4316
|
+
task: "孩子要干的活",
|
|
4317
|
+
agent: "已注册的 agent 名字;省略 = 通用孩子",
|
|
4318
|
+
agentScope: "agent 定义的查找范围(user / project / both)",
|
|
4319
|
+
why: "这一刀为什么切出来(调用方自己的话)——它会进孩子的提示词、多花孩子的 token;孩子从 task 里已经能读懂就别传",
|
|
4320
|
+
model: "覆盖孩子跑的模型",
|
|
4321
|
+
thinkingLevel: "覆盖孩子的思考档位——孩子的思考 token 是可调的成本项",
|
|
4322
|
+
wait: "true = 等这个任务完成再返回结果;false = 立刻返回 handle,结果走任务面/通知",
|
|
4323
|
+
label: "后台任务给人看的标签",
|
|
4324
|
+
readOnly: "strict = 孩子只有读工具(read/grep/find/ls,搜索开启时含 web_ds_search);lenient = 默认全工具。只需要读的活别给写权限",
|
|
4325
|
+
timeoutMs: "孩子的运行时长上限(毫秒)——超了按超时收尸;不传 = 不限(与不传时同)",
|
|
4326
|
+
projectContext: "孩子的项目上下文(AGENTS.md/CLAUDE.md):auto = 照常加载;none = 不加载——注意 none 只关「注入」不关「知识」:文件还在工作目录里,孩子会自己去翻(实测输入只差约 15%,不是省一半);这活不需要项目约定就传 none,要孩子遵守项目规矩就照常传(auto 也可能把项目里的写码规矩套进答案)"
|
|
4327
|
+
};
|
|
4328
|
+
const DELIVERY_CHECK_LEAD = "交付核对(引擎实测)";
|
|
4329
|
+
const DELIVERY_CHECK_NOTHING = "这孩子的结论没有依据:重派它,或明说缺口,别替它补数。";
|
|
4330
|
+
/** 父面渲染一行交付核对:工具/路径(实测)· 报错 · 正文长度 · 报告条数(自述) */
|
|
4331
|
+
function renderDeliveryCheck(input) {
|
|
4332
|
+
const d = input.delivery;
|
|
4333
|
+
if (!d) return "";
|
|
4334
|
+
const calls = Object.entries(d.tools);
|
|
4335
|
+
const totalCalls = calls.reduce((sum, [, count]) => sum + count, 0);
|
|
4336
|
+
const parts = [];
|
|
4337
|
+
if (calls.length > 0) {
|
|
4338
|
+
const tools = calls.map(([name, count]) => `${name}×${count}`).join(" ");
|
|
4339
|
+
const paths = d.paths.length > 0 ? `(${d.paths.slice(0, 5).join("、")}${d.paths.length > 5 ? ` 等 ${d.paths.length} 条` : ""})` : "";
|
|
4340
|
+
parts.push(`工具 ${tools}${paths}`);
|
|
4341
|
+
} else parts.push("没调用任何工具");
|
|
4342
|
+
parts.push(`报错 ${d.errors}`);
|
|
4343
|
+
parts.push(`正文 ${d.textChars} 字`);
|
|
4344
|
+
const findings = input.findings ?? 0;
|
|
4345
|
+
parts.push(`报告 ${findings} 条(${input.channel ?? "none"})`);
|
|
4346
|
+
return totalCalls === 0 && findings === 0 && d.textChars === 0 ? `${DELIVERY_CHECK_LEAD}:零产出——${parts.join(" · ")}。${DELIVERY_CHECK_NOTHING}` : `${DELIVERY_CHECK_LEAD}:${parts.join(" · ")}`;
|
|
4347
|
+
}
|
|
4348
|
+
const ROUTES = [
|
|
4349
|
+
{
|
|
4350
|
+
id: "create-a-task",
|
|
4351
|
+
name: "派一个子代理",
|
|
4352
|
+
when: "材料多、主题互不相干、每份都要读一堆材料时派——父一个人扛会算错(实测 92%→100%,n=3);成本 10 倍上下,一件能算完的活别派",
|
|
4353
|
+
how: {
|
|
4354
|
+
full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数",
|
|
4355
|
+
codemode_ptc: "tools.pi_task_codemode({ task, label? })(脚本内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数"
|
|
4356
|
+
},
|
|
4357
|
+
fact: "默认不等,立刻返回 handle;任务在任务记录里排队,可经任务面三工具查/停",
|
|
4358
|
+
detail: "docs/routes/create-a-task.md"
|
|
4359
|
+
},
|
|
4360
|
+
{
|
|
4361
|
+
id: "run-one-program",
|
|
4362
|
+
name: "批量调工具的一段程序",
|
|
4363
|
+
when: "连续多次工具调用、结果要过滤/归约后再看",
|
|
4364
|
+
how: {
|
|
4365
|
+
full_ptc: "ptc_run_code({ code, description })",
|
|
4366
|
+
codemode_ptc: "不适用——写程序是 full_ptc 面的编排能力;本面用 codemode 编排、pi_task_codemode 派任务"
|
|
4367
|
+
},
|
|
4368
|
+
fact: "程序内 tools.*;bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线);只在本面(full_ptc)注册 PTC 工具",
|
|
4369
|
+
detail: "docs/routes/run-one-program.md"
|
|
4370
|
+
},
|
|
4371
|
+
{
|
|
4372
|
+
id: "run-a-phased-workflow",
|
|
4373
|
+
name: "分阶段 workflow",
|
|
4374
|
+
when: "批处理有明确阶段,要阶段性可见进度",
|
|
4375
|
+
how: {
|
|
4376
|
+
full_ptc: "ptc_workflow({ meta, script, args? })",
|
|
4377
|
+
codemode_ptc: "不适用——同 run-one-program:workflow 是 full_ptc 的编排能力"
|
|
4378
|
+
},
|
|
4379
|
+
fact: "script 内可用 workflow.log/phase/parallel/pipeline;只在本面注册",
|
|
4380
|
+
detail: "docs/routes/run-a-phased-workflow.md"
|
|
4381
|
+
},
|
|
4382
|
+
{
|
|
4383
|
+
id: "use-the-built-in-tools",
|
|
4384
|
+
name: "直接用内置工具",
|
|
4385
|
+
when: "单次调用就够,或每步都要模型判断(写操作、审批敏感)",
|
|
4386
|
+
how: {
|
|
4387
|
+
full_ptc: "程序内 tools.read/grep/find/ls/edit/write;逐个直调走 pi 自己的工具",
|
|
4388
|
+
codemode_ptc: "脚本内 tools.<内置名> 直接调"
|
|
4389
|
+
},
|
|
4390
|
+
fact: "bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)",
|
|
4391
|
+
detail: "docs/routes/use-the-built-in-tools.md"
|
|
4392
|
+
},
|
|
4393
|
+
{
|
|
4394
|
+
id: "report-what-you-did",
|
|
4395
|
+
name: "交报告",
|
|
4396
|
+
when: "子代理收工:结论要有证据、副作用与用量要可核对",
|
|
4397
|
+
how: {
|
|
4398
|
+
full_ptc: "孩子的契约:最终消息末尾 fenced JSON(summary/findings/files_touched/usage);宿主自动解析",
|
|
4399
|
+
codemode_ptc: "同左——契约在孩子的追加提示词里,与 surface 无关"
|
|
4400
|
+
},
|
|
4401
|
+
fact: "reportChannel 恒在四值:tool/prompt-json/none/opted-out;工具通道(ptc_child_report)已是首选",
|
|
4402
|
+
detail: "docs/routes/report-what-you-did.md"
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
id: "reach-the-web",
|
|
4406
|
+
name: "上网查资料",
|
|
4407
|
+
when: "需要网页搜索、抓页面、页内找内容——判断力在干活的子代理侧",
|
|
4408
|
+
how: {
|
|
4409
|
+
full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search(search/open/find 三动作)",
|
|
4410
|
+
codemode_ptc: "同左——搜索只在子代理进程里注册,父会话没有这个工具"
|
|
4411
|
+
},
|
|
4412
|
+
fact: "webDsSearch 开关默认关(fail-closed);配额每进程计;概览走 snippet 超预算整条丢弃",
|
|
4413
|
+
detail: "docs/routes/reach-the-web.md"
|
|
4414
|
+
},
|
|
4415
|
+
{
|
|
4416
|
+
id: "manage-a-task",
|
|
4417
|
+
name: "管一个任务",
|
|
4418
|
+
when: "要知道孩子跑得怎样、读它的产出、或叫停它",
|
|
4419
|
+
how: {
|
|
4420
|
+
full_ptc: "ptc_task_list / ptc_task_output / ptc_task_stop(模型直调)——产出里的 delivery 是引擎实测轨迹",
|
|
4421
|
+
codemode_ptc: "同左(模型直调;脚本经结构化返回)——产出里的 delivery 是引擎实测轨迹"
|
|
4422
|
+
},
|
|
4423
|
+
fact: "有任务记录这三个工具才出现在模型面前(ADR-0007)",
|
|
4424
|
+
detail: "docs/routes/manage-a-task.md"
|
|
4425
|
+
}
|
|
4426
|
+
];
|
|
4427
|
+
const LONG_OUTPUT_GUIDANCE_WITH_REPORT = "产出超过截断阈值({bytes} 字节)。先看孩子的报告(归约层):{report}——响应里的 output 是截断后的尾部;报告没收录的完整原文用 read 读记录上的 outputPath。";
|
|
4428
|
+
const LONG_OUTPUT_GUIDANCE_NO_REPORT = "产出超过截断阈值({bytes} 字节),且孩子未交报告(reportChannel=none,散文可信度存疑)。响应里的 output 是截断后的尾部;完整原文用 read 读记录上的 outputPath。";
|
|
4429
|
+
function renderLongOutputGuidance(options) {
|
|
4430
|
+
if (options.report && options.reportChannel && options.reportChannel !== "none") {
|
|
4431
|
+
const findings = (options.report.findings ?? []).slice(0, 5).map((f) => `- ${f.what}${f.evidence ? `(证据:${f.evidence})` : ""}`).join("\n");
|
|
4432
|
+
return LONG_OUTPUT_GUIDANCE_WITH_REPORT.replace("{bytes}", String(options.bytes)).replace("{report}", [`summary: ${options.report.summary}`, findings].filter(Boolean).join("\n"));
|
|
4433
|
+
}
|
|
4434
|
+
return LONG_OUTPUT_GUIDANCE_NO_REPORT.replace("{bytes}", String(options.bytes));
|
|
4435
|
+
}
|
|
4436
|
+
function renderTaskNotifications(batch) {
|
|
4437
|
+
return [
|
|
4438
|
+
"<bg-task-notifications>",
|
|
4439
|
+
...batch.map((n) => `- task ${n.taskId}${n.label ? ` (${n.label})` : ""}: ${n.status}${n.stopReason ? ` — ${n.stopReason}` : ""}`),
|
|
4440
|
+
"</bg-task-notifications>"
|
|
4441
|
+
].join("\n");
|
|
4442
|
+
}
|
|
4443
|
+
function estimateTokens(text) {
|
|
4444
|
+
return Math.ceil(text.length / 2);
|
|
4445
|
+
}
|
|
4446
|
+
const ROUTE_INDEX_LEAD = {
|
|
4447
|
+
full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。派不派子代理自己判断——路由表第一行写了代价。",
|
|
4448
|
+
codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。派不派自己判断——路由表第一行写了代价。"
|
|
4449
|
+
};
|
|
4450
|
+
function renderRouteIndex(surface, detailBase, nudge = true) {
|
|
4451
|
+
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${detailBase ? route.detail.split("/").pop() : route.detail}`);
|
|
4452
|
+
let index = [
|
|
4453
|
+
`<ptc>`,
|
|
4454
|
+
...nudge ? [ROUTE_INDEX_LEAD[surface]] : [],
|
|
4455
|
+
...detailBase ? [`详情目录:${detailBase}`] : [],
|
|
4456
|
+
"路由表(一条一行,详情按需 read):",
|
|
4457
|
+
...lines,
|
|
4458
|
+
`</ptc>`
|
|
4459
|
+
].join("\n");
|
|
4460
|
+
const tokens = estimateTokens(index);
|
|
4461
|
+
if (tokens > 560) index += `\n> WARNING: index over token budget (${tokens} > 560); move detail into docs/routes/*.md`;
|
|
4462
|
+
return index;
|
|
4463
|
+
}
|
|
4249
4464
|
// ../ptc/src/task-binding.ts
|
|
4250
4465
|
const TASK_BINDING_NAME = "pi_task_ptc";
|
|
4251
4466
|
const TASK_DOOR_PARAMETERS = _Object_({
|
|
4252
4467
|
task: String$1({
|
|
4253
4468
|
minLength: 1,
|
|
4254
|
-
description:
|
|
4469
|
+
description: TASK_DOOR_PARAM_DESCRIPTIONS.task
|
|
4255
4470
|
}),
|
|
4256
|
-
agent: Optional(String$1({ description:
|
|
4257
|
-
why: Optional(String$1({ description:
|
|
4471
|
+
agent: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.agent })),
|
|
4472
|
+
why: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.why })),
|
|
4258
4473
|
agentScope: Optional(Union([
|
|
4259
4474
|
Literal("user"),
|
|
4260
4475
|
Literal("project"),
|
|
4261
4476
|
Literal("both")
|
|
4262
|
-
])),
|
|
4263
|
-
model: Optional(String$1({ description:
|
|
4264
|
-
thinkingLevel: Optional(String$1()),
|
|
4265
|
-
wait: Optional(Boolean$1({ description:
|
|
4266
|
-
label: Optional(String$1({ description:
|
|
4267
|
-
readOnly: Optional(Union([Literal("strict"), Literal("lenient")]))
|
|
4477
|
+
], { description: TASK_DOOR_PARAM_DESCRIPTIONS.agentScope })),
|
|
4478
|
+
model: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.model })),
|
|
4479
|
+
thinkingLevel: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.thinkingLevel })),
|
|
4480
|
+
wait: Optional(Boolean$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.wait })),
|
|
4481
|
+
label: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.label })),
|
|
4482
|
+
readOnly: Optional(Union([Literal("strict"), Literal("lenient")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.readOnly })),
|
|
4483
|
+
timeoutMs: Optional(Number$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.timeoutMs })),
|
|
4484
|
+
projectContext: Optional(Union([Literal("auto"), Literal("none")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.projectContext }))
|
|
4268
4485
|
});
|
|
4269
4486
|
function createTaskBinding(door) {
|
|
4270
4487
|
return {
|
|
@@ -4280,7 +4497,9 @@ function createTaskBinding(door) {
|
|
|
4280
4497
|
thinkingLevel: input.thinkingLevel,
|
|
4281
4498
|
agent: input.agent,
|
|
4282
4499
|
agentScope: input.agentScope,
|
|
4283
|
-
readOnly: input.readOnly
|
|
4500
|
+
readOnly: input.readOnly,
|
|
4501
|
+
timeoutMs: input.timeoutMs,
|
|
4502
|
+
projectContext: input.projectContext
|
|
4284
4503
|
});
|
|
4285
4504
|
}
|
|
4286
4505
|
};
|
|
@@ -4746,151 +4965,6 @@ function renderToolResult(outcome) {
|
|
|
4746
4965
|
fullOutputPath
|
|
4747
4966
|
};
|
|
4748
4967
|
}
|
|
4749
|
-
// ../routes/src/surface.ts
|
|
4750
|
-
const FALLBACK_SURFACE_MODE = "full_ptc";
|
|
4751
|
-
// ../routes/src/registry.ts
|
|
4752
|
-
const TOOLS = {
|
|
4753
|
-
ptc_run_code: {
|
|
4754
|
-
name: "ptc_run_code",
|
|
4755
|
-
description: (s) => ["写一段 TypeScript 程序批量调用工具:tools.<name>(args)。支持顶层 await、Promise.all 并行、循环与条件;程序返回值是归约后的结果,中间产物不进上下文。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达,模型直走 codemode。" : "什么时候写程序而不是逐个调工具,看系统提示词 <ptc> 节的路由表。"].join("")
|
|
4756
|
-
},
|
|
4757
|
-
ptc_workflow: {
|
|
4758
|
-
name: "ptc_workflow",
|
|
4759
|
-
description: (s) => ["结构化的多阶段程序:script 里可用 workflow.log / workflow.phase / workflow.parallel / workflow.pipeline。适合阶段明确的批处理。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达。" : "与 ptc_run_code 的取舍看 <ptc> 节的路由表。"].join("")
|
|
4760
|
-
},
|
|
4761
|
-
pi_task_codemode: {
|
|
4762
|
-
name: "pi_task_codemode",
|
|
4763
|
-
description: () => "建一条子代理任务,默认立刻返回 handle(taskId + label);wait:true 时阻塞到终态再返回结果。任务在任务记录里排队,由派发引擎起独立 pi 进程执行;完成后的报告与产出经 ptc_task_output 读取。"
|
|
4764
|
-
},
|
|
4765
|
-
ptc_task_list: {
|
|
4766
|
-
name: "ptc_task_list",
|
|
4767
|
-
description: () => "列出在飞的任务与最近完成的任务(最近 10 条已完成)。"
|
|
4768
|
-
},
|
|
4769
|
-
ptc_task_output: {
|
|
4770
|
-
name: "ptc_task_output",
|
|
4771
|
-
description: () => "按 task_id 读一条任务记录:状态、报告、产出。产出超过截断阈值时给尾部与完整产出位置(outputPath)。"
|
|
4772
|
-
},
|
|
4773
|
-
ptc_task_stop: {
|
|
4774
|
-
name: "ptc_task_stop",
|
|
4775
|
-
description: () => "按 task_id 叫停任务:排队中的直接抽掉(canceled),在跑的转 stopping。"
|
|
4776
|
-
}
|
|
4777
|
-
};
|
|
4778
|
-
const ROUTES = [
|
|
4779
|
-
{
|
|
4780
|
-
id: "create-a-task",
|
|
4781
|
-
name: "派一个子代理",
|
|
4782
|
-
when: "这件事需要它自己的上下文,或者要跟别的活并行",
|
|
4783
|
-
how: {
|
|
4784
|
-
full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)",
|
|
4785
|
-
codemode_ptc: "tools.pi_task_codemode({ task, label? })(脚本内)"
|
|
4786
|
-
},
|
|
4787
|
-
fact: "默认不等,立刻返回 handle;任务在任务记录里排队,可经任务面三工具查/停",
|
|
4788
|
-
detail: "docs/routes/create-a-task.md"
|
|
4789
|
-
},
|
|
4790
|
-
{
|
|
4791
|
-
id: "run-one-program",
|
|
4792
|
-
name: "批量调工具的一段程序",
|
|
4793
|
-
when: "连续多次工具调用、结果要过滤/归约后再看",
|
|
4794
|
-
how: {
|
|
4795
|
-
full_ptc: "ptc_run_code({ code, description })",
|
|
4796
|
-
codemode_ptc: "不适用——写程序是 full_ptc 面的编排能力;本面用 codemode 编排、pi_task_codemode 派任务"
|
|
4797
|
-
},
|
|
4798
|
-
fact: "程序内 tools.*;bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线);只在本面(full_ptc)注册 PTC 工具",
|
|
4799
|
-
detail: "docs/routes/run-one-program.md"
|
|
4800
|
-
},
|
|
4801
|
-
{
|
|
4802
|
-
id: "run-a-phased-workflow",
|
|
4803
|
-
name: "分阶段 workflow",
|
|
4804
|
-
when: "批处理有明确阶段,要阶段性可见进度",
|
|
4805
|
-
how: {
|
|
4806
|
-
full_ptc: "ptc_workflow({ meta, script, args? })",
|
|
4807
|
-
codemode_ptc: "不适用——同 run-one-program:workflow 是 full_ptc 的编排能力"
|
|
4808
|
-
},
|
|
4809
|
-
fact: "script 内可用 workflow.log/phase/parallel/pipeline;只在本面注册",
|
|
4810
|
-
detail: "docs/routes/run-a-phased-workflow.md"
|
|
4811
|
-
},
|
|
4812
|
-
{
|
|
4813
|
-
id: "use-the-built-in-tools",
|
|
4814
|
-
name: "直接用内置工具",
|
|
4815
|
-
when: "单次调用就够,或每步都要模型判断(写操作、审批敏感)",
|
|
4816
|
-
how: {
|
|
4817
|
-
full_ptc: "程序内 tools.read/grep/find/ls/edit/write;逐个直调走 pi 自己的工具",
|
|
4818
|
-
codemode_ptc: "脚本内 tools.<内置名> 直接调"
|
|
4819
|
-
},
|
|
4820
|
-
fact: "bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)",
|
|
4821
|
-
detail: "docs/routes/use-the-built-in-tools.md"
|
|
4822
|
-
},
|
|
4823
|
-
{
|
|
4824
|
-
id: "report-what-you-did",
|
|
4825
|
-
name: "交报告",
|
|
4826
|
-
when: "子代理收工:结论要有证据、副作用与用量要可核对",
|
|
4827
|
-
how: {
|
|
4828
|
-
full_ptc: "孩子的契约:最终消息末尾 fenced JSON(summary/findings/files_touched/usage);宿主自动解析",
|
|
4829
|
-
codemode_ptc: "同左——契约在孩子的追加提示词里,与 surface 无关"
|
|
4830
|
-
},
|
|
4831
|
-
fact: "reportChannel 恒在四值:tool/prompt-json/none/opted-out;工具通道(ptc_child_report)已是首选",
|
|
4832
|
-
detail: "docs/routes/report-what-you-did.md"
|
|
4833
|
-
},
|
|
4834
|
-
{
|
|
4835
|
-
id: "reach-the-web",
|
|
4836
|
-
name: "上网查资料",
|
|
4837
|
-
when: "需要网页搜索、抓页面、页内找内容——判断力在干活的子代理侧",
|
|
4838
|
-
how: {
|
|
4839
|
-
full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search(search/open/find 三动作)",
|
|
4840
|
-
codemode_ptc: "同左——搜索只在子代理进程里注册,父会话没有这个工具"
|
|
4841
|
-
},
|
|
4842
|
-
fact: "webDsSearch 开关默认关(fail-closed);配额每进程计;概览走 snippet 超预算整条丢弃",
|
|
4843
|
-
detail: "docs/routes/reach-the-web.md"
|
|
4844
|
-
},
|
|
4845
|
-
{
|
|
4846
|
-
id: "manage-a-task",
|
|
4847
|
-
name: "管一个任务",
|
|
4848
|
-
when: "要知道孩子跑得怎样、读它的产出、或叫停它",
|
|
4849
|
-
how: {
|
|
4850
|
-
full_ptc: "ptc_task_list / ptc_task_output / ptc_task_stop(模型直调)",
|
|
4851
|
-
codemode_ptc: "同左(模型直调;脚本经结构化返回)"
|
|
4852
|
-
},
|
|
4853
|
-
fact: "有任务记录这三个工具才出现在模型面前(ADR-0007)",
|
|
4854
|
-
detail: "docs/routes/manage-a-task.md"
|
|
4855
|
-
}
|
|
4856
|
-
];
|
|
4857
|
-
const LONG_OUTPUT_GUIDANCE_WITH_REPORT = "产出超过截断阈值({bytes} 字节)。先看孩子的报告(归约层):{report}——响应里的 output 是截断后的尾部;报告没收录的完整原文用 read 读记录上的 outputPath。";
|
|
4858
|
-
const LONG_OUTPUT_GUIDANCE_NO_REPORT = "产出超过截断阈值({bytes} 字节),且孩子未交报告(reportChannel=none,散文可信度存疑)。响应里的 output 是截断后的尾部;完整原文用 read 读记录上的 outputPath。";
|
|
4859
|
-
function renderLongOutputGuidance(options) {
|
|
4860
|
-
if (options.report && options.reportChannel && options.reportChannel !== "none") {
|
|
4861
|
-
const findings = (options.report.findings ?? []).slice(0, 5).map((f) => `- ${f.what}${f.evidence ? `(证据:${f.evidence})` : ""}`).join("\n");
|
|
4862
|
-
return LONG_OUTPUT_GUIDANCE_WITH_REPORT.replace("{bytes}", String(options.bytes)).replace("{report}", [`summary: ${options.report.summary}`, findings].filter(Boolean).join("\n"));
|
|
4863
|
-
}
|
|
4864
|
-
return LONG_OUTPUT_GUIDANCE_NO_REPORT.replace("{bytes}", String(options.bytes));
|
|
4865
|
-
}
|
|
4866
|
-
function renderTaskNotifications(batch) {
|
|
4867
|
-
return [
|
|
4868
|
-
"<bg-task-notifications>",
|
|
4869
|
-
...batch.map((n) => `- task ${n.taskId}${n.label ? ` (${n.label})` : ""}: ${n.status}${n.stopReason ? ` — ${n.stopReason}` : ""}`),
|
|
4870
|
-
"</bg-task-notifications>"
|
|
4871
|
-
].join("\n");
|
|
4872
|
-
}
|
|
4873
|
-
function estimateTokens(text) {
|
|
4874
|
-
return Math.ceil(text.length / 2);
|
|
4875
|
-
}
|
|
4876
|
-
const ROUTE_INDEX_LEAD = {
|
|
4877
|
-
full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。",
|
|
4878
|
-
codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。"
|
|
4879
|
-
};
|
|
4880
|
-
function renderRouteIndex(surface, detailBase, nudge = true) {
|
|
4881
|
-
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${detailBase ? route.detail.split("/").pop() : route.detail}`);
|
|
4882
|
-
let index = [
|
|
4883
|
-
`<ptc>`,
|
|
4884
|
-
...nudge ? [ROUTE_INDEX_LEAD[surface]] : [],
|
|
4885
|
-
...detailBase ? [`详情目录:${detailBase}`] : [],
|
|
4886
|
-
"路由表(一条一行,详情按需 read):",
|
|
4887
|
-
...lines,
|
|
4888
|
-
`</ptc>`
|
|
4889
|
-
].join("\n");
|
|
4890
|
-
const tokens = estimateTokens(index);
|
|
4891
|
-
if (tokens > 560) index += `\n> WARNING: index over token budget (${tokens} > 560); move detail into docs/routes/*.md`;
|
|
4892
|
-
return index;
|
|
4893
|
-
}
|
|
4894
4968
|
// ../ptc/src/render.ts
|
|
4895
4969
|
function renderPtcResult(surface) {
|
|
4896
4970
|
return (result, options, _theme, context) => {
|
|
@@ -5083,6 +5157,20 @@ const textResult = (value) => ({
|
|
|
5083
5157
|
}],
|
|
5084
5158
|
details: void 0
|
|
5085
5159
|
});
|
|
5160
|
+
function withDeliveryNote(value) {
|
|
5161
|
+
if (!value || typeof value !== "object") return value;
|
|
5162
|
+
const record = value;
|
|
5163
|
+
if (!("reportChannel" in record)) return value;
|
|
5164
|
+
const note = renderDeliveryCheck({
|
|
5165
|
+
delivery: record.delivery,
|
|
5166
|
+
findings: Array.isArray(record.report?.findings) ? record.report.findings.length : 0,
|
|
5167
|
+
channel: record.reportChannel
|
|
5168
|
+
});
|
|
5169
|
+
return note ? {
|
|
5170
|
+
...record,
|
|
5171
|
+
deliveryNote: note
|
|
5172
|
+
} : record;
|
|
5173
|
+
}
|
|
5086
5174
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
5087
5175
|
const publishedDetailDir = join(packageRoot, "docs", "routes");
|
|
5088
5176
|
const detailDir = existsSync(publishedDetailDir) ? publishedDetailDir : join(packageRoot, "..", "routes", "docs", "routes");
|
|
@@ -5393,7 +5481,7 @@ function createBindingTable(pi, session, getToolContext) {
|
|
|
5393
5481
|
return normalizeToolValue(outcome.result.structuredContent ?? outcome.result.content);
|
|
5394
5482
|
}]));
|
|
5395
5483
|
const taskBinding = createTaskBinding(session);
|
|
5396
|
-
table[TASK_BINDING_NAME] = (args) => taskBinding.execute(args);
|
|
5484
|
+
table[TASK_BINDING_NAME] = async (args) => withDeliveryNote(await taskBinding.execute(args));
|
|
5397
5485
|
return table;
|
|
5398
5486
|
}
|
|
5399
5487
|
function registerPtcTools(registerTool, options, surface) {
|
|
@@ -5420,10 +5508,10 @@ function registerTaskFace(registerTool, session, surface) {
|
|
|
5420
5508
|
const record = session.registry.get(args.task_id) ?? await session.readArchivedRecord(args.task_id);
|
|
5421
5509
|
if (!record) return textResult({ error: `unknown task id ${args.task_id}` });
|
|
5422
5510
|
const outputBytes = await session.outputSize(args.task_id);
|
|
5423
|
-
if (outputBytes === void 0) return textResult(record);
|
|
5511
|
+
if (outputBytes === void 0) return textResult(withDeliveryNote(record));
|
|
5424
5512
|
if (outputBytes > 51200) {
|
|
5425
5513
|
const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
|
|
5426
|
-
return textResult({
|
|
5514
|
+
return textResult(withDeliveryNote({
|
|
5427
5515
|
...record,
|
|
5428
5516
|
output: text,
|
|
5429
5517
|
guidance: renderLongOutputGuidance({
|
|
@@ -5431,13 +5519,13 @@ function registerTaskFace(registerTool, session, surface) {
|
|
|
5431
5519
|
report: record.report,
|
|
5432
5520
|
reportChannel: record.reportChannel ?? "none"
|
|
5433
5521
|
})
|
|
5434
|
-
});
|
|
5522
|
+
}));
|
|
5435
5523
|
}
|
|
5436
5524
|
const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
|
|
5437
|
-
return textResult({
|
|
5525
|
+
return textResult(withDeliveryNote({
|
|
5438
5526
|
...record,
|
|
5439
5527
|
output: text
|
|
5440
|
-
});
|
|
5528
|
+
}));
|
|
5441
5529
|
}
|
|
5442
5530
|
});
|
|
5443
5531
|
registerTool({
|
|
@@ -5468,14 +5556,17 @@ function registerTaskDoor(registerTool, session, surface) {
|
|
|
5468
5556
|
agentScope: args.agentScope,
|
|
5469
5557
|
model: args.model,
|
|
5470
5558
|
thinkingLevel: args.thinkingLevel,
|
|
5471
|
-
readOnly: args.readOnly
|
|
5559
|
+
readOnly: args.readOnly,
|
|
5560
|
+
timeoutMs: args.timeoutMs,
|
|
5561
|
+
projectContext: args.projectContext
|
|
5472
5562
|
});
|
|
5563
|
+
const shaped = withDeliveryNote(result);
|
|
5473
5564
|
return {
|
|
5474
|
-
...textResult(
|
|
5475
|
-
structuredContent:
|
|
5565
|
+
...textResult(shaped),
|
|
5566
|
+
structuredContent: shaped,
|
|
5476
5567
|
isError: result.status === "rejected"
|
|
5477
5568
|
};
|
|
5478
5569
|
}
|
|
5479
5570
|
});
|
|
5480
5571
|
}
|
|
5481
|
-
export { assemblePtcSession, piPtcEntry as default };
|
|
5572
|
+
export { assemblePtcSession, piPtcEntry as default, withDeliveryNote };
|
|
@@ -5,17 +5,29 @@
|
|
|
5
5
|
- `pi_task_ptc`:PTC 程序内的 binding,程序里 `tools.pi_task_ptc({...})`
|
|
6
6
|
- `pi_task_codemode`:注册的工具(exposure: codemode,模型不可见),codemode 脚本里 `tools.pi_task_codemode({...})`
|
|
7
7
|
|
|
8
|
-
参数:`task`(必填,孩子要干的活)、`label`、`wait`、`why`、`agent`、`agentScope`、`model`、`thinkingLevel`、`readOnly`;
|
|
8
|
+
参数:`task`(必填,孩子要干的活)、`label`、`wait`、`why`、`agent`、`agentScope`、`model`、`thinkingLevel`、`readOnly`、`timeoutMs`;
|
|
9
9
|
agent 定义里的 `tools / model / thinkingLevel / childReport` 由 frontmatter 钉住。
|
|
10
10
|
|
|
11
|
-
返回 handle:`{ taskId, label?, status: "queued" }
|
|
12
|
-
|
|
11
|
+
返回 handle:`{ taskId, label?, status: "queued" }`。默认不等;撞闸排队等(ADR-0010),
|
|
12
|
+
不抛异常。唯一的拒绝是派发被关(`/ptc off`,ADR-0004):此时返回
|
|
13
|
+
`{ status: "rejected", errorMessage }`。
|
|
13
14
|
|
|
14
15
|
任务建成后在任务记录里排队(满员排队不拒绝,ADR-0010),经
|
|
15
16
|
`ptc_task_list / ptc_task_output / ptc_task_stop` 查/停。
|
|
16
17
|
|
|
18
|
+
## 什么时候值得派(判据)
|
|
19
|
+
|
|
20
|
+
- **值得**:主题多、彼此独立、每个都要读一堆材料——父一个人扛会算错,一份材料一个孩子最划算。
|
|
21
|
+
- **不值得**:材料少、一口气能算完——派发只换 0~8% 准确率,代价是 10 倍上下成本。
|
|
22
|
+
- 实测(facts 题:4 份互不相干的档案各交给一个孩子,n=3):派发档准确率 100%,不派发 92%。
|
|
23
|
+
⚠️ **这 8 个点当方向别当效应量**:不派发档自身在两批之间就晃(bare 89%→97%,而那次代码改动对它零影响),
|
|
24
|
+
差值与波动同量级。稳的是**机制**:父同时扛 4 个主题的多步计算会算错(实测有一轮一个主题 3 条全错),
|
|
25
|
+
派发后每个孩子只对一份档案就全对。
|
|
26
|
+
|
|
17
27
|
## Briefing 纪律(省 token,派发前自查)
|
|
18
28
|
|
|
29
|
+
- **task 是孩子的唯一规格**:孩子看不到父会话,也看不到别的孩子。父写错,孩子照办,全链一致地错
|
|
30
|
+
(实测:有一轮父的任务书写了「用文件里给的值」,孩子交回档案里的原始参数,9 条事实全错)。
|
|
19
31
|
- **给确切路径、命令、决策**:孩子看不到父会话。task 里写死一个路径,省孩子一轮探索
|
|
20
32
|
(每轮探索 = 一次工具调用结果 + 推理 token)。
|
|
21
33
|
- **写明最终消息要带回什么**:孩子只回一次。要什么格式、哪些字段,task 里说清。
|
|
@@ -24,3 +36,35 @@ agent 定义里的 `tools / model / thinkingLevel / childReport` 由 frontmatter
|
|
|
24
36
|
- **同域的活合并给一个串行孩子**:每个孩子都付一份固定 harness 税(系统提示词 +
|
|
25
37
|
项目上下文)。多个重叠的小活拆并行 = 同一份探索被付费多次。
|
|
26
38
|
- **孩子不写报告文件**:交付物就是最终消息(含报告 JSON 块),落盘 .md 是浪费。
|
|
39
|
+
- **写清两件事:交付物格式 + 报告块**:孩子要同时交「交付物」和「报告」(末尾 JSON 块,或调
|
|
40
|
+
`ptc_child_report` 工具)。只写交付物格式时,孩子多半不交报告——父就只剩原始产出可读,只能重算。
|
|
41
|
+
更别写「恰好 N 行」这类排他措辞:孩子会照排他措辞办,把报告丢掉(实测 3/12 孩子中招,
|
|
42
|
+
父重算被记成幻觉)。
|
|
43
|
+
**要报告就点名形状**:「最后附上 child report 的 JSON 块(summary / findings / files_touched /
|
|
44
|
+
usage)」——实测点名形状 4/4 孩子照交;只说「附上报告块」1/4 交;**自己发明一套报告格式更糟**
|
|
45
|
+
(实测 4/4 不交契约块:孩子照父的格式办,把契约块顶掉了)。
|
|
46
|
+
- **汇总前核对孩子产出**:任务结果里的 `delivery` 是引擎实测的轨迹(它读了哪些文件、有没有报错),
|
|
47
|
+
不是孩子的自述。孩子零产出(没读材料、无报告、正文空)就**重派它,或明说缺口——别替它补数**:
|
|
48
|
+
父补的数没有孩子背书,保真度上等于「父说有、孩子没说」。
|
|
49
|
+
|
|
50
|
+
## 旋钮(用不用你自己判断)
|
|
51
|
+
|
|
52
|
+
参数都在参数表里。下面是它们的代价——什么时候值得付,由你判断:
|
|
53
|
+
|
|
54
|
+
- **`why`**(调用方自己的话):它进孩子的提示词 = 花孩子的 token。孩子从 task 里已经能读懂为什么要干,就别传。
|
|
55
|
+
- **`readOnly: "strict"`**:孩子的工具表压到 read/grep/find/ls(搜索开启时含 web_ds_search)。
|
|
56
|
+
只需要读的活别给写权限——省 token,也省掉它改坏东西的可能。
|
|
57
|
+
- **`thinkingLevel` / `model`**:孩子的成本项。活简单就给孩子低档位或更便宜的模型。
|
|
58
|
+
- **`wait: false`**(默认):立刻返回 handle,结果经任务面/通知回来。多个孩子并行时别在第一个上阻塞。
|
|
59
|
+
- **`timeoutMs`**:孩子的运行时长上限(毫秒)。**不传 = 不限**(默认行为);传了超时就收尸——
|
|
60
|
+
慢孩子不再一直烧 token,但它的活可能没干完。给了就别再无脑等它。
|
|
61
|
+
- **`projectContext`**:孩子的项目上下文(AGENTS.md/CLAUDE.md)。`auto`(默认)= 照常加载;
|
|
62
|
+
`none` = 不加载。**none 只关「注入」,不关「知识」**:文件还在工作目录里,孩子需要时会自己去翻
|
|
63
|
+
(实测 none 臂的孩子照样读到了项目口径)——所以 none 省的是注入那几百字符,
|
|
64
|
+
**实测输入只差约 15%,不是省一半**。这活只需要孩子读材料/算数、不需要项目约定时传 `none`;
|
|
65
|
+
要孩子遵守项目规矩就照常传。**另一头也要知道**:auto 注入的是整个项目文件,孩子可能把里面的
|
|
66
|
+
写码规矩套到答案上(实测 3 轮 4 条错全是「元 → 分」×100 改写)。
|
|
67
|
+
|
|
68
|
+
台架口径(2026-10-30,pi 1.4.0,facts 题 n=3):每个孩子付一份固定 harness 税 ≈ 1800 input token;
|
|
69
|
+
端到端比不派发贵 9~12 倍(官方编排 + 本仓派发 8.7×、本仓编排 + 本仓派发 11.6×;env 修复前是 15~22 倍)。
|
|
70
|
+
**派发值得的是准确性,不是省钱**:同样的题,派发档准确率 100%,不派发 92%(差值见上,与波动同量级)。
|
|
@@ -7,3 +7,21 @@
|
|
|
7
7
|
- `ptc_task_stop({ task_id, reason? })`:排队中的抽掉(canceled);在跑的转 stopping
|
|
8
8
|
|
|
9
9
|
任务记录是派发的唯一痕迹:每次建任务都建记录,前台任务也一样。
|
|
10
|
+
|
|
11
|
+
终态记录热存留最近 500 条(决策 29),被挤出的进压缩存档(默认留 30 天,
|
|
12
|
+
`archiveEnabled: false` 则直接删),凭精确 task id 经 `ptc_task_output` 仍读得到。
|
|
13
|
+
|
|
14
|
+
## 交付核对(delivery)
|
|
15
|
+
|
|
16
|
+
任务记录与产出里带 `delivery`:**引擎从孩子的进程事件流抽的实测轨迹**,不是孩子的自述。
|
|
17
|
+
|
|
18
|
+
- `tools`——孩子实际调用了哪些工具、各几次
|
|
19
|
+
- `paths`——它实际碰过的路径(去重,按出现顺序)
|
|
20
|
+
- `errors`——工具执行失败次数
|
|
21
|
+
- `textChars`——孩子最终正文长度(0 = 空交)
|
|
22
|
+
|
|
23
|
+
另有一行人话 `deliveryNote`,是同一份事实的渲染版。怎么用:
|
|
24
|
+
|
|
25
|
+
- 结论声称来自某份材料 ⇒ 对一下 `paths` 里有没有那份:**没有 ⇒ 这条结论没有依据**
|
|
26
|
+
- `paths` 空 + 报告 0 条 + 正文 0 字 ⇒ 这孩子零产出:**重派它,或明说缺口,别替它补数**
|
|
27
|
+
- `errors` > 0 ⇒ 它撞过错(路径不对、读不到),先看错在哪再决定重派还是改任务书
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
# 上网查资料(web_ds_search
|
|
1
|
+
# 上网查资料(web_ds_search · 搜索域)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
pi 面上,搜索能力**只活在被派出的子代理里**(父会话不注册这个工具)——「这条来源靠不靠谱」的判断力在干活的子代理侧。
|
|
4
|
+
|
|
5
|
+
> Kimi 面(`ptc-tools` 插件)例外:`web_ds_search` 模型可直接调用,配额按 MCP server
|
|
6
|
+
> 进程计(默认 30 次),DeepSeek key 走 env → Kimi Code `config.toml` 两级解析。
|
|
4
7
|
|
|
5
8
|
三动作:
|
|
6
9
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# 批量调工具的一段程序(ptc_run_code)
|
|
2
2
|
|
|
3
3
|
`ptc_run_code({ code, description, timeoutMs? })`:code 是 TypeScript 程序体,
|
|
4
|
-
支持顶层 await 与 return
|
|
4
|
+
支持顶层 await 与 return。程序跑在 **vm 运行格**里:唯一的出口是 `tools.*`(与直调
|
|
5
|
+
同一条工具管线),没有 `process` / `fetch` / 动态 `import`(v7.0.0 起,ADR-0019,
|
|
6
|
+
用过它们的旧代码会断)。程序内 `tools.<name>(args)` 调工具:
|
|
5
7
|
|
|
6
8
|
- 绑定表 = 内置名单 ∩ 会话活跃工具(`--tools` 收窄的会话不会拿到被滤掉的绑定)
|
|
7
|
-
- `bash`
|
|
9
|
+
- `bash` 不在程序绑定表里——旧 ADR-0040 的 opt-in 尚未接线,当前没有任何开关能开它
|
|
8
10
|
- 程序返回值是归约后的结果;undefined 返回标记为「无完成值」
|
|
9
11
|
|
|
10
|
-
默认 2 分钟超时(可经 timeoutMs
|
|
12
|
+
默认 2 分钟超时(可经 timeoutMs 调整)。输出超 50 KB 按 pi 截断契约切尾,
|
|
11
13
|
全文写在临时文件、路径进尾注。
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
单次调用、或每步都需要模型判断的场景(写操作、审批敏感),不要写程序,直接调。
|
|
4
4
|
|
|
5
5
|
- PTC 程序内:`tools.read / grep / find / ls / edit / write`(绑定表收窄规则见 run-one-program)
|
|
6
|
-
- `bash`
|
|
7
|
-
- 程序外的逐个直调走 pi
|
|
6
|
+
- `bash` 不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)——程序里跑 shell 没有路子
|
|
7
|
+
- 程序外的逐个直调走 pi 自己的工具调用管线;注意 pi 内置工具**没有 cwd 囚禁**:
|
|
8
|
+
strict 只是工具白名单收窄,绝对路径可读工作区外任何可读文件——白名单 ≠ 沙箱
|