@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +34 -27
  3. package/catalog/capabilities.json +8 -8
  4. package/docs/auto-delegation-policy.md +4 -4
  5. package/docs/command-reference-vietnamese.md +121 -81
  6. package/docs/context-window-policy.md +7 -6
  7. package/docs/distribution-standard.md +27 -10
  8. package/docs/herdr-workflow.md +3 -2
  9. package/docs/memory-policy.md +6 -3
  10. package/docs/operator-manual-vietnamese.md +107 -69
  11. package/docs/package-architecture-notes.md +6 -6
  12. package/docs/project-adapters.md +4 -4
  13. package/docs/project-onboarding.md +12 -11
  14. package/docs/publishing-for-teams.md +2 -1
  15. package/docs/quickstart-vietnamese.md +26 -22
  16. package/docs/release-install-policy.md +6 -6
  17. package/docs/runtime-harness-standard.md +7 -7
  18. package/docs/runtime-policy-design.md +1 -1
  19. package/docs/runtime-quality-baseline.md +1 -1
  20. package/docs/security-threat-model.md +1 -1
  21. package/docs/subagent-orchestration-capabilities.md +3 -3
  22. package/docs/subagents-and-multiagent.md +4 -4
  23. package/docs/task-implementation-contract.md +4 -4
  24. package/docs/task-lifecycle-vietnamese.md +2 -2
  25. package/docs/team-onboarding.md +22 -18
  26. package/docs/usage-observability.md +29 -8
  27. package/docs/workflow-recipes.md +12 -9
  28. package/docs-site/architecture.html +1 -1
  29. package/docs-site/commands.html +19 -15
  30. package/docs-site/content/commands.html +18 -14
  31. package/docs-site/content/permissions.html +11 -11
  32. package/docs-site/content/quickstart.html +12 -11
  33. package/docs-site/content/team.html +1 -1
  34. package/docs-site/content/workflows.html +17 -17
  35. package/docs-site/index.html +1 -1
  36. package/docs-site/mcp-approval.html +1 -1
  37. package/docs-site/mcp-auth.html +1 -1
  38. package/docs-site/mcp-servers.html +1 -1
  39. package/docs-site/mcp.html +1 -1
  40. package/docs-site/permissions.html +12 -12
  41. package/docs-site/profiles.html +1 -1
  42. package/docs-site/quickstart.html +13 -12
  43. package/docs-site/runtime.html +1 -1
  44. package/docs-site/scope.html +1 -1
  45. package/docs-site/security.html +1 -1
  46. package/docs-site/team.html +2 -2
  47. package/docs-site/workflows.html +18 -18
  48. package/package.json +1 -1
  49. package/packages/piagent-core/README.md +28 -26
  50. package/packages/piagent-core/extensions/piagent-guard.ts +1416 -196
  51. package/packages/piagent-core/package.json +1 -1
  52. package/packages/piagent-core/prompts/be-to-fe.md +4 -3
  53. package/packages/piagent-core/prompts/discuss.md +2 -2
  54. package/packages/piagent-core/prompts/plan.md +1 -1
  55. package/packages/piagent-core/prompts/platform-improve.md +2 -1
  56. package/packages/piagent-core/prompts/pr.md +1 -1
  57. package/packages/piagent-core/prompts/review.md +2 -1
  58. package/packages/piagent-core/prompts/scout.md +3 -2
  59. package/packages/piagent-core/prompts/task.md +3 -2
  60. package/scripts/init-project.sh +4 -3
  61. package/scripts/install-global.sh +47 -1
  62. package/scripts/profile-doctor.sh +1 -1
  63. package/scripts/setup.sh +4 -3
  64. package/scripts/team-doctor.sh +6 -8
  65. package/scripts/verify-local.sh +18 -17
  66. package/templates/project/.pi/context-index.json +2 -2
  67. package/templates/project/.pi/project-context.md +3 -3
  68. package/templates/project/AGENTS.md +4 -4
  69. package/packages/piagent-core/prompts/memory-policy.md +0 -52
  70. package/packages/piagent-core/prompts/model-options.md +0 -68
  71. package/packages/piagent-core/prompts/onboard-project.md +0 -147
  72. 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-project` after Pi login and model selection",
1529
+ "- Model/pass: run `/onboard` after Pi login and model selection",
1515
1530
  "- Scope: pending",
1516
1531
  "",
1517
- "Run `/onboard-project` to replace this placeholder with a concise project context snapshot.",
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-project or record piagent_context_index_record");
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
- "# Piagent usage snapshot",
3424
- "",
3425
- `- Session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
3426
- `- Session file: ${snapshot.sessionFile ?? "not persisted"}`,
3427
- `- CWD: ${snapshot.cwd}`,
3428
- `- Mode: ${snapshot.mode}`,
3429
- `- Model: ${snapshot.model}`,
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 /piagent-usage if the task is large.";
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-${workflow === "be-to-fe" ? "be-to-fe" : workflow === "scout" ? "scout" : "task"} <request>`,
3499
- "/piagent-usage",
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
- "# Piagent task preflight",
3514
- "",
3515
- `- Workflow: ${preflight.workflow}`,
3516
- `- Session: ${snapshot.sessionName ?? "unnamed"} (${snapshot.sessionId ?? "unknown"})`,
3517
- `- Model: ${snapshot.model}`,
3518
- `- Thinking: ${snapshot.thinkingLevel}`,
3519
- `- Entries: ${formatCount(snapshot.entries.branch)} active / ${formatCount(snapshot.entries.total)} total`,
3520
- `- Live context: ${live}`,
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.replace(/^\/(?:task|scout|be-to-fe|review|plan|platform-improve)\b\s*/i, "").trim();
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-${workflow} Read task intake from ${intakePath}. ${reason}`;
3916
+ return `/fresh ${workflow} Read task intake from ${intakePath}. ${reason}`;
3626
3917
  }
