@jini-ai/chat 0.5.4 → 0.5.6

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 (122) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/core/ag-ui/entry.d.ts +33 -0
  3. package/dist/core/ag-ui/entry.js +80 -0
  4. package/dist/core/agentic/chat-capabilities.d.ts +2 -4
  5. package/dist/core/agentic/index.d.ts +4 -17
  6. package/dist/core/agentic/index.js +4 -17
  7. package/dist/core/assistant-client/a2ui-action-poster.d.ts +46 -0
  8. package/dist/core/assistant-client/a2ui-action-poster.js +113 -0
  9. package/dist/core/assistant-client/ag-ui-transport.d.ts +68 -0
  10. package/dist/core/assistant-client/ag-ui-transport.js +147 -0
  11. package/dist/core/assistant-client/attachment-liveness.d.ts +37 -0
  12. package/dist/core/assistant-client/attachment-liveness.js +93 -0
  13. package/dist/core/assistant-client/durable-run-subscription.d.ts +39 -0
  14. package/dist/core/assistant-client/durable-run-subscription.js +96 -0
  15. package/dist/core/assistant-client/entry.d.ts +13 -0
  16. package/dist/core/assistant-client/entry.js +7 -0
  17. package/dist/core/assistant-client/messages.d.ts +73 -0
  18. package/dist/core/assistant-client/messages.js +68 -0
  19. package/dist/core/assistant-client/transport.d.ts +164 -0
  20. package/dist/core/assistant-client/transport.js +734 -0
  21. package/dist/core/browser/page-actions.d.ts +121 -0
  22. package/dist/core/browser/page-actions.js +113 -0
  23. package/dist/core/durable-runs/continuation.d.ts +18 -0
  24. package/dist/core/durable-runs/continuation.js +61 -0
  25. package/dist/core/durable-runs/ledger-contracts.d.ts +53 -0
  26. package/dist/core/durable-runs/ledger-contracts.js +2 -0
  27. package/dist/core/durable-runs/policies.d.ts +6 -0
  28. package/dist/core/durable-runs/policies.js +6 -0
  29. package/dist/core/durable-runs/ports.d.ts +123 -0
  30. package/dist/core/durable-runs/ports.js +2 -0
  31. package/dist/core/durable-runs/run-contracts.d.ts +17 -0
  32. package/dist/core/durable-runs/run-contracts.js +2 -0
  33. package/dist/core/durable-runs/run-lock.d.ts +4 -0
  34. package/dist/core/durable-runs/run-lock.js +4 -0
  35. package/dist/core/index.d.ts +6 -19
  36. package/dist/core/index.js +6 -19
  37. package/dist/core/messages.d.ts +2 -0
  38. package/dist/core/question-form/format.d.ts +0 -9
  39. package/dist/core/question-form/format.js +16 -2
  40. package/dist/core/question-form/normalize.js +1 -0
  41. package/dist/core/question-form/secret-answers.d.ts +38 -0
  42. package/dist/core/question-form/secret-answers.js +45 -0
  43. package/dist/core/question-form/types.d.ts +2 -0
  44. package/dist/core/question-form.d.ts +1 -0
  45. package/dist/core/question-form.js +1 -0
  46. package/dist/core/run-events/entry.d.ts +208 -16
  47. package/dist/core/run-events/entry.js +273 -39
  48. package/dist/core/transcript.d.ts +3 -2
  49. package/dist/core/transcript.js +19 -3
  50. package/dist/core/user-text-redaction.d.ts +56 -0
  51. package/dist/core/user-text-redaction.js +89 -0
  52. package/dist/embed-widget/browser/page-actions.d.ts +1 -120
  53. package/dist/embed-widget/browser/page-actions.js +2 -112
  54. package/dist/embed-widget/react/embed.d.ts +1 -1
  55. package/dist/embed-widget/react/embed.js +1 -1
  56. package/dist/react/artifact-types.d.ts +9 -15
  57. package/dist/react/components/QuestionForm.d.ts +2 -1
  58. package/dist/react/components/QuestionForm.js +18 -12
  59. package/dist/react/components/QuestionsPanel.d.ts +3 -2
  60. package/dist/react/components/QuestionsPanel.js +2 -2
  61. package/dist/react/features/chat-pane/agent-tools.d.ts +1 -1
  62. package/dist/react/features/chat-pane/agent-tools.js +0 -1
  63. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +5 -2
  64. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  65. package/dist/react/features/chat-pane/components/ChatPane.js +5 -2
  66. package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +2 -1
  67. package/dist/react/features/chat-pane/create-typed-answer-poster.js +33 -1
  68. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +3 -2
  69. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
  70. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +43 -22
  71. package/dist/react/features/chat-pane/model-options.d.ts +12 -0
  72. package/dist/react/features/chat-pane/model-options.js +55 -0
  73. package/dist/react/features/chat-pane/rules.d.ts +3 -2
  74. package/dist/react/features/chat-pane/rules.js +12 -4
  75. package/dist/react/features/chat-pane/types.d.ts +25 -1
  76. package/dist/react/hooks/assistant-chats-contracts.d.ts +164 -0
  77. package/dist/react/hooks/assistant-chats-contracts.js +2 -0
  78. package/dist/react/hooks/assistant-chats.d.ts +7 -0
  79. package/dist/react/hooks/assistant-chats.js +960 -0
  80. package/dist/react/hooks/attachment-preview-cache.d.ts +1 -1
  81. package/dist/react/hooks/attachment-preview-cache.js +1 -1
  82. package/dist/react/hooks/composer-draft-cache.d.ts +2 -2
  83. package/dist/react/hooks/composer-draft-cache.js +2 -2
  84. package/dist/react/hooks/fab-position.d.ts +90 -0
  85. package/dist/react/hooks/fab-position.js +271 -0
  86. package/dist/react/hooks/useConversation.d.ts +4 -1
  87. package/dist/react/hooks/useConversation.js +14 -12
  88. package/dist/react/hooks/useUserTextGuard.d.ts +11 -0
  89. package/dist/react/hooks/useUserTextGuard.js +19 -0
  90. package/dist/react/index.d.ts +12 -21
  91. package/dist/react/index.js +7 -0
  92. package/dist/server/durable-runs/accept.d.ts +25 -0
  93. package/dist/server/durable-runs/accept.js +21 -0
  94. package/dist/server/durable-runs/entry.d.ts +5 -0
  95. package/dist/server/durable-runs/entry.js +4 -0
  96. package/dist/server/durable-runs/recover.d.ts +5 -0
  97. package/dist/server/durable-runs/recover.js +147 -0
  98. package/dist/server/run-finalizer/ports.d.ts +2 -16
  99. package/dist/store/legacy/sqlite/messages.d.ts +24 -18
  100. package/dist/store/legacy/sqlite/messages.js +12 -2
  101. package/dist/store/pglite/index.d.ts +2 -1
  102. package/dist/store/postgres/index.d.ts +2 -1
  103. package/dist/store/runtime/chat-expiry-sweep.d.ts +40 -0
  104. package/dist/store/runtime/chat-expiry-sweep.js +70 -0
  105. package/dist/store/runtime/chat-history-store.d.ts +20 -0
  106. package/dist/store/runtime/chat-history-store.js +50 -0
  107. package/dist/store/runtime/durable-run-store.d.ts +21 -0
  108. package/dist/store/runtime/durable-run-store.js +342 -0
  109. package/dist/store/runtime/entry.d.ts +7 -0
  110. package/dist/store/runtime/entry.js +6 -0
  111. package/dist/store/runtime/kernel.d.ts +29 -0
  112. package/dist/store/runtime/kernel.js +2 -0
  113. package/dist/store/runtime/run-guarded-chat-store.d.ts +10 -0
  114. package/dist/store/runtime/run-guarded-chat-store.js +42 -0
  115. package/dist/store/runtime/run-ledger.d.ts +15 -0
  116. package/dist/store/runtime/run-ledger.js +206 -0
  117. package/dist/store/sql/boundary.d.ts +6 -0
  118. package/dist/store/sql/boundary.js +7 -1
  119. package/dist/store/sql/store.d.ts +3 -2
  120. package/dist/store/sql/store.js +9 -2
  121. package/dist/store/sqlite/index.d.ts +3 -2
  122. package/package.json +75 -10
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @module question-form/format
3
+ *
4
+ * Answer presentation: turning a submitted answer set into the prose the
5
+ * agent reads on its next turn, plus the label/value lookups also used by
6
+ * `normalize.ts`'s default-value resolution. Split out of the original
7
+ * single-file `question-form.ts`; see `../question-form.ts` for the public
8
+ * facade.
9
+ */
10
+ import { redactQuestionAnswers, isSecretQuestion } from './secret-answers.js';
11
+ import { CREDENTIAL_CARD_GUIDANCE, redactUserText } from '../user-text-redaction.js';
1
12
  /**
2
13
  * Format a finished set of answers into a prose user message the agent can
3
14
  * read on its next turn. The shape is stable enough that the agent can
@@ -6,10 +17,11 @@
6
17
  * @complexity O(q) in the number of questions on the form.
7
18
  */
