@danypops/pi-jittor 0.1.1 → 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.
@@ -1,47 +1,58 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type { ContextSegmentItem } from "@danypops/jittor";
2
2
  import {
3
- CONTEXT_HUB_CONTRIBUTION_CHANNEL,
4
- FOOTER_COMPACTION_RENDER_INTERVAL_MS,
5
- MAX_DYNAMIC_ROUTES,
6
- PAPYRUS_CONTEXT_INJECTION_CHANNEL,
7
- PAPYRUS_TASK_FOCUS_CHANNEL,
3
+ applyTaskFocusEvent,
4
+ CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN,
8
5
  CONTEXT_EVENT_DEDUP_LIMIT,
6
+ CONTEXT_HUB_CONTRIBUTION_CHANNEL,
9
7
  CompactionTelemetry,
10
- papyrusContextMetric,
11
- validatePapyrusContextInjection,
12
- applyTaskFocusEvent,
13
- validateTaskFocusEvent,
14
- toolLedgerSegment,
15
- TASK_DOMAINS,
16
- TASK_TYPES,
17
- USAGE_PERIODS,
18
8
  type ContextAssessment,
9
+ FOOTER_COMPACTION_RENDER_INTERVAL_MS,
10
+ MAX_DYNAMIC_ROUTES,
19
11
  type MetricObservation,
20
12
  type ModelCandidate,
21
13
  type ModelTaskDomain,
22
14
  type ModelTaskType,
15
+ PAPYRUS_CONTEXT_INJECTION_CHANNEL,
16
+ PAPYRUS_TASK_FOCUS_CHANNEL,
23
17
  type PolicyDecision,
18
+ papyrusContextMetric,
24
19
  type Route,
25
20
  type RouterStatus,
26
21
  type StoredMetricObservation,
22
+ TASK_DOMAINS,
23
+ TASK_TYPES,
24
+ toolLedgerSegment,
25
+ USAGE_PERIODS,
27
26
  type UsagePeriod,
27
+ validatePapyrusContextInjection,
28
+ validateTaskFocusEvent,
28
29
  } from "@danypops/jittor";
30
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
29
31
  import { showBenchmarkPanel } from "./benchmark-tui.ts";
30
- import { installIntegratedFooter, type CompactionProgress, type IntegratedFooterState } from "./footer.ts";
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";
31
47
  import { callJittor } from "./service-client.ts";
32
- import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
33
- import { showSettingsPanel } from "./settings-tui.ts";
34
- import { buildFooterBudget, formatFooterStatus, providerBudgetMetricQuery, showJittorPanel } from "./tui.ts";
35
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";
36
52
  import { showUsagePanel } from "./usage.ts";
37
- import { CodexRecoveryCapability, SYSTEM_RECOVERY_RUNTIME, type CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
38
- import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
39
- import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
40
- import { ContextHubCapability } from "./capabilities/context-hub.ts";
41
- import { buildContextReport } from "./context-report.ts";
42
53
 
43
- export { formatFooterStatus } from "./tui.ts";
44
54
  export type { CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
55
+ export { formatFooterStatus } from "./tui.ts";
45
56
 
46
57
  const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
47
58
  const RECOVERY_GUIDANCE = "Run /jittor off to disable blocking, or restart the daemon with: systemctl --user restart jittor.service";
@@ -58,9 +69,9 @@ function usageBudgetControl(enforcement: EnforcementControl): UsageBudgetControl
58
69
  const candidate = enforcement as EnforcementControl & Partial<UsageBudgetControl>;
59
70
  return typeof candidate.getUsageTokenBudget === "function" && typeof candidate.setUsageTokenBudget === "function"
60
71
  ? {
61
- getUsageTokenBudget: (period) => candidate.getUsageTokenBudget!(period),
62
- setUsageTokenBudget: (period, tokens) => candidate.setUsageTokenBudget!(period, tokens),
63
- }
72
+ getUsageTokenBudget: (period) => candidate.getUsageTokenBudget!(period),
73
+ setUsageTokenBudget: (period, tokens) => candidate.setUsageTokenBudget!(period, tokens),
74
+ }
64
75
  : { getUsageTokenBudget: () => undefined, setUsageTokenBudget() {} };
65
76
  }
66
77
 
