@jini-ai/chat 0.3.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +152 -6
  3. package/dist/core/ag-ui/entry.d.ts +53 -0
  4. package/dist/core/ag-ui/entry.js +135 -0
  5. package/dist/core/agentic/chat-capabilities.d.ts +1 -1
  6. package/dist/core/agentic/index.d.ts +1 -1
  7. package/dist/core/agentic/index.js +1 -1
  8. package/dist/core/assistant-content.d.ts +6 -2
  9. package/dist/core/assistant-content.js +2 -2
  10. package/dist/core/compact-events.d.ts +3 -1
  11. package/dist/core/compact-events.js +1 -1
  12. package/dist/core/events.d.ts +1 -1
  13. package/dist/core/events.js +1 -1
  14. package/dist/core/index.d.ts +3 -3
  15. package/dist/core/index.js +3 -3
  16. package/dist/core/messages.d.ts +4 -2
  17. package/dist/core/messages.js +2 -2
  18. package/dist/core/partial-json.d.ts +6 -2
  19. package/dist/core/partial-json.js +3 -3
  20. package/dist/core/persistence/index.d.ts +1 -1
  21. package/dist/core/persistence/index.js +1 -1
  22. package/dist/core/persistence/ports.d.ts +2 -123
  23. package/dist/core/persistence/title.d.ts +3 -1
  24. package/dist/core/persistence/title.js +1 -1
  25. package/dist/core/question-form/format.d.ts +16 -7
  26. package/dist/core/question-form/format.js +3 -3
  27. package/dist/core/question-form/normalize.d.ts +7 -2
  28. package/dist/core/question-form/normalize.js +5 -5
  29. package/dist/core/question-form/scan.d.ts +18 -6
  30. package/dist/core/question-form/scan.js +14 -14
  31. package/dist/core/run-activity.d.ts +16 -4
  32. package/dist/core/run-activity.js +11 -11
  33. package/dist/core/run-events/entry.d.ts +121 -0
  34. package/dist/core/run-events/entry.js +290 -0
  35. package/dist/core/todos.d.ts +20 -8
  36. package/dist/core/todos.js +11 -11
  37. package/dist/core/tool-events.d.ts +3 -1
  38. package/dist/core/tool-events.js +1 -1
  39. package/dist/core/tool-output.d.ts +3 -1
  40. package/dist/core/tool-output.js +1 -1
  41. package/dist/core/tools.d.ts +14 -3
  42. package/dist/core/tools.js +3 -3
  43. package/dist/core/transcript.d.ts +13 -5
  44. package/dist/core/transcript.js +5 -5
  45. package/dist/core/util/manifest.d.ts +9 -3
  46. package/dist/core/util/manifest.js +3 -3
  47. package/dist/core/util/markdown-context.d.ts +11 -3
  48. package/dist/core/util/markdown-context.js +3 -3
  49. package/dist/core/util/markup-attributes.d.ts +3 -1
  50. package/dist/core/util/markup-attributes.js +1 -1
  51. package/dist/core/util/parser.d.ts +9 -5
  52. package/dist/core/util/parser.js +13 -13
  53. package/dist/core/util/recover.d.ts +6 -2
  54. package/dist/core/util/recover.js +6 -6
  55. package/dist/core/util/strip.d.ts +19 -5
  56. package/dist/core/util/strip.js +17 -17
  57. package/dist/core/util/validate.d.ts +3 -1
  58. package/dist/core/util/validate.js +1 -1
  59. package/dist/embed-widget/browser/page-actions.d.ts +121 -0
  60. package/dist/embed-widget/browser/page-actions.js +113 -0
  61. package/dist/embed-widget/browser/session-store.d.ts +44 -0
  62. package/dist/embed-widget/browser/session-store.js +103 -0
  63. package/dist/embed-widget/react/embed.d.ts +45 -0
  64. package/dist/embed-widget/react/embed.js +87 -0
  65. package/dist/embed-widget/react/header.d.ts +21 -0
  66. package/dist/embed-widget/react/header.js +18 -0
  67. package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
  68. package/dist/embed-widget/transports/fetch-sse.js +175 -0
  69. package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
  70. package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
  71. package/dist/react/artifact-types.d.ts +3 -3
  72. package/dist/react/artifact-types.js +3 -3
  73. package/dist/react/components/A2uiSurfaceCard.js +34 -13
  74. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
  75. package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
  76. package/dist/react/components/AttachmentPreviewModal.js +2 -2
  77. package/dist/react/components/AttachmentTray.d.ts +3 -1
  78. package/dist/react/components/AttachmentTray.js +2 -2
  79. package/dist/react/components/ChatFab.js +1 -1
  80. package/dist/react/components/Composer.js +9 -9
  81. package/dist/react/components/ConversationList.js +1 -1
  82. package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
  83. package/dist/react/components/McpUiSurfaceCard.js +5 -5
  84. package/dist/react/components/MessageList.js +1 -1
  85. package/dist/react/components/MessageRow.js +9 -9
  86. package/dist/react/components/QuestionForm.js +5 -5
  87. package/dist/react/components/ToolCard.js +9 -9
  88. package/dist/react/components/composer-discovery.d.ts +29 -8
  89. package/dist/react/components/composer-discovery.js +10 -10
  90. package/dist/react/ext-event-renderer-registry.d.ts +15 -5
  91. package/dist/react/ext-event-renderer-registry.js +4 -4
  92. package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
  93. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
  94. package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
  95. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
  96. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
  97. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
  98. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
  99. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
  100. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
  101. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
  102. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
  103. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
  104. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
  105. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
  106. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +30 -30
  107. package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
  108. package/dist/react/features/chat-pane/rules.d.ts +13 -4
  109. package/dist/react/features/chat-pane/rules.js +5 -5
  110. package/dist/react/features/chat-pane/types.d.ts +1 -1
  111. package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
  112. package/dist/react/hooks/attachment-preview-cache.js +2 -2
  113. package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
  114. package/dist/react/hooks/composer-draft-cache.js +6 -6
  115. package/dist/react/hooks/useArtifactStream.d.ts +5 -1
  116. package/dist/react/hooks/useArtifactStream.js +3 -3
  117. package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
  118. package/dist/react/hooks/useChatFabDrag.js +4 -4
  119. package/dist/react/hooks/useComposer.js +8 -8
  120. package/dist/react/hooks/useConversation.js +56 -4
  121. package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
  122. package/dist/react/hooks/useExtEventGroups.js +1 -1
  123. package/dist/react/hooks/useLatestOperation.d.ts +8 -3
  124. package/dist/react/hooks/useLatestOperation.js +4 -4
  125. package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
  126. package/dist/react/hooks/usePinnedTodos.js +3 -3
  127. package/dist/react/hooks/useQuestionForms.d.ts +7 -2
  128. package/dist/react/hooks/useQuestionForms.js +7 -7
  129. package/dist/react/hooks/useRunActivity.d.ts +6 -1
  130. package/dist/react/hooks/useRunActivity.js +3 -3
  131. package/dist/react/hooks/useRunStream.d.ts +3 -1
  132. package/dist/react/hooks/useRunStream.js +1 -1
  133. package/dist/react/hooks/useToolTimeline.d.ts +3 -1
  134. package/dist/react/hooks/useToolTimeline.js +3 -3
  135. package/dist/react/index.d.ts +4 -4
  136. package/dist/react/index.js +6 -6
  137. package/dist/react/message-blocks.d.ts +10 -4
  138. package/dist/react/message-blocks.js +2 -2
  139. package/dist/react/styles/reference.css +3 -3
  140. package/dist/react/tool-renderer-registry.d.ts +8 -3
  141. package/dist/react/tool-renderer-registry.js +3 -3
  142. package/dist/react/util/defined-props.d.ts +3 -1
  143. package/dist/react/util/defined-props.js +1 -1
  144. package/dist/server/run-finalizer/entry.d.ts +47 -0
  145. package/dist/server/run-finalizer/entry.js +156 -0
  146. package/dist/server/run-finalizer/ports.d.ts +52 -0
  147. package/dist/store/contracts/index.d.ts +5 -0
  148. package/dist/store/contracts/index.js +5 -0
  149. package/dist/store/errors.d.ts +12 -0
  150. package/dist/store/errors.js +18 -0
  151. package/dist/store/index.d.ts +4 -0
  152. package/dist/store/index.js +2 -0
  153. package/dist/store/legacy/index.d.ts +3 -0
  154. package/dist/store/legacy/index.js +2 -0
  155. package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
  156. package/dist/store/legacy/sqlite/conversations.js +274 -0
  157. package/dist/store/legacy/sqlite/index.d.ts +6 -0
  158. package/dist/store/legacy/sqlite/index.js +5 -0
  159. package/dist/store/legacy/sqlite/messages.d.ts +156 -0
  160. package/dist/store/legacy/sqlite/messages.js +246 -0
  161. package/dist/store/legacy/sqlite/schema.d.ts +3 -0
  162. package/dist/store/legacy/sqlite/schema.js +40 -0
  163. package/dist/store/pglite/index.d.ts +21 -0
  164. package/dist/store/pglite/index.js +17 -0
  165. package/dist/store/ports.d.ts +121 -0
  166. package/dist/store/ports.js +2 -0
  167. package/dist/store/postgres/index.d.ts +21 -0
  168. package/dist/store/postgres/index.js +17 -0
  169. package/dist/store/sql/boundary.d.ts +28 -0
  170. package/dist/store/sql/boundary.js +32 -0
  171. package/dist/store/sql/cursor.d.ts +24 -0
  172. package/dist/store/sql/cursor.js +49 -0
  173. package/dist/store/sql/maintenance.d.ts +9 -0
  174. package/dist/store/sql/maintenance.js +16 -0
  175. package/dist/store/sql/store.d.ts +22 -0
  176. package/dist/store/sql/store.js +340 -0
  177. package/dist/store/sql/tables.d.ts +34 -0
  178. package/dist/store/sql/tables.js +2 -0
  179. package/dist/store/sqlite/index.d.ts +36 -0
  180. package/dist/store/sqlite/index.js +29 -0
  181. package/dist/store/sqlite/schema.d.ts +49 -0
  182. package/dist/store/sqlite/schema.js +80 -0
  183. package/package.json +211 -14
  184. package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
  185. package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
  186. package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
  187. package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
  188. package/dist/react/features/model-picker/constants.d.ts +0 -6
  189. package/dist/react/features/model-picker/constants.js +0 -9
  190. package/dist/react/features/model-picker/dependencies.d.ts +0 -11
  191. package/dist/react/features/model-picker/dependencies.js +0 -10
  192. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
  193. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
  194. package/dist/react/features/model-picker/index.d.ts +0 -32
  195. package/dist/react/features/model-picker/index.js +0 -29
  196. package/dist/react/features/model-picker/ports.d.ts +0 -11
  197. package/dist/react/features/model-picker/rules.d.ts +0 -26
  198. package/dist/react/features/model-picker/rules.js +0 -84
  199. package/dist/react/features/model-picker/types.d.ts +0 -38
  200. package/dist/react/features/model-picker/types.js +0 -2
  201. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
  202. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
  203. package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
  204. package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
  205. /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