8
19
  export function formatFormAnswers({ form, answers }) {
20
+ const safe = redactQuestionAnswers({ form, answers });
9
21
  const lines = [];
10
22
  lines.push(`[form answers — ${form.id}]`);
11
23
  for (const q of form.questions) {
12
- const v = answers[q.id];
24
+ const v = safe.answers[q.id];
13
25
  let display;
14
26
  if (Array.isArray(v)) {
15
27
  display = v.length > 0 ? v.map((value) => formOptionDisplayForValue(q, value)).join(', ') : '(skipped)';
@@ -22,7 +34,9 @@ export function formatFormAnswers({ form, answers }) {
22
34
  }
23
35
  lines.push(`- ${q.label}: ${display}`);
24
36
  }
25
- return lines.join('\n');
37
+ if (safe.secretRedacted || form.questions.some(question => isSecretQuestion({ question })))
38
+ lines.push(CREDENTIAL_CARD_GUIDANCE);
39
+ return redactUserText({ text: lines.join('\n') }).text;
26
40
  }
27
41
  function formOptionDisplayForValue(question, value) {
28
42
  const match = question.options?.find((option) => option.value === value || option.label === value);
@@ -25,6 +25,7 @@ export function mapRawQuestion({ q, index }) {
25
25
  const accept = typeof qo.accept === 'string' ? qo.accept : undefined;
26
26
  return {
27
27
  id,
28
+ ...(typeof qo.name === 'string' ? { name: qo.name } : {}),
28
29
  label,
29
30
  type,
30
31
  ...(options ? { options } : {}),
@@ -0,0 +1,38 @@
1
+ /** Chat question forms are model-visible text, never secure credential storage. */
2
+ import { type UserTextRedactionOptions } from '../user-text-redaction.js';
3
+ import type { FormQuestion, QuestionForm } from './types.js';
4
+ export declare function isSecretQuestion({ question }: {
5
+ readonly question: Pick<FormQuestion, 'id' | 'label' | 'type' | 'name'>;
6
+ }): boolean;
7
+ /** The map as well as its formatted transcript must be safe: hosts often persist both. */
8
+ export declare function redactQuestionAnswers({ form, answers }: {
9
+ readonly form: QuestionForm;
10
+ readonly answers: Readonly<Record<string, string | string[]>>;
11
+ }, options?: UserTextRedactionOptions): {
12
+ readonly answers: Record<string, string | string[]>;
13
+ readonly secretRedacted: boolean;
14
+ readonly count: number;
15
+ };
16
+ /** Never populate a credential text question from model-supplied defaults. */
17
+ export declare function withoutSecretQuestionDefaults({ form }: {
18
+ readonly form: QuestionForm;
19
+ }): QuestionForm;
20
+ /** Attributes are presentation policy, shared by any host renderer. */
21
+ export declare function questionTextInputProps({ question }: {
22
+ readonly question: FormQuestion;
23
+ }): {
24
+ type: 'password' | 'text';
25
+ autoComplete?: string;
26
+ spellCheck?: boolean;
27
+ };
28
+ export declare function questionTextareaProps({ question }: {
29
+ readonly question: FormQuestion;
30
+ }): {
31
+ autoComplete?: string;
32
+ spellCheck?: boolean;
33
+ style?: {
34
+ WebkitTextSecurity: string;
35
+ whiteSpace: 'pre-wrap';
36
+ };
37
+ };
38
+ //# sourceMappingURL=secret-answers.d.ts.map
@@ -0,0 +1,45 @@
1
+ /** Chat question forms are model-visible text, never secure credential storage. */
2
+ import { redactUserText } from '../user-text-redaction.js';
3
+ export function isSecretQuestion({ question }) {
4
+ if (question.type !== 'text' && question.type !== 'textarea')
5
+ return false;
6
+ const names = [question.id, question.label, question.name ?? ''].join(' ').replace(/([a-z])([A-Z])/g, '$1 $2');
7
+ return /(?:^|[^a-z0-9])(?:key|token|password|passwd|secret)(?:$|[^a-z0-9])/i.test(names);
8
+ }
9
+ /** The map as well as its formatted transcript must be safe: hosts often persist both. */
10
+ export function redactQuestionAnswers({ form, answers }, options = {}) {
11
+ let count = 0;
12
+ const cleaned = {};
13
+ const redact = options.redactUserText ?? redactUserText;
14
+ for (const question of form.questions) {
15
+ const value = answers[question.id];
16
+ if (value === undefined)
17
+ continue;
18
+ const guard = (text) => {
19
+ const result = redact({ text }, { secretField: isSecretQuestion({ question }) });
20
+ count += result.count;
21
+ return result.text;
22
+ };
23
+ cleaned[question.id] = Array.isArray(value) ? value.map(guard) : guard(value);
24
+ }
25
+ if (count > 0)
26
+ options.onSecretRedacted?.({ secretRedacted: true, count });
27
+ return { answers: cleaned, secretRedacted: count > 0, count };
28
+ }
29
+ /** Never populate a credential text question from model-supplied defaults. */
30
+ export function withoutSecretQuestionDefaults({ form }) {
31
+ return { ...form, questions: form.questions.map(question => {
32
+ if (!isSecretQuestion({ question }))
33
+ return question;
34
+ const { defaultValue: _defaultValue, ...safe } = question;
35
+ return safe;
36
+ }) };
37
+ }
38
+ /** Attributes are presentation policy, shared by any host renderer. */
39
+ export function questionTextInputProps({ question }) {
40
+ return isSecretQuestion({ question }) ? { type: 'password', autoComplete: 'new-password', spellCheck: false } : { type: 'text' };
41
+ }
42
+ export function questionTextareaProps({ question }) {
43
+ return isSecretQuestion({ question }) ? { autoComplete: 'off', spellCheck: false, style: { WebkitTextSecurity: 'disc', whiteSpace: 'pre-wrap' } } : {};
44
+ }
45
+ //# sourceMappingURL=secret-answers.js.map
@@ -37,6 +37,8 @@ export interface FormOption {
37
37
  description?: string;
38
38
  }
39
39
  export interface FormQuestion {
40
+ /** Optional model-supplied field name, retained for credential masking policy. */
41
+ name?: string;
40
42
  id: string;
41
43
  label: string;
42
44
  type: QuestionType;
@@ -36,4 +36,5 @@ export type { QuestionType, DirectionCard, FormOption, FormQuestion, QuestionFor
36
36
  export { splitOnQuestionForms, findFirstQuestionForm, parseQuestionForm, stripTrailingOpenQuestionForm, hasUnterminatedQuestionForm, parsePartialQuestionForm, } from './question-form/scan.js';
37
37
  export { isRenderableColor } from './question-form/normalize.js';
38
38
  export { formatFormAnswers, formOptionLabelForValue, formOptionValueForLabel } from './question-form/format.js';
39
+ export { isSecretQuestion, redactQuestionAnswers, withoutSecretQuestionDefaults, questionTextInputProps, questionTextareaProps } from './question-form/secret-answers.js';
39
40
  //# sourceMappingURL=question-form.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export { splitOnQuestionForms, findFirstQuestionForm, parseQuestionForm, stripTrailingOpenQuestionForm, hasUnterminatedQuestionForm, parsePartialQuestionForm, } from './question-form/scan.js';
2
2
  export { isRenderableColor } from './question-form/normalize.js';
3
3
  export { formatFormAnswers, formOptionLabelForValue, formOptionValueForLabel } from './question-form/format.js';
4
+ export { isSecretQuestion, redactQuestionAnswers, withoutSecretQuestionDefaults, questionTextInputProps, questionTextareaProps } from './question-form/secret-answers.js';
4
5
  //# sourceMappingURL=question-form.js.map
@@ -4,6 +4,30 @@
4
4
  * event names and excluded run-ID prefixes are explicit host inputs, never product wire defaults.
5
5
  * Authenticated routing, ownership, boot repair and storage transactions remain host responsibilities. */
6
6
  import type { AgentEvent } from "../events.js";
7
+ /**
8
+ * @file The ONE translation from an agent-daemon run stream to chat events, shared by the browser
9
+ * and the server.
10
+ *
11
+ * Purpose:
12
+ * A daemon run streams `@jini-ai/protocol` frames (`agent`/`stdout`/`stderr`/`error`/`end`). A saved
13
+ * chat row stores chat-core `AgentEvent`s plus a text `content` and a `run_status`. Until 2026-09-27
14
+ * the frame-to-event translation lived only in the admin SPA (`apps/admin/src/lib/assistant-transport.ts`),
15
+ * so only a connected browser could turn a run into a saved answer: close the tab, or lose it to a
16
+ * restart, and the row stayed `running` with no content forever (FINDING A,
17
+ * `ADS-memory/reports/2026-09-27-stuck-chat-root-cause.md`).
18
+ *
19
+ * The server-side finalizer (`server/runtime/composition/modules/assistant-run-finalizer.ts`) now
20
+ * saves the finished turn itself, and it must write exactly what the browser would have written. Two
21
+ * copies of this switch would drift, so both import this file through the shared
22
+ * `@jini-ai/chat/core/run-events` entry, with the host's bundler, test runner and TypeScript
23
+ * resolving the same package contract.
24
+ *
25
+ * Architectural role:
26
+ * PURE. No I/O, no DOM, no Node built-ins. The one value import is `@jini-ai/chat/core`'s content
27
+ * rule, itself pure and React-free, so the saved `content` and the browser's can never differ.
28
+ * `ReadableStream`/`TextDecoder` in {@link readSseFrames} are globals in both runtimes. Keep it that
29
+ * way — the admin bundle imports it.
30
+ */
7
31
  import type { ChatRunStatus } from "../messages.js";
8
32
  export interface RunAgentPayload {
9
33
  readonly type: string;
@@ -17,15 +41,53 @@ export interface RunProtocolEventWire {
17
41
  /** Display a wire scalar or JSON value using the existing empty-null convention. */
18
42
  export declare function asString({ value }: {
19
43
  value: unknown;
20
- }): string;
44
+ }, _options?: Record<string, never>): string;
45
+ /**
46
+ * Parses a `"usage"` wire payload into its renderable `AgentEvent`.
47
+ *
48
+ * The one case in {@link translateRunAgentPayload}'s switch with real branching: four independent
49
+ * optional fields, each `typeof`-checked before use (a malformed/missing field must not throw or
50
+ * silently coerce to `0`/`NaN`). Pulled out (2026-08-06, complexity pass, sixth pass) so those four
51
+ * ternaries are scored in their own scope instead of the switch's — this is the fix for the earlier
52
+ * `@complexityExemption`'s cognitive attribution on `translateRunAgentPayload`, which credited the
53
+ * `mcp-ui`/`a2ui` cases (each a one-line unwrap, zero branching — see their own comments below) for
54
+ * a cost that actually came from here.
55
+ */
21
56
  /** Keep only numeric usage fields; omit absent fields without changing JSON output. */
22
57
  export declare function parseUsageEvent({ payload }: {
23
58
  payload: RunAgentPayload;
24
- }): AgentEvent;
59
+ }, _options?: Record<string, never>): AgentEvent;
60
+ /**
61
+ * Reduces one wire-level `RunAgentPayload` into zero or one renderable `AgentEvent`s.
62
+ *
63
+ * Exported (a pure function, so directly testable with no `EventSource`/`fetch` stub needed — see
64
+ * `assistant-transport.transcript.test.ts`'s own module doc for the same reasoning applied to
65
+ * `runPrompt`) so `assistant-transport.a2ui.test.ts` can assert the `"a2ui"` branch below in
66
+ * isolation.
67
+ *
68
+ * @complexityExemption (2026-08-06, complexity pass, sixth pass; bar is ≤9/≤9) **Score: 12
69
+ * cyclomatic / 3 cognitive. Bar: ≤9 cyclomatic AND ≤9 cognitive. Cyclomatic-only exemption —
70
+ * cognitive already clears the bar since {@link parseUsageEvent} above took the switch's only real
71
+ * branching out of this function's own scope.** (An earlier version of this comment attributed the
72
+ * cognitive cost to the `mcp-ui`/`a2ui` cases below; that was wrong — an independent audit measured
73
+ * that extracting `parseUsageEvent` alone drops cognitive from 11 to 3, which only makes sense if
74
+ * `usage` was the real source. `mcp-ui`/`a2ui` are one-line unwraps with zero branching, exactly as
75
+ * their own comments already said.) Cyclomatic stays over by construction, not by accident: this is
76
+ * a flat `switch` over `RunProtocolEventWire`'s closed `payload.type` vocabulary, one `case` per
77
+ * wire type, and cyclomatic counts every `case` as a branch regardless of shape. Tried and rejected:
78
+ * a `Record<string, (payload) => AgentEvent | null>` lookup table scores lower but loses two things
79
+ * a switch over a TS discriminated union keeps — exhaustiveness checking (a lookup table compiles
80
+ * with a missing key; this switch does not, once `payload.type` is narrowed to the real union rather
81
+ * than the wire's untyped `string`), and the ability to attach a multi-paragraph comment to one case
82
+ * explaining a specific interop bug (the `mcp-ui`/`a2ui` cases' comments each document why THAT case
83
+ * cannot fall through to `default` — see below). A lookup table would have to carry those as a
84
+ * parallel structure, one step removed from the code they explain. Left as a `switch`, documented
85
+ * here rather than only in this session's report.
86
+ */
25
87
  /** Reduce one daemon payload; preserve UI envelopes and drop duplicate input fragments. */
26
88
  export declare function translateRunAgentPayload({ payload }: {
27
89
  payload: RunAgentPayload;
28
- }): AgentEvent | null;
90
+ }, _options?: Record<string, never>): AgentEvent | null;
29
91
  /** Host-supplied status messages; no host branding or log path defaults. */
30
92
  export interface RunNotices {
31
93
  readonly toolStepLimit: Extract<AgentEvent, {
@@ -43,13 +105,32 @@ export interface RunNotices {
43
105
  outcome: TerminalOutcome;
44
106
  }) => string;
45
107
  }
108
+ /**
109
+ * Turns a terminal stream `reason` into the one renderable event a human needs to see, or `null`
110
+ * when the reason speaks for itself.
111
+ *
112
+ * Only `max_tool_turns` qualifies today, and it qualifies for a specific reason: it is the one
113
+ * terminal reason that is INDISTINGUISHABLE from success in the pane. `stop`/`end_turn` mean the
114
+ * assistant finished; an `error` reason already renders as an error. A turn that hit the tool-step
115
+ * ceiling just stops — mid-task, with whatever partial text it had, and nothing on screen saying
116
+ * the work was cut short rather than completed. That is the failure this exists to close: the
117
+ * server now reports the loop's real reason (`byok-provider-turn.ts`'s `normalizeTurnResult`
118
+ * captures it instead of echoing the provider's last raw stop code), and until this, the browser
119
+ * received that reason and dropped it.
120
+ *
121
+ * Rendered as a `status` event rather than an `error`, deliberately: nothing failed. The turn did
122
+ * real work and stopped at a budget, and the useful next action is "ask it to continue", which is
123
+ * what the detail says.
124
+ */
46
125
  /** A host notice for a run that used its tool-step budget. */
47
126
  export declare function terminalReasonNotice({ reason, notice }: {
48
127
  reason: string;
49
128
  notice: Extract<AgentEvent, {
50
129
  kind: "status";
51
130
  }>;
52
- }): AgentEvent | null;
131
+ }, _options?: Record<string, never>): AgentEvent | null;
132
+ /** A daemon `end` frame's non-successful terminal classification, as the daemon itself recorded it
133
+ * in `@jini-ai/protocol`'s `RunEndPayload`. */
53
134
  export interface TerminalOutcome {
54
135
  readonly status: "failed" | "canceled";
55
136
  readonly code: string;
@@ -59,63 +140,174 @@ export interface TerminalOutcome {
59
140
  /** Read non-successful daemon classification without throwing on malformed frames. */
60
141
  export declare function readTerminalOutcome({ raw }: {
61
142
  raw: string | undefined;
62
- }): TerminalOutcome | null;
143
+ }, _options?: Record<string, never>): TerminalOutcome | null;
144
+ /**
145
+ * Turns a daemon `end` frame's terminal outcome into a visible `status` event when — and only when —
146
+ * the run did NOT succeed.
147
+ *
148
+ * WHY THIS EXISTS (2026-09-06 chat-death investigation, `ADS-memory/reports/2026-09-06-chat-death-investigation.md`):
149
+ * `@jini-ai/protocol`'s `RunEndPayload` carries `status`/`code`/`signal`/`resumable`, and
150
+ * `@jini-ai/daemon`'s `finish()` is the ONLY event a terminal run emits — there is no separate
151
+ * `error` frame for a failed run. {@link subscribeToRun}'s `end` listener read only `reason` (a
152
+ * field `RunEndPayload` does not even have — it is BYOK-only), so a run the daemon had already
153
+ * classified `failed` arrived here indistinguishable from a completed one, was reported through
154
+ * `onDone`, and was persisted to `chat.db` as `run_status='succeeded'` with empty content. Two such
155
+ * rows existed in the investigated host's `chat.db` — one `codex`, one `claude`, 578 ms and 552 ms — and they
156
+ * are what "the chat just craps out with no error" actually looks like on disk.
157
+ *
158
+ * This is the SEEN half of that fix; {@link terminalFailureError} below is the WRITTEN-DOWN half
159
+ * (2026-09-07, owner-approved). An earlier version of this comment said the persistence change was
160
+ * deliberately NOT made and was out of scope until the owner signed off — that is no longer true,
161
+ * and the two halves stayed separate functions only because they disagree on exactly one input:
162
+ * `canceled` earns a notice but is not a failure.
163
+ *
164
+ * Returns `null` for a successful or absent status so a normal turn gains no extra event.
165
+ */
63
166
  /** A visible terminal classification rendered with host-supplied copy. */
