aismemory 0.5.0 → 0.5.2

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 (86) hide show
  1. package/dist/__tests__/ais-http-error.test.d.ts +1 -0
  2. package/dist/__tests__/ais-http-error.test.js +49 -0
  3. package/dist/__tests__/ais-http-error.test.js.map +1 -0
  4. package/dist/__tests__/auth-staleness.test.d.ts +9 -0
  5. package/dist/__tests__/auth-staleness.test.js +46 -0
  6. package/dist/__tests__/auth-staleness.test.js.map +1 -0
  7. package/dist/__tests__/auto-handoff.test.js +108 -2
  8. package/dist/__tests__/auto-handoff.test.js.map +1 -1
  9. package/dist/__tests__/config.test.js +6 -3
  10. package/dist/__tests__/config.test.js.map +1 -1
  11. package/dist/__tests__/env-agent.test.d.ts +1 -0
  12. package/dist/__tests__/env-agent.test.js +19 -0
  13. package/dist/__tests__/env-agent.test.js.map +1 -0
  14. package/dist/__tests__/existing-hashes.test.d.ts +1 -0
  15. package/dist/__tests__/existing-hashes.test.js +122 -0
  16. package/dist/__tests__/existing-hashes.test.js.map +1 -0
  17. package/dist/__tests__/hydration.test.js +38 -0
  18. package/dist/__tests__/hydration.test.js.map +1 -1
  19. package/dist/__tests__/local-mirror.test.js +4 -0
  20. package/dist/__tests__/local-mirror.test.js.map +1 -1
  21. package/dist/__tests__/memory-write-failure.test.d.ts +1 -0
  22. package/dist/__tests__/memory-write-failure.test.js +148 -0
  23. package/dist/__tests__/memory-write-failure.test.js.map +1 -0
  24. package/dist/__tests__/oauth-credentials.test.d.ts +1 -0
  25. package/dist/__tests__/oauth-credentials.test.js +29 -0
  26. package/dist/__tests__/oauth-credentials.test.js.map +1 -0
  27. package/dist/__tests__/pipeline-ingestion.test.js +112 -1
  28. package/dist/__tests__/pipeline-ingestion.test.js.map +1 -1
  29. package/dist/__tests__/refresh.test.js +24 -0
  30. package/dist/__tests__/refresh.test.js.map +1 -1
  31. package/dist/__tests__/sync-memory-cli.test.d.ts +1 -0
  32. package/dist/__tests__/sync-memory-cli.test.js +200 -0
  33. package/dist/__tests__/sync-memory-cli.test.js.map +1 -0
  34. package/dist/__tests__/telemetry.test.d.ts +1 -0
  35. package/dist/__tests__/telemetry.test.js +67 -0
  36. package/dist/__tests__/telemetry.test.js.map +1 -0
  37. package/dist/__tests__/token-expiry-reauth.test.d.ts +1 -0
  38. package/dist/__tests__/token-expiry-reauth.test.js +201 -0
  39. package/dist/__tests__/token-expiry-reauth.test.js.map +1 -0
  40. package/dist/__tests__/tool-args.test.d.ts +1 -0
  41. package/dist/__tests__/tool-args.test.js +78 -0
  42. package/dist/__tests__/tool-args.test.js.map +1 -0
  43. package/dist/ais-http-error.d.ts +26 -0
  44. package/dist/ais-http-error.js +75 -0
  45. package/dist/ais-http-error.js.map +1 -0
  46. package/dist/auth-staleness.d.ts +27 -0
  47. package/dist/auth-staleness.js +41 -0
  48. package/dist/auth-staleness.js.map +1 -0
  49. package/dist/auto-handoff.d.ts +2 -0
  50. package/dist/auto-handoff.js +40 -16
  51. package/dist/auto-handoff.js.map +1 -1
  52. package/dist/cli/sync-memory.js +31 -36
  53. package/dist/cli/sync-memory.js.map +1 -1
  54. package/dist/config.js +4 -1
  55. package/dist/config.js.map +1 -1
  56. package/dist/env-agent.d.ts +10 -0
  57. package/dist/env-agent.js +14 -0
  58. package/dist/env-agent.js.map +1 -0
  59. package/dist/hydration.js +54 -3
  60. package/dist/hydration.js.map +1 -1
  61. package/dist/index.js +243 -115
  62. package/dist/index.js.map +1 -1
  63. package/dist/local-mirror.d.ts +5 -0
  64. package/dist/local-mirror.js +72 -14
  65. package/dist/local-mirror.js.map +1 -1
  66. package/dist/oauth-credentials.d.ts +14 -0
  67. package/dist/oauth-credentials.js +35 -0
  68. package/dist/oauth-credentials.js.map +1 -0
  69. package/dist/pipeline/bulk-store.d.ts +46 -0
  70. package/dist/pipeline/bulk-store.js +165 -0
  71. package/dist/pipeline/bulk-store.js.map +1 -0
  72. package/dist/pipeline/existing-hashes.d.ts +20 -0
  73. package/dist/pipeline/existing-hashes.js +111 -0
  74. package/dist/pipeline/existing-hashes.js.map +1 -0
  75. package/dist/pipeline/ingestion.d.ts +8 -4
  76. package/dist/pipeline/ingestion.js +36 -8
  77. package/dist/pipeline/ingestion.js.map +1 -1
  78. package/dist/telemetry.d.ts +22 -0
  79. package/dist/telemetry.js +28 -0
  80. package/dist/telemetry.js.map +1 -0
  81. package/dist/tool-args.d.ts +52 -0
  82. package/dist/tool-args.js +78 -0
  83. package/dist/tool-args.js.map +1 -0
  84. package/dist/trust-ledger.js +2 -2
  85. package/dist/trust-ledger.js.map +1 -1
  86. package/package.json +9 -4
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, appendFileSync, openSync, readSync, closeSync, fstatSync, writeSync, renameSync, unlinkSync, } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  export class LocalMirror {
@@ -28,20 +28,18 @@ export class LocalMirror {
28
28
  if (!existsSync(filePath))
29
29
  return;
30
30
  const cutoff = Date.now() - this.retentionDays * 24 * 60 * 60 * 1000;
31
- const lines = readFileSync(filePath, 'utf8')
32
- .split('\n')
33
- .filter((l) => l.length > 0);
34
- const surviving = lines.filter((line) => {
35
- try {
36
- const parsed = JSON.parse(line);
37
- return new Date(parsed.importedAt).getTime() >= cutoff;
38
- }
39
- catch {
40
- return false;
41
- }
42
- });
43
31
  const tmp = `${filePath}.tmp`;
44
- writeFileSync(tmp, surviving.join('\n') + (surviving.length > 0 ? '\n' : ''), { mode: 0o600 });
32
+ const outFd = openSync(tmp, 'w', 0o600);
33
+ try {
34
+ for (const line of this.readJsonlLines(filePath)) {
35
+ if (this.isWithinRetention(line, cutoff)) {
36
+ writeSync(outFd, `${line}\n`);
37
+ }
38
+ }
39
+ }
40
+ finally {
41
+ closeSync(outFd);
42
+ }
45
43
  renameSync(tmp, filePath);
46
44
  }
47
45
  scrub(agentId) {
@@ -49,8 +47,68 @@ export class LocalMirror {
49
47
  if (existsSync(filePath))
50
48
  unlinkSync(filePath);
51
49
  }
50
+ /** Collect provenance.sourceHash values from the local mirror JSONL cache. */
51
+ collectSourceHashes(agentId) {
52
+ const filePath = this.pathFor(agentId);
53
+ if (!existsSync(filePath))
54
+ return new Set();
55
+ const hashes = new Set();
56
+ for (const line of this.readJsonlLines(filePath)) {
57
+ try {
58
+ const parsed = JSON.parse(line);
59
+ const hash = parsed.memory?.provenance?.sourceHash;
60
+ if (typeof hash === 'string' && hash.length > 0) {
61
+ hashes.add(hash);
62
+ }
63
+ }
64
+ catch {
65
+ // Malformed lines are ignored (same as cleanup).
66
+ }
67
+ }
68
+ return hashes;
69
+ }
52
70
  pathFor(agentId) {
53
71
  return join(this.cacheDir, `${agentId}.jsonl`);
54
72
  }
73
+ /** Read JSONL lines without loading the whole file into memory. */
74
+ *readJsonlLines(filePath) {
75
+ const fd = openSync(filePath, 'r');
76
+ try {
77
+ const { size } = fstatSync(fd);
78
+ if (size === 0)
79
+ return;
80
+ const bufferSize = 64 * 1024;
81
+ const buffer = Buffer.alloc(bufferSize);
82
+ let leftover = '';
83
+ let position = 0;
84
+ while (position < size) {
85
+ const bytesRead = readSync(fd, buffer, 0, bufferSize, position);
86
+ if (bytesRead === 0)
87
+ break;
88
+ position += bytesRead;
89
+ const chunk = leftover + buffer.toString('utf8', 0, bytesRead);
90
+ const parts = chunk.split('\n');
91
+ leftover = parts.pop() ?? '';
92
+ for (const line of parts) {
93
+ if (line.length > 0)
94
+ yield line;
95
+ }
96
+ }
97
+ if (leftover.length > 0)
98
+ yield leftover;
99
+ }
100
+ finally {
101
+ closeSync(fd);
102
+ }
103
+ }
104
+ isWithinRetention(line, cutoff) {
105
+ try {
106
+ const parsed = JSON.parse(line);
107
+ return new Date(parsed.importedAt).getTime() >= cutoff;
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
55
113
  }
56
114
  //# sourceMappingURL=local-mirror.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-mirror.js","sourceRoot":"","sources":["../src/local-mirror.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-mirror.js","sourceRoot":"","sources":["../src/local-mirror.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface OAuthCredentials {
2
+ token: string;
3
+ agentId: string;
4
+ tenantId: string;
5
+ expiresAt: string;
6
+ }
7
+ export declare function credentialsDir(home?: string): string;
8
+ export declare function credentialsFilePath(home?: string): string;
9
+ /**
10
+ * Persist OAuth bearer credentials under ~/.aismemory with restrictive POSIX
11
+ * permissions (dir 0700, file 0600). Mirrors key-auth.ts / SyncConfig.persist.
12
+ */
13
+ export declare function saveOAuthCredentials(creds: OAuthCredentials, home?: string): void;
14
+ export declare function loadOAuthCredentials(home?: string): OAuthCredentials | null;
@@ -0,0 +1,35 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export function credentialsDir(home = homedir()) {
5
+ return join(home, '.aismemory');
6
+ }
7
+ export function credentialsFilePath(home = homedir()) {
8
+ return join(credentialsDir(home), 'credentials.json');
9
+ }
10
+ /**
11
+ * Persist OAuth bearer credentials under ~/.aismemory with restrictive POSIX
12
+ * permissions (dir 0700, file 0600). Mirrors key-auth.ts / SyncConfig.persist.
13
+ */
14
+ export function saveOAuthCredentials(creds, home = homedir()) {
15
+ const dir = credentialsDir(home);
16
+ const file = credentialsFilePath(home);
17
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
18
+ writeFileSync(file, JSON.stringify(creds, null, 2), { mode: 0o600 });
19
+ // writeFileSync mode is umask-dependent; chmod is the reliable path on POSIX.
20
+ if (process.platform !== 'win32') {
21
+ chmodSync(file, 0o600);
22
+ }
23
+ }
24
+ export function loadOAuthCredentials(home = homedir()) {
25
+ const file = credentialsFilePath(home);
26
+ try {
27
+ if (!existsSync(file))
28
+ return null;
29
+ return JSON.parse(readFileSync(file, 'utf-8'));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ //# sourceMappingURL=oauth-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-credentials.js","sourceRoot":"","sources":["../src/oauth-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,MAAM,UAAU,cAAc,CAAC,OAAe,OAAO,EAAE;IACrD,OAAO,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAe,OAAO,EAAE;IAC1D,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAuB,EAAE,OAAe,OAAO,EAAE;IACpF,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,8EAA8E;IAC9E,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAe,OAAO,EAAE;IAC3D,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAqB,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { AisMemoryDraft } from './types.js';
2
+ export declare const BULK_CHUNK_SIZE = 25;
3
+ export declare const BULK_TIMEOUT_MS = 20000;
4
+ export interface BulkWriteResult {
5
+ created: Array<{
6
+ index: number;
7
+ id: string;
8
+ }>;
9
+ failed: Array<{
10
+ index: number;
11
+ error: string;
12
+ }>;
13
+ }
14
+ export interface AisCreds {
15
+ apiBase: string;
16
+ apiKey: string;
17
+ tenantId: string;
18
+ }
19
+ /**
20
+ * POST a single chunk to the AIS bulk endpoint with a hard timeout.
21
+ * Throws on HTTP auth errors (401/403) or AbortError; returns a result with
22
+ * transient errors mapped to per-item failures for all other HTTP errors.
23
+ */
24
+ export declare function postBulkChunk(agentId: string, drafts: AisMemoryDraft[], creds: AisCreds, timeoutMs?: number): Promise<BulkWriteResult>;
25
+ /**
26
+ * POST a single memory via the single-write endpoint.
27
+ * Returns a BulkWriteResult-shaped object for uniform handling.
28
+ * Throws on auth errors; returns per-item failure for other errors/timeouts.
29
+ */
30
+ export declare function postSingleWrite(agentId: string, draft: AisMemoryDraft, originalIndex: number, creds: AisCreds, timeoutMs?: number): Promise<BulkWriteResult>;
31
+ /**
32
+ * Recursively write `drafts` mapped to their original `baseIndices`.
33
+ * On AbortError or 502/503/504, halves the chunk and retries; falls through to
34
+ * single-write when the chunk reaches size 1.
35
+ * Per-item `failed` entries from a successful bulk call are retried individually.
36
+ */
37
+ export declare function writeWithRetry(agentId: string, drafts: AisMemoryDraft[], baseIndices: number[], creds: AisCreds, timeoutMs?: number): Promise<BulkWriteResult>;
38
+ /**
39
+ * Public entry point: chunk `drafts` into BULK_CHUNK_SIZE batches, write each
40
+ * with timeout + recursive-split retry, and accumulate results.
41
+ */
42
+ export declare function bulkStoreViaAis(agentId: string, drafts: AisMemoryDraft[], creds: AisCreds): Promise<BulkWriteResult>;
43
+ /**
44
+ * Single-write-only mode: bypasses bulk endpoint entirely.
45
+ */
46
+ export declare function singleWriteAllViaAis(agentId: string, drafts: AisMemoryDraft[], creds: AisCreds): Promise<BulkWriteResult>;
@@ -0,0 +1,165 @@
1
+ export const BULK_CHUNK_SIZE = 25;
2
+ export const BULK_TIMEOUT_MS = 20_000;
3
+ function memoryPayload(draft) {
4
+ return {
5
+ content: draft.content,
6
+ type: draft.type,
7
+ importance: draft.importance,
8
+ metadata: { provenance: draft.provenance, trustScoreHint: draft.trustScore },
9
+ };
10
+ }
11
+ /**
12
+ * POST a single chunk to the AIS bulk endpoint with a hard timeout.
13
+ * Throws on HTTP auth errors (401/403) or AbortError; returns a result with
14
+ * transient errors mapped to per-item failures for all other HTTP errors.
15
+ */
16
+ export async function postBulkChunk(agentId, drafts, creds, timeoutMs = BULK_TIMEOUT_MS) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19
+ try {
20
+ const res = await fetch(`${creds.apiBase}/v1/agents/${agentId}/memory/bulk`, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ 'x-api-key': creds.apiKey,
25
+ 'x-tenant-id': creds.tenantId,
26
+ },
27
+ body: JSON.stringify({ memories: drafts.map(memoryPayload) }),
28
+ signal: controller.signal,
29
+ });
30
+ if (res.status === 401 || res.status === 403) {
31
+ const text = await res.text().catch(() => '');
32
+ throw new Error(`AIS bulk store auth error: HTTP ${res.status}${text ? ` — ${text.slice(0, 500)}` : ''}`);
33
+ }
34
+ if (!res.ok) {
35
+ const text = await res.text().catch(() => '');
36
+ throw new Error(`AIS bulk store failed: HTTP ${res.status} ${res.statusText}${text ? ` — ${text.slice(0, 500)}` : ''}`);
37
+ }
38
+ const json = (await res.json());
39
+ return { created: json.data?.created ?? [], failed: json.data?.failed ?? [] };
40
+ }
41
+ finally {
42
+ clearTimeout(timer);
43
+ }
44
+ }
45
+ /**
46
+ * POST a single memory via the single-write endpoint.
47
+ * Returns a BulkWriteResult-shaped object for uniform handling.
48
+ * Throws on auth errors; returns per-item failure for other errors/timeouts.
49
+ */
50
+ export async function postSingleWrite(agentId, draft, originalIndex, creds, timeoutMs = BULK_TIMEOUT_MS) {
51
+ const controller = new AbortController();
52
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
53
+ try {
54
+ const res = await fetch(`${creds.apiBase}/v1/agents/${agentId}/memory`, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ 'x-api-key': creds.apiKey,
59
+ 'x-tenant-id': creds.tenantId,
60
+ },
61
+ body: JSON.stringify(memoryPayload(draft)),
62
+ signal: controller.signal,
63
+ });
64
+ if (res.status === 401 || res.status === 403) {
65
+ const text = await res.text().catch(() => '');
66
+ throw new Error(`AIS single write auth error: HTTP ${res.status}${text ? ` — ${text.slice(0, 200)}` : ''}`);
67
+ }
68
+ if (!res.ok) {
69
+ const text = await res.text().catch(() => '');
70
+ return {
71
+ created: [],
72
+ failed: [{ index: originalIndex, error: `HTTP ${res.status}: ${text.slice(0, 200)}` }],
73
+ };
74
+ }
75
+ const json = (await res.json());
76
+ return { created: [{ index: originalIndex, id: json.data?.id ?? `unknown-${originalIndex}` }], failed: [] };
77
+ }
78
+ catch (err) {
79
+ if (err.name === 'AbortError') {
80
+ return { created: [], failed: [{ index: originalIndex, error: 'request timed out' }] };
81
+ }
82
+ throw err;
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+ /**
89
+ * Recursively write `drafts` mapped to their original `baseIndices`.
90
+ * On AbortError or 502/503/504, halves the chunk and retries; falls through to
91
+ * single-write when the chunk reaches size 1.
92
+ * Per-item `failed` entries from a successful bulk call are retried individually.
93
+ */
94
+ export async function writeWithRetry(agentId, drafts, baseIndices, creds, timeoutMs = BULK_TIMEOUT_MS) {
95
+ if (drafts.length === 0)
96
+ return { created: [], failed: [] };
97
+ if (drafts.length === 1) {
98
+ return postSingleWrite(agentId, drafts[0], baseIndices[0], creds, timeoutMs);
99
+ }
100
+ let chunkResult;
101
+ try {
102
+ chunkResult = await postBulkChunk(agentId, drafts, creds, timeoutMs);
103
+ // Re-map chunk-local indices back to global.
104
+ chunkResult = {
105
+ created: chunkResult.created.map((c) => ({ index: baseIndices[c.index], id: c.id })),
106
+ failed: chunkResult.failed.map((f) => ({ index: baseIndices[f.index], error: f.error })),
107
+ };
108
+ }
109
+ catch (err) {
110
+ const msg = err instanceof Error ? err.message : String(err);
111
+ const isTransient = err.name === 'AbortError' ||
112
+ msg.includes('502') ||
113
+ msg.includes('503') ||
114
+ msg.includes('504');
115
+ if (!isTransient)
116
+ throw err;
117
+ const half = Math.ceil(drafts.length / 2);
118
+ const [left, right] = await Promise.all([
119
+ writeWithRetry(agentId, drafts.slice(0, half), baseIndices.slice(0, half), creds, timeoutMs),
120
+ writeWithRetry(agentId, drafts.slice(half), baseIndices.slice(half), creds, timeoutMs),
121
+ ]);
122
+ return { created: [...left.created, ...right.created], failed: [...left.failed, ...right.failed] };
123
+ }
124
+ if (chunkResult.failed.length === 0)
125
+ return chunkResult;
126
+ // Retry per-item failures individually.
127
+ const retried = await Promise.all(chunkResult.failed.map((f) => {
128
+ const localIdx = baseIndices.indexOf(f.index);
129
+ const draft = localIdx >= 0 ? drafts[localIdx] : undefined;
130
+ if (!draft)
131
+ return Promise.resolve({ created: [], failed: [f] });
132
+ return postSingleWrite(agentId, draft, f.index, creds, timeoutMs);
133
+ }));
134
+ return {
135
+ created: [...chunkResult.created, ...retried.flatMap((r) => r.created)],
136
+ failed: retried.flatMap((r) => r.failed),
137
+ };
138
+ }
139
+ /**
140
+ * Public entry point: chunk `drafts` into BULK_CHUNK_SIZE batches, write each
141
+ * with timeout + recursive-split retry, and accumulate results.
142
+ */
143
+ export async function bulkStoreViaAis(agentId, drafts, creds) {
144
+ const allCreated = [];
145
+ const allFailed = [];
146
+ for (let start = 0; start < drafts.length; start += BULK_CHUNK_SIZE) {
147
+ const chunk = drafts.slice(start, start + BULK_CHUNK_SIZE);
148
+ const baseIndices = Array.from({ length: chunk.length }, (_, i) => start + i);
149
+ const result = await writeWithRetry(agentId, chunk, baseIndices, creds);
150
+ allCreated.push(...result.created);
151
+ allFailed.push(...result.failed);
152
+ }
153
+ return { created: allCreated, failed: allFailed };
154
+ }
155
+ /**
156
+ * Single-write-only mode: bypasses bulk endpoint entirely.
157
+ */
158
+ export async function singleWriteAllViaAis(agentId, drafts, creds) {
159
+ const results = await Promise.all(drafts.map((d, i) => postSingleWrite(agentId, d, i, creds)));
160
+ return {
161
+ created: results.flatMap((r) => r.created),
162
+ failed: results.flatMap((r) => r.failed),
163
+ };
164
+ }
165
+ //# sourceMappingURL=bulk-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-store.js","sourceRoot":"","sources":["../../src/pipeline/bulk-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { LocalMirror } from '../local-mirror.js';
2
+ export interface AisCredentials {
3
+ apiBase: string;
4
+ apiKey: string;
5
+ tenantId: string;
6
+ }
7
+ /**
8
+ * Fetch provenance.sourceHash values already stored in AIS.
9
+ *
10
+ * The list endpoint omits metadata, so we page by memory type (up to 100 per
11
+ * type) and hydrate metadata per row. This covers large agents better than a
12
+ * single 100-row browse while staying within existing AIS APIs.
13
+ *
14
+ * Individual list or detail failures are tolerated — the dedupe falls back to
15
+ * what was fetched successfully, preventing a transient AIS error from blocking
16
+ * the entire sync.
17
+ */
18
+ export declare function fetchAisSourceHashes(agentId: string, creds: AisCredentials): Promise<Set<string>>;
19
+ /** Merge local mirror hashes with AIS provenance hashes (when credentials exist). */
20
+ export declare function loadExistingHashes(agentId: string, mirror: LocalMirror, creds?: AisCredentials): Promise<Set<string>>;
@@ -0,0 +1,111 @@
1
+ const MEMORY_TYPES = [
2
+ 'fact',
3
+ 'event',
4
+ 'lesson',
5
+ 'context',
6
+ 'goal',
7
+ 'task',
8
+ ];
9
+ const AIS_PAGE_LIMIT = 100;
10
+ function extractSourceHashFromMetadata(metadata) {
11
+ if (typeof metadata !== 'object' || metadata === null)
12
+ return undefined;
13
+ const provenance = metadata.provenance;
14
+ if (typeof provenance !== 'object' || provenance === null)
15
+ return undefined;
16
+ const sourceHash = provenance.sourceHash;
17
+ return typeof sourceHash === 'string' && sourceHash.length > 0 ? sourceHash : undefined;
18
+ }
19
+ async function fetchJson(url, init) {
20
+ const res = await fetch(url, init);
21
+ if (!res.ok) {
22
+ const bodyText = await res.text().catch(() => '');
23
+ throw new Error(`AIS request failed: HTTP ${res.status} ${res.statusText}${bodyText ? ` — ${bodyText.slice(0, 500)}` : ''}`);
24
+ }
25
+ return (await res.json());
26
+ }
27
+ function aisHeaders(creds) {
28
+ return {
29
+ 'Content-Type': 'application/json',
30
+ 'x-api-key': creds.apiKey,
31
+ 'x-tenant-id': creds.tenantId,
32
+ };
33
+ }
34
+ async function listMemoriesByType(agentId, creds, type) {
35
+ const params = new URLSearchParams({
36
+ types: type,
37
+ limit: String(AIS_PAGE_LIMIT),
38
+ sortBy: 'createdAt',
39
+ order: 'desc',
40
+ includeExpired: 'true',
41
+ includeSuperseded: 'true',
42
+ expandRelationships: 'false',
43
+ });
44
+ try {
45
+ const json = await fetchJson(`${creds.apiBase}/v1/agents/${agentId}/memory?${params.toString()}`, { headers: aisHeaders(creds) });
46
+ return json.data?.memories ?? [];
47
+ }
48
+ catch {
49
+ // Tolerate transient list failures — dedupe falls back to local mirror only.
50
+ return [];
51
+ }
52
+ }
53
+ async function getMemoryMetadata(agentId, creds, memoryId) {
54
+ try {
55
+ const json = await fetchJson(`${creds.apiBase}/v1/agents/${agentId}/memory/${memoryId}`, { headers: aisHeaders(creds) });
56
+ return json.data?.metadata;
57
+ }
58
+ catch (err) {
59
+ // Tolerate transient detail failures — skip hash for this entry.
60
+ console.warn(`[aismemory] skipping memory hash lookup for ${memoryId}: ${err}`);
61
+ return undefined;
62
+ }
63
+ }
64
+ /**
65
+ * Fetch provenance.sourceHash values already stored in AIS.
66
+ *
67
+ * The list endpoint omits metadata, so we page by memory type (up to 100 per
68
+ * type) and hydrate metadata per row. This covers large agents better than a
69
+ * single 100-row browse while staying within existing AIS APIs.
70
+ *
71
+ * Individual list or detail failures are tolerated — the dedupe falls back to
72
+ * what was fetched successfully, preventing a transient AIS error from blocking
73
+ * the entire sync.
74
+ */
75
+ export async function fetchAisSourceHashes(agentId, creds) {
76
+ const hashes = new Set();
77
+ const seenIds = new Set();
78
+ for (const type of MEMORY_TYPES) {
79
+ const memories = await listMemoriesByType(agentId, creds, type);
80
+ const metadataList = await Promise.all(memories
81
+ .filter((m) => {
82
+ if (seenIds.has(m.id))
83
+ return false;
84
+ seenIds.add(m.id);
85
+ return true;
86
+ })
87
+ .map((m) => getMemoryMetadata(agentId, creds, m.id)));
88
+ for (const metadata of metadataList) {
89
+ const hash = extractSourceHashFromMetadata(metadata);
90
+ if (hash)
91
+ hashes.add(hash);
92
+ }
93
+ }
94
+ return hashes;
95
+ }
96
+ /** Merge local mirror hashes with AIS provenance hashes (when credentials exist). */
97
+ export async function loadExistingHashes(agentId, mirror, creds) {
98
+ const hashes = mirror.collectSourceHashes(agentId);
99
+ if (!creds)
100
+ return hashes;
101
+ try {
102
+ const aisHashes = await fetchAisSourceHashes(agentId, creds);
103
+ for (const hash of aisHashes)
104
+ hashes.add(hash);
105
+ }
106
+ catch {
107
+ // If the entire hash-fetch fails, proceed with mirror-only dedupe.
108
+ }
109
+ return hashes;
110
+ }
111
+ //# sourceMappingURL=existing-hashes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"existing-hashes.js","sourceRoot":"","sources":["../../src/pipeline/existing-hashes.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,8 @@ export interface BulkStoreResult {
15
15
  }
