@khorsheed/dsh-room 0.1.0

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 (95) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +48 -0
  4. package/README.zh.md +48 -0
  5. package/cordis.patch.yml +6 -0
  6. package/lib/client.js +11481 -0
  7. package/lib/index.js +3231 -0
  8. package/lib/invariant.js +26 -0
  9. package/lib/journal-CuRnWk66.js +435 -0
  10. package/lib/tool.js +350 -0
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +2615 -0
  13. package/lib/typert.remote-client.d.ts +55 -0
  14. package/lib/typert.remote-client.js +1711 -0
  15. package/lib/types/adapter.d.ts +42 -0
  16. package/lib/types/adapter.js +41 -0
  17. package/lib/types/agent-setup.d.ts +141 -0
  18. package/lib/types/agent-setup.js +145 -0
  19. package/lib/types/client/InviteAgentAction.d.ts +13 -0
  20. package/lib/types/client/InviteAgentAction.js +45 -0
  21. package/lib/types/client/InviteDialog.d.ts +90 -0
  22. package/lib/types/client/InviteDialog.js +216 -0
  23. package/lib/types/client/MemberCard.d.ts +33 -0
  24. package/lib/types/client/MemberCard.js +32 -0
  25. package/lib/types/client/MembersView.d.ts +5 -0
  26. package/lib/types/client/MembersView.js +168 -0
  27. package/lib/types/client/RoomComposer.d.ts +21 -0
  28. package/lib/types/client/RoomComposer.js +211 -0
  29. package/lib/types/client/RoomDockCapsules.d.ts +64 -0
  30. package/lib/types/client/RoomDockCapsules.js +277 -0
  31. package/lib/types/client/RoomEventView.d.ts +11 -0
  32. package/lib/types/client/RoomEventView.js +17 -0
  33. package/lib/types/client/RoomModelPicker.d.ts +18 -0
  34. package/lib/types/client/RoomModelPicker.js +134 -0
  35. package/lib/types/client/RoomPlanView.d.ts +17 -0
  36. package/lib/types/client/RoomPlanView.js +88 -0
  37. package/lib/types/client/RoomQueueStrip.d.ts +26 -0
  38. package/lib/types/client/RoomQueueStrip.js +28 -0
  39. package/lib/types/client/RoomRecoveryView.d.ts +11 -0
  40. package/lib/types/client/RoomRecoveryView.js +38 -0
  41. package/lib/types/client/RoomRelayView.d.ts +12 -0
  42. package/lib/types/client/RoomRelayView.js +20 -0
  43. package/lib/types/client/RoomRunView.d.ts +15 -0
  44. package/lib/types/client/RoomRunView.js +50 -0
  45. package/lib/types/client/RoomSpeechView.d.ts +18 -0
  46. package/lib/types/client/RoomSpeechView.js +62 -0
  47. package/lib/types/client/RoomStatsLine.d.ts +61 -0
  48. package/lib/types/client/RoomStatsLine.js +138 -0
  49. package/lib/types/client/RoomTaskLineView.d.ts +13 -0
  50. package/lib/types/client/RoomTaskLineView.js +21 -0
  51. package/lib/types/client/RoomTodoStrip.d.ts +24 -0
  52. package/lib/types/client/RoomTodoStrip.js +64 -0
  53. package/lib/types/client/format.d.ts +31 -0
  54. package/lib/types/client/format.js +48 -0
  55. package/lib/types/client/icons.d.ts +27 -0
  56. package/lib/types/client/icons.js +11 -0
  57. package/lib/types/client/index.d.ts +39 -0
  58. package/lib/types/client/index.js +471 -0
  59. package/lib/types/client/locales.d.ts +422 -0
  60. package/lib/types/client/locales.js +413 -0
  61. package/lib/types/client/member-color.d.ts +13 -0
  62. package/lib/types/client/member-color.js +22 -0
  63. package/lib/types/client/name-pool.d.ts +17 -0
  64. package/lib/types/client/name-pool.js +25 -0
  65. package/lib/types/client/nodes.d.ts +136 -0
  66. package/lib/types/client/nodes.js +212 -0
  67. package/lib/types/client/preset-visibility.d.ts +91 -0
  68. package/lib/types/client/preset-visibility.js +113 -0
  69. package/lib/types/client/request-ids.d.ts +7 -0
  70. package/lib/types/client/request-ids.js +21 -0
  71. package/lib/types/client/room-store.d.ts +137 -0
  72. package/lib/types/client/room-store.js +244 -0
  73. package/lib/types/client/slots.d.ts +319 -0
  74. package/lib/types/client/slots.js +1 -0
  75. package/lib/types/deliveries.d.ts +5 -0
  76. package/lib/types/deliveries.js +20 -0
  77. package/lib/types/dispatch.d.ts +105 -0
  78. package/lib/types/dispatch.js +467 -0
  79. package/lib/types/index.d.ts +301 -0
  80. package/lib/types/index.js +1200 -0
  81. package/lib/types/invariant.d.ts +16 -0
  82. package/lib/types/invariant.js +24 -0
  83. package/lib/types/journal.d.ts +117 -0
  84. package/lib/types/journal.js +434 -0
  85. package/lib/types/plan-service.d.ts +34 -0
  86. package/lib/types/plan-service.js +266 -0
  87. package/lib/types/plan.d.ts +168 -0
  88. package/lib/types/plan.js +494 -0
  89. package/lib/types/tool.d.ts +56 -0
  90. package/lib/types/tool.js +389 -0
  91. package/lib/types/types.d.ts +813 -0
  92. package/lib/types/types.js +1 -0
  93. package/locale/en.json +6 -0
  94. package/locale/zh.json +6 -0
  95. package/package.json +131 -0
