aismemory 0.5.0 → 0.5.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.
Files changed (77) hide show
  1. package/dist/__tests__/auth-staleness.test.d.ts +9 -0
  2. package/dist/__tests__/auth-staleness.test.js +46 -0
  3. package/dist/__tests__/auth-staleness.test.js.map +1 -0
  4. package/dist/__tests__/auto-handoff.test.js +108 -2
  5. package/dist/__tests__/auto-handoff.test.js.map +1 -1
  6. package/dist/__tests__/config.test.js +6 -3
  7. package/dist/__tests__/config.test.js.map +1 -1
  8. package/dist/__tests__/env-agent.test.d.ts +1 -0
  9. package/dist/__tests__/env-agent.test.js +19 -0
  10. package/dist/__tests__/env-agent.test.js.map +1 -0
  11. package/dist/__tests__/existing-hashes.test.d.ts +1 -0
  12. package/dist/__tests__/existing-hashes.test.js +122 -0
  13. package/dist/__tests__/existing-hashes.test.js.map +1 -0
  14. package/dist/__tests__/hydration.test.js +38 -0
  15. package/dist/__tests__/hydration.test.js.map +1 -1
  16. package/dist/__tests__/local-mirror.test.js +4 -0
  17. package/dist/__tests__/local-mirror.test.js.map +1 -1
  18. package/dist/__tests__/oauth-credentials.test.d.ts +1 -0
  19. package/dist/__tests__/oauth-credentials.test.js +29 -0
  20. package/dist/__tests__/oauth-credentials.test.js.map +1 -0
  21. package/dist/__tests__/pipeline-ingestion.test.js +112 -1
  22. package/dist/__tests__/pipeline-ingestion.test.js.map +1 -1
  23. package/dist/__tests__/refresh.test.js +24 -0
  24. package/dist/__tests__/refresh.test.js.map +1 -1
  25. package/dist/__tests__/sync-memory-cli.test.d.ts +1 -0
  26. package/dist/__tests__/sync-memory-cli.test.js +200 -0
  27. package/dist/__tests__/sync-memory-cli.test.js.map +1 -0
  28. package/dist/__tests__/telemetry.test.d.ts +1 -0
  29. package/dist/__tests__/telemetry.test.js +67 -0
  30. package/dist/__tests__/telemetry.test.js.map +1 -0
  31. package/dist/__tests__/token-expiry-reauth.test.d.ts +1 -0
  32. package/dist/__tests__/token-expiry-reauth.test.js +201 -0
  33. package/dist/__tests__/token-expiry-reauth.test.js.map +1 -0
  34. package/dist/__tests__/tool-args.test.d.ts +1 -0
  35. package/dist/__tests__/tool-args.test.js +78 -0
  36. package/dist/__tests__/tool-args.test.js.map +1 -0
  37. package/dist/auth-staleness.d.ts +27 -0
  38. package/dist/auth-staleness.js +41 -0
  39. package/dist/auth-staleness.js.map +1 -0
  40. package/dist/auto-handoff.d.ts +2 -0
  41. package/dist/auto-handoff.js +40 -16
  42. package/dist/auto-handoff.js.map +1 -1
  43. package/dist/cli/sync-memory.js +31 -36
  44. package/dist/cli/sync-memory.js.map +1 -1
  45. package/dist/config.js +4 -1
  46. package/dist/config.js.map +1 -1
  47. package/dist/env-agent.d.ts +10 -0
  48. package/dist/env-agent.js +14 -0
  49. package/dist/env-agent.js.map +1 -0
  50. package/dist/hydration.js +54 -3
  51. package/dist/hydration.js.map +1 -1
  52. package/dist/index.js +229 -113
  53. package/dist/index.js.map +1 -1
  54. package/dist/local-mirror.d.ts +5 -0
  55. package/dist/local-mirror.js +72 -14
  56. package/dist/local-mirror.js.map +1 -1
  57. package/dist/oauth-credentials.d.ts +14 -0
  58. package/dist/oauth-credentials.js +35 -0
  59. package/dist/oauth-credentials.js.map +1 -0
  60. package/dist/pipeline/bulk-store.d.ts +46 -0
  61. package/dist/pipeline/bulk-store.js +165 -0
  62. package/dist/pipeline/bulk-store.js.map +1 -0
  63. package/dist/pipeline/existing-hashes.d.ts +20 -0
  64. package/dist/pipeline/existing-hashes.js +111 -0
  65. package/dist/pipeline/existing-hashes.js.map +1 -0
  66. package/dist/pipeline/ingestion.d.ts +8 -4
  67. package/dist/pipeline/ingestion.js +36 -8
  68. package/dist/pipeline/ingestion.js.map +1 -1
  69. package/dist/telemetry.d.ts +22 -0
  70. package/dist/telemetry.js +28 -0
  71. package/dist/telemetry.js.map +1 -0
  72. package/dist/tool-args.d.ts +52 -0
  73. package/dist/tool-args.js +78 -0
  74. package/dist/tool-args.js.map +1 -0
  75. package/dist/trust-ledger.js +2 -2
  76. package/dist/trust-ledger.js.map +1 -1
  77. package/package.json +9 -4
