@jini-ai/chat 0.6.6 → 0.6.7

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 (156) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/accepted-input.d.ts +54 -0
  3. package/dist/core/accepted-input.js +165 -0
  4. package/dist/core/agent-plugin-chat-state.d.ts +43 -0
  5. package/dist/core/agent-plugin-chat-state.js +48 -0
  6. package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
  7. package/dist/core/assistant-client/messages.d.ts +1 -1
  8. package/dist/core/assistant-client/messages.js +2 -2
  9. package/dist/core/assistant-client/transport.d.ts +2 -2
  10. package/dist/core/assistant-client/transport.js +184 -97
  11. package/dist/core/assistant-content.d.ts +2 -1
  12. package/dist/core/assistant-content.js +9 -11
  13. package/dist/core/browser/page-actions.js +4 -2
  14. package/dist/core/composer-history.d.ts +16 -9
  15. package/dist/core/composer-history.js +28 -16
  16. package/dist/core/conversation-title.d.ts +3 -0
  17. package/dist/core/conversation-title.js +3 -0
  18. package/dist/core/durable-projection.js +20 -13
  19. package/dist/core/durable-runs/continuation.d.ts +4 -0
  20. package/dist/core/durable-runs/continuation.js +31 -6
  21. package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
  22. package/dist/core/durable-runs/ports.d.ts +6 -2
  23. package/dist/core/index.d.ts +2 -0
  24. package/dist/core/index.js +2 -0
  25. package/dist/core/messages.d.ts +2 -0
  26. package/dist/core/partial-json.d.ts +6 -7
  27. package/dist/core/partial-json.js +81 -47
  28. package/dist/core/persistence/title.js +4 -2
  29. package/dist/core/question-answer.d.ts +23 -0
  30. package/dist/core/question-answer.js +66 -0
  31. package/dist/core/question-form/format.d.ts +6 -6
  32. package/dist/core/question-form/format.js +16 -17
  33. package/dist/core/question-form/normalize.d.ts +18 -0
  34. package/dist/core/question-form/normalize.js +112 -122
  35. package/dist/core/question-form/scan.js +141 -203
  36. package/dist/core/run-activity.js +62 -49
  37. package/dist/core/run-events/entry.d.ts +10 -1
  38. package/dist/core/run-events/entry.js +117 -87
  39. package/dist/core/todos.d.ts +1 -1
  40. package/dist/core/todos.js +60 -85
  41. package/dist/core/tool-events.js +14 -12
  42. package/dist/core/transcript.js +62 -16
  43. package/dist/core/turn-usage.js +54 -32
  44. package/dist/core/user-text-redaction.js +3 -0
  45. package/dist/core/util/manifest.js +77 -82
  46. package/dist/core/util/markdown-context.js +51 -53
  47. package/dist/core/util/parser.js +136 -144
  48. package/dist/core/util/pointer.js +16 -16
  49. package/dist/core/util/strip.js +57 -67
  50. package/dist/core/util/validate.js +15 -17
  51. package/dist/embed-widget/browser/session-store.js +21 -12
  52. package/dist/embed-widget/transports/fetch-sse.js +108 -95
  53. package/dist/embed-widget/vanilla/index.js +81 -70
  54. package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
  55. package/dist/react/components/AttachmentPreviewModal.js +25 -1
  56. package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
  57. package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
  58. package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
  59. package/dist/react/components/ChatSettingsDialog.js +72 -0
  60. package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
  61. package/dist/react/components/ChatSettingsSegmented.js +11 -0
  62. package/dist/react/components/Composer.d.ts +5 -1
  63. package/dist/react/components/Composer.js +126 -82
  64. package/dist/react/components/ConversationList.js +25 -39
  65. package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
  66. package/dist/react/components/ConversationTitleEditor.js +54 -0
  67. package/dist/react/components/Icon.d.ts +1 -1
  68. package/dist/react/components/Icon.js +25 -45
  69. package/dist/react/components/Markdown.js +134 -123
  70. package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
  71. package/dist/react/components/McpUiSurfaceCard.js +30 -9
  72. package/dist/react/components/MessageList.d.ts +13 -13
  73. package/dist/react/components/MessageList.hooks.d.ts +6 -1
  74. package/dist/react/components/MessageList.hooks.js +19 -3
  75. package/dist/react/components/MessageList.js +34 -10
  76. package/dist/react/components/MessageRow.js +47 -31
  77. package/dist/react/components/MessageRow.usage.js +16 -11
  78. package/dist/react/components/MidRunUserMessage.js +16 -3
  79. package/dist/react/components/QuestionForm.js +148 -76
  80. package/dist/react/components/QuestionsPanel.js +1 -1
  81. package/dist/react/components/TodoCard.js +12 -6
  82. package/dist/react/components/ToolCard.js +47 -40
  83. package/dist/react/components/composer-discovery.js +6 -1
  84. package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
  85. package/dist/react/components/mcp-ui-surface-state.js +23 -8
  86. package/dist/react/ext-event-renderer-registry.d.ts +8 -0
  87. package/dist/react/ext-event-renderer-registry.js +5 -2
  88. package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
  89. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
  90. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
  91. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  92. package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
  93. package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
  94. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
  95. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
  96. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
  97. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
  98. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
  99. package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
  100. package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
  101. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
  102. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
  103. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
  104. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
  105. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
  106. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
  107. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
  108. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
  109. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
  110. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
  111. package/dist/react/features/chat-pane/model-options.d.ts +14 -0
  112. package/dist/react/features/chat-pane/model-options.js +45 -25
  113. package/dist/react/features/chat-pane/rules.d.ts +8 -9
  114. package/dist/react/features/chat-pane/rules.js +84 -46
  115. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  116. package/dist/react/features/chat-pane/styles.js +152 -36
  117. package/dist/react/features/chat-pane/types.d.ts +17 -8
  118. package/dist/react/hooks/assistant-chats.js +12 -5
  119. package/dist/react/hooks/composer-draft-cache.js +116 -118
  120. package/dist/react/hooks/composer-history-storage.js +5 -5
  121. package/dist/react/hooks/fab-position.js +7 -4
  122. package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
  123. package/dist/react/hooks/useAnsweredCards.js +38 -11
  124. package/dist/react/hooks/useArtifactStream.js +28 -22
  125. package/dist/react/hooks/useComposer.d.ts +20 -0
  126. package/dist/react/hooks/useComposer.js +22 -9
  127. package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
  128. package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
  129. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
  130. package/dist/react/hooks/useConversation.d.ts +7 -0
  131. package/dist/react/hooks/useConversation.js +23 -10
  132. package/dist/react/hooks/useExtEventGroups.js +39 -39
  133. package/dist/react/hooks/useQuestionForms.js +21 -26
  134. package/dist/react/hooks/useRunStream.js +10 -2
  135. package/dist/react/index.d.ts +7 -1
  136. package/dist/react/index.js +9 -0
  137. package/dist/react/message-blocks.d.ts +6 -4
  138. package/dist/react/message-blocks.js +94 -72
  139. package/dist/react/slots.d.ts +2 -2
  140. package/dist/react/tool-call-display.js +19 -17
  141. package/dist/server/durable-runs/recover.js +132 -80
  142. package/dist/server/run-finalizer/entry.js +25 -15
  143. package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
  144. package/dist/store/legacy/sqlite/conversations.js +69 -98
  145. package/dist/store/legacy/sqlite/messages.d.ts +10 -2
  146. package/dist/store/legacy/sqlite/messages.js +113 -71
  147. package/dist/store/runtime/chat-history-store.js +9 -3
  148. package/dist/store/runtime/durable-run-store.d.ts +2 -2
  149. package/dist/store/runtime/durable-run-store.js +165 -117
  150. package/dist/store/runtime/history-tools.js +118 -82
  151. package/dist/store/runtime/run-ledger.d.ts +4 -1
  152. package/dist/store/runtime/run-ledger.js +56 -16
  153. package/dist/store/sql/cursor.js +38 -18
  154. package/dist/store/sql/store.d.ts +18 -0
  155. package/dist/store/sql/store.js +46 -15
  156. package/package.json +12 -10
