@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-account-workspace-id.js","sources":["../../../src/chat-ui/mentions/use-account-workspace-id.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"use-account-workspace-id.js","sources":["../../../src/chat-ui/mentions/use-account-workspace-id.ts"],"sourcesContent":["import { useAccountIdentityQuery } from '../../auth/account-identity';\nimport { useCatalogQueryClient } from '../../catalog/runtime-catalog';\nimport type { AthenaStatewireTokenGetter } from '../../runtime/statewire-auth-headers';\nimport type { AccountIdentity } from '../../threads/api';\nimport type { MentionAuthContext } from './suggestions/data/api-client';\n\n/** The credential's agora identity, as far as it is known yet. */\nexport interface AccountIdentityState {\n /** Agora's user id (`user_…`) for the credential; unknown until `/me` answers. */\n userId: string | undefined;\n /** The host's workspace when it names one, else the credential's current workspace. */\n workspaceId: string | undefined;\n}\n\nexport interface AccountIdentityOptions {\n /**\n * The provider's token getter, which `auth.getAccessToken` wraps (`AthenaChat`\n * passes `useAthenaConfig().getToken`). Its identity keeps two providers\n * apart when only their getters know who they sign in.\n */\n tokenGetter?: AthenaStatewireTokenGetter;\n}\n\n/**\n * The chat's read of the credential's identity (`useAccountIdentityQuery`),\n * through the host's query cache: inside a provider with a session list, the\n * list's own read under the same key, so the page sends one `/me`. `enabled`\n * is the caller's: a reader that only needs the workspace skips the read when\n * the host names one.\n */\nfunction useAccountIdentityRead(\n auth: MentionAuthContext | null,\n { enabled, tokenGetter }: { enabled: boolean } & AccountIdentityOptions\n): AccountIdentity | undefined {\n const query = useAccountIdentityQuery({\n credential: {\n backendUrl: auth?.backendUrl ?? '',\n principal: auth?.userId,\n apiKey: auth?.apiKey,\n heldToken: auth?.accessToken,\n tokenGetter,\n },\n resolveAuth: async ({ force }: { force?: boolean } = {}) => ({\n token:\n (force ? await auth?.renewAccessToken?.() : await auth?.getAccessToken?.()) ??\n auth?.accessToken ??\n null,\n apiKey: auth?.apiKey,\n }),\n enabled: auth !== null && enabled,\n queryClient: useCatalogQueryClient(),\n });\n return query.data;\n}\n\n/** The workspace mentions search: the host's when it names one, else the credential's own. */\nexport function useAccountWorkspaceId(auth: MentionAuthContext | null): string | undefined {\n const identity = useAccountIdentityRead(auth, { enabled: !auth?.workspaceId });\n return auth?.workspaceId ?? identity?.workspaceId ?? undefined;\n}\n\n/**\n * The credential's agora user id and workspace from the same read. The auth\n * context carries the identity provider's user id, not agora's, so \"who is\n * the viewer\" (an answer label's \"for you\") needs this one, and it reads\n * `/me` even when the host names a workspace.\n */\nexport function useAccountIdentity(\n auth: MentionAuthContext | null,\n options: AccountIdentityOptions = {}\n): AccountIdentityState {\n const identity = useAccountIdentityRead(auth, { enabled: true, ...options });\n return {\n userId: identity?.userId,\n workspaceId: auth?.workspaceId ?? identity?.workspaceId ?? undefined,\n };\n}\n"],"names":[],"mappings":";;AA8BA,SAAS,uBACP,MACA,EAAE,SAAS,eACkB;AAC7B,QAAM,QAAQ,wBAAwB;AAAA,IACpC,YAAY;AAAA,MACV,aAAY,6BAAM,eAAc;AAAA,MAChC,WAAW,6BAAM;AAAA,MACjB,QAAQ,6BAAM;AAAA,MACd,WAAW,6BAAM;AAAA,MACjB;AAAA,IAAA;AAAA,IAEF,aAAa,OAAO,EAAE,MAAA,IAA+B,OAAC;;AAAO;AAAA,QAC3D,QACG,QAAQ,QAAM,kCAAM,qBAAN,iCAA6B,QAAM,kCAAM,mBAAN,oCAClD,6BAAM,gBACN;AAAA,QACF,QAAQ,6BAAM;AAAA,MAAA;AAAA;AAAA,IAEhB,SAAS,SAAS,QAAQ;AAAA,IAC1B,aAAa,sBAAA;AAAA,EAAsB,CACpC;AACD,SAAO,MAAM;AACf;AAcO,SAAS,mBACd,MACA,UAAkC,IACZ;AACtB,QAAM,WAAW,uBAAuB,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS;AAC3E,SAAO;AAAA,IACL,QAAQ,qCAAU;AAAA,IAClB,cAAa,6BAAM,iBAAe,qCAAU,gBAAe;AAAA,EAAA;AAE/D;"}
|
|
@@ -3,33 +3,87 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const react = require("@assistant-ui/react");
|
|
5
5
|
const lucideReact = require("lucide-react");
|
|
6
|
+
const require$$0 = require("react");
|
|
6
7
|
const useAthenaRunLive = require("../../chat/use-athena-run-live.cjs");
|
|
8
|
+
const voicePresentation = require("../../agent-runtime/src/voice-presentation.cjs");
|
|
7
9
|
const cnfast = require("cnfast");
|
|
8
|
-
const
|
|
9
|
-
"
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
10
|
+
const VoiceDelegationHeading = ({ live }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex size-4 items-center justify-center rounded bg-primary/15", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLinesIcon, { className: "size-2.5 text-primary", "aria-hidden": true }) }),
|
|
12
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-primary", children: "Voice task" }),
|
|
13
|
+
live ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14
|
+
"span",
|
|
15
|
+
{
|
|
16
|
+
className: "inline-flex items-center gap-1 text-[10px] text-primary/80",
|
|
17
|
+
"data-testid": "voice-delegation-live",
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderIcon, { className: "size-2.5 animate-spin", "aria-hidden": true }),
|
|
20
|
+
"running"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
) : null
|
|
24
|
+
] });
|
|
25
|
+
const VoiceDelegationCard = ({
|
|
26
|
+
text,
|
|
27
|
+
live,
|
|
28
|
+
echo = false,
|
|
29
|
+
className
|
|
30
|
+
}) => {
|
|
31
|
+
const [open, setOpen] = require$$0.useState(false);
|
|
32
|
+
const textId = require$$0.useId();
|
|
33
|
+
if (echo) {
|
|
34
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
className: cnfast.cn("flex flex-col items-end px-1 pb-0.5", className),
|
|
38
|
+
"data-testid": "voice-delegation-card",
|
|
39
|
+
"data-echo": "true",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
42
|
+
"button",
|
|
43
|
+
{
|
|
44
|
+
type: "button",
|
|
45
|
+
className: "inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-2 py-0.5 hover:bg-primary/10",
|
|
46
|
+
"aria-expanded": open,
|
|
47
|
+
"aria-controls": textId,
|
|
48
|
+
onClick: () => setOpen((value) => !value),
|
|
49
|
+
"data-testid": "voice-delegation-toggle",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsxRuntime.jsx(VoiceDelegationHeading, { live }),
|
|
52
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: open ? "Hide the task" : "Show the task" }),
|
|
53
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
54
|
+
lucideReact.ChevronDownIcon,
|
|
55
|
+
{
|
|
56
|
+
className: cnfast.cn("size-3 text-primary/70 transition-transform", open && "rotate-180"),
|
|
57
|
+
"aria-hidden": true
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
64
|
+
"div",
|
|
65
|
+
{
|
|
66
|
+
id: textId,
|
|
67
|
+
className: "mt-1 max-w-[85%] whitespace-pre-wrap break-words rounded-xl border border-primary/20 bg-primary/5 px-3 py-2 text-sm text-foreground",
|
|
68
|
+
children: text
|
|
69
|
+
}
|
|
70
|
+
) : null
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
);
|
|
31
74
|
}
|
|
32
|
-
|
|
75
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
className: cnfast.cn("flex justify-end px-1 pb-0.5", className),
|
|
79
|
+
"data-testid": "voice-delegation-card",
|
|
80
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-[85%] rounded-xl border border-primary/20 bg-primary/5 px-3 py-2", children: [
|
|
81
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 flex items-center gap-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(VoiceDelegationHeading, { live }) }),
|
|
82
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: text })
|
|
83
|
+
] })
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
};
|
|
33
87
|
const VoiceDelegationMessage = ({ className }) => {
|
|
34
88
|
const text = react.useAuiState(
|
|
35
89
|
(s) => s.message.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n")
|
|
@@ -43,8 +97,22 @@ const VoiceDelegationMessage = ({ className }) => {
|
|
|
43
97
|
}
|
|
44
98
|
return false;
|
|
45
99
|
});
|
|
100
|
+
const echo = react.useAuiState(
|
|
101
|
+
(s) => {
|
|
102
|
+
var _a;
|
|
103
|
+
return ((_a = s.thread.messages[s.message.index]) == null ? void 0 : _a.id) === s.message.id && voicePresentation.voiceDelegationEchoesRequest(s.thread.messages, s.message.index);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
46
106
|
const isRunning = useAthenaRunLive.useAthenaRunLive();
|
|
47
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
107
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
+
VoiceDelegationCard,
|
|
109
|
+
{
|
|
110
|
+
text,
|
|
111
|
+
live: isRunning && isTrailingUserMessage,
|
|
112
|
+
echo,
|
|
113
|
+
className
|
|
114
|
+
}
|
|
115
|
+
);
|
|
48
116
|
};
|
|
49
117
|
exports.VoiceDelegationCard = VoiceDelegationCard;
|
|
50
118
|
exports.VoiceDelegationMessage = VoiceDelegationMessage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-delegation-card.cjs","sources":["../../../src/chat-ui/messages/voice-delegation-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { AudioLinesIcon, LoaderIcon } from \"lucide-react\";\nimport type
|
|
1
|
+
{"version":3,"file":"voice-delegation-card.cjs","sources":["../../../src/chat-ui/messages/voice-delegation-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { voiceDelegationEchoesRequest } from \"@athenaintel/agent-runtime\";\nimport { AudioLinesIcon, ChevronDownIcon, LoaderIcon } from \"lucide-react\";\nimport { type FC, useId, useState } from \"react\";\nimport { useAthenaRunLive } from \"../../chat/use-athena-run-live\";\nimport { cn } from \"../../lib/utils\";\n\nexport interface VoiceDelegationCardProps {\n /** The live voice model's plain-language ask (never markdown). */\n text: string;\n /** Whether this delegation is plausibly the one running right now. */\n live: boolean;\n /**\n * The person's own turn right above already shows this ask\n * (`voiceDelegationEchoesRequest`): only the header shows, and the task's\n * wording opens on request.\n */\n echo?: boolean;\n className?: string;\n}\n\nconst VoiceDelegationHeading: FC<{ live: boolean }> = ({ live }) => (\n <>\n <span className=\"inline-flex size-4 items-center justify-center rounded bg-primary/15\">\n <AudioLinesIcon className=\"size-2.5 text-primary\" aria-hidden />\n </span>\n <span className=\"text-[10px] font-medium uppercase tracking-wide text-primary\">\n Voice task\n </span>\n {live ? (\n <span\n className=\"inline-flex items-center gap-1 text-[10px] text-primary/80\"\n data-testid=\"voice-delegation-live\"\n >\n <LoaderIcon className=\"size-2.5 animate-spin\" aria-hidden />\n running\n </span>\n ) : null}\n </>\n);\n\n/**\n * The card for a voice delegation's anchor message: the unit of work the live\n * voice model handed to the thread. It replaces the plain user bubble on every\n * surface, so delegated work reads as delegated work, not as the user typing\n * again. Right under the person's own words (`echo`) it is only their request\n * repeated, so it shrinks to its header and the task's wording opens on a tap;\n * otherwise (a follow-up whose spoken turn has not landed above it, a task with\n * no visible ask) it shows the ask in full. The run it dispatched streams below\n * as ordinary messages and tool cards. Pure presenter;\n * {@link VoiceDelegationMessage} binds it to the message in scope.\n */\nexport const VoiceDelegationCard: FC<VoiceDelegationCardProps> = ({\n text,\n live,\n echo = false,\n className,\n}) => {\n const [open, setOpen] = useState(false);\n const textId = useId();\n if (echo) {\n return (\n <div\n className={cn(\"flex flex-col items-end px-1 pb-0.5\", className)}\n data-testid=\"voice-delegation-card\"\n data-echo=\"true\"\n >\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-2 py-0.5 hover:bg-primary/10\"\n aria-expanded={open}\n aria-controls={textId}\n onClick={() => setOpen((value) => !value)}\n data-testid=\"voice-delegation-toggle\"\n >\n <VoiceDelegationHeading live={live} />\n <span className=\"sr-only\">{open ? \"Hide the task\" : \"Show the task\"}</span>\n <ChevronDownIcon\n className={cn(\"size-3 text-primary/70 transition-transform\", open && \"rotate-180\")}\n aria-hidden\n />\n </button>\n {open ? (\n <div\n id={textId}\n className=\"mt-1 max-w-[85%] whitespace-pre-wrap break-words rounded-xl border border-primary/20 bg-primary/5 px-3 py-2 text-sm text-foreground\"\n >\n {text}\n </div>\n ) : null}\n </div>\n );\n }\n return (\n <div\n className={cn(\"flex justify-end px-1 pb-0.5\", className)}\n data-testid=\"voice-delegation-card\"\n >\n <div className=\"max-w-[85%] rounded-xl border border-primary/20 bg-primary/5 px-3 py-2\">\n <div className=\"mb-1 flex items-center gap-1.5\">\n <VoiceDelegationHeading live={live} />\n </div>\n <div className=\"whitespace-pre-wrap break-words text-sm text-foreground\">{text}</div>\n </div>\n </div>\n );\n};\n\n/**\n * {@link VoiceDelegationCard} bound to the assistant-ui message in scope. The\n * live pulse shows only while this delegation is plausibly the one running:\n * the thread is running and no later user turn superseded it. It draws as an\n * echo when the person's own turn right above already shows the ask.\n */\nexport const VoiceDelegationMessage: FC<{ className?: string }> = ({ className }) => {\n const text = useAuiState((s) =>\n s.message.content\n .map((part) => (part.type === \"text\" ? part.text : \"\"))\n .filter(Boolean)\n .join(\"\\n\"),\n );\n const isTrailingUserMessage = useAuiState((s) => {\n const messages = s.thread.messages;\n for (let index = messages.length - 1; index >= 0; index--) {\n const candidate = messages[index];\n if (candidate?.role !== \"user\") continue;\n return candidate.id === s.message.id;\n }\n return false;\n });\n const echo = useAuiState(\n (s) =>\n s.thread.messages[s.message.index]?.id === s.message.id &&\n voiceDelegationEchoesRequest(s.thread.messages, s.message.index),\n );\n const isRunning = useAthenaRunLive();\n return (\n <VoiceDelegationCard\n text={text}\n live={isRunning && isTrailingUserMessage}\n echo={echo}\n className={className}\n />\n );\n};\n"],"names":["jsxs","Fragment","jsx","AudioLinesIcon","LoaderIcon","useState","useId","cn","ChevronDownIcon","useAuiState","voiceDelegationEchoesRequest","useAthenaRunLive"],"mappings":"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"}
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FC } from "react";
|
|
2
2
|
export interface VoiceDelegationCardProps {
|
|
3
3
|
/** The live voice model's plain-language ask (never markdown). */
|
|
4
4
|
text: string;
|
|
5
5
|
/** Whether this delegation is plausibly the one running right now. */
|
|
6
6
|
live: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The person's own turn right above already shows this ask
|
|
9
|
+
* (`voiceDelegationEchoesRequest`): only the header shows, and the task's
|
|
10
|
+
* wording opens on request.
|
|
11
|
+
*/
|
|
12
|
+
echo?: boolean;
|
|
7
13
|
className?: string;
|
|
8
14
|
}
|
|
9
15
|
/**
|
|
10
16
|
* The card for a voice delegation's anchor message: the unit of work the live
|
|
11
17
|
* voice model handed to the thread. It replaces the plain user bubble on every
|
|
12
|
-
* surface, so delegated work reads as delegated work
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
18
|
+
* surface, so delegated work reads as delegated work, not as the user typing
|
|
19
|
+
* again. Right under the person's own words (`echo`) it is only their request
|
|
20
|
+
* repeated, so it shrinks to its header and the task's wording opens on a tap;
|
|
21
|
+
* otherwise (a follow-up whose spoken turn has not landed above it, a task with
|
|
22
|
+
* no visible ask) it shows the ask in full. The run it dispatched streams below
|
|
23
|
+
* as ordinary messages and tool cards. Pure presenter;
|
|
24
|
+
* {@link VoiceDelegationMessage} binds it to the message in scope.
|
|
17
25
|
*/
|
|
18
26
|
export declare const VoiceDelegationCard: FC<VoiceDelegationCardProps>;
|
|
19
27
|
/**
|
|
20
28
|
* {@link VoiceDelegationCard} bound to the assistant-ui message in scope. The
|
|
21
29
|
* live pulse shows only while this delegation is plausibly the one running:
|
|
22
|
-
* the thread is running and no later user turn superseded it.
|
|
30
|
+
* the thread is running and no later user turn superseded it. It draws as an
|
|
31
|
+
* echo when the person's own turn right above already shows the ask.
|
|
23
32
|
*/
|
|
24
33
|
export declare const VoiceDelegationMessage: FC<{
|
|
25
34
|
className?: string;
|
|
@@ -1,33 +1,87 @@
|
|
|
1
|
-
import { jsx,
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useAuiState } from "@assistant-ui/react";
|
|
3
|
-
import { AudioLinesIcon, LoaderIcon } from "lucide-react";
|
|
3
|
+
import { ChevronDownIcon, AudioLinesIcon, LoaderIcon } from "lucide-react";
|
|
4
|
+
import { useState, useId } from "react";
|
|
4
5
|
import { useAthenaRunLive } from "../../chat/use-athena-run-live.js";
|
|
6
|
+
import { voiceDelegationEchoesRequest } from "../../agent-runtime/src/voice-presentation.js";
|
|
5
7
|
import { cn } from "cnfast";
|
|
6
|
-
const
|
|
7
|
-
"
|
|
8
|
-
{
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
8
|
+
const VoiceDelegationHeading = ({ live }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex size-4 items-center justify-center rounded bg-primary/15", children: /* @__PURE__ */ jsx(AudioLinesIcon, { className: "size-2.5 text-primary", "aria-hidden": true }) }),
|
|
10
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-primary", children: "Voice task" }),
|
|
11
|
+
live ? /* @__PURE__ */ jsxs(
|
|
12
|
+
"span",
|
|
13
|
+
{
|
|
14
|
+
className: "inline-flex items-center gap-1 text-[10px] text-primary/80",
|
|
15
|
+
"data-testid": "voice-delegation-live",
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ jsx(LoaderIcon, { className: "size-2.5 animate-spin", "aria-hidden": true }),
|
|
18
|
+
"running"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
) : null
|
|
22
|
+
] });
|
|
23
|
+
const VoiceDelegationCard = ({
|
|
24
|
+
text,
|
|
25
|
+
live,
|
|
26
|
+
echo = false,
|
|
27
|
+
className
|
|
28
|
+
}) => {
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const textId = useId();
|
|
31
|
+
if (echo) {
|
|
32
|
+
return /* @__PURE__ */ jsxs(
|
|
33
|
+
"div",
|
|
34
|
+
{
|
|
35
|
+
className: cn("flex flex-col items-end px-1 pb-0.5", className),
|
|
36
|
+
"data-testid": "voice-delegation-card",
|
|
37
|
+
"data-echo": "true",
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsxs(
|
|
40
|
+
"button",
|
|
41
|
+
{
|
|
42
|
+
type: "button",
|
|
43
|
+
className: "inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-2 py-0.5 hover:bg-primary/10",
|
|
44
|
+
"aria-expanded": open,
|
|
45
|
+
"aria-controls": textId,
|
|
46
|
+
onClick: () => setOpen((value) => !value),
|
|
47
|
+
"data-testid": "voice-delegation-toggle",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(VoiceDelegationHeading, { live }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: open ? "Hide the task" : "Show the task" }),
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
ChevronDownIcon,
|
|
53
|
+
{
|
|
54
|
+
className: cn("size-3 text-primary/70 transition-transform", open && "rotate-180"),
|
|
55
|
+
"aria-hidden": true
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
open ? /* @__PURE__ */ jsx(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
id: textId,
|
|
65
|
+
className: "mt-1 max-w-[85%] whitespace-pre-wrap break-words rounded-xl border border-primary/20 bg-primary/5 px-3 py-2 text-sm text-foreground",
|
|
66
|
+
children: text
|
|
67
|
+
}
|
|
68
|
+
) : null
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
);
|
|
29
72
|
}
|
|
30
|
-
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
className: cn("flex justify-end px-1 pb-0.5", className),
|
|
77
|
+
"data-testid": "voice-delegation-card",
|
|
78
|
+
children: /* @__PURE__ */ jsxs("div", { className: "max-w-[85%] rounded-xl border border-primary/20 bg-primary/5 px-3 py-2", children: [
|
|
79
|
+
/* @__PURE__ */ jsx("div", { className: "mb-1 flex items-center gap-1.5", children: /* @__PURE__ */ jsx(VoiceDelegationHeading, { live }) }),
|
|
80
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: text })
|
|
81
|
+
] })
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
};
|
|
31
85
|
const VoiceDelegationMessage = ({ className }) => {
|
|
32
86
|
const text = useAuiState(
|
|
33
87
|
(s) => s.message.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n")
|
|
@@ -41,8 +95,22 @@ const VoiceDelegationMessage = ({ className }) => {
|
|
|
41
95
|
}
|
|
42
96
|
return false;
|
|
43
97
|
});
|
|
98
|
+
const echo = useAuiState(
|
|
99
|
+
(s) => {
|
|
100
|
+
var _a;
|
|
101
|
+
return ((_a = s.thread.messages[s.message.index]) == null ? void 0 : _a.id) === s.message.id && voiceDelegationEchoesRequest(s.thread.messages, s.message.index);
|
|
102
|
+
}
|
|
103
|
+
);
|
|
44
104
|
const isRunning = useAthenaRunLive();
|
|
45
|
-
return /* @__PURE__ */ jsx(
|
|
105
|
+
return /* @__PURE__ */ jsx(
|
|
106
|
+
VoiceDelegationCard,
|
|
107
|
+
{
|
|
108
|
+
text,
|
|
109
|
+
live: isRunning && isTrailingUserMessage,
|
|
110
|
+
echo,
|
|
111
|
+
className
|
|
112
|
+
}
|
|
113
|
+
);
|
|
46
114
|
};
|
|
47
115
|
export {
|
|
48
116
|
VoiceDelegationCard,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-delegation-card.js","sources":["../../../src/chat-ui/messages/voice-delegation-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { AudioLinesIcon, LoaderIcon } from \"lucide-react\";\nimport type
|
|
1
|
+
{"version":3,"file":"voice-delegation-card.js","sources":["../../../src/chat-ui/messages/voice-delegation-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { voiceDelegationEchoesRequest } from \"@athenaintel/agent-runtime\";\nimport { AudioLinesIcon, ChevronDownIcon, LoaderIcon } from \"lucide-react\";\nimport { type FC, useId, useState } from \"react\";\nimport { useAthenaRunLive } from \"../../chat/use-athena-run-live\";\nimport { cn } from \"../../lib/utils\";\n\nexport interface VoiceDelegationCardProps {\n /** The live voice model's plain-language ask (never markdown). */\n text: string;\n /** Whether this delegation is plausibly the one running right now. */\n live: boolean;\n /**\n * The person's own turn right above already shows this ask\n * (`voiceDelegationEchoesRequest`): only the header shows, and the task's\n * wording opens on request.\n */\n echo?: boolean;\n className?: string;\n}\n\nconst VoiceDelegationHeading: FC<{ live: boolean }> = ({ live }) => (\n <>\n <span className=\"inline-flex size-4 items-center justify-center rounded bg-primary/15\">\n <AudioLinesIcon className=\"size-2.5 text-primary\" aria-hidden />\n </span>\n <span className=\"text-[10px] font-medium uppercase tracking-wide text-primary\">\n Voice task\n </span>\n {live ? (\n <span\n className=\"inline-flex items-center gap-1 text-[10px] text-primary/80\"\n data-testid=\"voice-delegation-live\"\n >\n <LoaderIcon className=\"size-2.5 animate-spin\" aria-hidden />\n running\n </span>\n ) : null}\n </>\n);\n\n/**\n * The card for a voice delegation's anchor message: the unit of work the live\n * voice model handed to the thread. It replaces the plain user bubble on every\n * surface, so delegated work reads as delegated work, not as the user typing\n * again. Right under the person's own words (`echo`) it is only their request\n * repeated, so it shrinks to its header and the task's wording opens on a tap;\n * otherwise (a follow-up whose spoken turn has not landed above it, a task with\n * no visible ask) it shows the ask in full. The run it dispatched streams below\n * as ordinary messages and tool cards. Pure presenter;\n * {@link VoiceDelegationMessage} binds it to the message in scope.\n */\nexport const VoiceDelegationCard: FC<VoiceDelegationCardProps> = ({\n text,\n live,\n echo = false,\n className,\n}) => {\n const [open, setOpen] = useState(false);\n const textId = useId();\n if (echo) {\n return (\n <div\n className={cn(\"flex flex-col items-end px-1 pb-0.5\", className)}\n data-testid=\"voice-delegation-card\"\n data-echo=\"true\"\n >\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-2 py-0.5 hover:bg-primary/10\"\n aria-expanded={open}\n aria-controls={textId}\n onClick={() => setOpen((value) => !value)}\n data-testid=\"voice-delegation-toggle\"\n >\n <VoiceDelegationHeading live={live} />\n <span className=\"sr-only\">{open ? \"Hide the task\" : \"Show the task\"}</span>\n <ChevronDownIcon\n className={cn(\"size-3 text-primary/70 transition-transform\", open && \"rotate-180\")}\n aria-hidden\n />\n </button>\n {open ? (\n <div\n id={textId}\n className=\"mt-1 max-w-[85%] whitespace-pre-wrap break-words rounded-xl border border-primary/20 bg-primary/5 px-3 py-2 text-sm text-foreground\"\n >\n {text}\n </div>\n ) : null}\n </div>\n );\n }\n return (\n <div\n className={cn(\"flex justify-end px-1 pb-0.5\", className)}\n data-testid=\"voice-delegation-card\"\n >\n <div className=\"max-w-[85%] rounded-xl border border-primary/20 bg-primary/5 px-3 py-2\">\n <div className=\"mb-1 flex items-center gap-1.5\">\n <VoiceDelegationHeading live={live} />\n </div>\n <div className=\"whitespace-pre-wrap break-words text-sm text-foreground\">{text}</div>\n </div>\n </div>\n );\n};\n\n/**\n * {@link VoiceDelegationCard} bound to the assistant-ui message in scope. The\n * live pulse shows only while this delegation is plausibly the one running:\n * the thread is running and no later user turn superseded it. It draws as an\n * echo when the person's own turn right above already shows the ask.\n */\nexport const VoiceDelegationMessage: FC<{ className?: string }> = ({ className }) => {\n const text = useAuiState((s) =>\n s.message.content\n .map((part) => (part.type === \"text\" ? part.text : \"\"))\n .filter(Boolean)\n .join(\"\\n\"),\n );\n const isTrailingUserMessage = useAuiState((s) => {\n const messages = s.thread.messages;\n for (let index = messages.length - 1; index >= 0; index--) {\n const candidate = messages[index];\n if (candidate?.role !== \"user\") continue;\n return candidate.id === s.message.id;\n }\n return false;\n });\n const echo = useAuiState(\n (s) =>\n s.thread.messages[s.message.index]?.id === s.message.id &&\n voiceDelegationEchoesRequest(s.thread.messages, s.message.index),\n );\n const isRunning = useAthenaRunLive();\n return (\n <VoiceDelegationCard\n text={text}\n live={isRunning && isTrailingUserMessage}\n echo={echo}\n className={className}\n />\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -117,8 +117,8 @@ function startHandoffCursorReveal({
|
|
|
117
117
|
}
|
|
118
118
|
pagesLoaded += 1;
|
|
119
119
|
waitingSince = now();
|
|
120
|
-
}).catch(() => {
|
|
121
|
-
pagesLoaded = Number.POSITIVE_INFINITY;
|
|
120
|
+
}).catch((error) => {
|
|
121
|
+
if (!editCommand.isHistoryPageLostToReconnect(error)) pagesLoaded = Number.POSITIVE_INFINITY;
|
|
122
122
|
}).finally(() => {
|
|
123
123
|
paging = false;
|
|
124
124
|
pageLandsAt = now() + 2 * HANDOFF_CURSOR_POLL_MS;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handoff-cursor.cjs","sources":["../../../src/chat-ui/thread/handoff-cursor.tsx"],"sourcesContent":["/**\n * Handovers open where the person was reading (`@athenaintel/agent-runtime`'s\n * `handoff-cursor`), for every host of `ChatThreadShell` (Athena in Chrome,\n * the desktop app, `AthenaChat`).\n *\n * As a source, `useReadThreadReadingCursor` reads the shell's viewport at the\n * moment of the click: `null` at the bottom, else the id of the last message\n * fully in view. As a destination, `HandoffCursorReveal` (mounted by the shell\n * for its `arrivalThreadId`) scrolls to the message the arrival names once the\n * transcript has it, paging older history in when it is further back, and\n * highlights it briefly; a cursor it can't find leaves the view at the bottom.\n */\nimport {\n type ThreadMessage,\n type ThreadViewportState,\n useAui,\n useThreadViewport,\n useThreadViewportStore,\n} from \"@assistant-ui/react\";\nimport {\n deepAgentHistoryHasOlder,\n HANDOFF_CURSOR_HIGHLIGHT_MS,\n HANDOFF_CURSOR_SETTLE_MS,\n handoffCursorMessageId,\n handoffCursorRevealStep,\n loadOlderDeepAgentHistoryPage,\n MESSAGE_ROW_ID_ATTRIBUTE,\n readDeepAgentStatewireExtras,\n readHandoffArrival,\n readingCursorOf,\n settleHandoffArrivalCursor,\n useHandoffArrivalCursor,\n} from \"@athenaintel/agent-runtime\";\nimport type { FC } from \"react\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { THREAD_VIEWPORT_ATTR, THREAD_VIEWPORT_FOOTER_ATTR } from \"./statewire-history-edge\";\n/** What assistant-ui's `ThreadPrimitive.Root` stamps on the thread panel. */\nconst THREAD_ROOT_SELECTOR = \"[data-aui-thread-root]\";\n/** Set on the revealed message while it is highlighted. */\nexport const HANDOFF_CURSOR_REVEALED_ATTR = \"data-handoff-cursor-revealed\";\n/** How often a landing looks for its message while the transcript loads. */\nexport const HANDOFF_CURSOR_POLL_MS = 150;\n\nconst escapeAttributeValue = (value: string): string =>\n typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\"\n ? CSS.escape(value)\n : value.replace(/[\"\\\\]/g, \"\\\\$&\");\n\n/** The thread viewport `anchor` sits in, or the one of the thread panel it sits in. */\nexport function findThreadViewport(anchor: Element | null | undefined): HTMLElement | null {\n if (!anchor) return null;\n const inside = anchor.closest<HTMLElement>(`[${THREAD_VIEWPORT_ATTR}]`);\n if (inside) return inside;\n return (\n anchor.closest(THREAD_ROOT_SELECTOR)?.querySelector<HTMLElement>(`[${THREAD_VIEWPORT_ATTR}]`) ??\n null\n );\n}\n\n/** How much of `viewport` its sticky footer (the composer dock) covers at the bottom. */\nfunction coveredBottomOf(viewport: HTMLElement): number {\n const footer = viewport.querySelector<HTMLElement>(`[${THREAD_VIEWPORT_FOOTER_ATTR}]`);\n if (!footer) return 0;\n return Math.max(0, viewport.getBoundingClientRect().bottom - footer.getBoundingClientRect().top);\n}\n\n/** Where `viewport` is being read: `null` at the bottom, else a message id. */\nexport function readThreadReadingCursor(viewport: HTMLElement | null): string | null {\n if (!viewport) return null;\n return readingCursorOf({ viewport, coveredBottomPx: coveredBottomOf(viewport) });\n}\n\n/**\n * Reads where the thread is being read, at the moment of a click: inside the\n * viewport (a composer toolbar, even from a portaled dialog) through the\n * thread's viewport store; elsewhere in the panel (a header menu) from the\n * DOM around `anchor`. `null` at the bottom or with no viewport found.\n */\nexport function useReadThreadReadingCursor(): (anchor?: Element | null) => string | null {\n const store = useThreadViewportStore({ optional: true });\n return useCallback(\n (anchor?: Element | null) =>\n readThreadReadingCursor(store?.getState().element.viewport ?? findThreadViewport(anchor)),\n [store],\n );\n}\n\n/** Scrolls `element` into `viewport`'s visible part: centred, or its top when taller. */\nfunction scrollMessageIntoView({\n viewport,\n element,\n}: {\n viewport: HTMLElement;\n element: HTMLElement;\n}): void {\n const box = viewport.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n const visible = Math.max(1, box.height - coveredBottomOf(viewport));\n const offsetTop = rect.top - box.top + viewport.scrollTop;\n const top =\n rect.height >= visible ? offsetTop - 8 : offsetTop - (visible - rect.height) / 2;\n // `instant`: the shell's viewport scrolls smoothly by default.\n viewport.scrollTo({ top: Math.max(0, top), behavior: \"instant\" });\n}\n\nfunction isInView({ viewport, element }: { viewport: HTMLElement; element: HTMLElement }): boolean {\n const box = viewport.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n return rect.bottom > box.top && rect.top < box.bottom - coveredBottomOf(viewport);\n}\n\n/**\n * A brief tint on the revealed message, through the Web Animations API so it\n * needs nothing from the host's stylesheet. Returns its undo.\n */\nfunction highlightMessage(element: HTMLElement): () => void {\n element.setAttribute(HANDOFF_CURSOR_REVEALED_ATTR, \"\");\n const animation =\n typeof element.animate === \"function\"\n ? element.animate(\n [\n {\n backgroundColor: \"rgba(37, 99, 235, 0.12)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0.35)\",\n borderRadius: \"12px\",\n },\n {\n backgroundColor: \"rgba(37, 99, 235, 0.12)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0.35)\",\n borderRadius: \"12px\",\n offset: 0.5,\n },\n {\n backgroundColor: \"rgba(37, 99, 235, 0)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0)\",\n borderRadius: \"12px\",\n },\n ],\n { duration: HANDOFF_CURSOR_HIGHLIGHT_MS, easing: \"ease-out\" },\n )\n : null;\n const timer = setTimeout(\n () => element.removeAttribute(HANDOFF_CURSOR_REVEALED_ATTR),\n HANDOFF_CURSOR_HIGHLIGHT_MS,\n );\n return () => {\n clearTimeout(timer);\n animation?.cancel();\n element.removeAttribute(HANDOFF_CURSOR_REVEALED_ATTR);\n };\n}\n\ninterface ThreadSnapshot {\n messages: readonly ThreadMessage[];\n isLoading: boolean;\n extras: unknown;\n}\n\n/**\n * Opens the transcript at `cursor`: looks for the message every\n * {@link HANDOFF_CURSOR_POLL_MS}, pages older history in while it is further\n * back, scrolls to it, holds it in view while the transcript settles (until\n * the person scrolls), and highlights it. `claim` takes the pickup right\n * before the scroll (`false`: another transcript showing the conversation took\n * it, and this one stays put). Calls `onSettled` once it gave up (the bottom)\n * or its hold ended. Returns its cancel, which leaves a revealed message's\n * hold and highlight to finish on their own unless `endHold` (a newer pickup\n * took over).\n */\nexport function startHandoffCursorReveal({\n cursor,\n readThread,\n readViewport,\n claim = () => true,\n onSettled,\n now = () => Date.now(),\n}: {\n cursor: string;\n readThread: () => ThreadSnapshot;\n readViewport: () => HTMLElement | null;\n claim?: () => boolean;\n onSettled: () => void;\n now?: () => number;\n}): (options?: { endHold?: boolean }) => void {\n // Progress restarts the wait: the reveal started, or a page of older history landed.\n let waitingSince = now();\n let pagesLoaded = 0;\n let paging = false;\n // A page that landed renders on a later commit: look again before paging on.\n let pageLandsAt = 0;\n let settled = false;\n let revealed: { element: HTMLElement; viewport: HTMLElement; at: number } | null = null;\n let personScrolled = false;\n let unhighlight: (() => void) | null = null;\n let detach: (() => void) | null = null;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n clearInterval(timer);\n detach?.();\n onSettled();\n };\n const pageOlder = () => {\n paging = true;\n loadOlderDeepAgentHistoryPage({\n readExtras: () => readDeepAgentStatewireExtras(readThread().extras),\n })\n .then((loaded) => {\n if (!loaded) {\n // Nothing older could be stated: the history is exhausted.\n pagesLoaded = Number.POSITIVE_INFINITY;\n return;\n }\n pagesLoaded += 1;\n waitingSince = now();\n })\n .catch(() => {\n pagesLoaded = Number.POSITIVE_INFINITY;\n })\n .finally(() => {\n paging = false;\n pageLandsAt = now() + 2 * HANDOFF_CURSOR_POLL_MS;\n });\n };\n const tick = () => {\n if (settled) return;\n if (revealed) {\n if (personScrolled || now() - revealed.at >= HANDOFF_CURSOR_SETTLE_MS) {\n settle();\n return;\n }\n if (revealed.element.isConnected && !isInView(revealed)) scrollMessageIntoView(revealed);\n return;\n }\n const viewport = readViewport();\n const { messages, isLoading, extras } = readThread();\n const targetId = handoffCursorMessageId({ messages, cursor });\n const element =\n targetId !== null && viewport\n ? viewport.querySelector<HTMLElement>(\n `[${MESSAGE_ROW_ID_ATTRIBUTE}=\"${escapeAttributeValue(targetId)}\"]`,\n )\n : null;\n const step = handoffCursorRevealStep({\n found: element !== null,\n // Still attaching, a page in flight, or the row not rendered yet.\n loading:\n isLoading ||\n paging ||\n now() < pageLandsAt ||\n messages.length === 0 ||\n (targetId !== null && !element),\n hasOlder: deepAgentHistoryHasOlder(extras),\n pagesLoaded,\n elapsedMs: now() - waitingSince,\n });\n switch (step) {\n case \"reveal\": {\n if (!viewport || !element) return;\n if (!claim()) {\n // Another transcript showing this conversation took the pickup.\n settled = true;\n clearInterval(timer);\n return;\n }\n scrollMessageIntoView({ viewport, element });\n unhighlight = highlightMessage(element);\n revealed = { element, viewport, at: now() };\n const stop = () => {\n personScrolled = true;\n };\n const events = [\"wheel\", \"touchstart\", \"pointerdown\", \"keydown\"] as const;\n for (const event of events) viewport.addEventListener(event, stop, { passive: true });\n detach = () => {\n for (const event of events) viewport.removeEventListener(event, stop);\n };\n return;\n }\n case \"page\":\n pageOlder();\n return;\n case \"wait\":\n return;\n case \"bottom\":\n settle();\n return;\n }\n };\n const timer = setInterval(tick, HANDOFF_CURSOR_POLL_MS);\n tick();\n return ({ endHold = false } = {}) => {\n if (endHold) unhighlight?.();\n if (settled) return;\n // A revealed message's hold finishes on its own timer, unless a newer pickup took over.\n if (revealed && !endHold) return;\n settled = true;\n clearInterval(timer);\n detach?.();\n unhighlight?.();\n };\n}\n\n/**\n * Opens `threadId`'s transcript at the message the source was reading, once,\n * when this surface picked the conversation up with a cursor\n * (`recordHandoffArrival({ cursor })`). When two transcripts show the\n * conversation, the first to find the message owns the pickup. Renders nothing.\n */\nexport const HandoffCursorReveal: FC<{ threadId: string | null | undefined }> = ({ threadId }) => {\n const cursor = useHandoffArrivalCursor(threadId);\n const aui = useAui();\n const viewport = useThreadViewport({\n optional: true,\n selector: (state: ThreadViewportState) => state.element.viewport,\n });\n const viewportRef = useRef(viewport);\n viewportRef.current = viewport;\n // The latest pickup's reveal: its hold outlives the claim that settles its cursor.\n const latestRef = useRef<ReturnType<typeof startHandoffCursorReveal> | null>(null);\n useEffect(() => {\n if (!threadId || !cursor) return undefined;\n // A newer pickup takes over: the previous message stops being held and highlighted.\n latestRef.current?.({ endHold: true });\n const cancel = startHandoffCursorReveal({\n cursor,\n readThread: () => {\n const state = aui.thread().getState();\n return { messages: state.messages, isLoading: state.isLoading, extras: state.extras };\n },\n readViewport: () => viewportRef.current ?? null,\n // Settled before the scroll, so a second transcript's reveal stops at its claim.\n claim: () => {\n if (readHandoffArrival(threadId)?.cursor !== cursor) return false;\n settleHandoffArrivalCursor(threadId);\n return true;\n },\n // Only this pickup's: a revealed message's hold ends after its claim\n // settled the cursor, when a later handover may have recorded another.\n onSettled: () => {\n if (readHandoffArrival(threadId)?.cursor === cursor) settleHandoffArrivalCursor(threadId);\n },\n });\n latestRef.current = cancel;\n return cancel;\n }, [aui, cursor, threadId]);\n return null;\n};\n"],"names":["THREAD_VIEWPORT_ATTR","THREAD_VIEWPORT_FOOTER_ATTR","readingCursorOf","useThreadViewportStore","useCallback","HANDOFF_CURSOR_HIGHLIGHT_MS","loadOlderDeepAgentHistoryPage","readDeepAgentStatewireExtras","HANDOFF_CURSOR_SETTLE_MS","handoffCursorMessageId","MESSAGE_ROW_ID_ATTRIBUTE","handoffCursorRevealStep","deepAgentHistoryHasOlder","useHandoffArrivalCursor","useAui","useThreadViewport","useRef","useEffect","readHandoffArrival","settleHandoffArrivalCursor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handoff-cursor.cjs","sources":["../../../src/chat-ui/thread/handoff-cursor.tsx"],"sourcesContent":["/**\n * Handovers open where the person was reading (`@athenaintel/agent-runtime`'s\n * `handoff-cursor`), for every host of `ChatThreadShell` (Athena in Chrome,\n * the desktop app, `AthenaChat`).\n *\n * As a source, `useReadThreadReadingCursor` reads the shell's viewport at the\n * moment of the click: `null` at the bottom, else the id of the last message\n * fully in view. As a destination, `HandoffCursorReveal` (mounted by the shell\n * for its `arrivalThreadId`) scrolls to the message the arrival names once the\n * transcript has it, paging older history in when it is further back, and\n * highlights it briefly; a cursor it can't find leaves the view at the bottom.\n */\nimport {\n type ThreadMessage,\n type ThreadViewportState,\n useAui,\n useThreadViewport,\n useThreadViewportStore,\n} from \"@assistant-ui/react\";\nimport {\n deepAgentHistoryHasOlder,\n HANDOFF_CURSOR_HIGHLIGHT_MS,\n HANDOFF_CURSOR_SETTLE_MS,\n handoffCursorMessageId,\n handoffCursorRevealStep,\n isHistoryPageLostToReconnect,\n loadOlderDeepAgentHistoryPage,\n MESSAGE_ROW_ID_ATTRIBUTE,\n readDeepAgentStatewireExtras,\n readHandoffArrival,\n readingCursorOf,\n settleHandoffArrivalCursor,\n useHandoffArrivalCursor,\n} from \"@athenaintel/agent-runtime\";\nimport type { FC } from \"react\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { THREAD_VIEWPORT_ATTR, THREAD_VIEWPORT_FOOTER_ATTR } from \"./statewire-history-edge\";\n/** What assistant-ui's `ThreadPrimitive.Root` stamps on the thread panel. */\nconst THREAD_ROOT_SELECTOR = \"[data-aui-thread-root]\";\n/** Set on the revealed message while it is highlighted. */\nexport const HANDOFF_CURSOR_REVEALED_ATTR = \"data-handoff-cursor-revealed\";\n/** How often a landing looks for its message while the transcript loads. */\nexport const HANDOFF_CURSOR_POLL_MS = 150;\n\nconst escapeAttributeValue = (value: string): string =>\n typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\"\n ? CSS.escape(value)\n : value.replace(/[\"\\\\]/g, \"\\\\$&\");\n\n/** The thread viewport `anchor` sits in, or the one of the thread panel it sits in. */\nexport function findThreadViewport(anchor: Element | null | undefined): HTMLElement | null {\n if (!anchor) return null;\n const inside = anchor.closest<HTMLElement>(`[${THREAD_VIEWPORT_ATTR}]`);\n if (inside) return inside;\n return (\n anchor.closest(THREAD_ROOT_SELECTOR)?.querySelector<HTMLElement>(`[${THREAD_VIEWPORT_ATTR}]`) ??\n null\n );\n}\n\n/** How much of `viewport` its sticky footer (the composer dock) covers at the bottom. */\nfunction coveredBottomOf(viewport: HTMLElement): number {\n const footer = viewport.querySelector<HTMLElement>(`[${THREAD_VIEWPORT_FOOTER_ATTR}]`);\n if (!footer) return 0;\n return Math.max(0, viewport.getBoundingClientRect().bottom - footer.getBoundingClientRect().top);\n}\n\n/** Where `viewport` is being read: `null` at the bottom, else a message id. */\nexport function readThreadReadingCursor(viewport: HTMLElement | null): string | null {\n if (!viewport) return null;\n return readingCursorOf({ viewport, coveredBottomPx: coveredBottomOf(viewport) });\n}\n\n/**\n * Reads where the thread is being read, at the moment of a click: inside the\n * viewport (a composer toolbar, even from a portaled dialog) through the\n * thread's viewport store; elsewhere in the panel (a header menu) from the\n * DOM around `anchor`. `null` at the bottom or with no viewport found.\n */\nexport function useReadThreadReadingCursor(): (anchor?: Element | null) => string | null {\n const store = useThreadViewportStore({ optional: true });\n return useCallback(\n (anchor?: Element | null) =>\n readThreadReadingCursor(store?.getState().element.viewport ?? findThreadViewport(anchor)),\n [store],\n );\n}\n\n/** Scrolls `element` into `viewport`'s visible part: centred, or its top when taller. */\nfunction scrollMessageIntoView({\n viewport,\n element,\n}: {\n viewport: HTMLElement;\n element: HTMLElement;\n}): void {\n const box = viewport.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n const visible = Math.max(1, box.height - coveredBottomOf(viewport));\n const offsetTop = rect.top - box.top + viewport.scrollTop;\n const top =\n rect.height >= visible ? offsetTop - 8 : offsetTop - (visible - rect.height) / 2;\n // `instant`: the shell's viewport scrolls smoothly by default.\n viewport.scrollTo({ top: Math.max(0, top), behavior: \"instant\" });\n}\n\nfunction isInView({ viewport, element }: { viewport: HTMLElement; element: HTMLElement }): boolean {\n const box = viewport.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n return rect.bottom > box.top && rect.top < box.bottom - coveredBottomOf(viewport);\n}\n\n/**\n * A brief tint on the revealed message, through the Web Animations API so it\n * needs nothing from the host's stylesheet. Returns its undo.\n */\nfunction highlightMessage(element: HTMLElement): () => void {\n element.setAttribute(HANDOFF_CURSOR_REVEALED_ATTR, \"\");\n const animation =\n typeof element.animate === \"function\"\n ? element.animate(\n [\n {\n backgroundColor: \"rgba(37, 99, 235, 0.12)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0.35)\",\n borderRadius: \"12px\",\n },\n {\n backgroundColor: \"rgba(37, 99, 235, 0.12)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0.35)\",\n borderRadius: \"12px\",\n offset: 0.5,\n },\n {\n backgroundColor: \"rgba(37, 99, 235, 0)\",\n boxShadow: \"0 0 0 2px rgba(37, 99, 235, 0)\",\n borderRadius: \"12px\",\n },\n ],\n { duration: HANDOFF_CURSOR_HIGHLIGHT_MS, easing: \"ease-out\" },\n )\n : null;\n const timer = setTimeout(\n () => element.removeAttribute(HANDOFF_CURSOR_REVEALED_ATTR),\n HANDOFF_CURSOR_HIGHLIGHT_MS,\n );\n return () => {\n clearTimeout(timer);\n animation?.cancel();\n element.removeAttribute(HANDOFF_CURSOR_REVEALED_ATTR);\n };\n}\n\ninterface ThreadSnapshot {\n messages: readonly ThreadMessage[];\n isLoading: boolean;\n extras: unknown;\n}\n\n/**\n * Opens the transcript at `cursor`: looks for the message every\n * {@link HANDOFF_CURSOR_POLL_MS}, pages older history in while it is further\n * back, scrolls to it, holds it in view while the transcript settles (until\n * the person scrolls), and highlights it. `claim` takes the pickup right\n * before the scroll (`false`: another transcript showing the conversation took\n * it, and this one stays put). Calls `onSettled` once it gave up (the bottom)\n * or its hold ended. Returns its cancel, which leaves a revealed message's\n * hold and highlight to finish on their own unless `endHold` (a newer pickup\n * took over).\n */\nexport function startHandoffCursorReveal({\n cursor,\n readThread,\n readViewport,\n claim = () => true,\n onSettled,\n now = () => Date.now(),\n}: {\n cursor: string;\n readThread: () => ThreadSnapshot;\n readViewport: () => HTMLElement | null;\n claim?: () => boolean;\n onSettled: () => void;\n now?: () => number;\n}): (options?: { endHold?: boolean }) => void {\n // Progress restarts the wait: the reveal started, or a page of older history landed.\n let waitingSince = now();\n let pagesLoaded = 0;\n let paging = false;\n // A page that landed renders on a later commit: look again before paging on.\n let pageLandsAt = 0;\n let settled = false;\n let revealed: { element: HTMLElement; viewport: HTMLElement; at: number } | null = null;\n let personScrolled = false;\n let unhighlight: (() => void) | null = null;\n let detach: (() => void) | null = null;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n clearInterval(timer);\n detach?.();\n onSettled();\n };\n const pageOlder = () => {\n paging = true;\n loadOlderDeepAgentHistoryPage({\n readExtras: () => readDeepAgentStatewireExtras(readThread().extras),\n })\n .then((loaded) => {\n if (!loaded) {\n // Nothing older could be stated: the history is exhausted.\n pagesLoaded = Number.POSITIVE_INFINITY;\n return;\n }\n pagesLoaded += 1;\n waitingSince = now();\n })\n .catch((error: unknown) => {\n // A dropped connection is not the end of the history: the re-attach\n // carries what was loaded, and the next look pages on from there.\n if (!isHistoryPageLostToReconnect(error)) pagesLoaded = Number.POSITIVE_INFINITY;\n })\n .finally(() => {\n paging = false;\n pageLandsAt = now() + 2 * HANDOFF_CURSOR_POLL_MS;\n });\n };\n const tick = () => {\n if (settled) return;\n if (revealed) {\n if (personScrolled || now() - revealed.at >= HANDOFF_CURSOR_SETTLE_MS) {\n settle();\n return;\n }\n if (revealed.element.isConnected && !isInView(revealed)) scrollMessageIntoView(revealed);\n return;\n }\n const viewport = readViewport();\n const { messages, isLoading, extras } = readThread();\n const targetId = handoffCursorMessageId({ messages, cursor });\n const element =\n targetId !== null && viewport\n ? viewport.querySelector<HTMLElement>(\n `[${MESSAGE_ROW_ID_ATTRIBUTE}=\"${escapeAttributeValue(targetId)}\"]`,\n )\n : null;\n const step = handoffCursorRevealStep({\n found: element !== null,\n // Still attaching, a page in flight, or the row not rendered yet.\n loading:\n isLoading ||\n paging ||\n now() < pageLandsAt ||\n messages.length === 0 ||\n (targetId !== null && !element),\n hasOlder: deepAgentHistoryHasOlder(extras),\n pagesLoaded,\n elapsedMs: now() - waitingSince,\n });\n switch (step) {\n case \"reveal\": {\n if (!viewport || !element) return;\n if (!claim()) {\n // Another transcript showing this conversation took the pickup.\n settled = true;\n clearInterval(timer);\n return;\n }\n scrollMessageIntoView({ viewport, element });\n unhighlight = highlightMessage(element);\n revealed = { element, viewport, at: now() };\n const stop = () => {\n personScrolled = true;\n };\n const events = [\"wheel\", \"touchstart\", \"pointerdown\", \"keydown\"] as const;\n for (const event of events) viewport.addEventListener(event, stop, { passive: true });\n detach = () => {\n for (const event of events) viewport.removeEventListener(event, stop);\n };\n return;\n }\n case \"page\":\n pageOlder();\n return;\n case \"wait\":\n return;\n case \"bottom\":\n settle();\n return;\n }\n };\n const timer = setInterval(tick, HANDOFF_CURSOR_POLL_MS);\n tick();\n return ({ endHold = false } = {}) => {\n if (endHold) unhighlight?.();\n if (settled) return;\n // A revealed message's hold finishes on its own timer, unless a newer pickup took over.\n if (revealed && !endHold) return;\n settled = true;\n clearInterval(timer);\n detach?.();\n unhighlight?.();\n };\n}\n\n/**\n * Opens `threadId`'s transcript at the message the source was reading, once,\n * when this surface picked the conversation up with a cursor\n * (`recordHandoffArrival({ cursor })`). When two transcripts show the\n * conversation, the first to find the message owns the pickup. Renders nothing.\n */\nexport const HandoffCursorReveal: FC<{ threadId: string | null | undefined }> = ({ threadId }) => {\n const cursor = useHandoffArrivalCursor(threadId);\n const aui = useAui();\n const viewport = useThreadViewport({\n optional: true,\n selector: (state: ThreadViewportState) => state.element.viewport,\n });\n const viewportRef = useRef(viewport);\n viewportRef.current = viewport;\n // The latest pickup's reveal: its hold outlives the claim that settles its cursor.\n const latestRef = useRef<ReturnType<typeof startHandoffCursorReveal> | null>(null);\n useEffect(() => {\n if (!threadId || !cursor) return undefined;\n // A newer pickup takes over: the previous message stops being held and highlighted.\n latestRef.current?.({ endHold: true });\n const cancel = startHandoffCursorReveal({\n cursor,\n readThread: () => {\n const state = aui.thread().getState();\n return { messages: state.messages, isLoading: state.isLoading, extras: state.extras };\n },\n readViewport: () => viewportRef.current ?? null,\n // Settled before the scroll, so a second transcript's reveal stops at its claim.\n claim: () => {\n if (readHandoffArrival(threadId)?.cursor !== cursor) return false;\n settleHandoffArrivalCursor(threadId);\n return true;\n },\n // Only this pickup's: a revealed message's hold ends after its claim\n // settled the cursor, when a later handover may have recorded another.\n onSettled: () => {\n if (readHandoffArrival(threadId)?.cursor === cursor) settleHandoffArrivalCursor(threadId);\n },\n });\n latestRef.current = cancel;\n return cancel;\n }, [aui, cursor, threadId]);\n return null;\n};\n"],"names":["THREAD_VIEWPORT_ATTR","THREAD_VIEWPORT_FOOTER_ATTR","readingCursorOf","useThreadViewportStore","useCallback","HANDOFF_CURSOR_HIGHLIGHT_MS","loadOlderDeepAgentHistoryPage","readDeepAgentStatewireExtras","isHistoryPageLostToReconnect","HANDOFF_CURSOR_SETTLE_MS","handoffCursorMessageId","MESSAGE_ROW_ID_ATTRIBUTE","handoffCursorRevealStep","deepAgentHistoryHasOlder","useHandoffArrivalCursor","useAui","useThreadViewport","useRef","useEffect","readHandoffArrival","settleHandoffArrivalCursor"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { useRef, useEffect, useCallback } from "react";
|
|
|
3
3
|
import { THREAD_VIEWPORT_ATTR, THREAD_VIEWPORT_FOOTER_ATTR } from "./statewire-history-edge.js";
|
|
4
4
|
import { useHandoffArrivalCursor, readHandoffArrival, settleHandoffArrivalCursor } from "../../agent-runtime/src/handoff-arrival.js";
|
|
5
5
|
import { readingCursorOf, handoffCursorMessageId, MESSAGE_ROW_ID_ATTRIBUTE, handoffCursorRevealStep, HANDOFF_CURSOR_HIGHLIGHT_MS, HANDOFF_CURSOR_SETTLE_MS } from "../../agent-runtime/src/handoff-cursor.js";
|
|
6
|
-
import { deepAgentHistoryHasOlder, loadOlderDeepAgentHistoryPage, readDeepAgentStatewireExtras } from "../../agent-runtime/src/edit-command.js";
|
|
6
|
+
import { deepAgentHistoryHasOlder, loadOlderDeepAgentHistoryPage, isHistoryPageLostToReconnect, readDeepAgentStatewireExtras } from "../../agent-runtime/src/edit-command.js";
|
|
7
7
|
const THREAD_ROOT_SELECTOR = "[data-aui-thread-root]";
|
|
8
8
|
const HANDOFF_CURSOR_REVEALED_ATTR = "data-handoff-cursor-revealed";
|
|
9
9
|
const HANDOFF_CURSOR_POLL_MS = 150;
|
|
@@ -115,8 +115,8 @@ function startHandoffCursorReveal({
|
|
|
115
115
|
}
|
|
116
116
|
pagesLoaded += 1;
|
|
117
117
|
waitingSince = now();
|
|
118
|
-
}).catch(() => {
|
|
119
|
-
pagesLoaded = Number.POSITIVE_INFINITY;
|
|
118
|
+
}).catch((error) => {
|
|
119
|
+
if (!isHistoryPageLostToReconnect(error)) pagesLoaded = Number.POSITIVE_INFINITY;
|
|
120
120
|
}).finally(() => {
|
|
121
121
|
paging = false;
|
|
122
122
|
pageLandsAt = now() + 2 * HANDOFF_CURSOR_POLL_MS;
|