aismemory 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -11
- package/dist/__tests__/agent-key-mode.test.js +72 -7
- package/dist/__tests__/agent-key-mode.test.js.map +1 -1
- package/dist/__tests__/ais-http.test.js +32 -0
- package/dist/__tests__/ais-http.test.js.map +1 -1
- package/dist/__tests__/credential-isolation.test.js +117 -3
- package/dist/__tests__/credential-isolation.test.js.map +1 -1
- package/dist/__tests__/device-flow-gate-e2e.test.js +5 -0
- package/dist/__tests__/device-flow-gate-e2e.test.js.map +1 -1
- package/dist/__tests__/doctor.test.js +1 -1
- package/dist/__tests__/doctor.test.js.map +1 -1
- package/dist/__tests__/mcp-tools.test.d.ts +1 -0
- package/dist/__tests__/mcp-tools.test.js +155 -0
- package/dist/__tests__/mcp-tools.test.js.map +1 -0
- package/dist/__tests__/memory-pull.test.d.ts +1 -0
- package/dist/__tests__/memory-pull.test.js +380 -0
- package/dist/__tests__/memory-pull.test.js.map +1 -0
- package/dist/__tests__/pending-device-flow-e2e.test.d.ts +19 -0
- package/dist/__tests__/pending-device-flow-e2e.test.js +159 -0
- package/dist/__tests__/pending-device-flow-e2e.test.js.map +1 -0
- package/dist/__tests__/pending-device-flow.test.d.ts +15 -0
- package/dist/__tests__/pending-device-flow.test.js +108 -0
- package/dist/__tests__/pending-device-flow.test.js.map +1 -0
- package/dist/__tests__/readme-quickstart-default-ais-url.test.d.ts +1 -0
- package/dist/__tests__/readme-quickstart-default-ais-url.test.js +80 -0
- package/dist/__tests__/readme-quickstart-default-ais-url.test.js.map +1 -0
- package/dist/__tests__/remember-duplicate-truth.test.d.ts +13 -0
- package/dist/__tests__/remember-duplicate-truth.test.js +106 -0
- package/dist/__tests__/remember-duplicate-truth.test.js.map +1 -0
- package/dist/__tests__/startup-credential-warning-e2e.test.d.ts +1 -0
- package/dist/__tests__/startup-credential-warning-e2e.test.js +102 -0
- package/dist/__tests__/startup-credential-warning-e2e.test.js.map +1 -0
- package/dist/__tests__/tool-args.test.js +10 -0
- package/dist/__tests__/tool-args.test.js.map +1 -1
- package/dist/__tests__/version-check.test.d.ts +1 -0
- package/dist/__tests__/version-check.test.js +349 -0
- package/dist/__tests__/version-check.test.js.map +1 -0
- package/dist/agent-key-mode.d.ts +1 -1
- package/dist/agent-key-mode.js +30 -10
- package/dist/agent-key-mode.js.map +1 -1
- package/dist/ais-http-error.d.ts +22 -0
- package/dist/ais-http-error.js +42 -0
- package/dist/ais-http-error.js.map +1 -1
- package/dist/ais-http.js +31 -13
- package/dist/ais-http.js.map +1 -1
- package/dist/cli/doctor.d.ts +16 -0
- package/dist/cli/doctor.js +64 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/pull-memory.d.ts +17 -0
- package/dist/cli/pull-memory.js +141 -0
- package/dist/cli/pull-memory.js.map +1 -0
- package/dist/index.js +107 -382
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +78 -0
- package/dist/mcp/tools.js +433 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/memory-pull.d.ts +114 -0
- package/dist/memory-pull.js +171 -0
- package/dist/memory-pull.js.map +1 -0
- package/dist/pending-device-flow.d.ts +49 -0
- package/dist/pending-device-flow.js +96 -0
- package/dist/pending-device-flow.js.map +1 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-args.d.ts +2 -0
- package/dist/tool-args.js +6 -0
- package/dist/tool-args.js.map +1 -1
- package/dist/version-check.d.ts +72 -0
- package/dist/version-check.js +237 -0
- package/dist/version-check.js.map +1 -0
- package/dist/workspace-key-mode.js +15 -6
- package/dist/workspace-key-mode.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the memory pull capability (CORBOT-31C43CBD).
|
|
3
|
+
*
|
|
4
|
+
* Covers the three things the backup has to get right:
|
|
5
|
+
* - pagination walks the whole corpus and terminates,
|
|
6
|
+
* - the JSON Lines file is written correctly and atomically,
|
|
7
|
+
* - the walk can only ever read the calling agent's own memories.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
10
|
+
import { mkdtempSync, rmSync, readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { pullMemories, parseBrowsePage, clampPageSize, toJsonLines, describePullFailure, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, } from '../memory-pull.js';
|
|
14
|
+
import { parseArgs, defaultBackupPath, writeBackupFile } from '../cli/pull-memory.js';
|
|
15
|
+
import { AisHttpError } from '../ais-http-error.js';
|
|
16
|
+
const AIS_URL = 'https://ais.test';
|
|
17
|
+
const AGENT = 'agent-self';
|
|
18
|
+
const KEY = 'ais_ak_test';
|
|
19
|
+
/** A corpus of `n` memories, newest first, as the browse path orders them. */
|
|
20
|
+
function makeCorpus(n) {
|
|
21
|
+
return Array.from({ length: n }, (_, i) => ({
|
|
22
|
+
id: `mem-${String(i).padStart(3, '0')}`,
|
|
23
|
+
content: `memory ${i}`,
|
|
24
|
+
type: 'fact',
|
|
25
|
+
createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1000).toISOString(),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A fake service that serves `corpus` over limit/offset, recording every
|
|
30
|
+
* request so the tests can assert on the URLs and headers actually sent.
|
|
31
|
+
*/
|
|
32
|
+
function makeFetch(corpus, recorded, options = {}) {
|
|
33
|
+
return (async (input, init) => {
|
|
34
|
+
const url = typeof input === 'string' ? input : input.toString();
|
|
35
|
+
recorded.push({ url, headers: (init?.headers ?? {}) });
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
const limit = Number(parsed.searchParams.get('limit'));
|
|
38
|
+
const offset = Number(parsed.searchParams.get('offset') ?? '0');
|
|
39
|
+
const page = corpus.slice(offset, offset + limit);
|
|
40
|
+
return new Response(JSON.stringify({
|
|
41
|
+
success: true,
|
|
42
|
+
data: {
|
|
43
|
+
memories: page,
|
|
44
|
+
totalCount: options.totalCount ?? corpus.length,
|
|
45
|
+
},
|
|
46
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function tempDir() {
|
|
50
|
+
return mkdtempSync(join(tmpdir(), 'aismemory-pull-test-'));
|
|
51
|
+
}
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
vi.unstubAllGlobals();
|
|
54
|
+
});
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Pagination
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
describe('pullMemories pagination', () => {
|
|
59
|
+
it('walks every page of a corpus larger than one page', async () => {
|
|
60
|
+
const corpus = makeCorpus(250);
|
|
61
|
+
const recorded = [];
|
|
62
|
+
const result = await pullMemories({
|
|
63
|
+
aisUrl: AIS_URL,
|
|
64
|
+
agentId: AGENT,
|
|
65
|
+
apiKey: KEY,
|
|
66
|
+
pageSize: 100,
|
|
67
|
+
fetchImpl: makeFetch(corpus, recorded),
|
|
68
|
+
});
|
|
69
|
+
expect(result.memories).toHaveLength(250);
|
|
70
|
+
expect(result.memories.map((m) => m.id)).toEqual(corpus.map((m) => m.id));
|
|
71
|
+
// 100 + 100 + 50: the short third page ends the walk, no wasted request.
|
|
72
|
+
expect(result.pagesFetched).toBe(3);
|
|
73
|
+
expect(result.truncated).toBe(false);
|
|
74
|
+
expect(result.totalCount).toBe(250);
|
|
75
|
+
});
|
|
76
|
+
it('advances the offset by a full page each time', async () => {
|
|
77
|
+
const recorded = [];
|
|
78
|
+
await pullMemories({
|
|
79
|
+
aisUrl: AIS_URL,
|
|
80
|
+
agentId: AGENT,
|
|
81
|
+
apiKey: KEY,
|
|
82
|
+
pageSize: 10,
|
|
83
|
+
fetchImpl: makeFetch(makeCorpus(25), recorded),
|
|
84
|
+
});
|
|
85
|
+
const offsets = recorded.map((r) => new URL(r.url).searchParams.get('offset'));
|
|
86
|
+
expect(offsets).toEqual(['0', '10', '20']);
|
|
87
|
+
});
|
|
88
|
+
it('stops after a single request when the corpus fits on one page', async () => {
|
|
89
|
+
const recorded = [];
|
|
90
|
+
const result = await pullMemories({
|
|
91
|
+
aisUrl: AIS_URL,
|
|
92
|
+
agentId: AGENT,
|
|
93
|
+
apiKey: KEY,
|
|
94
|
+
pageSize: 100,
|
|
95
|
+
fetchImpl: makeFetch(makeCorpus(7), recorded),
|
|
96
|
+
});
|
|
97
|
+
expect(result.pagesFetched).toBe(1);
|
|
98
|
+
expect(result.memories).toHaveLength(7);
|
|
99
|
+
});
|
|
100
|
+
it('handles an empty corpus without writing anything odd', async () => {
|
|
101
|
+
const recorded = [];
|
|
102
|
+
const result = await pullMemories({
|
|
103
|
+
aisUrl: AIS_URL,
|
|
104
|
+
agentId: AGENT,
|
|
105
|
+
apiKey: KEY,
|
|
106
|
+
fetchImpl: makeFetch([], recorded),
|
|
107
|
+
});
|
|
108
|
+
expect(result.memories).toEqual([]);
|
|
109
|
+
expect(result.pagesFetched).toBe(1);
|
|
110
|
+
expect(toJsonLines(result.memories)).toBe('');
|
|
111
|
+
});
|
|
112
|
+
it('exactly-full final page costs one extra request and still terminates', async () => {
|
|
113
|
+
const recorded = [];
|
|
114
|
+
const result = await pullMemories({
|
|
115
|
+
aisUrl: AIS_URL,
|
|
116
|
+
agentId: AGENT,
|
|
117
|
+
apiKey: KEY,
|
|
118
|
+
pageSize: 10,
|
|
119
|
+
fetchImpl: makeFetch(makeCorpus(20), recorded),
|
|
120
|
+
});
|
|
121
|
+
// Pages of 10, 10, then an empty page proves exhaustion.
|
|
122
|
+
expect(result.pagesFetched).toBe(3);
|
|
123
|
+
expect(result.memories).toHaveLength(20);
|
|
124
|
+
});
|
|
125
|
+
it('deduplicates by id when a concurrent write shifts the window', async () => {
|
|
126
|
+
// The browse path orders newest first, so a memory written mid-walk lands
|
|
127
|
+
// at offset 0 and pushes every later row down one. Without dedupe the row
|
|
128
|
+
// at the page boundary would be captured twice.
|
|
129
|
+
const corpus = makeCorpus(20);
|
|
130
|
+
const recorded = [];
|
|
131
|
+
let served = 0;
|
|
132
|
+
const shiftingFetch = (async (input) => {
|
|
133
|
+
const url = typeof input === 'string' ? input : input.toString();
|
|
134
|
+
recorded.push({ url, headers: {} });
|
|
135
|
+
const parsed = new URL(url);
|
|
136
|
+
const limit = Number(parsed.searchParams.get('limit'));
|
|
137
|
+
const offset = Number(parsed.searchParams.get('offset') ?? '0');
|
|
138
|
+
// After the first page is served, a new memory appears at the head.
|
|
139
|
+
const live = served === 0 ? corpus : [{ id: 'mem-new', content: 'written mid-walk' }, ...corpus];
|
|
140
|
+
served += 1;
|
|
141
|
+
const page = live.slice(offset, offset + limit);
|
|
142
|
+
return new Response(JSON.stringify({ success: true, data: { memories: page, totalCount: live.length } }), { status: 200 });
|
|
143
|
+
});
|
|
144
|
+
const result = await pullMemories({
|
|
145
|
+
aisUrl: AIS_URL,
|
|
146
|
+
agentId: AGENT,
|
|
147
|
+
apiKey: KEY,
|
|
148
|
+
pageSize: 10,
|
|
149
|
+
fetchImpl: shiftingFetch,
|
|
150
|
+
});
|
|
151
|
+
const ids = result.memories.map((m) => m.id);
|
|
152
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
153
|
+
// Every original memory is still captured despite the shift.
|
|
154
|
+
for (const original of corpus) {
|
|
155
|
+
expect(ids).toContain(original.id);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
it('reports progress per page', async () => {
|
|
159
|
+
const progress = [];
|
|
160
|
+
await pullMemories({
|
|
161
|
+
aisUrl: AIS_URL,
|
|
162
|
+
agentId: AGENT,
|
|
163
|
+
apiKey: KEY,
|
|
164
|
+
pageSize: 10,
|
|
165
|
+
fetchImpl: makeFetch(makeCorpus(25), []),
|
|
166
|
+
onProgress: (p) => progress.push(p.total),
|
|
167
|
+
});
|
|
168
|
+
expect(progress).toEqual([10, 20, 25]);
|
|
169
|
+
});
|
|
170
|
+
it('clamps the page size to what the server accepts', () => {
|
|
171
|
+
expect(clampPageSize(undefined)).toBe(DEFAULT_PAGE_SIZE);
|
|
172
|
+
expect(clampPageSize(5_000)).toBe(MAX_PAGE_SIZE);
|
|
173
|
+
expect(clampPageSize(0)).toBe(1);
|
|
174
|
+
expect(clampPageSize(-3)).toBe(1);
|
|
175
|
+
expect(clampPageSize(42)).toBe(42);
|
|
176
|
+
});
|
|
177
|
+
it('never asks the server for a limit above the cap', async () => {
|
|
178
|
+
const recorded = [];
|
|
179
|
+
await pullMemories({
|
|
180
|
+
aisUrl: AIS_URL,
|
|
181
|
+
agentId: AGENT,
|
|
182
|
+
apiKey: KEY,
|
|
183
|
+
pageSize: 10_000,
|
|
184
|
+
fetchImpl: makeFetch(makeCorpus(3), recorded),
|
|
185
|
+
});
|
|
186
|
+
const limit = new URL(recorded[0].url).searchParams.get('limit');
|
|
187
|
+
expect(limit).toBe(String(MAX_PAGE_SIZE));
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Own-agent-only
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
describe('pullMemories reads only its own agent', () => {
|
|
194
|
+
it('requests only the configured agent path and sends the key header', async () => {
|
|
195
|
+
const recorded = [];
|
|
196
|
+
await pullMemories({
|
|
197
|
+
aisUrl: AIS_URL,
|
|
198
|
+
agentId: AGENT,
|
|
199
|
+
apiKey: KEY,
|
|
200
|
+
pageSize: 10,
|
|
201
|
+
fetchImpl: makeFetch(makeCorpus(25), recorded),
|
|
202
|
+
});
|
|
203
|
+
expect(recorded).toHaveLength(3);
|
|
204
|
+
for (const request of recorded) {
|
|
205
|
+
expect(request.url.startsWith(`${AIS_URL}/v1/agents/${AGENT}/memory?`)).toBe(true);
|
|
206
|
+
expect(request.headers['x-agent-api-key']).toBe(KEY);
|
|
207
|
+
// No bearer token: a scoped key must never be paired with owner auth.
|
|
208
|
+
expect(request.headers['Authorization']).toBeUndefined();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
it('never sends principalAgentId, the delegated cross-agent read parameter', async () => {
|
|
212
|
+
const recorded = [];
|
|
213
|
+
await pullMemories({
|
|
214
|
+
aisUrl: AIS_URL,
|
|
215
|
+
agentId: AGENT,
|
|
216
|
+
apiKey: KEY,
|
|
217
|
+
pageSize: 10,
|
|
218
|
+
fetchImpl: makeFetch(makeCorpus(25), recorded),
|
|
219
|
+
});
|
|
220
|
+
for (const request of recorded) {
|
|
221
|
+
expect(new URL(request.url).searchParams.has('principalAgentId')).toBe(false);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
it('encodes the agent id into the path rather than letting it alter the route', async () => {
|
|
225
|
+
const recorded = [];
|
|
226
|
+
await pullMemories({
|
|
227
|
+
aisUrl: AIS_URL,
|
|
228
|
+
agentId: '../other-agent',
|
|
229
|
+
apiKey: KEY,
|
|
230
|
+
fetchImpl: makeFetch([], recorded),
|
|
231
|
+
});
|
|
232
|
+
const path = new URL(recorded[0].url).pathname;
|
|
233
|
+
expect(path).toBe('/v1/agents/..%2Fother-agent/memory');
|
|
234
|
+
expect(path).not.toContain('/other-agent/');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Response handling
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
describe('parseBrowsePage', () => {
|
|
241
|
+
it('reads memories and totalCount out of the documented shape', () => {
|
|
242
|
+
const page = parseBrowsePage({ success: true, data: { memories: [{ id: 'a' }], totalCount: 9 } });
|
|
243
|
+
expect(page.memories).toEqual([{ id: 'a' }]);
|
|
244
|
+
expect(page.totalCount).toBe(9);
|
|
245
|
+
});
|
|
246
|
+
it('omits totalCount when the server does not send one', () => {
|
|
247
|
+
const page = parseBrowsePage({ data: { memories: [] } });
|
|
248
|
+
expect(page.totalCount).toBeUndefined();
|
|
249
|
+
});
|
|
250
|
+
it('throws rather than silently returning nothing when data is missing', () => {
|
|
251
|
+
// An empty parse here would be indistinguishable from "no memories" and
|
|
252
|
+
// would overwrite a good backup with an empty file.
|
|
253
|
+
expect(() => parseBrowsePage({ success: false, error: { code: 'FORBIDDEN' } })).toThrow(/no .data. object/);
|
|
254
|
+
expect(() => parseBrowsePage('<html>gateway error</html>')).toThrow(/no .data. object/);
|
|
255
|
+
});
|
|
256
|
+
it('throws when memories is not an array or a memory has no id', () => {
|
|
257
|
+
expect(() => parseBrowsePage({ data: { memories: 'nope' } })).toThrow(/not an array/);
|
|
258
|
+
expect(() => parseBrowsePage({ data: { memories: [{ content: 'no id' }] } })).toThrow(/string .id./);
|
|
259
|
+
});
|
|
260
|
+
it('propagates a rejected read instead of writing an empty backup', async () => {
|
|
261
|
+
const failing = (async () => new Response(JSON.stringify({ success: false, error: { code: 'FORBIDDEN', message: 'nope' } }), {
|
|
262
|
+
status: 403,
|
|
263
|
+
}));
|
|
264
|
+
await expect(pullMemories({ aisUrl: AIS_URL, agentId: AGENT, apiKey: KEY, fetchImpl: failing })).rejects.toBeInstanceOf(AisHttpError);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
describe('describePullFailure', () => {
|
|
268
|
+
it('names the cause for the statuses an operator actually hits', () => {
|
|
269
|
+
expect(describePullFailure(new AisHttpError(401, '/p', null))).toMatch(/AIS_API_KEY/);
|
|
270
|
+
expect(describePullFailure(new AisHttpError(403, '/p', null))).toMatch(/own memories/);
|
|
271
|
+
expect(describePullFailure(new AisHttpError(404, '/p', null))).toMatch(/AIS_AGENT_ID/);
|
|
272
|
+
expect(describePullFailure(new AisHttpError(500, '/p', null))).toMatch(/HTTP 500/);
|
|
273
|
+
expect(describePullFailure(new Error('socket hang up'))).toBe('socket hang up');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// File output
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
describe('JSON Lines backup file', () => {
|
|
280
|
+
it('writes one complete JSON object per line, newest first', () => {
|
|
281
|
+
const memories = makeCorpus(3);
|
|
282
|
+
const text = toJsonLines(memories);
|
|
283
|
+
const lines = text.trimEnd().split('\n');
|
|
284
|
+
expect(lines).toHaveLength(3);
|
|
285
|
+
expect(text.endsWith('\n')).toBe(true);
|
|
286
|
+
for (const [i, line] of lines.entries()) {
|
|
287
|
+
expect(JSON.parse(line)).toEqual(memories[i]);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
it('keeps content with newlines on a single line', () => {
|
|
291
|
+
const text = toJsonLines([{ id: 'a', content: 'line one\nline two' }]);
|
|
292
|
+
expect(text.trimEnd().split('\n')).toHaveLength(1);
|
|
293
|
+
expect(JSON.parse(text.trimEnd())).toEqual({ id: 'a', content: 'line one\nline two' });
|
|
294
|
+
});
|
|
295
|
+
it('writes the file and creates missing parent directories', () => {
|
|
296
|
+
const dir = tempDir();
|
|
297
|
+
try {
|
|
298
|
+
const target = join(dir, 'nested', 'deeper', 'backup.jsonl');
|
|
299
|
+
writeBackupFile(target, toJsonLines(makeCorpus(2)));
|
|
300
|
+
expect(existsSync(target)).toBe(true);
|
|
301
|
+
expect(readFileSync(target, 'utf8').trimEnd().split('\n')).toHaveLength(2);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
rmSync(dir, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
it('replaces an existing backup and leaves no temporary file behind', () => {
|
|
308
|
+
const dir = tempDir();
|
|
309
|
+
try {
|
|
310
|
+
const target = join(dir, 'backup.jsonl');
|
|
311
|
+
writeFileSync(target, 'stale contents\n');
|
|
312
|
+
writeBackupFile(target, toJsonLines(makeCorpus(1)));
|
|
313
|
+
expect(readFileSync(target, 'utf8')).not.toContain('stale');
|
|
314
|
+
expect(readdirSync(dir)).toEqual(['backup.jsonl']);
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
rmSync(dir, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
it('round-trips a pull to a file that parses back to the same memories', async () => {
|
|
321
|
+
const dir = tempDir();
|
|
322
|
+
try {
|
|
323
|
+
const corpus = makeCorpus(25);
|
|
324
|
+
const result = await pullMemories({
|
|
325
|
+
aisUrl: AIS_URL,
|
|
326
|
+
agentId: AGENT,
|
|
327
|
+
apiKey: KEY,
|
|
328
|
+
pageSize: 10,
|
|
329
|
+
fetchImpl: makeFetch(corpus, []),
|
|
330
|
+
});
|
|
331
|
+
const target = join(dir, 'backup.jsonl');
|
|
332
|
+
writeBackupFile(target, toJsonLines(result.memories));
|
|
333
|
+
const parsed = readFileSync(target, 'utf8')
|
|
334
|
+
.trimEnd()
|
|
335
|
+
.split('\n')
|
|
336
|
+
.map((line) => JSON.parse(line));
|
|
337
|
+
expect(parsed).toEqual(corpus);
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
rmSync(dir, { recursive: true, force: true });
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
it('defaults to a per-agent path that is NOT the LocalMirror cache file', () => {
|
|
344
|
+
const path = defaultBackupPath(AGENT, '/home/someone');
|
|
345
|
+
expect(path).toBe(`/home/someone/.aismemory/backups/${AGENT}.jsonl`);
|
|
346
|
+
// LocalMirror owns ~/.aismemory/cache/<agentId>.jsonl, stores a different
|
|
347
|
+
// shape, and prunes it on a retention window — a backup written there
|
|
348
|
+
// would be silently deleted.
|
|
349
|
+
expect(path).not.toContain(`${'.aismemory'}/cache/`);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Argument parsing
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
describe('parseArgs', () => {
|
|
356
|
+
it('defaults to a quiet-off, full-page pull', () => {
|
|
357
|
+
expect(parseArgs([])).toEqual({ quiet: false, help: false });
|
|
358
|
+
});
|
|
359
|
+
it('reads --out, --page-size and --quiet', () => {
|
|
360
|
+
expect(parseArgs(['--out', './b.jsonl', '--page-size', '25', '--quiet'])).toEqual({
|
|
361
|
+
out: './b.jsonl',
|
|
362
|
+
pageSize: 25,
|
|
363
|
+
quiet: true,
|
|
364
|
+
help: false,
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
it('accepts --help and -h', () => {
|
|
368
|
+
expect(parseArgs(['--help']).help).toBe(true);
|
|
369
|
+
expect(parseArgs(['-h']).help).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
it('rejects a page size the server would reject', () => {
|
|
372
|
+
expect(() => parseArgs(['--page-size', '0'])).toThrow(/between 1 and/);
|
|
373
|
+
expect(() => parseArgs(['--page-size', '101'])).toThrow(/between 1 and/);
|
|
374
|
+
expect(() => parseArgs(['--page-size', 'lots'])).toThrow(/between 1 and/);
|
|
375
|
+
});
|
|
376
|
+
it('rejects an unknown option rather than ignoring it', () => {
|
|
377
|
+
expect(() => parseArgs(['--all-agents'])).toThrow(/Unknown option/);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
//# sourceMappingURL=memory-pull.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-pull.test.js","sourceRoot":"","sources":["../../src/__tests__/memory-pull.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process sign-in reuse — end-to-end (CORBOT-8F96A8EF).
|
|
3
|
+
*
|
|
4
|
+
* THE Jaire repro. Jaire runs through the published aismemory package, and
|
|
5
|
+
* its sign-in minted a new agent on every run — seven leftovers, and the same
|
|
6
|
+
* pattern behind 83 journey-probe orphans and ~723 harness agents.
|
|
7
|
+
*
|
|
8
|
+
* Cause: the server will reuse an earlier un-finalized flow's provisional
|
|
9
|
+
* agent if the client presents that flow's provisional token, but the client
|
|
10
|
+
* only ever held the token in a module-level variable. A stdio MCP server is
|
|
11
|
+
* respawned constantly, and a new process starts with that variable null, so
|
|
12
|
+
* the token was almost never sent and the server dutifully minted again.
|
|
13
|
+
*
|
|
14
|
+
* This drives the built binary as TWO SEPARATE PROCESSES sharing one home
|
|
15
|
+
* directory, against a fake Agent Identity Service that records what each
|
|
16
|
+
* sign-in presented. Process 2 must carry process 1's provisional token.
|
|
17
|
+
* Before the fix it carried nothing.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process sign-in reuse — end-to-end (CORBOT-8F96A8EF).
|
|
3
|
+
*
|
|
4
|
+
* THE Jaire repro. Jaire runs through the published aismemory package, and
|
|
5
|
+
* its sign-in minted a new agent on every run — seven leftovers, and the same
|
|
6
|
+
* pattern behind 83 journey-probe orphans and ~723 harness agents.
|
|
7
|
+
*
|
|
8
|
+
* Cause: the server will reuse an earlier un-finalized flow's provisional
|
|
9
|
+
* agent if the client presents that flow's provisional token, but the client
|
|
10
|
+
* only ever held the token in a module-level variable. A stdio MCP server is
|
|
11
|
+
* respawned constantly, and a new process starts with that variable null, so
|
|
12
|
+
* the token was almost never sent and the server dutifully minted again.
|
|
13
|
+
*
|
|
14
|
+
* This drives the built binary as TWO SEPARATE PROCESSES sharing one home
|
|
15
|
+
* directory, against a fake Agent Identity Service that records what each
|
|
16
|
+
* sign-in presented. Process 2 must carry process 1's provisional token.
|
|
17
|
+
* Before the fix it carried nothing.
|
|
18
|
+
*/
|
|
19
|
+
import { createServer } from 'node:http';
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
21
|
+
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
22
|
+
import { tmpdir } from 'node:os';
|
|
23
|
+
import { join, resolve } from 'node:path';
|
|
24
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
25
|
+
describe('aismemory sign-in reuses the provisional agent across processes', () => {
|
|
26
|
+
let server;
|
|
27
|
+
let port = 0;
|
|
28
|
+
const authorizeCalls = [];
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
server = createServer((req, res) => {
|
|
31
|
+
req.on('data', () => { });
|
|
32
|
+
req.on('end', () => {
|
|
33
|
+
if (req.url === '/v1/oauth/device/authorize' && req.method === 'POST') {
|
|
34
|
+
authorizeCalls.push({ authorization: req.headers['authorization'] });
|
|
35
|
+
const n = authorizeCalls.length;
|
|
36
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
37
|
+
res.end(JSON.stringify({
|
|
38
|
+
success: true,
|
|
39
|
+
data: {
|
|
40
|
+
device_code: `device-${n}`,
|
|
41
|
+
user_code: `TEST-${String(n).padStart(4, '0')}`,
|
|
42
|
+
verification_uri: `http://127.0.0.1:${port}/activate`,
|
|
43
|
+
verification_uri_complete: `http://127.0.0.1:${port}/activate?user_code=TEST-${n}`,
|
|
44
|
+
expires_in: 1800,
|
|
45
|
+
interval: 1,
|
|
46
|
+
agent_id: `agent-${n}`,
|
|
47
|
+
provisional_tenant_id: `tenant-${n}`,
|
|
48
|
+
provisional_token: `provisional-token-${n}`,
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (req.url === '/v1/oauth/token' && req.method === 'POST') {
|
|
54
|
+
// Nobody clicks the bond link in this test: the flow stays
|
|
55
|
+
// un-finalized, which is exactly the state whose token must survive
|
|
56
|
+
// to the next process.
|
|
57
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
58
|
+
res.end(JSON.stringify({ error: 'authorization_pending' }));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
res.writeHead(404);
|
|
62
|
+
res.end();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
await new Promise((done) => {
|
|
66
|
+
server.listen(0, '127.0.0.1', () => {
|
|
67
|
+
const addr = server.address();
|
|
68
|
+
if (addr && typeof addr === 'object')
|
|
69
|
+
port = addr.port;
|
|
70
|
+
done();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
afterAll(async () => {
|
|
75
|
+
await new Promise((r) => server?.close(() => r()));
|
|
76
|
+
});
|
|
77
|
+
it('process 2 presents the provisional token process 1 was issued', async () => {
|
|
78
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
79
|
+
if (!existsSync(distPath)) {
|
|
80
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
81
|
+
}
|
|
82
|
+
// ONE home directory, shared by both runs — the same machine, same user.
|
|
83
|
+
const home = mkdtempSync(join(tmpdir(), 'aismem-xproc-'));
|
|
84
|
+
// Pin AIS_AGENT_ID rather than inheriting it: the credentials path (and
|
|
85
|
+
// so the pending-flow path) keys off it, and a fleet seat has a real one
|
|
86
|
+
// in the ambient environment. Pinning keeps the test hermetic and makes
|
|
87
|
+
// the expected file path computable.
|
|
88
|
+
const TEST_AGENT_ID = 'xproc-test-agent';
|
|
89
|
+
const pendingFile = join(home, '.aismemory', 'agents', TEST_AGENT_ID, 'pending-device-flow.json');
|
|
90
|
+
/** Spawn the binary, make one tool call to force sign-in, then stop it. */
|
|
91
|
+
async function runOnce(waitForPersist) {
|
|
92
|
+
let proc = null;
|
|
93
|
+
try {
|
|
94
|
+
proc = spawn('node', [distPath], {
|
|
95
|
+
env: {
|
|
96
|
+
...process.env,
|
|
97
|
+
HOME: home,
|
|
98
|
+
AIS_AGENT_ID: TEST_AGENT_ID,
|
|
99
|
+
AIS_URL: `http://127.0.0.1:${port}`,
|
|
100
|
+
},
|
|
101
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
102
|
+
});
|
|
103
|
+
let out = '';
|
|
104
|
+
proc.stdout.on('data', (d) => { out += d.toString(); });
|
|
105
|
+
proc.stderr.on('data', () => { });
|
|
106
|
+
proc.stdin.write(JSON.stringify({
|
|
107
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
108
|
+
params: {
|
|
109
|
+
protocolVersion: '2024-11-05',
|
|
110
|
+
capabilities: {},
|
|
111
|
+
clientInfo: { name: 'xproc-test', version: '0.0.0' },
|
|
112
|
+
},
|
|
113
|
+
}) + '\n');
|
|
114
|
+
await waitFor(() => out.includes('"id":1'), 15000, 'initialize response');
|
|
115
|
+
const before = authorizeCalls.length;
|
|
116
|
+
proc.stdin.write(JSON.stringify({
|
|
117
|
+
jsonrpc: '2.0', id: 2, method: 'tools/call',
|
|
118
|
+
params: { name: 'whoami', arguments: {} },
|
|
119
|
+
}) + '\n');
|
|
120
|
+
// The sign-in attempt is what we are measuring, not the tool result.
|
|
121
|
+
await waitFor(() => authorizeCalls.length > before, 30000, 'device authorize call');
|
|
122
|
+
// Run 1 must not be killed the instant the SERVER logs the call: the
|
|
123
|
+
// client writes the token to disk only after it reads the response.
|
|
124
|
+
// Killing on the server-side counter raced that write and made this
|
|
125
|
+
// test pass alone but fail under a loaded full-suite run.
|
|
126
|
+
if (waitForPersist) {
|
|
127
|
+
await waitFor(() => existsSync(pendingFile), 30000, 'provisional token persisted to disk');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
proc?.kill('SIGKILL');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await runOnce(true);
|
|
136
|
+
await runOnce(false);
|
|
137
|
+
expect(authorizeCalls.length).toBeGreaterThanOrEqual(2);
|
|
138
|
+
// Run 1 is a genuine cold start: nothing to present, so it mints. Right.
|
|
139
|
+
expect(authorizeCalls[0]?.authorization).toBeUndefined();
|
|
140
|
+
// Run 2 is the bug. A brand-new process, but the SAME user and home —
|
|
141
|
+
// it must present run 1's provisional token so the server reuses that
|
|
142
|
+
// agent instead of minting an eighth Jaire.
|
|
143
|
+
expect(authorizeCalls[1]?.authorization).toBe('Bearer provisional-token-1');
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
rmSync(home, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
}, 180000);
|
|
149
|
+
});
|
|
150
|
+
async function waitFor(predicate, timeoutMs, what) {
|
|
151
|
+
const deadline = Date.now() + timeoutMs;
|
|
152
|
+
while (Date.now() < deadline) {
|
|
153
|
+
if (predicate())
|
|
154
|
+
return;
|
|
155
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Timed out after ${timeoutMs}ms waiting for ${what}`);
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=pending-device-flow-e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-device-flow-e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/pending-device-flow-e2e.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending device-flow persistence — unit tests (CORBOT-8F96A8EF).
|
|
3
|
+
*
|
|
4
|
+
* Jaire's sign-in minted a new agent on every run. The server already had the
|
|
5
|
+
* machinery to prevent it: present the provisional token from an earlier
|
|
6
|
+
* un-finalized flow and it reuses that provisional agent instead of minting
|
|
7
|
+
* another. The client just never presented one, because the token lived only
|
|
8
|
+
* in a module-level variable that is null in every freshly spawned process —
|
|
9
|
+
* and a stdio MCP server is spawned constantly.
|
|
10
|
+
*
|
|
11
|
+
* These cover the storage half. The cross-process proof (two separate node
|
|
12
|
+
* processes, one home directory, second run carries the first run's token)
|
|
13
|
+
* is in pending-device-flow-e2e.test.ts.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|