create-principles-disciple 1.141.0 → 1.142.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/codex-adapter/dist/pd-hook.d.ts +1 -0
- package/codex-adapter/dist/pd-hook.js +24 -1
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/config/pd-config-store.d.ts +5 -0
- package/console/dist/server/config/pd-config-store.js +25 -1
- package/console/dist/ui/api.js +6 -3
- package/console/dist/ui/utils/__tests__/token-storage.test.d.ts +1 -0
- package/console/dist/ui/utils/__tests__/token-storage.test.js +64 -0
- package/console/dist/ui/utils/token-storage.d.ts +22 -0
- package/console/dist/ui/utils/token-storage.js +47 -0
- package/console/dist/web/assets/app.js +34 -4
- package/core/dist/runtime-v2/__tests__/adversarial-loop.test.js +27 -3
- package/core/dist/runtime-v2/__tests__/adversarial-loop.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +144 -0
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js +73 -12
- package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +5 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/rulecode-context-v2-flag.test.js +11 -11
- package/core/dist/runtime-v2/feature-flags/__tests__/rulecode-context-v2-flag.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +5 -5
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +6 -6
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +18 -27
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js +40 -33
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-artificer-repair-replay.test.js +22 -4
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-artificer-repair-replay.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-chinese-feedback-contract.test.js +20 -4
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-chinese-feedback-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-out-of-scope-governance.test.js +20 -14
- package/core/dist/runtime-v2/internalization/__tests__/evaluator-out-of-scope-governance.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/evolution-alignment-contract.test.js +17 -4
- package/core/dist/runtime-v2/internalization/__tests__/evolution-alignment-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js +24 -0
- package/core/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/peer-prompt-language.test.js +18 -21
- package/core/dist/runtime-v2/internalization/__tests__/peer-prompt-language.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.js +19 -4
- package/core/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts +17 -6
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +23 -22
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts +6 -2
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.js +19 -14
- package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/core/dist/telemetry-event.d.ts +19 -36
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +253 -59
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/package.json +1 -1
- package/package.json +2 -2
- package/pd-cli/dist/commands/console.d.ts.map +1 -1
- package/pd-cli/dist/commands/console.js +15 -3
- package/pd-cli/dist/commands/console.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +42 -21
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/pd-cli/dist/services/console-launcher.d.ts +9 -0
- package/pd-cli/dist/services/console-launcher.d.ts.map +1 -1
- package/pd-cli/dist/services/console-launcher.js +47 -4
- package/pd-cli/dist/services/console-launcher.js.map +1 -1
- package/pd-cli/dist/services/pd-config-loader.d.ts +5 -0
- package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -1
- package/pd-cli/dist/services/pd-config-loader.js +25 -1
- package/pd-cli/dist/services/pd-config-loader.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +5 -3
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +10 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +290 -294
- package/plugin/dist/governance-audit.js +149 -149
- package/plugin/dist/rulehost-evidence.js +169 -169
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/package.json +2 -2
|
@@ -5,5 +5,6 @@ export interface PdHookResult {
|
|
|
5
5
|
exitCode: number;
|
|
6
6
|
stderr: string[];
|
|
7
7
|
}
|
|
8
|
+
export declare function annotateContextWarnings(warnings: readonly string[]): string[];
|
|
8
9
|
export declare function processHookInvocation(rawStdin: string, _env?: EnvMap, cwd?: string): Promise<PdHookResult>;
|
|
9
10
|
export {};
|
|
@@ -54,6 +54,26 @@ function diagnostic(reason, nextAction) {
|
|
|
54
54
|
function errorMessage(error) {
|
|
55
55
|
return error instanceof Error ? error.message.slice(0, MAX_DIAGNOSTIC) : 'unknown_error';
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* PRI-780 Codex capability declaration (structured UNSUPPORTED — suspension
|
|
59
|
+
* semantics, revised after Codex review round 2 P1): the Codex host has no
|
|
60
|
+
* runtime context provider, and the "unavailable → allow" contract is
|
|
61
|
+
* generation-time prompt discipline, NOT a runtime-enforced invariant. Handing
|
|
62
|
+
* v2 rules a truthy unavailable-posture context would let a persisted rule
|
|
63
|
+
* evaluate context-blind and DENY tool calls that were previously suspended —
|
|
64
|
+
* a silent governance behavior change. Codex therefore passes NO context
|
|
65
|
+
* provider: the shared gate skips v2 rules with its structured
|
|
66
|
+
* `rule_context_v2_unavailable` warning, and this hook annotates that warning
|
|
67
|
+
* with the explicit host-unsupported reason before it reaches Codex stderr
|
|
68
|
+
* (see annotateContextWarnings — ticket option B: 明确 unsupported + 结构化
|
|
69
|
+
* warning, never a silent skip).
|
|
70
|
+
*/
|
|
71
|
+
const CODEX_CONTEXT_UNSUPPORTED_NOTE = 'codex_runtime_context_unsupported: the Codex host provides no runtime context provider; v2 rules stay suspended on this host';
|
|
72
|
+
export function annotateContextWarnings(warnings) {
|
|
73
|
+
return warnings.map((warning) => warning.startsWith('rule_context_v2_unavailable')
|
|
74
|
+
? `${warning}; ${CODEX_CONTEXT_UNSUPPORTED_NOTE}`
|
|
75
|
+
: warning);
|
|
76
|
+
}
|
|
57
77
|
// Bounded governance-observation ingestion (Codex Governance Closure Slice
|
|
58
78
|
// A) followed by the Slice B signal-admission pass (SPEC §12/§13): detection
|
|
59
79
|
// → canonical pain → evidence promotion → one pending Diagnostician task.
|
|
@@ -163,8 +183,11 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
163
183
|
hostKind: 'codex',
|
|
164
184
|
toolSemantics: CODEX_TOOL_SEMANTICS,
|
|
165
185
|
events: codexEventEmitter(path.join(resolution.workspaceDir, '.state')),
|
|
186
|
+
// PRI-780 (revised after Codex review round 2 P1): NO context provider —
|
|
187
|
+
// v2 rules stay SUSPENDED on Codex (never loaded context-blind). See
|
|
188
|
+
// annotateContextWarnings for the structured unsupported declaration.
|
|
166
189
|
}).dispatch(event);
|
|
167
|
-
const stderr = [...(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic(warning, 'Inspect PD Workspace state and retry; the hook failed open.')), ...ingestionDiagnostics];
|
|
190
|
+
const stderr = [...annotateContextWarnings(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic(warning, 'Inspect PD Workspace state and retry; the hook failed open.')), ...ingestionDiagnostics];
|
|
168
191
|
return { stdout: adapter.encodeOutput(result, event.kind), exitCode: 0, stderr };
|
|
169
192
|
}
|
|
170
193
|
catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -86,6 +86,11 @@ export declare function loadPdConfig(workspaceDir: string): ConfigLoadResult;
|
|
|
86
86
|
* Compute feature flags from the loaded PD config (PRI-460).
|
|
87
87
|
* Works with both ok and error results (uses defaults for errors).
|
|
88
88
|
* Replaces the legacy `loadWorkspaceFeatureFlags` + `buildFeedbackChannelFlags` path.
|
|
89
|
+
*
|
|
90
|
+
* On a failed load, flags whose gate fail-closes on config errors are forced
|
|
91
|
+
* disabled with an observable warning — the registry default (ON for a
|
|
92
|
+
* graduated flag) must never be displayed as enforcing when the workspace
|
|
93
|
+
* config is unreadable.
|
|
89
94
|
*/
|
|
90
95
|
export declare function computeFlagsFromLoadResult(result: ConfigLoadResult): FeatureFlagsResult;
|
|
91
96
|
export declare function getConfigSummary(workspaceDir: string): {
|
|
@@ -212,14 +212,38 @@ export function loadPdConfig(workspaceDir) {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
// ── Feature Flags from Config ────────────────────────────────────────────────
|
|
215
|
+
/**
|
|
216
|
+
* Flags whose production gate FAILS CLOSED when the workspace config cannot
|
|
217
|
+
* be loaded: the gate cannot know the flag state, so the gated capability is
|
|
218
|
+
* suspended (OpenClaw `buildRuleContextIfEnabled` returns undefined context on
|
|
219
|
+
* config failure → v2 rules skip). Status derivation must mirror that posture
|
|
220
|
+
* or the Console would display "active" for rules that are actually not
|
|
221
|
+
* enforcing (PRI-780 review B3).
|
|
222
|
+
*/
|
|
223
|
+
const CONFIG_FAILURE_FAIL_CLOSED_FLAGS = new Set(['rulecode_context_v2']);
|
|
215
224
|
/**
|
|
216
225
|
* Compute feature flags from the loaded PD config (PRI-460).
|
|
217
226
|
* Works with both ok and error results (uses defaults for errors).
|
|
218
227
|
* Replaces the legacy `loadWorkspaceFeatureFlags` + `buildFeedbackChannelFlags` path.
|
|
228
|
+
*
|
|
229
|
+
* On a failed load, flags whose gate fail-closes on config errors are forced
|
|
230
|
+
* disabled with an observable warning — the registry default (ON for a
|
|
231
|
+
* graduated flag) must never be displayed as enforcing when the workspace
|
|
232
|
+
* config is unreadable.
|
|
219
233
|
*/
|
|
220
234
|
export function computeFlagsFromLoadResult(result) {
|
|
221
235
|
const effective = result.ok ? result.effective : result.defaults;
|
|
222
|
-
|
|
236
|
+
const flags = computeFeatureFlagsFromConfig(effective);
|
|
237
|
+
if (!result.ok) {
|
|
238
|
+
for (const flagId of CONFIG_FAILURE_FAIL_CLOSED_FLAGS) {
|
|
239
|
+
const flag = flags.flags[flagId];
|
|
240
|
+
if (flag?.enabled) {
|
|
241
|
+
flags.flags[flagId] = { ...flag, enabled: false };
|
|
242
|
+
flags.warnings.push(`${flagId}: config load failed — flag displayed as disabled (fail-closed, matches the gate's config-failure suspension)`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return flags;
|
|
223
247
|
}
|
|
224
248
|
// ── Get Redacted Summary ─────────────────────────────────────────────────────
|
|
225
249
|
export function getConfigSummary(workspaceDir) {
|
package/console/dist/ui/api.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { clearStoredToken, loadStoredToken, storeToken } from "./utils/token-storage.js";
|
|
1
2
|
import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, } from "./utils/signal-keywords-api.js";
|
|
2
3
|
import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateFailedTaskDetail, validateOwnerDecisionsData, validateOwnerResolutionResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateCorePrinciples, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateGovernanceExperienceSnapshot, validateRuleCodeOwnerReview, validateRuleCodeMutation, validateOwnerIdentityView, validateOwnerIdentityRegister, validateOwnerIdentityUnregister, } from "./utils/validators.js";
|
|
3
4
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
4
5
|
function getToken() {
|
|
5
|
-
|
|
6
|
+
// PRI-793: localStorage-backed (with one-time migration from the legacy
|
|
7
|
+
// sessionStorage copy) so the token survives browser restarts.
|
|
8
|
+
return loadStoredToken();
|
|
6
9
|
}
|
|
7
10
|
function setToken(token) {
|
|
8
|
-
|
|
11
|
+
storeToken(token);
|
|
9
12
|
}
|
|
10
13
|
function clearToken() {
|
|
11
|
-
|
|
14
|
+
clearStoredToken();
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* True when an unauthorized response should route the UI to the login route.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { clearStoredToken, loadStoredToken, storeToken } from "../token-storage.js";
|
|
3
|
+
/** Minimal in-memory Storage double (no window dependency). */
|
|
4
|
+
function fakeStorage() {
|
|
5
|
+
const map = new Map();
|
|
6
|
+
return {
|
|
7
|
+
get length() {
|
|
8
|
+
return map.size;
|
|
9
|
+
},
|
|
10
|
+
clear: () => map.clear(),
|
|
11
|
+
getItem: (k) => (map.has(k) ? map.get(k) : null),
|
|
12
|
+
key: (i) => Array.from(map.keys())[i] ?? null,
|
|
13
|
+
removeItem: (k) => void map.delete(k),
|
|
14
|
+
setItem: (k, v) => void map.set(k, v),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
describe("token-storage (PRI-793)", () => {
|
|
18
|
+
it("stores into localStorage and leaves no sessionStorage copy", () => {
|
|
19
|
+
const main = fakeStorage();
|
|
20
|
+
const legacy = fakeStorage();
|
|
21
|
+
storeToken("tok-1", main, legacy);
|
|
22
|
+
expect(main.getItem("pd_token")).toBe("tok-1");
|
|
23
|
+
expect(legacy.getItem("pd_token")).toBe(null);
|
|
24
|
+
});
|
|
25
|
+
it("loads the persisted token across 'browser restarts'", () => {
|
|
26
|
+
const main = fakeStorage();
|
|
27
|
+
const legacy = fakeStorage();
|
|
28
|
+
storeToken("tok-2", main, legacy);
|
|
29
|
+
expect(loadStoredToken(main, legacy)).toBe("tok-2");
|
|
30
|
+
});
|
|
31
|
+
it("migrates a legacy sessionStorage token once, then owns it in localStorage only", () => {
|
|
32
|
+
const main = fakeStorage();
|
|
33
|
+
const legacy = fakeStorage();
|
|
34
|
+
legacy.setItem("pd_token", "legacy-tok");
|
|
35
|
+
expect(loadStoredToken(main, legacy)).toBe("legacy-tok");
|
|
36
|
+
expect(main.getItem("pd_token")).toBe("legacy-tok");
|
|
37
|
+
expect(legacy.getItem("pd_token")).toBe(null);
|
|
38
|
+
});
|
|
39
|
+
it("returns null when nothing is stored anywhere", () => {
|
|
40
|
+
expect(loadStoredToken(fakeStorage(), fakeStorage())).toBe(null);
|
|
41
|
+
});
|
|
42
|
+
it("clear removes both storage copies so a stale token cannot resurrect", () => {
|
|
43
|
+
const main = fakeStorage();
|
|
44
|
+
const legacy = fakeStorage();
|
|
45
|
+
storeToken("tok-3", main, legacy);
|
|
46
|
+
legacy.setItem("pd_token", "stale");
|
|
47
|
+
clearStoredToken(main, legacy);
|
|
48
|
+
expect(main.getItem("pd_token")).toBe(null);
|
|
49
|
+
expect(legacy.getItem("pd_token")).toBe(null);
|
|
50
|
+
expect(loadStoredToken(main, legacy)).toBe(null);
|
|
51
|
+
});
|
|
52
|
+
it("re-login overwrites the persisted token", () => {
|
|
53
|
+
const main = fakeStorage();
|
|
54
|
+
const legacy = fakeStorage();
|
|
55
|
+
storeToken("old", main, legacy);
|
|
56
|
+
storeToken("new", main, legacy);
|
|
57
|
+
expect(loadStoredToken(main, legacy)).toBe("new");
|
|
58
|
+
});
|
|
59
|
+
it("degrades gracefully when no Storage exists (non-DOM environment)", () => {
|
|
60
|
+
expect(loadStoredToken(null, null)).toBe(null);
|
|
61
|
+
expect(() => storeToken("tok", null, null)).not.toThrow();
|
|
62
|
+
expect(() => clearStoredToken(null, null)).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side persistence for the Console login token (PRI-793).
|
|
3
|
+
*
|
|
4
|
+
* The token lives in localStorage so the Owner is not re-prompted on every
|
|
5
|
+
* browser start. sessionStorage is only a migration source: a token left
|
|
6
|
+
* there by older builds is promoted to localStorage once and then removed —
|
|
7
|
+
* localStorage is the single source of truth afterwards (P4).
|
|
8
|
+
*
|
|
9
|
+
* Clearing (logout / Settings "清除凭证") removes both keys so a stale copy
|
|
10
|
+
* can never resurrect the session.
|
|
11
|
+
*
|
|
12
|
+
* Storages are resolved from globalThis (browsers expose localStorage /
|
|
13
|
+
* sessionStorage there) and may be absent — non-DOM test environments run
|
|
14
|
+
* the same code path with null storages, where persistence degrades to
|
|
15
|
+
* "no stored token" instead of crashing. Explicit arguments override the
|
|
16
|
+
* builtins for tests.
|
|
17
|
+
*/
|
|
18
|
+
type TokenStorage = Storage | null | undefined;
|
|
19
|
+
export declare function loadStoredToken(storage?: TokenStorage, legacy?: TokenStorage): string | null;
|
|
20
|
+
export declare function storeToken(token: string, storage?: TokenStorage, legacy?: TokenStorage): void;
|
|
21
|
+
export declare function clearStoredToken(storage?: TokenStorage, legacy?: TokenStorage): void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side persistence for the Console login token (PRI-793).
|
|
3
|
+
*
|
|
4
|
+
* The token lives in localStorage so the Owner is not re-prompted on every
|
|
5
|
+
* browser start. sessionStorage is only a migration source: a token left
|
|
6
|
+
* there by older builds is promoted to localStorage once and then removed —
|
|
7
|
+
* localStorage is the single source of truth afterwards (P4).
|
|
8
|
+
*
|
|
9
|
+
* Clearing (logout / Settings "清除凭证") removes both keys so a stale copy
|
|
10
|
+
* can never resurrect the session.
|
|
11
|
+
*
|
|
12
|
+
* Storages are resolved from globalThis (browsers expose localStorage /
|
|
13
|
+
* sessionStorage there) and may be absent — non-DOM test environments run
|
|
14
|
+
* the same code path with null storages, where persistence degrades to
|
|
15
|
+
* "no stored token" instead of crashing. Explicit arguments override the
|
|
16
|
+
* builtins for tests.
|
|
17
|
+
*/
|
|
18
|
+
const TOKEN_KEY = "pd_token";
|
|
19
|
+
function builtin(kind) {
|
|
20
|
+
const g = globalThis;
|
|
21
|
+
return (kind === "local" ? g.localStorage : g.sessionStorage) ?? null;
|
|
22
|
+
}
|
|
23
|
+
export function loadStoredToken(storage, legacy) {
|
|
24
|
+
const main = storage ?? builtin("local");
|
|
25
|
+
const old = legacy ?? builtin("session");
|
|
26
|
+
const current = main?.getItem(TOKEN_KEY) ?? null;
|
|
27
|
+
if (current !== null)
|
|
28
|
+
return current;
|
|
29
|
+
const legacyToken = old?.getItem(TOKEN_KEY) ?? null;
|
|
30
|
+
if (legacyToken === null)
|
|
31
|
+
return null;
|
|
32
|
+
main?.setItem(TOKEN_KEY, legacyToken);
|
|
33
|
+
old?.removeItem(TOKEN_KEY);
|
|
34
|
+
return legacyToken;
|
|
35
|
+
}
|
|
36
|
+
export function storeToken(token, storage, legacy) {
|
|
37
|
+
const main = storage ?? builtin("local");
|
|
38
|
+
const old = legacy ?? builtin("session");
|
|
39
|
+
main?.setItem(TOKEN_KEY, token);
|
|
40
|
+
old?.removeItem(TOKEN_KEY);
|
|
41
|
+
}
|
|
42
|
+
export function clearStoredToken(storage, legacy) {
|
|
43
|
+
const main = storage ?? builtin("local");
|
|
44
|
+
const old = legacy ?? builtin("session");
|
|
45
|
+
main?.removeItem(TOKEN_KEY);
|
|
46
|
+
old?.removeItem(TOKEN_KEY);
|
|
47
|
+
}
|