@@ -4,7 +4,7 @@
4
4
  * Zero React, zero DOM/browser globals, zero Node built-ins, zero imports
5
5
  * from any product package's scope. See ADS-memory/reports/jini-port/extraction-plan.md
6
6
  * §12 C2/C3 and ADS-memory/reports/jini-port/recon/r4b-webui-design.md §1 for the design
7
- * this package targets, and source-map.md for exact provenance.
7
+ * this package targets, and archived provenance ledger for exact provenance.
8
8
  *
9
9
  * Was `@jini-ai/chat-core` (its own top-level package, one entry point) through npm 0.1.2 —
10
10
  * consolidated 2026-08-03 into `@jini-ai/chat`'s `./core` subpath alongside `./react` (formerly
@@ -31,13 +31,13 @@ export * from './compact-events.js';
31
31
  * vocabulary this used to sit alongside — `CapabilityDef`, `PAGE_CAPABILITIES`, the
32
32
  * `data-agent-*` convention, the policy gate, the protocol projections — moved to `@jini-ai/agentic`
33
33
  * on 2026-07-26; import it directly rather than through this package. See
34
- * `packages/agentic/source-map.md` and this package's own source-map.md for the extraction.
34
+ * `packages/agentic/archived provenance ledger` and this package's own archived provenance ledger for the extraction.
35
35
  */