@@ -0,0 +1,201 @@
1
+ import { createServer } from 'node:http';
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, resolve } from 'path';
6
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
7
+ import { generateAndSaveKeypair } from '../key-auth.js';
8
+ /**
9
+ * Regression: mid-session TOKEN_EXPIRED failures (CORBOT-A5BBD580).
10
+ *
11
+ * The MCP checked credential expiry only when loading credentials.json at
12
+ * startup; ensureCredentials() returned the cached creds forever after, and
13
+ * no TOKEN_EXPIRED response ever cleared them. A session starting inside the
14
+ * final hours of the 7-day token life expired mid-session and every memory
15
+ * write failed until the process was restarted — observed 2026-05-24,
16
+ * 2026-05-31, 2026-06-09.
17
+ *
18
+ * After the fix:
19
+ * 1. ensureCredentials() treats creds expiring within a safety margin as
20
+ * stale and silently re-runs key auth (challenge → did-prove).
21
+ * 2. A TOKEN_EXPIRED / INVALID_TOKEN response from any tool call clears
22
+ * the cached creds, re-auths, and retries the call once.
23
+ *
24
+ * Both scenarios drive the real binary against a fake AIS server, with a
25
+ * key file present in the fake HOME so re-auth is silent (no device flow).
26
+ */
27
+ const OLD_TOKEN = mintJwt('old-session-token');
28
+ const NEW_TOKEN = mintJwt('fresh-after-reauth');
29
+ function mintJwt(marker) {
30
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
31
+ const payload = Buffer.from(JSON.stringify({ sub: 'user-1', user_id: 'user-1', tenant_id: 'tenant-1', marker })).toString('base64url');
32
+ return `${header}.${payload}.fakesig-${marker}`;
33
+ }
34
+ describe('aismemory MCP token-expiry re-auth', () => {
35
+ let fakeAis;
36
+ let port = 0;
37
+ let didProveCalls = 0;
38
+ const agentTokensSeen = [];
39
+ beforeAll(async () => {
40
+ fakeAis = createServer((req, res) => {
41
+ req.on('data', () => { });
42
+ req.on('end', () => {
43
+ const auth = (req.headers['authorization'] ?? '').replace('Bearer ', '');
44
+ if (req.url === '/v1/auth/challenge' && req.method === 'POST') {
45
+ res.writeHead(200, { 'content-type': 'application/json' });
46
+ res.end(JSON.stringify({
47
+ success: true,
48
+ data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
49
+ }));
50
+ return;
51
+ }
52
+ if (req.url === '/v1/auth/did-prove' && req.method === 'POST') {
53
+ didProveCalls += 1;
54
+ res.writeHead(200, { 'content-type': 'application/json' });
55
+ res.end(JSON.stringify({
56
+ success: true,
57
+ data: {
58
+ bearerToken: NEW_TOKEN,
59
+ expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(),
60
+ },
61
+ }));
62
+ return;
63
+ }
64
+ if (req.url?.startsWith('/v1/agents') && req.method === 'GET') {
65
+ agentTokensSeen.push(auth);
66
+ if (auth !== NEW_TOKEN) {
67
+ res.writeHead(401, { 'content-type': 'application/json' });
68
+ res.end(JSON.stringify({ success: false, error: { code: 'TOKEN_EXPIRED', message: 'Token expired' } }));
69
+ return;
70
+ }
71
+ if (req.url === '/v1/agents') {
72
+ res.writeHead(200, { 'content-type': 'application/json' });
73
+ res.end(JSON.stringify({
74
+ success: true,
75
+ data: { agents: [{ id: 'agent-1', name: 'TestAgent', status: 'active' }] },
76
+ }));
77
+ return;
78
+ }
79
+ if (req.url === '/v1/agents/agent-1') {
80
+ res.writeHead(200, { 'content-type': 'application/json' });
81
+ res.end(JSON.stringify({
82
+ success: true,
83
+ data: { id: 'agent-1', name: 'TestAgent', status: 'active' },
84
+ }));
85
+ return;
86
+ }
87
+ }
88
+ // Hydration / memory refresh / telemetry — irrelevant here.
89
+ res.writeHead(404, { 'content-type': 'application/json' });
90
+ res.end(JSON.stringify({ success: false, error: { code: 'NOT_FOUND' } }));
91
+ });
92
+ });
93
+ await new Promise((r) => {
94
+ fakeAis.listen(0, '127.0.0.1', () => {
95
+ const addr = fakeAis.address();
96
+ if (addr && typeof addr === 'object')
97
+ port = addr.port;
98
+ r();
99
+ });
100
+ });
101
+ });
102
+ afterAll(async () => {
103
+ await new Promise((r) => fakeAis?.close(() => r()));
104
+ });
105
+ beforeEach(() => {
106
+ didProveCalls = 0;
107
+ agentTokensSeen.length = 0;
108
+ });
109
+ async function runWhoami(credsExpiresAt) {
110
+ const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
111
+ if (!existsSync(distPath)) {
112
+ throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
113
+ }
114
+ const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-reauth-'));
115
+ const aismemDir = join(fakeHome, '.aismemory');
116
+ mkdirSync(aismemDir, { recursive: true });
117
+ writeFileSync(join(aismemDir, 'credentials.json'), JSON.stringify({
118
+ token: OLD_TOKEN,
119
+ agentId: 'agent-1',
120
+ tenantId: 'tenant-1',
121
+ expiresAt: credsExpiresAt,
122
+ }));
123
+ await generateAndSaveKeypair({
124
+ userId: 'user-1',
125
+ userDid: 'did:web:test:users:user-1',
126
+ keysDir: join(aismemDir, 'keys'),
127
+ });
128
+ let proc = null;
129
+ try {
130
+ proc = spawn('node', [distPath], {
131
+ env: { ...process.env, HOME: fakeHome, AIS_URL: `http://127.0.0.1:${port}` },
132
+ stdio: ['pipe', 'pipe', 'pipe'],
133
+ });
134
+ let stdoutBuf = '';
135
+ proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
136
+ proc.stderr.on('data', () => { });
137
+ proc.stdin.write(JSON.stringify({
138
+ jsonrpc: '2.0', id: 1, method: 'initialize',
139
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'reauth-test', version: '0.0.0' } },
140
+ }) + '\n');
141
+ await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
142
+ proc.stdin.write(JSON.stringify({
143
+ jsonrpc: '2.0', id: 2, method: 'tools/call',
144
+ params: { name: 'whoami', arguments: {} },
145
+ }) + '\n');
146
+ await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'whoami response');
147
+ return extractResponseText(stdoutBuf, 2);
148
+ }
149
+ finally {
150
+ proc?.kill();
151
+ }
152
+ }
153
+ it('re-auths via key file and retries once when AIS rejects the stored token', async () => {
154
+ // Creds look fresh by clock (30 days out) but the server rejects them —
155
+ // covers server-side rotation/expiry discovered only on use.
156
+ const response = await runWhoami(new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString());
157
+ expect(didProveCalls).toBe(1);
158
+ expect(response).toContain('TestAgent');
159
+ expect(response).not.toContain('TOKEN_EXPIRED');
160
+ }, 30000);
161
+ it('refuses near-expiry cached creds and re-auths before using them', async () => {
162
+ // Creds expire in 60s — inside the safety margin. The old token must
163
+ // never reach the API.
164
+ const response = await runWhoami(new Date(Date.now() + 60 * 1000).toISOString());
165
+ expect(didProveCalls).toBe(1);
166
+ expect(agentTokensSeen).not.toContain(OLD_TOKEN);
167
+ expect(response).toContain('TestAgent');
168
+ }, 30000);
169
+ });
170
+ function waitFor(predicate, ms, label) {
171
+ return new Promise((resolveOuter, reject) => {
172
+ const start = Date.now();
173
+ const tick = () => {
174
+ if (predicate()) {
175
+ resolveOuter();
176
+ return;
177
+ }
178
+ if (Date.now() - start > ms) {
179
+ reject(new Error(`timeout waiting for ${label}`));
180
+ return;
181
+ }
182
+ setTimeout(tick, 50);
183
+ };
184
+ tick();
185
+ });
186
+ }
187
+ function extractResponseText(buf, id) {
188
+ for (const line of buf.split('\n')) {
189
+ if (!line.includes(`"id":${id}`))
190
+ continue;
191
+ try {
192
+ const parsed = JSON.parse(line);
193
+ const text = parsed.result?.content?.[0]?.text;
194
+ if (typeof text === 'string')
195
+ return text;
196
+ }
197
+ catch { /* malformed line; skip */ }
198
+ }
199
+ return '';
200
+ }
201
+ //# sourceMappingURL=token-expiry-reauth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-expiry-reauth.test.js","sourceRoot":"","sources":["../../src/__tests__/token-expiry-reauth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { MAX_MEMORY_CONTENT_LENGTH, parseAgentLoadArgs, parseEmptyToolArgs, parseHandoffArgs, parseRecallArgs, parseRememberArgs, } from '../tool-args.js';
3
+ describe('parseRememberArgs', () => {
4
+ it('accepts valid remember payload', () => {
5
+ const parsed = parseRememberArgs({
6
+ content: 'hello',
7
+ type: 'fact',
8
+ importance: 0.8,
9
+ });
10
+ expect(parsed).toEqual({ content: 'hello', type: 'fact', importance: 0.8 });
11
+ });
12
+ it('applies defaults for optional fields', () => {
13
+ expect(parseRememberArgs({ content: 'x' })).toEqual({
14
+ content: 'x',
15
+ type: 'context',
16
+ importance: 0.5,
17
+ });
18
+ });
19
+ it('rejects missing content', () => {
20
+ expect(() => parseRememberArgs({})).toThrow(/content is required/);
21
+ });
22
+ it('rejects invalid memory type', () => {
23
+ expect(() => parseRememberArgs({ content: 'x', type: 'invalid' })).toThrow();
24
+ });
25
+ it('rejects importance outside 0-1', () => {
26
+ expect(() => parseRememberArgs({ content: 'x', importance: 2 })).toThrow();
27
+ });
28
+ it('rejects oversized content', () => {
29
+ expect(() => parseRememberArgs({ content: 'x'.repeat(MAX_MEMORY_CONTENT_LENGTH + 1) })).toThrow(/exceeds maximum length/);
30
+ });
31
+ });
32
+ describe('parseRecallArgs', () => {
33
+ it('requires query', () => {
34
+ expect(() => parseRecallArgs({})).toThrow(/query is required/);
35
+ });
36
+ it('accepts query with optional limit and type', () => {
37
+ expect(parseRecallArgs({ query: 'cats', limit: 5, type: 'fact' })).toEqual({
38
+ query: 'cats',
39
+ limit: 5,
40
+ type: 'fact',
41
+ });
42
+ });
43
+ it('rejects limit above MAX_QUERY_LIMIT', () => {
44
+ expect(() => parseRecallArgs({ query: 'x', limit: 101 })).toThrow();
45
+ });
46
+ it('rejects oversized query', () => {
47
+ expect(() => parseRecallArgs({ query: 'q'.repeat(MAX_MEMORY_CONTENT_LENGTH + 1) })).toThrow(/exceeds maximum length/);
48
+ });
49
+ });
50
+ describe('parseHandoffArgs', () => {
51
+ it('accepts empty handoff args', () => {
52
+ expect(parseHandoffArgs({})).toEqual({});
53
+ });
54
+ it('accepts summary and keyLearnings', () => {
55
+ expect(parseHandoffArgs({ summary: 'done', keyLearnings: ['a', 'b'] })).toEqual({ summary: 'done', keyLearnings: ['a', 'b'] });
56
+ });
57
+ it('rejects too many keyLearnings', () => {
58
+ expect(() => parseHandoffArgs({ keyLearnings: Array.from({ length: 101 }, () => 'x') })).toThrow(/maximum of 100/);
59
+ });
60
+ });
61
+ describe('parseAgentLoadArgs', () => {
62
+ it('requires non-empty query', () => {
63
+ expect(() => parseAgentLoadArgs({})).toThrow(/query is required/);
64
+ expect(() => parseAgentLoadArgs({ query: ' ' })).toThrow(/query is required/);
65
+ });
66
+ it('trims whitespace from query', () => {
67
+ expect(parseAgentLoadArgs({ query: ' Corbot ' })).toEqual({ query: 'Corbot' });
68
+ });
69
+ });
70
+ describe('parseEmptyToolArgs', () => {
71
+ it('accepts empty object for no-arg tools', () => {
72
+ expect(parseEmptyToolArgs({})).toEqual({});
73
+ });
74
+ it('rejects unexpected keys', () => {
75
+ expect(() => parseEmptyToolArgs({ extra: true })).toThrow();
76
+ });
77
+ });
78
+ //# sourceMappingURL=tool-args.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-args.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-args.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Credential staleness + auth-error detection for the MCP re-auth loop.
3
+ *
4
+ * key-auth.ts promises: "When the JWT expires (or AIS rotates JWT_SECRET)
5
+ * the client silently re-runs challenge/prove. The user notices nothing."
6
+ * These helpers are the detection half of that promise (CORBOT-A5BBD580):
7
+ * ensureCredentials() uses isCredsStale() to refuse near-expiry cached
8
+ * creds, and the tool dispatcher uses isAuthErrorResult() to clear creds
9
+ * and retry once when AIS rejects a token mid-session.
10
+ */
11
+ /**
12
+ * Safety margin before the recorded expiry at which cached credentials are
13
+ * treated as stale. Five minutes comfortably covers clock skew between the
14
+ * client and AIS plus the duration of any single tool call.
15
+ */
16
+ export declare const CREDS_STALENESS_MARGIN_MS: number;
17
+ /**
18
+ * True when credentials expiring at `expiresAt` should no longer be used at
19
+ * time `nowMs`. Unparseable expiry timestamps are treated as stale — a
20
+ * re-auth is cheap, silently using a token of unknown validity is not.
21
+ */
22
+ export declare function isCredsStale(expiresAt: string, nowMs: number, marginMs?: number): boolean;
23
+ /**
24
+ * True when an AIS response body is a token-rejection error
25
+ * (TOKEN_EXPIRED or INVALID_TOKEN) — the signal to re-auth and retry.
26
+ */
27
+ export declare function isAuthErrorResult(result: unknown): boolean;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Credential staleness + auth-error detection for the MCP re-auth loop.
3
+ *
4
+ * key-auth.ts promises: "When the JWT expires (or AIS rotates JWT_SECRET)
5
+ * the client silently re-runs challenge/prove. The user notices nothing."
6
+ * These helpers are the detection half of that promise (CORBOT-A5BBD580):
7
+ * ensureCredentials() uses isCredsStale() to refuse near-expiry cached
8
+ * creds, and the tool dispatcher uses isAuthErrorResult() to clear creds
9
+ * and retry once when AIS rejects a token mid-session.
10
+ */
11
+ /**
12
+ * Safety margin before the recorded expiry at which cached credentials are
13
+ * treated as stale. Five minutes comfortably covers clock skew between the
14
+ * client and AIS plus the duration of any single tool call.
15
+ */
16
+ export const CREDS_STALENESS_MARGIN_MS = 5 * 60 * 1000;
17
+ /**
18
+ * True when credentials expiring at `expiresAt` should no longer be used at
19
+ * time `nowMs`. Unparseable expiry timestamps are treated as stale — a
20
+ * re-auth is cheap, silently using a token of unknown validity is not.
21
+ */
22
+ export function isCredsStale(expiresAt, nowMs, marginMs = CREDS_STALENESS_MARGIN_MS) {
23
+ const expiresMs = Date.parse(expiresAt);
24
+ if (Number.isNaN(expiresMs))
25
+ return true;
26
+ return expiresMs - nowMs <= marginMs;
27
+ }
28
+ /**
29
+ * True when an AIS response body is a token-rejection error
30
+ * (TOKEN_EXPIRED or INVALID_TOKEN) — the signal to re-auth and retry.
31
+ */
32
+ export function isAuthErrorResult(result) {
33
+ if (typeof result !== 'object' || result === null)
34
+ return false;
35
+ const r = result;
36
+ if (r.success !== false)
37
+ return false;
38
+ const code = r.error?.code;
39
+ return code === 'TOKEN_EXPIRED' || code === 'INVALID_TOKEN';
40
+ }
41
+ //# sourceMappingURL=auth-staleness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-staleness.js","sourceRoot":"","sources":["../src/auth-staleness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEvD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,SAAiB,EACjB,KAAa,EACb,WAAmB,yBAAyB;IAE5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,SAAS,GAAG,KAAK,IAAI,QAAQ,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,CAAC,GAAG,MAA0D,CAAC;IACrE,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC;IAC3B,OAAO,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,eAAe,CAAC;AAC9D,CAAC"}
@@ -1,9 +1,11 @@
1
1
  export declare class ActivityTracker {
2
2
  private toolCounts;
3
3
  private storedMemories;
4
+ private storedMemoryCount;
4
5
  recordToolUse(toolName: string): void;
5
6
  recordMemoryStored(content: string): void;
6
7
  getToolCounts(): Record<string, number>;
8
+ getStoredMemoryCount(): number;
7
9
  getStoredMemories(): string[];
8
10
  hasActivity(): boolean;
9
11
  }
