@remnic/core 9.3.531 → 9.3.533

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.
Files changed (42) hide show
  1. package/dist/access-cli.js +4 -4
  2. package/dist/access-http.d.ts +1 -1
  3. package/dist/access-mcp.d.ts +1 -1
  4. package/dist/access-service.d.ts +1 -1
  5. package/dist/bootstrap.d.ts +1 -1
  6. package/dist/{chunk-CWWMTTQE.js → chunk-6GUG4YNM.js} +44 -1
  7. package/dist/{chunk-CWWMTTQE.js.map → chunk-6GUG4YNM.js.map} +1 -1
  8. package/dist/{chunk-KAUXI453.js → chunk-KLZDGMDI.js} +2 -2
  9. package/dist/{chunk-KCLX6LOV.js → chunk-SRAF4TIS.js} +4 -4
  10. package/dist/{chunk-BECQDWBA.js → chunk-TQNRI55H.js} +35 -2
  11. package/dist/chunk-TQNRI55H.js.map +1 -0
  12. package/dist/{chunk-QMYXNM4P.js → chunk-VU3SVYMA.js} +3 -3
  13. package/dist/{cli-CPe_2KB1.d.ts → cli-9pwA_PXm.d.ts} +1 -1
  14. package/dist/cli.d.ts +3 -3
  15. package/dist/cli.js +3 -3
  16. package/dist/{connectors-cli-DbTPNj2H.d.ts → connectors-cli-2iaQ5tX2.d.ts} +1 -1
  17. package/dist/connectors-cli.d.ts +2 -2
  18. package/dist/explicit-capture.d.ts +1 -1
  19. package/dist/{framework-CyHYDcri.d.ts → framework-CNDn2164.d.ts} +24 -7
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +9 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/live-connectors-runner.d.ts +1 -1
  24. package/dist/live-connectors-runner.js +3 -3
  25. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  26. package/dist/orchestrator.d.ts +1 -1
  27. package/dist/orchestrator.js +4 -4
  28. package/dist/{state-store-4QZISH3J.js → state-store-Z3EN56O5.js} +2 -2
  29. package/package.json +1 -1
  30. package/src/connectors/live/framework.ts +71 -6
  31. package/src/connectors/live/github.ts +10 -0
  32. package/src/connectors/live/gmail.ts +10 -0
  33. package/src/connectors/live/google-drive.ts +12 -4
  34. package/src/connectors/live/index.ts +2 -0
  35. package/src/connectors/live/live-connectors.test.ts +141 -0
  36. package/src/connectors/live/notion.ts +8 -0
  37. package/src/index.ts +2 -0
  38. package/dist/chunk-BECQDWBA.js.map +0 -1
  39. /package/dist/{chunk-KAUXI453.js.map → chunk-KLZDGMDI.js.map} +0 -0
  40. /package/dist/{chunk-KCLX6LOV.js.map → chunk-SRAF4TIS.js.map} +0 -0
  41. /package/dist/{chunk-QMYXNM4P.js.map → chunk-VU3SVYMA.js.map} +0 -0
  42. /package/dist/{state-store-4QZISH3J.js.map → state-store-Z3EN56O5.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { L as LiveConnector, C as ConnectorConfig, a as ConnectorDocument } from './framework-CyHYDcri.js';
1
+ import { L as LiveConnector, C as ConnectorConfig, a as ConnectorDocument } from './framework-CNDn2164.js';
2
2
  import { LiveConnectorsConfig } from './types.js';
3
3
  import './types-BliCnURB.js';
4
4
  import './index-DJ9QWMw-.js';
@@ -2,9 +2,9 @@ import {
2
2
  builtInLiveConnectorDefinitions,
3
3
  hasEnabledLiveConnector,
4
4
  runLiveConnectorsOnce
5
- } from "./chunk-QMYXNM4P.js";
6
- import "./chunk-BECQDWBA.js";
7
- import "./chunk-CWWMTTQE.js";
5
+ } from "./chunk-VU3SVYMA.js";
6
+ import "./chunk-TQNRI55H.js";
7
+ import "./chunk-6GUG4YNM.js";
8
8
  import "./chunk-OKTXM5H4.js";
