@phi-code-admin/phi-code 0.86.0 → 0.88.0
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 +104 -0
- package/docs/adr/0001-phase-contract.md +57 -0
- package/docs/adr/0002-independent-review.md +47 -0
- package/docs/fork-policy.md +18 -0
- package/extensions/phi/agents.ts +7 -4
- package/extensions/phi/benchmark.ts +129 -49
- package/extensions/phi/browser.ts +10 -37
- package/extensions/phi/btw/btw.ts +1 -7
- package/extensions/phi/chrome/index.ts +1283 -741
- package/extensions/phi/commit.ts +9 -14
- package/extensions/phi/goal/index.ts +10 -33
- package/extensions/phi/init.ts +37 -47
- package/extensions/phi/keys.ts +2 -7
- package/extensions/phi/mcp/callback-server.ts +162 -165
- package/extensions/phi/mcp/config.ts +122 -136
- package/extensions/phi/mcp/errors.ts +18 -23
- package/extensions/phi/mcp/index.ts +322 -355
- package/extensions/phi/mcp/oauth-provider.ts +289 -289
- package/extensions/phi/mcp/server-manager.ts +390 -413
- package/extensions/phi/mcp/tool-bridge.ts +381 -415
- package/extensions/phi/memory.ts +2 -2
- package/extensions/phi/models.ts +27 -26
- package/extensions/phi/orchestrator.ts +451 -237
- package/extensions/phi/productivity.ts +4 -2
- package/extensions/phi/providers/agent-def.ts +1 -1
- package/extensions/phi/providers/alibaba.ts +56 -7
- package/extensions/phi/providers/context-window.ts +4 -1
- package/extensions/phi/providers/live-models.ts +5 -20
- package/extensions/phi/providers/orchestrator-helpers.ts +31 -5
- package/extensions/phi/providers/phase-machine.ts +283 -0
- package/extensions/phi/setup.ts +196 -169
- package/extensions/phi/skill-loader.ts +14 -17
- package/extensions/phi/smart-router.ts +6 -6
- package/extensions/phi/web-search.ts +90 -50
- package/package.json +1 -1
package/extensions/phi/commit.ts
CHANGED
|
@@ -162,21 +162,22 @@ function deriveSubject(stagedFiles: string[], assistantHint: string): string {
|
|
|
162
162
|
|
|
163
163
|
export default function commitExtension(pi: ExtensionAPI) {
|
|
164
164
|
pi.registerCommand("commit", {
|
|
165
|
-
description:
|
|
165
|
+
description:
|
|
166
|
+
"Deterministically commit staged changes ([phi] prefix, no LLM). Use --all to stage everything first.",
|
|
166
167
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
167
168
|
try {
|
|
168
169
|
// 1. Parse args: extract the --all flag, keep the rest as the message.
|
|
169
170
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
170
171
|
const stageAll = tokens.includes("--all");
|
|
171
|
-
const userMessage = tokens
|
|
172
|
+
const userMessage = tokens
|
|
173
|
+
.filter((t) => t !== "--all")
|
|
174
|
+
.join(" ")
|
|
175
|
+
.trim();
|
|
172
176
|
|
|
173
177
|
// 2. Confirm we are inside a git repo with changes.
|
|
174
178
|
const status = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
|
175
179
|
if (status.code !== 0) {
|
|
176
|
-
ctx.ui.notify(
|
|
177
|
-
"Not a git repository (or git unavailable). Nothing to commit.",
|
|
178
|
-
"warning",
|
|
179
|
-
);
|
|
180
|
+
ctx.ui.notify("Not a git repository (or git unavailable). Nothing to commit.", "warning");
|
|
180
181
|
return;
|
|
181
182
|
}
|
|
182
183
|
if (status.stdout.trim().length === 0) {
|
|
@@ -197,10 +198,7 @@ export default function commitExtension(pi: ExtensionAPI) {
|
|
|
197
198
|
const afterStatus = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
|
198
199
|
const stagedFiles = parseStagedFiles(afterStatus.stdout);
|
|
199
200
|
if (stagedFiles.length === 0) {
|
|
200
|
-
ctx.ui.notify(
|
|
201
|
-
"No staged changes to commit. Stage files first, or run `/commit --all`.",
|
|
202
|
-
"warning",
|
|
203
|
-
);
|
|
201
|
+
ctx.ui.notify("No staged changes to commit. Stage files first, or run `/commit --all`.", "warning");
|
|
204
202
|
return;
|
|
205
203
|
}
|
|
206
204
|
|
|
@@ -243,10 +241,7 @@ export default function commitExtension(pi: ExtensionAPI) {
|
|
|
243
241
|
"info",
|
|
244
242
|
);
|
|
245
243
|
} catch (err) {
|
|
246
|
-
ctx.ui.notify(
|
|
247
|
-
`/commit error: ${err instanceof Error ? err.message : String(err)}`,
|
|
248
|
-
"error",
|
|
249
|
-
);
|
|
244
|
+
ctx.ui.notify(`/commit error: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
250
245
|
}
|
|
251
246
|
},
|
|
252
247
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
4
|
import { defineTool, type ExtensionAPI, getAgentDir } from "phi-code";
|
|
6
5
|
import { Type } from "typebox";
|
|
7
6
|
|
|
@@ -218,12 +217,7 @@ export default function goal(pi: ExtensionAPI) {
|
|
|
218
217
|
});
|
|
219
218
|
}
|
|
220
219
|
|
|
221
|
-
async function startGoal(
|
|
222
|
-
objective: string,
|
|
223
|
-
tokenBudget: number | undefined,
|
|
224
|
-
pi: ExtensionAPI,
|
|
225
|
-
ctx: StatusContext,
|
|
226
|
-
) {
|
|
220
|
+
async function startGoal(objective: string, tokenBudget: number | undefined, pi: ExtensionAPI, ctx: StatusContext) {
|
|
227
221
|
const validationError = validateObjective(objective);
|
|
228
222
|
if (validationError) {
|
|
229
223
|
ctx.ui.notify(validationError, "warning");
|
|
@@ -300,12 +294,7 @@ function clearGoal(ctx: StatusContext) {
|
|
|
300
294
|
ctx.ui.notify(`Goal cleared: ${stoppedGoal}`, "warning");
|
|
301
295
|
}
|
|
302
296
|
|
|
303
|
-
async function editGoal(
|
|
304
|
-
objective: string,
|
|
305
|
-
tokenBudget: number | undefined,
|
|
306
|
-
pi: ExtensionAPI,
|
|
307
|
-
ctx: StatusContext,
|
|
308
|
-
) {
|
|
297
|
+
async function editGoal(objective: string, tokenBudget: number | undefined, pi: ExtensionAPI, ctx: StatusContext) {
|
|
309
298
|
const validationError = validateObjective(objective);
|
|
310
299
|
if (validationError) {
|
|
311
300
|
ctx.ui.notify(validationError, "warning");
|
|
@@ -368,11 +357,7 @@ function editedGoalStatus(status: GoalStatus): GoalStatus {
|
|
|
368
357
|
}
|
|
369
358
|
|
|
370
359
|
function normalizeGoalForBudget(goal: ActiveGoal): ActiveGoal {
|
|
371
|
-
if (
|
|
372
|
-
goal.status === "active" &&
|
|
373
|
-
goal.tokenBudget !== undefined &&
|
|
374
|
-
goal.tokensUsed >= goal.tokenBudget
|
|
375
|
-
) {
|
|
360
|
+
if (goal.status === "active" && goal.tokenBudget !== undefined && goal.tokensUsed >= goal.tokenBudget) {
|
|
376
361
|
return { ...goal, status: "budget_limited" };
|
|
377
362
|
}
|
|
378
363
|
return goal;
|
|
@@ -382,11 +367,7 @@ function incrementGoal(goal: ActiveGoal): ActiveGoal {
|
|
|
382
367
|
return { ...goal, iteration: goal.iteration + 1, updatedAt: Date.now() };
|
|
383
368
|
}
|
|
384
369
|
|
|
385
|
-
function pauseGoalAfterAgentEnd(
|
|
386
|
-
ctx: StatusContext,
|
|
387
|
-
goal: ActiveGoal,
|
|
388
|
-
assistant: AssistantMessageLike,
|
|
389
|
-
) {
|
|
370
|
+
function pauseGoalAfterAgentEnd(ctx: StatusContext, goal: ActiveGoal, assistant: AssistantMessageLike) {
|
|
390
371
|
cancelContinuationPending();
|
|
391
372
|
activeGoal = transitionGoal(goal, "paused");
|
|
392
373
|
persistGoal(activeGoal);
|
|
@@ -582,7 +563,8 @@ function buildResumePrompt(goal: ActiveGoal) {
|
|
|
582
563
|
}
|
|
583
564
|
|
|
584
565
|
export function buildGoalSystemPrompt(goal: ActiveGoal) {
|
|
585
|
-
const budgetLine =
|
|
566
|
+
const budgetLine =
|
|
567
|
+
goal.tokenBudget === undefined ? "" : `\n- Respect the goal token budget (${formatBudget(goal)} used).`;
|
|
586
568
|
return `Active /goal:\n${goalObjectiveBlock(goal)}\n\nGoal-mode rules:\n- Keep going until the active goal is completely resolved end-to-end.\n- Treat the current worktree, command output, tests, and external state as authoritative.\n- Do not redefine the goal into a smaller task; audit every requirement before completion.\n- Do not stop at analysis, a plan, TODO list, partial fixes, or suggested next steps.\n- Autonomously perform implementation and verification with the available tools when they are needed to complete the goal.\n- Persevere through recoverable tool failures by trying reasonable alternatives instead of yielding early.\n- If the goal is not complete at the end of a turn, expect an automatic continuation and keep working from where you left off.\n- Only call the goal_complete tool after the goal is fully complete and verified.${budgetLine}`;
|
|
587
569
|
}
|
|
588
570
|
|
|
@@ -709,12 +691,10 @@ function loadGoalFromSession(ctx: StatusContext): ActiveGoal | undefined {
|
|
|
709
691
|
| {
|
|
710
692
|
getBranch?: () => Array<{ type?: string; customType?: string; data?: unknown }>;
|
|
711
693
|
getEntries?: () => Array<{ type?: string; customType?: string; data?: unknown }>;
|
|
712
|
-
|
|
694
|
+
}
|
|
713
695
|
| undefined;
|
|
714
696
|
const entries = sessionManager?.getBranch?.() ?? sessionManager?.getEntries?.() ?? [];
|
|
715
|
-
const entry = entries
|
|
716
|
-
.filter((entry) => entry.type === "custom" && entry.customType === GOAL_STATE_ENTRY_TYPE)
|
|
717
|
-
.pop();
|
|
697
|
+
const entry = entries.filter((entry) => entry.type === "custom" && entry.customType === GOAL_STATE_ENTRY_TYPE).pop();
|
|
718
698
|
const data = entry?.data as GoalStateEntryData | undefined;
|
|
719
699
|
return isGoal(data?.goal) && data.goal.status !== "complete" ? data.goal : undefined;
|
|
720
700
|
}
|
|
@@ -750,9 +730,7 @@ function readState(): Record<string, unknown> {
|
|
|
750
730
|
if (!existsSync(STATE_FILE)) return {};
|
|
751
731
|
try {
|
|
752
732
|
const parsed = JSON.parse(readFileSync(STATE_FILE, "utf8")) as unknown;
|
|
753
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
754
|
-
? (parsed as Record<string, unknown>)
|
|
755
|
-
: {};
|
|
733
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
|
|
756
734
|
} catch {
|
|
757
735
|
return {};
|
|
758
736
|
}
|
|
@@ -766,7 +744,6 @@ function clearLegacyPersistedGoal(cwd: string) {
|
|
|
766
744
|
writeFileSync(STATE_FILE, `${JSON.stringify(goals, null, 2)}\n`);
|
|
767
745
|
}
|
|
768
746
|
|
|
769
|
-
|
|
770
747
|
function isGoal(value: unknown): value is ActiveGoal {
|
|
771
748
|
if (!value || typeof value !== "object") return false;
|
|
772
749
|
const goal = value as Partial<ActiveGoal>;
|
package/extensions/phi/init.ts
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
* so a network failure on /v1/models cannot lose the user's input.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { ExtensionAPI } from "phi-code";
|
|
21
|
-
import { writeFile, mkdir, copyFile, readdir, readFile, chmod } from "node:fs/promises";
|
|
22
|
-
import { join, resolve } from "node:path";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
20
|
import { existsSync } from "node:fs";
|
|
21
|
+
import { chmod, copyFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join, resolve } from "node:path";
|
|
24
|
+
import type { ExtensionAPI } from "phi-code";
|
|
25
25
|
import { fetchLiveModels, pingProvider, toPersistedModel } from "./providers/live-models.js";
|
|
26
26
|
import { isOpenCodeGoAnthropicModel, OPENCODE_GO_ANTHROPIC_BASE_URL } from "./providers/opencode-go.js";
|
|
27
27
|
|
|
@@ -218,12 +218,30 @@ function getAllAvailableModels(providers: DetectedProvider[]): Array<{ ref: stri
|
|
|
218
218
|
// ─── Routing Presets ─────────────────────────────────────────────────────
|
|
219
219
|
|
|
220
220
|
const TASK_ROLES = [
|
|
221
|
-
{
|
|
221
|
+
{
|
|
222
|
+
key: "code",
|
|
223
|
+
label: "Code Generation",
|
|
224
|
+
desc: "Writing and modifying code",
|
|
225
|
+
agent: "code",
|
|
226
|
+
defaultModel: "default",
|
|
227
|
+
},
|
|
222
228
|
{ key: "debug", label: "Debugging", desc: "Finding and fixing bugs", agent: "code", defaultModel: "default" },
|
|
223
229
|
{ key: "plan", label: "Planning", desc: "Architecture and design", agent: "plan", defaultModel: "default" },
|
|
224
|
-
{
|
|
230
|
+
{
|
|
231
|
+
key: "explore",
|
|
232
|
+
label: "Exploration",
|
|
233
|
+
desc: "Code reading and analysis",
|
|
234
|
+
agent: "explore",
|
|
235
|
+
defaultModel: "default",
|
|
236
|
+
},
|
|
225
237
|
{ key: "test", label: "Testing", desc: "Running and writing tests", agent: "test", defaultModel: "default" },
|
|
226
|
-
{
|
|
238
|
+
{
|
|
239
|
+
key: "review",
|
|
240
|
+
label: "Code Review",
|
|
241
|
+
desc: "Quality and security review",
|
|
242
|
+
agent: "review",
|
|
243
|
+
defaultModel: "default",
|
|
244
|
+
},
|
|
227
245
|
] as const;
|
|
228
246
|
|
|
229
247
|
const KEYWORDS: Record<string, string[]> = {
|
|
@@ -235,9 +253,7 @@ const KEYWORDS: Record<string, string[]> = {
|
|
|
235
253
|
review: ["review", "audit", "quality", "security", "improve", "optimize"],
|
|
236
254
|
};
|
|
237
255
|
|
|
238
|
-
function createRouting(
|
|
239
|
-
assignments: Record<string, { preferred: string; fallback: string }>,
|
|
240
|
-
): RoutingConfig {
|
|
256
|
+
function createRouting(assignments: Record<string, { preferred: string; fallback: string }>): RoutingConfig {
|
|
241
257
|
const routes: RoutingConfig["routes"] = {};
|
|
242
258
|
for (const role of TASK_ROLES) {
|
|
243
259
|
const assignment = assignments[role.key];
|
|
@@ -251,7 +267,7 @@ function createRouting(
|
|
|
251
267
|
}
|
|
252
268
|
return {
|
|
253
269
|
routes,
|
|
254
|
-
default: { model: assignments
|
|
270
|
+
default: { model: assignments.default?.preferred || "default", agent: null },
|
|
255
271
|
};
|
|
256
272
|
}
|
|
257
273
|
|
|
@@ -358,10 +374,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
358
374
|
}
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
async function persistProviderKey(
|
|
362
|
-
provider: DetectedProvider,
|
|
363
|
-
apiKey: string,
|
|
364
|
-
): Promise<void> {
|
|
377
|
+
async function persistProviderKey(provider: DetectedProvider, apiKey: string): Promise<void> {
|
|
365
378
|
const config = await readModelsConfig();
|
|
366
379
|
const existing = config.providers[provider.id] ?? {};
|
|
367
380
|
config.providers[provider.id] = {
|
|
@@ -448,7 +461,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
448
461
|
|
|
449
462
|
// Orchestrator fallback (used only when a specific route has no model).
|
|
450
463
|
// This is NOT the chat default — `/model` controls that.
|
|
451
|
-
assignments
|
|
464
|
+
assignments.default = {
|
|
452
465
|
preferred: "default",
|
|
453
466
|
fallback: availableModels[0]?.ref || "default",
|
|
454
467
|
};
|
|
@@ -464,10 +477,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
464
477
|
if (result.source === "live" && result.models.length > 0) {
|
|
465
478
|
provider.models = result.models.map((m) => m.id);
|
|
466
479
|
provider.available = true;
|
|
467
|
-
ctx.ui.notify(
|
|
468
|
-
`${provider.name} is running with ${provider.models.length} model(s).\n`,
|
|
469
|
-
"info",
|
|
470
|
-
);
|
|
480
|
+
ctx.ui.notify(`${provider.name} is running with ${provider.models.length} model(s).\n`, "info");
|
|
471
481
|
} else {
|
|
472
482
|
ctx.ui.notify(
|
|
473
483
|
`${provider.name} not reachable on port ${port}. Start it and re-run \`/phi-init\`.\n`,
|
|
@@ -483,10 +493,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
483
493
|
"info",
|
|
484
494
|
);
|
|
485
495
|
|
|
486
|
-
const apiKey = await ctx.ui.input(
|
|
487
|
-
`Enter your ${provider.name} API key`,
|
|
488
|
-
"Paste your key here",
|
|
489
|
-
);
|
|
496
|
+
const apiKey = await ctx.ui.input(`Enter your ${provider.name} API key`, "Paste your key here");
|
|
490
497
|
|
|
491
498
|
if (apiKey === undefined) {
|
|
492
499
|
ctx.ui.notify("Cancelled. No key saved.", "warning");
|
|
@@ -632,10 +639,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
632
639
|
return `${status} ${p.name}${tag}${modelCount}`;
|
|
633
640
|
}),
|
|
634
641
|
];
|
|
635
|
-
const addProvider = await ctx.ui.select(
|
|
636
|
-
"Configure a provider (add multiple!):",
|
|
637
|
-
providerOptions,
|
|
638
|
-
);
|
|
642
|
+
const addProvider = await ctx.ui.select("Configure a provider (add multiple!):", providerOptions);
|
|
639
643
|
|
|
640
644
|
const choiceIdx = providerOptions.indexOf(addProvider ?? "");
|
|
641
645
|
if (choiceIdx <= 0) {
|
|
@@ -657,18 +661,12 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
657
661
|
|
|
658
662
|
const available = providers.filter((p) => p.available);
|
|
659
663
|
if (available.length === 0) {
|
|
660
|
-
ctx.ui.notify(
|
|
661
|
-
"No providers available. Run `/phi-init` again after setting up a provider.",
|
|
662
|
-
"error",
|
|
663
|
-
);
|
|
664
|
+
ctx.ui.notify("No providers available. Run `/phi-init` again after setting up a provider.", "error");
|
|
664
665
|
return;
|
|
665
666
|
}
|
|
666
667
|
|
|
667
668
|
const allModels = getAllAvailableModels(providers);
|
|
668
|
-
ctx.ui.notify(
|
|
669
|
-
`\n${allModels.length} models available from ${available.length} provider(s).\n`,
|
|
670
|
-
"info",
|
|
671
|
-
);
|
|
669
|
+
ctx.ui.notify(`\n${allModels.length} models available from ${available.length} provider(s).\n`, "info");
|
|
672
670
|
|
|
673
671
|
// 2. Assign models to agents
|
|
674
672
|
ctx.ui.notify("Assign a model to each agent role:\n", "info");
|
|
@@ -680,11 +678,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
680
678
|
|
|
681
679
|
ctx.ui.notify("Writing routing configuration...", "info");
|
|
682
680
|
const routing = createRouting(assignments);
|
|
683
|
-
await writeFile(
|
|
684
|
-
join(agentDir, "routing.json"),
|
|
685
|
-
JSON.stringify(routing, null, 2),
|
|
686
|
-
"utf-8",
|
|
687
|
-
);
|
|
681
|
+
await writeFile(join(agentDir, "routing.json"), JSON.stringify(routing, null, 2), "utf-8");
|
|
688
682
|
|
|
689
683
|
ctx.ui.notify("Setting up sub-agents...", "info");
|
|
690
684
|
await copyBundledAgents();
|
|
@@ -702,10 +696,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
702
696
|
const a = assignments[role.key];
|
|
703
697
|
ctx.ui.notify(` ${role.label}: \`${a.preferred}\` (fallback: \`${a.fallback}\`)`, "info");
|
|
704
698
|
}
|
|
705
|
-
ctx.ui.notify(
|
|
706
|
-
"\nChat default model: use `/model` (this wizard does NOT change the chat default).",
|
|
707
|
-
"info",
|
|
708
|
-
);
|
|
699
|
+
ctx.ui.notify("\nChat default model: use `/model` (this wizard does NOT change the chat default).", "info");
|
|
709
700
|
ctx.ui.notify("\nNext steps:", "info");
|
|
710
701
|
ctx.ui.notify(" - `/model` to pick the chat default model (sticky across prompts)", "info");
|
|
711
702
|
ctx.ui.notify(" - `/plan <description>` to run the orchestrator with the roles above", "info");
|
|
@@ -729,8 +720,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
729
720
|
description: "Reconfigure the per-role models used by /plan (provider-qualified, cross-provider)",
|
|
730
721
|
handler: async (_args, ctx) => {
|
|
731
722
|
try {
|
|
732
|
-
const registryModels: Array<{ provider?: string; id?: string }> =
|
|
733
|
-
ctx.modelRegistry?.getAvailable?.() || [];
|
|
723
|
+
const registryModels: Array<{ provider?: string; id?: string }> = ctx.modelRegistry?.getAvailable?.() || [];
|
|
734
724
|
const available: Array<{ ref: string; display: string }> = [];
|
|
735
725
|
const seen = new Set<string>();
|
|
736
726
|
for (const m of registryModels) {
|
package/extensions/phi/keys.ts
CHANGED
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { ApiKeyStore, type ExtensionAPI, getApiKeyStore, getConfigWatcher } from "phi-code";
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
(key: string, timeoutMs?: number): Promise<{ ok: boolean; error?: string }>;
|
|
27
|
-
}
|
|
25
|
+
type ProviderPingFn = (key: string, timeoutMs?: number) => Promise<{ ok: boolean; error?: string }>;
|
|
28
26
|
|
|
29
27
|
const PROVIDER_PING_REGISTRY: Record<string, ProviderPingFn> = {};
|
|
30
28
|
|
|
@@ -152,10 +150,7 @@ export default function keysExtension(pi: ExtensionAPI) {
|
|
|
152
150
|
return;
|
|
153
151
|
}
|
|
154
152
|
|
|
155
|
-
ctx.ui.notify(
|
|
156
|
-
"Unknown subcommand. Use: `/keys [list|set|remove|test|reload]`",
|
|
157
|
-
"warning",
|
|
158
|
-
);
|
|
153
|
+
ctx.ui.notify("Unknown subcommand. Use: `/keys [list|set|remove|test|reload]`", "warning");
|
|
159
154
|
} catch (err) {
|
|
160
155
|
ctx.ui.notify(`/keys error: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
161
156
|
}
|