@piagent/platform 1.2.2 → 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 +18 -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 +119 -85
  6. package/docs/context-window-policy.md +7 -7
  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 +81 -72
  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 -2
  15. package/docs/quickstart-vietnamese.md +25 -23
  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 +13 -12
  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 +27 -27
  50. package/packages/piagent-core/extensions/piagent-guard.ts +1055 -190
  51. package/packages/piagent-core/package.json +1 -1
  52. package/packages/piagent-core/prompts/be-to-fe.md +3 -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 +1 -1
  56. package/packages/piagent-core/prompts/pr.md +1 -1
  57. package/packages/piagent-core/prompts/review.md +1 -1
  58. package/packages/piagent-core/prompts/scout.md +2 -2
  59. package/packages/piagent-core/prompts/task.md +2 -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 -69
@@ -1526,10 +1526,10 @@ function ensureProjectContextPlaceholder(cwd: string): void {
1526
1526
  "",
1527
1527
  "- Generated: not yet",
1528
1528
  "- Profile: see `.pi/piagent-profile.json`",
1529
- "- Model/pass: run `/onboard-project` after Pi login and model selection",
1529
+ "- Model/pass: run `/onboard` after Pi login and model selection",
1530
1530
  "- Scope: pending",
1531
1531
  "",
1532
- "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.",
1533
1533
  ""
1534
1534
  ].join("\n"));
1535
1535
  }