3627
- return `/fresh-${workflow} ${trimTaskForInline(task)}`;
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-project to select a profile)";
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-task or /fresh-scout for new work."
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
- pi.registerTool({
4381
- name: "piagent_exec_policy_check",
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
- const request = String(args ?? "").trim();
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: "Show the active runtime permission profile and guard boundaries",
6017
+ description: "Legacy alias for /permission status",
5570
6018
  handler: async (_args, ctx) => {
5571
- const profile = loadProfileFromContext(ctx);
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
- `projectContext: ${projectContextExists ? "exists" : "missing"}${projectContextExists ? "" : " — run /onboard-project"}`
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: "Show project memory policy and available memory files",
6382
+ description: "Legacy alias for /memory",
5923
6383
  handler: async (_args, ctx) => {
5924
- const profile = loadProfileFromContext(ctx);
5925
- const settings = resolveMemorySettings(profile);
5926
- ctx.ui.notify(`Project memory: ${settings.enabled ? settings.mode : "off"}`, "info");
5927
- pi.sendMessage(
5928
- {
5929
- customType: "piagent-memory-status",
5930
- content: [
5931
- `memory: ${settings.enabled ? settings.mode : "off"}`,
5932
- `scope: ${settings.scope}`,
5933
- `summary: ${settings.summaryFile}`,
5934
- `handbook: ${settings.handbookFile}`,
5935
- `writePolicy: ${settings.writePolicy}`
5936
- ].join("\n"),
5937
- display: true,
5938
- details: settings
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: "Show or search the compact project context index without a model follow-up",
6754
+ description: "Legacy alias for /context index/search",
6155
6755
  handler: async (args, ctx) => {
6156
6756
  const raw = String(args ?? "").trim();
6157
- const profile = loadProfileFromContext(ctx);
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: "Show live context usage, session file, and token/cost follow-up commands",
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
- const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
6248
- const context = snapshot.contextUsage
6249
- ? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
6250
- : "context unavailable";
6251
- ctx.ui.notify(`Piagent usage: ${context}`, "info");
6252
- pi.sendMessage(
6253
- {
6254
- customType: "piagent-usage-snapshot",
6255
- content: formatUsageSnapshot(snapshot),
6256
- display: true,
6257
- details: snapshot
6258
- },
6259
- { triggerTurn: false }
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: "Check context health before a large task; optionally compact",
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
- const workflow = raw.match(/\b(?:scout|be-to-fe|review|plan|platform-improve|task)\b/i)?.[0]?.toLowerCase() ?? "task";
6269
- const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
6270
- const preflight = buildContextPreflight(snapshot, workflow, raw.length);
6271
- const context = snapshot.contextUsage
6272
- ? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
6273
- : "context unavailable";
6274
- ctx.ui.notify(`Task preflight: ${preflight.recommendation}; ${context}`, preflight.recommendation === "ok" ? "info" : "warning");
6275
-
6276
- if (/\bcompact\b/i.test(raw)) {
6277
- ctx.compact({
6278
- customInstructions: [
6279
- "Preserve current task decisions, project constraints, changed files, verify commands, open blockers, and next action.",
6280
- "Drop repeated mandatory-flow boilerplate and stale exploration details.",
6281
- "After compaction, required project context must be re-read from current repository files before implementation."
6282
- ].join("\n")
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
- pi.sendMessage(
6287
- {
6288
- customType: "piagent-task-preflight",
6289
- content: formatContextPreflight(preflight, snapshot),
6290
- display: true,
6291
- details: preflight
6292
- },
6293
- { triggerTurn: false }
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-${workflow} <request>`, "warning");
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: "Start a fresh governed session and run /task with the request",
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: "Start a fresh governed session and run /scout read-only with the request",
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: "Start a fresh governed session and run /be-to-fe with the request",
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
  }