64
167
  export declare function terminalOutcomeNotice({ raw, notices }: {
65
168
  raw: string | undefined;
66
169
  notices: RunNotices;
67
- }): AgentEvent | null;
170
+ }, _options?: Record<string, never>): AgentEvent | null;
171
+ /**
172
+ * The reportable `Error` for a daemon `end` frame the daemon itself classified as `failed`, or
173
+ * `null` for every other terminal outcome.
174
+ *
175
+ * WHY THIS EXISTS (2026-09-07, owner-approved): it is what makes a dead run RECOVERABLE FROM THE
176
+ * DATABASE ALONE. Until now a failed run was reported only through `onDone`, so the durable record
177
+ * said `run_status='succeeded'` with empty content: the live transcript was the ONLY place the
178
+ * failure was visible, and once it was gone the row was indistinguishable from a successful turn
179
+ * that happened to answer with nothing. That lie is why diagnosing chat deaths burned multiple
180
+ * sessions and carried a wrong premise through two handoffs. `f682eff2` deliberately left it in
181
+ * place ("a behavior change to what the product writes down... out of scope until the owner signs
182
+ * off"); the owner has now signed off, and this is that change.
183
+ *
184
+ * NOTHING IN THIS FILE COMPUTES `runStatus`. Three pieces of `@jini-ai/chat` do, and the whole
185
+ * effect of this function rests on all three:
186
+ * 1. `useRunStream`'s `onError` sets the run's status to `'error'` — and its `onDone` is written
187
+ * as `prev.status === 'error' ? prev.status : 'done'`, so an `onDone` arriving AFTER this
188
+ * preserves the failure instead of overwriting it. That is what lets {@link subscribeToRun}
189
+ * report the failure and STILL settle the run through `finish()` with its collected events
190
+ * intact, rather than having to choose between the two. The call order in that listener is
191
+ * load-bearing, not incidental.
192
+ * 2. `useConversation` maps run status `'error'` to `ChatMessage.runStatus: 'failed'`.
193
+ * 3. `isTerminalRunStatus` already counts `'failed'` as terminal, so `assistant-chats.ts`'s
194
+ * `persistableMessages` KEEPS the message (it discards only still-streaming turns) and
195
+ * `AssistantDock/hooks/AssistantDock.hooks.tsx`'s `shouldPublishOnMessagesChange` still
196
+ * settles the dock. A failed run therefore cannot hang the pane waiting for a terminal state
197
+ * that never arrives — which is the failure this change would otherwise have traded the wrong
198
+ * record for.
199
+ *
200
+ * `canceled` is excluded on purpose: a run the operator stopped is not a failure, and
201
+ * `useRunStream.cancel()` already stamps its own `'canceled'` status. Marking it `failed` would
202
+ * swap one wrong record for another.
203
+ *
204
+ * Historical rows are NOT retrofitted. The investigated host's `chat.db` held 2 such rows on 2026-09-07 and
205
+ * the pre-split `content.db` copy held 6; nothing distinguishes a genuinely empty
206
+ * successful answer from a death after the fact, so a migration could only guess. Going-forward
207
+ * correctness is the goal.
208
+ */
68
209
  /** Report a failed run; operator cancellation is not a failure. */
