pi-ptc-subagents 6.2.0 → 7.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-ptc-subagents
2
2
 
3
- Programmable tool calling and subagent dispatch for [pi](https://pi.dev).
3
+ Programmable tool calling and subagent dispatch for [pi](https://pi.dev) — and a [Kimi Code](https://www.kimi.com/code) plugin (`ptc-tools`, MCP) that brings the same "write one program, batch-call your tools" to Kimi sessions. The npm package name is the pi-face distribution; the Kimi face ships from this repo's releases.
4
4
 
5
5
  pi calls tools one at a time. This package lets the model write a program that calls them
6
6
  in one go — and lets that program spawn pi subagents. Two capabilities. One package.
package/dist/index.js CHANGED
@@ -3986,69 +3986,43 @@ function createSearchMemory() {
3986
3986
  };
3987
3987
  }
3988
3988
  // ../tui/src/bounds.ts
3989
- const RENDER_BOUNDS = {
3989
+ const RENDER_BOUNDS$1 = {
3990
3990
  MAX_LOG_LINES_EXPANDED: 12,
3991
3991
  VALUE_TREE_MAX_DEPTH: 4,
3992
3992
  VALUE_TREE_MAX_CHILDREN: 6,
3993
3993
  VALUE_TREE_MAX_LINE_CHARS: 120
3994
3994
  };
3995
3995
  // ../tui/src/text.ts
3996
- const ANSI_PATTERN = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
3997
- function stripAnsi(text) {
3998
- return text.replace(ANSI_PATTERN, "");
3996
+ const ANSI_PATTERN$1 = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
3997
+ function stripAnsi$1(text) {
3998
+ return text.replace(ANSI_PATTERN$1, "");
3999
3999
  }
4000
- function sanitizeText(text) {
4001
- return stripAnsi(text.replace(/\r/g, ""));
4000
+ function sanitizeText$1(text) {
4001
+ return stripAnsi$1(text.replace(/\r/g, ""));
4002
4002
  }
4003
- const MAX_LINE_CHARS = RENDER_BOUNDS.VALUE_TREE_MAX_LINE_CHARS;
4004
- function truncateLine(line, maxChars = MAX_LINE_CHARS) {
4005
- if (line.length <= maxChars) return line;
4006
- let cut = maxChars;
4007
- while (cut > 0 && (line.charCodeAt(cut) & 64512) === 56320) cut--;
4008
- return `${line.slice(0, cut)}…`;
4009
- }
4010
- function renderModelValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
4011
- const pad = " ".repeat(depth);
4012
- if (depth >= RENDER_BOUNDS.VALUE_TREE_MAX_DEPTH) return "…";
4013
- if (typeof value === "string") return depth === 0 ? value : JSON.stringify(value);
4014
- if (value === null || typeof value === "number" || typeof value === "boolean") return String(value);
4015
- if (undefinedValue(value)) return "(无完成值)";
4016
- if (value !== null && typeof value === "object") {
4017
- if (seen.has(value)) return "[Circular]";
4018
- seen.add(value);
4019
- }
4020
- if (Array.isArray(value)) {
4021
- const shown = value.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
4022
- const rest = value.length - shown.length;
4023
- const oneLine = `[${shown.map((v) => JSON.stringify(v)).join(", ")}]`;
4024
- 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}]`;
4025
- seen.delete(value);
4026
- return rendered;
4003
+ RENDER_BOUNDS$1.VALUE_TREE_MAX_LINE_CHARS;
4004
+ // ../tui/src/truncate.ts
4005
+ const TRUNCATION_MAX_BYTES$1 = 51200;
4006
+ const TMP_FILE_TTL_MS$1 = 864e5;
4007
+ const TMP_FILE_PREFIX$1 = "pi-ptc-output-";
4008
+ let sweptTmp$1 = false;
4009
+ function sweepExpiredTmp$1() {
4010
+ sweptTmp$1 = true;
4011
+ let names;
4012
+ try {
4013
+ names = readdirSync(tmpdir());
4014
+ } catch {
4015
+ return;
4027
4016
  }
4028
- if (value !== null && typeof value === "object") {
4029
- const entries = Object.entries(value);
4030
- const shown = entries.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
4031
- const rest = entries.length - shown.length;
4032
- const oneLine = `{ ${shown.map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }`;
4033
- 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}}`;
4034
- seen.delete(value);
4035
- return rendered;
4017
+ for (const name of names) {
4018
+ if (!name.startsWith(TMP_FILE_PREFIX$1)) continue;
4019
+ try {
4020
+ const file = join(tmpdir(), name);
4021
+ if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS$1) rmSync(file, { force: true });
4022
+ } catch {}
4036
4023
  }
4037
- return String(value);
4038
4024
  }
4039
- function primitive(v) {
4040
- return v === null || [
4041
- "string",
4042
- "number",
4043
- "boolean"
4044
- ].includes(typeof v);
4045
- }
4046
- function undefinedValue(value) {
4047
- return typeof value === "object" && value !== null && value.$void === true;
4048
- }
4049
- // ../tui/src/truncate.ts
4050
- const TRUNCATION_MAX_BYTES = 51200;
4051
- function applyAdr0015Truncation(text) {
4025
+ function applyAdr0015Truncation$1(text) {
4052
4026
  const lines = text.split("\n");
4053
4027
  const bytes = Buffer.byteLength(text, "utf8");
4054
4028
  if (bytes <= 51200 && lines.length <= 2e3) return {
@@ -4062,10 +4036,11 @@ function applyAdr0015Truncation(text) {
4062
4036
  kept = keptLines.join("\n");
4063
4037
  }
4064
4038
  const keptBuffer = Buffer.from(kept, "utf8");
4065
- if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES).toString("utf8");
4066
- const fullOutputPath = join(tmpdir(), `pi-ptc-output-${randomUUID()}.txt`);
4039
+ if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES$1).toString("utf8");
4040
+ const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX$1}${randomUUID()}.txt`);
4067
4041
  mkdirSync(tmpdir(), { recursive: true });
4068
4042
  writeFileSync(fullOutputPath, text, "utf8");
4043
+ if (!sweptTmp$1) sweepExpiredTmp$1();
4069
4044
  return {
4070
4045
  text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
4071
4046
  truncated: true,
@@ -4132,14 +4107,14 @@ async function runWebSearch(action, context) {
4132
4107
  };
4133
4108
  return {
4134
4109
  ok: true,
4135
- text: sanitizeText(renderSearchOverview(outcome.value, budget))
4110
+ text: sanitizeText$1(renderSearchOverview(outcome.value, budget))
4136
4111
  };
4137
4112
  }
4138
4113
  if (action.kind === "open") {
4139
4114
  const cached = context.memory.lookup(action.url);
4140
4115
  if (cached !== void 0) return {
4141
4116
  ok: true,
4142
- text: sanitizeText(`(记忆命中,未发请求)\n${cached.slice(0, budget)}`)
4117
+ text: sanitizeText$1(`(记忆命中,未发请求)\n${cached.slice(0, budget)}`)
4143
4118
  };
4144
4119
  if (!context.quota.tryAcquire()) return {
4145
4120
  ok: false,
@@ -4158,7 +4133,7 @@ async function runWebSearch(action, context) {
4158
4133
  context.memory.storeText(action.url, outcome.value);
4159
4134
  return {
4160
4135
  ok: true,
4161
- text: sanitizeText(outcome.value.slice(0, budget))
4136
+ text: sanitizeText$1(outcome.value.slice(0, budget))
4162
4137
  };
4163
4138
  }
4164
4139
  let regex;
@@ -4178,7 +4153,7 @@ async function runWebSearch(action, context) {
4178
4153
  })).filter(({ line }) => regex.test(line));
4179
4154
  return {
4180
4155
  ok: true,
4181
- text: sanitizeText(lines.length > 0 ? lines.slice(0, 50).map(({ line, index }) => `${index + 1}: ${line}`).join("\n") : "(页内无匹配行)").slice(0, budget)
4156
+ text: sanitizeText$1(lines.length > 0 ? lines.slice(0, 50).map(({ line, index }) => `${index + 1}: ${line}`).join("\n") : "(页内无匹配行)").slice(0, budget)
4182
4157
  };
4183
4158
  }
4184
4159
  /** 页文获取:记忆命中免费;否则占额抓取并回存记忆 */
@@ -4208,7 +4183,7 @@ async function ensurePageText(url, context) {
4208
4183
  text: outcome.value
4209
4184
  };
4210
4185
  }
4211
- // ../search/src/tool.ts
4186
+ // src/web-ds-search-tool.ts
4212
4187
  const WEB_DS_SEARCH_PARAMETERS = _Object_({
4213
4188
  action: Union([
4214
4189
  Literal("search"),
@@ -4371,14 +4346,13 @@ const WORKER_ENV_ALLOW_LIST = [
4371
4346
  "TEMP",
4372
4347
  "TMP"
4373
4348
  ];
4374
- const WORKER_EXEC_ARGV_DENY = /^(-e|--eval|-p|--print|--input-type|-)(=|$)/;
4375
4349
  function workerEnv() {
4376
4350
  return Object.fromEntries(WORKER_ENV_ALLOW_LIST.filter((key) => key in process.env).map((key) => [key, process.env[key]]));
4377
4351
  }
4378
4352
  function spawnPtcWorker() {
4379
4353
  return new Worker(buildWorkerUrl(import.meta.url), {
4380
4354
  env: workerEnv(),
4381
- execArgv: process.execArgv.filter((arg) => !WORKER_EXEC_ARGV_DENY.test(arg)),
4355
+ execArgv: [],
4382
4356
  resourceLimits: {
4383
4357
  maxOldGenerationSizeMb: 512,
4384
4358
  maxYoungGenerationSizeMb: 64
@@ -4655,7 +4629,111 @@ function resolveBindingNames(activeToolNames, optIns = []) {
4655
4629
  const allowed = /* @__PURE__ */ new Set([...DEFAULT_BINDING_NAMES, ...optIns]);
4656
4630
  return KNOWN_BINDING_NAMES.filter((name) => activeToolNames.includes(name) && allowed.has(name));
4657
4631
  }
4658
- // ../ptc/src/result-text.ts
4632
+ // ../output/dist/index.js
4633
+ const RENDER_BOUNDS = {
4634
+ MAX_LOG_LINES_EXPANDED: 12,
4635
+ VALUE_TREE_MAX_DEPTH: 4,
4636
+ VALUE_TREE_MAX_CHILDREN: 6,
4637
+ VALUE_TREE_MAX_LINE_CHARS: 120
4638
+ };
4639
+ const ANSI_PATTERN = /\x1B\[[0-9:;<=>?]*[ -/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
4640
+ function stripAnsi(text) {
4641
+ return text.replace(ANSI_PATTERN, "");
4642
+ }
4643
+ function sanitizeText(text) {
4644
+ return stripAnsi(text.replace(/\r/g, ""));
4645
+ }
4646
+ const MAX_LINE_CHARS = RENDER_BOUNDS.VALUE_TREE_MAX_LINE_CHARS;
4647
+ function truncateLine(line, maxChars = MAX_LINE_CHARS) {
4648
+ if (line.length <= maxChars) return line;
4649
+ let cut = maxChars;
4650
+ while (cut > 0 && (line.charCodeAt(cut) & 64512) === 56320) cut--;
4651
+ return `${line.slice(0, cut)}…`;
4652
+ }
4653
+ function renderModelValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
4654
+ const pad = " ".repeat(depth);
4655
+ if (depth >= RENDER_BOUNDS.VALUE_TREE_MAX_DEPTH) return "…";
4656
+ if (typeof value === "string") return depth === 0 ? value : JSON.stringify(value);
4657
+ if (value === null || typeof value === "number" || typeof value === "boolean") return String(value);
4658
+ if (undefinedValue(value)) return "(无完成值)";
4659
+ if (value !== null && typeof value === "object") {
4660
+ if (seen.has(value)) return "[Circular]";
4661
+ seen.add(value);
4662
+ }
4663
+ if (Array.isArray(value)) {
4664
+ const shown = value.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
4665
+ const rest = value.length - shown.length;
4666
+ const oneLine = `[${shown.map((v) => JSON.stringify(v)).join(", ")}]`;
4667
+ 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}]`;
4668
+ seen.delete(value);
4669
+ return rendered;
4670
+ }
4671
+ if (value !== null && typeof value === "object") {
4672
+ const entries = Object.entries(value);
4673
+ const shown = entries.slice(0, RENDER_BOUNDS.VALUE_TREE_MAX_CHILDREN);
4674
+ const rest = entries.length - shown.length;
4675
+ const oneLine = `{ ${shown.map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ")} }`;
4676
+ 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}}`;
4677
+ seen.delete(value);
4678
+ return rendered;
4679
+ }
4680
+ return String(value);
4681
+ }
4682
+ function primitive(v) {
4683
+ return v === null || [
4684
+ "string",
4685
+ "number",
4686
+ "boolean"
4687
+ ].includes(typeof v);
4688
+ }
4689
+ function undefinedValue(value) {
4690
+ return typeof value === "object" && value !== null && value.$void === true;
4691
+ }
4692
+ const TRUNCATION_MAX_BYTES = 51200;
4693
+ const TMP_FILE_TTL_MS = 864e5;
4694
+ const TMP_FILE_PREFIX = "pi-ptc-output-";
4695
+ let sweptTmp = false;
4696
+ function sweepExpiredTmp() {
4697
+ sweptTmp = true;
4698
+ let names;
4699
+ try {
4700
+ names = readdirSync(tmpdir());
4701
+ } catch {
4702
+ return;
4703
+ }
4704
+ for (const name of names) {
4705
+ if (!name.startsWith(TMP_FILE_PREFIX)) continue;
4706
+ try {
4707
+ const file = join(tmpdir(), name);
4708
+ if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS) rmSync(file, { force: true });
4709
+ } catch {}
4710
+ }
4711
+ }
4712
+ function applyAdr0015Truncation(text) {
4713
+ const lines = text.split("\n");
4714
+ const bytes = Buffer.byteLength(text, "utf8");
4715
+ if (bytes <= 51200 && lines.length <= 2e3) return {
4716
+ text,
4717
+ truncated: false
4718
+ };
4719
+ let keptLines = lines.slice(-2e3);
4720
+ let kept = keptLines.join("\n");
4721
+ while (Buffer.byteLength(kept, "utf8") > 51200 && keptLines.length > 1) {
4722
+ keptLines = keptLines.slice(Math.max(1, Math.floor(keptLines.length / 2)));
4723
+ kept = keptLines.join("\n");
4724
+ }
4725
+ const keptBuffer = Buffer.from(kept, "utf8");
4726
+ if (keptBuffer.length > 51200) kept = keptBuffer.subarray(keptBuffer.length - TRUNCATION_MAX_BYTES).toString("utf8");
4727
+ const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX}${randomUUID()}.txt`);
4728
+ mkdirSync(tmpdir(), { recursive: true });
4729
+ writeFileSync(fullOutputPath, text, "utf8");
4730
+ if (!sweptTmp) sweepExpiredTmp();
4731
+ return {
4732
+ text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
4733
+ truncated: true,
4734
+ fullOutputPath
4735
+ };
4736
+ }
4659
4737
  function renderToolResult(outcome) {
4660
4738
  let body;
4661
4739
  if (outcome.status === "fulfilled") body = (outcome.logs.length > 0 ? [...outcome.logs, renderModelValue(outcome.value)] : [renderModelValue(outcome.value)]).join("\n");
@@ -4832,7 +4910,7 @@ function renderPtcResult(surface) {
4832
4910
  if (options.expanded && !options.isPartial) for (const block of result.content) {
4833
4911
  if (block.type !== "text") continue;
4834
4912
  const lines = block.text.split("\n");
4835
- const kept = lines.slice(0, RENDER_BOUNDS.MAX_LOG_LINES_EXPANDED);
4913
+ const kept = lines.slice(0, RENDER_BOUNDS$1.MAX_LOG_LINES_EXPANDED);
4836
4914
  for (const line of kept) box.addChild(new Text(line));
4837
4915
  if (lines.length > kept.length) box.addChild(new Text(`…(共 ${lines.length} 行,省略 ${lines.length - kept.length} 行)`));
4838
4916
  }
@@ -5344,7 +5422,7 @@ function registerTaskFace(registerTool, session, surface) {
5344
5422
  const outputBytes = await session.outputSize(args.task_id);
5345
5423
  if (outputBytes === void 0) return textResult(record);
5346
5424
  if (outputBytes > 51200) {
5347
- const { text } = applyAdr0015Truncation(await session.readOutputTail(args.task_id, 51200) ?? "");
5425
+ const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
5348
5426
  return textResult({
5349
5427
  ...record,
5350
5428
  output: text,
@@ -5355,7 +5433,7 @@ function registerTaskFace(registerTool, session, surface) {
5355
5433
  })
5356
5434
  });
5357
5435
  }
5358
- const { text } = applyAdr0015Truncation(await session.readOutputTail(args.task_id, 51200) ?? "");
5436
+ const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
5359
5437
  return textResult({
5360
5438
  ...record,
5361
5439
  output: text
package/dist/worker.js CHANGED
@@ -1,66 +1,76 @@
1
1
  import { stripTypeScriptTypes } from "node:module";
2
- import { inspect } from "node:util";
3
2
  import { parentPort, workerData } from "node:worker_threads";
3
+ import vm from "node:vm";
4
4
  // ../ptc/src/compile.ts
5
5
  function compileProgram(source) {
6
- const stripped = stripTypeScriptTypes(`async function __ptcProgram() {\n${source}\n}`, { mode: "strip" });
7
- return new Function(`"use strict";\n${stripped}\nreturn __ptcProgram;`)();
8
- }
9
- // ../ptc/src/workflow-helpers.ts
10
- function createWorkflowHelpers(emit) {
11
- return {
12
- log: (...args) => emit(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")),
13
- phase: (name) => emit(`— phase: ${name} —`),
14
- parallel: (items) => Promise.allSettled(items.map((run) => run())),
15
- pipeline: (items, step) => {
16
- const results = [];
17
- return items.reduce((chain, item, index) => chain.then(async () => {
18
- results.push(await step(item, index));
19
- }), Promise.resolve()).then(() => results);
20
- }
21
- };
6
+ return `"use strict";\n${stripTypeScriptTypes(`async function __ptcProgram() {\n${source}\n}`, { mode: "strip" })}\n;globalThis.__ptcProgram = __ptcProgram;`;
22
7
  }
23
8
  // ../ptc/src/worker-main.ts
9
+ const CELL_PREAMBLE = `"use strict";
10
+ (function () {
11
+ var __bridge = globalThis.__ptcCall;
12
+ var __emit = globalThis.__ptcConsole;
13
+ var __args = JSON.parse(globalThis.__ptcArgsJson);
14
+ // contextified 全局属性不可 delete(vm 限制)但可写——置 undefined 即脱手,桥只剩序言闭包可达
15
+ globalThis.__ptcCall = undefined;
16
+ globalThis.__ptcConsole = undefined;
17
+ globalThis.__ptcArgsJson = undefined;
18
+ function __ptcFmt(v) {
19
+ if (typeof v === "string") return v;
20
+ try { var s = JSON.stringify(v); return s === undefined ? String(v) : s; } catch (e) { return String(v); }
21
+ }
22
+ function __ptcJoin(values) {
23
+ var out = [];
24
+ for (var i = 0; i < values.length; i++) out.push(__ptcFmt(values[i]));
25
+ return out.join(" ");
26
+ }
27
+ var __ptcTools = {};
28
+ __PTC_NAMES__.forEach(function (name) {
29
+ __ptcTools[name] = function (args) {
30
+ return new Promise(function (resolve, reject) {
31
+ __bridge(name, JSON.stringify(args === undefined ? null : args)).then(function (json) {
32
+ var value;
33
+ try { value = JSON.parse(json); } catch (e) { reject(e); return; }
34
+ resolve(value);
35
+ }, reject);
36
+ });
37
+ };
38
+ });
39
+ globalThis.tools = Object.freeze(__ptcTools);
40
+ globalThis.console = Object.freeze({
41
+ log: function () { __emit("log", __ptcJoin(arguments)); },
42
+ info: function () { __emit("info", __ptcJoin(arguments)); },
43
+ warn: function () { __emit("warn", __ptcJoin(arguments)); },
44
+ error: function () { __emit("error", __ptcJoin(arguments)); },
45
+ debug: function () { __emit("debug", __ptcJoin(arguments)); }
46
+ });
47
+ globalThis.args = Object.freeze(__args);
48
+ if (__PTC_WORKFLOW__) {
49
+ globalThis.workflow = Object.freeze({
50
+ log: function () { __emit("log", __ptcJoin(arguments)); },
51
+ phase: function (name) { __emit("log", "— phase: " + name + " —"); },
52
+ parallel: function (items) { return Promise.allSettled(items.map(function (run) { return run(); })); },
53
+ pipeline: function (items, step) {
54
+ var results = [];
55
+ return items.reduce(function (chain, item, index) {
56
+ return chain.then(function () { return Promise.resolve(step(item, index)).then(function (r) { results.push(r); }); });
57
+ }, Promise.resolve()).then(function () { return results; });
58
+ }
59
+ });
60
+ }
61
+ })();
62
+ `;
24
63
  function workerMain(deps) {
25
- let tools = {};
64
+ let bindingNames = [];
65
+ let surface = "run_code";
26
66
  let pending = /* @__PURE__ */ new Map();
27
67
  let nextCallId = 1;
28
68
  let currentRunId = null;
29
69
  let logs = [];
30
- const globalBaseline = /* @__PURE__ */ new Set();
31
- const consoleBaseline = { ...console };
32
70
  deps.onFrame((frame) => {
33
71
  if (frame.kind === "init") {
34
- for (const name of Object.getOwnPropertyNames(globalThis)) globalBaseline.add(name);
35
- tools = Object.freeze(Object.fromEntries(frame.bindingNames.map((name) => [name, (args) => {
36
- if (currentRunId === null) return Promise.reject(/* @__PURE__ */ new Error("run 已结束:reset 后不再接受工具调用"));
37
- const call = new Promise((resolve, reject) => {
38
- const callId = nextCallId++;
39
- pending.set(callId, {
40
- resolve,
41
- reject
42
- });
43
- deps.post({
44
- kind: "call",
45
- runId: currentRunId ?? "",
46
- callId,
47
- name,
48
- args
49
- });
50
- });
51
- call.catch(() => {});
52
- return call;
53
- }])));
54
- defineGlobal("tools", tools);
55
- if (frame.surface === "workflow") defineGlobal("workflow", createWorkflowHelpers((text) => {
56
- logs.push(text);
57
- deps.post({
58
- kind: "log",
59
- runId: currentRunId ?? "",
60
- level: "log",
61
- text
62
- });
63
- }));
72
+ bindingNames = frame.bindingNames;
73
+ surface = frame.surface;
64
74
  deps.post({ kind: "ready" });
65
75
  return;
66
76
  }
@@ -68,8 +78,6 @@ function workerMain(deps) {
68
78
  currentRunId = null;
69
79
  for (const { reject } of pending.values()) reject(/* @__PURE__ */ new Error("run reset:worker 被复用"));
70
80
  pending = /* @__PURE__ */ new Map();
71
- for (const name of Object.getOwnPropertyNames(globalThis)) if (!globalBaseline.has(name)) delete globalThis[name];
72
- Object.assign(console, consoleBaseline);
73
81
  deps.post({ kind: "ready" });
74
82
  return;
75
83
  }
@@ -77,25 +85,28 @@ function workerMain(deps) {
77
85
  const entry = pending.get(frame.callId);
78
86
  if (!entry) return;
79
87
  pending.delete(frame.callId);
80
- if (frame.ok) entry.resolve(frame.value);
81
- else entry.reject(new Error(frame.error));
88
+ if (frame.ok) {
89
+ let json;
90
+ try {
91
+ json = JSON.stringify(frame.value ?? null);
92
+ } catch {
93
+ json = void 0;
94
+ }
95
+ if (json === void 0) entry.reject(/* @__PURE__ */ new Error("binding 返回值不可 JSON 序列化(如循环引用)"));
96
+ else entry.resolve(json);
97
+ } else entry.reject(new Error(frame.error));
82
98
  return;
83
99
  }
84
100
  if (frame.kind === "cancel") return;
85
101
  run(frame.code, frame.id, frame.args);
86
102
  });
87
- function defineGlobal(name, value) {
88
- Object.defineProperty(globalThis, name, {
89
- value,
90
- writable: false,
91
- configurable: true
92
- });
103
+ function preamble() {
104
+ return CELL_PREAMBLE.replace(/__PTC_NAMES__/g, () => JSON.stringify(bindingNames)).replace(/__PTC_WORKFLOW__/g, () => surface === "workflow" ? "true" : "false");
93
105
  }
94
106
  async function run(code, runId, args) {
95
107
  currentRunId = runId;
96
108
  logs = [];
97
- defineGlobal("args", args ?? {});
98
- installConsole((level, text) => {
109
+ const postLog = (level, text) => {
99
110
  logs.push(text);
100
111
  deps.post({
101
112
  kind: "log",
@@ -103,17 +114,54 @@ function workerMain(deps) {
103
114
  level,
104
115
  text
105
116
  });
106
- });
107
- const outcome = await execute(code);
117
+ };
118
+ const outcome = await execute(code, vm.createContext({
119
+ __ptcArgsJson: JSON.stringify(args ?? {}),
120
+ __ptcCall(name, argsJson) {
121
+ if (currentRunId === null) return Promise.reject(/* @__PURE__ */ new Error("run 已结束:reset 后不再接受工具调用"));
122
+ let parsed = null;
123
+ try {
124
+ parsed = JSON.parse(argsJson);
125
+ } catch {
126
+ return Promise.reject(/* @__PURE__ */ new Error("工具参数不是合法 JSON"));
127
+ }
128
+ const call = new Promise((resolve, reject) => {
129
+ const callId = nextCallId++;
130
+ pending.set(callId, {
131
+ resolve,
132
+ reject
133
+ });
134
+ deps.post({
135
+ kind: "call",
136
+ runId: currentRunId ?? "",
137
+ callId,
138
+ name,
139
+ args: parsed
140
+ });
141
+ });
142
+ call.catch(() => {});
143
+ return call;
144
+ },
145
+ __ptcConsole(level, text) {
146
+ postLog(level, String(text));
147
+ }
148
+ }));
108
149
  deps.post({
109
150
  kind: "settled",
110
151
  runId,
111
152
  outcome
112
153
  });
113
154
  }
114
- async function execute(code) {
155
+ function errorMessage(error) {
156
+ if (error instanceof Error) return error.message;
157
+ if (error && typeof error === "object" && typeof error.message === "string") return error.message;
158
+ return String(error);
159
+ }
160
+ async function execute(code, context) {
115
161
  try {
116
- const value = await compileProgram(code).call({ tools });
162
+ vm.runInContext(preamble(), context, { filename: "ptc_preamble.js" });
163
+ vm.runInContext(compileProgram(code), context, { filename: "run_code.js" });
164
+ const value = await context.__ptcProgram.call(context);
117
165
  if (value === void 0) return {
118
166
  status: "fulfilled",
119
167
  value: { $void: true },
@@ -133,21 +181,12 @@ function workerMain(deps) {
133
181
  } catch (error) {
134
182
  return {
135
183
  status: "rejected",
136
- error: error instanceof Error ? error.message : String(error),
184
+ error: errorMessage(error),
137
185
  logs
138
186
  };
139
187
  }
140
188
  }
141
189
  }
142
- function installConsole(emit) {
143
- for (const level of [
144
- "log",
145
- "info",
146
- "warn",
147
- "error",
148
- "debug"
149
- ]) console[level] = (...args) => emit(level, args.map((a) => typeof a === "string" ? a : inspect(a)).join(" "));
150
- }
151
190
  // ../ptc/src/worker-entry.ts
152
191
  if (!parentPort) throw new Error("worker-entry 只能在 worker 线程里加载");
153
192
  Object.freeze(workerData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ptc-subagents",
3
- "version": "6.2.0",
3
+ "version": "7.1.0",
4
4
  "description": "Programmable tool calling (PTC) and subagent fan-out for pi",
5
5
  "keywords": [
6
6
  "pi",
@@ -31,10 +31,14 @@
31
31
  },
32
32
  "scripts": {
33
33
  "build": "pnpm -r --filter=!@pi-ptc/website run build",
34
+ "build:kimi": "pnpm --filter @pi-ptc/kimi-mcp run build && node scripts/assemble-kimi.mjs",
34
35
  "prepack": "pnpm run build && rm -rf dist docs/routes && cp -R packages/entry/dist dist && cp -R packages/routes/docs/routes docs/routes",
35
36
  "check": "pnpm -r --filter=!@pi-ptc/website run check",
36
37
  "lint": "oxlint",
37
- "test": "node --test tests/*.test.mjs",
38
+ "test": "pnpm run test:pi && pnpm run test:output && pnpm run test:kimi",
39
+ "test:pi": "node --test tests/pi/*.test.mjs",
40
+ "test:output": "node --test tests/output/*.test.mjs",
41
+ "test:kimi": "node --test tests/kimi/*.test.mjs",
38
42
  "pretest": "pnpm run build",
39
43
  "fmt": "oxfmt",
40
44
  "fmt:check": "oxfmt --check"