@jini-ai/chat 0.5.4 → 0.5.5

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 +4 -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 +29 -17
  71. package/dist/react/features/chat-pane/model-options.d.ts +10 -0
  72. package/dist/react/features/chat-pane/model-options.js +51 -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,121 +1,2 @@
1
- /** Resolved target supplied by the host; the client never derives a path from a title. */
2
- export interface ResolvedPageTarget {
3
- readonly slug: string;
4
- readonly title: string;
5
- readonly path: string;
6
- }
7
- export type PageAction = {
8
- readonly type: 'navigate';
9
- readonly target: ResolvedPageTarget;
10
- readonly auto: boolean;
11
- } | {
12
- readonly type: 'scroll_to' | 'highlight';
13
- readonly target: ResolvedPageTarget;
14
- };
15
- export type NavigateAction = Extract<PageAction, {
16
- type: 'navigate';
17
- }>;
18
- export type NonNavigateAction = Exclude<PageAction, {
19
- type: 'navigate';
20
- }>;
21
- export interface DirectiveCarryingEvent {
22
- readonly kind: string;
23
- readonly name?: string;
24
- readonly data?: unknown;
25
- }
26
- export interface SplitPageActions {
27
- readonly navigate: NavigateAction | null;
28
- readonly other: NonNavigateAction | null;
29
- }
30
- /** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
31
- export declare function isQueuedPageAction(args: {
32
- value: unknown;
33
- }): args is {
34
- value: PageAction;
35
- };
36
- /** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
37
- export declare function isPageActionDirective(args: {
38
- value: unknown;
39
- }): args is {
40
- value: {
41
- kind: 'page_action';
42
- action: PageAction;
43
- };
44
- };
45
- /** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
46
- export declare function extractPageActions({ events, directiveEventName }: {
47
- events: readonly DirectiveCarryingEvent[] | undefined;
48
- directiveEventName: string;
49
- }): PageAction[];
50
- /** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
51
- export declare function splitPageActions({ actions }: {
52
- actions: readonly PageAction[];
53
- }): SplitPageActions;
54
- export interface TargetSelectionPolicy {
55
- readonly candidateSelector: string;
56
- readonly excludedAncestorSelector: string;
57
- }
58
- /** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
59
- export declare function findTargetElement({ title, root, selection }: {
60
- title: string;
61
- root: Pick<ParentNode, 'querySelectorAll'>;
62
- selection: TargetSelectionPolicy;
63
- }): Element | null;
64
- export interface PageActionTimerPort {
65
- schedule(args: {
66
- callback: () => void;
67
- delayMs: number;
68
- }): unknown;
69
- cancel(args: {
70
- handle: unknown;
71
- }): void;
72
- }
73
- export interface PageActionPort {
74
- allows(args: {
75
- action: PageAction;
76
- }): boolean;
77
- navigate(args: {
78
- action: NavigateAction;
79
- }): boolean;
80
- execute(args: {
81
- action: NonNavigateAction;
82
- }): boolean;
83
- clearHighlight(args: Record<string, never>): void;
84
- }
85
- export interface BrowserPageActionController extends PageActionPort {
86
- applyHighlight(args: {
87
- element: Element;
88
- }): void;
89
- scrollToElement(args: {
90
- element: Element;
91
- }): void;
92
- }
93
- export interface BrowserPageActionsArgs {
94
- readonly root: Pick<ParentNode, 'querySelectorAll'>;
95
- readonly selection: TargetSelectionPolicy;
96
- readonly allowPath: (args: {
97
- path: string;
98
- }) => boolean;
99
- readonly navigate: (args: {
100
- path: string;
101
- }) => void;
102
- readonly reducedMotion: (args: Record<string, never>) => boolean;
103
- readonly scroll: (args: {
104
- element: Element;
105
- options: ScrollIntoViewOptions;
106
- }) => void;
107
- readonly timers: PageActionTimerPort;
108
- readonly highlightClass: string;
109
- readonly fadeAnimationName: string;
110
- readonly cleanupFallbackMs: number;
111
- }
112
- /**
113
- * Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
114
- * At most one highlight belongs to this instance; cleanup and navigation remove it.
115
- * URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
116
- * animation events cannot remove the current highlight. Navigation paths are resolved by the host,
117
- * never guessed from a title; each controller owns its mutable highlight state.
118
- * @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
119
- */
120
- export declare function createBrowserPageActions(args: BrowserPageActionsArgs): BrowserPageActionController;
1
+ export * from '../../core/browser/page-actions.js';
121
2
  //# sourceMappingURL=page-actions.d.ts.map