69
210
  export declare function terminalFailureError({ raw }: {
70
211
  raw: string | undefined;
71
- }): Error | null;
212
+ }, _options?: Record<string, never>): Error | null;
213
+ /** Reads a terminal frame's `reason` from either stream shape without letting a malformed or absent
214
+ * body prevent the turn from ending: the daemon path wraps it in a `RunProtocolEventWire.payload`,
215
+ * the BYOK path sends a bare `{reason}`, and `subscribeToRun`'s `end` event may carry no data at
216
+ * all. A notice is a nicety; finishing the run is not. */
72
217
  /** Read a wrapped daemon or bare provider terminal reason, failing soft. */
73
218
  export declare function readTerminalReason({ raw, wrapped }: {
74
219
  raw: string | undefined;
75
220
  wrapped: boolean;
76
- }): string;
221
+ }, _options?: Record<string, never>): string;
222
+ /**
223
+ * Parses one blank-line-delimited SSE frame's raw text into `{event, data}`, split out as its own
224
+ * pure function so it is directly testable with a plain string, no `ReadableStream`/reader involved.
225
+ *
226
+ * Returns `null` for a frame with no `data:` lines — {@link readSseFrames} skips yielding those
227
+ * (a bare `event: ping` keepalive, for example, or a frame carrying only an `id:` field).
228
+ */
77
229
  /** Parse the existing named SSE framing; bare keepalive frames return null. */