@@ -25,6 +25,47 @@ function errorFrom(body, status) {
25
25
  }
26
26
  return error;
27
27
  }
28
+ /**
29
+ * The deadline's abort becomes a readable timeout; every other failure (a network error, a non-2xx
30
+ * rejection) passes through unchanged.
31
+ */
32
+ function timeoutAware(error, timeoutMs) {
33
+ return error instanceof Error && error.name === 'AbortError'
34
+ ? new Error(`Timed out after ${timeoutMs}ms waiting for the server.`)
35
+ : error;
36
+ }
37
+ /** One POST of a confirmed action and its parsed answer; a non-2xx response rejects via {@link errorFrom}. */
38
+ async function postToolCall({ call, endpoint, headers, signal, fetch }) {
39
+ // `{name, arguments}` on the MCP-UI side becomes `{toolName, params}` on the wire: `arguments`
40
+ // is a reserved word in a JS function scope and reads badly in a server handler, and the
41
+ // renamed pair matches what the reference implementation's redemption route already accepts.
42
+ const request = {
43
+ toolName: call.name,
44
+ params: { ...call.arguments },
45
+ };
46
+ const response = await fetch(endpoint, {
47
+ method: 'POST',
48
+ // Same-origin cookie session is the usual authenticator for the human's second step.
49
+ credentials: 'same-origin',
50
+ headers: { ...headers, 'content-type': 'application/json' },
51
+ body: JSON.stringify(request),
52
+ signal,
53
+ });
54
+ const raw = await response.text();
55
+ let parsed = raw;
56
+ if (raw.length > 0) {
57
+ try {
58
+ parsed = JSON.parse(raw);
59
+ }
60
+ catch {
61
+ // Left as text — a proxy's HTML error page is more useful surfaced verbatim than swapped
62
+ // for a generic "invalid JSON", which would hide what actually answered.
63
+ }
64
+ }
65
+ if (!response.ok)
66
+ throw errorFrom(parsed, response.status);
67
+ return parsed;
68
+ }
28
69
  /**
29
70
  * Builds an {@link McpUiToolCallHandler} that POSTs `{toolName, params}` and returns the parsed
30
71
  * response.
@@ -45,46 +86,9 @@ export function createMcpUiToolCaller({ baseUrl, fetch }, options = {}) {
45
86
  return async (call) => {
46
87
  const controller = new AbortController();
47
88
  const timer = setTimeout(() => controller.abort(), timeoutMs);
48
- try {
49
- // `{name, arguments}` on the MCP-UI side becomes `{toolName, params}` on the wire: `arguments`
50
- // is a reserved word in a JS function scope and reads badly in a server handler, and the
51
- // renamed pair matches what the reference implementation's redemption route already accepts.
52
- const request = {
53
- toolName: call.name,
54
- params: { ...call.arguments },
55
- };
56
- const response = await fetch(endpoint, {
57
- method: 'POST',
58
- // Same-origin cookie session is the usual authenticator for the human's second step.
59
- credentials: 'same-origin',
60
- headers: { ...options.headers, 'content-type': 'application/json' },
61
- body: JSON.stringify(request),
62
- signal: controller.signal,
63
- });
64
- const raw = await response.text();
65
- let parsed = raw;
66
- if (raw.length > 0) {
67
- try {
68
- parsed = JSON.parse(raw);
69
- }
70
- catch {
71
- // Left as text — a proxy's HTML error page is more useful surfaced verbatim than swapped
72
- // for a generic "invalid JSON", which would hide what actually answered.
73
- }
74
- }
75
- if (!response.ok)
76
- throw errorFrom(parsed, response.status);
77
- return parsed;
78
- }
79
- catch (error) {
80
- if (error instanceof Error && error.name === 'AbortError') {
81
- throw new Error(`Timed out after ${timeoutMs}ms waiting for the server.`);
82
- }
83
- throw error;
84
- }
85
- finally {
86
- clearTimeout(timer);
87
- }
89
+ return postToolCall({ call, endpoint, headers: options.headers, signal: controller.signal, fetch })
90
+ .catch((error) => { throw timeoutAware(error, timeoutMs); })
91
+ .finally(() => clearTimeout(timer));
88
92
  };
89
93
  }
90
94
  //# sourceMappingURL=create-mcp-ui-tool-caller.js.map
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @module create-mid-run-message-poster
3
3
  *
4
- * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
5
- * human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
4
+ * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts the
5
+ * human's text and attachment refs to the live agent. Pass it as `ChatPane`'s
6
6
  * `deliverMidRunMessage`.
7
7
  *
8
8
  * ```tsx
@@ -10,14 +10,14 @@
10
10
  * ```
11
11
  *
12
12
  * Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
13
- * `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
13
+ * `POST /api/runs/:runId/messages`: `202` when the agent took the whole turn, `409`
14
14
  * with `details.delivery` when it did not.
15
15
  *
16
16
  * ## Why it resolves an outcome instead of throwing
17
17
  *
18
18
  * The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
19
- * run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
20
- * which is already ending (or may be — never cut short a run the host could not reach). A 404 means
19
+ * run for text-only turns, or queues an attachment turn intact; `'not-running'` queues behind the
20
+ * ending run; `'failed'` retains the draft for retry. A 404 means
21
21
  * the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
22
22
  * too: no live process to steer, but an interrupt still works.
23
23
  */
