pi-ptc-subagents 7.1.0 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -37
- package/dist/index.js +591 -274
- package/docs/routes/create-a-task.md +50 -3
- package/docs/routes/manage-a-task.md +75 -3
- package/docs/routes/reach-the-web.md +5 -2
- package/docs/routes/run-one-program.md +5 -3
- package/docs/routes/use-the-built-in-tools.md +3 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, s
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { execSync, spawn } from "node:child_process";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { mkdir, open, readFile, rename, writeFile } from "node:fs/promises";
|
|
6
|
+
import { mkdir, open, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
7
7
|
import { gunzipSync, gzip } from "node:zlib";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -2746,6 +2746,15 @@ const TERMINAL_STATUSES = [
|
|
|
2746
2746
|
"canceled",
|
|
2747
2747
|
"lost"
|
|
2748
2748
|
];
|
|
2749
|
+
const TASK_STATUSES = [
|
|
2750
|
+
"queued",
|
|
2751
|
+
"running",
|
|
2752
|
+
"stopping",
|
|
2753
|
+
"succeeded",
|
|
2754
|
+
"failed",
|
|
2755
|
+
"canceled",
|
|
2756
|
+
"lost"
|
|
2757
|
+
];
|
|
2749
2758
|
function isTerminal(status) {
|
|
2750
2759
|
return TERMINAL_STATUSES.includes(status);
|
|
2751
2760
|
}
|
|
@@ -2848,7 +2857,8 @@ var TaskRegistry = class {
|
|
|
2848
2857
|
const next = {
|
|
2849
2858
|
...current,
|
|
2850
2859
|
report: command.report,
|
|
2851
|
-
reportChannel: command.channel
|
|
2860
|
+
reportChannel: command.channel,
|
|
2861
|
+
delivery: command.delivery ?? current.delivery
|
|
2852
2862
|
};
|
|
2853
2863
|
this.#records.set(command.id, next);
|
|
2854
2864
|
this.#emit(next, current);
|
|
@@ -3058,12 +3068,12 @@ var FileOutputStorage = class {
|
|
|
3058
3068
|
// ../dispatch/src/report.ts
|
|
3059
3069
|
const REPORT_CONTRACT_BLOCK = [
|
|
3060
3070
|
"<child-report-contract>",
|
|
3061
|
-
"Before finishing,
|
|
3071
|
+
"Before finishing, deliver a child report — preferably by calling the ptc_child_report tool (its schema fixes the shape); if that tool is not available, put a fenced JSON block at the very end of your final message:",
|
|
3062
3072
|
"```json",
|
|
3063
3073
|
"{ \"summary\": \"one sentence conclusion\", \"findings\": [{ \"what\": \"claim\", \"evidence\": \"where it comes from\" }], \"files_touched\": [\"relative/path\"], \"usage\": { \"input\": 0, \"output\": 0, \"cost\": 0, \"turns\": 0 } }",
|
|
3064
3074
|
"```",
|
|
3065
|
-
"Rules:
|
|
3066
|
-
"findings/files_touched/usage may be omitted when empty.
|
|
3075
|
+
"Rules: the report is a SUMMARY — it is NOT the deliverable. The deliverable is your prose answer, in exactly the format the task asked for; never hide the deliverable inside the JSON block. \"summary\" is one sentence of conclusion. \"findings\" carries load-bearing evidence only; never recap content you merely read. Do NOT write report/summary .md files — your final message is what the parent reads.",
|
|
3076
|
+
"findings/files_touched/usage may be omitted when empty.",
|
|
3067
3077
|
"</child-report-contract>"
|
|
3068
3078
|
].join("\n");
|
|
3069
3079
|
function extractReportFromText(text) {
|
|
@@ -3158,6 +3168,7 @@ function buildPiArgv(input, context) {
|
|
|
3158
3168
|
].filter(Boolean).join("\n")
|
|
3159
3169
|
];
|
|
3160
3170
|
argv.push("--no-extensions", "--no-skills");
|
|
3171
|
+
if (input.projectContext === "none") argv.push("--no-context-files");
|
|
3161
3172
|
if (supportsNoMcp(context.piVersion)) argv.push("--no-mcp");
|
|
3162
3173
|
const model = input.model ?? input.agentModel ?? context.sessionModel;
|
|
3163
3174
|
const thinking = input.thinkingLevel ?? input.agentThinkingLevel;
|
|
@@ -3220,12 +3231,16 @@ function findAgent(name, dirs, scope = "both") {
|
|
|
3220
3231
|
return null;
|
|
3221
3232
|
}
|
|
3222
3233
|
// ../dispatch/src/jsonl.ts
|
|
3234
|
+
const MAX_PATHS = 20;
|
|
3223
3235
|
function parseChildStream(stdout) {
|
|
3224
3236
|
let text = "";
|
|
3225
3237
|
let toolReport = null;
|
|
3226
3238
|
let observedUsage = null;
|
|
3227
3239
|
let observedModel = null;
|
|
3228
3240
|
let observedProvider = null;
|
|
3241
|
+
const tools = {};
|
|
3242
|
+
const paths = [];
|
|
3243
|
+
let errors = 0;
|
|
3229
3244
|
for (const line of stdout.split("\n")) {
|
|
3230
3245
|
const trimmed = line.trim();
|
|
3231
3246
|
if (!trimmed.startsWith("{")) continue;
|
|
@@ -3235,6 +3250,17 @@ function parseChildStream(stdout) {
|
|
|
3235
3250
|
} catch {
|
|
3236
3251
|
continue;
|
|
3237
3252
|
}
|
|
3253
|
+
if (event.type === "tool_execution_start") {
|
|
3254
|
+
const name = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
3255
|
+
tools[name] = (tools[name] ?? 0) + 1;
|
|
3256
|
+
const path = event.args?.path;
|
|
3257
|
+
if (typeof path === "string" && path && !paths.includes(path) && paths.length < MAX_PATHS) paths.push(path);
|
|
3258
|
+
continue;
|
|
3259
|
+
}
|
|
3260
|
+
if (event.type === "tool_execution_end") {
|
|
3261
|
+
if (event.isError === true || event.result?.isError === true) errors++;
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3238
3264
|
if (event.type !== "message_end" || !event.message) continue;
|
|
3239
3265
|
const message = event.message;
|
|
3240
3266
|
if (message.role === "assistant") {
|
|
@@ -3262,7 +3288,13 @@ function parseChildStream(stdout) {
|
|
|
3262
3288
|
toolReport,
|
|
3263
3289
|
observedUsage,
|
|
3264
3290
|
observedModel,
|
|
3265
|
-
observedProvider
|
|
3291
|
+
observedProvider,
|
|
3292
|
+
delivery: {
|
|
3293
|
+
tools,
|
|
3294
|
+
paths,
|
|
3295
|
+
errors,
|
|
3296
|
+
textChars: text.length
|
|
3297
|
+
}
|
|
3266
3298
|
};
|
|
3267
3299
|
}
|
|
3268
3300
|
var NotificationCenter = class {
|
|
@@ -3318,6 +3350,47 @@ var NotificationCenter = class {
|
|
|
3318
3350
|
function isTerminalStatus(status) {
|
|
3319
3351
|
return status === "succeeded" || status === "failed" || status === "canceled" || status === "lost";
|
|
3320
3352
|
}
|
|
3353
|
+
/** 护栏的检查间隔(秒级问一次就够——阈值本身是分钟量级) */
|
|
3354
|
+
const STALL_CHECK_INTERVAL_MS = 1e3;
|
|
3355
|
+
var ChildActivityTracker = class {
|
|
3356
|
+
#buffer = "";
|
|
3357
|
+
#lastActivityAt;
|
|
3358
|
+
/** 在飞的工具调用计数(嵌套调用靠计数,不靠集合——start/end 成对进出) */
|
|
3359
|
+
#toolOpen = 0;
|
|
3360
|
+
#now;
|
|
3361
|
+
constructor(now = Date.now) {
|
|
3362
|
+
this.#now = now;
|
|
3363
|
+
this.#lastActivityAt = now();
|
|
3364
|
+
}
|
|
3365
|
+
/** 喂一段 stdout 增量(chunk 边界不按行切 ⇒ 自己攒半行) */
|
|
3366
|
+
push(chunk) {
|
|
3367
|
+
this.#lastActivityAt = this.#now();
|
|
3368
|
+
this.#buffer += chunk;
|
|
3369
|
+
let index = this.#buffer.indexOf("\n");
|
|
3370
|
+
while (index >= 0) {
|
|
3371
|
+
this.#consume(this.#buffer.slice(0, index));
|
|
3372
|
+
this.#buffer = this.#buffer.slice(index + 1);
|
|
3373
|
+
index = this.#buffer.indexOf("\n");
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
/** 两条都成立 ⇒ 孩子什么都没在做 */
|
|
3377
|
+
isStalled(now, thresholdMs) {
|
|
3378
|
+
if (this.#toolOpen > 0) return false;
|
|
3379
|
+
return now - this.#lastActivityAt >= thresholdMs;
|
|
3380
|
+
}
|
|
3381
|
+
#consume(line) {
|
|
3382
|
+
const trimmed = line.trim();
|
|
3383
|
+
if (!trimmed.includes("\"tool_execution_start\"") && !trimmed.includes("\"tool_execution_end\"")) return;
|
|
3384
|
+
let event;
|
|
3385
|
+
try {
|
|
3386
|
+
event = JSON.parse(trimmed);
|
|
3387
|
+
} catch {
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
if (event.type === "tool_execution_start") this.#toolOpen++;
|
|
3391
|
+
else if (event.type === "tool_execution_end") this.#toolOpen = Math.max(0, this.#toolOpen - 1);
|
|
3392
|
+
}
|
|
3393
|
+
};
|
|
3321
3394
|
// ../dispatch/src/engine.ts
|
|
3322
3395
|
const DEFAULT_STOP_GRACE_MS = 5e3;
|
|
3323
3396
|
const STDOUT_KEEP_CHARS = 16777216;
|
|
@@ -3359,6 +3432,7 @@ var TaskEngine = class {
|
|
|
3359
3432
|
#terminalRecordKeep;
|
|
3360
3433
|
#archiveEnabled;
|
|
3361
3434
|
#archiveTtlDays;
|
|
3435
|
+
#stallMs;
|
|
3362
3436
|
#purgeTail = Promise.resolve();
|
|
3363
3437
|
#children = /* @__PURE__ */ new Map();
|
|
3364
3438
|
#ending = false;
|
|
@@ -3367,7 +3441,7 @@ var TaskEngine = class {
|
|
|
3367
3441
|
this.#registry = options.registry;
|
|
3368
3442
|
this.#slots = options.slots ?? new DispatchSlotCounter();
|
|
3369
3443
|
this.#output = options.output ?? new InMemoryOutputStorage();
|
|
3370
|
-
this.#spawn = options.spawnChild ?? ((argv) => spawnChild(argv));
|
|
3444
|
+
this.#spawn = options.spawnChild ?? ((argv, env) => spawnChild(argv, env));
|
|
3371
3445
|
this.#gates = options.gates ?? [];
|
|
3372
3446
|
this.#buildArgv = options.buildArgv ?? ((input, context) => buildPiArgv(input, context));
|
|
3373
3447
|
this.#resolveAgent = options.resolveAgent ?? (() => null);
|
|
@@ -3381,6 +3455,7 @@ var TaskEngine = class {
|
|
|
3381
3455
|
this.#terminalRecordKeep = options.terminalRecordKeep ?? DEFAULT_TERMINAL_RECORD_KEEP;
|
|
3382
3456
|
this.#archiveEnabled = options.archiveEnabled ?? true;
|
|
3383
3457
|
this.#archiveTtlDays = options.archiveTtlDays ?? DEFAULT_ARCHIVE_TTL_DAYS;
|
|
3458
|
+
this.#stallMs = options.stallMs ?? 3e5;
|
|
3384
3459
|
this.#ready = options.storageDir ? this.#restore(options.storageDir) : Promise.resolve();
|
|
3385
3460
|
this.#stopGraceMs = options.stopGraceMs ?? DEFAULT_STOP_GRACE_MS;
|
|
3386
3461
|
const whyOpt = options.whyEnabled;
|
|
@@ -3420,7 +3495,8 @@ var TaskEngine = class {
|
|
|
3420
3495
|
label: input.label,
|
|
3421
3496
|
whySuppressed,
|
|
3422
3497
|
ownerPid: this.#owner.pid,
|
|
3423
|
-
ownerBootMs: this.#owner.bootMs
|
|
3498
|
+
ownerBootMs: this.#owner.bootMs,
|
|
3499
|
+
sessionId: this.#context.sessionId
|
|
3424
3500
|
}
|
|
3425
3501
|
}).then(async () => {
|
|
3426
3502
|
if (input.wait) {
|
|
@@ -3511,9 +3587,21 @@ var TaskEngine = class {
|
|
|
3511
3587
|
};
|
|
3512
3588
|
const child = this.#spawn(argv, env);
|
|
3513
3589
|
this.#children.set(taskId, child);
|
|
3590
|
+
const timeoutMs = typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? input.timeoutMs : void 0;
|
|
3591
|
+
const timer = timeoutMs ? setTimeout(() => {
|
|
3592
|
+
this.stop(taskId, `timeout after ${timeoutMs}ms`);
|
|
3593
|
+
}, timeoutMs) : void 0;
|
|
3594
|
+
timer?.unref();
|
|
3595
|
+
const activity = new ChildActivityTracker();
|
|
3596
|
+
const stallMs = this.#stallMs > 0 ? this.#stallMs : void 0;
|
|
3597
|
+
const stallGuard = stallMs ? setInterval(() => {
|
|
3598
|
+
if (activity.isStalled(Date.now(), stallMs)) this.stop(taskId, `stalled: no activity for ${stallMs}ms`);
|
|
3599
|
+
}, Math.min(STALL_CHECK_INTERVAL_MS, stallMs)) : void 0;
|
|
3600
|
+
stallGuard?.unref();
|
|
3514
3601
|
let text = "";
|
|
3515
3602
|
let outputTruncated = false;
|
|
3516
3603
|
child.onOutput((chunk) => {
|
|
3604
|
+
activity.push(chunk);
|
|
3517
3605
|
text += chunk;
|
|
3518
3606
|
if (text.length > STDOUT_KEEP_CHARS) {
|
|
3519
3607
|
text = text.slice(-16777216);
|
|
@@ -3523,6 +3611,8 @@ var TaskEngine = class {
|
|
|
3523
3611
|
const exitCode = await new Promise((resolveExit) => {
|
|
3524
3612
|
child.onExit((code) => resolveExit(code));
|
|
3525
3613
|
});
|
|
3614
|
+
if (timer) clearTimeout(timer);
|
|
3615
|
+
if (stallGuard) clearInterval(stallGuard);
|
|
3526
3616
|
this.#children.delete(taskId);
|
|
3527
3617
|
const outputPath = await this.#output.write(taskId, text);
|
|
3528
3618
|
const parsed = parseChildStream(text);
|
|
@@ -3538,7 +3628,9 @@ var TaskEngine = class {
|
|
|
3538
3628
|
report = extracted.report ?? void 0;
|
|
3539
3629
|
channel = extracted.channel;
|
|
3540
3630
|
}
|
|
3541
|
-
|
|
3631
|
+
const selfUsage = report?.usage;
|
|
3632
|
+
const selfUsageEmpty = !selfUsage || !selfUsage.input && !selfUsage.output && !selfUsage.cost;
|
|
3633
|
+
if (report && selfUsageEmpty && parsed.observedUsage) report = {
|
|
3542
3634
|
...report,
|
|
3543
3635
|
usage: parsed.observedUsage
|
|
3544
3636
|
};
|
|
@@ -3546,7 +3638,8 @@ var TaskEngine = class {
|
|
|
3546
3638
|
kind: "attach-report",
|
|
3547
3639
|
id: taskId,
|
|
3548
3640
|
report,
|
|
3549
|
-
channel
|
|
3641
|
+
channel,
|
|
3642
|
+
delivery: parsed.delivery
|
|
3550
3643
|
});
|
|
3551
3644
|
await this.#registry.submit({
|
|
3552
3645
|
kind: "attach-output",
|
|
@@ -3582,6 +3675,7 @@ var TaskEngine = class {
|
|
|
3582
3675
|
whySuppressed,
|
|
3583
3676
|
report: report ?? void 0,
|
|
3584
3677
|
reportChannel: channel,
|
|
3678
|
+
delivery: parsed.delivery,
|
|
3585
3679
|
outputTruncated
|
|
3586
3680
|
};
|
|
3587
3681
|
}
|
|
@@ -3667,6 +3761,23 @@ var TaskEngine = class {
|
|
|
3667
3761
|
return Promise.resolve(void 0);
|
|
3668
3762
|
}
|
|
3669
3763
|
}
|
|
3764
|
+
/** 归档区的全部记录(冷存按 id 存 gz)——scope=all 时一次列出;异步读,不堵宿主事件循环 */
|
|
3765
|
+
async readArchivedRecords() {
|
|
3766
|
+
const dir = this.#storageDir ? join(this.#storageDir, "archive") : null;
|
|
3767
|
+
if (!dir || !existsSync(dir)) return [];
|
|
3768
|
+
const out = [];
|
|
3769
|
+
for (const file of await readdir(dir)) {
|
|
3770
|
+
if (!file.endsWith(".json.gz")) continue;
|
|
3771
|
+
try {
|
|
3772
|
+
const parsed = JSON.parse(gunzipSync(await readFile(join(dir, file))).toString("utf8"));
|
|
3773
|
+
if (parsed && typeof parsed.id === "string") out.push({
|
|
3774
|
+
...parsed,
|
|
3775
|
+
archived: true
|
|
3776
|
+
});
|
|
3777
|
+
} catch {}
|
|
3778
|
+
}
|
|
3779
|
+
return out;
|
|
3780
|
+
}
|
|
3670
3781
|
#readArchivedOutput(taskId) {
|
|
3671
3782
|
const file = this.#storageDir ? join(this.#storageDir, "archive", `${taskId}.output.gz`) : null;
|
|
3672
3783
|
if (!file || !existsSync(file)) return void 0;
|
|
@@ -3793,6 +3904,362 @@ var TaskEngine = class {
|
|
|
3793
3904
|
await this.#purgeTail.catch(() => void 0);
|
|
3794
3905
|
}
|
|
3795
3906
|
};
|
|
3907
|
+
// ../tui/src/bounds.ts
|
|
3908
|
+
const RENDER_BOUNDS$1 = {
|
|
3909
|
+
MAX_LOG_LINES_EXPANDED: 12,
|
|
3910
|
+
VALUE_TREE_MAX_DEPTH: 4,
|
|
3911
|
+
VALUE_TREE_MAX_CHILDREN: 6,
|
|
3912
|
+
VALUE_TREE_MAX_LINE_CHARS: 120
|
|
3913
|
+
};
|
|
3914
|
+
// ../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, "");
|
|
3918
|
+
}
|
|
3919
|
+
function sanitizeText$1(text) {
|
|
3920
|
+
return stripAnsi$1(text.replace(/\r/g, ""));
|
|
3921
|
+
}
|
|
3922
|
+
const MAX_LINE_CHARS$1 = RENDER_BOUNDS$1.VALUE_TREE_MAX_LINE_CHARS;
|
|
3923
|
+
function truncateLine$1(line, maxChars = MAX_LINE_CHARS$1) {
|
|
3924
|
+
if (line.length <= maxChars) return line;
|
|
3925
|
+
let cut = maxChars;
|
|
3926
|
+
while (cut > 0 && (line.charCodeAt(cut) & 64512) === 56320) cut--;
|
|
3927
|
+
return `${line.slice(0, cut)}…`;
|
|
3928
|
+
}
|
|
3929
|
+
// ../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;
|
|
3936
|
+
let names;
|
|
3937
|
+
try {
|
|
3938
|
+
names = readdirSync(tmpdir());
|
|
3939
|
+
} catch {
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
for (const name of names) {
|
|
3943
|
+
if (!name.startsWith(TMP_FILE_PREFIX$1)) continue;
|
|
3944
|
+
try {
|
|
3945
|
+
const file = join(tmpdir(), name);
|
|
3946
|
+
if (Date.now() - statSync(file).mtimeMs > TMP_FILE_TTL_MS$1) rmSync(file, { force: true });
|
|
3947
|
+
} catch {}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
function applyAdr0015Truncation$1(text) {
|
|
3951
|
+
const lines = text.split("\n");
|
|
3952
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
3953
|
+
if (bytes <= 51200 && lines.length <= 2e3) return {
|
|
3954
|
+
text,
|
|
3955
|
+
truncated: false
|
|
3956
|
+
};
|
|
3957
|
+
let keptLines = lines.slice(-2e3);
|
|
3958
|
+
let kept = keptLines.join("\n");
|
|
3959
|
+
while (Buffer.byteLength(kept, "utf8") > 51200 && keptLines.length > 1) {
|
|
3960
|
+
keptLines = keptLines.slice(Math.max(1, Math.floor(keptLines.length / 2)));
|
|
3961
|
+
kept = keptLines.join("\n");
|
|
3962
|
+
}
|
|
3963
|
+
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`);
|
|
3966
|
+
mkdirSync(tmpdir(), { recursive: true });
|
|
3967
|
+
writeFileSync(fullOutputPath, text, "utf8");
|
|
3968
|
+
if (!sweptTmp$1) sweepExpiredTmp$1();
|
|
3969
|
+
return {
|
|
3970
|
+
text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
|
|
3971
|
+
truncated: true,
|
|
3972
|
+
fullOutputPath
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
// ../task/src/list-tasks.ts
|
|
3976
|
+
function selectVisibleTasks(records, recentCompletedLimit = 10) {
|
|
3977
|
+
const inFlight = records.filter((record) => !isTerminal(record.status));
|
|
3978
|
+
const recentCompleted = records.filter((record) => isTerminal(record.status)).sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0)).slice(0, recentCompletedLimit);
|
|
3979
|
+
return [...inFlight, ...recentCompleted];
|
|
3980
|
+
}
|
|
3981
|
+
function taskFaceVisible(records) {
|
|
3982
|
+
return records.length > 0;
|
|
3983
|
+
}
|
|
3984
|
+
const TASK_TEXT_MAX_CHARS = 80;
|
|
3985
|
+
const SUMMARY_MAX_CHARS = 100;
|
|
3986
|
+
const LABEL_MAX_CHARS = 120;
|
|
3987
|
+
function projectTaskEntry(record, options = {}) {
|
|
3988
|
+
return {
|
|
3989
|
+
id: record.id,
|
|
3990
|
+
status: record.status,
|
|
3991
|
+
...record.label ? { label: truncateLine$1(record.label, LABEL_MAX_CHARS) } : {},
|
|
3992
|
+
...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) } : {},
|
|
3995
|
+
...options.archived ? { archived: true } : {},
|
|
3996
|
+
...options.withSession && record.sessionId ? { sessionId: record.sessionId } : {}
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
3999
|
+
function fitTaskEntries(entries, limit) {
|
|
4000
|
+
const picked = [];
|
|
4001
|
+
let bytes = 2;
|
|
4002
|
+
for (const entry of entries) {
|
|
4003
|
+
if (picked.length >= limit) break;
|
|
4004
|
+
const size = Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
4005
|
+
if (bytes + size > 49152) break;
|
|
4006
|
+
picked.push(entry);
|
|
4007
|
+
bytes += size;
|
|
4008
|
+
}
|
|
4009
|
+
return picked;
|
|
4010
|
+
}
|
|
4011
|
+
// ../routes/src/surface.ts
|
|
4012
|
+
const FALLBACK_SURFACE_MODE = "full_ptc";
|
|
4013
|
+
// ../routes/src/registry.ts
|
|
4014
|
+
const TOOLS = {
|
|
4015
|
+
ptc_run_code: {
|
|
4016
|
+
name: "ptc_run_code",
|
|
4017
|
+
description: (s) => ["写一段 TypeScript 程序批量调用工具:tools.<name>(args)。支持顶层 await、Promise.all 并行、循环与条件;程序返回值是归约后的结果,中间产物不进上下文。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达,模型直走 codemode。" : [
|
|
4018
|
+
"什么时候写程序而不是逐个调工具,看系统提示词 <ptc> 节的路由表。",
|
|
4019
|
+
"程序内可调本会话已启用的同名工具(read / write / edit / grep / find / ls;bash 默认不可用),参数与直接调用该工具完全一致;每次调用返回 Promise,要 await,返回文本结果,失败抛异常。",
|
|
4020
|
+
"程序内还可调 task_records({ scope: \"all\" }) 取任务记录(all 含压缩归档)——要挑很多条时在程序里筛完只 return 要的几条。",
|
|
4021
|
+
"例:const files = await tools.find({ pattern: \"*.py\" }); for (const f of files) await tools.write({ path: f, content: body });"
|
|
4022
|
+
].join("")].join("")
|
|
4023
|
+
},
|
|
4024
|
+
ptc_workflow: {
|
|
4025
|
+
name: "ptc_workflow",
|
|
4026
|
+
description: (s) => ["结构化的多阶段程序:script 里可用 workflow.log / workflow.phase / workflow.parallel / workflow.pipeline。适合阶段明确的批处理。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达。" : "与 ptc_run_code 的取舍看 <ptc> 节的路由表。"].join("")
|
|
4027
|
+
},
|
|
4028
|
+
pi_task_codemode: {
|
|
4029
|
+
name: "pi_task_codemode",
|
|
4030
|
+
description: () => "建一条子代理任务,默认立刻返回 handle(taskId + label);wait:true 时阻塞到终态再返回结果。任务在任务记录里排队,由派发引擎起独立 pi 进程执行;完成后的报告与产出经 ptc_task_output 读取。"
|
|
4031
|
+
},
|
|
4032
|
+
ptc_task_list: {
|
|
4033
|
+
name: "ptc_task_list",
|
|
4034
|
+
description: () => "列出在飞的任务与最近完成的任务(默认最近 50 条已完成)。响应给精简条目(id/status/label/finishedAt/任务摘要)与候选总数——总数比返回条数大,说明还有更早的记录没列出来。scope=all 连压缩归档一起列(更全也更慢),status 与 session 可筛。"
|
|
4035
|
+
},
|
|
4036
|
+
ptc_task_output: {
|
|
4037
|
+
name: "ptc_task_output",
|
|
4038
|
+
description: () => "按 task_id 读一条任务记录:状态、报告、孩子的正文(text——那是交付物,报告只是摘要)与实测轨迹。正文超长时按截断契约给尾部与全文路径;记录里的 outputPath 指向原始事件流。"
|
|
4039
|
+
},
|
|
4040
|
+
ptc_task_stop: {
|
|
4041
|
+
name: "ptc_task_stop",
|
|
4042
|
+
description: () => "按 task_id 叫停任务:排队中的直接抽掉(canceled),在跑的转 stopping。"
|
|
4043
|
+
}
|
|
4044
|
+
};
|
|
4045
|
+
const TASK_DOOR_PARAM_DESCRIPTIONS = {
|
|
4046
|
+
task: "孩子要干的活",
|
|
4047
|
+
agent: "已注册的 agent 名字;省略 = 通用孩子",
|
|
4048
|
+
agentScope: "agent 定义的查找范围(user / project / both)",
|
|
4049
|
+
why: "这一刀为什么切出来(调用方自己的话)——它会进孩子的提示词、多花孩子的 token;孩子从 task 里已经能读懂就别传",
|
|
4050
|
+
model: "覆盖孩子跑的模型",
|
|
4051
|
+
thinkingLevel: "覆盖孩子的思考档位——孩子的思考 token 是可调的成本项",
|
|
4052
|
+
wait: "true = 等这个任务完成再返回结果;false = 立刻返回 handle,结果走任务面/通知",
|
|
4053
|
+
label: "后台任务给人看的标签",
|
|
4054
|
+
readOnly: "strict = 孩子只有读工具(read/grep/find/ls,搜索开启时含 web_ds_search);lenient = 默认全工具。只需要读的活别给写权限",
|
|
4055
|
+
timeoutMs: "孩子的运行时长上限(毫秒)——超了按超时收尸;不传 = 不限(与不传时同)",
|
|
4056
|
+
projectContext: "孩子的项目上下文(AGENTS.md/CLAUDE.md):auto = 照常加载;none = 不加载——注意 none 只关「注入」不关「知识」:文件还在工作目录里,孩子会自己去翻(实测输入只差约 15%,不是省一半);这活不需要项目约定就传 none,要孩子遵守项目规矩就照常传(auto 也可能把项目里的写码规矩套进答案)"
|
|
4057
|
+
};
|
|
4058
|
+
const DELIVERY_CHECK_LEAD = "交付核对(引擎实测)";
|
|
4059
|
+
const DELIVERY_CHECK_NOTHING = "这孩子的结论没有依据:重派它,或明说缺口,别替它补数。";
|
|
4060
|
+
/** 父面渲染一行交付核对:工具/路径(实测)· 报错 · 正文长度 · 报告条数(自述) */
|
|
4061
|
+
function renderDeliveryCheck(input) {
|
|
4062
|
+
const d = input.delivery;
|
|
4063
|
+
if (!d) return "";
|
|
4064
|
+
const calls = Object.entries(d.tools);
|
|
4065
|
+
const totalCalls = calls.reduce((sum, [, count]) => sum + count, 0);
|
|
4066
|
+
const parts = [];
|
|
4067
|
+
if (calls.length > 0) {
|
|
4068
|
+
const tools = calls.map(([name, count]) => `${name}×${count}`).join(" ");
|
|
4069
|
+
const paths = d.paths.length > 0 ? `(${d.paths.slice(0, 5).join("、")}${d.paths.length > 5 ? ` 等 ${d.paths.length} 条` : ""})` : "";
|
|
4070
|
+
parts.push(`工具 ${tools}${paths}`);
|
|
4071
|
+
} else parts.push("没调用任何工具");
|
|
4072
|
+
parts.push(`报错 ${d.errors}`);
|
|
4073
|
+
parts.push(`正文 ${d.textChars} 字`);
|
|
4074
|
+
const findings = input.findings ?? 0;
|
|
4075
|
+
parts.push(`报告 ${findings} 条(${input.channel ?? "none"})`);
|
|
4076
|
+
return totalCalls === 0 && findings === 0 && d.textChars === 0 ? `${DELIVERY_CHECK_LEAD}:零产出——${parts.join(" · ")}。${DELIVERY_CHECK_NOTHING}` : `${DELIVERY_CHECK_LEAD}:${parts.join(" · ")}`;
|
|
4077
|
+
}
|
|
4078
|
+
const ROUTES = [
|
|
4079
|
+
{
|
|
4080
|
+
id: "create-a-task",
|
|
4081
|
+
name: "派一个子代理",
|
|
4082
|
+
when: "材料多、主题互不相干、每份都要读一堆材料时派——父一个人扛会算错(实测 92%→100%,n=3);成本 10 倍上下,一件能算完的活别派",
|
|
4083
|
+
how: {
|
|
4084
|
+
full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数",
|
|
4085
|
+
codemode_ptc: "tools.pi_task_codemode({ task, label? })(脚本内)——task 是孩子的唯一规格(写死路径/命令/交付物格式);要报告就点名 child report 的 JSON 块(summary/findings),别自创报告格式;汇总前核对孩子产出(delivery),零产出就重派别补数"
|
|
4086
|
+
},
|
|
4087
|
+
fact: "默认不等,立刻返回 handle;任务在任务记录里排队,可经任务面三工具查/停",
|
|
4088
|
+
detail: "docs/routes/create-a-task.md"
|
|
4089
|
+
},
|
|
4090
|
+
{
|
|
4091
|
+
id: "run-one-program",
|
|
4092
|
+
name: "批量调工具的一段程序",
|
|
4093
|
+
when: "连续多次工具调用、结果要过滤/归约后再看",
|
|
4094
|
+
how: {
|
|
4095
|
+
full_ptc: "ptc_run_code({ code, description })",
|
|
4096
|
+
codemode_ptc: "不适用——写程序是 full_ptc 面的编排能力;本面用 codemode 编排、pi_task_codemode 派任务"
|
|
4097
|
+
},
|
|
4098
|
+
fact: "程序内 tools.*;bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线);只在本面(full_ptc)注册 PTC 工具",
|
|
4099
|
+
detail: "docs/routes/run-one-program.md"
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
id: "run-a-phased-workflow",
|
|
4103
|
+
name: "分阶段 workflow",
|
|
4104
|
+
when: "批处理有明确阶段,要阶段性可见进度",
|
|
4105
|
+
how: {
|
|
4106
|
+
full_ptc: "ptc_workflow({ meta, script, args? })",
|
|
4107
|
+
codemode_ptc: "不适用——同 run-one-program:workflow 是 full_ptc 的编排能力"
|
|
4108
|
+
},
|
|
4109
|
+
fact: "script 内可用 workflow.log/phase/parallel/pipeline;只在本面注册",
|
|
4110
|
+
detail: "docs/routes/run-a-phased-workflow.md"
|
|
4111
|
+
},
|
|
4112
|
+
{
|
|
4113
|
+
id: "use-the-built-in-tools",
|
|
4114
|
+
name: "直接用内置工具",
|
|
4115
|
+
when: "单次调用就够,或每步都要模型判断(写操作、审批敏感)",
|
|
4116
|
+
how: {
|
|
4117
|
+
full_ptc: "程序内 tools.read/grep/find/ls/edit/write;逐个直调走 pi 自己的工具",
|
|
4118
|
+
codemode_ptc: "脚本内 tools.<内置名> 直接调"
|
|
4119
|
+
},
|
|
4120
|
+
fact: "bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)",
|
|
4121
|
+
detail: "docs/routes/use-the-built-in-tools.md"
|
|
4122
|
+
},
|
|
4123
|
+
{
|
|
4124
|
+
id: "report-what-you-did",
|
|
4125
|
+
name: "交报告",
|
|
4126
|
+
when: "子代理收工:结论要有证据、副作用与用量要可核对",
|
|
4127
|
+
how: {
|
|
4128
|
+
full_ptc: "孩子的契约:最终消息末尾 fenced JSON(summary/findings/files_touched/usage);宿主自动解析",
|
|
4129
|
+
codemode_ptc: "同左——契约在孩子的追加提示词里,与 surface 无关"
|
|
4130
|
+
},
|
|
4131
|
+
fact: "reportChannel 恒在四值:tool/prompt-json/none/opted-out;工具通道(ptc_child_report)已是首选",
|
|
4132
|
+
detail: "docs/routes/report-what-you-did.md"
|
|
4133
|
+
},
|
|
4134
|
+
{
|
|
4135
|
+
id: "reach-the-web",
|
|
4136
|
+
name: "上网查资料",
|
|
4137
|
+
when: "需要网页搜索、抓页面、页内找内容——判断力在干活的子代理侧",
|
|
4138
|
+
how: {
|
|
4139
|
+
full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search(search/open/find 三动作)",
|
|
4140
|
+
codemode_ptc: "同左——搜索只在子代理进程里注册,父会话没有这个工具"
|
|
4141
|
+
},
|
|
4142
|
+
fact: "webDsSearch 开关默认关(fail-closed);配额每进程计;概览走 snippet 超预算整条丢弃",
|
|
4143
|
+
detail: "docs/routes/reach-the-web.md"
|
|
4144
|
+
},
|
|
4145
|
+
{
|
|
4146
|
+
id: "manage-a-task",
|
|
4147
|
+
name: "管一个任务",
|
|
4148
|
+
when: "要知道孩子跑得怎样、读它的产出、或叫停它",
|
|
4149
|
+
how: {
|
|
4150
|
+
full_ptc: "ptc_task_list / ptc_task_output / ptc_task_stop(模型直调)——产出里的 delivery 是引擎实测轨迹",
|
|
4151
|
+
codemode_ptc: "同左(模型直调;脚本经结构化返回)——产出里的 delivery 是引擎实测轨迹"
|
|
4152
|
+
},
|
|
4153
|
+
fact: "有任务记录这三个工具才出现在模型面前(ADR-0007)",
|
|
4154
|
+
detail: "docs/routes/manage-a-task.md"
|
|
4155
|
+
}
|
|
4156
|
+
];
|
|
4157
|
+
const LONG_OUTPUT_GUIDANCE_WITH_REPORT = "产出超过截断阈值({bytes} 字节)。先看孩子的报告(归约层):{report}——响应里的 output 是截断后的尾部;报告没收录的完整原文用 read 读记录上的 outputPath。";
|
|
4158
|
+
const LONG_OUTPUT_GUIDANCE_NO_REPORT = "产出超过截断阈值({bytes} 字节),且孩子未交报告(reportChannel=none,散文可信度存疑)。响应里的 output 是截断后的尾部;完整原文用 read 读记录上的 outputPath。";
|
|
4159
|
+
function renderLongOutputGuidance(options) {
|
|
4160
|
+
if (options.report && options.reportChannel && options.reportChannel !== "none") {
|
|
4161
|
+
const findings = (options.report.findings ?? []).slice(0, 5).map((f) => `- ${f.what}${f.evidence ? `(证据:${f.evidence})` : ""}`).join("\n");
|
|
4162
|
+
return LONG_OUTPUT_GUIDANCE_WITH_REPORT.replace("{bytes}", String(options.bytes)).replace("{report}", [`summary: ${options.report.summary}`, findings].filter(Boolean).join("\n"));
|
|
4163
|
+
}
|
|
4164
|
+
return LONG_OUTPUT_GUIDANCE_NO_REPORT.replace("{bytes}", String(options.bytes));
|
|
4165
|
+
}
|
|
4166
|
+
const COMPACTED_TASK_REMINDER_LEAD = "对话刚被压缩——启动这些任务的消息已经不在上下文里了,但任务还在跑或已经跑完。用 ptc_task_list 看全部(scope=all 连压缩归档一起列),用 ptc_task_output 取产出(给的是孩子的正文,不是原始事件流)。";
|
|
4167
|
+
function renderCompactedTaskReminder(tasks) {
|
|
4168
|
+
const lines = tasks.map((t) => `- ${t.id}${t.label ? ` (${t.label})` : ""}: ${t.status}`);
|
|
4169
|
+
return [
|
|
4170
|
+
"<compacted-tasks>",
|
|
4171
|
+
COMPACTED_TASK_REMINDER_LEAD,
|
|
4172
|
+
...lines,
|
|
4173
|
+
"</compacted-tasks>"
|
|
4174
|
+
].join("\n");
|
|
4175
|
+
}
|
|
4176
|
+
function renderTaskNotifications(batch) {
|
|
4177
|
+
return [
|
|
4178
|
+
"<bg-task-notifications>",
|
|
4179
|
+
...batch.map((n) => `- task ${n.taskId}${n.label ? ` (${n.label})` : ""}: ${n.status}${n.stopReason ? ` — ${n.stopReason}` : ""}`),
|
|
4180
|
+
"</bg-task-notifications>"
|
|
4181
|
+
].join("\n");
|
|
4182
|
+
}
|
|
4183
|
+
function estimateTokens(text) {
|
|
4184
|
+
return Math.ceil(text.length / 2);
|
|
4185
|
+
}
|
|
4186
|
+
const ROUTE_INDEX_LEAD = {
|
|
4187
|
+
full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。派不派子代理自己判断——路由表第一行写了代价。",
|
|
4188
|
+
codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。派不派自己判断——路由表第一行写了代价。"
|
|
4189
|
+
};
|
|
4190
|
+
function renderRouteIndex(surface, detailBase, nudge = true) {
|
|
4191
|
+
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${detailBase ? route.detail.split("/").pop() : route.detail}`);
|
|
4192
|
+
let index = [
|
|
4193
|
+
`<ptc>`,
|
|
4194
|
+
...nudge ? [ROUTE_INDEX_LEAD[surface]] : [],
|
|
4195
|
+
...detailBase ? [`详情目录:${detailBase}`] : [],
|
|
4196
|
+
"路由表(一条一行,详情按需 read):",
|
|
4197
|
+
...lines,
|
|
4198
|
+
`</ptc>`
|
|
4199
|
+
].join("\n");
|
|
4200
|
+
const tokens = estimateTokens(index);
|
|
4201
|
+
if (tokens > 560) index += `\n> WARNING: index over token budget (${tokens} > 560); move detail into docs/routes/*.md`;
|
|
4202
|
+
return index;
|
|
4203
|
+
}
|
|
4204
|
+
function taskListSnapshot(registry, query = {}, archived = []) {
|
|
4205
|
+
const scope = query.scope === "all" ? "all" : "recent";
|
|
4206
|
+
const limit = Math.min(Math.max(query.limit ?? 50, 1), 200);
|
|
4207
|
+
const candidates = (scope === "all" ? [...registry.list(), ...archived] : registry.list()).filter((record) => {
|
|
4208
|
+
if (query.status && record.status !== query.status) return false;
|
|
4209
|
+
if (query.session && record.sessionId !== query.session) return false;
|
|
4210
|
+
return true;
|
|
4211
|
+
});
|
|
4212
|
+
const visible = selectVisibleTasks(candidates, limit);
|
|
4213
|
+
const withSession = query.session !== void 0;
|
|
4214
|
+
return {
|
|
4215
|
+
scope,
|
|
4216
|
+
total: candidates.length,
|
|
4217
|
+
tasks: fitTaskEntries(visible.map((record) => projectTaskEntry(record, {
|
|
4218
|
+
archived: "archived" in record,
|
|
4219
|
+
withSession
|
|
4220
|
+
})), limit)
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
function faceVisible(registry) {
|
|
4224
|
+
return taskFaceVisible(registry.list());
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* 压缩后重注入的清单:在飞任务 + 最近完成的 5 条。
|
|
4228
|
+
* 没有任务就不注入(空清单只是噪音)。
|
|
4229
|
+
*/
|
|
4230
|
+
function compactedTaskReminder(registry) {
|
|
4231
|
+
const records = registry.list();
|
|
4232
|
+
const inFlight = records.filter((record) => !isTerminal(record.status));
|
|
4233
|
+
const recentCompleted = records.filter((record) => isTerminal(record.status)).sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0)).slice(0, 5);
|
|
4234
|
+
if (inFlight.length === 0 && recentCompleted.length === 0) return void 0;
|
|
4235
|
+
return renderCompactedTaskReminder([...inFlight, ...recentCompleted].map((record) => ({
|
|
4236
|
+
id: record.id,
|
|
4237
|
+
status: record.status,
|
|
4238
|
+
label: record.label
|
|
4239
|
+
})));
|
|
4240
|
+
}
|
|
4241
|
+
// ../task/src/output.ts
|
|
4242
|
+
/**
|
|
4243
|
+
* 任务产出读法:先给归约后的孩子正文(从事件流现算,不另存一份),
|
|
4244
|
+
* 没有正文才退回原始尾部 + 分层引导(完整原文在 outputPath)。
|
|
4245
|
+
*/
|
|
4246
|
+
async function readTaskOutput(source, record) {
|
|
4247
|
+
const taskId = record.id;
|
|
4248
|
+
const outputBytes = await source.outputSize(taskId);
|
|
4249
|
+
if (outputBytes === void 0) return {};
|
|
4250
|
+
const tail = await source.readOutputTail(taskId, 51200) ?? "";
|
|
4251
|
+
let answer = parseChildStream(tail).text;
|
|
4252
|
+
if (!answer && outputBytes > 51200) answer = parseChildStream(await source.readOutput(taskId) ?? "").text;
|
|
4253
|
+
if (answer) return { text: applyAdr0015Truncation$1(answer).text };
|
|
4254
|
+
return {
|
|
4255
|
+
output: applyAdr0015Truncation$1(tail).text,
|
|
4256
|
+
guidance: renderLongOutputGuidance({
|
|
4257
|
+
bytes: outputBytes,
|
|
4258
|
+
report: record.report,
|
|
4259
|
+
reportChannel: record.reportChannel ?? "none"
|
|
4260
|
+
})
|
|
4261
|
+
};
|
|
4262
|
+
}
|
|
3796
4263
|
// ../search/src/provider.ts
|
|
3797
4264
|
const SEARCH_COUNT_RANGE = {
|
|
3798
4265
|
min: 1,
|
|
@@ -3985,68 +4452,6 @@ function createSearchMemory() {
|
|
|
3985
4452
|
}
|
|
3986
4453
|
};
|
|
3987
4454
|
}
|
|
3988
|
-
// ../tui/src/bounds.ts
|
|
3989
|
-
const RENDER_BOUNDS$1 = {
|
|
3990
|
-
MAX_LOG_LINES_EXPANDED: 12,
|
|
3991
|
-
VALUE_TREE_MAX_DEPTH: 4,
|
|
3992
|
-
VALUE_TREE_MAX_CHILDREN: 6,
|
|
3993
|
-
VALUE_TREE_MAX_LINE_CHARS: 120
|
|
3994
|
-
};
|
|
3995
|
-
// ../tui/src/text.ts
|
|
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
|
-
}
|
|
4000
|
-
function sanitizeText$1(text) {
|
|
4001
|
-
return stripAnsi$1(text.replace(/\r/g, ""));
|
|
4002
|
-
}
|
|
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;
|
|
4016
|
-
}
|
|
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 {}
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
function applyAdr0015Truncation$1(text) {
|
|
4026
|
-
const lines = text.split("\n");
|
|
4027
|
-
const bytes = Buffer.byteLength(text, "utf8");
|
|
4028
|
-
if (bytes <= 51200 && lines.length <= 2e3) return {
|
|
4029
|
-
text,
|
|
4030
|
-
truncated: false
|
|
4031
|
-
};
|
|
4032
|
-
let keptLines = lines.slice(-2e3);
|
|
4033
|
-
let kept = keptLines.join("\n");
|
|
4034
|
-
while (Buffer.byteLength(kept, "utf8") > 51200 && keptLines.length > 1) {
|
|
4035
|
-
keptLines = keptLines.slice(Math.max(1, Math.floor(keptLines.length / 2)));
|
|
4036
|
-
kept = keptLines.join("\n");
|
|
4037
|
-
}
|
|
4038
|
-
const keptBuffer = Buffer.from(kept, "utf8");
|
|
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`);
|
|
4041
|
-
mkdirSync(tmpdir(), { recursive: true });
|
|
4042
|
-
writeFileSync(fullOutputPath, text, "utf8");
|
|
4043
|
-
if (!sweptTmp$1) sweepExpiredTmp$1();
|
|
4044
|
-
return {
|
|
4045
|
-
text: [`…(已截断:省略前 ${lines.length - keptLines.length} 行 / ${bytes - Buffer.byteLength(kept, "utf8")} 字节,全文在 ${fullOutputPath})`, kept].join("\n"),
|
|
4046
|
-
truncated: true,
|
|
4047
|
-
fullOutputPath
|
|
4048
|
-
};
|
|
4049
|
-
}
|
|
4050
4455
|
// ../search/src/run.ts
|
|
4051
4456
|
const DOMAIN_INTERVAL_MS = 50;
|
|
4052
4457
|
const domainTails = /* @__PURE__ */ new Map();
|
|
@@ -4251,20 +4656,22 @@ const TASK_BINDING_NAME = "pi_task_ptc";
|
|
|
4251
4656
|
const TASK_DOOR_PARAMETERS = _Object_({
|
|
4252
4657
|
task: String$1({
|
|
4253
4658
|
minLength: 1,
|
|
4254
|
-
description:
|
|
4659
|
+
description: TASK_DOOR_PARAM_DESCRIPTIONS.task
|
|
4255
4660
|
}),
|
|
4256
|
-
agent: Optional(String$1({ description:
|
|
4257
|
-
why: Optional(String$1({ description:
|
|
4661
|
+
agent: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.agent })),
|
|
4662
|
+
why: Optional(String$1({ description: TASK_DOOR_PARAM_DESCRIPTIONS.why })),
|
|
4258
4663
|
agentScope: Optional(Union([
|
|
4259
4664
|
Literal("user"),
|
|
4260
4665
|
Literal("project"),
|
|
4261
4666
|
Literal("both")
|
|
4262
|
-
])),
|
|
4263
|
-
model: Optional(String$1({ description:
|
|
4264
|
-
thinkingLevel: Optional(String$1()),
|
|
4265
|
-
wait: Optional(Boolean$1({ description:
|
|
4266
|
-
label: Optional(String$1({ description:
|
|
4267
|
-
readOnly: Optional(Union([Literal("strict"), Literal("lenient")]))
|
|
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 }))
|
|
4268
4675
|
});
|
|
4269
4676
|
function createTaskBinding(door) {
|
|
4270
4677
|
return {
|
|
@@ -4280,11 +4687,14 @@ function createTaskBinding(door) {
|
|
|
4280
4687
|
thinkingLevel: input.thinkingLevel,
|
|
4281
4688
|
agent: input.agent,
|
|
4282
4689
|
agentScope: input.agentScope,
|
|
4283
|
-
readOnly: input.readOnly
|
|
4690
|
+
readOnly: input.readOnly,
|
|
4691
|
+
timeoutMs: input.timeoutMs,
|
|
4692
|
+
projectContext: input.projectContext
|
|
4284
4693
|
});
|
|
4285
4694
|
}
|
|
4286
4695
|
};
|
|
4287
4696
|
}
|
|
4697
|
+
const TASK_RECORDS_BINDING_NAME = "task_records";
|
|
4288
4698
|
// ../ptc/src/limits.ts
|
|
4289
4699
|
const DEFAULT_LIMITS = {
|
|
4290
4700
|
timeoutMs: 12e4,
|
|
@@ -4746,151 +5156,6 @@ function renderToolResult(outcome) {
|
|
|
4746
5156
|
fullOutputPath
|
|
4747
5157
|
};
|
|
4748
5158
|
}
|
|
4749
|
-
// ../routes/src/surface.ts
|
|
4750
|
-
const FALLBACK_SURFACE_MODE = "full_ptc";
|
|
4751
|
-
// ../routes/src/registry.ts
|
|
4752
|
-
const TOOLS = {
|
|
4753
|
-
ptc_run_code: {
|
|
4754
|
-
name: "ptc_run_code",
|
|
4755
|
-
description: (s) => ["写一段 TypeScript 程序批量调用工具:tools.<name>(args)。支持顶层 await、Promise.all 并行、循环与条件;程序返回值是归约后的结果,中间产物不进上下文。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达,模型直走 codemode。" : "什么时候写程序而不是逐个调工具,看系统提示词 <ptc> 节的路由表。"].join("")
|
|
4756
|
-
},
|
|
4757
|
-
ptc_workflow: {
|
|
4758
|
-
name: "ptc_workflow",
|
|
4759
|
-
description: (s) => ["结构化的多阶段程序:script 里可用 workflow.log / workflow.phase / workflow.parallel / workflow.pipeline。适合阶段明确的批处理。", s === "codemode_ptc" ? "本会话里我从 codemode 脚本可达。" : "与 ptc_run_code 的取舍看 <ptc> 节的路由表。"].join("")
|
|
4760
|
-
},
|
|
4761
|
-
pi_task_codemode: {
|
|
4762
|
-
name: "pi_task_codemode",
|
|
4763
|
-
description: () => "建一条子代理任务,默认立刻返回 handle(taskId + label);wait:true 时阻塞到终态再返回结果。任务在任务记录里排队,由派发引擎起独立 pi 进程执行;完成后的报告与产出经 ptc_task_output 读取。"
|
|
4764
|
-
},
|
|
4765
|
-
ptc_task_list: {
|
|
4766
|
-
name: "ptc_task_list",
|
|
4767
|
-
description: () => "列出在飞的任务与最近完成的任务(最近 10 条已完成)。"
|
|
4768
|
-
},
|
|
4769
|
-
ptc_task_output: {
|
|
4770
|
-
name: "ptc_task_output",
|
|
4771
|
-
description: () => "按 task_id 读一条任务记录:状态、报告、产出。产出超过截断阈值时给尾部与完整产出位置(outputPath)。"
|
|
4772
|
-
},
|
|
4773
|
-
ptc_task_stop: {
|
|
4774
|
-
name: "ptc_task_stop",
|
|
4775
|
-
description: () => "按 task_id 叫停任务:排队中的直接抽掉(canceled),在跑的转 stopping。"
|
|
4776
|
-
}
|
|
4777
|
-
};
|
|
4778
|
-
const ROUTES = [
|
|
4779
|
-
{
|
|
4780
|
-
id: "create-a-task",
|
|
4781
|
-
name: "派一个子代理",
|
|
4782
|
-
when: "这件事需要它自己的上下文,或者要跟别的活并行",
|
|
4783
|
-
how: {
|
|
4784
|
-
full_ptc: "tools.pi_task_ptc({ task, label? })(程序内)",
|
|
4785
|
-
codemode_ptc: "tools.pi_task_codemode({ task, label? })(脚本内)"
|
|
4786
|
-
},
|
|
4787
|
-
fact: "默认不等,立刻返回 handle;任务在任务记录里排队,可经任务面三工具查/停",
|
|
4788
|
-
detail: "docs/routes/create-a-task.md"
|
|
4789
|
-
},
|
|
4790
|
-
{
|
|
4791
|
-
id: "run-one-program",
|
|
4792
|
-
name: "批量调工具的一段程序",
|
|
4793
|
-
when: "连续多次工具调用、结果要过滤/归约后再看",
|
|
4794
|
-
how: {
|
|
4795
|
-
full_ptc: "ptc_run_code({ code, description })",
|
|
4796
|
-
codemode_ptc: "不适用——写程序是 full_ptc 面的编排能力;本面用 codemode 编排、pi_task_codemode 派任务"
|
|
4797
|
-
},
|
|
4798
|
-
fact: "程序内 tools.*;bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线);只在本面(full_ptc)注册 PTC 工具",
|
|
4799
|
-
detail: "docs/routes/run-one-program.md"
|
|
4800
|
-
},
|
|
4801
|
-
{
|
|
4802
|
-
id: "run-a-phased-workflow",
|
|
4803
|
-
name: "分阶段 workflow",
|
|
4804
|
-
when: "批处理有明确阶段,要阶段性可见进度",
|
|
4805
|
-
how: {
|
|
4806
|
-
full_ptc: "ptc_workflow({ meta, script, args? })",
|
|
4807
|
-
codemode_ptc: "不适用——同 run-one-program:workflow 是 full_ptc 的编排能力"
|
|
4808
|
-
},
|
|
4809
|
-
fact: "script 内可用 workflow.log/phase/parallel/pipeline;只在本面注册",
|
|
4810
|
-
detail: "docs/routes/run-a-phased-workflow.md"
|
|
4811
|
-
},
|
|
4812
|
-
{
|
|
4813
|
-
id: "use-the-built-in-tools",
|
|
4814
|
-
name: "直接用内置工具",
|
|
4815
|
-
when: "单次调用就够,或每步都要模型判断(写操作、审批敏感)",
|
|
4816
|
-
how: {
|
|
4817
|
-
full_ptc: "程序内 tools.read/grep/find/ls/edit/write;逐个直调走 pi 自己的工具",
|
|
4818
|
-
codemode_ptc: "脚本内 tools.<内置名> 直接调"
|
|
4819
|
-
},
|
|
4820
|
-
fact: "bash 当前不在程序绑定表里(旧 ADR-0040 的 opt-in 尚未接线)",
|
|
4821
|
-
detail: "docs/routes/use-the-built-in-tools.md"
|
|
4822
|
-
},
|
|
4823
|
-
{
|
|
4824
|
-
id: "report-what-you-did",
|
|
4825
|
-
name: "交报告",
|
|
4826
|
-
when: "子代理收工:结论要有证据、副作用与用量要可核对",
|
|
4827
|
-
how: {
|
|
4828
|
-
full_ptc: "孩子的契约:最终消息末尾 fenced JSON(summary/findings/files_touched/usage);宿主自动解析",
|
|
4829
|
-
codemode_ptc: "同左——契约在孩子的追加提示词里,与 surface 无关"
|
|
4830
|
-
},
|
|
4831
|
-
fact: "reportChannel 恒在四值:tool/prompt-json/none/opted-out;工具通道(ptc_child_report)已是首选",
|
|
4832
|
-
detail: "docs/routes/report-what-you-did.md"
|
|
4833
|
-
},
|
|
4834
|
-
{
|
|
4835
|
-
id: "reach-the-web",
|
|
4836
|
-
name: "上网查资料",
|
|
4837
|
-
when: "需要网页搜索、抓页面、页内找内容——判断力在干活的子代理侧",
|
|
4838
|
-
how: {
|
|
4839
|
-
full_ptc: "派子代理(pi_task_ptc),孩子用 web_ds_search(search/open/find 三动作)",
|
|
4840
|
-
codemode_ptc: "同左——搜索只在子代理进程里注册,父会话没有这个工具"
|
|
4841
|
-
},
|
|
4842
|
-
fact: "webDsSearch 开关默认关(fail-closed);配额每进程计;概览走 snippet 超预算整条丢弃",
|
|
4843
|
-
detail: "docs/routes/reach-the-web.md"
|
|
4844
|
-
},
|
|
4845
|
-
{
|
|
4846
|
-
id: "manage-a-task",
|
|
4847
|
-
name: "管一个任务",
|
|
4848
|
-
when: "要知道孩子跑得怎样、读它的产出、或叫停它",
|
|
4849
|
-
how: {
|
|
4850
|
-
full_ptc: "ptc_task_list / ptc_task_output / ptc_task_stop(模型直调)",
|
|
4851
|
-
codemode_ptc: "同左(模型直调;脚本经结构化返回)"
|
|
4852
|
-
},
|
|
4853
|
-
fact: "有任务记录这三个工具才出现在模型面前(ADR-0007)",
|
|
4854
|
-
detail: "docs/routes/manage-a-task.md"
|
|
4855
|
-
}
|
|
4856
|
-
];
|
|
4857
|
-
const LONG_OUTPUT_GUIDANCE_WITH_REPORT = "产出超过截断阈值({bytes} 字节)。先看孩子的报告(归约层):{report}——响应里的 output 是截断后的尾部;报告没收录的完整原文用 read 读记录上的 outputPath。";
|
|
4858
|
-
const LONG_OUTPUT_GUIDANCE_NO_REPORT = "产出超过截断阈值({bytes} 字节),且孩子未交报告(reportChannel=none,散文可信度存疑)。响应里的 output 是截断后的尾部;完整原文用 read 读记录上的 outputPath。";
|
|
4859
|
-
function renderLongOutputGuidance(options) {
|
|
4860
|
-
if (options.report && options.reportChannel && options.reportChannel !== "none") {
|
|
4861
|
-
const findings = (options.report.findings ?? []).slice(0, 5).map((f) => `- ${f.what}${f.evidence ? `(证据:${f.evidence})` : ""}`).join("\n");
|
|
4862
|
-
return LONG_OUTPUT_GUIDANCE_WITH_REPORT.replace("{bytes}", String(options.bytes)).replace("{report}", [`summary: ${options.report.summary}`, findings].filter(Boolean).join("\n"));
|
|
4863
|
-
}
|
|
4864
|
-
return LONG_OUTPUT_GUIDANCE_NO_REPORT.replace("{bytes}", String(options.bytes));
|
|
4865
|
-
}
|
|
4866
|
-
function renderTaskNotifications(batch) {
|
|
4867
|
-
return [
|
|
4868
|
-
"<bg-task-notifications>",
|
|
4869
|
-
...batch.map((n) => `- task ${n.taskId}${n.label ? ` (${n.label})` : ""}: ${n.status}${n.stopReason ? ` — ${n.stopReason}` : ""}`),
|
|
4870
|
-
"</bg-task-notifications>"
|
|
4871
|
-
].join("\n");
|
|
4872
|
-
}
|
|
4873
|
-
function estimateTokens(text) {
|
|
4874
|
-
return Math.ceil(text.length / 2);
|
|
4875
|
-
}
|
|
4876
|
-
const ROUTE_INDEX_LEAD = {
|
|
4877
|
-
full_ptc: "批量调工具默认先写程序(ptc_run_code);单次调用或要逐步判断、审批敏感的才直连。",
|
|
4878
|
-
codemode_ptc: "编排写 codemode 脚本;批量派活经脚本内的 pi_task_codemode;要查/停任务用任务面三工具。"
|
|
4879
|
-
};
|
|
4880
|
-
function renderRouteIndex(surface, detailBase, nudge = true) {
|
|
4881
|
-
const lines = ROUTES.map((route) => `- ${route.name} — ${route.when}。这面怎么走:${route.how[surface]}。细节:${detailBase ? route.detail.split("/").pop() : route.detail}`);
|
|
4882
|
-
let index = [
|
|
4883
|
-
`<ptc>`,
|
|
4884
|
-
...nudge ? [ROUTE_INDEX_LEAD[surface]] : [],
|
|
4885
|
-
...detailBase ? [`详情目录:${detailBase}`] : [],
|
|
4886
|
-
"路由表(一条一行,详情按需 read):",
|
|
4887
|
-
...lines,
|
|
4888
|
-
`</ptc>`
|
|
4889
|
-
].join("\n");
|
|
4890
|
-
const tokens = estimateTokens(index);
|
|
4891
|
-
if (tokens > 560) index += `\n> WARNING: index over token budget (${tokens} > 560); move detail into docs/routes/*.md`;
|
|
4892
|
-
return index;
|
|
4893
|
-
}
|
|
4894
5159
|
// ../ptc/src/render.ts
|
|
4895
5160
|
function renderPtcResult(surface) {
|
|
4896
5161
|
return (result, options, _theme, context) => {
|
|
@@ -5032,22 +5297,6 @@ function validateWorkflowArgs(args) {
|
|
|
5032
5297
|
throw new Error(`workflow args 必须是 JSON 值:${error instanceof Error ? error.message : String(error)}`);
|
|
5033
5298
|
}
|
|
5034
5299
|
}
|
|
5035
|
-
// ../task/src/list-tasks.ts
|
|
5036
|
-
function selectVisibleTasks(records, recentCompletedLimit = 10) {
|
|
5037
|
-
const inFlight = records.filter((record) => !isTerminal(record.status));
|
|
5038
|
-
const recentCompleted = records.filter((record) => isTerminal(record.status)).sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0)).slice(0, recentCompletedLimit);
|
|
5039
|
-
return [...inFlight, ...recentCompleted];
|
|
5040
|
-
}
|
|
5041
|
-
function taskFaceVisible(records) {
|
|
5042
|
-
return records.length > 0;
|
|
5043
|
-
}
|
|
5044
|
-
// ../task/src/lifecycle.ts
|
|
5045
|
-
function listTasks(registry, recentCompletedLimit) {
|
|
5046
|
-
return selectVisibleTasks(registry.list(), recentCompletedLimit);
|
|
5047
|
-
}
|
|
5048
|
-
function faceVisible(registry) {
|
|
5049
|
-
return taskFaceVisible(registry.list());
|
|
5050
|
-
}
|
|
5051
5300
|
// src/session.ts
|
|
5052
5301
|
function assemblePtcSession(options = {}) {
|
|
5053
5302
|
const registry = new TaskRegistry(options.onTransition);
|
|
@@ -5065,14 +5314,46 @@ function assemblePtcSession(options = {}) {
|
|
|
5065
5314
|
...opts,
|
|
5066
5315
|
task
|
|
5067
5316
|
}),
|
|
5068
|
-
|
|
5317
|
+
taskListSnapshot: (query) => taskListSnapshot(registry, query),
|
|
5069
5318
|
stopTask: (taskId, reason) => engine.stop(taskId, reason),
|
|
5070
5319
|
faceVisible: () => faceVisible(registry),
|
|
5071
5320
|
shutdown: () => engine.shutdown(),
|
|
5072
5321
|
readOutput: (taskId) => engine.readOutput(taskId),
|
|
5073
5322
|
outputSize: (taskId) => engine.outputSize(taskId),
|
|
5074
5323
|
readOutputTail: (taskId, maxBytes) => engine.readOutputTail(taskId, maxBytes),
|
|
5075
|
-
readArchivedRecord: (taskId) => engine.readArchivedRecord(taskId)
|
|
5324
|
+
readArchivedRecord: (taskId) => engine.readArchivedRecord(taskId),
|
|
5325
|
+
readArchivedRecords: () => engine.readArchivedRecords(),
|
|
5326
|
+
get sessionId() {
|
|
5327
|
+
return options.context?.sessionId;
|
|
5328
|
+
}
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
5331
|
+
/**
|
|
5332
|
+
* 引擎运行时上下文:孩子的模型默认继承父会话的当前模型。
|
|
5333
|
+
* 优先级(旧 ADR-0044):调用参数 → agent 定义 → 会话模型(缺省继承)。
|
|
5334
|
+
* sessionModel 是活 getter——派发那一刻才读,用户中途换模型也跟着换;
|
|
5335
|
+
* floor 0.86 也有 ctx.model(实测 0.86.0 的 ExtensionContext.model 同为活 getter,provider/id 同形)→ 无版本门;
|
|
5336
|
+
* 会话没有模型 → undefined → 不发 --model;扩展被卸载后 ctx 的 getter 会抛(pi 的 assertActive)→ 吞掉退回旧行为。
|
|
5337
|
+
*/
|
|
5338
|
+
function buildEngineContext(ctx, piVersion) {
|
|
5339
|
+
return {
|
|
5340
|
+
depth: 0,
|
|
5341
|
+
piVersion,
|
|
5342
|
+
get sessionModel() {
|
|
5343
|
+
try {
|
|
5344
|
+
const model = ctx.model;
|
|
5345
|
+
return model ? `${model.provider}/${model.id}` : void 0;
|
|
5346
|
+
} catch {
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
},
|
|
5350
|
+
get sessionId() {
|
|
5351
|
+
try {
|
|
5352
|
+
return ctx.sessionManager.getSessionId();
|
|
5353
|
+
} catch {
|
|
5354
|
+
return;
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5076
5357
|
};
|
|
5077
5358
|
}
|
|
5078
5359
|
// src/index.ts
|
|
@@ -5083,6 +5364,20 @@ const textResult = (value) => ({
|
|
|
5083
5364
|
}],
|
|
5084
5365
|
details: void 0
|
|
5085
5366
|
});
|
|
5367
|
+
function withDeliveryNote(value) {
|
|
5368
|
+
if (!value || typeof value !== "object") return value;
|
|
5369
|
+
const record = value;
|
|
5370
|
+
if (!("reportChannel" in record)) return value;
|
|
5371
|
+
const note = renderDeliveryCheck({
|
|
5372
|
+
delivery: record.delivery,
|
|
5373
|
+
findings: Array.isArray(record.report?.findings) ? record.report.findings.length : 0,
|
|
5374
|
+
channel: record.reportChannel
|
|
5375
|
+
});
|
|
5376
|
+
return note ? {
|
|
5377
|
+
...record,
|
|
5378
|
+
deliveryNote: note
|
|
5379
|
+
} : record;
|
|
5380
|
+
}
|
|
5086
5381
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
5087
5382
|
const publishedDetailDir = join(packageRoot, "docs", "routes");
|
|
5088
5383
|
const detailDir = existsSync(publishedDetailDir) ? publishedDetailDir : join(packageRoot, "..", "routes", "docs", "routes");
|
|
@@ -5162,10 +5457,7 @@ function piPtcEntry(pi) {
|
|
|
5162
5457
|
session = assemblePtcSession({
|
|
5163
5458
|
onTransition: () => applyTaskFaceVisibility(),
|
|
5164
5459
|
whyEnabled: () => whyEnabledOverride ?? whyEnabledPersisted,
|
|
5165
|
-
context:
|
|
5166
|
-
depth: 0,
|
|
5167
|
-
piVersion
|
|
5168
|
-
},
|
|
5460
|
+
context: buildEngineContext(ctx, piVersion),
|
|
5169
5461
|
engine: {
|
|
5170
5462
|
notifications,
|
|
5171
5463
|
resolveAgent: (name, scope) => findAgent(name, agentDirs, scope),
|
|
@@ -5241,6 +5533,24 @@ function piPtcEntry(pi) {
|
|
|
5241
5533
|
};
|
|
5242
5534
|
registerTaskFace(registerTracked, session, surface);
|
|
5243
5535
|
applyTaskFaceVisibility();
|
|
5536
|
+
let compactReminderPending = false;
|
|
5537
|
+
pi.on("session_compact", () => {
|
|
5538
|
+
compactReminderPending = true;
|
|
5539
|
+
});
|
|
5540
|
+
pi.on("context", (event) => {
|
|
5541
|
+
if (!compactReminderPending) return void 0;
|
|
5542
|
+
compactReminderPending = false;
|
|
5543
|
+
const reminder = compactedTaskReminder(session.registry);
|
|
5544
|
+
if (!reminder) return void 0;
|
|
5545
|
+
return { messages: [...event.messages, {
|
|
5546
|
+
role: "user",
|
|
5547
|
+
content: [{
|
|
5548
|
+
type: "text",
|
|
5549
|
+
text: reminder
|
|
5550
|
+
}],
|
|
5551
|
+
timestamp: Date.now()
|
|
5552
|
+
}] };
|
|
5553
|
+
});
|
|
5244
5554
|
let toolContext;
|
|
5245
5555
|
const getBindings = () => createBindingTable(pi, session, () => toolContext);
|
|
5246
5556
|
registerPtcTools(registerTracked, {
|
|
@@ -5393,7 +5703,12 @@ function createBindingTable(pi, session, getToolContext) {
|
|
|
5393
5703
|
return normalizeToolValue(outcome.result.structuredContent ?? outcome.result.content);
|
|
5394
5704
|
}]));
|
|
5395
5705
|
const taskBinding = createTaskBinding(session);
|
|
5396
|
-
table[TASK_BINDING_NAME] = (args) => taskBinding.execute(args);
|
|
5706
|
+
table[TASK_BINDING_NAME] = async (args) => withDeliveryNote(await taskBinding.execute(args));
|
|
5707
|
+
table[TASK_RECORDS_BINDING_NAME] = async (args) => {
|
|
5708
|
+
const records = session.registry.list();
|
|
5709
|
+
if (args?.scope !== "all") return records;
|
|
5710
|
+
return [...records, ...await session.readArchivedRecords()];
|
|
5711
|
+
};
|
|
5397
5712
|
return table;
|
|
5398
5713
|
}
|
|
5399
5714
|
function registerPtcTools(registerTool, options, surface) {
|
|
@@ -5406,9 +5721,23 @@ function registerTaskFace(registerTool, session, surface) {
|
|
|
5406
5721
|
name: "ptc_task_list",
|
|
5407
5722
|
label: "任务列表",
|
|
5408
5723
|
description: TOOLS.ptc_task_list.description(surface),
|
|
5409
|
-
parameters: _Object_({
|
|
5724
|
+
parameters: _Object_({
|
|
5725
|
+
scope: Optional(Union([Literal("recent"), Literal("all")], { description: "recent = 只热存(默认);all = 含压缩归档(更全但更慢)" })),
|
|
5726
|
+
limit: Optional(Number$1({ description: "取多少条(默认 50,上限 200)" })),
|
|
5727
|
+
status: Optional(Union(TASK_STATUSES.map((s) => Literal(s)), { description: "只看某个状态" })),
|
|
5728
|
+
session: Optional(String$1({ description: "按会话筛:current = 本会话,或给一个会话 id" }))
|
|
5729
|
+
}),
|
|
5410
5730
|
defaultActive: false,
|
|
5411
|
-
execute: async () =>
|
|
5731
|
+
execute: async (_id, args) => {
|
|
5732
|
+
const query = {
|
|
5733
|
+
scope: args.scope,
|
|
5734
|
+
limit: args.limit,
|
|
5735
|
+
status: args.status,
|
|
5736
|
+
session: args.session === "current" ? session.sessionId : args.session
|
|
5737
|
+
};
|
|
5738
|
+
const archived = query.scope === "all" ? await session.readArchivedRecords() : [];
|
|
5739
|
+
return textResult(taskListSnapshot(session.registry, query, archived));
|
|
5740
|
+
}
|
|
5412
5741
|
});
|
|
5413
5742
|
registerTool({
|
|
5414
5743
|
name: "ptc_task_output",
|
|
@@ -5419,25 +5748,10 @@ function registerTaskFace(registerTool, session, surface) {
|
|
|
5419
5748
|
execute: async (_id, args) => {
|
|
5420
5749
|
const record = session.registry.get(args.task_id) ?? await session.readArchivedRecord(args.task_id);
|
|
5421
5750
|
if (!record) return textResult({ error: `unknown task id ${args.task_id}` });
|
|
5422
|
-
|
|
5423
|
-
if (outputBytes === void 0) return textResult(record);
|
|
5424
|
-
if (outputBytes > 51200) {
|
|
5425
|
-
const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
|
|
5426
|
-
return textResult({
|
|
5427
|
-
...record,
|
|
5428
|
-
output: text,
|
|
5429
|
-
guidance: renderLongOutputGuidance({
|
|
5430
|
-
bytes: outputBytes,
|
|
5431
|
-
report: record.report,
|
|
5432
|
-
reportChannel: record.reportChannel ?? "none"
|
|
5433
|
-
})
|
|
5434
|
-
});
|
|
5435
|
-
}
|
|
5436
|
-
const { text } = applyAdr0015Truncation$1(await session.readOutputTail(args.task_id, 51200) ?? "");
|
|
5437
|
-
return textResult({
|
|
5751
|
+
return textResult(withDeliveryNote({
|
|
5438
5752
|
...record,
|
|
5439
|
-
|
|
5440
|
-
});
|
|
5753
|
+
...await readTaskOutput(session, record)
|
|
5754
|
+
}));
|
|
5441
5755
|
}
|
|
5442
5756
|
});
|
|
5443
5757
|
registerTool({
|
|
@@ -5468,14 +5782,17 @@ function registerTaskDoor(registerTool, session, surface) {
|
|
|
5468
5782
|
agentScope: args.agentScope,
|
|
5469
5783
|
model: args.model,
|
|
5470
5784
|
thinkingLevel: args.thinkingLevel,
|
|
5471
|
-
readOnly: args.readOnly
|
|
5785
|
+
readOnly: args.readOnly,
|
|
5786
|
+
timeoutMs: args.timeoutMs,
|
|
5787
|
+
projectContext: args.projectContext
|
|
5472
5788
|
});
|
|
5789
|
+
const shaped = withDeliveryNote(result);
|
|
5473
5790
|
return {
|
|
5474
|
-
...textResult(
|
|
5475
|
-
structuredContent:
|
|
5791
|
+
...textResult(shaped),
|
|
5792
|
+
structuredContent: shaped,
|
|
5476
5793
|
isError: result.status === "rejected"
|
|
5477
5794
|
};
|
|
5478
5795
|
}
|
|
5479
5796
|
});
|
|
5480
5797
|
}
|
|
5481
|
-
export { assemblePtcSession, piPtcEntry as default };
|
|
5798
|
+
export { assemblePtcSession, buildEngineContext, piPtcEntry as default, withDeliveryNote };
|