@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.
@@ -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 `${encodeURIComponent(sessionKey)}::${encodeURIComponent(agentId)}`;
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 [encodedSessionKey, encodedAgentId] = key.split("::");
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
- return {
13
- sessionKey: decodeURIComponent(encodedSessionKey),
14
- agentId: decodeURIComponent(encodedAgentId)
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 key = encodeToggleKey(sessionKey, agentId);
78
+ const keys = toggleKeyCandidates(sessionKey, agentId);
61
79
  const primary = await readPrimary();
62
- if (primary.entries[key]) {
80
+ const primaryKey = keys.find((key) => primary.entries[key]);
81
+ if (primaryKey) {
63
82
  return {
64
- disabled: primary.entries[key].disabled,
83
+ disabled: primary.entries[primaryKey].disabled,
65
84
  source: "primary",
66
- updatedAt: primary.entries[key].updatedAt
85
+ updatedAt: primary.entries[primaryKey].updatedAt
67
86
  };
68
87
  }
69
88
  const secondary = await readSecondary();
70
- if (secondary.entries[key]) {
89
+ const secondaryKey = keys.find((key) => secondary.entries[key]);
90
+ if (secondaryKey) {
71
91
  return {
72
- disabled: secondary.entries[key].disabled,
92
+ disabled: secondary.entries[secondaryKey].disabled,
73
93
  source: "secondary",
74
- updatedAt: secondary.entries[key].updatedAt
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 key = encodeToggleKey(sessionKey, agentId);
111
+ const keys = toggleKeyCandidates(sessionKey, agentId);
92
112
  await queueWrite(async () => {
93
113
  const current = await readPrimary();
94
- delete current.entries[key];
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)}::${encodeURIComponent(agentId)}`;\n}\n\nfunction decodeToggleKey(key: string): { sessionKey: string; agentId: string } | null {\n const [encodedSessionKey, encodedAgentId] = key.split(\"::\");\n if (!encodedSessionKey || !encodedAgentId) return null;\n return {\n sessionKey: decodeURIComponent(encodedSessionKey),\n agentId: decodeURIComponent(encodedAgentId),\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 key = encodeToggleKey(sessionKey, agentId);\n const primary = await readPrimary();\n if (primary.entries[key]) {\n return {\n disabled: primary.entries[key].disabled,\n source: \"primary\" as const,\n updatedAt: primary.entries[key].updatedAt,\n };\n }\n const secondary = await readSecondary();\n if (secondary.entries[key]) {\n return {\n disabled: secondary.entries[key].disabled,\n source: \"secondary\" as const,\n updatedAt: secondary.entries[key].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 key = encodeToggleKey(sessionKey, agentId);\n await queueWrite(async () => {\n const current = await readPrimary();\n delete current.entries[key];\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":[]}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.591",
3
+ "version": "9.3.592",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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");
@@ -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 `${encodeURIComponent(sessionKey)}::${encodeURIComponent(agentId)}`;
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 [encodedSessionKey, encodedAgentId] = key.split("::");
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
- return {
38
- sessionKey: decodeURIComponent(encodedSessionKey),
39
- agentId: decodeURIComponent(encodedAgentId),
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 key = encodeToggleKey(sessionKey, agentId);
122
+ const keys = toggleKeyCandidates(sessionKey, agentId);
101
123
  const primary = await readPrimary();
102
- if (primary.entries[key]) {
124
+ const primaryKey = keys.find((key) => primary.entries[key]);
125
+ if (primaryKey) {
103
126
  return {
104
- disabled: primary.entries[key].disabled,
127
+ disabled: primary.entries[primaryKey].disabled,
105
128
  source: "primary" as const,
106
- updatedAt: primary.entries[key].updatedAt,
129
+ updatedAt: primary.entries[primaryKey].updatedAt,
107
130
  };
108
131
  }
109
132
  const secondary = await readSecondary();
110
- if (secondary.entries[key]) {
133
+ const secondaryKey = keys.find((key) => secondary.entries[key]);
134
+ if (secondaryKey) {
111
135
  return {
112
- disabled: secondary.entries[key].disabled,
136
+ disabled: secondary.entries[secondaryKey].disabled,
113
137
  source: "secondary" as const,
114
- updatedAt: secondary.entries[key].updatedAt,
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 key = encodeToggleKey(sessionKey, agentId);
157
+ const keys = toggleKeyCandidates(sessionKey, agentId);
134
158
  await queueWrite(async () => {
135
159
  const current = await readPrimary();
136
- delete current.entries[key];
160
+ for (const key of keys) {
161
+ delete current.entries[key];
162
+ }
137
163
  await writeToggleFile(current);
138
164
  });
139
165
  },