36
36
  export * from './agentic/index.js';
37
37
  /**
38
38
  * Durable chat history — the storage-neutral `ChatHistoryStore` port and the local title
39
39
  * heuristic. Types and pure functions only, so this package stays framework-free and
40
- * `runtime: universal`; the SQLite implementation lives in `@jini-ai/sqlite`'s `chat-history`
40
+ * `runtime: universal`; the SQLite implementation lives in `@jini-ai/sqlite-chat`'s `chat-history`
41
41
  * module, and a host binds it to its own authentication.
42
42
  */
43
43
  export * from './persistence/index.js';
@@ -8,12 +8,14 @@ export type ChatRole = 'user' | 'assistant';
8
8
  * `RunStatus` for a different shape at a different layer — a richer `{ id, state, ... }` record.
9
9
  * This is the flat string union a chat message stamps on itself. While both were called
10
10
  * `RunStatus`, every consumer importing both packages had to alias one on import, and nothing
11
- * stopped the two from being confused at a glance. See source-map.md.
11
+ * stopped the two from being confused at a glance. See archived provenance ledger.
12
12
  */
13
13
  export declare const CHAT_RUN_STATUSES: readonly ["queued", "running", "succeeded", "failed", "canceled"];
14
14
  export type ChatRunStatus = (typeof CHAT_RUN_STATUSES)[number];
15
15
  /** `true` once a run has reached a terminal status (no further events will arrive). */
16
- export declare function isTerminalRunStatus(status: ChatRunStatus | undefined): boolean;
16
+ export declare function isTerminalRunStatus({ status }: {
17
+ status: ChatRunStatus | undefined;
18
+ }): boolean;
17
19
  /** A file or image a user turn carries alongside its text. */