9
9
  import "./chunk-EYIEWJNI.js";
10
10
  import "./chunk-JUC24CTX.js";
@@ -31,7 +31,7 @@ import './local-llm.js';
31
31
  import './fallback-llm.js';
32
32
  import './resolve-provider-secret.js';
33
33
  import './live-connectors-runner.js';
34
- import './framework-CyHYDcri.js';
34
+ import './framework-CNDn2164.js';
35
35
  import './relevance.js';
36
36
  import './negative.js';
37
37
  import './session-observer-state.js';
@@ -34,7 +34,7 @@ import './runtime/better-sqlite.js';
34
34
  import 'better-sqlite3';
35
35
  import './session-integrity.js';
36
36
  import './resolve-provider-secret.js';
37
- import './framework-CyHYDcri.js';
37
+ import './framework-CNDn2164.js';
38
38
  import './memory-provenance.js';
39
39
  import './user-model.js';
40
40
  import './lcm/archive.js';
@@ -26,7 +26,7 @@ import {
26
26
  sanitizeSessionKeyForFilename,
27
27
  shouldFilterLifecycleRecallCandidate,
28
28
  summarizeGraphShadowComparison
29
- } from "./chunk-KAUXI453.js";
29
+ } from "./chunk-KLZDGMDI.js";
30
30
  import "./chunk-Y2SXZ5KZ.js";
31
31
  import "./chunk-BFBF3XEF.js";
32
32
  import "./chunk-S53PKKWK.js";
@@ -69,7 +69,7 @@ import "./chunk-ZZTOURJI.js";
69
69
  import "./chunk-XKXKSQU7.js";
70
70
  import "./chunk-3GPTTA4J.js";
71
71
  import "./chunk-IISBCCWR.js";
72
- import "./chunk-QMYXNM4P.js";
72
+ import "./chunk-VU3SVYMA.js";
73
73
  import "./chunk-H63EDPFJ.js";
74
74
  import "./chunk-PD6O7AXF.js";
75
75
  import "./chunk-YAZNBMNF.js";
@@ -127,8 +127,8 @@ import "./chunk-HENLZHIT.js";
127
127
  import "./chunk-7DTASS5T.js";
128
128
  import "./chunk-KFY3SGN7.js";
129
129
  import "./chunk-PCI747N2.js";
130
- import "./chunk-BECQDWBA.js";
131
- import "./chunk-CWWMTTQE.js";
130
+ import "./chunk-TQNRI55H.js";
131
+ import "./chunk-6GUG4YNM.js";
132
132
  import "./chunk-JXS5PDQ7.js";
133
133
  import "./chunk-SKGV326D.js";
134
134
  import "./chunk-BEUDU7Y4.js";
@@ -10,7 +10,7 @@ import {
10
10
  readConnectorState,
11
11
  withConnectorStateLock,
12
12
  writeConnectorState
13
- } from "./chunk-CWWMTTQE.js";
13
+ } from "./chunk-6GUG4YNM.js";
14
14
  import "./chunk-EYIEWJNI.js";
15
15
  import "./chunk-JUC24CTX.js";
16
16
  import "./chunk-PZ5AY32C.js";
@@ -27,4 +27,4 @@ export {
27
27
  withConnectorStateLock,
28
28
  writeConnectorState
29
29
  };
30
- //# sourceMappingURL=state-store-4QZISH3J.js.map
30
+ //# sourceMappingURL=state-store-Z3EN56O5.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.531",
3
+ "version": "9.3.533",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,15 +20,61 @@
20
20
 