16
16
  export type BulkStoreFn = (drafts: AisMemoryDraft[]) => Promise<BulkStoreResult>;
17
17
  export type ReviewFn = (drafts: AisMemoryDraft[]) => Promise<AisMemoryDraft[]>;
18
+ /** AIS bulk endpoint limit — must not exceed this per request. */
19
+ export declare const BULK_BATCH_SIZE = 100;
18
20
  export interface IngestionOptions {
19
21
  adapter: SourceAdapter;
20
22
  rawSourceData: string | FilesystemResult;
@@ -43,10 +45,12 @@ export interface IngestionOptions {
43
45
  * Failure semantics:
44
46
  * - If `bulkStore` throws, neither ledger nor mirror is updated — the caller
45
47
  * may safely retry the entire run without double-counting.
46
- * - If `mirror.append` throws after a successful `bulkStore`, the ledger has
47
- * already been advanced (AIS accepted the memories) and the failure is
48
- * surfaced as a `warnings` entry rather than an exception so the caller
49
- * receives a valid `SyncResult`.
48
+ * - If `bulkStore` returns any failures, the trust ledger is not advanced —
49
+ * partial AIS writes must not count toward auto-commit trust.
50
+ * - If `mirror.append` throws after a fully successful `bulkStore`, the
51
+ * ledger has already been advanced (AIS accepted all memories) and the
52
+ * failure is surfaced as a `warnings` entry rather than an exception so
53
+ * the caller receives a valid `SyncResult`.
50
54
  *
51
55
  * Zero-import path:
52
56
  * - When all candidates are dedupe-skipped or rejected by `review`,
@@ -1,5 +1,7 @@
1
1
  import { preCommitDedupe } from './dedupe.js';
2
2
  import { applyTrustTags } from './trust-tagger.js';
3
+ /** AIS bulk endpoint limit — must not exceed this per request. */
4
+ export const BULK_BATCH_SIZE = 100;
3
5
  /**
4
6
  * Run the full ingestion pipeline for a single source adapter + scope pair.
5
7
  *
@@ -15,10 +17,12 @@ import { applyTrustTags } from './trust-tagger.js';
15
17
  * Failure semantics:
16
18
  * - If `bulkStore` throws, neither ledger nor mirror is updated — the caller
17
19
  * may safely retry the entire run without double-counting.
18
- * - If `mirror.append` throws after a successful `bulkStore`, the ledger has
19
- * already been advanced (AIS accepted the memories) and the failure is
20
- * surfaced as a `warnings` entry rather than an exception so the caller
21
- * receives a valid `SyncResult`.
20
+ * - If `bulkStore` returns any failures, the trust ledger is not advanced —
21
+ * partial AIS writes must not count toward auto-commit trust.
22
+ * - If `mirror.append` throws after a fully successful `bulkStore`, the
23
+ * ledger has already been advanced (AIS accepted all memories) and the
24
+ * failure is surfaced as a `warnings` entry rather than an exception so
25
+ * the caller receives a valid `SyncResult`.
22
26
  *
23
27
  * Zero-import path:
24
28
  * - When all candidates are dedupe-skipped or rejected by `review`,
@@ -47,10 +51,13 @@ export async function runIngestion(options) {
47
51
  failed: [],
48
52
  };
49
53
  }
50
- const storeResult = await bulkStore(approved);
51
- // Advance the trust ledger FIRST — if AIS accepted the memories, the
52
- // trust ladder must reflect that regardless of local-mirror success.
53
- ledger.recordSync(agentId, adapter.id, scopeKey);
54
+ // Split approved drafts into chunks to stay within AIS bulk limit.
55
+ const storeResult = await chunkedBulkStore(bulkStore, approved);
56
+ // Advance trust only on a clean bulk store partial failures must not
57
+ // increment syncCount toward auto-commit.
58
+ if (storeResult.failed.length === 0) {
59
+ ledger.recordSync(agentId, adapter.id, scopeKey);
60
+ }
54
61
  const mirrorEntries = storeResult.created.map((c) => ({
55
62
  aisMemoryId: c.id,
56
63
  memory: approved[c.index],
@@ -72,6 +79,27 @@ export async function runIngestion(options) {
72
79
  failed: storeResult.failed,
73
80
  };
74
81
  }
82
+ /**
83
+ * Call `fn` in batches of BULK_BATCH_SIZE, remapping per-batch indices to
84
+ * global indices so callers see a single aggregated BulkStoreResult.
85
+ */
86
+ async function chunkedBulkStore(fn, drafts) {
87
+ if (drafts.length <= BULK_BATCH_SIZE) {
88
+ return fn(drafts);
89
+ }
90
+ const aggregated = { created: [], failed: [] };
91
+ for (let offset = 0; offset < drafts.length; offset += BULK_BATCH_SIZE) {
92
+ const chunk = drafts.slice(offset, offset + BULK_BATCH_SIZE);
93
+ const result = await fn(chunk);
94
+ for (const c of result.created) {
95
+ aggregated.created.push({ index: c.index + offset, id: c.id });
96
+ }
97
+ for (const f of result.failed) {
98
+ aggregated.failed.push({ index: f.index + offset, error: f.error });
99
+ }
100
+ }
101
+ return aggregated;
102
+ }
75
103
  function scopeToKey(scope) {
76
104
  // Stable multi-field encoding prevents collisions when optional fields
77
105
  // are absent. Pipe separator is safe because none of the component