@@ -1,15 +1,30 @@
1
+ /** Max verbatim snippets kept for handoff keyLearnings (FIFO). */
2
+ const MAX_STORED_MEMORY_SNIPPETS = 10;
3
+ /** Max chars per snippet to bound handoff payload size. */
4
+ const MAX_STORED_MEMORY_SNIPPET_CHARS = 512;
1
5
  export class ActivityTracker {
2
6
  toolCounts = {};
3
7
  storedMemories = [];
8
+ storedMemoryCount = 0;
4
9
  recordToolUse(toolName) {
5
10
  this.toolCounts[toolName] = (this.toolCounts[toolName] ?? 0) + 1;
6
11
  }
7
12
  recordMemoryStored(content) {
8
- this.storedMemories.push(content);
13
+ this.storedMemoryCount++;
14
+ const snippet = content.length > MAX_STORED_MEMORY_SNIPPET_CHARS
15
+ ? `${content.slice(0, MAX_STORED_MEMORY_SNIPPET_CHARS)}…`
16
+ : content;
17
+ this.storedMemories.push(snippet);
18
+ if (this.storedMemories.length > MAX_STORED_MEMORY_SNIPPETS) {
19
+ this.storedMemories.shift();
20
+ }
9
21
  }
10
22
  getToolCounts() {
11
23
  return { ...this.toolCounts };
12
24
  }
25
+ getStoredMemoryCount() {
26
+ return this.storedMemoryCount;
27
+ }
13
28
  getStoredMemories() {
14
29
  return [...this.storedMemories];
15
30
  }
@@ -24,30 +39,39 @@ export function buildHandoffPayload(tracker) {
24
39
  const toolParts = Object.entries(tracker.getToolCounts())
25
40
  .map(([name, count]) => `${name} (${count})`)
26
41
  .join(', ');
42
+ const memoryCount = tracker.getStoredMemoryCount();
27
43
  const memories = tracker.getStoredMemories();
28
- const summary = `Used tools: ${toolParts}.${memories.length > 0 ? ` Stored ${memories.length} memories.` : ''}`;
44
+ const summary = `Used tools: ${toolParts}.${memoryCount > 0 ? ` Stored ${memoryCount} memories.` : ''}`;
29
45
  return { summary, keyLearnings: memories };
30
46
  }
47
+ const HANDOFF_FETCH_TIMEOUT_MS = 5_000;
31
48
  export function setupAutoHandoff(aisUrl, agentId, token, tracker, onBeforeExit) {
32
- const doHandoff = () => {
49
+ let exiting = false;
50
+ const doHandoff = async () => {
51
+ if (exiting)
52
+ return;
53
+ exiting = true;
33
54
  if (!tracker.hasActivity())
34
55
  return;
35
56
  const payload = buildHandoffPayload(tracker);
36
- fetch(`${aisUrl}/v1/agents/${agentId}/handoff`, {
37
- method: 'POST',
38
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
39
- body: JSON.stringify(payload),
40
- }).catch(() => { }); // Best-effort
57
+ try {
58
+ await fetch(`${aisUrl}/v1/agents/${agentId}/handoff`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
61
+ body: JSON.stringify(payload),
62
+ signal: AbortSignal.timeout(HANDOFF_FETCH_TIMEOUT_MS),
63
+ });
64
+ }
65
+ catch {
66
+ // Best-effort — still exit if POST fails or times out
67
+ }
41
68
  if (onBeforeExit)
42
69
  onBeforeExit();
43
70
  };
44
- process.on('SIGTERM', () => {
45
- doHandoff();
46
- process.exit(0);
47
- });
48
- process.on('SIGINT', () => {
49
- doHandoff();
50
- process.exit(0);
51
- });
71
+ const onSignal = () => {
72
+ void doHandoff().finally(() => process.exit(0));
73
+ };
74
+ process.on('SIGTERM', onSignal);
75
+ process.on('SIGINT', onSignal);
52
76
  }
53
77
  //# sourceMappingURL=auto-handoff.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auto-handoff.js","sourceRoot":"","sources":["../src/auto-handoff.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,eAAe;IAClB,UAAU,GAA2B,EAAE,CAAC;IACxC,cAAc,GAAa,EAAE,CAAC;IAEtC,aAAa,CAAC,QAAgB;QAC5B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;IAED,kBAAkB,CAAC,OAAe;QAChC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,aAAa;QACX,OAAO,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IAChC,CAAC;IAED,iBAAiB;QACf,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAED,WAAW;QACT,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACjD,CAAC;CACF;AAOD,MAAM,UAAU,mBAAmB,CAAC,OAAwB;IAC1D,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3B,OAAO,EAAE,OAAO,EAAE,uCAAuC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;SACtD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;SAC5C,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,eAAe,SAAS,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,QAAQ,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAChH,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,OAAe,EACf,KAAa,EACb,OAAwB,EACxB,YAAyB;IAEzB,MAAM,SAAS,GAAG,GAAS,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAAE,OAAO;QACnC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC7C,KAAK,CAAC,GAAG,MAAM,cAAc,OAAO,UAAU,EAAE;YAC9C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;YACjF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,cAAc;QAClC,IAAI,YAAY;YAAE,YAAY,EAAE,CAAC;IACnC,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"auto-handoff.js","sourceRoot":"","sources":["../src/auto-handoff.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,8 @@ import { join, resolve } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { createInterface } from 'node:readline/promises';
5
5
  import { runIngestion } from '../pipeline/ingestion.js';
6
+ import { loadExistingHashes } from '../pipeline/existing-hashes.js';
7
+ import { bulkStoreViaAis, singleWriteAllViaAis } from '../pipeline/bulk-store.js';
6
8
  import { LocalClaudeSource } from '../sources/local-claude.js';
7
9
  import { SyncConfig } from '../config.js';
8
10
  import { TrustLedger } from '../trust-ledger.js';
@@ -11,7 +13,13 @@ import { interactiveReview, bulkSelectAll } from '../review/cli-review.js';
11
13
  import { resolveScope } from '../pipeline/scope-resolver.js';
12
14
  import { readClaudeMemoryTree } from './read-claude-memory-tree.js';
13
15
  function parseArgs(argv) {
14
- const args = { source: 'claude-local', dryRun: false, saveScope: false };
16
+ const args = {
17
+ source: 'claude-local',
18
+ dryRun: false,
19
+ saveScope: false,
20
+ acceptAll: false,
21
+ singleWrite: false,
22
+ };
15
23
  for (let i = 0; i < argv.length; i++) {
16
24
  if (argv[i] === '--source' && argv[i + 1]) {
17
25
  args.source = argv[++i];
@@ -25,6 +33,12 @@ function parseArgs(argv) {
25
33
  else if (argv[i] === '--save-scope') {
26
34
  args.saveScope = true;
27
35
  }
36
+ else if (argv[i] === '--accept-all') {
37
+ args.acceptAll = true;
38
+ }
39
+ else if (argv[i] === '--single-write') {
40
+ args.singleWrite = true;
41
+ }
28
42
  }
29
43
  return args;
30
44
  }
@@ -33,38 +47,6 @@ function getAdapter(id) {
33
47
  return new LocalClaudeSource();
34
48
  throw new Error(`Unknown source: ${id}. Phase 1 supports only 'claude-local'.`);
35
49
  }
36
- async function bulkStoreViaAis(agentId, drafts) {
37
- const apiBase = process.env['AIS_DOMAIN']
38
- ? `https://${process.env['AIS_DOMAIN']}`
39
- : 'https://ais.agentsandswarms.ai';
40
- const apiKey = process.env['AIS_SERVICE_KEY'] ?? process.env['AIS_API_KEY'];
41
- const tenantId = process.env['AIS_TENANT_ID'];
42
- if (!apiKey || !tenantId)
43
- throw new Error('AIS_API_KEY and AIS_TENANT_ID must be set');
44
- const body = {
45
- memories: drafts.map((d) => ({
46
- content: d.content,
47
- type: d.type,
48
- importance: d.importance,
49
- metadata: { provenance: d.provenance, trustScoreHint: d.trustScore },
50
- })),
51
- };
52
- const res = await fetch(`${apiBase}/v1/agents/${agentId}/memory/bulk`, {
53
- method: 'POST',
54
- headers: {
55
- 'Content-Type': 'application/json',
56
- 'x-api-key': apiKey,
57
- 'x-tenant-id': tenantId,
58
- },
59
- body: JSON.stringify(body),
60
- });
61
- if (!res.ok) {
62
- const bodyText = await res.text().catch(() => '');
63
- throw new Error(`AIS bulk store failed: HTTP ${res.status} ${res.statusText}${bodyText ? ` — ${bodyText.slice(0, 500)}` : ''}`);
64
- }
65
- const json = (await res.json());
66
- return { created: json.data?.created ?? [], failed: json.data?.failed ?? [] };
67
- }
68
50
  async function main() {
69
51
  const args = parseArgs(process.argv.slice(2));
70
52
  const agentId = process.env['AIS_AGENT_ID'];
@@ -112,19 +94,32 @@ async function main() {
112
94
  scope.label,
113
95
  ].join('|');
114
96
  const firstSyncForScope = !ledger.isTrusted(agentId, adapter.id, scopeKey);
115
- const review = async (drafts) => args.dryRun
97
+ const review = async (drafts) => args.dryRun || args.acceptAll
116
98
  ? bulkSelectAll(drafts)
117
99
  : interactiveReview(drafts, {
118
100
  sourceId: adapter.id,
119
101
  scopeLabel: scope.label,
120
102
  firstSyncForScope,
121
103
  });
104
+ const apiKey = process.env['AIS_SERVICE_KEY'] ?? process.env['AIS_API_KEY'];
105
+ const tenantId = process.env['AIS_TENANT_ID'];
106
+ const apiBase = process.env['AIS_DOMAIN']
107
+ ? `https://${process.env['AIS_DOMAIN']}`
108
+ : 'https://ais.agentsandswarms.ai';
122
109
  const bulkStore = args.dryRun
123
110
  ? async (drafts) => ({
124
111
  created: drafts.map((_, i) => ({ index: i, id: `dry-run-${i}` })),
125
112
  failed: [],
126
113
  })
127
- : (drafts) => bulkStoreViaAis(agentId, drafts);
114
+ : (() => {
115
+ if (!apiKey || !tenantId)
116
+ throw new Error('AIS_API_KEY and AIS_TENANT_ID must be set');
117
+ const creds = { apiBase, apiKey, tenantId };
118
+ return args.singleWrite
119
+ ? (drafts) => singleWriteAllViaAis(agentId, drafts, creds)
120
+ : (drafts) => bulkStoreViaAis(agentId, drafts, creds);
121
+ })();
122
+ const existingHashes = await loadExistingHashes(agentId, mirror, apiKey && tenantId ? { apiBase, apiKey, tenantId } : undefined);
128
123
  const result = await runIngestion({
129
124
  adapter,
130
125
  rawSourceData,
@@ -133,7 +128,7 @@ async function main() {
133
128
  config,
134
129
  ledger,
135
130
  mirror,
136
- existingHashes: new Set(),
131
+ existingHashes,
137
132
  bulkStore,
138
133
  review,
139
134
  });