18
20
  export interface ChatAttachment {
19
21
  path: string;
@@ -5,12 +5,12 @@
5
5
  * `RunStatus` for a different shape at a different layer — a richer `{ id, state, ... }` record.
6
6
  * This is the flat string union a chat message stamps on itself. While both were called
7
7
  * `RunStatus`, every consumer importing both packages had to alias one on import, and nothing
8
- * stopped the two from being confused at a glance. See source-map.md.
8
+ * stopped the two from being confused at a glance. See archived provenance ledger.
9
9
  */
10
10
  export const CHAT_RUN_STATUSES = ['queued', 'running', 'succeeded', 'failed', 'canceled'];
11
11
  const TERMINAL_RUN_STATUSES = new Set(['succeeded', 'failed', 'canceled']);
12
12
  /** `true` once a run has reached a terminal status (no further events will arrive). */
13
- export function isTerminalRunStatus(status) {
13
+ export function isTerminalRunStatus({ status }) {
14
14
  return status !== undefined && TERMINAL_RUN_STATUSES.has(status);
15
15
  }
16
16
  //# sourceMappingURL=messages.js.map
@@ -23,7 +23,9 @@
23
23
  * depth `d` — one linear scan plus a bounded fixed-point cleanup loop over
24
24
  * the (already shrinking) tail.
25
25
  */
26
- export declare function repairJsonPrefix(buf: string): string;
26
+ export declare function repairJsonPrefix({ buf }: {
27
+ buf: string;
28
+ }): string;
27
29
  /**
28
30
  * Best-effort parse of a (possibly truncated) JSON prefix.
29
31
  *
@@ -32,5 +34,7 @@ export declare function repairJsonPrefix(buf: string): string;
32
34
  * parse failure — callers should fall back to their last good value.
33
35
  * @complexity O(n) — dominated by {@link repairJsonPrefix}.
34
36
  */
35
- export declare function parsePartialJson(buf: string): unknown;
37
+ export declare function parsePartialJson({ buf }: {
38
+ buf: string;
39
+ }): unknown;
36
40
  //# sourceMappingURL=partial-json.d.ts.map
@@ -23,7 +23,7 @@
23
23
  * depth `d` — one linear scan plus a bounded fixed-point cleanup loop over
24
24
  * the (already shrinking) tail.
25
25
  */
26
- export function repairJsonPrefix(buf) {
26
+ export function repairJsonPrefix({ buf }) {
27
27
  const stack = []; // closers owed, e.g. ['}', ']']
28
28
  let inStr = false;
29
29
  let esc = false;
@@ -105,12 +105,12 @@ export function repairJsonPrefix(buf) {
105
105
  * parse failure — callers should fall back to their last good value.
106
106
  * @complexity O(n) — dominated by {@link repairJsonPrefix}.
107
107
  */
108
- export function parsePartialJson(buf) {
108
+ export function parsePartialJson({ buf }) {
109
109
  const trimmed = buf.trim();
110
110
  if (!trimmed)
111
111
  return null;
112
112
  try {
113
- return JSON.parse(repairJsonPrefix(trimmed));
113
+ return JSON.parse(repairJsonPrefix({ buf: trimmed }));
114
114
  }
115
115
  catch {
116
116
  return null;
@@ -2,7 +2,7 @@
2
2
  * @module persistence
3
3
  *
4
4
  * Durable chat history: the storage-neutral port plus the local title heuristic. No driver, no
5
- * DDL, no I/O — a SQLite implementation lives in `@jini-ai/sqlite`'s `chat-history` module, and
5
+ * DDL, no I/O — a SQLite implementation lives in `@jini-ai/sqlite-chat`'s `chat-history` module, and
6
6
  * a host binds it to its own auth. See `./ports.ts` for why the owner scope is bound into the
7
7
  * store rather than passed per call.
8
8
  */
@@ -2,7 +2,7 @@
2
2
  * @module persistence
3
3
  *
4
4
  * Durable chat history: the storage-neutral port plus the local title heuristic. No driver, no
5
- * DDL, no I/O — a SQLite implementation lives in `@jini-ai/sqlite`'s `chat-history` module, and
5
+ * DDL, no I/O — a SQLite implementation lives in `@jini-ai/sqlite-chat`'s `chat-history` module, and
6
6
  * a host binds it to its own auth. See `./ports.ts` for why the owner scope is bound into the
7
7
  * store rather than passed per call.
8
8
  */
@@ -1,124 +1,3 @@
1
- /**
2
- * @module persistence/ports
3
- *
4
- * The storage-neutral contract for durable chat history: conversations and their messages,
5
- * scoped to an owner. Types and signatures only — this module imports no driver, opens no
6
- * file, and issues no DDL, so it stays inside `chat-core`'s `runtime: universal` promise.
7
- *
8
- * Why here and not in `@jini-ai/agentic`: `chat-core` already depends on `agentic`, so a port
9
- * living in `agentic` could not import {@link ChatMessage} — the type it exists to persist —
10
- * without inverting that edge. It would have to redefine the message shape (two sources of
11
- * truth) or be generic over an opaque type (a persistence abstraction naming nothing).
12
- *
13
- * Why the owner scope is baked into the port rather than passed per call: every read must be
14
- * `WHERE scope = ? AND owner = ? AND id = ?`, never `WHERE id = ?`. A port whose methods take
15
- * a bare id invites exactly one bug — a handler that forgets the predicate — and that bug is
16
- * an omission, so review does not reliably catch it. {@link ChatHistoryStore} is obtained
17
- * already-bound to a {@link ChatOwnerScope}; there is no method on it that can read across
18
- * owners. Hosts bind the scope from their own auth, which is the one part of this that cannot
19
- * be reused.
20
- */
21
- import type { ChatMessage } from '../messages.js';
22
- /**
23
- * Which kind of principal owns a conversation.
24
- *
25
- * `user` is an authenticated account; `guest` is an anonymous visitor identified only by an
26
- * opaque bearer token. They are separated rather than collapsed into one `ownerId` because the
27
- * retention and disclosure rules differ: guest history expires, user history does not, and a
28
- * guest's identifier is a credential that must be stored hashed.
29
- */
30
- export type ChatOwnerKind = 'user' | 'guest';
31
- /**
32
- * The isolation predicate, resolved by the host from its own authentication, and bound into a
33
- * store before any query runs.
34
- *
35
- * `scopeId` is the host's partition key — a workspace, a tenant, a project, whatever that host
36
- * partitions by. This package does not interpret it; it only guarantees every statement filters
37
- * on it.
38
- *
39
- * `ownerId` for a `guest` MUST already be hashed by the host. This package never sees, stores,
40
- * or logs a raw bearer token, and cannot verify that a caller honored this — it is stated here
41
- * because the constraint is real and the type system cannot express it.
42
- */
43
- export interface ChatOwnerScope {
44
- readonly scopeId: string;
45
- readonly ownerKind: ChatOwnerKind;
46
- readonly ownerId: string;
47
- }
48
- /** Where a conversation's current title came from — see {@link ChatHistoryStore.rename}. */
49
- export type ChatTitleSource = 'fallback' | 'generated' | 'manual';
50
- /**
51
- * A conversation as the list UI needs it: enough to render a row without loading any messages.
52
- *
53
- * `messageCount` is computed by the adapter in the same round-trip as the list itself, so a
54
- * list of N conversations costs one query rather than N+1.
55
- */
56
- export interface ChatConversation {
57
- readonly id: string;
58
- readonly title: string | null;
59
- readonly titleSource: ChatTitleSource;
60
- readonly messageCount: number;
61
- readonly createdAt: number;
62
- readonly updatedAt: number;
63
- /** Absent means no expiry — the row is retained until explicitly deleted. */
64
- readonly expiresAt?: number;
65
- }
66
- /** Everything a host may specify when starting a conversation. */
67
- export interface CreateChatConversationInput {
68
- readonly id: string;
69
- readonly title?: string | null;
70
- readonly titleSource?: ChatTitleSource;
71
- /** Epoch ms. Omit for history that never expires. */
72
- readonly expiresAt?: number;
73
- }
74
- /**
75
- * Durable chat history for exactly one {@link ChatOwnerScope}.
76
- *
77
- * Every method is already scoped. `get(id)` for an id belonging to another owner resolves to
78
- * `null` rather than throwing — a caller must not be able to distinguish "does not exist" from
79
- * "exists but is not yours", since that distinction is itself an enumeration oracle.
80
- */
81
- export interface ChatHistoryStore {
82
- /** Most-recently-updated first. */
83
- list(): Promise<ChatConversation[]>;
84
- /** `null` when the id does not exist *or* is not owned by this scope. */
85
- get(id: string): Promise<ChatConversation | null>;
86
- create(input: CreateChatConversationInput): Promise<ChatConversation>;
87
- /**
88
- * Sets the title and stamps `titleSource`. A `manual` title is never overwritten by a
89
- * `generated` one — that rule lives in the adapter so no host can forget it, and it is the
90
- * reason `titleSource` is a column rather than a UI concern.
91
- */
92
- rename(id: string, title: string, source?: ChatTitleSource): Promise<ChatConversation | null>;
93
- /** Bumps `updatedAt`, optionally extending expiry for a still-active guest session. */
94
- touch(id: string, options?: {
95
- readonly expiresAt?: number;
96
- }): Promise<void>;
97
- /** Cascades to the conversation's messages. No-op when the id is not owned by this scope. */
98
- delete(id: string): Promise<void>;
99
- /** Ordered by position. Empty when the id is not owned by this scope. */
100
- messages(conversationId: string): Promise<ChatMessage[]>;
101
- /**
102
- * Inserts or updates one message, assigning the next position on insert.
103
- *
104
- * `null` when the conversation is not owned by this scope — so a mis-scoped write is a
105
- * no-op rather than a cross-owner insert.
106
- */
107
- appendMessage(conversationId: string, message: ChatMessage): Promise<ChatMessage | null>;
108
- }
109
- /**
110
- * Retention. Deliberately NOT on {@link ChatHistoryStore}: a sweep runs across every owner, so
111
- * putting it on a per-owner store would require an unscoped store to exist, which is the exact
112
- * escape hatch this design removes.
113
- */
114
- export interface ChatHistoryMaintenance {
115
- /**
116
- * Hard-deletes conversations whose `expiresAt` is at or before `now`, cascading to messages.
117
- *
118
- * `limit` bounds one call so a large backlog is drained in chunks rather than held in a
119
- * single long write transaction — an unbounded `DELETE` is what starves concurrent inserts.
120
- * Returns the number of conversations deleted, so a caller can loop until it reports 0.
121
- */
122
- sweepExpired(now: number, limit?: number): Promise<number>;
123
- }
1
+ /** Compatibility types; the neutral store entry is the single source of truth. */
2
+ export type * from '@jini-ai/chat/store';
124
3
  //# sourceMappingURL=ports.d.ts.map
@@ -31,5 +31,7 @@
31
31
  *
32
32
  * @complexity O(n) in prompt length; no allocation beyond the intermediate strings.
33
33
  */
34
- export declare function deriveConversationTitle(prompt: string): string;
34
+ export declare function deriveConversationTitle({ prompt }: {
35
+ prompt: string;
36
+ }): string;
35
37
  //# sourceMappingURL=title.d.ts.map
@@ -122,7 +122,7 @@ function trimCjkTitle(input) {
122
122
  *
123
123
  * @complexity O(n) in prompt length; no allocation beyond the intermediate strings.
124
124
  */
125
- export function deriveConversationTitle(prompt) {
125
+ export function deriveConversationTitle({ prompt }) {
126
126
  const cleaned = cleanPrompt(prompt);
127
127
  if (!cleaned)
128
128
  return '';
@@ -15,13 +15,22 @@ import type { FormOption, QuestionForm } from './types.js';
15
15
  *
16
16
  * @complexity O(q) in the number of questions on the form.
17
17
  */
18
- export declare function formatFormAnswers(form: QuestionForm, answers: Record<string, string | string[]>): string;
18
+ export declare function formatFormAnswers({ form, answers }: {
19
+ form: QuestionForm;
20
+ answers: Record<string, string | string[]>;
21
+ }): string;
19
22
  /** Resolve a submitted answer value back to its display label, or the value itself when unmatched. */
20
- export declare function formOptionLabelForValue(question: {
21
- options?: FormOption[] | undefined;
22
- }, value: string): string;
23
+ export declare function formOptionLabelForValue({ question, value }: {
24
+ question: {
25
+ options?: FormOption[] | undefined;
26
+ };
27
+ value: string;
28
+ }): string;
23
29
  /** Resolve a label or value to the canonical option `value`, or the input itself when unmatched. */
24
- export declare function formOptionValueForLabel(question: {
25
- options?: FormOption[] | undefined;
26
- }, labelOrValue: string): string;
30
+ export declare function formOptionValueForLabel({ question, labelOrValue }: {
31
+ question: {
32
+ options?: FormOption[] | undefined;
33
+ };
34
+ labelOrValue: string;
35
+ }): string;
27
36
  //# sourceMappingURL=format.d.ts.map
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @complexity O(q) in the number of questions on the form.
7
7
  */
8
- export function formatFormAnswers(form, answers) {
8
+ export function formatFormAnswers({ form, answers }) {
9
9
  const lines = [];
10
10
  lines.push(`[form answers — ${form.id}]`);
11
11
  for (const q of form.questions) {
@@ -33,12 +33,12 @@ function formOptionDisplayForValue(question, value) {
33
33
  return `${match.label} [value: ${match.value}]`;
34
34
  }
35
35
  /** Resolve a submitted answer value back to its display label, or the value itself when unmatched. */
36
- export function formOptionLabelForValue(question, value) {
36
+ export function formOptionLabelForValue({ question, value }) {
37
37
  const match = question.options?.find((option) => option.value === value || option.label === value);
38
38
  return match?.label ?? value;
39
39
  }
40
40
  /** Resolve a label or value to the canonical option `value`, or the input itself when unmatched. */
41
- export function formOptionValueForLabel(question, labelOrValue) {
41
+ export function formOptionValueForLabel({ question, labelOrValue }) {
42
42
  const match = question.options?.find((option) => option.value === labelOrValue || option.label === labelOrValue);
43
43
  return match?.value ?? labelOrValue;
44
44
  }
@@ -10,7 +10,10 @@
10
10
  * question objects.
11
11
  */
12
12
  import type { FormQuestion } from './types.js';
13
- export declare function mapRawQuestion(q: unknown, index: number): FormQuestion | null;
13
+ export declare function mapRawQuestion({ q, index }: {
14
+ q: unknown;
15
+ index: number;
16
+ }): FormQuestion | null;
14
17
  /**
15
18
  * Whether a model-supplied palette entry may be written into a CSS value.
16
19
  *
@@ -26,5 +29,7 @@ export declare function mapRawQuestion(q: unknown, index: number): FormQuestion
26
29
  * rather than substituted: a silently recoloured swatch would misrepresent the agent's proposal,
27
30
  * and a card with fewer swatches is honest about what survived validation.
28
31
  */
29
- export declare function isRenderableColor(value: string): boolean;
32
+ export declare function isRenderableColor({ value }: {
33
+ value: string;
34
+ }): boolean;
30
35
  //# sourceMappingURL=normalize.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { formOptionValueForLabel } from './format.js';
2
- export function mapRawQuestion(q, index) {
2
+ export function mapRawQuestion({ q, index }) {
3
3
  if (!q || typeof q !== 'object')
4
4
  return null;
5
5
  const qo = q;
@@ -128,9 +128,9 @@ function parseDefaultValue(question, options) {
128
128
  ? String(question.default)
129
129
  : undefined;
130
130
  if (typeof raw === 'string')
131
- return formOptionValueForLabel({ options }, raw);
131
+ return formOptionValueForLabel({ question: { options }, labelOrValue: raw });
132
132
  if (Array.isArray(raw)) {
133
- return raw.filter((value) => typeof value === 'string').map((value) => formOptionValueForLabel({ options }, value));
133
+ return raw.filter((value) => typeof value === 'string').map((value) => formOptionValueForLabel({ question: { options }, labelOrValue: value }));
134
134
  }
135
135
  return undefined;
136
136
  }
@@ -158,7 +158,7 @@ const COLOR_FUNCTION = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(\s
158
158
  * rather than substituted: a silently recoloured swatch would misrepresent the agent's proposal,
159
159
  * and a card with fewer swatches is honest about what survived validation.
160
160
  */
161
- export function isRenderableColor(value) {
161
+ export function isRenderableColor({ value }) {
162
162
  const candidate = value.trim();
163
163
  // Bounded before any regex runs: these come from model output, and the function pattern's `\s*`
164
164
  // plus a character class is the shape that makes a pathological input worth not accepting at all.
@@ -181,7 +181,7 @@ function parseDirectionCards(raw) {
181
181
  const mood = typeof e.mood === 'string' ? e.mood : '';
182
182
  const references = Array.isArray(e.references) ? e.references.filter((r) => typeof r === 'string').slice(0, 6) : [];
183
183
  const palette = Array.isArray(e.palette)
184
- ? e.palette.filter((p) => typeof p === 'string' && isRenderableColor(p)).slice(0, 8)
184
+ ? e.palette.filter((p) => typeof p === 'string' && isRenderableColor({ value: p })).slice(0, 8)
185
185
  : [];
186
186
  const displayFont = typeof e.displayFont === 'string' ? e.displayFont : 'Georgia, serif';
187
187
  const bodyFont = typeof e.bodyFont === 'string' ? e.bodyFont : '-apple-system, system-ui, sans-serif';
@@ -8,9 +8,13 @@ import type { FormSegment, QuestionForm } from './types.js';
8
8
  * @complexity O(n) amortized in `input.length` — each iteration advances the
9
9
  * cursor past the segment it just emitted.
10
10
  */
11
- export declare function splitOnQuestionForms(input: string): FormSegment[];
11
+ export declare function splitOnQuestionForms({ input }: {
12
+ input: string;
13
+ }): FormSegment[];
12
14
  /** The first complete, parseable `<question-form>` in `input`, or `null`. */
13
- export declare function findFirstQuestionForm(input: string): {
15
+ export declare function findFirstQuestionForm({ input }: {
16
+ input: string;
17
+ }): {
14
18
  form: QuestionForm;
15
19
  raw: string;
16
20
  } | null;
@@ -21,19 +25,25 @@ export declare function findFirstQuestionForm(input: string): {
21
25
  * question-form block. For a still-streaming block use
22
26
  * {@link parsePartialQuestionForm} instead.
23
27
  */
24
- export declare function parseQuestionForm(input: string): QuestionForm | null;
28
+ export declare function parseQuestionForm({ input }: {
29
+ input: string;
30
+ }): QuestionForm | null;
25
31
  /**
26
32
  * Drop a trailing, not-yet-closed question-form block from streaming text so
27
33
  * a chat surface doesn't flash raw `<question-form>{…` markup before the
28
34
  * JSON finishes. Returns the visible text plus whether such an open block
29
35
  * existed (meaning a form is mid-generation).
30
36
  */
31
- export declare function stripTrailingOpenQuestionForm(input: string): {
37
+ export declare function stripTrailingOpenQuestionForm({ input }: {
38
+ input: string;
39
+ }): {
32
40
  text: string;
33
41
  hadOpenForm: boolean;
34
42
  };
35
43
  /** `true` when a question-form open tag is present but its close tag hasn't streamed in yet. */
36
- export declare function hasUnterminatedQuestionForm(input: string): boolean;
44
+ export declare function hasUnterminatedQuestionForm({ input }: {
45
+ input: string;
46
+ }): boolean;
37
47
  /**
38
48
  * Tolerant parser for a still-streaming `<question-form>` block. Unlike
39
49
  * {@link tryParseForm} it does not require valid, complete JSON: it reads the
@@ -46,5 +56,7 @@ export declare function hasUnterminatedQuestionForm(input: string): boolean;
46
56
  * @complexity O(n) in `input.length` (one regex scan plus a linear
47
57
  * string-aware brace walk via {@link parsePartialJson}).
48
58
  */
49
- export declare function parsePartialQuestionForm(input: string): QuestionForm | null;
59
+ export declare function parsePartialQuestionForm({ input }: {
60
+ input: string;
61
+ }): QuestionForm | null;
50
62
  //# sourceMappingURL=scan.d.ts.map
@@ -33,7 +33,7 @@ function matchedTagAndAttrs(m) {
33
33
  * @complexity O(n) amortized in `input.length` — each iteration advances the
34
34
  * cursor past the segment it just emitted.
35
35
  */
36
- export function splitOnQuestionForms(input) {
36
+ export function splitOnQuestionForms({ input }) {
37
37
  const out = [];
38
38
  let cursor = 0;
39
39
  while (cursor < input.length) {
@@ -57,7 +57,7 @@ export function splitOnQuestionForms(input) {
57
57
  out.push({ kind: 'text', text: input.slice(cursor, openStart) });
58
58
  }
59
59
  const body = input.slice(openEnd, closeIdx);
60
- const attrs = parseQuotedAttrs(rawAttrs);
60
+ const attrs = parseQuotedAttrs({ raw: rawAttrs });
61
61
  const form = tryParseForm(body, attrs);
62
62
  const blockEnd = closeIdx + closeTag.length;
63
63
  if (form) {
@@ -72,8 +72,8 @@ export function splitOnQuestionForms(input) {
72
72
  return out;
73
73
  }
74
74
  /** The first complete, parseable `<question-form>` in `input`, or `null`. */
75
- export function findFirstQuestionForm(input) {
76
- for (const seg of splitOnQuestionForms(input)) {
75
+ export function findFirstQuestionForm({ input }) {
76
+ for (const seg of splitOnQuestionForms({ input: input })) {
77
77
  if (seg.kind === 'form')
78
78
  return { form: seg.form, raw: seg.raw };
79
79
  }
@@ -86,8 +86,8 @@ export function findFirstQuestionForm(input) {
86
86
  * question-form block. For a still-streaming block use
87
87
  * {@link parsePartialQuestionForm} instead.
88
88
  */
89
- export function parseQuestionForm(input) {
90
- return findFirstQuestionForm(input)?.form ?? null;
89
+ export function parseQuestionForm({ input }) {
90
+ return findFirstQuestionForm({ input: input })?.form ?? null;
91
91
  }
92
92
  /**
93
93
  * Drop a trailing, not-yet-closed question-form block from streaming text so
@@ -95,7 +95,7 @@ export function parseQuestionForm(input) {
95
95
  * JSON finishes. Returns the visible text plus whether such an open block
96
96
  * existed (meaning a form is mid-generation).
97
97
  */
98
- export function stripTrailingOpenQuestionForm(input) {
98
+ export function stripTrailingOpenQuestionForm({ input }) {
99
99
  let cursor = 0;
100
100
  while (cursor < input.length) {
101
101
  const slice = input.slice(cursor);
@@ -115,8 +115,8 @@ export function stripTrailingOpenQuestionForm(input) {
115
115
  return { text: input, hadOpenForm: false };
116
116
  }
117
117
  /** `true` when a question-form open tag is present but its close tag hasn't streamed in yet. */
118
- export function hasUnterminatedQuestionForm(input) {
119
- return stripTrailingOpenQuestionForm(input).hadOpenForm;
118
+ export function hasUnterminatedQuestionForm({ input }) {
119
+ return stripTrailingOpenQuestionForm({ input: input }).hadOpenForm;
120
120
  }
121
121
  function findCloseTag(input, from, closeTag) {
122
122
  const closeLower = closeTag.toLowerCase();
@@ -154,7 +154,7 @@ function tryParseForm(body, attrs) {
154
154
  return null;
155
155
  const questions = [];
156
156
  rawQuestions.forEach((q, i) => {
157
- const mapped = mapRawQuestion(q, i);
157
+ const mapped = mapRawQuestion({ q: q, index: i });
158
158
  if (mapped)
159
159
  questions.push(mapped);
160
160
  });
@@ -184,14 +184,14 @@ function tryParseForm(body, attrs) {
184
184
  * @complexity O(n) in `input.length` (one regex scan plus a linear
185
185
  * string-aware brace walk via {@link parsePartialJson}).
186
186
  */
187
- export function parsePartialQuestionForm(input) {
187
+ export function parsePartialQuestionForm({ input }) {
188
188
  const m = OPEN_RE.exec(input);
189
189
  if (!m)
190
190
  return null;
191
191
  const { tagName, rawAttrs } = matchedTagAndAttrs(m);
192
192
  const closeTag = `</${tagName}>`;
193
193
  const openEnd = m.index + m[0].length;
194
- const attrs = parseQuotedAttrs(rawAttrs);
194
+ const attrs = parseQuotedAttrs({ raw: rawAttrs });
195
195
  const closeIdx = findCloseTag(input, openEnd, closeTag);
196
196
  const rawBody = closeIdx === -1 ? input.slice(openEnd) : input.slice(openEnd, closeIdx);
197
197
  // Strip the fenced ```json wrapper some models emit. The opening fence is
@@ -203,7 +203,7 @@ export function parsePartialQuestionForm(input) {
203
203
  // Derive form-level metadata from the *parsed top-level object*, not a
204
204
  // whole-body regex scan: a nested question/option `id`/`title`/`description`
205
205
  // must not masquerade as the form's own.
206
- const parsed = parsePartialJson(body);
206
+ const parsed = parsePartialJson({ buf: body });
207
207
  const top = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
208
208
  const topTitle = typeof top.title === 'string' && top.title.trim().length > 0 ? top.title : undefined;
209
209
  // `id` keys a still-editable panel, so it must be stable for the whole
@@ -343,7 +343,7 @@ function shapeStreamingQuestions(rawQuestions, closedCount) {
343
343
  const hasId = typeof q.id === 'string' && q.id.trim().length > 0;
344
344
  if (!isClosed && !hasId)
345
345
  return;
346
- const mapped = mapRawQuestion(raw, index);
346
+ const mapped = mapRawQuestion({ q: raw, index: index });
347
347
  if (mapped)
348
348
  out.push(mapped);
349
349
  });
@@ -48,7 +48,11 @@ export interface RunActivityState {
48
48
  /** After this long with nothing new on screen, a thinking/writing line says "Still working…". */
49
49
  export declare const RUN_ACTIVITY_IDLE_MS = 20000;
50
50
  /** `"page.fill"` → `"Page Fill"`, `"mcp__supabase__list_projects"` → `"List Projects"`. Display only. */
51
- export declare function humanizeToolName(name: string, input?: unknown): string;
51
+ export declare function humanizeToolName({ name }: {
52
+ name: string;
53
+ }, { input }?: {
54
+ input?: (unknown) | undefined;
55
+ }): string;
52
56
  /**
53
57
  * The latest signal in a running turn's events.
54
58
  *
@@ -58,9 +62,13 @@ export declare function humanizeToolName(name: string, input?: unknown): string;
58
62
  *
59
63
  * @complexity O(n) in events, O(open tool calls) extra space.
60
64
  */
61
- export declare function deriveRunActivity(events: readonly AgentEvent[] | undefined): RunActivityState;
65
+ export declare function deriveRunActivity({ events }: {
66
+ events: readonly AgentEvent[] | undefined;
67
+ }): RunActivityState;
62
68
  /** `4` → `"4 s"`, `80` → `"1 m 20 s"`, `120` → `"2 m 0 s"`. */
63
- export declare function formatActivityClock(totalSeconds: number): string;
69
+ export declare function formatActivityClock({ totalSeconds }: {
70
+ totalSeconds: number;
71
+ }): string;
64
72
  /** A translator: the chat package's `t(key, vars)`. */
65
73
  export type RunActivityTranslate = (key: string, vars?: Record<string, string | number>) => string;
66
74
  /** The clock values a label may use. */
@@ -84,5 +92,9 @@ export declare const RUN_ACTIVITY_LABELS: {
84
92
  *
85
93
  * @complexity O(1).
86
94
  */
87
- export declare function describeRunActivity(activity: RunActivity, clock: RunActivityClock, t: RunActivityTranslate): string;
95
+ export declare function describeRunActivity({ activity, clock, t }: {
96
+ activity: RunActivity;
97
+ clock: RunActivityClock;
98
+ t: RunActivityTranslate;
99
+ }): string;
88
100
  //# sourceMappingURL=run-activity.d.ts.map
@@ -24,7 +24,7 @@ function stringField(data, key) {
24
24
  return typeof value === 'string' && value ? value : undefined;
25
25
  }
26
26
  /** `"page.fill"` → `"Page Fill"`, `"mcp__supabase__list_projects"` → `"List Projects"`. Display only. */
27
- export function humanizeToolName(name, input) {
27
+ export function humanizeToolName({ name }, { input } = {}) {
28
28
  const real = WRAPPER_TOOL_NAMES.has(name) ? (stringField(input, 'toolId') ?? name) : name;
29
29
  const bare = real.startsWith('mcp__') ? real.split('__').slice(2).join('__') || real : real;
30
30
  return bare
@@ -69,7 +69,7 @@ function scanEvent(scan, event, index) {
69
69
  *
70
70
  * @complexity O(n) in events, O(open tool calls) extra space.
71
71
  */
72
- export function deriveRunActivity(events) {
72
+ export function deriveRunActivity({ events }) {
73
73
  const scan = { open: [], lastVisible: null, lastCardIndex: -1, retry: null, progress: null, visibleCount: 0 };
74
74
  (events ?? []).forEach((event, index) => scanEvent(scan, event, index));
75
75
  const visibleCount = scan.visibleCount;
@@ -102,7 +102,7 @@ export function deriveRunActivity(events) {
102
102
  return {
103
103
  activity: {
104
104
  kind: 'running-tool',
105
- tool: humanizeToolName(newestOpen.name, newestOpen.input),
105
+ tool: humanizeToolName({ name: newestOpen.name }, { input: newestOpen.input }),
106
106
  ...(reported !== undefined ? { reportedSeconds: reported } : {}),
107
107
  },
108
108
  key: `tool:${newestOpen.id}`,
@@ -115,7 +115,7 @@ export function deriveRunActivity(events) {
115
115
  return { activity: { kind: 'thinking' }, key: `thinking:${lastVisibleIndex}`, visibleCount };
116
116
  }
117
117
  /** `4` → `"4 s"`, `80` → `"1 m 20 s"`, `120` → `"2 m 0 s"`. */
118
- export function formatActivityClock(totalSeconds) {
118
+ export function formatActivityClock({ totalSeconds }) {
119
119
  const seconds = Math.max(0, Math.floor(totalSeconds));
120
120
  const minutes = Math.floor(seconds / 60);
121
121
  return minutes > 0 ? `${minutes} m ${seconds % 60} s` : `${seconds} s`;
@@ -126,14 +126,14 @@ export function formatActivityClock(totalSeconds) {
126
126
  */
127
127
  export const RUN_ACTIVITY_LABELS = {
128
128
  thinking: (_a, clock, t) => clock.idleMs >= RUN_ACTIVITY_IDLE_MS
129
- ? t('Still working… {time}', { time: formatActivityClock(clock.seconds) })
130
- : t('Thinking… {time}', { time: formatActivityClock(clock.seconds) }),
129
+ ? t('Still working… {time}', { time: formatActivityClock({ totalSeconds: clock.seconds }) })
130
+ : t('Thinking… {time}', { time: formatActivityClock({ totalSeconds: clock.seconds }) }),
131
131
  writing: (_a, clock, t) => clock.idleMs >= RUN_ACTIVITY_IDLE_MS
132
- ? t('Still working… {time}', { time: formatActivityClock(clock.seconds) })
132
+ ? t('Still working… {time}', { time: formatActivityClock({ totalSeconds: clock.seconds }) })
133
133
  : t('Writing…'),
134
- 'finding-tool': (_a, clock, t) => t('Finding the right tool… {time}', { time: formatActivityClock(clock.seconds) }),
135
- 'running-tool': (a, clock, t) => t('Running {tool}… {time}', { tool: a.tool, time: formatActivityClock(clock.seconds) }),
136
- 'awaiting-answer': (_a, clock, t) => t('Waiting for your answer above · {time}', { time: formatActivityClock(clock.seconds) }),
134
+ 'finding-tool': (_a, clock, t) => t('Finding the right tool… {time}', { time: formatActivityClock({ totalSeconds: clock.seconds }) }),
135
+ 'running-tool': (a, clock, t) => t('Running {tool}… {time}', { tool: a.tool, time: formatActivityClock({ totalSeconds: clock.seconds }) }),
136
+ 'awaiting-answer': (_a, clock, t) => t('Waiting for your answer above · {time}', { time: formatActivityClock({ totalSeconds: clock.seconds }) }),
137
137
  retrying: (a, _clock, t) => {
138
138
  const who = a.service ? t("{service}'s servers are busy", { service: a.service }) : t('The AI service is busy');
139
139
  return a.attempt !== undefined && a.maxAttempts !== undefined
@@ -146,7 +146,7 @@ export const RUN_ACTIVITY_LABELS = {
146
146
  *
147
147
  * @complexity O(1).
148
148
  */
149
- export function describeRunActivity(activity, clock, t) {
149
+ export function describeRunActivity({ activity, clock, t }) {
150
150
  const label = RUN_ACTIVITY_LABELS[activity.kind];
151
151
  return label(activity, clock, t);
152
152
  }