aismemory 0.7.4 → 0.8.0

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 (101) hide show
  1. package/README.md +211 -11
  2. package/dist/__tests__/agent-archive.test.d.ts +1 -0
  3. package/dist/__tests__/agent-archive.test.js +153 -0
  4. package/dist/__tests__/agent-archive.test.js.map +1 -0
  5. package/dist/__tests__/agent-key-mode.test.js +88 -7
  6. package/dist/__tests__/agent-key-mode.test.js.map +1 -1
  7. package/dist/__tests__/ais-http.test.js +32 -0
  8. package/dist/__tests__/ais-http.test.js.map +1 -1
  9. package/dist/__tests__/credential-isolation.test.js +117 -3
  10. package/dist/__tests__/credential-isolation.test.js.map +1 -1
  11. package/dist/__tests__/credential-resolution.test.js +5 -5
  12. package/dist/__tests__/credential-resolution.test.js.map +1 -1
  13. package/dist/__tests__/device-flow-gate-e2e.test.js +5 -0
  14. package/dist/__tests__/device-flow-gate-e2e.test.js.map +1 -1
  15. package/dist/__tests__/doctor.test.js +144 -79
  16. package/dist/__tests__/doctor.test.js.map +1 -1
  17. package/dist/__tests__/mcp-tools.test.d.ts +1 -0
  18. package/dist/__tests__/mcp-tools.test.js +189 -0
  19. package/dist/__tests__/mcp-tools.test.js.map +1 -0
  20. package/dist/__tests__/memory-pull.test.d.ts +1 -0
  21. package/dist/__tests__/memory-pull.test.js +380 -0
  22. package/dist/__tests__/memory-pull.test.js.map +1 -0
  23. package/dist/__tests__/memory-write-failure.test.js +4 -8
  24. package/dist/__tests__/memory-write-failure.test.js.map +1 -1
  25. package/dist/__tests__/no-registered-keys-disk-bearer-fallback.test.js +4 -7
  26. package/dist/__tests__/no-registered-keys-disk-bearer-fallback.test.js.map +1 -1
  27. package/dist/__tests__/pending-device-flow-e2e.test.d.ts +19 -0
  28. package/dist/__tests__/pending-device-flow-e2e.test.js +159 -0
  29. package/dist/__tests__/pending-device-flow-e2e.test.js.map +1 -0
  30. package/dist/__tests__/pending-device-flow.test.d.ts +15 -0
  31. package/dist/__tests__/pending-device-flow.test.js +108 -0
  32. package/dist/__tests__/pending-device-flow.test.js.map +1 -0
  33. package/dist/__tests__/provisional-register-mcp-remember.test.js +4 -8
  34. package/dist/__tests__/provisional-register-mcp-remember.test.js.map +1 -1
  35. package/dist/__tests__/readme-quickstart-default-ais-url.test.d.ts +1 -0
  36. package/dist/__tests__/readme-quickstart-default-ais-url.test.js +80 -0
  37. package/dist/__tests__/readme-quickstart-default-ais-url.test.js.map +1 -0
  38. package/dist/__tests__/reconnect-survival-e2e.test.js +3 -4
  39. package/dist/__tests__/reconnect-survival-e2e.test.js.map +1 -1
  40. package/dist/__tests__/remember-duplicate-truth.test.d.ts +13 -0
  41. package/dist/__tests__/remember-duplicate-truth.test.js +106 -0
  42. package/dist/__tests__/remember-duplicate-truth.test.js.map +1 -0
  43. package/dist/__tests__/retry-after.test.d.ts +4 -0
  44. package/dist/__tests__/retry-after.test.js +128 -0
  45. package/dist/__tests__/retry-after.test.js.map +1 -0
  46. package/dist/__tests__/startup-credential-warning-e2e.test.d.ts +1 -0
  47. package/dist/__tests__/startup-credential-warning-e2e.test.js +102 -0
  48. package/dist/__tests__/startup-credential-warning-e2e.test.js.map +1 -0
  49. package/dist/__tests__/tool-args.test.js +10 -0
  50. package/dist/__tests__/tool-args.test.js.map +1 -1
  51. package/dist/__tests__/version-check.test.d.ts +1 -0
  52. package/dist/__tests__/version-check.test.js +349 -0
  53. package/dist/__tests__/version-check.test.js.map +1 -0
  54. package/dist/agent-archive.d.ts +36 -0
  55. package/dist/agent-archive.js +69 -0
  56. package/dist/agent-archive.js.map +1 -0
  57. package/dist/agent-key-mode.d.ts +1 -1
  58. package/dist/agent-key-mode.js +56 -19
  59. package/dist/agent-key-mode.js.map +1 -1
  60. package/dist/ais-http-error.d.ts +26 -2
  61. package/dist/ais-http-error.js +52 -4
  62. package/dist/ais-http-error.js.map +1 -1
  63. package/dist/ais-http.js +46 -16
  64. package/dist/ais-http.js.map +1 -1
  65. package/dist/cli/doctor.d.ts +17 -2
  66. package/dist/cli/doctor.js +93 -14
  67. package/dist/cli/doctor.js.map +1 -1
  68. package/dist/cli/pull-memory.d.ts +17 -0
  69. package/dist/cli/pull-memory.js +141 -0
  70. package/dist/cli/pull-memory.js.map +1 -0
  71. package/dist/index.js +135 -398
  72. package/dist/index.js.map +1 -1
  73. package/dist/mcp/tools.d.ts +82 -0
  74. package/dist/mcp/tools.js +453 -0
  75. package/dist/mcp/tools.js.map +1 -0
  76. package/dist/memory-pull.d.ts +114 -0
  77. package/dist/memory-pull.js +171 -0
  78. package/dist/memory-pull.js.map +1 -0
  79. package/dist/pending-device-flow.d.ts +49 -0
  80. package/dist/pending-device-flow.js +96 -0
  81. package/dist/pending-device-flow.js.map +1 -0
  82. package/dist/retry-after.d.ts +26 -0
  83. package/dist/retry-after.js +42 -0
  84. package/dist/retry-after.js.map +1 -0
  85. package/dist/session-identity.d.ts +26 -0
  86. package/dist/session-identity.js +89 -0
  87. package/dist/session-identity.js.map +1 -0
  88. package/dist/telemetry.d.ts +1 -1
  89. package/dist/telemetry.js.map +1 -1
  90. package/dist/tool-args.d.ts +4 -0
  91. package/dist/tool-args.js +16 -0
  92. package/dist/tool-args.js.map +1 -1
  93. package/dist/version-check.d.ts +72 -0
  94. package/dist/version-check.js +237 -0
  95. package/dist/version-check.js.map +1 -0
  96. package/dist/workspace-key-mode.js +22 -6
  97. package/dist/workspace-key-mode.js.map +1 -1
  98. package/package.json +4 -3
  99. package/dist/harness-identity.d.ts +0 -17
  100. package/dist/harness-identity.js +0 -105
  101. package/dist/harness-identity.js.map +0 -1
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Memory pull — read an agent's own memories back out of the Agent Identity
3
+ * Service and into a local JSON Lines backup (CORBOT-31C43CBD).
4
+ *
5
+ * WHY THIS EXISTS
6
+ * The package could only ever push local -> Agent Identity Service
7
+ * (LocalMirror, aismemory-sync). Nothing an agent API key can call brought
8
+ * memories back down, so memories written from the Claude app or from Codex
9
+ * never reached the local copy. The two export routes do not help a key-only
10
+ * caller:
11
+ * * POST /v1/agents/:agentId/export -> 403, `export` is not in the
12
+ * agent-API-key allowlist (agentApiKeyAuth.ts ALLOWED_AGENT_SUBRESOURCES).
13
+ * * POST /v1/agents/:agentId/memory/export -> 400 "platform is required";
14
+ * it pushes to a third-party platform adapter and refuses file paths over
15
+ * HTTP, so it cannot produce a local file.
16
+ *
17
+ * WHAT THIS DOES INSTEAD
18
+ * Pages the browse path of GET /v1/agents/:agentId/memory, which IS already on
19
+ * the allowlist and already supports `limit` + `offset` pagination. No server
20
+ * change and no access scope change: this is a client that uses a route the
21
+ * key can call today.
22
+ *
23
+ * SCOPE SAFETY
24
+ * The agent id is taken from the caller's own configuration and interpolated
25
+ * into the path; the server additionally rejects any request whose path agent
26
+ * does not equal the agent the key encodes. `principalAgentId` (the delegated
27
+ * cross-agent read parameter) is never sent, so a pull can only ever read the
28
+ * caller's own memories.
29
+ */
30
+ import { AisHttpError } from './ais-http-error.js';
31
+ import { aisKeyGet } from './agent-key-mode.js';
32
+ /**
33
+ * Server-side cap on `limit` for GET /v1/agents/:agentId/memory
34
+ * (agent-identity-service `env.benchQueryLimitCap`; a larger value is a 400).
35
+ */
36
+ export const MAX_PAGE_SIZE = 100;
37
+ /** Page size used when the caller does not choose one. */
38
+ export const DEFAULT_PAGE_SIZE = 100;
39
+ /**
40
+ * Hard stop on pages fetched, so a server that never reports exhaustion cannot
41
+ * spin forever. At the maximum page size this covers 500,000 memories, an order
42
+ * of magnitude above the 50,000 the `.aismem` export format itself allows.
43
+ */
44
+ export const MAX_PAGES = 5_000;
45
+ function isRecord(value) {
46
+ return typeof value === 'object' && value !== null;
47
+ }
48
+ /**
49
+ * Pull `data.memories` and `data.totalCount` out of a browse response, rejecting
50
+ * anything that does not have the documented shape. A silently-empty parse would
51
+ * be indistinguishable from "this agent has no memories" and would write an
52
+ * empty backup over a good one.
53
+ */
54
+ export function parseBrowsePage(payload) {
55
+ if (!isRecord(payload) || !isRecord(payload['data'])) {
56
+ throw new Error('Unexpected response from the memory browse route: no `data` object. ' +
57
+ 'The Agent Identity Service may be returning an error page.');
58
+ }
59
+ const data = payload['data'];
60
+ const rawMemories = data['memories'];
61
+ if (!Array.isArray(rawMemories)) {
62
+ throw new Error('Unexpected response from the memory browse route: `data.memories` is not an array.');
63
+ }
64
+ const memories = [];
65
+ for (const entry of rawMemories) {
66
+ if (!isRecord(entry) || typeof entry['id'] !== 'string') {
67
+ throw new Error('Unexpected response from the memory browse route: a memory has no string `id`.');
68
+ }
69
+ memories.push(entry);
70
+ }
71
+ const rawTotal = data['totalCount'];
72
+ const totalCount = typeof rawTotal === 'number' && Number.isFinite(rawTotal) ? rawTotal : undefined;
73
+ return totalCount === undefined ? { memories } : { memories, totalCount };
74
+ }
75
+ /** Clamp a requested page size into the range the server accepts. */
76
+ export function clampPageSize(requested) {
77
+ if (requested === undefined || !Number.isFinite(requested))
78
+ return DEFAULT_PAGE_SIZE;
79
+ const floored = Math.floor(requested);
80
+ if (floored < 1)
81
+ return 1;
82
+ if (floored > MAX_PAGE_SIZE)
83
+ return MAX_PAGE_SIZE;
84
+ return floored;
85
+ }
86
+ /**
87
+ * Page the whole corpus for one agent, newest first.
88
+ *
89
+ * Deduplicates by memory id. This is load-bearing rather than defensive: the
90
+ * browse path orders by `createdAt` descending, so a memory written while the
91
+ * walk is in progress lands at offset 0 and shifts every later row down one,
92
+ * which would otherwise re-read a row that has already been captured.
93
+ */
94
+ export async function pullMemories(options) {
95
+ const { aisUrl, agentId, apiKey, fetchImpl, onProgress } = options;
96
+ const pageSize = clampPageSize(options.pageSize);
97
+ const seen = new Set();
98
+ const memories = [];
99
+ let offset = 0;
100
+ let pagesFetched = 0;
101
+ let totalCount;
102
+ let truncated = false;
103
+ for (;;) {
104
+ if (pagesFetched >= MAX_PAGES) {
105
+ truncated = true;
106
+ break;
107
+ }
108
+ const params = new URLSearchParams();
109
+ params.set('limit', String(pageSize));
110
+ params.set('offset', String(offset));
111
+ // Browse, not recall: no `query`, and relationship expansion off so a
112
+ // backup carries the stored memories rather than a graph walk around them.
113
+ params.set('expandRelationships', 'false');
114
+ const payload = await aisKeyGet(aisUrl, `/v1/agents/${encodeURIComponent(agentId)}/memory?${params.toString()}`, apiKey, fetchImpl);
115
+ pagesFetched += 1;
116
+ const page = parseBrowsePage(payload);
117
+ if (page.totalCount !== undefined)
118
+ totalCount = page.totalCount;
119
+ let added = 0;
120
+ for (const memory of page.memories) {
121
+ if (seen.has(memory.id))
122
+ continue;
123
+ seen.add(memory.id);
124
+ memories.push(memory);
125
+ added += 1;
126
+ }
127
+ onProgress?.(totalCount === undefined
128
+ ? { page: pagesFetched, added, total: memories.length }
129
+ : { page: pagesFetched, added, total: memories.length, totalCount });
130
+ // A short page means the corpus is exhausted. Advance by the page length
131
+ // actually returned, not by the number of NEW rows, or a shifted window
132
+ // would make the walk step backwards and never terminate.
133
+ if (page.memories.length < pageSize)
134
+ break;
135
+ offset += page.memories.length;
136
+ }
137
+ return totalCount === undefined
138
+ ? { memories, pagesFetched, truncated }
139
+ : { memories, pagesFetched, totalCount, truncated };
140
+ }
141
+ /**
142
+ * Render memories as JSON Lines: one memory object per line, in the order they
143
+ * were pulled (newest first). Always ends with a newline so the file can be
144
+ * appended to or concatenated cleanly.
145
+ */
146
+ export function toJsonLines(memories) {
147
+ if (memories.length === 0)
148
+ return '';
149
+ return `${memories.map((memory) => JSON.stringify(memory)).join('\n')}\n`;
150
+ }
151
+ /**
152
+ * Turn an out-of-scope or rejected pull into a message that names the cause,
153
+ * rather than leaving a bare HTTP status for the operator to decode.
154
+ */
155
+ export function describePullFailure(error) {
156
+ if (error instanceof AisHttpError) {
157
+ if (error.status === 401) {
158
+ return 'The Agent Identity Service rejected the agent API key (HTTP 401). Check AIS_API_KEY.';
159
+ }
160
+ if (error.status === 403) {
161
+ return ('The Agent Identity Service refused the read (HTTP 403). An agent API key may only read ' +
162
+ 'its own memories — check that AIS_AGENT_ID is the agent this key was minted for.');
163
+ }
164
+ if (error.status === 404) {
165
+ return 'No such agent in this tenant (HTTP 404). Check AIS_AGENT_ID.';
166
+ }
167
+ return `The Agent Identity Service returned HTTP ${error.status}.`;
168
+ }
169
+ return error instanceof Error ? error.message : String(error);
170
+ }
171
+ //# sourceMappingURL=memory-pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-pull.js","sourceRoot":"","sources":["../src/memory-pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pending device-flow persistence (CORBOT-8F96A8EF).
3
+ *
4
+ * The orphan bug, client side. `startDeviceFlow` can hand the server the
5
+ * provisional token from an earlier un-finalized flow, and the server will
6
+ * then reuse that provisional agent instead of minting another
7
+ * (tryReuseProvisional, CORBOT-CDDC15FA). But the only place that token
8
+ * lived was `activeFlow`, a module-level variable — process-local, and null
9
+ * in every newly spawned process. A stdio server is respawned constantly, so
10
+ * in practice the token was almost never sent and sign-in minted a fresh
11
+ * agent nearly every run. That is where Jaire's leftovers came from.
12
+ *
13
+ * So the token is persisted next to the credentials file, under the same
14
+ * 0700 directory / 0600 file permissions: it is a bearer proof-of-session
15
+ * for an unbonded agent and must not be world-readable.
16
+ *
17
+ * It is deliberately NOT part of OAuthCredentials: those are the live bonded
18
+ * session, this is a half-finished sign-in. Keeping them in separate files
19
+ * means a stale pending flow can never be mistaken for a usable credential,
20
+ * and clearing one does not disturb the other.
21
+ */
22
+ /**
23
+ * A device flow that started but was never bonded. `provisionalToken` is the
24
+ * only field the server needs; the rest is there so a human reading the file
25
+ * (or a support session) can tell what it belongs to.
26
+ */
27
+ export interface PendingDeviceFlow {
28
+ provisionalToken: string;
29
+ agentId: string;
30
+ provisionalTenantId: string;
31
+ /** ISO timestamp of when this flow was started. */
32
+ startedAt: string;
33
+ }
34
+ /**
35
+ * Sits beside credentials.json, so it follows the same per-agent isolation
36
+ * that `credentialsFilePath` already implements (AIS_CREDENTIALS_FILE
37
+ * override, then AIS_AGENT_ID sub-directory, then the shared default).
38
+ */
39
+ export declare function pendingDeviceFlowPath(home?: string): string;
40
+ /** Persist the un-finalized flow so the next process reuses this agent. */
41
+ export declare function savePendingDeviceFlow(flow: PendingDeviceFlow, home?: string): void;
42
+ /**
43
+ * Read a still-usable pending flow, or null. Returns null (and does not
44
+ * throw) for a missing, unreadable, malformed, or expired file: every one of
45
+ * those means "no token to reuse", and the caller's fallback is correct.
46
+ */
47
+ export declare function loadPendingDeviceFlow(home?: string, now?: number): PendingDeviceFlow | null;
48
+ /** Drop the record once the flow has bonded (or is known dead). */
49
+ export declare function clearPendingDeviceFlow(home?: string): void;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pending device-flow persistence (CORBOT-8F96A8EF).
3
+ *
4
+ * The orphan bug, client side. `startDeviceFlow` can hand the server the
5
+ * provisional token from an earlier un-finalized flow, and the server will
6
+ * then reuse that provisional agent instead of minting another
7
+ * (tryReuseProvisional, CORBOT-CDDC15FA). But the only place that token
8
+ * lived was `activeFlow`, a module-level variable — process-local, and null
9
+ * in every newly spawned process. A stdio server is respawned constantly, so
10
+ * in practice the token was almost never sent and sign-in minted a fresh
11
+ * agent nearly every run. That is where Jaire's leftovers came from.
12
+ *
13
+ * So the token is persisted next to the credentials file, under the same
14
+ * 0700 directory / 0600 file permissions: it is a bearer proof-of-session
15
+ * for an unbonded agent and must not be world-readable.
16
+ *
17
+ * It is deliberately NOT part of OAuthCredentials: those are the live bonded
18
+ * session, this is a half-finished sign-in. Keeping them in separate files
19
+ * means a stale pending flow can never be mistaken for a usable credential,
20
+ * and clearing one does not disturb the other.
21
+ */
22
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
23
+ import { homedir } from 'node:os';
24
+ import { dirname, join } from 'node:path';
25
+ import { credentialsFilePath } from './oauth-credentials.js';
26
+ /**
27
+ * Provisional tokens are signed with a 7-day expiry server-side, matching the
28
+ * claim window. Past that the server rejects reuse anyway, so a local record
29
+ * older than this is dead weight and is discarded rather than sent.
30
+ */
31
+ const PENDING_FLOW_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
32
+ /**
33
+ * Sits beside credentials.json, so it follows the same per-agent isolation
34
+ * that `credentialsFilePath` already implements (AIS_CREDENTIALS_FILE
35
+ * override, then AIS_AGENT_ID sub-directory, then the shared default).
36
+ */
37
+ export function pendingDeviceFlowPath(home = homedir()) {
38
+ return join(dirname(credentialsFilePath(home)), 'pending-device-flow.json');
39
+ }
40
+ /** Persist the un-finalized flow so the next process reuses this agent. */
41
+ export function savePendingDeviceFlow(flow, home = homedir()) {
42
+ const file = pendingDeviceFlowPath(home);
43
+ try {
44
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
45
+ writeFileSync(file, JSON.stringify(flow, null, 2), { mode: 0o600 });
46
+ // writeFileSync's mode is umask-dependent; chmod is the reliable path.
47
+ if (process.platform !== 'win32') {
48
+ chmodSync(file, 0o600);
49
+ }
50
+ }
51
+ catch {
52
+ // Best effort only. A read-only or full home directory must not break
53
+ // sign-in — the caller simply falls back to minting, which is today's
54
+ // behavior, not a regression.
55
+ }
56
+ }
57
+ /**
58
+ * Read a still-usable pending flow, or null. Returns null (and does not
59
+ * throw) for a missing, unreadable, malformed, or expired file: every one of
60
+ * those means "no token to reuse", and the caller's fallback is correct.
61
+ */
62
+ export function loadPendingDeviceFlow(home = homedir(), now = Date.now()) {
63
+ const file = pendingDeviceFlowPath(home);
64
+ try {
65
+ if (!existsSync(file))
66
+ return null;
67
+ const parsed = JSON.parse(readFileSync(file, 'utf-8'));
68
+ if (typeof parsed.provisionalToken !== 'string' || parsed.provisionalToken.length === 0) {
69
+ return null;
70
+ }
71
+ const startedAt = typeof parsed.startedAt === 'string' ? Date.parse(parsed.startedAt) : NaN;
72
+ if (Number.isFinite(startedAt) && now - startedAt > PENDING_FLOW_MAX_AGE_MS) {
73
+ return null;
74
+ }
75
+ return {
76
+ provisionalToken: parsed.provisionalToken,
77
+ agentId: typeof parsed.agentId === 'string' ? parsed.agentId : '',
78
+ provisionalTenantId: typeof parsed.provisionalTenantId === 'string' ? parsed.provisionalTenantId : '',
79
+ startedAt: typeof parsed.startedAt === 'string' ? parsed.startedAt : '',
80
+ };
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ /** Drop the record once the flow has bonded (or is known dead). */
87
+ export function clearPendingDeviceFlow(home = homedir()) {
88
+ try {
89
+ rmSync(pendingDeviceFlowPath(home), { force: true });
90
+ }
91
+ catch {
92
+ // Same best-effort rationale as save: a stale file costs one rejected
93
+ // reuse attempt, which degrades to minting.
94
+ }
95
+ }
96
+ //# sourceMappingURL=pending-device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-device-flow.js","sourceRoot":"","sources":["../src/pending-device-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Retry-After handling (Jaire C9, CORBOT-D452451A).
3
+ *
4
+ * AIS sends `Retry-After` on every 429 and 503. A read that is told to wait a
5
+ * few seconds waits and tries once more; a longer wait is not slept through
6
+ * inside a tool call — the error says, in plain words, how long to wait.
7
+ */
8
+ /** Longest server-requested wait a GET sleeps through before its one retry. */
9
+ export declare const MAX_HONOURED_RETRY_AFTER_SECONDS = 5;
10
+ /** Statuses whose `Retry-After` means "back off, then the same call can work". */
11
+ export declare const RETRY_AFTER_STATUSES: ReadonlySet<number>;
12
+ /**
13
+ * Seconds to wait, from a `Retry-After` value (delta-seconds or HTTP-date).
14
+ * Null when the header is absent or unreadable.
15
+ */
16
+ export declare function parseRetryAfterSeconds(value: string | null | undefined, nowMs?: number): number | null;
17
+ /** The server's requested wait on a 429/503, or null when there is none. */
18
+ export declare function retryAfterOf(res: {
19
+ status: number;
20
+ headers?: {
21
+ get(name: string): string | null;
22
+ };
23
+ }): number | null;
24
+ /** True when a GET should sleep `seconds` and try once more. */
25
+ export declare function shouldHonourRetryAfter(seconds: number | null): seconds is number;
26
+ export declare function sleepSeconds(seconds: number): Promise<void>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Retry-After handling (Jaire C9, CORBOT-D452451A).
3
+ *
4
+ * AIS sends `Retry-After` on every 429 and 503. A read that is told to wait a
5
+ * few seconds waits and tries once more; a longer wait is not slept through
6
+ * inside a tool call — the error says, in plain words, how long to wait.
7
+ */
8
+ /** Longest server-requested wait a GET sleeps through before its one retry. */
9
+ export const MAX_HONOURED_RETRY_AFTER_SECONDS = 5;
10
+ /** Statuses whose `Retry-After` means "back off, then the same call can work". */
11
+ export const RETRY_AFTER_STATUSES = new Set([429, 503]);
12
+ /**
13
+ * Seconds to wait, from a `Retry-After` value (delta-seconds or HTTP-date).
14
+ * Null when the header is absent or unreadable.
15
+ */
16
+ export function parseRetryAfterSeconds(value, nowMs = Date.now()) {
17
+ if (value === null || value === undefined)
18
+ return null;
19
+ const trimmed = value.trim();
20
+ if (/^\d+$/.test(trimmed))
21
+ return Number(trimmed);
22
+ if (!/[A-Za-z]/.test(trimmed))
23
+ return null;
24
+ const at = Date.parse(trimmed);
25
+ if (Number.isNaN(at))
26
+ return null;
27
+ return Math.max(0, Math.ceil((at - nowMs) / 1000));
28
+ }
29
+ /** The server's requested wait on a 429/503, or null when there is none. */
30
+ export function retryAfterOf(res) {
31
+ if (!RETRY_AFTER_STATUSES.has(res.status))
32
+ return null;
33
+ return parseRetryAfterSeconds(res.headers?.get('retry-after'));
34
+ }
35
+ /** True when a GET should sleep `seconds` and try once more. */
36
+ export function shouldHonourRetryAfter(seconds) {
37
+ return seconds !== null && seconds <= MAX_HONOURED_RETRY_AFTER_SECONDS;
38
+ }
39
+ export function sleepSeconds(seconds) {
40
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
41
+ }
42
+ //# sourceMappingURL=retry-after.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-after.js","sourceRoot":"","sources":["../src/retry-after.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC;AAElD,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7E;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAgC,EAAE,QAAgB,IAAI,CAAC,GAAG,EAAE;IACjG,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,GAAuE;IAClG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,sBAAsB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,sBAAsB,CAAC,OAAsB;IAC3D,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,IAAI,gCAAgC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ declare const sessionIdentitySchema: z.ZodObject<{
3
+ version: z.ZodLiteral<1>;
4
+ source: z.ZodEnum<{
5
+ agent_load: "agent_load";
6
+ env: "env";
7
+ }>;
8
+ agentId: z.ZodString;
9
+ }, z.core.$strict>;
10
+ export type SessionIdentity = z.infer<typeof sessionIdentitySchema>;
11
+ export declare const IDENTITY_REPAIR = "Set AIS_AGENT_ID for THIS session only \u2014 export it in the shell that launches aismemory, or set it in the env block of this session's own MCP config \u2014 then restart the session and run aismemory doctor. Switch agents inside a session with agent_load. Never pin a machine, install or harness to one agent; other sessions on this machine run as other agents.";
12
+ /**
13
+ * Lists leftover per-harness manifest files from the removed machine-wide pin.
14
+ * They are read by nothing; doctor only reports them so a human can delete
15
+ * them deliberately. Never deletes anything.
16
+ */
17
+ export declare function legacyHarnessManifests(home?: string): string[];
18
+ /** Reads this process's own identity. No file, no shared state, no machine-wide pin. */
19
+ export declare function loadSessionIdentity(env?: NodeJS.ProcessEnv): SessionIdentity | null;
20
+ /** The identity this session holds after a successful in-session agent_load switch. */
21
+ export declare function switchedTo(previous: SessionIdentity, agentId: string): SessionIdentity;
22
+ export declare function assertPinnedAgent(pin: SessionIdentity | null, agentId: string): void;
23
+ /** Verify the same whoami response exposed by the selected auth adapter, before every mutation. */
24
+ export declare function verifyPinnedIdentity(pin: SessionIdentity | null, agentId: string, whoami: () => Promise<unknown>): Promise<void>;
25
+ export declare function requiresIdentityGate(name: string, args: Record<string, unknown>): boolean;
26
+ export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Session identity — one identity per PROCESS, never per machine.
3
+ *
4
+ * A session's identity is chosen at launch through the AIS_AGENT_ID
5
+ * environment variable (exported in the launching shell, or set in the `env`
6
+ * block of that session's own MCP config) and can be switched in-session with
7
+ * the agent_load tool. Many agents run on one machine at the same time; that
8
+ * is the product. Nothing here pins a machine, an install or a harness to a
9
+ * single agent (CORBOT-FBDC8652).
10
+ *
11
+ * The gate below only REFUSES a write whose live agent does not match the
12
+ * identity this session selected. It never installs a pin anywhere, and it
13
+ * never reads shared per-machine state.
14
+ */
15
+ import { readdirSync } from 'node:fs';
16
+ import { homedir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { z } from 'zod';
19
+ const sessionIdentitySchema = z.object({
20
+ version: z.literal(1),
21
+ /** How this session's identity was selected. */
22
+ source: z.enum(['env', 'agent_load']),
23
+ agentId: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/),
24
+ }).strict();
25
+ export const IDENTITY_REPAIR = 'Set AIS_AGENT_ID for THIS session only — export it in the shell that launches aismemory, or set it in the env block of this session\'s own MCP config — then restart the session and run aismemory doctor. Switch agents inside a session with agent_load. Never pin a machine, install or harness to one agent; other sessions on this machine run as other agents.';
26
+ /** The directory the removed per-harness manifest pin used to write to. */
27
+ const LEGACY_HARNESS_DIR = ['.aismemory', 'harnesses'];
28
+ /**
29
+ * Lists leftover per-harness manifest files from the removed machine-wide pin.
30
+ * They are read by nothing; doctor only reports them so a human can delete
31
+ * them deliberately. Never deletes anything.
32
+ */
33
+ export function legacyHarnessManifests(home = homedir()) {
34
+ try {
35
+ const dir = join(home, ...LEGACY_HARNESS_DIR);
36
+ return readdirSync(dir).filter((name) => name.endsWith('.json')).sort().map((name) => join(dir, name));
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ }
42
+ /** Reads this process's own identity. No file, no shared state, no machine-wide pin. */
43
+ export function loadSessionIdentity(env = process.env) {
44
+ // Trim before validating: secret injection (a sourced .env file, a CI secret,
45
+ // a Kubernetes secret mount) commonly appends a trailing newline. This runs
46
+ // BEFORE main()'s agent-API-key check, so an untrimmed value failed the
47
+ // agentId regex here and killed the process with a raw schema error — agent
48
+ // API key mode could never engage even though its own loader trims for
49
+ // exactly this reason (CORBOT-D8D35CF0).
50
+ const agentId = env['AIS_AGENT_ID']?.trim();
51
+ if (agentId === undefined || agentId === '')
52
+ return null;
53
+ return sessionIdentitySchema.parse({ version: 1, source: 'env', agentId });
54
+ }
55
+ /** The identity this session holds after a successful in-session agent_load switch. */
56
+ export function switchedTo(previous, agentId) {
57
+ return sessionIdentitySchema.parse({ version: previous.version, source: 'agent_load', agentId });
58
+ }
59
+ export function assertPinnedAgent(pin, agentId) {
60
+ if (!pin)
61
+ return;
62
+ if (pin.agentId !== agentId) {
63
+ throw new Error(`Identity gate: expected ${pin.agentId}, received ${agentId}. Writes stopped. ${IDENTITY_REPAIR}`);
64
+ }
65
+ }
66
+ /** Verify the same whoami response exposed by the selected auth adapter, before every mutation. */
67
+ export async function verifyPinnedIdentity(pin, agentId, whoami) {
68
+ if (!pin)
69
+ return;
70
+ assertPinnedAgent(pin, agentId);
71
+ let result;
72
+ try {
73
+ result = await whoami();
74
+ }
75
+ catch {
76
+ throw new Error(`Identity gate: whoami unavailable for ${agentId}. Writes stopped. ${IDENTITY_REPAIR}`);
77
+ }
78
+ const parsed = z.object({ success: z.literal(true), data: z.object({
79
+ id: z.string().optional(), agentId: z.string().optional(),
80
+ }) }).safeParse(result);
81
+ if (!parsed.success || (parsed.data.data.id ?? parsed.data.data.agentId) !== agentId) {
82
+ throw new Error(`Identity gate: whoami did not confirm ${agentId}. Writes stopped. ${IDENTITY_REPAIR}`);
83
+ }
84
+ }
85
+ export function requiresIdentityGate(name, args) {
86
+ return ['remember', 'handoff', 'dream', 'inbox_send'].includes(name) ||
87
+ (name === 'inbox_read' && args['ack'] === true);
88
+ }
89
+ //# sourceMappingURL=session-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-identity.js","sourceRoot":"","sources":["../src/session-identity.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type TelemetryEventName = 'auth.device_flow.start' | 'auth.key.success' | 'auth.key.fallback' | 'auth.key.autoenroll' | 'auth.renew.success' | 'auth.renew.retry' | 'auth.renew.hard_expiry' | 'tool.call' | 'ais.http.failure';
1
+ export type TelemetryEventName = 'auth.device_flow.start' | 'auth.key.success' | 'auth.key.fallback' | 'auth.key.autoenroll' | 'auth.renew.success' | 'auth.renew.retry' | 'auth.renew.hard_expiry' | 'tool.call' | 'ais.http.failure' | 'ais.http.retry';
2
2
  export interface TelemetryEvent {
3
3
  event: TelemetryEventName;
4
4
  ts: string;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2BzC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAEnD,gEAAgE;AAChE,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrE,OAAO,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,KAAmD;IAC/E,IAAI,CAAC,kBAAkB,EAAE;QAAE,OAAO;IAClC,MAAM,OAAO,GAAmB;QAC9B,GAAG,KAAK;QACR,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACzC,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC7E,CAAC"}
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA4BzC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAEnD,gEAAgE;AAChE,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrE,OAAO,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,KAAmD;IAC/E,IAAI,CAAC,kBAAkB,EAAE;QAAE,OAAO;IAClC,MAAM,OAAO,GAAmB;QAC9B,GAAG,KAAK;QACR,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACzC,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC7E,CAAC"}
@@ -18,6 +18,7 @@ export declare const rememberArgsSchema: z.ZodObject<{
18
18
  task: "task";
19
19
  }>>>;
20
20
  importance: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
21
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
22
  }, z.core.$strip>;
22
23
  export declare const recallArgsSchema: z.ZodObject<{
23
24
  principalAgentId: z.ZodOptional<z.ZodString>;
@@ -31,10 +32,13 @@ export declare const recallArgsSchema: z.ZodObject<{
31
32
  goal: "goal";
32
33
  task: "task";
33
34
  }>>;
35
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
36
  }, z.core.$strip>;
35
37
  export declare const handoffArgsSchema: z.ZodObject<{
36
38
  summary: z.ZodOptional<z.ZodString>;
37
39
  keyLearnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
40
+ docPath: z.ZodOptional<z.ZodString>;
41
+ docPrUrl: z.ZodOptional<z.ZodString>;
38
42
  }, z.core.$strip>;
39
43
  export declare const agentLoadArgsSchema: z.ZodObject<{
40
44
  query: z.ZodString;
package/dist/tool-args.js CHANGED
@@ -8,6 +8,13 @@ export const MAX_MEMORY_CONTENT_LENGTH = 256 * 1024;
8
8
  /** Max recall limit (matches AIS memory-handlers). */
9
9
  export const MAX_QUERY_LIMIT = 100;
10
10
  const memoryTypeSchema = z.enum(['fact', 'event', 'lesson', 'context', 'goal', 'task']);
11
+ /**
12
+ * Memory tags (Jaire C5). The service trims, lower-cases, de-duplicates and
13
+ * validates them (1-64 chars of a-z 0-9 . _ : -, at most 20); this only
14
+ * checks the shape.
15
+ */
16
+ const tagsArgSchema = z.array(z.string({ message: 'tags must be strings' }), { message: 'tags must be an array of strings' })
17
+ .max(20, 'a memory can carry at most 20 tags');
11
18
  export const rememberArgsSchema = z.object({
12
19
  content: z
13
20
  .string({ message: 'content is required and must be a string' })
@@ -15,6 +22,7 @@ export const rememberArgsSchema = z.object({
15
22
  .max(MAX_MEMORY_CONTENT_LENGTH, `content exceeds maximum length of ${MAX_MEMORY_CONTENT_LENGTH} bytes`),
16
23
  type: memoryTypeSchema.optional().default('context'),
17
24
  importance: z.number().min(0).max(1).optional().default(0.5),
25
+ tags: tagsArgSchema.optional(),
18
26
  });
19
27
  /**
20
28
  * Optional delegated-read principal (CORBOT-3785D10D).
@@ -34,6 +42,8 @@ export const recallArgsSchema = z.object({
34
42
  .max(MAX_MEMORY_CONTENT_LENGTH, `query exceeds maximum length of ${MAX_MEMORY_CONTENT_LENGTH} bytes`),
35
43
  limit: z.number().int().min(1).max(MAX_QUERY_LIMIT).optional(),
36
44
  type: memoryTypeSchema.optional(),
45
+ /** Only memories carrying every one of these tags. */
46
+ tags: tagsArgSchema.optional(),
37
47
  });
38
48
  export const handoffArgsSchema = z.object({
39
49
  summary: z
@@ -46,6 +56,12 @@ export const handoffArgsSchema = z.object({
46
56
  .max(MAX_MEMORY_CONTENT_LENGTH, `keyLearnings item exceeds maximum length of ${MAX_MEMORY_CONTENT_LENGTH} bytes`))
47
57
  .max(100, 'keyLearnings exceeds maximum of 100 items')
48
58
  .optional(),
59
+ // CORBOT-E3541A7F: the route accepts these two and persists them in the
60
+ // doc_path / doc_pr_url columns. Same shape the route validates — a string
61
+ // it ignores when blank — so nothing is added here beyond letting the
62
+ // caller's value survive the parse instead of being stripped.
63
+ docPath: z.string().optional(),
64
+ docPrUrl: z.string().optional(),
49
65
  });
50
66
  export const agentLoadArgsSchema = z.object({
51
67
  query: z