@khorsheed/dsh-room 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.en.md +29 -62
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +28 -61
  5. package/lib/client.js +34057 -6483
  6. package/lib/index.js +4045 -730
  7. package/lib/journal-B4FOGO6S.js +482 -0
  8. package/lib/journal-Rid9AAnD.js +485 -0
  9. package/lib/tool.js +112 -166
  10. package/lib/typert.host.js +2517 -363
  11. package/lib/typert.remote-client.d.ts +53 -9
  12. package/lib/typert.remote-client.js +1909 -280
  13. package/lib/types/agent-setup.d.ts +1 -1
  14. package/lib/types/client/InviteAgentAction.js +4 -16
  15. package/lib/types/client/InviteDialog.d.ts +28 -6
  16. package/lib/types/client/InviteDialog.js +76 -12
  17. package/lib/types/client/MemberCard.d.ts +5 -1
  18. package/lib/types/client/MemberCard.js +4 -4
  19. package/lib/types/client/MembersView.d.ts +1 -1
  20. package/lib/types/client/MembersView.js +58 -22
  21. package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
  22. package/lib/types/client/RemoveMemberConfirm.js +85 -0
  23. package/lib/types/client/RoomApprovalView.d.ts +11 -0
  24. package/lib/types/client/RoomApprovalView.js +28 -0
  25. package/lib/types/client/RoomComposer.d.ts +1 -1
  26. package/lib/types/client/RoomComposer.js +228 -37
  27. package/lib/types/client/RoomDecisionView.d.ts +22 -0
  28. package/lib/types/client/RoomDecisionView.js +35 -0
  29. package/lib/types/client/RoomHelpView.d.ts +17 -0
  30. package/lib/types/client/RoomHelpView.js +56 -0
  31. package/lib/types/client/RoomInboxView.d.ts +10 -0
  32. package/lib/types/client/RoomInboxView.js +14 -0
  33. package/lib/types/client/RoomQuestionView.d.ts +13 -0
  34. package/lib/types/client/RoomQuestionView.js +61 -0
  35. package/lib/types/client/RoomRelayView.js +1 -1
  36. package/lib/types/client/RoomRunView.d.ts +3 -1
  37. package/lib/types/client/RoomRunView.js +12 -5
  38. package/lib/types/client/RoomSettingsView.d.ts +19 -0
  39. package/lib/types/client/RoomSettingsView.js +30 -0
  40. package/lib/types/client/RoomSpeechView.d.ts +22 -8
  41. package/lib/types/client/RoomSpeechView.js +94 -11
  42. package/lib/types/client/RoomStatsLine.d.ts +26 -5
  43. package/lib/types/client/RoomStatsLine.js +53 -7
  44. package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
  45. package/lib/types/client/RoomTaskCapsule.js +37 -0
  46. package/lib/types/client/TasksTab.d.ts +41 -0
  47. package/lib/types/client/TasksTab.js +43 -0
  48. package/lib/types/client/TasksView.d.ts +44 -0
  49. package/lib/types/client/TasksView.js +149 -0
  50. package/lib/types/client/TeamView.d.ts +33 -0
  51. package/lib/types/client/TeamView.js +210 -0
  52. package/lib/types/client/at-menu.d.ts +127 -0
  53. package/lib/types/client/at-menu.js +173 -0
  54. package/lib/types/client/draft-chips.d.ts +67 -0
  55. package/lib/types/client/draft-chips.js +95 -0
  56. package/lib/types/client/executions.d.ts +0 -30
  57. package/lib/types/client/executions.js +4 -34
  58. package/lib/types/client/icons-local.d.ts +25 -0
  59. package/lib/types/client/icons-local.js +10 -0
  60. package/lib/types/client/icons.d.ts +2 -1
  61. package/lib/types/client/icons.js +1 -0
  62. package/lib/types/client/index.js +276 -72
  63. package/lib/types/client/locales.d.ts +482 -52
  64. package/lib/types/client/locales.js +490 -60
  65. package/lib/types/client/nodes.d.ts +112 -17
  66. package/lib/types/client/nodes.js +197 -26
  67. package/lib/types/client/preset-visibility.d.ts +12 -2
  68. package/lib/types/client/preset-visibility.js +54 -13
  69. package/lib/types/client/slots.d.ts +124 -51
  70. package/lib/types/client/task-tag.d.ts +32 -0
  71. package/lib/types/client/task-tag.js +33 -0
  72. package/lib/types/client/tasks.d.ts +50 -0
  73. package/lib/types/client/tasks.js +82 -0
  74. package/lib/types/client/team.d.ts +73 -0
  75. package/lib/types/client/team.js +187 -0
  76. package/lib/types/decisions.d.ts +49 -0
  77. package/lib/types/decisions.js +101 -0
  78. package/lib/types/deliveries.d.ts +17 -1
  79. package/lib/types/deliveries.js +82 -6
  80. package/lib/types/dispatch.d.ts +92 -4
  81. package/lib/types/dispatch.js +477 -109
  82. package/lib/types/help.d.ts +137 -0
  83. package/lib/types/help.js +216 -0
  84. package/lib/types/helps.d.ts +26 -0
  85. package/lib/types/helps.js +47 -0
  86. package/lib/types/index.d.ts +292 -52
  87. package/lib/types/index.js +1415 -199
  88. package/lib/types/jobs.d.ts +22 -0
  89. package/lib/types/jobs.js +77 -0
  90. package/lib/types/journal.d.ts +17 -27
  91. package/lib/types/journal.js +120 -43
  92. package/lib/types/messaging.d.ts +65 -0
  93. package/lib/types/messaging.js +160 -0
  94. package/lib/types/plan-service.js +7 -1
  95. package/lib/types/report-gate.d.ts +81 -0
  96. package/lib/types/report-gate.js +89 -0
  97. package/lib/types/tool-definition.d.ts +43 -20
  98. package/lib/types/tool-definition.js +145 -212
  99. package/lib/types/tool.d.ts +2 -2
  100. package/lib/types/tool.js +3 -2
  101. package/lib/types/types.d.ts +755 -78
  102. package/locale/zh.json +1 -1
  103. package/package.json +27 -29
  104. package/lib/invariant.js +0 -26
  105. package/lib/types/client/RoomActivityDock.d.ts +0 -19
  106. package/lib/types/client/RoomActivityDock.js +0 -105
  107. package/lib/types/client/RoomDockCapsules.d.ts +0 -64
  108. package/lib/types/client/RoomDockCapsules.js +0 -277
  109. package/lib/types/client/RoomPlanTab.d.ts +0 -15
  110. package/lib/types/client/RoomPlanTab.js +0 -13
  111. package/lib/types/client/RoomTaskLineView.d.ts +0 -13
  112. package/lib/types/client/RoomTaskLineView.js +0 -21
  113. package/lib/types/invariant.d.ts +0 -16
  114. package/lib/types/invariant.js +0 -24
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "多Agent协同",
3
+ "title": "协作间",
4
4
  "description": "把会话升级为有协调者和成员的共享协作空间。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@khorsheed/dsh-room",