@@ -30,7 +30,7 @@ export interface CreateMidRunMessagePosterOptions extends UserTextRedactionOptio
30
30
  readonly headers?: Readonly<Record<string, string>>;
31
31
  }
32
32
  /**
33
- * Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
33
+ * Posts text and optional opaque attachment refs to `${baseUrl}/api/runs/:runId/messages`.
34
34
  *
35
35
  * @param deps.baseUrl - Origin to call; `''` for same-origin.
36
36
  * @param deps.fetch - The fetch to use (the browser's, in production).
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @module create-mid-run-message-poster
3
3
  *
4
- * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
5
- * human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
4
+ * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts the
5
+ * human's text and attachment refs to the live agent. Pass it as `ChatPane`'s
6
6
  * `deliverMidRunMessage`.
7
7
  *
8
8
  * ```tsx
@@ -10,14 +10,14 @@
10
10
  * ```
11
11
  *
12
12
  * Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
13
- * `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
13
+ * `POST /api/runs/:runId/messages`: `202` when the agent took the whole turn, `409`
14
14
  * with `details.delivery` when it did not.
15
15
  *
16
16
  * ## Why it resolves an outcome instead of throwing
17
17
  *
18
18
  * The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
19
- * run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
20
- * which is already ending (or may be — never cut short a run the host could not reach). A 404 means
19
+ * run for text-only turns, or queues an attachment turn intact; `'not-running'` queues behind the
20
+ * ending run; `'failed'` retains the draft for retry. A 404 means
21
21
  * the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
22
22
  * too: no live process to steer, but an interrupt still works.
23
23
  */