@@ -1,113 +1,3 @@
1
- function record(value) {
2
- return typeof value === 'object' && value !== null && !Array.isArray(value);
3
- }
4
- function target(value) {
5
- return record(value) && typeof value.slug === 'string' && typeof value.title === 'string' && typeof value.path === 'string';
6
- }
7
- /** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
8
- export function isQueuedPageAction(args) {
9
- const { value } = args;
10
- if (!record(value) || !target(value.target))
11
- return false;
12
- return value.type === 'navigate' ? typeof value.auto === 'boolean' : value.type === 'scroll_to' || value.type === 'highlight';
13
- }
14
- /** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
15
- export function isPageActionDirective(args) {
16
- const { value } = args;
17
- return record(value) && value.kind === 'page_action' && isQueuedPageAction({ value: value.action });
18
- }
19
- /** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
20
- export function extractPageActions({ events, directiveEventName }) {
21
- const actions = [];
22
- for (const event of events ?? []) {
23
- const envelope = { value: event.data };
24
- if (event.kind === 'ext' && event.name === directiveEventName && isPageActionDirective(envelope)) {
25
- actions.push(envelope.value.action);
26
- }
27
- }
28
- return actions;
29
- }
30
- /** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
31
- export function splitPageActions({ actions }) {
32
- return {
33
- navigate: actions.find((a) => a.type === 'navigate') ?? null,
34
- other: actions.find((a) => a.type !== 'navigate') ?? null,
35
- };
36
- }
37
- /** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
38
- export function findTargetElement({ title, root, selection }) {
39
- const needle = title.trim();
40
- if (!needle)
41
- return null;
42
- const candidates = Array.from(root.querySelectorAll(selection.candidateSelector))
43
- .filter(el => !el.closest(selection.excludedAncestorSelector));
44
- return candidates.find(el => (el.textContent ?? '').trim() === needle)
45
- ?? candidates.find(el => (el.textContent ?? '').trim().toLowerCase() === needle.toLowerCase()) ?? null;
46
- }
47
- /**
48
- * Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
49
- * At most one highlight belongs to this instance; cleanup and navigation remove it.
50
- * URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
51
- * animation events cannot remove the current highlight. Navigation paths are resolved by the host,
52
- * never guessed from a title; each controller owns its mutable highlight state.
53
- * @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
54
- */
55
- export function createBrowserPageActions(args) {
56
- if (!Number.isFinite(args.cleanupFallbackMs) || args.cleanupFallbackMs < 0)
57
- throw new Error('Invalid highlight deadline');
58
- let current = null;
59
- const clearHighlight = (_args) => {
60
- if (!current)
61
- return;
62
- const old = current;
63
- current = null;
64
- args.timers.cancel({ handle: old.handle });
65
- old.element.removeEventListener('animationend', old.onEnd);
66
- old.element.classList.remove(args.highlightClass);
67
- };
68
- function highlight(element) {
69
- if (current?.element === element)
70
- return;
71
- clearHighlight({});
72
- const onEnd = event => {
73
- if (event.target === element && event.animationName === args.fadeAnimationName)
74
- clearHighlight({});
75
- };
76
- element.classList.add(args.highlightClass);
77
- element.addEventListener('animationend', onEnd);
78
- // Identity check makes even a late callback from a canceled timer harmless.
79
- const handle = args.timers.schedule({ delayMs: args.cleanupFallbackMs, callback: () => {
80
- if (current?.element === element && current.onEnd === onEnd)
81
- clearHighlight({});
82
- } });
83
- current = { element, handle, onEnd };
84
- }
85
- const scrollToElement = ({ element }) => {
86
- args.scroll({ element, options: { behavior: args.reducedMotion({}) ? 'auto' : 'smooth', block: 'center' } });
87
- };
88
- return {
89
- applyHighlight({ element }) { highlight(element); },
90
- scrollToElement,
91
- allows({ action }) { return args.allowPath({ path: action.target.path }); },
92
- navigate({ action }) {
93
- if (!args.allowPath({ path: action.target.path }))
94
- return false;
95
- clearHighlight({});
96
- args.navigate({ path: action.target.path });
97
- return true;
98
- },
99
- execute({ action }) {
100
- if (!args.allowPath({ path: action.target.path }))
101
- return false;
102
- const element = findTargetElement({ title: action.target.title, root: args.root, selection: args.selection });
103
- if (!element)
104
- return false;
105
- scrollToElement({ element });
106
- if (action.type === 'highlight')
107
- highlight(element);
108
- return true;
109
- },
110
- clearHighlight,
111
- };
112
- }
1
+ // Compatibility path; shared page-action decisions belong to chat core.
2
+ export * from '../../core/browser/page-actions.js';
113
3
  //# sourceMappingURL=page-actions.js.map
@@ -1,7 +1,7 @@
1
1
  import type { ChatTransport } from '../../core/transport.js';
2
2
  import type { ChatPaneAgent } from '../../react/chat-pane.js';
3
3
  import type { BrowserSessionStore } from '../browser/session-store.js';
4
- import type { PageActionPort } from '../browser/page-actions.js';
4
+ import type { PageActionPort } from '../../core/browser/page-actions.js';
5
5
  import type { EmbedHeaderCopy, EscapeKeyPort } from './header.js';
6
6
  export { EmbedChatHeader } from './header.js';
7
7
  export type { EmbedChatHeaderArgs, EmbedHeaderCopy, EscapeKeyPort } from './header.js';
@@ -5,7 +5,7 @@ import { isTerminalRunStatus } from '../../core/messages.js';
5
5
  import { ChatPane } from '../../react/chat-pane.js';
6
6
  import { ChatFab } from '../../react/components/ChatFab.js';
