@danypops/pi-jittor 0.2.0 → 0.2.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/extension/src/benchmark-tui.ts +27 -12
- package/extension/src/capabilities/codex-recovery.ts +39 -23
- package/extension/src/capabilities/context-hub.ts +1 -5
- package/extension/src/capabilities/local-run-telemetry.ts +14 -10
- package/extension/src/capabilities/provider-response-telemetry.ts +20 -6
- package/extension/src/context-breakdown.ts +60 -29
- package/extension/src/context-report.ts +16 -5
- package/extension/src/context-view.ts +39 -7
- package/extension/src/footer.ts +56 -26
- package/extension/src/index.ts +228 -97
- package/extension/src/service-client.ts +1 -1
- package/extension/src/settings-tui.ts +35 -20
- package/extension/src/settings.ts +13 -10
- package/extension/src/tui.ts +148 -63
- package/extension/src/usage.ts +124 -42
- package/package.json +3 -3
package/extension/src/index.ts
CHANGED
|
@@ -1,50 +1,58 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContextSegmentItem } from "@danypops/jittor";
|
|
2
2
|
import {
|
|
3
|
+
applyTaskFocusEvent,
|
|
3
4
|
CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN,
|
|
4
|
-
CONTEXT_HUB_CONTRIBUTION_CHANNEL,
|
|
5
|
-
FOOTER_COMPACTION_RENDER_INTERVAL_MS,
|
|
6
|
-
MAX_DYNAMIC_ROUTES,
|
|
7
|
-
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
8
|
-
PAPYRUS_TASK_FOCUS_CHANNEL,
|
|
9
5
|
CONTEXT_EVENT_DEDUP_LIMIT,
|
|
6
|
+
CONTEXT_HUB_CONTRIBUTION_CHANNEL,
|
|
10
7
|
CompactionTelemetry,
|
|
11
|
-
papyrusContextMetric,
|
|
12
|
-
validatePapyrusContextInjection,
|
|
13
|
-
applyTaskFocusEvent,
|
|
14
|
-
validateTaskFocusEvent,
|
|
15
|
-
toolLedgerSegment,
|
|
16
|
-
TASK_DOMAINS,
|
|
17
|
-
TASK_TYPES,
|
|
18
|
-
USAGE_PERIODS,
|
|
19
8
|
type ContextAssessment,
|
|
9
|
+
FOOTER_COMPACTION_RENDER_INTERVAL_MS,
|
|
10
|
+
MAX_DYNAMIC_ROUTES,
|
|
20
11
|
type MetricObservation,
|
|
21
12
|
type ModelCandidate,
|
|
22
13
|
type ModelTaskDomain,
|
|
23
14
|
type ModelTaskType,
|
|
15
|
+
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
16
|
+
PAPYRUS_TASK_FOCUS_CHANNEL,
|
|
24
17
|
type PolicyDecision,
|
|
18
|
+
papyrusContextMetric,
|
|
25
19
|
type Route,
|
|
26
20
|
type RouterStatus,
|
|
27
21
|
type StoredMetricObservation,
|
|
22
|
+
TASK_DOMAINS,
|
|
23
|
+
TASK_TYPES,
|
|
24
|
+
toolLedgerSegment,
|
|
25
|
+
USAGE_PERIODS,
|
|
28
26
|
type UsagePeriod,
|
|
27
|
+
validatePapyrusContextInjection,
|
|
28
|
+
validateTaskFocusEvent,
|
|
29
29
|
} from "@danypops/jittor";
|
|
30
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
30
31
|
import { showBenchmarkPanel } from "./benchmark-tui.ts";
|
|
31
|
-
import {
|
|
32
|
+
import { CodexRecoveryCapability, type CodexRecoveryRuntime, SYSTEM_RECOVERY_RUNTIME } from "./capabilities/codex-recovery.ts";
|
|
33
|
+
import { ContextHubCapability } from "./capabilities/context-hub.ts";
|
|
34
|
+
import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
|
|
35
|
+
import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
|
|
36
|
+
import {
|
|
37
|
+
basePromptSegment,
|
|
38
|
+
buildBasePromptItems,
|
|
39
|
+
buildMessageHistoryTree,
|
|
40
|
+
composeContextBreakdown,
|
|
41
|
+
messageHistorySegment,
|
|
42
|
+
type SessionEntryLike,
|
|
43
|
+
type SessionTreeNodeLike,
|
|
44
|
+
} from "./context-breakdown.ts";
|
|
45
|
+
import { showContextView } from "./context-view.ts";
|
|
46
|
+
import { type CompactionProgress, type IntegratedFooterState, installIntegratedFooter } from "./footer.ts";
|
|
32
47
|
import { callJittor } from "./service-client.ts";
|
|
33
|
-
import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
34
|
-
import { showSettingsPanel } from "./settings-tui.ts";
|
|
35
|
-
import { buildFooterBudget, formatFooterStatus, providerBudgetMetricQuery, showJittorPanel } from "./tui.ts";
|
|
36
48
|
import { cacheSessionSecret, forgetSessionSecret, sessionSecretField } from "./session-identity.ts";
|
|
49
|
+
import { type CodexRecoveryControl, type EnforcementControl, persistentEnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
50
|
+
import { showSettingsPanel } from "./settings-tui.ts";
|
|
51
|
+
import { buildFooterBudget, providerBudgetMetricQuery, showJittorPanel } from "./tui.ts";
|
|
37
52
|
import { showUsagePanel } from "./usage.ts";
|
|
38
|
-
import { CodexRecoveryCapability, SYSTEM_RECOVERY_RUNTIME, type CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
39
|
-
import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
|
|
40
|
-
import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
|
|
41
|
-
import { ContextHubCapability } from "./capabilities/context-hub.ts";
|
|
42
|
-
import { basePromptSegment, buildBasePromptItems, buildMessageHistoryTree, composeContextBreakdown, messageHistorySegment, type SessionEntryLike, type SessionTreeNodeLike } from "./context-breakdown.ts";
|
|
43
|
-
import { showContextView } from "./context-view.ts";
|
|
44
|
-
import type { ContextSegmentItem } from "@danypops/jittor";
|
|
45
53
|
|
|
46
|
-
export { formatFooterStatus } from "./tui.ts";
|
|
47
54
|
export type { CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
55
|
+
export { formatFooterStatus } from "./tui.ts";
|
|
48
56
|
|
|
49
57
|
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
50
58
|
const RECOVERY_GUIDANCE = "Run /jittor off to disable blocking, or restart the daemon with: systemctl --user restart jittor.service";
|
|
@@ -61,9 +69,9 @@ function usageBudgetControl(enforcement: EnforcementControl): UsageBudgetControl
|
|
|
61
69
|
const candidate = enforcement as EnforcementControl & Partial<UsageBudgetControl>;
|
|
62
70
|
return typeof candidate.getUsageTokenBudget === "function" && typeof candidate.setUsageTokenBudget === "function"
|
|
63
71
|
? {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
getUsageTokenBudget: (period) => candidate.getUsageTokenBudget!(period),
|
|
73
|
+
setUsageTokenBudget: (period, tokens) => candidate.setUsageTokenBudget!(period, tokens),
|
|
74
|
+
}
|
|
67
75
|
: { getUsageTokenBudget: () => undefined, setUsageTokenBudget() {} };
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -72,9 +80,9 @@ function recoveryControl(enforcement: EnforcementControl): CodexRecoveryControl
|
|
|
72
80
|
const set = (candidate as Partial<CodexRecoveryControl>).setCodexRecoveryEnabled;
|
|
73
81
|
return typeof candidate.isCodexRecoveryEnabled === "function" && typeof set === "function"
|
|
74
82
|
? {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
|
|
84
|
+
setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
|
|
85
|
+
}
|
|
78
86
|
: { isCodexRecoveryEnabled: () => false, setCodexRecoveryEnabled() {} };
|
|
79
87
|
}
|
|
80
88
|
|
|
@@ -87,9 +95,9 @@ async function recordMetrics(client: JittorExtensionClient, metrics: MetricObser
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState, sessionId: string): Promise<void> {
|
|
90
|
-
const status = await client.call("router.status", { session_id: sessionId }) as RouterStatus;
|
|
98
|
+
const status = (await client.call("router.status", { session_id: sessionId })) as RouterStatus;
|
|
91
99
|
const query = providerBudgetMetricQuery(status);
|
|
92
|
-
const metrics = query ? await client.call("metrics.query", query) as StoredMetricObservation[] : [];
|
|
100
|
+
const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
|
|
93
101
|
state.providerBudget = buildFooterBudget(status, metrics);
|
|
94
102
|
state.requestRender?.();
|
|
95
103
|
}
|
|
@@ -98,10 +106,14 @@ function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
|
|
|
98
106
|
if (milliseconds <= 0) return Promise.resolve();
|
|
99
107
|
return new Promise((resolve, reject) => {
|
|
100
108
|
const timer = setTimeout(resolve, milliseconds);
|
|
101
|
-
signal?.addEventListener(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
signal?.addEventListener(
|
|
110
|
+
"abort",
|
|
111
|
+
() => {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
reject(new Error("Jittor throttle cancelled"));
|
|
114
|
+
},
|
|
115
|
+
{ once: true },
|
|
116
|
+
);
|
|
105
117
|
});
|
|
106
118
|
}
|
|
107
119
|
|
|
@@ -114,7 +126,7 @@ async function applyRoute(pi: ExtensionAPI, ctx: ExtensionContext, route: Route)
|
|
|
114
126
|
const model = ctx.modelRegistry.find(route.provider, route.model);
|
|
115
127
|
if (!model) return false;
|
|
116
128
|
if (!ctx.model || ctx.model.provider !== route.provider || ctx.model.id !== route.model) {
|
|
117
|
-
if (!await pi.setModel(model)) return false;
|
|
129
|
+
if (!(await pi.setModel(model))) return false;
|
|
118
130
|
}
|
|
119
131
|
if (THINKING_LEVELS.has(route.thinking)) pi.setThinkingLevel(route.thinking as Parameters<ExtensionAPI["setThinkingLevel"]>[0]);
|
|
120
132
|
return true;
|
|
@@ -142,7 +154,12 @@ function modelCost(model: PiRouteModel): number {
|
|
|
142
154
|
export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: string): ModelCandidate[] {
|
|
143
155
|
const candidates: ModelCandidate[] = [];
|
|
144
156
|
for (const model of models) {
|
|
145
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
!model.provider ||
|
|
159
|
+
!model.id ||
|
|
160
|
+
candidates.some((candidate) => candidate.provider === model.provider && candidate.model === model.id)
|
|
161
|
+
)
|
|
162
|
+
continue;
|
|
146
163
|
const level = supportsThinking(model, thinking) ? thinking : "off";
|
|
147
164
|
candidates.push({ provider: model.provider, model: model.id, thinking: level });
|
|
148
165
|
if (candidates.length >= MAX_DYNAMIC_ROUTES) break;
|
|
@@ -153,13 +170,21 @@ export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: stri
|
|
|
153
170
|
export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
|
|
154
171
|
const catalog = models
|
|
155
172
|
.filter((model) => model.provider.length > 0 && model.id.length > 0)
|
|
156
|
-
.filter(
|
|
173
|
+
.filter(
|
|
174
|
+
(model, index, rows) => rows.findIndex((candidate) => candidate.provider === model.provider && candidate.id === model.id) === index,
|
|
175
|
+
);
|
|
157
176
|
if (!catalog.some((model) => model.provider === current.provider && model.id === current.id)) catalog.push(current);
|
|
158
|
-
const currentLevel = THINKING_DESCENDING.indexOf(thinking as typeof THINKING_DESCENDING[number]);
|
|
177
|
+
const currentLevel = THINKING_DESCENDING.indexOf(thinking as (typeof THINKING_DESCENDING)[number]);
|
|
159
178
|
const lowerLevels = THINKING_DESCENDING.slice(currentLevel >= 0 ? currentLevel + 1 : 0);
|
|
160
179
|
const routes: Route[] = [];
|
|
161
180
|
const add = (route: Route): void => {
|
|
162
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
routes.length >= MAX_DYNAMIC_ROUTES ||
|
|
183
|
+
routes.some(
|
|
184
|
+
(candidate) => candidate.provider === route.provider && candidate.model === route.model && candidate.thinking === route.thinking,
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return;
|
|
163
188
|
routes.push(route);
|
|
164
189
|
};
|
|
165
190
|
add({ provider: current.provider, model: current.id, thinking });
|
|
@@ -170,7 +195,12 @@ export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thin
|
|
|
170
195
|
.filter((model) => model.provider !== current.provider || model.id !== current.id)
|
|
171
196
|
.sort((left, right) => {
|
|
172
197
|
const providerPriority = Number(left.provider !== current.provider) - Number(right.provider !== current.provider);
|
|
173
|
-
return
|
|
198
|
+
return (
|
|
199
|
+
providerPriority ||
|
|
200
|
+
modelCost(left) - modelCost(right) ||
|
|
201
|
+
left.provider.localeCompare(right.provider) ||
|
|
202
|
+
left.id.localeCompare(right.id)
|
|
203
|
+
);
|
|
174
204
|
});
|
|
175
205
|
for (const model of alternatives) {
|
|
176
206
|
const level = [thinking, ...lowerLevels].find((candidate) => supportsThinking(model, candidate)) ?? "off";
|
|
@@ -182,7 +212,10 @@ export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thin
|
|
|
182
212
|
async function syncAvailableRoutes(pi: ExtensionAPI, client: JittorExtensionClient, ctx: ExtensionContext): Promise<void> {
|
|
183
213
|
const session_id = ctx.sessionManager.getSessionId();
|
|
184
214
|
const secret = sessionSecretField(session_id);
|
|
185
|
-
if (!ctx.model) {
|
|
215
|
+
if (!ctx.model) {
|
|
216
|
+
await client.call("router.available_routes", { routes: [], session_id, ...secret });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
186
219
|
const models = ctx.modelRegistry.getAvailable() as PiRouteModel[];
|
|
187
220
|
const routes = routesFromPi(models, ctx.model as PiRouteModel, pi.getThinkingLevel());
|
|
188
221
|
await client.call("router.available_routes", { routes, session_id, ...secret });
|
|
@@ -197,7 +230,13 @@ async function syncCurrentRoute(
|
|
|
197
230
|
): Promise<void> {
|
|
198
231
|
if (!model) return;
|
|
199
232
|
const session_id = ctx.sessionManager.getSessionId();
|
|
200
|
-
await client.call("router.current_route", {
|
|
233
|
+
await client.call("router.current_route", {
|
|
234
|
+
provider: model.provider,
|
|
235
|
+
model: model.id,
|
|
236
|
+
thinking,
|
|
237
|
+
session_id,
|
|
238
|
+
...sessionSecretField(session_id),
|
|
239
|
+
});
|
|
201
240
|
}
|
|
202
241
|
|
|
203
242
|
function halt(ctx: ExtensionContext, reason: string): false {
|
|
@@ -215,12 +254,21 @@ async function applyDecision(
|
|
|
215
254
|
): Promise<boolean> {
|
|
216
255
|
if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
|
|
217
256
|
if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
|
|
218
|
-
if (!decision.route || await applyRoute(pi, ctx, decision.route)) return true;
|
|
257
|
+
if (!decision.route || (await applyRoute(pi, ctx, decision.route))) return true;
|
|
219
258
|
if (allowResync) {
|
|
220
259
|
await syncAvailableRoutes(pi, client, ctx);
|
|
221
|
-
return applyDecision(
|
|
260
|
+
return applyDecision(
|
|
261
|
+
pi,
|
|
262
|
+
client,
|
|
263
|
+
ctx,
|
|
264
|
+
(await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
|
|
265
|
+
false,
|
|
266
|
+
);
|
|
222
267
|
}
|
|
223
|
-
return halt(
|
|
268
|
+
return halt(
|
|
269
|
+
ctx,
|
|
270
|
+
`Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`,
|
|
271
|
+
);
|
|
224
272
|
}
|
|
225
273
|
|
|
226
274
|
/**
|
|
@@ -228,22 +276,39 @@ async function applyDecision(
|
|
|
228
276
|
* comes from pi.getThinkingLevel() at message_end time, not from the message itself -- AssistantMessage
|
|
229
277
|
* has no thinking field of its own, and the level can't have changed mid-message.
|
|
230
278
|
*/
|
|
231
|
-
function assistantUsageMetrics(
|
|
279
|
+
function assistantUsageMetrics(
|
|
280
|
+
message: unknown,
|
|
281
|
+
observedAt: number,
|
|
282
|
+
taskId: string | null = null,
|
|
283
|
+
thinking: string | null = null,
|
|
284
|
+
): MetricObservation[] {
|
|
232
285
|
if (typeof message !== "object" || message === null || Array.isArray(message)) return [];
|
|
233
286
|
const value = message as Record<string, unknown>;
|
|
234
|
-
if (value
|
|
235
|
-
const usage = value
|
|
236
|
-
const provider = typeof value
|
|
237
|
-
const model = typeof value
|
|
287
|
+
if (value.role !== "assistant" || typeof value.usage !== "object" || value.usage === null) return [];
|
|
288
|
+
const usage = value.usage as Record<string, unknown>;
|
|
289
|
+
const provider = typeof value.provider === "string" ? value.provider : "unknown";
|
|
290
|
+
const model = typeof value.model === "string" ? value.model : "unknown";
|
|
238
291
|
const scope = `${provider}:${model}`;
|
|
239
|
-
const attributes = {
|
|
292
|
+
const attributes = {
|
|
293
|
+
provider,
|
|
294
|
+
model,
|
|
295
|
+
...(taskId === null ? {} : { taskId }),
|
|
296
|
+
...(thinking === null || thinking.length === 0 ? {} : { thinking }),
|
|
297
|
+
};
|
|
240
298
|
const metrics: MetricObservation[] = [];
|
|
241
|
-
for (const [field, metric] of [
|
|
299
|
+
for (const [field, metric] of [
|
|
300
|
+
["input", "input-tokens"],
|
|
301
|
+
["output", "output-tokens"],
|
|
302
|
+
["cacheRead", "cache-read-tokens"],
|
|
303
|
+
["cacheWrite", "cache-write-tokens"],
|
|
304
|
+
] as const) {
|
|
242
305
|
const amount = usage[field];
|
|
243
|
-
if (typeof amount === "number" && Number.isFinite(amount))
|
|
306
|
+
if (typeof amount === "number" && Number.isFinite(amount))
|
|
307
|
+
metrics.push({ source: "pi", scope, metric, value: amount, unit: "tokens", observedAt, attributes });
|
|
244
308
|
}
|
|
245
|
-
const cost = typeof usage
|
|
246
|
-
if (typeof cost === "number" && Number.isFinite(cost))
|
|
309
|
+
const cost = typeof usage.cost === "object" && usage.cost !== null ? (usage.cost as Record<string, unknown>).total : undefined;
|
|
310
|
+
if (typeof cost === "number" && Number.isFinite(cost))
|
|
311
|
+
metrics.push({ source: "pi", scope, metric: "cost", value: cost, unit: "usd", observedAt, attributes });
|
|
247
312
|
return metrics;
|
|
248
313
|
}
|
|
249
314
|
|
|
@@ -320,11 +385,14 @@ export function registerJittorExtension(
|
|
|
320
385
|
// Non-blocking: compaction UI starts immediately as cold-start; if a learned estimate resolves
|
|
321
386
|
// before this compaction finishes (and this is still the active compaction, not a later one),
|
|
322
387
|
// upgrade the same progress object in place so the drain bar and status text switch to "learned".
|
|
323
|
-
void client
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
388
|
+
void client
|
|
389
|
+
.call("compaction.estimate", {})
|
|
390
|
+
.then((estimate) => {
|
|
391
|
+
if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
|
|
392
|
+
footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
|
|
393
|
+
footerState.requestRender?.();
|
|
394
|
+
})
|
|
395
|
+
.catch(() => undefined);
|
|
328
396
|
compactionTimer = setInterval(() => footerState.requestRender?.(), FOOTER_COMPACTION_RENDER_INTERVAL_MS);
|
|
329
397
|
signal.addEventListener("abort", finishCompactionUi, { once: true });
|
|
330
398
|
if (signal.aborted) finishCompactionUi();
|
|
@@ -338,14 +406,17 @@ export function registerJittorExtension(
|
|
|
338
406
|
enforcement.setEnabled(false);
|
|
339
407
|
ctx.ui.setStatus("jittor", undefined);
|
|
340
408
|
showFooter(ctx);
|
|
341
|
-
ctx.ui.notify(
|
|
409
|
+
ctx.ui.notify(
|
|
410
|
+
"Jittor enforcement is off (monitor-only); the informational footer remains independent and provider requests will not be blocked.",
|
|
411
|
+
"warning",
|
|
412
|
+
);
|
|
342
413
|
};
|
|
343
414
|
const enable = async (ctx: ExtensionContext): Promise<void> => {
|
|
344
415
|
try {
|
|
345
416
|
await syncCurrentRoute(pi, client, ctx);
|
|
346
417
|
await syncAvailableRoutes(pi, client, ctx);
|
|
347
418
|
await client.call("telemetry.poll", {});
|
|
348
|
-
const readinessDecision = await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision;
|
|
419
|
+
const readinessDecision = (await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision;
|
|
349
420
|
if (readinessDecision.action === "halt") throw new Error(readinessDecision.reason);
|
|
350
421
|
enforcement.setEnabled(true);
|
|
351
422
|
showFooter(ctx);
|
|
@@ -365,7 +436,7 @@ export function registerJittorExtension(
|
|
|
365
436
|
const action = args.trim().toLowerCase();
|
|
366
437
|
if (action === "" || action === "settings") {
|
|
367
438
|
await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
|
|
368
|
-
setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
|
|
439
|
+
setEnforcement: async (enabled) => (enabled ? enable(ctx) : disable(ctx)),
|
|
369
440
|
setFooter: async (enabled) => {
|
|
370
441
|
enforcement.setFooterEnabled(enabled);
|
|
371
442
|
showFooter(ctx);
|
|
@@ -401,11 +472,18 @@ export function registerJittorExtension(
|
|
|
401
472
|
return;
|
|
402
473
|
}
|
|
403
474
|
const candidates = benchmarkCandidatesFromPi(ctx.modelRegistry.getAvailable() as PiRouteModel[], pi.getThinkingLevel());
|
|
404
|
-
await showBenchmarkPanel(
|
|
475
|
+
await showBenchmarkPanel(
|
|
476
|
+
ctx,
|
|
477
|
+
client,
|
|
478
|
+
candidates,
|
|
479
|
+
`${ctx.model.provider}/${ctx.model.id}`,
|
|
480
|
+
requestedDomain ?? "general",
|
|
481
|
+
requestedType ?? "general",
|
|
482
|
+
);
|
|
405
483
|
return;
|
|
406
484
|
}
|
|
407
485
|
if (action === "outcome accepted" || action === "outcome rejected") {
|
|
408
|
-
const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
|
|
486
|
+
const explicitOutcome = action.endsWith("accepted") ? ("accepted" as const) : ("rejected" as const);
|
|
409
487
|
const outcomeMetric = localRunTelemetry.explicitOutcomeMetric(explicitOutcome);
|
|
410
488
|
if (!outcomeMetric) {
|
|
411
489
|
ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
|
|
@@ -433,11 +511,20 @@ export function registerJittorExtension(
|
|
|
433
511
|
}
|
|
434
512
|
if (action === "recovery cancel") {
|
|
435
513
|
cancelRecovery(true);
|
|
436
|
-
ctx.ui.notify(
|
|
514
|
+
ctx.ui.notify(
|
|
515
|
+
`Jittor Codex recovery cooldown and attempt window cleared; recovery remains ${codexRecovery.isCodexRecoveryEnabled() ? "on" : "off"}.`,
|
|
516
|
+
"info",
|
|
517
|
+
);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (action === "off" || action === "disable") {
|
|
521
|
+
disable(ctx);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (action === "on" || action === "enable") {
|
|
525
|
+
await enable(ctx);
|
|
437
526
|
return;
|
|
438
527
|
}
|
|
439
|
-
if (action === "off" || action === "disable") { disable(ctx); return; }
|
|
440
|
-
if (action === "on" || action === "enable") { await enable(ctx); return; }
|
|
441
528
|
if (action === "footer off" || action === "footer disable") {
|
|
442
529
|
enforcement.setFooterEnabled(false);
|
|
443
530
|
ctx.ui.setFooter(undefined);
|
|
@@ -452,15 +539,19 @@ export function registerJittorExtension(
|
|
|
452
539
|
return;
|
|
453
540
|
}
|
|
454
541
|
if (action === "context") {
|
|
455
|
-
const summary = await client.call("context.assess", {}) as ContextAssessment;
|
|
456
|
-
const average =
|
|
542
|
+
const summary = (await client.call("context.assess", {})) as ContextAssessment;
|
|
543
|
+
const average =
|
|
544
|
+
summary.injection.averageCharacters === null ? "unknown" : Math.round(summary.injection.averageCharacters).toLocaleString();
|
|
457
545
|
const p95 = summary.injection.p95Characters === null ? "unknown" : Math.round(summary.injection.p95Characters).toLocaleString();
|
|
458
|
-
ctx.ui.notify(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
546
|
+
ctx.ui.notify(
|
|
547
|
+
[
|
|
548
|
+
`Papyrus injection: ${summary.injection.runs} runs · avg ${average} chars · p95 ${p95} chars · unchanged ${summary.injection.unchangedRate === null ? "unknown" : `${(summary.injection.unchangedRate * 100).toFixed(1)}%`}`,
|
|
549
|
+
`Mix: rules ${summary.injection.ruleCharacters.toLocaleString()} chars · tasks ${summary.injection.taskCharacters.toLocaleString()} chars · estimated ${summary.injection.estimatedTokens.toLocaleString()} tokens`,
|
|
550
|
+
`Compactions: ${summary.compaction.completed} completed · ${summary.compaction.aborted} aborted · ${summary.compaction.perRun === null ? "unknown" : summary.compaction.perRun.toFixed(3)} per agent run · ${summary.compaction.perTurn === null ? "unknown" : summary.compaction.perTurn.toFixed(3)} per turn`,
|
|
551
|
+
`Completeness: ${summary.completeness}`,
|
|
552
|
+
].join("\n"),
|
|
553
|
+
"info",
|
|
554
|
+
);
|
|
464
555
|
return;
|
|
465
556
|
}
|
|
466
557
|
// Reached only for the explicit "status" keyword or any other unrecognized text; bare "" is
|
|
@@ -474,7 +565,8 @@ export function registerJittorExtension(
|
|
|
474
565
|
});
|
|
475
566
|
|
|
476
567
|
pi.registerCommand("context", {
|
|
477
|
-
description:
|
|
568
|
+
description:
|
|
569
|
+
"Context Hub: real usage plus every segment's estimated size (base prompt, message history, tool schemas by owning extension, and whatever other extensions contributed), each tagged with how it was attributed",
|
|
478
570
|
handler: async (_args, ctx) => {
|
|
479
571
|
const activeToolNames = new Set(pi.getActiveTools());
|
|
480
572
|
const toolSegment = toolLedgerSegment(pi.getAllTools().filter((tool) => activeToolNames.has(tool.name)));
|
|
@@ -487,7 +579,11 @@ export function registerJittorExtension(
|
|
|
487
579
|
const branchEntryIds = new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id));
|
|
488
580
|
const messageHistory = buildMessageHistoryTree(tree, activeEntryIds, branchEntryIds);
|
|
489
581
|
const usage = ctx.getContextUsage();
|
|
490
|
-
const ownSegments = [
|
|
582
|
+
const ownSegments = [
|
|
583
|
+
basePromptSegment(lastObservedBasePromptTokens, lastObservedBasePromptItems),
|
|
584
|
+
messageHistorySegment(messageHistory),
|
|
585
|
+
toolSegment,
|
|
586
|
+
];
|
|
491
587
|
const breakdown = composeContextBreakdown({
|
|
492
588
|
totalTokens: usage?.tokens ?? null,
|
|
493
589
|
contextWindow: ctx.model?.contextWindow ?? null,
|
|
@@ -503,14 +599,19 @@ export function registerJittorExtension(
|
|
|
503
599
|
const action = args.trim().toLowerCase();
|
|
504
600
|
if (action === "budget" || action.startsWith("budget ")) {
|
|
505
601
|
const [, periodText, valueText] = action.split(/\s+/);
|
|
506
|
-
const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? periodText as UsagePeriod : undefined;
|
|
602
|
+
const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? (periodText as UsagePeriod) : undefined;
|
|
507
603
|
if (!period) {
|
|
508
|
-
const values = USAGE_PERIODS.map(
|
|
604
|
+
const values = USAGE_PERIODS.map(
|
|
605
|
+
({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`,
|
|
606
|
+
).join(" · ");
|
|
509
607
|
ctx.ui.notify(`Token budgets · ${values}`, "info");
|
|
510
608
|
return;
|
|
511
609
|
}
|
|
512
610
|
if (valueText === undefined) {
|
|
513
|
-
ctx.ui.notify(
|
|
611
|
+
ctx.ui.notify(
|
|
612
|
+
`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget: ${usageBudgets.getUsageTokenBudget(period)?.toLocaleString() ?? "not configured"}`,
|
|
613
|
+
"info",
|
|
614
|
+
);
|
|
514
615
|
return;
|
|
515
616
|
}
|
|
516
617
|
if (valueText === "off" || valueText === "clear") {
|
|
@@ -524,7 +625,10 @@ export function registerJittorExtension(
|
|
|
524
625
|
return;
|
|
525
626
|
}
|
|
526
627
|
usageBudgets.setUsageTokenBudget(period, tokens);
|
|
527
|
-
ctx.ui.notify(
|
|
628
|
+
ctx.ui.notify(
|
|
629
|
+
`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`,
|
|
630
|
+
"info",
|
|
631
|
+
);
|
|
528
632
|
return;
|
|
529
633
|
}
|
|
530
634
|
if (action !== "" && action !== "cost" && action !== "tokens") {
|
|
@@ -589,13 +693,16 @@ export function registerJittorExtension(
|
|
|
589
693
|
|
|
590
694
|
pi.on("session_compact", async (event) => {
|
|
591
695
|
finishCompactionUi();
|
|
592
|
-
await recordMetrics(client, [compactionTelemetry.complete({ reason: event.reason, willRetry: event.willRetry })]).catch(
|
|
696
|
+
await recordMetrics(client, [compactionTelemetry.complete({ reason: event.reason, willRetry: event.willRetry })]).catch(
|
|
697
|
+
() => undefined,
|
|
698
|
+
);
|
|
593
699
|
});
|
|
594
700
|
|
|
595
701
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
596
702
|
if (footerState.compaction) {
|
|
597
703
|
finishCompactionUi();
|
|
598
|
-
if (compactionTelemetry.hasOpenCompaction())
|
|
704
|
+
if (compactionTelemetry.hasOpenCompaction())
|
|
705
|
+
await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
|
|
599
706
|
}
|
|
600
707
|
scheduleCodexRecovery(ctx);
|
|
601
708
|
if (!enforcement.isFooterEnabled()) return;
|
|
@@ -613,7 +720,7 @@ export function registerJittorExtension(
|
|
|
613
720
|
if (event.source !== "extension") cancelRecovery(true);
|
|
614
721
|
if (event.source === "extension" || !enforcement.isEnabled()) return { action: "continue" as const };
|
|
615
722
|
try {
|
|
616
|
-
const next = await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision;
|
|
723
|
+
const next = (await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision;
|
|
617
724
|
if (next.action === "halt") {
|
|
618
725
|
ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
619
726
|
return { action: "handled" as const };
|
|
@@ -626,7 +733,9 @@ export function registerJittorExtension(
|
|
|
626
733
|
});
|
|
627
734
|
|
|
628
735
|
pi.on("model_select", async (event, ctx) => {
|
|
629
|
-
await syncCurrentRoute(pi, client, ctx, event.model)
|
|
736
|
+
await syncCurrentRoute(pi, client, ctx, event.model)
|
|
737
|
+
.then(() => syncAvailableRoutes(pi, client, ctx))
|
|
738
|
+
.catch(() => undefined);
|
|
630
739
|
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
631
740
|
});
|
|
632
741
|
|
|
@@ -644,7 +753,12 @@ export function registerJittorExtension(
|
|
|
644
753
|
try {
|
|
645
754
|
await syncCurrentRoute(pi, client, ctx);
|
|
646
755
|
await syncAvailableRoutes(pi, client, ctx);
|
|
647
|
-
await applyDecision(
|
|
756
|
+
await applyDecision(
|
|
757
|
+
pi,
|
|
758
|
+
client,
|
|
759
|
+
ctx,
|
|
760
|
+
(await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
|
|
761
|
+
);
|
|
648
762
|
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
649
763
|
} catch {
|
|
650
764
|
halt(ctx, "Jittor could not verify or apply a safe route");
|
|
@@ -662,7 +776,9 @@ export function registerJittorExtension(
|
|
|
662
776
|
pi.on("after_provider_response", async (event, ctx) => {
|
|
663
777
|
localRunTelemetry.onProviderResponse();
|
|
664
778
|
if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
|
|
665
|
-
const notifySchemaDrift = (message: string) => {
|
|
779
|
+
const notifySchemaDrift = (message: string) => {
|
|
780
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error");
|
|
781
|
+
};
|
|
666
782
|
await providerResponseTelemetry.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift);
|
|
667
783
|
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
668
784
|
});
|
|
@@ -674,13 +790,27 @@ export function registerJittorExtension(
|
|
|
674
790
|
|
|
675
791
|
pi.on("message_end", async (event, ctx) => {
|
|
676
792
|
if (event.message.role === "assistant") {
|
|
677
|
-
if (event.message.provider === "openai-codex")
|
|
678
|
-
|
|
793
|
+
if (event.message.provider === "openai-codex")
|
|
794
|
+
codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
|
|
795
|
+
await providerResponseTelemetry.handleMessageEnd(
|
|
796
|
+
client,
|
|
797
|
+
event.message.provider,
|
|
798
|
+
event.message.stopReason,
|
|
799
|
+
event.message.errorMessage,
|
|
800
|
+
);
|
|
679
801
|
}
|
|
680
802
|
const metrics = assistantUsageMetrics(event.message, Date.now(), focusedTaskId, pi.getThinkingLevel());
|
|
681
803
|
if (metrics.length > 0) {
|
|
682
|
-
const amount = (name: string): number =>
|
|
683
|
-
|
|
804
|
+
const amount = (name: string): number =>
|
|
805
|
+
metrics
|
|
806
|
+
.filter((metric) => metric.metric === name && typeof metric.value === "number")
|
|
807
|
+
.reduce((sum, metric) => sum + (metric.value ?? 0), 0);
|
|
808
|
+
compactionTelemetry.observeProviderUsage({
|
|
809
|
+
input: amount("input-tokens"),
|
|
810
|
+
output: amount("output-tokens"),
|
|
811
|
+
cacheRead: amount("cache-read-tokens"),
|
|
812
|
+
cacheWrite: amount("cache-write-tokens"),
|
|
813
|
+
});
|
|
684
814
|
await recordMetrics(client, metrics).catch(() => undefined);
|
|
685
815
|
}
|
|
686
816
|
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
@@ -688,7 +818,8 @@ export function registerJittorExtension(
|
|
|
688
818
|
|
|
689
819
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
690
820
|
finishCompactionUi();
|
|
691
|
-
if (compactionTelemetry.hasOpenCompaction())
|
|
821
|
+
if (compactionTelemetry.hasOpenCompaction())
|
|
822
|
+
await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
|
|
692
823
|
stopPapyrusContext?.();
|
|
693
824
|
stopPapyrusTaskFocus?.();
|
|
694
825
|
stopContextHub?.();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createRetryingClient, type RetryingClient } from "@danypops/vehicle-client/daemon-client";
|
|
2
1
|
import { connectJittorClient, type JittorClient, type OperationInputs, type OperationName, type OperationOutputs } from "@danypops/jittor";
|
|
2
|
+
import { createRetryingClient, type RetryingClient } from "@danypops/vehicle-client/daemon-client";
|
|
3
3
|
|
|
4
4
|
type JittorConnector = () => Promise<JittorClient>;
|
|
5
5
|
|