@@ -2825,7 +2825,7 @@ function buildContextIndexStatus(cwd: string, profile: ProjectProfile): {
2825
2825
  if (!settings.enabled || settings.writePolicy === "off") {
2826
2826
  warnings.push("context index disabled by profile");
2827
2827
  } else if (!exists) {
2828
- 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");
2829
2829
  }
2830
2830
  if (exists && !index) warnings.push("context index exists but cannot be parsed");
2831
2831
  if (index && settings.requireCitations && index.citations.length === 0) warnings.push("context index has no citations");
@@ -3756,7 +3756,7 @@ function buildContextPreflight(snapshot: UsageSnapshot, workflow = "task", input
3756
3756
  const live = snapshot.contextUsage;
3757
3757
  let projectedContext: ContextPreflight["projectedContext"];
3758
3758
  let recommendation: ContextPreflight["recommendation"] = "unknown";
3759
- 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.";
3760
3760
 
3761
3761
  if (live && live.tokens !== null && live.percent !== null) {
3762
3762
  const projectedTokens = live.tokens + inputTokenEstimate;
@@ -3795,8 +3795,8 @@ function buildContextPreflight(snapshot: UsageSnapshot, workflow = "task", input
3795
3795
  commands: [
3796
3796
  "/task-preflight",
3797
3797
  "/task-preflight compact",
3798
- `/fresh-${workflow === "be-to-fe" ? "be-to-fe" : workflow === "scout" ? "scout" : "task"} <request>`,
3799
- "/piagent-usage",
3798
+ `/fresh ${workflow === "be-to-fe" ? "be-to-fe" : workflow === "scout" ? "scout" : "task"} <request>`,
3799
+ "/usage",
3800
3800
  "/session"
3801
3801
  ]
3802
3802
  };
@@ -3856,7 +3856,10 @@ function extractTaskRequest(text: string): string {
3856
3856
  }
3857
3857
 
3858
3858
  function stripLeadingWorkflowCommand(input: string): string {
3859
- 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();
3860
3863
  }
3861
3864
 
3862
3865
  function trimTaskForInline(input: string): string {
@@ -3868,6 +3871,10 @@ function trimTaskForInline(input: string): string {
3868
3871
  function chooseFreshWorkflow(original: string, task: string): "task" | "scout" | "be-to-fe" {
3869
3872
  const semantic = stripLeadingWorkflowCommand(task || original).toLowerCase();
3870
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";
3871
3878
  if (starts.startsWith("/be-to-fe")) return "be-to-fe";
3872
3879
  if (starts.startsWith("/scout")) return "scout";
3873
3880
  const asksForWrite = /\b(implement|support|surface|consume|write|change|fix)\b/.test(semantic);
@@ -3881,11 +3888,11 @@ function chooseFreshWorkflow(original: string, task: string): "task" | "scout" |
3881
3888
  }
3882
3889
 
3883
3890
  function isPiagentWorkflowInput(text: string): boolean {
3884
- 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());
3885
3892
  }
3886
3893
 
3887
3894
  function isFreshOrUtilityInput(text: string): boolean {
3888
- return /^\/(?:fresh-task|fresh-scout|fresh-be-to-fe|task-preflight|piagent-usage|piagent-logs|setname|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());
3889
3896
  }
3890
3897
 
3891
3898
  function taskInboxDir(cwd: string): string {
@@ -3906,9 +3913,9 @@ function buildFreshCommand(cwd: string, workflow: "task" | "scout" | "be-to-fe",
3906
3913
  const task = extractTaskRequest(originalText);
3907
3914
  if (originalText.length >= LONG_INPUT_CHARS) {
3908
3915
  const intakePath = writeTaskInbox(cwd, workflow, originalText);
3909
- return `/fresh-${workflow} Read task intake from ${intakePath}. ${reason}`;
3916
+ return `/fresh ${workflow} Read task intake from ${intakePath}. ${reason}`;
3910
3917
  }
3911
- return `/fresh-${workflow} ${trimTaskForInline(task)}`;
3918
+ return `/fresh ${workflow} ${trimTaskForInline(task)}`;
3912
3919
  }
3913
3920
 
3914
3921
  function shortTaskLabel(text: string): string {
@@ -4218,13 +4225,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
4218
4225
  }
4219
4226
  const profileHint = explicitProfile || (projectTrusted && fs.existsSync(projectProfilePath(ctx.cwd)))
4220
4227
  ? ""
4221
- : " (run /onboard-project to select a profile)";
4228
+ : " (run /onboard to select a profile)";
4222
4229
  const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
4223
4230
  const preflight = buildContextPreflight(snapshot, "task", 0);
4224
4231
  const capabilityState = verifyProjectCapabilityState(extensionDir, ctx.cwd, projectTrusted);
4225
4232
  const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
4226
4233
  const contextHint = preflight.recommendation === "fresh-session"
4227
- ? " 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."
4228
4235
  : preflight.recommendation === "compact"
4229
4236
  ? " Context is warm; run /task-preflight before large work."
4230
4237
  : "";
@@ -4253,6 +4260,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4253
4260
  pi.on("input", async (event, ctx) => {
4254
4261
  const text = event.text.trim();
4255
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
+ }
4256
4266
 
4257
4267
  const imageAttachment = attachLocalImagesFromText(text, event.images, ctx.cwd);
4258
4268
  if (imageAttachment?.attached.length) {
@@ -5837,6 +5847,135 @@ export default function piagentGuard(pi: ExtensionAPI) {
5837
5847
  );
5838
5848
  }
5839
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
+
5840
5979
  function registerPermissionProfileCommand(
5841
5980
  name: string,
5842
5981
  mode: PermissionProfileMode,
@@ -5845,35 +5984,39 @@ export default function piagentGuard(pi: ExtensionAPI) {
5845
5984
  pi.registerCommand(name, {
5846
5985
  description,
5847
5986
  handler: async (args, ctx) => {
5848
- const request = String(args ?? "").trim();
5849
- setPermissionOverrideForContext(ctx, mode);
5850
- const profile = loadProfileFromContext(ctx);
5851
- const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
5852
- const isActive = permissionProfile.mode === mode && !permissionProfile.warning;
5853
- const level = !isActive || mode === "trusted-full-access" ? "warning" : "info";
5854
- const message = isActive
5855
- ? `Piagent permission profile set to ${mode} for this session.`
5856
- : `Requested ${mode}, but active profile is ${permissionProfile.mode}.`;
5857
- ctx.ui.notify(message, level);
5858
- if (permissionProfile.warning) ctx.ui.notify(permissionProfile.warning, "warning");
5859
- if (permissionProfile.mode === "trusted-full-access") {
5860
- ctx.ui.notify("Trusted full access is active; protected paths, secret redaction, and destructive/external confirmations remain enforced.", "warning");
5861
- }
5862
- emitPermissionStatus(ctx, permissionProfile);
5863
- if (request) {
5864
- pi.sendUserMessage(request, { deliverAs: "followUp" });
5865
- }
5987
+ applyPermissionProfileCommand(ctx, mode, String(args ?? ""));
5866
5988
  }
5867
5989
  });
5868
5990
  }
5869
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
+
5870
6016
  pi.registerCommand("permission-status", {
5871
- description: "Show the active runtime permission profile and guard boundaries",
6017
+ description: "Legacy alias for /permission status",
5872
6018
  handler: async (_args, ctx) => {
5873
- const profile = loadProfileFromContext(ctx);
5874
- const permissionProfile = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
5875
- ctx.ui.notify(`Piagent permission profile: ${permissionProfile.mode}`, permissionProfile.mode === "trusted-full-access" ? "warning" : "info");
5876
- emitPermissionStatus(ctx, permissionProfile);
6019
+ emitCurrentPermissionStatus(ctx);
5877
6020
  }
5878
6021
  });
5879
6022
 
@@ -6161,7 +6304,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
6161
6304
  `profile: ${applied.mode ?? profileName}`,
6162
6305
  "updated: .pi/piagent-profile.json",
6163
6306
  "updated: .pi/piagent-profile.lock.json",
6164
- `projectContext: ${projectContextExists ? "exists" : "missing"}${projectContextExists ? "" : " — run /onboard-project"}`
6307
+ `projectContext: ${projectContextExists ? "exists" : "missing"}${projectContextExists ? "" : " — run /onboard"}`
6165
6308
  ].join("\n"),
6166
6309
  display: true,
6167
6310
  details: {
@@ -6220,27 +6363,39 @@ export default function piagentGuard(pi: ExtensionAPI) {
6220
6363
  }
6221
6364
  });
6222
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
+
6223
6381
  pi.registerCommand("piagent-memory", {
6224
- description: "Show project memory policy and available memory files",
6382
+ description: "Legacy alias for /memory",
6225
6383
  handler: async (_args, ctx) => {
6226
- const profile = loadProfileFromContext(ctx);
6227
- const settings = resolveMemorySettings(profile);
6228
- ctx.ui.notify(`Project memory: ${settings.enabled ? settings.mode : "off"}`, "info");
6229
- pi.sendMessage(
6230
- {
6231
- customType: "piagent-memory-status",
6232
- content: [
6233
- `memory: ${settings.enabled ? settings.mode : "off"}`,
6234
- `scope: ${settings.scope}`,
6235
- `summary: ${settings.summaryFile}`,
6236
- `handbook: ${settings.handbookFile}`,
6237
- `writePolicy: ${settings.writePolicy}`
6238
- ].join("\n"),
6239
- display: true,
6240
- details: settings
6241
- },
6242
- { triggerTurn: false }
6243
- );
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);
6244
6399
  }
6245
6400
  });
6246
6401
 
@@ -6452,54 +6607,154 @@ export default function piagentGuard(pi: ExtensionAPI) {
6452
6607
  }
6453
6608
  registerMcpCommand();
6454
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
+
6455
6753
  pi.registerCommand("context-index", {
6456
- description: "Show or search the compact project context index without a model follow-up",
6754
+ description: "Legacy alias for /context index/search",
6457
6755
  handler: async (args, ctx) => {
6458
6756
  const raw = String(args ?? "").trim();
6459
- const profile = loadProfileFromContext(ctx);
6460
- if (/^search\s+/i.test(raw)) {
6461
- const query = raw.replace(/^search\s+/i, "").trim();
6462
- let matches: Array<{ id: string; kind: string; label: string; match: string }> = [];
6463
- let error: string | undefined;
6464
- try {
6465
- matches = query ? searchContextIndex(ctx.cwd, profile, query, 8) : [];
6466
- } catch (caught) {
6467
- error = caught instanceof Error ? caught.message : String(caught);
6468
- }
6469
- pi.sendMessage(
6470
- {
6471
- customType: "piagent-context-index-search",
6472
- content: error
6473
- ? `Context index search failed: ${error}`
6474
- : matches.length
6475
- ? matches.map((match) => `${match.id} [${match.kind}] ${match.label}: ${match.match}`).join("\n")
6476
- : "No context index matches.",
6477
- display: true,
6478
- details: { query, matches, error }
6479
- },
6480
- { triggerTurn: false }
6481
- );
6482
- return;
6483
- }
6484
- const status = buildContextIndexStatus(ctx.cwd, profile);
6485
- ctx.ui.notify(`Context index: ${status.exists ? `${status.nodes} nodes` : "missing"}`, status.warnings.length ? "warning" : "info");
6486
- pi.sendMessage(
6487
- {
6488
- customType: "piagent-context-index-status",
6489
- content: [
6490
- `contextIndex: ${status.enabled ? "enabled" : "off"}`,
6491
- `path: ${status.path} (${status.exists ? "exists" : "missing"})`,
6492
- `nodes: ${status.nodes}`,
6493
- `edges: ${status.edges}`,
6494
- `citations: ${status.citations}`,
6495
- `updatedAt: ${status.updatedAt ?? "never"}`,
6496
- `warnings: ${status.warnings.join("; ") || "none"}`
6497
- ].join("\n"),
6498
- display: true,
6499
- details: status
6500
- },
6501
- { triggerTurn: false }
6502
- );
6757
+ await runPiagentContextNamespace(raw ? raw : "index", ctx);
6503
6758
  }
6504
6759
  });
6505
6760
 
@@ -6543,117 +6798,701 @@ export default function piagentGuard(pi: ExtensionAPI) {
6543
6798
  }
6544
6799
  });
6545
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
+
6546
7038
  pi.registerCommand("piagent-usage", {
6547
- description: "Show live context usage, session file, and token/cost follow-up commands",
6548
- handler: async (_args, ctx) => {
6549
- const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
6550
- const context = snapshot.contextUsage
6551
- ? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
6552
- : "context unavailable";
6553
- ctx.ui.notify(`Piagent usage: ${context}`, "info");
6554
- pi.sendMessage(
6555
- {
6556
- customType: "piagent-usage-snapshot",
6557
- content: formatUsageSnapshot(snapshot),
6558
- display: true,
6559
- details: snapshot
6560
- },
6561
- { triggerTurn: false }
6562
- );
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);
6563
7059
  }
6564
7060
  });
6565
7061
 
6566
7062
  pi.registerCommand("piagent-logs", {
6567
- description: "Show compact log policy and recent captured large tool outputs",
7063
+ description: "Legacy alias for /usage logs",
6568
7064
  handler: async (_args, ctx) => {
6569
- const captures = readRecentToolResultCaptures(ctx.cwd, 5);
6570
- ctx.ui.notify(`Piagent logs: ${captures.length ? `${captures.length} recent capture(s)` : "no compacted captures yet"}`, "info");
6571
- pi.sendMessage(
6572
- {
6573
- customType: "piagent-log-captures",
6574
- content: formatToolResultCaptureStatus(ctx.cwd, captures),
6575
- display: true,
6576
- details: {
6577
- policy: {
6578
- compactAboveChars: TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
6579
- compactAboveLines: TOOL_RESULT_COMPACT_LINE_THRESHOLD,
6580
- previewMaxChars: TOOL_RESULT_PREVIEW_MAX_CHARS,
6581
- captureMaxChars: TOOL_RESULT_CAPTURE_MAX_CHARS
6582
- },
6583
- captures
6584
- }
6585
- },
6586
- { triggerTurn: false }
6587
- );
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>");
6588
7092
  }
6589
7093
  });
6590
7094
 
6591
7095
  pi.registerCommand("setname", {
6592
- description: "Set the current Pi session name for reports and resume lists",
7096
+ description: "Legacy alias for /name",
6593
7097
  handler: async (args, ctx) => {
6594
- const name = cleanSessionNameInput(String(args ?? ""));
6595
- if (!name) {
6596
- ctx.ui.notify("Usage: /setname <task/session name>", "warning");
6597
- return;
6598
- }
6599
- const previousName = currentSessionName(ctx);
6600
- pi.setSessionName(name);
6601
- appendSessionTrace(pi, {
6602
- event: "session_name_set",
6603
- previousName: previousName || undefined,
6604
- sessionName: name
6605
- });
6606
- ctx.ui.notify(`Session name set: ${name}`, "info");
6607
- pi.sendMessage(
6608
- {
6609
- customType: "piagent-session-name-set",
6610
- content: `sessionName: ${name}`,
6611
- display: true,
6612
- details: { sessionName: name, previousName: previousName || undefined }
6613
- },
6614
- { triggerTurn: false }
6615
- );
7098
+ setSessionNameFromCommand(ctx, String(args ?? ""), "/setname <task/session name>");
6616
7099
  }
6617
7100
  });
6618
7101
 
6619
7102
  pi.registerCommand("task-preflight", {
6620
- description: "Check context health before a large task; optionally compact",
7103
+ description: "Legacy alias for /context preflight; add compact to compact",
6621
7104
  handler: async (args, ctx) => {
6622
7105
  const raw = String(args ?? "").trim();
6623
- const workflow = raw.match(/\b(?:scout|be-to-fe|review|plan|platform-improve|task)\b/i)?.[0]?.toLowerCase() ?? "task";
6624
- const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
6625
- const preflight = buildContextPreflight(snapshot, workflow, raw.length);
6626
- const context = snapshot.contextUsage
6627
- ? `${formatCount(snapshot.contextUsage.tokens)} / ${formatCount(snapshot.contextUsage.contextWindow)} (${formatPercent(snapshot.contextUsage.percent)})`
6628
- : "context unavailable";
6629
- ctx.ui.notify(`Task preflight: ${preflight.recommendation}; ${context}`, preflight.recommendation === "ok" ? "info" : "warning");
6630
-
6631
- if (/\bcompact\b/i.test(raw)) {
6632
- ctx.compact({
6633
- customInstructions: [
6634
- "Preserve current task decisions, project constraints, changed files, verify commands, open blockers, and next action.",
6635
- "Drop repeated mandatory-flow boilerplate and stale exploration details.",
6636
- "After compaction, required project context must be re-read from current repository files before implementation."
6637
- ].join("\n")
6638
- });
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;
6639
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
+ }
6640
7228
 
6641
- pi.sendMessage(
6642
- {
6643
- customType: "piagent-task-preflight",
6644
- content: formatContextPreflight(preflight, snapshot),
6645
- display: true,
6646
- details: preflight
6647
- },
6648
- { triggerTurn: false }
6649
- );
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);
6650
7489
  }
6651
7490
  });