@@ -38,7 +38,7 @@ async function deliveryFor(response) {
38
38
  return response.status === 404 ? 'unsupported' : 'failed';
39
39
  }
40
40
  /**
41
- * Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
41
+ * Posts text and optional opaque attachment refs to `${baseUrl}/api/runs/:runId/messages`.
42
42
  *
43
43
  * @param deps.baseUrl - Origin to call; `''` for same-origin.
44
44
  * @param deps.fetch - The fetch to use (the browser's, in production).
@@ -48,7 +48,7 @@ async function deliveryFor(response) {
48
48
  export function createMidRunMessagePoster({ baseUrl, fetch }, options = {}) {
49
49
  const origin = baseUrl.replace(/\/$/u, '');
50
50
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
51
- return async ({ runId, text }) => {
51
+ return async ({ runId, text, attachments }) => {
52
52
  const result = (options.redactUserText ?? redactUserText)({ text });
53
53
  if (result.secretRedacted)
54
54
  options.onSecretRedacted?.({ secretRedacted: true, count: result.count });
@@ -59,7 +59,7 @@ export function createMidRunMessagePoster({ baseUrl, fetch }, options = {}) {
59
59
  // Same-origin cookie session: the human's own credentials authorize steering their run.
60
60
  credentials: 'same-origin',
61
61
  headers: { ...options.headers, 'content-type': 'application/json' },
62
- body: JSON.stringify({ text: result.text }),
62
+ body: JSON.stringify({ text: result.text, ...(attachments?.length ? { attachmentIds: attachments.map(attachment => attachment.path) } : {}) }),
63
63
  signal: controller.signal,
64
64
  }).then(deliveryFor, () => 'failed');
65
65
  clearTimeout(timer);
@@ -22,9 +22,9 @@
22
22
  *
23
23
  * ## Why it resolves an outcome instead of throwing
24
24
  *
25
- * The pane must tell three cases apart and act differently on each, never by queueing the text as a
26
- * new run: 202 delivered it; 409 means nothing is waiting any more (answered, expired, or never
27
- * open); anything else is a failure the human can retry. A rejection would collapse the last two.
25
+ * The pane tells three cases apart: 202 delivered the answer; 409 means nothing is waiting and
26
+ * permits one ordinary-message fallback; other failures retain the draft for retry. A rejection
27
+ * would collapse the last two and risk resending an answer whose delivery was uncertain.
28
28
  */