3
- "description": "Room conversations with native or external coordinators, background delegation, durable goal plans and evidence review, shared member controls, live output and session navigation; the session-granted room model tools (`room_invite` / `room_task` / `room_message`) ship as the package's `./tool` composition entry",
3
+ "description": "Room conversations with native or external coordinators, background delegation, durable goal plans and evidence review, shared member controls, live output and session navigation; the session-granted room model tools (`room_invite` / `room_message` / `room_read` / `room_plan`) ship as the package's `./tool` composition entry",
4
4
  "keywords": [
5
5
  "dsh",
6
6
  "dsh-plugin",
7
7
  "cordis",
8
8
  "multi-agent"
9
9
  ],
10
- "version": "0.2.1",
10
+ "version": "0.2.3",
11
11
  "type": "module",
12
12
  "main": "lib/index.js",
13
13
  "types": "lib/types/index.d.ts",
@@ -16,10 +16,6 @@
16
16
  "types": "./lib/types/index.d.ts",
17
17
  "default": "./lib/index.js"
18
18
  },
19
- "./invariant": {
20
- "types": "./lib/types/invariant.d.ts",
21
- "default": "./lib/invariant.js"
22
- },
23
19
  "./client": {
24
20
  "types": "./lib/types/client/index.d.ts",
25
21
  "default": "./lib/client.js"
@@ -42,7 +38,8 @@
42
38
  },
43
39
  "./src/*": "./src/*",
44
40
  "./package.json": "./package.json",
45
- "./locale/*.json": "./locale/*.json"
41
+ "./locale/*.json": "./locale/*.json",
42
+ "./tool/locale/*.json": "./locale/*.json"
46
43
  },
