@jini-ai/chat 0.6.2 → 0.6.3

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.6.3 — 2026-10-14
2
+
3
+ - A failed MCP bridge ends the run once with its own message (e350056c): durable recovery no longer retries `MCP_BRIDGE_UNAVAILABLE` runs until the budget runs out and then shows the generic "could not continue" notice; the run is finalized with the bridge failure's exact text.
4
+ - The composer accepts file drops while a run streams (7c3d57a7), matching the attach button and paste; the drop gate (`isChatPaneFileDropEnabled` in `rules.ts`) blocks only on a missing uploader, a disabled/agent-less pane, or an upload in progress.
5
+ - Rides along: a regression test for the lone composer send button (3516cc04).
6
+
1
7
  ## 0.6.2 — 2026-10-13
2
8
 
3
9
  - A lone composer send button stays at the footer's end (b1d7a7b7): a host with no attach or accessory controls (a public visitor chat) left the send group as the footer's only child, so `space-between` put it at the bottom-left; `margin-left: auto` on a first-child send group keeps it on the right, as in the admin composer.
@@ -7,7 +7,7 @@ import { appendComposerDiscovery } from '../../../components/composer-discovery.
7
7
  import { MessageList } from '../../../components/MessageList.js';
8
8
  import { useT } from '../../../hooks/context.js';
9
9
  import { definedProps } from '../../../util/defined-props.js';
10
- import { describeTypedAnswerNotice, isChatPaneApiModeConfigured } from '../rules.js';
10
+ import { describeTypedAnswerNotice, isChatPaneApiModeConfigured, isChatPaneFileDropEnabled } from '../rules.js';
11
11
  import { useChatPane } from '../hooks/useChatPane.hooks.js';
12
12
  import { useChatPaneAgentControl } from '../hooks/useChatPaneAgentControl.hooks.js';
13
13
  import { useChatPaneComposerPlaceholder } from '../hooks/useChatPaneComposerPlaceholder.hooks.js';
@@ -258,11 +258,12 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
258
258
  // the provider directly over HTTP and never touches the CLI inventory.
259
259
  const unavailable = pane.selectedAgent === undefined && !apiModeConfigured;
260
260
  const fileDrop = useChatPaneFileDrop({
261
- enabled: surface.uploadAttachments !== undefined
262
- && !disabled
263
- && !unavailable
264
- && !pane.conversation.isStreaming
265
- && !pane.isUploadingAttachments,
261
+ enabled: isChatPaneFileDropEnabled({
262
+ canUpload: surface.uploadAttachments !== undefined,
263
+ disabled,
264
+ unavailable,
265
+ uploading: pane.isUploadingAttachments,
266
+ }),
266
267
  onFiles: pane.addAttachments,
267
268
  });
268
269
  // `composerSlots`'s type excludes both `leadingAccessories` and `footerAccessories` (see
@@ -48,6 +48,20 @@ export declare function isChatPaneApiModeConfigured({ executionMode, apiModeAvai
48
48
  readonly apiModeAvailable: boolean;
49
49
  readonly byokRuntime: ByokRuntimeSummary | undefined;
50
50
  }): boolean;
51
+ /**
52
+ * Whether the composer's drop target accepts dropped files right now. A running run is NOT a
53
+ * reason to refuse: files staged mid-run sit in the composer and go out with the next message
54
+ * (`send()` queues that turn with its attachments; the queue flush waits for uploads too), the same
55
+ * as the attach button and paste, which were never gated on the run. Only a missing uploader, a
56
+ * disabled or agent-less pane, or an upload already in progress (the attach button's own gate)
57
+ * blocks a drop.
58
+ */
59
+ export declare function isChatPaneFileDropEnabled({ canUpload, disabled, unavailable, uploading, }: {
60
+ readonly canUpload: boolean;
61
+ readonly disabled: boolean;
62
+ readonly unavailable: boolean;
63
+ readonly uploading: boolean;
64
+ }): boolean;
51
65
  /**
52
66
  * The single source of truth for "may this pane send right now?", shared by the composer-driven
53
67
  * `send()` and every agent-driven caller so the two can never enforce different rules.
@@ -54,6 +54,17 @@ export function resolveChatPaneSelection({ agents, requested }) {
54
54
  export function isChatPaneApiModeConfigured({ executionMode, apiModeAvailable, byokRuntime, }) {
55
55
  return executionMode === 'api' && apiModeAvailable && Boolean(byokRuntime?.model?.trim());
56
56
  }
57
+ /**
58
+ * Whether the composer's drop target accepts dropped files right now. A running run is NOT a
59
+ * reason to refuse: files staged mid-run sit in the composer and go out with the next message
60
+ * (`send()` queues that turn with its attachments; the queue flush waits for uploads too), the same
61
+ * as the attach button and paste, which were never gated on the run. Only a missing uploader, a
62
+ * disabled or agent-less pane, or an upload already in progress (the attach button's own gate)
63
+ * blocks a drop.
64
+ */
65
+ export function isChatPaneFileDropEnabled({ canUpload, disabled, unavailable, uploading, }) {
66
+ return canUpload && !disabled && !unavailable && !uploading;
67
+ }
57
68
  /**
58
69
  * The single source of truth for "may this pane send right now?", shared by the composer-driven
59
70
  * `send()` and every agent-driven caller so the two can never enforce different rules.
@@ -3,6 +3,9 @@ import { classifyAgentServiceFailure } from "@jini-ai/agent-runtime";
3
3
  import { continuationRequest } from "../../core/durable-runs/continuation.js";
4
4
  import { RECOVERY_LIMIT, RECOVERY_WINDOW_MS, RUN_STALENESS_MS, SAVED_WORK_NOTICE, EXHAUSTED_NOTICE } from "../../core/durable-runs/policies.js";
5
5
  export { RECOVERY_LIMIT, RECOVERY_WINDOW_MS, RUN_STALENESS_MS, SAVED_WORK_NOTICE, EXHAUSTED_NOTICE } from "../../core/durable-runs/policies.js";
6
+ // translateRunFrame saves a run error frame as a status label holding only its message: the
7
+ // daemon's MCP_BRIDGE_UNAVAILABLE code is dropped, so the bridge failure is matched by its text.
8
+ const MCP_BRIDGE_FAILURE = /The assistant's tools did not load \(MCP bridge "jini" status: [^\r\n]*/;
6
9
  /** Retryability is independent of session resumability. Inspect executor diagnostics, never
7
10
  * answer text or tool results: those can quote unrelated failures from completed work. Old
8
11
  * attempt diagnostics remain visible but cannot decide the fate of their continuation. */
