pi-ptc-subagents 7.3.0 → 7.5.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 CHANGED
@@ -1,3 +1,4 @@
1
+ import * as piHost from "@earendil-works/pi-coding-agent";
1
2
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
4
  import { join } from "node:path";
@@ -3905,34 +3906,73 @@ var TaskEngine = class {
3905
3906
  }
3906
3907
  };
3907
3908
  // ../tui/src/bounds.ts
3908
- const RENDER_BOUNDS$1 = {
3909
+ const RENDER_BOUNDS = {
3909
3910
  MAX_LOG_LINES_EXPANDED: 12,
3910
3911
  VALUE_TREE_MAX_DEPTH: 4,
3911
3912
  VALUE_TREE_MAX_CHILDREN: 6,
3912
3913
  VALUE_TREE_MAX_LINE_CHARS: 120
3913
3914
  };
3914
3915
  // ../tui/src/text.ts
3915
- const ANSI_PATTERN$1 = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
3916
- function stripAnsi$1(text) {
3917
- return text.replace(ANSI_PATTERN$1, "");
3916
+ const ANSI_PATTERN = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
3917
+ function stripAnsi(text) {
3918
+ return text.replace(ANSI_PATTERN, "");
3918
3919
  }
3919
- function sanitizeText$1(text) {
3920
- return stripAnsi$1(text.replace(/\r/g, ""));
3920
+ function sanitizeText(text) {
3921
+ return stripAnsi(text.replace(/\r/g, ""));
3921
3922
  }
3922
- const MAX_LINE_CHARS$1 = RENDER_BOUNDS$1.VALUE_TREE_MAX_LINE_CHARS;
3923
- function truncateLine$1(line, maxChars = MAX_LINE_CHARS$1) {
3923
+ const MAX_LINE_CHARS = RENDER_BOUNDS.VALUE_TREE_MAX_LINE_CHARS;
3924
+ function truncateLine(line, maxChars = MAX_LINE_CHARS) {
3924
3925
  if (line.length <= maxChars) return line;
3925
3926
  let cut = maxChars;
3926
3927
  while (cut > 0 && (line.charCodeAt(cut) & 64512) === 56320) cut--;
3927
3928
  return `${line.slice(0, cut)}…`;
3928
3929
  }
3930
+ function renderModelValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
3931
+ const pad = " ".repeat(depth);
3932
+ if (depth >= RENDER_BOUNDS.VALUE_TREE_MAX_DEPTH) return "…";
3933
+ if (typeof value === "string") return depth === 0 ? value : JSON.stringify(value);
3934
+ if (value === null || typeof value === "number" || typeof value === "boolean") return String(value);
3935
+ if (undefinedValue(value)) return "(无完成值)";
3936
+ if (value !== null && typeof value === "object") {
3937
+ if (seen.has(value)) return "[Circular]";
3938
+ seen.add(value);
3939
+ }
3940
+ if (Array.isArray(value)) {
3941
+ const shown = value.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
3942
+ const rest = value.length - shown.length;
3943
+ const oneLine = `[${shown.map((v) => JSON.stringify(v)).join(", ")}]`;
3944
+ const rendered = value.length === 0 ? "[]" : rest === 0 && shown.every((v) => primitive(v)) && oneLine.length <= MAX_LINE_CHARS ? oneLine : `[\n${shown.map((v) => truncateLine(`${pad} ${renderModelValue(v, depth + 1, seen)}`)).join(",\n")}${rest > 0 ? `,\n${pad} …(余 ${rest} 项)` : ""}\n${pad}]`;
3945
+ seen.delete(value);
3946
+ return rendered;
3947
+ }
3948
+ if (value !== null && typeof value === "object") {
3949
+ const entries = Object.entries(value);
3950
+ const shown = entries.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
3951
+ const rest = entries.length - shown.length;
3952
+ const oneLine = `{ ${shown.map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }`;
3953
+ const rendered = entries.length === 0 ? "{}" : rest === 0 && shown.every(([, v]) => primitive(v)) && oneLine.length <= MAX_LINE_CHARS ? oneLine : `{\n${shown.map(([k, v]) => truncateLine(`${pad} ${k}: ${renderModelValue(v, depth + 1, seen)}`)).join(",\n")}${rest > 0 ? `,\n${pad} …(余 ${rest} 项)` : ""}\n${pad}}`;
3954
+ seen.delete(value);
3955
+ return rendered;
3956
+ }
3957
+ return String(value);
3958
+ }
3959
+ function primitive(v) {
3960
+ return v === null || [
3961
+ "string",
3962
+ "number",
3963
+ "boolean"
3964
+ ].includes(typeof v);
3965
+ }
3966
+ function undefinedValue(value) {
3967
+ return typeof value === "object" && value !== null && value.$void === true;
3968
+ }
3929
3969
  // ../tui/src/truncate.ts
3930
- const TRUNCATION_MAX_BYTES$1 = 51200;
3931
- const TMP_FILE_TTL_MS$1 = 864e5;
3932
- const TMP_FILE_PREFIX$1 = "pi-ptc-output-";
3933
- let sweptTmp$1 = false;
3934
- function sweepExpiredTmp$1() {
3935
- sweptTmp$1 = true;
3970
+ const TRUNCATION_MAX_BYTES = 51200;
3971
+ const TMP_FILE_TTL_MS = 864e5;
3972
+ const TMP_FILE_PREFIX = "pi-ptc-output-";
3973
+ let sweptTmp = false;
3974
+ function sweepExpiredTmp() {
3975
+ sweptTmp = true;
3936
3976
  let names;
3937
3977
  try {
3938
3978
  names = readdirSync(tmpdir());
@@ -3940,14 +3980,14 @@ function sweepExpiredTmp$1() {
3940
3980
  return;
3941
3981
  }
3942
3982
  for (const name of names) {
3943
- if (!name.startsWith(TMP_FILE_PREFIX$1)) continue;
3983
+ if (!name.startsWith(TMP_FILE_PREFIX)) continue;
3944
3984
  try {
3945
3985
  const file = join(tmpdir(), name);
3946
- if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS$1) rmSync(file, { force: true });
3986
+ if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS) rmSync(file, { force: true });
3947
3987
  } catch {}
3948
3988
  }
3949
3989
  }
3950
- function applyAdr0015Truncation$1(text) {
3990
+ function applyAdr0015Truncation(text) {
3951
3991
  const lines = text.split("\n");
3952
3992
  const bytes = Buffer.byteLength(text, "utf8");
3953
3993
  if (bytes <= 51200 && lines.length <= 2e3) return {
@@ -3961,11 +4001,11 @@ function applyAdr0015Truncation$1(text) {
3961
4001
  kept = keptLines.join("\n");
3962
4002
  }
3963
4003
  const keptBuffer = Buffer.from(kept, "utf8");
3964
- if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES$1).toString("utf8");
3965
- const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX$1}${randomUUID()}.txt`);
4004
+ if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES).toString("utf8");
4005
+ const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX}${randomUUID()}.txt`);
3966
4006
  mkdirSync(tmpdir(), { recursive: true });
3967
4007
  writeFileSync(fullOutputPath, text, "utf8");
3968
- if (!sweptTmp$1) sweepExpiredTmp$1();
4008
+ if (!sweptTmp) sweepExpiredTmp();
3969
4009
  return {
3970
4010
  text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
3971
4011
  truncated: true,
@@ -3988,10 +4028,10 @@ function projectTaskEntry(record, options = {}) {
3988
4028
  return {
3989
4029
  id: record.id,
3990
4030
  status: record.status,
3991
- ...record.label ? { label: truncateLine$1(record.label, LABEL_MAX_CHARS) } : {},
4031
+ ...record.label ? { label: truncateLine(record.label, LABEL_MAX_CHARS) } : {},
3992
4032
  ...record.finishedAt !== void 0 ? { finishedAt: record.finishedAt } : {},
3993
- ...record.task ? { task: truncateLine$1(record.task, TASK_TEXT_MAX_CHARS) } : {},
3994
- ...record.report?.summary ? { summary: truncateLine$1(record.report.summary, SUMMARY_MAX_CHARS) } : {},
4033
+ ...record.task ? { task: truncateLine(record.task, TASK_TEXT_MAX_CHARS) } : {},
4034
+ ...record.report?.summary ? { summary: truncateLine(record.report.summary, SUMMARY_MAX_CHARS) } : {},
3995
4035
  ...options.archived ? { archived: true } : {},
3996
4036
  ...options.withSession && record.sessionId ? { sessionId: record.sessionId } : {}
3997
4037
  };
@@ -4018,6 +4058,7 @@ const TOOLS = {
4018
4058
  "什么时候写程序而不是逐个调工具,看系统提示词 <ptc> 节的路由表。",
4019
4059
  "程序内可调本会话已启用的同名工具(read / write / edit / grep / find / ls;bash 默认不可用),参数与直接调用该工具完全一致;每次调用返回 Promise,要 await,返回文本结果,失败抛异常。",
4020
4060
  "程序内还可调 task_records({ scope: \"all\" }) 取任务记录(all 含压缩归档)——要挑很多条时在程序里筛完只 return 要的几条。",
4061
+ "程序内还可调 task_output({ taskId }) 取孩子交回的正文——返回 {text}(孩子的散文,不是原始事件流);text 空时看 status:还在跑就等一会儿再取,别当没产出;收多份正文时读完只 return 汇总。",
4021
4062
  "例:const files = await tools.find({ pattern: \"*.py\" }); for (const f of files) await tools.write({ path: f, content: body });"
4022
4063
  ].join("")].join("")
4023
4064
  },
@@ -4081,10 +4122,9 @@ const ROUTES = [
4081
4122
  name: "派一个子代理",
4082
4123
  when: "材料多、主题互不相干、每份都要读一堆材料时派——父一个人扛会算错(实测 92%→100%,n=3);成本 10 倍上下,一件能算完的活别派",
4083
4124
  how: {
4084
- full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数",
4125
+ full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式",
4085
4126
  codemode_ptc: "tools.pi_task_codemode({ task, label? })(脚本内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数"
4086
4127
  },
4087
- fact: "默认不等,立刻返回 handle;任务在任务记录里排队,可经任务面三工具查/停",
4088
4128
  detail: "docs/routes/create-a-task.md"
4089
4129
  },
4090
4130
  {
@@ -4095,7 +4135,6 @@ const ROUTES = [
4095
4135
  full_ptc: "ptc_run_code({ code, description })",
4096
4136
  codemode_ptc: "不适用——写程序是 full_ptc 面的编排能力;本面用 codemode 编排、pi_task_codemode 派任务"
4097
4137
  },
4098
- fact: "程序内 tools.*;bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线);只在本面(full_ptc)注册 PTC 工具",
4099
4138
  detail: "docs/routes/run-one-program.md"
4100
4139
  },
4101
4140
  {
@@ -4106,7 +4145,6 @@ const ROUTES = [
4106
4145
  full_ptc: "ptc_workflow({ meta, script, args? })",
4107
4146
  codemode_ptc: "不适用——同 run-one-program:workflow 是 full_ptc 的编排能力"
4108
4147
  },
4109
- fact: "script 内可用 workflow.log/phase/parallel/pipeline;只在本面注册",
4110
4148
  detail: "docs/routes/run-a-phased-workflow.md"
4111
4149
  },
4112
4150
  {
@@ -4114,10 +4152,9 @@ const ROUTES = [
4114
4152
  name: "直接用内置工具",
4115
4153
  when: "单次调用就够,或每步都要模型判断(写操作、审批敏感)",
4116
4154
  how: {
4117
- full_ptc: "程序内 tools.read/grep/find/ls/edit/write;逐个直调走 pi 自己的工具",
4155
+ full_ptc: "程序内可调同名工具;逐个直调走 pi 自己的工具",
4118
4156
  codemode_ptc: "脚本内 tools.<内置名> 直接调"
4119
4157
  },
4120
- fact: "bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)",
4121
4158
  detail: "docs/routes/use-the-built-in-tools.md"
4122
4159
  },
4123
4160
  {
@@ -4128,7 +4165,6 @@ const ROUTES = [
4128
4165
  full_ptc: "孩子的契约:最终消息末尾 fenced JSON(summary/findings/files_touched/usage);宿主自动解析",
4129
4166
  codemode_ptc: "同左——契约在孩子的追加提示词里,与 surface 无关"
4130
4167
  },
4131
- fact: "reportChannel 恒在四值:tool/prompt-json/none/opted-out;工具通道(ptc_child_report)已是首选",
4132
4168
  detail: "docs/routes/report-what-you-did.md"
4133
4169
  },
4134
4170
  {
@@ -4136,10 +4172,9 @@ const ROUTES = [
4136
4172
  name: "上网查资料",
4137
4173
  when: "需要网页搜索、抓页面、页内找内容——判断力在干活的子代理侧",
4138
4174
  how: {
4139
- full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search(search/open/find 三动作)",
4175
+ full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search",
4140
4176
  codemode_ptc: "同左——搜索只在子代理进程里注册,父会话没有这个工具"
4141
4177
  },
4142
- fact: "webDsSearch 开关默认关(fail-closed);配额每进程计;概览走 snippet 超预算整条丢弃",
4143
4178
  detail: "docs/routes/reach-the-web.md"
4144
4179
  },
4145
4180
  {
@@ -4150,7 +4185,6 @@ const ROUTES = [
4150
4185
  full_ptc: "ptc_task_list / ptc_task_output / ptc_task_stop(模型直调)——产出里的 delivery 是引擎实测轨迹",
4151
4186
  codemode_ptc: "同左(模型直调;脚本经结构化返回)——产出里的 delivery 是引擎实测轨迹"
4152
4187
  },
4153
- fact: "有任务记录这三个工具才出现在模型面前(ADR-0007)",
4154
4188
  detail: "docs/routes/manage-a-task.md"
4155
4189
  }
4156
4190
  ];
@@ -4184,7 +4218,7 @@ function estimateTokens(text) {
4184
4218
  return Math.ceil(text.length / 2);
4185
4219
  }
4186
4220
  const ROUTE_INDEX_LEAD = {
4187
- full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。派不派子代理自己判断——路由表第一行写了代价。",
4221
+ full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或逐步判断、审批敏感的才直连。派不派子代理自己判断——第一行写了代价。",
4188
4222
  codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。派不派自己判断——路由表第一行写了代价。"
4189
4223
  };
4190
4224
  function renderRouteIndex(surface, detailBase, nudge = true) {
@@ -4250,9 +4284,9 @@ async function readTaskOutput(source, record) {
4250
4284
  const tail = await source.readOutputTail(taskId, 51200) ?? "";
4251
4285
  let answer = parseChildStream(tail).text;
4252
4286
  if (!answer && outputBytes > 51200) answer = parseChildStream(await source.readOutput(taskId) ?? "").text;
4253
- if (answer) return { text: applyAdr0015Truncation$1(answer).text };
4287
+ if (answer) return { text: applyAdr0015Truncation(answer).text };
4254
4288
  return {
4255
- output: applyAdr0015Truncation$1(tail).text,
4289
+ output: applyAdr0015Truncation(tail).text,
4256
4290
  guidance: renderLongOutputGuidance({
4257
4291
  bytes: outputBytes,
4258
4292
  report: record.report,
@@ -4512,14 +4546,14 @@ async function runWebSearch(action, context) {
4512
4546
  };
4513
4547
  return {
4514
4548
  ok: true,
4515
- text: sanitizeText$1(renderSearchOverview(outcome.value, budget))
4549
+ text: sanitizeText(renderSearchOverview(outcome.value, budget))
4516
4550
  };
4517
4551
  }
4518
4552
  if (action.kind === "open") {
4519
4553
  const cached = context.memory.lookup(action.url);
4520
4554
  if (cached !== void 0) return {
4521
4555
  ok: true,
4522
- text: sanitizeText$1(`(记忆命中,未发请求)\n${cached.slice(0, budget)}`)
4556
+ text: sanitizeText(`(记忆命中,未发请求)\n${cached.slice(0, budget)}`)
4523
4557
  };
4524
4558
  if (!context.quota.tryAcquire()) return {
4525
4559
  ok: false,
@@ -4538,7 +4572,7 @@ async function runWebSearch(action, context) {
4538
4572
  context.memory.storeText(action.url, outcome.value);
4539
4573
  return {
4540
4574
  ok: true,
4541
- text: sanitizeText$1(outcome.value.slice(0, budget))
4575
+ text: sanitizeText(outcome.value.slice(0, budget))
4542
4576
  };
4543
4577
  }
4544
4578
  let regex;
@@ -4558,7 +4592,7 @@ async function runWebSearch(action, context) {
4558
4592
  })).filter(({ line }) => regex.test(line));
4559
4593
  return {
4560
4594
  ok: true,
4561
- text: sanitizeText$1(lines.length > 0 ? lines.slice(0, 50).map(({ line, index }) => `${index + 1}: ${line}`).join("\n") : "(页内无匹配行)").slice(0, budget)
4595
+ text: sanitizeText(lines.length > 0 ? lines.slice(0, 50).map(({ line, index }) => `${index + 1}: ${line}`).join("\n") : "(页内无匹配行)").slice(0, budget)
4562
4596
  };
4563
4597
  }
4564
4598
  /** 页文获取:记忆命中免费;否则占额抓取并回存记忆 */
@@ -4651,50 +4685,6 @@ function createWebDsSearchTool(context) {
4651
4685
  }
4652
4686
  };
4653
4687
  }
4654
- // ../ptc/src/task-binding.ts
4655
- const TASK_BINDING_NAME = "pi_task_ptc";
4656
- const TASK_DOOR_PARAMETERS = _Object_({
4657
- task: String$1({
4658
- minLength: 1,
4659
- description: TASK_DOOR_PARAM_DESCRIPTIONS.task
4660
- }),
4661
- agent: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.agent })),
4662
- why: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.why })),
4663
- agentScope: Optional(Union([
4664
- Literal("user"),
4665
- Literal("project"),
4666
- Literal("both")
4667
- ], { description: TASK_DOOR_PARAM_DESCRIPTIONS.agentScope })),
4668
- model: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.model })),
4669
- thinkingLevel: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.thinkingLevel })),
4670
- wait: Optional(Boolean$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.wait })),
4671
- label: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.label })),
4672
- readOnly: Optional(Union([Literal("strict"), Literal("lenient")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.readOnly })),
4673
- timeoutMs: Optional(Number$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.timeoutMs })),
4674
- projectContext: Optional(Union([Literal("auto"), Literal("none")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.projectContext }))
4675
- });
4676
- function createTaskBinding(door) {
4677
- return {
4678
- name: TASK_BINDING_NAME,
4679
- parameters: TASK_DOOR_PARAMETERS,
4680
- execute(args) {
4681
- const input = args ?? {};
4682
- return door.createTask(input.task, {
4683
- label: input.label,
4684
- wait: input.wait,
4685
- why: input.why,
4686
- model: input.model,
4687
- thinkingLevel: input.thinkingLevel,
4688
- agent: input.agent,
4689
- agentScope: input.agentScope,
4690
- readOnly: input.readOnly,
4691
- timeoutMs: input.timeoutMs,
4692
- projectContext: input.projectContext
4693
- });
4694
- }
4695
- };
4696
- }
4697
- const TASK_RECORDS_BINDING_NAME = "task_records";
4698
4688
  // ../ptc/src/limits.ts