@@ -69,9 +80,9 @@ function recoveryControl(enforcement: EnforcementControl): CodexRecoveryControl
69
80
  const set = (candidate as Partial<CodexRecoveryControl>).setCodexRecoveryEnabled;
70
81
  return typeof candidate.isCodexRecoveryEnabled === "function" && typeof set === "function"
71
82
  ? {
72
- isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
73
- setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
74
- }
83
+ isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
84
+ setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
85
+ }
75
86
  : { isCodexRecoveryEnabled: () => false, setCodexRecoveryEnabled() {} };
76
87
  }
77
88
 
@@ -84,9 +95,9 @@ async function recordMetrics(client: JittorExtensionClient, metrics: MetricObser
84
95
  }
85
96
 
86
97
  async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState, sessionId: string): Promise<void> {
87
- 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;
88
99
  const query = providerBudgetMetricQuery(status);
89
- const metrics = query ? await client.call("metrics.query", query) as StoredMetricObservation[] : [];
100
+ const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
90
101
  state.providerBudget = buildFooterBudget(status, metrics);
91
102
  state.requestRender?.();
92
103
  }
@@ -95,10 +106,14 @@ function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
95
106
  if (milliseconds <= 0) return Promise.resolve();
96
107
  return new Promise((resolve, reject) => {
97
108
  const timer = setTimeout(resolve, milliseconds);
98
- signal?.addEventListener("abort", () => {
99
- clearTimeout(timer);
100
- reject(new Error("Jittor throttle cancelled"));
101
- }, { once: true });
109
+ signal?.addEventListener(
110
+ "abort",
111
+ () => {
112
+ clearTimeout(timer);
113
+ reject(new Error("Jittor throttle cancelled"));
114
+ },
115
+ { once: true },
116
+ );
102
117
  });
103
118
  }
104
119
 
@@ -111,7 +126,7 @@ async function applyRoute(pi: ExtensionAPI, ctx: ExtensionContext, route: Route)
111
126
  const model = ctx.modelRegistry.find(route.provider, route.model);
112
127
  if (!model) return false;
113
128
  if (!ctx.model || ctx.model.provider !== route.provider || ctx.model.id !== route.model) {
114
- if (!await pi.setModel(model)) return false;
129
+ if (!(await pi.setModel(model))) return false;
115
130
  }
116
131
  if (THINKING_LEVELS.has(route.thinking)) pi.setThinkingLevel(route.thinking as Parameters<ExtensionAPI["setThinkingLevel"]>[0]);
117
132
  return true;