7
7
  import { JiniChatProvider } from '../../react/components/JiniChatProvider.js';
8
- import { extractPageActions, splitPageActions, isQueuedPageAction } from '../browser/page-actions.js';
8
+ import { extractPageActions, splitPageActions, isQueuedPageAction } from '../../core/browser/page-actions.js';
9
9
  import { EmbedChatHeader } from './header.js';
10
10
  export { EmbedChatHeader } from './header.js';
11
11
  const shellStyles = `
@@ -1,22 +1,16 @@
1
1
  /**
2
2
  * @module artifact-types
3
3
  *
4
- * Minimal `ArtifactFile`/`ArtifactRenderer`/`RendererRegistry` shapes,
5
- * defined locally per this task's SCOPE NOTE: r4b calls the artifact-renderer
6
- * package `@jini-ai/artifacts-react`; the actual locked name
7
- * (`ADS-memory/reports/jini-port/extraction-plan.md` §3) is `@jini-ai/renderers-react`, which
8
- * is still a placeholder stub being built in a separate session. `chat-react`
9
- * needs these shapes now (for `useArtifactStream` and the `ArtifactFile`
10
- * field on `ProjectContextValue`), so they are defined here to the exact
11
- * shape r4b §2 specifies, with the intent that a future pass re-points every
12
- * import below at `@jini-ai/renderers-react`'s real exports once that package
13
- * lands, deletes this file, and removes the re-export from `index.ts`.
4
+ * Chat's public streaming artifact contracts, used by useArtifactStream and
5
+ * ProjectContextValue. The renderer implementation is available in @jini-ai/ui,
6
+ * but direct delegation would change this compatibility surface: chat registers
7
+ * mutably, resolves newest-first, returns unregister handles, accepts files
8
+ * without manifests and returns the matched file. UI's registry is immutable,
9
+ * replaces registrations by ID, requires a manifest and returns that manifest;
10
+ * its predicate and resolve signatures also pass hints as a separate option.
14
11
  *
15
- * TODO(renderers-react): replace this file's contents with
16
- * `export * from '@jini-ai/renderers-react'` (or the equivalent named
17
- * re-exports) once `@jini-ai/renderers-react` ships real `ArtifactFile`/
18
- * `ArtifactRenderer`/`RendererRegistry` implementations. Do not block this
19
- * package's other work on that package landing first.
12
+ * TODO(renderer-delegation): Coordinator must resolve these contracts before
13
+ * delegating to UI. A wildcard re-export is not a behavior-preserving migration.
20
14
  */
21
15
  import type { ArtifactManifest } from '../core/index.js';
22
16
  /** Generic artifact-file shape a host's project/workspace file maps onto. */
@@ -1,3 +1,4 @@
1
+ import { type UserTextRedactionOptions } from '../../core/user-text-redaction.js';
1
2
  import type { QuestionForm as QuestionFormType } from '../../core/index.js';
2
3
  export interface QuestionFormFileSubmission {
3
4
  questionId: string;
@@ -9,7 +10,7 @@ export interface QuestionFormHandle {
9
10
  /** Submit with no answers — every question is optional, so this records each as "(skipped)" and moves on. */
10
11
  skipAll: () => void;
11
12
  }
12
- export interface QuestionFormProps {
13
+ export interface QuestionFormProps extends UserTextRedactionOptions {
13
14
  form: QuestionFormType;
14
15
  /** Whether the user can still submit answers — the host disables this once the turn is no longer the most recent one. */
15
16
  interactive: boolean;
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CREDENTIAL_CARD_GUIDANCE, SECRET_REDACTED_NOTICE } from '../../core/user-text-redaction.js';
3
+ import { useUserTextGuard } from '../hooks/useUserTextGuard.js';
2
4
  /**
3
5
  * @module QuestionForm
4
6
  *
@@ -13,15 +15,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
15
  * string wrapped in `useT()`.
14
16
  */
15
17
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
16
- import { formatFormAnswers, formOptionValueForLabel } from '../../core/index.js';
18
+ import { formatFormAnswers, formOptionValueForLabel, redactQuestionAnswers, isSecretQuestion, withoutSecretQuestionDefaults, questionTextInputProps, questionTextareaProps } from '../../core/index.js';
17
19
  import { useT } from '../hooks/context.js';
18
- export const QuestionForm = forwardRef(function QuestionForm({ form, interactive, submittedAnswers, hideInternalSubmit = false, draftAnswers, onReadyChange, onDraftChange, onAnswerChange, onSubmit }, ref) {
20
+ export const QuestionForm = forwardRef(function QuestionForm({ form: incomingForm, interactive, submittedAnswers, hideInternalSubmit = false, draftAnswers, onReadyChange, onDraftChange, onAnswerChange, onSubmit, redactUserText, onSecretRedacted }, ref) {
19
21
  const t = useT();
22
+ const form = useMemo(() => withoutSecretQuestionDefaults({ form: incomingForm }), [incomingForm]);
23
+ const guard = useUserTextGuard({ ...(redactUserText ? { redactUserText } : {}), ...(onSecretRedacted ? { onSecretRedacted } : {}) });
20
24
  const initial = useMemo(() => buildInitialState(form, submittedAnswers, draftAnswers), [form, submittedAnswers, draftAnswers]);
21
25
  const [answers, setAnswers] = useState(initial);
22
26
  const [fileAnswers, setFileAnswers] = useState({});
23
27
  const locked = !interactive || !onSubmit || submittedAnswers !== undefined;
24
- const currentAnswers = submittedAnswers ?? answers;
28
+ const currentAnswers = submittedAnswers ? redactQuestionAnswers({ form, answers: submittedAnswers }).answers : answers;
25
29
  useEffect(() => {
26
30
  setFileAnswers({});
27
31
  }, [form.id]);
@@ -51,8 +55,8 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
51
55
  return;
52
56
  const next = { ...answers, [id]: value };
53
57
  setAnswers(next);
54
- onDraftChange?.(draftSafeAnswers(form, next));
55
- onAnswerChange?.(id, value);
58
+ onDraftChange?.(redactQuestionAnswers({ form, answers: draftSafeAnswers(form, next) }).answers);
59
+ onAnswerChange?.(id, redactQuestionAnswers({ form, answers: { [id]: value } }).answers[id]);
56
60
  }
57
61
  function toggleCheckbox(id, option, maxSelections) {
58
62
  if (locked)
@@ -64,7 +68,7 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
64
68
  const next = has ? current.filter((v) => v !== option) : [...current, option];
65
69
  const nextAnswers = { ...answers, [id]: next };
66
70
  setAnswers(nextAnswers);
67
- onDraftChange?.(draftSafeAnswers(form, nextAnswers));
71
+ onDraftChange?.(redactQuestionAnswers({ form, answers: draftSafeAnswers(form, nextAnswers) }).answers);
68
72
  onAnswerChange?.(id, next);
69
73
  }
70
74
  function updateCheckboxCustom(q, raw) {
@@ -79,11 +83,13 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
79
83
  return;
80
84
  if (!ready)
81
85
  return;
86
+ const safe = redactQuestionAnswers({ form, answers }, { ...(redactUserText ? { redactUserText } : {}), onSecretRedacted: guard.notifyRedaction });
87
+ setAnswers(safe.answers);
82
88
  const files = collectFileSubmissions(form, fileAnswers);
83
89
  if (files.length > 0)
84
- onSubmit(formatFormAnswers({ form: form, answers: answers }), answers, files);
90
+ onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers, files);
85
91
  else
86
- onSubmit(formatFormAnswers({ form: form, answers: answers }), answers);
92
+ onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers);
87
93
  }
88
94
  function handleSkipAll() {
89
95
  if (locked || !onSubmit)
@@ -113,18 +119,18 @@ export const QuestionForm = forwardRef(function QuestionForm({ form, interactive
113
119
  }, [onReadyChange, locked, ready]);
114
120
  return (_jsxs("div", { className: `question-form${locked ? ' question-form-locked' : ''}`, "data-form-id": form.id, children: [_jsxs("div", { className: "question-form-head", children: [_jsx("span", { className: "question-form-icon", "aria-hidden": true, children: "?" }), _jsxs("div", { className: "question-form-titles", children: [_jsx("div", { className: "question-form-title", children: form.title }), form.description ? _jsx("div", { className: "question-form-desc", children: form.description }) : null] }), locked ? _jsx("span", { className: "question-form-pill", children: t('Answered') }) : null] }), _jsx("div", { className: "question-form-body", children: form.questions.map((q) => {
115
121
  const value = currentAnswers[q.id];
116
- return (_jsxs("div", { className: "qf-field", children: [_jsxs("label", { className: "qf-label", children: [_jsx("span", { children: q.label }), q.required ? (_jsx("span", { className: "qf-required", "aria-label": t('required'), children: "*" })) : null] }), q.help ? _jsx("div", { className: "qf-help", children: q.help }) : null, q.type === 'radio' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => (_jsxs("label", { className: `qf-chip${value === opt.value ? ' qf-chip-on' : ''}`, title: opt.description, children: [_jsx("input", { type: "radio", name: `${form.id}-${q.id}`, value: opt.value, checked: value === opt.value, disabled: locked, "aria-label": opt.label, onChange: () => update(q.id, opt.value) }), _jsx(OptionCopy, { option: opt })] }, opt.value))) })) : null, q.type === 'radio' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'checkbox' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => {
122
+ return (_jsxs("div", { className: "qf-field", children: [_jsxs("label", { className: "qf-label", children: [_jsx("span", { children: q.label }), q.required ? (_jsx("span", { className: "qf-required", "aria-label": t('required'), children: "*" })) : null] }), q.help ? _jsx("div", { className: "qf-help", children: q.help }) : null, isSecretQuestion({ question: q }) ? _jsx("div", { className: "qf-help", children: t(CREDENTIAL_CARD_GUIDANCE) }) : null, q.type === 'radio' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => (_jsxs("label", { className: `qf-chip${value === opt.value ? ' qf-chip-on' : ''}`, title: opt.description, children: [_jsx("input", { type: "radio", name: `${form.id}-${q.id}`, value: opt.value, checked: value === opt.value, disabled: locked, "aria-label": opt.label, onChange: () => update(q.id, opt.value) }), _jsx(OptionCopy, { option: opt })] }, opt.value))) })) : null, q.type === 'radio' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'checkbox' && q.options ? (_jsx("div", { className: "qf-options", children: q.options.map((opt) => {
117
123
  const arr = Array.isArray(value) ? value : [];
118
124
  const on = arr.includes(opt.value);
119
125
  const maxed = q.maxSelections !== undefined && !on && arr.length >= q.maxSelections;
120
126
  return (_jsxs("label", { title: opt.description, className: `qf-chip${on ? ' qf-chip-on' : ''}${maxed ? ' qf-chip-disabled' : ''}`, children: [_jsx("input", { type: "checkbox", value: opt.value, checked: on, disabled: locked || maxed, "aria-label": opt.label, onChange: () => toggleCheckbox(q.id, opt.value, q.maxSelections) }), _jsx(OptionCopy, { option: opt })] }, opt.value));
121
- }) })) : null, q.type === 'checkbox' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customCheckboxValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => updateCheckboxCustom(q, next) })) : null, q.type === 'select' && q.options ? (_jsxs("select", { className: "qf-select", value: typeof value === 'string' && questionValueIsKnown(q, value) ? value : '', disabled: locked, onChange: (e) => update(q.id, e.target.value), children: [_jsx("option", { value: "", disabled: true, children: q.placeholder ?? t('Choose one') }), q.options.map((opt) => (_jsx("option", { value: opt.value, title: opt.description, children: opt.label }, opt.value)))] })) : null, q.type === 'select' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'text' ? _jsx("input", { type: "text", className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'number' ? _jsx("input", { type: "number", className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'range' ? (_jsxs("div", { className: "qf-range-wrap", children: [_jsx("input", { type: "range", className: "qf-range", value: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0), min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }), _jsx("output", { className: "qf-range-value", children: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0) })] })) : null, q.type === 'date' || q.type === 'time' || q.type === 'datetime-local' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'color' ? _jsx("input", { type: "color", className: "qf-color", value: normalizeColorInputValue(value), disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'url' || q.type === 'email' || q.type === 'tel' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'file' ? (_jsxs("div", { className: "qf-file-wrap", children: [_jsx("input", { type: "file", className: "qf-file", multiple: q.multiple, accept: q.accept, disabled: locked, onChange: (e) => {
127
+ }) })) : null, q.type === 'checkbox' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customCheckboxValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => updateCheckboxCustom(q, next) })) : null, q.type === 'select' && q.options ? (_jsxs("select", { className: "qf-select", value: typeof value === 'string' && questionValueIsKnown(q, value) ? value : '', disabled: locked, onChange: (e) => update(q.id, e.target.value), children: [_jsx("option", { value: "", disabled: true, children: q.placeholder ?? t('Choose one') }), q.options.map((opt) => (_jsx("option", { value: opt.value, title: opt.description, children: opt.label }, opt.value)))] })) : null, q.type === 'select' && q.options && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null, q.type === 'text' ? _jsx("input", { ...questionTextInputProps({ question: q }), className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'number' ? _jsx("input", { type: "number", className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'range' ? (_jsxs("div", { className: "qf-range-wrap", children: [_jsx("input", { type: "range", className: "qf-range", value: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0), min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }), _jsx("output", { className: "qf-range-value", children: typeof value === 'string' && value.trim() ? value : String(q.min ?? 0) })] })) : null, q.type === 'date' || q.type === 'time' || q.type === 'datetime-local' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'color' ? _jsx("input", { type: "color", className: "qf-color", value: normalizeColorInputValue(value), disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'url' || q.type === 'email' || q.type === 'tel' ? _jsx("input", { type: q.type, className: "qf-input", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'file' ? (_jsxs("div", { className: "qf-file-wrap", children: [_jsx("input", { type: "file", className: "qf-file", multiple: q.multiple, accept: q.accept, disabled: locked, onChange: (e) => {
122
128
  const files = Array.from(e.target.files ?? []);
123
129
  const names = files.map((file) => file.name);
124
130
  setFileAnswers((current) => ({ ...current, [q.id]: files }));
125
131
  update(q.id, q.multiple ? names : (names[0] ?? ''));
126
- } }), fileValueLabel(value) ? _jsx("div", { className: "qf-file-summary", children: fileValueLabel(value) }) : null] })) : null, q.type === 'switch' ? (_jsxs("label", { className: "qf-switch", children: [_jsx("input", { type: "checkbox", role: "switch", checked: value === 'true', disabled: locked, onChange: (e) => update(q.id, e.target.checked ? 'true' : 'false') }), _jsx("span", { "aria-hidden": true })] })) : null, q.type === 'textarea' ? _jsx("textarea", { className: "qf-textarea", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, rows: 3, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 ? (_jsx("div", { className: "qf-direction-cards", children: q.cards.map((card) => (_jsx(DirectionCardView, { card: card, formId: form.id, questionId: q.id, selected: value === card.id || value === card.label, disabled: locked, onSelect: () => update(q.id, card.id) }, card.id))) })) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null] }, q.id));
127
- }) }), hideInternalSubmit ? null : (_jsxs("div", { className: "question-form-foot", children: [locked ? _jsx("span", { className: "qf-locked-note", children: submittedAnswers ? t('You answered this') : t('A newer message answered this') }) : _jsx("span", { className: "qf-hint", children: t('Answer above, then continue') }), !locked ? (_jsx("button", { type: "button", className: "primary", onClick: handleSubmit, disabled: !ready, title: ready ? t('Submit your answers') : t('Answer the required questions first'), children: form.submitLabel ?? t('Continue') })) : null] }))] }));
132
+ } }), fileValueLabel(value) ? _jsx("div", { className: "qf-file-summary", children: fileValueLabel(value) }) : null] })) : null, q.type === 'switch' ? (_jsxs("label", { className: "qf-switch", children: [_jsx("input", { type: "checkbox", role: "switch", checked: value === 'true', disabled: locked, onChange: (e) => update(q.id, e.target.checked ? 'true' : 'false') }), _jsx("span", { "aria-hidden": true })] })) : null, q.type === 'textarea' ? _jsx("textarea", { ...questionTextareaProps({ question: q }), className: "qf-textarea", value: typeof value === 'string' ? value : '', placeholder: q.placeholder, disabled: locked, rows: 3, onChange: (e) => update(q.id, e.target.value) }) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 ? (_jsx("div", { className: "qf-direction-cards", children: q.cards.map((card) => (_jsx(DirectionCardView, { card: card, formId: form.id, questionId: q.id, selected: value === card.id || value === card.label, disabled: locked, onSelect: () => update(q.id, card.id) }, card.id))) })) : null, q.type === 'direction-cards' && q.cards && q.cards.length > 0 && shouldRenderCustomChoice(q) ? (_jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: customSingleValue(q, value), placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: (next) => update(q.id, next) })) : null] }, q.id));
133
+ }) }), guard.secretRedacted ? _jsx("div", { role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, hideInternalSubmit ? null : (_jsxs("div", { className: "question-form-foot", children: [locked ? _jsx("span", { className: "qf-locked-note", children: submittedAnswers ? t('You answered this') : t('A newer message answered this') }) : _jsx("span", { className: "qf-hint", children: t('Answer above, then continue') }), !locked ? (_jsx("button", { type: "button", className: "primary", onClick: handleSubmit, disabled: !ready, title: ready ? t('Submit your answers') : t('Answer the required questions first'), children: form.submitLabel ?? t('Continue') })) : null] }))] }));
128
134
  });
129
135
  function OptionCopy({ option }) {
130
136
  return (_jsxs("span", { className: "qf-chip-copy", children: [_jsx("span", { children: option.label }), option.description ? _jsx("span", { className: "qf-chip-desc", children: option.description }) : null] }));
@@ -1,5 +1,6 @@
1
+ import type { UserTextRedactionOptions } from '../../core/user-text-redaction.js';
1
2
  import type { QuestionForm as QuestionFormType } from '../../core/index.js';
2
- export interface QuestionsPanelProps {
3
+ export interface QuestionsPanelProps extends UserTextRedactionOptions {
3
4
  form: QuestionFormType | null;
4
5
  interactive: boolean;
5
6
  /** Disables Continue/Skip while the turn is busy, without locking the form itself. */
@@ -9,5 +10,5 @@ export interface QuestionsPanelProps {
9
10
  generating?: boolean;
10
11
  onSubmit: (text: string, answers: Record<string, string | string[]>) => void;
11
12
  }
12
- export declare function QuestionsPanel({ form, interactive, submitDisabled, submittedAnswers, generating, onSubmit }: QuestionsPanelProps): import("react").JSX.Element | null;
13
+ export declare function QuestionsPanel({ form, interactive, submitDisabled, submittedAnswers, generating, onSubmit, redactUserText, onSecretRedacted }: QuestionsPanelProps): import("react").JSX.Element | null;
13
14
  //# sourceMappingURL=QuestionsPanel.d.ts.map
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  import { useRef, useState } from 'react';
21
21
  import { useT } from '../hooks/context.js';
22
22
  import { QuestionForm } from './QuestionForm.js';
23
- export function QuestionsPanel({ form, interactive, submitDisabled = false, submittedAnswers, generating = false, onSubmit }) {
23
+ export function QuestionsPanel({ form, interactive, submitDisabled = false, submittedAnswers, generating = false, onSubmit, redactUserText, onSecretRedacted }) {
24
24
  const t = useT();
25
25
  const formRef = useRef(null);
26
26
  const [ready, setReady] = useState(false);
@@ -28,6 +28,6 @@ export function QuestionsPanel({ form, interactive, submitDisabled = false, subm
28
28
  return null;
29
29
  const answered = submittedAnswers !== undefined;
30
30
  const busy = submitDisabled || generating;
31
- return (_jsxs("div", { className: "qp-panel", children: [_jsx(QuestionForm, { ref: formRef, form: form, interactive: interactive, ...(submittedAnswers !== undefined ? { submittedAnswers } : {}), hideInternalSubmit: true, onReadyChange: setReady, onSubmit: onSubmit }), !answered ? (_jsxs("div", { className: "qp-foot", children: [generating ? _jsx("span", { className: "qp-hint", children: t('Still generating…') }) : null, _jsx("button", { type: "button", className: "qp-skip", disabled: busy, onClick: () => formRef.current?.skipAll(), children: t('Skip') }), _jsx("button", { type: "button", className: "qp-continue primary", disabled: busy || !ready, onClick: () => formRef.current?.submit(), children: t('Continue') })] })) : null] }));
31
+ return (_jsxs("div", { className: "qp-panel", children: [_jsx(QuestionForm, { ref: formRef, form: form, interactive: interactive, ...(submittedAnswers !== undefined ? { submittedAnswers } : {}), hideInternalSubmit: true, ...(redactUserText ? { redactUserText } : {}), ...(onSecretRedacted ? { onSecretRedacted } : {}), onReadyChange: setReady, onSubmit: onSubmit }), !answered ? (_jsxs("div", { className: "qp-foot", children: [generating ? _jsx("span", { className: "qp-hint", children: t('Still generating…') }) : null, _jsx("button", { type: "button", className: "qp-skip", disabled: busy, onClick: () => formRef.current?.skipAll(), children: t('Skip') }), _jsx("button", { type: "button", className: "qp-continue primary", disabled: busy || !ready, onClick: () => formRef.current?.submit(), children: t('Continue') })] })) : null] }));
32
32
  }
33
33
  //# sourceMappingURL=QuestionsPanel.js.map
@@ -1,4 +1,4 @@
1
- import { type CapabilityDef, type CapabilityInputSchema, type CapabilityRisk } from '@jini-ai/agentic';
1
+ import type { CapabilityDef, CapabilityInputSchema, CapabilityRisk } from '@jini-ai/agentic';
2
2
  /** @deprecated Use `CapabilityRisk` from `@jini-ai/agentic`. */
3
3
  export type ChatPaneAgentToolRisk = CapabilityRisk;
4
4
  /** @deprecated Use `CapabilityInputSchema` from `@jini-ai/agentic`. */
@@ -12,7 +12,6 @@
12
12
  * `@jini-ai/agentic` directly.
13
13
  */
14
14
  import { CHAT_CAPABILITIES } from '@jini-ai/chat';
15
- import {} from '@jini-ai/agentic';
16
15
  /**
17
16
  * The bounded, explicit set of chat-pane capabilities every transport surface hosts.
18
17
  *
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { pickerModelValue, pickerCatalogNote, pickerModelOptions } from '../model-options.js';
2
3
  import { createPortal } from 'react-dom';
3
4
  import { AgentIcon, RemixIcon, SearchableModelSelect } from '@jini-ai/ui';
4
5
  import { useT } from '../../../hooks/context.js';
@@ -30,11 +31,13 @@ function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t
30
31
  function RuntimeModelSelects({ selectedAgent, value, onChange, t }) {
31
32
  if (!selectedAgent)
32
33
  return null;
33
- return (_jsxs("div", { className: "jini-runtime-models", children: [(selectedAgent.models?.length ?? 0) > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Model') }), _jsx("select", { "aria-label": t('Model'), value: value.model ?? '', onChange: (event) => onChange({
34
+ const models = pickerModelOptions(selectedAgent);
35
+ const note = pickerCatalogNote(selectedAgent, t);
36
+ return (_jsxs("div", { className: "jini-runtime-models", children: [note ? _jsx("small", { children: note }) : null, models.length > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Model') }), _jsxs("select", { "aria-label": t('Model'), value: pickerModelValue(selectedAgent, value.model), onChange: (event) => onChange({
34
37
  agentId: value.agentId,
35
38
  model: event.target.value,
36
39
  ...(value.reasoning ? { reasoning: value.reasoning } : {}),
37
- }), children: selectedAgent.models?.map((model) => (_jsx("option", { value: model.id, children: model.label }, model.id))) })] })) : null, (selectedAgent.reasoningOptions?.length ?? 0) > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Reasoning') }), _jsx("select", { "aria-label": t('Reasoning'), value: value.reasoning ?? '', onChange: (event) => onChange({
40
+ }), children: [_jsx("option", { value: "", disabled: true, children: t('Choose model') }), models.map((model) => (_jsx("option", { value: model.id, children: model.label }, model.id)))] })] })) : null, (selectedAgent.reasoningOptions?.length ?? 0) > 0 ? (_jsxs("label", { className: "jini-runtime-select", children: [_jsx("span", { children: t('Reasoning') }), _jsx("select", { "aria-label": t('Reasoning'), value: value.reasoning ?? '', onChange: (event) => onChange({
38
41
  agentId: value.agentId,
39
42
  ...(value.model ? { model: value.model } : {}),
40
43
  reasoning: event.target.value,
@@ -6,5 +6,5 @@ import type { ChatPaneProps } from '../types.js';
6
6
  * @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
7
7
  * @overallScore 100/100
8
8
  */
9
- export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
9
+ export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
10
10
  //# sourceMappingURL=ChatPane.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { SECRET_REDACTED_NOTICE } from '../../../../core/user-text-redaction.js';
2
3
  import { useEffect, useRef } from 'react';
3
4
  import { WorkingDirPicker } from '@jini-ai/ui';
4
5
  import { Composer } from '../../../components/Composer.js';
@@ -178,7 +179,7 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
178
179
  * @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
179
180
  * @overallScore 100/100
180
181
  */
181
- export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
182
+ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
182
183
  const t = useT();
183
184
  const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
184
185
  const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
@@ -207,6 +208,8 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
207
208
  uploadAttachments,
208
209
  onActivityChange,
209
210
  onMessagesChange,
211
+ redactUserText,
212
+ onSecretRedacted,
210
213
  workingDirectory,
211
214
  initialWorkingDirectory,
212
215
  onChangeWorkingDirectory,
@@ -265,6 +268,6 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
265
268
  footerAccessories: (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ source: { onRescan: runtimeView.rescanAgents } }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ source: { onExecutionModeChange, byokRuntime, onByokModelChange } }) })),
266
269
  } });
267
270
  const { rootRef, controlsRef } = useChatPaneControlsHeight();
268
- return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
271
+ return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), pane.secretRedacted ? _jsx("div", { className: "jini-chat-pane__notice", role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
269
272
  }
270
273
  //# sourceMappingURL=ChatPane.js.map
@@ -26,8 +26,9 @@
26
26
  * new run: 202 delivered it; 409 means nothing is waiting any more (answered, expired, or never
27
27
  * open); anything else is a failure the human can retry. A rejection would collapse the last two.
28
28
  */
29
+ import { type UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
29
30
  import type { DeliverTypedAnswer } from './types.js';
30
- export interface CreateTypedAnswerPosterOptions {
31
+ export interface CreateTypedAnswerPosterOptions extends UserTextRedactionOptions {
31
32
  /** Path appended to `baseUrl`. Defaults to `/api/mcp-ui/tool-calls`, as for `createMcpUiToolCaller`. */
32
33
  readonly path?: string;
33
34
  /** Abandon a post after this many ms (reported as `'failed'`). Defaults to 30s. */
@@ -1,3 +1,32 @@
1
+ /**
2
+ * @module create-typed-answer-poster
3
+ *
4
+ * The client half of a typed answer: a ready-made {@link DeliverTypedAnswer} that posts text the
5
+ * human typed into the composer to the MCP-UI tool-call route, for the question a running agent is
6
+ * holding open. Pass it as `ChatPane`'s `deliverTypedAnswer`.
7
+ *
8
+ * ```tsx
9
+ * <ChatPane deliverTypedAnswer={createTypedAnswerPoster({ baseUrl: '', fetch, toolName: 'ask_choice' })} />
10
+ * ```
11
+ *
12
+ * Sibling of `create-mcp-ui-tool-caller.ts`, and the same wire shape: `{toolName, params}` to the
13
+ * same route. The difference is what the params carry and what the caller needs back.
14
+ *
15
+ * ## Why the body names no exchange
16
+ *
17
+ * A human typing into the composer has never seen an exchange id; the only client-side copy lives
18
+ * inside the surface's own (model-influenced, sandboxed) HTML. So the body carries only the tool and
19
+ * the text under {@link TYPED_ANSWER_PARAM}, and the server resolves which open exchange — for that
20
+ * tool and the request's own authenticated principal — the answer is for (`@jini-ai/daemon`'s
21
+ * `findTypedAnswerTarget`).
22
+ *
23
+ * ## Why it resolves an outcome instead of throwing
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.
28
+ */
29
+ import { redactUserText } from '../../../core/user-text-redaction.js';
1
30
  /**
2
31
  * The params key a typed answer travels under. Spelled here rather than imported because this
3
32
  * browser package cannot depend on `@jini-ai/daemon`, which owns the canonical
@@ -28,6 +57,9 @@ export function createTypedAnswerPoster({ baseUrl, fetch, toolName }, options =
28
57
  const endpoint = `${baseUrl.replace(/\/$/u, '')}${options.path ?? DEFAULT_PATH}`;
29
58
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
30
59
  const deliver = async ({ text }) => {
60
+ const result = (options.redactUserText ?? redactUserText)({ text });
61
+ if (result.secretRedacted)
62
+ options.onSecretRedacted?.({ secretRedacted: true, count: result.count });
31
63
  const controller = new AbortController();
32
64
  const timer = setTimeout(() => controller.abort(), timeoutMs);
33
65
  const outcome = await fetch(endpoint, {
@@ -35,7 +67,7 @@ export function createTypedAnswerPoster({ baseUrl, fetch, toolName }, options =
35
67
  // Same-origin cookie session: the human's own credentials authorize the answer.
36
68
  credentials: 'same-origin',
37
69
  headers: { ...options.headers, 'content-type': 'application/json' },
38
- body: JSON.stringify({ toolName, params: { [TYPED_ANSWER_PARAM]: text } }),
70
+ body: JSON.stringify({ toolName, params: { [TYPED_ANSWER_PARAM]: result.text } }),
39
71
  signal: controller.signal,
40
72
  }).then((response) => deliveryFor(response.status), () => 'failed');
41
73
  clearTimeout(timer);