lsd-pi 1.1.4 → 1.1.5
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 +2 -1
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
- package/dist/resources/extensions/cache-timer/index.js +4 -0
- package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/dist/resources/extensions/codex-rotate/README.md +9 -3
- package/dist/resources/extensions/codex-rotate/commands.js +15 -8
- package/dist/resources/extensions/codex-rotate/index.js +17 -8
- package/dist/resources/extensions/memory/auto-extract.js +160 -77
- package/dist/resources/extensions/shared/rtk.js +89 -87
- package/dist/resources/extensions/subagent/index.js +32 -7
- package/dist/startup-model-validation.js +12 -2
- package/dist/update-check.js +2 -2
- package/dist/update-cmd.js +3 -3
- package/package.json +2 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +23 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
- package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +20 -53
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +79 -63
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +47 -11
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +241 -37
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
- package/packages/pi-coding-agent/package.json +9 -4
- package/packages/pi-coding-agent/src/core/agent-session.ts +29 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
- package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
- package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
- package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +9 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +19 -59
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +77 -66
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +52 -10
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +286 -46
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
- package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
- package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
- package/packages/pi-tui/dist/components/loader.d.ts +26 -6
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +178 -18
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/src/components/loader.ts +196 -19
- package/pkg/dist/modes/interactive/theme/themes.js +2 -2
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
- package/src/resources/extensions/cache-timer/index.ts +101 -96
- package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/src/resources/extensions/codex-rotate/README.md +9 -3
- package/src/resources/extensions/codex-rotate/commands.ts +335 -329
- package/src/resources/extensions/codex-rotate/index.ts +85 -75
- package/src/resources/extensions/memory/auto-extract.ts +297 -203
- package/src/resources/extensions/memory/tests/auto-extract.test.ts +191 -144
- package/src/resources/extensions/shared/rtk.js +112 -0
- package/src/resources/extensions/subagent/index.ts +34 -6
package/README.md
CHANGED
|
@@ -242,8 +242,9 @@ A few quality-of-life touches in the TUI:
|
|
|
242
242
|
|
|
243
243
|
- the footer can show a live cache timer for the current prompt-cache window
|
|
244
244
|
- `/hotkeys` gives you a full shortcut reference on demand
|
|
245
|
-
- `/settings` now includes toggles for Codex rotate, the cache timer, RTK shell-command compression, and a configurable **Main accent** preset
|
|
245
|
+
- `/settings` now includes toggles for Codex rotate, the cache timer, **Pin last prompt**, RTK shell-command compression, and a configurable **Main accent** preset
|
|
246
246
|
- changing the main accent also updates accent-driven UI elements and the text input border across thinking levels
|
|
247
|
+
- when **Pin last prompt** is enabled, LSD keeps your most recent non-command prompt visible above the editor as a lightweight reminder
|
|
247
248
|
|
|
248
249
|
Some workflow/automation commands still use the legacy namespace:
|
|
249
250
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* with await_job.
|
|
7
7
|
*/
|
|
8
8
|
import { getShellConfig, sanitizeCommand, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, } from "@gsd/pi-coding-agent";
|
|
9
|
+
import { Text } from "@gsd/pi-tui";
|
|
9
10
|
import { Type } from "@sinclair/typebox";
|
|
10
11
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
12
|
import { createWriteStream } from "node:fs";
|
|
@@ -70,6 +71,19 @@ export function createAsyncBashTool(getManager, getCwd) {
|
|
|
70
71
|
"Check /jobs to see all running and recent background jobs.",
|
|
71
72
|
],
|
|
72
73
|
parameters: schema,
|
|
74
|
+
renderCall(args, theme, options) {
|
|
75
|
+
const cmd = args.command ?? "";
|
|
76
|
+
const display = cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd;
|
|
77
|
+
const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
|
|
78
|
+
let text = indicator + theme.fg("toolTitle", theme.bold("async_bash "));
|
|
79
|
+
text += theme.fg("bashMode", "$ ");
|
|
80
|
+
text += theme.fg("accent", display || theme.fg("muted", "..."));
|
|
81
|
+
if (args.label)
|
|
82
|
+
text += theme.fg("muted", ` (${args.label})`);
|
|
83
|
+
if (args.timeout)
|
|
84
|
+
text += theme.fg("dim", ` timeout:${args.timeout}s`);
|
|
85
|
+
return new Text(text, 0, 0);
|
|
86
|
+
},
|
|
73
87
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
74
88
|
const manager = getManager();
|
|
75
89
|
const cwd = getCwd();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* If specific job IDs are provided, waits for those jobs.
|
|
5
5
|
* If omitted, waits for any running job to complete.
|
|
6
6
|
*/
|
|
7
|
+
import { Text } from "@gsd/pi-tui";
|
|
7
8
|
import { Type } from "@sinclair/typebox";
|
|
8
9
|
const DEFAULT_TIMEOUT_SECONDS = 120;
|
|
9
10
|
const schema = Type.Object({
|
|
@@ -21,6 +22,19 @@ export function createAwaitTool(getManager) {
|
|
|
21
22
|
label: "Await Background Job",
|
|
22
23
|
description: "Wait for background jobs to complete. Provide specific job IDs or omit to wait for the next job that finishes. Returns results of completed jobs.",
|
|
23
24
|
parameters: schema,
|
|
25
|
+
renderCall(args, theme, options) {
|
|
26
|
+
const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
|
|
27
|
+
let text = indicator + theme.fg("toolTitle", theme.bold("await_job"));
|
|
28
|
+
if (args.jobs && args.jobs.length > 0) {
|
|
29
|
+
text += " " + theme.fg("accent", args.jobs.join(", "));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
text += theme.fg("muted", " (any)");
|
|
33
|
+
}
|
|
34
|
+
if (args.timeout)
|
|
35
|
+
text += theme.fg("dim", ` timeout:${args.timeout}s`);
|
|
36
|
+
return new Text(text, 0, 0);
|
|
37
|
+
},
|
|
24
38
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
25
39
|
const manager = getManager();
|
|
26
40
|
const { jobs: jobIds, timeout } = params;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cancel_job tool — cancel a running background job.
|
|
3
3
|
*/
|
|
4
|
+
import { Text } from "@gsd/pi-tui";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
5
6
|
const schema = Type.Object({
|
|
6
7
|
job_id: Type.String({ description: "The background job ID to cancel (e.g. bg_a1b2c3d4)" }),
|
|
@@ -11,6 +12,12 @@ export function createCancelJobTool(getManager) {
|
|
|
11
12
|
label: "Cancel Background Job",
|
|
12
13
|
description: "Cancel a running background job by its ID.",
|
|
13
14
|
parameters: schema,
|
|
15
|
+
renderCall(args, theme, options) {
|
|
16
|
+
const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
|
|
17
|
+
let text = indicator + theme.fg("toolTitle", theme.bold("cancel_job "));
|
|
18
|
+
text += theme.fg("accent", args.job_id);
|
|
19
|
+
return new Text(text, 0, 0);
|
|
20
|
+
},
|
|
14
21
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
15
22
|
const manager = getManager();
|
|
16
23
|
const result = manager.cancel(params.job_id);
|
|
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { getAgentDir } from "@gsd/pi-coding-agent";
|
|
16
16
|
const STATUS_KEY = "cache-timer";
|
|
17
|
+
const IS_MEMORY_MAINTENANCE_WORKER = process.env.LSD_MEMORY_EXTRACT === "1" || process.env.LSD_MEMORY_DREAM === "1";
|
|
17
18
|
// ANSI color codes for timer display
|
|
18
19
|
const ANSI_RESET = "\x1b[0m";
|
|
19
20
|
const ANSI_YELLOW = "\x1b[33m";
|
|
@@ -65,6 +66,9 @@ function formatElapsed(ms) {
|
|
|
65
66
|
return `⏱ ${time}`;
|
|
66
67
|
}
|
|
67
68
|
export default function cacheTimerExtension(pi) {
|
|
69
|
+
if (IS_MEMORY_MAINTENANCE_WORKER) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
68
72
|
let timer = null;
|
|
69
73
|
let startTime = null;
|
|
70
74
|
let enabled = readEnabled();
|
|
@@ -18,6 +18,7 @@ Implemented a Codex OAuth rotation extension for LSD that manages multiple ChatG
|
|
|
18
18
|
- Writes `openai-codex` credential array with `api_key` type
|
|
19
19
|
- Avoids the `set()` method (which appends duplicates)
|
|
20
20
|
- Maintains stable credential order for index-based backoff
|
|
21
|
+
- Followed by a live `AuthStorage.reload()` in the extension so the running process sees account changes immediately
|
|
21
22
|
|
|
22
23
|
3. **Background Refresh Timer** (`index.ts`)
|
|
23
24
|
- Runs every 10 minutes
|
|
@@ -42,11 +43,10 @@ Implemented a Codex OAuth rotation extension for LSD that manages multiple ChatG
|
|
|
42
43
|
- `/codex import-cockpit` - Import from Cockpit
|
|
43
44
|
- `/codex sync` - Force refresh all tokens
|
|
44
45
|
|
|
45
|
-
6. **Error Detection**
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- Integrates with `AuthStorage.markUsageLimitReached()`
|
|
46
|
+
6. **Error Detection / Rotation Path**
|
|
47
|
+
- Quota/rate-limit/auth classification lives in `quota.ts`
|
|
48
|
+
- Same-turn retry and per-credential backoff are performed by core `RetryHandler`
|
|
49
|
+
- The extension's responsibility is to keep `auth.json` and the live in-memory auth state in sync
|
|
50
50
|
|
|
51
51
|
## File Structure
|
|
52
52
|
|
|
@@ -87,6 +87,7 @@ Using `FileAuthStorageBackend.withLockAsync()`:
|
|
|
87
87
|
- Prevents race conditions with multiple LSD instances
|
|
88
88
|
- Replaces entire credential array (not just updating keys)
|
|
89
89
|
- Maintains stable order for index-based backoff
|
|
90
|
+
- Requires a follow-up live auth reload in the current process so retry rotation uses the updated credential set
|
|
90
91
|
|
|
91
92
|
### 4. Session-Sticky Credential Selection
|
|
92
93
|
|
|
@@ -105,15 +106,15 @@ LSD already uses session-sticky selection by default:
|
|
|
105
106
|
|
|
106
107
|
## Next Steps: Phase 2 (Resilience)
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
Remaining improvements that could still be added:
|
|
109
110
|
|
|
110
|
-
1. **
|
|
111
|
-
-
|
|
112
|
-
-
|
|
111
|
+
1. **Richer `/codex status` diagnostics**
|
|
112
|
+
- Show live backoff / credential-availability state from `AuthStorage`
|
|
113
|
+
- Make it easier to verify when an account was rotated away after a usage-limit hit
|
|
113
114
|
|
|
114
|
-
2. **
|
|
115
|
-
- Could listen for external auth
|
|
116
|
-
- Would re-sync on change detection
|
|
115
|
+
2. **External change detection**
|
|
116
|
+
- Could listen for external auth/account file modifications
|
|
117
|
+
- Would re-sync / reload on change detection
|
|
117
118
|
|
|
118
119
|
3. **Migration helpers**
|
|
119
120
|
- Could add `/codex migrate` to assist users moving from Cockpit
|
|
@@ -121,8 +122,12 @@ The following features were planned but not yet implemented:
|
|
|
121
122
|
|
|
122
123
|
## Testing
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
Implemented regression coverage now includes:
|
|
126
|
+
- `src/tests/codex-rotate-auth-reload.test.ts`
|
|
127
|
+
- Verifies that codex-rotate syncs require a live auth reload for the running process to see the latest credentials
|
|
128
|
+
- Verifies `quota_exhausted` backoff on one `openai-codex` credential causes the next retry to select the next credential
|
|
125
129
|
|
|
130
|
+
Manual smoke test:
|
|
126
131
|
1. Add an account: `/codex add`
|
|
127
132
|
2. Check status: `/codex status`
|
|
128
133
|
3. Add another account: `/codex add`
|
|
@@ -97,14 +97,20 @@ Codex access tokens work identically to API keys:
|
|
|
97
97
|
- Background timer runs every 10 minutes
|
|
98
98
|
- Tokens are refreshed when expiring within 5 minutes
|
|
99
99
|
- Refreshed tokens are atomically synced to auth.json
|
|
100
|
+
- After every successful sync, the extension reloads live `AuthStorage` so retries and rotation see the latest credentials immediately
|
|
100
101
|
- Failed refreshes disable the account with a reason
|
|
101
102
|
|
|
102
103
|
### Error Handling
|
|
103
104
|
|
|
104
|
-
The extension
|
|
105
|
+
The extension now relies on LSD core retry/backoff handling rather than its own `agent_end` hook:
|
|
106
|
+
- The Codex provider surfaces friendly usage-limit / rate-limit / auth errors
|
|
107
|
+
- Core `RetryHandler` classifies those failures and calls `markUsageLimitReached(...)`
|
|
108
|
+
- If another `openai-codex` credential is available, LSD automatically rotates and retries the same prompt
|
|
105
109
|
- Rate limit errors (429) → 30s backoff
|
|
106
|
-
- Quota exhausted errors → 30min backoff
|
|
107
|
-
- Auth errors (401) → treated as
|
|
110
|
+
- Quota exhausted / usage limit errors → 30min backoff
|
|
111
|
+
- Auth errors (401) → treated as immediate credential-rotation failures
|
|
112
|
+
|
|
113
|
+
To make that work reliably, the extension reloads the live auth state after every successful sync so the retry handler sees the current credential pool immediately.
|
|
108
114
|
|
|
109
115
|
## Security
|
|
110
116
|
|
|
@@ -63,6 +63,13 @@ function displayAccounts(ctx, accounts) {
|
|
|
63
63
|
});
|
|
64
64
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
65
65
|
}
|
|
66
|
+
async function syncAccountsToAuthAndReload(ctx) {
|
|
67
|
+
const synced = await syncAccountsToAuth(getAllAccounts());
|
|
68
|
+
if (synced) {
|
|
69
|
+
ctx.modelRegistry.authStorage.reload();
|
|
70
|
+
}
|
|
71
|
+
return synced;
|
|
72
|
+
}
|
|
66
73
|
/**
|
|
67
74
|
* Register the /codex command
|
|
68
75
|
*/
|
|
@@ -118,7 +125,7 @@ export function registerCodexCommand(pi) {
|
|
|
118
125
|
disabled: false,
|
|
119
126
|
});
|
|
120
127
|
// Sync to auth.json
|
|
121
|
-
const success = await
|
|
128
|
+
const success = await syncAccountsToAuthAndReload(ctx);
|
|
122
129
|
if (success) {
|
|
123
130
|
ctx.ui.notify(`Added account: ${email || account.accountId}. Synced to auth.json.`, "success");
|
|
124
131
|
}
|
|
@@ -176,7 +183,7 @@ export function registerCodexCommand(pi) {
|
|
|
176
183
|
const confirmed = await ctx.ui.select(`Remove account: ${account.email || account.accountId}?`, ["Yes, remove", "Cancel"], { signal: AbortSignal.timeout(30000) });
|
|
177
184
|
if (confirmed === "Yes, remove") {
|
|
178
185
|
removeAccount(account.id);
|
|
179
|
-
await
|
|
186
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
180
187
|
ctx.ui.notify(`Removed account: ${account.email || account.accountId}`, "success");
|
|
181
188
|
}
|
|
182
189
|
return;
|
|
@@ -200,7 +207,7 @@ export function registerCodexCommand(pi) {
|
|
|
200
207
|
return;
|
|
201
208
|
}
|
|
202
209
|
updateAccount(account.id, { disabled: false, disabledReason: undefined });
|
|
203
|
-
await
|
|
210
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
204
211
|
ctx.ui.notify(`Enabled account: ${account.email || account.accountId}`, "success");
|
|
205
212
|
return;
|
|
206
213
|
}
|
|
@@ -223,7 +230,7 @@ export function registerCodexCommand(pi) {
|
|
|
223
230
|
return;
|
|
224
231
|
}
|
|
225
232
|
updateAccount(account.id, { disabled: true, disabledReason: "manually disabled" });
|
|
226
|
-
await
|
|
233
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
227
234
|
ctx.ui.notify(`Disabled account: ${account.email || account.accountId}`, "success");
|
|
228
235
|
return;
|
|
229
236
|
}
|
|
@@ -234,7 +241,7 @@ export function registerCodexCommand(pi) {
|
|
|
234
241
|
ctx.ui.notify("No account found to import.", "warning");
|
|
235
242
|
return;
|
|
236
243
|
}
|
|
237
|
-
|
|
244
|
+
addAccount({
|
|
238
245
|
email: imported.email,
|
|
239
246
|
accountId: imported.accountId,
|
|
240
247
|
refreshToken: imported.refreshToken,
|
|
@@ -243,7 +250,7 @@ export function registerCodexCommand(pi) {
|
|
|
243
250
|
lastUsed: undefined,
|
|
244
251
|
disabled: false,
|
|
245
252
|
});
|
|
246
|
-
await
|
|
253
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
247
254
|
ctx.ui.notify(`Imported account: ${imported.email || imported.accountId}`, "success");
|
|
248
255
|
return;
|
|
249
256
|
}
|
|
@@ -257,7 +264,7 @@ export function registerCodexCommand(pi) {
|
|
|
257
264
|
for (const acc of imported) {
|
|
258
265
|
addAccount(acc);
|
|
259
266
|
}
|
|
260
|
-
await
|
|
267
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
261
268
|
ctx.ui.notify(`Imported ${imported.length} account(s) from Cockpit Tools`, "success");
|
|
262
269
|
return;
|
|
263
270
|
}
|
|
@@ -278,7 +285,7 @@ export function registerCodexCommand(pi) {
|
|
|
278
285
|
results.failed++;
|
|
279
286
|
}
|
|
280
287
|
}
|
|
281
|
-
await
|
|
288
|
+
await syncAccountsToAuthAndReload(ctx);
|
|
282
289
|
if (results.failed === 0) {
|
|
283
290
|
ctx.ui.notify(`Synced ${results.success} account(s) to auth.json`, "success");
|
|
284
291
|
}
|
|
@@ -10,10 +10,13 @@ import { registerCodexCommand } from "./commands.js";
|
|
|
10
10
|
import { REFRESH_INTERVAL_MS } from "./config.js";
|
|
11
11
|
import { logCodexRotateError } from "./logger.js";
|
|
12
12
|
let refreshTimer = null;
|
|
13
|
+
async function reloadLiveAuthState(ctx) {
|
|
14
|
+
ctx.modelRegistry.authStorage.reload();
|
|
15
|
+
}
|
|
13
16
|
/**
|
|
14
17
|
* Refresh all accounts that need it
|
|
15
18
|
*/
|
|
16
|
-
async function refreshExpiringAccounts() {
|
|
19
|
+
async function refreshExpiringAccounts(ctx) {
|
|
17
20
|
try {
|
|
18
21
|
const accountsNeedingRefresh = getAccountsNeedingRefresh();
|
|
19
22
|
if (accountsNeedingRefresh.length === 0) {
|
|
@@ -41,7 +44,10 @@ async function refreshExpiringAccounts() {
|
|
|
41
44
|
if (successCount > 0) {
|
|
42
45
|
// Sync refreshed accounts to auth.json
|
|
43
46
|
const allAccounts = getAllAccounts();
|
|
44
|
-
await syncAccountsToAuth(allAccounts);
|
|
47
|
+
const synced = await syncAccountsToAuth(allAccounts);
|
|
48
|
+
if (synced) {
|
|
49
|
+
await reloadLiveAuthState(ctx);
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
52
|
if (failCount > 0 && successCount === 0) {
|
|
47
53
|
logCodexRotateError(`Failed to refresh ${failCount} account(s)`);
|
|
@@ -54,12 +60,12 @@ async function refreshExpiringAccounts() {
|
|
|
54
60
|
/**
|
|
55
61
|
* Start the background refresh timer
|
|
56
62
|
*/
|
|
57
|
-
function startRefreshTimer() {
|
|
63
|
+
function startRefreshTimer(ctx) {
|
|
58
64
|
if (refreshTimer) {
|
|
59
65
|
clearInterval(refreshTimer);
|
|
60
66
|
}
|
|
61
67
|
refreshTimer = setInterval(() => {
|
|
62
|
-
void refreshExpiringAccounts();
|
|
68
|
+
void refreshExpiringAccounts(ctx);
|
|
63
69
|
}, REFRESH_INTERVAL_MS);
|
|
64
70
|
}
|
|
65
71
|
/**
|
|
@@ -78,17 +84,20 @@ export default function CodexRotateExtension(pi) {
|
|
|
78
84
|
// Register commands
|
|
79
85
|
registerCodexCommand(pi);
|
|
80
86
|
// Session start hook
|
|
81
|
-
pi.on("session_start", async (_event) => {
|
|
87
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
82
88
|
const accounts = getAllAccounts();
|
|
83
89
|
if (accounts.length === 0) {
|
|
84
90
|
return;
|
|
85
91
|
}
|
|
86
92
|
// Refresh any expiring accounts immediately
|
|
87
|
-
await refreshExpiringAccounts();
|
|
93
|
+
await refreshExpiringAccounts(ctx);
|
|
88
94
|
// Sync to auth.json
|
|
89
|
-
await syncAccountsToAuth(getAllAccounts());
|
|
95
|
+
const synced = await syncAccountsToAuth(getAllAccounts());
|
|
96
|
+
if (synced) {
|
|
97
|
+
await reloadLiveAuthState(ctx);
|
|
98
|
+
}
|
|
90
99
|
// Start background refresh timer
|
|
91
|
-
startRefreshTimer();
|
|
100
|
+
startRefreshTimer(ctx);
|
|
92
101
|
});
|
|
93
102
|
// Session shutdown hook
|
|
94
103
|
pi.on("session_shutdown", () => {
|