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/README.md +37 -3
- package/dist/index.js +202 -213
- package/dist/ptc/cli.js +61 -0
- package/dist/ptc/fs-bindings-DCGErhvt.js +423 -0
- package/dist/ptc/index.js +77 -0
- package/dist/ptc/worker.js +197 -0
- package/docs/routes/reach-the-web.md +1 -0
- package/docs/routes/run-one-program.md +1 -0
- package/package.json +9 -4
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
|
|
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
|
|
3916
|
-
function stripAnsi
|
|
3917
|
-
return text.replace(ANSI_PATTERN
|
|
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
|
|
3920
|
-
return stripAnsi
|
|
3920
|
+
function sanitizeText(text) {
|
|
3921
|
+
return stripAnsi(text.replace(/\r/g, ""));
|
|
3921
3922
|
}
|
|
3922
|
-
const MAX_LINE_CHARS
|
|
3923
|
-
function truncateLine
|
|
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
|
|
3931
|
-
const TMP_FILE_TTL_MS
|
|
3932
|
-
const TMP_FILE_PREFIX
|
|
3933
|
-
let sweptTmp
|
|
3934
|
-
function sweepExpiredTmp
|
|
3935
|
-
sweptTmp
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3965
|
-
const fullOutputPath = join(tmpdir(), `${TMP_FILE_PREFIX
|
|
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
|
|
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
|
|
4031
|
+
...record.label ? { label: truncateLine(record.label, LABEL_MAX_CHARS) } : {},
|
|
3992
4032
|
...record.finishedAt !== void 0 ? { finishedAt: record.finishedAt } : {},
|
|
3993
|
-
...record.task ? { task: truncateLine
|
|
3994
|
-
...record.report?.summary ? { summary: truncateLine
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
4287
|
+
if (answer) return { text: applyAdr0015Truncation(answer).text };
|
|
4254
4288
|
return {
|
|
4255
|
-
output: applyAdr0015Truncation
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
// ../
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
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 };
|