@piagent/platform 1.2.1 → 1.2.3
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 +34 -0
- package/README.md +34 -27
- package/catalog/capabilities.json +8 -8
- package/docs/auto-delegation-policy.md +4 -4
- package/docs/command-reference-vietnamese.md +121 -81
- package/docs/context-window-policy.md +7 -6
- package/docs/distribution-standard.md +27 -10
- package/docs/herdr-workflow.md +3 -2
- package/docs/memory-policy.md +6 -3
- package/docs/operator-manual-vietnamese.md +107 -69
- package/docs/package-architecture-notes.md +6 -6
- package/docs/project-adapters.md +4 -4
- package/docs/project-onboarding.md +12 -11
- package/docs/publishing-for-teams.md +2 -1
- package/docs/quickstart-vietnamese.md +26 -22
- package/docs/release-install-policy.md +6 -6
- package/docs/runtime-harness-standard.md +7 -7
- package/docs/runtime-policy-design.md +1 -1
- package/docs/runtime-quality-baseline.md +1 -1
- package/docs/security-threat-model.md +1 -1
- package/docs/subagent-orchestration-capabilities.md +3 -3
- package/docs/subagents-and-multiagent.md +4 -4
- package/docs/task-implementation-contract.md +4 -4
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +22 -18
- package/docs/usage-observability.md +29 -8
- package/docs/workflow-recipes.md +12 -9
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +19 -15
- package/docs-site/content/commands.html +18 -14
- package/docs-site/content/permissions.html +11 -11
- package/docs-site/content/quickstart.html +12 -11
- package/docs-site/content/team.html +1 -1
- package/docs-site/content/workflows.html +17 -17
- 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 +12 -12
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +13 -12
- 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 +2 -2
- package/docs-site/workflows.html +18 -18
- package/package.json +1 -1
- package/packages/piagent-core/README.md +28 -26
- package/packages/piagent-core/extensions/piagent-guard.ts +1416 -196
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/prompts/be-to-fe.md +4 -3
- package/packages/piagent-core/prompts/discuss.md +2 -2
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +2 -1
- package/packages/piagent-core/prompts/pr.md +1 -1
- package/packages/piagent-core/prompts/review.md +2 -1
- package/packages/piagent-core/prompts/scout.md +3 -2
- package/packages/piagent-core/prompts/task.md +3 -2
- package/scripts/init-project.sh +4 -3
- package/scripts/install-global.sh +47 -1
- package/scripts/profile-doctor.sh +1 -1
- package/scripts/setup.sh +4 -3
- package/scripts/team-doctor.sh +6 -8
- package/scripts/verify-local.sh +18 -17
- package/templates/project/.pi/context-index.json +2 -2
- package/templates/project/.pi/project-context.md +3 -3
- package/templates/project/AGENTS.md +4 -4
- package/packages/piagent-core/prompts/memory-policy.md +0 -52
- package/packages/piagent-core/prompts/model-options.md +0 -68
- package/packages/piagent-core/prompts/onboard-project.md +0 -147
- package/packages/piagent-core/prompts/piagent-commands.md +0 -68
|
@@ -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
|
}
|
|
@@ -1511,10 +1526,10 @@ function ensureProjectContextPlaceholder(cwd: string): void {
|
|
|
1511
1526
|
"",
|
|
1512
1527
|
"- Generated: not yet",
|
|
1513
1528
|
"- Profile: see `.pi/piagent-profile.json`",
|
|
1514
|
-
"- Model/pass: run `/onboard
|
|
1529
|
+
"- Model/pass: run `/onboard` after Pi login and model selection",
|
|
1515
1530
|
"- Scope: pending",
|
|
1516
1531
|
"",
|
|
1517
|
-
"Run `/onboard
|
|
1532
|
+
"Run `/onboard` to replace this placeholder with a concise project context snapshot.",
|
|
1518
1533
|
""
|
|
1519
1534
|
].join("\n"));
|
|
1520
1535
|
}
|
|
@@ -2810,7 +2825,7 @@ function buildContextIndexStatus(cwd: string, profile: ProjectProfile): {
|
|
|
2810
2825
|
if (!settings.enabled || settings.writePolicy === "off") {
|
|
2811
2826
|
warnings.push("context index disabled by profile");
|
|
2812
2827
|
} else if (!exists) {
|
|
2813
|
-
warnings.push("context index missing; run /onboard
|
|
2828
|
+
warnings.push("context index missing; run /onboard or record piagent_context_index_record");
|
|
2814
2829
|
}
|
|
2815
2830
|
if (exists && !index) warnings.push("context index exists but cannot be parsed");
|
|
2816
2831
|
if (index && settings.requireCitations && index.citations.length === 0) warnings.push("context index has no citations");
|
|
@@ -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
|
|
|
@@ -3456,7 +3756,7 @@ function buildContextPreflight(snapshot: UsageSnapshot, workflow = "task", input
|
|
|
3456
3756
|
const live = snapshot.contextUsage;
|
|
3457
3757
|
let projectedContext: ContextPreflight["projectedContext"];
|
|
3458
3758
|
let recommendation: ContextPreflight["recommendation"] = "unknown";
|
|
3459
|
-
let reason = "Context usage is unavailable; use /session or /
|
|
3759
|
+
let reason = "Context usage is unavailable; use /session or /usage if the task is large.";
|
|
3460
3760
|
|
|
3461
3761
|
if (live && live.tokens !== null && live.percent !== null) {
|
|
3462
3762
|
const projectedTokens = live.tokens + inputTokenEstimate;
|
|
@@ -3495,8 +3795,8 @@ function buildContextPreflight(snapshot: UsageSnapshot, workflow = "task", input
|
|
|
3495
3795
|
commands: [
|
|
3496
3796
|
"/task-preflight",
|
|
3497
3797
|
"/task-preflight compact",
|
|
3498
|
-
`/fresh
|
|
3499
|
-
"/
|
|
3798
|
+
`/fresh ${workflow === "be-to-fe" ? "be-to-fe" : workflow === "scout" ? "scout" : "task"} <request>`,
|
|
3799
|
+
"/usage",
|
|
3500
3800
|
"/session"
|
|
3501
3801
|
]
|
|
3502
3802
|
};
|
|
@@ -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
|
|
|
@@ -3572,7 +3856,10 @@ function extractTaskRequest(text: string): string {
|
|
|
3572
3856
|
}
|
|
3573
3857
|
|
|
3574
3858
|
function stripLeadingWorkflowCommand(input: string): string {
|
|
3575
|
-
return input
|
|
3859
|
+
return input
|
|
3860
|
+
.replace(/^\/(?:piagent-workflow|workflow)\s+(?:task|scout|be-to-fe|review|plan|platform-improve|discuss|commit|pr)\b\s*/i, "")
|
|
3861
|
+
.replace(/^\/(?:task|scout|be-to-fe|review|plan|platform-improve|discuss|commit|pr)\b\s*/i, "")
|
|
3862
|
+
.trim();
|
|
3576
3863
|
}
|
|
3577
3864
|
|
|
3578
3865
|
function trimTaskForInline(input: string): string {
|
|
@@ -3584,6 +3871,10 @@ function trimTaskForInline(input: string): string {
|
|
|
3584
3871
|
function chooseFreshWorkflow(original: string, task: string): "task" | "scout" | "be-to-fe" {
|
|
3585
3872
|
const semantic = stripLeadingWorkflowCommand(task || original).toLowerCase();
|
|
3586
3873
|
const starts = original.trim().toLowerCase();
|
|
3874
|
+
const workflowStart = starts.match(/^\/(?:piagent-workflow|workflow)\s+(task|scout|be-to-fe)\b/);
|
|
3875
|
+
if (workflowStart?.[1] === "be-to-fe") return "be-to-fe";
|
|
3876
|
+
if (workflowStart?.[1] === "scout") return "scout";
|
|
3877
|
+
if (workflowStart?.[1] === "task") return "task";
|
|
3587
3878
|
if (starts.startsWith("/be-to-fe")) return "be-to-fe";
|
|
3588
3879
|
if (starts.startsWith("/scout")) return "scout";
|
|
3589
3880
|
const asksForWrite = /\b(implement|support|surface|consume|write|change|fix)\b/.test(semantic);
|
|
@@ -3597,11 +3888,11 @@ function chooseFreshWorkflow(original: string, task: string): "task" | "scout" |
|
|
|
3597
3888
|
}
|
|
3598
3889
|
|
|
3599
3890
|
function isPiagentWorkflowInput(text: string): boolean {
|
|
3600
|
-
return /^\/(?:task|be-to-fe|scout|review|plan|platform-improve)\b/i.test(text.trim());
|
|
3891
|
+
return /^\/(?:piagent-workflow|workflow|task|be-to-fe|scout|review|plan|platform-improve)\b/i.test(text.trim());
|
|
3601
3892
|
}
|
|
3602
3893
|
|
|
3603
3894
|
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());
|
|
3895
|
+
return /^\/(?:usage|logs|context|commands|permission|memory|onboard|name|fresh|fresh-task|fresh-scout|fresh-be-to-fe|task-preflight|piagent-usage|piagent-logs|piagent-session|piagent-context|piagent-commands|piagent-permission|model-options|memory-policy|onboard-project|setname|session|compact)\b/i.test(text.trim());
|
|
3605
3896
|
}
|
|
3606
3897
|
|
|
3607
3898
|
function taskInboxDir(cwd: string): string {
|
|
@@ -3622,9 +3913,9 @@ function buildFreshCommand(cwd: string, workflow: "task" | "scout" | "be-to-fe",
|
|
|
3622
3913
|
const task = extractTaskRequest(originalText);
|
|
3623
3914
|
if (originalText.length >= LONG_INPUT_CHARS) {
|
|
3624
3915
|
const intakePath = writeTaskInbox(cwd, workflow, originalText);
|
|
3625
|
-
return `/fresh
|
|
3916
|
+
return `/fresh ${workflow} Read task intake from ${intakePath}. ${reason}`;
|
|
3626
3917
|
}
|
|
3627
|
-
return `/fresh
|
|
3918
|
+
return `/fresh ${workflow} ${trimTaskForInline(task)}`;
|
|
3628
3919
|
}
|
|
3629
3920
|
|
|
3630
3921
|
function shortTaskLabel(text: string): string {
|
|
@@ -3934,13 +4225,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3934
4225
|
}
|
|
3935
4226
|
const profileHint = explicitProfile || (projectTrusted && fs.existsSync(projectProfilePath(ctx.cwd)))
|
|
3936
4227
|
? ""
|
|
3937
|
-
: " (run /onboard
|
|
4228
|
+
: " (run /onboard to select a profile)";
|
|
3938
4229
|
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
3939
4230
|
const preflight = buildContextPreflight(snapshot, "task", 0);
|
|
3940
4231
|
const capabilityState = verifyProjectCapabilityState(extensionDir, ctx.cwd, projectTrusted);
|
|
3941
4232
|
const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
3942
4233
|
const contextHint = preflight.recommendation === "fresh-session"
|
|
3943
|
-
? " Context is high; use /fresh
|
|
4234
|
+
? " Context is high; use /fresh task or /fresh scout for new work."
|
|
3944
4235
|
: preflight.recommendation === "compact"
|
|
3945
4236
|
? " Context is warm; run /task-preflight before large work."
|
|
3946
4237
|
: "";
|
|
@@ -3969,6 +4260,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3969
4260
|
pi.on("input", async (event, ctx) => {
|
|
3970
4261
|
const text = event.text.trim();
|
|
3971
4262
|
if (!text || isFreshOrUtilityInput(text)) return { action: "continue" };
|
|
4263
|
+
if (/^\/piagent-workflow\b/i.test(text)) {
|
|
4264
|
+
return { action: "transform", text: text.replace(/^\/piagent-workflow\b/i, "/workflow") };
|
|
4265
|
+
}
|
|
3972
4266
|
|
|
3973
4267
|
const imageAttachment = attachLocalImagesFromText(text, event.images, ctx.cwd);
|
|
3974
4268
|
if (imageAttachment?.attached.length) {
|
|
@@ -4073,6 +4367,24 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4073
4367
|
resultChanged = true;
|
|
4074
4368
|
}
|
|
4075
4369
|
|
|
4370
|
+
const captureCache = new Map<string, ToolResultCaptureSummary>();
|
|
4371
|
+
const compactedContent = compactToolResultTextContent(ctx.cwd, event, ctx, resultContent, captureCache);
|
|
4372
|
+
const compactionCaptures = [...compactedContent.captures];
|
|
4373
|
+
if (compactedContent.captures.length > 0) {
|
|
4374
|
+
resultContent = compactedContent.content;
|
|
4375
|
+
resultChanged = true;
|
|
4376
|
+
}
|
|
4377
|
+
if (resultDetails !== undefined) {
|
|
4378
|
+
const compactedDetails = compactToolResultDetails(ctx.cwd, event, ctx, resultDetails, captureCache, compactionCaptures);
|
|
4379
|
+
if (compactionCaptures.length > compactedContent.captures.length) {
|
|
4380
|
+
resultDetails = compactedDetails;
|
|
4381
|
+
resultChanged = true;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
if (compactionCaptures.length > 0) {
|
|
4385
|
+
resultDetails = attachToolResultCompactionDetails(resultDetails, compactionCaptures);
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4076
4388
|
if (resultChanged) {
|
|
4077
4389
|
return resultDetails === undefined
|
|
4078
4390
|
? { content: resultContent }
|
|
@@ -4377,8 +4689,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4377
4689
|
}
|
|
4378
4690
|
});
|
|
4379
4691
|
|
|
4380
|
-
|
|
4381
|
-
|
|
4692
|
+
pi.registerTool({
|
|
4693
|
+
name: "piagent_exec_policy_check",
|
|
4382
4694
|
label: "Piagent Exec Policy Check",
|
|
4383
4695
|
description: "Evaluate a shell command against piagent exec policy before running it.",
|
|
4384
4696
|
promptSnippet: "Use this before high-impact, complex, generated, or unfamiliar shell commands.",
|
|
@@ -5535,6 +5847,135 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5535
5847
|
);
|
|
5536
5848
|
}
|
|
5537
5849
|
|
|
5850
|
+
function emitRuntimeMessage(
|
|
5851
|
+
ctx: ExtensionContext,
|
|
5852
|
+
customType: string,
|
|
5853
|
+
content: string,
|
|
5854
|
+
details: Record<string, unknown> = {},
|
|
5855
|
+
notify?: { message: string; level: "info" | "warning" | "error" }
|
|
5856
|
+
): void {
|
|
5857
|
+
if (notify) ctx.ui.notify(notify.message, notify.level);
|
|
5858
|
+
pi.sendMessage(
|
|
5859
|
+
{ customType, content, display: true, details },
|
|
5860
|
+
{ triggerTurn: false }
|
|
5861
|
+
);
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
function commandArgs(raw: string): { action: string; rest: string; tokens: string[] } {
|
|
5865
|
+
const trimmed = String(raw ?? "").trim();
|
|
5866
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
5867
|
+
const action = (tokens.shift() ?? "").toLowerCase();
|
|
5868
|
+
return { action, rest: tokens.join(" "), tokens };
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
function shellArg(value: string): string {
|
|
5872
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
function usageExactCommands(cwd: string): string[] {
|
|
5876
|
+
const project = shellArg(cwd);
|
|
5877
|
+
return [
|
|
5878
|
+
"/session",
|
|
5879
|
+
`piagent-usage ${project}`,
|
|
5880
|
+
`piagent-usage --history ${project} --days 7`,
|
|
5881
|
+
"piagent-usage --history --all-projects --days 7 --csv"
|
|
5882
|
+
];
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
function sendWorkflowFollowUp(command: string): void {
|
|
5886
|
+
pi.sendUserMessage(command, { deliverAs: "followUp" });
|
|
5887
|
+
}
|
|
5888
|
+
|
|
5889
|
+
async function selectRuntimeAction(
|
|
5890
|
+
ctx: ExtensionContext,
|
|
5891
|
+
title: string,
|
|
5892
|
+
entries: Array<{ value: string; label: string; description?: string; recommended?: boolean }>,
|
|
5893
|
+
defaultValue?: string
|
|
5894
|
+
): Promise<string | undefined> {
|
|
5895
|
+
if (ctx.hasUI === false) return undefined;
|
|
5896
|
+
return await selectValueFromUi(ctx, title, entries, defaultValue ?? entries.find((entry) => entry.recommended)?.value);
|
|
5897
|
+
}
|
|
5898
|
+
|
|
5899
|
+
function emitCurrentPermissionStatus(ctx: ExtensionContext): void {
|
|
5900
|
+
const profile = loadProfileFromContext(ctx);
|
|
5901
|
+
const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
5902
|
+
ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, permissionProfile.mode === "trusted-full-access" ? "warning" : "info");
|
|
5903
|
+
emitPermissionStatus(ctx, permissionProfile);
|
|
5904
|
+
}
|
|
5905
|
+
|
|
5906
|
+
function applyPermissionProfileCommand(ctx: ExtensionContext, mode: PermissionProfileMode, request = ""): void {
|
|
5907
|
+
setPermissionOverrideForContext(ctx, mode);
|
|
5908
|
+
const profile = loadProfileFromContext(ctx);
|
|
5909
|
+
const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
5910
|
+
const isActive = permissionProfile.mode === mode && !permissionProfile.warning;
|
|
5911
|
+
const level = !isActive || mode === "trusted-full-access" ? "warning" : "info";
|
|
5912
|
+
const message = isActive
|
|
5913
|
+
? `Piagent permission profile set to ${mode} for this session.`
|
|
5914
|
+
: `Requested ${mode}, but active profile is ${permissionProfile.mode}.`;
|
|
5915
|
+
ctx.ui.notify(message, level);
|
|
5916
|
+
if (permissionProfile.warning) ctx.ui.notify(permissionProfile.warning, "warning");
|
|
5917
|
+
if (permissionProfile.mode === "trusted-full-access") {
|
|
5918
|
+
ctx.ui.notify("Trusted full access is active; protected paths, secret redaction, and destructive/external confirmations remain enforced.", "warning");
|
|
5919
|
+
}
|
|
5920
|
+
emitPermissionStatus(ctx, permissionProfile);
|
|
5921
|
+
if (request.trim()) sendWorkflowFollowUp(request.trim());
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
function permissionModeFromAction(action: string): PermissionProfileMode | undefined {
|
|
5925
|
+
if (["read", "readonly", "read-only", "ro"].includes(action)) return "read-only";
|
|
5926
|
+
if (["write", "workspace", "workspace-write", "ww"].includes(action)) return "workspace-write";
|
|
5927
|
+
if (["full", "full-access", "trusted", "trusted-full-access"].includes(action)) return "trusted-full-access";
|
|
5928
|
+
return undefined;
|
|
5929
|
+
}
|
|
5930
|
+
|
|
5931
|
+
async function runPermissionNamespace(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
5932
|
+
const { action, rest } = commandArgs(raw);
|
|
5933
|
+
if (!action) {
|
|
5934
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent permission", [
|
|
5935
|
+
{ value: "status", label: "Status", description: "Show current session permission", recommended: true },
|
|
5936
|
+
{ value: "read-only", label: "Read-only", description: "Scout/review without writes" },
|
|
5937
|
+
{ value: "workspace-write", label: "Workspace write", description: "Normal governed implementation mode" },
|
|
5938
|
+
{ value: "full-access", label: "Full access", description: "Trusted local full access; guardrails still apply" },
|
|
5939
|
+
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
5940
|
+
], "status");
|
|
5941
|
+
if (chosen && chosen !== "help") {
|
|
5942
|
+
if (chosen === "status") emitCurrentPermissionStatus(ctx);
|
|
5943
|
+
else applyPermissionProfileCommand(ctx, permissionModeFromAction(chosen) as PermissionProfileMode);
|
|
5944
|
+
return;
|
|
5945
|
+
}
|
|
5946
|
+
emitRuntimeMessage(ctx, "piagent-permission-help", [
|
|
5947
|
+
"namespace: /permission",
|
|
5948
|
+
"status: /permission status",
|
|
5949
|
+
"read: /permission read-only",
|
|
5950
|
+
"write: /permission workspace-write",
|
|
5951
|
+
"full: /permission full-access",
|
|
5952
|
+
"legacy: /piagent-permission"
|
|
5953
|
+
].join("\n"));
|
|
5954
|
+
return;
|
|
5955
|
+
}
|
|
5956
|
+
if (["status", "show", "current"].includes(action)) {
|
|
5957
|
+
emitCurrentPermissionStatus(ctx);
|
|
5958
|
+
return;
|
|
5959
|
+
}
|
|
5960
|
+
if (action === "help") {
|
|
5961
|
+
emitRuntimeMessage(ctx, "piagent-permission-help", [
|
|
5962
|
+
"namespace: /permission",
|
|
5963
|
+
"status: /permission status",
|
|
5964
|
+
"read: /permission read-only",
|
|
5965
|
+
"write: /permission workspace-write",
|
|
5966
|
+
"full: /permission full-access",
|
|
5967
|
+
"legacy: /piagent-permission"
|
|
5968
|
+
].join("\n"));
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5971
|
+
const mode = permissionModeFromAction(action);
|
|
5972
|
+
if (mode) {
|
|
5973
|
+
applyPermissionProfileCommand(ctx, mode, rest);
|
|
5974
|
+
return;
|
|
5975
|
+
}
|
|
5976
|
+
emitRuntimeMessage(ctx, "piagent-permission-error", `unknown permission action: ${action}\nRun /permission help`, { action }, { message: `Unknown permission action: ${action}`, level: "warning" });
|
|
5977
|
+
}
|
|
5978
|
+
|
|
5538
5979
|
function registerPermissionProfileCommand(
|
|
5539
5980
|
name: string,
|
|
5540
5981
|
mode: PermissionProfileMode,
|
|
@@ -5543,35 +5984,39 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5543
5984
|
pi.registerCommand(name, {
|
|
5544
5985
|
description,
|
|
5545
5986
|
handler: async (args, ctx) => {
|
|
5546
|
-
|
|
5547
|
-
setPermissionOverrideForContext(ctx, mode);
|
|
5548
|
-
const profile = loadProfileFromContext(ctx);
|
|
5549
|
-
const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
5550
|
-
const isActive = permissionProfile.mode === mode && !permissionProfile.warning;
|
|
5551
|
-
const level = !isActive || mode === "trusted-full-access" ? "warning" : "info";
|
|
5552
|
-
const message = isActive
|
|
5553
|
-
? `Piagent permission profile set to ${mode} for this session.`
|
|
5554
|
-
: `Requested ${mode}, but active profile is ${permissionProfile.mode}.`;
|
|
5555
|
-
ctx.ui.notify(message, level);
|
|
5556
|
-
if (permissionProfile.warning) ctx.ui.notify(permissionProfile.warning, "warning");
|
|
5557
|
-
if (permissionProfile.mode === "trusted-full-access") {
|
|
5558
|
-
ctx.ui.notify("Trusted full access is active; protected paths, secret redaction, and destructive/external confirmations remain enforced.", "warning");
|
|
5559
|
-
}
|
|
5560
|
-
emitPermissionStatus(ctx, permissionProfile);
|
|
5561
|
-
if (request) {
|
|
5562
|
-
pi.sendUserMessage(request, { deliverAs: "followUp" });
|
|
5563
|
-
}
|
|
5987
|
+
applyPermissionProfileCommand(ctx, mode, String(args ?? ""));
|
|
5564
5988
|
}
|
|
5565
5989
|
});
|
|
5566
5990
|
}
|
|
5567
5991
|
|
|
5992
|
+
pi.registerCommand("piagent-permission", {
|
|
5993
|
+
description: "Legacy alias for /permission",
|
|
5994
|
+
getArgumentCompletions: (prefix: string) => {
|
|
5995
|
+
const actions = ["status", "read-only", "workspace-write", "full-access", "help"];
|
|
5996
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
5997
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
5998
|
+
},
|
|
5999
|
+
handler: async (args, ctx) => {
|
|
6000
|
+
await runPermissionNamespace(String(args ?? ""), ctx);
|
|
6001
|
+
}
|
|
6002
|
+
});
|
|
6003
|
+
|
|
6004
|
+
pi.registerCommand("permission", {
|
|
6005
|
+
description: "Show or switch runtime permission without a model follow-up",
|
|
6006
|
+
getArgumentCompletions: (prefix: string) => {
|
|
6007
|
+
const actions = ["status", "read-only", "workspace-write", "full-access", "help"];
|
|
6008
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
6009
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
6010
|
+
},
|
|
6011
|
+
handler: async (args, ctx) => {
|
|
6012
|
+
await runPermissionNamespace(String(args ?? ""), ctx);
|
|
6013
|
+
}
|
|
6014
|
+
});
|
|
6015
|
+
|
|
5568
6016
|
pi.registerCommand("permission-status", {
|
|
5569
|
-
description: "
|
|
6017
|
+
description: "Legacy alias for /permission status",
|
|
5570
6018
|
handler: async (_args, ctx) => {
|
|
5571
|
-
|
|
5572
|
-
const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
5573
|
-
ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, permissionProfile.mode === "trusted-full-access" ? "warning" : "info");
|
|
5574
|
-
emitPermissionStatus(ctx, permissionProfile);
|
|
6019
|
+
emitCurrentPermissionStatus(ctx);
|
|
5575
6020
|
}
|
|
5576
6021
|
});
|
|
5577
6022
|
|
|
@@ -5859,7 +6304,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5859
6304
|
`profile: ${applied.mode ?? profileName}`,
|
|
5860
6305
|
"updated: .pi/piagent-profile.json",
|
|
5861
6306
|
"updated: .pi/piagent-profile.lock.json",
|
|
5862
|
-
|
|
6307
|
+
`projectContext: ${projectContextExists ? "exists" : "missing"}${projectContextExists ? "" : " — run /onboard"}`
|
|
5863
6308
|
].join("\n"),
|
|
5864
6309
|
display: true,
|
|
5865
6310
|
details: {
|
|
@@ -5918,27 +6363,39 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5918
6363
|
}
|
|
5919
6364
|
});
|
|
5920
6365
|
|
|
6366
|
+
function emitMemoryPolicyStatus(ctx: ExtensionContext): void {
|
|
6367
|
+
const profile = loadProfileFromContext(ctx);
|
|
6368
|
+
const settings = resolveMemorySettings(profile);
|
|
6369
|
+
ctx.ui.notify(`Project memory: ${settings.enabled ? settings.mode : "off"}`, "info");
|
|
6370
|
+
emitRuntimeMessage(ctx, "piagent-memory-status", [
|
|
6371
|
+
`memory: ${settings.enabled ? settings.mode : "off"}`,
|
|
6372
|
+
`scope: ${settings.scope}`,
|
|
6373
|
+
`summary: ${settings.summaryFile}`,
|
|
6374
|
+
`handbook: ${settings.handbookFile}`,
|
|
6375
|
+
`writePolicy: ${settings.writePolicy}`,
|
|
6376
|
+
"rules: explicit-only by default; current repo files remain source of truth",
|
|
6377
|
+
"remember: ask the agent clearly, then it must use piagent_memory_note"
|
|
6378
|
+
].join("\n"), settings);
|
|
6379
|
+
}
|
|
6380
|
+
|
|
5921
6381
|
pi.registerCommand("piagent-memory", {
|
|
5922
|
-
description: "
|
|
6382
|
+
description: "Legacy alias for /memory",
|
|
5923
6383
|
handler: async (_args, ctx) => {
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
},
|
|
5940
|
-
{ triggerTurn: false }
|
|
5941
|
-
);
|
|
6384
|
+
emitMemoryPolicyStatus(ctx);
|
|
6385
|
+
}
|
|
6386
|
+
});
|
|
6387
|
+
|
|
6388
|
+
pi.registerCommand("memory", {
|
|
6389
|
+
description: "Show project memory policy and available memory files without a model follow-up",
|
|
6390
|
+
handler: async (_args, ctx) => {
|
|
6391
|
+
emitMemoryPolicyStatus(ctx);
|
|
6392
|
+
}
|
|
6393
|
+
});
|
|
6394
|
+
|
|
6395
|
+
pi.registerCommand("memory-policy", {
|
|
6396
|
+
description: "Legacy alias for /memory",
|
|
6397
|
+
handler: async (_args, ctx) => {
|
|
6398
|
+
emitMemoryPolicyStatus(ctx);
|
|
5942
6399
|
}
|
|
5943
6400
|
});
|
|
5944
6401
|
|
|
@@ -6150,54 +6607,154 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6150
6607
|
}
|
|
6151
6608
|
registerMcpCommand();
|
|
6152
6609
|
|
|
6610
|
+
function emitContextIndexSearch(ctx: ExtensionContext, query: string): void {
|
|
6611
|
+
const profile = loadProfileFromContext(ctx);
|
|
6612
|
+
let matches: Array<{ id: string; kind: string; label: string; match: string }> = [];
|
|
6613
|
+
let error: string | undefined;
|
|
6614
|
+
try {
|
|
6615
|
+
matches = query ? searchContextIndex(ctx.cwd, profile, query, 8) : [];
|
|
6616
|
+
} catch (caught) {
|
|
6617
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
6618
|
+
}
|
|
6619
|
+
emitRuntimeMessage(ctx, "piagent-context-index-search", error
|
|
6620
|
+
? `Context index search failed: ${error}`
|
|
6621
|
+
: matches.length
|
|
6622
|
+
? matches.map((match) => `${match.id} [${match.kind}] ${match.label}: ${match.match}`).join("\n")
|
|
6623
|
+
: "No context index matches.", { query, matches, error });
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
function emitContextIndexStatus(ctx: ExtensionContext): void {
|
|
6627
|
+
const profile = loadProfileFromContext(ctx);
|
|
6628
|
+
const status = buildContextIndexStatus(ctx.cwd, profile);
|
|
6629
|
+
ctx.ui.notify(`Context index: ${status.exists ? `${status.nodes} nodes` : "missing"}`, status.warnings.length ? "warning" : "info");
|
|
6630
|
+
emitRuntimeMessage(ctx, "piagent-context-index-status", [
|
|
6631
|
+
`contextIndex: ${status.enabled ? "enabled" : "off"}`,
|
|
6632
|
+
`path: ${status.path} (${status.exists ? "exists" : "missing"})`,
|
|
6633
|
+
`nodes: ${status.nodes}`,
|
|
6634
|
+
`edges: ${status.edges}`,
|
|
6635
|
+
`citations: ${status.citations}`,
|
|
6636
|
+
`updatedAt: ${status.updatedAt ?? "never"}`,
|
|
6637
|
+
`warnings: ${status.warnings.join("; ") || "none"}`
|
|
6638
|
+
].join("\n"), status);
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
function parsePreflightWorkflow(raw: string): string {
|
|
6642
|
+
return raw.match(/\b(?:scout|be-to-fe|review|plan|platform-improve|task)\b/i)?.[0]?.toLowerCase() ?? "task";
|
|
6643
|
+
}
|
|
6644
|
+
|
|
6645
|
+
function compactCurrentSession(ctx: ExtensionContext): void {
|
|
6646
|
+
ctx.compact({
|
|
6647
|
+
customInstructions: [
|
|
6648
|
+
"Preserve current task decisions, project constraints, changed files, verify commands, open blockers, and next action.",
|
|
6649
|
+
"Drop repeated mandatory-flow boilerplate and stale exploration details.",
|
|
6650
|
+
"After compaction, required project context must be re-read from current repository files before implementation."
|
|
6651
|
+
].join("\n")
|
|
6652
|
+
});
|
|
6653
|
+
}
|
|
6654
|
+
|
|
6655
|
+
function emitContextPreflight(ctx: ExtensionContext, raw: string, shouldCompact = false): void {
|
|
6656
|
+
const workflow = parsePreflightWorkflow(raw);
|
|
6657
|
+
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
6658
|
+
const preflight = buildContextPreflight(snapshot, workflow, raw.length);
|
|
6659
|
+
const context = snapshot.contextUsage
|
|
6660
|
+
? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
|
|
6661
|
+
: "context unavailable";
|
|
6662
|
+
ctx.ui.notify(`Task preflight: ${preflight.recommendation}; ${context}`, preflight.recommendation === "ok" ? "info" : "warning");
|
|
6663
|
+
if (shouldCompact) compactCurrentSession(ctx);
|
|
6664
|
+
emitRuntimeMessage(ctx, "piagent-task-preflight", formatContextPreflight(preflight, snapshot), preflight);
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6667
|
+
async function runPiagentContextNamespace(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
6668
|
+
const { action, rest } = commandArgs(raw);
|
|
6669
|
+
if (!action) {
|
|
6670
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent context", [
|
|
6671
|
+
{ value: "index", label: "Context index", description: "Show compact project map", recommended: true },
|
|
6672
|
+
{ value: "preflight", label: "Preflight", description: "Check whether to run, compact, or fresh-session" },
|
|
6673
|
+
{ value: "compact", label: "Compact", description: "Compact current session with Piagent carry-over rules" },
|
|
6674
|
+
{ value: "search", label: "Search index", description: "Use /context search <keyword>" },
|
|
6675
|
+
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
6676
|
+
], "index");
|
|
6677
|
+
if (chosen === "index") {
|
|
6678
|
+
emitContextIndexStatus(ctx);
|
|
6679
|
+
return;
|
|
6680
|
+
}
|
|
6681
|
+
if (chosen === "preflight") {
|
|
6682
|
+
emitContextPreflight(ctx, "task");
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
if (chosen === "compact") {
|
|
6686
|
+
emitContextPreflight(ctx, "task compact", true);
|
|
6687
|
+
return;
|
|
6688
|
+
}
|
|
6689
|
+
emitRuntimeMessage(ctx, "piagent-context-help", [
|
|
6690
|
+
"namespace: /context",
|
|
6691
|
+
"index: /context index",
|
|
6692
|
+
"search: /context search <keyword>",
|
|
6693
|
+
"preflight: /context preflight [task|scout|be-to-fe|review|plan]",
|
|
6694
|
+
"compact: /context compact [task|scout|be-to-fe]",
|
|
6695
|
+
"legacy: /piagent-context | /context-index | /task-preflight"
|
|
6696
|
+
].join("\n"));
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6699
|
+
if (["index", "status", "show", "current"].includes(action)) {
|
|
6700
|
+
emitContextIndexStatus(ctx);
|
|
6701
|
+
return;
|
|
6702
|
+
}
|
|
6703
|
+
if (action === "search") {
|
|
6704
|
+
emitContextIndexSearch(ctx, rest);
|
|
6705
|
+
return;
|
|
6706
|
+
}
|
|
6707
|
+
if (["preflight", "check"].includes(action)) {
|
|
6708
|
+
emitContextPreflight(ctx, rest || "task");
|
|
6709
|
+
return;
|
|
6710
|
+
}
|
|
6711
|
+
if (action === "compact") {
|
|
6712
|
+
emitContextPreflight(ctx, `${rest || "task"} compact`, true);
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
if (action === "help") {
|
|
6716
|
+
emitRuntimeMessage(ctx, "piagent-context-help", [
|
|
6717
|
+
"namespace: /context",
|
|
6718
|
+
"index: /context index",
|
|
6719
|
+
"search: /context search <keyword>",
|
|
6720
|
+
"preflight: /context preflight [task|scout|be-to-fe|review|plan]",
|
|
6721
|
+
"compact: /context compact [task|scout|be-to-fe]",
|
|
6722
|
+
"legacy: /piagent-context | /context-index | /task-preflight"
|
|
6723
|
+
].join("\n"));
|
|
6724
|
+
return;
|
|
6725
|
+
}
|
|
6726
|
+
emitContextIndexSearch(ctx, [action, rest].filter(Boolean).join(" "));
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
pi.registerCommand("piagent-context", {
|
|
6730
|
+
description: "Legacy alias for /context",
|
|
6731
|
+
getArgumentCompletions: (prefix: string) => {
|
|
6732
|
+
const actions = ["index", "search", "preflight", "compact", "help"];
|
|
6733
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
6734
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
6735
|
+
},
|
|
6736
|
+
handler: async (args, ctx) => {
|
|
6737
|
+
await runPiagentContextNamespace(String(args ?? ""), ctx);
|
|
6738
|
+
}
|
|
6739
|
+
});
|
|
6740
|
+
|
|
6741
|
+
pi.registerCommand("context", {
|
|
6742
|
+
description: "Context index, search, preflight, and compact controls without a model follow-up",
|
|
6743
|
+
getArgumentCompletions: (prefix: string) => {
|
|
6744
|
+
const actions = ["index", "search", "preflight", "compact", "help"];
|
|
6745
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
6746
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
6747
|
+
},
|
|
6748
|
+
handler: async (args, ctx) => {
|
|
6749
|
+
await runPiagentContextNamespace(String(args ?? ""), ctx);
|
|
6750
|
+
}
|
|
6751
|
+
});
|
|
6752
|
+
|
|
6153
6753
|
pi.registerCommand("context-index", {
|
|
6154
|
-
description: "
|
|
6754
|
+
description: "Legacy alias for /context index/search",
|
|
6155
6755
|
handler: async (args, ctx) => {
|
|
6156
6756
|
const raw = String(args ?? "").trim();
|
|
6157
|
-
|
|
6158
|
-
if (/^search\s+/i.test(raw)) {
|
|
6159
|
-
const query = raw.replace(/^search\s+/i, "").trim();
|
|
6160
|
-
let matches: Array<{ id: string; kind: string; label: string; match: string }> = [];
|
|
6161
|
-
let error: string | undefined;
|
|
6162
|
-
try {
|
|
6163
|
-
matches = query ? searchContextIndex(ctx.cwd, profile, query, 8) : [];
|
|
6164
|
-
} catch (caught) {
|
|
6165
|
-
error = caught instanceof Error ? caught.message : String(caught);
|
|
6166
|
-
}
|
|
6167
|
-
pi.sendMessage(
|
|
6168
|
-
{
|
|
6169
|
-
customType: "piagent-context-index-search",
|
|
6170
|
-
content: error
|
|
6171
|
-
? `Context index search failed: ${error}`
|
|
6172
|
-
: matches.length
|
|
6173
|
-
? matches.map((match) => `${match.id} [${match.kind}] ${match.label}: ${match.match}`).join("\n")
|
|
6174
|
-
: "No context index matches.",
|
|
6175
|
-
display: true,
|
|
6176
|
-
details: { query, matches, error }
|
|
6177
|
-
},
|
|
6178
|
-
{ triggerTurn: false }
|
|
6179
|
-
);
|
|
6180
|
-
return;
|
|
6181
|
-
}
|
|
6182
|
-
const status = buildContextIndexStatus(ctx.cwd, profile);
|
|
6183
|
-
ctx.ui.notify(`Context index: ${status.exists ? `${status.nodes} nodes` : "missing"}`, status.warnings.length ? "warning" : "info");
|
|
6184
|
-
pi.sendMessage(
|
|
6185
|
-
{
|
|
6186
|
-
customType: "piagent-context-index-status",
|
|
6187
|
-
content: [
|
|
6188
|
-
`contextIndex: ${status.enabled ? "enabled" : "off"}`,
|
|
6189
|
-
`path: ${status.path} (${status.exists ? "exists" : "missing"})`,
|
|
6190
|
-
`nodes: ${status.nodes}`,
|
|
6191
|
-
`edges: ${status.edges}`,
|
|
6192
|
-
`citations: ${status.citations}`,
|
|
6193
|
-
`updatedAt: ${status.updatedAt ?? "never"}`,
|
|
6194
|
-
`warnings: ${status.warnings.join("; ") || "none"}`
|
|
6195
|
-
].join("\n"),
|
|
6196
|
-
display: true,
|
|
6197
|
-
details: status
|
|
6198
|
-
},
|
|
6199
|
-
{ triggerTurn: false }
|
|
6200
|
-
);
|
|
6757
|
+
await runPiagentContextNamespace(raw ? raw : "index", ctx);
|
|
6201
6758
|
}
|
|
6202
6759
|
});
|
|
6203
6760
|
|
|
@@ -6241,64 +6798,701 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6241
6798
|
}
|
|
6242
6799
|
});
|
|
6243
6800
|
|
|
6801
|
+
function emitUsageSnapshot(ctx: ExtensionContext): void {
|
|
6802
|
+
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
6803
|
+
const context = snapshot.contextUsage
|
|
6804
|
+
? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
|
|
6805
|
+
: "context unavailable";
|
|
6806
|
+
ctx.ui.notify(`Piagent usage: ${context}`, "info");
|
|
6807
|
+
emitRuntimeMessage(ctx, "piagent-usage-snapshot", formatUsageSnapshot(snapshot), snapshot);
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
function emitUsageHistoryHint(ctx: ExtensionContext): void {
|
|
6811
|
+
const commands = usageExactCommands(ctx.cwd);
|
|
6812
|
+
emitRuntimeMessage(ctx, "piagent-usage-history-help", [
|
|
6813
|
+
"usageHistory: terminal/RPC report",
|
|
6814
|
+
`project: ${redactText(ctx.cwd)}`,
|
|
6815
|
+
`current: ${commands[1]}`,
|
|
6816
|
+
`history: ${commands[2]}`,
|
|
6817
|
+
`weeklyCsv: ${commands[3]}`,
|
|
6818
|
+
"insidePi: /session",
|
|
6819
|
+
"note: history reads ~/.pi/agent/sessions/**/*.jsonl, including ended sessions and subagents unless --no-subagents is used"
|
|
6820
|
+
].join("\n"), { commands });
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6823
|
+
function emitToolLogCaptures(ctx: ExtensionContext): void {
|
|
6824
|
+
const captures = readRecentToolResultCaptures(ctx.cwd, 5);
|
|
6825
|
+
ctx.ui.notify(`Piagent logs: ${captures.length ? `${captures.length} recent capture(s)` : "no compacted captures yet"}`, "info");
|
|
6826
|
+
emitRuntimeMessage(ctx, "piagent-log-captures", formatToolResultCaptureStatus(ctx.cwd, captures), {
|
|
6827
|
+
policy: {
|
|
6828
|
+
compactAboveChars: TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
|
|
6829
|
+
compactAboveLines: TOOL_RESULT_COMPACT_LINE_THRESHOLD,
|
|
6830
|
+
previewMaxChars: TOOL_RESULT_PREVIEW_MAX_CHARS,
|
|
6831
|
+
captureMaxChars: TOOL_RESULT_CAPTURE_MAX_CHARS
|
|
6832
|
+
},
|
|
6833
|
+
captures
|
|
6834
|
+
});
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
async function runUsageNamespace(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
6838
|
+
const { action, rest } = commandArgs(raw);
|
|
6839
|
+
if (!action) {
|
|
6840
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent usage", [
|
|
6841
|
+
{ value: "live", label: "Live usage", description: "Current context/session/model", recommended: true },
|
|
6842
|
+
{ value: "history", label: "History/report", description: "Exact terminal commands for old sessions and weekly CSV" },
|
|
6843
|
+
{ value: "preflight", label: "Preflight", description: "Check task/context health" },
|
|
6844
|
+
{ value: "compact", label: "Compact", description: "Compact current session with Piagent carry-over rules" },
|
|
6845
|
+
{ value: "logs", label: "Tool logs", description: "Recent compacted large tool outputs" },
|
|
6846
|
+
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
6847
|
+
], "live");
|
|
6848
|
+
if (chosen === "live") {
|
|
6849
|
+
emitUsageSnapshot(ctx);
|
|
6850
|
+
return;
|
|
6851
|
+
}
|
|
6852
|
+
if (chosen === "history") {
|
|
6853
|
+
emitUsageHistoryHint(ctx);
|
|
6854
|
+
return;
|
|
6855
|
+
}
|
|
6856
|
+
if (chosen === "preflight") {
|
|
6857
|
+
emitContextPreflight(ctx, "task");
|
|
6858
|
+
return;
|
|
6859
|
+
}
|
|
6860
|
+
if (chosen === "compact") {
|
|
6861
|
+
emitContextPreflight(ctx, "task compact", true);
|
|
6862
|
+
return;
|
|
6863
|
+
}
|
|
6864
|
+
if (chosen === "logs") {
|
|
6865
|
+
emitToolLogCaptures(ctx);
|
|
6866
|
+
return;
|
|
6867
|
+
}
|
|
6868
|
+
emitRuntimeMessage(ctx, "piagent-usage-help", [
|
|
6869
|
+
"namespace: /usage",
|
|
6870
|
+
"live: /usage live",
|
|
6871
|
+
"history: /usage history",
|
|
6872
|
+
"preflight: /usage preflight [task|scout|be-to-fe]",
|
|
6873
|
+
"compact: /usage compact [task|scout|be-to-fe]",
|
|
6874
|
+
"logs: /usage logs",
|
|
6875
|
+
"native exact session: /session",
|
|
6876
|
+
"legacy: /piagent-usage | /task-preflight | /logs"
|
|
6877
|
+
].join("\n"));
|
|
6878
|
+
return;
|
|
6879
|
+
}
|
|
6880
|
+
if (["live", "status", "current", "context"].includes(action)) {
|
|
6881
|
+
emitUsageSnapshot(ctx);
|
|
6882
|
+
return;
|
|
6883
|
+
}
|
|
6884
|
+
if (["history", "cost", "exact", "report", "reports"].includes(action)) {
|
|
6885
|
+
emitUsageHistoryHint(ctx);
|
|
6886
|
+
return;
|
|
6887
|
+
}
|
|
6888
|
+
if (["preflight", "check"].includes(action)) {
|
|
6889
|
+
emitContextPreflight(ctx, rest || "task");
|
|
6890
|
+
return;
|
|
6891
|
+
}
|
|
6892
|
+
if (action === "compact") {
|
|
6893
|
+
emitContextPreflight(ctx, `${rest || "task"} compact`, true);
|
|
6894
|
+
return;
|
|
6895
|
+
}
|
|
6896
|
+
if (["logs", "log"].includes(action)) {
|
|
6897
|
+
emitToolLogCaptures(ctx);
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6900
|
+
if (action === "help") {
|
|
6901
|
+
emitRuntimeMessage(ctx, "piagent-usage-help", [
|
|
6902
|
+
"namespace: /usage",
|
|
6903
|
+
"live | history | preflight | compact | logs",
|
|
6904
|
+
"examples:",
|
|
6905
|
+
"/usage live",
|
|
6906
|
+
"/usage history",
|
|
6907
|
+
"/usage compact scout"
|
|
6908
|
+
].join("\n"));
|
|
6909
|
+
return;
|
|
6910
|
+
}
|
|
6911
|
+
emitRuntimeMessage(ctx, "piagent-usage-error", `unknown usage action: ${action}\nRun /usage help`, { action }, { message: `Unknown usage action: ${action}`, level: "warning" });
|
|
6912
|
+
}
|
|
6913
|
+
|
|
6914
|
+
function setSessionNameFromCommand(ctx: ExtensionContext, raw: string, usage = "/name <task/session name>"): void {
|
|
6915
|
+
const name = cleanSessionNameInput(raw);
|
|
6916
|
+
if (!name) {
|
|
6917
|
+
ctx.ui.notify(`Usage: ${usage}`, "warning");
|
|
6918
|
+
return;
|
|
6919
|
+
}
|
|
6920
|
+
const previousName = currentSessionName(ctx);
|
|
6921
|
+
pi.setSessionName(name);
|
|
6922
|
+
appendSessionTrace(pi, {
|
|
6923
|
+
event: "session_name_set",
|
|
6924
|
+
previousName: previousName || undefined,
|
|
6925
|
+
sessionName: name
|
|
6926
|
+
});
|
|
6927
|
+
ctx.ui.notify(`Session name set: ${name}`, "info");
|
|
6928
|
+
emitRuntimeMessage(ctx, "piagent-session-name-set", `sessionName: ${name}`, { sessionName: name, previousName: previousName || undefined });
|
|
6929
|
+
}
|
|
6930
|
+
|
|
6931
|
+
function emitSessionStatus(ctx: ExtensionContext): void {
|
|
6932
|
+
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
6933
|
+
emitRuntimeMessage(ctx, "piagent-session-status", [
|
|
6934
|
+
`session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
|
|
6935
|
+
`file: ${snapshot.sessionFile ?? "not persisted"}`,
|
|
6936
|
+
`cwd: ${redactText(snapshot.cwd)}`,
|
|
6937
|
+
`model: ${snapshot.model}; thinking: ${snapshot.thinkingLevel}`,
|
|
6938
|
+
`entries: ${formatCount(snapshot.entries.branch)} active / ${formatCount(snapshot.entries.total)} total`,
|
|
6939
|
+
"name: /name <task name>",
|
|
6940
|
+
"resume: use Pi native /resume or /session"
|
|
6941
|
+
].join("\n"), snapshot, { message: `Piagent session: ${snapshot.sessionName ?? "unnamed"}`, level: "info" });
|
|
6942
|
+
}
|
|
6943
|
+
|
|
6944
|
+
function emitSessionResumeHelp(ctx: ExtensionContext): void {
|
|
6945
|
+
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
6946
|
+
const sessionFile = snapshot.sessionFile ? shellArg(snapshot.sessionFile) : "<session-file>";
|
|
6947
|
+
emitRuntimeMessage(ctx, "piagent-session-resume-help", [
|
|
6948
|
+
`session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
|
|
6949
|
+
`file: ${snapshot.sessionFile ?? "not persisted"}`,
|
|
6950
|
+
"continueLatest: pi --continue",
|
|
6951
|
+
"pickByName: pi --resume",
|
|
6952
|
+
`exactFile: pi --session ${sessionFile}`,
|
|
6953
|
+
"afterResume: run Pi native /session and /usage live"
|
|
6954
|
+
].join("\n"), snapshot);
|
|
6955
|
+
}
|
|
6956
|
+
|
|
6957
|
+
async function runSessionNamespace(raw: string, ctx: any): Promise<void> {
|
|
6958
|
+
const { action, rest } = commandArgs(raw);
|
|
6959
|
+
if (!action) {
|
|
6960
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent session", [
|
|
6961
|
+
{ value: "current", label: "Current session", description: "Name, id, file, model", recommended: true },
|
|
6962
|
+
{ value: "name", label: "Set name", description: "Use /name <task name>" },
|
|
6963
|
+
{ value: "resume", label: "Resume help", description: "Commands for continuing old sessions" },
|
|
6964
|
+
{ value: "fresh-task", label: "Fresh task", description: "Use /fresh task <request>" },
|
|
6965
|
+
{ value: "fresh-scout", label: "Fresh scout", description: "Use /fresh scout <request>" },
|
|
6966
|
+
{ value: "usage", label: "Usage", description: "Current context/session usage" },
|
|
6967
|
+
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
6968
|
+
], "current");
|
|
6969
|
+
if (chosen === "current") {
|
|
6970
|
+
emitSessionStatus(ctx);
|
|
6971
|
+
return;
|
|
6972
|
+
}
|
|
6973
|
+
if (chosen === "resume") {
|
|
6974
|
+
emitSessionResumeHelp(ctx);
|
|
6975
|
+
return;
|
|
6976
|
+
}
|
|
6977
|
+
if (chosen === "usage") {
|
|
6978
|
+
emitUsageSnapshot(ctx);
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
emitRuntimeMessage(ctx, "piagent-session-help", [
|
|
6982
|
+
"session helpers:",
|
|
6983
|
+
"current: /usage live or Pi native /session",
|
|
6984
|
+
"name: /name <task/session name>",
|
|
6985
|
+
"fresh: /fresh task|scout|be-to-fe <request>",
|
|
6986
|
+
"resume: Pi native /resume or /session",
|
|
6987
|
+
"legacy: /piagent-session | /setname | /fresh-task | /fresh-scout | /fresh-be-to-fe"
|
|
6988
|
+
].join("\n"));
|
|
6989
|
+
return;
|
|
6990
|
+
}
|
|
6991
|
+
if (["current", "status", "show"].includes(action)) {
|
|
6992
|
+
emitSessionStatus(ctx);
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
if (["name", "set", "rename"].includes(action)) {
|
|
6996
|
+
setSessionNameFromCommand(ctx, rest);
|
|
6997
|
+
return;
|
|
6998
|
+
}
|
|
6999
|
+
if (action === "resume") {
|
|
7000
|
+
emitSessionResumeHelp(ctx);
|
|
7001
|
+
return;
|
|
7002
|
+
}
|
|
7003
|
+
if (["usage", "cost"].includes(action)) {
|
|
7004
|
+
emitUsageSnapshot(ctx);
|
|
7005
|
+
return;
|
|
7006
|
+
}
|
|
7007
|
+
if (["fresh", "new"].includes(action)) {
|
|
7008
|
+
const next = commandArgs(rest);
|
|
7009
|
+
const workflow = next.action === "be-to-fe" ? "be-to-fe" : next.action === "scout" ? "scout" : "task";
|
|
7010
|
+
await startFreshWorkflow(workflow, next.rest, ctx);
|
|
7011
|
+
return;
|
|
7012
|
+
}
|
|
7013
|
+
if (["fresh-task", "task"].includes(action)) {
|
|
7014
|
+
await startFreshWorkflow("task", rest, ctx);
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
if (["fresh-scout", "scout"].includes(action)) {
|
|
7018
|
+
await startFreshWorkflow("scout", rest, ctx);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
if (["fresh-be-to-fe", "be-to-fe"].includes(action)) {
|
|
7022
|
+
await startFreshWorkflow("be-to-fe", rest, ctx);
|
|
7023
|
+
return;
|
|
7024
|
+
}
|
|
7025
|
+
if (action === "help") {
|
|
7026
|
+
emitRuntimeMessage(ctx, "piagent-session-help", [
|
|
7027
|
+
"session helpers:",
|
|
7028
|
+
"/usage live",
|
|
7029
|
+
"/name ABC-123 Short task name",
|
|
7030
|
+
"/fresh task Implement <request>",
|
|
7031
|
+
"native: /session | /resume"
|
|
7032
|
+
].join("\n"));
|
|
7033
|
+
return;
|
|
7034
|
+
}
|
|
7035
|
+
setSessionNameFromCommand(ctx, [action, rest].filter(Boolean).join(" "));
|
|
7036
|
+
}
|
|
7037
|
+
|
|
6244
7038
|
pi.registerCommand("piagent-usage", {
|
|
6245
|
-
description: "
|
|
7039
|
+
description: "Legacy alias for /usage",
|
|
7040
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7041
|
+
const actions = ["live", "history", "preflight", "compact", "logs", "help"];
|
|
7042
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7043
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7044
|
+
},
|
|
7045
|
+
handler: async (args, ctx) => {
|
|
7046
|
+
await runUsageNamespace(String(args ?? ""), ctx);
|
|
7047
|
+
}
|
|
7048
|
+
});
|
|
7049
|
+
|
|
7050
|
+
pi.registerCommand("usage", {
|
|
7051
|
+
description: "Usage namespace: live, history, preflight, compact, and logs without a model follow-up",
|
|
7052
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7053
|
+
const actions = ["live", "history", "preflight", "compact", "logs", "help"];
|
|
7054
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7055
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7056
|
+
},
|
|
7057
|
+
handler: async (args, ctx) => {
|
|
7058
|
+
await runUsageNamespace(String(args ?? ""), ctx);
|
|
7059
|
+
}
|
|
7060
|
+
});
|
|
7061
|
+
|
|
7062
|
+
pi.registerCommand("piagent-logs", {
|
|
7063
|
+
description: "Legacy alias for /usage logs",
|
|
6246
7064
|
handler: async (_args, ctx) => {
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
7065
|
+
emitToolLogCaptures(ctx);
|
|
7066
|
+
}
|
|
7067
|
+
});
|
|
7068
|
+
|
|
7069
|
+
pi.registerCommand("logs", {
|
|
7070
|
+
description: "Show recent compacted large tool outputs without a model follow-up",
|
|
7071
|
+
handler: async (_args, ctx) => {
|
|
7072
|
+
emitToolLogCaptures(ctx);
|
|
7073
|
+
}
|
|
7074
|
+
});
|
|
7075
|
+
|
|
7076
|
+
pi.registerCommand("piagent-session", {
|
|
7077
|
+
description: "Legacy session helper namespace; prefer /usage, /name, and /fresh",
|
|
7078
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7079
|
+
const actions = ["current", "name", "resume", "fresh", "usage", "help"];
|
|
7080
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7081
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7082
|
+
},
|
|
7083
|
+
handler: async (args, ctx) => {
|
|
7084
|
+
await runSessionNamespace(String(args ?? ""), ctx);
|
|
7085
|
+
}
|
|
7086
|
+
});
|
|
7087
|
+
|
|
7088
|
+
pi.registerCommand("name", {
|
|
7089
|
+
description: "Set the current session name for Agent Watch/report mapping",
|
|
7090
|
+
handler: async (args, ctx) => {
|
|
7091
|
+
setSessionNameFromCommand(ctx, String(args ?? ""), "/name <task/session name>");
|
|
7092
|
+
}
|
|
7093
|
+
});
|
|
7094
|
+
|
|
7095
|
+
pi.registerCommand("setname", {
|
|
7096
|
+
description: "Legacy alias for /name",
|
|
7097
|
+
handler: async (args, ctx) => {
|
|
7098
|
+
setSessionNameFromCommand(ctx, String(args ?? ""), "/setname <task/session name>");
|
|
6261
7099
|
}
|
|
6262
7100
|
});
|
|
6263
7101
|
|
|
6264
7102
|
pi.registerCommand("task-preflight", {
|
|
6265
|
-
description: "
|
|
7103
|
+
description: "Legacy alias for /context preflight; add compact to compact",
|
|
6266
7104
|
handler: async (args, ctx) => {
|
|
6267
7105
|
const raw = String(args ?? "").trim();
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
7106
|
+
emitContextPreflight(ctx, raw || "task", /\bcompact\b/i.test(raw));
|
|
7107
|
+
}
|
|
7108
|
+
});
|
|
7109
|
+
|
|
7110
|
+
type WorkflowCommandName = "task" | "scout" | "be-to-fe" | "discuss" | "plan" | "review" | "platform-improve" | "commit" | "pr" | "onboard";
|
|
7111
|
+
|
|
7112
|
+
const WORKFLOW_ALIASES: Record<string, WorkflowCommandName> = {
|
|
7113
|
+
task: "task",
|
|
7114
|
+
implement: "task",
|
|
7115
|
+
scout: "scout",
|
|
7116
|
+
audit: "scout",
|
|
7117
|
+
"be-to-fe": "be-to-fe",
|
|
7118
|
+
befe: "be-to-fe",
|
|
7119
|
+
discuss: "discuss",
|
|
7120
|
+
clarify: "discuss",
|
|
7121
|
+
plan: "plan",
|
|
7122
|
+
review: "review",
|
|
7123
|
+
"platform-improve": "platform-improve",
|
|
7124
|
+
platform: "platform-improve",
|
|
7125
|
+
commit: "commit",
|
|
7126
|
+
pr: "pr",
|
|
7127
|
+
onboard: "onboard",
|
|
7128
|
+
"onboard-project": "onboard"
|
|
7129
|
+
};
|
|
7130
|
+
|
|
7131
|
+
function workflowChoices(): Array<{ value: string; label: string; description: string; recommended?: boolean }> {
|
|
7132
|
+
return [
|
|
7133
|
+
{ value: "task", label: "Task", description: "Implement a bounded task", recommended: true },
|
|
7134
|
+
{ value: "scout", label: "Scout", description: "Read-only audit/research" },
|
|
7135
|
+
{ value: "be-to-fe", label: "BE to FE", description: "Backend read-only, frontend implementation" },
|
|
7136
|
+
{ value: "discuss", label: "Discuss", description: "Clarify before planning/editing" },
|
|
7137
|
+
{ value: "plan", label: "Plan", description: "Create an implementation plan" },
|
|
7138
|
+
{ value: "review", label: "Review", description: "Review diff/source read-only" },
|
|
7139
|
+
{ value: "commit", label: "Commit", description: "Guarded local commit workflow" },
|
|
7140
|
+
{ value: "pr", label: "PR", description: "Guarded pull request preparation" },
|
|
7141
|
+
{ value: "onboard", label: "Onboard", description: "First-read project onboarding scout" },
|
|
7142
|
+
{ value: "platform-improve", label: "Platform", description: "Improve Pi Agent Platform itself" },
|
|
7143
|
+
{ value: "help", label: "Help", description: "Show typed workflow forms" }
|
|
7144
|
+
];
|
|
7145
|
+
}
|
|
7146
|
+
|
|
7147
|
+
function buildOnboardingWorkflowPrompt(focus: string): string {
|
|
7148
|
+
return [
|
|
7149
|
+
"Run the Pi Agent Platform first-read onboarding workflow for this repository.",
|
|
7150
|
+
"",
|
|
7151
|
+
`Optional focus: ${focus.trim() || "whole repository"}`,
|
|
7152
|
+
"",
|
|
7153
|
+
"Preconditions:",
|
|
7154
|
+
"- The operator has logged in and selected the intended model/thinking level.",
|
|
7155
|
+
"- Stay read-only except writing Piagent onboarding state through piagent tools.",
|
|
7156
|
+
"",
|
|
7157
|
+
"Mandatory flow:",
|
|
7158
|
+
"1. Call piagent_context with detail=full.",
|
|
7159
|
+
"2. If the project is unprofiled, call piagent_profile_options, do a lightweight root scout, recommend a profile, and use piagent_profile_apply only after the operator choice is clear.",
|
|
7160
|
+
"3. Prefer /profile setup or piagent_profile_tech_options + piagent_profile_tech_apply for tech stack selection.",
|
|
7161
|
+
"4. Re-call piagent_context after profile/tech changes.",
|
|
7162
|
+
"5. Call piagent_memory_status and treat memory as advisory.",
|
|
7163
|
+
"6. Read AGENTS.md, README/package/build config, required context, docs/architecture files, source map, and verify command definitions. Do not ingest the whole repo.",
|
|
7164
|
+
"7. Identify project purpose, stack/runtime, ownership boundaries, high-risk areas, protected paths, verify commands, MCP/tool capabilities, selected tech stack, memory policy, and conventions.",
|
|
7165
|
+
"8. Write a concise .pi/project-context.md snapshot and record it with piagent_project_onboarding_record so .pi/context-index.json is generated.",
|
|
7166
|
+
"9. Call piagent_context_index_status and report pending/stale warnings.",
|
|
7167
|
+
"",
|
|
7168
|
+
"Final output: profile, tech stack, context files read, verification matrix, high-risk areas, context-index status, memory status, and any missing operator decisions."
|
|
7169
|
+
].join("\n");
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
function emitWorkflowHelp(ctx: ExtensionContext): void {
|
|
7173
|
+
emitRuntimeMessage(ctx, "piagent-workflow-help", [
|
|
7174
|
+
"namespace: /workflow",
|
|
7175
|
+
"daily: /workflow task <request>",
|
|
7176
|
+
"readOnly: /workflow scout <area/spec/risk>",
|
|
7177
|
+
"beToFe: /workflow be-to-fe <BE spec/change + FE outcome>",
|
|
7178
|
+
"clarify: /workflow discuss <rough idea>",
|
|
7179
|
+
"plan: /workflow plan <goal>",
|
|
7180
|
+
"review: /workflow review <target or diff>",
|
|
7181
|
+
"git: /workflow commit [message] | /workflow pr [title]",
|
|
7182
|
+
"onboard: /workflow onboard [focus]",
|
|
7183
|
+
"aliases still work: /task, /scout, /be-to-fe, /commit, /pr"
|
|
7184
|
+
].join("\n"), { workflows: workflowChoices().map((choice) => choice.value) });
|
|
7185
|
+
}
|
|
7186
|
+
|
|
7187
|
+
async function runWorkflowNamespace(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
7188
|
+
const { action, rest } = commandArgs(raw);
|
|
7189
|
+
if (!action) {
|
|
7190
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent workflow", workflowChoices(), "task");
|
|
7191
|
+
if (!chosen || chosen === "help") {
|
|
7192
|
+
emitWorkflowHelp(ctx);
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7195
|
+
if (chosen === "onboard") {
|
|
7196
|
+
sendWorkflowFollowUp(buildOnboardingWorkflowPrompt(""));
|
|
7197
|
+
ctx.ui.notify("Workflow launched: onboard", "info");
|
|
7198
|
+
return;
|
|
6284
7199
|
}
|
|
7200
|
+
emitRuntimeMessage(ctx, "piagent-workflow-selected", [
|
|
7201
|
+
`workflow: ${chosen}`,
|
|
7202
|
+
`run: /workflow ${chosen} <request>`,
|
|
7203
|
+
"tip: type the request after the workflow name so the agent receives the task in one turn"
|
|
7204
|
+
].join("\n"), { workflow: chosen });
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
const workflow = WORKFLOW_ALIASES[action];
|
|
7208
|
+
if (!workflow || workflow === undefined) {
|
|
7209
|
+
emitRuntimeMessage(ctx, "piagent-workflow-error", `unknown workflow: ${action}\nRun /workflow help`, { action }, { message: `Unknown workflow: ${action}`, level: "warning" });
|
|
7210
|
+
return;
|
|
7211
|
+
}
|
|
7212
|
+
if (workflow === "onboard") {
|
|
7213
|
+
sendWorkflowFollowUp(buildOnboardingWorkflowPrompt(rest));
|
|
7214
|
+
ctx.ui.notify("Workflow launched: onboard", "info");
|
|
7215
|
+
return;
|
|
7216
|
+
}
|
|
7217
|
+
if (!rest.trim()) {
|
|
7218
|
+
emitRuntimeMessage(ctx, "piagent-workflow-needs-request", [
|
|
7219
|
+
`workflow: ${workflow}`,
|
|
7220
|
+
`run: /workflow ${workflow} <request>`,
|
|
7221
|
+
`alias: /${workflow} <request>`
|
|
7222
|
+
].join("\n"), { workflow });
|
|
7223
|
+
return;
|
|
7224
|
+
}
|
|
7225
|
+
sendWorkflowFollowUp(`/${workflow} ${rest}`);
|
|
7226
|
+
ctx.ui.notify(`Workflow launched: ${workflow}`, "info");
|
|
7227
|
+
}
|
|
6285
7228
|
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
7229
|
+
function emitModelOptions(ctx: ExtensionContext): void {
|
|
7230
|
+
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
7231
|
+
emitRuntimeMessage(ctx, "piagent-model-options", [
|
|
7232
|
+
`current: ${snapshot.model}`,
|
|
7233
|
+
`thinking: ${snapshot.thinkingLevel}`,
|
|
7234
|
+
"selector: /model or Ctrl+L",
|
|
7235
|
+
"cycleModel: Ctrl+P / Shift+Ctrl+P",
|
|
7236
|
+
"thinkingLevel: Shift+Tab",
|
|
7237
|
+
"terminalCatalog: piagent-models",
|
|
7238
|
+
"scopeConfig: piagent-model-scope --preset team",
|
|
7239
|
+
"codexFamily: openai-codex/gpt-5.4-mini, openai-codex/gpt-5.5, openai-codex/gpt-5.6-luna/sol/terra",
|
|
7240
|
+
"claudeFamily: anthropic/claude-haiku, anthropic/claude-sonnet, anthropic/claude-opus, anthropic/claude-fable-5",
|
|
7241
|
+
"rule: choose model/thinking by task effort; do not claim savings without benchmark evidence"
|
|
7242
|
+
].join("\n"), snapshot, { message: `Pi model: ${snapshot.model}`, level: "info" });
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7245
|
+
function emitPiagentCommandHelp(ctx: ExtensionContext, topic = "overview"): void {
|
|
7246
|
+
const normalized = topic.toLowerCase();
|
|
7247
|
+
const sections: Record<string, string[]> = {
|
|
7248
|
+
overview: [
|
|
7249
|
+
"Piagent command surface:",
|
|
7250
|
+
"runtime: /workflow | /usage | /context | /permission | /commands | /profile | /memory | /onboard | /name | /fresh",
|
|
7251
|
+
"native: /model | /session | /resume | /compact | /mcp",
|
|
7252
|
+
"workflow: /workflow task|scout|be-to-fe|review|commit|pr <request>",
|
|
7253
|
+
"mcp: /mcp is Pi native; governed MCP checks stay at /piagent-mcp to avoid collision",
|
|
7254
|
+
"legacy: /piagent-* commands still work where they existed",
|
|
7255
|
+
"principle: runtime commands run immediately; workflows intentionally launch an agent turn"
|
|
7256
|
+
],
|
|
7257
|
+
workflow: [
|
|
7258
|
+
"Workflow entrypoint:",
|
|
7259
|
+
"/workflow",
|
|
7260
|
+
"/workflow task <request>",
|
|
7261
|
+
"/workflow scout <read-only request>",
|
|
7262
|
+
"/workflow be-to-fe <BE spec/change + FE outcome>",
|
|
7263
|
+
"/workflow onboard [focus]"
|
|
7264
|
+
],
|
|
7265
|
+
usage: [
|
|
7266
|
+
"Usage/session:",
|
|
7267
|
+
"/usage",
|
|
7268
|
+
"/usage history",
|
|
7269
|
+
"/name <task name>",
|
|
7270
|
+
"/fresh task|scout|be-to-fe <request>",
|
|
7271
|
+
"native: /session | /resume"
|
|
7272
|
+
],
|
|
7273
|
+
context: [
|
|
7274
|
+
"Context:",
|
|
7275
|
+
"/context",
|
|
7276
|
+
"/context index",
|
|
7277
|
+
"/context search <keyword>",
|
|
7278
|
+
"/context preflight task",
|
|
7279
|
+
"/context compact task"
|
|
7280
|
+
],
|
|
7281
|
+
permission: [
|
|
7282
|
+
"Permission:",
|
|
7283
|
+
"/permission",
|
|
7284
|
+
"/permission read-only",
|
|
7285
|
+
"/permission workspace-write",
|
|
7286
|
+
"/permission full-access"
|
|
7287
|
+
],
|
|
7288
|
+
model: [
|
|
7289
|
+
"Model:",
|
|
7290
|
+
"/model or Ctrl+L opens Pi native selector",
|
|
7291
|
+
"/model-options shows local Piagent model guidance",
|
|
7292
|
+
"Ctrl+P cycles model scope; Shift+Tab cycles thinking"
|
|
7293
|
+
],
|
|
7294
|
+
memory: [
|
|
7295
|
+
"Memory:",
|
|
7296
|
+
"/memory",
|
|
7297
|
+
"Memory is explicit-only by default and advisory, not source of truth"
|
|
7298
|
+
],
|
|
7299
|
+
mcp: [
|
|
7300
|
+
"MCP:",
|
|
7301
|
+
"/piagent-mcp opens governed MCP menu",
|
|
7302
|
+
"/piagent-mcp status|get|doctor|approve|reject|reset|enable|disable",
|
|
7303
|
+
"/mcp remains Pi native MCP panel"
|
|
7304
|
+
],
|
|
7305
|
+
subagents: [
|
|
7306
|
+
"Subagents:",
|
|
7307
|
+
"/subagents-doctor",
|
|
7308
|
+
"/subagents-models",
|
|
7309
|
+
"/subagents-fleet",
|
|
7310
|
+
"/subagent-cost",
|
|
7311
|
+
"Daily work should start from /workflow; parent decides bounded subagents when worth token cost"
|
|
7312
|
+
],
|
|
7313
|
+
terminal: [
|
|
7314
|
+
"Terminal helpers:",
|
|
7315
|
+
"piagent-install --stable",
|
|
7316
|
+
"piagent-doctor /path/to/project --strict-share",
|
|
7317
|
+
"piagent-usage --history --all-projects --days 7 --csv",
|
|
7318
|
+
"piagent-mcp --preset core --scope global --replace",
|
|
7319
|
+
"piagent-subagents --preset safe"
|
|
7320
|
+
]
|
|
7321
|
+
};
|
|
7322
|
+
const lines = sections[normalized] ?? sections.overview;
|
|
7323
|
+
emitRuntimeMessage(ctx, "piagent-command-help", lines.join("\n"), { topic: normalized, topics: Object.keys(sections) }, { message: `Piagent commands: ${normalized}`, level: "info" });
|
|
7324
|
+
}
|
|
7325
|
+
|
|
7326
|
+
async function runPiagentCommandsNamespace(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
7327
|
+
const topic = String(raw ?? "").trim().toLowerCase();
|
|
7328
|
+
if (topic) {
|
|
7329
|
+
emitPiagentCommandHelp(ctx, topic);
|
|
7330
|
+
return;
|
|
7331
|
+
}
|
|
7332
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent commands", [
|
|
7333
|
+
{ value: "overview", label: "Overview", description: "The simplified command map", recommended: true },
|
|
7334
|
+
{ value: "workflow", label: "Workflow", description: "Task/scout/review/git/onboard launcher" },
|
|
7335
|
+
{ value: "usage", label: "Usage/session", description: "Usage, reports, session names, resume" },
|
|
7336
|
+
{ value: "context", label: "Context", description: "Index/search/preflight/compact" },
|
|
7337
|
+
{ value: "permission", label: "Permission", description: "Read/write/full access controls" },
|
|
7338
|
+
{ value: "model", label: "Model", description: "Native model selector and thinking" },
|
|
7339
|
+
{ value: "mcp", label: "MCP", description: "Governed MCP commands" },
|
|
7340
|
+
{ value: "subagents", label: "Subagents", description: "Health, fleet, cost" },
|
|
7341
|
+
{ value: "terminal", label: "Terminal", description: "piagent-* helpers" }
|
|
7342
|
+
], "overview");
|
|
7343
|
+
emitPiagentCommandHelp(ctx, chosen ?? "overview");
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
function emitOnboardingStatus(ctx: ExtensionContext): void {
|
|
7347
|
+
const profile = loadProfileFromContext(ctx);
|
|
7348
|
+
const projectContextExists = fs.existsSync(projectContextFilePath(ctx.cwd));
|
|
7349
|
+
const techManifestExists = fs.existsSync(techStackPath(ctx.cwd));
|
|
7350
|
+
const indexStatus = buildContextIndexStatus(ctx.cwd, profile);
|
|
7351
|
+
const memory = resolveMemorySettings(profile);
|
|
7352
|
+
emitRuntimeMessage(ctx, "piagent-onboarding-status", [
|
|
7353
|
+
`profile: ${profile.mode ?? profile.projectId ?? "unprofiled"}`,
|
|
7354
|
+
`profileFile: ${fs.existsSync(projectProfilePath(ctx.cwd)) ? "exists" : "missing"}`,
|
|
7355
|
+
`projectContext: ${projectContextExists ? "exists" : "missing"}`,
|
|
7356
|
+
`techStack: ${techManifestExists ? "exists" : "missing"}`,
|
|
7357
|
+
`contextIndex: ${indexStatus.exists ? `${indexStatus.nodes} nodes` : "missing"}`,
|
|
7358
|
+
`memory: ${memory.enabled ? memory.mode : "off"}`,
|
|
7359
|
+
"next: /onboard run | /profile setup | /profile tech setup"
|
|
7360
|
+
].join("\n"), { profile, projectContextExists, techManifestExists, indexStatus, memory }, { message: `Onboarding: ${projectContextExists && indexStatus.exists ? "ready" : "pending"}`, level: projectContextExists && indexStatus.exists ? "info" : "warning" });
|
|
7361
|
+
}
|
|
7362
|
+
|
|
7363
|
+
async function runOnboardingCommand(raw: string, ctx: ExtensionContext): Promise<void> {
|
|
7364
|
+
const { action, rest } = commandArgs(raw);
|
|
7365
|
+
if (!action) {
|
|
7366
|
+
const chosen = await selectRuntimeAction(ctx, "Piagent onboarding", [
|
|
7367
|
+
{ value: "status", label: "Status", description: "Profile/context/index readiness", recommended: true },
|
|
7368
|
+
{ value: "run", label: "Run onboarding scout", description: "Launch the agent workflow to write project context" },
|
|
7369
|
+
{ value: "profile", label: "Profile status", description: "Show profile options" },
|
|
7370
|
+
{ value: "setup", label: "Profile + tech setup", description: "Select profile and tech stack" },
|
|
7371
|
+
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
7372
|
+
], "status");
|
|
7373
|
+
if (chosen === "run") {
|
|
7374
|
+
sendWorkflowFollowUp(buildOnboardingWorkflowPrompt(""));
|
|
7375
|
+
ctx.ui.notify("Workflow launched: onboard", "info");
|
|
7376
|
+
return;
|
|
7377
|
+
}
|
|
7378
|
+
if (chosen === "profile") {
|
|
7379
|
+
emitProfileStatus(ctx, "list");
|
|
7380
|
+
return;
|
|
7381
|
+
}
|
|
7382
|
+
if (chosen === "setup") {
|
|
7383
|
+
await runProfileTechWizard(ctx);
|
|
7384
|
+
return;
|
|
7385
|
+
}
|
|
7386
|
+
if (chosen === "status") {
|
|
7387
|
+
emitOnboardingStatus(ctx);
|
|
7388
|
+
return;
|
|
7389
|
+
}
|
|
7390
|
+
emitRuntimeMessage(ctx, "piagent-onboarding-help", [
|
|
7391
|
+
"namespace: /onboard",
|
|
7392
|
+
"status: /onboard status",
|
|
7393
|
+
"run: /onboard run [focus]",
|
|
7394
|
+
"profile: /onboard profile",
|
|
7395
|
+
"setup: /onboard setup [profile]",
|
|
7396
|
+
"workflow alias: /workflow onboard [focus]"
|
|
7397
|
+
].join("\n"));
|
|
7398
|
+
return;
|
|
7399
|
+
}
|
|
7400
|
+
if (["status", "show", "current"].includes(action)) {
|
|
7401
|
+
emitOnboardingStatus(ctx);
|
|
7402
|
+
return;
|
|
7403
|
+
}
|
|
7404
|
+
if (["run", "scout", "start"].includes(action)) {
|
|
7405
|
+
sendWorkflowFollowUp(buildOnboardingWorkflowPrompt(rest));
|
|
7406
|
+
ctx.ui.notify("Workflow launched: onboard", "info");
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
if (["profile", "profiles"].includes(action)) {
|
|
7410
|
+
emitProfileStatus(ctx, "list");
|
|
7411
|
+
return;
|
|
7412
|
+
}
|
|
7413
|
+
if (["setup", "wizard", "select"].includes(action)) {
|
|
7414
|
+
await runProfileTechWizard(ctx, rest || undefined);
|
|
7415
|
+
return;
|
|
7416
|
+
}
|
|
7417
|
+
if (["tech", "stack"].includes(action)) {
|
|
7418
|
+
emitProfileTechStatus(ctx);
|
|
7419
|
+
return;
|
|
7420
|
+
}
|
|
7421
|
+
if (action === "help") {
|
|
7422
|
+
emitRuntimeMessage(ctx, "piagent-onboarding-help", [
|
|
7423
|
+
"namespace: /onboard",
|
|
7424
|
+
"/onboard status",
|
|
7425
|
+
"/onboard run [focus]",
|
|
7426
|
+
"/onboard setup [profile]",
|
|
7427
|
+
"/workflow onboard [focus]"
|
|
7428
|
+
].join("\n"));
|
|
7429
|
+
return;
|
|
7430
|
+
}
|
|
7431
|
+
sendWorkflowFollowUp(buildOnboardingWorkflowPrompt([action, rest].filter(Boolean).join(" ")));
|
|
7432
|
+
ctx.ui.notify("Workflow launched: onboard", "info");
|
|
7433
|
+
}
|
|
7434
|
+
|
|
7435
|
+
pi.registerCommand("workflow", {
|
|
7436
|
+
description: "One menu for Piagent task, scout, review, git, and onboarding workflows",
|
|
7437
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7438
|
+
const actions = Object.keys(WORKFLOW_ALIASES).filter((name) => !["implement", "audit", "clarify", "platform", "onboard-project"].includes(name));
|
|
7439
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7440
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7441
|
+
},
|
|
7442
|
+
handler: async (args, ctx) => {
|
|
7443
|
+
await runWorkflowNamespace(String(args ?? ""), ctx);
|
|
7444
|
+
}
|
|
7445
|
+
});
|
|
7446
|
+
|
|
7447
|
+
pi.registerCommand("piagent-commands", {
|
|
7448
|
+
description: "Legacy alias for /commands",
|
|
7449
|
+
handler: async (args, ctx) => {
|
|
7450
|
+
await runPiagentCommandsNamespace(String(args ?? ""), ctx);
|
|
7451
|
+
}
|
|
7452
|
+
});
|
|
7453
|
+
|
|
7454
|
+
pi.registerCommand("commands", {
|
|
7455
|
+
description: "Runtime command menu/help for Pi Agent Platform; no model follow-up",
|
|
7456
|
+
handler: async (args, ctx) => {
|
|
7457
|
+
await runPiagentCommandsNamespace(String(args ?? ""), ctx);
|
|
7458
|
+
}
|
|
7459
|
+
});
|
|
7460
|
+
|
|
7461
|
+
pi.registerCommand("model-options", {
|
|
7462
|
+
description: "Show Pi model selector/thinking guidance without a model follow-up",
|
|
7463
|
+
handler: async (_args, ctx) => {
|
|
7464
|
+
emitModelOptions(ctx);
|
|
7465
|
+
}
|
|
7466
|
+
});
|
|
7467
|
+
|
|
7468
|
+
pi.registerCommand("onboard-project", {
|
|
7469
|
+
description: "Legacy alias for /onboard",
|
|
7470
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7471
|
+
const actions = ["status", "run", "profile", "setup", "tech", "help"];
|
|
7472
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7473
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7474
|
+
},
|
|
7475
|
+
handler: async (args, ctx) => {
|
|
7476
|
+
await runOnboardingCommand(String(args ?? ""), ctx);
|
|
7477
|
+
}
|
|
7478
|
+
});
|
|
7479
|
+
|
|
7480
|
+
pi.registerCommand("onboard", {
|
|
7481
|
+
description: "Runtime onboarding menu; run launches the first-read onboarding workflow",
|
|
7482
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7483
|
+
const actions = ["status", "run", "profile", "setup", "tech", "help"];
|
|
7484
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7485
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7486
|
+
},
|
|
7487
|
+
handler: async (args, ctx) => {
|
|
7488
|
+
await runOnboardingCommand(String(args ?? ""), ctx);
|
|
6295
7489
|
}
|
|
6296
7490
|
});
|
|
6297
7491
|
|
|
6298
7492
|
async function startFreshWorkflow(workflow: "task" | "scout" | "be-to-fe", args: string, ctx: any) {
|
|
6299
7493
|
const request = String(args ?? "").trim();
|
|
6300
7494
|
if (!request) {
|
|
6301
|
-
ctx.ui.notify(`Usage: /fresh
|
|
7495
|
+
ctx.ui.notify(`Usage: /fresh ${workflow} <request>`, "warning");
|
|
6302
7496
|
return;
|
|
6303
7497
|
}
|
|
6304
7498
|
|
|
@@ -6315,22 +7509,48 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6315
7509
|
}
|
|
6316
7510
|
}
|
|
6317
7511
|
|
|
7512
|
+
pi.registerCommand("fresh", {
|
|
7513
|
+
description: "Open a fresh governed session for task, scout, or BE-to-FE work",
|
|
7514
|
+
getArgumentCompletions: (prefix: string) => {
|
|
7515
|
+
const actions = ["task", "scout", "be-to-fe", "help"];
|
|
7516
|
+
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7517
|
+
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7518
|
+
},
|
|
7519
|
+
handler: async (args, ctx) => {
|
|
7520
|
+
const { action, rest } = commandArgs(String(args ?? ""));
|
|
7521
|
+
if (!action || action === "help") {
|
|
7522
|
+
emitRuntimeMessage(ctx, "piagent-fresh-help", [
|
|
7523
|
+
"namespace: /fresh",
|
|
7524
|
+
"/fresh task <request>",
|
|
7525
|
+
"/fresh scout <read-only request>",
|
|
7526
|
+
"/fresh be-to-fe <BE spec/change + FE outcome>"
|
|
7527
|
+
].join("\n"));
|
|
7528
|
+
return;
|
|
7529
|
+
}
|
|
7530
|
+
const workflow = action === "be-to-fe" ? "be-to-fe" : action === "scout" ? "scout" : "task";
|
|
7531
|
+
const request = action === "task" || action === "scout" || action === "be-to-fe"
|
|
7532
|
+
? rest
|
|
7533
|
+
: [action, rest].filter(Boolean).join(" ");
|
|
7534
|
+
await startFreshWorkflow(workflow, request, ctx);
|
|
7535
|
+
}
|
|
7536
|
+
});
|
|
7537
|
+
|
|
6318
7538
|
pi.registerCommand("fresh-task", {
|
|
6319
|
-
description: "
|
|
7539
|
+
description: "Legacy alias for /fresh task",
|
|
6320
7540
|
handler: async (args, ctx) => {
|
|
6321
7541
|
await startFreshWorkflow("task", String(args ?? ""), ctx);
|
|
6322
7542
|
}
|
|
6323
7543
|
});
|
|
6324
7544
|
|
|
6325
7545
|
pi.registerCommand("fresh-scout", {
|
|
6326
|
-
description: "
|
|
7546
|
+
description: "Legacy alias for /fresh scout",
|
|
6327
7547
|
handler: async (args, ctx) => {
|
|
6328
7548
|
await startFreshWorkflow("scout", String(args ?? ""), ctx);
|
|
6329
7549
|
}
|
|
6330
7550
|
});
|
|
6331
7551
|
|
|
6332
7552
|
pi.registerCommand("fresh-be-to-fe", {
|
|
6333
|
-
description: "
|
|
7553
|
+
description: "Legacy alias for /fresh be-to-fe",
|
|
6334
7554
|
handler: async (args, ctx) => {
|
|
6335
7555
|
await startFreshWorkflow("be-to-fe", String(args ?? ""), ctx);
|
|
6336
7556
|
}
|