@jini-ai/chat 0.5.6 → 0.5.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 (48) hide show
  1. package/dist/core/durable-projection.d.ts +3 -1
  2. package/dist/core/durable-projection.js +40 -7
  3. package/dist/core/index.d.ts +1 -0
  4. package/dist/core/index.js +1 -0
  5. package/dist/core/run-events/entry.d.ts +12 -1
  6. package/dist/core/run-events/entry.js +17 -6
  7. package/dist/core/tool-call-detail.d.ts +36 -0
  8. package/dist/core/tool-call-detail.js +49 -0
  9. package/dist/core/turn-usage.d.ts +11 -0
  10. package/dist/core/turn-usage.js +17 -0
  11. package/dist/react/chat-pane.d.ts +2 -2
  12. package/dist/react/chat-pane.js +1 -1
  13. package/dist/react/components/Composer.d.ts +2 -0
  14. package/dist/react/components/Composer.js +5 -2
  15. package/dist/react/components/Markdown.js +6 -6
  16. package/dist/react/components/MessageList.d.ts +7 -12
  17. package/dist/react/components/MessageList.js +20 -2
  18. package/dist/react/components/MessageRow.d.ts +6 -1
  19. package/dist/react/components/MessageRow.js +10 -4
  20. package/dist/react/components/MidRunUserMessage.d.ts +27 -0
  21. package/dist/react/components/MidRunUserMessage.js +39 -0
  22. package/dist/react/components/ToolCard.js +33 -22
  23. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  24. package/dist/react/features/chat-pane/components/ChatPane.js +11 -3
  25. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
  26. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
  27. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
  28. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +55 -6
  29. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
  30. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
  31. package/dist/react/features/chat-pane/index.d.ts +2 -1
  32. package/dist/react/features/chat-pane/index.js +1 -0
  33. package/dist/react/features/chat-pane/rules.d.ts +13 -0
  34. package/dist/react/features/chat-pane/rules.js +14 -0
  35. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  36. package/dist/react/features/chat-pane/styles.js +7 -0
  37. package/dist/react/features/chat-pane/types.d.ts +23 -0
  38. package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
  39. package/dist/react/hooks/composer-draft-cache.js +20 -0
  40. package/dist/react/hooks/useComposer.d.ts +14 -0
  41. package/dist/react/hooks/useComposer.js +78 -6
  42. package/dist/react/index.d.ts +4 -2
  43. package/dist/react/index.js +3 -1
  44. package/dist/server/durable-runs/recover.js +19 -11
  45. package/dist/server/run-finalizer/entry.js +2 -1
  46. package/dist/store/runtime/durable-run-store.js +5 -2
  47. package/dist/store/runtime/run-ledger.js +5 -2
  48. package/package.json +8 -8
