oc-codex-multi-auth 6.0.0 → 6.1.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/LICENSE +1 -1
- package/README.md +56 -18
- package/assets/readme-hero.svg +10 -10
- package/config/README.md +1 -1
- package/config/minimal-opencode.json +1 -1
- package/config/opencode-legacy.json +1 -1
- package/config/opencode-modern.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -2525
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +34 -0
- package/dist/lib/accounts/persistence.d.ts.map +1 -0
- package/dist/lib/accounts/persistence.js +153 -0
- package/dist/lib/accounts/persistence.js.map +1 -0
- package/dist/lib/accounts/recovery.d.ts +59 -0
- package/dist/lib/accounts/recovery.d.ts.map +1 -0
- package/dist/lib/accounts/recovery.js +229 -0
- package/dist/lib/accounts/recovery.js.map +1 -0
- package/dist/lib/accounts/rotation.d.ts +40 -0
- package/dist/lib/accounts/rotation.d.ts.map +1 -0
- package/dist/lib/accounts/rotation.js +204 -0
- package/dist/lib/accounts/rotation.js.map +1 -0
- package/dist/lib/accounts/state.d.ts +92 -0
- package/dist/lib/accounts/state.d.ts.map +1 -0
- package/dist/lib/accounts/state.js +366 -0
- package/dist/lib/accounts/state.js.map +1 -0
- package/dist/lib/accounts.d.ts +48 -91
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +121 -699
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +5 -2
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/auth/login-runner.d.ts +32 -0
- package/dist/lib/auth/login-runner.d.ts.map +1 -1
- package/dist/lib/auth/login-runner.js +79 -64
- package/dist/lib/auth/login-runner.js.map +1 -1
- package/dist/lib/auth/server.d.ts.map +1 -1
- package/dist/lib/auth/server.js +3 -8
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +53 -2
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +65 -6
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +77 -32
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/error-sentinels.d.ts +20 -0
- package/dist/lib/error-sentinels.d.ts.map +1 -0
- package/dist/lib/error-sentinels.js +28 -0
- package/dist/lib/error-sentinels.js.map +1 -0
- package/dist/lib/errors.d.ts +100 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +116 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/logger.d.ts +14 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +65 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/oauth-constants.d.ts +18 -0
- package/dist/lib/oauth-constants.d.ts.map +1 -0
- package/dist/lib/oauth-constants.js +18 -0
- package/dist/lib/oauth-constants.js.map +1 -0
- package/dist/lib/oauth-success.d.ts +2 -0
- package/dist/lib/oauth-success.d.ts.map +1 -0
- package/dist/lib/oauth-success.html +711 -711
- package/dist/lib/oauth-success.js +714 -0
- package/dist/lib/oauth-success.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +12 -3
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -1
- package/dist/lib/prompts/opencode-codex.js +3 -2
- package/dist/lib/prompts/opencode-codex.js.map +1 -1
- package/dist/lib/recovery/hook.d.ts +43 -0
- package/dist/lib/recovery/hook.d.ts.map +1 -0
- package/dist/lib/recovery/hook.js +325 -0
- package/dist/lib/recovery/hook.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +1 -0
- package/dist/lib/recovery/index.d.ts.map +1 -1
- package/dist/lib/recovery/index.js +1 -0
- package/dist/lib/recovery/index.js.map +1 -1
- package/dist/lib/recovery/storage.d.ts.map +1 -1
- package/dist/lib/recovery/storage.js +5 -1
- package/dist/lib/recovery/storage.js.map +1 -1
- package/dist/lib/recovery.d.ts +15 -30
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +14 -312
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +1 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +58 -10
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +8 -2
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/runtime.d.ts +101 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +77 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/schemas.d.ts +164 -1
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +251 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/atomic-write.d.ts +31 -0
- package/dist/lib/storage/atomic-write.d.ts.map +1 -0
- package/dist/lib/storage/atomic-write.js +75 -0
- package/dist/lib/storage/atomic-write.js.map +1 -0
- package/dist/lib/storage/backup.d.ts +13 -0
- package/dist/lib/storage/backup.d.ts.map +1 -0
- package/dist/lib/storage/backup.js +59 -0
- package/dist/lib/storage/backup.js.map +1 -0
- package/dist/lib/storage/errors.d.ts +17 -0
- package/dist/lib/storage/errors.d.ts.map +1 -0
- package/dist/lib/storage/errors.js +38 -0
- package/dist/lib/storage/errors.js.map +1 -0
- package/dist/lib/storage/export-import.d.ts +84 -0
- package/dist/lib/storage/export-import.d.ts.map +1 -0
- package/dist/lib/storage/export-import.js +222 -0
- package/dist/lib/storage/export-import.js.map +1 -0
- package/dist/lib/storage/flagged.d.ts +29 -0
- package/dist/lib/storage/flagged.d.ts.map +1 -0
- package/dist/lib/storage/flagged.js +207 -0
- package/dist/lib/storage/flagged.js.map +1 -0
- package/dist/lib/storage/identity.d.ts +72 -0
- package/dist/lib/storage/identity.d.ts.map +1 -0
- package/dist/lib/storage/identity.js +230 -0
- package/dist/lib/storage/identity.js.map +1 -0
- package/dist/lib/storage/keychain.d.ts +146 -0
- package/dist/lib/storage/keychain.d.ts.map +1 -0
- package/dist/lib/storage/keychain.js +278 -0
- package/dist/lib/storage/keychain.js.map +1 -0
- package/dist/lib/storage/load-save.d.ts +61 -0
- package/dist/lib/storage/load-save.d.ts.map +1 -0
- package/dist/lib/storage/load-save.js +606 -0
- package/dist/lib/storage/load-save.js.map +1 -0
- package/dist/lib/storage/migrations.d.ts +35 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js +37 -0
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage/normalize.d.ts +22 -0
- package/dist/lib/storage/normalize.d.ts.map +1 -0
- package/dist/lib/storage/normalize.js +114 -0
- package/dist/lib/storage/normalize.js.map +1 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +2 -1
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage/state.d.ts +46 -0
- package/dist/lib/storage/state.d.ts.map +1 -0
- package/dist/lib/storage/state.js +100 -0
- package/dist/lib/storage/state.js.map +1 -0
- package/dist/lib/storage/worktree-lock.d.ts +79 -0
- package/dist/lib/storage/worktree-lock.d.ts.map +1 -0
- package/dist/lib/storage/worktree-lock.js +265 -0
- package/dist/lib/storage/worktree-lock.js.map +1 -0
- package/dist/lib/storage.d.ts +21 -155
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +24 -1167
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/_shared.d.ts +16 -0
- package/dist/lib/tools/_shared.d.ts.map +1 -0
- package/dist/lib/tools/_shared.js +21 -0
- package/dist/lib/tools/_shared.js.map +1 -0
- package/dist/lib/tools/codex-dashboard.d.ts +8 -0
- package/dist/lib/tools/codex-dashboard.d.ts.map +1 -0
- package/dist/lib/tools/codex-dashboard.js +176 -0
- package/dist/lib/tools/codex-dashboard.js.map +1 -0
- package/dist/lib/tools/codex-diag.d.ts +19 -0
- package/dist/lib/tools/codex-diag.d.ts.map +1 -0
- package/dist/lib/tools/codex-diag.js +139 -0
- package/dist/lib/tools/codex-diag.js.map +1 -0
- package/dist/lib/tools/codex-diff.d.ts +44 -0
- package/dist/lib/tools/codex-diff.d.ts.map +1 -0
- package/dist/lib/tools/codex-diff.js +284 -0
- package/dist/lib/tools/codex-diff.js.map +1 -0
- package/dist/lib/tools/codex-doctor.d.ts +8 -0
- package/dist/lib/tools/codex-doctor.d.ts.map +1 -0
- package/dist/lib/tools/codex-doctor.js +284 -0
- package/dist/lib/tools/codex-doctor.js.map +1 -0
- package/dist/lib/tools/codex-export.d.ts +8 -0
- package/dist/lib/tools/codex-export.d.ts.map +1 -0
- package/dist/lib/tools/codex-export.js +63 -0
- package/dist/lib/tools/codex-export.js.map +1 -0
- package/dist/lib/tools/codex-health.d.ts +8 -0
- package/dist/lib/tools/codex-health.d.ts.map +1 -0
- package/dist/lib/tools/codex-health.js +124 -0
- package/dist/lib/tools/codex-health.js.map +1 -0
- package/dist/lib/tools/codex-help.d.ts +8 -0
- package/dist/lib/tools/codex-help.d.ts.map +1 -0
- package/dist/lib/tools/codex-help.js +127 -0
- package/dist/lib/tools/codex-help.js.map +1 -0
- package/dist/lib/tools/codex-import.d.ts +8 -0
- package/dist/lib/tools/codex-import.d.ts.map +1 -0
- package/dist/lib/tools/codex-import.js +97 -0
- package/dist/lib/tools/codex-import.js.map +1 -0
- package/dist/lib/tools/codex-keychain.d.ts +36 -0
- package/dist/lib/tools/codex-keychain.d.ts.map +1 -0
- package/dist/lib/tools/codex-keychain.js +289 -0
- package/dist/lib/tools/codex-keychain.js.map +1 -0
- package/dist/lib/tools/codex-label.d.ts +8 -0
- package/dist/lib/tools/codex-label.d.ts.map +1 -0
- package/dist/lib/tools/codex-label.js +143 -0
- package/dist/lib/tools/codex-label.js.map +1 -0
- package/dist/lib/tools/codex-limits.d.ts +8 -0
- package/dist/lib/tools/codex-limits.d.ts.map +1 -0
- package/dist/lib/tools/codex-limits.js +497 -0
- package/dist/lib/tools/codex-limits.js.map +1 -0
- package/dist/lib/tools/codex-list.d.ts +8 -0
- package/dist/lib/tools/codex-list.d.ts.map +1 -0
- package/dist/lib/tools/codex-list.js +260 -0
- package/dist/lib/tools/codex-list.js.map +1 -0
- package/dist/lib/tools/codex-metrics.d.ts +8 -0
- package/dist/lib/tools/codex-metrics.d.ts.map +1 -0
- package/dist/lib/tools/codex-metrics.js +180 -0
- package/dist/lib/tools/codex-metrics.js.map +1 -0
- package/dist/lib/tools/codex-next.d.ts +8 -0
- package/dist/lib/tools/codex-next.d.ts.map +1 -0
- package/dist/lib/tools/codex-next.js +54 -0
- package/dist/lib/tools/codex-next.js.map +1 -0
- package/dist/lib/tools/codex-note.d.ts +8 -0
- package/dist/lib/tools/codex-note.d.ts.map +1 -0
- package/dist/lib/tools/codex-note.js +79 -0
- package/dist/lib/tools/codex-note.js.map +1 -0
- package/dist/lib/tools/codex-refresh.d.ts +8 -0
- package/dist/lib/tools/codex-refresh.d.ts.map +1 -0
- package/dist/lib/tools/codex-refresh.js +78 -0
- package/dist/lib/tools/codex-refresh.js.map +1 -0
- package/dist/lib/tools/codex-remove.d.ts +8 -0
- package/dist/lib/tools/codex-remove.d.ts.map +1 -0
- package/dist/lib/tools/codex-remove.js +184 -0
- package/dist/lib/tools/codex-remove.js.map +1 -0
- package/dist/lib/tools/codex-setup.d.ts +8 -0
- package/dist/lib/tools/codex-setup.d.ts.map +1 -0
- package/dist/lib/tools/codex-setup.js +26 -0
- package/dist/lib/tools/codex-setup.js.map +1 -0
- package/dist/lib/tools/codex-status.d.ts +8 -0
- package/dist/lib/tools/codex-status.d.ts.map +1 -0
- package/dist/lib/tools/codex-status.js +263 -0
- package/dist/lib/tools/codex-status.js.map +1 -0
- package/dist/lib/tools/codex-switch.d.ts +8 -0
- package/dist/lib/tools/codex-switch.d.ts.map +1 -0
- package/dist/lib/tools/codex-switch.js +122 -0
- package/dist/lib/tools/codex-switch.js.map +1 -0
- package/dist/lib/tools/codex-tag.d.ts +8 -0
- package/dist/lib/tools/codex-tag.d.ts.map +1 -0
- package/dist/lib/tools/codex-tag.js +101 -0
- package/dist/lib/tools/codex-tag.js.map +1 -0
- package/dist/lib/tools/index.d.ts +143 -0
- package/dist/lib/tools/index.d.ts.map +1 -0
- package/dist/lib/tools/index.js +67 -0
- package/dist/lib/tools/index.js.map +1 -0
- package/dist/lib/ui/format.d.ts.map +1 -1
- package/dist/lib/ui/format.js +5 -0
- package/dist/lib/ui/format.js.map +1 -1
- package/dist/lib/ui/runtime.d.ts +10 -0
- package/dist/lib/ui/runtime.d.ts.map +1 -1
- package/dist/lib/ui/runtime.js +16 -11
- package/dist/lib/ui/runtime.js.map +1 -1
- package/dist/lib/ui/select.d.ts.map +1 -1
- package/dist/lib/ui/select.js +12 -3
- package/dist/lib/ui/select.js.map +1 -1
- package/dist/lib/ui/theme.d.ts +19 -0
- package/dist/lib/ui/theme.d.ts.map +1 -1
- package/dist/lib/ui/theme.js +27 -0
- package/dist/lib/ui/theme.js.map +1 -1
- package/package.json +24 -16
- package/scripts/copy-oauth-success.js +16 -6
- package/scripts/install-oc-codex-multi-auth-core.js +462 -381
- package/scripts/install-oc-codex-multi-auth.js +15 -15
- package/scripts/test-all-models.sh +1 -1
- package/dist/lib/audit.d.ts +0 -45
- package/dist/lib/audit.d.ts.map +0 -1
- package/dist/lib/audit.js +0 -154
- package/dist/lib/audit.js.map +0 -1
- package/dist/lib/auth-rate-limit.d.ts +0 -20
- package/dist/lib/auth-rate-limit.d.ts.map +0 -1
- package/dist/lib/auth-rate-limit.js +0 -91
- package/dist/lib/auth-rate-limit.js.map +0 -1
- package/dist/lib/runtime-contracts.d.ts +0 -16
- package/dist/lib/runtime-contracts.d.ts.map +0 -1
- package/dist/lib/runtime-contracts.js +0 -24
- package/dist/lib/runtime-contracts.js.map +0 -1
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account storage load/save pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. This module owns:
|
|
5
|
+
* - the `.gitignore` side-effect when writing into a project repo,
|
|
6
|
+
* - the legacy project + global storage migrations triggered on ENOENT,
|
|
7
|
+
* - the project-global fallback seed flow,
|
|
8
|
+
* - the atomic write (temp file + rename + EEMPTY guard),
|
|
9
|
+
* - and the `withAccountStorageTransaction` read-modify-write primitive
|
|
10
|
+
* that every mutating caller above the storage layer uses.
|
|
11
|
+
*
|
|
12
|
+
* The error-handling contract is subtle and load-bearing: forward-compat
|
|
13
|
+
* (`UNSUPPORTED_SCHEMA_VERSION`) and unknown-V2 failures MUST reach the
|
|
14
|
+
* caller. Swallowing either would overwrite future-schema credentials or
|
|
15
|
+
* silently discard a user's V2 file, which is exactly the class of bug the
|
|
16
|
+
* audit flagged.
|
|
17
|
+
*/
|
|
18
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { ACCOUNTS_FILE_NAME, LEGACY_ACCOUNTS_FILE_NAME } from "../constants.js";
|
|
21
|
+
import { createLogger } from "../logger.js";
|
|
22
|
+
import { AnyAccountStorageSchema, getValidationErrors } from "../schemas.js";
|
|
23
|
+
import { renameWithWindowsRetry } from "./atomic-write.js";
|
|
24
|
+
import { formatStorageErrorHint, StorageError } from "./errors.js";
|
|
25
|
+
import { normalizeAccountStorage } from "./normalize.js";
|
|
26
|
+
import { getConfigDir } from "./paths.js";
|
|
27
|
+
import { getCurrentLegacyProjectStoragePath, getCurrentProjectRoot, getCurrentProjectStorageKey, getCurrentStoragePath, getStoragePath, withStorageLock, } from "./state.js";
|
|
28
|
+
import { buildV2RecoveryHint, UNKNOWN_V2_FORMAT_CODE, } from "./migrations.js";
|
|
29
|
+
import { acquireOrDetectLock } from "./worktree-lock.js";
|
|
30
|
+
import { isKeychainOptInEnabled, readFromKeychain, writeToKeychain, deleteFromKeychain, } from "./keychain.js";
|
|
31
|
+
import os from "node:os";
|
|
32
|
+
const log = createLogger("storage");
|
|
33
|
+
/**
|
|
34
|
+
* Probes the worktree lock for the currently active storage path and surfaces
|
|
35
|
+
* any foreign live lock as a non-fatal warning. The lock check is advisory:
|
|
36
|
+
* Phase 4 F2 deliberately chose warn-over-block so a user with two legitimate
|
|
37
|
+
* OpenCode sessions on the same project (separate worktrees, IDE + CLI) is
|
|
38
|
+
* never stranded. The warning still carries enough detail (pid, host, cwd)
|
|
39
|
+
* for the user to reconcile state manually if a rotation was lost to a race.
|
|
40
|
+
*
|
|
41
|
+
* Failure modes:
|
|
42
|
+
* - Storage path is not resolvable (no project, no global dir set): skip
|
|
43
|
+
* silently, the actual storage call will surface the real error.
|
|
44
|
+
* - Lock file unreadable (disk full, EACCES): log at debug so it cannot
|
|
45
|
+
* drown the normal "collision detected" warning, then continue. A broken
|
|
46
|
+
* sidecar must never gate auth-critical reads/writes.
|
|
47
|
+
*/
|
|
48
|
+
async function checkWorktreeLockForCurrentStorage(operation) {
|
|
49
|
+
let path;
|
|
50
|
+
try {
|
|
51
|
+
path = getStoragePath();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
log.debug("Skipping worktree lock check: storage path unavailable", {
|
|
55
|
+
error: String(error),
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await acquireOrDetectLock(path);
|
|
61
|
+
if (!result.acquired && result.foreign) {
|
|
62
|
+
log.warn("Multi-worktree collision detected on account storage", {
|
|
63
|
+
operation,
|
|
64
|
+
storagePath: path,
|
|
65
|
+
foreignPid: result.foreign.pid,
|
|
66
|
+
foreignHost: result.foreign.hostname,
|
|
67
|
+
foreignCwd: result.foreign.cwd,
|
|
68
|
+
foreignLastActive: result.foreign.lastActive,
|
|
69
|
+
ourPid: process.pid,
|
|
70
|
+
ourHost: os.hostname(),
|
|
71
|
+
ourCwd: process.cwd(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
log.debug("Worktree lock probe failed", {
|
|
77
|
+
operation,
|
|
78
|
+
storagePath: path,
|
|
79
|
+
error: String(error),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function ensureGitignore(storagePath) {
|
|
84
|
+
if (!getCurrentStoragePath())
|
|
85
|
+
return;
|
|
86
|
+
const configDir = dirname(storagePath);
|
|
87
|
+
const inferredProjectRoot = dirname(configDir);
|
|
88
|
+
const candidateRoots = [getCurrentProjectRoot(), inferredProjectRoot].filter((root) => typeof root === "string" && root.length > 0);
|
|
89
|
+
const projectRoot = candidateRoots.find((root) => existsSync(join(root, ".git")));
|
|
90
|
+
if (!projectRoot)
|
|
91
|
+
return;
|
|
92
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
93
|
+
try {
|
|
94
|
+
let content = "";
|
|
95
|
+
if (existsSync(gitignorePath)) {
|
|
96
|
+
content = await fs.readFile(gitignorePath, "utf-8");
|
|
97
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
98
|
+
if (lines.includes(".opencode") || lines.includes(".opencode/") || lines.includes("/.opencode") || lines.includes("/.opencode/")) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const newContent = content.endsWith("\n") || content === "" ? content : content + "\n";
|
|
103
|
+
await fs.writeFile(gitignorePath, newContent + ".opencode/\n", "utf-8");
|
|
104
|
+
log.debug("Added .opencode to .gitignore", { path: gitignorePath });
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
log.warn("Failed to update .gitignore", { error: String(error) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function migrateStorageFileIfNeeded(legacyPath, nextPath, persist, label) {
|
|
111
|
+
if (!legacyPath || legacyPath === nextPath || !existsSync(legacyPath)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const legacyContent = await fs.readFile(legacyPath, "utf-8");
|
|
116
|
+
const legacyData = JSON.parse(legacyContent);
|
|
117
|
+
const normalized = normalizeAccountStorage(legacyData, legacyPath);
|
|
118
|
+
if (!normalized)
|
|
119
|
+
return null;
|
|
120
|
+
await persist(normalized);
|
|
121
|
+
try {
|
|
122
|
+
await fs.unlink(legacyPath);
|
|
123
|
+
log.info(`Removed legacy ${label} after migration`, { path: legacyPath });
|
|
124
|
+
}
|
|
125
|
+
catch (unlinkError) {
|
|
126
|
+
const code = unlinkError.code;
|
|
127
|
+
if (code !== "ENOENT") {
|
|
128
|
+
log.warn(`Failed to remove legacy ${label} after migration`, {
|
|
129
|
+
path: legacyPath,
|
|
130
|
+
error: String(unlinkError),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
log.info(`Migrated legacy ${label}`, {
|
|
135
|
+
from: legacyPath,
|
|
136
|
+
to: nextPath,
|
|
137
|
+
accounts: normalized.accounts.length,
|
|
138
|
+
});
|
|
139
|
+
return normalized;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// Forward-compat failures should not be masked as migration warnings.
|
|
143
|
+
if (error instanceof StorageError && error.code === "UNSUPPORTED_SCHEMA_VERSION") {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
log.warn(`Failed to migrate legacy ${label}`, {
|
|
147
|
+
from: legacyPath,
|
|
148
|
+
to: nextPath,
|
|
149
|
+
error: String(error),
|
|
150
|
+
});
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function migrateLegacyProjectStorageIfNeeded(persist = saveAccounts) {
|
|
155
|
+
return migrateStorageFileIfNeeded(getCurrentLegacyProjectStoragePath(), getStoragePath(), persist, "project account storage");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolves the global (non-project) account storage path.
|
|
159
|
+
*/
|
|
160
|
+
function getGlobalAccountsStoragePath() {
|
|
161
|
+
return join(getConfigDir(), ACCOUNTS_FILE_NAME);
|
|
162
|
+
}
|
|
163
|
+
function getLegacyGlobalAccountsStoragePath() {
|
|
164
|
+
return join(getConfigDir(), LEGACY_ACCOUNTS_FILE_NAME);
|
|
165
|
+
}
|
|
166
|
+
async function migrateLegacyGlobalStorageIfNeeded() {
|
|
167
|
+
const nextPath = getGlobalAccountsStoragePath();
|
|
168
|
+
const persistGlobalStorage = async (storage) => {
|
|
169
|
+
await writeAccountsToPathUnlocked(nextPath, storage);
|
|
170
|
+
};
|
|
171
|
+
return migrateStorageFileIfNeeded(getLegacyGlobalAccountsStoragePath(), nextPath, persistGlobalStorage, "global account storage");
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Returns true when project-scoped storage is active and a global fallback is meaningful.
|
|
175
|
+
*/
|
|
176
|
+
function shouldUseProjectGlobalFallback() {
|
|
177
|
+
return Boolean(getCurrentStoragePath() && getCurrentProjectRoot());
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Loads account data from global storage as a fallback when project storage is missing.
|
|
181
|
+
* Returns null for missing/unusable global storage and never throws to callers.
|
|
182
|
+
*/
|
|
183
|
+
async function loadGlobalAccountsFallback() {
|
|
184
|
+
const currentStoragePath = getCurrentStoragePath();
|
|
185
|
+
if (!shouldUseProjectGlobalFallback() || !currentStoragePath) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const migrated = await migrateLegacyGlobalStorageIfNeeded();
|
|
189
|
+
if (migrated) {
|
|
190
|
+
return migrated;
|
|
191
|
+
}
|
|
192
|
+
const globalStoragePath = getGlobalAccountsStoragePath();
|
|
193
|
+
if (globalStoragePath === currentStoragePath) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const content = await fs.readFile(globalStoragePath, "utf-8");
|
|
198
|
+
const data = JSON.parse(content);
|
|
199
|
+
const schemaErrors = getValidationErrors(AnyAccountStorageSchema, data);
|
|
200
|
+
if (schemaErrors.length > 0) {
|
|
201
|
+
log.warn("Global account storage schema validation warnings", {
|
|
202
|
+
path: globalStoragePath,
|
|
203
|
+
errors: schemaErrors.slice(0, 5),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const normalized = normalizeAccountStorage(data, globalStoragePath);
|
|
207
|
+
if (!normalized)
|
|
208
|
+
return null;
|
|
209
|
+
log.info("Loaded global account storage as project fallback", {
|
|
210
|
+
from: globalStoragePath,
|
|
211
|
+
to: currentStoragePath,
|
|
212
|
+
accounts: normalized.accounts.length,
|
|
213
|
+
});
|
|
214
|
+
return normalized;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
// Propagate forward-compat failures so the caller can surface them to the
|
|
218
|
+
// user instead of silently falling back to an empty global pool.
|
|
219
|
+
if (error instanceof StorageError && error.code === "UNSUPPORTED_SCHEMA_VERSION") {
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
const code = error.code;
|
|
223
|
+
if (code !== "ENOENT") {
|
|
224
|
+
log.warn("Failed to load global fallback account storage", {
|
|
225
|
+
from: globalStoragePath,
|
|
226
|
+
to: currentStoragePath,
|
|
227
|
+
error: String(error),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Core account-loading routine shared by normal reads and transactional storage handlers.
|
|
235
|
+
* Handles schema normalization, legacy migration, and optional fallback seeding.
|
|
236
|
+
*/
|
|
237
|
+
async function loadAccountsInternal(persistMigration) {
|
|
238
|
+
// Advisory: surface multi-worktree collisions before the read but never
|
|
239
|
+
// block. Must come before the try/catch so a genuine storage error still
|
|
240
|
+
// takes precedence over any lock-related log output below.
|
|
241
|
+
await checkWorktreeLockForCurrentStorage("load");
|
|
242
|
+
// Opt-in keychain backend. When `CODEX_KEYCHAIN=1` is set, the keychain
|
|
243
|
+
// holds the authoritative V3 JSON blob and the on-disk file (if any) is
|
|
244
|
+
// only kept as a post-migration rollback artefact. If the keychain lookup
|
|
245
|
+
// fails for any reason (native module missing, keychain locked, no entry
|
|
246
|
+
// yet) we fall through to the existing JSON load path so the plugin never
|
|
247
|
+
// silently loses credentials. This preserves the default-off contract
|
|
248
|
+
// documented in docs/audits/13-phased-roadmap.md#phase-4-f1.
|
|
249
|
+
if (isKeychainOptInEnabled()) {
|
|
250
|
+
const projectKey = getCurrentProjectStorageKey();
|
|
251
|
+
try {
|
|
252
|
+
const blob = await readFromKeychain(projectKey);
|
|
253
|
+
if (blob !== null) {
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(blob);
|
|
256
|
+
const normalized = normalizeAccountStorage(parsed, "<keychain>");
|
|
257
|
+
if (normalized)
|
|
258
|
+
return normalized;
|
|
259
|
+
}
|
|
260
|
+
catch (parseErr) {
|
|
261
|
+
// Corrupt keychain entry: log but fall through to JSON so the
|
|
262
|
+
// user can recover from their on-disk backup.
|
|
263
|
+
log.warn("keychain: stored payload failed to parse; falling back to JSON", {
|
|
264
|
+
error: String(parseErr),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
log.info("keychain: no entry found; falling back to JSON read");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
log.warn("keychain: read failed; falling back to JSON", {
|
|
274
|
+
error: String(err),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const path = getStoragePath();
|
|
280
|
+
const content = await fs.readFile(path, "utf-8");
|
|
281
|
+
const data = JSON.parse(content);
|
|
282
|
+
const schemaErrors = getValidationErrors(AnyAccountStorageSchema, data);
|
|
283
|
+
if (schemaErrors.length > 0) {
|
|
284
|
+
log.warn("Account storage schema validation warnings", { errors: schemaErrors.slice(0, 5) });
|
|
285
|
+
}
|
|
286
|
+
const normalized = normalizeAccountStorage(data, path);
|
|
287
|
+
const storedVersion = data && typeof data === "object" && !Array.isArray(data)
|
|
288
|
+
? data.version
|
|
289
|
+
: undefined;
|
|
290
|
+
if (normalized && storedVersion !== normalized.version) {
|
|
291
|
+
log.info("Migrating account storage to v3", { from: storedVersion, to: normalized.version });
|
|
292
|
+
if (persistMigration) {
|
|
293
|
+
try {
|
|
294
|
+
await persistMigration(normalized);
|
|
295
|
+
}
|
|
296
|
+
catch (saveError) {
|
|
297
|
+
log.warn("Failed to persist migrated storage", { error: String(saveError) });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return normalized;
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
// Forward-compat failures must reach the caller instead of being silently
|
|
305
|
+
// downgraded to an empty load, which would clobber the user's future-format
|
|
306
|
+
// credentials on the next save.
|
|
307
|
+
if (error instanceof StorageError && error.code === "UNSUPPORTED_SCHEMA_VERSION") {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
// Unknown-V2 detection must NOT be silently dropped: the catch below
|
|
311
|
+
// swallows generic errors by design (keeps an unreadable file from
|
|
312
|
+
// crashing the whole plugin), but V2 is a specific, recoverable case
|
|
313
|
+
// where the user needs to know their credentials were quarantined.
|
|
314
|
+
// Re-throw so the UI/CLI layer can render the recovery hint.
|
|
315
|
+
if (error instanceof StorageError && error.code === UNKNOWN_V2_FORMAT_CODE) {
|
|
316
|
+
// Annotate with the concrete storage path that triggered the reject
|
|
317
|
+
// so the recovery hint points at the real file.
|
|
318
|
+
const concretePath = (() => {
|
|
319
|
+
try {
|
|
320
|
+
return getStoragePath();
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
})();
|
|
326
|
+
if (concretePath) {
|
|
327
|
+
throw new StorageError(error.message, UNKNOWN_V2_FORMAT_CODE, concretePath, buildV2RecoveryHint(concretePath), error);
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
const code = error.code;
|
|
332
|
+
if (code === "ENOENT") {
|
|
333
|
+
const migrated = persistMigration
|
|
334
|
+
? await migrateLegacyProjectStorageIfNeeded(persistMigration)
|
|
335
|
+
: null;
|
|
336
|
+
if (migrated)
|
|
337
|
+
return migrated;
|
|
338
|
+
if (!shouldUseProjectGlobalFallback()) {
|
|
339
|
+
const migratedGlobal = persistMigration
|
|
340
|
+
? await migrateLegacyGlobalStorageIfNeeded()
|
|
341
|
+
: null;
|
|
342
|
+
if (migratedGlobal)
|
|
343
|
+
return migratedGlobal;
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const globalFallback = await loadGlobalAccountsFallback();
|
|
347
|
+
if (!globalFallback)
|
|
348
|
+
return null;
|
|
349
|
+
if (persistMigration) {
|
|
350
|
+
const seedPath = getStoragePath();
|
|
351
|
+
try {
|
|
352
|
+
await fs.access(seedPath);
|
|
353
|
+
return globalFallback;
|
|
354
|
+
}
|
|
355
|
+
catch (accessError) {
|
|
356
|
+
const accessCode = accessError.code;
|
|
357
|
+
if (accessCode !== "ENOENT") {
|
|
358
|
+
log.warn("Failed to inspect project seed path before fallback seeding", {
|
|
359
|
+
path: seedPath,
|
|
360
|
+
error: String(accessError),
|
|
361
|
+
});
|
|
362
|
+
return globalFallback;
|
|
363
|
+
}
|
|
364
|
+
// File is missing; proceed with seed write.
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
await persistMigration(globalFallback);
|
|
368
|
+
log.info("Seeded project account storage from global fallback", {
|
|
369
|
+
path: seedPath,
|
|
370
|
+
accounts: globalFallback.accounts.length,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (persistError) {
|
|
374
|
+
log.warn("Failed to seed project storage from global fallback", {
|
|
375
|
+
path: seedPath,
|
|
376
|
+
error: String(persistError),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return globalFallback;
|
|
381
|
+
}
|
|
382
|
+
log.error("Failed to load account storage", { error: String(error) });
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Writes account storage without acquiring the outer storage mutex.
|
|
388
|
+
* Callers must already be inside withStorageLock when using this helper directly.
|
|
389
|
+
*/
|
|
390
|
+
async function writeAccountsToPathUnlocked(path, storage) {
|
|
391
|
+
const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
392
|
+
const tempPath = `${path}.${uniqueSuffix}.tmp`;
|
|
393
|
+
try {
|
|
394
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
395
|
+
await ensureGitignore(path);
|
|
396
|
+
// Normalize before persisting so every write path enforces dedup semantics
|
|
397
|
+
// (exact identity dedupe plus legacy email dedupe for identity-less records).
|
|
398
|
+
const normalizedStorage = normalizeAccountStorage(storage) ?? storage;
|
|
399
|
+
const content = JSON.stringify(normalizedStorage, null, 2);
|
|
400
|
+
await fs.writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
|
|
401
|
+
const stats = await fs.stat(tempPath);
|
|
402
|
+
if (stats.size === 0) {
|
|
403
|
+
const emptyError = Object.assign(new Error("File written but size is 0"), { code: "EEMPTY" });
|
|
404
|
+
throw emptyError;
|
|
405
|
+
}
|
|
406
|
+
await renameWithWindowsRetry(tempPath, path);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
try {
|
|
410
|
+
await fs.unlink(tempPath);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Ignore cleanup failure.
|
|
414
|
+
}
|
|
415
|
+
const err = error;
|
|
416
|
+
const code = err?.code || "UNKNOWN";
|
|
417
|
+
const hint = formatStorageErrorHint(error, path);
|
|
418
|
+
log.error("Failed to save accounts", {
|
|
419
|
+
path,
|
|
420
|
+
code,
|
|
421
|
+
message: err?.message,
|
|
422
|
+
hint,
|
|
423
|
+
});
|
|
424
|
+
throw new StorageError(`Failed to save accounts: ${err?.message || "Unknown error"}`, code, path, hint, err instanceof Error ? err : undefined);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Post-keychain-write migration helper: if a legacy on-disk JSON file still
|
|
429
|
+
* exists for the current storage path, rename it with a timestamped
|
|
430
|
+
* `.migrated-to-keychain.<ts>` suffix instead of deleting it. Preserving the
|
|
431
|
+
* original file as a rollback artefact is load-bearing: it is the user's
|
|
432
|
+
* explicit escape hatch if the keychain backend turns out to be unreliable
|
|
433
|
+
* on their platform.
|
|
434
|
+
*
|
|
435
|
+
* Atomicity across a partial migration window (F1 post-merge HIGH finding):
|
|
436
|
+
* if the rename fails (EACCES, EBUSY on Windows, disk full, parent dir
|
|
437
|
+
* permission drift) the file at `path` would otherwise hold a stale-but-valid
|
|
438
|
+
* V3 blob while the keychain holds the authoritative fresh blob. This is
|
|
439
|
+
* safe while the opt-in is on (keychain wins at load time) but silently
|
|
440
|
+
* resurrects stale credentials if the user later unsets `CODEX_KEYCHAIN`.
|
|
441
|
+
* We resolve this by overwriting the file with the fresh normalized blob
|
|
442
|
+
* when the rename fails so both sides agree, at the cost of losing that
|
|
443
|
+
* one rollback artefact. This matches the "rollback invariant" documented
|
|
444
|
+
* in the F1 post-merge review (option (a)).
|
|
445
|
+
*/
|
|
446
|
+
async function migrateOnDiskJsonToKeychainBackup(path, freshStorage) {
|
|
447
|
+
try {
|
|
448
|
+
await fs.access(path);
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
return; // No legacy file to migrate.
|
|
452
|
+
}
|
|
453
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
454
|
+
const backup = `${path}.migrated-to-keychain.${timestamp}`;
|
|
455
|
+
try {
|
|
456
|
+
await fs.rename(path, backup);
|
|
457
|
+
// Re-apply 0o600 after rename (F1 post-merge LOW finding). POSIX
|
|
458
|
+
// preserves mode across a rename in-place, but if the filesystem layer
|
|
459
|
+
// or a prior process ever changed the mode (cp from a world-readable
|
|
460
|
+
// source, umask drift on a foreign mount, manual `touch`) the backup
|
|
461
|
+
// could end up group/world-readable. Re-chmod is a cheap belt-and-
|
|
462
|
+
// braces guard. Windows ignores POSIX mode bits, so skip there.
|
|
463
|
+
if (process.platform !== "win32") {
|
|
464
|
+
try {
|
|
465
|
+
await fs.chmod(backup, 0o600);
|
|
466
|
+
}
|
|
467
|
+
catch (chmodErr) {
|
|
468
|
+
log.warn("keychain: failed to chmod backup to 0o600 after rename", {
|
|
469
|
+
backup,
|
|
470
|
+
error: String(chmodErr),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
log.info("keychain: migrated on-disk JSON to keychain; original preserved for rollback", {
|
|
475
|
+
from: path,
|
|
476
|
+
backup,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
log.warn("keychain: failed to rename on-disk JSON after successful keychain write; overwriting on-disk copy with fresh blob to prevent stale-rollback-on-opt-out", {
|
|
481
|
+
path,
|
|
482
|
+
error: String(err),
|
|
483
|
+
});
|
|
484
|
+
try {
|
|
485
|
+
await writeAccountsToPathUnlocked(path, freshStorage);
|
|
486
|
+
}
|
|
487
|
+
catch (writeErr) {
|
|
488
|
+
// Last-resort: on-disk refresh also failed. The keychain still
|
|
489
|
+
// holds the authoritative blob so current operation succeeds, but
|
|
490
|
+
// a subsequent opt-in toggle off would now surface the stale
|
|
491
|
+
// file. Log at error so the operator can reconcile manually.
|
|
492
|
+
log.error("keychain: failed to refresh stale on-disk JSON after rename failure; opt-in toggle off may surface stale credentials", {
|
|
493
|
+
path,
|
|
494
|
+
error: String(writeErr),
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function saveAccountsUnlocked(storage) {
|
|
500
|
+
// Refresh our lock (or surface a collision) on every write. This also
|
|
501
|
+
// bumps `lastActive`, which is the stale-detection timestamp read by
|
|
502
|
+
// other worktrees on their next acquire.
|
|
503
|
+
await checkWorktreeLockForCurrentStorage("save");
|
|
504
|
+
if (isKeychainOptInEnabled()) {
|
|
505
|
+
// Normalize before serializing so the keychain receives the same shape
|
|
506
|
+
// the JSON backend would have written. Using the same JSON format keeps
|
|
507
|
+
// migration and rollback symmetric: a rolled-back JSON file is valid
|
|
508
|
+
// input for a future opt-in migration in either direction.
|
|
509
|
+
const normalizedStorage = normalizeAccountStorage(storage) ?? storage;
|
|
510
|
+
const blob = JSON.stringify(normalizedStorage, null, 2);
|
|
511
|
+
const projectKey = getCurrentProjectStorageKey();
|
|
512
|
+
const result = await writeToKeychain(projectKey, blob);
|
|
513
|
+
if (result.ok) {
|
|
514
|
+
await migrateOnDiskJsonToKeychainBackup(getStoragePath(), normalizedStorage);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
log.warn("keychain: write failed; falling back to JSON for this save", {
|
|
518
|
+
error: result.error,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
await writeAccountsToPathUnlocked(getStoragePath(), storage);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Loads OAuth accounts from disk storage.
|
|
525
|
+
* Automatically migrates v1 storage to v3 format if needed.
|
|
526
|
+
* @returns AccountStorageV3 if file exists and is valid, null otherwise
|
|
527
|
+
* @throws StorageError (code `UNSUPPORTED_SCHEMA_VERSION`) when the on-disk
|
|
528
|
+
* `version` field is greater than the newest format this plugin understands.
|
|
529
|
+
* Surfacing the error stops a downgraded plugin from overwriting the user's
|
|
530
|
+
* future-schema credentials with a stale or empty payload.
|
|
531
|
+
*/
|
|
532
|
+
export async function loadAccounts() {
|
|
533
|
+
return withStorageLock(async () => loadAccountsInternal(saveAccountsUnlocked));
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Executes a read-modify-write transaction under the storage lock and exposes
|
|
537
|
+
* an unlocked persist callback so nested save operations do not deadlock.
|
|
538
|
+
*/
|
|
539
|
+
export async function withAccountStorageTransaction(handler) {
|
|
540
|
+
return withStorageLock(async () => {
|
|
541
|
+
const current = await loadAccountsInternal(saveAccountsUnlocked);
|
|
542
|
+
return handler(current, saveAccountsUnlocked);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Persists account storage to disk using atomic write (temp file + rename).
|
|
547
|
+
* Creates the .opencode directory if it doesn't exist.
|
|
548
|
+
* Verifies file was written correctly and provides detailed error messages.
|
|
549
|
+
* @param storage - Account storage data to save
|
|
550
|
+
* @throws StorageError with platform-aware hints on failure
|
|
551
|
+
*/
|
|
552
|
+
export async function saveAccounts(storage) {
|
|
553
|
+
return withStorageLock(async () => {
|
|
554
|
+
await saveAccountsUnlocked(storage);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Deletes the account storage file from disk.
|
|
559
|
+
* Silently ignores if file doesn't exist.
|
|
560
|
+
*
|
|
561
|
+
* Ordering (F1 post-merge MEDIUM finding): unlink the on-disk JSON FIRST,
|
|
562
|
+
* then delete the keychain entry. If we cleared the keychain first and the
|
|
563
|
+
* unlink failed for a non-ENOENT reason (EACCES, EBUSY, filesystem drift),
|
|
564
|
+
* a subsequent load with opt-in still on would take the "no keychain entry,
|
|
565
|
+
* fall back to JSON" branch (see `loadAccountsInternal`) and resurrect the
|
|
566
|
+
* credentials from the still-present JSON file. Callers typically run
|
|
567
|
+
* `clearAccounts` to recover from a compromised token, so a silent
|
|
568
|
+
* resurrection is a meaningful failure mode.
|
|
569
|
+
*
|
|
570
|
+
* Fail-safe invariant: if the JSON unlink fails (non-ENOENT), we skip the
|
|
571
|
+
* keychain delete and log at `error`. Both copies remain in sync so the
|
|
572
|
+
* caller can retry safely. The operation is still best-effort (never
|
|
573
|
+
* throws) to preserve the existing contract above the storage layer.
|
|
574
|
+
*/
|
|
575
|
+
export async function clearAccounts() {
|
|
576
|
+
return withStorageLock(async () => {
|
|
577
|
+
let jsonCleared = true;
|
|
578
|
+
try {
|
|
579
|
+
const path = getStoragePath();
|
|
580
|
+
await fs.unlink(path);
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
const code = error.code;
|
|
584
|
+
if (code !== "ENOENT") {
|
|
585
|
+
jsonCleared = false;
|
|
586
|
+
log.error("Failed to clear account storage; skipping keychain delete to keep storage sides in sync. Caller should retry.", { error: String(error) });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Only delete the keychain entry after the on-disk copy is gone (or
|
|
590
|
+
// was already absent). This preserves atomicity-enough semantics: a
|
|
591
|
+
// partial failure leaves both sides present rather than clearing
|
|
592
|
+
// one side and letting a subsequent load rehydrate from the other.
|
|
593
|
+
if (jsonCleared && isKeychainOptInEnabled()) {
|
|
594
|
+
try {
|
|
595
|
+
const projectKey = getCurrentProjectStorageKey();
|
|
596
|
+
await deleteFromKeychain(projectKey);
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
log.warn("keychain: delete during clearAccounts failed", {
|
|
600
|
+
error: String(err),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
//# sourceMappingURL=load-save.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-save.js","sourceRoot":"","sources":["../../../lib/storage/load-save.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,41 @@
|
|
|
5
5
|
import { type ModelFamily } from "../prompts/codex.js";
|
|
6
6
|
import type { AccountIdSource } from "../types.js";
|
|
7
7
|
export type CooldownReason = "auth-failure" | "network-error";
|
|
8
|
+
/**
|
|
9
|
+
* StorageError code emitted when a `version: 2` account file is loaded.
|
|
10
|
+
*
|
|
11
|
+
* V2 is an intermediate account schema produced by legacy 4.x plugin builds
|
|
12
|
+
* that never shipped with a documented shape nor a forward-migrator to V3.
|
|
13
|
+
* The safe behaviour is to refuse to load V2 explicitly so the user's
|
|
14
|
+
* credentials are not silently discarded; callers surface this code to the
|
|
15
|
+
* user alongside a recovery hint.
|
|
16
|
+
*/
|
|
17
|
+
export declare const UNKNOWN_V2_FORMAT_CODE = "UNKNOWN_V2_FORMAT";
|
|
18
|
+
/**
|
|
19
|
+
* Minimal detection shape for V2 account storage.
|
|
20
|
+
*
|
|
21
|
+
* The full V2 schema was never documented and no V2 migrator shipped, so this
|
|
22
|
+
* type is intentionally coarse: it only captures the fields needed to detect
|
|
23
|
+
* that a file claims `version: 2` and contains something account-shaped. Do
|
|
24
|
+
* not extend this shape without evidence of the real V2 layout - inventing
|
|
25
|
+
* fields risks producing a silently-wrong migration.
|
|
26
|
+
*/
|
|
27
|
+
export interface AccountStorageV2Detected {
|
|
28
|
+
version: 2;
|
|
29
|
+
accounts?: unknown[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Diagnostic message shown when the loader refuses a V2 storage file.
|
|
33
|
+
* Pulled into a helper so storage.ts, import paths, and tests produce
|
|
34
|
+
* identical copy without drifting.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildV2RejectionMessage(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Recovery hint shown alongside the V2 rejection message.
|
|
39
|
+
* @param path - Absolute path of the offending storage file, or a placeholder
|
|
40
|
+
* when the caller cannot determine the source (e.g. in-memory normalization).
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildV2RecoveryHint(path: string): string;
|
|
8
43
|
export interface RateLimitStateV3 {
|
|
9
44
|
[key: string]: number | undefined;
|
|
10
45
|
}
|