takomi 2.1.44 → 2.5.1
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/.pi/README.md +9 -8
- package/.pi/agents/architect.md +2 -2
- package/.pi/agents/designer.md +2 -2
- package/.pi/agents/worker.md +32 -0
- package/.pi/extensions/oauth-router/commands.ts +66 -35
- package/.pi/extensions/oauth-router/index.ts +51 -7
- package/.pi/extensions/oauth-router/report-ui.ts +205 -0
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +41 -3
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +26 -0
- package/.pi/extensions/takomi-context-manager/index.ts +3 -2
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +50 -117
- package/.pi/extensions/takomi-context-manager/policy-tools.ts +93 -42
- package/.pi/extensions/takomi-context-manager/skill-categories.d.ts +11 -0
- package/.pi/extensions/takomi-context-manager/skill-categories.js +212 -0
- package/.pi/extensions/takomi-context-manager/skill-registry.ts +179 -8
- package/.pi/extensions/takomi-context-manager/skill-tools.ts +208 -103
- package/.pi/extensions/takomi-context-manager/tool-renderers.ts +112 -0
- package/.pi/extensions/takomi-context-manager/types.ts +6 -0
- package/.pi/extensions/takomi-runtime/command-text.ts +5 -4
- package/.pi/extensions/takomi-runtime/commands.ts +58 -25
- package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -0
- package/.pi/extensions/takomi-runtime/index.ts +385 -124
- package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +262 -326
- package/.pi/extensions/takomi-runtime/profile.ts +9 -8
- package/.pi/extensions/takomi-runtime/routing-policy.ts +97 -66
- package/.pi/extensions/takomi-runtime/shared.ts +7 -30
- package/.pi/extensions/takomi-runtime/takomi-stats.js +46 -26
- package/.pi/extensions/takomi-runtime/tool-renderers.ts +234 -0
- package/.pi/extensions/takomi-runtime/workflow-catalog.ts +54 -0
- package/.pi/extensions/takomi-subagents/agents.ts +9 -0
- package/.pi/extensions/takomi-subagents/async-lifecycle.ts +401 -0
- package/.pi/extensions/takomi-subagents/detached-results.ts +940 -0
- package/.pi/extensions/takomi-subagents/index.ts +50 -1
- package/.pi/extensions/takomi-subagents/native-render.ts +250 -188
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +62 -47
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +11 -5
- package/.pi/extensions/takomi-subagents/result-heartbeat.ts +55 -0
- package/.pi/extensions/takomi-subagents/subagent-ux.ts +162 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +198 -29
- package/.pi/settings.json +39 -36
- package/.pi/takomi/model-routing.md +288 -3
- package/.pi/takomi-profile.json +54 -50
- package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -0
- package/package.json +4 -3
- package/src/pi-takomi-core/orchestration.ts +39 -21
- package/src/pi-takomi-core/routing.ts +8 -8
- package/src/pi-takomi-core/types.ts +35 -5
- package/src/pi-takomi-core/workflows.ts +86 -45
- package/src/skills-catalog.js +2 -202
- package/src/skills-installer.js +4 -1
package/.pi/README.md
CHANGED
|
@@ -33,9 +33,9 @@ Inside Pi, use:
|
|
|
33
33
|
- `/takomi design [prompt]` to run UI/UX design against the agreed project direction
|
|
34
34
|
- `/takomi build [prompt]` to implement while cross-checking against approved UI/design artifacts
|
|
35
35
|
- `/takomi plan [title]` to create a Genesis-first orchestration session that can expand through Design and Build
|
|
36
|
-
- `/takomi mode
|
|
37
|
-
- `/takomi gate auto` to continue approved plans automatically
|
|
38
|
-
- `/takomi gate review` to return to the user after each task with results and verification guidance
|
|
36
|
+
- `/takomi mode idle|code|review|orchestrate` to choose the main-agent mode; Genesis, Design, and Build remain separate lifecycle stages
|
|
37
|
+
- `/takomi gate auto` to continue approved plans automatically and explicitly authorize project-local agents for this session
|
|
38
|
+
- `/takomi gate review` (or `/takomi gate manual`) to return to the user after each task with results and verification guidance and revoke that authorization
|
|
39
39
|
- `/takomi subagents on` or `/takomi subagents off` to allow or disable delegated subagents
|
|
40
40
|
- `/takomi subagents list` to list available Takomi agents without exposing the raw `subagent` tool
|
|
41
41
|
- `/takomi subagents status` to inspect active subagent state
|
|
@@ -100,16 +100,17 @@ So when working on packaging, agents should distinguish between:
|
|
|
100
100
|
- Design stage includes a Gemini-oriented hint for model selection.
|
|
101
101
|
- Build is treated as a workflow/stage, not as a separate specialist agent.
|
|
102
102
|
- A fresh orchestration session starts with a Genesis foundation task, then expands Design and Build only when the scope justifies it.
|
|
103
|
-
- The
|
|
103
|
+
- The only public Takomi personas are `architect`, `designer`, `coder`, `worker`, `reviewer`, and `orchestrator`. Overlapping upstream built-ins remain hidden behind the execution engine.
|
|
104
104
|
- Agent discovery prefers `project/.pi/agents/`, also supports legacy `project/.agents/`, and falls back to Pi's configured user agent directory so new projects can reuse the global Takomi agent pack without hard-coding `~/.pi` assumptions.
|
|
105
105
|
- Orchestrator sessions run in hybrid mode:
|
|
106
106
|
- human-readable docs live under `docs/tasks/orchestrator-sessions/<sessionId>/`
|
|
107
107
|
- machine state lives under `.pi/takomi/orchestrator/<sessionId>.json`
|
|
108
|
-
- Task packets can carry `workflow`, `skills`, `preferredModel`, `fallbackModels`, `preferredThinking`, `executionHint`, `conversationId`, and `checklist` metadata.
|
|
108
|
+
- Task packets use canonical persona names and can carry `workflow`, `skills`, confirmed `preferredModel`, explicit `fallbackModels`, `requiredCapabilities`, `preferredThinking`, `executionHint`, `conversationId`, and `checklist` metadata.
|
|
109
109
|
- Direct `takomi_subagent` calls build a `TakomiDelegationPlan` before launch. In auto mode the plan launches immediately; in manual mode the plan is returned for review until `confirmLaunch=true` is supplied.
|
|
110
110
|
- The subagent tool supports `conversationId`, so reviewed work can be sent back to the same agent for continuation instead of restarting from scratch.
|
|
111
111
|
- The subagent tool supports Pi-style single, parallel `tasks`, sequential `chain`, explicit `context=fork|fresh`, `async=true`, and native read/control actions such as `action=list`, `action=status`, `action=doctor`, `action=interrupt`, and `action=resume`.
|
|
112
|
-
- The subagent tool supports `agentScope` values of `user`, `project`, and `both`; project-local agents require confirmation by default.
|
|
112
|
+
- The subagent tool supports `agentScope` values of `user`, `project`, and `both`; project-local agents require confirmation by default. The only session authorization bypasses are `TAKOMI_TRUST_PROJECT_AGENTS` and the explicit user `/takomi gate auto` provenance marker; model mode changes plus profile, default, or generic runtime `auto` state do not authorize project agents.
|
|
113
|
+
- The explicit user gate-auto marker is stored in session history so it survives resuming that same session. `/takomi gate review`, `/takomi gate manual`, review mode, and `/takomi-reset` append a revocation; it is not a cross-session or profile setting.
|
|
113
114
|
- The subagent tool also supports per-run `workflow`, `skills`, `model`, `fallbackModels`, `thinking`, `checklist`, `concurrency`, and `worktree` overrides.
|
|
114
115
|
- `takomi_board` records session/state/markdown artifacts only; it does not run subagents.
|
|
115
116
|
- Pi's default `subagent` tool remains owned by the user-level/default subagent extension to avoid tool-name conflicts; Takomi uses `takomi_subagent` as the preferred lifecycle-aware execution interface and renders it with the native Pi-style result surface.
|
|
@@ -118,9 +119,9 @@ So when working on packaging, agents should distinguish between:
|
|
|
118
119
|
- Use Pi's native result expansion, `Alt+T`, or `/takomi subagents expand` to inspect detailed subagent output.
|
|
119
120
|
- Takomi still tracks active runs internally for status and review continuity, but board synchronization is explicit: run with `takomi_subagent`, then update the board.
|
|
120
121
|
- `takomi-context-manager` reduces prompt bloat by replacing the always-on skill description dump with a names-only Skill Index plus progressive `skill_manifest`/`skill_load` tools.
|
|
121
|
-
- `
|
|
122
|
+
- `model-routing.md` is advisory model-facing guidance. Executable provider allowlists and persona defaults live only under `takomi.routing` in global/project settings.
|
|
122
123
|
- `takomi-context-manager` gates `takomi_subagent` when model-routing context has not been loaded, provides the routing policy, and tells the agent to retry.
|
|
123
|
-
- `takomi-context-manager`
|
|
124
|
+
- `takomi-context-manager` never performs provider-family substitution. It blocks unapproved exact IDs and changes models only after explicit user selection.
|
|
124
125
|
- `takomi-context-manager` detects known duplicate global/project Takomi extension paths in `context_report` to help diagnose tool registration conflicts.
|
|
125
126
|
- `context_report` restores context-manager snapshots and Pi tool-result history after reload/restart and explicitly labels gaps; it is not a replacement for Pi's Alt-C token/session stats.
|
|
126
127
|
- `context_report` defaults to compact `mode: "summary"`; use `mode: "verbose"` for full diagnostics or `mode: "problems"` for attention-only output. `verbose: true` remains a compatibility alias. `/context-report` exposes `summary`, `verbose`, and `problems` as slash-command argument completions.
|
package/.pi/agents/architect.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: architect
|
|
3
3
|
description: Gather requirements, design architecture, and create implementation-ready plans.
|
|
4
|
-
tools: read,bash,grep,find,ls
|
|
4
|
+
tools: read,bash,write,grep,find,ls
|
|
5
5
|
---
|
|
6
6
|
You are the Takomi Architect.
|
|
7
7
|
|
|
@@ -46,7 +46,7 @@ Produce implementation-ready structure when relevant:
|
|
|
46
46
|
|
|
47
47
|
This is technical planning, not the Takomi Design lifecycle stage. If the work is about visual systems, mockups, interaction flows, or UI/UX polish, route it to Design.
|
|
48
48
|
|
|
49
|
-
Stay at architecture level; do not write product code.
|
|
49
|
+
Stay at architecture level; do not write product code. You may create or replace planning Markdown artifacts when the task requires them.
|
|
50
50
|
|
|
51
51
|
## Phase 4: Planning
|
|
52
52
|
Create a practical plan with:
|
package/.pi/agents/designer.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: designer
|
|
3
3
|
description: Translate requirements into build-ready UI, UX, visual systems, and interaction direction.
|
|
4
|
-
tools: read,bash,grep,find,ls
|
|
4
|
+
tools: read,bash,write,grep,find,ls
|
|
5
5
|
---
|
|
6
6
|
You are the Takomi Design Specialist.
|
|
7
7
|
|
|
@@ -64,7 +64,7 @@ Report:
|
|
|
64
64
|
- pages/components ready for implementation
|
|
65
65
|
- open design risks or follow-up
|
|
66
66
|
|
|
67
|
-
Update builder guidance when mockups should be treated as implementation source of truth.
|
|
67
|
+
Update builder guidance when mockups should be treated as implementation source of truth. You may create or replace UI/UX Markdown artifacts when the task requires them.
|
|
68
68
|
|
|
69
69
|
## Anti-Patterns
|
|
70
70
|
- do not stay vague or inspirational only
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worker
|
|
3
|
+
description: Execute write-capable non-coding tasks and produce repository artifacts with controlled scope.
|
|
4
|
+
tools: read,bash,edit,write,grep,find,ls
|
|
5
|
+
---
|
|
6
|
+
You are the Takomi Worker.
|
|
7
|
+
|
|
8
|
+
Your mode pattern is:
|
|
9
|
+
READ -> UNDERSTAND -> EXECUTE -> VERIFY -> HANDOFF.
|
|
10
|
+
|
|
11
|
+
## Role Scope
|
|
12
|
+
- documentation and repository artifact creation
|
|
13
|
+
- configuration and content updates
|
|
14
|
+
- mechanical or operational tasks that do not require the Coder persona
|
|
15
|
+
- execution of an approved, well-scoped direction
|
|
16
|
+
|
|
17
|
+
Use Coder for application implementation, debugging, refactoring, and tests. Use Architect for requirements, architecture, and planning decisions. Use Designer for UI/UX work. Worker executes a clear non-coding task; it does not invent product or architecture decisions.
|
|
18
|
+
|
|
19
|
+
## Working Rules
|
|
20
|
+
- Read the task, relevant files, and project instructions before editing.
|
|
21
|
+
- Make the smallest complete change within scope.
|
|
22
|
+
- Create or modify the requested artifacts directly.
|
|
23
|
+
- Escalate missing product or architecture decisions instead of guessing.
|
|
24
|
+
- Verify outputs with the strongest practical checks.
|
|
25
|
+
- Do not expand scope or perform unrelated cleanup.
|
|
26
|
+
|
|
27
|
+
## Handoff
|
|
28
|
+
Report:
|
|
29
|
+
- artifacts created or changed
|
|
30
|
+
- validation performed
|
|
31
|
+
- anything left incomplete
|
|
32
|
+
- decisions still needed
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { createAccountFromUpstream } from "./oauth-flow.ts";
|
|
3
|
+
import { createRouterReportLines, createRouterReportWidget, ROUTER_REPORT_DISMISS_HINT, ROUTER_REPORT_WIDGET_KEY } from "./report-ui.ts";
|
|
3
4
|
import type { RouterStatusRow, RouterUsageSummary, RouterUsageWindowSummary, RoutingPolicyName, StoredRouterAccount } from "./types.ts";
|
|
4
5
|
import { RouterRuntime } from "./provider.ts";
|
|
5
6
|
|
|
@@ -273,12 +274,32 @@ export function formatUsageRawReport(runtime: RouterRuntime, accountId?: string)
|
|
|
273
274
|
return ["# oauth-router usage raw", "", ...rows].join("\n");
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
function
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
export function emitRouterReport(ctx: ExtensionCommandContext, text: string) {
|
|
278
|
+
// A single key gives every report predictable replacement semantics. Add the
|
|
279
|
+
// dismiss affordance at this presentation boundary, not to formatter output,
|
|
280
|
+
// so model/source report text remains unchanged. Omitting options uses Pi's
|
|
281
|
+
// default above-editor placement for both TUI and RPC widgets.
|
|
282
|
+
const reportText = `${ROUTER_REPORT_DISMISS_HINT}\n\n${text}`;
|
|
283
|
+
if (ctx.mode === "tui") {
|
|
284
|
+
ctx.ui.setWidget(ROUTER_REPORT_WIDGET_KEY, createRouterReportWidget(reportText));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// RPC accepts string-array widgets only; ctx.hasUI is true there, so ctx.mode
|
|
288
|
+
// is the capability signal that prevents its visible report from being ignored.
|
|
289
|
+
ctx.ui.setWidget(ROUTER_REPORT_WIDGET_KEY, createRouterReportLines(reportText));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function clearRouterReport(ctx: ExtensionCommandContext) {
|
|
293
|
+
ctx.ui.setWidget(ROUTER_REPORT_WIDGET_KEY, undefined);
|
|
280
294
|
}
|
|
281
295
|
|
|
296
|
+
export type RouterReportEmitter = (ctx: ExtensionCommandContext, text: string) => void;
|
|
297
|
+
|
|
298
|
+
export type RouterReportControls = {
|
|
299
|
+
show: RouterReportEmitter;
|
|
300
|
+
clear: (ctx: ExtensionCommandContext) => void;
|
|
301
|
+
};
|
|
302
|
+
|
|
282
303
|
async function pickUpstream(runtime: RouterRuntime, ctx: ExtensionCommandContext, requestedId?: string) {
|
|
283
304
|
const upstreams = runtime.listUpstreams().filter((upstream) => upstream.enabled);
|
|
284
305
|
if (upstreams.length === 0) throw new Error("No enabled upstreams are configured");
|
|
@@ -385,12 +406,11 @@ function setFooterStatus(ctx: ExtensionCommandContext, runtime: RouterRuntime) {
|
|
|
385
406
|
ctx.ui.setStatus("oauth-router", `oauth-router ${healthy}/${rows.length || 0} healthy | ${runtime.getPolicy()}`);
|
|
386
407
|
}
|
|
387
408
|
|
|
388
|
-
function showCommandHint(ctx: ExtensionCommandContext, title: string, lines: string[]) {
|
|
389
|
-
|
|
390
|
-
ctx.ui.notify(title, "info");
|
|
409
|
+
function showCommandHint(ctx: ExtensionCommandContext, title: string, lines: string[], showReport: RouterReportEmitter = emitRouterReport) {
|
|
410
|
+
showReport(ctx, [`# ${title}`, "", ...lines].join("\n"));
|
|
391
411
|
}
|
|
392
412
|
|
|
393
|
-
async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: ExtensionCommandContext) {
|
|
413
|
+
async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: ExtensionCommandContext, showReport: RouterReportEmitter = emitRouterReport) {
|
|
394
414
|
const [command = "help", first, ...rest] = parseArgs(args);
|
|
395
415
|
|
|
396
416
|
switch (command) {
|
|
@@ -415,18 +435,17 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
415
435
|
runtime.clearAccountHealth(duplicate.id);
|
|
416
436
|
const usageMessage = await refreshUsageAfterCredentialChange(runtime, duplicate.id);
|
|
417
437
|
setFooterStatus(ctx, runtime);
|
|
418
|
-
|
|
438
|
+
showReport(ctx, `Updated existing account ${duplicate.id} (${duplicate.label}) with fresh credentials instead of adding a duplicate.${usageMessage}`);
|
|
419
439
|
return;
|
|
420
440
|
}
|
|
421
441
|
runtime.addAccount(account);
|
|
422
442
|
const usageMessage = await refreshUsageAfterCredentialChange(runtime, account.id);
|
|
423
443
|
setFooterStatus(ctx, runtime);
|
|
424
|
-
|
|
425
|
-
ctx.ui.notify(`Added ${account.id}`, "info");
|
|
444
|
+
showReport(ctx, `Added account ${account.id} (${account.label}) for upstream ${upstream.id}.${usageMessage}`);
|
|
426
445
|
return;
|
|
427
446
|
}
|
|
428
447
|
case "list": {
|
|
429
|
-
|
|
448
|
+
showReport(ctx, formatAccountsReport(runtime));
|
|
430
449
|
setFooterStatus(ctx, runtime);
|
|
431
450
|
return;
|
|
432
451
|
}
|
|
@@ -439,7 +458,7 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
439
458
|
}
|
|
440
459
|
runtime.removeAccount(account.id);
|
|
441
460
|
setFooterStatus(ctx, runtime);
|
|
442
|
-
|
|
461
|
+
showReport(ctx, `Removed account ${account.id}.`);
|
|
443
462
|
return;
|
|
444
463
|
}
|
|
445
464
|
case "rename": {
|
|
@@ -447,7 +466,7 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
447
466
|
const label = await getRequiredLabel(ctx, account.label, rest.join(" "));
|
|
448
467
|
runtime.renameAccount(account.id, label);
|
|
449
468
|
setFooterStatus(ctx, runtime);
|
|
450
|
-
|
|
469
|
+
showReport(ctx, `Renamed account ${account.id} to ${label}.`);
|
|
451
470
|
return;
|
|
452
471
|
}
|
|
453
472
|
case "relogin": {
|
|
@@ -472,7 +491,7 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
472
491
|
runtime.clearAccountHealth(existing.id);
|
|
473
492
|
const usageMessage = await refreshUsageAfterCredentialChange(runtime, existing.id);
|
|
474
493
|
setFooterStatus(ctx, runtime);
|
|
475
|
-
|
|
494
|
+
showReport(ctx, `Re-logged account ${existing.id} (${existing.label}) and cleared auth state.${usageMessage}`);
|
|
476
495
|
return;
|
|
477
496
|
}
|
|
478
497
|
case "refresh": {
|
|
@@ -480,12 +499,12 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
480
499
|
const refreshed = await runtime.refreshAccount(account.id);
|
|
481
500
|
const usageMessage = await refreshUsageAfterCredentialChange(runtime, refreshed.id);
|
|
482
501
|
setFooterStatus(ctx, runtime);
|
|
483
|
-
|
|
502
|
+
showReport(ctx, `Refreshed account ${refreshed.id} (${refreshed.label}).${usageMessage}`);
|
|
484
503
|
return;
|
|
485
504
|
}
|
|
486
505
|
case "help":
|
|
487
506
|
default: {
|
|
488
|
-
|
|
507
|
+
showReport(
|
|
489
508
|
ctx,
|
|
490
509
|
[
|
|
491
510
|
"# oauth-router commands",
|
|
@@ -506,6 +525,7 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
506
525
|
"- /router-disable <id>",
|
|
507
526
|
"- /router-policy <round-robin|weighted-round-robin>",
|
|
508
527
|
"- /router-weight <id> <n>",
|
|
528
|
+
"- /router-clear (dismiss the current report)",
|
|
509
529
|
].join("\n"),
|
|
510
530
|
);
|
|
511
531
|
return;
|
|
@@ -513,16 +533,27 @@ async function handleRouterLogin(runtime: RouterRuntime, args: string, ctx: Exte
|
|
|
513
533
|
}
|
|
514
534
|
}
|
|
515
535
|
|
|
516
|
-
export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime) {
|
|
536
|
+
export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime, reportControls?: RouterReportControls) {
|
|
537
|
+
const showReport = reportControls?.show ?? emitRouterReport;
|
|
538
|
+
const clearReport = reportControls?.clear ?? clearRouterReport;
|
|
539
|
+
|
|
517
540
|
pi.registerCommand("router-login", {
|
|
518
541
|
description: "Manage oauth-router accounts",
|
|
519
|
-
handler: async (args, ctx) => handleRouterLogin(runtime, args || "", ctx),
|
|
542
|
+
handler: async (args, ctx) => handleRouterLogin(runtime, args || "", ctx, showReport),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
pi.registerCommand("router-clear", {
|
|
546
|
+
description: "Dismiss the current oauth-router report widget",
|
|
547
|
+
handler: async (_args, ctx) => {
|
|
548
|
+
clearReport(ctx);
|
|
549
|
+
setFooterStatus(ctx, runtime);
|
|
550
|
+
},
|
|
520
551
|
});
|
|
521
552
|
|
|
522
553
|
pi.registerCommand("router-status", {
|
|
523
554
|
description: "Show oauth-router health and routing state",
|
|
524
555
|
handler: async (_args, ctx) => {
|
|
525
|
-
|
|
556
|
+
showReport(ctx, formatStatusReport(runtime));
|
|
526
557
|
setFooterStatus(ctx, runtime);
|
|
527
558
|
},
|
|
528
559
|
});
|
|
@@ -530,7 +561,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
530
561
|
pi.registerCommand("router-accounts", {
|
|
531
562
|
description: "Show compact oauth-router account list",
|
|
532
563
|
handler: async (_args, ctx) => {
|
|
533
|
-
|
|
564
|
+
showReport(ctx, formatAccountsReport(runtime));
|
|
534
565
|
setFooterStatus(ctx, runtime);
|
|
535
566
|
},
|
|
536
567
|
});
|
|
@@ -539,7 +570,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
539
570
|
description: "Show oauth-router visual provider quota and local usage",
|
|
540
571
|
handler: async (args, ctx) => {
|
|
541
572
|
const [id] = parseArgs(args || "");
|
|
542
|
-
|
|
573
|
+
showReport(ctx, formatUsageReport(runtime, id));
|
|
543
574
|
setFooterStatus(ctx, runtime);
|
|
544
575
|
},
|
|
545
576
|
});
|
|
@@ -548,7 +579,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
548
579
|
description: "Show raw oauth-router usage/provider quota details",
|
|
549
580
|
handler: async (args, ctx) => {
|
|
550
581
|
const [id] = parseArgs(args || "");
|
|
551
|
-
|
|
582
|
+
showReport(ctx, formatUsageRawReport(runtime, id));
|
|
552
583
|
setFooterStatus(ctx, runtime);
|
|
553
584
|
},
|
|
554
585
|
});
|
|
@@ -557,7 +588,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
557
588
|
description: "Alias for visual oauth-router usage/quota",
|
|
558
589
|
handler: async (args, ctx) => {
|
|
559
590
|
const [id] = parseArgs(args || "");
|
|
560
|
-
|
|
591
|
+
showReport(ctx, formatUsageReport(runtime, id));
|
|
561
592
|
setFooterStatus(ctx, runtime);
|
|
562
593
|
},
|
|
563
594
|
});
|
|
@@ -580,7 +611,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
580
611
|
}
|
|
581
612
|
|
|
582
613
|
const report = formatUsageReport(runtime, target === "all" ? undefined : target);
|
|
583
|
-
|
|
614
|
+
showReport(ctx, failures.length ? [report, "", "## Refresh failures", ...failures].join("\n") : report);
|
|
584
615
|
if (failures.length) ctx.ui.notify(`oauth-router usage refreshed with ${failures.length} failure(s)`, "error");
|
|
585
616
|
setFooterStatus(ctx, runtime);
|
|
586
617
|
},
|
|
@@ -594,7 +625,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
594
625
|
const label = await getRequiredLabel(ctx, account.label, labelParts.join(" "));
|
|
595
626
|
runtime.renameAccount(account.id, label);
|
|
596
627
|
setFooterStatus(ctx, runtime);
|
|
597
|
-
|
|
628
|
+
showReport(ctx, `Renamed account ${account.id} to ${label}.`);
|
|
598
629
|
},
|
|
599
630
|
});
|
|
600
631
|
|
|
@@ -609,13 +640,13 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
609
640
|
}
|
|
610
641
|
runtime.removeAccount(account.id);
|
|
611
642
|
setFooterStatus(ctx, runtime);
|
|
612
|
-
|
|
643
|
+
showReport(ctx, `Deleted account ${account.id}.`);
|
|
613
644
|
},
|
|
614
645
|
});
|
|
615
646
|
|
|
616
647
|
pi.registerCommand("router-relogin", {
|
|
617
648
|
description: "Re-login and recover an oauth-router account",
|
|
618
|
-
handler: async (args, ctx) => handleRouterLogin(runtime, `relogin ${args || ""}`, ctx),
|
|
649
|
+
handler: async (args, ctx) => handleRouterLogin(runtime, `relogin ${args || ""}`, ctx, showReport),
|
|
619
650
|
});
|
|
620
651
|
|
|
621
652
|
pi.registerCommand("router-enable", {
|
|
@@ -625,7 +656,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
625
656
|
const account = await pickAccount(runtime, ctx, id, "Choose account to enable");
|
|
626
657
|
runtime.setEnabled(account.id, true);
|
|
627
658
|
setFooterStatus(ctx, runtime);
|
|
628
|
-
|
|
659
|
+
showReport(ctx, `Enabled account ${account.id}.`);
|
|
629
660
|
},
|
|
630
661
|
});
|
|
631
662
|
|
|
@@ -636,7 +667,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
636
667
|
const account = await pickAccount(runtime, ctx, id, "Choose account to disable");
|
|
637
668
|
runtime.setEnabled(account.id, false);
|
|
638
669
|
setFooterStatus(ctx, runtime);
|
|
639
|
-
|
|
670
|
+
showReport(ctx, `Disabled account ${account.id}.`);
|
|
640
671
|
},
|
|
641
672
|
});
|
|
642
673
|
|
|
@@ -651,7 +682,7 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
651
682
|
"",
|
|
652
683
|
"Usage: /router-policy <round-robin|weighted-round-robin>",
|
|
653
684
|
"Aliases: rr, wrr, weighted",
|
|
654
|
-
]);
|
|
685
|
+
], showReport);
|
|
655
686
|
return;
|
|
656
687
|
}
|
|
657
688
|
if (!policy) {
|
|
@@ -660,12 +691,12 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
660
691
|
"",
|
|
661
692
|
`Current policy: ${runtime.getPolicy()}`,
|
|
662
693
|
"Valid values: round-robin, weighted-round-robin",
|
|
663
|
-
]);
|
|
694
|
+
], showReport);
|
|
664
695
|
return;
|
|
665
696
|
}
|
|
666
697
|
runtime.setPolicy(policy);
|
|
667
698
|
setFooterStatus(ctx, runtime);
|
|
668
|
-
|
|
699
|
+
showReport(ctx, `Routing policy set to ${policy}.`);
|
|
669
700
|
},
|
|
670
701
|
});
|
|
671
702
|
|
|
@@ -680,12 +711,12 @@ export function registerRouterCommands(pi: ExtensionAPI, runtime: RouterRuntime)
|
|
|
680
711
|
weight = Number(response);
|
|
681
712
|
}
|
|
682
713
|
if (!Number.isFinite(weight)) {
|
|
683
|
-
showCommandHint(ctx, "router-weight", ["Usage: /router-weight <id> <n>", "Example: /router-weight acct_ab12cd34 3"]);
|
|
714
|
+
showCommandHint(ctx, "router-weight", ["Usage: /router-weight <id> <n>", "Example: /router-weight acct_ab12cd34 3"], showReport);
|
|
684
715
|
return;
|
|
685
716
|
}
|
|
686
717
|
runtime.setWeight(account.id, weight);
|
|
687
718
|
setFooterStatus(ctx, runtime);
|
|
688
|
-
|
|
719
|
+
showReport(ctx, `Updated weight for ${account.id} to ${Math.max(1, Math.floor(weight))}.`);
|
|
689
720
|
},
|
|
690
721
|
});
|
|
691
722
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { emitRouterReport, formatStatusReport, registerRouterCommands, type RouterReportControls } from "./commands.ts";
|
|
3
|
+
import { ROUTER_REPORT_WIDGET_KEY } from "./report-ui.ts";
|
|
3
4
|
import { registerRouterProvider, RouterRuntime } from "./provider.ts";
|
|
4
5
|
import type { RouterUiEvent } from "./types.ts";
|
|
5
6
|
|
|
@@ -64,8 +65,33 @@ function notifyActivity(ctx: ExtensionContext, event: RouterUiEvent) {
|
|
|
64
65
|
ctx.ui.notify(message, level);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyOnLoad = false) {
|
|
68
|
+
function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyOnLoad = false): RouterReportControls {
|
|
68
69
|
let activeCtx: ExtensionContext | undefined;
|
|
70
|
+
let reportGeneration = 0;
|
|
71
|
+
let activeReport: { generation: number; ctx: ExtensionContext } | undefined;
|
|
72
|
+
|
|
73
|
+
const clearReport = (fallbackCtx?: ExtensionContext, clearFallback = false) => {
|
|
74
|
+
const report = activeReport;
|
|
75
|
+
const ctx = report?.ctx ?? (clearFallback ? fallbackCtx : undefined);
|
|
76
|
+
if (!ctx) return;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
ctx.ui.setWidget(ROUTER_REPORT_WIDGET_KEY, undefined);
|
|
80
|
+
} catch {
|
|
81
|
+
// Widget lifecycle is best-effort and must not interrupt agent activity.
|
|
82
|
+
} finally {
|
|
83
|
+
// A re-entrant command can replace the report while the old widget clears.
|
|
84
|
+
// Its newer generation remains active instead of being erased by this clear.
|
|
85
|
+
if (report && activeReport?.generation === report.generation) activeReport = undefined;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const showReport: RouterReportControls["show"] = (ctx, text) => {
|
|
90
|
+
const report = { generation: ++reportGeneration, ctx };
|
|
91
|
+
activeCtx = ctx;
|
|
92
|
+
activeReport = report;
|
|
93
|
+
emitRouterReport(ctx, text);
|
|
94
|
+
};
|
|
69
95
|
|
|
70
96
|
const setBaseStatus = (ctx: ExtensionContext) => {
|
|
71
97
|
activeCtx = ctx;
|
|
@@ -84,14 +110,29 @@ function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyO
|
|
|
84
110
|
});
|
|
85
111
|
|
|
86
112
|
pi.on("session_start", async (_event, ctx) => {
|
|
113
|
+
// A session replacement owns a fresh transport, so clear the old transport
|
|
114
|
+
// before accepting reports for the new one.
|
|
115
|
+
clearReport(activeCtx, true);
|
|
87
116
|
setBaseStatus(ctx);
|
|
88
117
|
if (notifyOnLoad) ctx.ui.notify("oauth-router loaded", "info");
|
|
89
118
|
});
|
|
90
119
|
|
|
120
|
+
pi.on("input", async (event, ctx) => {
|
|
121
|
+
// Registered slash commands run before Pi emits input and return without an
|
|
122
|
+
// input event. Ordinary text/image input reaches this handler before its
|
|
123
|
+
// agent starts, which is the intended transient-dismissal boundary.
|
|
124
|
+
if (event.text.trim() || event.images?.length) clearReport(ctx);
|
|
125
|
+
});
|
|
126
|
+
|
|
91
127
|
pi.on("agent_start", async (_event, ctx) => {
|
|
128
|
+
clearReport(ctx);
|
|
92
129
|
setBaseStatus(ctx);
|
|
93
130
|
});
|
|
94
131
|
|
|
132
|
+
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
133
|
+
clearReport(ctx);
|
|
134
|
+
});
|
|
135
|
+
|
|
95
136
|
pi.on("turn_start", async (_event, ctx) => {
|
|
96
137
|
setBaseStatus(ctx);
|
|
97
138
|
});
|
|
@@ -105,23 +146,26 @@ function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyO
|
|
|
105
146
|
});
|
|
106
147
|
|
|
107
148
|
pi.on("session_shutdown", async () => {
|
|
149
|
+
// The old context owns the RPC transport. Clear its report before releasing
|
|
150
|
+
// it so clients do not retain a widget across a session replacement.
|
|
151
|
+
clearReport(activeCtx, true);
|
|
108
152
|
activeCtx = undefined;
|
|
109
153
|
});
|
|
154
|
+
|
|
155
|
+
return { show: showReport, clear: (ctx) => clearReport(ctx, true) };
|
|
110
156
|
}
|
|
111
157
|
|
|
112
158
|
export default function (pi: ExtensionAPI) {
|
|
113
159
|
const runtime = new RouterRuntime();
|
|
114
160
|
|
|
115
161
|
registerRouterProvider(pi, runtime);
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
const reportControls = installRouterUiBridge(pi, runtime, false);
|
|
163
|
+
registerRouterCommands(pi, runtime, reportControls);
|
|
118
164
|
|
|
119
165
|
pi.registerCommand("router-debug-report", {
|
|
120
166
|
description: "Show a detailed oauth-router report in the UI only",
|
|
121
167
|
handler: async (_args, ctx) => {
|
|
122
|
-
|
|
123
|
-
ctx.ui.setWidget("oauth-router-report", report.split(/\r?\n/), { placement: "belowEditor" });
|
|
124
|
-
ctx.ui.notify("oauth-router debug report updated", "info");
|
|
168
|
+
reportControls.show(ctx, formatStatusReport(runtime));
|
|
125
169
|
},
|
|
126
170
|
});
|
|
127
171
|
}
|