78
230
  export declare function parseFrame({ rawFrame }: {
79
231
  rawFrame: string;
80
- }): {
232
+ }, _options?: Record<string, never>): {
81
233
  event: string;
82
234
  data: string;
83
235
  } | null;
84
- /** Decode the existing named SSE framing; cancellation always releases its reader. */
236
+ /**
237
+ * Splits a `text/event-stream` response body into `{event, data}` frames. Frames are
238
+ * blank-line-delimited per the SSE spec; per-frame field parsing lives in {@link parseFrame} above.
239
+ *
240
+ * @complexity O(n) in response body bytes; O(1) additional buffering per chunk beyond the
241
+ * not-yet-terminated tail of the current frame.
242
+ * @overallScore 100
243
+ */
244
+ /** Decode complete named frames; cancellation always releases its reader. Inherits shared framing costs. */
85
245
  export declare function readSseFrames({ body }: {
86
246
  body: ReadableStream<Uint8Array>;
87
- }): AsyncGenerator<{
247
+ }, _options?: Record<string, never>): AsyncGenerator<{
88
248
  event: string;
89
249
  data: string;
90
250
  }>;
251
+ /**
252
+ * Whether the agent daemon holds this run, so its event stream can be read at
253
+ * `/api/runs/:id/events`. A BYOK or AG-UI run id names a turn that lived on one browser request, and
254
+ * there is nothing server-side to watch.
255
+ */
91
256
  /** Identify runs held by the daemon using the host's excluded ID prefixes. */
