@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 +8 -5
- package/extension/src/index.ts +53 -4
- package/extension/src/settings.ts +1 -0
- package/package.json +1 -1
- package/src/domain/codex-recovery.ts +12 -2
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
|
|
44
|
+
Transient Codex recovery is securely off by default and controlled through the existing Jittor command surface:
|
|
45
45
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
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.
|
package/extension/src/index.ts
CHANGED
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
@@ -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
|
-
|
|
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
|
}
|