@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.
- package/CHANGELOG.md +18 -0
- package/README.md +34 -27
- package/catalog/capabilities.json +8 -8
- package/docs/auto-delegation-policy.md +4 -4
- package/docs/command-reference-vietnamese.md +119 -85
- package/docs/context-window-policy.md +7 -7
- package/docs/distribution-standard.md +27 -10
- package/docs/herdr-workflow.md +3 -2
- package/docs/memory-policy.md +6 -3
- package/docs/operator-manual-vietnamese.md +81 -72
- package/docs/package-architecture-notes.md +6 -6
- package/docs/project-adapters.md +4 -4
- package/docs/project-onboarding.md +12 -11
- package/docs/publishing-for-teams.md +2 -2
- package/docs/quickstart-vietnamese.md +25 -23
- package/docs/release-install-policy.md +6 -6
- package/docs/runtime-harness-standard.md +7 -7
- package/docs/runtime-policy-design.md +1 -1
- package/docs/runtime-quality-baseline.md +1 -1
- package/docs/security-threat-model.md +1 -1
- package/docs/subagent-orchestration-capabilities.md +3 -3
- package/docs/subagents-and-multiagent.md +4 -4
- package/docs/task-implementation-contract.md +4 -4
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +22 -18
- package/docs/usage-observability.md +13 -12
- package/docs/workflow-recipes.md +12 -9
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +19 -15
- package/docs-site/content/commands.html +18 -14
- package/docs-site/content/permissions.html +11 -11
- package/docs-site/content/quickstart.html +12 -11
- package/docs-site/content/team.html +1 -1
- package/docs-site/content/workflows.html +17 -17
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +12 -12
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +13 -12
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +2 -2
- package/docs-site/workflows.html +18 -18
- package/package.json +1 -1
- package/packages/piagent-core/README.md +27 -27
- package/packages/piagent-core/extensions/piagent-guard.ts +1055 -190
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/prompts/be-to-fe.md +3 -3
- package/packages/piagent-core/prompts/discuss.md +2 -2
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +1 -1
- package/packages/piagent-core/prompts/pr.md +1 -1
- package/packages/piagent-core/prompts/review.md +1 -1
- package/packages/piagent-core/prompts/scout.md +2 -2
- package/packages/piagent-core/prompts/task.md +2 -2
- package/scripts/init-project.sh +4 -3
- package/scripts/install-global.sh +47 -1
- package/scripts/profile-doctor.sh +1 -1
- package/scripts/setup.sh +4 -3
- package/scripts/team-doctor.sh +6 -8
- package/scripts/verify-local.sh +18 -17
- package/templates/project/.pi/context-index.json +2 -2
- package/templates/project/.pi/project-context.md +3 -3
- package/templates/project/AGENTS.md +4 -4
- package/packages/piagent-core/prompts/memory-policy.md +0 -52
- package/packages/piagent-core/prompts/model-options.md +0 -68
- package/packages/piagent-core/prompts/onboard-project.md +0 -147
- package/packages/piagent-core/prompts/piagent-commands.md +0 -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
|
|
1529
|
+
"- Model/pass: run `/onboard` after Pi login and model selection",
|
|
1530
1530
|
"- Scope: pending",
|
|
1531
1531
|
"",
|
|
1532
|
-
"Run `/onboard
|
|
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
|
|
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 /
|
|
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
|
|
3799
|
-
"/
|
|
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
|
|
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
|
|
3916
|
+
return `/fresh ${workflow} Read task intake from ${intakePath}. ${reason}`;
|
|
3910
3917
|
}
|
|
3911
|
-
return `/fresh
|
|
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
|
|
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
|
|
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
|
-
|
|
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: "
|
|
6017
|
+
description: "Legacy alias for /permission status",
|
|
5872
6018
|
handler: async (_args, ctx) => {
|
|
5873
|
-
|
|
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
|
-
|
|
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: "
|
|
6382
|
+
description: "Legacy alias for /memory",
|
|
6225
6383
|
handler: async (_args, ctx) => {
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
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: "
|
|
6754
|
+
description: "Legacy alias for /context index/search",
|
|
6457
6755
|
handler: async (args, ctx) => {
|
|
6458
6756
|
const raw = String(args ?? "").trim();
|
|
6459
|
-
|
|
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: "
|
|
6548
|
-
|
|
6549
|
-
const
|
|
6550
|
-
const
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
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: "
|
|
7063
|
+
description: "Legacy alias for /usage logs",
|
|
6568
7064
|
handler: async (_args, ctx) => {
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
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: "
|
|
7096
|
+
description: "Legacy alias for /name",
|
|
6593
7097
|
handler: async (args, ctx) => {
|
|
6594
|
-
|
|
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: "
|
|
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
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
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
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
}
|