@@ -6,7 +6,7 @@ export { RECOVERY_LIMIT, RECOVERY_WINDOW_MS, RUN_STALENESS_MS, SAVED_WORK_NOTICE
6
6
  /** Retryability is independent of session resumability. Inspect executor diagnostics, never
7
7
  * answer text or tool results: those can quote unrelated failures from completed work. Old
8
8
  * attempt diagnostics remain visible but cannot decide the fate of their continuation. */
9
- function attemptFailure(run) {
9
+ function attemptDiagnostics({ run }, _optional = {}) {
10
10
  const events = run.message.events ?? [];
11
11
  // Checkpoints merge adjacent text, so a base's event count is not a durable offset. The
12
12
  // coordinator's recovery marker separates attempts and survives that compaction.
@@ -19,7 +19,10 @@ function attemptFailure(run) {
19
19
  break;
20
20
  }
21
21
  }
22
- const diagnostics = events.slice(marker >= 0 ? marker + 1 : run.attemptBase.length).flatMap((event) => event.kind === "status" ? [event.label, event.detail ?? ""] : event.kind === "raw" ? [event.line] : []).join("\n");
22
+ return events.slice(marker >= 0 ? marker + 1 : run.attemptBase.length).flatMap((event) => event.kind === "status" ? [event.label, event.detail ?? ""] : event.kind === "raw" ? [event.line] : []).join("\n");
23
+ }
24
+ function attemptFailure(run) {
25
+ const diagnostics = attemptDiagnostics({ run }, {});
23
26
  if (/inactivity[_ -]?(timeout|watchdog)|watchdog.*(kill|stop)/i.test(diagnostics))
24
27
  return "inactivity-watchdog";
25
28
  const service = classifyAgentServiceFailure({ text: diagnostics });
@@ -34,13 +37,11 @@ function attemptFailure(run) {
34
37
  return "permanent";
35
38
  return "retryable";
36
39
  }
37
- function exclusion(run, failure) {
40
+ function exclusion(run) {
38
41
  if (run.cancelReason)
39
42
  return run.cancelReason;
40
43
  if (!run.principalId || run.engine !== "daemon")
41
44
  return "excluded";
42
- if (failure === "auth-failure" || failure === "inactivity-watchdog")
43
- return failure;
44
45
  return null;
45
46
  }
46
47
  function budgetNotice(run, now) {
@@ -71,12 +72,15 @@ export function createDurableRecovery(ports, _optional = {}) {
71
72
  }
72
73
  async function continueDead(run) {
73
74
  await ports.cancelAttempt(run, {}).catch(() => undefined);
74
- const native = await nativeAllowed(run);
75
+ // A locator captured before a hard cancel can outlive its rollout. Reuse reconstruction,
76
+ // including prior turns/tool outcomes, instead of retrying a known missing native session.
77
+ const missingResume = run.request.agentId === 'codex' && /no rollout found for thread id/.test(attemptDiagnostics({ run }, {}));
78
+ const native = !missingResume && await nativeAllowed(run);
75
79
  const nextRunId = ports.mintRunId();
76
80
  const saved = [...(run.message.events ?? [])];
77
81
  if (!saved.some((event) => event.kind === "text") && run.message.content)
78
82
  saved.unshift({ kind: "text", text: run.message.content });
79
- const events = recoveredRunEvents({ saved }, {});
83
+ const events = recoveredRunEvents({ saved }, { notice: missingResume ? 'silent' : 'visible' });
80
84
  if (!await ports.store.advance({ run, nextRunId, now: ports.now(), events }, {}))
81
85
  return "superseded";
82
86
  const next = await ports.store.load({ messageId: run.messageId }, {});
@@ -98,10 +102,8 @@ export function createDurableRecovery(ports, _optional = {}) {
98
102
  return "continued";
99
103
  }
100
104
  async function decide(run, trigger, liveRunId) {
101
- const failure = attemptFailure(run);
102
- const reason = exclusion(run, failure);
103
- if (reason)
104
- return finalize(run, reason === "auth-failure" ? "Not logged in. Saved work is above." : "Stopped. Saved work is above.", true);
105
+ if (exclusion(run))
106
+ return finalize(run, "Stopped. Saved work is above.", true);
105
107
  // A terminal executor end confirms this attempt is dead even when the daemon retains its
106
108
  // replayable record (HTTP 200). Other triggers must obtain fresh liveness evidence.
107
109
  const probe = trigger === "attempt-failed" ? "dead" : liveRunId === run.runId ? "live" : await ports.probe(run, {});
@@ -110,6 +112,12 @@ export function createDurableRecovery(ports, _optional = {}) {
110
112
  ports.attach(run, {});
111
113
  return "reattached";
112
114
  }
115
+ // Diagnostics decide only the fate of an attempt that is no longer running. Read before the
116
+ // probe, raw stdout quoting a tool description ("OAuth-authenticated") canceled a live turn
117
+ // as "Not logged in" (luvira, 2026-10-08).
118
+ const failure = attemptFailure(run);
119
+ if (failure === "auth-failure" || failure === "inactivity-watchdog")
120
+ return finalize(run, failure === "auth-failure" ? "Not logged in. Saved work is above." : "Stopped. Saved work is above.", true);
113
121
  if (failure === "permanent")
114
122
  return finalize(run, EXHAUSTED_NOTICE);
115
123
  return resolveMissing(run, probe, trigger);
@@ -86,8 +86,9 @@ export function createAssistantRunFinalizer(args, options = {}) {
86
86
  // Each connection replays the complete history, rather than appending duplicate deltas.
87
87
  watch.events = [];
88
88
  watch.failed = false;
89
+ const carry = {};
89
90
  for await (const frame of readSseFrames({ body: response.body })) {
90
- const outcome = translateRunFrame({ kind: frame.event, raw: frame.data, notices: args.notices });
91
+ const outcome = translateRunFrame({ kind: frame.event, raw: frame.data, notices: args.notices }, { carry });
91
92
  watch.events.push(...outcome.events);
92
93
  if (outcome.error)
93
94
  watch.failed = true;
@@ -88,8 +88,11 @@ function continuationEvents({ current, incoming }, _optional = {}) {
88
88
  break;
89
89
  }
90
90
  }
91
- if (marker >= 0)
92
- return [...recoveredRunEvents({ saved }, {}), ...incoming.slice(marker + 1)];
91
+ if (marker >= 0) {
92
+ const event = incoming[marker];
93
+ const notice = event.kind === 'status' && event.label === '' ? 'silent' : 'visible';
94
+ return [...recoveredRunEvents({ saved }, { notice }), ...incoming.slice(marker + 1)];
95
+ }
93
96
  return runContentFromEvents({ events: incoming }).length >= current.message.content.length ? incoming : saved;
94
97
  }
95
98
  /** Same kernel and message lock as checkpoint/settle. Claim changes run_id before any spawn;
@@ -126,15 +126,18 @@ export function createChatRunLedger({ kernel: hostKernel, isDaemonRunId }, _opti
126
126
  async checkpoint(progress, _optional = {}) {
127
127
  return kernel.transaction(async () => {
128
128
  await kernel.lockKey(runLockKey(progress));
129
- const current = await kernel.run((db) => db.selectFrom("ai_chat_messages").select(["content", "events_json"])
129
+ const current = await kernel.run((db) => db.selectFrom("ai_chat_messages").select(["content", "events_json", "run_status"])
130
130
  .where((eb) => eb.and([isRunRow(eb, progress), notTerminal(eb)])).executeTakeFirst());
131
131
  if (!current || progress.content.length < current.content.length)
132
132
  return false;
133
133
  const events = keepToolCheckpoints(readEvents(current.events_json), progress.events);
134
134
  const grown = progress.content.length > current.content.length || hasNewToolProgress(readEvents(current.events_json), events);
135
+ // A checkpoint is the run producing output, so an accepted (`queued`) row is now `running`;
136
+ // nothing else moves it there, and a streaming answer must not read as still waiting.
137
+ const runStatus = current.run_status === "queued" ? "running" : current.run_status;
135
138
  const result = await kernel.run((db) => db
136
139
  .updateTable("ai_chat_messages")
137
- .set({ content: progress.content, events_json: JSON.stringify(events) })
140
+ .set({ content: progress.content, events_json: JSON.stringify(events), run_status: runStatus })
138
141
  .where((eb) => eb.and([isRunRow(eb, progress), notTerminal(eb)]))
139
142
  .executeTakeFirst());
140
143
  if (Number(result.numUpdatedRows) > 0 && grown) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/chat",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
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": {
@@ -194,10 +194,10 @@
194
194
  "@radix-ui/react-select": "^2.3.7",
195
195
  "@radix-ui/react-slot": "^1.3.3",
196
196
  "recharts": "^3.10.1",
197
- "@jini-ai/agent-runtime": "^0.4.1",
197
+ "@jini-ai/agent-runtime": "^0.4.4",
198
198
  "@jini-ai/agentic": "^0.4.2",
199
- "@jini-ai/protocol": "^0.4.1",
200
- "@jini-ai/ui": "^0.4.5",
199
+ "@jini-ai/protocol": "^0.4.3",
200
+ "@jini-ai/ui": "^0.4.7",
201
201
  "@jini-ai/diagnostics": "^0.5.0"
202
202
  },
203
203
  "peerDependenciesMeta": {
@@ -286,12 +286,12 @@
286
286
  "@electric-sql/pglite": "0.5.8",
287
287
  "pg": "8.23.0",
288
288
  "@ag-ui/core": "0.0.58",
289
- "@jini-ai/protocol": "^0.4.2",
290
- "@jini-ai/agent-runtime": "0.4.3",
291
- "@jini-ai/ui": "^0.4.6",
289
+ "@jini-ai/ui": "^0.4.7",
292
290
  "@jini-ai/db": "0.3.1",
293
- "@jini-ai/agentic": "^0.4.3",
291
+ "@jini-ai/protocol": "^0.4.3",
294
292
  "@jini-ai/diagnostics": "^0.5.1",
293
+ "@jini-ai/agentic": "^0.4.3",
294
+ "@jini-ai/agent-runtime": "0.4.4",
295
295
  "@jini-ai/core": "^0.4.2"
296
296
  },
297
297
  "typesVersions": {