pi-ptc-subagents 7.0.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +143 -65
  3. package/package.json +6 -2
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ptc-subagents",
3
- "version": "7.0.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"