@danypops/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/README.md CHANGED
@@ -41,14 +41,17 @@ Blocking always has a daemon-independent escape hatch. `/jittor off` immediately
41
41
 
42
42
  ### Opt-in Codex settled-turn recovery
43
43
 
44
- Transient Codex recovery is securely off by default. Opt in explicitly through `$XDG_CONFIG_HOME/jittor/extension.json` (or `~/.config/jittor/extension.json`):
44
+ Transient Codex recovery is securely off by default and controlled through the existing Jittor command surface:
45
45
 
46
- ```json
47
- {
48
- "codexRecoveryEnabled": true
49
- }
46
+ ```text
47
+ /jittor recovery status
48
+ /jittor recovery on
49
+ /jittor recovery off
50
+ /jittor recovery cancel
50
51
  ```
51
52
 
53
+ The on/off choice persists privately in `$XDG_CONFIG_HOME/jittor/extension.json` (or `~/.config/jittor/extension.json`). Status reports only enabled state, cooldown, bounded attempt/window counters, and the normalized failure class. `cancel` clears the current cooldown and attempt window without changing the persisted on/off choice.
54
+
52
55
  Jittor observes finalized Codex assistant errors through Pi's public message lifecycle, classifies only bounded error metadata, and waits for `agent_settled` before acting. That boundary guarantees Pi's built-in retry, compaction retry, and queued follow-up work has finished. A transient concurrency, rate-limit, overload, or transport failure then schedules one hidden follow-up with Retry-After-aware capped jitter. Recovery is limited to three attempts per ten-minute window, never overlaps pending Pi messages, resets after success, and is canceled by human input or session shutdown. Quota, authentication, invalid-request, unknown, and aborted failures remain terminal. Raw provider payloads are never retained or injected.
53
56
 
54
57
  Run `/jittor usage` for a colored Unicode token histogram with X/Y axes, provider/model series, input/output/cache totals, refresh, and `24h`, `7d`, `30d`, or `90d` ranges. Left/Right changes range and `r` refreshes. Usage is persisted by the daemon from finalized Pi assistant messages.
@@ -7,8 +7,10 @@ import {
7
7
  CODEX_RECOVERY_MAX_DELAY_MS,
8
8
  FOOTER_COMPACTION_RENDER_INTERVAL_MS,
9
9
  MAX_DYNAMIC_ROUTES,
10
+ MILLISECONDS_PER_MINUTE,
11
+ MILLISECONDS_PER_SECOND,
10
12
  } from "../../src/constants.ts";
11
- import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
13
+ import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureKind, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
12
14
  import type { MetricObservation, StoredMetricObservation } from "../../src/domain/metric.ts";
13
15
  import type { PolicyDecision, Route } from "../../src/policy.ts";
14
16
  import type { RouterStatus } from "../../src/ports/router-controller.ts";
@@ -48,9 +50,13 @@ const SYSTEM_RECOVERY_RUNTIME: CodexRecoveryRuntime = {
48
50
 
49
51
  function recoveryControl(enforcement: EnforcementControl): CodexRecoveryControl {
50
52
  const candidate = enforcement as EnforcementControl & Partial<CodexRecoveryControl>;
51
- return typeof candidate.isCodexRecoveryEnabled === "function"
52
- ? { isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!() }
53
- : { isCodexRecoveryEnabled: () => false };
53
+ const set = (candidate as Partial<CodexRecoveryControl>).setCodexRecoveryEnabled;
54
+ return typeof candidate.isCodexRecoveryEnabled === "function" && typeof set === "function"
55
+ ? {
56
+ isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
57
+ setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
58
+ }
59
+ : { isCodexRecoveryEnabled: () => false, setCodexRecoveryEnabled() {} };
54
60
  }
55
61
 
56
62
  function header(headers: Record<string, string>, name: string): string | undefined {
@@ -216,12 +222,33 @@ export function registerJittorExtension(
216
222
  jitterRatio: CODEX_RECOVERY_JITTER_RATIO,
217
223
  }, recoveryRuntime.random);
218
224
  let recoveryTimer: unknown;
225
+ let recoveryCooldown: { until: number; attempt: number; failureKind: CodexFailureKind } | undefined;
219
226
  let lastCodexResponse: CodexFailureMetadata = {};
220
227
  const cancelRecovery = (resetPolicy: boolean): void => {
221
228
  if (recoveryTimer !== undefined) recoveryRuntime.clearTimeout(recoveryTimer);
222
229
  recoveryTimer = undefined;
230
+ recoveryCooldown = undefined;
223
231
  if (resetPolicy) recoveryPolicy.cancel();
224
232
  };
233
+ const recoveryStatusText = (): string => {
234
+ const now = recoveryRuntime.now();
235
+ const state = recoveryPolicy.state(now);
236
+ const enabled = codexRecovery.isCodexRecoveryEnabled();
237
+ const attempt = recoveryCooldown?.attempt ?? (state.pending ? state.attempts + 1 : state.attempts);
238
+ const phase = recoveryCooldown
239
+ ? `cooldown ${Math.ceil(Math.max(0, recoveryCooldown.until - now) / MILLISECONDS_PER_SECOND)}s`
240
+ : state.pending ? "pending"
241
+ : state.attempts >= CODEX_RECOVERY_MAX_ATTEMPTS ? "exhausted"
242
+ : state.attempts > 0 ? "waiting" : "idle";
243
+ const failureKind = recoveryCooldown?.failureKind ?? state.lastFailureKind;
244
+ return [
245
+ `Codex recovery: ${enabled ? "on" : "off"}`,
246
+ phase,
247
+ `attempt ${attempt}/${CODEX_RECOVERY_MAX_ATTEMPTS}`,
248
+ `window ${CODEX_RECOVERY_ATTEMPT_WINDOW_MS / MILLISECONDS_PER_MINUTE}m`,
249
+ ...(failureKind ? [failureKind] : []),
250
+ ].join(" · ");
251
+ };
225
252
  const scheduleCodexRecovery = (ctx: ExtensionContext): void => {
226
253
  if (!codexRecovery.isCodexRecoveryEnabled() || recoveryTimer !== undefined || !ctx.isIdle() || ctx.hasPendingMessages()) return;
227
254
  const plan = recoveryPolicy.plan(recoveryRuntime.now());
@@ -231,8 +258,10 @@ export function registerJittorExtension(
231
258
  return;
232
259
  }
233
260
  if (plan.action !== "schedule") return;
261
+ recoveryCooldown = { until: recoveryRuntime.now() + plan.delayMs, attempt: plan.attempt, failureKind: plan.failureKind };
234
262
  recoveryTimer = recoveryRuntime.setTimeout(async () => {
235
263
  recoveryTimer = undefined;
264
+ recoveryCooldown = undefined;
236
265
  if (!ctx.isIdle() || ctx.hasPendingMessages()) return;
237
266
  const attempt = recoveryPolicy.recordAttempt(recoveryRuntime.now());
238
267
  if (!attempt) return;
@@ -296,6 +325,26 @@ export function registerJittorExtension(
296
325
  description: "Inspect or control Jittor routing, budgets, and usage",
297
326
  handler: async (args, ctx) => {
298
327
  const action = args.trim().toLowerCase();
328
+ if (action === "recovery" || action === "recovery status") {
329
+ ctx.ui.notify(recoveryStatusText(), "info");
330
+ return;
331
+ }
332
+ if (action === "recovery on" || action === "recovery enable") {
333
+ codexRecovery.setCodexRecoveryEnabled(true);
334
+ ctx.ui.notify("Jittor Codex recovery enabled; bounded retries begin only after transient failures fully settle.", "info");
335
+ return;
336
+ }
337
+ if (action === "recovery off" || action === "recovery disable") {
338
+ cancelRecovery(true);
339
+ codexRecovery.setCodexRecoveryEnabled(false);
340
+ ctx.ui.notify("Jittor Codex recovery disabled and pending recovery cleared.", "info");
341
+ return;
342
+ }
343
+ if (action === "recovery cancel") {
344
+ cancelRecovery(true);
345
+ ctx.ui.notify(`Jittor Codex recovery cooldown and attempt window cleared; recovery remains ${codexRecovery.isCodexRecoveryEnabled() ? "on" : "off"}.`, "info");
346
+ return;
347
+ }
299
348
  if (action === "off" || action === "disable") { disable(ctx); return; }
300
349
  if (action === "on" || action === "enable") { await enable(ctx); return; }
301
350
  if (action === "footer off" || action === "footer disable") {
@@ -11,6 +11,7 @@ export interface EnforcementControl {
11
11
 
12
12
  export interface CodexRecoveryControl {
13
13
  isCodexRecoveryEnabled(): boolean;
14
+ setCodexRecoveryEnabled(enabled: boolean): void;
14
15
  }
15
16
 
16
17
  export interface PersistentExtensionControl extends EnforcementControl, CodexRecoveryControl {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/jittor",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Just-in-Time Token Optimizing Router for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package", "llm-router", "token-budget"],
@@ -50,6 +50,7 @@ export class CodexRecoveryPolicy {
50
50
  private pendingFailure: CodexFailure | undefined;
51
51
  private attempts = 0;
52
52
  private windowStartedAt: number | undefined;
53
+ private lastFailureKind: CodexFailureKind | undefined;
53
54
 
54
55
  constructor(
55
56
  private readonly options: CodexRecoveryOptions,
@@ -65,6 +66,7 @@ export class CodexRecoveryPolicy {
65
66
  observeFailure(failure: CodexFailure, now: number): void {
66
67
  this.normalizeWindow(now);
67
68
  this.pendingFailure = failure.transient ? failure : undefined;
69
+ this.lastFailureKind = failure.transient ? failure.kind : undefined;
68
70
  }
69
71
 
70
72
  observeSuccess(): void {
@@ -75,6 +77,7 @@ export class CodexRecoveryPolicy {
75
77
  this.pendingFailure = undefined;
76
78
  this.attempts = 0;
77
79
  this.windowStartedAt = undefined;
80
+ this.lastFailureKind = undefined;
78
81
  }
79
82
 
80
83
  abandonFailure(): void {
@@ -112,14 +115,21 @@ export class CodexRecoveryPolicy {
112
115
  return attempt;
113
116
  }
114
117
 
115
- state(): { attempts: number; pending: boolean } {
116
- return { attempts: this.attempts, pending: this.pendingFailure !== undefined };
118
+ state(now?: number): { attempts: number; pending: boolean; lastFailureKind?: CodexFailureKind; windowStartedAt?: number } {
119
+ if (now !== undefined) this.normalizeWindow(now);
120
+ return {
121
+ attempts: this.attempts,
122
+ pending: this.pendingFailure !== undefined,
123
+ ...(this.lastFailureKind ? { lastFailureKind: this.lastFailureKind } : {}),
124
+ ...(this.windowStartedAt !== undefined ? { windowStartedAt: this.windowStartedAt } : {}),
125
+ };
117
126
  }
118
127
 
119
128
  private normalizeWindow(now: number): void {
120
129
  if (this.windowStartedAt !== undefined && now - this.windowStartedAt >= this.options.attemptWindowMs) {
121
130
  this.attempts = 0;
122
131
  this.windowStartedAt = undefined;
132
+ if (!this.pendingFailure) this.lastFailureKind = undefined;
123
133
  }
124
134
  }
125
135
  }