@danypops/jittor 0.3.0 → 0.4.0
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 +6 -0
- package/extension/src/index.ts +16 -0
- package/extension/src/settings-tui.ts +153 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,12 @@ The on/off choice persists privately in `$XDG_CONFIG_HOME/jittor/extension.json`
|
|
|
54
54
|
|
|
55
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.
|
|
56
56
|
|
|
57
|
+
### Settings
|
|
58
|
+
|
|
59
|
+
Run `/jittor settings` for one keyboard-navigable TUI covering routing enforcement, the informational footer, Codex recovery, and all four token-budget thresholds. It uses explicit ON/OFF and configured/not-configured labels, remains bounded on narrow terminals, confirms weaker enforcement and recovery changes, and applies enforcement enablement through the same readiness checks as `/jittor on`. Existing non-TUI commands remain available for automation.
|
|
60
|
+
|
|
61
|
+
### Usage graphs
|
|
62
|
+
|
|
57
63
|
Run `/jittor usage` for a colored Unicode cumulative token graph with X/Y axes, provider/model series, input/output/cache totals, refresh, and explicit **Hourly**, **Daily**, **Weekly**, and **Monthly** periods. Left/Right changes period and `r` refreshes. Usage is persisted by the daemon from finalized Pi assistant messages.
|
|
58
64
|
|
|
59
65
|
Token-budget thresholds are optional and must be configured by the user; Jittor never infers a token allowance from Codex or another provider's subscription percentage. Configure or clear one period with `/jittor usage budget <hourly|daily|weekly|monthly> <positive-tokens|off>`, and inspect all four with `/jittor usage budget`. A configured budget appears as a horizontal threshold on the cumulative graph with explicit remaining or **OVER BUDGET** state. These private settings persist in `$XDG_CONFIG_HOME/jittor/extension.json` (or `~/.config/jittor/extension.json`).
|
package/extension/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { parseCodexRateLimitHeaders } from "../../src/providers/codex.ts";
|
|
|
19
19
|
import { installIntegratedFooter, type IntegratedFooterState } from "./footer.ts";
|
|
20
20
|
import { callJittor } from "./service-client.ts";
|
|
21
21
|
import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
22
|
+
import { showSettingsPanel } from "./settings-tui.ts";
|
|
22
23
|
import { buildFooterBudget, formatFooterStatus, showJittorPanel } from "./tui.ts";
|
|
23
24
|
import { showUsagePanel } from "./usage.ts";
|
|
24
25
|
|
|
@@ -372,6 +373,21 @@ export function registerJittorExtension(
|
|
|
372
373
|
ctx.ui.notify("Jittor informational footer enabled; routing enforcement is unchanged.", "info");
|
|
373
374
|
return;
|
|
374
375
|
}
|
|
376
|
+
if (action === "settings") {
|
|
377
|
+
await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
|
|
378
|
+
setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
|
|
379
|
+
setFooter: async (enabled) => {
|
|
380
|
+
enforcement.setFooterEnabled(enabled);
|
|
381
|
+
showFooter(ctx);
|
|
382
|
+
if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
|
|
383
|
+
},
|
|
384
|
+
setRecovery: (enabled) => {
|
|
385
|
+
if (!enabled) cancelRecovery(true);
|
|
386
|
+
codexRecovery.setCodexRecoveryEnabled(enabled);
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
375
391
|
if (action === "usage budget" || action.startsWith("usage budget ")) {
|
|
376
392
|
const [, , periodText, valueText] = action.split(/\s+/);
|
|
377
393
|
const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? periodText as UsagePeriod : undefined;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { USAGE_PERIODS, type UsagePeriod } from "../../src/domain/usage.ts";
|
|
4
|
+
import type { CodexRecoveryControl, EnforcementControl, UsageBudgetControl } from "./settings.ts";
|
|
5
|
+
|
|
6
|
+
export interface SettingsSnapshot {
|
|
7
|
+
enforcementEnabled: boolean;
|
|
8
|
+
footerEnabled: boolean;
|
|
9
|
+
codexRecoveryEnabled: boolean;
|
|
10
|
+
usageTokenBudgets: Partial<Record<UsagePeriod, number>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SettingsTheme {
|
|
14
|
+
fg(color: "accent" | "success" | "warning" | "error" | "muted" | "dim" | "borderMuted", text: string): string;
|
|
15
|
+
bold(text: string): string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type SettingsKey = "enforcement" | "footer" | "recovery" | `budget-${UsagePeriod}`;
|
|
19
|
+
type SettingsAction = { kind: "activate"; key: SettingsKey } | { kind: "close" };
|
|
20
|
+
|
|
21
|
+
export interface SettingsEffects {
|
|
22
|
+
setEnforcement(enabled: boolean): void | Promise<void>;
|
|
23
|
+
setFooter(enabled: boolean): void | Promise<void>;
|
|
24
|
+
setRecovery(enabled: boolean): void | Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SETTINGS_KEYS: SettingsKey[] = [
|
|
28
|
+
"enforcement",
|
|
29
|
+
"footer",
|
|
30
|
+
"recovery",
|
|
31
|
+
...USAGE_PERIODS.map(({ id }) => `budget-${id}` as const),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function state(enabled: boolean, theme: SettingsTheme): string {
|
|
35
|
+
return enabled ? theme.fg("success", "ON") : theme.fg("muted", "OFF");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function budgetLabel(period: UsagePeriod, snapshot: SettingsSnapshot): string {
|
|
39
|
+
const value = snapshot.usageTokenBudgets[period];
|
|
40
|
+
return value === undefined ? "not configured" : `${value.toLocaleString()} tokens`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function rowText(key: SettingsKey, snapshot: SettingsSnapshot, theme: SettingsTheme): string {
|
|
44
|
+
if (key === "enforcement") return `Routing enforcement ${state(snapshot.enforcementEnabled, theme)}`;
|
|
45
|
+
if (key === "footer") return `Informational footer ${state(snapshot.footerEnabled, theme)}`;
|
|
46
|
+
if (key === "recovery") return `Codex recovery ${state(snapshot.codexRecoveryEnabled, theme)}`;
|
|
47
|
+
const period = key.slice("budget-".length) as UsagePeriod;
|
|
48
|
+
return `${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget ${budgetLabel(period, snapshot)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function settingsSnapshot(
|
|
52
|
+
enforcement: EnforcementControl,
|
|
53
|
+
recovery: CodexRecoveryControl,
|
|
54
|
+
budgets: UsageBudgetControl,
|
|
55
|
+
): SettingsSnapshot {
|
|
56
|
+
return {
|
|
57
|
+
enforcementEnabled: enforcement.isEnabled(),
|
|
58
|
+
footerEnabled: enforcement.isFooterEnabled(),
|
|
59
|
+
codexRecoveryEnabled: recovery.isCodexRecoveryEnabled(),
|
|
60
|
+
usageTokenBudgets: Object.fromEntries(USAGE_PERIODS.map(({ id }) => [id, budgets.getUsageTokenBudget(id)])),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function renderSettingsView(snapshot: SettingsSnapshot, selected: number, width: number, theme: SettingsTheme): string[] {
|
|
65
|
+
const safeWidth = Math.max(20, width);
|
|
66
|
+
const lines = [
|
|
67
|
+
theme.bold("Jittor Settings"),
|
|
68
|
+
theme.fg("dim", "Token budgets are user values; provider quotas remain separate."),
|
|
69
|
+
"",
|
|
70
|
+
];
|
|
71
|
+
for (let index = 0; index < SETTINGS_KEYS.length; index += 1) {
|
|
72
|
+
const selectedRow = index === selected;
|
|
73
|
+
const prefix = selectedRow ? theme.fg("accent", "› ") : " ";
|
|
74
|
+
lines.push(`${prefix}${rowText(SETTINGS_KEYS[index]!, snapshot, theme)}`);
|
|
75
|
+
}
|
|
76
|
+
lines.push("", theme.fg("dim", "↑/↓ select · Enter edit · Esc close"));
|
|
77
|
+
return lines.map((line) => visibleWidth(line) <= safeWidth ? line : truncateToWidth(line, safeWidth, "…"));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function plainTheme(): SettingsTheme {
|
|
81
|
+
return { fg: (_color, text) => text, bold: (text) => text };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function editBudget(ctx: ExtensionCommandContext, budgets: UsageBudgetControl, period: UsagePeriod): Promise<void> {
|
|
85
|
+
const label = USAGE_PERIODS.find((candidate) => candidate.id === period)!.label;
|
|
86
|
+
const current = budgets.getUsageTokenBudget(period);
|
|
87
|
+
const input = await ctx.ui.input(`${label} token budget`, current?.toLocaleString() ?? "positive token count or off");
|
|
88
|
+
if (input === undefined) return;
|
|
89
|
+
const normalized = input.trim().toLowerCase();
|
|
90
|
+
if (normalized === "off" || normalized === "clear") {
|
|
91
|
+
budgets.setUsageTokenBudget(period, undefined);
|
|
92
|
+
ctx.ui.notify(`${label} token budget cleared.`, "info");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const tokens = Number(normalized.replaceAll(",", ""));
|
|
96
|
+
if (!Number.isFinite(tokens) || tokens <= 0) {
|
|
97
|
+
ctx.ui.notify("Enter a positive token count, or `off` to clear this threshold.", "warning");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
budgets.setUsageTokenBudget(period, tokens);
|
|
101
|
+
ctx.ui.notify(`${label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function showSettingsPanel(
|
|
105
|
+
ctx: ExtensionCommandContext,
|
|
106
|
+
enforcement: EnforcementControl,
|
|
107
|
+
recovery: CodexRecoveryControl,
|
|
108
|
+
budgets: UsageBudgetControl,
|
|
109
|
+
effects: SettingsEffects = {
|
|
110
|
+
setEnforcement: (enabled) => enforcement.setEnabled(enabled),
|
|
111
|
+
setFooter: (enabled) => enforcement.setFooterEnabled(enabled),
|
|
112
|
+
setRecovery: (enabled) => recovery.setCodexRecoveryEnabled(enabled),
|
|
113
|
+
},
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
if (ctx.mode !== "tui") {
|
|
116
|
+
ctx.ui.notify(renderSettingsView(settingsSnapshot(enforcement, recovery, budgets), -1, 100, plainTheme()).slice(0, -2).join("\n"), "info");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (;;) {
|
|
120
|
+
const snapshot = settingsSnapshot(enforcement, recovery, budgets);
|
|
121
|
+
const action = await ctx.ui.custom<SettingsAction>((tui, theme, _keybindings, done) => {
|
|
122
|
+
let selected = 0;
|
|
123
|
+
return {
|
|
124
|
+
invalidate() {},
|
|
125
|
+
render(width: number): string[] { return renderSettingsView(snapshot, selected, width, theme); },
|
|
126
|
+
handleInput(data: string): void {
|
|
127
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || data === "q") done({ kind: "close" });
|
|
128
|
+
else if (matchesKey(data, "up")) { selected = (selected - 1 + SETTINGS_KEYS.length) % SETTINGS_KEYS.length; tui.requestRender(); }
|
|
129
|
+
else if (matchesKey(data, "down")) { selected = (selected + 1) % SETTINGS_KEYS.length; tui.requestRender(); }
|
|
130
|
+
else if (matchesKey(data, "return") || matchesKey(data, "enter") || matchesKey(data, "space")) done({ kind: "activate", key: SETTINGS_KEYS[selected]! });
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
if (!action || action.kind === "close") return;
|
|
135
|
+
if (action.key === "enforcement") {
|
|
136
|
+
if (enforcement.isEnabled()) {
|
|
137
|
+
if (await ctx.ui.confirm("Disable routing enforcement?", "Jittor will remain monitor-only and will no longer block unsafe provider requests.")) await effects.setEnforcement(false);
|
|
138
|
+
} else await effects.setEnforcement(true);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (action.key === "footer") {
|
|
142
|
+
await effects.setFooter(!enforcement.isFooterEnabled());
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (action.key === "recovery") {
|
|
146
|
+
if (!recovery.isCodexRecoveryEnabled()) {
|
|
147
|
+
if (await ctx.ui.confirm("Enable Codex recovery?", "Jittor may start bounded hidden retries only after transient Codex failures fully settle.")) await effects.setRecovery(true);
|
|
148
|
+
} else await effects.setRecovery(false);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
await editBudget(ctx, budgets, action.key.slice("budget-".length) as UsagePeriod);
|
|
152
|
+
}
|
|
153
|
+
}
|