@athenaintel/react 0.41.2 → 0.41.3
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 +6 -0
- package/dist/agent-runtime/src/durable-thread-snapshot-cache.cjs +158 -0
- package/dist/agent-runtime/src/durable-thread-snapshot-cache.cjs.map +1 -0
- package/dist/agent-runtime/src/durable-thread-snapshot-cache.js +158 -0
- package/dist/agent-runtime/src/durable-thread-snapshot-cache.js.map +1 -0
- package/dist/agent-runtime/src/thread-snapshot-mirror.cjs +400 -0
- package/dist/agent-runtime/src/thread-snapshot-mirror.cjs.map +1 -0
- package/dist/agent-runtime/src/thread-snapshot-mirror.js +400 -0
- package/dist/agent-runtime/src/thread-snapshot-mirror.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +5 -1
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +5 -1
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +4 -3
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +7 -1
- package/dist/chat/super-grouping/activity-timeline.js +4 -3
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/offline/offline-history.cjs +77 -0
- package/dist/offline/offline-history.cjs.map +1 -0
- package/dist/offline/offline-history.d.ts +60 -0
- package/dist/offline/offline-history.js +77 -0
- package/dist/offline/offline-history.js.map +1 -0
- package/dist/provider/AthenaProvider.cjs +46 -3
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +8 -1
- package/dist/provider/AthenaProvider.js +46 -3
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/threads/offline-session-list.cjs +75 -0
- package/dist/threads/offline-session-list.cjs.map +1 -0
- package/dist/threads/offline-session-list.d.ts +27 -0
- package/dist/threads/offline-session-list.js +75 -0
- package/dist/threads/offline-session-list.js.map +1 -0
- package/dist/threads/statewire-thread-list.cjs +63 -4
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +21 -2
- package/dist/threads/statewire-thread-list.js +64 -5
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -807,6 +807,12 @@ Both use the same bearer / API key as session creation. The full recipe
|
|
|
807
807
|
template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
|
|
808
808
|
exported if you hold the bridge state yourself.
|
|
809
809
|
|
|
810
|
+
## What's new in 0.41.3
|
|
811
|
+
|
|
812
|
+
**A pinned session stays in the sidebar.** The session list reads the newest 50 sessions, and its Pinned group was built from that page alone, so a session pinned long ago left the sidebar once 50 newer sessions existed (on staging, four pinned sessions became three when two new sessions started). The provider now also reads the viewer's pinned sessions on their own (`favorites_only`, under the same app scope and search, every page of them up to 500) and adds the ones the page does not list to the Pinned group. Unpinning such a session takes it out of the sidebar, since it is not one of the newest. Nothing to change in your app.
|
|
813
|
+
|
|
814
|
+
**"Completed with issues" means the turn ended on a problem.** A finished turn's collapsed card said "Completed with issues" when any tool call in it had failed, so a long run that opened its pull request after a few failed reads or commands read as a failure. It now says so only when the turn ended on a failed call (no later call completed) or held a call the person refused; otherwise it reads `Used N tools`. `getActivitySummary` follows the same rule.
|
|
815
|
+
|
|
810
816
|
## What's new in 0.41.2
|
|
811
817
|
|
|
812
818
|
**A session opened while its run is streaming shows, and keeps, its own model.** `useAthenaThreadModel()` could report `ready` with no model before the session's snapshot had arrived. On a session whose run was streaming, the connection reported `connected` before the attach snapshot applied, and the SDK took the placeholder the thread shows meanwhile, which holds no server config, as what the server sent. A picker seeded from it showed the agent's default model, and the provider then wrote that pick to the session (`config/patch {model: null}`), so the session lost its model for its next turns. `ready`, and every config write the provider decides, now wait for a state the server sent: the placeholder and a remembered snapshot never count, whenever the connection reports `connected`. The `statewire.attach` diagnostics span (time to the session's snapshot) now ends when that snapshot arrives, not when the thread first renders.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const threadSnapshotCache = require("./thread-snapshot-cache.cjs");
|
|
4
|
+
const threadSnapshotMirror = require("./thread-snapshot-mirror.cjs");
|
|
5
|
+
const DEFAULT_MAX_THREADS = 30;
|
|
6
|
+
const OFFLINE_SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
7
|
+
function browserOffline() {
|
|
8
|
+
var _a;
|
|
9
|
+
const host = globalThis;
|
|
10
|
+
return ((_a = host.navigator) == null ? void 0 : _a.onLine) === false;
|
|
11
|
+
}
|
|
12
|
+
function createDurableThreadSnapshotCache({
|
|
13
|
+
mirror,
|
|
14
|
+
isOffline = browserOffline,
|
|
15
|
+
maxThreads = DEFAULT_MAX_THREADS,
|
|
16
|
+
maxAgeMs,
|
|
17
|
+
offlineMaxAgeMs = OFFLINE_SNAPSHOT_MAX_AGE_MS,
|
|
18
|
+
now = () => Date.now()
|
|
19
|
+
}) {
|
|
20
|
+
let ownerId = null;
|
|
21
|
+
let epoch = 0;
|
|
22
|
+
let primed = null;
|
|
23
|
+
let hydration = Promise.resolve();
|
|
24
|
+
const offline = /* @__PURE__ */ new Map();
|
|
25
|
+
const offlineSeeds = /* @__PURE__ */ new Map();
|
|
26
|
+
const restored = /* @__PURE__ */ new Set();
|
|
27
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
28
|
+
const notify = () => {
|
|
29
|
+
for (const listener of listeners) {
|
|
30
|
+
try {
|
|
31
|
+
listener();
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const clearOfflineSeed = (threadId) => {
|
|
37
|
+
if (offlineSeeds.delete(threadId)) notify();
|
|
38
|
+
};
|
|
39
|
+
const keepOffline = (threadId, entry) => {
|
|
40
|
+
offline.delete(threadId);
|
|
41
|
+
offline.set(threadId, entry);
|
|
42
|
+
while (offline.size > maxThreads) {
|
|
43
|
+
const oldest = offline.keys().next().value;
|
|
44
|
+
if (oldest === void 0) break;
|
|
45
|
+
offline.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const memory = threadSnapshotCache.createDeepAgentThreadSnapshotCache({
|
|
49
|
+
maxThreads,
|
|
50
|
+
maxAgeMs,
|
|
51
|
+
now,
|
|
52
|
+
onChange: (change) => {
|
|
53
|
+
const owner = ownerId;
|
|
54
|
+
if (owner === null) return;
|
|
55
|
+
switch (change.type) {
|
|
56
|
+
case "remember":
|
|
57
|
+
mirror.write({ ownerId: owner, threadId: change.threadId, state: change.state, at: change.at });
|
|
58
|
+
return;
|
|
59
|
+
case "forget":
|
|
60
|
+
mirror.remove({ ownerId: owner, threadId: change.threadId });
|
|
61
|
+
return;
|
|
62
|
+
case "clear":
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const offlineEntry = (threadId) => {
|
|
68
|
+
const entry = offline.get(threadId);
|
|
69
|
+
return entry && now() - entry.at <= offlineMaxAgeMs ? entry : void 0;
|
|
70
|
+
};
|
|
71
|
+
const dropEverything = () => {
|
|
72
|
+
memory.clear();
|
|
73
|
+
offline.clear();
|
|
74
|
+
restored.clear();
|
|
75
|
+
if (offlineSeeds.size > 0) {
|
|
76
|
+
offlineSeeds.clear();
|
|
77
|
+
notify();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
remember(threadId, state) {
|
|
82
|
+
memory.remember(threadId, state);
|
|
83
|
+
restored.delete(threadId);
|
|
84
|
+
if (threadSnapshotMirror.isRestingThreadSnapshot(state)) keepOffline(threadId, { state, at: now() });
|
|
85
|
+
clearOfflineSeed(threadId);
|
|
86
|
+
},
|
|
87
|
+
take(threadId) {
|
|
88
|
+
if (!isOffline()) return memory.take(threadId);
|
|
89
|
+
const entry = offlineEntry(threadId);
|
|
90
|
+
if (!entry) return void 0;
|
|
91
|
+
if (offlineSeeds.get(threadId) !== entry.at) {
|
|
92
|
+
offlineSeeds.set(threadId, entry.at);
|
|
93
|
+
notify();
|
|
94
|
+
}
|
|
95
|
+
return entry.state;
|
|
96
|
+
},
|
|
97
|
+
peek(threadId) {
|
|
98
|
+
return isOffline() ? offlineEntry(threadId) !== void 0 : memory.peek(threadId);
|
|
99
|
+
},
|
|
100
|
+
forget(threadId) {
|
|
101
|
+
memory.forget(threadId);
|
|
102
|
+
offline.delete(threadId);
|
|
103
|
+
restored.delete(threadId);
|
|
104
|
+
clearOfflineSeed(threadId);
|
|
105
|
+
},
|
|
106
|
+
clear() {
|
|
107
|
+
dropEverything();
|
|
108
|
+
},
|
|
109
|
+
owner: () => ownerId,
|
|
110
|
+
setOwner(next) {
|
|
111
|
+
if (next === ownerId) return hydration;
|
|
112
|
+
const previous = ownerId;
|
|
113
|
+
ownerId = next;
|
|
114
|
+
epoch += 1;
|
|
115
|
+
dropEverything();
|
|
116
|
+
if (previous !== null) void mirror.clearOwner(previous);
|
|
117
|
+
if (next === null) {
|
|
118
|
+
hydration = Promise.resolve();
|
|
119
|
+
return hydration;
|
|
120
|
+
}
|
|
121
|
+
const readEpoch = epoch;
|
|
122
|
+
const read = (primed == null ? void 0 : primed.ownerId) === next ? primed.read : mirror.load(next);
|
|
123
|
+
primed = null;
|
|
124
|
+
hydration = read.then((entries) => {
|
|
125
|
+
if (readEpoch !== epoch || ownerId !== next) return;
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (!offline.has(entry.threadId)) keepOffline(entry.threadId, { state: entry.state, at: entry.at });
|
|
128
|
+
if (memory.peek(entry.threadId)) continue;
|
|
129
|
+
memory.restore(entry.threadId, entry.state, entry.at);
|
|
130
|
+
if (memory.peek(entry.threadId)) restored.add(entry.threadId);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return hydration;
|
|
134
|
+
},
|
|
135
|
+
prime(next) {
|
|
136
|
+
if (next === ownerId || (primed == null ? void 0 : primed.ownerId) === next) return;
|
|
137
|
+
primed = { ownerId: next, read: mirror.load(next) };
|
|
138
|
+
},
|
|
139
|
+
offlineSeedAt: (threadId) => offlineSeeds.get(threadId) ?? null,
|
|
140
|
+
subscribeOfflineSeeds(listener) {
|
|
141
|
+
listeners.add(listener);
|
|
142
|
+
return () => {
|
|
143
|
+
listeners.delete(listener);
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
isRestored: (threadId) => restored.has(threadId),
|
|
147
|
+
whenHydrated: () => hydration,
|
|
148
|
+
async wipe() {
|
|
149
|
+
epoch += 1;
|
|
150
|
+
primed = null;
|
|
151
|
+
dropEverything();
|
|
152
|
+
await mirror.clearAll();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
exports.OFFLINE_SNAPSHOT_MAX_AGE_MS = OFFLINE_SNAPSHOT_MAX_AGE_MS;
|
|
157
|
+
exports.createDurableThreadSnapshotCache = createDurableThreadSnapshotCache;
|
|
158
|
+
//# sourceMappingURL=durable-thread-snapshot-cache.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-thread-snapshot-cache.cjs","sources":["../../../../agent-runtime/src/durable-thread-snapshot-cache.ts"],"sourcesContent":["import {\n createDeepAgentThreadSnapshotCache,\n type DeepAgentThreadSnapshotCache,\n} from \"./thread-snapshot-cache\";\nimport { isRestingThreadSnapshot, type ThreadSnapshotMirror } from \"./thread-snapshot-mirror\";\nimport type { DeepAgentStreamValues } from \"./types\";\n\n/**\n * The thread snapshot cache with a durable, encrypted mirror behind it (`thread-snapshot-mirror.ts`):\n * a reload seeds a recently visited thread from what it last showed, and a thread can be read while\n * the transport cannot connect at all.\n *\n * - **Online, nothing changes.** `take` serves the in-memory entry within its age bound (one hour by\n * default), exactly as the plain cache does, and the attach snapshot replaces the seed.\n * - **Offline** (`isOffline()`: the host's own word that Athena can't be reached), `take` serves the\n * thread's last resting transcript this page holds (what it read, or what the mirror kept), up to\n * `offlineMaxAgeMs` old and past the online bound, so a person can read their recent\n * conversations on a plane. Never a mid-stream state: a run the connection dropped is a partial\n * transcript, and the resting one before it is what shows. Every offline seed, however recent, is\n * recorded with its capture time (`offlineSeedAt`) so the host can say the transcript is \"as of\"\n * then; only the next state the wire delivers for the thread (its attach) clears it.\n * - **Owned.** Entries belong to one owner (`setOwner`): a change drops every entry, deletes the\n * previous owner's mirrored records and reads the new owner's. A snapshot captured under one\n * account never seeds a mount under another. `wipe` drops everything (a sign-out).\n */\nexport interface DurableThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /** Bind the cache to the acting owner (`null`: nobody signed in; nothing is mirrored). */\n setOwner(ownerId: string | null): Promise<void>;\n /**\n * Start reading an owner's mirrored snapshots before the owner is known for sure (a host's\n * last-owner hint at boot): `setOwner` adopts the read if that owner is the one bound, and it\n * is discarded otherwise. Binds nothing and deletes nothing.\n */\n prime(ownerId: string): void;\n /** The owner the cache is bound to. */\n owner(): string | null;\n /**\n * When the seed `take` last served for `threadId` was captured, if it was served while Athena\n * could not be reached; `null` otherwise, and again once the wire delivers the thread.\n */\n offlineSeedAt(threadId: string): number | null;\n /** Calls `listener` whenever an offline seed is served or cleared; returns what stops it. */\n subscribeOfflineSeeds(listener: () => void): () => void;\n /** Whether the entry `take` would serve for `threadId` came back from the mirror. */\n isRestored(threadId: string): boolean;\n /** Resolves once the current owner's mirrored snapshots have been read (tests, telemetry). */\n whenHydrated(): Promise<void>;\n /** Drop every entry, in memory and in the mirror (a sign-out, a workspace switch). */\n wipe(): Promise<void>;\n}\n\nexport interface DurableThreadSnapshotCacheOptions {\n mirror: ThreadSnapshotMirror;\n /** Whether Athena can't be reached right now (the host's reachability, or the browser's). */\n isOffline?: () => boolean;\n maxThreads?: number;\n /** The online age bound (the plain cache's). */\n maxAgeMs?: number;\n /** How old a mirrored snapshot may be and still be read offline. */\n offlineMaxAgeMs?: number;\n now?: () => number;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\n/** What a device keeps for reading offline: the offline plan's 30 days. */\nexport const OFFLINE_SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;\n\nfunction browserOffline(): boolean {\n const host = globalThis as { navigator?: { onLine?: boolean } };\n return host.navigator?.onLine === false;\n}\n\nexport function createDurableThreadSnapshotCache({\n mirror,\n isOffline = browserOffline,\n maxThreads = DEFAULT_MAX_THREADS,\n maxAgeMs,\n offlineMaxAgeMs = OFFLINE_SNAPSHOT_MAX_AGE_MS,\n now = () => Date.now(),\n}: DurableThreadSnapshotCacheOptions): DurableThreadSnapshotCache {\n let ownerId: string | null = null;\n // Bumped on every owner change and wipe: a mirror read in flight then must not admit what it read.\n let epoch = 0;\n let primed: { ownerId: string; read: ReturnType<ThreadSnapshotMirror[\"load\"]> } | null = null;\n let hydration: Promise<void> = Promise.resolve();\n // Every resting snapshot readable offline (the mirror's, and this page's own), newest last.\n const offline = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const offlineSeeds = new Map<string, number>();\n const restored = new Set<string>();\n const listeners = new Set<() => void>();\n\n const notify = (): void => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A host listener's fault never reaches the wire path.\n }\n }\n };\n const clearOfflineSeed = (threadId: string): void => {\n if (offlineSeeds.delete(threadId)) notify();\n };\n const keepOffline = (threadId: string, entry: { state: DeepAgentStreamValues; at: number }): void => {\n offline.delete(threadId);\n offline.set(threadId, entry);\n while (offline.size > maxThreads) {\n const oldest = offline.keys().next().value;\n if (oldest === undefined) break;\n offline.delete(oldest);\n }\n };\n\n const memory = createDeepAgentThreadSnapshotCache({\n maxThreads,\n maxAgeMs,\n now,\n onChange: (change) => {\n const owner = ownerId;\n if (owner === null) return;\n switch (change.type) {\n case \"remember\":\n mirror.write({ ownerId: owner, threadId: change.threadId, state: change.state, at: change.at });\n return;\n case \"forget\":\n mirror.remove({ ownerId: owner, threadId: change.threadId });\n return;\n case \"clear\":\n return;\n }\n },\n });\n\n /** The thread's last resting transcript within the offline bound. */\n const offlineEntry = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = offline.get(threadId);\n return entry && now() - entry.at <= offlineMaxAgeMs ? entry : undefined;\n };\n\n const dropEverything = (): void => {\n memory.clear();\n offline.clear();\n restored.clear();\n if (offlineSeeds.size > 0) {\n offlineSeeds.clear();\n notify();\n }\n };\n\n return {\n remember(threadId, state) {\n memory.remember(threadId, state);\n restored.delete(threadId);\n // A mid-stream state keeps the resting transcript before it readable offline, as the mirror\n // keeps that one's record. One too long to seal still reads from memory.\n if (isRestingThreadSnapshot(state)) keepOffline(threadId, { state, at: now() });\n // The wire delivered the thread: the transcript is current again.\n clearOfflineSeed(threadId);\n },\n take(threadId) {\n if (!isOffline()) return memory.take(threadId);\n const entry = offlineEntry(threadId);\n if (!entry) return undefined;\n if (offlineSeeds.get(threadId) !== entry.at) {\n offlineSeeds.set(threadId, entry.at);\n notify();\n }\n return entry.state;\n },\n peek(threadId) {\n return isOffline() ? offlineEntry(threadId) !== undefined : memory.peek(threadId);\n },\n forget(threadId) {\n memory.forget(threadId);\n offline.delete(threadId);\n restored.delete(threadId);\n clearOfflineSeed(threadId);\n },\n clear() {\n dropEverything();\n },\n owner: () => ownerId,\n setOwner(next) {\n if (next === ownerId) return hydration;\n const previous = ownerId;\n ownerId = next;\n epoch += 1;\n dropEverything();\n if (previous !== null) void mirror.clearOwner(previous);\n if (next === null) {\n hydration = Promise.resolve();\n return hydration;\n }\n const readEpoch = epoch;\n const read = primed?.ownerId === next ? primed.read : mirror.load(next);\n primed = null;\n hydration = read.then((entries) => {\n // The owner moved on, or everything was wiped, while the read was in flight.\n if (readEpoch !== epoch || ownerId !== next) return;\n for (const entry of entries) {\n if (!offline.has(entry.threadId)) keepOffline(entry.threadId, { state: entry.state, at: entry.at });\n if (memory.peek(entry.threadId)) continue;\n memory.restore(entry.threadId, entry.state, entry.at);\n if (memory.peek(entry.threadId)) restored.add(entry.threadId);\n }\n });\n return hydration;\n },\n prime(next) {\n if (next === ownerId || primed?.ownerId === next) return;\n primed = { ownerId: next, read: mirror.load(next) };\n },\n offlineSeedAt: (threadId) => offlineSeeds.get(threadId) ?? null,\n subscribeOfflineSeeds(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isRestored: (threadId) => restored.has(threadId),\n whenHydrated: () => hydration,\n async wipe() {\n epoch += 1;\n primed = null;\n dropEverything();\n await mirror.clearAll();\n },\n };\n}\n"],"names":["createDeepAgentThreadSnapshotCache","isRestingThreadSnapshot"],"mappings":"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"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createDeepAgentThreadSnapshotCache } from "./thread-snapshot-cache.js";
|
|
2
|
+
import { isRestingThreadSnapshot } from "./thread-snapshot-mirror.js";
|
|
3
|
+
const DEFAULT_MAX_THREADS = 30;
|
|
4
|
+
const OFFLINE_SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5
|
+
function browserOffline() {
|
|
6
|
+
var _a;
|
|
7
|
+
const host = globalThis;
|
|
8
|
+
return ((_a = host.navigator) == null ? void 0 : _a.onLine) === false;
|
|
9
|
+
}
|
|
10
|
+
function createDurableThreadSnapshotCache({
|
|
11
|
+
mirror,
|
|
12
|
+
isOffline = browserOffline,
|
|
13
|
+
maxThreads = DEFAULT_MAX_THREADS,
|
|
14
|
+
maxAgeMs,
|
|
15
|
+
offlineMaxAgeMs = OFFLINE_SNAPSHOT_MAX_AGE_MS,
|
|
16
|
+
now = () => Date.now()
|
|
17
|
+
}) {
|
|
18
|
+
let ownerId = null;
|
|
19
|
+
let epoch = 0;
|
|
20
|
+
let primed = null;
|
|
21
|
+
let hydration = Promise.resolve();
|
|
22
|
+
const offline = /* @__PURE__ */ new Map();
|
|
23
|
+
const offlineSeeds = /* @__PURE__ */ new Map();
|
|
24
|
+
const restored = /* @__PURE__ */ new Set();
|
|
25
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
26
|
+
const notify = () => {
|
|
27
|
+
for (const listener of listeners) {
|
|
28
|
+
try {
|
|
29
|
+
listener();
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const clearOfflineSeed = (threadId) => {
|
|
35
|
+
if (offlineSeeds.delete(threadId)) notify();
|
|
36
|
+
};
|
|
37
|
+
const keepOffline = (threadId, entry) => {
|
|
38
|
+
offline.delete(threadId);
|
|
39
|
+
offline.set(threadId, entry);
|
|
40
|
+
while (offline.size > maxThreads) {
|
|
41
|
+
const oldest = offline.keys().next().value;
|
|
42
|
+
if (oldest === void 0) break;
|
|
43
|
+
offline.delete(oldest);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const memory = createDeepAgentThreadSnapshotCache({
|
|
47
|
+
maxThreads,
|
|
48
|
+
maxAgeMs,
|
|
49
|
+
now,
|
|
50
|
+
onChange: (change) => {
|
|
51
|
+
const owner = ownerId;
|
|
52
|
+
if (owner === null) return;
|
|
53
|
+
switch (change.type) {
|
|
54
|
+
case "remember":
|
|
55
|
+
mirror.write({ ownerId: owner, threadId: change.threadId, state: change.state, at: change.at });
|
|
56
|
+
return;
|
|
57
|
+
case "forget":
|
|
58
|
+
mirror.remove({ ownerId: owner, threadId: change.threadId });
|
|
59
|
+
return;
|
|
60
|
+
case "clear":
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const offlineEntry = (threadId) => {
|
|
66
|
+
const entry = offline.get(threadId);
|
|
67
|
+
return entry && now() - entry.at <= offlineMaxAgeMs ? entry : void 0;
|
|
68
|
+
};
|
|
69
|
+
const dropEverything = () => {
|
|
70
|
+
memory.clear();
|
|
71
|
+
offline.clear();
|
|
72
|
+
restored.clear();
|
|
73
|
+
if (offlineSeeds.size > 0) {
|
|
74
|
+
offlineSeeds.clear();
|
|
75
|
+
notify();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
remember(threadId, state) {
|
|
80
|
+
memory.remember(threadId, state);
|
|
81
|
+
restored.delete(threadId);
|
|
82
|
+
if (isRestingThreadSnapshot(state)) keepOffline(threadId, { state, at: now() });
|
|
83
|
+
clearOfflineSeed(threadId);
|
|
84
|
+
},
|
|
85
|
+
take(threadId) {
|
|
86
|
+
if (!isOffline()) return memory.take(threadId);
|
|
87
|
+
const entry = offlineEntry(threadId);
|
|
88
|
+
if (!entry) return void 0;
|
|
89
|
+
if (offlineSeeds.get(threadId) !== entry.at) {
|
|
90
|
+
offlineSeeds.set(threadId, entry.at);
|
|
91
|
+
notify();
|
|
92
|
+
}
|
|
93
|
+
return entry.state;
|
|
94
|
+
},
|
|
95
|
+
peek(threadId) {
|
|
96
|
+
return isOffline() ? offlineEntry(threadId) !== void 0 : memory.peek(threadId);
|
|
97
|
+
},
|
|
98
|
+
forget(threadId) {
|
|
99
|
+
memory.forget(threadId);
|
|
100
|
+
offline.delete(threadId);
|
|
101
|
+
restored.delete(threadId);
|
|
102
|
+
clearOfflineSeed(threadId);
|
|
103
|
+
},
|
|
104
|
+
clear() {
|
|
105
|
+
dropEverything();
|
|
106
|
+
},
|
|
107
|
+
owner: () => ownerId,
|
|
108
|
+
setOwner(next) {
|
|
109
|
+
if (next === ownerId) return hydration;
|
|
110
|
+
const previous = ownerId;
|
|
111
|
+
ownerId = next;
|
|
112
|
+
epoch += 1;
|
|
113
|
+
dropEverything();
|
|
114
|
+
if (previous !== null) void mirror.clearOwner(previous);
|
|
115
|
+
if (next === null) {
|
|
116
|
+
hydration = Promise.resolve();
|
|
117
|
+
return hydration;
|
|
118
|
+
}
|
|
119
|
+
const readEpoch = epoch;
|
|
120
|
+
const read = (primed == null ? void 0 : primed.ownerId) === next ? primed.read : mirror.load(next);
|
|
121
|
+
primed = null;
|
|
122
|
+
hydration = read.then((entries) => {
|
|
123
|
+
if (readEpoch !== epoch || ownerId !== next) return;
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (!offline.has(entry.threadId)) keepOffline(entry.threadId, { state: entry.state, at: entry.at });
|
|
126
|
+
if (memory.peek(entry.threadId)) continue;
|
|
127
|
+
memory.restore(entry.threadId, entry.state, entry.at);
|
|
128
|
+
if (memory.peek(entry.threadId)) restored.add(entry.threadId);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return hydration;
|
|
132
|
+
},
|
|
133
|
+
prime(next) {
|
|
134
|
+
if (next === ownerId || (primed == null ? void 0 : primed.ownerId) === next) return;
|
|
135
|
+
primed = { ownerId: next, read: mirror.load(next) };
|
|
136
|
+
},
|
|
137
|
+
offlineSeedAt: (threadId) => offlineSeeds.get(threadId) ?? null,
|
|
138
|
+
subscribeOfflineSeeds(listener) {
|
|
139
|
+
listeners.add(listener);
|
|
140
|
+
return () => {
|
|
141
|
+
listeners.delete(listener);
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
isRestored: (threadId) => restored.has(threadId),
|
|
145
|
+
whenHydrated: () => hydration,
|
|
146
|
+
async wipe() {
|
|
147
|
+
epoch += 1;
|
|
148
|
+
primed = null;
|
|
149
|
+
dropEverything();
|
|
150
|
+
await mirror.clearAll();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
OFFLINE_SNAPSHOT_MAX_AGE_MS,
|
|
156
|
+
createDurableThreadSnapshotCache
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=durable-thread-snapshot-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-thread-snapshot-cache.js","sources":["../../../../agent-runtime/src/durable-thread-snapshot-cache.ts"],"sourcesContent":["import {\n createDeepAgentThreadSnapshotCache,\n type DeepAgentThreadSnapshotCache,\n} from \"./thread-snapshot-cache\";\nimport { isRestingThreadSnapshot, type ThreadSnapshotMirror } from \"./thread-snapshot-mirror\";\nimport type { DeepAgentStreamValues } from \"./types\";\n\n/**\n * The thread snapshot cache with a durable, encrypted mirror behind it (`thread-snapshot-mirror.ts`):\n * a reload seeds a recently visited thread from what it last showed, and a thread can be read while\n * the transport cannot connect at all.\n *\n * - **Online, nothing changes.** `take` serves the in-memory entry within its age bound (one hour by\n * default), exactly as the plain cache does, and the attach snapshot replaces the seed.\n * - **Offline** (`isOffline()`: the host's own word that Athena can't be reached), `take` serves the\n * thread's last resting transcript this page holds (what it read, or what the mirror kept), up to\n * `offlineMaxAgeMs` old and past the online bound, so a person can read their recent\n * conversations on a plane. Never a mid-stream state: a run the connection dropped is a partial\n * transcript, and the resting one before it is what shows. Every offline seed, however recent, is\n * recorded with its capture time (`offlineSeedAt`) so the host can say the transcript is \"as of\"\n * then; only the next state the wire delivers for the thread (its attach) clears it.\n * - **Owned.** Entries belong to one owner (`setOwner`): a change drops every entry, deletes the\n * previous owner's mirrored records and reads the new owner's. A snapshot captured under one\n * account never seeds a mount under another. `wipe` drops everything (a sign-out).\n */\nexport interface DurableThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /** Bind the cache to the acting owner (`null`: nobody signed in; nothing is mirrored). */\n setOwner(ownerId: string | null): Promise<void>;\n /**\n * Start reading an owner's mirrored snapshots before the owner is known for sure (a host's\n * last-owner hint at boot): `setOwner` adopts the read if that owner is the one bound, and it\n * is discarded otherwise. Binds nothing and deletes nothing.\n */\n prime(ownerId: string): void;\n /** The owner the cache is bound to. */\n owner(): string | null;\n /**\n * When the seed `take` last served for `threadId` was captured, if it was served while Athena\n * could not be reached; `null` otherwise, and again once the wire delivers the thread.\n */\n offlineSeedAt(threadId: string): number | null;\n /** Calls `listener` whenever an offline seed is served or cleared; returns what stops it. */\n subscribeOfflineSeeds(listener: () => void): () => void;\n /** Whether the entry `take` would serve for `threadId` came back from the mirror. */\n isRestored(threadId: string): boolean;\n /** Resolves once the current owner's mirrored snapshots have been read (tests, telemetry). */\n whenHydrated(): Promise<void>;\n /** Drop every entry, in memory and in the mirror (a sign-out, a workspace switch). */\n wipe(): Promise<void>;\n}\n\nexport interface DurableThreadSnapshotCacheOptions {\n mirror: ThreadSnapshotMirror;\n /** Whether Athena can't be reached right now (the host's reachability, or the browser's). */\n isOffline?: () => boolean;\n maxThreads?: number;\n /** The online age bound (the plain cache's). */\n maxAgeMs?: number;\n /** How old a mirrored snapshot may be and still be read offline. */\n offlineMaxAgeMs?: number;\n now?: () => number;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\n/** What a device keeps for reading offline: the offline plan's 30 days. */\nexport const OFFLINE_SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;\n\nfunction browserOffline(): boolean {\n const host = globalThis as { navigator?: { onLine?: boolean } };\n return host.navigator?.onLine === false;\n}\n\nexport function createDurableThreadSnapshotCache({\n mirror,\n isOffline = browserOffline,\n maxThreads = DEFAULT_MAX_THREADS,\n maxAgeMs,\n offlineMaxAgeMs = OFFLINE_SNAPSHOT_MAX_AGE_MS,\n now = () => Date.now(),\n}: DurableThreadSnapshotCacheOptions): DurableThreadSnapshotCache {\n let ownerId: string | null = null;\n // Bumped on every owner change and wipe: a mirror read in flight then must not admit what it read.\n let epoch = 0;\n let primed: { ownerId: string; read: ReturnType<ThreadSnapshotMirror[\"load\"]> } | null = null;\n let hydration: Promise<void> = Promise.resolve();\n // Every resting snapshot readable offline (the mirror's, and this page's own), newest last.\n const offline = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const offlineSeeds = new Map<string, number>();\n const restored = new Set<string>();\n const listeners = new Set<() => void>();\n\n const notify = (): void => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A host listener's fault never reaches the wire path.\n }\n }\n };\n const clearOfflineSeed = (threadId: string): void => {\n if (offlineSeeds.delete(threadId)) notify();\n };\n const keepOffline = (threadId: string, entry: { state: DeepAgentStreamValues; at: number }): void => {\n offline.delete(threadId);\n offline.set(threadId, entry);\n while (offline.size > maxThreads) {\n const oldest = offline.keys().next().value;\n if (oldest === undefined) break;\n offline.delete(oldest);\n }\n };\n\n const memory = createDeepAgentThreadSnapshotCache({\n maxThreads,\n maxAgeMs,\n now,\n onChange: (change) => {\n const owner = ownerId;\n if (owner === null) return;\n switch (change.type) {\n case \"remember\":\n mirror.write({ ownerId: owner, threadId: change.threadId, state: change.state, at: change.at });\n return;\n case \"forget\":\n mirror.remove({ ownerId: owner, threadId: change.threadId });\n return;\n case \"clear\":\n return;\n }\n },\n });\n\n /** The thread's last resting transcript within the offline bound. */\n const offlineEntry = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = offline.get(threadId);\n return entry && now() - entry.at <= offlineMaxAgeMs ? entry : undefined;\n };\n\n const dropEverything = (): void => {\n memory.clear();\n offline.clear();\n restored.clear();\n if (offlineSeeds.size > 0) {\n offlineSeeds.clear();\n notify();\n }\n };\n\n return {\n remember(threadId, state) {\n memory.remember(threadId, state);\n restored.delete(threadId);\n // A mid-stream state keeps the resting transcript before it readable offline, as the mirror\n // keeps that one's record. One too long to seal still reads from memory.\n if (isRestingThreadSnapshot(state)) keepOffline(threadId, { state, at: now() });\n // The wire delivered the thread: the transcript is current again.\n clearOfflineSeed(threadId);\n },\n take(threadId) {\n if (!isOffline()) return memory.take(threadId);\n const entry = offlineEntry(threadId);\n if (!entry) return undefined;\n if (offlineSeeds.get(threadId) !== entry.at) {\n offlineSeeds.set(threadId, entry.at);\n notify();\n }\n return entry.state;\n },\n peek(threadId) {\n return isOffline() ? offlineEntry(threadId) !== undefined : memory.peek(threadId);\n },\n forget(threadId) {\n memory.forget(threadId);\n offline.delete(threadId);\n restored.delete(threadId);\n clearOfflineSeed(threadId);\n },\n clear() {\n dropEverything();\n },\n owner: () => ownerId,\n setOwner(next) {\n if (next === ownerId) return hydration;\n const previous = ownerId;\n ownerId = next;\n epoch += 1;\n dropEverything();\n if (previous !== null) void mirror.clearOwner(previous);\n if (next === null) {\n hydration = Promise.resolve();\n return hydration;\n }\n const readEpoch = epoch;\n const read = primed?.ownerId === next ? primed.read : mirror.load(next);\n primed = null;\n hydration = read.then((entries) => {\n // The owner moved on, or everything was wiped, while the read was in flight.\n if (readEpoch !== epoch || ownerId !== next) return;\n for (const entry of entries) {\n if (!offline.has(entry.threadId)) keepOffline(entry.threadId, { state: entry.state, at: entry.at });\n if (memory.peek(entry.threadId)) continue;\n memory.restore(entry.threadId, entry.state, entry.at);\n if (memory.peek(entry.threadId)) restored.add(entry.threadId);\n }\n });\n return hydration;\n },\n prime(next) {\n if (next === ownerId || primed?.ownerId === next) return;\n primed = { ownerId: next, read: mirror.load(next) };\n },\n offlineSeedAt: (threadId) => offlineSeeds.get(threadId) ?? null,\n subscribeOfflineSeeds(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isRestored: (threadId) => restored.has(threadId),\n whenHydrated: () => hydration,\n async wipe() {\n epoch += 1;\n primed = null;\n dropEverything();\n await mirror.clearAll();\n },\n };\n}\n"],"names":[],"mappings":"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"}
|