29
29
  import { type UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
30
30
  import type { DeliverTypedAnswer } from './types.js';
@@ -22,9 +22,9 @@
22
22
  *
23
23
  * ## Why it resolves an outcome instead of throwing
24
24
  *
25
- * The pane must tell three cases apart and act differently on each, never by queueing the text as a
26
- * new run: 202 delivered it; 409 means nothing is waiting any more (answered, expired, or never
27
- * open); anything else is a failure the human can retry. A rejection would collapse the last two.
25
+ * The pane tells three cases apart: 202 delivered the answer; 409 means nothing is waiting and
26
+ * permits one ordinary-message fallback; other failures retain the draft for retry. A rejection
27
+ * would collapse the last two and risk resending an answer whose delivery was uncertain.
28
28
  */
29
29
  import { redactUserText } from '../../../core/user-text-redaction.js';
30
30
  /**
@@ -38,7 +38,10 @@ export declare function runtimeOptionLabel({ options, value, fallback }: {
38
38
  fallback: string;
39
39
  }): string;
40
40
  /**
41
- * Computes a viewport-bounded fixed position for the body-portaled picker.
41
+ * Computes a viewport-bounded fixed position for the body-portaled picker, centred on the trigger
42
+ * and clamped into the viewport and (when given) into `bounds`. `bounds` (the composer box) keeps a
43
+ * popover centred on an icon near a narrow dock's left edge from spilling onto the page beside it,
44
+ * the same clamp `runtimeDropUpPosition` applies to the model/effort drop-ups.
42
45
  *
43
46
  * @complexity Time/space: O(1).
44
47
  * @overallScore 100/100
@@ -46,6 +49,8 @@ export declare function runtimeOptionLabel({ options, value, fallback }: {
46
49
  export declare function runtimePopoverPosition({ trigger, placement }: {
47
50
  trigger: DOMRect;
48
51
  placement: RuntimePickerPlacement;
52
+ }, { bounds }?: {
53
+ bounds?: Pick<DOMRect, 'left' | 'right'>;
49
54
  }): CSSProperties;
50
55
  /** `Tab` wraps focus around the popover's control list instead of leaving it (the popover is a
51
56
  * modal-ish dialog). Returns the index to wrap TO, or `null` when `Tab` should behave natively
@@ -38,16 +38,21 @@ export function runtimeOptionLabel({ options, value, fallback }) {
38
38
  return options?.find((option) => option.id === value)?.label ?? value;
39
39
  }
40
40
  /**
41
- * Computes a viewport-bounded fixed position for the body-portaled picker.
41
+ * Computes a viewport-bounded fixed position for the body-portaled picker, centred on the trigger
42
+ * and clamped into the viewport and (when given) into `bounds`. `bounds` (the composer box) keeps a
43
+ * popover centred on an icon near a narrow dock's left edge from spilling onto the page beside it,
44
+ * the same clamp `runtimeDropUpPosition` applies to the model/effort drop-ups.
42
45
  *
43
46
  * @complexity Time/space: O(1).
44
47
  * @overallScore 100/100
45
48
  */
46
- export function runtimePopoverPosition({ trigger, placement }) {
49
+ export function runtimePopoverPosition({ trigger, placement }, { bounds } = {}) {
47
50
  const margin = 12;
48
51
  const gap = 8;
49
- const width = Math.min(320, window.innerWidth - margin * 2);
50
- const left = Math.min(Math.max(trigger.left + trigger.width / 2 - width / 2, margin), window.innerWidth - width - margin);
52
+ const minLeft = Math.max(margin, bounds?.left ?? -Infinity);
53
+ const maxRight = Math.min(window.innerWidth - margin, bounds?.right ?? Infinity);
54
+ const width = Math.max(0, Math.min(320, maxRight - minLeft));
55
+ const left = Math.min(Math.max(trigger.left + trigger.width / 2 - width / 2, minLeft), maxRight - width);
51
56
  if (placement === 'up') {
52
57
  const availableHeight = Math.max(0, trigger.top - margin - gap);
53
58
  return {
@@ -205,8 +210,9 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
205
210
  return;
206
211
  const update = () => {
207
212
  const rect = triggerRef.current?.getBoundingClientRect();
213
+ const bounds = triggerRef.current?.closest('.jini-composer')?.getBoundingClientRect();
208
214
  if (rect)
209
- setPosition(runtimePopoverPosition({ trigger: rect, placement: placement }));
215
+ setPosition(runtimePopoverPosition({ trigger: rect, placement: placement }, bounds ? { bounds } : {}));
210
216
  };
211
217
  update();
212
218
  window.addEventListener('resize', update);
@@ -1,3 +1,4 @@
1
+ import { type AcceptedChatInput } from '../../../../core/accepted-input.js';
1
2
  import type { UserTextRedactionOptions } from '../../../../core/user-text-redaction.js';
2
3
  import { type ChatPaneSendBlocker } from '../rules.js';
3
4
  import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverMidRunMessage, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
@@ -20,6 +21,8 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
20
21
  initialDraft?: string;
21
22
  composerHistoryScope?: string;
22
23
  composerHistoryStorage?: ComposerHistoryStoragePort;
24
+ /** Recall retention; defaults to 100. */
25
+ composerHistoryCap?: number;
23
26
  /** Forwarded verbatim to `useComposer`; see `validateAttachments` there for the contract. */
24
27
  validateAttachments?: (attachments: readonly ChatAttachment[]) => Promise<readonly ChatAttachment[]>;
25
28
  uploadAttachments?: (files: File[], options?: ChatPaneAttachmentUploadOptions) => Promise<ChatAttachment[]>;
@@ -85,20 +88,22 @@ export interface UseChatPaneResult extends UseChatPaneWorkingDirectoryResult {
85
88
  * was silently swallowed.
86
89
  */
87
90
  queuedPrompt: string | null;
91
+ /** Individual queued messages, in send order, with their own staged files. */
92
+ queuedMessages: readonly AcceptedChatInput[];
93
+ /** Drops only the named queued message. */
94
+ cancelQueuedMessage: (input: {
95
+ id: string;
96
+ }, options?: {}) => void;
88
97
  /** Drops the queued prompt without ever sending it. */
89
98
  cancelQueued: () => void;
90
99
  /**
91
- * Why the last typed answer was NOT sent, or `null`. Set when `deliverTypedAnswer` reports the
92
- * question no longer waiting or could not be reached, and when {@link send} holds back an answer
93
- * whose question has since closed; the draft stays in the composer. Cleared by the next send and
94
- * by `reset()`.
100
+ * Delivery failure for the last typed answer, or `null`. Failures retain the draft; a definitive
101
+ * not-pending refusal queues one ordinary message instead. Cleared by the next send and `reset()`.
95
102
  */
96
103
  typedAnswerNotice: TypedAnswerNotice | null;
97
104
  /**
98
105
  * Sends the composer draft as an ordinary next turn (queued behind a running run, as {@link send}
99
- * does) even though it was typed as an answer to a question that has since closed. The one way
100
- * past {@link send}'s hold on such a draft other than clearing it: the human chose a new, possibly
101
- * paid, run on purpose.
106
+ * does), bypassing typed-answer routing.
102
107
  */
103
108
  sendAsNewMessage: () => Promise<void>;
104
109
  /**