@piagent/platform 1.2.4 → 1.2.6
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 +26 -0
- package/README.md +3 -2
- package/catalog/capabilities.json +4 -4
- package/docs/command-reference-vietnamese.md +9 -8
- package/docs/context-engine.md +177 -0
- package/docs/context-window-policy.md +13 -8
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +4 -0
- package/docs/quality-benchmark.md +14 -1
- package/docs/release-install-policy.md +6 -6
- package/docs/runtime-harness-standard.md +16 -7
- package/docs/security-threat-model.md +1 -1
- package/docs/task-lifecycle-vietnamese.md +4 -4
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +6 -0
- package/docs-site/architecture.html +4 -3
- package/docs-site/commands.html +2 -1
- package/docs-site/content/context-engine.html +47 -0
- package/docs-site/context-engine.html +171 -0
- package/docs-site/index.html +2 -1
- package/docs-site/mcp-approval.html +2 -1
- package/docs-site/mcp-auth.html +2 -1
- package/docs-site/mcp-servers.html +2 -1
- package/docs-site/mcp.html +2 -1
- package/docs-site/permissions.html +2 -1
- package/docs-site/profiles.html +2 -1
- package/docs-site/quickstart.html +2 -1
- package/docs-site/runtime.html +4 -3
- package/docs-site/scope.html +2 -1
- package/docs-site/security.html +2 -1
- package/docs-site/team.html +2 -1
- package/docs-site/workflows.html +2 -1
- package/package.json +3 -2
- package/packages/piagent-core/README.md +8 -2
- package/packages/piagent-core/capabilities/capability-core.js +4 -0
- package/packages/piagent-core/extensions/context-engine.js +1175 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +785 -17
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +2 -0
- package/packages/piagent-core/prompts/scout.md +10 -24
- package/packages/piagent-core/prompts/task.md +25 -45
- package/scripts/build-docs-site.mjs +6 -0
- package/scripts/context-engine.mjs +115 -0
- package/scripts/install-global.sh +14 -1
- package/scripts/piagent-cli.mjs +2 -1
- package/scripts/quality-benchmark.sh +71 -1
- package/scripts/update-global.mjs +7 -1
- package/scripts/verify-local.sh +8 -0
|
@@ -73,6 +73,18 @@ import {
|
|
|
73
73
|
normalizeActionToken,
|
|
74
74
|
normalizeShellCommandForPolicy
|
|
75
75
|
} from "./guard-shell-analysis.ts";
|
|
76
|
+
import {
|
|
77
|
+
appendContextTelemetry,
|
|
78
|
+
buildContextEfficiencyReport,
|
|
79
|
+
buildContextIndexV2,
|
|
80
|
+
buildContextPack,
|
|
81
|
+
buildTestImpact,
|
|
82
|
+
classifyContextTask,
|
|
83
|
+
contextIndexV2Status,
|
|
84
|
+
estimateContextTokens,
|
|
85
|
+
searchContextIndexV2,
|
|
86
|
+
toolResultFingerprint
|
|
87
|
+
} from "./context-engine.js";
|
|
76
88
|
|
|
77
89
|
import type { ActionClassification } from "./guard-shell-analysis.ts";
|
|
78
90
|
|
|
@@ -144,6 +156,51 @@ const CONTEXT_INDEX_EDGE_KINDS = ["uses_tech", "depends_on", "verified_by", "pro
|
|
|
144
156
|
const CONTEXT_INDEX_FILE = ".pi/context-index.json";
|
|
145
157
|
const TECH_STACK_MANIFEST_FILE = ".pi/tech-stack.json";
|
|
146
158
|
const TECH_CONTEXT_DIR = ".pi/tech-context";
|
|
159
|
+
const PIAGENT_TOOL_GROUPS = {
|
|
160
|
+
loader: ["piagent_tools"],
|
|
161
|
+
governance: [
|
|
162
|
+
"piagent_context",
|
|
163
|
+
"piagent_context_preflight",
|
|
164
|
+
"piagent_task_start",
|
|
165
|
+
"piagent_context_record",
|
|
166
|
+
"piagent_verify_record",
|
|
167
|
+
"piagent_trace_record",
|
|
168
|
+
"piagent_task_gate_check"
|
|
169
|
+
],
|
|
170
|
+
policy: [
|
|
171
|
+
"piagent_permission_status",
|
|
172
|
+
"piagent_exec_policy_check",
|
|
173
|
+
"piagent_tool_policy_check"
|
|
174
|
+
],
|
|
175
|
+
retrieval: [
|
|
176
|
+
"piagent_context_engine",
|
|
177
|
+
"piagent_context_budget",
|
|
178
|
+
"piagent_context_index_status",
|
|
179
|
+
"piagent_context_index_search"
|
|
180
|
+
],
|
|
181
|
+
knowledge: [
|
|
182
|
+
"piagent_memory_status",
|
|
183
|
+
"piagent_memory_search",
|
|
184
|
+
"piagent_memory_citation_record",
|
|
185
|
+
"piagent_document_read",
|
|
186
|
+
"piagent_source_checkout",
|
|
187
|
+
"piagent_orchestration_policy"
|
|
188
|
+
],
|
|
189
|
+
onboarding: [
|
|
190
|
+
"piagent_profile_options",
|
|
191
|
+
"piagent_profile_apply",
|
|
192
|
+
"piagent_profile_tech_options",
|
|
193
|
+
"piagent_profile_tech_apply",
|
|
194
|
+
"piagent_profile_tech_context_record",
|
|
195
|
+
"piagent_project_onboarding_record",
|
|
196
|
+
"piagent_context_index_record",
|
|
197
|
+
"piagent_memory_note"
|
|
198
|
+
],
|
|
199
|
+
usage: ["piagent_usage_snapshot"]
|
|
200
|
+
} as const;
|
|
201
|
+
type PiagentToolGroup = keyof typeof PIAGENT_TOOL_GROUPS;
|
|
202
|
+
const PIAGENT_TOOL_ORDER = Object.values(PIAGENT_TOOL_GROUPS).flat();
|
|
203
|
+
const PIAGENT_TOOL_NAMES = new Set<string>(PIAGENT_TOOL_ORDER);
|
|
147
204
|
|
|
148
205
|
const TECH_OPTIONS: TechOption[] = [
|
|
149
206
|
{ id: "nextjs", label: "Next.js", role: "frontend", description: "React framework with App Router/SSR/static rendering patterns.", context7Query: "next.js", topics: ["app-router", "routing", "data-fetching", "server-components"] },
|
|
@@ -327,6 +384,8 @@ const DEFAULT_POLICY: BasePolicy = {
|
|
|
327
384
|
toolRegistry: {
|
|
328
385
|
defaultMode: "advisory",
|
|
329
386
|
alwaysAllowedTools: [
|
|
387
|
+
"piagent_tools",
|
|
388
|
+
"piagent_context_engine",
|
|
330
389
|
"piagent_context",
|
|
331
390
|
"piagent_permission_status",
|
|
332
391
|
"piagent_exec_policy_check",
|
|
@@ -1799,6 +1858,25 @@ function normalizeWorkPlanSteps(values: unknown): WorkPlanStep[] {
|
|
|
1799
1858
|
}
|
|
1800
1859
|
|
|
1801
1860
|
function defaultWorkPlan(summary: string, riskLane: TaskContract["riskLane"]): WorkPlanStep[] {
|
|
1861
|
+
if (riskLane === "tiny") {
|
|
1862
|
+
return [
|
|
1863
|
+
{
|
|
1864
|
+
id: "implement",
|
|
1865
|
+
title: summary.slice(0, 160) || "Implement the approved bounded change.",
|
|
1866
|
+
role: "parent",
|
|
1867
|
+
mode: "single-writer",
|
|
1868
|
+
status: "pending"
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
id: "verify",
|
|
1872
|
+
title: "Review the changed files and record exact verification evidence.",
|
|
1873
|
+
role: "parent",
|
|
1874
|
+
mode: "review",
|
|
1875
|
+
status: "pending",
|
|
1876
|
+
dependsOn: ["implement"]
|
|
1877
|
+
}
|
|
1878
|
+
];
|
|
1879
|
+
}
|
|
1802
1880
|
const steps: WorkPlanStep[] = [
|
|
1803
1881
|
{
|
|
1804
1882
|
id: "plan",
|
|
@@ -1817,7 +1895,7 @@ function defaultWorkPlan(summary: string, riskLane: TaskContract["riskLane"]): W
|
|
|
1817
1895
|
},
|
|
1818
1896
|
{
|
|
1819
1897
|
id: "review",
|
|
1820
|
-
title:
|
|
1898
|
+
title: "Run explicit review lenses against the diff and verify evidence.",
|
|
1821
1899
|
role: "piagent-reviewer",
|
|
1822
1900
|
mode: "review",
|
|
1823
1901
|
status: "pending",
|
|
@@ -4205,16 +4283,161 @@ function checkoutReferenceRepo(repoRef: string, forceUpdate = false): ReferenceR
|
|
|
4205
4283
|
return { host, owner, repo, cloneUrl, checkoutPath, commit, fetched };
|
|
4206
4284
|
}
|
|
4207
4285
|
|
|
4286
|
+
function toolGroupsForPrompt(prompt: string): PiagentToolGroup[] {
|
|
4287
|
+
const signal = classifyContextTask(prompt);
|
|
4288
|
+
const lower = prompt.toLowerCase();
|
|
4289
|
+
const groups = new Set<PiagentToolGroup>(["loader"]);
|
|
4290
|
+
|
|
4291
|
+
if (signal.workflow === "usage") {
|
|
4292
|
+
groups.add("usage");
|
|
4293
|
+
return [...groups];
|
|
4294
|
+
}
|
|
4295
|
+
if (signal.workflow === "onboard") {
|
|
4296
|
+
groups.add("governance");
|
|
4297
|
+
groups.add("policy");
|
|
4298
|
+
groups.add("retrieval");
|
|
4299
|
+
groups.add("knowledge");
|
|
4300
|
+
groups.add("onboarding");
|
|
4301
|
+
return [...groups];
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
groups.add("governance");
|
|
4305
|
+
groups.add("policy");
|
|
4306
|
+
if (
|
|
4307
|
+
signal.lane !== "tiny"
|
|
4308
|
+
|| ["scout", "review", "plan", "release"].includes(signal.workflow)
|
|
4309
|
+
|| /\b(context|document|memory|repository|source|subagent|vendor)\b/.test(lower)
|
|
4310
|
+
) {
|
|
4311
|
+
groups.add("retrieval");
|
|
4312
|
+
}
|
|
4313
|
+
if (
|
|
4314
|
+
signal.lane === "high-risk"
|
|
4315
|
+
|| ["scout", "review", "plan", "release"].includes(signal.workflow)
|
|
4316
|
+
|| /\b(document|memory|orchestration|source checkout|subagent|vendor)\b/.test(lower)
|
|
4317
|
+
) {
|
|
4318
|
+
groups.add("knowledge");
|
|
4319
|
+
}
|
|
4320
|
+
if (/\b(profile|tech stack|onboard|project context)\b/.test(lower)) groups.add("onboarding");
|
|
4321
|
+
if (/\b(token|usage|cost)\b/.test(lower)) groups.add("usage");
|
|
4322
|
+
return [...groups];
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
function compactSessionTask(cwd: string): TaskContract | undefined {
|
|
4326
|
+
const directory = path.join(stateRoot(cwd), "tasks");
|
|
4327
|
+
if (!fs.existsSync(directory)) return undefined;
|
|
4328
|
+
const candidates = fs.readdirSync(directory, { withFileTypes: true })
|
|
4329
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
4330
|
+
.map((entry) => {
|
|
4331
|
+
const absolute = path.join(directory, entry.name);
|
|
4332
|
+
return { absolute, mtimeMs: fs.statSync(absolute).mtimeMs };
|
|
4333
|
+
})
|
|
4334
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
4335
|
+
return candidates[0] ? readJsonFile<TaskContract>(candidates[0].absolute) : undefined;
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
function semanticCompactionInstructions(cwd: string): string {
|
|
4339
|
+
const task = compactSessionTask(cwd);
|
|
4340
|
+
const taskState = task
|
|
4341
|
+
? [
|
|
4342
|
+
`Current task: ${task.taskId} (${task.riskLane})`,
|
|
4343
|
+
`Goal: ${task.summary}`,
|
|
4344
|
+
`Acceptance: ${task.acceptanceCriteria.join("; ") || "not recorded"}`,
|
|
4345
|
+
`Scope: ${task.scope.join(", ") || "not recorded"}`,
|
|
4346
|
+
`Changed files: ${task.changedFiles.join(", ") || "none recorded"}`,
|
|
4347
|
+
`Verify commands: ${task.verifyCommands.join(" | ") || "not recorded"}`,
|
|
4348
|
+
`Outcome/blocker: ${task.trace.outcome}${task.trace.friction ? `; ${task.trace.friction}` : ""}`
|
|
4349
|
+
].join("\n")
|
|
4350
|
+
: "No persisted task contract was found. Derive the current goal from the most recent user request.";
|
|
4351
|
+
return [
|
|
4352
|
+
"Create a structured Piagent carry-over summary.",
|
|
4353
|
+
taskState,
|
|
4354
|
+
"",
|
|
4355
|
+
"Preserve:",
|
|
4356
|
+
"- current goal, acceptance criteria, explicit user decisions, and non-negotiable constraints",
|
|
4357
|
+
"- architecture facts and invariants verified from repository files",
|
|
4358
|
+
"- files changed, exact verification evidence, unresolved failures, blockers, and next action",
|
|
4359
|
+
"- citations or paths needed to re-read advisory context",
|
|
4360
|
+
"",
|
|
4361
|
+
"Discard:",
|
|
4362
|
+
"- superseded plans, repeated reads, raw tool logs, successful intermediate output, and speculative reasoning",
|
|
4363
|
+
"- full source excerpts that can be re-read from the repository",
|
|
4364
|
+
"",
|
|
4365
|
+
"Do not convert assumptions into facts. Mark unknowns explicitly. After compaction, re-read current files before editing."
|
|
4366
|
+
].join("\n");
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
function environmentFeatureEnabled(name: string, fallback = true): boolean {
|
|
4370
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
4371
|
+
if (!value) return fallback;
|
|
4372
|
+
return !["0", "false", "no", "off", "disabled"].includes(value);
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4208
4375
|
export default function piagentGuard(pi: ExtensionAPI) {
|
|
4209
4376
|
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
|
4210
4377
|
const policy = loadPolicy(extensionDir);
|
|
4211
4378
|
const bashResults = createBashResultLedger({ maxEntries: 300 });
|
|
4379
|
+
const dynamicToolsEnabled = environmentFeatureEnabled("PIAGENT_DYNAMIC_TOOLS");
|
|
4380
|
+
const contextTelemetryEnabled = environmentFeatureEnabled("PIAGENT_CONTEXT_TELEMETRY");
|
|
4381
|
+
const autoContextEnabled = environmentFeatureEnabled("PIAGENT_AUTO_CONTEXT");
|
|
4212
4382
|
// Advisory tool-registry verdicts are surfaced once per tool per session. A
|
|
4213
4383
|
// notice on every call would be constant noise for a tool used all day, and
|
|
4214
4384
|
// noise that is always present is read as background rather than as a warning.
|
|
4215
4385
|
const advisedTools = new Set<string>();
|
|
4386
|
+
const seenToolResults = new Map<string, { outputHash: string; recordedAt: string }>();
|
|
4387
|
+
const autoPackedPrompts = new Set<string>();
|
|
4388
|
+
|
|
4389
|
+
function telemetry(ctx: ExtensionContext, payload: Record<string, unknown>): void {
|
|
4390
|
+
if (!contextTelemetryEnabled) return;
|
|
4391
|
+
try {
|
|
4392
|
+
const safePayload = redactForStorage(payload) as Record<string, unknown>;
|
|
4393
|
+
appendContextTelemetry(ctx.cwd, {
|
|
4394
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
4395
|
+
sessionName: currentSessionName(ctx),
|
|
4396
|
+
model: `${ctx.model?.provider ?? "unknown"}/${ctx.model?.id ?? "unknown"}`,
|
|
4397
|
+
thinkingLevel: String(pi.getThinkingLevel()),
|
|
4398
|
+
...safePayload
|
|
4399
|
+
});
|
|
4400
|
+
} catch {
|
|
4401
|
+
// Telemetry is local observability. It must never block the agent loop.
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
function activateToolGroups(ctx: ExtensionContext, groups: PiagentToolGroup[], additive = false): string[] {
|
|
4406
|
+
const current = pi.getActiveTools();
|
|
4407
|
+
if (!dynamicToolsEnabled) return current;
|
|
4408
|
+
const selected = new Set<string>();
|
|
4409
|
+
if (additive) {
|
|
4410
|
+
for (const toolName of current) selected.add(toolName);
|
|
4411
|
+
} else {
|
|
4412
|
+
for (const toolName of current) {
|
|
4413
|
+
if (!PIAGENT_TOOL_NAMES.has(toolName)) selected.add(toolName);
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
const normalizedGroups = [...new Set<PiagentToolGroup>(["loader", ...groups])];
|
|
4417
|
+
for (const toolName of PIAGENT_TOOL_ORDER) {
|
|
4418
|
+
if (normalizedGroups.some((group) => PIAGENT_TOOL_GROUPS[group].includes(toolName as never))) {
|
|
4419
|
+
selected.add(toolName);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
const ordered = [
|
|
4423
|
+
...current.filter((toolName) => selected.has(toolName) && !PIAGENT_TOOL_NAMES.has(toolName)),
|
|
4424
|
+
...PIAGENT_TOOL_ORDER.filter((toolName) => selected.has(toolName))
|
|
4425
|
+
];
|
|
4426
|
+
const unchanged = ordered.length === current.length && ordered.every((toolName, index) => toolName === current[index]);
|
|
4427
|
+
if (!unchanged) pi.setActiveTools(ordered);
|
|
4428
|
+
telemetry(ctx, {
|
|
4429
|
+
event: "tool_activation",
|
|
4430
|
+
groups: normalizedGroups,
|
|
4431
|
+
additive,
|
|
4432
|
+
previousCount: current.length,
|
|
4433
|
+
activeCount: ordered.length,
|
|
4434
|
+
piagentTools: ordered.filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName))
|
|
4435
|
+
});
|
|
4436
|
+
return ordered;
|
|
4437
|
+
}
|
|
4216
4438
|
|
|
4217
4439
|
pi.on("session_start", async (_event, ctx) => {
|
|
4440
|
+
activateToolGroups(ctx, []);
|
|
4218
4441
|
const projectTrusted = ctx.isProjectTrusted();
|
|
4219
4442
|
const explicitProfile = Boolean(process.env.PIAGENT_PROFILE?.trim());
|
|
4220
4443
|
const profile = loadProfile(ctx.cwd, projectTrusted);
|
|
@@ -4255,6 +4478,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4255
4478
|
// the first thing the operator reads.
|
|
4256
4479
|
const updateNotice = updateAvailabilityNotice();
|
|
4257
4480
|
if (updateNotice) ctx.ui.notify(updateNotice, "info");
|
|
4481
|
+
let engineStatus: unknown;
|
|
4482
|
+
try {
|
|
4483
|
+
engineStatus = await contextIndexV2Status(ctx.cwd);
|
|
4484
|
+
} catch (error) {
|
|
4485
|
+
engineStatus = { exists: false, error: error instanceof Error ? error.message : String(error) };
|
|
4486
|
+
}
|
|
4487
|
+
telemetry(ctx, { event: "session_start", activeTools: pi.getActiveTools().length, index: engineStatus });
|
|
4258
4488
|
});
|
|
4259
4489
|
|
|
4260
4490
|
pi.on("input", async (event, ctx) => {
|
|
@@ -4263,6 +4493,18 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4263
4493
|
if (/^\/piagent-workflow\b/i.test(text)) {
|
|
4264
4494
|
return { action: "transform", text: text.replace(/^\/piagent-workflow\b/i, "/workflow") };
|
|
4265
4495
|
}
|
|
4496
|
+
const taskSignal = classifyContextTask(text);
|
|
4497
|
+
activateToolGroups(ctx, toolGroupsForPrompt(text));
|
|
4498
|
+
telemetry(ctx, {
|
|
4499
|
+
event: "user_input",
|
|
4500
|
+
source: event.source,
|
|
4501
|
+
promptHash: taskSignal.promptHash,
|
|
4502
|
+
promptChars: taskSignal.promptChars,
|
|
4503
|
+
workflow: taskSignal.workflow,
|
|
4504
|
+
riskLane: taskSignal.lane,
|
|
4505
|
+
explicitPathCount: taskSignal.paths.length,
|
|
4506
|
+
termCount: taskSignal.terms.length
|
|
4507
|
+
});
|
|
4266
4508
|
|
|
4267
4509
|
const imageAttachment = attachLocalImagesFromText(text, event.images, ctx.cwd);
|
|
4268
4510
|
if (imageAttachment?.attached.length) {
|
|
@@ -4307,6 +4549,128 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4307
4549
|
: { action: "transform", text: command };
|
|
4308
4550
|
});
|
|
4309
4551
|
|
|
4552
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
4553
|
+
const active = new Set<string>(pi.getActiveTools() as string[]);
|
|
4554
|
+
const toolMetadata = pi.getAllTools()
|
|
4555
|
+
.filter((tool) => active.has(tool.name))
|
|
4556
|
+
.map((tool) => ({
|
|
4557
|
+
name: tool.name,
|
|
4558
|
+
description: tool.description,
|
|
4559
|
+
parameters: tool.parameters,
|
|
4560
|
+
promptGuidelines: tool.promptGuidelines
|
|
4561
|
+
}));
|
|
4562
|
+
const toolSchemaTokens = estimateContextTokens(JSON.stringify(toolMetadata));
|
|
4563
|
+
const systemPromptTokens = estimateContextTokens(event.systemPrompt);
|
|
4564
|
+
const query = looksLikeGovernedBoilerplate(event.prompt) ? extractTaskRequest(event.prompt) : event.prompt.trim();
|
|
4565
|
+
const signal = classifyContextTask(query);
|
|
4566
|
+
telemetry(ctx, {
|
|
4567
|
+
event: "agent_prompt",
|
|
4568
|
+
promptHash: signal.promptHash,
|
|
4569
|
+
promptChars: signal.promptChars,
|
|
4570
|
+
workflow: signal.workflow,
|
|
4571
|
+
riskLane: signal.lane,
|
|
4572
|
+
activeTools: active.size,
|
|
4573
|
+
activePiagentTools: [...active].filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName)).length,
|
|
4574
|
+
toolSchemaTokens,
|
|
4575
|
+
systemPromptTokens,
|
|
4576
|
+
systemPromptHash: crypto.createHash("sha256").update(event.systemPrompt).digest("hex"),
|
|
4577
|
+
contextUsage: ctx.getContextUsage()
|
|
4578
|
+
});
|
|
4579
|
+
|
|
4580
|
+
if (
|
|
4581
|
+
query.length < 20
|
|
4582
|
+
|| signal.workflow === "usage"
|
|
4583
|
+
|| !autoContextEnabled
|
|
4584
|
+
|| autoPackedPrompts.has(signal.promptHash)
|
|
4585
|
+
|| !active.has("piagent_context_engine")
|
|
4586
|
+
) {
|
|
4587
|
+
return;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
autoPackedPrompts.add(signal.promptHash);
|
|
4591
|
+
if (autoPackedPrompts.size > 50) autoPackedPrompts.delete(autoPackedPrompts.values().next().value as string);
|
|
4592
|
+
try {
|
|
4593
|
+
const status = await contextIndexV2Status(ctx.cwd);
|
|
4594
|
+
if (!status.exists || status.stale) {
|
|
4595
|
+
telemetry(ctx, {
|
|
4596
|
+
event: "context_pack",
|
|
4597
|
+
queryHash: signal.promptHash,
|
|
4598
|
+
confidence: "none",
|
|
4599
|
+
candidates: 0,
|
|
4600
|
+
selected: 0,
|
|
4601
|
+
skipped: status.exists ? "stale-index" : "missing-index"
|
|
4602
|
+
});
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
const budgetTokens = signal.lane === "high-risk" ? 1_200 : signal.lane === "tiny" ? 500 : 900;
|
|
4606
|
+
const pack = await buildContextPack(ctx.cwd, query, { budgetTokens, includeCode: false, limit: 12 });
|
|
4607
|
+
telemetry(ctx, {
|
|
4608
|
+
event: "context_pack",
|
|
4609
|
+
queryHash: pack.queryHash,
|
|
4610
|
+
confidence: pack.confidence,
|
|
4611
|
+
candidates: pack.candidates,
|
|
4612
|
+
selected: pack.selected.length,
|
|
4613
|
+
estimatedTokens: pack.estimatedTokens,
|
|
4614
|
+
selectedPaths: pack.selected.map((item) => item.path),
|
|
4615
|
+
finderRecommended: pack.finderRecommended
|
|
4616
|
+
});
|
|
4617
|
+
if (!["medium", "high"].includes(pack.confidence) || pack.selected.length === 0) return;
|
|
4618
|
+
return {
|
|
4619
|
+
message: {
|
|
4620
|
+
customType: "piagent-context-pack-v2",
|
|
4621
|
+
content: pack.text,
|
|
4622
|
+
display: false,
|
|
4623
|
+
details: {
|
|
4624
|
+
schemaVersion: 2,
|
|
4625
|
+
queryHash: pack.queryHash,
|
|
4626
|
+
confidence: pack.confidence,
|
|
4627
|
+
estimatedTokens: pack.estimatedTokens,
|
|
4628
|
+
paths: pack.selected.map((item) => item.path)
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
};
|
|
4632
|
+
} catch (error) {
|
|
4633
|
+
telemetry(ctx, {
|
|
4634
|
+
event: "context_pack",
|
|
4635
|
+
queryHash: signal.promptHash,
|
|
4636
|
+
confidence: "none",
|
|
4637
|
+
candidates: 0,
|
|
4638
|
+
selected: 0,
|
|
4639
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4640
|
+
});
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4643
|
+
|
|
4644
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
4645
|
+
const message = event.message as unknown as { usage?: unknown; stopReason?: unknown; role?: unknown };
|
|
4646
|
+
telemetry(ctx, {
|
|
4647
|
+
event: "turn_end",
|
|
4648
|
+
turnIndex: event.turnIndex,
|
|
4649
|
+
toolResults: event.toolResults.length,
|
|
4650
|
+
role: message.role,
|
|
4651
|
+
stopReason: message.stopReason,
|
|
4652
|
+
usage: message.usage,
|
|
4653
|
+
contextUsage: ctx.getContextUsage()
|
|
4654
|
+
});
|
|
4655
|
+
});
|
|
4656
|
+
|
|
4657
|
+
pi.on("session_compact", async (event, ctx) => {
|
|
4658
|
+
telemetry(ctx, {
|
|
4659
|
+
event: "session_compact",
|
|
4660
|
+
reason: event.reason,
|
|
4661
|
+
willRetry: event.willRetry,
|
|
4662
|
+
fromExtension: event.fromExtension
|
|
4663
|
+
});
|
|
4664
|
+
});
|
|
4665
|
+
|
|
4666
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
4667
|
+
telemetry(ctx, {
|
|
4668
|
+
event: "session_shutdown",
|
|
4669
|
+
reason: event.reason,
|
|
4670
|
+
targetSessionFile: event.targetSessionFile ? path.basename(event.targetSessionFile) : undefined
|
|
4671
|
+
});
|
|
4672
|
+
});
|
|
4673
|
+
|
|
4310
4674
|
pi.on("tool_result", async (event, ctx) => {
|
|
4311
4675
|
const profile = loadProfileFromContext(ctx);
|
|
4312
4676
|
const pathPolicy = effectiveProtectedPaths(policy, profile);
|
|
@@ -4367,6 +4731,40 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4367
4731
|
resultChanged = true;
|
|
4368
4732
|
}
|
|
4369
4733
|
|
|
4734
|
+
const fingerprint = toolResultFingerprint(event.toolName, event.input, resultContent);
|
|
4735
|
+
const previousFingerprint = seenToolResults.get(fingerprint.key);
|
|
4736
|
+
const repeated = previousFingerprint?.outputHash === fingerprint.outputHash;
|
|
4737
|
+
seenToolResults.set(fingerprint.key, { outputHash: fingerprint.outputHash, recordedAt: nowIso() });
|
|
4738
|
+
if (seenToolResults.size > 500) seenToolResults.delete(seenToolResults.keys().next().value as string);
|
|
4739
|
+
if (repeated && ["read", "grep", "find", "ls"].includes(event.toolName) && fingerprint.outputChars > 0) {
|
|
4740
|
+
resultContent = [{
|
|
4741
|
+
type: "text",
|
|
4742
|
+
text: `[Piagent delta: unchanged ${event.toolName} result; ${fingerprint.outputChars} chars / ${fingerprint.outputLines} lines match the previous identical call.]`
|
|
4743
|
+
}];
|
|
4744
|
+
resultDetails = isPlainRecord(resultDetails)
|
|
4745
|
+
? {
|
|
4746
|
+
...resultDetails,
|
|
4747
|
+
piagentDelta: {
|
|
4748
|
+
unchanged: true,
|
|
4749
|
+
previousRecordedAt: previousFingerprint?.recordedAt,
|
|
4750
|
+
outputHash: fingerprint.outputHash,
|
|
4751
|
+
originalChars: fingerprint.outputChars,
|
|
4752
|
+
originalLines: fingerprint.outputLines
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
: {
|
|
4756
|
+
value: resultDetails,
|
|
4757
|
+
piagentDelta: {
|
|
4758
|
+
unchanged: true,
|
|
4759
|
+
previousRecordedAt: previousFingerprint?.recordedAt,
|
|
4760
|
+
outputHash: fingerprint.outputHash,
|
|
4761
|
+
originalChars: fingerprint.outputChars,
|
|
4762
|
+
originalLines: fingerprint.outputLines
|
|
4763
|
+
}
|
|
4764
|
+
};
|
|
4765
|
+
resultChanged = true;
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4370
4768
|
const captureCache = new Map<string, ToolResultCaptureSummary>();
|
|
4371
4769
|
const compactedContent = compactToolResultTextContent(ctx.cwd, event, ctx, resultContent, captureCache);
|
|
4372
4770
|
const compactionCaptures = [...compactedContent.captures];
|
|
@@ -4385,6 +4783,21 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4385
4783
|
resultDetails = attachToolResultCompactionDetails(resultDetails, compactionCaptures);
|
|
4386
4784
|
}
|
|
4387
4785
|
|
|
4786
|
+
telemetry(ctx, {
|
|
4787
|
+
event: "tool_result",
|
|
4788
|
+
toolName: event.toolName,
|
|
4789
|
+
inputHash: fingerprint.inputHash,
|
|
4790
|
+
outputHash: fingerprint.outputHash,
|
|
4791
|
+
outputChars: fingerprint.outputChars,
|
|
4792
|
+
outputLines: fingerprint.outputLines,
|
|
4793
|
+
repeated,
|
|
4794
|
+
compacted: compactionCaptures.length > 0,
|
|
4795
|
+
compactedCaptures: compactionCaptures.length,
|
|
4796
|
+
sensitiveValuesRedacted,
|
|
4797
|
+
isError: event.isError,
|
|
4798
|
+
usage: event.usage
|
|
4799
|
+
});
|
|
4800
|
+
|
|
4388
4801
|
if (resultChanged) {
|
|
4389
4802
|
return resultDetails === undefined
|
|
4390
4803
|
? { content: resultContent }
|
|
@@ -4393,6 +4806,15 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4393
4806
|
});
|
|
4394
4807
|
|
|
4395
4808
|
pi.on("tool_call", async (event, ctx) => {
|
|
4809
|
+
const callFingerprint = toolResultFingerprint(event.toolName, event.input, []);
|
|
4810
|
+
const target = extractLikelyPathFromInput(ctx.cwd, event.input as Record<string, unknown>);
|
|
4811
|
+
telemetry(ctx, {
|
|
4812
|
+
event: "tool_call",
|
|
4813
|
+
toolName: event.toolName,
|
|
4814
|
+
inputHash: callFingerprint.inputHash,
|
|
4815
|
+
targetHash: target ? crypto.createHash("sha256").update(target).digest("hex") : undefined,
|
|
4816
|
+
targetPath: target ? redactText(target) : undefined
|
|
4817
|
+
});
|
|
4396
4818
|
const projectTrusted = ctx.isProjectTrusted();
|
|
4397
4819
|
const capabilityState = verifyProjectCapabilityState(extensionDir, ctx.cwd, projectTrusted);
|
|
4398
4820
|
const recoveryTools = new Set(["piagent_profile_options", "piagent_profile_apply", "piagent_context", "read", "grep", "find", "ls"]);
|
|
@@ -4555,6 +4977,148 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4555
4977
|
}
|
|
4556
4978
|
});
|
|
4557
4979
|
|
|
4980
|
+
pi.registerTool({
|
|
4981
|
+
name: "piagent_tools",
|
|
4982
|
+
label: "Piagent Tool Loader",
|
|
4983
|
+
description: "Activate an additional Piagent tool group only when the current task needs it.",
|
|
4984
|
+
promptSnippet: "Load governance, policy, retrieval, knowledge, onboarding, or usage tools on demand.",
|
|
4985
|
+
promptGuidelines: [
|
|
4986
|
+
"Load the smallest group that resolves the current need; do not activate every group."
|
|
4987
|
+
],
|
|
4988
|
+
parameters: Type.Object({
|
|
4989
|
+
groups: Type.Array(StringEnum(["governance", "policy", "retrieval", "knowledge", "onboarding", "usage"] as const), { minItems: 1 })
|
|
4990
|
+
}),
|
|
4991
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
4992
|
+
const groups = [...new Set(params.groups)] as PiagentToolGroup[];
|
|
4993
|
+
if (!dynamicToolsEnabled) {
|
|
4994
|
+
return {
|
|
4995
|
+
content: [{ type: "text", text: "Dynamic Piagent tool loading is disabled by PIAGENT_DYNAMIC_TOOLS." }],
|
|
4996
|
+
details: { groups, disabled: true, activePiagentTools: pi.getActiveTools().filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName)) }
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
const activeTools = activateToolGroups(ctx, groups, true);
|
|
5000
|
+
return {
|
|
5001
|
+
content: [{
|
|
5002
|
+
type: "text",
|
|
5003
|
+
text: `Piagent tools activated: ${groups.join(", ")}. Active Piagent tools: ${activeTools.filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName)).length}.`
|
|
5004
|
+
}],
|
|
5005
|
+
details: {
|
|
5006
|
+
groups,
|
|
5007
|
+
activePiagentTools: activeTools.filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName))
|
|
5008
|
+
}
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
});
|
|
5012
|
+
|
|
5013
|
+
pi.registerTool({
|
|
5014
|
+
name: "piagent_context_engine",
|
|
5015
|
+
label: "Pi Context Engine",
|
|
5016
|
+
description: "Build or query the local code index, return a token-budgeted context pack, map impacted tests, or report context efficiency.",
|
|
5017
|
+
promptSnippet: "Use this instead of broad repository scouting when the task needs code navigation.",
|
|
5018
|
+
promptGuidelines: [
|
|
5019
|
+
"Prefer pack for an unfamiliar task, search for a named symbol/path, impact before targeted verification, and efficiency only for usage analysis.",
|
|
5020
|
+
"Index evidence is advisory; re-read selected files before editing.",
|
|
5021
|
+
"Run one bounded finder pass only when pack confidence is low."
|
|
5022
|
+
],
|
|
5023
|
+
parameters: Type.Object({
|
|
5024
|
+
action: StringEnum(["status", "rebuild", "search", "pack", "impact", "efficiency"] as const),
|
|
5025
|
+
query: Type.Optional(Type.String()),
|
|
5026
|
+
files: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5027
|
+
budgetTokens: Type.Optional(Type.Number({ minimum: 200, maximum: 50000 })),
|
|
5028
|
+
refresh: Type.Optional(Type.Boolean())
|
|
5029
|
+
}),
|
|
5030
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
5031
|
+
const profile = loadProfileFromContext(ctx);
|
|
5032
|
+
const pathPolicy = effectiveProtectedPaths(policy, profile);
|
|
5033
|
+
const excludePatterns = Array.from(new Set([
|
|
5034
|
+
...pathPolicy.readProtectedPaths,
|
|
5035
|
+
...pathPolicy.writeProtectedPaths,
|
|
5036
|
+
".pi/context-index.json",
|
|
5037
|
+
".pi/piagent-state/**"
|
|
5038
|
+
]));
|
|
5039
|
+
let result: unknown;
|
|
5040
|
+
let text: string;
|
|
5041
|
+
if (params.action === "status") {
|
|
5042
|
+
result = await contextIndexV2Status(ctx.cwd);
|
|
5043
|
+
const status = result as Awaited<ReturnType<typeof contextIndexV2Status>>;
|
|
5044
|
+
text = [
|
|
5045
|
+
`indexV2: ${status.exists ? "ready" : "missing"}`,
|
|
5046
|
+
`path: ${status.path}`,
|
|
5047
|
+
`files: ${status.files ?? 0}`,
|
|
5048
|
+
`symbols: ${status.symbols ?? 0}`,
|
|
5049
|
+
`imports: ${status.imports ?? 0}`,
|
|
5050
|
+
`stale: ${status.stale ? "yes" : "no"}`,
|
|
5051
|
+
`warnings: ${status.warnings.join("; ") || "none"}`
|
|
5052
|
+
].join("\n");
|
|
5053
|
+
} else if (params.action === "rebuild") {
|
|
5054
|
+
result = await buildContextIndexV2(ctx.cwd, { excludePatterns });
|
|
5055
|
+
const built = result as Awaited<ReturnType<typeof buildContextIndexV2>>;
|
|
5056
|
+
text = [
|
|
5057
|
+
"indexV2: rebuilt",
|
|
5058
|
+
`files: ${built.files}; symbols: ${built.symbols}; imports: ${built.imports}`,
|
|
5059
|
+
`changed: ${built.changed}; removed: ${built.removed}`,
|
|
5060
|
+
`skipped: ${built.skippedLarge} large, ${built.skippedBinary} binary`,
|
|
5061
|
+
`duration: ${built.durationMs}ms`
|
|
5062
|
+
].join("\n");
|
|
5063
|
+
} else if (params.action === "search") {
|
|
5064
|
+
if (!params.query?.trim()) throw new Error("query is required for context search");
|
|
5065
|
+
let status = await contextIndexV2Status(ctx.cwd);
|
|
5066
|
+
if (params.refresh || !status.exists) {
|
|
5067
|
+
await buildContextIndexV2(ctx.cwd, { excludePatterns });
|
|
5068
|
+
status = await contextIndexV2Status(ctx.cwd);
|
|
5069
|
+
}
|
|
5070
|
+
result = await searchContextIndexV2(ctx.cwd, params.query, { limit: 15 });
|
|
5071
|
+
const search = result as Awaited<ReturnType<typeof searchContextIndexV2>>;
|
|
5072
|
+
text = [
|
|
5073
|
+
`confidence: ${search.confidence}`,
|
|
5074
|
+
`indexStale: ${status.stale ? "yes" : "no"}`,
|
|
5075
|
+
...search.results.map((item) => `- ${item.path}: ${item.sources.join("+")}; ${item.symbols.slice(0, 4).map((symbol) => `${symbol.name}@${symbol.line}`).join(", ") || "no symbols"}`)
|
|
5076
|
+
].join("\n");
|
|
5077
|
+
} else if (params.action === "pack") {
|
|
5078
|
+
if (!params.query?.trim()) throw new Error("query is required for a context pack");
|
|
5079
|
+
let status = await contextIndexV2Status(ctx.cwd);
|
|
5080
|
+
if (params.refresh || !status.exists) {
|
|
5081
|
+
await buildContextIndexV2(ctx.cwd, { excludePatterns });
|
|
5082
|
+
status = await contextIndexV2Status(ctx.cwd);
|
|
5083
|
+
}
|
|
5084
|
+
const pack = await buildContextPack(ctx.cwd, params.query, {
|
|
5085
|
+
budgetTokens: params.budgetTokens ?? 6_000,
|
|
5086
|
+
includeCode: true,
|
|
5087
|
+
limit: 18
|
|
5088
|
+
});
|
|
5089
|
+
result = { ...pack, text: undefined, status };
|
|
5090
|
+
text = pack.text;
|
|
5091
|
+
} else if (params.action === "impact") {
|
|
5092
|
+
result = await buildTestImpact(ctx.cwd, params.files ?? []);
|
|
5093
|
+
const impact = result as Awaited<ReturnType<typeof buildTestImpact>>;
|
|
5094
|
+
text = [
|
|
5095
|
+
`changed: ${impact.changedFiles.join(", ") || "none"}`,
|
|
5096
|
+
`impacted: ${impact.impactedFiles.map((item) => `${item.path} via ${item.via}`).join(", ") || "none"}`,
|
|
5097
|
+
`tests: ${impact.tests.join(", ") || "none"}`
|
|
5098
|
+
].join("\n");
|
|
5099
|
+
} else {
|
|
5100
|
+
result = buildContextEfficiencyReport(ctx.cwd);
|
|
5101
|
+
const report = result as ReturnType<typeof buildContextEfficiencyReport>;
|
|
5102
|
+
text = [
|
|
5103
|
+
`contextWasteScore: ${report.metrics.contextWasteScore}/100 (lower is better)`,
|
|
5104
|
+
`activeTools: ${report.metrics.averageActiveTools}`,
|
|
5105
|
+
`toolSchemaShare: ${formatPercent(report.metrics.toolSchemaShare)}`,
|
|
5106
|
+
`duplicateReads: ${report.metrics.duplicateReads}/${report.metrics.readCalls}`,
|
|
5107
|
+
`duplicateOutput: ${formatPercent(report.metrics.duplicateOutputRate)}`,
|
|
5108
|
+
`lowConfidencePacks: ${report.metrics.lowConfidencePacks}/${report.sample.contextPacks}`,
|
|
5109
|
+
...report.recommendations.map((recommendation) => `- ${recommendation}`)
|
|
5110
|
+
].join("\n");
|
|
5111
|
+
}
|
|
5112
|
+
telemetry(ctx, {
|
|
5113
|
+
event: "context_engine_action",
|
|
5114
|
+
action: params.action,
|
|
5115
|
+
queryHash: params.query ? crypto.createHash("sha256").update(params.query).digest("hex") : undefined,
|
|
5116
|
+
fileCount: params.files?.length ?? 0
|
|
5117
|
+
});
|
|
5118
|
+
return { content: [{ type: "text", text }], details: result };
|
|
5119
|
+
}
|
|
5120
|
+
});
|
|
5121
|
+
|
|
4558
5122
|
pi.registerTool({
|
|
4559
5123
|
name: "piagent_context",
|
|
4560
5124
|
label: "Piagent Context",
|
|
@@ -4573,6 +5137,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4573
5137
|
const pathPolicy = effectiveProtectedPaths(policy, profile);
|
|
4574
5138
|
const orchestrationPolicy = resolveOrchestrationPolicy(profile, policy);
|
|
4575
5139
|
const contextIndex = buildContextIndexStatus(ctx.cwd, profile);
|
|
5140
|
+
let contextEngine: Awaited<ReturnType<typeof contextIndexV2Status>> | { exists: false; warnings: string[] };
|
|
5141
|
+
try {
|
|
5142
|
+
contextEngine = await contextIndexV2Status(ctx.cwd);
|
|
5143
|
+
} catch (error) {
|
|
5144
|
+
contextEngine = { exists: false, warnings: [error instanceof Error ? error.message : String(error)] };
|
|
5145
|
+
}
|
|
4576
5146
|
const requiredContext = [
|
|
4577
5147
|
...policy.defaultRequiredContext,
|
|
4578
5148
|
...(profile.requiredContext ?? [])
|
|
@@ -4596,6 +5166,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4596
5166
|
exists: fs.existsSync(projectContextFilePath(ctx.cwd))
|
|
4597
5167
|
},
|
|
4598
5168
|
contextIndex,
|
|
5169
|
+
contextEngine,
|
|
4599
5170
|
protectedPaths: profile.protectedPaths ?? [],
|
|
4600
5171
|
shellProtectedPaths: profile.shellProtectedPaths ?? profile.protectedPaths ?? [],
|
|
4601
5172
|
readOnlyPaths: profile.readOnlyPaths ?? [],
|
|
@@ -4631,6 +5202,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4631
5202
|
`profile: ${payload.profile.path} (${payload.profile.exists ? "exists" : "missing"})`,
|
|
4632
5203
|
`projectContext: ${payload.projectContext.path} (${payload.projectContext.exists ? "exists" : "missing"})`,
|
|
4633
5204
|
`contextIndex: ${payload.contextIndex.path} (${payload.contextIndex.exists ? `${payload.contextIndex.nodes} nodes` : "missing"})`,
|
|
5205
|
+
`contextEngine: ${payload.contextEngine.exists ? `${payload.contextEngine.files ?? 0} files / ${payload.contextEngine.symbols ?? 0} symbols${payload.contextEngine.stale ? " / stale" : ""}` : "missing"}`,
|
|
4634
5206
|
`requiredContext: ${payload.requiredContext.join(", ") || "none"}`,
|
|
4635
5207
|
`verifyCommands: ${Object.keys(payload.verifyCommands).join(", ") || "none"}`,
|
|
4636
5208
|
`mcpCapabilities: ${payload.mcpCapabilities.join(", ") || "none"}`,
|
|
@@ -5368,12 +5940,30 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5368
5940
|
} catch (error) {
|
|
5369
5941
|
contextIndexError = error instanceof Error ? error.message : String(error);
|
|
5370
5942
|
}
|
|
5943
|
+
let contextEngine: Awaited<ReturnType<typeof buildContextIndexV2>> | undefined;
|
|
5944
|
+
let contextEngineError: string | undefined;
|
|
5945
|
+
try {
|
|
5946
|
+
const pathPolicy = effectiveProtectedPaths(policy, profile);
|
|
5947
|
+
contextEngine = await buildContextIndexV2(ctx.cwd, {
|
|
5948
|
+
excludePatterns: Array.from(new Set([
|
|
5949
|
+
...pathPolicy.readProtectedPaths,
|
|
5950
|
+
...pathPolicy.writeProtectedPaths,
|
|
5951
|
+
".pi/context-index.json",
|
|
5952
|
+
".pi/piagent-state/**"
|
|
5953
|
+
]))
|
|
5954
|
+
});
|
|
5955
|
+
} catch (error) {
|
|
5956
|
+
contextEngineError = error instanceof Error ? error.message : String(error);
|
|
5957
|
+
}
|
|
5371
5958
|
appendTrace(ctx.cwd, { event: "project_onboarding_record", contextFile: snapshot.contextFile, sourceFiles: params.sourceFiles, contextIndex: contextIndex?.policy.path, contextIndexError });
|
|
5372
5959
|
appendSessionTrace(pi, { event: "project_onboarding_record", contextFile: snapshot.contextFile, sourceFiles: params.sourceFiles, contextIndex: contextIndex?.policy.path, contextIndexError });
|
|
5373
5960
|
|
|
5374
5961
|
return {
|
|
5375
|
-
content: [{
|
|
5376
|
-
|
|
5962
|
+
content: [{
|
|
5963
|
+
type: "text",
|
|
5964
|
+
text: `Project onboarding snapshot recorded: .pi/project-context.md${contextIndex ? ` and ${contextIndex.policy.path}` : contextIndexError ? ` (context index skipped: ${contextIndexError})` : ""}${contextEngine ? `; Context Engine indexed ${contextEngine.files} files / ${contextEngine.symbols} symbols` : contextEngineError ? `; Context Engine skipped: ${contextEngineError}` : ""}`
|
|
5965
|
+
}],
|
|
5966
|
+
details: { ...snapshot, contextIndex, contextIndexError, contextEngine, contextEngineError }
|
|
5377
5967
|
};
|
|
5378
5968
|
}
|
|
5379
5969
|
});
|
|
@@ -5385,7 +5975,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
5385
5975
|
promptSnippet: "Start a governed implementation task and persist the task contract.",
|
|
5386
5976
|
promptGuidelines: [
|
|
5387
5977
|
"Call this before source edits in a project managed by Pi Agent Platform.",
|
|
5388
|
-
"Use piagent_context first when possible."
|
|
5978
|
+
"Use piagent_context first when possible.",
|
|
5979
|
+
"Use tiny for a bounded low-risk change, normal for ordinary multi-file work, and high-risk for security, data, release, migration, or external-impact work.",
|
|
5980
|
+
"Tiny tasks should stay on the compact parent-only work plan unless the caller supplies a justified custom plan."
|
|
5389
5981
|
],
|
|
5390
5982
|
parameters: Type.Object({
|
|
5391
5983
|
taskId: Type.Optional(Type.String({ minLength: 1 })),
|
|
@@ -6638,17 +7230,129 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6638
7230
|
].join("\n"), status);
|
|
6639
7231
|
}
|
|
6640
7232
|
|
|
7233
|
+
async function emitContextEngineStatus(ctx: ExtensionContext): Promise<void> {
|
|
7234
|
+
try {
|
|
7235
|
+
const status = await contextIndexV2Status(ctx.cwd);
|
|
7236
|
+
emitRuntimeMessage(ctx, "piagent-context-engine-status", [
|
|
7237
|
+
`contextEngine: ${status.exists ? "ready" : "missing"}`,
|
|
7238
|
+
`path: ${status.path}`,
|
|
7239
|
+
`files: ${status.files ?? 0}`,
|
|
7240
|
+
`symbols: ${status.symbols ?? 0}`,
|
|
7241
|
+
`imports: ${status.imports ?? 0}`,
|
|
7242
|
+
`builtAt: ${status.builtAt ?? "never"}`,
|
|
7243
|
+
`stale: ${status.stale ? "yes" : "no"}`,
|
|
7244
|
+
`warnings: ${status.warnings.join("; ") || "none"}`,
|
|
7245
|
+
"rebuild: /context rebuild"
|
|
7246
|
+
].join("\n"), status);
|
|
7247
|
+
} catch (error) {
|
|
7248
|
+
emitRuntimeMessage(ctx, "piagent-context-engine-status", `Context Engine status failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
|
|
7252
|
+
async function rebuildContextEngine(ctx: ExtensionContext): Promise<void> {
|
|
7253
|
+
const profile = loadProfileFromContext(ctx);
|
|
7254
|
+
const pathPolicy = effectiveProtectedPaths(policy, profile);
|
|
7255
|
+
try {
|
|
7256
|
+
const result = await buildContextIndexV2(ctx.cwd, {
|
|
7257
|
+
excludePatterns: Array.from(new Set([
|
|
7258
|
+
...pathPolicy.readProtectedPaths,
|
|
7259
|
+
...pathPolicy.writeProtectedPaths,
|
|
7260
|
+
".pi/context-index.json",
|
|
7261
|
+
".pi/piagent-state/**"
|
|
7262
|
+
]))
|
|
7263
|
+
});
|
|
7264
|
+
telemetry(ctx, { event: "context_engine_action", action: "rebuild", ...result });
|
|
7265
|
+
emitRuntimeMessage(ctx, "piagent-context-engine-rebuild", [
|
|
7266
|
+
"contextEngine: rebuilt",
|
|
7267
|
+
`files: ${result.files}; symbols: ${result.symbols}; imports: ${result.imports}`,
|
|
7268
|
+
`changed: ${result.changed}; removed: ${result.removed}`,
|
|
7269
|
+
`skipped: ${result.skippedLarge} large, ${result.skippedBinary} binary`,
|
|
7270
|
+
`duration: ${result.durationMs}ms`
|
|
7271
|
+
].join("\n"), result);
|
|
7272
|
+
} catch (error) {
|
|
7273
|
+
emitRuntimeMessage(ctx, "piagent-context-engine-rebuild", `Context Engine rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
7274
|
+
}
|
|
7275
|
+
}
|
|
7276
|
+
|
|
7277
|
+
async function emitContextEngineSearch(ctx: ExtensionContext, query: string): Promise<boolean> {
|
|
7278
|
+
const status = await contextIndexV2Status(ctx.cwd);
|
|
7279
|
+
if (!status.exists || !query.trim()) return false;
|
|
7280
|
+
const search = await searchContextIndexV2(ctx.cwd, query, { limit: 12 });
|
|
7281
|
+
emitRuntimeMessage(ctx, "piagent-context-engine-search", [
|
|
7282
|
+
`confidence: ${search.confidence}`,
|
|
7283
|
+
`stale: ${status.stale ? "yes" : "no"}`,
|
|
7284
|
+
...search.results.map((item) => `- ${item.path}: ${item.sources.join("+")}; ${item.symbols.slice(0, 4).map((symbol) => `${symbol.name}@${symbol.line}`).join(", ") || "no symbols"}`)
|
|
7285
|
+
].join("\n"), { queryHash: crypto.createHash("sha256").update(query).digest("hex"), search });
|
|
7286
|
+
return true;
|
|
7287
|
+
}
|
|
7288
|
+
|
|
7289
|
+
async function emitContextPack(ctx: ExtensionContext, query: string): Promise<void> {
|
|
7290
|
+
if (!query.trim()) {
|
|
7291
|
+
emitRuntimeMessage(ctx, "piagent-context-pack-help", "Usage: /context pack <task or symbol>");
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
const status = await contextIndexV2Status(ctx.cwd);
|
|
7295
|
+
if (!status.exists) {
|
|
7296
|
+
emitRuntimeMessage(ctx, "piagent-context-pack-help", "Context Engine index is missing. Run /context rebuild first.");
|
|
7297
|
+
return;
|
|
7298
|
+
}
|
|
7299
|
+
const pack = await buildContextPack(ctx.cwd, query, { budgetTokens: 1_500, includeCode: false, limit: 15 });
|
|
7300
|
+
telemetry(ctx, {
|
|
7301
|
+
event: "context_pack",
|
|
7302
|
+
queryHash: pack.queryHash,
|
|
7303
|
+
confidence: pack.confidence,
|
|
7304
|
+
candidates: pack.candidates,
|
|
7305
|
+
selected: pack.selected.length,
|
|
7306
|
+
estimatedTokens: pack.estimatedTokens,
|
|
7307
|
+
selectedPaths: pack.selected.map((item) => item.path),
|
|
7308
|
+
source: "command"
|
|
7309
|
+
});
|
|
7310
|
+
emitRuntimeMessage(ctx, "piagent-context-pack-v2", pack.text, {
|
|
7311
|
+
queryHash: pack.queryHash,
|
|
7312
|
+
confidence: pack.confidence,
|
|
7313
|
+
estimatedTokens: pack.estimatedTokens,
|
|
7314
|
+
paths: pack.selected.map((item) => item.path),
|
|
7315
|
+
finderRecommended: pack.finderRecommended
|
|
7316
|
+
});
|
|
7317
|
+
}
|
|
7318
|
+
|
|
7319
|
+
async function emitTestImpact(ctx: ExtensionContext, raw: string): Promise<void> {
|
|
7320
|
+
const files = raw.split(/\s+/).map((file) => file.trim()).filter(Boolean);
|
|
7321
|
+
const impact = await buildTestImpact(ctx.cwd, files);
|
|
7322
|
+
emitRuntimeMessage(ctx, "piagent-context-impact", [
|
|
7323
|
+
`changed: ${impact.changedFiles.join(", ") || "none"}`,
|
|
7324
|
+
`impacted: ${impact.impactedFiles.map((item) => `${item.path} via ${item.via}`).join(", ") || "none"}`,
|
|
7325
|
+
`tests: ${impact.tests.join(", ") || "none"}`
|
|
7326
|
+
].join("\n"), impact);
|
|
7327
|
+
}
|
|
7328
|
+
|
|
7329
|
+
function emitContextEfficiency(ctx: ExtensionContext): void {
|
|
7330
|
+
const report = buildContextEfficiencyReport(ctx.cwd);
|
|
7331
|
+
emitRuntimeMessage(ctx, "piagent-context-efficiency", [
|
|
7332
|
+
`contextWasteScore: ${report.metrics.contextWasteScore}/100 (lower is better)`,
|
|
7333
|
+
`activeTools: ${report.metrics.averageActiveTools}`,
|
|
7334
|
+
`toolSchemaShare: ${formatPercent(report.metrics.toolSchemaShare)}`,
|
|
7335
|
+
`duplicateReads: ${report.metrics.duplicateReads}/${report.metrics.readCalls}`,
|
|
7336
|
+
`duplicateOutput: ${formatPercent(report.metrics.duplicateOutputRate)}`,
|
|
7337
|
+
`lowConfidencePacks: ${report.metrics.lowConfidencePacks}/${report.sample.contextPacks}`,
|
|
7338
|
+
...report.recommendations.map((recommendation) => `- ${recommendation}`)
|
|
7339
|
+
].join("\n"), report);
|
|
7340
|
+
}
|
|
7341
|
+
|
|
6641
7342
|
function parsePreflightWorkflow(raw: string): string {
|
|
6642
7343
|
return raw.match(/\b(?:scout|be-to-fe|review|plan|platform-improve|task)\b/i)?.[0]?.toLowerCase() ?? "task";
|
|
6643
7344
|
}
|
|
6644
7345
|
|
|
6645
7346
|
function compactCurrentSession(ctx: ExtensionContext): void {
|
|
7347
|
+
const instructions = semanticCompactionInstructions(ctx.cwd);
|
|
7348
|
+
telemetry(ctx, {
|
|
7349
|
+
event: "compaction_requested",
|
|
7350
|
+
mode: "semantic",
|
|
7351
|
+
instructionTokens: estimateContextTokens(instructions),
|
|
7352
|
+
hasTaskContract: Boolean(compactSessionTask(ctx.cwd))
|
|
7353
|
+
});
|
|
6646
7354
|
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")
|
|
7355
|
+
customInstructions: instructions
|
|
6652
7356
|
});
|
|
6653
7357
|
}
|
|
6654
7358
|
|
|
@@ -6668,7 +7372,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6668
7372
|
const { action, rest } = commandArgs(raw);
|
|
6669
7373
|
if (!action) {
|
|
6670
7374
|
const chosen = await selectRuntimeAction(ctx, "Piagent context", [
|
|
6671
|
-
{ value: "index", label: "
|
|
7375
|
+
{ value: "index", label: "Index status", description: "Architecture map plus local code index", recommended: true },
|
|
7376
|
+
{ value: "pack", label: "Context pack", description: "Rank paths and symbols for a task" },
|
|
7377
|
+
{ value: "impact", label: "Test impact", description: "Map changed files to dependents and tests" },
|
|
7378
|
+
{ value: "efficiency", label: "Efficiency", description: "Show transparent context waste metrics" },
|
|
7379
|
+
{ value: "rebuild", label: "Rebuild index", description: "Incrementally refresh changed files" },
|
|
6672
7380
|
{ value: "preflight", label: "Preflight", description: "Check whether to run, compact, or fresh-session" },
|
|
6673
7381
|
{ value: "compact", label: "Compact", description: "Compact current session with Piagent carry-over rules" },
|
|
6674
7382
|
{ value: "search", label: "Search index", description: "Use /context search <keyword>" },
|
|
@@ -6676,6 +7384,27 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6676
7384
|
], "index");
|
|
6677
7385
|
if (chosen === "index") {
|
|
6678
7386
|
emitContextIndexStatus(ctx);
|
|
7387
|
+
await emitContextEngineStatus(ctx);
|
|
7388
|
+
return;
|
|
7389
|
+
}
|
|
7390
|
+
if (chosen === "rebuild") {
|
|
7391
|
+
await rebuildContextEngine(ctx);
|
|
7392
|
+
return;
|
|
7393
|
+
}
|
|
7394
|
+
if (chosen === "efficiency") {
|
|
7395
|
+
emitContextEfficiency(ctx);
|
|
7396
|
+
return;
|
|
7397
|
+
}
|
|
7398
|
+
if (chosen === "pack") {
|
|
7399
|
+
await emitContextPack(ctx, "");
|
|
7400
|
+
return;
|
|
7401
|
+
}
|
|
7402
|
+
if (chosen === "impact") {
|
|
7403
|
+
await emitTestImpact(ctx, "");
|
|
7404
|
+
return;
|
|
7405
|
+
}
|
|
7406
|
+
if (chosen === "search") {
|
|
7407
|
+
emitRuntimeMessage(ctx, "piagent-context-search-help", "Usage: /context search <keyword or symbol>");
|
|
6679
7408
|
return;
|
|
6680
7409
|
}
|
|
6681
7410
|
if (chosen === "preflight") {
|
|
@@ -6689,7 +7418,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6689
7418
|
emitRuntimeMessage(ctx, "piagent-context-help", [
|
|
6690
7419
|
"namespace: /context",
|
|
6691
7420
|
"index: /context index",
|
|
7421
|
+
"rebuild: /context rebuild",
|
|
6692
7422
|
"search: /context search <keyword>",
|
|
7423
|
+
"pack: /context pack <task or symbol>",
|
|
7424
|
+
"impact: /context impact [changed files]",
|
|
7425
|
+
"efficiency: /context efficiency",
|
|
6693
7426
|
"preflight: /context preflight [task|scout|be-to-fe|review|plan]",
|
|
6694
7427
|
"compact: /context compact [task|scout|be-to-fe]",
|
|
6695
7428
|
"legacy: /piagent-context | /context-index | /task-preflight"
|
|
@@ -6698,10 +7431,31 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6698
7431
|
}
|
|
6699
7432
|
if (["index", "status", "show", "current"].includes(action)) {
|
|
6700
7433
|
emitContextIndexStatus(ctx);
|
|
7434
|
+
await emitContextEngineStatus(ctx);
|
|
7435
|
+
return;
|
|
7436
|
+
}
|
|
7437
|
+
if (["rebuild", "build", "refresh"].includes(action)) {
|
|
7438
|
+
await rebuildContextEngine(ctx);
|
|
6701
7439
|
return;
|
|
6702
7440
|
}
|
|
6703
7441
|
if (action === "search") {
|
|
6704
|
-
|
|
7442
|
+
try {
|
|
7443
|
+
if (!await emitContextEngineSearch(ctx, rest)) emitContextIndexSearch(ctx, rest);
|
|
7444
|
+
} catch {
|
|
7445
|
+
emitContextIndexSearch(ctx, rest);
|
|
7446
|
+
}
|
|
7447
|
+
return;
|
|
7448
|
+
}
|
|
7449
|
+
if (action === "pack") {
|
|
7450
|
+
await emitContextPack(ctx, rest);
|
|
7451
|
+
return;
|
|
7452
|
+
}
|
|
7453
|
+
if (["impact", "tests"].includes(action)) {
|
|
7454
|
+
await emitTestImpact(ctx, rest);
|
|
7455
|
+
return;
|
|
7456
|
+
}
|
|
7457
|
+
if (["efficiency", "stats", "waste"].includes(action)) {
|
|
7458
|
+
emitContextEfficiency(ctx);
|
|
6705
7459
|
return;
|
|
6706
7460
|
}
|
|
6707
7461
|
if (["preflight", "check"].includes(action)) {
|
|
@@ -6716,7 +7470,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6716
7470
|
emitRuntimeMessage(ctx, "piagent-context-help", [
|
|
6717
7471
|
"namespace: /context",
|
|
6718
7472
|
"index: /context index",
|
|
7473
|
+
"rebuild: /context rebuild",
|
|
6719
7474
|
"search: /context search <keyword>",
|
|
7475
|
+
"pack: /context pack <task or symbol>",
|
|
7476
|
+
"impact: /context impact [changed files]",
|
|
7477
|
+
"efficiency: /context efficiency",
|
|
6720
7478
|
"preflight: /context preflight [task|scout|be-to-fe|review|plan]",
|
|
6721
7479
|
"compact: /context compact [task|scout|be-to-fe]",
|
|
6722
7480
|
"legacy: /piagent-context | /context-index | /task-preflight"
|
|
@@ -6729,7 +7487,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6729
7487
|
pi.registerCommand("piagent-context", {
|
|
6730
7488
|
description: "Legacy alias for /context",
|
|
6731
7489
|
getArgumentCompletions: (prefix: string) => {
|
|
6732
|
-
const actions = ["index", "search", "preflight", "compact", "help"];
|
|
7490
|
+
const actions = ["index", "rebuild", "search", "pack", "impact", "efficiency", "preflight", "compact", "help"];
|
|
6733
7491
|
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
6734
7492
|
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
6735
7493
|
},
|
|
@@ -6739,9 +7497,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6739
7497
|
});
|
|
6740
7498
|
|
|
6741
7499
|
pi.registerCommand("context", {
|
|
6742
|
-
description: "Context index,
|
|
7500
|
+
description: "Context index, retrieval pack, test impact, efficiency, preflight, and compact controls without a model follow-up",
|
|
6743
7501
|
getArgumentCompletions: (prefix: string) => {
|
|
6744
|
-
const actions = ["index", "search", "preflight", "compact", "help"];
|
|
7502
|
+
const actions = ["index", "rebuild", "search", "pack", "impact", "efficiency", "preflight", "compact", "help"];
|
|
6745
7503
|
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
6746
7504
|
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
6747
7505
|
},
|
|
@@ -6843,6 +7601,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6843
7601
|
{ value: "preflight", label: "Preflight", description: "Check task/context health" },
|
|
6844
7602
|
{ value: "compact", label: "Compact", description: "Compact current session with Piagent carry-over rules" },
|
|
6845
7603
|
{ value: "logs", label: "Tool logs", description: "Recent compacted large tool outputs" },
|
|
7604
|
+
{ value: "efficiency", label: "Efficiency", description: "Context waste score and causes" },
|
|
6846
7605
|
{ value: "help", label: "Help", description: "Show typed forms" }
|
|
6847
7606
|
], "live");
|
|
6848
7607
|
if (chosen === "live") {
|
|
@@ -6865,6 +7624,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6865
7624
|
emitToolLogCaptures(ctx);
|
|
6866
7625
|
return;
|
|
6867
7626
|
}
|
|
7627
|
+
if (chosen === "efficiency") {
|
|
7628
|
+
emitContextEfficiency(ctx);
|
|
7629
|
+
return;
|
|
7630
|
+
}
|
|
6868
7631
|
emitRuntimeMessage(ctx, "piagent-usage-help", [
|
|
6869
7632
|
"namespace: /usage",
|
|
6870
7633
|
"live: /usage live",
|
|
@@ -6872,6 +7635,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6872
7635
|
"preflight: /usage preflight [task|scout|be-to-fe]",
|
|
6873
7636
|
"compact: /usage compact [task|scout|be-to-fe]",
|
|
6874
7637
|
"logs: /usage logs",
|
|
7638
|
+
"efficiency: /usage efficiency",
|
|
6875
7639
|
"native exact session: /session",
|
|
6876
7640
|
"legacy: /piagent-usage | /task-preflight | /logs"
|
|
6877
7641
|
].join("\n"));
|
|
@@ -6897,10 +7661,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
6897
7661
|
emitToolLogCaptures(ctx);
|
|
6898
7662
|
return;
|
|
6899
7663
|
}
|
|
7664
|
+
if (["efficiency", "stats", "waste"].includes(action)) {
|
|
7665
|
+
emitContextEfficiency(ctx);
|
|
7666
|
+
return;
|
|
7667
|
+
}
|
|
6900
7668
|
if (action === "help") {
|
|
6901
7669
|
emitRuntimeMessage(ctx, "piagent-usage-help", [
|
|
6902
7670
|
"namespace: /usage",
|
|
6903
|
-
"live | history | preflight | compact | logs",
|
|
7671
|
+
"live | history | preflight | compact | logs | efficiency",
|
|
6904
7672
|
"examples:",
|
|
6905
7673
|
"/usage live",
|
|
6906
7674
|
"/usage history",
|
|
@@ -7038,7 +7806,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
7038
7806
|
pi.registerCommand("piagent-usage", {
|
|
7039
7807
|
description: "Legacy alias for /usage",
|
|
7040
7808
|
getArgumentCompletions: (prefix: string) => {
|
|
7041
|
-
const actions = ["live", "history", "preflight", "compact", "logs", "help"];
|
|
7809
|
+
const actions = ["live", "history", "preflight", "compact", "logs", "efficiency", "help"];
|
|
7042
7810
|
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7043
7811
|
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7044
7812
|
},
|
|
@@ -7050,7 +7818,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
7050
7818
|
pi.registerCommand("usage", {
|
|
7051
7819
|
description: "Usage namespace: live, history, preflight, compact, and logs without a model follow-up",
|
|
7052
7820
|
getArgumentCompletions: (prefix: string) => {
|
|
7053
|
-
const actions = ["live", "history", "preflight", "compact", "logs", "help"];
|
|
7821
|
+
const actions = ["live", "history", "preflight", "compact", "logs", "efficiency", "help"];
|
|
7054
7822
|
const typed = String(prefix ?? "").trim().toLowerCase();
|
|
7055
7823
|
return actions.filter((action) => action.startsWith(typed)).map((action) => ({ value: action, label: action }));
|
|
7056
7824
|
},
|