@jini-ai/chat 0.6.6 → 0.6.10
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/CHANGELOG.md +129 -1
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +187 -98
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +75 -47
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +19 -9
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +53 -24
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +18 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,131 @@
|
|
|
1
|
+
## 0.6.10
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- f9d9894: Hide recovered tool failures when tool calls are hidden. Keep unrecovered failures in one compact, localized disclosure per reply, preserving their existing details.
|
|
6
|
+
- 8ff5653: Make the chat-pane transcript readable from outside the component — a must-have for an AI (or an
|
|
7
|
+
e2e harness) verifying that commands it gave the pane (create a form, add a user, change a
|
|
8
|
+
permission) actually happened.
|
|
9
|
+
|
|
10
|
+
Before this, `<ChatPane>` fully encapsulated its conversation state: `useConversation()`'s
|
|
11
|
+
`messages` never left the component, `ChatPaneProps` had no callback that reported the live message
|
|
12
|
+
list, and the rendered transcript (`MessageList`/`MessageRow`/`ToolCard`) carried zero
|
|
13
|
+
`data-agent-element` tags — unlike every other interactive surface in this app's reference pages.
|
|
14
|
+
|
|
15
|
+
- `ChatPaneProps.onMessagesChange?: (messages: ChatMessage[]) => void` — fires with the full message
|
|
16
|
+
array on every change, mirroring the existing `onActivityChange` pattern. Each `ChatMessage`
|
|
17
|
+
already carries its `events` array (`tool_use`/`tool_result`), so a subscriber gets complete,
|
|
18
|
+
structured tool-call data for free — no DOM parsing needed.
|
|
19
|
+
- `data-agent-element="chat-transcript"` (role `list`) on the message-list container, and
|
|
20
|
+
`data-agent-element="chat-message-<id>"` (role `region`) on every message row, so `page.*` DOM
|
|
21
|
+
tools can enumerate the transcript the same way they read everything else in this app.
|
|
22
|
+
- `ToolCard` now wraps whatever it renders (any of its dozen built-in card variants, or a host's own
|
|
23
|
+
custom-registered renderer) in one `data-agent-element="tool-call-<use.id>"` (role `region`)
|
|
24
|
+
element — added at the single dispatch point every branch already funnels through, so no
|
|
25
|
+
individual card variant's own markup changed. The collapsed-by-default accordion detail (args/
|
|
26
|
+
result JSON) sits inside this tagged element, not gated behind expanding it: `textContent` (what
|
|
27
|
+
the `page.*` DOM driver actually reads) is layout-agnostic, so the full detail is readable whether
|
|
28
|
+
or not anything has been clicked open.
|
|
29
|
+
|
|
30
|
+
- 3fe4d14: Add compatibility regression fixtures for legacy inline image results, media-only saved events and delegated echo notes; runtime behavior is unchanged.
|
|
31
|
+
- 8ff5653: Add `createDaemonAttachmentUploader`, so composer drag-and-drop is one line instead of 160.
|
|
32
|
+
|
|
33
|
+
`ChatPane` has always gated its drop target and file picker on `uploadAttachments` being supplied — but
|
|
34
|
+
supplying it meant writing the client half yourself: a bounded-concurrency worker pool that preserves
|
|
35
|
+
input order, per-turn quota accounting, abort and timeout plumbing, and cleanup of files that already
|
|
36
|
+
landed when a later one fails. Every host would write the same ~160 lines, and the ordering and
|
|
37
|
+
cleanup details are exactly the ones that get skipped.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<ChatPane
|
|
41
|
+
transport={transport}
|
|
42
|
+
uploadAttachments={createDaemonAttachmentUploader(daemonUrl)}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That talks to `@jini-ai/http-kit`'s `POST`/`DELETE /api/attachments`. Pass `''` when a dev-server proxy
|
|
47
|
+
already forwards `/api` from the page's own origin. `maxAttachmentBytes`, `maxAttachmentCount`,
|
|
48
|
+
`maxBatchBytes`, `timeoutMs`, and `concurrency` are all configurable and all default to the daemon side's
|
|
49
|
+
own limits, so the client's early rejection matches what the daemon would have said anyway.
|
|
50
|
+
|
|
51
|
+
Client-side quotas are a courtesy, never the boundary — telling a user their 400 MB video is too large
|
|
52
|
+
before the browser streams it, not after. The daemon re-derives every one of them.
|
|
53
|
+
|
|
54
|
+
Two behavioral notes for anyone porting hand-rolled code onto this:
|
|
55
|
+
|
|
56
|
+
- **The request always sends `content-type: application/octet-stream`**, not `file.type`. The daemon
|
|
57
|
+
sniffs the real kind from the bytes and ignores the header, so forwarding the browser's guess buys
|
|
58
|
+
nothing — and breaks real uploads: a dropped `.json` file arrives as `application/json`, which an
|
|
59
|
+
app-wide `express.json()` on the daemon then claims, draining the request stream before the upload
|
|
60
|
+
route can read a byte.
|
|
61
|
+
- **Batch accounting is per uploader instance**, not module-global, so two panes pointed at two daemons
|
|
62
|
+
cannot consume each other's per-turn quota. Reservations are rolled back on failure, so a retry of a
|
|
63
|
+
failed turn is not refused for quota it never actually used.
|
|
64
|
+
|
|
65
|
+
- 72ea04d: Folded `@jini-ai/renderers-react` into `@jini-ai/ui` as an independently-importable, tree-shakeable
|
|
66
|
+
`./renderers` subpath, and retired the package.
|
|
67
|
+
|
|
68
|
+
Real (non-comment) imports of `@jini-ai/renderers-react` anywhere in the repo were exactly 3 files,
|
|
69
|
+
2 symbols, all inside `@jini-ai/ui`'s own `features/html-viewer` — `openSandboxedPreviewInNewTab`
|
|
70
|
+
and `useSandboxBridge`. `@jini-ai/ui` was already the package's only real consumer, so a standalone
|
|
71
|
+
package bought nothing, the same reasoning that folded `@jini-ai/ui-core` into `./core` and
|
|
72
|
+
`@jini-ai/composio` into `@jini-ai/admin`. The whole `src/**` tree (registry, renderers, srcDoc
|
|
73
|
+
host, annotation-canvas, preview-modal-shell, i18n) moves to `packages/ui/src/renderers/` as one
|
|
74
|
+
`git mv`, unchanged in shape; `html-viewer`'s two consumers switch from the package-qualified
|
|
75
|
+
import to a relative one, since they're now in the same package.
|
|
76
|
+
|
|
77
|
+
**The `@jini-ai/renderers-react → @jini-ai/chat` dependency was dropped first, before the move,
|
|
78
|
+
not after.** `registry.ts` imported `ArtifactManifest`/`inferLegacyManifest` from `@jini-ai/chat`,
|
|
79
|
+
and `types.ts` re-exported five manifest types from there. `@jini-ai/chat` already depends on
|
|
80
|
+
`@jini-ai/ui`, so folding renderers-react into `ui` unchanged would have created a real `ui ↔ chat`
|
|
81
|
+
package cycle. The five manifest types (`ArtifactManifest`, `ArtifactKind`, `ArtifactRendererId`,
|
|
82
|
+
`ArtifactExportKind`, `ArtifactStatus`) are now defined locally in `types.ts`, copied verbatim from
|
|
83
|
+
`@jini-ai/chat`'s `src/core/util/types.ts` — a small duplicated-type cost, accepted because a
|
|
84
|
+
renderer package must not depend on a chat package.
|
|
85
|
+
|
|
86
|
+
**`resolveArtifactManifest` no longer infers a legacy manifest from a file's name/extension** — a
|
|
87
|
+
real, disclosed API behavior change. It now returns `file.manifest ?? null`. Manifest inference
|
|
88
|
+
(guessing kind/renderer from `.md`/`.html`/`.svg`/`.tsx`, etc.) is a caller concern that depends on
|
|
89
|
+
a product's own naming conventions, not something a generic renderer registry should do on a
|
|
90
|
+
caller's behalf; a caller that still wants that inference should resolve a manifest for the file
|
|
91
|
+
itself (see `@jini-ai/chat`'s `inferLegacyManifest`) before handing it to this registry. Verified
|
|
92
|
+
zero external callers of `RendererRegistry`/`resolveArtifactManifest`/`createDefaultRendererRegistry`
|
|
93
|
+
anywhere in Jini or Tovu before making this change — `@jini-ai/chat`'s own `RendererRegistry` is an
|
|
94
|
+
unrelated, locally-defined class in `artifact-types.ts`, and `@jini-ai/artifacts`' own
|
|
95
|
+
`resolveArtifactManifest` is an unrelated 3-argument function; both are same-name coincidences, not
|
|
96
|
+
real dependents. The four bundled renderers (`HtmlRenderer`, `MarkdownRenderer`, `SvgRenderer`,
|
|
97
|
+
`ReactComponentRenderer`) gate `canRender` on a resolvable manifest before falling back to any
|
|
98
|
+
`file.kind`/extension heuristic of their own, so this ripples into every test that exercised
|
|
99
|
+
default-registry routing via a bare file name with no explicit `manifest` — all updated to attach
|
|
100
|
+
one, with the extension-inference-specific assertions flipped to assert the removed behavior no
|
|
101
|
+
longer fires. No test was weakened or deleted: every changed assertion now tests the new,
|
|
102
|
+
documented contract, or restores a manifest to keep testing what it always meant to test.
|
|
103
|
+
|
|
104
|
+
**`@jini-ai/renderers-react` is retired, not versioned forward.** It was published (0.1.2), and
|
|
105
|
+
`packages/renderers-react` is deleted from this workspace. Deleting the source does not unpublish
|
|
106
|
+
anything, so whoever runs the next release should follow up with:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
npm deprecate @jini-ai/renderers-react "Merged into @jini-ai/ui; import from '@jini-ai/ui/renderers' instead."
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`@jini-ai/chat`'s dead `@jini-ai/renderers-react` dependency (declared, never imported — its own
|
|
113
|
+
`react/artifact-types.ts` defines a local placeholder registry instead, per its
|
|
114
|
+
`TODO(renderers-react)` header, left untouched here as out of scope) is removed from its
|
|
115
|
+
`package.json`, hence the accompanying patch bump.
|
|
116
|
+
|
|
117
|
+
- 0c247a8: Fix visitor chat streaming announcements, resumed focus, frame batching, persistence and lazy mount/retry handling. Document the additive A2uiSurfaceCard registry prop for host validation and rendering.
|
|
118
|
+
- e16a907: Clear floating-point noise in displayed costs, reconcile resumed counter resets and revisited sessions, and expose reported output tokens to assistive technology.
|
|
119
|
+
- ea8d883: Release modal semantics when focus leaves chat, announce bounded rendered reply text, and retry opening without reloading mounted assets.
|
|
120
|
+
- Updated dependencies [22f0619]
|
|
121
|
+
- Updated dependencies [82ed2d3]
|
|
122
|
+
- Updated dependencies [5a3de86]
|
|
123
|
+
- @jini-ai/core@0.6.10
|
|
124
|
+
|
|
125
|
+
## 0.6.7 — 2026-10-22
|
|
126
|
+
|
|
127
|
+
- 2026-10-22: Memory fixes (durable run store reads its own row, ledger parses events once), shared settings segmented control, Slack token rule restored, coverage.
|
|
128
|
+
|
|
1
129
|
## 0.6.6 — 2026-10-16
|
|
2
130
|
|
|
3
131
|
- Staged attachment rows sit 8px apart so the top row's remove target stays 44px (4884cf15).
|
|
@@ -114,7 +242,6 @@
|
|
|
114
242
|
- Correct renderer priority and opt-in stylesheet documentation; retain the layout rationale with neutral provenance.
|
|
115
243
|
- BREAKING: replace the local finalizer Clock with core Clock.nowMs(); core/React helpers and ext-event slot-key callbacks take required objects.
|
|
116
244
|
|
|
117
|
-
|
|
118
245
|
- BREAKING: ChatStore methods use required argument objects; paging options move to the second bag. ChatHistoryMaintenance and ChatStoreFactory follow the same convention.
|
|
119
246
|
- BREAKING: SQLite/Postgres/PGlite factories use core `Clock` through `{ clock }` rather than `{ now }`; system-time defaults and SQL behavior are preserved.
|
|
120
247
|
|
|
@@ -204,5 +331,6 @@
|
|
|
204
331
|
- @jini-ai/agentic@0.1.1
|
|
205
332
|
|
|
206
333
|
### C2 storage ownership
|
|
334
|
+
|
|
207
335
|
- Added `./store/legacy` and `./store/legacy/sqlite`; preserved sync local project chat CRUD and schema.
|
|
208
336
|
- Retained the original owner-isolation suite on the concern adapter after draining the old package.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentEvent } from './events.js';
|
|
2
|
+
import type { ChatAttachment, ChatMessage } from './messages.js';
|
|
3
|
+
export interface AcceptedChatInput {
|
|
4
|
+
id: string;
|
|
5
|
+
text: string;
|
|
6
|
+
attachments?: ChatAttachment[];
|
|
7
|
+
secretRedaction?: ChatMessage['secretRedaction'];
|
|
8
|
+
/** Wire ids replaced by this receipt remain seen by recall. */
|
|
9
|
+
aliases?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
/** Display queue constituents using their accepted identities; storage and delivery retain one turn.
|
|
12
|
+
* MOVE: Receipts travel in events_json, so separate bubbles survive a history reload without another store.
|
|
13
|
+
* @complexity O(m + e + i) time/space for m messages, e receipt events and i accepted queue inputs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function projectAcceptedInputMessages({ messages }: {
|
|
16
|
+
messages: readonly ChatMessage[];
|
|
17
|
+
}, _options?: {}): ChatMessage[];
|
|
18
|
+
type UserMessageEvent = Extract<AgentEvent, {
|
|
19
|
+
kind: 'ext';
|
|
20
|
+
}> & {
|
|
21
|
+
data: AcceptedChatInput & {
|
|
22
|
+
optimistic?: boolean;
|
|
23
|
+
toolUseId?: string;
|
|
24
|
+
afterInputId?: string;
|
|
25
|
+
inputs?: AcceptedChatInput[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Reads the existing event-backed user bubble without depending on a host renderer. */
|
|
29
|
+
export declare function acceptedInputEvent({ event }: {
|
|
30
|
+
event: AgentEvent;
|
|
31
|
+
}, _options?: {}): UserMessageEvent | null;
|
|
32
|
+
/** Builds the same bubble for delivered answers and steering; queues use the existing pending row. */
|
|
33
|
+
export declare function acceptedInputUserEvent({ input, inputs, toolUseId, afterInputId }: {
|
|
34
|
+
input: AcceptedChatInput;
|
|
35
|
+
inputs?: AcceptedChatInput[];
|
|
36
|
+
toolUseId?: string;
|
|
37
|
+
afterInputId?: string;
|
|
38
|
+
}, { receipt }?: {
|
|
39
|
+
receipt?: 'optimistic' | 'accepted';
|
|
40
|
+
}): AgentEvent;
|
|
41
|
+
/** Projects saved answers and reconciles optimistic bubbles with wire events, occurrence for
|
|
42
|
+
* occurrence. Legacy steering receipts have no client id: only unmatched optimistic input may
|
|
43
|
+
* match by text; two real events with equal text always remain distinct.
|
|
44
|
+
* @complexity O(e + u + a) time, O(e + u) space for e events, u user inputs and total answer-label lookup cost a.
|
|
45
|
+
*/
|
|
46
|
+
export declare function projectAcceptedInputEvents({ events }: {
|
|
47
|
+
events: readonly AgentEvent[];
|
|
48
|
+
}, _options?: {}): AgentEvent[];
|
|
49
|
+
/** The sole transcript-to-recall projection. A merged queued turn retains its original inputs. */
|
|
50
|
+
export declare function acceptedInputsFromMessages({ messages }: {
|
|
51
|
+
messages: readonly ChatMessage[];
|
|
52
|
+
}, _options?: {}): AcceptedChatInput[];
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=accepted-input.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Accepted composer input has one identity across optimistic display, replay and recall. */
|
|
2
|
+
import { questionAnswerFromResult } from './question-answer.js';
|
|
3
|
+
import { MID_RUN_USER_MESSAGE_EVENT_NAME } from './turn-usage.js';
|
|
4
|
+
/** Display queue constituents using their accepted identities; storage and delivery retain one turn.
|
|
5
|
+
* MOVE: Receipts travel in events_json, so separate bubbles survive a history reload without another store.
|
|
6
|
+
* @complexity O(m + e + i) time/space for m messages, e receipt events and i accepted queue inputs.
|
|
7
|
+
*/
|
|
8
|
+
export function projectAcceptedInputMessages({ messages }, _options = {}) {
|
|
9
|
+
return messages.flatMap(message => {
|
|
10
|
+
if (message.role !== 'user')
|
|
11
|
+
return [message];
|
|
12
|
+
const inputs = (message.events ?? []).flatMap(event => {
|
|
13
|
+
const input = acceptedInputEvent({ event });
|
|
14
|
+
return input ? [input.data] : [];
|
|
15
|
+
});
|
|
16
|
+
if (inputs.length === 0)
|
|
17
|
+
return [message];
|
|
18
|
+
const { attachments: _attachments, secretRedaction: _redaction, ...turn } = message;
|
|
19
|
+
// MOVE: Older receipts have no per-input files; retain those legacy files on the final bubble.
|
|
20
|
+
return inputs.map((input, index) => ({ ...turn, id: input.id, content: input.text,
|
|
21
|
+
attachments: input.attachments ?? (index === inputs.length - 1 ? message.attachments ?? [] : []), ...(input.secretRedaction ? { secretRedaction: input.secretRedaction } : {}) }));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** Reads the existing event-backed user bubble without depending on a host renderer. */
|
|
25
|
+
export function acceptedInputEvent({ event }, _options = {}) {
|
|
26
|
+
if (event.kind !== 'ext' || event.name !== MID_RUN_USER_MESSAGE_EVENT_NAME)
|
|
27
|
+
return null;
|
|
28
|
+
const data = event.data;
|
|
29
|
+
return data && typeof data.id === 'string' && typeof data.text === 'string' ? event : null;
|
|
30
|
+
}
|
|
31
|
+
/** Project a persisted answer through the same receipt owner as composer input. */
|
|
32
|
+
function questionAnswerEvent({ event, input }, _options = {}) {
|
|
33
|
+
if (event.kind !== 'tool_result')
|
|
34
|
+
return null;
|
|
35
|
+
const text = questionAnswerFromResult({ result: event, input });
|
|
36
|
+
if (text === undefined)
|
|
37
|
+
return null;
|
|
38
|
+
return { kind: 'ext', name: MID_RUN_USER_MESSAGE_EVENT_NAME,
|
|
39
|
+
data: { id: `answer:${event.toolUseId}`, text, toolUseId: event.toolUseId } };
|
|
40
|
+
}
|
|
41
|
+
/** Builds the same bubble for delivered answers and steering; queues use the existing pending row. */
|
|
42
|
+
export function acceptedInputUserEvent({ input, inputs, toolUseId, afterInputId }, { receipt = 'optimistic' } = {}) {
|
|
43
|
+
return { kind: 'ext', name: MID_RUN_USER_MESSAGE_EVENT_NAME,
|
|
44
|
+
data: { ...input, ...(receipt === 'optimistic' ? { optimistic: true } : {}), ...(inputs === undefined ? {} : { inputs }), ...(afterInputId === undefined ? {} : { afterInputId }), ...(toolUseId === undefined ? {} : { toolUseId }) } };
|
|
45
|
+
}
|
|
46
|
+
function expandQuestionAnswers({ events }, _options = {}) {
|
|
47
|
+
const savedAnswers = new Set(events.flatMap(event => {
|
|
48
|
+
const input = acceptedInputEvent({ event });
|
|
49
|
+
return input && !input.data.optimistic && input.data.toolUseId ? [input.data.toolUseId] : [];
|
|
50
|
+
}));
|
|
51
|
+
// MOVE: Persisted picks carry values; the original tool input retains their human-readable labels.
|
|
52
|
+
const questions = new Map(events.flatMap(event => event.kind === 'tool_use' ? [[event.id, event.input]] : []));
|
|
53
|
+
return events.flatMap(event => {
|
|
54
|
+
const answer = questionAnswerEvent({ event, input: event.kind === 'tool_result' ? questions.get(event.toolUseId) : undefined });
|
|
55
|
+
return answer && !savedAnswers.has(answer.data.toolUseId) ? [event, answer] : [event];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function collectOptimisticInputs({ events }, _options = {}) {
|
|
59
|
+
const optimisticByText = new Map();
|
|
60
|
+
const optimisticByQuestion = new Map();
|
|
61
|
+
for (const event of events) {
|
|
62
|
+
const input = acceptedInputEvent({ event });
|
|
63
|
+
if (!input?.data.optimistic)
|
|
64
|
+
continue;
|
|
65
|
+
if (input.data.toolUseId)
|
|
66
|
+
optimisticByQuestion.set(input.data.toolUseId, input);
|
|
67
|
+
else {
|
|
68
|
+
const list = optimisticByText.get(input.data.text) ?? { events: [], index: 0 };
|
|
69
|
+
list.events.push(input);
|
|
70
|
+
optimisticByText.set(input.data.text, list);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { byText: optimisticByText, byQuestion: optimisticByQuestion };
|
|
74
|
+
}
|
|
75
|
+
function matchOptimisticInput({ input, optimistic, seen }, _options = {}) {
|
|
76
|
+
const candidates = optimistic.byText.get(input.data.text);
|
|
77
|
+
const next = candidates?.events[candidates.index];
|
|
78
|
+
const follows = next?.data.afterInputId === undefined || seen.has(next.data.afterInputId);
|
|
79
|
+
// Keep predecessor testing before the caller adds this receipt's ids to seen.
|
|
80
|
+
return input.data.toolUseId ? optimistic.byQuestion.get(input.data.toolUseId) : follows ? next : undefined;
|
|
81
|
+
}
|
|
82
|
+
function mergeAcceptedReceipt({ input, pending }, _options = {}) {
|
|
83
|
+
return { ...input, data: { ...input.data, id: pending.data.id,
|
|
84
|
+
...(pending.data.inputs ? { inputs: pending.data.inputs } : {}),
|
|
85
|
+
aliases: [...new Set([...(input.data.aliases ?? []), input.data.id, ...(pending.data.aliases ?? [])])] } };
|
|
86
|
+
}
|
|
87
|
+
function acceptedInputIds({ input }, _options = {}) {
|
|
88
|
+
return [input.data.id, ...(input.data.toolUseId ? [`answer:${input.data.toolUseId}`] : []), ...(input.data.aliases ?? [])];
|
|
89
|
+
}
|
|
90
|
+
function reconcileAcceptedInput({ event, optimistic, state }, _options = {}) {
|
|
91
|
+
const input = acceptedInputEvent({ event });
|
|
92
|
+
if (!input || input.data.optimistic)
|
|
93
|
+
return;
|
|
94
|
+
const { replacements, replaced, seen } = state;
|
|
95
|
+
const ids = acceptedInputIds({ input });
|
|
96
|
+
if (ids.some(id => seen.has(id))) {
|
|
97
|
+
replaced.add(event);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const pending = matchOptimisticInput({ input, optimistic, seen });
|
|
101
|
+
for (const id of ids)
|
|
102
|
+
seen.add(id);
|
|
103
|
+
if (!pending)
|
|
104
|
+
return;
|
|
105
|
+
advanceOptimisticInput({ input, optimistic });
|
|
106
|
+
for (const id of [pending.data.id, ...(pending.data.aliases ?? [])])
|
|
107
|
+
seen.add(id);
|
|
108
|
+
optimistic.byQuestion.delete(input.data.toolUseId ?? '');
|
|
109
|
+
replaced.add(pending);
|
|
110
|
+
replacements.set(event, mergeAcceptedReceipt({ input, pending }));
|
|
111
|
+
}
|
|
112
|
+
function advanceOptimisticInput({ input, optimistic }, _options = {}) {
|
|
113
|
+
const candidates = optimistic.byText.get(input.data.text);
|
|
114
|
+
if (candidates && !input.data.toolUseId)
|
|
115
|
+
candidates.index += 1;
|
|
116
|
+
}
|
|
117
|
+
/** Projects saved answers and reconciles optimistic bubbles with wire events, occurrence for
|
|
118
|
+
* occurrence. Legacy steering receipts have no client id: only unmatched optimistic input may
|
|
119
|
+
* match by text; two real events with equal text always remain distinct.
|
|
120
|
+
* @complexity O(e + u + a) time, O(e + u) space for e events, u user inputs and total answer-label lookup cost a.
|
|
121
|
+
*/
|
|
122
|
+
export function projectAcceptedInputEvents({ events }, _options = {}) {
|
|
123
|
+
const expanded = expandQuestionAnswers({ events });
|
|
124
|
+
const optimistic = collectOptimisticInputs({ events: expanded });
|
|
125
|
+
const state = { replacements: new Map(), replaced: new Set(), seen: new Set() };
|
|
126
|
+
for (const event of expanded)
|
|
127
|
+
reconcileAcceptedInput({ event, optimistic, state });
|
|
128
|
+
const { replacements, replaced } = state;
|
|
129
|
+
const projected = [];
|
|
130
|
+
const emitted = new Set();
|
|
131
|
+
for (const event of expanded) {
|
|
132
|
+
if (replaced.has(event))
|
|
133
|
+
continue;
|
|
134
|
+
const next = replacements.get(event) ?? event;
|
|
135
|
+
const input = acceptedInputEvent({ event: next });
|
|
136
|
+
if (input && emitted.has(input.data.id))
|
|
137
|
+
continue;
|
|
138
|
+
if (input)
|
|
139
|
+
emitted.add(input.data.id);
|
|
140
|
+
projected.push(next);
|
|
141
|
+
}
|
|
142
|
+
return projected;
|
|
143
|
+
}
|
|
144
|
+
/** The sole transcript-to-recall projection. A merged queued turn retains its original inputs. */
|
|
145
|
+
export function acceptedInputsFromMessages({ messages }, _options = {}) {
|
|
146
|
+
return messages.flatMap(message => {
|
|
147
|
+
if (message.role === 'user') {
|
|
148
|
+
if (message.inputPending)
|
|
149
|
+
return [];
|
|
150
|
+
// Queue constituents travel in events_json, already preserved by the existing chat store.
|
|
151
|
+
const receipts = (message.events ?? []).flatMap(event => {
|
|
152
|
+
const input = acceptedInputEvent({ event });
|
|
153
|
+
return input ? [input.data] : [];
|
|
154
|
+
});
|
|
155
|
+
return receipts.length > 0 ? receipts : [{ id: message.id, text: message.content }];
|
|
156
|
+
}
|
|
157
|
+
return projectAcceptedInputEvents({ events: message.events ?? [] }).flatMap(event => {
|
|
158
|
+
const input = acceptedInputEvent({ event });
|
|
159
|
+
if (!input)
|
|
160
|
+
return [];
|
|
161
|
+
return input.data.inputs?.map((receipt, index) => index === 0 ? { ...receipt, aliases: input.data.aliases ?? [] } : receipt) ?? [input.data];
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=accepted-input.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Browser-local per-chat decisions; the person's mode remains in the host's settings ledger. */
|
|
2
|
+
export interface AgentPluginChatState {
|
|
3
|
+
decision: {
|
|
4
|
+
selected: readonly string[];
|
|
5
|
+
asked?: boolean;
|
|
6
|
+
} | null;
|
|
7
|
+
excluded: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface AgentPluginUsage {
|
|
10
|
+
conversationId: string;
|
|
11
|
+
state: AgentPluginChatState;
|
|
12
|
+
plugins: readonly {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
export interface ChatPluginStorage {
|
|
18
|
+
getItem(key: string): string | null;
|
|
19
|
+
setItem(key: string, value: string): void;
|
|
20
|
+
}
|
|
21
|
+
export declare const AGENT_PLUGIN_STATE_CHANGED = "jini:agent-plugin-state";
|
|
22
|
+
/** Corrupt/blocked storage permits a fresh question, as clearing localStorage does. */
|
|
23
|
+
export declare function readAgentPluginChatState({ conversationId }: {
|
|
24
|
+
conversationId: string;
|
|
25
|
+
}, { storage }?: {
|
|
26
|
+
storage?: ChatPluginStorage | undefined;
|
|
27
|
+
}): AgentPluginChatState;
|
|
28
|
+
/** Persist only decision/exclusions. Existing exclusions win over an in-flight server response. */
|
|
29
|
+
export declare function writeAgentPluginChatState({ conversationId, state }: {
|
|
30
|
+
conversationId: string;
|
|
31
|
+
state: AgentPluginChatState;
|
|
32
|
+
}, { storage }?: {
|
|
33
|
+
storage?: ChatPluginStorage | undefined;
|
|
34
|
+
}): void;
|
|
35
|
+
/** Called only for the protocol's structured plugin event, before delivering it to the transcript. */
|
|
36
|
+
export declare function captureAgentPluginUsage({ event }: {
|
|
37
|
+
event: {
|
|
38
|
+
kind: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
data?: unknown;
|
|
41
|
+
};
|
|
42
|
+
}, _options?: {}): void;
|
|
43
|
+
//# sourceMappingURL=agent-plugin-chat-state.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const AGENT_PLUGIN_STATE_CHANGED = 'jini:agent-plugin-state';
|
|
2
|
+
const key = (conversationId) => `jini:chat-agent-plugins:${conversationId}`;
|
|
3
|
+
function browserStorage() {
|
|
4
|
+
try {
|
|
5
|
+
return typeof window === 'undefined' ? undefined : window.localStorage;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isStringList({ value }) {
|
|
12
|
+
return Array.isArray(value) && value.every(id => typeof id === 'string');
|
|
13
|
+
}
|
|
14
|
+
/** A stored row is usable only when its exclusions and (null or listed) decision are all string ids. */
|
|
15
|
+
function usableChatState({ value }) {
|
|
16
|
+
return value && isStringList({ value: value.excluded }) && (value.decision === null || isStringList({ value: value.decision?.selected })) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
/** Corrupt/blocked storage permits a fresh question, as clearing localStorage does. */
|
|
19
|
+
export function readAgentPluginChatState({ conversationId }, { storage = browserStorage() } = {}) {
|
|
20
|
+
try {
|
|
21
|
+
const state = usableChatState({ value: JSON.parse(storage?.getItem(key(conversationId)) ?? 'null') });
|
|
22
|
+
if (state)
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
catch { /* Private browsing/corrupt rows behave as a new chat decision. */ }
|
|
26
|
+
return { decision: null, excluded: [] };
|
|
27
|
+
}
|
|
28
|
+
/** Persist only decision/exclusions. Existing exclusions win over an in-flight server response. */
|
|
29
|
+
export function writeAgentPluginChatState({ conversationId, state }, { storage = browserStorage() } = {}) {
|
|
30
|
+
const current = readAgentPluginChatState({ conversationId }, { storage });
|
|
31
|
+
const next = { decision: state.decision, excluded: [...new Set([...current.excluded, ...state.excluded])] };
|
|
32
|
+
try {
|
|
33
|
+
storage?.setItem(key(conversationId), JSON.stringify(next));
|
|
34
|
+
}
|
|
35
|
+
catch { /* Session can continue without persistence. */ }
|
|
36
|
+
if (typeof window !== 'undefined')
|
|
37
|
+
window.dispatchEvent(new CustomEvent(AGENT_PLUGIN_STATE_CHANGED, { detail: { conversationId, state: next } }));
|
|
38
|
+
}
|
|
39
|
+
/** Called only for the protocol's structured plugin event, before delivering it to the transcript. */
|
|
40
|
+
export function captureAgentPluginUsage({ event }, _options = {}) {
|
|
41
|
+
if (event.kind !== 'ext' || event.name !== 'agent_plugins')
|
|
42
|
+
return;
|
|
43
|
+
const usage = event.data;
|
|
44
|
+
if (typeof usage?.conversationId !== 'string' || !usage.state || !Array.isArray(usage.state.excluded))
|
|
45
|
+
return;
|
|
46
|
+
writeAgentPluginChatState({ conversationId: usage.conversationId, state: usage.state });
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=agent-plugin-chat-state.js.map
|
|
@@ -80,34 +80,38 @@ export function createA2uiActionPoster({ baseUrl, path, fetch, reportFailure },
|
|
|
80
80
|
}
|
|
81
81
|
const controller = new AbortController();
|
|
82
82
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
83
|
-
try
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
credentials: "same-origin",
|
|
87
|
-
headers: { ...options.headers, "content-type": "application/json" },
|
|
88
|
-
body: JSON.stringify({ exchangeId, message }),
|
|
89
|
-
signal: controller.signal,
|
|
90
|
-
});
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
const detail = await response.text().catch(() => "");
|
|
93
|
-
reportFailure({ args: [`[a2ui] action delivery failed (${response.status}) for surface ${exchangeId}`, detail] }, {});
|
|
94
|
-
return { ok: false, reason: reasonForStatus(response.status) };
|
|
95
|
-
}
|
|
96
|
-
return { ok: true };
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
100
|
-
reportFailure({ args: [`[a2ui] action delivery failed for surface ${exchangeId}`, timedOut ? `timed out after ${timeoutMs}ms` : error] }, {});
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
reason: timedOut
|
|
104
|
-
? "This took too long to deliver and was given up on."
|
|
105
|
-
: "Couldn't reach the server to deliver this action.",
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
}
|
|
83
|
+
// Cleared on every outcome; a `.finally` rather than try/finally so no path falls through.
|
|
84
|
+
return postOnce({ endpoint, exchangeId, message, timeoutMs, reportFailure, fetch, signal: controller.signal, headers: options.headers })
|
|
85
|
+
.finally(() => clearTimeout(timer));
|
|
111
86
|
};
|
|
112
87
|
}
|
|
88
|
+
/** One POST attempt and its classification; never rejects. @complexity O(1). */
|
|
89
|
+
async function postOnce(attempt) {
|
|
90
|
+
const { exchangeId, reportFailure } = attempt;
|
|
91
|
+
try {
|
|
92
|
+
const response = await attempt.fetch(attempt.endpoint, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
credentials: "same-origin",
|
|
95
|
+
headers: { ...attempt.headers, "content-type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ exchangeId, message: attempt.message }),
|
|
97
|
+
signal: attempt.signal,
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const detail = await response.text().catch(() => "");
|
|
101
|
+
reportFailure({ args: [`[a2ui] action delivery failed (${response.status}) for surface ${exchangeId}`, detail] }, {});
|
|
102
|
+
return { ok: false, reason: reasonForStatus(response.status) };
|
|
103
|
+
}
|
|
104
|
+
return { ok: true };
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
108
|
+
reportFailure({ args: [`[a2ui] action delivery failed for surface ${exchangeId}`, timedOut ? `timed out after ${attempt.timeoutMs}ms` : error] }, {});
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
reason: timedOut
|
|
112
|
+
? "This took too long to deliver and was given up on."
|
|
113
|
+
: "Couldn't reach the server to deliver this action.",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
113
117
|
//# sourceMappingURL=a2ui-action-poster.js.map
|
|
@@ -8,7 +8,7 @@ export interface AssistantConversation {
|
|
|
8
8
|
updatedAt: number;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* The subset of a live transcript that is worth writing: user turns, and assistant turns whose run
|
|
11
|
+
* The subset of a live transcript that is worth writing: accepted user turns, and assistant turns whose run
|
|
12
12
|
* has reached a terminal status.
|
|
13
13
|
*
|
|
14
14
|
* An assistant message that is still `queued` or `running` is rewritten on every delta, so
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isTerminalRunStatus } from "../messages.js";
|
|
2
2
|
/**
|
|
3
|
-
* The subset of a live transcript that is worth writing: user turns, and assistant turns whose run
|
|
3
|
+
* The subset of a live transcript that is worth writing: accepted user turns, and assistant turns whose run
|
|
4
4
|
* has reached a terminal status.
|
|
5
5
|
*
|
|
6
6
|
* An assistant message that is still `queued` or `running` is rewritten on every delta, so
|
|
@@ -18,7 +18,7 @@ import { isTerminalRunStatus } from "../messages.js";
|
|
|
18
18
|
* Deleting them here would trade a history bug for a forensics hole.
|
|
19
19
|
*/
|
|
20
20
|
export function persistableMessages({ messages }, _options = {}) {
|
|
21
|
-
return messages.filter((message) => message.role === "user"
|
|
21
|
+
return messages.filter((message) => message.role === "user" ? !message.inputPending : isTerminalRunStatus({ status: message.runStatus }));
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* The key the admin's chat writers use to remember "this message is already saved": the message id,
|
|
@@ -2,7 +2,7 @@ import type { AgentEvent } from "../events.js";
|
|
|
2
2
|
import type { ChatMessage } from "../messages.js";
|
|
3
3
|
import type { ChatTransport, RunHandlers, StartRunInput } from "../transport.js";
|
|
4
4
|
import type { RunNotices } from "../run-events/entry.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { AgUiRunClient } from "./ag-ui-transport.js";
|
|
6
6
|
import type { DurableRunBinding } from "./durable-run-subscription.js";
|
|
7
7
|
/** Per-turn dispatch input; hosts may return their fuller saved settings without UI dependencies. */
|
|
8
8
|
export interface AssistantExecutionConfig {
|
|
@@ -127,7 +127,7 @@ export interface AssistantTransportPorts {
|
|
|
127
127
|
messageId: unknown;
|
|
128
128
|
conversationId?: string;
|
|
129
129
|
}, optional: Record<string, never>) => Promise<Response>;
|
|
130
|
-
readonly agUi:
|
|
130
|
+
readonly agUi: AgUiRunClient;
|
|
131
131
|
}
|
|
132
132
|
export interface ByokFrameContext {
|
|
133
133
|
collected: AgentEvent[];
|