@@ -0,0 +1,134 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The room composer's main-agent model picker: our own trigger over the
4
+ * official per-session ModelDirectory (ui-model-selection's public
5
+ * `ctx.modelDirectories` service), because the official model seat
6
+ * (`conversation.input.model`) rides the hidden InputBar fallback and slots
7
+ * are single-owner — no cross-plugin API re-hosts it. The room's bare-message
8
+ * path IS the session's root agent, so this directory's `select()` writes
9
+ * exactly what the official composer seat switches (the durable per-session
10
+ * selection through the session controller's selectModel remote); @-addressed
11
+ * member dispatches ride room's own submit remote and are untouched. The
12
+ * trigger and the two-level menu mirror the official ModelSelect (figma
13
+ * 496:26454): a root pane of Model / Effort cells drilling into the
14
+ * provider-grouped model list and the current model's effort levels. The menu
15
+ * opens upward absolutely from the picker (the composer sits at the
16
+ * conversation's bottom) rather than portaled — the takeover card has no
17
+ * overflow clip above the row.
18
+ */
19
+ import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
20
+ import { IconCheckOutlineMedium, IconChevronDownOutlineMedium, IconChevronRightOutlineMedium } from './icons.js';
21
+ import css from './RoomModelPicker.module.css';
22
+ /**
23
+ * Render the main-agent model seat: trigger (current model name + effort in
24
+ * the caption tone + chevron) and, while open, the two-level menu.
25
+ * @param props - the shared directory, the error sink, and the locale seat.
26
+ * @returns the picker.
27
+ */
28
+ export function RoomModelPicker({ directory, onError, t }) {
29
+ const state = useSyncExternalStore(fn => directory.store.subscribe(fn), () => directory.store.getSnapshot());
30
+ const [open, setOpen] = useState(false);
31
+ const [pane, setPane] = useState('root');
32
+ const rootRef = useRef(null);
33
+ const choices = useMemo(() => state.groups.flatMap(group => group.models.map(model => ({ group, model }))), [state.groups]);
34
+ const currentChoice = choices.find(choice => choice.group.id === state.current?.provider && choice.model.id === state.current.model);
35
+ const reasoning = currentChoice?.model.reasoning;
36
+ const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
37
+ const effortLabel = reasoning === undefined
38
+ ? undefined
39
+ : effectiveEffort === undefined
40
+ ? t('composer.model.effortDefault')
41
+ : reasoning.efforts.find(level => level.id === effectiveEffort)?.name ?? effectiveEffort;
42
+ const busy = state.status === 'selecting';
43
+ // Click-outside closes the dropdown (the member composer's picker pattern);
44
+ // Escape backs out of a drilled pane first, then closes.
45
+ useEffect(() => {
46
+ if (!open)
47
+ return;
48
+ const onPointerDown = (event) => {
49
+ if (rootRef.current?.contains(event.target) === true)
50
+ return;
51
+ setOpen(false);
52
+ setPane('root');
53
+ };
54
+ const onKeyDown = (event) => {
55
+ if (event.key !== 'Escape')
56
+ return;
57
+ if (pane !== 'root')
58
+ setPane('root');
59
+ else
60
+ setOpen(false);
61
+ };
62
+ document.addEventListener('mousedown', onPointerDown);
63
+ document.addEventListener('keydown', onKeyDown);
64
+ return () => {
65
+ document.removeEventListener('mousedown', onPointerDown);
66
+ document.removeEventListener('keydown', onKeyDown);
67
+ };
68
+ }, [open, pane]);
69
+ const show = () => {
70
+ setPane('root');
71
+ setOpen(true);
72
+ // The official seat's show(): ensure the shared advisory catalog is
73
+ // loaded; failures land on the store and render as the in-menu status.
74
+ void directory.load().catch(() => { });
75
+ };
76
+ const settle = (promise) => {
77
+ void promise.then((result) => {
78
+ // alpha.2 resolves a RemoteResult whose `ok` carries the outcome;
79
+ // 0.1.5's void resolution (any non-Result value) reads as success.
80
+ if (typeof result === 'object' && result !== null && 'ok' in result && !result.ok) {
81
+ onError(t('composer.model.failed'));
82
+ return;
83
+ }
84
+ setOpen(false);
85
+ setPane('root');
86
+ }, () => {
87
+ onError(t('composer.model.failed'));
88
+ });
89
+ };
90
+ const choose = (selection) => {
91
+ if (state.current?.provider === selection.provider && state.current.model === selection.model) {
92
+ setOpen(false);
93
+ setPane('root');
94
+ return;
95
+ }
96
+ settle(directory.select(selection));
97
+ };
98
+ const chooseEffort = (effort) => {
99
+ if (state.current === null || effectiveEffort === effort) {
100
+ setOpen(false);
101
+ setPane('root');
102
+ return;
103
+ }
104
+ settle(directory.select({
105
+ provider: state.current.provider,
106
+ model: state.current.model,
107
+ ...effort === undefined ? {} : { reasoningEffort: effort },
108
+ }));
109
+ };
110
+ const waiting = state.current === null && state.status === 'loading';
111
+ const modelLabel = waiting
112
+ ? t('composer.model.loading')
113
+ : currentChoice?.model.name
114
+ ?? (state.current === null ? t('composer.model.select') : `${state.current.provider}/${state.current.model}`);
115
+ return (_jsxs("div", { className: css.root, ref: rootRef, children: [_jsxs("button", { type: "button", className: css.trigger, "aria-label": t('composer.model.picker'), "aria-haspopup": "menu", "aria-expanded": open, title: effortLabel === undefined ? modelLabel : `${modelLabel} · ${effortLabel}`, onClick: () => {
116
+ if (open) {
117
+ setOpen(false);
118
+ setPane('root');
119
+ }
120
+ else {
121
+ show();
122
+ }
123
+ }, children: [_jsx("span", { className: css.triggerLabel, children: modelLabel }), effortLabel !== undefined && _jsx("span", { className: css.triggerEffort, children: effortLabel }), _jsx(IconChevronDownOutlineMedium, { className: open ? css.chevronOpen : css.chevron })] }), open && (_jsxs("div", { className: css.menu, role: "menu", "aria-label": t('composer.model.picker'), "aria-busy": state.status === 'loading' || busy, children: [pane === 'root' && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", role: "menuitem", className: css.cell, onClick: () => { setPane('model'); }, children: [_jsx("span", { className: css.cellLabel, children: t('composer.model.menu.model') }), _jsx("span", { className: css.cellValue, children: modelLabel }), _jsx(IconChevronRightOutlineMedium, { className: css.cellChevron })] }), reasoning !== undefined && (_jsxs("button", { type: "button", role: "menuitem", className: css.cell, onClick: () => { setPane('effort'); }, children: [_jsx("span", { className: css.cellLabel, children: t('composer.model.menu.effort') }), _jsx("span", { className: css.cellValue, children: effortLabel }), _jsx(IconChevronRightOutlineMedium, { className: css.cellChevron })] }))] })), pane === 'model' && (_jsxs(_Fragment, { children: [state.status === 'loading' && (_jsx("div", { className: css.status, children: t('composer.model.status.loading') })), _jsx("div", { className: css.groups, children: state.groups.map(group => (_jsxs("section", { role: "group", "aria-label": group.name, className: css.group, children: [_jsx("div", { className: css.groupTitle, children: group.name }), group.models.map((model) => {
124
+ const selected = state.current?.provider === group.id && state.current.model === model.id;
125
+ return (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": selected, className: css.option, title: model.name, disabled: busy, onClick: () => { choose({ provider: group.id, model: model.id }); }, children: [_jsx("span", { className: css.modelName, children: model.name }), _jsx("span", { className: css.check, children: selected ? _jsx(IconCheckOutlineMedium, {}) : null })] }, model.id));
126
+ })] }, group.id))) }), state.status === 'ready' && choices.length === 0 && (_jsx("div", { className: css.status, children: t('composer.model.empty') }))] })), pane === 'effort' && (reasoning === undefined
127
+ ? _jsx("div", { className: css.status, children: t('composer.model.emptyEfforts') })
128
+ : [
129
+ ...reasoning.defaultEffort === undefined
130
+ ? [{ key: 'provider-default', effort: undefined, label: t('composer.model.effortDefault') }]
131
+ : [],
132
+ ...reasoning.efforts.map(level => ({ key: `effort:${level.id}`, effort: level.id, label: level.name })),
133
+ ].map(level => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": effectiveEffort === level.effort, className: css.option, disabled: busy, onClick: () => { chooseEffort(level.effort); }, children: [_jsx("span", { className: css.modelName, children: level.label }), _jsx("span", { className: css.check, children: effectiveEffort === level.effort ? _jsx(IconCheckOutlineMedium, {}) : null })] }, level.key))))] }))] }));
134
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { RoomPlan } from '../plan.ts';
3
+ import type { RoomMember } from '../types.ts';
4
+ import type { RoomComposerProps, RoomMutationOutcome } from './slots.ts';
5
+ type PlanView = Omit<RoomPlan, 'requests'>;
6
+ export interface RoomPlanViewProps {
7
+ plan?: PlanView | undefined;
8
+ members: readonly RoomMember[];
9
+ command: (json: string) => Promise<RoomMutationOutcome>;
10
+ openSession?: ((id: string) => void) | undefined;
11
+ stopMember?: ((name: string) => void) | undefined;
12
+ t: RoomComposerProps['t'];
13
+ }
14
+ /** Goal review is optional room chrome; ordinary chat never opens this form. */
15
+ export declare function RoomPlanView({ plan, members, command, openSession, stopMember, t }: RoomPlanViewProps): ReactNode;
16
+ export {};
17
+ //# sourceMappingURL=RoomPlanView.d.ts.map
@@ -0,0 +1,88 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { RoomRequestIds } from './request-ids.js';
4
+ import css from './RoomPlanView.module.css';
5
+ /** Goal review is optional room chrome; ordinary chat never opens this form. */
6
+ export function RoomPlanView({ plan, members, command, openSession, stopMember, t }) {
7
+ const [creating, setCreating] = useState(false);
8
+ const [objective, setObjective] = useState('');
9
+ const [execute, setExecute] = useState(false);
10
+ const [busy, setBusy] = useState(false);
11
+ const [error, setError] = useState(null);
12
+ const [reason, setReason] = useState('');
13
+ const [references, setReferences] = useState('');
14
+ const [artifacts, setArtifacts] = useState('');
15
+ const [reviewTask, setReviewTask] = useState(null);
16
+ const [goalControls, setGoalControls] = useState(false);
17
+ const [parallel, setParallel] = useState(plan?.budget.maxParallel ?? 2);
18
+ const [attempts, setAttempts] = useState(plan?.budget.maxAttempts ?? 12);
19
+ const [perTask, setPerTask] = useState(plan?.budget.maxAttemptsPerTask ?? 3);
20
+ const [minutes, setMinutes] = useState((plan?.budget.maxActiveMs ?? 1800000) / 60000);
21
+ useEffect(() => {
22
+ if (plan === undefined)
23
+ return;
24
+ setParallel(plan.budget.maxParallel);
25
+ setAttempts(plan.budget.maxAttempts);
26
+ setPerTask(plan.budget.maxAttemptsPerTask);
27
+ setMinutes(plan.budget.maxActiveMs / 60000);
28
+ }, [plan?.id, plan?.budget.maxParallel, plan?.budget.maxAttempts, plan?.budget.maxAttemptsPerTask, plan?.budget.maxActiveMs]);
29
+ const ids = useRef(new RoomRequestIds());
30
+ const revision = useRef(undefined);
31
+ const lines = (value) => value.split('\n').map(line => line.trim()).filter(Boolean);
32
+ const evidence = { summary: reason.trim(), references: lines(references), artifacts: lines(artifacts) };
33
+ const hasEvidence = evidence.summary !== '' && evidence.references.length > 0;
34
+ const status = (value) => t(`plan.status.${value}`);
35
+ const run = async (action) => {
36
+ if (busy)
37
+ return;
38
+ const requestId = ids.current.forInput('plan', JSON.stringify(action));
39
+ if (revision.current?.id !== requestId)
40
+ revision.current = { id: requestId, revision: plan?.revision ?? 0 };
41
+ setBusy(true);
42
+ setError(null);
43
+ try {
44
+ const result = await command(JSON.stringify({ ...action, ...action.action === 'create' ? { id: `goal-${requestId}` } : {}, requestId, expectedRevision: revision.current.revision }));
45
+ if (!result.ok) {
46
+ if (result.message.includes('revision changed'))
47
+ ids.current.complete('plan', requestId);
48
+ setError(result.message);
49
+ return;
50
+ }
51
+ ids.current.complete('plan', requestId);
52
+ setReviewTask(null);
53
+ setReason('');
54
+ setReferences('');
55
+ setArtifacts('');
56
+ if (action.action === 'create') {
57
+ setCreating(false);
58
+ setObjective('');
59
+ }
60
+ }
61
+ catch {
62
+ setError(t('composer.error.generic'));
63
+ }
64
+ finally {
65
+ setBusy(false);
66
+ }
67
+ };
68
+ const budget = { maxParallel: parallel, maxAttempts: attempts, maxAttemptsPerTask: perTask, maxActiveMs: Math.round(minutes * 60000) };
69
+ const budgetFields = _jsxs(_Fragment, { children: [_jsx("p", { children: t('plan.budgetHelp') }), _jsxs("div", { className: css.budget, children: [_jsxs("label", { children: [t('plan.parallel'), _jsx("input", { type: "number", min: 1, max: 8, value: parallel, onChange: event => setParallel(Number(event.target.value)) })] }), _jsxs("label", { children: [t('plan.attempts'), _jsx("input", { type: "number", min: 1, max: 100, value: attempts, onChange: event => setAttempts(Number(event.target.value)) })] }), _jsxs("label", { children: [t('plan.perTask'), _jsx("input", { type: "number", min: 1, max: 10, value: perTask, onChange: event => setPerTask(Number(event.target.value)) })] }), _jsxs("label", { children: [t('plan.minutes'), _jsx("input", { type: "number", min: 1, max: 1440, value: minutes, onChange: event => setMinutes(Number(event.target.value)) })] })] })] });
70
+ const evidenceFields = _jsxs("div", { className: css.fields, children: [_jsxs("label", { children: [t('plan.reason'), _jsx("textarea", { value: reason, onChange: event => setReason(event.target.value) })] }), _jsxs("label", { children: [t('plan.references'), _jsx("textarea", { value: references, onChange: event => setReferences(event.target.value) })] }), _jsxs("label", { children: [t('plan.artifacts'), _jsx("textarea", { value: artifacts, onChange: event => setArtifacts(event.target.value) })] })] });
71
+ const list = (values) => _jsx("ul", { children: values.map((value, index) => _jsx("li", { children: /^https?:\/\//i.test(value)
72
+ ? _jsx("a", { href: value, target: "_blank", rel: "noreferrer", children: value }) : _jsx("span", { className: css.reference, children: value }) }, `${index}:${value}`)) });
73
+ const attemptView = (attempt) => _jsxs("details", { className: css.attempt, children: [_jsxs("summary", { children: [t('plan.attempt'), " ", attempt.number, " \u00B7 ", status(attempt.status)] }), _jsx("div", { className: css.reference, children: attempt.id }), attempt.error && _jsx("p", { role: "status", children: attempt.error }), attempt.submission && _jsxs(_Fragment, { children: [_jsx("p", { className: css.text, children: attempt.submission.summary }), list(attempt.submission.references), list(attempt.submission.artifacts)] }), attempt.review && _jsxs("p", { children: [attempt.review.by, " \u00B7 ", attempt.review.reason] }), attempt.retry && _jsxs("p", { children: [attempt.retry.by, " \u00B7 ", attempt.retry.reason] })] }, attempt.id);
74
+ const taskView = (task) => {
75
+ const owner = members.find(member => member.id === task.ownerMemberId);
76
+ const attempt = task.attempts.at(-1);
77
+ const uncertain = attempt?.status === 'uncertain';
78
+ const reviewable = attempt?.status === 'submitted' && attempt.settledAt !== undefined;
79
+ return _jsxs("details", { id: `room-plan-task-${task.id}`, className: task.parentId === undefined ? css.task : css.childTask, children: [_jsxs("summary", { children: [task.title, " \u00B7 ", task.kind === 'group' ? t('plan.group') : status(task.status), owner && ` · ${owner.name}`] }), _jsx("p", { className: css.text, children: task.instruction }), task.parentId && _jsxs("p", { children: [t('plan.parent'), ": ", plan?.tasks.find(parent => parent.id === task.parentId)?.title ?? task.parentId] }), task.criteria.length > 0 && _jsxs(_Fragment, { children: [_jsx("strong", { children: t('plan.criteria') }), list(task.criteria)] }), task.inputRefs.length > 0 && _jsxs(_Fragment, { children: [_jsx("strong", { children: t('plan.inputs') }), list(task.inputRefs)] }), task.artifactPaths.length > 0 && _jsxs(_Fragment, { children: [_jsx("strong", { children: t('plan.expectedArtifacts') }), list(task.artifactPaths)] }), task.dependsOn.length > 0 && _jsxs("div", { children: [t('plan.dependencies'), ": ", task.dependsOn.map(id => _jsx("a", { className: css.dependency, href: `#room-plan-task-${id}`, children: plan?.tasks.find(task => task.id === id)?.title ?? id }, id))] }), _jsxs("div", { className: css.actions, children: [owner?.childSessionId && openSession && _jsx("button", { type: "button", onClick: () => openSession(owner.childSessionId), children: t('plan.session') }), owner && stopMember && attempt?.startedAt !== undefined && attempt.settledAt === undefined && ['running', 'submitted'].includes(attempt.status) && _jsx("button", { type: "button", onClick: () => stopMember(owner.name), children: t('composer.stop') }), (uncertain || (!closed && (reviewable || task.status === 'failed'))) && _jsx("button", { type: "button", onClick: () => { setReviewTask(task.id); setGoalControls(false); setReason(''); setReferences(''); setArtifacts(''); }, children: t(uncertain ? 'plan.reconcile' : reviewable ? 'plan.review' : 'plan.retry') })] }), reviewTask === task.id && attempt && _jsxs("div", { className: css.review, children: [evidenceFields, _jsxs("div", { className: css.actions, children: [reviewable && _jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: busy || !hasEvidence, onClick: () => { void run({ action: 'review', taskId: task.id, attemptId: attempt.id, decision: 'accepted', reason, references: lines(references) }); }, children: t('plan.accept') }), _jsx("button", { type: "button", disabled: busy || !hasEvidence, onClick: () => { void run({ action: 'review', taskId: task.id, attemptId: attempt.id, decision: 'rework', reason, references: lines(references) }); }, children: t('plan.rework') })] }), uncertain && _jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: busy || !hasEvidence, onClick: () => { void run({ action: 'reconcile', taskId: task.id, attemptId: attempt.id, outcome: 'submitted', evidence }); }, children: t('plan.reconcileSubmitted') }), _jsx("button", { type: "button", disabled: busy || !hasEvidence, onClick: () => { void run({ action: 'reconcile', taskId: task.id, attemptId: attempt.id, outcome: 'failed', evidence }); }, children: t('plan.reconcileFailed') })] }), !uncertain && !reviewable && task.status === 'failed' && _jsx("button", { type: "button", disabled: busy || reason.trim() === '', onClick: () => { void run({ action: 'retry', taskId: task.id, reason }); }, children: t('plan.retry') })] })] }), task.attempts.map(attemptView)] }, task.id);
80
+ };
81
+ const leaf = plan?.tasks.filter(task => task.kind === 'task' && task.status !== 'cancelled') ?? [];
82
+ const accepted = leaf.filter(task => task.status === 'accepted').length;
83
+ const closed = plan !== undefined && ['completed', 'cancelled'].includes(plan.status);
84
+ const createForm = _jsxs("div", { className: css.fields, children: [_jsxs("label", { children: [t('plan.objective'), _jsx("textarea", { value: objective, onChange: event => setObjective(event.target.value) })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: execute, onChange: event => setExecute(event.target.checked) }), t('plan.execute')] }), budgetFields, _jsx("button", { type: "button", disabled: busy || objective.trim() === '', onClick: () => { void run({ action: 'create', objective: objective.trim(), mode: execute ? 'execute' : 'draft', budget }); }, children: t(execute ? 'plan.start' : 'plan.saveDraft') })] });
85
+ return _jsxs("details", { className: css.root, "data-testid": "room-plan", children: [_jsx("summary", { children: plan === undefined ? t('plan.create') : `${plan.objective} · ${status(plan.status)} · ${t('plan.acceptedCount', { done: accepted, total: leaf.length })}` }), error && _jsx("p", { role: "alert", children: error }), plan === undefined ? createForm : _jsxs(_Fragment, { children: [_jsxs("p", { children: [t('plan.revision'), ": ", plan.revision, " \u00B7 ", t('plan.attempts'), ": ", plan.tasks.reduce((count, task) => count + task.attempts.length, 0), "/", plan.budget.maxAttempts, " \u00B7 ", t('plan.parallel'), ": ", plan.budget.maxParallel] }), plan.reason && _jsx("p", { role: "status", children: plan.reason }), !closed && _jsx("div", { className: css.actions, children: plan.status === 'running' ? _jsx("button", { type: "button", disabled: busy, onClick: () => { void run({ action: 'pause', goalId: plan.id, reason: t('plan.humanPause') }); }, children: t('plan.pause') })
86
+ : _jsx("button", { type: "button", disabled: busy, onClick: () => { void run({ action: 'resume' }); }, children: t('plan.resume') }) }), plan.stages.map(stage => _jsxs("details", { className: css.stage, open: true, children: [_jsx("summary", { children: stage.title }), plan.tasks.filter(task => task.stageId === stage.id).map(taskView)] }, stage.id)), plan.completion && _jsxs(_Fragment, { children: [_jsx("p", { children: plan.completion.summary }), list(plan.completion.references), list(plan.completion.artifacts)] }), !closed && _jsxs("details", { className: css.review, open: goalControls, onToggle: event => { const open = event.currentTarget.open; setGoalControls(open); if (open)
87
+ setReviewTask(null); }, children: [_jsx("summary", { children: t('plan.goalControls') }), goalControls && _jsxs(_Fragment, { children: [budgetFields, _jsx("button", { type: "button", disabled: busy, onClick: () => { void run({ action: 'budget', budget }); }, children: t('plan.updateBudget') }), evidenceFields, _jsxs("div", { className: css.actions, children: [_jsx("button", { type: "button", disabled: busy || !hasEvidence || leaf.length === 0 || accepted !== leaf.length, onClick: () => { void run({ action: 'complete', evidence }); }, children: t('plan.complete') }), _jsx("button", { type: "button", disabled: busy || reason.trim() === '', onClick: () => { void run({ action: 'cancel', reason }); }, children: t('plan.cancel') })] })] })] }), closed && _jsx("button", { type: "button", onClick: () => setCreating(value => !value), children: t('plan.create') }), closed && creating && createForm] })] });
88
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The room session's queue strip — a read-only echo of the official
3
+ * QueueDock (harness ui-conversation queue/QueueDock.tsx), whose
4
+ * `conversation.input.dock` seat hides with the InputBar fallback under the
5
+ * composer takeover. The queue itself is NOT re-implemented: a bare message
6
+ * sent while the main agent's turn runs goes through the official input
7
+ * machine's `submit()`, whose busy admission enqueues exactly as the
8
+ * official bar's does — this strip only makes the queued rows visible again
9
+ * (count header + previews; one item renders its preview directly).
10
+ * Read-only by design: per-row mutations (edit/remove/steer) stay with the
11
+ * hidden official dock.
12
+ */
13
+ import { type ReactNode } from 'react';
14
+ import type { RoomComposerProps } from './slots.ts';
15
+ /** One queued message, pre-selected by the composer from the inbox projection (the legacy snapshot queue on 0.1.5). */
16
+ export interface RoomQueueItem {
17
+ readonly id: string;
18
+ readonly preview: string;
19
+ }
20
+ /** Props: the still-queued rows plus the room locale seat. */
21
+ export interface RoomQueueStripProps {
22
+ readonly items: readonly RoomQueueItem[];
23
+ readonly t: RoomComposerProps['t'];
24
+ }
25
+ export declare function RoomQueueStrip({ items, t }: RoomQueueStripProps): ReactNode;
26
+ //# sourceMappingURL=RoomQueueStrip.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The room session's queue strip — a read-only echo of the official
4
+ * QueueDock (harness ui-conversation queue/QueueDock.tsx), whose
5
+ * `conversation.input.dock` seat hides with the InputBar fallback under the
6
+ * composer takeover. The queue itself is NOT re-implemented: a bare message
7
+ * sent while the main agent's turn runs goes through the official input
8
+ * machine's `submit()`, whose busy admission enqueues exactly as the
9
+ * official bar's does — this strip only makes the queued rows visible again
10
+ * (count header + previews; one item renders its preview directly).
11
+ * Read-only by design: per-row mutations (edit/remove/steer) stay with the
12
+ * hidden official dock.
13
+ */
14
+ import { useId, useState } from 'react';
15
+ import { IconChevronDownOutlineMedium, IconChevronUpOutlineMedium, IconQueueOutlineMedium } from './icons.js';
16
+ import css from './RoomQueueStrip.module.css';
17
+ export function RoomQueueStrip({ items, t }) {
18
+ const [collapsed, setCollapsed] = useState(true);
19
+ const listId = useId();
20
+ if (items.length === 0)
21
+ return null;
22
+ // Single-item strip: no count header, the row itself carries the glyph
23
+ // (the official QueueDock's single-item posture).
24
+ if (items.length === 1) {
25
+ return (_jsx("div", { className: css.root, "data-testid": "room-queue-strip", children: _jsxs("div", { className: css.header, role: "status", children: [_jsx("span", { className: css.lead, "aria-hidden": true, children: _jsx(IconQueueOutlineMedium, {}) }), _jsx("span", { className: css.preview, children: items[0].preview })] }) }));
26
+ }
27
+ return (_jsxs("div", { className: css.root, "data-testid": "room-queue-strip", children: [_jsxs("button", { type: "button", className: css.header, "aria-controls": listId, "aria-expanded": !collapsed, onClick: () => { setCollapsed(v => !v); }, children: [_jsx("span", { className: css.lead, "aria-hidden": true, children: _jsx(IconQueueOutlineMedium, {}) }), _jsx("span", { className: css.count, children: t('queue.count', { n: items.length }) }), _jsx("span", { className: css.chevron, "aria-hidden": true, children: collapsed ? _jsx(IconChevronUpOutlineMedium, {}) : _jsx(IconChevronDownOutlineMedium, {}) })] }), !collapsed && (_jsx("ul", { id: listId, className: css.list, children: items.map(item => (_jsx("li", { className: css.row, children: _jsx("span", { className: css.preview, children: item.preview }) }, item.id))) }))] }));
28
+ }
@@ -0,0 +1,11 @@
1
+ import type { RoomDelivery, RoomMember } from '../types.ts';
2
+ import type { RoomComposerProps } from './slots.ts';
3
+ /** Ordinary chat needs the same explicit recovery boundary as formal goals. */
4
+ export declare function RoomRecoveryView({ deliveries, members, reconcile, openSession, t }: {
5
+ deliveries: readonly RoomDelivery[];
6
+ members: readonly RoomMember[];
7
+ reconcile: NonNullable<RoomComposerProps['reconcileDelivery']>;
8
+ openSession: RoomComposerProps['openPlanSession'];
9
+ t: RoomComposerProps['t'];
10
+ }): import("react").JSX.Element | null;
11
+ //# sourceMappingURL=RoomRecoveryView.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import css from './RoomPlanView.module.css';
4
+ /** Ordinary chat needs the same explicit recovery boundary as formal goals. */
5
+ export function RoomRecoveryView({ deliveries, members, reconcile, openSession, t }) {
6
+ const [selected, setSelected] = useState(null);
7
+ const [evidence, setEvidence] = useState('');
8
+ const [busy, setBusy] = useState(false);
9
+ const [error, setError] = useState(null);
10
+ const rows = deliveries.filter(row => row.status === 'uncertain' && row.plan?.taskId === undefined);
11
+ if (rows.length === 0)
12
+ return null;
13
+ const resolve = async (id, outcome) => {
14
+ if (busy || !evidence.trim())
15
+ return;
16
+ setBusy(true);
17
+ setError(null);
18
+ try {
19
+ const result = await reconcile(id, outcome, evidence.trim());
20
+ if (result.ok) {
21
+ setSelected(null);
22
+ setEvidence('');
23
+ }
24
+ else
25
+ setError(result.message);
26
+ }
27
+ catch {
28
+ setError(t('composer.error.generic'));
29
+ }
30
+ finally {
31
+ setBusy(false);
32
+ }
33
+ };
34
+ return _jsxs("details", { className: css.root, "data-testid": "room-recovery", open: true, children: [_jsx("summary", { children: t('recovery.title') }), _jsx("p", { children: t('recovery.help') }), error && _jsx("p", { role: "alert", children: error }), rows.map(row => {
35
+ const member = members.find(member => member.id === row.memberId);
36
+ return _jsxs("div", { className: css.task, children: [_jsx("strong", { children: member?.name ?? row.memberId }), _jsx("p", { className: css.text, children: row.text }), _jsxs("div", { className: css.actions, children: [member?.childSessionId && openSession && _jsx("button", { type: "button", onClick: () => openSession(member.childSessionId), children: t('plan.session') }), _jsx("button", { type: "button", disabled: busy, onClick: () => { setSelected(row.id); setEvidence(''); setError(null); }, children: t('plan.reconcile') })] }), selected === row.id && _jsxs("div", { className: css.fields, children: [_jsxs("label", { children: [t('plan.reason'), _jsx("textarea", { value: evidence, onChange: event => setEvidence(event.target.value) })] }), _jsxs("div", { className: css.actions, children: [_jsx("button", { type: "button", disabled: busy || !evidence.trim(), onClick: () => { void resolve(row.id, 'done'); }, children: t('recovery.done') }), _jsx("button", { type: "button", disabled: busy || !evidence.trim(), onClick: () => { void resolve(row.id, 'cancelled'); }, children: t('recovery.cancelled') })] })] })] }, row.id);
37
+ })] });
38
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Relay gate node ('room-relay' keyed renderer): a member-to-member
3
+ * notification waiting at the human gate — a dim RoomEventView-register row
4
+ * (`⇢ ada → bill: 摘要…`) plus [确认派发] [忽略] while pending. Resolutions
5
+ * fold in place through the journal (the Definition updates the row), so a
6
+ * confirmed/dismissed/sent relay reads as a plain dim line with its outcome.
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ import type { RoomRelayViewProps } from './slots.ts';
10
+ /** The relay gate row. */
11
+ export declare function RoomRelayView({ node, confirmRelay, dismissRelay, t }: RoomRelayViewProps): ReactNode;
12
+ //# sourceMappingURL=RoomRelayView.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import css from './RoomRelayView.module.css';
3
+ /** The relay gate row. */
4
+ export function RoomRelayView({ node, confirmRelay, dismissRelay, t }) {
5
+ const data = node.data;
6
+ let suffix = '';
7
+ switch (data.state) {
8
+ case 'confirmed':
9
+ suffix = t('relay.state.confirmed');
10
+ break;
11
+ case 'dismissed':
12
+ suffix = t('relay.state.dismissed');
13
+ break;
14
+ case 'sent':
15
+ suffix = t('relay.state.sent');
16
+ break;
17
+ case 'pending': break;
18
+ }
19
+ return (_jsxs("div", { className: css.row, "data-state": data.state, children: [_jsxs("span", { className: css.text, children: [t('relay.line', { from: data.from, to: data.to, content: data.content }), suffix === '' ? '' : ` ${suffix}`] }), data.state === 'pending' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.action, onClick: () => { void confirmRelay(data.relayId); }, children: t('relay.confirm') }), _jsx("button", { type: "button", className: css.action, onClick: () => { void dismissRelay(data.relayId); }, children: t('relay.dismiss') })] }))] }));
20
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Member run node ('room-run' keyed renderer): a ToolRow-isomorphic 24px
3
+ * line — StateDot +「ada 正在工作… · 12s」+ the running sweep (with a
4
+ * prefers-reduced-motion fallback). The WHOLE row jumps into the member's
5
+ * child session (looked up from the room store's roster — run events carry
6
+ * no handle), and a trailing stop button cancels through the Remote. A
7
+ * failed run stays as a dim error row with the journaled reason (truncated,
8
+ * the full text on hover); done/cancelled are hidden by the Definition and
9
+ * never reach here.
10
+ */
11
+ import { type ReactNode } from 'react';
12
+ import type { RoomRunViewProps } from './slots.ts';
13
+ /** The member run row. */
14
+ export declare function RoomRunView({ node, sessionId, roomStore, openSession, cancelMember, renderMemberOutput, t }: RoomRunViewProps): ReactNode;
15
+ //# sourceMappingURL=RoomRunView.d.ts.map
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Member run node ('room-run' keyed renderer): a ToolRow-isomorphic 24px
4
+ * line — StateDot +「ada 正在工作… · 12s」+ the running sweep (with a
5
+ * prefers-reduced-motion fallback). The WHOLE row jumps into the member's
6
+ * child session (looked up from the room store's roster — run events carry
7
+ * no handle), and a trailing stop button cancels through the Remote. A
8
+ * failed run stays as a dim error row with the journaled reason (truncated,
9
+ * the full text on hover); done/cancelled are hidden by the Definition and
10
+ * never reach here.
11
+ */
12
+ import { useEffect, useState, useSyncExternalStore } from 'react';
13
+ import { StateDot, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
14
+ import { IconStopFillMedium } from './icons.js';
15
+ import { formatDurationMs } from './format.js';
16
+ import css from './RoomRunView.module.css';
17
+ /** The member run row. */
18
+ export function RoomRunView({ node, sessionId, roomStore, openSession, cancelMember, renderMemberOutput, t }) {
19
+ const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
20
+ const recovered = state?.runs.find(run => run.member === node.data.member && run.startedAt === node.data.startedAt);
21
+ const data = node.data.state === 'running' && recovered !== undefined ? { ...node.data, ...recovered } : node.data;
22
+ const [now, setNow] = useState(() => Date.now());
23
+ useEffect(() => {
24
+ if (data.state !== 'running')
25
+ return undefined;
26
+ const timer = setInterval(() => { setNow(Date.now()); }, 1000);
27
+ return () => { clearInterval(timer); };
28
+ }, [data.state]);
29
+ // The run events carry no delegation handle; the roster record does.
30
+ const childSessionId = state?.members.find(entry => entry.name === data.member)?.childSessionId;
31
+ // The Definition hides done/cancelled; guard the same states here.
32
+ if (data.state === 'done' || data.state === 'cancelled')
33
+ return null;
34
+ const failed = data.state === 'failed';
35
+ const elapsed = failed ? data.elapsedMs : Math.max(0, now - data.startedAt);
36
+ const jump = () => {
37
+ if (childSessionId !== undefined)
38
+ openSession(childSessionId);
39
+ };
40
+ const onKeyDown = (event) => {
41
+ if (event.key === 'Enter' || event.key === ' ') {
42
+ event.preventDefault();
43
+ jump();
44
+ }
45
+ };
46
+ return (_jsxs("div", { children: [_jsxs("div", { className: css.run, "data-state": failed ? 'failed' : 'running', role: "button", tabIndex: 0, onClick: jump, onKeyDown: onKeyDown, children: [_jsx(StateDot, { state: failed ? 'error' : 'ongoing' }), _jsx("span", { className: css.title, children: failed ? t('run.failed', { member: data.member }) : t('run.working', { member: data.member }) }), _jsxs("span", { className: css.elapsed, "aria-hidden": true, children: ["\u00B7 ", elapsed === undefined ? t('run.durationUnknown') : formatDurationMs(elapsed)] }), failed && data.error !== undefined && (_jsx("span", { className: css.reason, title: data.error, children: data.error })), !failed && (_jsx(Tooltip, { label: t('run.stop'), side: "bottom", children: _jsx("button", { type: "button", className: css.stop, "aria-label": t('run.stop'), onClick: (event) => {
47
+ event.stopPropagation();
48
+ void cancelMember(data.member);
49
+ }, children: _jsx(IconStopFillMedium, {}) }) }))] }), !failed && childSessionId !== undefined && renderMemberOutput?.(childSessionId, data.startedAt)] }));
50
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Member speech node ('room-speech' keyed renderer): a 2px member-color rail
3
+ * bands the whole block (identity row: 16px member-color dot + refChip-styled
4
+ * name capsule + provider in tertiary grey; the unframed full-column markdown
5
+ * body — the official MarkdownText, typographically identical to main-agent
6
+ * speech; and the action row), so one glance tells WHOSE paragraph it is. A
7
+ * body past {@link COLLAPSE_CHARS} starts clamped to ~10 lines under a
8
+ * bg-base fade with an expand/collapse toggle. The action row replicates the
9
+ * official MessageIconActions chrome: copy, child-session jump (when the
10
+ * speech carries its handle), honest duration, and a hover-revealed clock.
11
+ * Deliberately absent: branch (forking the room session has undefined
12
+ * roster/blackboard semantics) and TPS/TTFT (a CLI run has no token stream).
13
+ */
14
+ import { type ReactNode } from 'react';
15
+ import type { RoomSpeechViewProps } from './slots.ts';
16
+ /** The member speech row. */
17
+ export declare function RoomSpeechView({ node, sessionId, roomStore, openSession, t }: RoomSpeechViewProps): ReactNode;
18
+ //# sourceMappingURL=RoomSpeechView.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Member speech node ('room-speech' keyed renderer): a 2px member-color rail
4
+ * bands the whole block (identity row: 16px member-color dot + refChip-styled
5
+ * name capsule + provider in tertiary grey; the unframed full-column markdown
6
+ * body — the official MarkdownText, typographically identical to main-agent
7
+ * speech; and the action row), so one glance tells WHOSE paragraph it is. A
8
+ * body past {@link COLLAPSE_CHARS} starts clamped to ~10 lines under a
9
+ * bg-base fade with an expand/collapse toggle. The action row replicates the
10
+ * official MessageIconActions chrome: copy, child-session jump (when the
11
+ * speech carries its handle), honest duration, and a hover-revealed clock.
12
+ * Deliberately absent: branch (forking the room session has undefined
13
+ * roster/blackboard semantics) and TPS/TTFT (a CLI run has no token stream).
14
+ */
15
+ import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
16
+ import { MarkdownText, Tooltip, writeClipboard } from '@deepseek-ai/dsh-client-ui-primitives';
17
+ import { IconCheckOutlineMedium, IconCopyOutlineMedium, IconLinkOutlineMedium } from './icons.js';
18
+ import { memberColor } from './member-color.js';
19
+ import { formatClock, formatDurationMs } from './format.js';
20
+ import css from './RoomSpeechView.module.css';
21
+ /**
22
+ * Speeches longer than this start collapsed. Chars, not rendered lines: a
23
+ * stable threshold that needs no layout measurement — roughly ten rendered
24
+ * lines of prose.
25
+ */
26
+ const COLLAPSE_CHARS = 600;
27
+ /** Localized Markdown chrome (code-fence copy buttons, footnotes) over the room namespace. */
28
+ function markdownLabels(t) {
29
+ return {
30
+ code: { copyLabel: t('action.copy'), copiedLabel: t('action.copied') },
31
+ footnotes: t('markdown.footnotes'),
32
+ };
33
+ }
34
+ /** The member speech row. */
35
+ export function RoomSpeechView({ node, sessionId, roomStore, openSession, t }) {
36
+ const data = node.data;
37
+ const [copied, setCopied] = useState(false);
38
+ const [expanded, setExpanded] = useState(false);
39
+ const copyTimer = useRef(null);
40
+ useEffect(() => () => {
41
+ if (copyTimer.current !== null)
42
+ clearTimeout(copyTimer.current);
43
+ }, []);
44
+ const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
45
+ const member = state?.members.find(entry => entry.name === data.member);
46
+ const provider = member === undefined
47
+ ? undefined
48
+ : member.kind === 'main-agent' ? t('member.kind.main') : member.provider;
49
+ const child = data.childSessionId;
50
+ const collapsible = data.text.length > COLLAPSE_CHARS;
51
+ const clamped = collapsible && !expanded;
52
+ const labels = useMemo(() => markdownLabels(t), [t]);
53
+ const copy = () => {
54
+ void writeClipboard(data.text).then((ok) => {
55
+ if (!ok)
56
+ return;
57
+ setCopied(true);
58
+ copyTimer.current = setTimeout(() => { setCopied(false); }, 1000);
59
+ });
60
+ };
61
+ return (_jsxs("div", { className: css.speech, "data-time-hover-root": "", style: { borderLeft: `2px solid ${memberColor(data.member)}` }, children: [_jsxs("div", { className: css.identity, children: [_jsx("span", { className: css.dot, style: { background: memberColor(data.member) }, "aria-hidden": true }), _jsx("span", { className: css.chip, children: data.member }), provider !== undefined && provider !== '' && _jsx("span", { className: css.provider, children: provider }), data.interrupted !== undefined && _jsx("span", { className: css.meta, children: t(data.interrupted === 'cancelled' ? 'speech.stopped' : 'speech.failed') })] }), _jsxs("div", { className: clamped ? `${css.body} ${css.clamped}` : css.body, children: [_jsx(MarkdownText, { text: data.text, labels: labels }), clamped && _jsx("div", { className: css.fade, "aria-hidden": true })] }), collapsible && (_jsx("button", { type: "button", className: css.expandToggle, onClick: () => { setExpanded(value => !value); }, children: expanded ? t('speech.collapse') : t('speech.expand') })), _jsxs("div", { className: css.actions, children: [_jsx(Tooltip, { label: copied ? t('action.copied') : t('action.copy'), side: "bottom", children: _jsx("button", { type: "button", className: css.action, "aria-label": copied ? t('action.copied') : t('action.copy'), onClick: copy, children: copied ? _jsx(IconCheckOutlineMedium, {}) : _jsx(IconCopyOutlineMedium, {}) }) }), child !== undefined && (_jsx(Tooltip, { label: t('speech.jump'), side: "bottom", children: _jsx("button", { type: "button", className: css.action, "aria-label": t('speech.jump'), onClick: () => { openSession(child); }, children: _jsx(IconLinkOutlineMedium, {}) }) })), data.durationMs !== undefined && (_jsx("span", { className: css.meta, children: formatDurationMs(data.durationMs) })), _jsx("span", { className: css.time, children: formatClock(data.time) })] })] }));
62
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The room session's stats row — a RoomComposer footer replicating the
3
+ * official StatsLine (harness ui-conversation chat/StatsLine.tsx), which
4
+ * lives inside the InputBar fallback the composer takeover hides. Every
5
+ * figure rides the durable whole-log projections through the framework's
6
+ * `useProjection` seat: `sessionStats` for turn/step counts and wall times,
7
+ * `tokenUsage` for billing. A key with no served value drops its groups;
8
+ * with neither projection served the row does not render at all (silent
9
+ * degrade). Unlike the official row there is no window-fold fallback — the
10
+ * takeover has no business re-deriving what the host already projects.
11
+ *
12
+ * Semantics: the figures are the ROOM SESSION's own — its main agent's
13
+ * turns and billing. CLI members consume in their own child sessions, so
14
+ * this row is never a whole-room aggregate.
15
+ */
16
+ import { type ReactNode } from 'react';
17
+ import type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client';
18
+ import type { TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client';
19
+ import type { RoomComposerProps } from './slots.ts';
20
+ /**
21
+ * Compact token count: 517 / 12.2K / 517K / 1.2M (one decimal under three
22
+ * digits). Copied from the official StatsLine.
23
+ * @param n - token count.
24
+ * @returns display string.
25
+ */
26
+ export declare function formatTokens(n: number): string;
27
+ /**
28
+ * Compact duration: 45.2s under a minute, 2m42s from there on. Copied from
29
+ * the official StatsLine.
30
+ * @param ms - duration in milliseconds.
31
+ * @returns display string.
32
+ */
33
+ export declare function formatDuration(ms: number): string;
34
+ /**
35
+ * Throughput figure: integer at 10+, one decimal below. Copied from the
36
+ * official message-chrome formatTokensPerSecond.
37
+ * @param tps - tokens per second.
38
+ * @returns display number without unit.
39
+ */
40
+ export declare function formatTokensPerSecond(tps: number): string;
41
+ /**
42
+ * Sum the three disjoint prompt-side billing buckets. Copied from the
43
+ * official StatsLine.
44
+ * @param usage - the session's token-usage projection value.
45
+ * @returns billed input tokens.
46
+ */
47
+ export declare function billedInputTokens(usage: TokenUsageProjection): number;
48
+ /**
49
+ * Cache-hit share of prompt-side input over the whole durable log. Copied
50
+ * from the official StatsLine.
51
+ * @param usage - the session's token-usage projection value.
52
+ * @returns rounded integer percent, or null when no input was billed.
53
+ */
54
+ export declare function cacheHitPercent(usage: TokenUsageProjection): number | null;
55
+ /** Props: the projection read seat plus the room locale seat. */
56
+ export interface RoomStatsLineProps {
57
+ readonly useProjection: UseProjection;
58
+ readonly t: RoomComposerProps['t'];
59
+ }
60
+ export declare const RoomStatsLine: import("react").MemoExoticComponent<({ useProjection, t }: RoomStatsLineProps) => ReactNode>;
61
+ //# sourceMappingURL=RoomStatsLine.d.ts.map