@ilya-lesikov/pi-pi 0.6.0 → 0.8.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-ask-user/index.ts +1 -1
- 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 +7 -7
- package/extensions/orchestrator/agents/constraints.test.ts +44 -0
- package/extensions/orchestrator/agents/constraints.ts +3 -0
- 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 +23 -1
- package/extensions/orchestrator/command-handlers.ts +1 -1
- package/extensions/orchestrator/config.ts +8 -4
- package/extensions/orchestrator/context.test.ts +54 -0
- package/extensions/orchestrator/context.ts +65 -2
- package/extensions/orchestrator/custom-footer.ts +5 -2
- package/extensions/orchestrator/doctor.test.ts +3 -1
- package/extensions/orchestrator/doctor.ts +40 -2
- package/extensions/orchestrator/event-handlers.test.ts +97 -1
- package/extensions/orchestrator/event-handlers.ts +222 -48
- package/extensions/orchestrator/flant-infra.test.ts +312 -0
- package/extensions/orchestrator/flant-infra.ts +407 -44
- package/extensions/orchestrator/index.ts +1 -1
- package/extensions/orchestrator/integration.test.ts +312 -18
- package/extensions/orchestrator/messages.test.ts +30 -0
- package/extensions/orchestrator/messages.ts +6 -0
- package/extensions/orchestrator/model-registry.test.ts +124 -13
- package/extensions/orchestrator/model-registry.ts +91 -33
- package/extensions/orchestrator/orchestrator.test.ts +113 -0
- package/extensions/orchestrator/orchestrator.ts +163 -3
- package/extensions/orchestrator/phases/brainstorm.ts +9 -20
- package/extensions/orchestrator/phases/implementation.test.ts +11 -0
- package/extensions/orchestrator/phases/implementation.ts +4 -6
- package/extensions/orchestrator/phases/planning.test.ts +16 -0
- package/extensions/orchestrator/phases/planning.ts +11 -4
- package/extensions/orchestrator/phases/review-task.ts +1 -4
- package/extensions/orchestrator/phases/review.test.ts +62 -0
- package/extensions/orchestrator/phases/review.ts +58 -15
- package/extensions/orchestrator/plannotator.ts +9 -6
- package/extensions/orchestrator/pp-menu.test.ts +74 -1
- package/extensions/orchestrator/pp-menu.ts +366 -94
- 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.ts +41 -20
- package/extensions/orchestrator/test-helpers.ts +18 -3
- package/extensions/orchestrator/usage-tracker.test.ts +131 -3
- package/extensions/orchestrator/usage-tracker.ts +78 -11
- package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
- package/extensions/orchestrator/validate-artifacts.ts +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { SUB_MODEL_PREFIX, SUB_PROVIDER } from "./flant-infra.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A usage unit is subscription-routed (flat-rate personal Claude subscription,
|
|
8
|
+
* not per-token billed) when its main-turn provider is the subscription
|
|
9
|
+
* provider, or when the registered model id carries the `sub/` prefix. The
|
|
10
|
+
* prefix is the only signal available for subagents (subagents:completed has no
|
|
11
|
+
* provider field), so we always check it in addition to the provider.
|
|
12
|
+
*/
|
|
13
|
+
export function isSubscriptionRouted(modelId?: string, provider?: string): boolean {
|
|
14
|
+
if (provider === SUB_PROVIDER) return true;
|
|
15
|
+
if (typeof modelId !== "string") return false;
|
|
16
|
+
// Accept a bare `sub/<m>` id OR a full provider-prefixed spec
|
|
17
|
+
// `pp-flant-anthropic-sub/sub/<m>` passed as a single id (the form callers
|
|
18
|
+
// store/pass without a separate provider field).
|
|
19
|
+
return modelId.startsWith(SUB_MODEL_PREFIX) || modelId.startsWith(`${SUB_PROVIDER}/`);
|
|
20
|
+
}
|
|
4
21
|
|
|
5
22
|
export interface ModelUsage {
|
|
6
23
|
inputTokens: number;
|
|
@@ -9,6 +26,8 @@ export interface ModelUsage {
|
|
|
9
26
|
cacheWriteTokens: number;
|
|
10
27
|
cacheSupported: boolean;
|
|
11
28
|
turns: number;
|
|
29
|
+
/** Flat-rate personal subscription: dollars are excluded from cost totals, tokens are not. */
|
|
30
|
+
subscription: boolean;
|
|
12
31
|
}
|
|
13
32
|
|
|
14
33
|
export interface UsageTracker {
|
|
@@ -19,6 +38,8 @@ export interface UsageTracker {
|
|
|
19
38
|
getTotalOutputTokens(): number;
|
|
20
39
|
getTotalCacheReadTokens(): number;
|
|
21
40
|
getTotalCacheWriteTokens(): number;
|
|
41
|
+
/** Total input the model actually processed: uncached input + cache read + cache write (main + subagents). */
|
|
42
|
+
getTotalProcessedInputTokens(): number;
|
|
22
43
|
getTotalCost(): number;
|
|
23
44
|
getMainInputTokens(): number;
|
|
24
45
|
getMainOutputTokens(): number;
|
|
@@ -46,6 +67,8 @@ export interface SubagentUsage {
|
|
|
46
67
|
cost: number;
|
|
47
68
|
durationMs: number;
|
|
48
69
|
toolUses: number;
|
|
70
|
+
/** Flat-rate personal subscription: dollars are excluded from cost totals, tokens are not. */
|
|
71
|
+
subscription: boolean;
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
interface TrackerState {
|
|
@@ -58,6 +81,8 @@ interface TrackerState {
|
|
|
58
81
|
totalTurns: number;
|
|
59
82
|
subagentInputTokens: number;
|
|
60
83
|
subagentOutputTokens: number;
|
|
84
|
+
subagentCacheReadTokens: number;
|
|
85
|
+
subagentCacheWriteTokens: number;
|
|
61
86
|
subagentCost: number;
|
|
62
87
|
models: Map<string, ModelUsage>;
|
|
63
88
|
subagents: SubagentUsage[];
|
|
@@ -89,6 +114,8 @@ function createInitialState(): TrackerState {
|
|
|
89
114
|
totalTurns: 0,
|
|
90
115
|
subagentInputTokens: 0,
|
|
91
116
|
subagentOutputTokens: 0,
|
|
117
|
+
subagentCacheReadTokens: 0,
|
|
118
|
+
subagentCacheWriteTokens: 0,
|
|
92
119
|
subagentCost: 0,
|
|
93
120
|
models: new Map<string, ModelUsage>(),
|
|
94
121
|
subagents: [],
|
|
@@ -99,12 +126,15 @@ export function createUsageTracker(): UsageTracker {
|
|
|
99
126
|
const state = createInitialState();
|
|
100
127
|
|
|
101
128
|
return {
|
|
102
|
-
recordTurn(modelId: string,
|
|
129
|
+
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number, cacheSupported?: boolean): void {
|
|
103
130
|
const safeInput = toFiniteNumber(input);
|
|
104
131
|
const safeOutput = toFiniteNumber(output);
|
|
105
132
|
const safeCacheRead = toFiniteNumber(cacheRead);
|
|
106
133
|
const safeCacheWrite = toFiniteNumber(cacheWrite);
|
|
107
|
-
const
|
|
134
|
+
const subscription = isSubscriptionRouted(modelId, provider);
|
|
135
|
+
// Subscription-routed turns are flat-rate: keep the tokens but exclude the
|
|
136
|
+
// fictitious per-token dollars so totals stay paid-only by construction.
|
|
137
|
+
const safeCost = subscription ? 0 : toFiniteNumber(cost);
|
|
108
138
|
|
|
109
139
|
state.totalInputTokens += safeInput;
|
|
110
140
|
state.totalOutputTokens += safeOutput;
|
|
@@ -113,7 +143,11 @@ export function createUsageTracker(): UsageTracker {
|
|
|
113
143
|
state.totalCost += safeCost;
|
|
114
144
|
state.totalTurns += 1;
|
|
115
145
|
|
|
116
|
-
|
|
146
|
+
// Key subscription turns under the sub/ prefix even when detected only by
|
|
147
|
+
// provider (bare model id), so a paid and a subscription turn for the same
|
|
148
|
+
// underlying model never share a row and the paid dollars stay visible.
|
|
149
|
+
const baseKey = modelId || "unknown-model";
|
|
150
|
+
const key = subscription && !baseKey.startsWith(SUB_MODEL_PREFIX) ? `${SUB_MODEL_PREFIX}${baseKey}` : baseKey;
|
|
117
151
|
const usage = state.models.get(key) ?? {
|
|
118
152
|
inputTokens: 0,
|
|
119
153
|
outputTokens: 0,
|
|
@@ -121,6 +155,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
121
155
|
cacheWriteTokens: 0,
|
|
122
156
|
cacheSupported: false,
|
|
123
157
|
turns: 0,
|
|
158
|
+
subscription: false,
|
|
124
159
|
};
|
|
125
160
|
|
|
126
161
|
usage.inputTokens += safeInput;
|
|
@@ -128,6 +163,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
128
163
|
usage.cacheReadTokens += safeCacheRead;
|
|
129
164
|
usage.cacheWriteTokens += safeCacheWrite;
|
|
130
165
|
if (cacheSupported) usage.cacheSupported = true;
|
|
166
|
+
if (subscription) usage.subscription = true;
|
|
131
167
|
usage.turns += 1;
|
|
132
168
|
state.models.set(key, usage);
|
|
133
169
|
},
|
|
@@ -142,12 +178,17 @@ export function createUsageTracker(): UsageTracker {
|
|
|
142
178
|
const safeTotal = toFiniteNumber(tokens.total);
|
|
143
179
|
const safeCacheRead = toFiniteNumber(tokens.cacheRead);
|
|
144
180
|
const safeCacheWrite = toFiniteNumber(tokens.cacheWrite);
|
|
145
|
-
|
|
181
|
+
// Subagents carry no provider field, so detect subscription routing from
|
|
182
|
+
// the registered model id prefix. Zero the dollars when subscription.
|
|
183
|
+
const subscription = isSubscriptionRouted(meta?.modelId);
|
|
184
|
+
const safeCost = subscription ? 0 : toFiniteNumber(tokens.cost ?? cost);
|
|
146
185
|
|
|
147
186
|
const effectiveInput = safeInput === 0 && safeOutput === 0 ? safeTotal : safeInput;
|
|
148
187
|
|
|
149
188
|
state.subagentInputTokens += effectiveInput;
|
|
150
189
|
state.subagentOutputTokens += safeOutput;
|
|
190
|
+
state.subagentCacheReadTokens += safeCacheRead;
|
|
191
|
+
state.subagentCacheWriteTokens += safeCacheWrite;
|
|
151
192
|
state.subagentCost += safeCost;
|
|
152
193
|
|
|
153
194
|
const cacheSupported = typeof tokens.cacheRead === "number" || typeof tokens.cacheWrite === "number";
|
|
@@ -164,6 +205,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
164
205
|
cost: safeCost,
|
|
165
206
|
durationMs: toFiniteNumber(meta?.durationMs),
|
|
166
207
|
toolUses: toFiniteNumber(meta?.toolUses),
|
|
208
|
+
subscription,
|
|
167
209
|
});
|
|
168
210
|
},
|
|
169
211
|
|
|
@@ -183,24 +225,33 @@ export function createUsageTracker(): UsageTracker {
|
|
|
183
225
|
state.subagents = [];
|
|
184
226
|
state.subagentInputTokens = 0;
|
|
185
227
|
state.subagentOutputTokens = 0;
|
|
228
|
+
state.subagentCacheReadTokens = 0;
|
|
229
|
+
state.subagentCacheWriteTokens = 0;
|
|
186
230
|
state.subagentCost = 0;
|
|
187
231
|
for (const sa of summary.subagents as Record<string, unknown>[]) {
|
|
232
|
+
const modelId = typeof sa.modelId === "string" ? sa.modelId : "unknown";
|
|
233
|
+
// Pre-change summaries lack the flag; recover it from the sub/ prefix
|
|
234
|
+
// so legacy subscription rows are not restored as paid.
|
|
235
|
+
const subscription = sa.subscription === true || isSubscriptionRouted(modelId);
|
|
188
236
|
const entry: SubagentUsage = {
|
|
189
237
|
description: typeof sa.description === "string" ? sa.description : "unknown",
|
|
190
238
|
agentType: typeof sa.agentType === "string" ? sa.agentType : "unknown",
|
|
191
|
-
modelId
|
|
239
|
+
modelId,
|
|
192
240
|
inputTokens: toFiniteNumber(sa.inputTokens),
|
|
193
241
|
outputTokens: toFiniteNumber(sa.outputTokens),
|
|
194
242
|
cacheReadTokens: toFiniteNumber(sa.cacheReadTokens),
|
|
195
243
|
cacheWriteTokens: toFiniteNumber(sa.cacheWriteTokens),
|
|
196
244
|
cacheSupported: sa.cacheSupported === true,
|
|
197
|
-
cost: toFiniteNumber(sa.cost),
|
|
245
|
+
cost: subscription ? 0 : toFiniteNumber(sa.cost),
|
|
198
246
|
durationMs: toFiniteNumber(sa.durationMs),
|
|
199
247
|
toolUses: toFiniteNumber(sa.toolUses),
|
|
248
|
+
subscription,
|
|
200
249
|
};
|
|
201
250
|
state.subagents.push(entry);
|
|
202
251
|
state.subagentInputTokens += entry.inputTokens;
|
|
203
252
|
state.subagentOutputTokens += entry.outputTokens;
|
|
253
|
+
state.subagentCacheReadTokens += entry.cacheReadTokens;
|
|
254
|
+
state.subagentCacheWriteTokens += entry.cacheWriteTokens;
|
|
204
255
|
state.subagentCost += entry.cost;
|
|
205
256
|
}
|
|
206
257
|
}
|
|
@@ -213,6 +264,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
213
264
|
cacheWriteTokens: toFiniteNumber(usage.cacheWriteTokens),
|
|
214
265
|
cacheSupported: (usage as any).cacheSupported === true,
|
|
215
266
|
turns: toFiniteNumber(usage.turns),
|
|
267
|
+
subscription: (usage as any).subscription === true || isSubscriptionRouted(modelId),
|
|
216
268
|
});
|
|
217
269
|
}
|
|
218
270
|
}
|
|
@@ -230,11 +282,22 @@ export function createUsageTracker(): UsageTracker {
|
|
|
230
282
|
},
|
|
231
283
|
|
|
232
284
|
getTotalCacheReadTokens(): number {
|
|
233
|
-
return state.totalCacheReadTokens;
|
|
285
|
+
return state.totalCacheReadTokens + state.subagentCacheReadTokens;
|
|
234
286
|
},
|
|
235
287
|
|
|
236
288
|
getTotalCacheWriteTokens(): number {
|
|
237
|
-
return state.totalCacheWriteTokens;
|
|
289
|
+
return state.totalCacheWriteTokens + state.subagentCacheWriteTokens;
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
getTotalProcessedInputTokens(): number {
|
|
293
|
+
return (
|
|
294
|
+
state.totalInputTokens +
|
|
295
|
+
state.subagentInputTokens +
|
|
296
|
+
state.totalCacheReadTokens +
|
|
297
|
+
state.subagentCacheReadTokens +
|
|
298
|
+
state.totalCacheWriteTokens +
|
|
299
|
+
state.subagentCacheWriteTokens
|
|
300
|
+
);
|
|
238
301
|
},
|
|
239
302
|
|
|
240
303
|
getTotalCost(): number {
|
|
@@ -262,9 +325,11 @@ export function createUsageTracker(): UsageTracker {
|
|
|
262
325
|
},
|
|
263
326
|
|
|
264
327
|
getCacheHitRate(): number {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
328
|
+
const totalCacheRead = state.totalCacheReadTokens + state.subagentCacheReadTokens;
|
|
329
|
+
// Session-wide, token-weighted average: cache reads over all processed
|
|
330
|
+
// input (uncached input + cache read + cache write). Including cache
|
|
331
|
+
// writes keeps first-touch (uncached) content from inflating the rate.
|
|
332
|
+
const denominator = this.getTotalProcessedInputTokens();
|
|
268
333
|
if (denominator <= 0) return 0;
|
|
269
334
|
return totalCacheRead / denominator;
|
|
270
335
|
},
|
|
@@ -324,6 +389,8 @@ export function createUsageTracker(): UsageTracker {
|
|
|
324
389
|
state.totalTurns = 0;
|
|
325
390
|
state.subagentInputTokens = 0;
|
|
326
391
|
state.subagentOutputTokens = 0;
|
|
392
|
+
state.subagentCacheReadTokens = 0;
|
|
393
|
+
state.subagentCacheWriteTokens = 0;
|
|
327
394
|
state.subagentCost = 0;
|
|
328
395
|
state.models.clear();
|
|
329
396
|
state.subagents = [];
|
|
@@ -41,6 +41,26 @@ Ship minimal fix.
|
|
|
41
41
|
expect(result.ok).toBe(true);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
it("accepts a Pattern constraints section", () => {
|
|
45
|
+
const content = `# Plan
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
Add a new annotation.
|
|
49
|
+
|
|
50
|
+
## Checklist
|
|
51
|
+
- [ ] Add annotation — Done when: tests pass
|
|
52
|
+
|
|
53
|
+
## Pattern constraints
|
|
54
|
+
- Mirror deletePolicies: one typed slice, kebab-case values.
|
|
55
|
+
|
|
56
|
+
## Blockers
|
|
57
|
+
- none
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const result = validatePlan(content);
|
|
61
|
+
expect(result.ok).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
44
64
|
it("accepts multiline Done when continuation", () => {
|
|
45
65
|
const content = `# Plan
|
|
46
66
|
|
|
@@ -5,7 +5,7 @@ const USER_REQUEST_ALLOWED_SECTIONS = ["Problem", "Constraints"] as const;
|
|
|
5
5
|
const RESEARCH_REQUIRED_SECTIONS = ["Affected Code", "Architecture Context", "Constraints & Edge Cases"] as const;
|
|
6
6
|
const RESEARCH_ALLOWED_SECTIONS = ["Affected Code", "Architecture Context", "Constraints & Edge Cases", "Open Questions"] as const;
|
|
7
7
|
const PLAN_REQUIRED_SECTIONS = ["Scope", "Checklist"] as const;
|
|
8
|
-
const PLAN_ALLOWED_SECTIONS = ["Scope", "Checklist", "Blockers"] as const;
|
|
8
|
+
const PLAN_ALLOWED_SECTIONS = ["Scope", "Checklist", "Pattern constraints", "Blockers"] as const;
|
|
9
9
|
const PLACEHOLDER_PATTERNS = /^(?:[-*.…—]|tbd|todo|n\/a|na|none|\.{2,})$/i;
|
|
10
10
|
|
|
11
11
|
function splitLines(content: string): string[] {
|
|
@@ -186,7 +186,7 @@ export function validatePlan(content: string): ValidationResult {
|
|
|
186
186
|
const errors: string[] = [];
|
|
187
187
|
const h1 = getH1(content);
|
|
188
188
|
const firstHeading = getFirstHeading(content);
|
|
189
|
-
const expectedSections = formatSectionList(PLAN_REQUIRED_SECTIONS, ["Blockers"]);
|
|
189
|
+
const expectedSections = formatSectionList(PLAN_REQUIRED_SECTIONS, ["Pattern constraints", "Blockers"]);
|
|
190
190
|
|
|
191
191
|
if (!firstHeading) {
|
|
192
192
|
errors.push("Missing required heading: # Plan. Expected first heading: # Plan");
|