@remnic/core 9.3.591 → 9.3.592
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/dist/session-toggles.js +37 -15
- package/dist/session-toggles.js.map +1 -1
- package/package.json +1 -1
- package/src/session-toggles.test.ts +20 -0
- package/src/session-toggles.ts +41 -15
package/dist/session-toggles.js
CHANGED
|
@@ -3,16 +3,34 @@ import "./chunk-PZ5AY32C.js";
|
|
|
3
3
|
// src/session-toggles.ts
|
|
4
4
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5
5
|
import path from "path";
|
|
6
|
+
var TOGGLE_KEY_SEPARATOR = "::";
|
|
7
|
+
function encodeToggleComponent(value) {
|
|
8
|
+
return encodeURIComponent(value).replaceAll(":", "%3A");
|
|
9
|
+
}
|
|
6
10
|
function encodeToggleKey(sessionKey, agentId) {
|
|
7
|
-
return `${
|
|
11
|
+
return `${encodeToggleComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeToggleComponent(agentId)}`;
|
|
12
|
+
}
|
|
13
|
+
function encodeLegacyToggleKey(sessionKey, agentId) {
|
|
14
|
+
return `${encodeURIComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeURIComponent(agentId)}`;
|
|
15
|
+
}
|
|
16
|
+
function toggleKeyCandidates(sessionKey, agentId) {
|
|
17
|
+
const key = encodeToggleKey(sessionKey, agentId);
|
|
18
|
+
const legacyKey = encodeLegacyToggleKey(sessionKey, agentId);
|
|
19
|
+
return key === legacyKey ? [key] : [key, legacyKey];
|
|
8
20
|
}
|
|
9
21
|
function decodeToggleKey(key) {
|
|
10
|
-
const
|
|
22
|
+
const parts = key.split(TOGGLE_KEY_SEPARATOR);
|
|
23
|
+
if (parts.length !== 2) return null;
|
|
24
|
+
const [encodedSessionKey, encodedAgentId] = parts;
|
|
11
25
|
if (!encodedSessionKey || !encodedAgentId) return null;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
try {
|
|
27
|
+
return {
|
|
28
|
+
sessionKey: decodeURIComponent(encodedSessionKey),
|
|
29
|
+
agentId: decodeURIComponent(encodedAgentId)
|
|
30
|
+
};
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
16
34
|
}
|
|
17
35
|
async function safeReadToggleFile(filePath) {
|
|
18
36
|
try {
|
|
@@ -57,21 +75,23 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
57
75
|
return resolved.disabled;
|
|
58
76
|
},
|
|
59
77
|
async resolve(sessionKey, agentId) {
|
|
60
|
-
const
|
|
78
|
+
const keys = toggleKeyCandidates(sessionKey, agentId);
|
|
61
79
|
const primary = await readPrimary();
|
|
62
|
-
|
|
80
|
+
const primaryKey = keys.find((key) => primary.entries[key]);
|
|
81
|
+
if (primaryKey) {
|
|
63
82
|
return {
|
|
64
|
-
disabled: primary.entries[
|
|
83
|
+
disabled: primary.entries[primaryKey].disabled,
|
|
65
84
|
source: "primary",
|
|
66
|
-
updatedAt: primary.entries[
|
|
85
|
+
updatedAt: primary.entries[primaryKey].updatedAt
|
|
67
86
|
};
|
|
68
87
|
}
|
|
69
88
|
const secondary = await readSecondary();
|
|
70
|
-
|
|
89
|
+
const secondaryKey = keys.find((key) => secondary.entries[key]);
|
|
90
|
+
if (secondaryKey) {
|
|
71
91
|
return {
|
|
72
|
-
disabled: secondary.entries[
|
|
92
|
+
disabled: secondary.entries[secondaryKey].disabled,
|
|
73
93
|
source: "secondary",
|
|
74
|
-
updatedAt: secondary.entries[
|
|
94
|
+
updatedAt: secondary.entries[secondaryKey].updatedAt
|
|
75
95
|
};
|
|
76
96
|
}
|
|
77
97
|
return { disabled: false, source: "none" };
|
|
@@ -88,10 +108,12 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
88
108
|
});
|
|
89
109
|
},
|
|
90
110
|
async clear(sessionKey, agentId) {
|
|
91
|
-
const
|
|
111
|
+
const keys = toggleKeyCandidates(sessionKey, agentId);
|
|
92
112
|
await queueWrite(async () => {
|
|
93
113
|
const current = await readPrimary();
|
|
94
|
-
|
|
114
|
+
for (const key of keys) {
|
|
115
|
+
delete current.entries[key];
|
|
116
|
+
}
|
|
95
117
|
await writeToggleFile(current);
|
|
96
118
|
});
|
|
97
119
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-toggles.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface SessionToggleStore {\n isDisabled(sessionKey: string, agentId: string): Promise<boolean>;\n resolve(sessionKey: string, agentId: string): Promise<{\n disabled: boolean;\n source: \"primary\" | \"secondary\" | \"none\";\n updatedAt?: string;\n }>;\n setDisabled(sessionKey: string, agentId: string, disabled: boolean): Promise<void>;\n clear(sessionKey: string, agentId: string): Promise<void>;\n list(): Promise<Array<{ sessionKey: string; agentId: string; disabled: boolean; updatedAt: string }>>;\n}\n\ninterface ToggleEntry {\n disabled: boolean;\n updatedAt: string;\n}\n\ninterface ToggleFile {\n version: 1;\n entries: Record<string, ToggleEntry>;\n}\n\ninterface FileToggleStoreOptions {\n secondaryReadOnlyPath?: string;\n}\n\nfunction encodeToggleKey(sessionKey: string, agentId: string): string {\n return `${encodeURIComponent(sessionKey)}
|
|
1
|
+
{"version":3,"sources":["../src/session-toggles.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface SessionToggleStore {\n isDisabled(sessionKey: string, agentId: string): Promise<boolean>;\n resolve(sessionKey: string, agentId: string): Promise<{\n disabled: boolean;\n source: \"primary\" | \"secondary\" | \"none\";\n updatedAt?: string;\n }>;\n setDisabled(sessionKey: string, agentId: string, disabled: boolean): Promise<void>;\n clear(sessionKey: string, agentId: string): Promise<void>;\n list(): Promise<Array<{ sessionKey: string; agentId: string; disabled: boolean; updatedAt: string }>>;\n}\n\ninterface ToggleEntry {\n disabled: boolean;\n updatedAt: string;\n}\n\ninterface ToggleFile {\n version: 1;\n entries: Record<string, ToggleEntry>;\n}\n\ninterface FileToggleStoreOptions {\n secondaryReadOnlyPath?: string;\n}\n\nconst TOGGLE_KEY_SEPARATOR = \"::\";\n\nfunction encodeToggleComponent(value: string): string {\n return encodeURIComponent(value).replaceAll(\":\", \"%3A\");\n}\n\nfunction encodeToggleKey(sessionKey: string, agentId: string): string {\n return `${encodeToggleComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeToggleComponent(agentId)}`;\n}\n\nfunction encodeLegacyToggleKey(sessionKey: string, agentId: string): string {\n return `${encodeURIComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeURIComponent(agentId)}`;\n}\n\nfunction toggleKeyCandidates(sessionKey: string, agentId: string): string[] {\n const key = encodeToggleKey(sessionKey, agentId);\n const legacyKey = encodeLegacyToggleKey(sessionKey, agentId);\n return key === legacyKey ? [key] : [key, legacyKey];\n}\n\nfunction decodeToggleKey(key: string): { sessionKey: string; agentId: string } | null {\n const parts = key.split(TOGGLE_KEY_SEPARATOR);\n if (parts.length !== 2) return null;\n const [encodedSessionKey, encodedAgentId] = parts;\n if (!encodedSessionKey || !encodedAgentId) return null;\n try {\n return {\n sessionKey: decodeURIComponent(encodedSessionKey),\n agentId: decodeURIComponent(encodedAgentId),\n };\n } catch {\n return null;\n }\n}\n\nasync function safeReadToggleFile(filePath: string): Promise<ToggleFile> {\n try {\n const raw = await readFile(filePath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<ToggleFile>;\n if (!parsed || typeof parsed !== \"object\" || typeof parsed.entries !== \"object\") {\n return { version: 1, entries: {} };\n }\n return {\n version: 1,\n entries: Object.fromEntries(\n Object.entries(parsed.entries).filter(\n ([, value]) =>\n value &&\n typeof value === \"object\" &&\n typeof value.disabled === \"boolean\" &&\n typeof value.updatedAt === \"string\",\n ),\n ) as Record<string, ToggleEntry>,\n };\n } catch {\n return { version: 1, entries: {} };\n }\n}\n\nexport function createFileToggleStore(\n filePath: string,\n options: FileToggleStoreOptions = {},\n): SessionToggleStore {\n let writeChain = Promise.resolve();\n\n async function queueWrite(operation: () => Promise<void>): Promise<void> {\n const run = writeChain.catch(() => undefined).then(operation);\n writeChain = run.catch(() => undefined);\n await run;\n }\n\n async function writeToggleFile(next: ToggleFile): Promise<void> {\n await mkdir(path.dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(next, null, 2), \"utf8\");\n }\n\n async function readPrimary(): Promise<ToggleFile> {\n return safeReadToggleFile(filePath);\n }\n\n async function readSecondary(): Promise<ToggleFile> {\n if (!options.secondaryReadOnlyPath) return { version: 1, entries: {} };\n return safeReadToggleFile(options.secondaryReadOnlyPath);\n }\n\n return {\n async isDisabled(sessionKey: string, agentId: string): Promise<boolean> {\n const resolved = await this.resolve(sessionKey, agentId);\n return resolved.disabled;\n },\n\n async resolve(sessionKey: string, agentId: string) {\n const keys = toggleKeyCandidates(sessionKey, agentId);\n const primary = await readPrimary();\n const primaryKey = keys.find((key) => primary.entries[key]);\n if (primaryKey) {\n return {\n disabled: primary.entries[primaryKey].disabled,\n source: \"primary\" as const,\n updatedAt: primary.entries[primaryKey].updatedAt,\n };\n }\n const secondary = await readSecondary();\n const secondaryKey = keys.find((key) => secondary.entries[key]);\n if (secondaryKey) {\n return {\n disabled: secondary.entries[secondaryKey].disabled,\n source: \"secondary\" as const,\n updatedAt: secondary.entries[secondaryKey].updatedAt,\n };\n }\n return { disabled: false, source: \"none\" as const };\n },\n\n async setDisabled(sessionKey: string, agentId: string, disabled: boolean): Promise<void> {\n const key = encodeToggleKey(sessionKey, agentId);\n await queueWrite(async () => {\n const current = await readPrimary();\n current.entries[key] = {\n disabled,\n updatedAt: new Date().toISOString(),\n };\n await writeToggleFile(current);\n });\n },\n\n async clear(sessionKey: string, agentId: string): Promise<void> {\n const keys = toggleKeyCandidates(sessionKey, agentId);\n await queueWrite(async () => {\n const current = await readPrimary();\n for (const key of keys) {\n delete current.entries[key];\n }\n await writeToggleFile(current);\n });\n },\n\n async list() {\n const current = await readPrimary();\n return Object.entries(current.entries)\n .map(([key, value]) => {\n const decoded = decodeToggleKey(key);\n if (!decoded) return null;\n return {\n sessionKey: decoded.sessionKey,\n agentId: decoded.agentId,\n disabled: value.disabled,\n updatedAt: value.updatedAt,\n };\n })\n .filter((value): value is { sessionKey: string; agentId: string; disabled: boolean; updatedAt: string } =>\n value !== null\n );\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -24,6 +24,26 @@ test("createFileToggleStore round-trips disabled state for encoded session keys"
|
|
|
24
24
|
assert.equal(Object.keys(raw.entries).length, 1);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
test("createFileToggleStore lists and clears keys containing the separator", async () => {
|
|
28
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-toggle-store-separator-"));
|
|
29
|
+
const filePath = path.join(root, "session-toggles.json");
|
|
30
|
+
const store = createFileToggleStore(filePath);
|
|
31
|
+
|
|
32
|
+
await store.setDisabled("pi::session", "agent::one", true);
|
|
33
|
+
|
|
34
|
+
assert.equal(await store.isDisabled("pi::session", "agent::one"), true);
|
|
35
|
+
const listed = await store.list();
|
|
36
|
+
assert.equal(listed.length, 1);
|
|
37
|
+
assert.equal(listed[0]?.sessionKey, "pi::session");
|
|
38
|
+
assert.equal(listed[0]?.agentId, "agent::one");
|
|
39
|
+
assert.equal(listed[0]?.disabled, true);
|
|
40
|
+
|
|
41
|
+
await store.clear("pi::session", "agent::one");
|
|
42
|
+
|
|
43
|
+
assert.equal(await store.isDisabled("pi::session", "agent::one"), false);
|
|
44
|
+
assert.deepEqual(await store.list(), []);
|
|
45
|
+
});
|
|
46
|
+
|
|
27
47
|
test("createFileToggleStore recovers from malformed primary store contents", async () => {
|
|
28
48
|
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-toggle-store-bad-"));
|
|
29
49
|
const filePath = path.join(root, "session-toggles.json");
|
package/src/session-toggles.ts
CHANGED
|
@@ -27,17 +27,39 @@ interface FileToggleStoreOptions {
|
|
|
27
27
|
secondaryReadOnlyPath?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const TOGGLE_KEY_SEPARATOR = "::";
|
|
31
|
+
|
|
32
|
+
function encodeToggleComponent(value: string): string {
|
|
33
|
+
return encodeURIComponent(value).replaceAll(":", "%3A");
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function encodeToggleKey(sessionKey: string, agentId: string): string {
|
|
31
|
-
return `${
|
|
37
|
+
return `${encodeToggleComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeToggleComponent(agentId)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function encodeLegacyToggleKey(sessionKey: string, agentId: string): string {
|
|
41
|
+
return `${encodeURIComponent(sessionKey)}${TOGGLE_KEY_SEPARATOR}${encodeURIComponent(agentId)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toggleKeyCandidates(sessionKey: string, agentId: string): string[] {
|
|
45
|
+
const key = encodeToggleKey(sessionKey, agentId);
|
|
46
|
+
const legacyKey = encodeLegacyToggleKey(sessionKey, agentId);
|
|
47
|
+
return key === legacyKey ? [key] : [key, legacyKey];
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
function decodeToggleKey(key: string): { sessionKey: string; agentId: string } | null {
|
|
35
|
-
const
|
|
51
|
+
const parts = key.split(TOGGLE_KEY_SEPARATOR);
|
|
52
|
+
if (parts.length !== 2) return null;
|
|
53
|
+
const [encodedSessionKey, encodedAgentId] = parts;
|
|
36
54
|
if (!encodedSessionKey || !encodedAgentId) return null;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
try {
|
|
56
|
+
return {
|
|
57
|
+
sessionKey: decodeURIComponent(encodedSessionKey),
|
|
58
|
+
agentId: decodeURIComponent(encodedAgentId),
|
|
59
|
+
};
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
async function safeReadToggleFile(filePath: string): Promise<ToggleFile> {
|
|
@@ -97,21 +119,23 @@ export function createFileToggleStore(
|
|
|
97
119
|
},
|
|
98
120
|
|
|
99
121
|
async resolve(sessionKey: string, agentId: string) {
|
|
100
|
-
const
|
|
122
|
+
const keys = toggleKeyCandidates(sessionKey, agentId);
|
|
101
123
|
const primary = await readPrimary();
|
|
102
|
-
|
|
124
|
+
const primaryKey = keys.find((key) => primary.entries[key]);
|
|
125
|
+
if (primaryKey) {
|
|
103
126
|
return {
|
|
104
|
-
disabled: primary.entries[
|
|
127
|
+
disabled: primary.entries[primaryKey].disabled,
|
|
105
128
|
source: "primary" as const,
|
|
106
|
-
updatedAt: primary.entries[
|
|
129
|
+
updatedAt: primary.entries[primaryKey].updatedAt,
|
|
107
130
|
};
|
|
108
131
|
}
|
|
109
132
|
const secondary = await readSecondary();
|
|
110
|
-
|
|
133
|
+
const secondaryKey = keys.find((key) => secondary.entries[key]);
|
|
134
|
+
if (secondaryKey) {
|
|
111
135
|
return {
|
|
112
|
-
disabled: secondary.entries[
|
|
136
|
+
disabled: secondary.entries[secondaryKey].disabled,
|
|
113
137
|
source: "secondary" as const,
|
|
114
|
-
updatedAt: secondary.entries[
|
|
138
|
+
updatedAt: secondary.entries[secondaryKey].updatedAt,
|
|
115
139
|
};
|
|
116
140
|
}
|
|
117
141
|
return { disabled: false, source: "none" as const };
|
|
@@ -130,10 +154,12 @@ export function createFileToggleStore(
|
|
|
130
154
|
},
|
|
131
155
|
|
|
132
156
|
async clear(sessionKey: string, agentId: string): Promise<void> {
|
|
133
|
-
const
|
|
157
|
+
const keys = toggleKeyCandidates(sessionKey, agentId);
|
|
134
158
|
await queueWrite(async () => {
|
|
135
159
|
const current = await readPrimary();
|
|
136
|
-
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
delete current.entries[key];
|
|
162
|
+
}
|
|
137
163
|
await writeToggleFile(current);
|
|
138
164
|
});
|
|
139
165
|
},
|