@athenaintel/react 0.53.1 → 0.54.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.
- package/README.md +18 -1
- package/dist/agent-runtime/src/call-recap.cjs +2 -3
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -1
- package/dist/agent-runtime/src/call-recap.d.ts +1 -2
- package/dist/agent-runtime/src/call-recap.js +1 -2
- package/dist/agent-runtime/src/call-recap.js.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +7 -3
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.d.ts +10 -2
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js +8 -4
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
- package/dist/agent-runtime/src/dictation-session.d.ts +158 -0
- package/dist/agent-runtime/src/edit-command.cjs +6 -0
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.d.ts +8 -0
- package/dist/agent-runtime/src/edit-command.js +6 -0
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs +6 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.d.ts +13 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js +6 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/agent-runtime/src/index.d.ts +6 -6
- package/dist/agent-runtime/src/message-window.cjs +111 -7
- package/dist/agent-runtime/src/message-window.cjs.map +1 -1
- package/dist/agent-runtime/src/message-window.d.ts +66 -6
- package/dist/agent-runtime/src/message-window.js +112 -8
- package/dist/agent-runtime/src/message-window.js.map +1 -1
- package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
- package/dist/agent-runtime/src/offline-status.d.ts +33 -0
- package/dist/agent-runtime/src/offline-status.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +4 -3
- package/dist/agent-runtime/src/subagent-transcripts.cjs +23 -1
- package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
- package/dist/agent-runtime/src/subagent-transcripts.js +25 -3
- package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
- package/dist/agent-runtime/src/subagents.cjs +112 -0
- package/dist/agent-runtime/src/subagents.cjs.map +1 -1
- package/dist/agent-runtime/src/subagents.d.ts +103 -0
- package/dist/agent-runtime/src/subagents.js +113 -1
- package/dist/agent-runtime/src/subagents.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +37 -9
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +8 -0
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs +6 -0
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.js +7 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +39 -10
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +14 -4
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js +41 -12
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +5 -9
- package/dist/agent-runtime/src/voice-presentation.cjs +34 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.d.ts +35 -0
- package/dist/agent-runtime/src/voice-presentation.js +34 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +4 -2
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +17 -8
- package/dist/assets/AthenaAssetEmbed.js +4 -2
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +7 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/auth/account-identity.cjs +69 -0
- package/dist/auth/account-identity.cjs.map +1 -0
- package/dist/auth/account-identity.d.ts +57 -0
- package/dist/auth/account-identity.js +69 -0
- package/dist/auth/account-identity.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +1 -1
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +1 -1
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.cjs +1 -0
- package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.js +1 -0
- package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
- package/dist/chat/use-statewire-approval-request.cjs +5 -5
- package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
- package/dist/chat/use-statewire-approval-request.d.ts +7 -0
- package/dist/chat/use-statewire-approval-request.js +5 -5
- package/dist/chat/use-statewire-approval-request.js.map +1 -1
- package/dist/chat-ui/mentions/use-account-workspace-id.cjs +18 -58
- package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
- package/dist/chat-ui/mentions/use-account-workspace-id.js +18 -58
- package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
- package/dist/chat-ui/messages/voice-delegation-card.cjs +93 -25
- package/dist/chat-ui/messages/voice-delegation-card.cjs.map +1 -1
- package/dist/chat-ui/messages/voice-delegation-card.d.ts +16 -7
- package/dist/chat-ui/messages/voice-delegation-card.js +95 -27
- package/dist/chat-ui/messages/voice-delegation-card.js.map +1 -1
- package/dist/chat-ui/thread/handoff-cursor.cjs +2 -2
- package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -1
- package/dist/chat-ui/thread/handoff-cursor.js +3 -3
- package/dist/chat-ui/thread/handoff-cursor.js.map +1 -1
- package/dist/collab/client.cjs +94 -6
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +27 -0
- package/dist/collab/client.js +94 -6
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +1 -1
- package/dist/keryx-http/src/initial-sync.cjs +121 -0
- package/dist/keryx-http/src/initial-sync.cjs.map +1 -0
- package/dist/keryx-http/src/initial-sync.d.ts +77 -0
- package/dist/keryx-http/src/initial-sync.js +104 -0
- package/dist/keryx-http/src/initial-sync.js.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +8 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +6 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +8 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/api.cjs +1 -2
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +3 -2
- package/dist/threads/api.js +1 -2
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +5 -7
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +7 -9
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/tools/subagent-task-tool-ui.cjs +15 -5
- package/dist/tools/subagent-task-tool-ui.cjs.map +1 -1
- package/dist/tools/subagent-task-tool-ui.d.ts +5 -4
- package/dist/tools/subagent-task-tool-ui.js +16 -6
- package/dist/tools/subagent-task-tool-ui.js.map +1 -1
- package/package.json +1 -1
|
@@ -60,6 +60,8 @@ export interface DeepAgentMessageWindow {
|
|
|
60
60
|
hasOlder: boolean;
|
|
61
61
|
/** Pages joined onto the head. */
|
|
62
62
|
pages: number;
|
|
63
|
+
/** The conversation the window's message documents belong to (their key's thread); absent before one is mounted. */
|
|
64
|
+
threadId?: string;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* The requests to state for one more page of older history, or `null` when
|
|
@@ -69,12 +71,6 @@ export interface DeepAgentMessageWindow {
|
|
|
69
71
|
* into maximal requests first (see {@link coalescedInterestRequests}).
|
|
70
72
|
*/
|
|
71
73
|
export declare function pageInterestRequests(window: DeepAgentMessageWindow, size?: number): DeepAgentInterestRequest[] | null;
|
|
72
|
-
/**
|
|
73
|
-
* Whether every `stated` request is carried by `carried` (the same anchor, at
|
|
74
|
-
* least as wide): a reconnect's hello that carries what the client last
|
|
75
|
-
* stated — the host remembers a client id's interests — needs no restatement.
|
|
76
|
-
*/
|
|
77
|
-
export declare function interestsCovered(carried: readonly DeepAgentInterestRequest[], stated: readonly DeepAgentInterestRequest[]): boolean;
|
|
78
74
|
/**
|
|
79
75
|
* The window off the mounted documents, or `null` while the head interest has
|
|
80
76
|
* not resolved (pre-attach, before the first `set` answer, a wire without
|
|
@@ -85,6 +81,70 @@ export declare function interestsCovered(carried: readonly DeepAgentInterestRequ
|
|
|
85
81
|
* malformed document reads as absent.
|
|
86
82
|
*/
|
|
87
83
|
export declare function readDeepAgentMessageWindow(mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined): DeepAgentMessageWindow | null;
|
|
84
|
+
/**
|
|
85
|
+
* The view a client holds, as it declares it on a re-attach
|
|
86
|
+
* (`x-athena-deep-agent-interests`): what the host would have remembered for
|
|
87
|
+
* its client id, read off the attach's own interest document.
|
|
88
|
+
*/
|
|
89
|
+
export interface DeepAgentInterestDeclaration {
|
|
90
|
+
/** Every request the interest document echoes that still covers something, the head first. */
|
|
91
|
+
readonly requests: readonly DeepAgentInterestRequest[];
|
|
92
|
+
/** The oldest message the head covers: the host holds the head down to it as the transcript grows. */
|
|
93
|
+
readonly floor?: string;
|
|
94
|
+
/** Per followed subagent lane, the oldest step a follow window covers. */
|
|
95
|
+
readonly follows?: Readonly<Record<string, string>>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The view to declare on a re-attach, read off the mounted interest document,
|
|
99
|
+
* or `null` before one resolved. Requests are taken as the host echoed them
|
|
100
|
+
* (lane `follow`s included); a page whose anchor left the transcript covers
|
|
101
|
+
* nothing and is left out, the head never. The floors are what the host
|
|
102
|
+
* commits from the same document (the head's first message, each lane's
|
|
103
|
+
* deepest follow window's first step).
|
|
104
|
+
*/
|
|
105
|
+
export declare function readDeepAgentInterestDeclaration(mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined): DeepAgentInterestDeclaration | null;
|
|
106
|
+
/**
|
|
107
|
+
* `declaration` as the header value: JSON with every non-ASCII character
|
|
108
|
+
* escaped (a header value is Latin-1 on the wire), or `null` when it is
|
|
109
|
+
* larger than the host reads — the attach then falls back to the host's memo
|
|
110
|
+
* or the declared head, as before.
|
|
111
|
+
*/
|
|
112
|
+
export declare function encodeDeepAgentInterestDeclaration(declaration: DeepAgentInterestDeclaration): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Attach headers with the view a re-attach holds declared
|
|
115
|
+
* (`x-athena-deep-agent-interests`), so its hello carries that view on any
|
|
116
|
+
* host instance. A first attach declares nothing (`null`); an explicit entry
|
|
117
|
+
* wins.
|
|
118
|
+
*/
|
|
119
|
+
export declare function withDeclaredInterests(headers: Record<string, string>, declaration: DeepAgentInterestDeclaration | null): Record<string, string>;
|
|
120
|
+
/**
|
|
121
|
+
* Whether `hello` (a re-attach's window) carries what `held` covered: it
|
|
122
|
+
* reaches `held`'s oldest message, or there is nothing older to reach.
|
|
123
|
+
*/
|
|
124
|
+
export declare function messageWindowCarries({ hello, held, }: {
|
|
125
|
+
hello: DeepAgentMessageWindow;
|
|
126
|
+
held: DeepAgentMessageWindow;
|
|
127
|
+
}): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The statement that loads `held` again after a hello that did not carry it
|
|
130
|
+
* (a host that neither read the declared view nor remembers the client).
|
|
131
|
+
*
|
|
132
|
+
* The requests it stood on, the head widened to reach the first page's anchor:
|
|
133
|
+
* a head holding its floor covers more than it asked for once a run appended
|
|
134
|
+
* past it, and `hello` (the re-attach's own window) shows what was appended
|
|
135
|
+
* while the view was away, which the head must span too. Without pages, the
|
|
136
|
+
* head is widened to everything it covered. Where that is more than one
|
|
137
|
+
* request can state, the whole chain is restated in maximal windows, each
|
|
138
|
+
* anchored inside the one above it (`coalescedInterestRequests`), so no page
|
|
139
|
+
* is left without its anchor. A hello that does not reach the held view at
|
|
140
|
+
* all (more was appended than it shows) gets the widest head one request can
|
|
141
|
+
* state: the best an older host allows.
|
|
142
|
+
*/
|
|
143
|
+
export declare function restatedInterestRequests({ held, hello, }: {
|
|
144
|
+
held: DeepAgentMessageWindow;
|
|
145
|
+
/** The re-attach's own window; omitted, nothing is known to have been appended. */
|
|
146
|
+
hello?: DeepAgentMessageWindow | null;
|
|
147
|
+
}): DeepAgentInterestRequest[];
|
|
88
148
|
/**
|
|
89
149
|
* The windowed wire's read command on the statewire command union, so
|
|
90
150
|
* `sendCommand({ type: 'history/tool_output', … })` typechecks wherever the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEEP_AGENT_MAX_INTERESTS, DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, DEEP_AGENT_INTEREST_PROTOCOL, DEEP_AGENT_MAIN_NS, DEEP_AGENT_CUT_LANE_DOCUMENT,
|
|
1
|
+
import { DEEP_AGENT_MAX_MESSAGE_WINDOW, DEEP_AGENT_MAX_INTERESTS, DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, DEEP_AGENT_INTEREST_PROTOCOL, DEEP_AGENT_MAIN_NS, DEEP_AGENT_CUT_LANE_DOCUMENT, DEEP_AGENT_DEFAULT_MESSAGE_WINDOW, DEEP_AGENT_MIN_MESSAGE_WINDOW, DEEP_AGENT_MESSAGE_INTERESTS_HEADER, DEEP_AGENT_MESSAGE_WINDOW_HEADER, DEEP_AGENT_HISTORY_MESSAGE_COMMAND, DEEP_AGENT_TRUNCATED_CONTENT_KEY, DEEP_AGENT_MAX_MESSAGE_INTERESTS_HEADER_BYTES } from "./generated/runconfig-contract.js";
|
|
2
2
|
import { DEEP_AGENT_STATEWIRE_PROTOCOL } from "./statewire-transport.js";
|
|
3
3
|
const isIndex = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
4
4
|
function clampMessageWindowSize(size) {
|
|
@@ -102,15 +102,11 @@ function pageInterestRequests(window, size) {
|
|
|
102
102
|
if (appended.length <= DEEP_AGENT_MAX_INTERESTS) return appended;
|
|
103
103
|
return coalescedInterestRequests(window.chain, page.before - 1);
|
|
104
104
|
}
|
|
105
|
-
function interestsCovered(carried, stated) {
|
|
106
|
-
return stated.every(
|
|
107
|
-
(request) => carried.some((have) => have.at === request.at && have.before >= request.before)
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
105
|
function readDeepAgentMessageWindow(mounted) {
|
|
111
106
|
var _a, _b, _c;
|
|
112
107
|
if (mounted === void 0) return null;
|
|
113
108
|
let windows;
|
|
109
|
+
let threadId;
|
|
114
110
|
const documents = /* @__PURE__ */ new Map();
|
|
115
111
|
for (const document of mounted.values()) {
|
|
116
112
|
const key = document.key;
|
|
@@ -118,6 +114,7 @@ function readDeepAgentMessageWindow(mounted) {
|
|
|
118
114
|
windows = (_a = readDeepAgentInterestDocument(document.value)) == null ? void 0 : _a.windows;
|
|
119
115
|
} else if (document.protocol === DEEP_AGENT_STATEWIRE_PROTOCOL && key !== null && key[1] === DEEP_AGENT_MAIN_NS && (key.length === 3 || key.length === 4 && key[3] === DEEP_AGENT_CUT_LANE_DOCUMENT)) {
|
|
120
116
|
documents.set(key[2], document.value);
|
|
117
|
+
if (threadId === void 0 && typeof key[0] === "string") threadId = key[0];
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
120
|
const head = windows == null ? void 0 : windows[0];
|
|
@@ -147,7 +144,110 @@ function readDeepAgentMessageWindow(mounted) {
|
|
|
147
144
|
const message = documents.get(entry.id);
|
|
148
145
|
if (message !== void 0) messages.push(message);
|
|
149
146
|
}
|
|
150
|
-
return { requests, chain, messages, hasOlder, pages };
|
|
147
|
+
return { requests, chain, messages, hasOlder, pages, ...threadId !== void 0 && { threadId } };
|
|
148
|
+
}
|
|
149
|
+
const echoedRequest = (window) => {
|
|
150
|
+
const { ns, before, at, after, follow } = window;
|
|
151
|
+
if (ns !== DEEP_AGENT_MAIN_NS || !isIndex(before)) return null;
|
|
152
|
+
if (at !== void 0 && typeof at !== "string") return null;
|
|
153
|
+
if (after !== void 0 && !isIndex(after)) return null;
|
|
154
|
+
const followBefore = typeof follow === "object" && follow !== null ? follow.before : void 0;
|
|
155
|
+
if (follow !== void 0 && !isIndex(followBefore)) return null;
|
|
156
|
+
return {
|
|
157
|
+
ns: DEEP_AGENT_MAIN_NS,
|
|
158
|
+
before,
|
|
159
|
+
...at !== void 0 && { at },
|
|
160
|
+
...after !== void 0 && { after },
|
|
161
|
+
...isIndex(followBefore) && { follow: { before: followBefore } }
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const firstChainId = (chain) => {
|
|
165
|
+
var _a;
|
|
166
|
+
if (!Array.isArray(chain)) return void 0;
|
|
167
|
+
const id = (_a = chain[0]) == null ? void 0 : _a.id;
|
|
168
|
+
return typeof id === "string" && id ? id : void 0;
|
|
169
|
+
};
|
|
170
|
+
function readDeepAgentInterestDeclaration(mounted) {
|
|
171
|
+
var _a;
|
|
172
|
+
if (mounted === void 0) return null;
|
|
173
|
+
let windows;
|
|
174
|
+
for (const document of mounted.values()) {
|
|
175
|
+
if (document.protocol !== DEEP_AGENT_INTEREST_PROTOCOL || document.main !== true) continue;
|
|
176
|
+
const value = document.value;
|
|
177
|
+
if (value !== null && typeof value === "object" && Array.isArray(value.windows)) {
|
|
178
|
+
windows = value.windows;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (windows === void 0 || windows.length === 0) return null;
|
|
182
|
+
const requests = [];
|
|
183
|
+
const deepest = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const [index, window] of windows.entries()) {
|
|
185
|
+
if (typeof window !== "object" || window === null) return null;
|
|
186
|
+
const record = window;
|
|
187
|
+
const request = echoedRequest(record);
|
|
188
|
+
if (request === null) return null;
|
|
189
|
+
if (index === 0 && request.at !== void 0) return null;
|
|
190
|
+
if (index === 0 || firstChainId(record.chain) !== void 0) requests.push(request);
|
|
191
|
+
const follows2 = record.follows;
|
|
192
|
+
if (typeof follows2 !== "object" || follows2 === null) continue;
|
|
193
|
+
for (const [ns, follow] of Object.entries(follows2)) {
|
|
194
|
+
const chain = follow == null ? void 0 : follow.chain;
|
|
195
|
+
if (!Array.isArray(chain) || chain.length <= (((_a = deepest.get(ns)) == null ? void 0 : _a.length) ?? 0)) continue;
|
|
196
|
+
deepest.set(ns, chain);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const floor = firstChainId(windows[0].chain);
|
|
200
|
+
const follows = {};
|
|
201
|
+
for (const [ns, chain] of deepest) {
|
|
202
|
+
const step = firstChainId(chain);
|
|
203
|
+
if (step !== void 0) follows[ns] = step;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
requests,
|
|
207
|
+
...floor !== void 0 && { floor },
|
|
208
|
+
...Object.keys(follows).length > 0 && { follows }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function encodeDeepAgentInterestDeclaration(declaration) {
|
|
212
|
+
const encoded = JSON.stringify(declaration).replace(
|
|
213
|
+
/[^ -~]/g,
|
|
214
|
+
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
215
|
+
);
|
|
216
|
+
return encoded.length > DEEP_AGENT_MAX_MESSAGE_INTERESTS_HEADER_BYTES ? null : encoded;
|
|
217
|
+
}
|
|
218
|
+
function withDeclaredInterests(headers, declaration) {
|
|
219
|
+
if (declaration === null || DEEP_AGENT_MESSAGE_INTERESTS_HEADER in headers) return headers;
|
|
220
|
+
const encoded = encodeDeepAgentInterestDeclaration(declaration);
|
|
221
|
+
return encoded === null ? headers : { ...headers, [DEEP_AGENT_MESSAGE_INTERESTS_HEADER]: encoded };
|
|
222
|
+
}
|
|
223
|
+
function messageWindowCarries({
|
|
224
|
+
hello,
|
|
225
|
+
held
|
|
226
|
+
}) {
|
|
227
|
+
var _a;
|
|
228
|
+
const oldest = (_a = held.chain[0]) == null ? void 0 : _a.id;
|
|
229
|
+
if (oldest === void 0 || !hello.hasOlder) return true;
|
|
230
|
+
return hello.chain.some((entry) => entry.id === oldest);
|
|
231
|
+
}
|
|
232
|
+
function restatedInterestRequests({
|
|
233
|
+
held,
|
|
234
|
+
hello = null
|
|
235
|
+
}) {
|
|
236
|
+
var _a, _b;
|
|
237
|
+
const [head, ...pages] = held.requests;
|
|
238
|
+
if (head === void 0) return [];
|
|
239
|
+
const newest = (_a = held.chain.at(-1)) == null ? void 0 : _a.id;
|
|
240
|
+
const since = hello === null || newest === void 0 ? -1 : hello.chain.findIndex((entry) => entry.id === newest);
|
|
241
|
+
const chain = since === -1 || hello === null ? held.chain : [...held.chain, ...hello.chain.slice(since + 1)];
|
|
242
|
+
const anchor = (_b = pages[0]) == null ? void 0 : _b.at;
|
|
243
|
+
const start = anchor === void 0 ? 0 : chain.findIndex((entry) => entry.id === anchor);
|
|
244
|
+
const covered = hello !== null && since === -1 ? DEEP_AGENT_MAX_MESSAGE_WINDOW : chain.length - Math.max(0, start);
|
|
245
|
+
if (covered > DEEP_AGENT_MAX_MESSAGE_WINDOW) {
|
|
246
|
+
const coalesced = coalescedInterestRequests(chain, 0);
|
|
247
|
+
if (coalesced !== null) return coalesced;
|
|
248
|
+
}
|
|
249
|
+
const before = Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.max(head.before, covered));
|
|
250
|
+
return [{ ...head, before }, ...pages];
|
|
151
251
|
}
|
|
152
252
|
function buildToolOutputCommand({ messageId }) {
|
|
153
253
|
return { type: DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, messageId };
|
|
@@ -191,10 +291,12 @@ export {
|
|
|
191
291
|
buildHistoryMessageCommand,
|
|
192
292
|
buildToolOutputCommand,
|
|
193
293
|
clampMessageWindowSize,
|
|
294
|
+
encodeDeepAgentInterestDeclaration,
|
|
194
295
|
headInterestRequest,
|
|
195
|
-
|
|
296
|
+
messageWindowCarries,
|
|
196
297
|
pageInterestRequest,
|
|
197
298
|
pageInterestRequests,
|
|
299
|
+
readDeepAgentInterestDeclaration,
|
|
198
300
|
readDeepAgentInterestDocument,
|
|
199
301
|
readDeepAgentInterestResolution,
|
|
200
302
|
readDeepAgentMessageWindow,
|
|
@@ -202,6 +304,8 @@ export {
|
|
|
202
304
|
readToolOutputResult,
|
|
203
305
|
readTruncatedToolContent,
|
|
204
306
|
readTruncatedToolOutputArtifact,
|
|
307
|
+
restatedInterestRequests,
|
|
308
|
+
withDeclaredInterests,
|
|
205
309
|
withDeclaredMessageWindow
|
|
206
310
|
};
|
|
207
311
|
//# sourceMappingURL=message-window.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-window.js","sources":["../../../../agent-runtime/src/message-window.ts"],"sourcesContent":["import type { LangChainMessage } from '@assistant-ui/react-langgraph';\nimport type { StatewireDocuments } from 'statewire';\nimport {\n DEEP_AGENT_CUT_LANE_DOCUMENT,\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n DEEP_AGENT_HISTORY_MESSAGE_COMMAND,\n DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND,\n DEEP_AGENT_INTEREST_PROTOCOL,\n DEEP_AGENT_MAIN_NS,\n DEEP_AGENT_MAX_INTERESTS,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_MESSAGE_WINDOW_HEADER,\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n DEEP_AGENT_TRUNCATED_CONTENT_KEY,\n} from './generated/runconfig-contract';\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from './statewire-transport';\n\n/**\n * The interest window — the client half of agora's\n * `agora/graphs/athena_deep_agent/message_window.py`.\n *\n * The wire main document carries no `messages`. A client states its interests\n * on the `athena/interest` lane as one array (`set(requests)`): the first is\n * the head (the newest `before` messages of the `main` namespace), the rest\n * are pages anchored `at` the first message of the window they extend. The\n * host holds one ephemeral main document per attach, `{ windows }`, one\n * resolution per request in order (the request echoed plus the covered\n * `chain` and `more` flags; an anchor that vanished leaves an empty window in\n * place), and one shared `athena` document per covered message, keyed\n * `[threadId, \"main\", messageId]` — or `[threadId, \"main\", messageId, \"cut\"]`\n * for a finished card a version-2 view carries cut to its newest steps (its\n * whole lane pages in through `history/lane`, see `lane-history.ts`).\n * {@link readDeepAgentMessageWindow} joins them into the transcript the\n * converter renders.\n *\n * Two host rules shape the client half. The head window never drops below\n * the oldest message it has carried on an attach (a run's appends grow the\n * view, nothing loaded slides out), so a page stays anchored at the head's\n * first message while the transcript grows; and the host remembers a client\n * id's interests across its attaches, so a reconnect's hello carries the\n * view the client held. A fresh client's hello carries the head it declared\n * on the attach headers ({@link withDeclaredMessageWindow}) — a statement\n * cannot ride the first attach, the hello negotiates the lanes.\n *\n * Pages are appended one per load until the host's interest cap, then the\n * loaded range is restated in as few maximal requests as it takes\n * ({@link pageInterestRequests}); the whole transcript stays reachable up to\n * `MAX_INTERESTS × MAX_MESSAGE_WINDOW` messages. A truncated tool output\n * comes back whole through `history/tool_output` ({@link buildToolOutputCommand});\n * a message's stored form — provenance blobs and all — through\n * `history/message` ({@link buildHistoryMessageCommand}).\n */\n\nconst isIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\n/** What a surface declares for its window: the interest size (the host clamps to its bounds). */\nexport interface DeepAgentMessageWindowDeclaration {\n size?: number;\n}\n\nexport function clampMessageWindowSize(size?: number): number {\n if (size === undefined || !Number.isFinite(size)) return DEEP_AGENT_DEFAULT_MESSAGE_WINDOW;\n return Math.max(\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.trunc(size))\n );\n}\n\n/** An `athena/interest` request over the linear transcript. */\nexport interface DeepAgentInterestRequest {\n readonly ns: typeof DEEP_AGENT_MAIN_NS;\n /** Messages ending at the anchor (the tail without `at`). */\n readonly before: number;\n /** Anchor message id; a page's is the first message of the window it extends. */\n readonly at?: string;\n /** Messages past the anchor (requires `at`). */\n readonly after?: number;\n /** The newest steps of every subagent lane a covered card names (see `lane-follow.ts`). */\n readonly follow?: { readonly before: number };\n}\n\nexport function headInterestRequest(size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size) };\n}\n\nexport function pageInterestRequest(at: string, size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size), at };\n}\n\n/**\n * Attach headers with the head window declared (`x-athena-deep-agent-window`):\n * the hello of a client the host does not remember carries the newest `size`\n * messages. Part of the one attach recipe, so a pooled dial asks for the shape\n * the thread's own attach would. An explicit entry (tests, overrides) wins.\n */\nexport function withDeclaredMessageWindow(\n headers: Record<string, string>,\n declaration: DeepAgentMessageWindowDeclaration = {}\n): Record<string, string> {\n if (DEEP_AGENT_MESSAGE_WINDOW_HEADER in headers) return headers;\n return {\n ...headers,\n [DEEP_AGENT_MESSAGE_WINDOW_HEADER]: String(clampMessageWindowSize(declaration.size)),\n };\n}\n\nexport interface DeepAgentInterestChainEntry {\n id: string;\n siblings: string[];\n}\n\n/** The host's resolution document of one interest: the request echoed plus its coverage. */\nexport interface DeepAgentInterestResolution extends DeepAgentInterestRequest {\n /** Covered message ids, oldest first (a page ends at its anchor). */\n chain: DeepAgentInterestChainEntry[];\n more: { before: boolean; after: boolean };\n}\n\nexport function readDeepAgentInterestResolution(value: unknown): DeepAgentInterestResolution | null {\n if (typeof value !== 'object' || value === null) return null;\n const { ns, before, at, after, chain, more } = value as Record<string, unknown>;\n if (ns !== DEEP_AGENT_MAIN_NS || !isIndex(before) || !Array.isArray(chain)) return null;\n if (at !== undefined && typeof at !== 'string') return null;\n if (after !== undefined && !isIndex(after)) return null;\n if (typeof more !== 'object' || more === null) return null;\n const { before: moreBefore, after: moreAfter } = more as Record<string, unknown>;\n if (typeof moreBefore !== 'boolean' || typeof moreAfter !== 'boolean') return null;\n const entries: DeepAgentInterestChainEntry[] = [];\n for (const entry of chain) {\n const { id, siblings } = (entry ?? {}) as Record<string, unknown>;\n if (typeof id !== 'string') return null;\n entries.push({\n id,\n siblings: Array.isArray(siblings) ? siblings.filter((s) => typeof s === 'string') : [id],\n });\n }\n return {\n ns: DEEP_AGENT_MAIN_NS,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n chain: entries,\n more: { before: moreBefore, after: moreAfter },\n };\n}\n\n/** The attach's interest document: one resolution per stated request, in order. */\nexport interface DeepAgentInterestDocument {\n windows: DeepAgentInterestResolution[];\n}\n\nexport function readDeepAgentInterestDocument(value: unknown): DeepAgentInterestDocument | null {\n if (typeof value !== 'object' || value === null) return null;\n const { windows } = value as Record<string, unknown>;\n if (!Array.isArray(windows)) return null;\n const resolutions: DeepAgentInterestResolution[] = [];\n for (const window of windows) {\n const resolution = readDeepAgentInterestResolution(window);\n if (resolution === null) return null;\n resolutions.push(resolution);\n }\n return { windows: resolutions };\n}\n\nconst interestRequest = ({ ns, before, at, after }: DeepAgentInterestRequest): DeepAgentInterestRequest => ({\n ns,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n});\n\n/** The loaded transcript window, joined from the head interest and its anchored pages. */\nexport interface DeepAgentMessageWindow {\n /** The requests the joined view stands on, in order (the head first): what a `set` restates. A stale page (its anchor gone) or one the head already covers is left out. */\n requests: DeepAgentInterestRequest[];\n /** Covered chain, oldest first. */\n chain: DeepAgentInterestChainEntry[];\n /** The chain's message documents, in chain order (an unmounted one is skipped). */\n messages: LangChainMessage[];\n /** Whether the host holds messages older than the window. */\n hasOlder: boolean;\n /** Pages joined onto the head. */\n pages: number;\n}\n\n/**\n * The loaded chain plus `grow` older messages, restated in as few requests as\n * it takes: a maximal head, then maximal pages each anchored at the first\n * message of the window above it. `null` once even that exceeds the host's\n * interest cap — the reachable depth is `MAX_INTERESTS × MAX_MESSAGE_WINDOW`.\n */\nfunction coalescedInterestRequests(\n chain: readonly DeepAgentInterestChainEntry[],\n grow: number\n): DeepAgentInterestRequest[] | null {\n const loaded = chain.length;\n const requests: DeepAgentInterestRequest[] = [\n { ns: DEEP_AGENT_MAIN_NS, before: Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, loaded) },\n ];\n // `low` is the chain index of the lowest message the plan covers so far.\n let low = loaded - (requests[0] as DeepAgentInterestRequest).before;\n while (low > 0) {\n const before = Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, low + 1);\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before,\n at: (chain[low] as DeepAgentInterestChainEntry).id,\n });\n low = low + 1 - before;\n }\n // The plan covers the chain; the new messages extend its lowest request\n // when that stays within one window, else they are a page of their own.\n const last = requests[requests.length - 1] as DeepAgentInterestRequest;\n if (last.before + grow <= DEEP_AGENT_MAX_MESSAGE_WINDOW) {\n requests[requests.length - 1] = { ...last, before: last.before + grow };\n } else {\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before: grow + 1,\n at: (chain[0] as DeepAgentInterestChainEntry).id,\n });\n }\n return requests.length > DEEP_AGENT_MAX_INTERESTS ? null : requests;\n}\n\n/**\n * The requests to state for one more page of older history, or `null` when\n * there is nothing to load (no anchor, the beginning reached) or the host's\n * interest cap is spent. One page is appended, anchored at the window's first\n * message, while the cap allows; at the cap the loaded range is coalesced\n * into maximal requests first (see {@link coalescedInterestRequests}).\n */\nexport function pageInterestRequests(\n window: DeepAgentMessageWindow,\n size?: number\n): DeepAgentInterestRequest[] | null {\n const anchor = window.chain[0]?.id;\n if (anchor === undefined || !window.hasOlder) return null;\n const page = pageInterestRequest(anchor, size);\n const appended = [...window.requests, page];\n if (appended.length <= DEEP_AGENT_MAX_INTERESTS) return appended;\n // A page's `before` counts its anchor: it adds `before - 1` older messages.\n return coalescedInterestRequests(window.chain, page.before - 1);\n}\n\n/**\n * Whether every `stated` request is carried by `carried` (the same anchor, at\n * least as wide): a reconnect's hello that carries what the client last\n * stated — the host remembers a client id's interests — needs no restatement.\n */\nexport function interestsCovered(\n carried: readonly DeepAgentInterestRequest[],\n stated: readonly DeepAgentInterestRequest[]\n): boolean {\n return stated.every((request) =>\n carried.some((have) => have.at === request.at && have.before >= request.before)\n );\n}\n\n/**\n * The window off the mounted documents, or `null` while the head interest has\n * not resolved (pre-attach, before the first `set` answer, a wire without\n * documents). A page joins wherever its anchor sits in the chain so far — at\n * the first message it extends the view, inside the head (the head widened\n * past it) it adds nothing and is left out of `requests`; a page whose anchor\n * left the transcript (a rewind emptied it in place) is left out too. A\n * malformed document reads as absent.\n */\nexport function readDeepAgentMessageWindow(\n mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined\n): DeepAgentMessageWindow | null {\n if (mounted === undefined) return null;\n let windows: DeepAgentInterestResolution[] | undefined;\n const documents = new Map<string, LangChainMessage>();\n for (const document of mounted.values()) {\n const key = document.key;\n if (document.protocol === DEEP_AGENT_INTEREST_PROTOCOL && document.main === true) {\n windows = readDeepAgentInterestDocument(document.value)?.windows;\n } else if (\n document.protocol === DEEP_AGENT_STATEWIRE_PROTOCOL &&\n key !== null &&\n key[1] === DEEP_AGENT_MAIN_NS &&\n (key.length === 3 || (key.length === 4 && key[3] === DEEP_AGENT_CUT_LANE_DOCUMENT))\n ) {\n documents.set(key[2] as string, document.value as LangChainMessage);\n }\n }\n const head = windows?.[0];\n if (windows === undefined || head === undefined) return null;\n let chain = head.chain;\n let hasOlder = head.more.before;\n const requests = [interestRequest(head)];\n const loaded = new Set(chain.map((entry) => entry.id));\n let pages = 0;\n for (const page of windows.slice(1)) {\n if (page.at === undefined || !loaded.has(page.at)) continue;\n const anchorAt = page.chain.findIndex((entry) => entry.id === page.at);\n if (anchorAt < 0) continue;\n // Both windows are ranges of one linear transcript sharing the anchor:\n // what the page holds below it and the chain does not is a prefix.\n const fresh = page.chain.slice(0, anchorAt).filter((entry) => !loaded.has(entry.id));\n if (fresh.length === 0) {\n // Nothing new, but a page that starts where the loaded chain starts\n // has looked below it: its `more.before` is the answer for the edge.\n if (page.chain[0]?.id === chain[0]?.id) hasOlder = page.more.before;\n continue;\n }\n chain = [...fresh, ...chain];\n for (const entry of fresh) loaded.add(entry.id);\n hasOlder = page.more.before;\n requests.push(interestRequest(page));\n pages += 1;\n }\n const messages: LangChainMessage[] = [];\n for (const entry of chain) {\n const message = documents.get(entry.id);\n if (message !== undefined) messages.push(message);\n }\n return { requests, chain, messages, hasOlder, pages };\n}\n\n/**\n * The windowed wire's read command on the statewire command union, so\n * `sendCommand({ type: 'history/tool_output', … })` typechecks wherever the\n * deep-agent host is driven (`DeepAgentThread.history_tool_output`, an\n * attach-ACL read).\n */\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** A tool output the windowed wire carried cut, whole. */\n historyToolOutput: ToolOutputCommand;\n /** One stored message whole — the per-client shape, by command. */\n historyMessage: HistoryMessageCommand;\n }\n }\n}\n\n/** The `history/tool_output` command: a truncated tool output, whole. */\nexport interface ToolOutputCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildToolOutputCommand({ messageId }: { messageId: string }): ToolOutputCommand {\n return { type: DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, messageId };\n}\n\nexport interface ToolOutputResult {\n messageId: string;\n content: unknown;\n}\n\nexport function readToolOutputResult(payload: unknown): ToolOutputResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, content } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n return { messageId, content };\n}\n\n/**\n * The `history/message` command: a message as the host stores it — the\n * model-provenance blobs the wire strips and an untruncated tool output\n * included. Every client receives one projected wire form; this is the one\n * per-client shape, fetched on demand like `history/tool_output`.\n */\nexport interface HistoryMessageCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_MESSAGE_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildHistoryMessageCommand({\n messageId,\n}: {\n messageId: string;\n}): HistoryMessageCommand {\n return { type: DEEP_AGENT_HISTORY_MESSAGE_COMMAND, messageId };\n}\n\nexport interface HistoryMessageResult {\n messageId: string;\n message: LangChainMessage;\n}\n\nexport function readHistoryMessageResult(payload: unknown): HistoryMessageResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, message } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n if (typeof message !== 'object' || message === null) return null;\n return { messageId, message: message as LangChainMessage };\n}\n\n/** The wire marker on a tool message whose content the host cut. */\nexport interface TruncatedToolContent {\n /** Length of the full output, in characters. */\n chars: number;\n}\n\n/**\n * The truncation marker off a raw wire message, or `null` when the message's\n * content is whole. Pure and selector-safe on the marker object.\n */\nexport function readTruncatedToolContent(message: unknown): TruncatedToolContent | null {\n if (typeof message !== 'object' || message === null) return null;\n const marker = (message as Record<string, unknown>)[DEEP_AGENT_TRUNCATED_CONTENT_KEY];\n if (typeof marker !== 'object' || marker === null) return null;\n const chars = (marker as Record<string, unknown>).chars;\n return isIndex(chars) ? { chars } : null;\n}\n\n/**\n * `artifact` key the shared converter lifts a truncated tool output's marker\n * into (with the tool message id), so a tool card can ask for the full output\n * — the converter joins the ToolMessage into its tool-call part and would\n * otherwise drop the wire marker.\n */\nexport const ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY = '_athena_content_truncated';\n\nexport interface TruncatedToolOutputArtifact extends TruncatedToolContent {\n /** The tool message to name in `history/tool_output`. */\n messageId: string;\n}\n\n/**\n * The lifted truncation marker off a tool-call part's `artifact`, or `null`.\n */\nexport function readTruncatedToolOutputArtifact(\n artifact: unknown\n): TruncatedToolOutputArtifact | null {\n if (typeof artifact !== 'object' || artifact === null) return null;\n const lifted = (artifact as Record<string, unknown>)[ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY];\n if (typeof lifted !== 'object' || lifted === null) return null;\n const { chars, messageId } = lifted as Record<string, unknown>;\n if (!isIndex(chars) || typeof messageId !== 'string' || !messageId) return null;\n return { chars, messageId };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-window.js","sources":["../../../../agent-runtime/src/message-window.ts"],"sourcesContent":["import type { LangChainMessage } from '@assistant-ui/react-langgraph';\nimport type { StatewireDocuments } from 'statewire';\nimport {\n DEEP_AGENT_CUT_LANE_DOCUMENT,\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n DEEP_AGENT_HISTORY_MESSAGE_COMMAND,\n DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND,\n DEEP_AGENT_INTEREST_PROTOCOL,\n DEEP_AGENT_MAIN_NS,\n DEEP_AGENT_MAX_INTERESTS,\n DEEP_AGENT_MAX_MESSAGE_INTERESTS_HEADER_BYTES,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_MESSAGE_INTERESTS_HEADER,\n DEEP_AGENT_MESSAGE_WINDOW_HEADER,\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n DEEP_AGENT_TRUNCATED_CONTENT_KEY,\n} from './generated/runconfig-contract';\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from './statewire-transport';\n\n/**\n * The interest window — the client half of agora's\n * `agora/graphs/athena_deep_agent/message_window.py`.\n *\n * The wire main document carries no `messages`. A client states its interests\n * on the `athena/interest` lane as one array (`set(requests)`): the first is\n * the head (the newest `before` messages of the `main` namespace), the rest\n * are pages anchored `at` the first message of the window they extend. The\n * host holds one ephemeral main document per attach, `{ windows }`, one\n * resolution per request in order (the request echoed plus the covered\n * `chain` and `more` flags; an anchor that vanished leaves an empty window in\n * place), and one shared `athena` document per covered message, keyed\n * `[threadId, \"main\", messageId]` — or `[threadId, \"main\", messageId, \"cut\"]`\n * for a finished card a version-2 view carries cut to its newest steps (its\n * whole lane pages in through `history/lane`, see `lane-history.ts`).\n * {@link readDeepAgentMessageWindow} joins them into the transcript the\n * converter renders.\n *\n * Two host rules shape the client half. The head window never drops below\n * the oldest message it has carried on an attach (a run's appends grow the\n * view, nothing loaded slides out), so a page stays anchored at the head's\n * first message while the transcript grows; and a re-attach's hello carries\n * the view the client held. A fresh client's hello carries the head it\n * declared on the attach headers ({@link withDeclaredMessageWindow}) — a\n * statement cannot ride the first attach, the hello negotiates the lanes. A\n * re-attach declares the view it holds there too\n * ({@link readDeepAgentInterestDeclaration}, {@link withDeclaredInterests}),\n * so its hello carries every page it had loaded on whichever host instance it\n * lands on: the host's own memo of a client id lives on one instance, and a\n * deploy moves the thread to another.\n *\n * Pages are appended one per load until the host's interest cap, then the\n * loaded range is restated in as few maximal requests as it takes\n * ({@link pageInterestRequests}), up to `MAX_INTERESTS × MAX_MESSAGE_WINDOW`\n * messages. The interest document lists every loaded message and the host\n * keeps it within a byte budget, which binds first: about a thousand\n * messages, after which the host refuses a page as `oversized-interest`\n * (`useDeepAgentWindowSync` then reports `canLoadOlder` false). A truncated tool output\n * comes back whole through `history/tool_output` ({@link buildToolOutputCommand});\n * a message's stored form — provenance blobs and all — through\n * `history/message` ({@link buildHistoryMessageCommand}).\n */\n\nconst isIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\n/** What a surface declares for its window: the interest size (the host clamps to its bounds). */\nexport interface DeepAgentMessageWindowDeclaration {\n size?: number;\n}\n\nexport function clampMessageWindowSize(size?: number): number {\n if (size === undefined || !Number.isFinite(size)) return DEEP_AGENT_DEFAULT_MESSAGE_WINDOW;\n return Math.max(\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.trunc(size))\n );\n}\n\n/** An `athena/interest` request over the linear transcript. */\nexport interface DeepAgentInterestRequest {\n readonly ns: typeof DEEP_AGENT_MAIN_NS;\n /** Messages ending at the anchor (the tail without `at`). */\n readonly before: number;\n /** Anchor message id; a page's is the first message of the window it extends. */\n readonly at?: string;\n /** Messages past the anchor (requires `at`). */\n readonly after?: number;\n /** The newest steps of every subagent lane a covered card names (see `lane-follow.ts`). */\n readonly follow?: { readonly before: number };\n}\n\nexport function headInterestRequest(size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size) };\n}\n\nexport function pageInterestRequest(at: string, size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size), at };\n}\n\n/**\n * Attach headers with the head window declared (`x-athena-deep-agent-window`):\n * the hello of a client the host does not remember carries the newest `size`\n * messages. Part of the one attach recipe, so a pooled dial asks for the shape\n * the thread's own attach would. An explicit entry (tests, overrides) wins.\n */\nexport function withDeclaredMessageWindow(\n headers: Record<string, string>,\n declaration: DeepAgentMessageWindowDeclaration = {}\n): Record<string, string> {\n if (DEEP_AGENT_MESSAGE_WINDOW_HEADER in headers) return headers;\n return {\n ...headers,\n [DEEP_AGENT_MESSAGE_WINDOW_HEADER]: String(clampMessageWindowSize(declaration.size)),\n };\n}\n\nexport interface DeepAgentInterestChainEntry {\n id: string;\n siblings: string[];\n}\n\n/** The host's resolution document of one interest: the request echoed plus its coverage. */\nexport interface DeepAgentInterestResolution extends DeepAgentInterestRequest {\n /** Covered message ids, oldest first (a page ends at its anchor). */\n chain: DeepAgentInterestChainEntry[];\n more: { before: boolean; after: boolean };\n}\n\nexport function readDeepAgentInterestResolution(value: unknown): DeepAgentInterestResolution | null {\n if (typeof value !== 'object' || value === null) return null;\n const { ns, before, at, after, chain, more } = value as Record<string, unknown>;\n if (ns !== DEEP_AGENT_MAIN_NS || !isIndex(before) || !Array.isArray(chain)) return null;\n if (at !== undefined && typeof at !== 'string') return null;\n if (after !== undefined && !isIndex(after)) return null;\n if (typeof more !== 'object' || more === null) return null;\n const { before: moreBefore, after: moreAfter } = more as Record<string, unknown>;\n if (typeof moreBefore !== 'boolean' || typeof moreAfter !== 'boolean') return null;\n const entries: DeepAgentInterestChainEntry[] = [];\n for (const entry of chain) {\n const { id, siblings } = (entry ?? {}) as Record<string, unknown>;\n if (typeof id !== 'string') return null;\n entries.push({\n id,\n siblings: Array.isArray(siblings) ? siblings.filter((s) => typeof s === 'string') : [id],\n });\n }\n return {\n ns: DEEP_AGENT_MAIN_NS,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n chain: entries,\n more: { before: moreBefore, after: moreAfter },\n };\n}\n\n/** The attach's interest document: one resolution per stated request, in order. */\nexport interface DeepAgentInterestDocument {\n windows: DeepAgentInterestResolution[];\n}\n\nexport function readDeepAgentInterestDocument(value: unknown): DeepAgentInterestDocument | null {\n if (typeof value !== 'object' || value === null) return null;\n const { windows } = value as Record<string, unknown>;\n if (!Array.isArray(windows)) return null;\n const resolutions: DeepAgentInterestResolution[] = [];\n for (const window of windows) {\n const resolution = readDeepAgentInterestResolution(window);\n if (resolution === null) return null;\n resolutions.push(resolution);\n }\n return { windows: resolutions };\n}\n\nconst interestRequest = ({ ns, before, at, after }: DeepAgentInterestRequest): DeepAgentInterestRequest => ({\n ns,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n});\n\n/** The loaded transcript window, joined from the head interest and its anchored pages. */\nexport interface DeepAgentMessageWindow {\n /** The requests the joined view stands on, in order (the head first): what a `set` restates. A stale page (its anchor gone) or one the head already covers is left out. */\n requests: DeepAgentInterestRequest[];\n /** Covered chain, oldest first. */\n chain: DeepAgentInterestChainEntry[];\n /** The chain's message documents, in chain order (an unmounted one is skipped). */\n messages: LangChainMessage[];\n /** Whether the host holds messages older than the window. */\n hasOlder: boolean;\n /** Pages joined onto the head. */\n pages: number;\n /** The conversation the window's message documents belong to (their key's thread); absent before one is mounted. */\n threadId?: string;\n}\n\n/**\n * The loaded chain plus `grow` older messages, restated in as few requests as\n * it takes: a maximal head, then maximal pages each anchored at the first\n * message of the window above it. `null` once even that exceeds the host's\n * interest cap — the reachable depth is `MAX_INTERESTS × MAX_MESSAGE_WINDOW`.\n */\nfunction coalescedInterestRequests(\n chain: readonly DeepAgentInterestChainEntry[],\n grow: number\n): DeepAgentInterestRequest[] | null {\n const loaded = chain.length;\n const requests: DeepAgentInterestRequest[] = [\n { ns: DEEP_AGENT_MAIN_NS, before: Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, loaded) },\n ];\n // `low` is the chain index of the lowest message the plan covers so far.\n let low = loaded - (requests[0] as DeepAgentInterestRequest).before;\n while (low > 0) {\n const before = Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, low + 1);\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before,\n at: (chain[low] as DeepAgentInterestChainEntry).id,\n });\n low = low + 1 - before;\n }\n // The plan covers the chain; the new messages extend its lowest request\n // when that stays within one window, else they are a page of their own.\n const last = requests[requests.length - 1] as DeepAgentInterestRequest;\n if (last.before + grow <= DEEP_AGENT_MAX_MESSAGE_WINDOW) {\n requests[requests.length - 1] = { ...last, before: last.before + grow };\n } else {\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before: grow + 1,\n at: (chain[0] as DeepAgentInterestChainEntry).id,\n });\n }\n return requests.length > DEEP_AGENT_MAX_INTERESTS ? null : requests;\n}\n\n/**\n * The requests to state for one more page of older history, or `null` when\n * there is nothing to load (no anchor, the beginning reached) or the host's\n * interest cap is spent. One page is appended, anchored at the window's first\n * message, while the cap allows; at the cap the loaded range is coalesced\n * into maximal requests first (see {@link coalescedInterestRequests}).\n */\nexport function pageInterestRequests(\n window: DeepAgentMessageWindow,\n size?: number\n): DeepAgentInterestRequest[] | null {\n const anchor = window.chain[0]?.id;\n if (anchor === undefined || !window.hasOlder) return null;\n const page = pageInterestRequest(anchor, size);\n const appended = [...window.requests, page];\n if (appended.length <= DEEP_AGENT_MAX_INTERESTS) return appended;\n // A page's `before` counts its anchor: it adds `before - 1` older messages.\n return coalescedInterestRequests(window.chain, page.before - 1);\n}\n\n/**\n * The window off the mounted documents, or `null` while the head interest has\n * not resolved (pre-attach, before the first `set` answer, a wire without\n * documents). A page joins wherever its anchor sits in the chain so far — at\n * the first message it extends the view, inside the head (the head widened\n * past it) it adds nothing and is left out of `requests`; a page whose anchor\n * left the transcript (a rewind emptied it in place) is left out too. A\n * malformed document reads as absent.\n */\nexport function readDeepAgentMessageWindow(\n mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined\n): DeepAgentMessageWindow | null {\n if (mounted === undefined) return null;\n let windows: DeepAgentInterestResolution[] | undefined;\n let threadId: string | undefined;\n const documents = new Map<string, LangChainMessage>();\n for (const document of mounted.values()) {\n const key = document.key;\n if (document.protocol === DEEP_AGENT_INTEREST_PROTOCOL && document.main === true) {\n windows = readDeepAgentInterestDocument(document.value)?.windows;\n } else if (\n document.protocol === DEEP_AGENT_STATEWIRE_PROTOCOL &&\n key !== null &&\n key[1] === DEEP_AGENT_MAIN_NS &&\n (key.length === 3 || (key.length === 4 && key[3] === DEEP_AGENT_CUT_LANE_DOCUMENT))\n ) {\n documents.set(key[2] as string, document.value as LangChainMessage);\n if (threadId === undefined && typeof key[0] === 'string') threadId = key[0];\n }\n }\n const head = windows?.[0];\n if (windows === undefined || head === undefined) return null;\n let chain = head.chain;\n let hasOlder = head.more.before;\n const requests = [interestRequest(head)];\n const loaded = new Set(chain.map((entry) => entry.id));\n let pages = 0;\n for (const page of windows.slice(1)) {\n if (page.at === undefined || !loaded.has(page.at)) continue;\n const anchorAt = page.chain.findIndex((entry) => entry.id === page.at);\n if (anchorAt < 0) continue;\n // Both windows are ranges of one linear transcript sharing the anchor:\n // what the page holds below it and the chain does not is a prefix.\n const fresh = page.chain.slice(0, anchorAt).filter((entry) => !loaded.has(entry.id));\n if (fresh.length === 0) {\n // Nothing new, but a page that starts where the loaded chain starts\n // has looked below it: its `more.before` is the answer for the edge.\n if (page.chain[0]?.id === chain[0]?.id) hasOlder = page.more.before;\n continue;\n }\n chain = [...fresh, ...chain];\n for (const entry of fresh) loaded.add(entry.id);\n hasOlder = page.more.before;\n requests.push(interestRequest(page));\n pages += 1;\n }\n const messages: LangChainMessage[] = [];\n for (const entry of chain) {\n const message = documents.get(entry.id);\n if (message !== undefined) messages.push(message);\n }\n return { requests, chain, messages, hasOlder, pages, ...(threadId !== undefined && { threadId }) };\n}\n\n/**\n * The view a client holds, as it declares it on a re-attach\n * (`x-athena-deep-agent-interests`): what the host would have remembered for\n * its client id, read off the attach's own interest document.\n */\nexport interface DeepAgentInterestDeclaration {\n /** Every request the interest document echoes that still covers something, the head first. */\n readonly requests: readonly DeepAgentInterestRequest[];\n /** The oldest message the head covers: the host holds the head down to it as the transcript grows. */\n readonly floor?: string;\n /** Per followed subagent lane, the oldest step a follow window covers. */\n readonly follows?: Readonly<Record<string, string>>;\n}\n\nconst echoedRequest = (window: Record<string, unknown>): DeepAgentInterestRequest | null => {\n const { ns, before, at, after, follow } = window;\n if (ns !== DEEP_AGENT_MAIN_NS || !isIndex(before)) return null;\n if (at !== undefined && typeof at !== 'string') return null;\n if (after !== undefined && !isIndex(after)) return null;\n const followBefore =\n typeof follow === 'object' && follow !== null ? (follow as Record<string, unknown>).before : undefined;\n if (follow !== undefined && !isIndex(followBefore)) return null;\n return {\n ns: DEEP_AGENT_MAIN_NS,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n ...(isIndex(followBefore) && { follow: { before: followBefore } }),\n };\n};\n\nconst firstChainId = (chain: unknown): string | undefined => {\n if (!Array.isArray(chain)) return undefined;\n const id = (chain[0] as { id?: unknown } | undefined)?.id;\n return typeof id === 'string' && id ? id : undefined;\n};\n\n/**\n * The view to declare on a re-attach, read off the mounted interest document,\n * or `null` before one resolved. Requests are taken as the host echoed them\n * (lane `follow`s included); a page whose anchor left the transcript covers\n * nothing and is left out, the head never. The floors are what the host\n * commits from the same document (the head's first message, each lane's\n * deepest follow window's first step).\n */\nexport function readDeepAgentInterestDeclaration(\n mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined\n): DeepAgentInterestDeclaration | null {\n if (mounted === undefined) return null;\n let windows: unknown[] | undefined;\n for (const document of mounted.values()) {\n if (document.protocol !== DEEP_AGENT_INTEREST_PROTOCOL || document.main !== true) continue;\n const value = document.value as { windows?: unknown } | null;\n if (value !== null && typeof value === 'object' && Array.isArray(value.windows)) {\n windows = value.windows;\n }\n }\n if (windows === undefined || windows.length === 0) return null;\n const requests: DeepAgentInterestRequest[] = [];\n const deepest = new Map<string, unknown[]>();\n for (const [index, window] of windows.entries()) {\n if (typeof window !== 'object' || window === null) return null;\n const record = window as Record<string, unknown>;\n const request = echoedRequest(record);\n if (request === null) return null;\n if (index === 0 && request.at !== undefined) return null;\n if (index === 0 || firstChainId(record.chain) !== undefined) requests.push(request);\n const follows = record.follows;\n if (typeof follows !== 'object' || follows === null) continue;\n for (const [ns, follow] of Object.entries(follows)) {\n const chain = (follow as { chain?: unknown } | null)?.chain;\n if (!Array.isArray(chain) || chain.length <= (deepest.get(ns)?.length ?? 0)) continue;\n deepest.set(ns, chain);\n }\n }\n const floor = firstChainId((windows[0] as Record<string, unknown>).chain);\n const follows: Record<string, string> = {};\n for (const [ns, chain] of deepest) {\n const step = firstChainId(chain);\n if (step !== undefined) follows[ns] = step;\n }\n return {\n requests,\n ...(floor !== undefined && { floor }),\n ...(Object.keys(follows).length > 0 && { follows }),\n };\n}\n\n/**\n * `declaration` as the header value: JSON with every non-ASCII character\n * escaped (a header value is Latin-1 on the wire), or `null` when it is\n * larger than the host reads — the attach then falls back to the host's memo\n * or the declared head, as before.\n */\nexport function encodeDeepAgentInterestDeclaration(\n declaration: DeepAgentInterestDeclaration\n): string | null {\n // Everything outside printable ASCII as a JSON escape (a surrogate pair as two).\n const encoded = JSON.stringify(declaration).replace(\n /[^ -~]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`\n );\n return encoded.length > DEEP_AGENT_MAX_MESSAGE_INTERESTS_HEADER_BYTES ? null : encoded;\n}\n\n/**\n * Attach headers with the view a re-attach holds declared\n * (`x-athena-deep-agent-interests`), so its hello carries that view on any\n * host instance. A first attach declares nothing (`null`); an explicit entry\n * wins.\n */\nexport function withDeclaredInterests(\n headers: Record<string, string>,\n declaration: DeepAgentInterestDeclaration | null\n): Record<string, string> {\n if (declaration === null || DEEP_AGENT_MESSAGE_INTERESTS_HEADER in headers) return headers;\n const encoded = encodeDeepAgentInterestDeclaration(declaration);\n return encoded === null ? headers : { ...headers, [DEEP_AGENT_MESSAGE_INTERESTS_HEADER]: encoded };\n}\n\n/**\n * Whether `hello` (a re-attach's window) carries what `held` covered: it\n * reaches `held`'s oldest message, or there is nothing older to reach.\n */\nexport function messageWindowCarries({\n hello,\n held,\n}: {\n hello: DeepAgentMessageWindow;\n held: DeepAgentMessageWindow;\n}): boolean {\n const oldest = held.chain[0]?.id;\n if (oldest === undefined || !hello.hasOlder) return true;\n return hello.chain.some((entry) => entry.id === oldest);\n}\n\n/**\n * The statement that loads `held` again after a hello that did not carry it\n * (a host that neither read the declared view nor remembers the client).\n *\n * The requests it stood on, the head widened to reach the first page's anchor:\n * a head holding its floor covers more than it asked for once a run appended\n * past it, and `hello` (the re-attach's own window) shows what was appended\n * while the view was away, which the head must span too. Without pages, the\n * head is widened to everything it covered. Where that is more than one\n * request can state, the whole chain is restated in maximal windows, each\n * anchored inside the one above it (`coalescedInterestRequests`), so no page\n * is left without its anchor. A hello that does not reach the held view at\n * all (more was appended than it shows) gets the widest head one request can\n * state: the best an older host allows.\n */\nexport function restatedInterestRequests({\n held,\n hello = null,\n}: {\n held: DeepAgentMessageWindow;\n /** The re-attach's own window; omitted, nothing is known to have been appended. */\n hello?: DeepAgentMessageWindow | null;\n}): DeepAgentInterestRequest[] {\n const [head, ...pages] = held.requests;\n if (head === undefined) return [];\n const newest = held.chain.at(-1)?.id;\n const since =\n hello === null || newest === undefined\n ? -1\n : hello.chain.findIndex((entry) => entry.id === newest);\n const chain = since === -1 || hello === null ? held.chain : [...held.chain, ...hello.chain.slice(since + 1)];\n const anchor = pages[0]?.at;\n const start = anchor === undefined ? 0 : chain.findIndex((entry) => entry.id === anchor);\n const covered =\n hello !== null && since === -1\n ? DEEP_AGENT_MAX_MESSAGE_WINDOW\n : chain.length - Math.max(0, start);\n if (covered > DEEP_AGENT_MAX_MESSAGE_WINDOW) {\n const coalesced = coalescedInterestRequests(chain, 0);\n if (coalesced !== null) return coalesced;\n }\n const before = Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.max(head.before, covered));\n return [{ ...head, before }, ...pages];\n}\n\n/**\n * The windowed wire's read command on the statewire command union, so\n * `sendCommand({ type: 'history/tool_output', … })` typechecks wherever the\n * deep-agent host is driven (`DeepAgentThread.history_tool_output`, an\n * attach-ACL read).\n */\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** A tool output the windowed wire carried cut, whole. */\n historyToolOutput: ToolOutputCommand;\n /** One stored message whole — the per-client shape, by command. */\n historyMessage: HistoryMessageCommand;\n }\n }\n}\n\n/** The `history/tool_output` command: a truncated tool output, whole. */\nexport interface ToolOutputCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildToolOutputCommand({ messageId }: { messageId: string }): ToolOutputCommand {\n return { type: DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, messageId };\n}\n\nexport interface ToolOutputResult {\n messageId: string;\n content: unknown;\n}\n\nexport function readToolOutputResult(payload: unknown): ToolOutputResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, content } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n return { messageId, content };\n}\n\n/**\n * The `history/message` command: a message as the host stores it — the\n * model-provenance blobs the wire strips and an untruncated tool output\n * included. Every client receives one projected wire form; this is the one\n * per-client shape, fetched on demand like `history/tool_output`.\n */\nexport interface HistoryMessageCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_MESSAGE_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildHistoryMessageCommand({\n messageId,\n}: {\n messageId: string;\n}): HistoryMessageCommand {\n return { type: DEEP_AGENT_HISTORY_MESSAGE_COMMAND, messageId };\n}\n\nexport interface HistoryMessageResult {\n messageId: string;\n message: LangChainMessage;\n}\n\nexport function readHistoryMessageResult(payload: unknown): HistoryMessageResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, message } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n if (typeof message !== 'object' || message === null) return null;\n return { messageId, message: message as LangChainMessage };\n}\n\n/** The wire marker on a tool message whose content the host cut. */\nexport interface TruncatedToolContent {\n /** Length of the full output, in characters. */\n chars: number;\n}\n\n/**\n * The truncation marker off a raw wire message, or `null` when the message's\n * content is whole. Pure and selector-safe on the marker object.\n */\nexport function readTruncatedToolContent(message: unknown): TruncatedToolContent | null {\n if (typeof message !== 'object' || message === null) return null;\n const marker = (message as Record<string, unknown>)[DEEP_AGENT_TRUNCATED_CONTENT_KEY];\n if (typeof marker !== 'object' || marker === null) return null;\n const chars = (marker as Record<string, unknown>).chars;\n return isIndex(chars) ? { chars } : null;\n}\n\n/**\n * `artifact` key the shared converter lifts a truncated tool output's marker\n * into (with the tool message id), so a tool card can ask for the full output\n * — the converter joins the ToolMessage into its tool-call part and would\n * otherwise drop the wire marker.\n */\nexport const ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY = '_athena_content_truncated';\n\nexport interface TruncatedToolOutputArtifact extends TruncatedToolContent {\n /** The tool message to name in `history/tool_output`. */\n messageId: string;\n}\n\n/**\n * The lifted truncation marker off a tool-call part's `artifact`, or `null`.\n */\nexport function readTruncatedToolOutputArtifact(\n artifact: unknown\n): TruncatedToolOutputArtifact | null {\n if (typeof artifact !== 'object' || artifact === null) return null;\n const lifted = (artifact as Record<string, unknown>)[ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY];\n if (typeof lifted !== 'object' || lifted === null) return null;\n const { chars, messageId } = lifted as Record<string, unknown>;\n if (!isIndex(chars) || typeof messageId !== 'string' || !messageId) return null;\n return { chars, messageId };\n}\n"],"names":["follows"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offline-status.cjs","sources":["../../../../agent-runtime/src/offline-status.ts"],"sourcesContent":["/**\n * Athena's offline language: the one set of states and words every app uses to\n * say where the person stands when the network, or Athena, is out of reach —\n * Olympus, the desktop app, the phone app, the Chrome extension and the Office\n * add-in.\n *\n * The rule the words follow: say what still works before what doesn't. Work\n * done while Athena can't be reached is kept on the device and syncs when\n * Athena is back, so every message leads with that promise. Nothing here is\n * shown while Athena answers and nothing waits; the \"back\" messages are brief.\n *\n * Pure strings and small functions, no UI library: each app draws them its own\n * way (a banner, a header chip, a tooltip, a sheet). Reachability itself is\n * each app's own (the browser's or OS's online flag AND agora answering its\n * health read); this module only names the result.\n */\n\n/** Where the app stands with Athena. */\nexport type ConnectionState =\n /** Athena answers. Nothing is shown. */\n | 'online'\n /** The device has no network. */\n | 'offline'\n /** The network is up but Athena doesn't answer. */\n | 'unreachable'\n /** Athena answers again and what waited is being sent. */\n | 'reconnecting'\n /** Just back, and everything that waited was sent. Shown briefly. */\n | 'synced';\n\n/** Where one document's edits stand, for the chip in its header. */\nexport type DocumentSyncState =\n /** In Athena. */\n | 'saved'\n /** Being sent to Athena. */\n | 'saving'\n /** Kept on this device while Athena can't be reached; syncs when it is back. */\n | 'on-device'\n /** Kept only while this tab or app stays open (no device storage); syncs when Athena is back. */\n | 'in-memory'\n /** Sending what was kept on this device. */\n | 'syncing'\n /** Some changes overlap with Athena's version: the person chooses. */\n | 'needs-review'\n /** The document can no longer take these edits (access or state changed): kept, not sent. */\n | 'view-only'\n /**\n * Athena answers but isn't taking this document's changes for now: it holds the document while it\n * repairs it, or hasn't confirmed the latest changes for longer than usual (`SavingDelayCause`\n * says which). Kept, and saved once Athena takes them. Never said while Athena can't be reached:\n * that is `on-device` or `in-memory`.\n */\n | 'delayed';\n\n/** Why a document's saving is delayed while Athena answers (`DocumentSyncState` `delayed`). */\nexport type SavingDelayCause =\n /** Athena holds the document while it repairs it, and takes no changes to it until then. */\n | 'repairing'\n /** Athena hasn't confirmed the latest changes for longer than usual. */\n | 'unconfirmed';\n\n/** How a sentence names the device the person is on. */\nexport type DeviceNoun =\n | 'this browser'\n | 'this Mac'\n | 'this computer'\n | 'this phone'\n | 'this tablet'\n | 'this device';\n\n/**\n * What the app really keeps of work done while Athena can't be reached, so the\n * words never promise more: `device` (stored on the device, survives closing),\n * `memory` (kept only while this tab or app stays open: a tenant that turns\n * device copies off, or storage the app can't open), `none` (a surface that\n * keeps no work offline at all, like the Chrome side panel).\n */\nexport type OfflineKeeping = 'device' | 'memory' | 'none';\n\n/** A title and the one line under it (a banner, a sheet, a tooltip's heading). */\nexport interface StatusCopy {\n title: string;\n detail: string;\n}\n\n/** A short label for a chip or a disabled control, and its one-line explanation. */\nexport interface LabelCopy {\n label: string;\n detail: string;\n}\n\nfunction plural(count: number, one: string, many: string): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n/** What a tab or an app is called when work lasts only while it stays open. */\nfunction openContainer(device: DeviceNoun): string {\n return device === 'this browser' ? 'this tab' : 'the app';\n}\n\n/** The promise after \"You're offline\" / \"Can't reach Athena\", honest to what is kept. */\nfunction keepingPromise({\n keeping,\n device,\n when,\n}: {\n keeping: OfflineKeeping;\n device: DeviceNoun;\n when: string;\n}): string {\n switch (keeping) {\n case 'device':\n return `You can keep working. Changes are saved on ${device} and sync ${when}.`;\n case 'memory':\n return `You can keep working, but changes are kept only until you close ${openContainer(device)}. They sync ${when}.`;\n case 'none':\n return `You’re still signed in. This reconnects ${when}.`;\n }\n}\n\n/**\n * The app-wide status line, or `null` while Athena answers and nothing waits.\n * `waiting` counts changes not in Athena yet: while `reconnecting` they are\n * being sent; while `online` they wait for something else (a copy that sends\n * when it is next opened), and the line stays until they are gone.\n * `keeping` is what the app really keeps offline (default `device`). A surface\n * that keeps nothing (`none`) has nothing to sync, so its way back is only\n * \"Back online\", never \"Syncing…\" or \"All changes synced\".\n */\nexport function connectionCopy({\n state,\n device,\n waiting = 0,\n keeping = 'device',\n}: {\n state: ConnectionState;\n device: DeviceNoun;\n waiting?: number;\n keeping?: OfflineKeeping;\n}): StatusCopy | null {\n switch (state) {\n case 'online':\n return waiting > 0\n ? {\n title: 'Not synced yet',\n detail: `${plural(waiting, 'change', 'changes')} on ${device} ${waiting === 1 ? 'hasn’t' : 'haven’t'} synced yet.`,\n }\n : null;\n case 'offline':\n return {\n title: 'You’re offline',\n detail: keepingPromise({ keeping, device, when: 'when you’re back online' }),\n };\n case 'unreachable':\n return {\n title: 'Can’t reach Athena',\n detail: keepingPromise({ keeping, device, when: 'when Athena is back' }),\n };\n case 'reconnecting':\n if (keeping === 'none') return { title: 'Back online', detail: '' };\n return {\n title: 'Back online',\n detail:\n waiting > 0 ? `Syncing ${plural(waiting, 'change', 'changes')}…` : 'Syncing…',\n };\n case 'synced':\n return keeping === 'none'\n ? { title: 'Back online', detail: '' }\n : { title: 'All changes synced', detail: '' };\n }\n}\n\n/**\n * The line under \"Saving delayed\": why saving waits, and what is kept meanwhile, never promising\n * more than the app keeps.\n */\nfunction savingDelayedDetail({\n delay,\n keeping,\n device,\n}: {\n delay: SavingDelayCause;\n keeping: Exclude<OfflineKeeping, 'none'>;\n device: DeviceNoun;\n}): string {\n if (delay === 'repairing') {\n return keeping === 'memory'\n ? `You can keep working, but your changes are kept only until you close ${openContainer(device)}. They save once Athena finishes repairing this document.`\n : `You can keep working. Your changes are kept on ${device} and save once Athena finishes repairing this document.`;\n }\n return keeping === 'memory'\n ? `Athena is taking longer than usual to save your latest changes. They’re kept only until you close ${openContainer(device)}, so keep it open.`\n : `Athena is taking longer than usual to save your latest changes. They’re kept on ${device} in the meantime.`;\n}\n\n/**\n * The chip in a document's header, and the line it explains itself with. For `delayed`, `delay`\n * says why (default `unconfirmed`) and `keeping` whether the changes are kept on the device or only\n * while the tab or app stays open (default `device`); every other state ignores both.\n */\nexport function documentSyncCopy({\n state,\n device,\n delay = 'unconfirmed',\n keeping = 'device',\n}: {\n state: DocumentSyncState;\n device: DeviceNoun;\n delay?: SavingDelayCause;\n keeping?: Exclude<OfflineKeeping, 'none'>;\n}): LabelCopy {\n switch (state) {\n case 'saved':\n return { label: 'Saved', detail: 'Saved to Athena.' };\n case 'saving':\n return { label: 'Saving…', detail: 'Saving to Athena.' };\n case 'on-device':\n return {\n label: `Saved on ${device}`,\n detail: `Saved on ${device}. Your changes sync when Athena is back.`,\n };\n case 'in-memory':\n return {\n label: 'Not saved yet',\n detail: `Your changes are kept only until you close ${openContainer(device)}, and save when Athena is back.`,\n };\n case 'syncing':\n return { label: 'Syncing…', detail: `Sending the changes saved on ${device}.` };\n case 'needs-review':\n return {\n label: 'Review changes',\n detail: 'Some of your changes overlap with changes made in Athena. Choose what to keep.',\n };\n case 'view-only':\n return {\n label: 'View only',\n detail: `You can’t edit this anymore. Your changes stay on ${device}.`,\n };\n case 'delayed':\n return { label: 'Saving delayed', detail: savingDelayedDetail({ delay, keeping, device }) };\n }\n}\n\n/**\n * How long a document's edits may wait unconfirmed on a connection that is up before its saving\n * reads as delayed (`delayed`, `unconfirmed`), and before its room asks Keryx why: the stall window\n * every editor shares (`ACK_STALL_MS` in `@athenaintel/asset-host/local-updates`, which Olympus's\n * `room-sync-store.test.ts` holds equal).\n */\nexport const UNCONFIRMED_EDITS_STALL_MS = 15_000;\n\n/**\n * Whether a document's saving is delayed, and why (`DocumentSyncState` `delayed`): only while Athena\n * answers and the document's room is connected; `repairing` when Keryx refuses the room's writes\n * (quarantined while it is repaired: said at once, before anything waits), `unconfirmed` once its\n * edits have stayed unconfirmed past {@link UNCONFIRMED_EDITS_STALL_MS}; `null` otherwise. Every\n * app's chip asks it after a hold and after edits kept without a connection, and ahead of syncing\n * and saving, so a room that refuses writes never spins and being offline is never a delay.\n */\nexport function savingDelayCause({\n reachable,\n connected,\n writesRefused,\n unconfirmedStalled,\n}: {\n /** Athena answers (the app's reachability, never the socket alone). */\n reachable: boolean;\n /** The document's room is connected. */\n connected: boolean;\n /** Keryx's document status says the room takes no writes for now. */\n writesRefused: boolean;\n /** Local edits have waited unconfirmed past the stall window on this connection. */\n unconfirmedStalled: boolean;\n}): SavingDelayCause | null {\n if (!reachable || !connected) return null;\n if (writesRefused) return 'repairing';\n return unconfirmedStalled ? 'unconfirmed' : null;\n}\n\n/** When something that needs Athena works again (the second half of `NEEDS_CONNECTION`'s line). */\nconst AVAILABLE_AGAIN = 'It’s available again when you’re back online.';\n\n/**\n * The reason on a control that needs Athena (share, run, upload, invite…),\n * shown disabled while Athena can't be reached rather than failing when used.\n */\nexport const NEEDS_CONNECTION: LabelCopy = {\n label: 'Needs a connection',\n detail: `This needs Athena. ${AVAILABLE_AGAIN}`,\n};\n\n/** What a composer was given to attach: a photo (from the phone's library or camera) or any file. */\nexport type AttachmentKind = 'photo' | 'file';\n\n/**\n * What a composer says when a photo or file is picked, pasted or dropped while\n * Athena can't be reached. It isn't attached: only Athena can take the upload.\n * In a chat whose messages wait to be sent meanwhile (`messagesWait`: the chat\n * keeps an outbox), the message itself still goes, so that comes first. Where\n * nothing waits (the Chrome side panel, a launcher that keeps its draft), it\n * says only when attaching works again, as `NEEDS_CONNECTION` does.\n */\nexport function attachmentNeedsConnection({\n kind,\n messagesWait,\n}: {\n kind: AttachmentKind;\n messagesWait: boolean;\n}): string {\n const attaching = `Attaching ${kind === 'photo' ? 'a photo' : 'a file'} needs Athena. ${AVAILABLE_AGAIN}`;\n return messagesWait\n ? `You can still send your message: it goes when Athena is back. ${attaching}`\n : attaching;\n}\n\n/** What a sent message carries besides its text that can't wait for Athena. */\nfunction heldExtras(withQuote: boolean): string {\n return withQuote ? 'an attachment or a quote' : 'an attachment';\n}\n\n/**\n * What a composer says when a message is sent while Athena can't be reached\n * with an attachment that never reached Athena (or, `withQuote`, a quote the\n * chat can't keep waiting). Its text could wait in the chat outbox; the\n * attachment can't, so the draft stays as it is.\n */\nexport function attachedMessageNeedsConnection({\n withQuote = false,\n}: {\n withQuote?: boolean;\n} = {}): string {\n return `Your text can wait and go when Athena is back, but ${heldExtras(withQuote)} can’t. Remove ${\n withQuote ? 'it' : 'the attachment'\n } to send the text.`;\n}\n\n/**\n * What a composer says when a message with an attachment (or, `withQuote`, a\n * quote) is sent while messages written when Athena couldn't be reached still\n * wait to go: those go first, and this one can't wait behind them.\n */\nexport function attachedMessageBehindWaiting({\n withQuote = false,\n}: {\n withQuote?: boolean;\n} = {}): string {\n return `Messages you wrote while Athena couldn’t be reached go first. A message with ${heldExtras(\n withQuote\n )} can’t wait behind them, so send it once they’ve gone.`;\n}\n\n/**\n * The reason on New Presentation while Athena can't be reached, in a workspace\n * whose new presentations start from its design system's template: a\n * presentation made offline starts blank, and only Athena can copy the\n * template, so none is made until it can.\n */\nexport const NEW_PRESENTATION_NEEDS_TEMPLATE: LabelCopy = {\n label: 'Needs a connection',\n detail:\n 'New presentations in this workspace start from its design template, which needs Athena. You can make one when you’re back online.',\n};\n\n/**\n * Whether an item opens without a connection on this device, for lists.\n * `canKeep` says whether opening it online would keep a copy here (the\n * deployment allows device copies and the item's type is one that is kept):\n * only then is the person told to open it once.\n */\nexport function offlineAvailabilityCopy({\n available,\n canKeep,\n device,\n}: {\n available: boolean;\n canKeep: boolean;\n device: DeviceNoun;\n}): LabelCopy {\n if (available) {\n return { label: 'Available offline', detail: `Opens without a connection on ${device}.` };\n }\n return canKeep\n ? {\n label: 'Not available offline',\n detail: `Open it once while online to keep it on ${device}.`,\n }\n : { label: 'Not available offline', detail: 'This opens only with a connection.' };\n}\n\n/**\n * The one-time explainer shown the first time the person goes offline, on a\n * surface that keeps work there (`keeping` `device`). A surface that keeps\n * nothing (`none`: the Chrome side panel, the Office task pane) shows no\n * explainer; its status line already says all that holds.\n */\nexport function firstOfflineExplainer({ device }: { device: DeviceNoun }): StatusCopy & {\n action: string;\n} {\n return {\n title: 'You can keep working offline',\n detail: `Documents you’ve opened recently keep working on ${device}. Your changes sync when you’re back online.`,\n action: 'Got it',\n };\n}\n\n/**\n * The summary heading of the one \"Waiting to sync\" list, from what waits on\n * the device; `null` when nothing does.\n */\nexport function waitingSummary({\n changes = 0,\n messages = 0,\n recordings = 0,\n}: {\n changes?: number;\n messages?: number;\n recordings?: number;\n}): string | null {\n const parts = [\n changes > 0 ? plural(changes, 'change', 'changes') : null,\n messages > 0 ? plural(messages, 'message', 'messages') : null,\n recordings > 0 ? plural(recordings, 'recording', 'recordings') : null,\n ].filter((part): part is string => part !== null);\n return parts.length === 0 ? null : `Waiting to sync: ${parts.join(', ')}`;\n}\n\n/**\n * Where one item in the \"Waiting to sync\" list stands: a change, a message or a\n * recording made while Athena couldn't be reached.\n */\nexport type WaitingItemState =\n /** Saved on the device; it syncs when Athena is back. */\n | 'waiting'\n /** Being sent to Athena now. */\n | 'syncing'\n /**\n * Kept only while this tab or app stays open: it couldn't be saved on the\n * device (the organization keeps nothing on devices, or storage failed).\n */\n | 'in-memory'\n /**\n * Saved on the device in something that isn't open (a document closed before\n * its edits synced, one made offline): it syncs when it is next opened with a\n * connection (opening it offline sends nothing).\n */\n | 'when-opened'\n /** Made in another workspace: it syncs once that workspace is open again, with a connection. */\n | 'other-workspace'\n /** It doesn't go on its own: the person chooses (a message that waited more than a day). */\n | 'needs-you'\n /** Athena didn't take it: the app adds why, and what the person can do about it. */\n | 'not-synced';\n\n/** The state of one item in the \"Waiting to sync\" list, and the line that explains it. */\nexport function waitingItemCopy({\n state,\n device,\n}: {\n state: WaitingItemState;\n device: DeviceNoun;\n}): LabelCopy {\n switch (state) {\n case 'waiting':\n return {\n label: 'Waiting to sync',\n detail: `Saved on ${device}. It syncs when Athena is back.`,\n };\n case 'syncing':\n return { label: 'Syncing…', detail: `Sending what was saved on ${device}.` };\n case 'in-memory':\n return {\n label: 'Not saved yet',\n detail: `Kept only until you close ${openContainer(device)}. It syncs when Athena is back.`,\n };\n case 'when-opened':\n return {\n label: 'Not synced yet',\n detail: `Saved on ${device}. It syncs when you next open it with a connection.`,\n };\n case 'other-workspace':\n return {\n label: 'Waiting for its workspace',\n detail: 'It syncs once you’re in the workspace it was made in, with a connection.',\n };\n case 'needs-you':\n return { label: 'Waiting for you', detail: 'Open it to choose what happens to it.' };\n case 'not-synced':\n return { label: 'Couldn’t sync', detail: 'Athena didn’t take it.' };\n }\n}\n\n/** The headings of the \"Waiting to sync\" list, one per kind of item. */\nexport const WAITING_SECTION_TITLES = {\n changes: 'Changes',\n messages: 'Messages',\n recordings: 'Recordings',\n} as const;\n\n/**\n * What the person can do with an item Athena didn't take (`not-synced`) in the\n * \"Waiting to sync\" list: send it again, or delete the copy on the device. That\n * copy is the item's only one, so the app asks first (`deleteFromDeviceConfirm`)\n * and never deletes it as a side effect of anything else.\n */\nexport function notSyncedActions({ device }: { device: DeviceNoun }): {\n retry: string;\n remove: string;\n} {\n return { retry: 'Try again', remove: `Delete from ${device}` };\n}\n\n/** One of `notSyncedActions`, or showing where the item is kept on the device. */\nexport type NotSyncedAction = 'retry' | 'reveal' | 'remove';\n\n/**\n * What the person hears when the device couldn't do what they asked for an\n * item Athena didn't take (its files, not Athena, failed).\n */\nexport function notSyncedActionFailed({\n action,\n device,\n}: {\n action: NotSyncedAction;\n device: DeviceNoun;\n}): string {\n switch (action) {\n case 'retry':\n return 'Couldn’t send it again. Try again in a moment.';\n case 'reveal':\n return `Couldn’t show where it’s kept on ${device}.`;\n case 'remove':\n return `Couldn’t delete it from ${device}.`;\n }\n}\n\n/**\n * The question before deleting such an item's copy on the device, naming the\n * item: the destructive choice is never the default.\n */\nexport function deleteFromDeviceConfirm({\n name,\n device,\n}: {\n name: string;\n device: DeviceNoun;\n}): StatusCopy & { confirm: string; cancel: string } {\n return {\n title: `Delete “${name}” from ${device}?`,\n detail: 'Athena didn’t take it, so this is its only copy. Deleting it can’t be undone.',\n confirm: 'Delete',\n cancel: 'Cancel',\n };\n}\n"],"names":[],"mappings":";;AAyPO,MAAM,6BAA6B;AA+B1C,MAAM,kBAAkB;AAMjB,MAAM,mBAA8B;AAAA,EACzC,OAAO;AAAA,EACP,QAAQ,sBAAsB,eAAe;AAC/C;AAaO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGW;AACT,QAAM,YAAY,aAA4C,QAAQ,kBAAkB,eAAe;AACvG,SAAO,eACH,iEAAiE,SAAS,KAC1E;AACN;AAGA,SAAS,WAAW,WAA4B;AAC9C,SAAO,YAAY,6BAA6B;AAClD;AAQO,SAAS,+BAA+B;AAAA,EAC7C,YAAY;AACd,IAEI,IAAY;AACd,SAAO,sDAAsD,WAAW,SAAS,CAAC,kBAChF,YAAY,OAAO,gBACrB;AACF;AAOO,SAAS,6BAA6B;AAAA,EAC3C,YAAY;AACd,IAEI,IAAY;AACd,SAAO,gFAAgF;AAAA,IACrF;AAAA,EAAA,CACD;AACH;;;;;;"}
|
|
1
|
+
{"version":3,"file":"offline-status.cjs","sources":["../../../../agent-runtime/src/offline-status.ts"],"sourcesContent":["/**\n * Athena's offline language: the one set of states and words every app uses to\n * say where the person stands when the network, or Athena, is out of reach —\n * Olympus, the desktop app, the phone app, the Chrome extension and the Office\n * add-in.\n *\n * The rule the words follow: say what still works before what doesn't. Work\n * done while Athena can't be reached is kept on the device and syncs when\n * Athena is back, so every message leads with that promise. Nothing here is\n * shown while Athena answers and nothing waits; the \"back\" messages are brief.\n *\n * Pure strings and small functions, no UI library: each app draws them its own\n * way (a banner, a header chip, a tooltip, a sheet). Reachability itself is\n * each app's own (the browser's or OS's online flag AND agora answering its\n * health read); this module only names the result.\n */\n\n/** Where the app stands with Athena. */\nexport type ConnectionState =\n /** Athena answers. Nothing is shown. */\n | 'online'\n /** The device has no network. */\n | 'offline'\n /** The network is up but Athena doesn't answer. */\n | 'unreachable'\n /** Athena answers again and what waited is being sent. */\n | 'reconnecting'\n /** Just back, and everything that waited was sent. Shown briefly. */\n | 'synced';\n\n/** Where one document's edits stand, for the chip in its header. */\nexport type DocumentSyncState =\n /** In Athena. */\n | 'saved'\n /** Being sent to Athena. */\n | 'saving'\n /** Kept on this device while Athena can't be reached; syncs when it is back. */\n | 'on-device'\n /** Kept only while this tab or app stays open (no device storage); syncs when Athena is back. */\n | 'in-memory'\n /** Sending what was kept on this device. */\n | 'syncing'\n /** Some changes overlap with Athena's version: the person chooses. */\n | 'needs-review'\n /** The document can no longer take these edits (access or state changed): kept, not sent. */\n | 'view-only'\n /**\n * Athena answers but isn't taking this document's changes for now: it holds the document while it\n * repairs it, or hasn't confirmed the latest changes for longer than usual (`SavingDelayCause`\n * says which). Kept, and saved once Athena takes them. Never said while Athena can't be reached:\n * that is `on-device` or `in-memory`.\n */\n | 'delayed';\n\n/** Why a document's saving is delayed while Athena answers (`DocumentSyncState` `delayed`). */\nexport type SavingDelayCause =\n /** Athena holds the document while it repairs it, and takes no changes to it until then. */\n | 'repairing'\n /** Athena hasn't confirmed the latest changes for longer than usual. */\n | 'unconfirmed';\n\n/** How a sentence names the device the person is on. */\nexport type DeviceNoun =\n | 'this browser'\n | 'this Mac'\n | 'this computer'\n | 'this phone'\n | 'this tablet'\n | 'this device';\n\n/**\n * What the app really keeps of work done while Athena can't be reached, so the\n * words never promise more: `device` (stored on the device, survives closing),\n * `memory` (kept only while this tab or app stays open: a tenant that turns\n * device copies off, or storage the app can't open), `none` (a surface that\n * keeps no work offline at all, like the Chrome side panel).\n */\nexport type OfflineKeeping = 'device' | 'memory' | 'none';\n\n/** A title and the one line under it (a banner, a sheet, a tooltip's heading). */\nexport interface StatusCopy {\n title: string;\n detail: string;\n}\n\n/** A short label for a chip or a disabled control, and its one-line explanation. */\nexport interface LabelCopy {\n label: string;\n detail: string;\n}\n\nfunction plural(count: number, one: string, many: string): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n/** What a tab or an app is called when work lasts only while it stays open. */\nfunction openContainer(device: DeviceNoun): string {\n return device === 'this browser' ? 'this tab' : 'the app';\n}\n\n/** The promise after \"You're offline\" / \"Can't reach Athena\", honest to what is kept. */\nfunction keepingPromise({\n keeping,\n device,\n when,\n}: {\n keeping: OfflineKeeping;\n device: DeviceNoun;\n when: string;\n}): string {\n switch (keeping) {\n case 'device':\n return `You can keep working. Changes are saved on ${device} and sync ${when}.`;\n case 'memory':\n return `You can keep working, but changes are kept only until you close ${openContainer(device)}. They sync ${when}.`;\n case 'none':\n return `You’re still signed in. This reconnects ${when}.`;\n }\n}\n\n/**\n * The app-wide status line, or `null` while Athena answers and nothing waits.\n * `waiting` counts changes not in Athena yet: while `reconnecting` they are\n * being sent; while `online` they wait for something else (a copy that sends\n * when it is next opened), and the line stays until they are gone.\n * `keeping` is what the app really keeps offline (default `device`). A surface\n * that keeps nothing (`none`) has nothing to sync, so its way back is only\n * \"Back online\", never \"Syncing…\" or \"All changes synced\".\n */\nexport function connectionCopy({\n state,\n device,\n waiting = 0,\n keeping = 'device',\n}: {\n state: ConnectionState;\n device: DeviceNoun;\n waiting?: number;\n keeping?: OfflineKeeping;\n}): StatusCopy | null {\n switch (state) {\n case 'online':\n return waiting > 0\n ? {\n title: 'Not synced yet',\n detail: `${plural(waiting, 'change', 'changes')} on ${device} ${waiting === 1 ? 'hasn’t' : 'haven’t'} synced yet.`,\n }\n : null;\n case 'offline':\n return {\n title: 'You’re offline',\n detail: keepingPromise({ keeping, device, when: 'when you’re back online' }),\n };\n case 'unreachable':\n return {\n title: 'Can’t reach Athena',\n detail: keepingPromise({ keeping, device, when: 'when Athena is back' }),\n };\n case 'reconnecting':\n if (keeping === 'none') return { title: 'Back online', detail: '' };\n return {\n title: 'Back online',\n detail:\n waiting > 0 ? `Syncing ${plural(waiting, 'change', 'changes')}…` : 'Syncing…',\n };\n case 'synced':\n return keeping === 'none'\n ? { title: 'Back online', detail: '' }\n : { title: 'All changes synced', detail: '' };\n }\n}\n\n/**\n * The line under \"Saving delayed\": why saving waits, and what is kept meanwhile, never promising\n * more than the app keeps.\n */\nfunction savingDelayedDetail({\n delay,\n keeping,\n device,\n}: {\n delay: SavingDelayCause;\n keeping: Exclude<OfflineKeeping, 'none'>;\n device: DeviceNoun;\n}): string {\n if (delay === 'repairing') {\n return keeping === 'memory'\n ? `You can keep working, but your changes are kept only until you close ${openContainer(device)}. They save once Athena finishes repairing this document.`\n : `You can keep working. Your changes are kept on ${device} and save once Athena finishes repairing this document.`;\n }\n return keeping === 'memory'\n ? `Athena is taking longer than usual to save your latest changes. They’re kept only until you close ${openContainer(device)}, so keep it open.`\n : `Athena is taking longer than usual to save your latest changes. They’re kept on ${device} in the meantime.`;\n}\n\n/**\n * The chip in a document's header, and the line it explains itself with. For `delayed`, `delay`\n * says why (default `unconfirmed`) and `keeping` whether the changes are kept on the device or only\n * while the tab or app stays open (default `device`); every other state ignores both.\n */\nexport function documentSyncCopy({\n state,\n device,\n delay = 'unconfirmed',\n keeping = 'device',\n}: {\n state: DocumentSyncState;\n device: DeviceNoun;\n delay?: SavingDelayCause;\n keeping?: Exclude<OfflineKeeping, 'none'>;\n}): LabelCopy {\n switch (state) {\n case 'saved':\n return { label: 'Saved', detail: 'Saved to Athena.' };\n case 'saving':\n return { label: 'Saving…', detail: 'Saving to Athena.' };\n case 'on-device':\n return {\n label: `Saved on ${device}`,\n detail: `Saved on ${device}. Your changes sync when Athena is back.`,\n };\n case 'in-memory':\n return {\n label: 'Not saved yet',\n detail: `Your changes are kept only until you close ${openContainer(device)}, and save when Athena is back.`,\n };\n case 'syncing':\n return { label: 'Syncing…', detail: `Sending the changes saved on ${device}.` };\n case 'needs-review':\n return {\n label: 'Review changes',\n detail: 'Some of your changes overlap with changes made in Athena. Choose what to keep.',\n };\n case 'view-only':\n return {\n label: 'View only',\n detail: `You can’t edit this anymore. Your changes stay on ${device}.`,\n };\n case 'delayed':\n return { label: 'Saving delayed', detail: savingDelayedDetail({ delay, keeping, device }) };\n }\n}\n\n/**\n * How long a document's edits may wait unconfirmed on a connection that is up before its saving\n * reads as delayed (`delayed`, `unconfirmed`), and before its room asks Keryx why: the stall window\n * every editor shares (`ACK_STALL_MS` in `@athenaintel/asset-host/local-updates`, which Olympus's\n * `room-sync-store.test.ts` holds equal).\n */\nexport const UNCONFIRMED_EDITS_STALL_MS = 15_000;\n\n/**\n * Whether a document's saving is delayed, and why (`DocumentSyncState` `delayed`): only while Athena\n * answers and the document's room is connected; `repairing` when Keryx refuses the room's writes\n * (quarantined while it is repaired: said at once, before anything waits), `unconfirmed` once its\n * edits have stayed unconfirmed past {@link UNCONFIRMED_EDITS_STALL_MS}; `null` otherwise. Every\n * app's chip asks it after a hold and after edits kept without a connection, and ahead of syncing\n * and saving, so a room that refuses writes never spins and being offline is never a delay.\n */\nexport function savingDelayCause({\n reachable,\n connected,\n writesRefused,\n unconfirmedStalled,\n}: {\n /** Athena answers (the app's reachability, never the socket alone). */\n reachable: boolean;\n /** The document's room is connected. */\n connected: boolean;\n /** Keryx's document status says the room takes no writes for now. */\n writesRefused: boolean;\n /** Local edits have waited unconfirmed past the stall window on this connection. */\n unconfirmedStalled: boolean;\n}): SavingDelayCause | null {\n if (!reachable || !connected) return null;\n if (writesRefused) return 'repairing';\n return unconfirmedStalled ? 'unconfirmed' : null;\n}\n\n/** When something that needs Athena works again (the second half of `NEEDS_CONNECTION`'s line). */\nconst AVAILABLE_AGAIN = 'It’s available again when you’re back online.';\n\n/**\n * The reason on a control that needs Athena (share, run, upload, invite…),\n * shown disabled while Athena can't be reached rather than failing when used.\n */\nexport const NEEDS_CONNECTION: LabelCopy = {\n label: 'Needs a connection',\n detail: `This needs Athena. ${AVAILABLE_AGAIN}`,\n};\n\n/** What a composer was given to attach: a photo (from the phone's library or camera) or any file. */\nexport type AttachmentKind = 'photo' | 'file';\n\n/**\n * What a composer says when a photo or file is picked, pasted or dropped while\n * Athena can't be reached. It isn't attached: only Athena can take the upload.\n * In a chat whose messages wait to be sent meanwhile (`messagesWait`: the chat\n * keeps an outbox), the message itself still goes, so that comes first. Where\n * nothing waits (the Chrome side panel, a launcher that keeps its draft), it\n * says only when attaching works again, as `NEEDS_CONNECTION` does.\n */\nexport function attachmentNeedsConnection({\n kind,\n messagesWait,\n}: {\n kind: AttachmentKind;\n messagesWait: boolean;\n}): string {\n const attaching = `Attaching ${kind === 'photo' ? 'a photo' : 'a file'} needs Athena. ${AVAILABLE_AGAIN}`;\n return messagesWait\n ? `You can still send your message: it goes when Athena is back. ${attaching}`\n : attaching;\n}\n\n/** What a sent message carries besides its text that can't wait for Athena. */\nfunction heldExtras(withQuote: boolean): string {\n return withQuote ? 'an attachment or a quote' : 'an attachment';\n}\n\n/**\n * What a composer says when a message is sent while Athena can't be reached\n * with an attachment that never reached Athena (or, `withQuote`, a quote the\n * chat can't keep waiting). Its text could wait in the chat outbox; the\n * attachment can't, so the draft stays as it is.\n */\nexport function attachedMessageNeedsConnection({\n withQuote = false,\n}: {\n withQuote?: boolean;\n} = {}): string {\n return `Your text can wait and go when Athena is back, but ${heldExtras(withQuote)} can’t. Remove ${\n withQuote ? 'it' : 'the attachment'\n } to send the text.`;\n}\n\n/**\n * What a composer says when a message with an attachment (or, `withQuote`, a\n * quote) is sent while messages written when Athena couldn't be reached still\n * wait to go: those go first, and this one can't wait behind them.\n */\nexport function attachedMessageBehindWaiting({\n withQuote = false,\n}: {\n withQuote?: boolean;\n} = {}): string {\n return `Messages you wrote while Athena couldn’t be reached go first. A message with ${heldExtras(\n withQuote\n )} can’t wait behind them, so send it once they’ve gone.`;\n}\n\n/**\n * The reason on New Presentation while Athena can't be reached, in a workspace\n * whose new presentations start from its design system's template: a\n * presentation made offline starts blank, and only Athena can copy the\n * template, so none is made until it can.\n */\nexport const NEW_PRESENTATION_NEEDS_TEMPLATE: LabelCopy = {\n label: 'Needs a connection',\n detail:\n 'New presentations in this workspace start from its design template, which needs Athena. You can make one when you’re back online.',\n};\n\n/**\n * What a download of a spreadsheet made from an Excel Macro-Enabled Workbook\n * (`.xlsm`) says when it went without the macros because they come from Athena,\n * which can't be reached, and the device hasn't kept them (they travel with the\n * workbook and never run). The file is the workbook without them; downloading\n * it again with a connection keeps them.\n */\nexport function downloadedWithoutMacros({ device }: { device: DeviceNoun }): string {\n return `Downloaded without the spreadsheet’s macros: they come from Athena, which can’t be reached, and ${device} hasn’t kept them. Download it again when you’re back online to keep them.`;\n}\n\n/**\n * Save's question for a spreadsheet whose file is an Excel Macro-Enabled\n * Workbook (`.xlsm`) while its macros come from Athena, which can't be reached,\n * and the device hasn't kept them: the `.xlsm` is never written without them,\n * so the person may save an Excel Workbook (`.xlsx`) beside it, and Save writes\n * the `.xlsm` again once Athena answers.\n */\nexport function macrosNeedConnectionToSave({\n name,\n device,\n}: {\n name: string;\n device: DeviceNoun;\n}): StatusCopy {\n return {\n title: `Athena can’t save the macros in “${name}” right now.`,\n detail: `They come from Athena, which can’t be reached, and ${device} hasn’t kept them. Save it as an Excel Workbook (.xlsx) instead. “${name}” stays as it is, macros included, and Save writes it again when you’re back online.`,\n };\n}\n\n/**\n * What saving a version kept on the device as a new spreadsheet says when the\n * spreadsheet was made from an Excel Macro-Enabled Workbook (`.xlsm`) and\n * Athena can't be reached: the new spreadsheet is made all the same (on the\n * device, and it syncs when Athena is back), but without the macros, which only\n * Athena can keep with a new spreadsheet (they travel with the workbook and\n * never run). The kept version stays on the device, so saving it again with a\n * connection keeps them.\n */\nexport function savedAsNewWithoutMacros({ device }: { device: DeviceNoun }): string {\n return `The new spreadsheet was made without the macros: only Athena can keep them in a new spreadsheet, and it can’t be reached. The kept version stays on ${device}: save it as a new spreadsheet again when you’re back online to keep them.`;\n}\n\n/**\n * Whether an item opens without a connection on this device, for lists.\n * `canKeep` says whether opening it online would keep a copy here (the\n * deployment allows device copies and the item's type is one that is kept):\n * only then is the person told to open it once.\n */\nexport function offlineAvailabilityCopy({\n available,\n canKeep,\n device,\n}: {\n available: boolean;\n canKeep: boolean;\n device: DeviceNoun;\n}): LabelCopy {\n if (available) {\n return { label: 'Available offline', detail: `Opens without a connection on ${device}.` };\n }\n return canKeep\n ? {\n label: 'Not available offline',\n detail: `Open it once while online to keep it on ${device}.`,\n }\n : { label: 'Not available offline', detail: 'This opens only with a connection.' };\n}\n\n/**\n * The one-time explainer shown the first time the person goes offline, on a\n * surface that keeps work there (`keeping` `device`). A surface that keeps\n * nothing (`none`: the Chrome side panel, the Office task pane) shows no\n * explainer; its status line already says all that holds.\n */\nexport function firstOfflineExplainer({ device }: { device: DeviceNoun }): StatusCopy & {\n action: string;\n} {\n return {\n title: 'You can keep working offline',\n detail: `Documents you’ve opened recently keep working on ${device}. Your changes sync when you’re back online.`,\n action: 'Got it',\n };\n}\n\n/**\n * The summary heading of the one \"Waiting to sync\" list, from what waits on\n * the device; `null` when nothing does.\n */\nexport function waitingSummary({\n changes = 0,\n messages = 0,\n recordings = 0,\n}: {\n changes?: number;\n messages?: number;\n recordings?: number;\n}): string | null {\n const parts = [\n changes > 0 ? plural(changes, 'change', 'changes') : null,\n messages > 0 ? plural(messages, 'message', 'messages') : null,\n recordings > 0 ? plural(recordings, 'recording', 'recordings') : null,\n ].filter((part): part is string => part !== null);\n return parts.length === 0 ? null : `Waiting to sync: ${parts.join(', ')}`;\n}\n\n/**\n * Where one item in the \"Waiting to sync\" list stands: a change, a message or a\n * recording made while Athena couldn't be reached.\n */\nexport type WaitingItemState =\n /** Saved on the device; it syncs when Athena is back. */\n | 'waiting'\n /** Being sent to Athena now. */\n | 'syncing'\n /**\n * Kept only while this tab or app stays open: it couldn't be saved on the\n * device (the organization keeps nothing on devices, or storage failed).\n */\n | 'in-memory'\n /**\n * Saved on the device in something that isn't open (a document closed before\n * its edits synced, one made offline): it syncs when it is next opened with a\n * connection (opening it offline sends nothing).\n */\n | 'when-opened'\n /** Made in another workspace: it syncs once that workspace is open again, with a connection. */\n | 'other-workspace'\n /** It doesn't go on its own: the person chooses (a message that waited more than a day). */\n | 'needs-you'\n /** Athena didn't take it: the app adds why, and what the person can do about it. */\n | 'not-synced';\n\n/** The state of one item in the \"Waiting to sync\" list, and the line that explains it. */\nexport function waitingItemCopy({\n state,\n device,\n}: {\n state: WaitingItemState;\n device: DeviceNoun;\n}): LabelCopy {\n switch (state) {\n case 'waiting':\n return {\n label: 'Waiting to sync',\n detail: `Saved on ${device}. It syncs when Athena is back.`,\n };\n case 'syncing':\n return { label: 'Syncing…', detail: `Sending what was saved on ${device}.` };\n case 'in-memory':\n return {\n label: 'Not saved yet',\n detail: `Kept only until you close ${openContainer(device)}. It syncs when Athena is back.`,\n };\n case 'when-opened':\n return {\n label: 'Not synced yet',\n detail: `Saved on ${device}. It syncs when you next open it with a connection.`,\n };\n case 'other-workspace':\n return {\n label: 'Waiting for its workspace',\n detail: 'It syncs once you’re in the workspace it was made in, with a connection.',\n };\n case 'needs-you':\n return { label: 'Waiting for you', detail: 'Open it to choose what happens to it.' };\n case 'not-synced':\n return { label: 'Couldn’t sync', detail: 'Athena didn’t take it.' };\n }\n}\n\n/** The headings of the \"Waiting to sync\" list, one per kind of item. */\nexport const WAITING_SECTION_TITLES = {\n changes: 'Changes',\n messages: 'Messages',\n recordings: 'Recordings',\n} as const;\n\n/**\n * What the person can do with an item Athena didn't take (`not-synced`) in the\n * \"Waiting to sync\" list: send it again, or delete the copy on the device. That\n * copy is the item's only one, so the app asks first (`deleteFromDeviceConfirm`)\n * and never deletes it as a side effect of anything else.\n */\nexport function notSyncedActions({ device }: { device: DeviceNoun }): {\n retry: string;\n remove: string;\n} {\n return { retry: 'Try again', remove: `Delete from ${device}` };\n}\n\n/** One of `notSyncedActions`, or showing where the item is kept on the device. */\nexport type NotSyncedAction = 'retry' | 'reveal' | 'remove';\n\n/**\n * What the person hears when the device couldn't do what they asked for an\n * item Athena didn't take (its files, not Athena, failed).\n */\nexport function notSyncedActionFailed({\n action,\n device,\n}: {\n action: NotSyncedAction;\n device: DeviceNoun;\n}): string {\n switch (action) {\n case 'retry':\n return 'Couldn’t send it again. Try again in a moment.';\n case 'reveal':\n return `Couldn’t show where it’s kept on ${device}.`;\n case 'remove':\n return `Couldn’t delete it from ${device}.`;\n }\n}\n\n/**\n * The question before deleting such an item's copy on the device, naming the\n * item: the destructive choice is never the default.\n */\nexport function deleteFromDeviceConfirm({\n name,\n device,\n}: {\n name: string;\n device: DeviceNoun;\n}): StatusCopy & { confirm: string; cancel: string } {\n return {\n title: `Delete “${name}” from ${device}?`,\n detail: 'Athena didn’t take it, so this is its only copy. Deleting it can’t be undone.',\n confirm: 'Delete',\n cancel: 'Cancel',\n };\n}\n"],"names":[],"mappings":";;AAyPO,MAAM,6BAA6B;AA+B1C,MAAM,kBAAkB;AAMjB,MAAM,mBAA8B;AAAA,EACzC,OAAO;AAAA,EACP,QAAQ,sBAAsB,eAAe;AAC/C;AAaO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGW;AACT,QAAM,YAAY,aAA4C,QAAQ,kBAAkB,eAAe;AACvG,SAAO,eACH,iEAAiE,SAAS,KAC1E;AACN;AAGA,SAAS,WAAW,WAA4B;AAC9C,SAAO,YAAY,6BAA6B;AAClD;AAQO,SAAS,+BAA+B;AAAA,EAC7C,YAAY;AACd,IAEI,IAAY;AACd,SAAO,sDAAsD,WAAW,SAAS,CAAC,kBAChF,YAAY,OAAO,gBACrB;AACF;AAOO,SAAS,6BAA6B;AAAA,EAC3C,YAAY;AACd,IAEI,IAAY;AACd,SAAO,gFAAgF;AAAA,IACrF;AAAA,EAAA,CACD;AACH;;;;;;"}
|
|
@@ -169,6 +169,39 @@ export declare function attachedMessageBehindWaiting({ withQuote, }?: {
|
|
|
169
169
|
* template, so none is made until it can.
|
|
170
170
|
*/
|
|
171
171
|
export declare const NEW_PRESENTATION_NEEDS_TEMPLATE: LabelCopy;
|
|
172
|
+
/**
|
|
173
|
+
* What a download of a spreadsheet made from an Excel Macro-Enabled Workbook
|
|
174
|
+
* (`.xlsm`) says when it went without the macros because they come from Athena,
|
|
175
|
+
* which can't be reached, and the device hasn't kept them (they travel with the
|
|
176
|
+
* workbook and never run). The file is the workbook without them; downloading
|
|
177
|
+
* it again with a connection keeps them.
|
|
178
|
+
*/
|
|
179
|
+
export declare function downloadedWithoutMacros({ device }: {
|
|
180
|
+
device: DeviceNoun;
|
|
181
|
+
}): string;
|
|
182
|
+
/**
|
|
183
|
+
* Save's question for a spreadsheet whose file is an Excel Macro-Enabled
|
|
184
|
+
* Workbook (`.xlsm`) while its macros come from Athena, which can't be reached,
|
|
185
|
+
* and the device hasn't kept them: the `.xlsm` is never written without them,
|
|
186
|
+
* so the person may save an Excel Workbook (`.xlsx`) beside it, and Save writes
|
|
187
|
+
* the `.xlsm` again once Athena answers.
|
|
188
|
+
*/
|
|
189
|
+
export declare function macrosNeedConnectionToSave({ name, device, }: {
|
|
190
|
+
name: string;
|
|
191
|
+
device: DeviceNoun;
|
|
192
|
+
}): StatusCopy;
|
|
193
|
+
/**
|
|
194
|
+
* What saving a version kept on the device as a new spreadsheet says when the
|
|
195
|
+
* spreadsheet was made from an Excel Macro-Enabled Workbook (`.xlsm`) and
|
|
196
|
+
* Athena can't be reached: the new spreadsheet is made all the same (on the
|
|
197
|
+
* device, and it syncs when Athena is back), but without the macros, which only
|
|
198
|
+
* Athena can keep with a new spreadsheet (they travel with the workbook and
|
|
199
|
+
* never run). The kept version stays on the device, so saving it again with a
|
|
200
|
+
* connection keeps them.
|
|
201
|
+
*/
|
|
202
|
+
export declare function savedAsNewWithoutMacros({ device }: {
|
|
203
|
+
device: DeviceNoun;
|
|
204
|
+
}): string;
|
|
172
205
|
/**
|
|
173
206
|
* Whether an item opens without a connection on this device, for lists.
|
|
174
207
|
* `canKeep` says whether opening it online would keep a copy here (the
|