21
21
  /**
22
22
  * Free-form connector configuration. Validated by each connector's
23
- * `validateConfig` implementation. Stored alongside the cursor in the state
24
- * store. MUST be JSON-serializable: no functions, no class instances, no
23
+ * `validateConfig` implementation and passed to `syncIncremental` for runtime
24
+ * use. MUST be JSON-serializable: no functions, no class instances, no
25
25
  * circular references.
26
26
  *
27
- * Connectors MUST NOT persist secrets here credentials belong in OS keychain
28
- * / OAuth token storage (PR 2 design).
27
+ * Runtime configs may contain hydrated credentials. Do not persist a runtime
28
+ * config directly. Any code that needs to store connector settings must call
29
+ * `persistableConnectorConfig()` so credentials are stripped or replaced by
30
+ * connector-owned references.
29
31
  */
30
32
  export type ConnectorConfig = Record<string, unknown>;
31
33
 
34
+ const DEFAULT_SECRET_KEY_PARTS = [
35
+ "token",
36
+ "secret",
37
+ "password",
38
+ "credential",
39
+ "apikey",
40
+ "accesskey",
41
+ "privatekey",
42
+ "authorization",
43
+ "authheader",
44
+ "cookie",
45
+ ] as const;
46
+
47
+ /**
48
+ * Redact secret-looking keys from a JSON-serializable connector config.
49
+ * Built-in connectors provide explicit projections, but this default keeps
50
+ * third-party connectors from accidentally persisting obvious credentials.
51
+ */
52
+ export function redactConnectorConfigSecrets(config: ConnectorConfig): ConnectorConfig {
53
+ return redactConnectorConfigValue(config) as ConnectorConfig;
54
+ }
55
+
56
+ function redactConnectorConfigValue(value: unknown): unknown {
57
+ if (Array.isArray(value)) {
58
+ return value.map((item) => redactConnectorConfigValue(item));
59
+ }
60
+ if (typeof value !== "object" || value === null) {
61
+ return value;
62
+ }
63
+ const out: Record<string, unknown> = {};
64
+ for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {
65
+ if (isConnectorSecretConfigKey(key)) {
66
+ continue;
67
+ }
68
+ out[key] = redactConnectorConfigValue(nested);
69
+ }
70
+ return out;
71
+ }
72
+
73
+ function isConnectorSecretConfigKey(key: string): boolean {
74
+ const normalized = key.replace(/[^a-z0-9]/giu, "").toLowerCase();
75
+ return DEFAULT_SECRET_KEY_PARTS.some((part) => normalized.includes(part));
76
+ }
77
+
32
78
  /**
33
79
  * Opaque cursor describing "where the last sync left off". Each connector
34
80
  * defines what `kind` and `value` mean (e.g. Drive: `{kind: "pageToken",
@@ -135,11 +181,20 @@ export interface LiveConnector {
135
181
 
136
182
  /**
137
183
  * Validate raw user-supplied config. MUST throw on malformed input — never
138
- * silently default. The returned object is what gets persisted and passed
139
- * back to `syncIncremental`. Connectors SHOULD strip unknown fields.
184
+ * silently default. The returned object is the runtime config passed back to
185
+ * `syncIncremental`; it may contain hydrated credentials and MUST NOT be
186
+ * persisted directly. Connectors SHOULD strip unknown fields.
140
187
  */
141
188
  validateConfig(raw: unknown): ConnectorConfig;
142
189
 
