@ilya-lesikov/pi-pi 0.7.0 → 0.9.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/3p/pi-plannotator/apps/pi-extension/README.md +6 -5
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +25 -10
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.code-review.test.ts +172 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +50 -12
- package/3p/pi-plannotator/apps/pi-extension/server/project.test.ts +45 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +7 -6
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +41 -25
- package/3p/pi-subagents/src/agent-manager.ts +34 -1
- package/3p/pi-subagents/src/agent-runner.ts +66 -33
- package/3p/pi-subagents/src/index.ts +3 -38
- package/3p/pi-subagents/src/types.ts +4 -0
- package/extensions/orchestrator/agents/advisor.ts +35 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/code-reviewer.ts +27 -11
- package/extensions/orchestrator/agents/constraints.test.ts +82 -0
- package/extensions/orchestrator/agents/constraints.ts +66 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +35 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/planner.ts +9 -8
- package/extensions/orchestrator/agents/prompts.test.ts +120 -0
- package/extensions/orchestrator/agents/reviewer.ts +48 -0
- package/extensions/orchestrator/agents/task.ts +6 -20
- package/extensions/orchestrator/agents/tool-routing.ts +39 -1
- package/extensions/orchestrator/ai-comment-cleanup.test.ts +89 -0
- package/extensions/orchestrator/ai-comment-cleanup.ts +73 -0
- package/extensions/orchestrator/command-handlers.test.ts +47 -0
- package/extensions/orchestrator/command-handlers.ts +44 -28
- package/extensions/orchestrator/config.test.ts +40 -1
- package/extensions/orchestrator/config.ts +18 -2
- package/extensions/orchestrator/context.test.ts +54 -0
- package/extensions/orchestrator/context.ts +81 -2
- package/extensions/orchestrator/custom-footer.test.ts +91 -0
- package/extensions/orchestrator/custom-footer.ts +20 -20
- package/extensions/orchestrator/event-handlers.test.ts +412 -2
- package/extensions/orchestrator/event-handlers.ts +556 -227
- package/extensions/orchestrator/flant-infra.test.ts +25 -0
- package/extensions/orchestrator/flant-infra.ts +91 -0
- package/extensions/orchestrator/index.ts +1 -1
- package/extensions/orchestrator/integration.test.ts +411 -20
- package/extensions/orchestrator/messages.test.ts +30 -0
- package/extensions/orchestrator/messages.ts +6 -0
- package/extensions/orchestrator/model-registry.test.ts +48 -1
- package/extensions/orchestrator/model-registry.ts +43 -1
- package/extensions/orchestrator/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/orchestrator/orchestrator.test.ts +113 -0
- package/extensions/orchestrator/orchestrator.ts +197 -60
- package/extensions/orchestrator/phases/brainstorm.ts +20 -27
- package/extensions/orchestrator/phases/implementation.test.ts +11 -0
- package/extensions/orchestrator/phases/implementation.ts +4 -6
- package/extensions/orchestrator/phases/machine.test.ts +36 -0
- package/extensions/orchestrator/phases/machine.ts +11 -1
- package/extensions/orchestrator/phases/planning.test.ts +16 -0
- package/extensions/orchestrator/phases/planning.ts +11 -4
- package/extensions/orchestrator/phases/review-task.test.ts +20 -0
- package/extensions/orchestrator/phases/review-task.ts +47 -22
- package/extensions/orchestrator/phases/review.test.ts +34 -0
- package/extensions/orchestrator/phases/review.ts +44 -6
- package/extensions/orchestrator/plannotator.ts +9 -6
- package/extensions/orchestrator/pp-menu.test.ts +207 -1
- package/extensions/orchestrator/pp-menu.ts +514 -402
- package/extensions/orchestrator/pp-state-tools.test.ts +192 -0
- package/extensions/orchestrator/pp-state-tools.ts +249 -0
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +128 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +98 -0
- package/extensions/orchestrator/rate-limit-fallback.ts +243 -0
- package/extensions/orchestrator/state.test.ts +9 -0
- package/extensions/orchestrator/state.ts +15 -0
- package/extensions/orchestrator/test-helpers.ts +4 -1
- package/extensions/orchestrator/transition-controller.test.ts +100 -0
- package/extensions/orchestrator/transition-controller.ts +61 -3
- package/extensions/orchestrator/usage-tracker.ts +5 -1
- package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
- package/extensions/orchestrator/validate-artifacts.ts +2 -2
- package/package.json +1 -1
- package/AGENTS.md +0 -28
|
@@ -427,6 +427,7 @@ describe("flant-infra", () => {
|
|
|
427
427
|
enabled: false,
|
|
428
428
|
autoUpdate: true,
|
|
429
429
|
cacheTTLDays: 7,
|
|
430
|
+
switchBackIntervalMinutes: 30,
|
|
430
431
|
subscription: false,
|
|
431
432
|
lastUpdated: null,
|
|
432
433
|
cachedFlantModels: null,
|
|
@@ -465,6 +466,7 @@ describe("flant-infra", () => {
|
|
|
465
466
|
enabled: true,
|
|
466
467
|
autoUpdate: false,
|
|
467
468
|
cacheTTLDays: 3,
|
|
469
|
+
switchBackIntervalMinutes: 30,
|
|
468
470
|
subscription: false,
|
|
469
471
|
lastUpdated: "2026-01-02T03:04:05.000Z",
|
|
470
472
|
cachedFlantModels: ["gpt-5-4", "claude-opus-4-6"],
|
|
@@ -488,6 +490,7 @@ describe("flant-infra", () => {
|
|
|
488
490
|
enabled: true,
|
|
489
491
|
autoUpdate: true,
|
|
490
492
|
cacheTTLDays: 14,
|
|
493
|
+
switchBackIntervalMinutes: 30,
|
|
491
494
|
subscription: true,
|
|
492
495
|
lastUpdated: "2026-02-01T00:00:00.000Z",
|
|
493
496
|
cachedFlantModels: ["claude-opus-4-6", "gpt-5-4"],
|
|
@@ -508,4 +511,26 @@ describe("flant-infra", () => {
|
|
|
508
511
|
expect(loaded).toEqual(settings);
|
|
509
512
|
expect(existsSync(join(dir, "extensions", "pp", "cache", "flant-models.json"))).toBe(true);
|
|
510
513
|
});
|
|
514
|
+
|
|
515
|
+
it("normalizes switchBackIntervalMinutes: default when missing, parsed, floored to >=1", async () => {
|
|
516
|
+
const dir = makeTempDir();
|
|
517
|
+
const mod = await loadFlantInfraModule(dir);
|
|
518
|
+
const settingsDir = join(dir, "extensions", "pp", "cache");
|
|
519
|
+
const settingsPath = join(settingsDir, "flant-models.json");
|
|
520
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
521
|
+
|
|
522
|
+
// Missing -> default 30.
|
|
523
|
+
writeFileSync(settingsPath, JSON.stringify({ enabled: true }), "utf-8");
|
|
524
|
+
expect(mod.loadFlantSettings().switchBackIntervalMinutes).toBe(30);
|
|
525
|
+
|
|
526
|
+
// String numeric -> parsed and rounded.
|
|
527
|
+
writeFileSync(settingsPath, JSON.stringify({ enabled: true, switchBackIntervalMinutes: "45" }), "utf-8");
|
|
528
|
+
expect(mod.loadFlantSettings().switchBackIntervalMinutes).toBe(45);
|
|
529
|
+
|
|
530
|
+
// Invalid / <1 -> floored to 1.
|
|
531
|
+
writeFileSync(settingsPath, JSON.stringify({ enabled: true, switchBackIntervalMinutes: 0 }), "utf-8");
|
|
532
|
+
expect(mod.loadFlantSettings().switchBackIntervalMinutes).toBe(1);
|
|
533
|
+
writeFileSync(settingsPath, JSON.stringify({ enabled: true, switchBackIntervalMinutes: "nonsense" }), "utf-8");
|
|
534
|
+
expect(mod.loadFlantSettings().switchBackIntervalMinutes).toBe(30);
|
|
535
|
+
});
|
|
511
536
|
});
|
|
@@ -35,6 +35,13 @@ export interface FlantSettings {
|
|
|
35
35
|
* Claude OAuth token (billed against their personal Claude subscription).
|
|
36
36
|
*/
|
|
37
37
|
subscription: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Minutes between out-of-band "is the subscription limit cleared yet?" probes
|
|
40
|
+
* while the sub→non-sub rate-limit fallback is active. On each interval a
|
|
41
|
+
* cheap probe hits the sub model; on success the user is asked to switch back.
|
|
42
|
+
* Default 30.
|
|
43
|
+
*/
|
|
44
|
+
switchBackIntervalMinutes: number;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
const GEMINI_MAP: Record<string, string> = {
|
|
@@ -64,6 +71,7 @@ const DEFAULT_SETTINGS: FlantSettings = {
|
|
|
64
71
|
cachedFlantModels: null,
|
|
65
72
|
cachedOpenRouterData: null,
|
|
66
73
|
subscription: false,
|
|
74
|
+
switchBackIntervalMinutes: 30,
|
|
67
75
|
};
|
|
68
76
|
|
|
69
77
|
/** Provider name for the personal-subscription Claude routing. */
|
|
@@ -245,10 +253,15 @@ function normalizeSettings(raw: unknown): FlantSettings {
|
|
|
245
253
|
if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
|
|
246
254
|
const value = raw as Record<string, unknown>;
|
|
247
255
|
const cacheTTLDays = Math.max(1, Math.round(toNumber(value.cacheTTLDays, DEFAULT_SETTINGS.cacheTTLDays)));
|
|
256
|
+
const switchBackIntervalMinutes = Math.max(
|
|
257
|
+
1,
|
|
258
|
+
Math.round(toNumber(value.switchBackIntervalMinutes, DEFAULT_SETTINGS.switchBackIntervalMinutes)),
|
|
259
|
+
);
|
|
248
260
|
return {
|
|
249
261
|
enabled: !!value.enabled,
|
|
250
262
|
autoUpdate: value.autoUpdate === undefined ? true : !!value.autoUpdate,
|
|
251
263
|
cacheTTLDays,
|
|
264
|
+
switchBackIntervalMinutes,
|
|
252
265
|
subscription: !!value.subscription,
|
|
253
266
|
lastUpdated: typeof value.lastUpdated === "string" ? value.lastUpdated : null,
|
|
254
267
|
cachedFlantModels: Array.isArray(value.cachedFlantModels)
|
|
@@ -347,6 +360,81 @@ function mapFlantToOpenRouterId(modelId: string): string | null {
|
|
|
347
360
|
return null;
|
|
348
361
|
}
|
|
349
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Out-of-band probe: is the personal Claude subscription limit cleared yet?
|
|
365
|
+
* Sends a tiny, fully throwaway request (NOT part of the session, so it cannot
|
|
366
|
+
* pollute conversation/context/cache) to the gateway's Anthropic endpoint using
|
|
367
|
+
* the personal Claude OAuth token. Returns:
|
|
368
|
+
* "ok" — 200: capacity is back (offer switch-back)
|
|
369
|
+
* "rate_limited" — 429: still limited (stay on non-sub, retry later)
|
|
370
|
+
* "error" — credentials missing / network / other status (treat as not-back)
|
|
371
|
+
*
|
|
372
|
+
* The OAuth token is refreshed first so a failure is a genuine 429 and not an
|
|
373
|
+
* expired-token false negative. `max_tokens: 1` + an explicit "just respond hi"
|
|
374
|
+
* instruction keep output at ~1 token.
|
|
375
|
+
*/
|
|
376
|
+
// Derive the gateway probe model id (`sub/<bare-claude-id>`) from any stored
|
|
377
|
+
// form: `pp-flant-anthropic-sub/sub/<m>`, `sub/<m>`, or a bare `<m>`. Exported
|
|
378
|
+
// for testing the derivation without a network call.
|
|
379
|
+
export function subProbeModelId(modelId: string): string {
|
|
380
|
+
let bare = modelId;
|
|
381
|
+
if (bare.startsWith(`${SUB_PROVIDER}/`)) bare = bare.slice(`${SUB_PROVIDER}/`.length);
|
|
382
|
+
if (bare.startsWith(SUB_MODEL_PREFIX)) bare = bare.slice(SUB_MODEL_PREFIX.length);
|
|
383
|
+
return `${SUB_MODEL_PREFIX}${bare}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export async function probeSubscriptionCleared(
|
|
387
|
+
modelId: string,
|
|
388
|
+
): Promise<"ok" | "rate_limited" | "error"> {
|
|
389
|
+
const log = getLogger();
|
|
390
|
+
await refreshClaudeOAuthToken();
|
|
391
|
+
const oauthToken = readClaudeOAuthToken();
|
|
392
|
+
const gatewayKey = readGatewayApiKey();
|
|
393
|
+
if (!oauthToken || !gatewayKey) {
|
|
394
|
+
log.debug({ s: "flant", hasOAuth: !!oauthToken, hasGatewayKey: !!gatewayKey }, "probe skipped: missing credentials");
|
|
395
|
+
return "error";
|
|
396
|
+
}
|
|
397
|
+
// The gateway expects the bare claude-* id under the `sub/` prefix.
|
|
398
|
+
const probeModel = subProbeModelId(modelId);
|
|
399
|
+
try {
|
|
400
|
+
const res = await fetch("https://llm-api.flant.ru/v1/messages", {
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: {
|
|
403
|
+
"content-type": "application/json",
|
|
404
|
+
"anthropic-version": "2023-06-01",
|
|
405
|
+
// Claude Code OAuth identity headers — the gateway requires these for
|
|
406
|
+
// subscription (sub/) routing; without them it can reject the probe and
|
|
407
|
+
// we would never detect that the limit cleared. Mirrors doctor.ts.
|
|
408
|
+
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
|
|
409
|
+
"user-agent": "claude-cli/1.0.0",
|
|
410
|
+
"x-app": "cli",
|
|
411
|
+
Authorization: `Bearer ${oauthToken}`,
|
|
412
|
+
"x-litellm-api-key": `Bearer ${gatewayKey}`,
|
|
413
|
+
},
|
|
414
|
+
body: JSON.stringify({
|
|
415
|
+
model: probeModel,
|
|
416
|
+
max_tokens: 1,
|
|
417
|
+
temperature: 0,
|
|
418
|
+
messages: [{ role: "user", content: "just respond hi" }],
|
|
419
|
+
}),
|
|
420
|
+
signal: AbortSignal.timeout(30000),
|
|
421
|
+
});
|
|
422
|
+
if (res.status === 429) {
|
|
423
|
+
log.debug({ s: "flant", model: probeModel }, "probe: still rate limited");
|
|
424
|
+
return "rate_limited";
|
|
425
|
+
}
|
|
426
|
+
if (res.ok) {
|
|
427
|
+
log.debug({ s: "flant", model: probeModel }, "probe: capacity back");
|
|
428
|
+
return "ok";
|
|
429
|
+
}
|
|
430
|
+
log.debug({ s: "flant", model: probeModel, status: res.status }, "probe: unexpected status");
|
|
431
|
+
return "error";
|
|
432
|
+
} catch (err: any) {
|
|
433
|
+
log.debug({ s: "flant", err: err?.message }, "probe failed");
|
|
434
|
+
return "error";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
350
438
|
export async function discoverFlantModels(apiKey: string): Promise<string[]> {
|
|
351
439
|
const res = await fetch("https://llm-api.flant.ru/v1/models", {
|
|
352
440
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -655,6 +743,9 @@ export function generateFlantConfig(models: string[], subscriptionActive = false
|
|
|
655
743
|
explore: { model: modelSpec(fastModel, sub), thinking: "low" },
|
|
656
744
|
librarian: { model: modelSpec(fastModel, sub), thinking: "medium" },
|
|
657
745
|
task: { model: modelSpec(taskModel, sub), thinking: "medium" },
|
|
746
|
+
advisor: { model: modelSpec(latestGpt ?? fallback, sub), thinking: "high" },
|
|
747
|
+
"deep-debugger": { model: modelSpec(latestGpt ?? fallback, sub), thinking: "high" },
|
|
748
|
+
reviewer: { model: modelSpec(latestGpt ?? fallback, sub), thinking: "high" },
|
|
658
749
|
},
|
|
659
750
|
presetGroups: {
|
|
660
751
|
planners: buildPresetGroup({
|
|
@@ -65,7 +65,7 @@ function registerSubagentTools(pi: ExtensionAPI): void {
|
|
|
65
65
|
...event.content,
|
|
66
66
|
{
|
|
67
67
|
type: "text" as const,
|
|
68
|
-
text: `\n\n<validation-error>\nPlan structure is invalid:\n${result.errors.map((e) => `- ${e}`).join("\n")}\n\nFix immediately. Required structure:\n# Plan\n## Scope\n<2-4 lines>\n## Checklist\n- [ ] <outcome> — Done when: <observable condition>\n## Blockers (optional)\n<issues>\n\nRewrite the file now.\n</validation-error>`,
|
|
68
|
+
text: `\n\n<validation-error>\nPlan structure is invalid:\n${result.errors.map((e) => `- ${e}`).join("\n")}\n\nFix immediately. Required structure:\n# Plan\n## Scope\n<2-4 lines>\n## Checklist\n- [ ] <outcome> — Done when: <observable condition>\n## Pattern constraints (optional; include when adding a type/function/user-facing value)\n<closest existing analog + conventions to mirror>\n## Blockers (optional)\n<issues>\n\nRewrite the file now.\n</validation-error>`,
|
|
69
69
|
},
|
|
70
70
|
],
|
|
71
71
|
};
|