92
257
  export declare function isDaemonRunId({ runId, excludedPrefixes }: {
93
258
  runId: string;
94
259
  excludedPrefixes: readonly string[];
95
- }): boolean;
260
+ }, _options?: Record<string, never>): boolean;
96
261
  /** Copy the host's interruption notice into a saved turn. */
97
262
  export declare function runInterruptedNotice({ notice }: {
98
263
  notice: Extract<AgentEvent, {
99
264
  kind: "status";
100
265
  }>;
101
- }): AgentEvent;
266
+ }, _options?: Record<string, never>): AgentEvent;
267
+ /**
268
+ * A chat message's `content` for a list of events: `@jini-ai/chat`'s own rule
269
+ * (`assistantContentFromEvents`, also what `useConversation` uses for a live turn), so a row the
270
+ * server finalizes reads the same as one the browser saved. That rule puts a paragraph break where a
271
+ * tool call or card sat between two text runs, so a working note is never glued onto the answer.
272
+ */
102
273
  /** Use the same paragraph-boundary rule as a live chat turn. */
103
274
  export declare function runContentFromEvents({ events }: {
104
275
  events: readonly AgentEvent[];
105
- }): string;
276
+ }, _options?: Record<string, never>): string;
277
+ /**
278
+ * The events to SAVE for a turn: streamed `text`/`thinking` deltas joined into one event per run
279
+ * (`@jini-ai/chat`'s `mergeAdjacentTextEvents`). Live, one event per few tokens is what streams words
280
+ * onto the screen; saved, it only repeats the event wrapper per token. Used by every saver (the server
281
+ * finalizer and the chat PUT route the browser saves through), so both write the same shape.
282
+ */
106
283
  /** Compact adjacent text/thinking deltas for durable storage. */