@@ -139,7 +154,12 @@ function modelCost(model: PiRouteModel): number {
139
154
  export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: string): ModelCandidate[] {
140
155
  const candidates: ModelCandidate[] = [];
141
156
  for (const model of models) {
142
- if (!model.provider || !model.id || candidates.some((candidate) => candidate.provider === model.provider && candidate.model === model.id)) continue;
157
+ if (
158
+ !model.provider ||
159
+ !model.id ||
160
+ candidates.some((candidate) => candidate.provider === model.provider && candidate.model === model.id)
161
+ )
162
+ continue;
143
163
  const level = supportsThinking(model, thinking) ? thinking : "off";
144
164
  candidates.push({ provider: model.provider, model: model.id, thinking: level });
145
165
  if (candidates.length >= MAX_DYNAMIC_ROUTES) break;
@@ -150,13 +170,21 @@ export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: stri
150
170
  export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
151
171
  const catalog = models
152
172
  .filter((model) => model.provider.length > 0 && model.id.length > 0)
153
- .filter((model, index, rows) => rows.findIndex((candidate) => candidate.provider === model.provider && candidate.id === model.id) === index);
173
+ .filter(
174
+ (model, index, rows) => rows.findIndex((candidate) => candidate.provider === model.provider && candidate.id === model.id) === index,
175
+ );
154
176
  if (!catalog.some((model) => model.provider === current.provider && model.id === current.id)) catalog.push(current);
155
- const currentLevel = THINKING_DESCENDING.indexOf(thinking as typeof THINKING_DESCENDING[number]);
177
+ const currentLevel = THINKING_DESCENDING.indexOf(thinking as (typeof THINKING_DESCENDING)[number]);
156
178
  const lowerLevels = THINKING_DESCENDING.slice(currentLevel >= 0 ? currentLevel + 1 : 0);
157
179
  const routes: Route[] = [];
158
180
  const add = (route: Route): void => {
159
- if (routes.length >= MAX_DYNAMIC_ROUTES || routes.some((candidate) => candidate.provider === route.provider && candidate.model === route.model && candidate.thinking === route.thinking)) return;
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;
160
188
  routes.push(route);
161
189
  };
162
190
  add({ provider: current.provider, model: current.id, thinking });
@@ -167,7 +195,12 @@ export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thin
167
195
  .filter((model) => model.provider !== current.provider || model.id !== current.id)
168
196
  .sort((left, right) => {
169
197
  const providerPriority = Number(left.provider !== current.provider) - Number(right.provider !== current.provider);
170
- return providerPriority || modelCost(left) - modelCost(right) || left.provider.localeCompare(right.provider) || left.id.localeCompare(right.id);
198
+ return (
199
+ providerPriority ||
200
+ modelCost(left) - modelCost(right) ||
201
+ left.provider.localeCompare(right.provider) ||
202
+ left.id.localeCompare(right.id)
203
+ );
171
204
  });
172
205
  for (const model of alternatives) {
173
206
  const level = [thinking, ...lowerLevels].find((candidate) => supportsThinking(model, candidate)) ?? "off";
@@ -179,7 +212,10 @@ export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thin
179
212
  async function syncAvailableRoutes(pi: ExtensionAPI, client: JittorExtensionClient, ctx: ExtensionContext): Promise<void> {
180
213
  const session_id = ctx.sessionManager.getSessionId();
181
214
  const secret = sessionSecretField(session_id);
182
- if (!ctx.model) { await client.call("router.available_routes", { routes: [], session_id, ...secret }); return; }
215
+ if (!ctx.model) {
216
+ await client.call("router.available_routes", { routes: [], session_id, ...secret });
217
+ return;
218
+ }
183
219
  const models = ctx.modelRegistry.getAvailable() as PiRouteModel[];
184
220
  const routes = routesFromPi(models, ctx.model as PiRouteModel, pi.getThinkingLevel());
185
221
  await client.call("router.available_routes", { routes, session_id, ...secret });
@@ -194,7 +230,13 @@ async function syncCurrentRoute(
194
230
  ): Promise<void> {
195
231
  if (!model) return;
196
232
  const session_id = ctx.sessionManager.getSessionId();
197
- await client.call("router.current_route", { provider: model.provider, model: model.id, thinking, session_id, ...sessionSecretField(session_id) });
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
+ });
198
240
  }
199
241
 
200
242
  function halt(ctx: ExtensionContext, reason: string): false {
@@ -212,12 +254,21 @@ async function applyDecision(
212
254
  ): Promise<boolean> {
213
255
  if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
214
256
  if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
215
- if (!decision.route || await applyRoute(pi, ctx, decision.route)) return true;
257
+ if (!decision.route || (await applyRoute(pi, ctx, decision.route))) return true;
216
258
  if (allowResync) {
217
259
  await syncAvailableRoutes(pi, client, ctx);
218
- return applyDecision(pi, client, ctx, await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision, false);
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
+ );
219
267
  }
220
- return halt(ctx, `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`);
268
+ return halt(
269
+ ctx,
270
+ `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`,
271
+ );
221
272
  }
222
273
 
223
274
  /**
@@ -225,22 +276,39 @@ async function applyDecision(
225
276
  * comes from pi.getThinkingLevel() at message_end time, not from the message itself -- AssistantMessage
226
277
  * has no thinking field of its own, and the level can't have changed mid-message.
227
278
  */
228
- function assistantUsageMetrics(message: unknown, observedAt: number, taskId: string | null = null, thinking: string | null = null): MetricObservation[] {
279
+ function assistantUsageMetrics(
280
+ message: unknown,
281
+ observedAt: number,
282
+ taskId: string | null = null,
283
+ thinking: string | null = null,
284
+ ): MetricObservation[] {
229
285
  if (typeof message !== "object" || message === null || Array.isArray(message)) return [];
230
286
  const value = message as Record<string, unknown>;
231
- if (value["role"] !== "assistant" || typeof value["usage"] !== "object" || value["usage"] === null) return [];
232
- const usage = value["usage"] as Record<string, unknown>;
233
- const provider = typeof value["provider"] === "string" ? value["provider"] : "unknown";
234
- const model = typeof value["model"] === "string" ? value["model"] : "unknown";
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";
235
291
  const scope = `${provider}:${model}`;
236
- const attributes = { provider, model, ...(taskId === null ? {} : { taskId }), ...(thinking === null || thinking.length === 0 ? {} : { thinking }) };
292
+ const attributes = {
293
+ provider,
294
+ model,
295
+ ...(taskId === null ? {} : { taskId }),
296
+ ...(thinking === null || thinking.length === 0 ? {} : { thinking }),
297
+ };
237
298
  const metrics: MetricObservation[] = [];
238
- for (const [field, metric] of [["input", "input-tokens"], ["output", "output-tokens"], ["cacheRead", "cache-read-tokens"], ["cacheWrite", "cache-write-tokens"]] as const) {
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) {
239
305
  const amount = usage[field];
240
- if (typeof amount === "number" && Number.isFinite(amount)) metrics.push({ source: "pi", scope, metric, value: amount, unit: "tokens", observedAt, attributes });
306
+ if (typeof amount === "number" && Number.isFinite(amount))
307
+ metrics.push({ source: "pi", scope, metric, value: amount, unit: "tokens", observedAt, attributes });
241
308
  }
242
- const cost = typeof usage["cost"] === "object" && usage["cost"] !== null ? (usage["cost"] as Record<string, unknown>)["total"] : undefined;
243
- if (typeof cost === "number" && Number.isFinite(cost)) metrics.push({ source: "pi", scope, metric: "cost", value: cost, unit: "usd", observedAt, attributes });
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 });
244
312
  return metrics;
245
313
  }
246
314
 
@@ -259,6 +327,12 @@ export function registerJittorExtension(
259
327
  const codexRecoveryCapability = new CodexRecoveryCapability(pi, codexRecovery, recoveryRuntime);
260
328
  const contextHub = new ContextHubCapability();
261
329
  const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
330
+ // Cached from the most recent before_agent_start observation: Pi's own base system prompt is
331
+ // only ever visible transiently inside that hook's event, so /context reuses this rather than
332
+ // going without it entirely. Measured as of THIS extension's own place in the before_agent_start
333
+ // chain -- see buildBasePromptItems' own doc comment for the load-order caveat this implies.
334
+ let lastObservedBasePromptTokens: number | null = null;
335
+ let lastObservedBasePromptItems: ContextSegmentItem[] = [];
262
336
  const contextObservations = new Set<string>();
263
337
  const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
264
338
  try {
@@ -311,11 +385,14 @@ export function registerJittorExtension(
311
385
  // Non-blocking: compaction UI starts immediately as cold-start; if a learned estimate resolves
312
386
  // before this compaction finishes (and this is still the active compaction, not a later one),
313
387
  // upgrade the same progress object in place so the drain bar and status text switch to "learned".
314
- void client.call("compaction.estimate", {}).then((estimate) => {
315
- if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
316
- footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
317
- footerState.requestRender?.();
318
- }).catch(() => undefined);
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);
319
396
  compactionTimer = setInterval(() => footerState.requestRender?.(), FOOTER_COMPACTION_RENDER_INTERVAL_MS);
320
397
  signal.addEventListener("abort", finishCompactionUi, { once: true });
321
398
  if (signal.aborted) finishCompactionUi();
@@ -329,14 +406,17 @@ export function registerJittorExtension(
329
406
  enforcement.setEnabled(false);
330
407
  ctx.ui.setStatus("jittor", undefined);
331
408
  showFooter(ctx);
332
- ctx.ui.notify("Jittor enforcement is off (monitor-only); the informational footer remains independent and provider requests will not be blocked.", "warning");
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
+ );
333
413
  };
334
414
  const enable = async (ctx: ExtensionContext): Promise<void> => {
335
415
  try {
336
416
  await syncCurrentRoute(pi, client, ctx);
337
417
  await syncAvailableRoutes(pi, client, ctx);
338
418
  await client.call("telemetry.poll", {});
339
- 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;
340
420
  if (readinessDecision.action === "halt") throw new Error(readinessDecision.reason);
341
421
  enforcement.setEnabled(true);
342
422
  showFooter(ctx);
@@ -356,7 +436,7 @@ export function registerJittorExtension(
356
436
  const action = args.trim().toLowerCase();
357
437
  if (action === "" || action === "settings") {
358
438
  await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
359
- setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
439
+ setEnforcement: async (enabled) => (enabled ? enable(ctx) : disable(ctx)),
360
440
  setFooter: async (enabled) => {
361
441
  enforcement.setFooterEnabled(enabled);
362
442
  showFooter(ctx);
@@ -392,11 +472,18 @@ export function registerJittorExtension(
392
472
  return;
393
473
  }
394
474
  const candidates = benchmarkCandidatesFromPi(ctx.modelRegistry.getAvailable() as PiRouteModel[], pi.getThinkingLevel());
395
- await showBenchmarkPanel(ctx, client, candidates, `${ctx.model.provider}/${ctx.model.id}`, requestedDomain ?? "general", requestedType ?? "general");
475
+ await showBenchmarkPanel(
476
+ ctx,
477
+ client,
478
+ candidates,
479
+ `${ctx.model.provider}/${ctx.model.id}`,
480
+ requestedDomain ?? "general",
481
+ requestedType ?? "general",
482
+ );
396
483
  return;
397
484
  }
398
485
  if (action === "outcome accepted" || action === "outcome rejected") {
399
- const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
486
+ const explicitOutcome = action.endsWith("accepted") ? ("accepted" as const) : ("rejected" as const);
400
487
  const outcomeMetric = localRunTelemetry.explicitOutcomeMetric(explicitOutcome);
401
488
  if (!outcomeMetric) {
402
489
  ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
@@ -424,11 +511,20 @@ export function registerJittorExtension(
424
511
  }
425
512
  if (action === "recovery cancel") {
426
513
  cancelRecovery(true);
427
- ctx.ui.notify(`Jittor Codex recovery cooldown and attempt window cleared; recovery remains ${codexRecovery.isCodexRecoveryEnabled() ? "on" : "off"}.`, "info");
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);
428
526
  return;
429
527
  }
430
- if (action === "off" || action === "disable") { disable(ctx); return; }
431
- if (action === "on" || action === "enable") { await enable(ctx); return; }
432
528
  if (action === "footer off" || action === "footer disable") {
433
529
  enforcement.setFooterEnabled(false);
434
530
  ctx.ui.setFooter(undefined);
@@ -443,15 +539,19 @@ export function registerJittorExtension(
443
539
  return;
444
540
  }
445
541
  if (action === "context") {
446
- const summary = await client.call("context.assess", {}) as ContextAssessment;
447
- const average = summary.injection.averageCharacters === null ? "unknown" : Math.round(summary.injection.averageCharacters).toLocaleString();
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();
448
545
  const p95 = summary.injection.p95Characters === null ? "unknown" : Math.round(summary.injection.p95Characters).toLocaleString();
449
- ctx.ui.notify([
450
- `Papyrus injection: ${summary.injection.runs} runs · avg ${average} chars · p95 ${p95} chars · unchanged ${summary.injection.unchangedRate === null ? "unknown" : `${(summary.injection.unchangedRate * 100).toFixed(1)}%`}`,
451
- `Mix: rules ${summary.injection.ruleCharacters.toLocaleString()} chars · tasks ${summary.injection.taskCharacters.toLocaleString()} chars · estimated ${summary.injection.estimatedTokens.toLocaleString()} tokens`,
452
- `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`,
453
- `Completeness: ${summary.completeness}`,
454
- ].join("\n"), "info");
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
+ );
455
555
  return;
456
556
  }
457
557
  // Reached only for the explicit "status" keyword or any other unrecognized text; bare "" is
@@ -465,11 +565,31 @@ export function registerJittorExtension(
465
565
  });
466
566
 
467
567
  pi.registerCommand("context", {
468
- description: "Context Hub: real usage plus every segment's estimated size (tool schemas by owning extension, and whatever other extensions contributed), each tagged with how it was attributed",
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",
469
570
  handler: async (_args, ctx) => {
470
- const toolSegment = toolLedgerSegment(pi.getAllTools());
471
- const segments = [toolSegment, ...contextHub.contributedSegments()];
472
- ctx.ui.notify(buildContextReport(segments, ctx.getContextUsage()), "info");
571
+ const activeToolNames = new Set(pi.getActiveTools());
572
+ const toolSegment = toolLedgerSegment(pi.getAllTools().filter((tool) => activeToolNames.has(tool.name)));
573
+ // Real tree (not just the linear current-branch path): surfaces content sitting in an
574
+ // abandoned /tree branch, which cost real tokens to generate but isn't in context now.
575
+ const tree = ctx.sessionManager.getTree() as SessionTreeNodeLike[];
576
+ // buildContextEntries(), NOT getBranch(): getBranch() returns every raw entry on the current
577
+ // path including everything a real compaction has already summarized away.
578
+ const activeEntryIds = new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id));
579
+ const branchEntryIds = new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id));
580
+ const messageHistory = buildMessageHistoryTree(tree, activeEntryIds, branchEntryIds);
581
+ const usage = ctx.getContextUsage();
582
+ const ownSegments = [
583
+ basePromptSegment(lastObservedBasePromptTokens, lastObservedBasePromptItems),
584
+ messageHistorySegment(messageHistory),
585
+ toolSegment,
586
+ ];
587
+ const breakdown = composeContextBreakdown({
588
+ totalTokens: usage?.tokens ?? null,
589
+ contextWindow: ctx.model?.contextWindow ?? null,
590
+ segments: [...ownSegments, ...contextHub.contributedSegments()],
591
+ });
592
+ await showContextView(ctx, breakdown);
473
593
  },
474
594
  });
475
595
 
@@ -479,14 +599,19 @@ export function registerJittorExtension(
479
599
  const action = args.trim().toLowerCase();
480
600
  if (action === "budget" || action.startsWith("budget ")) {
481
601
  const [, periodText, valueText] = action.split(/\s+/);
482
- 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;
483
603
  if (!period) {
484
- const values = USAGE_PERIODS.map(({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`).join(" · ");
604
+ const values = USAGE_PERIODS.map(
605
+ ({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`,
606
+ ).join(" · ");
485
607
  ctx.ui.notify(`Token budgets · ${values}`, "info");
486
608
  return;
487
609
  }
488
610
  if (valueText === undefined) {
489
- ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget: ${usageBudgets.getUsageTokenBudget(period)?.toLocaleString() ?? "not configured"}`, "info");
611
+ ctx.ui.notify(
612
+ `${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget: ${usageBudgets.getUsageTokenBudget(period)?.toLocaleString() ?? "not configured"}`,
613
+ "info",
614
+ );
490
615
  return;
491
616
  }
492
617
  if (valueText === "off" || valueText === "clear") {
@@ -500,7 +625,10 @@ export function registerJittorExtension(
500
625
  return;
501
626
  }
502
627
  usageBudgets.setUsageTokenBudget(period, tokens);
503
- ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
628
+ ctx.ui.notify(
629
+ `${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`,
630
+ "info",
631
+ );
504
632
  return;
505
633
  }
506
634
  if (action !== "" && action !== "cost" && action !== "tokens") {
@@ -542,6 +670,15 @@ export function registerJittorExtension(
542
670
  }
543
671
  });
544
672
 
673
+ pi.on("before_agent_start", async (event) => {
674
+ // No new hook, no new risk: measures event.systemPrompt's length and structural
675
+ // event.systemPromptOptions as-of this handler's own place in the before_agent_start chain --
676
+ // see buildBasePromptItems' own doc comment for the resulting load-order caveat.
677
+ const characters = (event.systemPrompt ?? "").length;
678
+ lastObservedBasePromptTokens = Math.ceil(characters / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN);
679
+ lastObservedBasePromptItems = buildBasePromptItems(event.systemPromptOptions, characters);
680
+ });
681
+
545
682
  pi.on("session_before_compact", async (event, ctx) => {
546
683
  beginCompactionUi(ctx, event.signal);
547
684
  const usage = ctx.getContextUsage();
@@ -556,13 +693,16 @@ export function registerJittorExtension(
556
693
 
557
694
  pi.on("session_compact", async (event) => {
558
695
  finishCompactionUi();
559
- await recordMetrics(client, [compactionTelemetry.complete({ reason: event.reason, willRetry: event.willRetry })]).catch(() => undefined);
696
+ await recordMetrics(client, [compactionTelemetry.complete({ reason: event.reason, willRetry: event.willRetry })]).catch(
697
+ () => undefined,
698
+ );
560
699
  });
561
700
 
562
701
  pi.on("agent_settled", async (_event, ctx) => {
563
702
  if (footerState.compaction) {
564
703
  finishCompactionUi();
565
- if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
704
+ if (compactionTelemetry.hasOpenCompaction())
705
+ await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
566
706
  }
567
707
  scheduleCodexRecovery(ctx);
568
708
  if (!enforcement.isFooterEnabled()) return;
@@ -580,7 +720,7 @@ export function registerJittorExtension(
580
720
  if (event.source !== "extension") cancelRecovery(true);
581
721
  if (event.source === "extension" || !enforcement.isEnabled()) return { action: "continue" as const };
582
722
  try {
583
- 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;
584
724
  if (next.action === "halt") {
585
725
  ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
586
726
  return { action: "handled" as const };
@@ -593,7 +733,9 @@ export function registerJittorExtension(
593
733
  });
594
734
 
595
735
  pi.on("model_select", async (event, ctx) => {
596
- await syncCurrentRoute(pi, client, ctx, event.model).then(() => syncAvailableRoutes(pi, client, ctx)).catch(() => undefined);
736
+ await syncCurrentRoute(pi, client, ctx, event.model)
737
+ .then(() => syncAvailableRoutes(pi, client, ctx))
738
+ .catch(() => undefined);
597
739
  if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
598
740
  });
599
741
 
@@ -611,7 +753,12 @@ export function registerJittorExtension(
611
753
  try {
612
754
  await syncCurrentRoute(pi, client, ctx);
613
755
  await syncAvailableRoutes(pi, client, ctx);
614
- await applyDecision(pi, client, ctx, await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision);
756
+ await applyDecision(
757
+ pi,
758
+ client,
759
+ ctx,
760
+ (await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
761
+ );
615
762
  await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
616
763
  } catch {
617
764
  halt(ctx, "Jittor could not verify or apply a safe route");
@@ -629,7 +776,9 @@ export function registerJittorExtension(
629
776
  pi.on("after_provider_response", async (event, ctx) => {
630
777
  localRunTelemetry.onProviderResponse();
631
778
  if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
632
- const notifySchemaDrift = (message: string) => { if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error"); };
779
+ const notifySchemaDrift = (message: string) => {
780
+ if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error");
781
+ };
633
782
  await providerResponseTelemetry.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift);
634
783
  if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
635
784
  });
@@ -641,13 +790,27 @@ export function registerJittorExtension(
641
790
 
642
791
  pi.on("message_end", async (event, ctx) => {
643
792
  if (event.message.role === "assistant") {
644
- if (event.message.provider === "openai-codex") codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
645
- await providerResponseTelemetry.handleMessageEnd(client, event.message.provider, event.message.stopReason, event.message.errorMessage);
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
+ );
646
801
  }
647
802
  const metrics = assistantUsageMetrics(event.message, Date.now(), focusedTaskId, pi.getThinkingLevel());
648
803
  if (metrics.length > 0) {
649
- const amount = (name: string): number => metrics.filter((metric) => metric.metric === name && typeof metric.value === "number").reduce((sum, metric) => sum + (metric.value ?? 0), 0);
650
- compactionTelemetry.observeProviderUsage({ input: amount("input-tokens"), output: amount("output-tokens"), cacheRead: amount("cache-read-tokens"), cacheWrite: amount("cache-write-tokens") });
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
+ });
651
814
  await recordMetrics(client, metrics).catch(() => undefined);
652
815
  }
653
816
  if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
@@ -655,7 +818,8 @@ export function registerJittorExtension(
655
818
 
656
819
  pi.on("session_shutdown", async (_event, ctx) => {
657
820
  finishCompactionUi();
658
- if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
821
+ if (compactionTelemetry.hasOpenCompaction())
822
+ await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
659
823
  stopPapyrusContext?.();
660
824
  stopPapyrusTaskFocus?.();
661
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