4699
4689
  const DEFAULT_LIMITS = {
4700
4690
  timeoutMs: 12e4,
@@ -5023,127 +5013,7 @@ var WorkerPool = class {
5023
5013
  this.#spawned = Math.max(0, this.#spawned - 1);
5024
5014
  }
5025
5015
  };
5026
- // ../ptc/src/bindings.ts
5027
- const KNOWN_BINDING_NAMES = [
5028
- "read",
5029
- "bash",
5030
- "grep",
5031
- "find",
5032
- "ls",
5033
- "edit",
5034
- "write"
5035
- ];
5036
- const OPT_IN_BINDING_NAMES = ["bash"];
5037
- const DEFAULT_BINDING_NAMES = KNOWN_BINDING_NAMES.filter((name) => !OPT_IN_BINDING_NAMES.includes(name));
5038
- function resolveBindingNames(activeToolNames, optIns = []) {
5039
- const allowed = /* @__PURE__ */ new Set([...DEFAULT_BINDING_NAMES, ...optIns]);
5040
- return KNOWN_BINDING_NAMES.filter((name) => activeToolNames.includes(name) && allowed.has(name));
5041
- }
5042
- // ../output/dist/index.js
5043
- const RENDER_BOUNDS = {
5044
- MAX_LOG_LINES_EXPANDED: 12,
5045
- VALUE_TREE_MAX_DEPTH: 4,
5046
- VALUE_TREE_MAX_CHILDREN: 6,
5047
- VALUE_TREE_MAX_LINE_CHARS: 120
5048
- };
5049
- const ANSI_PATTERN = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
5050
- function stripAnsi(text) {
5051
- return text.replace(ANSI_PATTERN, "");
5052
- }
5053
- function sanitizeText(text) {
5054
- return stripAnsi(text.replace(/\r/g, ""));
5055
- }
5056
- const MAX_LINE_CHARS = RENDER_BOUNDS.VALUE_TREE_MAX_LINE_CHARS;
5057
- function truncateLine(line, maxChars = MAX_LINE_CHARS) {
5058
- if (line.length <= maxChars) return line;
5059
- let cut = maxChars;
5060
- while (cut > 0 && (line.charCodeAt(cut) & 64512) === 56320) cut--;
5061
- return `${line.slice(0, cut)}…`;
5062
- }
5063
- function renderModelValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
5064
- const pad = " ".repeat(depth);
5065
- if (depth >= RENDER_BOUNDS.VALUE_TREE_MAX_DEPTH) return "…";
5066
- if (typeof value === "string") return depth === 0 ? value : JSON.stringify(value);
5067
- if (value === null || typeof value === "number" || typeof value === "boolean") return String(value);
5068
- if (undefinedValue(value)) return "(无完成值)";
5069
- if (value !== null && typeof value === "object") {
5070
- if (seen.has(value)) return "[Circular]";
5071
- seen.add(value);
5072
- }
5073
- if (Array.isArray(value)) {
5074
- const shown = value.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
5075
- const rest = value.length - shown.length;
5076
- const oneLine = `[${shown.map((v) => JSON.stringify(v)).join(", ")}]`;
5077
- const rendered = value.length === 0 ? "[]" : rest === 0 && shown.every((v) => primitive(v)) && oneLine.length <= MAX_LINE_CHARS ? oneLine : `[\n${shown.map((v) => truncateLine(`${pad} ${renderModelValue(v, depth + 1, seen)}`)).join(",\n")}${rest > 0 ? `,\n${pad} …(余 ${rest} 项)` : ""}\n${pad}]`;
5078
- seen.delete(value);
5079
- return rendered;
5080
- }
5081
- if (value !== null && typeof value === "object") {
5082
- const entries = Object.entries(value);
5083
- const shown = entries.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
5084
- const rest = entries.length - shown.length;
5085
- const oneLine = `{ ${shown.map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }`;
5086
- const rendered = entries.length === 0 ? "{}" : rest === 0 && shown.every(([, v]) => primitive(v)) && oneLine.length <= MAX_LINE_CHARS ? oneLine : `{\n${shown.map(([k, v]) => truncateLine(`${pad} ${k}: ${renderModelValue(v, depth + 1, seen)}`)).join(",\n")}${rest > 0 ? `,\n${pad} …(余 ${rest} 项)` : ""}\n${pad}}`;
5087
- seen.delete(value);
5088
- return rendered;
5089
- }
5090
- return String(value);
5091
- }
5092
- function primitive(v) {
5093
- return v === null || [
5094
- "string",
5095
- "number",
5096
- "boolean"
5097
- ].includes(typeof v);
5098
- }
5099
- function undefinedValue(value) {
5100
- return typeof value === "object" && value !== null && value.$void === true;
5101
- }
5102
- const TRUNCATION_MAX_BYTES = 51200;
5103
- const TMP_FILE_TTL_MS = 864e5;
5104
- const TMP_FILE_PREFIX = "pi-ptc-output-";
5105
- let sweptTmp = false;
5106
- function sweepExpiredTmp() {
5107
- sweptTmp = true;
5108
- let names;
5109
- try {
5110
- names = readdirSync(tmpdir());
5111
- } catch {
5112
- return;
5113
- }
5114
- for (const name of names) {
5115
- if (!name.startsWith(TMP_FILE_PREFIX)) continue;
5116
- try {
5117
- const file = join(tmpdir(), name);
5118
- if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS) rmSync(file, { force: true });
5119
- } catch {}
5120
- }
5121
- }
5122
- function applyAdr0015Truncation(text) {
5123
- const lines = text.split("\n");
5124
- const bytes = Buffer.byteLength(text, "utf8");
5125
- if (bytes <= 51200 && lines.length <= 2e3) return {
5126
- text,
5127
- truncated: false
5128
- };
5129
- let keptLines = lines.slice(-2e3);
5130
- let kept = keptLines.join("\n");
5131
- while (Buffer.byteLength(kept, "utf8") > 51200 && keptLines.length > 1) {
5132
- keptLines = keptLines.slice(Math.max(1, Math.floor(keptLines.length / 2)));
5133
- kept = keptLines.join("\n");
5134
- }
5135
- const keptBuffer = Buffer.from(kept, "utf8");
5136
- if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES).toString("utf8");
5137
- const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX}${randomUUID()}.txt`);
5138
- mkdirSync(tmpdir(), { recursive: true });
5139
- writeFileSync(fullOutputPath, text, "utf8");
5140
- if (!sweptTmp) sweepExpiredTmp();
5141
- return {
5142
- text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
5143
- truncated: true,
5144
- fullOutputPath
5145
- };
5146
- }
5016
+ // ../output/src/result-text.ts
5147
5017
  function renderToolResult(outcome) {
5148
5018
  let body;
5149
5019
  if (outcome.status === "fulfilled") body = (outcome.logs.length > 0 ? [...outcome.logs, renderModelValue(outcome.value)] : [renderModelValue(outcome.value)]).join("\n");
@@ -5156,7 +5026,7 @@ function renderToolResult(outcome) {
5156
5026
  fullOutputPath
5157
5027
  };
5158
5028
  }
5159
- // ../ptc/src/render.ts
5029
+ // src/ptc-render.ts
5160
5030
  function renderPtcResult(surface) {
5161
5031
  return (result, options, _theme, context) => {
5162
5032
  const args = context.args;
@@ -5175,14 +5045,14 @@ function renderPtcResult(surface) {
5175
5045
  if (options.expanded && !options.isPartial) for (const block of result.content) {
5176
5046
  if (block.type !== "text") continue;
5177
5047
  const lines = block.text.split("\n");
5178
- const kept = lines.slice(0, RENDER_BOUNDS$1.MAX_LOG_LINES_EXPANDED);
5048
+ const kept = lines.slice(0, RENDER_BOUNDS.MAX_LOG_LINES_EXPANDED);
5179
5049
  for (const line of kept) box.addChild(new Text(line));
5180
5050
  if (lines.length > kept.length) box.addChild(new Text(`…(共 ${lines.length} 行,省略 ${lines.length - kept.length} 行)`));
5181
5051
  }
5182
5052
  return box;
5183
5053
  };
5184
5054
  }
5185
- // ../ptc/src/tools.ts
5055
+ // src/ptc-tools.ts
5186
5056
  const RUN_CODE_PARAMETERS = _Object_({
5187
5057
  code: String$1({ description: "要执行的 TypeScript 程序体,可用顶层 await 和 return" }),
5188
5058
  description: String$1({ description: "这段程序要干什么(给人看的一行)" }),
@@ -5297,6 +5167,80 @@ function validateWorkflowArgs(args) {
5297
5167
  throw new Error(`workflow args 必须是 JSON 值:${error instanceof Error ? error.message : String(error)}`);
5298
5168
  }
5299
5169
  }
5170
+ // src/ptc-bindings.ts
5171
+ const KNOWN_BINDING_NAMES = [
5172
+ "read",
5173
+ "bash",
5174
+ "grep",
5175
+ "find",
5176
+ "ls",
5177
+ "edit",
5178
+ "write"
5179
+ ];
5180
+ const OPT_IN_BINDING_NAMES = ["bash"];
5181
+ const DEFAULT_BINDING_NAMES = KNOWN_BINDING_NAMES.filter((name) => !OPT_IN_BINDING_NAMES.includes(name));
5182
+ function resolveBindingNames(activeToolNames, optIns = []) {
5183
+ const allowed = /* @__PURE__ */ new Set([...DEFAULT_BINDING_NAMES, ...optIns]);
5184
+ return KNOWN_BINDING_NAMES.filter((name) => activeToolNames.includes(name) && allowed.has(name));
5185
+ }
5186
+ // src/task-binding.ts
5187
+ const TASK_BINDING_NAME = "pi_task_ptc";
5188
+ const TASK_DOOR_PARAMETERS = _Object_({
5189
+ task: String$1({
5190
+ minLength: 1,
5191
+ description: TASK_DOOR_PARAM_DESCRIPTIONS.task
5192
+ }),
5193
+ agent: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.agent })),
5194
+ why: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.why })),
5195
+ agentScope: Optional(Union([
5196
+ Literal("user"),
5197
+ Literal("project"),
5198
+ Literal("both")
5199
+ ], { description: TASK_DOOR_PARAM_DESCRIPTIONS.agentScope })),
5200
+ model: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.model })),
5201
+ thinkingLevel: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.thinkingLevel })),
5202
+ wait: Optional(Boolean$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.wait })),
5203
+ label: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.label })),
5204
+ readOnly: Optional(Union([Literal("strict"), Literal("lenient")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.readOnly })),
5205
+ timeoutMs: Optional(Number$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.timeoutMs })),
5206
+ projectContext: Optional(Union([Literal("auto"), Literal("none")], { description: TASK_DOOR_PARAM_DESCRIPTIONS.projectContext }))
5207
+ });
5208
+ function createTaskBinding(door) {
5209
+ return {
5210
+ name: TASK_BINDING_NAME,
5211
+ parameters: TASK_DOOR_PARAMETERS,
5212
+ execute(args) {
5213
+ const input = args ?? {};
5214
+ return door.createTask(input.task, {
5215
+ label: input.label,
5216
+ wait: input.wait,
5217
+ why: input.why,
5218
+ model: input.model,
5219
+ thinkingLevel: input.thinkingLevel,
5220
+ agent: input.agent,
5221
+ agentScope: input.agentScope,
5222
+ readOnly: input.readOnly,
5223
+ timeoutMs: input.timeoutMs,
5224
+ projectContext: input.projectContext
5225
+ });
5226
+ }
5227
+ };
5228
+ }
5229
+ const TASK_RECORDS_BINDING_NAME = "task_records";
5230
+ const TASK_OUTPUT_BINDING_NAME = "task_output";
5231
+ function createTaskOutputBinding(source) {
5232
+ return async (args) => {
5233
+ const raw = args;
5234
+ const taskId = raw?.taskId ?? raw?.task_id;
5235
+ if (typeof taskId !== "string" || taskId === "") return { error: "taskId 必须是非空字符串" };
5236
+ const record = source.registry.get(taskId) ?? await source.readArchivedRecord(taskId);
5237
+ if (!record) return { error: `unknown task id ${taskId}` };
5238
+ return {
5239
+ ...await readTaskOutput(source, record),
5240
+ status: record.status
5241
+ };
5242
+ };
5243
+ }
5300
5244
  // src/session.ts
5301
5245
  function assemblePtcSession(options = {}) {
5302
5246
  const registry = new TaskRegistry(options.onTransition);
@@ -5552,10 +5496,15 @@ function piPtcEntry(pi) {
5552
5496
  }] };
5553
5497
  });
5554
5498
  let toolContext;
5555
- const getBindings = () => createBindingTable(pi, session, () => toolContext);
5499
+ const cwd = process.cwd();
5500
+ const getBindings = () => createBindingTable(pi, session, () => toolContext, {
5501
+ cwd,
5502
+ extCtx: ctx,
5503
+ piVersion
5504
+ });
5556
5505
  registerPtcTools(registerTracked, {
5557
5506
  getBindings,
5558
- cwd: process.cwd(),
5507
+ cwd,
5559
5508
  surface,
5560
5509
  getPool: () => pool,
5561
5510
  onToolContext: (ctx) => {
@@ -5687,13 +5636,52 @@ function normalizeToolValue(value) {
5687
5636
  }
5688
5637
  return value;
5689
5638
  }
5690
- function createBindingTable(pi, session, getToolContext) {
5639
+ const PI_TOOL_FACTORIES = Object.fromEntries([
5640
+ "read",
5641
+ "write",
5642
+ "edit",
5643
+ "grep",
5644
+ "ls",
5645
+ "find"
5646
+ ].map((name) => [name, piHost[`create${name[0].toUpperCase()}${name.slice(1)}ToolDefinition`]]));
5647
+ function hasNestedToolCalls(piVersion) {
5648
+ if (!piVersion) return true;
5649
+ const [major = "0", minor = "0"] = piVersion.trim().replace(/^v/, "").split(".");
5650
+ const M = Number.parseInt(major, 10);
5651
+ const m = Number.parseInt(minor, 10);
5652
+ if (Number.isNaN(M) || Number.isNaN(m)) return true;
5653
+ return M > 0 || M === 0 && m >= 99;
5654
+ }
5655
+ function createBindingTable(pi, session, getToolContext, env) {
5656
+ const { cwd, extCtx, piVersion } = env;
5691
5657
  const active = pi.getActiveTools();
5692
5658
  const known = new Set(KNOWN_BINDING_NAMES);
5693
5659
  const names = resolveBindingNames(!getToolContext() && !active.some((name) => known.has(name)) ? pi.getAllTools().map((tool) => tool.name) : active);
5660
+ let piTools;
5661
+ const piToolFor = (name) => {
5662
+ if (!piTools) {
5663
+ piTools = {};
5664
+ for (const [tool, factory] of Object.entries(PI_TOOL_FACTORIES)) if (typeof factory === "function") piTools[tool] = factory(cwd);
5665
+ }
5666
+ return piTools[name];
5667
+ };
5694
5668
  const table = Object.fromEntries(names.map((name) => [name, async (args, callCtx) => {
5695
5669
  const ctx = getToolContext();
5696
- if (!ctx?.executeTool) throw new Error(`此 pi 版本没有程序内调用工具的通道(需 pi ≥ 0.99,binding: ${name})`);
5670
+ if (!ctx?.executeTool) {
5671
+ const def = hasNestedToolCalls(piVersion) ? void 0 : piToolFor(name);
5672
+ if (!def) throw new Error(`此 pi 版本没有程序内调用工具的通道(需 pi ≥ 0.99,binding: ${name})`);
5673
+ const prepared = def.prepareArguments ? def.prepareArguments(args) : args;
5674
+ const result = await def.execute(`ptc-${name}`, prepared, callCtx.signal, void 0, extCtx);
5675
+ for (const block of result.content ?? []) if (block.type === "image") callCtx.emitImage(block);
5676
+ if (result.isError) {
5677
+ const text = (result.content ?? []).map((block) => {
5678
+ const obj = block;
5679
+ return obj.type === "text" ? obj.text ?? "" : `[${obj.type ?? "block"}]`;
5680
+ }).join("\n");
5681
+ throw new Error(text || `工具 ${name} 执行失败`);
5682
+ }
5683
+ return normalizeToolValue(result.structuredContent ?? result.content);
5684
+ }
5697
5685
  const outcome = await ctx.executeTool(name, args, { signal: callCtx.signal });
5698
5686
  for (const block of outcome.result.content) if (block.type === "image") callCtx.emitImage(block);
5699
5687
  if (outcome.isError) {
@@ -5709,6 +5697,7 @@ function createBindingTable(pi, session, getToolContext) {
5709
5697
  if (args?.scope !== "all") return records;
5710
5698
  return [...records, ...await session.readArchivedRecords()];
5711
5699
  };
5700
+ table[TASK_OUTPUT_BINDING_NAME] = createTaskOutputBinding(session);
5712
5701
  return table;
5713
5702
  }
5714
5703
  function registerPtcTools(registerTool, options, surface) {
@@ -5795,4 +5784,4 @@ function registerTaskDoor(registerTool, session, surface) {
5795
5784
  }
5796
5785
  });
5797
5786
  }
5798
- export { assemblePtcSession, buildEngineContext, piPtcEntry as default, withDeliveryNote };
5787
+ export { assemblePtcSession, buildEngineContext, createTaskOutputBinding, piPtcEntry as default, withDeliveryNote };