107
284
  export declare function runEventsForSave({ events }: {
108
285
  events: readonly AgentEvent[];
109
- }): AgentEvent[];
286
+ }, _options?: Record<string, never>): AgentEvent[];
287
+ /** What one daemon stream frame means for the turn: events to append, a failure, and whether it ended. */
288
+ /** Set when the frame reports a failure. Once any frame has, the turn is `failed` (the browser's
289
+ * `useRunStream` keeps an `'error'` status through a later `onDone`). */
290
+ /** Set only on the `end` frame: the daemon's own terminal classification. */
110
291
  export interface RunFrameOutcome {
111
292
  readonly events: AgentEvent[];
112
293
  readonly error?: Error;
113
294
  readonly terminal?: Extract<ChatRunStatus, "succeeded" | "failed" | "canceled">;
114
295
  }
296
+ /**
297
+ * Translates one named daemon SSE frame (`event:` name plus raw `data:` text) into its effect on the
298
+ * turn. The browser's `subscribeToRun` dispatches the result to `RunHandlers`; the server finalizer
299
+ * folds it into the row it saves. Never throws: a malformed frame yields no events rather than
300
+ * stranding either reader mid-turn.
301
+ *
302
+ * An `error` frame with no data is a connection error, not a run error — the browser's
303
+ * `EventSource` reports those on the same event name, so it must check for data before calling this.
304
+ *
305
+ * @complexity O(size of the frame).
306
+ */
115
307
  /** Fold one named daemon frame into events, a failure and an optional terminal status. */
116
308
  export declare function translateRunFrame({ kind, raw, notices }: {
117
309
  kind: string;
118
310
  raw: string | undefined;
119
311
  notices: RunNotices;
120
- }): RunFrameOutcome;
312
+ }, _options?: Record<string, never>): RunFrameOutcome;
121
313
  //# sourceMappingURL=entry.d.ts.map