6652
7491
 
6653
7492
  async function startFreshWorkflow(workflow: "task" | "scout" | "be-to-fe", args: string, ctx: any) {
6654
7493
  const request = String(args ?? "").trim();
6655
7494
  if (!request) {
6656
- ctx.ui.notify(`Usage: /fresh-${workflow} <request>`, "warning");
7495
+ ctx.ui.notify(`Usage: /fresh ${workflow} <request>`, "warning");
6657
7496
  return;
6658
7497
  }
6659
7498
 
@@ -6670,22 +7509,48 @@ export default function piagentGuard(pi: ExtensionAPI) {
6670
7509
  }
6671
7510
  }
6672
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
+
6673
7538
  pi.registerCommand("fresh-task", {
6674
- description: "Start a fresh governed session and run /task with the request",
7539
+ description: "Legacy alias for /fresh task",
6675
7540
  handler: async (args, ctx) => {
6676
7541
  await startFreshWorkflow("task", String(args ?? ""), ctx);
6677
7542
  }
6678
7543
  });
6679
7544
 
6680
7545
  pi.registerCommand("fresh-scout", {
6681
- description: "Start a fresh governed session and run /scout read-only with the request",
7546
+ description: "Legacy alias for /fresh scout",
6682
7547
  handler: async (args, ctx) => {
6683
7548
  await startFreshWorkflow("scout", String(args ?? ""), ctx);
6684
7549
  }
6685
7550
  });
6686
7551
 
6687
7552
  pi.registerCommand("fresh-be-to-fe", {
6688
- description: "Start a fresh governed session and run /be-to-fe with the request",
7553
+ description: "Legacy alias for /fresh be-to-fe",
6689
7554
  handler: async (args, ctx) => {
6690
7555
  await startFreshWorkflow("be-to-fe", String(args ?? ""), ctx);
6691
7556
  }