@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.
@@ -1,50 +1,58 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
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 { 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";
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
- getUsageTokenBudget: (period) => candidate.getUsageTokenBudget!(period),
65
- setUsageTokenBudget: (period, tokens) => candidate.setUsageTokenBudget!(period, tokens),
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
- isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
76
- setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
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("abort", () => {
102
- clearTimeout(timer);
103
- reject(new Error("Jittor throttle cancelled"));
104
- }, { once: true });
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 (!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;
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((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
+ );
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 (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;
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 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
+ );
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) { 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
+ }
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", { 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
+ });
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(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
+ );
222
267
  }
223
- 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
+ );
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(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[] {
232
285
  if (typeof message !== "object" || message === null || Array.isArray(message)) return [];
233
286
  const value = message as Record<string, unknown>;
234
- if (value["role"] !== "assistant" || typeof value["usage"] !== "object" || value["usage"] === null) return [];
235
- const usage = value["usage"] as Record<string, unknown>;
236
- const provider = typeof value["provider"] === "string" ? value["provider"] : "unknown";
237
- 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";
238
291
  const scope = `${provider}:${model}`;
239
- 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
+ };
240
298
  const metrics: MetricObservation[] = [];
241
- 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) {
242
305
  const amount = usage[field];
243
- 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 });
244
308
  }
245
- const cost = typeof usage["cost"] === "object" && usage["cost"] !== null ? (usage["cost"] as Record<string, unknown>)["total"] : undefined;
246
- 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 });
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.call("compaction.estimate", {}).then((estimate) => {
324
- if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
325
- footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
326
- footerState.requestRender?.();
327
- }).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);
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("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
+ );
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(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
+ );
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(`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);
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 = 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();
457
545
  const p95 = summary.injection.p95Characters === null ? "unknown" : Math.round(summary.injection.p95Characters).toLocaleString();
458
- ctx.ui.notify([
459
- `Papyrus injection: ${summary.injection.runs} runs · avg ${average} chars · p95 ${p95} chars · unchanged ${summary.injection.unchangedRate === null ? "unknown" : `${(summary.injection.unchangedRate * 100).toFixed(1)}%`}`,
460
- `Mix: rules ${summary.injection.ruleCharacters.toLocaleString()} chars · tasks ${summary.injection.taskCharacters.toLocaleString()} chars · estimated ${summary.injection.estimatedTokens.toLocaleString()} tokens`,
461
- `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`,
462
- `Completeness: ${summary.completeness}`,
463
- ].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
+ );
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: "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",
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 = [basePromptSegment(lastObservedBasePromptTokens, lastObservedBasePromptItems), messageHistorySegment(messageHistory), toolSegment];
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(({ 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(" · ");
509
607
  ctx.ui.notify(`Token budgets · ${values}`, "info");
510
608
  return;
511
609
  }
512
610
  if (valueText === undefined) {
513
- 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
+ );
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(`${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
+ );
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(() => undefined);
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()) 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);
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).then(() => syncAvailableRoutes(pi, client, ctx)).catch(() => undefined);
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(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
+ );
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) => { 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
+ };
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") codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
678
- 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
+ );
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 => metrics.filter((metric) => metric.metric === name && typeof metric.value === "number").reduce((sum, metric) => sum + (metric.value ?? 0), 0);
683
- 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
+ });
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()) 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);
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