190
+ /**
191
+ * Return the subset of validated config that may be written to disk. Use this
192
+ * for state/config persistence instead of `validateConfig()`. Connectors that
193
+ * need credentials should omit the raw values here and store only non-secret
194
+ * scope/schedule fields plus any connector-owned secret references.
195
+ */
196
+ persistConfig?(validated: ConnectorConfig): ConnectorConfig;
197
+
143
198
  /**
144
199
  * Run one incremental sync pass. See `SyncIncrementalArgs` /
145
200
  * `SyncIncrementalResult` for the contract.
@@ -147,6 +202,16 @@ export interface LiveConnector {
147
202
  syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult>;
148
203
  }
149
204
 
205
+ export function persistableConnectorConfig(
206
+ connector: Pick<LiveConnector, "persistConfig">,
207
+ validated: ConnectorConfig,
208
+ ): ConnectorConfig {
209
+ if (typeof connector.persistConfig === "function") {
210
+ return connector.persistConfig(validated);
211
+ }
212
+ return redactConnectorConfigSecrets(validated);
213
+ }
214
+
150
215
  /**
151
216
  * Regex enforcing the connector-id naming rule. Exported so connectors and
152
217
  * tests can validate ids consistently with the registry.
@@ -514,6 +514,16 @@ export function createGitHubConnector(
514
514
  return validateGitHubConfig(raw) as unknown as ConnectorConfig;
515
515
  },
516
516
 
517
+ persistConfig(validated: ConnectorConfig): ConnectorConfig {
518
+ const config = validateGitHubConfig(validated);
519
+ return Object.freeze({
520
+ userLogin: config.userLogin,
521
+ repos: config.repos,
522
+ pollIntervalMs: config.pollIntervalMs,
523
+ includeDiscussions: config.includeDiscussions,
524
+ });
525
+ },
526
+
517
527
  async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
518
528
  const config = validateGitHubConfig(args.config);
519
529
  throwIfAborted(args.abortSignal);
@@ -848,6 +848,16 @@ export function createGmailConnector(
848
848
  return validateGmailConfig(raw) as unknown as ConnectorConfig;
849
849
  },
850
850
 
851
+ persistConfig(validated: ConnectorConfig): ConnectorConfig {
852
+ const config = validateGmailConfig(validated);
853
+ return Object.freeze({
854
+ clientId: config.clientId,
855
+ userId: config.userId,
856
+ query: config.query,
857
+ pollIntervalMs: config.pollIntervalMs,
858
+ });
859
+ },
860
+
851
861
  async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
852
862
  const config = validateGmailConfig(args.config);
853
863
  throwIfAborted(args.abortSignal);
@@ -397,14 +397,22 @@ export function createGoogleDriveConnector(
397
397
 
398
398
  validateConfig(raw: unknown): ConnectorConfig {
399
399
  // Cast to ConnectorConfig (Record<string, unknown>) per framework
400
- // contract. The frozen object survives JSON round-trips through the
401
- // state store.
400
+ // contract. Persist only `persistConfig()` output; this runtime object
401
+ // carries hydrated credentials.
402
402
  return validateGoogleDriveConfig(raw) as unknown as ConnectorConfig;
403
403
  },
404
404
 
405
+ persistConfig(validated: ConnectorConfig): ConnectorConfig {
406
+ const config = validateGoogleDriveConfig(validated);
407
+ return Object.freeze({
408
+ clientId: config.clientId,
409
+ pollIntervalMs: config.pollIntervalMs,
410
+ folderIds: config.folderIds,
411
+ });
412
+ },
413
+
405
414
  async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
406
- // Re-validate on every pass: the framework persists raw config, and
407
- // a JS caller could mutate it between passes.
415
+ // Re-validate on every pass: a JS caller could mutate it between passes.
408
416
  const config = validateGoogleDriveConfig(args.config);
409
417
  throwIfAborted(args.abortSignal);
410
418
 
@@ -13,6 +13,8 @@
13
13
  export {
14
14
  CONNECTOR_ID_PATTERN,
15
15
  isValidConnectorId,
16
+ persistableConnectorConfig,
17
+ redactConnectorConfigSecrets,
16
18
  type ConnectorConfig,
17
19
  type ConnectorCursor,
18
20
  type ConnectorDocument,
@@ -6,6 +6,10 @@ import test from "node:test";
6
6
 
7
7
  import {
8
8
  CONNECTOR_ID_PATTERN,
9
+ createGitHubConnector,
10
+ createGmailConnector,
11
+ createGoogleDriveConnector,
12
+ createNotionConnector,
9
13
  type ConnectorConfig,
10
14
  type ConnectorCursor,
11
15
  type ConnectorDocument,
@@ -13,6 +17,8 @@ import {
13
17
  type LiveConnector,
14
18
  LiveConnectorRegistry,
15
19
  LiveConnectorRegistryError,
20
+ persistableConnectorConfig,
21
+ redactConnectorConfigSecrets,
16
22
  type SyncIncrementalArgs,
17
23
  type SyncIncrementalResult,
18
24
  isValidConnectorId,
@@ -21,6 +27,10 @@ import {
21
27
  withConnectorStateLock,
22
28
  writeConnectorState,
23
29
  } from "./index.js";
30
+ import {
31
+ persistableConnectorConfig as persistableConnectorConfigFromRoot,
32
+ redactConnectorConfigSecrets as redactConnectorConfigSecretsFromRoot,
33
+ } from "../../index.js";
24
34
  import {
25
35
  _connectorStatePathForTest,
26
36
  _refreshConnectorLockForTest,
@@ -122,6 +132,137 @@ test("CONNECTOR_ID_PATTERN matches isValidConnectorId", () => {
122
132
  assert.ok(!CONNECTOR_ID_PATTERN.test("Drive"));
123
133
  });
124
134
 
135
+ test("redactConnectorConfigSecrets removes nested secret-shaped keys", () => {
136
+ assert.deepEqual(
137
+ redactConnectorConfigSecrets({
138
+ endpoint: "https://example.invalid",
139
+ authorization: "Bearer synthetic-token",
140
+ authHeader: "Basic synthetic-credentials",
141
+ authorName: "kept",
142
+ nested: {
143
+ accessToken: "synthetic-token",
144
+ cookieHeader: "sid=synthetic-session",
145
+ publicLabel: "kept",
146
+ },
147
+ list: [
148
+ { clientSecret: "synthetic-secret", sessionCookie: "synthetic-cookie", label: "kept" },
149
+ ],
150
+ }),
151
+ {
152
+ endpoint: "https://example.invalid",
153
+ authorName: "kept",
154
+ nested: {
155
+ publicLabel: "kept",
156
+ },
157
+ list: [{ label: "kept" }],
158
+ }
159
+ );
160
+ });
161
+
162
+ test("persisted-config helpers are exported from the package root", () => {
163
+ assert.equal(persistableConnectorConfigFromRoot, persistableConnectorConfig);
164
+ assert.equal(redactConnectorConfigSecretsFromRoot, redactConnectorConfigSecrets);
165
+ });
166
+
167
+ test("built-in live connectors expose persistable config without credential material", () => {
168
+ const cases: Array<{
169
+ connector: LiveConnector;
170
+ raw: ConnectorConfig;
171
+ secretKeys: readonly string[];
172
+ secretValues: readonly string[];
173
+ expected: ConnectorConfig;
174
+ }> = [
175
+ {
176
+ connector: createGitHubConnector(),
177
+ raw: {
178
+ token: "synthetic-github-token",
179
+ userLogin: "octocat",
180
+ repos: ["owner/repo"],
181
+ pollIntervalMs: 300_000,
182
+ includeDiscussions: true,
183
+ },
184
+ secretKeys: ["token"],
185
+ secretValues: ["synthetic-github-token"],
186
+ expected: {
187
+ userLogin: "octocat",
188
+ repos: ["owner/repo"],
189
+ pollIntervalMs: 300_000,
190
+ includeDiscussions: true,
191
+ },
192
+ },
193
+ {
194
+ connector: createGmailConnector(),
195
+ raw: {
196
+ clientId: "synthetic-gmail-client-id",
197
+ clientSecret: "synthetic-gmail-client-secret",
198
+ refreshToken: "synthetic-gmail-refresh-token",
199
+ userId: "me",
200
+ query: "in:inbox",
201
+ pollIntervalMs: 300_000,
202
+ },
203
+ secretKeys: ["clientSecret", "refreshToken"],
204
+ secretValues: ["synthetic-gmail-client-secret", "synthetic-gmail-refresh-token"],
205
+ expected: {
206
+ clientId: "synthetic-gmail-client-id",
207
+ userId: "me",
208
+ query: "in:inbox",
209
+ pollIntervalMs: 300_000,
210
+ },
211
+ },
212
+ {
213
+ connector: createGoogleDriveConnector(),
214
+ raw: {
215
+ clientId: "synthetic-drive-client-id",
216
+ clientSecret: "synthetic-drive-client-secret",
217
+ refreshToken: "synthetic-drive-refresh-token",
218
+ folderIds: ["folder_12345678"],
219
+ pollIntervalMs: 300_000,
220
+ },
221
+ secretKeys: ["clientSecret", "refreshToken"],
222
+ secretValues: ["synthetic-drive-client-secret", "synthetic-drive-refresh-token"],
223
+ expected: {
224
+ clientId: "synthetic-drive-client-id",
225
+ folderIds: ["folder_12345678"],
226
+ pollIntervalMs: 300_000,
227
+ },
228
+ },
229
+ {
230
+ connector: createNotionConnector(),
231
+ raw: {
232
+ token: "secret_synthetic-notion-token",
233
+ databaseIds: ["0123456789abcdef0123456789abcdef"],
234
+ pollIntervalMs: 300_000,
235
+ },
236
+ secretKeys: ["token"],
237
+ secretValues: ["secret_synthetic-notion-token"],
238
+ expected: {
239
+ databaseIds: ["0123456789abcdef0123456789abcdef"],
240
+ pollIntervalMs: 300_000,
241
+ },
242
+ },
243
+ ];
244
+
245
+ for (const testCase of cases) {
246
+ const runtimeConfig = testCase.connector.validateConfig(testCase.raw);
247
+ for (const key of testCase.secretKeys) {
248
+ assert.ok(key in runtimeConfig, `${testCase.connector.id} runtime config should keep ${key}`);
249
+ }
250
+
251
+ const persisted = persistableConnectorConfig(testCase.connector, runtimeConfig);
252
+ assert.deepEqual(persisted, testCase.expected);
253
+ const persistedJson = JSON.stringify(persisted);
254
+ for (const key of testCase.secretKeys) {
255
+ assert.ok(!(key in persisted), `${testCase.connector.id} persisted config should omit ${key}`);
256
+ }
257
+ for (const value of testCase.secretValues) {
258
+ assert.ok(
259
+ !persistedJson.includes(value),
260
+ `${testCase.connector.id} persisted config leaked ${value}`,
261
+ );
262
+ }
263
+ }
264
+ });
265
+
125
266
  // ───────────────────────────────────────────────────────────────────────────
126
267
  // Registry
127
268
  // ───────────────────────────────────────────────────────────────────────────
@@ -643,6 +643,14 @@ export function createNotionConnector(
643
643
  return validateNotionConfig(raw) as unknown as ConnectorConfig;
644
644
  },
645
645
 
646
+ persistConfig(validated: ConnectorConfig): ConnectorConfig {
647
+ const config = validateNotionConfig(validated);
648
+ return Object.freeze({
649
+ databaseIds: config.databaseIds,
650
+ pollIntervalMs: config.pollIntervalMs,
651
+ });
652
+ },
653
+
646
654
  async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
647
655
  const config = validateNotionConfig(args.config);
648
656
  throwIfAborted(args.abortSignal);
package/src/index.ts CHANGED
@@ -794,6 +794,8 @@ export { coerceInstallExtension } from "./connectors/coerce.js";
794
794
  export {
795
795
  CONNECTOR_ID_PATTERN,
796
796
  isValidConnectorId,
797
+ persistableConnectorConfig,
798
+ redactConnectorConfigSecrets,
797
799
  LiveConnectorRegistry,
798
800
  LiveConnectorRegistryError,
799
801
  listConnectorStates,