47
44
  "dsh": {
48
45
  "bundle": {
@@ -59,36 +56,38 @@
59
56
  "compat": {
60
57
  "minHost": "0.1.5-rc.1",
61
58
  "notes": "0.1.5-rc.1 full-line boot-verified 2026-09-25 (42 packages including capture) through the three compat layers: preset-registry dual-name probe, dual-shape typert codecs, and typert faces carrying zod@4; 0.1.7-rc.2 is the 3080 production-verified line. External coordinators require the unpublished local-agent family with prepared members, shared model/effort controls and authenticated member tools; without it these operations degrade to unavailable. Scoped cwd and cross-restart mappings are supported. Native DSH session model switching retains host semantics. Room vocabulary is registered through the active loader. DSH/Kimi source-host runtime checks and candidate tarball fresh/upgrade checks on the npm host pass; Codex/Claude auth and full P95 streaming acceptance remain pending. The session-granted room tool row folded back from the standalone `@khorsheed/dsh-room-tool` companion into this package at 0.2.0 as the `./tool` composition entry — the preset row id `room-tool` is unchanged, so a preset's composition state is unaffected once its row name points at `@khorsheed/dsh-room/tool`; the old npm name is deprecated.",
62
- "verifiedHost": "0.1.7-rc.2"
59
+ "verifiedHost": "0.2.1-rc.1"
63
60
  }
64
61
  },
65
62
  "license": "MIT",
63
+ "engines": {
64
+ "dsh": ">=0.1.5-rc.1 <0.3.0-0"
65
+ },
66
66
  "dependencies": {
67
67
  "zod": "^4.4.3"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@deepseek-ai/cordis": "^4.0.1",
71
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.1",
72
- "@deepseek-ai/dsh-agent-preset-registry": "^0.1.0-rc.6 || ^0.2.0-rc.1",
73
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.2.0-rc.1",
74
- "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6 || ^0.2.0-rc.1",
75
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.2.0-rc.1",
76
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6 || ^0.2.0-rc.1",
77
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6 || ^0.2.0-rc.1",
78
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.2.0-rc.1",
79
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6 || ^0.2.0-rc.1",
80
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1",
81
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1",
82
- "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.2.0-rc.1",
83
- "@deepseek-ai/dsh-session-stats": "^0.1.0-rc.6 || ^0.2.0-rc.1",
84
- "@deepseek-ai/dsh-subagent": "^0.1.0-rc.6 || ^0.2.0-rc.1",
85
- "@deepseek-ai/dsh-token-meter": "^0.1.0-rc.6 || ^0.2.0-rc.1",
86
- "@deepseek-ai/dsh-tool-todo": "^0.1.0-rc.6 || ^0.2.0-rc.1",
87
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1",
88
- "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6 || ^0.2.0-rc.1",
89
- "@khorsheed/dsh-local-agent": "^0.1.0-rc.8",
71
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
72
+ "@deepseek-ai/dsh-agent-preset-registry": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
73
+ "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
74
+ "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
75
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
76
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
77
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
79
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
80
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
81
+ "@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
82
+ "@deepseek-ai/dsh-session-stats": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
83
+ "@deepseek-ai/dsh-subagent": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
84
+ "@deepseek-ai/dsh-token-meter": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
85
+ "@deepseek-ai/dsh-tool-todo": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
86
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
87
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
88
+ "@khorsheed/dsh-local-agent": "^0.2.0",
90
89
  "react": "^18.2.0",
91
- "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.0-rc.6 || ^0.2.0-rc.1"
90
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1"
92
91
  },
93
92
  "peerDependenciesMeta": {
94
93
  "@deepseek-ai/dsh-agent-preset-registry": {
@@ -115,7 +114,6 @@
115
114
  },
116
115
  "files": [
117
116
  "lib/index.js",
118
- "lib/invariant.js",
119
117
  "lib/client.js",
120
118
  "lib/tool.js",
121
119
  "lib/journal-*.js",
package/lib/invariant.js DELETED
@@ -1,26 +0,0 @@
1
- //#region lib/types/invariant.js
2
- /**
3
- * Package-owned invariant companion for `@khorsheed/dsh-room`.
4
- * @module @khorsheed/dsh-room/invariant
5
- */
6
- const PACKAGE_NAME = "@khorsheed/dsh-room";
7
- /** Cordis companion plugin name. */
8
- const name = "room-invariant";
9
- /** Service required before the companion can reserve package ownership. */
10
- const inject = ["invariants"];
11
- /**
12
- * Placeholder (Step 0/1 spike): no runtime invariant yet. The `room/created`
13
- * marker write passes through Session.append, whose validation rejects any
14
- * non-JSON payload, and the client contributions ride the slot/locale
15
- * lifecycles disposed with the plugin fiber. Later steps (roster transition
16
- * validation, dispatch invariants) install real checks here.
17
- */
18
- const install = () => {};
19
- /**
20
- * Register this package's invariant companion.
21
- * @param ctx - Cordis context carrying the invariant service.
22
- * @returns the installed registration's disposer after setup succeeds.
23
- */
24
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
25
- //#endregion
26
- export { apply, inject, name };
@@ -1,19 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- import type { RoomState } from '../types.ts';
3
- import type { RoomComposerProps } from './slots.ts';
4
- export interface RoomActivityDockProps {
5
- sessionId?: string;
6
- backgroundSessions?: RoomComposerProps['backgroundSessions'];
7
- activeChildren?: RoomComposerProps['activeChildren'];
8
- stopChild?: RoomComposerProps['stopChild'];
9
- state: RoomState;
10
- openSession?: RoomComposerProps['openPlanSession'];
11
- stopExecution?: RoomComposerProps['stopExecution'];
12
- planCommand?: RoomComposerProps['planCommand'];
13
- openPlan?: RoomComposerProps['openPlan'];
14
- stopMember?: RoomComposerProps['stopMember'];
15
- t: RoomComposerProps['t'];
16
- }
17
- /** Composer-owned capsules: panels grow above their triggers, never into the input. */
18
- export declare function RoomActivityDock({ state, sessionId, backgroundSessions, activeChildren, stopChild, openSession, stopExecution, planCommand, openPlan, stopMember, t }: RoomActivityDockProps): ReactNode;
19
- //# sourceMappingURL=RoomActivityDock.d.ts.map
@@ -1,105 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';
3
- import { IconAgentPresetOutlineMedium, IconChecklistOutlineMedium, IconStopFillMedium } from './icons.js';
4
- import { descendantRows, executionRows } from './executions.js';
5
- import { formatDurationMs } from './format.js';
6
- import { RoomPlanView } from './RoomPlanView.js';
7
- import css from './RoomActivityDock.module.css';
8
- const EMPTY_SESSIONS = { byId: {} };
9
- const NO_SESSIONS = { subscribe: () => () => { }, getSnapshot: () => EMPTY_SESSIONS };
10
- /** Composer-owned capsules: panels grow above their triggers, never into the input. */
11
- export function RoomActivityDock({ state, sessionId, backgroundSessions = NO_SESSIONS, activeChildren, stopChild, openSession, stopExecution, planCommand, openPlan, stopMember, t }) {
12
- const [expanded, setExpanded] = useState(null);
13
- const [filter, setFilter] = useState('recent');
14
- const [detail, setDetail] = useState(false);
15
- const [pending, setPending] = useState(null);
16
- const [error, setError] = useState(null);
17
- const [now, setNow] = useState(Date.now);
18
- const [active, setActive] = useState([]);
19
- const root = useRef(null);
20
- const id = useId();
21
- const sessions = useSyncExternalStore(backgroundSessions.subscribe, backgroundSessions.getSnapshot);
22
- const descendants = sessionId === undefined ? [] : descendantRows(sessions.byId, sessionId, state, now, active);
23
- const rows = [...executionRows(state), ...descendants];
24
- const hasChildren = Object.values(sessions.byId).some(child => child.parentId === sessionId && child.origin === 'subagent');
25
- useEffect(() => {
26
- if (!activeChildren || !hasChildren)
27
- return;
28
- let disposed = false;
29
- let pending = false;
30
- const refresh = async () => {
31
- if (pending)
32
- return;
33
- pending = true;
34
- try {
35
- const value = await activeChildren();
36
- if (!disposed && value !== undefined)
37
- setActive(value);
38
- }
39
- catch { /* Unknown is not evidence that a delegation stopped. */ }
40
- finally {
41
- pending = false;
42
- }
43
- };
44
- void refresh();
45
- const timer = setInterval(() => { void refresh(); }, 2000);
46
- return () => { disposed = true; clearInterval(timer); };
47
- }, [activeChildren, hasChildren]);
48
- const running = rows.filter(row => row.status === 'running');
49
- useEffect(() => {
50
- if (expanded === null)
51
- return;
52
- const close = (event) => { if (event.target instanceof Node && !root.current?.contains(event.target))
53
- setExpanded(null); };
54
- const escape = (event) => { if (event.key === 'Escape') {
55
- setExpanded(null);
56
- root.current?.querySelector('[aria-expanded="true"]')?.focus();
57
- } };
58
- document.addEventListener('pointerdown', close);
59
- document.addEventListener('keydown', escape);
60
- return () => { document.removeEventListener('pointerdown', close); document.removeEventListener('keydown', escape); };
61
- }, [expanded]);
62
- useEffect(() => {
63
- if (expanded !== 'agents' || running.length === 0)
64
- return;
65
- setNow(Date.now());
66
- const timer = setInterval(() => setNow(Date.now()), 1000);
67
- return () => clearInterval(timer);
68
- }, [expanded, running.length]);
69
- const stop = async (row) => {
70
- if (pending !== null)
71
- return;
72
- setPending(row.id);
73
- setError(null);
74
- try {
75
- const result = row.sessionTotal && row.childSessionId ? await stopChild?.(row.childSessionId) : row.execution ? await stopExecution?.(row.member, row.execution) : undefined;
76
- if (!result)
77
- return;
78
- if (!result.ok)
79
- setError(result.message);
80
- }
81
- catch {
82
- setError(t('composer.error.generic'));
83
- }
84
- finally {
85
- setPending(null);
86
- }
87
- };
88
- const leaf = state.plan?.tasks.filter(task => task.kind === 'task' && task.status !== 'cancelled') ?? [];
89
- const accepted = leaf.filter(task => task.status === 'accepted').length;
90
- const visible = filter === 'running' ? running : filter === 'attention'
91
- ? rows.filter(row => ['failed', 'uncertain', 'submitted'].includes(row.status)) : filter === 'recent' ? rows.slice(0, 6) : rows;
92
- if (rows.length === 0 && state.plan === undefined)
93
- return null;
94
- return _jsxs("div", { ref: root, className: css.root, "data-testid": "room-activity-dock", children: [expanded === 'agents' && _jsxs("section", { className: css.panel, id: `${id}-agents`, "aria-label": t('activity.agents'), children: [_jsxs("div", { className: css.heading, children: [_jsx("strong", { children: t('activity.agents') }), _jsx("div", { className: css.filters, children: ['recent', 'running', 'attention', 'all'].map(value => _jsx("button", { type: "button", "aria-pressed": filter === value, onClick: () => setFilter(value), children: t(`activity.filter.${value}`) }, value)) })] }), error && _jsx("p", { role: "alert", children: error }), _jsx("div", { className: css.grid, children: visible.map(row => _jsxs("article", { className: css.card, children: [_jsxs("button", { type: "button", className: css.cardMain, disabled: !row.childSessionId || !openSession, onClick: () => row.childSessionId && openSession?.(row.childSessionId), "aria-label": `${t('activity.session')}: ${row.title}`, children: [_jsxs("span", { className: css.meta, children: [_jsx("span", { children: row.member }), _jsx("span", { "data-status": row.status, children: t(`activity.status.${row.status}`) })] }), _jsx("span", { className: css.title, title: row.title, children: row.title }), _jsx("span", { className: css.secondary, children: [row.provider, row.model, row.effort].filter(Boolean).join(' · ') })] }), row.error && _jsx("p", { className: css.error, children: row.error }), _jsxs("div", { className: css.footer, children: [_jsxs("span", { children: [row.sessionTotal ? `${t('activity.sessionTotal')} · ` : '', row.status === 'queued' ? t('activity.notStarted') : row.status === 'running' && row.startedAt !== undefined ? formatDurationMs(Math.max(0, now - row.startedAt)) : row.elapsedMs === undefined ? '—' : formatDurationMs(row.elapsedMs), row.tokens === undefined ? '' : ` · ${Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 }).format(row.tokens)} tok`] }), row.status === 'running' && ((row.execution && stopExecution) || (row.sessionTotal && stopChild)) ? _jsxs("button", { type: "button", disabled: pending !== null, onClick: () => { void stop(row); }, "aria-label": `${t('activity.stop')}: ${row.title}`, children: [_jsx(IconStopFillMedium, { size: 12 }), t('activity.stop')] })
95
- : row.childSessionId && openSession && _jsx("button", { type: "button", onClick: () => openSession(row.childSessionId), children: t('activity.session') })] })] }, row.id)) }), visible.length === 0 && _jsx("p", { className: css.empty, children: t('activity.empty') })] }), expanded === 'plan' && state.plan && _jsxs("section", { className: css.panel, id: `${id}-plan`, "aria-label": t('activity.plan'), children: [_jsxs("div", { className: css.heading, children: [_jsx("strong", { children: state.plan.objective }), _jsxs("span", { className: css.secondary, children: [t(`plan.status.${state.plan.status}`), " \u00B7 ", t('plan.acceptedCount', { done: accepted, total: leaf.length })] })] }), state.plan.reason && _jsx("p", { className: css.error, children: state.plan.reason }), _jsx("div", { className: css.planList, children: leaf.map(task => {
96
- const member = state.members.find(member => member.id === task.ownerMemberId);
97
- return _jsxs("button", { type: "button", className: css.planRow, onClick: () => { if (openPlan)
98
- openPlan();
99
- else
100
- setDetail(true); }, children: [_jsx("span", { className: css.taskDot, "data-status": task.status, "aria-hidden": true }), _jsx("span", { children: task.title }), _jsxs("span", { className: css.secondary, children: [member?.name, " \u00B7 ", t(`plan.status.${task.status}`)] })] }, task.id);
101
- }) }), leaf.length === 0 && _jsx("p", { className: css.empty, children: t('activity.planning') }), _jsxs("div", { className: css.footer, children: [_jsx("span", {}), _jsx("button", { type: "button", onClick: () => { if (openPlan)
102
- openPlan();
103
- else
104
- setDetail(value => !value); }, children: t('activity.planDetails') })] }), detail && !openPlan && planCommand && _jsx(RoomPlanView, { expanded: true, plan: state.plan, members: state.members, command: planCommand, openSession: openSession, stopMember: stopMember, t: t })] }), _jsxs("div", { className: css.capsules, children: [rows.length > 0 && _jsxs("button", { type: "button", className: css.capsule, "aria-expanded": expanded === 'agents', "aria-controls": `${id}-agents`, title: running.length > 0 ? t('activity.runningHint', { count: new Set(running.map(row => row.member)).size }) : t('activity.agents'), onClick: () => setExpanded(value => value === 'agents' ? null : 'agents'), children: [_jsx("span", { className: css.icon, children: running.length > 0 ? _jsx("span", { className: css.runningDot, "data-testid": "room-running-dot", "aria-hidden": true }) : _jsx(IconAgentPresetOutlineMedium, { size: 16 }) }), t('activity.agents')] }), state.plan && _jsxs("button", { type: "button", className: css.capsule, "aria-expanded": expanded === 'plan', "aria-controls": `${id}-plan`, onClick: () => setExpanded(value => value === 'plan' ? null : 'plan'), children: [_jsx(IconChecklistOutlineMedium, { size: 16 }), t('activity.plan'), " ", _jsxs("span", { className: css.secondary, children: [accepted, "/", leaf.length] })] })] })] });
105
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * The room dock capsules, rendered by the RoomComposer itself above the input
3
- * card — the seat of the old task-board strip (NOT a `conversation.input.dock`
4
- * entry: that slot lives inside the official composer fallback, which the
5
- * room's composer takeover hides). One collapsed row of two capsules plus a
6
- * quick-add button:
7
- *
8
- * ```
9
- * [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
10
- * ```
11
- *
12
- * The row is ANCHORED just above the input card: an expanded panel renders
13
- * above the capsule row (top-to-bottom: panel → capsules → input card) and
14
- * grows upward only, so folding/unfolding never shifts the row under the
15
- * user's pointer.
16
- *
17
- * The GOAL capsule's only visual focus is the progress ring: an SVG circle
18
- * (tertiary track + business-primary progress arc) followed by the percent
19
- * and the truncated goal text; without a goal it renders the 「+ 设定目标」
20
- * guide state (no ring). It expands into the goal card: the full text with an
21
- * inline [编辑], a progress bar with the done/total fraction, and the recent
22
- * advance records (who finished what, relative time).
23
- *
24
- * The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
25
- * the official primitives — never a Unicode glyph), then the open-task count
26
- * and the members currently running (color dot + name). It expands into the
27
- * Linear-style task panel: a member filter chip row (color-dot capsules, the
28
- * selected one takes a tinted fill of the member color — no outline) with the
29
- * +添加 trigger at its right end, small-caps tertiary group headers, and task
30
- * rows (colored status icon — business-primary half-ring for in_progress,
31
- * tertiary empty ring for pending, filled check for done, filled error × for
32
- * failed — + title + member chip + right-aligned tertiary status/relative
33
- * time + a row-end [完成]; a failed row's action is [关闭], dismissing to
34
- * cancelled — the run did not finish, so it never closes done).
35
- * Closing a task plays a restrained completion beat (the check pops, the
36
- * title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
37
- * a status change (`key = id:status`) so the CSS animation replays. A blocked
38
- * row greys with a 「等 ada」 tag until its `blockedBy` member has no open
39
- * task left (display only: nothing auto-dispatches). The trailing + opens
40
- * the panel with the add form already expanded (member select + title +
41
- * optional "等谁"). Panels close on a second capsule click or a click
42
- * outside; no overlay.
43
- *
44
- * Visual layer: the capsules ride the official chip register (28px, r24,
45
- * interactive hover fill, 13/20/500 label) with a visible outline (the
46
- * InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
47
- * the expanded cards are the
48
- * official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
49
- * shadow-lv3); the task capsule sweeps a restrained glare band while tasks
50
- * run (the ToolRow pattern, reduced-motion safe). Every token reference
51
- * carries the light-theme literal as fallback; no divider lines anywhere.
52
- *
53
- * FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
54
- * not count — the main agent is always there) has no use for the guide/task
55
- * pair, which would be pure noise. The collapsed row then renders a single
56
- * 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
57
- * straight from the dock. The moment a goal is set or a task exists the pair
58
- * returns, and the invite entry retires — the members tab already carries it.
59
- */
60
- import { type ReactNode } from 'react';
61
- import type { RoomDockCapsulesProps } from './slots.ts';
62
- /** The dock capsules (goal + tasks) with their expanded cards. */
63
- export declare function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }: RoomDockCapsulesProps): ReactNode;
64
- //# sourceMappingURL=RoomDockCapsules.d.ts.map
@@ -1,277 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * The room dock capsules, rendered by the RoomComposer itself above the input
4
- * card — the seat of the old task-board strip (NOT a `conversation.input.dock`
5
- * entry: that slot lives inside the official composer fallback, which the
6
- * room's composer takeover hides). One collapsed row of two capsules plus a
7
- * quick-add button:
8
- *
9
- * ```
10
- * [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
11
- * ```
12
- *
13
- * The row is ANCHORED just above the input card: an expanded panel renders
14
- * above the capsule row (top-to-bottom: panel → capsules → input card) and
15
- * grows upward only, so folding/unfolding never shifts the row under the
16
- * user's pointer.
17
- *
18
- * The GOAL capsule's only visual focus is the progress ring: an SVG circle
19
- * (tertiary track + business-primary progress arc) followed by the percent
20
- * and the truncated goal text; without a goal it renders the 「+ 设定目标」
21
- * guide state (no ring). It expands into the goal card: the full text with an
22
- * inline [编辑], a progress bar with the done/total fraction, and the recent
23
- * advance records (who finished what, relative time).
24
- *
25
- * The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
26
- * the official primitives — never a Unicode glyph), then the open-task count
27
- * and the members currently running (color dot + name). It expands into the
28
- * Linear-style task panel: a member filter chip row (color-dot capsules, the
29
- * selected one takes a tinted fill of the member color — no outline) with the
30
- * +添加 trigger at its right end, small-caps tertiary group headers, and task
31
- * rows (colored status icon — business-primary half-ring for in_progress,
32
- * tertiary empty ring for pending, filled check for done, filled error × for
33
- * failed — + title + member chip + right-aligned tertiary status/relative
34
- * time + a row-end [完成]; a failed row's action is [关闭], dismissing to
35
- * cancelled — the run did not finish, so it never closes done).
36
- * Closing a task plays a restrained completion beat (the check pops, the
37
- * title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
38
- * a status change (`key = id:status`) so the CSS animation replays. A blocked
39
- * row greys with a 「等 ada」 tag until its `blockedBy` member has no open
40
- * task left (display only: nothing auto-dispatches). The trailing + opens
41
- * the panel with the add form already expanded (member select + title +
42
- * optional "等谁"). Panels close on a second capsule click or a click
43
- * outside; no overlay.
44
- *
45
- * Visual layer: the capsules ride the official chip register (28px, r24,
46
- * interactive hover fill, 13/20/500 label) with a visible outline (the
47
- * InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
48
- * the expanded cards are the
49
- * official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
50
- * shadow-lv3); the task capsule sweeps a restrained glare band while tasks
51
- * run (the ToolRow pattern, reduced-motion safe). Every token reference
52
- * carries the light-theme literal as fallback; no divider lines anywhere.
53
- *
54
- * FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
55
- * not count — the main agent is always there) has no use for the guide/task
56
- * pair, which would be pure noise. The collapsed row then renders a single
57
- * 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
58
- * straight from the dock. The moment a goal is set or a task exists the pair
59
- * returns, and the invite entry retires — the members tab already carries it.
60
- */
61
- import { useEffect, useRef, useState, useSyncExternalStore, } from 'react';
62
- import { IconChecklistOutlineMedium } from './icons.js';
63
- import { taskProgress } from '../journal.js';
64
- import { formatRelativeTime } from './format.js';
65
- import { memberColor } from './member-color.js';
66
- import { InviteDialog } from './InviteDialog.js';
67
- import css from './RoomDockCapsules.module.css';
68
- /** Glyph tooltip text per status. */
69
- function statusLabel(status, t) {
70
- switch (status) {
71
- case 'pending': return t('tasks.status.pending');
72
- case 'in_progress': return t('tasks.status.in_progress');
73
- case 'done': return t('tasks.status.done');
74
- case 'cancelled': return t('tasks.status.cancelled');
75
- case 'failed': return t('tasks.status.failed');
76
- }
77
- }
78
- /* The Linear-style status icons: colored, 14px, centered in the 16px cell. */
79
- /** Done: filled success circle, check cut in the card surface tone. */
80
- function DoneGlyph() {
81
- return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphDone, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.2 7.3L6.2 9.3L9.8 5.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })] }));
82
- }
83
- /** In-progress: business-primary half ring; CSS spins the svg. */
84
- function ProgressGlyph() {
85
- return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphProgress, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeDasharray: `${Math.PI * 5.5} ${2 * Math.PI * 5.5}` }) }));
86
- }
87
- /** Pending: the tertiary empty ring. */
88
- function PendingGlyph() {
89
- return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphPending, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
90
- }
91
- /** Cancelled: the pending ring in the dimmer caption tone. */
92
- function CancelledGlyph() {
93
- return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphCancelled, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
94
- }
95
- /** Failed: filled error circle, × cut in the card surface tone (DoneGlyph's cutout idiom). */
96
- function FailedGlyph() {
97
- return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphFailed, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.8 4.8L9.2 9.2M9.2 4.8L4.8 9.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round" })] }));
98
- }
99
- function StatusGlyph({ status }) {
100
- switch (status) {
101
- case 'done': return _jsx(DoneGlyph, {});
102
- case 'in_progress': return _jsx(ProgressGlyph, {});
103
- case 'pending': return _jsx(PendingGlyph, {});
104
- case 'cancelled': return _jsx(CancelledGlyph, {});
105
- case 'failed': return _jsx(FailedGlyph, {});
106
- }
107
- }
108
- /**
109
- * The goal capsule's progress ring — the collapsed row's single visual focus:
110
- * a tertiary track arc plus the business-primary progress arc, -90° rotated
111
- * so the arc starts at twelve o'clock.
112
- */
113
- function GoalRing({ fraction }) {
114
- const clamped = Math.min(1, Math.max(0, fraction));
115
- const R = 5.5;
116
- const C = 2 * Math.PI * R;
117
- return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.ring, children: [_jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringTrack, strokeWidth: "2" }), _jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringFill, strokeWidth: "2", strokeLinecap: "round", strokeDasharray: `${C * clamped} ${C}`, transform: "rotate(-90 7 7)" })] }));
118
- }
119
- /**
120
- * A task is BLOCKED while the member its `blockedBy` names still has an open
121
- * (pending/in_progress) task — the row greys until the wait is over. The task
122
- * ITSELF never counts as its own blocker (a "等谁" pointing at the owner
123
- * would otherwise grey forever). Display only: nothing auto-dispatches when
124
- * it clears.
125
- */
126
- function isBlocked(task, tasks) {
127
- return task.blockedBy !== undefined
128
- && tasks.some(entry => entry.id !== task.id && entry.member === task.blockedBy
129
- && (entry.status === 'pending' || entry.status === 'in_progress'));
130
- }
131
- /** How many recent advance records the goal card lists. */
132
- const RECENT_ADVANCES = 5;
133
- /** One task row of the task panel. */
134
- function TaskRow({ task, tasks, now, closeTask, t }) {
135
- const blocked = isBlocked(task, tasks);
136
- const open = task.status === 'pending' || task.status === 'in_progress';
137
- return (_jsxs("li", { className: css.item, "data-status": task.status, "data-blocked": blocked || undefined, children: [_jsx("span", { className: css.glyph, title: statusLabel(task.status, t), children: _jsx(StatusGlyph, { status: task.status }) }), _jsx("span", { className: css.content, children: _jsx("span", { className: css.title, children: task.title }) }), _jsxs("span", { className: css.memberChip, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(task.member) }, "aria-hidden": true }), task.member] }), blocked && _jsx("span", { className: css.blockedTag, children: t('tasks.blocked', { member: task.blockedBy ?? '' }) }), _jsxs("span", { className: css.meta, children: [statusLabel(task.status, t), " \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] }), open && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id); }, children: t('tasks.close') })), task.status === 'failed' && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id, 'cancelled'); }, children: t('tasks.dismiss') }))] }));
138
- }
139
- /** The dock capsules (goal + tasks) with their expanded cards. */
140
- export function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans = false, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }) {
141
- const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
142
- const [open, setOpen] = useState(null);
143
- const [addOpen, setAddOpen] = useState(false);
144
- const [filter, setFilter] = useState('');
145
- const [editingGoal, setEditingGoal] = useState(false);
146
- const [goalDraft, setGoalDraft] = useState('');
147
- const [draftTitle, setDraftTitle] = useState('');
148
- const [draftMember, setDraftMember] = useState('');
149
- const [draftBlockedBy, setDraftBlockedBy] = useState('');
150
- const [error, setError] = useState(null);
151
- const [inviteOpen, setInviteOpen] = useState(false);
152
- const [providers, setProviders] = useState(undefined);
153
- const rootRef = useRef(null);
154
- // Collapse on any press outside the dock (no overlay; a second click on the
155
- // capsule toggles too).
156
- useEffect(() => {
157
- if (open === null)
158
- return;
159
- const onPointerDown = (event) => {
160
- if (rootRef.current !== null && event.target instanceof Node
161
- && !rootRef.current.contains(event.target)) {
162
- setOpen(null);
163
- setAddOpen(false);
164
- setEditingGoal(false);
165
- }
166
- };
167
- document.addEventListener('mousedown', onPointerDown);
168
- return () => { document.removeEventListener('mousedown', onPointerDown); };
169
- }, [open]);
170
- // A cache miss (first pull in flight) or a non-room: the dock vanishes.
171
- if (state === undefined || roomStore.isRoomCached(sessionId) !== true)
172
- return null;
173
- // Fresh room (no goal, no tasks — membership does not count, the main
174
- // agent is always there): the guide/task pair would be noise, so the row
175
- // carries a single 「+ 邀请成员」 capsule that opens the invite dialog
176
- // straight from the dock. The pair returns the moment a goal or a task
177
- // exists; the invite entry then retires (the members tab carries it).
178
- const fresh = state.goal === undefined && state.tasks.length === 0;
179
- if (fresh) {
180
- const openInvite = () => {
181
- setProviders(undefined);
182
- setInviteOpen(true);
183
- void listProviders().then(setProviders);
184
- };
185
- const submitInvite = (values) => invite({
186
- provider: values.provider,
187
- name: values.name,
188
- // Blank instructions are omitted, not sent: the host rejects a
189
- // present-but-blank role, and an absent one simply carries no preset.
190
- ...values.instructions === '' ? {} : { instructions: values.instructions },
191
- ...values.cwd === '' ? {} : { cwd: values.cwd },
192
- ...values.firstTask === '' ? {} : { firstTask: values.firstTask },
193
- ...values.model === '' ? {} : { model: values.model },
194
- });
195
- return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [_jsx("div", { className: css.capsules, children: _jsx("button", { type: "button", className: css.capsule, onClick: openInvite, children: _jsx("span", { className: css.capsuleGuide, children: t('members.invite') }) }) }), inviteOpen && (_jsx(InviteDialog, { mode: "invite", providers: providers?.providers, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: state.members.map(entry => entry.name), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, onSubmit: submitInvite, onClose: () => { setInviteOpen(false); }, t: t }))] }));
196
- }
197
- const now = Date.now();
198
- const progress = taskProgress(state.tasks);
199
- const runners = [...new Set(state.tasks.filter(task => task.status === 'in_progress').map(task => task.member))];
200
- const members = state.members;
201
- const chosen = members.some(member => member.name === draftMember)
202
- ? draftMember
203
- : (members[0]?.name ?? '');
204
- const advances = state.tasks
205
- .filter(task => task.status === 'done')
206
- .slice()
207
- .sort((a, b) => b.updatedAt - a.updatedAt);
208
- const fraction = progress.total === 0 ? 0 : progress.done / progress.total;
209
- const toggle = (panel) => {
210
- if (open === panel) {
211
- setOpen(null);
212
- setAddOpen(false);
213
- setEditingGoal(false);
214
- return;
215
- }
216
- setOpen(panel);
217
- setAddOpen(false);
218
- setEditingGoal(false);
219
- setError(null);
220
- if (panel === 'goal' && state.goal === undefined) {
221
- setGoalDraft('');
222
- setEditingGoal(true);
223
- }
224
- };
225
- const saveGoal = async () => {
226
- const outcome = await setGoal(goalDraft);
227
- if (!outcome.ok) {
228
- setError(outcome.message);
229
- return;
230
- }
231
- setEditingGoal(false);
232
- setError(null);
233
- };
234
- const add = async () => {
235
- const title = draftTitle.trim();
236
- if (title === '' || chosen === '')
237
- return;
238
- const outcome = await addTask(chosen, title, draftBlockedBy === '' ? undefined : draftBlockedBy);
239
- if (!outcome.ok) {
240
- setError(outcome.message);
241
- return;
242
- }
243
- setDraftTitle('');
244
- setDraftBlockedBy('');
245
- setError(null);
246
- };
247
- const onAddKeyDown = (event) => {
248
- if (event.key === 'Enter') {
249
- event.preventDefault();
250
- void add();
251
- }
252
- };
253
- const visibleTasks = filter === ''
254
- ? undefined
255
- : state.tasks.filter(task => task.member === filter);
256
- return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [!formalPlans && open === 'goal' && (_jsxs("div", { className: css.card, children: [editingGoal ? (_jsxs("div", { className: css.goalEditRow, children: [_jsx("input", { className: css.input, value: goalDraft, placeholder: t('goal.placeholder'), onChange: event => { setGoalDraft(event.target.value); }, onKeyDown: (event) => {
257
- if (event.key === 'Enter') {
258
- event.preventDefault();
259
- void saveGoal();
260
- }
261
- } }), _jsx("button", { type: "button", className: css.action, onClick: () => { void saveGoal(); }, children: t('goal.save') }), state.goal !== undefined && (_jsx("button", { type: "button", className: css.action, onClick: () => { setEditingGoal(false); }, children: t('goal.cancel') }))] })) : (_jsxs("div", { className: css.goalRow, children: [_jsx("span", { className: css.goalText, children: state.goal }), _jsx("button", { type: "button", className: css.action, onClick: () => {
262
- setGoalDraft(state.goal ?? '');
263
- setEditingGoal(true);
264
- }, children: t('goal.edit') })] })), _jsxs("div", { className: css.progressRow, children: [_jsx("div", { className: css.progressTrack, role: "progressbar", "aria-valuemin": 0, "aria-valuemax": progress.total, "aria-valuenow": progress.done, children: _jsx("div", { className: css.progressFill, style: { width: `${fraction * 100}%` } }) }), _jsxs("span", { className: css.progressMeta, children: [progress.done, "/", progress.total] })] }), _jsx("div", { className: css.recentLabel, children: t('goal.recent') }), advances.length === 0 ? (_jsx("div", { className: css.meta, children: t('goal.noAdvances') })) : (_jsx("ul", { className: css.list, children: advances.slice(0, RECENT_ADVANCES).map(task => (_jsxs("li", { className: css.advanceRow, children: [t('advance.line', { member: task.member, title: task.title }), _jsxs("span", { className: css.meta, children: [" \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] })] }, task.id))) })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), open === 'tasks' && (_jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.panelBar, children: [_jsxs("div", { className: css.chips, role: "group", "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), children: [_jsx("button", { type: "button", className: css.chip, "data-active": filter === '' || undefined, onClick: () => { setFilter(''); }, children: t('tasks.filter.all') }), members.map(member => (_jsxs("button", { type: "button", className: css.chip, "data-active": filter === member.name || undefined, style: { '--room-chip-color': memberColor(member.name) }, onClick: () => { setFilter(member.name); }, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(member.name) }, "aria-hidden": true }), member.name] }, member.name)))] }), !addOpen && (_jsxs("button", { type: "button", className: css.addTrigger, onClick: () => { setAddOpen(true); }, children: ["\uFF0B ", t('tasks.addTrigger')] }))] }), _jsx("div", { className: css.groups, children: filter === ''
265
- ? members.map((member) => {
266
- const own = state.tasks.filter(task => task.member === member.name);
267
- if (own.length === 0)
268
- return null;
269
- return (_jsxs("div", { className: css.group, children: [_jsx("div", { className: css.head, children: member.name }), _jsx("ul", { className: css.list, children: own.map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })] }, member.name));
270
- })
271
- : (_jsx("ul", { className: css.list, children: (visibleTasks ?? []).map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })) }), addOpen && (_jsxs("div", { className: css.addRow, children: [_jsx("select", { className: css.memberSelect, "aria-label": t('tasks.addMember'), value: chosen, onChange: event => { setDraftMember(event.target.value); }, children: members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name))) }), _jsx("input", { className: css.input, value: draftTitle, placeholder: t('tasks.addPlaceholder'), onChange: event => { setDraftTitle(event.target.value); }, onKeyDown: onAddKeyDown }), _jsxs("select", { className: css.memberSelect, "aria-label": t('tasks.addBlockedBy'), value: draftBlockedBy, onChange: event => { setDraftBlockedBy(event.target.value); }, children: [_jsx("option", { value: "", children: t('tasks.addBlockedByNone') }), members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name)))] }), _jsx("button", { type: "button", className: css.action, disabled: draftTitle.trim() === '' || chosen === '', onClick: () => { void add(); }, children: t('tasks.add') })] })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), _jsxs("div", { className: css.capsules, children: [!formalPlans && _jsx("button", { type: "button", className: css.capsule, "aria-expanded": open === 'goal', "aria-label": t('goal.label'), onClick: () => { toggle('goal'); }, children: state.goal === undefined ? (_jsx("span", { className: css.capsuleGuide, children: t('goal.set') })) : (_jsxs(_Fragment, { children: [_jsx(GoalRing, { fraction: fraction }), _jsxs("span", { className: css.capsulePercent, children: [Math.round(fraction * 100), "%"] }), _jsx("span", { className: css.capsuleText, children: state.goal })] })) }), _jsxs("button", { type: "button", className: css.capsule, "aria-expanded": open === 'tasks', "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), "data-running": runners.length > 0 || undefined, onClick: () => { toggle('tasks'); }, children: [_jsx(IconChecklistOutlineMedium, { size: 14, className: css.checklistIcon }), progress.total === 0 ? (_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') }), _jsxs("span", { className: css.capsuleCount, children: [progress.done, "/", progress.total] }), runners.length > 0 && (_jsxs("span", { className: css.runners, children: [runners.map(name => (_jsxs("span", { className: css.runner, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(name) }, "aria-hidden": true }), name] }, name))), _jsx("span", { className: css.runnerSuffix, children: t('tasks.doing') })] }))] }))] }), _jsx("button", { type: "button", className: css.addCapsule, "aria-label": t('tasks.addTrigger'), onClick: () => {
272
- setOpen('tasks');
273
- setAddOpen(true);
274
- setEditingGoal(false);
275
- setError(null);
276
- }, children: "\uFF0B" })] })] }));
277
- }