@@ -23,6 +26,10 @@ function attemptDiagnostics({ run }, _optional = {}) {
23
26
  }
24
27
  function attemptFailure(run) {
25
28
  const diagnostics = attemptDiagnostics({ run }, {});
29
+ // MCP_BRIDGE_UNAVAILABLE needs bridge repair; another attempt repeats the same failure.
30
+ // Classify it before interpreting the bridge's stderr as an agent auth/watchdog failure.
31
+ if (MCP_BRIDGE_FAILURE.test(diagnostics))
32
+ return "permanent";
26
33
  if (/inactivity[_ -]?(timeout|watchdog)|watchdog.*(kill|stop)/i.test(diagnostics))
27
34
  return "inactivity-watchdog";
28
35
  const service = classifyAgentServiceFailure({ text: diagnostics });
@@ -118,8 +125,9 @@ export function createDurableRecovery(ports, _optional = {}) {
118
125
  const failure = attemptFailure(run);
119
126
  if (failure === "auth-failure" || failure === "inactivity-watchdog")
120
127
  return finalize(run, failure === "auth-failure" ? "Not logged in. Saved work is above." : "Stopped. Saved work is above.", true);
128
+ // A bridge failure shows its own reason (and stderr): the generic notice hides what needs repair.
121
129
  if (failure === "permanent")
122
- return finalize(run, EXHAUSTED_NOTICE);
130
+ return finalize(run, attemptDiagnostics({ run }, {}).match(MCP_BRIDGE_FAILURE)?.[0] ?? EXHAUSTED_NOTICE);
123
131
  return resolveMissing(run, probe, trigger);
124
132
  }
125
133
  async function resolveMissing(run, probe, trigger) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/chat",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Jini's chat/artifact frontend, consolidated into one package with two subpaths: framework-free chat vocabulary and pure parsers in `/core`, headless hooks + presentational components + slots in `/react`. Import only the layer you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -196,7 +196,7 @@
196
196
  "registry": "https://registry.npmjs.org"
197
197
  },
198
198
  "dependencies": {
199
- "@jini-ai/core": "^0.4.5"
199
+ "@jini-ai/core": "^0.4.6"
200
200
  },
201
201
  "peerDependencies": {
202
202
  "react": "^18.3.0 || ^19.0.0",
@@ -302,13 +302,13 @@
302
302
  "@electric-sql/pglite": "0.5.8",
303
303
  "pg": "8.23.0",
304
304
  "@ag-ui/core": "0.0.58",
305
+ "@jini-ai/agent-runtime": "0.4.11",
306
+ "@jini-ai/ui": "^0.5.3",
305
307
  "@jini-ai/agentic": "^0.4.4",
308
+ "@jini-ai/protocol": "^0.4.4",
306
309
  "@jini-ai/db": "0.3.4",
307
- "@jini-ai/agent-runtime": "0.4.9",
308
- "@jini-ai/core": "^0.4.5",
309
310
  "@jini-ai/diagnostics": "^0.5.2",
310
- "@jini-ai/protocol": "^0.4.4",
311
- "@jini-ai/ui": "^0.5.2"
311
+ "@jini-ai/core": "^0.4.6"
312
312
  },
313
313
  "typesVersions": {
314
314
  "*": {