@piagent/platform 1.2.1 → 1.2.2
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/catalog/capabilities.json +4 -4
- package/docs/command-reference-vietnamese.md +9 -3
- package/docs/context-window-policy.md +1 -0
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +39 -10
- package/docs/publishing-for-teams.md +1 -0
- package/docs/quickstart-vietnamese.md +4 -2
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +3 -3
- package/docs/usage-observability.md +20 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/quickstart.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +2 -2
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/README.md +2 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +406 -51
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/prompts/be-to-fe.md +1 -0
- package/packages/piagent-core/prompts/piagent-commands.md +1 -0
- package/packages/piagent-core/prompts/platform-improve.md +1 -0
- package/packages/piagent-core/prompts/review.md +1 -0
- package/packages/piagent-core/prompts/scout.md +1 -0
- package/packages/piagent-core/prompts/task.md +1 -0
package/docs-site/team.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.2 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/workflows.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.2 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ Shared Pi package for reusable project workflows.
|
|
|
11
11
|
- `subagents/*.md`: piagent roles for `pi-subagents`.
|
|
12
12
|
- `policies/base-policy.json`: default runtime policy, including protected path and shell protected path defaults.
|
|
13
13
|
- input hook support for local screenshot/image paths pasted into chat; supported images are attached as `[image1]`, `[image2]`, ...
|
|
14
|
+
- compact tool-result rendering: oversized redacted output is previewed in Pi and captured under `.pi/piagent-state/tool-results/` for offline audit/reporting.
|
|
14
15
|
|
|
15
16
|
## Trusted run wrapper
|
|
16
17
|
|
|
@@ -84,6 +85,7 @@ Inside Pi, slash commands can switch the current session without writing the pro
|
|
|
84
85
|
- `/scout`: governed read-only scout/audit workflow.
|
|
85
86
|
- `/task`: governed implementation lifecycle.
|
|
86
87
|
- `/task-preflight`: check whether the active session should run, compact, or start fresh before large work.
|
|
88
|
+
- `/piagent-logs`: show compact-log policy and recent oversized tool-output captures.
|
|
87
89
|
- `/fresh-task`, `/fresh-scout`, `/fresh-be-to-fe`: start a fresh governed session and replay the compact workflow prompt.
|
|
88
90
|
- `/commit`: create a guarded local commit from reviewed files only; no push.
|
|
89
91
|
- `/pr`: prepare a pull request; push/PR creation still requires explicit operator confirmation.
|
|
@@ -128,6 +128,13 @@ const BOILERPLATE_COLLAPSE_CHARS = 300;
|
|
|
128
128
|
const MAX_INLINE_COLLAPSED_TASK_CHARS = 2200;
|
|
129
129
|
const MAX_CHAT_IMAGE_ATTACHMENTS = 4;
|
|
130
130
|
const MAX_CHAT_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
131
|
+
const TOOL_RESULT_COMPACT_CHAR_THRESHOLD = 12_000;
|
|
132
|
+
const TOOL_RESULT_COMPACT_LINE_THRESHOLD = 180;
|
|
133
|
+
const TOOL_RESULT_PREVIEW_HEAD_LINES = 24;
|
|
134
|
+
const TOOL_RESULT_PREVIEW_TAIL_LINES = 24;
|
|
135
|
+
const TOOL_RESULT_PREVIEW_INTERESTING_LINES = 24;
|
|
136
|
+
const TOOL_RESULT_PREVIEW_MAX_CHARS = 6_000;
|
|
137
|
+
const TOOL_RESULT_CAPTURE_MAX_CHARS = 500_000;
|
|
131
138
|
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp"] as const;
|
|
132
139
|
const ORCHESTRATION_MODES = ["solo-first", "bounded-subagents", "parallel-readonly"] as const;
|
|
133
140
|
const REVIEW_LENSES = ["correctness", "tests", "scope", "security", "docs", "release", "package"] as const;
|
|
@@ -1488,6 +1495,14 @@ function observedBashLedgerPath(cwd: string): string {
|
|
|
1488
1495
|
return path.join(stateRoot(cwd), "observed-bash.jsonl");
|
|
1489
1496
|
}
|
|
1490
1497
|
|
|
1498
|
+
function toolResultCaptureRoot(cwd: string): string {
|
|
1499
|
+
return path.join(stateRoot(cwd), "tool-results");
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function toolResultCaptureIndexPath(cwd: string): string {
|
|
1503
|
+
return path.join(toolResultCaptureRoot(cwd), "index.jsonl");
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1491
1506
|
function safeTaskId(taskId: string): string {
|
|
1492
1507
|
return taskId.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "task";
|
|
1493
1508
|
}
|
|
@@ -3382,6 +3397,308 @@ function hasOperatorSessionName(name: string): boolean {
|
|
|
3382
3397
|
return Boolean(normalized && normalized !== "session");
|
|
3383
3398
|
}
|
|
3384
3399
|
|
|
3400
|
+
function cleanSessionNameInput(input: string): string {
|
|
3401
|
+
let name = input.trim();
|
|
3402
|
+
if ((name.startsWith("\"") && name.endsWith("\"")) || (name.startsWith("'") && name.endsWith("'"))) {
|
|
3403
|
+
name = name.slice(1, -1).trim();
|
|
3404
|
+
}
|
|
3405
|
+
return name;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
type ToolResultCaptureSummary = {
|
|
3409
|
+
path?: string;
|
|
3410
|
+
error?: string;
|
|
3411
|
+
source: string;
|
|
3412
|
+
toolName: string;
|
|
3413
|
+
originalChars: number;
|
|
3414
|
+
originalLines: number;
|
|
3415
|
+
previewChars?: number;
|
|
3416
|
+
storedChars?: number;
|
|
3417
|
+
storedTruncated?: boolean;
|
|
3418
|
+
sha256: string;
|
|
3419
|
+
};
|
|
3420
|
+
|
|
3421
|
+
function normalizeToolResultText(text: string): string {
|
|
3422
|
+
return text.replace(/\r\n/g, "\n");
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
function toolResultLineCount(text: string): number {
|
|
3426
|
+
if (!text) return 0;
|
|
3427
|
+
return normalizeToolResultText(text).split("\n").length;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
function shouldCompactToolResultText(text: string): boolean {
|
|
3431
|
+
return text.length > TOOL_RESULT_COMPACT_CHAR_THRESHOLD
|
|
3432
|
+
|| toolResultLineCount(text) > TOOL_RESULT_COMPACT_LINE_THRESHOLD;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
function clipPreviewLine(line: string, maxChars = 260): string {
|
|
3436
|
+
if (line.length <= maxChars) return line;
|
|
3437
|
+
return `${line.slice(0, maxChars).trimEnd()} ... [line clipped]`;
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
function boundedPreview(text: string): string {
|
|
3441
|
+
if (text.length <= TOOL_RESULT_PREVIEW_MAX_CHARS) return text;
|
|
3442
|
+
const marker = "\n[Piagent preview shortened further to stay light.]\n";
|
|
3443
|
+
const edge = Math.floor((TOOL_RESULT_PREVIEW_MAX_CHARS - marker.length) / 2);
|
|
3444
|
+
return `${text.slice(0, edge).trimEnd()}${marker}${text.slice(-edge).trimStart()}`;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
function interestingToolResultLines(lines: string[]): string[] {
|
|
3448
|
+
const interesting = /\b(?:error|errors|err!|failed?|failure|warning|warn|exception|traceback|assert(?:ion)?|panic|fatal|timeout|timed out|denied|not found|cannot|eacces|enoent|ts\d{3,5}|err_[a-z0-9_]+)\b/i;
|
|
3449
|
+
const seen = new Set<string>();
|
|
3450
|
+
const result: string[] = [];
|
|
3451
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3452
|
+
const line = lines[index];
|
|
3453
|
+
if (!interesting.test(line)) continue;
|
|
3454
|
+
const rendered = `${index + 1}: ${clipPreviewLine(line)}`;
|
|
3455
|
+
if (seen.has(rendered)) continue;
|
|
3456
|
+
seen.add(rendered);
|
|
3457
|
+
result.push(rendered);
|
|
3458
|
+
if (result.length >= TOOL_RESULT_PREVIEW_INTERESTING_LINES) break;
|
|
3459
|
+
}
|
|
3460
|
+
return result;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
function storedToolResultText(text: string): { text: string; truncated: boolean } {
|
|
3464
|
+
if (text.length <= TOOL_RESULT_CAPTURE_MAX_CHARS) return { text, truncated: false };
|
|
3465
|
+
const marker = `\n\n[Piagent capture truncated: omitted ${formatCount(text.length - TOOL_RESULT_CAPTURE_MAX_CHARS)} chars from the middle.]\n\n`;
|
|
3466
|
+
const edge = Math.floor((TOOL_RESULT_CAPTURE_MAX_CHARS - marker.length) / 2);
|
|
3467
|
+
return {
|
|
3468
|
+
text: `${text.slice(0, edge).trimEnd()}${marker}${text.slice(-edge).trimStart()}`,
|
|
3469
|
+
truncated: true
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
function compactToolResultPreview(toolName: string, source: string, text: string, capture: ToolResultCaptureSummary): string {
|
|
3474
|
+
const normalized = normalizeToolResultText(text);
|
|
3475
|
+
const lines = normalized.split("\n");
|
|
3476
|
+
const head = lines.slice(0, TOOL_RESULT_PREVIEW_HEAD_LINES).map((line) => clipPreviewLine(line));
|
|
3477
|
+
const tailStart = Math.max(TOOL_RESULT_PREVIEW_HEAD_LINES, lines.length - TOOL_RESULT_PREVIEW_TAIL_LINES);
|
|
3478
|
+
const tail = lines.slice(tailStart).map((line) => clipPreviewLine(line));
|
|
3479
|
+
const omitted = Math.max(0, lines.length - head.length - tail.length);
|
|
3480
|
+
const interesting = interestingToolResultLines(lines);
|
|
3481
|
+
const captureLine = capture.path
|
|
3482
|
+
? `capture: ${capture.path}${capture.storedTruncated ? " (stored head/tail sample; original too large)" : ""}`
|
|
3483
|
+
: `capture: unavailable (${capture.error ?? "write failed"})`;
|
|
3484
|
+
const sections = [
|
|
3485
|
+
`[Piagent compacted large ${toolName} output from ${source}: ${formatCount(capture.originalChars)} chars / ${formatCount(capture.originalLines)} lines.]`,
|
|
3486
|
+
`[${captureLine}]`,
|
|
3487
|
+
"[Preview keeps head, notable lines, and tail.]",
|
|
3488
|
+
"",
|
|
3489
|
+
"head:",
|
|
3490
|
+
...head
|
|
3491
|
+
];
|
|
3492
|
+
if (interesting.length > 0) {
|
|
3493
|
+
sections.push("", "notable:", ...interesting);
|
|
3494
|
+
}
|
|
3495
|
+
if (omitted > 0) {
|
|
3496
|
+
sections.push("", `... ${formatCount(omitted)} middle line${omitted === 1 ? "" : "s"} omitted ...`);
|
|
3497
|
+
}
|
|
3498
|
+
sections.push("", "tail:", ...tail);
|
|
3499
|
+
return boundedPreview(sections.join("\n"));
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
function maybeNumber(value: unknown): number | undefined {
|
|
3503
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3504
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return Number.parseInt(value, 10);
|
|
3505
|
+
return undefined;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
function currentSessionField(ctx: ExtensionContext, field: "getSessionFile" | "getSessionId" | "getSessionName"): string | undefined {
|
|
3509
|
+
try {
|
|
3510
|
+
const value = ctx.sessionManager[field]?.();
|
|
3511
|
+
return value === undefined || value === null ? undefined : redactText(String(value));
|
|
3512
|
+
} catch {
|
|
3513
|
+
return undefined;
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
function writeToolResultCapture(
|
|
3518
|
+
cwd: string,
|
|
3519
|
+
event: any,
|
|
3520
|
+
ctx: ExtensionContext,
|
|
3521
|
+
source: string,
|
|
3522
|
+
text: string,
|
|
3523
|
+
cache: Map<string, ToolResultCaptureSummary>
|
|
3524
|
+
): ToolResultCaptureSummary {
|
|
3525
|
+
const normalized = normalizeToolResultText(text);
|
|
3526
|
+
const sha256 = crypto.createHash("sha256").update(normalized).digest("hex");
|
|
3527
|
+
const originalLines = toolResultLineCount(normalized);
|
|
3528
|
+
const cached = cache.get(sha256);
|
|
3529
|
+
if (cached?.path) return { ...cached, source };
|
|
3530
|
+
|
|
3531
|
+
const toolName = String(event?.toolName ?? "tool");
|
|
3532
|
+
const recordedAt = nowIso();
|
|
3533
|
+
const date = recordedAt.slice(0, 10);
|
|
3534
|
+
const filename = `${recordedAt.replace(/[:.]/g, "-")}-${slugify(toolName)}-${sha256.slice(0, 12)}.log`;
|
|
3535
|
+
const relativePath = [".pi", "piagent-state", "tool-results", date, filename].join("/");
|
|
3536
|
+
const absolutePath = path.join(cwd, ".pi", "piagent-state", "tool-results", date, filename);
|
|
3537
|
+
const capture = storedToolResultText(normalized);
|
|
3538
|
+
const details = event?.details && typeof event.details === "object" ? event.details : {};
|
|
3539
|
+
const exitCode = maybeNumber(details.exitCode ?? details.status ?? event?.exitCode);
|
|
3540
|
+
const metadata = {
|
|
3541
|
+
schemaVersion: 1,
|
|
3542
|
+
recordedAt,
|
|
3543
|
+
cwd: redactText(cwd),
|
|
3544
|
+
sessionId: currentSessionField(ctx, "getSessionId"),
|
|
3545
|
+
sessionName: currentSessionField(ctx, "getSessionName"),
|
|
3546
|
+
sessionFile: currentSessionField(ctx, "getSessionFile"),
|
|
3547
|
+
toolName,
|
|
3548
|
+
source,
|
|
3549
|
+
isError: event?.isError === true,
|
|
3550
|
+
exitCode,
|
|
3551
|
+
input: redactForStorage(event?.input),
|
|
3552
|
+
path: relativePath,
|
|
3553
|
+
originalChars: normalized.length,
|
|
3554
|
+
originalLines,
|
|
3555
|
+
storedChars: capture.text.length,
|
|
3556
|
+
storedTruncated: capture.truncated,
|
|
3557
|
+
sha256
|
|
3558
|
+
};
|
|
3559
|
+
|
|
3560
|
+
try {
|
|
3561
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
3562
|
+
fs.writeFileSync(absolutePath, [
|
|
3563
|
+
"# Piagent compacted tool result capture",
|
|
3564
|
+
JSON.stringify(metadata),
|
|
3565
|
+
"---",
|
|
3566
|
+
capture.text
|
|
3567
|
+
].join("\n"));
|
|
3568
|
+
fs.appendFileSync(toolResultCaptureIndexPath(cwd), `${JSON.stringify(metadata)}\n`);
|
|
3569
|
+
const summary = {
|
|
3570
|
+
path: relativePath,
|
|
3571
|
+
source,
|
|
3572
|
+
toolName,
|
|
3573
|
+
originalChars: normalized.length,
|
|
3574
|
+
originalLines,
|
|
3575
|
+
storedChars: capture.text.length,
|
|
3576
|
+
storedTruncated: capture.truncated,
|
|
3577
|
+
sha256
|
|
3578
|
+
};
|
|
3579
|
+
cache.set(sha256, summary);
|
|
3580
|
+
return summary;
|
|
3581
|
+
} catch (error) {
|
|
3582
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3583
|
+
const summary = {
|
|
3584
|
+
error: message,
|
|
3585
|
+
source,
|
|
3586
|
+
toolName,
|
|
3587
|
+
originalChars: normalized.length,
|
|
3588
|
+
originalLines,
|
|
3589
|
+
sha256
|
|
3590
|
+
};
|
|
3591
|
+
cache.set(sha256, summary);
|
|
3592
|
+
return summary;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
function compactToolResultTextContent(
|
|
3597
|
+
cwd: string,
|
|
3598
|
+
event: any,
|
|
3599
|
+
ctx: ExtensionContext,
|
|
3600
|
+
content: unknown,
|
|
3601
|
+
cache: Map<string, ToolResultCaptureSummary>
|
|
3602
|
+
): { content: unknown; captures: ToolResultCaptureSummary[] } {
|
|
3603
|
+
if (!Array.isArray(content)) return { content, captures: [] };
|
|
3604
|
+
const captures: ToolResultCaptureSummary[] = [];
|
|
3605
|
+
const compacted = content.map((block, index) => {
|
|
3606
|
+
if (!block || typeof block !== "object") return block;
|
|
3607
|
+
const typed = block as { type?: unknown; text?: unknown };
|
|
3608
|
+
if (typed.type !== "text" || typeof typed.text !== "string" || !shouldCompactToolResultText(typed.text)) return block;
|
|
3609
|
+
const source = `content[${index}].text`;
|
|
3610
|
+
const capture = writeToolResultCapture(cwd, event, ctx, source, typed.text, cache);
|
|
3611
|
+
captures.push(capture);
|
|
3612
|
+
return {
|
|
3613
|
+
...block,
|
|
3614
|
+
text: compactToolResultPreview(String(event?.toolName ?? "tool"), source, typed.text, capture)
|
|
3615
|
+
};
|
|
3616
|
+
});
|
|
3617
|
+
return { content: compacted, captures };
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
function compactToolResultDetails(
|
|
3621
|
+
cwd: string,
|
|
3622
|
+
event: any,
|
|
3623
|
+
ctx: ExtensionContext,
|
|
3624
|
+
value: unknown,
|
|
3625
|
+
cache: Map<string, ToolResultCaptureSummary>,
|
|
3626
|
+
captures: ToolResultCaptureSummary[],
|
|
3627
|
+
source = "details",
|
|
3628
|
+
depth = 0
|
|
3629
|
+
): unknown {
|
|
3630
|
+
if (typeof value === "string") {
|
|
3631
|
+
if (!shouldCompactToolResultText(value)) return value;
|
|
3632
|
+
const capture = writeToolResultCapture(cwd, event, ctx, source, value, cache);
|
|
3633
|
+
captures.push(capture);
|
|
3634
|
+
return compactToolResultPreview(String(event?.toolName ?? "tool"), source, value, capture);
|
|
3635
|
+
}
|
|
3636
|
+
if (Array.isArray(value)) {
|
|
3637
|
+
if (depth >= 6) return value;
|
|
3638
|
+
return value.map((item, index) => compactToolResultDetails(cwd, event, ctx, item, cache, captures, `${source}[${index}]`, depth + 1));
|
|
3639
|
+
}
|
|
3640
|
+
if (isPlainRecord(value)) {
|
|
3641
|
+
if (depth >= 6) return value;
|
|
3642
|
+
return Object.fromEntries(
|
|
3643
|
+
Object.entries(value).map(([key, item]) => [
|
|
3644
|
+
key,
|
|
3645
|
+
compactToolResultDetails(cwd, event, ctx, item, cache, captures, `${source}.${key}`, depth + 1)
|
|
3646
|
+
])
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
return value;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
function attachToolResultCompactionDetails(details: unknown, captures: ToolResultCaptureSummary[]): unknown {
|
|
3653
|
+
if (captures.length === 0) return details;
|
|
3654
|
+
const compacted = captures.map((capture) => ({
|
|
3655
|
+
path: capture.path,
|
|
3656
|
+
error: capture.error,
|
|
3657
|
+
source: capture.source,
|
|
3658
|
+
toolName: capture.toolName,
|
|
3659
|
+
originalChars: capture.originalChars,
|
|
3660
|
+
originalLines: capture.originalLines,
|
|
3661
|
+
storedChars: capture.storedChars,
|
|
3662
|
+
storedTruncated: capture.storedTruncated,
|
|
3663
|
+
sha256: capture.sha256
|
|
3664
|
+
}));
|
|
3665
|
+
if (isPlainRecord(details)) return { ...details, piagentCompactedToolResults: compacted };
|
|
3666
|
+
if (details === undefined) return { piagentCompactedToolResults: compacted };
|
|
3667
|
+
return { value: details, piagentCompactedToolResults: compacted };
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
function readRecentToolResultCaptures(cwd: string, limit = 5): Record<string, unknown>[] {
|
|
3671
|
+
const indexPath = toolResultCaptureIndexPath(cwd);
|
|
3672
|
+
if (!fs.existsSync(indexPath)) return [];
|
|
3673
|
+
const lines = fs.readFileSync(indexPath, "utf8").trim().split(/\n/).filter(Boolean);
|
|
3674
|
+
const captures: Record<string, unknown>[] = [];
|
|
3675
|
+
for (const line of lines.slice(-Math.max(limit * 3, limit))) {
|
|
3676
|
+
try {
|
|
3677
|
+
const parsed = JSON.parse(line);
|
|
3678
|
+
if (isPlainRecord(parsed)) captures.push(redactForStorage(parsed) as Record<string, unknown>);
|
|
3679
|
+
} catch {
|
|
3680
|
+
// A partial JSONL line should not make a status command noisy or unusable.
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
return captures.slice(-limit);
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
function formatToolResultCaptureStatus(cwd: string, captures: Record<string, unknown>[]): string {
|
|
3687
|
+
const root = normalizeRelative(cwd, toolResultCaptureRoot(cwd)) ?? ".pi/piagent-state/tool-results";
|
|
3688
|
+
const lines = [
|
|
3689
|
+
`logPolicy: compact above ${formatCount(TOOL_RESULT_COMPACT_CHAR_THRESHOLD)} chars or ${formatCount(TOOL_RESULT_COMPACT_LINE_THRESHOLD)} lines`,
|
|
3690
|
+
`captureRoot: ${root}`,
|
|
3691
|
+
`recent: ${captures.length || "none"}`
|
|
3692
|
+
];
|
|
3693
|
+
for (const capture of captures) {
|
|
3694
|
+
const originalLines = typeof capture.originalLines === "number" ? formatCount(capture.originalLines) : "unknown";
|
|
3695
|
+
const originalChars = typeof capture.originalChars === "number" ? formatCount(capture.originalChars) : "unknown";
|
|
3696
|
+
const exit = capture.exitCode === undefined ? "" : ` exit=${capture.exitCode}`;
|
|
3697
|
+
lines.push(`- ${capture.recordedAt ?? "unknown"} ${capture.toolName ?? "tool"}${exit}: ${originalLines} lines / ${originalChars} chars -> ${capture.path ?? "no capture path"}`);
|
|
3698
|
+
}
|
|
3699
|
+
return lines.join("\n");
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3385
3702
|
function buildUsageSnapshot(ctx: ExtensionContext, thinkingLevel?: string): UsageSnapshot {
|
|
3386
3703
|
const contextUsage = ctx.getContextUsage();
|
|
3387
3704
|
const contextWithThinking = ctx as ExtensionContext & { getThinkingLevel?: () => string };
|
|
@@ -3420,30 +3737,13 @@ function formatUsageSnapshot(snapshot: UsageSnapshot): string {
|
|
|
3420
3737
|
? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} tokens (${formatPercent(snapshot.contextUsage.percent)})`
|
|
3421
3738
|
: "unavailable";
|
|
3422
3739
|
return [
|
|
3423
|
-
|
|
3424
|
-
""
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
`- Thinking: ${snapshot.thinkingLevel}`,
|
|
3431
|
-
`- Entries: ${formatCount(snapshot.entries.branch)} on active branch / ${formatCount(snapshot.entries.total)} total`,
|
|
3432
|
-
`- Live context: ${context}`,
|
|
3433
|
-
"",
|
|
3434
|
-
"Exact billed input/output/cache/cost totals are exposed by Pi via `/session` or RPC `get_session_stats`, not directly inside this command context.",
|
|
3435
|
-
"",
|
|
3436
|
-
"From another terminal:",
|
|
3437
|
-
"",
|
|
3438
|
-
"```bash",
|
|
3439
|
-
"piagent-usage /path/to/project",
|
|
3440
|
-
"```",
|
|
3441
|
-
"",
|
|
3442
|
-
"Historical totals / weekly report:",
|
|
3443
|
-
"",
|
|
3444
|
-
"```bash",
|
|
3445
|
-
"piagent-usage --history /path/to/project --days 7",
|
|
3446
|
-
"```"
|
|
3740
|
+
`usage: ${context}`,
|
|
3741
|
+
`session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
|
|
3742
|
+
`model: ${snapshot.model}; thinking: ${snapshot.thinkingLevel}`,
|
|
3743
|
+
`entries: ${formatCount(snapshot.entries.branch)} active / ${formatCount(snapshot.entries.total)} total`,
|
|
3744
|
+
`file: ${snapshot.sessionFile ?? "not persisted"}`,
|
|
3745
|
+
"exact: /session | piagent-usage /path/to/project",
|
|
3746
|
+
"history: piagent-usage --history /path/to/project --days 7"
|
|
3447
3747
|
].join("\n");
|
|
3448
3748
|
}
|
|
3449
3749
|
|
|
@@ -3510,30 +3810,14 @@ function formatContextPreflight(preflight: ContextPreflight, snapshot: UsageSnap
|
|
|
3510
3810
|
? `${formatCount(preflight.projectedContext.tokens)} (${formatPercent(preflight.projectedContext.percent)})`
|
|
3511
3811
|
: "unavailable";
|
|
3512
3812
|
return [
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
`- Incoming input estimate: ${formatCount(preflight.inputTokenEstimate)} tokens from ${formatCount(preflight.inputChars)} chars`,
|
|
3522
|
-
`- Projected context: ${projected}`,
|
|
3523
|
-
`- Recommendation: ${preflight.recommendation}`,
|
|
3524
|
-
`- Reason: ${preflight.reason}`,
|
|
3525
|
-
"",
|
|
3526
|
-
"Commands:",
|
|
3527
|
-
"",
|
|
3528
|
-
"```text",
|
|
3529
|
-
...preflight.commands,
|
|
3530
|
-
"```",
|
|
3531
|
-
"",
|
|
3532
|
-
"Notes:",
|
|
3533
|
-
"",
|
|
3534
|
-
"- Do not paste the full mandatory flow into every task. Platform prompts and piagent tools already carry it.",
|
|
3535
|
-
"- Use `/scout` for read-only risk mapping. Use `/fresh-scout` when the current session is already heavy.",
|
|
3536
|
-
"- If exact billed token/cost totals are needed, run `/session` in Pi or `piagent-usage <project-path>` from another terminal."
|
|
3813
|
+
`preflight: ${preflight.recommendation}`,
|
|
3814
|
+
`workflow: ${preflight.workflow}`,
|
|
3815
|
+
`session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
|
|
3816
|
+
`model: ${snapshot.model}; thinking: ${snapshot.thinkingLevel}`,
|
|
3817
|
+
`context: ${live}; projected: ${projected}`,
|
|
3818
|
+
`input: ~${formatCount(preflight.inputTokenEstimate)} tokens from ${formatCount(preflight.inputChars)} chars`,
|
|
3819
|
+
`reason: ${preflight.reason}`,
|
|
3820
|
+
`next: ${preflight.commands.join(" | ")}`
|
|
3537
3821
|
].join("\n");
|
|
3538
3822
|
}
|
|
3539
3823
|
|
|
@@ -3601,7 +3885,7 @@ function isPiagentWorkflowInput(text: string): boolean {
|
|
|
3601
3885
|
}
|
|
3602
3886
|
|
|
3603
3887
|
function isFreshOrUtilityInput(text: string): boolean {
|
|
3604
|
-
return /^\/(?:fresh-task|fresh-scout|fresh-be-to-fe|task-preflight|piagent-usage|session|compact)\b/i.test(text.trim());
|
|
3888
|
+
return /^\/(?:fresh-task|fresh-scout|fresh-be-to-fe|task-preflight|piagent-usage|piagent-logs|setname|session|compact)\b/i.test(text.trim());
|
|
3605
3889
|
}
|
|
3606
3890
|
|
|
3607
3891
|
function taskInboxDir(cwd: string): string {
|
|
@@ -4073,6 +4357,24 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4073
4357
|
resultChanged = true;
|
|
4074
4358
|
}
|
|
4075
4359
|
|
|
4360
|
+
const captureCache = new Map<string, ToolResultCaptureSummary>();
|
|
4361
|
+
const compactedContent = compactToolResultTextContent(ctx.cwd, event, ctx, resultContent, captureCache);
|
|
4362
|
+
const compactionCaptures = [...compactedContent.captures];
|
|
4363
|
+
if (compactedContent.captures.length > 0) {
|
|
4364
|
+
resultContent = compactedContent.content;
|
|
4365
|
+
resultChanged = true;
|
|
4366
|
+
}
|
|
4367
|
+
if (resultDetails !== undefined) {
|
|
4368
|
+
const compactedDetails = compactToolResultDetails(ctx.cwd, event, ctx, resultDetails, captureCache, compactionCaptures);
|
|
4369
|
+
if (compactionCaptures.length > compactedContent.captures.length) {
|
|
4370
|
+
resultDetails = compactedDetails;
|
|
4371
|
+
resultChanged = true;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
if (compactionCaptures.length > 0) {
|
|
4375
|
+
resultDetails = attachToolResultCompactionDetails(resultDetails, compactionCaptures);
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4076
4378
|
if (resultChanged) {
|
|
4077
4379
|
return resultDetails === undefined
|
|
4078
4380
|
? { content: resultContent }
|
|
@@ -4377,8 +4679,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4377
4679
|
}
|
|
4378
4680
|
});
|
|
4379
4681
|
|
|
4380
|
-
|
|
4381
|
-
|
|
4682
|
+
pi.registerTool({
|
|
4683
|
+
name: "piagent_exec_policy_check",
|
|
4382
4684
|
label: "Piagent Exec Policy Check",
|
|
4383
4685
|
description: "Evaluate a shell command against piagent exec policy before running it.",
|
|
4384
4686
|
promptSnippet: "Use this before high-impact, complex, generated, or unfamiliar shell commands.",
|
|
@@ -6261,6 +6563,59 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6261
6563
|
}
|
|
6262
6564
|
});
|
|
6263
6565
|
|
|
6566
|
+
pi.registerCommand("piagent-logs", {
|
|
6567
|
+
description: "Show compact log policy and recent captured large tool outputs",
|
|
6568
|
+
handler: async (_args, ctx) => {
|
|
6569
|
+
const captures = readRecentToolResultCaptures(ctx.cwd, 5);
|
|
6570
|
+
ctx.ui.notify(`Piagent logs: ${captures.length ? `${captures.length} recent capture(s)` : "no compacted captures yet"}`, "info");
|
|
6571
|
+
pi.sendMessage(
|
|
6572
|
+
{
|
|
6573
|
+
customType: "piagent-log-captures",
|
|
6574
|
+
content: formatToolResultCaptureStatus(ctx.cwd, captures),
|
|
6575
|
+
display: true,
|
|
6576
|
+
details: {
|
|
6577
|
+
policy: {
|
|
6578
|
+
compactAboveChars: TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
|
|
6579
|
+
compactAboveLines: TOOL_RESULT_COMPACT_LINE_THRESHOLD,
|
|
6580
|
+
previewMaxChars: TOOL_RESULT_PREVIEW_MAX_CHARS,
|
|
6581
|
+
captureMaxChars: TOOL_RESULT_CAPTURE_MAX_CHARS
|
|
6582
|
+
},
|
|
6583
|
+
captures
|
|
6584
|
+
}
|
|
6585
|
+
},
|
|
6586
|
+
{ triggerTurn: false }
|
|
6587
|
+
);
|
|
6588
|
+
}
|
|
6589
|
+
});
|
|
6590
|
+
|
|
6591
|
+
pi.registerCommand("setname", {
|
|
6592
|
+
description: "Set the current Pi session name for reports and resume lists",
|
|
6593
|
+
handler: async (args, ctx) => {
|
|
6594
|
+
const name = cleanSessionNameInput(String(args ?? ""));
|
|
6595
|
+
if (!name) {
|
|
6596
|
+
ctx.ui.notify("Usage: /setname <task/session name>", "warning");
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6599
|
+
const previousName = currentSessionName(ctx);
|
|
6600
|
+
pi.setSessionName(name);
|
|
6601
|
+
appendSessionTrace(pi, {
|
|
6602
|
+
event: "session_name_set",
|
|
6603
|
+
previousName: previousName || undefined,
|
|
6604
|
+
sessionName: name
|
|
6605
|
+
});
|
|
6606
|
+
ctx.ui.notify(`Session name set: ${name}`, "info");
|
|
6607
|
+
pi.sendMessage(
|
|
6608
|
+
{
|
|
6609
|
+
customType: "piagent-session-name-set",
|
|
6610
|
+
content: `sessionName: ${name}`,
|
|
6611
|
+
display: true,
|
|
6612
|
+
details: { sessionName: name, previousName: previousName || undefined }
|
|
6613
|
+
},
|
|
6614
|
+
{ triggerTurn: false }
|
|
6615
|
+
);
|
|
6616
|
+
}
|
|
6617
|
+
});
|
|
6618
|
+
|
|
6264
6619
|
pi.registerCommand("task-preflight", {
|
|
6265
6620
|
description: "Check context health before a large task; optionally compact",
|
|
6266
6621
|
handler: async (args, ctx) => {
|
|
@@ -63,6 +63,7 @@ Mandatory flow:
|
|
|
63
63
|
16. Run the exact FE verify command from the profile/task through Pi bash and record the observed result with `piagent_verify_record`. Ad-hoc commands are advisory only and will not satisfy the passing gate.
|
|
64
64
|
17. Record handoff with `piagent_trace_record`.
|
|
65
65
|
18. Call `piagent_task_gate_check`; if it fails, report blocked/partial instead of done.
|
|
66
|
+
19. Do not paste full test/build/tool logs into chat or final output. Summarize the signal, quote only relevant failing lines, and use `/piagent-logs` or a narrower command when oversized output has been compacted.
|
|
66
67
|
|
|
67
68
|
For generic projects, use profile `be-readonly-fe` when the repo policy is “BE scout only, FE write allowed”.
|
|
68
69
|
|
|
@@ -34,6 +34,7 @@ Mandatory flow:
|
|
|
34
34
|
9. Explain context overflow prevention when relevant:
|
|
35
35
|
- `/task-preflight` checks whether to proceed, compact, or fresh-session.
|
|
36
36
|
- `/fresh-task`, `/fresh-scout`, and `/fresh-be-to-fe` open a new governed session and replay the compact workflow prompt.
|
|
37
|
+
- `/piagent-logs` shows compact-log policy and recent oversized tool-output captures without a model follow-up.
|
|
37
38
|
- `/scout` is the read-only audit/scout workflow.
|
|
38
39
|
- `/context-index` shows the compact advisory project context graph without a model follow-up.
|
|
39
40
|
- `/context-index search <keyword>` searches profile/project/tech/task pointers before broad re-scouting.
|
|
@@ -42,6 +42,7 @@ Mandatory flow:
|
|
|
42
42
|
12. Implement only the bounded target behavior.
|
|
43
43
|
13. If runtime behavior changes, update README/docs and add/adjust a decision note when appropriate.
|
|
44
44
|
14. For source-changing tasks, run the exact verify command from `task.verifyCommands` through Pi bash, record observed verify evidence, trace, then call `piagent_task_gate_check` before final.
|
|
45
|
+
15. Do not paste full test/build/tool logs into chat or final output. Summarize the signal, quote only relevant failing lines, and use `/piagent-logs` or a narrower command when oversized output has been compacted.
|
|
45
46
|
|
|
46
47
|
Default verification:
|
|
47
48
|
|
|
@@ -26,6 +26,7 @@ Rules:
|
|
|
26
26
|
- security/high-risk only when relevant.
|
|
27
27
|
If the user asks for a loop, use `/review-loop` semantics or equivalent parent-controlled max-round loop. Continue single-agent for tiny diffs or unavailable subagent tooling.
|
|
28
28
|
9. Report findings by severity.
|
|
29
|
+
10. Do not paste full diff/test/tool logs into chat or final output. Summarize the signal, quote only relevant lines, and use `/piagent-logs` or narrower commands when oversized output has been compacted.
|
|
29
30
|
|
|
30
31
|
Output:
|
|
31
32
|
|
|
@@ -34,6 +34,7 @@ Preflight first:
|
|
|
34
34
|
12. Run the exact verify command from the task contract and record with `piagent_verify_record`.
|
|
35
35
|
13. Record handoff with `piagent_trace_record`.
|
|
36
36
|
14. Call `piagent_task_gate_check`. If the gate fails, report partial/blocked.
|
|
37
|
+
15. Do not paste full tool/test logs into chat or final output. Summarize the signal, quote only relevant lines, and use `/piagent-logs` or a narrower command when oversized output has been compacted.
|
|
37
38
|
|
|
38
39
|
Output:
|
|
39
40
|
|
|
@@ -52,6 +52,7 @@ Mandatory flow:
|
|
|
52
52
|
16. Call `piagent_task_gate_check`. If gate fails, final outcome is blocked/partial, not done.
|
|
53
53
|
17. If the user asks about token/context/cost usage, call `piagent_usage_snapshot`; for exact token/cost totals, tell the user to run `/session` or `piagent-usage <project-path>`.
|
|
54
54
|
18. If verify cannot run, stop and report the exact blocker. Do not call it done.
|
|
55
|
+
19. Do not paste full test/build/tool logs into chat or final output. Summarize the signal, quote only the relevant failing lines, and use `/piagent-logs` or a narrower command when oversized output has been compacted.
|
|
55
56
|
|
|
56
57
|
Do not ask the user to paste this mandatory flow. The platform prompt already contains it. If the user pasted the full flow, treat it as boilerplate and extract only the task request.
|
|
57
58
|
|