@cecwxf/dsh-room 0.0.0-stage → 0.1.1

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.
package/dist/client.js ADDED
@@ -0,0 +1,3015 @@
1
+ // dsh-room Rooms UI client bundle — generated by build.mjs
2
+ window.__ModuleLoader__.load({
3
+ id: "@cecwxf/dsh-room",
4
+ factory: (require) => {
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ "use strict";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name2 in all)
14
+ __defProp(target, name2, { get: all[name2], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // src/client/index.jsx
27
+ var index_exports = {};
28
+ __export(index_exports, {
29
+ Avatar: () => Avatar,
30
+ RoomsIcon: () => RoomsIcon,
31
+ apply: () => apply,
32
+ inject: () => inject,
33
+ name: () => name
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_react = require("react");
37
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
38
+
39
+ // src/client/rooms.css
40
+ var rooms_default = '/* Collab Rooms UI \u2014 original surface inside the dsh shell. Uses only dsh --dsw-* tokens.\n Independent Rooms surface. Prefix: rm- (rooms). */\n\n.rm-root {\n --rm-accent: var(--dsw-alias-state-business-primary);\n --rm-hover: var(--dsw-alias-interactive-bg-hover);\n --rm-border: var(--dsw-alias-border-l2);\n --rm-bg: var(--dsw-alias-bg-base);\n --rm-panel: var(--dsw-alias-bg-module-platform);\n --rm-label: var(--dsw-alias-label-primary);\n --rm-muted: var(--dsw-alias-label-secondary);\n --rm-quiet: var(--dsw-alias-label-tertiary);\n font: 13px/1.5 var(--dsw-font-family);\n color: var(--rm-label);\n box-sizing: border-box;\n}\n.rm-root *, .rm-root *::before, .rm-root *::after { box-sizing: border-box; }\n.rm-root button, .rm-root input, .rm-root textarea { font: inherit; }\n\n/* \u2500\u2500 sidebar (joined rooms + public search) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-side {\n display: flex; flex-direction: column; height: 100%; min-height: 0;\n padding: 0 8px 8px 0; gap: 8px;\n}\n.rm-side-toolbar {\n display: flex; align-items: center; gap: 6px; flex: none; padding: 4px 0 2px;\n}\n.rm-side-title {\n flex: 1; font-size: 12px; font-weight: 600; letter-spacing: .04em;\n text-transform: uppercase; color: var(--rm-quiet);\n}\n.rm-icon-btn {\n width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 8px; background: transparent; color: var(--rm-muted); cursor: pointer; padding: 0;\n}\n.rm-icon-btn:hover { background: var(--rm-hover); color: var(--rm-label); }\n.rm-search {\n display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px;\n border: .5px solid var(--rm-border); border-radius: 8px; background: var(--rm-panel);\n}\n.rm-search input {\n flex: 1; min-width: 0; border: 0; background: transparent; outline: none; color: var(--rm-label); font-size: 13px;\n}\n.rm-search input::placeholder { color: var(--rm-quiet); }\n.rm-side-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 12px; }\n.rm-group-label { font-size: 11px; font-weight: 600; color: var(--rm-quiet); padding: 0 4px 4px; text-transform: uppercase; letter-spacing: .03em; }\n.rm-room-list { display: flex; flex-direction: column; gap: 2px; }\n.rm-room-item {\n display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;\n border: 0; border-radius: 10px; background: transparent; color: inherit; cursor: pointer; text-align: left;\n}\n.rm-room-item:hover { background: var(--rm-hover); }\n.rm-room-item[aria-current="true"] { background: var(--rm-hover); }\n.rm-room-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }\n.rm-room-item-name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item-meta { font-size: 11.5px; color: var(--rm-quiet); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item-trail { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }\n.rm-badge {\n display: inline-flex; align-items: center; gap: 3px; height: 18px; padding: 0 6px;\n border-radius: 999px; font-size: 10.5px; font-weight: 500; background: var(--rm-panel); color: var(--rm-muted);\n border: .5px solid var(--rm-border);\n}\n.rm-badge-public { color: var(--rm-accent); border-color: color-mix(in srgb, var(--rm-accent) 35%, transparent); background: color-mix(in srgb, var(--rm-accent) 8%, transparent); }\n.rm-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--rm-quiet); }\n.rm-dot-online { background: var(--dsw-alias-state-success-primary, #2bb673); }\n.rm-dot-connecting { background: var(--dsw-alias-state-warn-primary, #e9a23b); }\n.rm-dot-offline { background: var(--dsw-alias-state-error-primary, #e5484d); }\n.rm-empty-hint { padding: 8px 10px; font-size: 12px; color: var(--rm-quiet); line-height: 1.45; }\n.rm-dir-item {\n display: flex; align-items: flex-start; gap: 10px; padding: 10px; border-radius: 10px;\n border: .5px solid var(--rm-border); background: var(--rm-bg); margin-bottom: 6px;\n}\n.rm-dir-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }\n.rm-dir-tags { display: flex; flex-wrap: wrap; gap: 4px; }\n.rm-tag { font-size: 11px; color: var(--rm-muted); background: var(--rm-hover); border-radius: 4px; padding: 0 5px; }\n.rm-menu {\n position: fixed; z-index: 1000; min-width: 180px; padding: 4px;\n border-radius: 10px; background: var(--dsw-alias-bg-layer-1, var(--rm-bg));\n border: .5px solid var(--rm-border);\n box-shadow: 0 8px 24px color-mix(in srgb, var(--rm-label) 12%, transparent);\n display: flex; flex-direction: column;\n}\n.rm-menu button {\n display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;\n border: 0; border-radius: 6px; background: transparent; color: var(--rm-label); cursor: pointer; text-align: left;\n}\n.rm-menu button:hover { background: var(--rm-hover); }\n\n/* \u2500\u2500 main: room workspace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-main { display: flex; flex: 1; min-height: 0; height: 100%; width: 100%; background: var(--rm-bg); }\n.rm-workspace { display: flex; flex: 1; min-width: 0; min-height: 0; }\n.rm-chat { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }\n.rm-chat-header {\n flex: none; display: flex; align-items: center; gap: 10px; height: 48px;\n padding: 0 16px; border-bottom: .5px solid var(--rm-border);\n}\n.rm-chat-header-title { font-size: 14px; font-weight: 600; }\n.rm-chat-header-sub { font-size: 12px; color: var(--rm-quiet); }\n.rm-messages {\n flex: 1; min-height: 0; overflow: auto; padding: 16px 18px 8px;\n display: flex; flex-direction: column; gap: 2px;\n}\n.rm-day {\n align-self: center; margin: 10px 0 12px; padding: 2px 10px; border-radius: 999px;\n font-size: 11px; color: var(--rm-quiet); background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n}\n.rm-msg {\n display: grid; grid-template-columns: 32px 1fr; gap: 0 10px; padding: 4px 6px; border-radius: 8px;\n max-width: 720px;\n}\n.rm-msg:hover { background: color-mix(in srgb, var(--rm-hover) 60%, transparent); }\n.rm-msg-self { margin-left: auto; grid-template-columns: 1fr; max-width: 640px; }\n.rm-msg-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }\n.rm-msg-author { font-size: 12px; font-weight: 600; color: var(--rm-muted); }\n.rm-msg-time { font-size: 11px; color: var(--rm-quiet); }\n.rm-msg-body { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }\n.rm-msg-self .rm-msg-body {\n background: var(--dsw-alias-label-primary); color: var(--dsw-alias-label-primary-foreground);\n padding: 8px 12px; border-radius: 12px 12px 4px 12px; width: fit-content; margin-left: auto;\n}\n.rm-msg-event {\n grid-template-columns: 1fr; max-width: 560px; margin: 6px 0;\n padding: 8px 12px; border: .5px solid var(--rm-border); border-radius: 10px; background: var(--rm-panel);\n}\n.rm-mention { color: var(--rm-accent); font-weight: 500; }\n.rm-msg-self .rm-mention { color: inherit; text-decoration: underline; text-underline-offset: 2px; }\n.rm-task-card { display: flex; flex-direction: column; gap: 4px; }\n.rm-task-head { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--rm-muted); }\n.rm-task-title { font-weight: 600; font-size: 13.5px; }\n.rm-composer {\n flex: none; display: flex; align-items: flex-end; gap: 8px; padding: 10px 16px 14px;\n border-top: .5px solid var(--rm-border);\n}\n.rm-composer-box {\n flex: 1; display: flex; align-items: flex-end; gap: 6px; min-height: 44px;\n padding: 8px 10px; border: .5px solid var(--rm-border); border-radius: 12px; background: var(--rm-panel);\n}\n.rm-composer-box:focus-within { border-color: color-mix(in srgb, var(--rm-accent) 50%, var(--rm-border)); }\n.rm-composer-box textarea {\n flex: 1; min-height: 28px; max-height: 120px; border: 0; background: transparent; outline: none;\n resize: none; color: var(--rm-label); line-height: 1.45;\n}\n.rm-composer-box textarea::placeholder { color: var(--rm-quiet); }\n.rm-composer-mode {\n width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 8px; background: transparent; color: var(--rm-muted); cursor: pointer; flex: none;\n}\n.rm-composer-mode:hover, .rm-composer-mode[aria-pressed="true"] {\n color: var(--rm-accent); background: color-mix(in srgb, var(--rm-accent) 12%, transparent);\n}\n.rm-chat-hint { margin: 48px auto; text-align: center; color: var(--rm-quiet); font-size: 13px; max-width: 360px; line-height: 1.5; }\n\n/* \u2500\u2500 detail column (members / invite / about) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-detail {\n flex: 0 0 280px; width: 280px; display: flex; flex-direction: column; min-height: 0;\n border-left: .5px solid var(--rm-border); background: var(--rm-bg); overflow: auto;\n}\n.rm-detail-section { padding: 14px 14px 8px; border-bottom: .5px solid var(--rm-border); }\n.rm-detail-section:last-child { border-bottom: 0; }\n.rm-detail-h {\n display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em; color: var(--rm-quiet); margin-bottom: 8px;\n}\n.rm-member {\n display: flex; align-items: center; gap: 8px; padding: 5px 4px; border-radius: 8px; min-height: 34px;\n}\n.rm-member:hover { background: var(--rm-hover); }\n.rm-member-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }\n.rm-member-actions { display: none; gap: 2px; }\n.rm-member:hover .rm-member-actions, .rm-member:focus-within .rm-member-actions { display: inline-flex; }\n.rm-mini {\n width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 6px; background: transparent; color: var(--rm-muted); cursor: pointer; font-size: 12px;\n}\n.rm-mini:hover { background: var(--dsw-alias-interactive-bg-active); color: var(--rm-label); }\n.rm-task-row { display: flex; align-items: center; gap: 6px; padding: 4px; font-size: 12.5px; }\n.rm-task-row-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-about { display: grid; grid-template-columns: 88px 1fr; gap: 6px 8px; font-size: 12.5px; margin: 0; }\n.rm-about dt { color: var(--rm-quiet); margin: 0; }\n.rm-about dd { margin: 0; min-width: 0; overflow-wrap: anywhere; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n.rm-mono { font-family: var(--dsw-font-markdown-code-block-font-family, ui-monospace, monospace); font-size: 11.5px; }\n.rm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }\n.rm-danger { color: var(--dsw-alias-state-error-primary, #e5484d) !important; }\n.rm-note { font-size: 12px; color: var(--rm-quiet); line-height: 1.45; margin: 0 0 8px; }\n\n/* \u2500\u2500 empty / welcome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-welcome-wrap {\n flex: 1; display: flex; align-items: center; justify-content: center; padding: 32px; background: var(--rm-bg);\n}\n.rm-welcome {\n width: min(420px, 100%); display: flex; flex-direction: column; gap: 10px;\n padding: 20px; border-radius: 14px; border: .5px solid var(--rm-border); background: var(--rm-panel);\n}\n.rm-welcome h1 { margin: 0; font-size: 16px; font-weight: 600; }\n.rm-welcome p { margin: 0; font-size: 13px; color: var(--rm-muted); line-height: 1.5; }\n.rm-welcome-actions { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }\n.rm-welcome-action {\n display: flex; align-items: flex-start; gap: 10px; width: 100%; padding: 10px 12px;\n border: .5px solid var(--rm-border); border-radius: 10px; background: var(--rm-bg);\n color: var(--rm-label); cursor: pointer; text-align: left;\n}\n.rm-welcome-action:hover { background: var(--rm-hover); }\n.rm-welcome-action b { display: block; font-size: 13px; }\n.rm-welcome-action span { display: block; font-size: 12px; color: var(--rm-quiet); margin-top: 2px; }\n\n/* \u2500\u2500 avatars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-avatar {\n display: inline-flex; align-items: center; justify-content: center; flex: none;\n font-weight: 600; letter-spacing: .02em; user-select: none;\n}\n.rm-avatar-wrap { position: relative; display: inline-flex; flex: none; }\n\n/* \u2500\u2500 dialogs (inside dsh Modal) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-form { display: flex; flex-direction: column; gap: 14px; }\n.rm-field { display: flex; flex-direction: column; gap: 6px; }\n.rm-field-label { font-size: 13px; font-weight: 500; }\n.rm-field-hint { font-size: 12px; color: var(--rm-quiet); }\n.rm-name-row { display: flex; align-items: center; gap: 10px; }\n.rm-name-row > :last-child { flex: 1; }\n.rm-switch-row {\n display: flex; align-items: center; justify-content: space-between; gap: 14px;\n padding: 10px 12px; border-radius: 10px; background: var(--rm-panel);\n}\n.rm-error { color: var(--dsw-alias-state-error-primary, #e5484d); font-size: 12.5px; }\n.rm-invite {\n display: block; padding: 10px 12px; border-radius: 8px; background: var(--rm-panel);\n font-family: var(--dsw-font-markdown-code-block-font-family, ui-monospace, monospace);\n font-size: 12px; word-break: break-all; user-select: all;\n}\n\n/* Dual transport + approvals + agent config */\n.rm-mode-row { display: flex; gap: 8px; }\n.rm-mode-btn {\n flex: 1; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--rm-border, #2a2f3a);\n background: transparent; color: inherit; cursor: pointer; font: inherit;\n}\n.rm-mode-btn.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 16%, transparent); }\n.rm-lan-browse { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; align-items: center; }\n.rm-lan-peer {\n font-size: 12px; padding: 4px 8px; border-radius: 999px; border: 1px solid var(--rm-border, #2a2f3a);\n background: transparent; color: inherit; cursor: pointer;\n}\n.rm-approvals { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--rm-border, #2a2f3a); background: color-mix(in srgb, #f5a524 12%, transparent); }\n.rm-approval { display: flex; gap: 12px; align-items: center; justify-content: space-between; }\n.rm-approval-body { font-size: 13px; }\n.rm-approval-actions { display: flex; gap: 6px; flex-shrink: 0; }\n.rm-input { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; font: inherit; }\n\n.rm-ws-bar { display: flex; flex-direction: column; gap: 4px; padding: 8px 12px; border-bottom: 1px solid var(--rm-border, #2a2f3a); }\n.rm-ws-chip { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; cursor: pointer; font: inherit; font-size: 12.5px; }\n.rm-ws-path { font-size: 11px; color: var(--rm-quiet, #8b93a7); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item.is-active { background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); }\n\n/* Native-aligned room composer (mirrors dsh conversation control row) */\n.rm-composer-card {\n box-sizing: border-box;\n width: 100%;\n max-width: 920px;\n margin: 0 auto;\n border-radius: 16px;\n background: var(--rm-elev, var(--rm-panel, #12151c));\n border: 1px solid var(--rm-border, #2a2f3a);\n box-shadow: 0 1px 2px rgba(0,0,0,.06);\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 0 6px;\n}\n.rm-composer-grow { position: relative; padding: 0 4px; }\n.rm-composer-grow textarea {\n box-sizing: border-box; width: 100%; min-height: 36px; max-height: 160px;\n resize: none; border: none; outline: none; background: transparent;\n color: inherit; font: inherit; font-size: 14px; line-height: 22px;\n padding: 4px 12px 0 14px;\n}\n.rm-composer-grow textarea:disabled { opacity: .55; cursor: not-allowed; }\n.rm-composer-row {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;\n gap: 10px; min-width: 0; padding: 2px 8px 4px;\n}\n.rm-composer-tools, .rm-composer-modes, .rm-composer-trailing {\n display: flex; align-items: center; min-width: 0; gap: 8px;\n}\n.rm-composer-trailing { flex: none; margin-left: auto; }\n.rm-composer-add {\n width: 28px; height: 28px; border: none; border-radius: 999px;\n background: color-mix(in srgb, var(--rm-fg, #e8eaed) 8%, transparent);\n color: inherit; cursor: pointer; display: grid; place-items: center;\n}\n.rm-composer-add:hover, .rm-composer-add[aria-pressed="true"] {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 22%, transparent);\n}\n.rm-composer-select {\n max-width: 220px; height: 28px; border: none; border-radius: 8px;\n background: transparent; color: var(--rm-quiet, #8b93a7);\n font: inherit; font-size: 13px; font-weight: 500; line-height: 20px;\n padding: 0 8px; cursor: pointer; white-space: nowrap;\n}\n.rm-composer-select:hover:not(:disabled) {\n background: color-mix(in srgb, var(--rm-fg, #e8eaed) 8%, transparent);\n}\n.rm-composer-select:disabled { opacity: .5; cursor: default; }\n.rm-composer-model-text { display: inline; }\n.rm-composer-model-icon { display: none; font-size: 10px; }\n.rm-composer-effort {\n margin-left: 6px; font-size: 11px; opacity: .75;\n text-transform: uppercase; letter-spacing: .02em;\n}\n.rm-composer-send {\n width: 34px; height: 34px; border: none; border-radius: 999px;\n background: var(--rm-accent, #5b8cff); color: #fff; cursor: pointer;\n display: grid; place-items: center; transform: translateY(-2px);\n}\n.rm-composer-send:hover:not(:disabled) { filter: brightness(1.08); }\n.rm-composer-send:disabled { opacity: .4; cursor: default; }\n.rm-composer-popwrap { position: relative; }\n.rm-composer-menu {\n position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 40;\n min-width: 200px; max-height: 320px; overflow: auto;\n border: 1px solid var(--rm-border, #2a2f3a); border-radius: 12px;\n background: var(--rm-elev, #12151c); box-shadow: 0 8px 24px rgba(0,0,0,.28);\n padding: 6px; display: flex; flex-direction: column; gap: 2px;\n}\n.rm-composer-menu-wide { min-width: 280px; max-width: 340px; }\n.rm-composer-menu-label {\n font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;\n color: var(--rm-quiet, #8b93a7); padding: 6px 8px 2px;\n}\n.rm-composer-menu button[role="menuitem"] {\n text-align: left; border: none; background: transparent; color: inherit;\n font: inherit; font-size: 13px; padding: 6px 8px; border-radius: 8px; cursor: pointer;\n}\n.rm-composer-menu button[role="menuitem"]:hover,\n.rm-composer-menu button[role="menuitem"].is-active {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 16%, transparent);\n}\n.rm-composer-check {\n display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 8px; cursor: pointer;\n}\n.rm-grant-row {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 2px 8px; font-size: 12.5px;\n}\n.rm-grant-row select {\n font: inherit; font-size: 12px; border-radius: 6px;\n border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; padding: 2px 6px;\n}\n.rm-peer-grant-block { border-top: 1px solid var(--rm-border, #2a2f3a); padding-top: 4px; margin-top: 4px; }\n.rm-peer-grant-name { font-size: 12px; font-weight: 600; padding: 4px 8px; overflow: hidden; text-overflow: ellipsis; }\n\n/* Hide legacy tight composer if still referenced */\n.rm-composer { display: none; }\n\n.rm-composer-grow { position: relative; }\n.rm-mention-menu {\n position: absolute; left: 8px; right: 8px; bottom: calc(100% + 6px); z-index: 50;\n max-height: 240px; overflow: auto;\n border: 1px solid var(--rm-border, #2a2f3a); border-radius: 12px;\n background: var(--rm-elev, #12151c); box-shadow: 0 10px 28px rgba(0,0,0,.32);\n padding: 4px; display: flex; flex-direction: column; gap: 2px;\n}\n.rm-mention-item {\n display: flex; align-items: center; gap: 8px; text-align: left;\n border: none; background: transparent; color: inherit; font: inherit;\n padding: 6px 8px; border-radius: 8px; cursor: pointer;\n}\n.rm-mention-item:hover, .rm-mention-item.is-active {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 18%, transparent);\n}\n.rm-mention-item-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }\n.rm-mention-item-name { font-size: 13px; font-weight: 600; }\n.rm-mention-item-meta { font-size: 11px; color: var(--rm-quiet, #8b93a7); }\n';
41
+
42
+ // src/client/store.js
43
+ var API = "/api/collab";
44
+ async function call(path, init) {
45
+ const res = await fetch(API + path, { credentials: "same-origin", ...init, headers: { ...init && init.body ? { "content-type": "application/json" } : {}, ...init && init.headers || {} } });
46
+ const body = await res.json().catch(() => ({}));
47
+ if (!res.ok) throw new Error(body.error || `${res.status} ${res.statusText}`);
48
+ return body;
49
+ }
50
+ var post = (path, body) => call(path, { method: "POST", body: JSON.stringify(body || {}) });
51
+ function createRoomsStore() {
52
+ let snap = {
53
+ self: null,
54
+ relayUrl: "",
55
+ lanRelayPort: null,
56
+ workspaces: [],
57
+ activeWorkspaceId: null,
58
+ rooms: [],
59
+ activeRoomId: null,
60
+ connected: false,
61
+ selected: null,
62
+ messages: {},
63
+ dialog: null,
64
+ toast: null,
65
+ approvals: [],
66
+ search: { open: false, query: "", results: [], loading: false, error: null }
67
+ };
68
+ const subs = /* @__PURE__ */ new Set();
69
+ const set = (patch) => {
70
+ snap = { ...snap, ...typeof patch === "function" ? patch(snap) : patch };
71
+ for (const s of subs) s();
72
+ };
73
+ let es = null;
74
+ let searchTimer = null;
75
+ function connect() {
76
+ if (es) return;
77
+ es = new EventSource(API + "/stream");
78
+ es.addEventListener("rooms", (e) => {
79
+ const st = JSON.parse(e.data);
80
+ set((s) => {
81
+ const selected = s.selected && st.rooms.some((r) => r.roomId === s.selected) ? s.selected : st.activeRoomId || st.rooms[0] && st.rooms[0].roomId || null;
82
+ return { ...st, connected: true, selected };
83
+ });
84
+ const sel = snap.selected;
85
+ if (sel && !snap.messages[sel]) void loadMessages(sel);
86
+ });
87
+ es.addEventListener("message", (e) => {
88
+ const { roomId, message } = JSON.parse(e.data);
89
+ set((s) => {
90
+ const cur = s.messages[roomId];
91
+ if (!cur || cur.some((m) => m.id === message.id)) return {};
92
+ return { messages: { ...s.messages, [roomId]: [...cur, message].sort((a, b) => a.ts - b.ts) } };
93
+ });
94
+ });
95
+ es.onerror = () => set({ connected: false });
96
+ }
97
+ async function loadMessages(roomId) {
98
+ try {
99
+ const r = await call(`/rooms/${encodeURIComponent(roomId)}/messages`);
100
+ set((s) => ({ messages: { ...s.messages, [roomId]: r.messages } }));
101
+ } catch (e) {
102
+ toast(e.message);
103
+ }
104
+ }
105
+ function toast(text) {
106
+ set({ toast: text });
107
+ }
108
+ async function runSearch(query) {
109
+ set((s) => ({ search: { ...s.search, loading: true, error: null } }));
110
+ try {
111
+ const q = query.trim();
112
+ const tag = (/(^|\s)#([\w-]+)/.exec(q) || [])[2];
113
+ const text = q.replace(/(^|\s)#[\w-]+/g, " ").trim();
114
+ const p = new URLSearchParams();
115
+ if (text) p.set("q", text);
116
+ if (tag) p.set("tag", tag);
117
+ const r = await call(`/directory?${p}`);
118
+ set((s) => s.search.query === query ? { search: { ...s.search, results: r.rooms, loading: false } } : {});
119
+ } catch (e) {
120
+ set((s) => ({ search: { ...s.search, loading: false, error: e.message } }));
121
+ }
122
+ }
123
+ const actions = {
124
+ select(roomId) {
125
+ set({ selected: roomId });
126
+ if (!snap.messages[roomId]) void loadMessages(roomId);
127
+ void post(`/rooms/${encodeURIComponent(roomId)}/active`).catch(() => {
128
+ });
129
+ },
130
+ openDialog(dialog) {
131
+ set({ dialog });
132
+ },
133
+ closeDialog() {
134
+ set({ dialog: null });
135
+ },
136
+ toast,
137
+ clearToast() {
138
+ set({ toast: null });
139
+ },
140
+ setSearchOpen(open) {
141
+ set((s) => ({ search: { ...s.search, open, query: open ? s.search.query : "", results: open ? s.search.results : [] } }));
142
+ if (open) void runSearch(snap.search.query);
143
+ },
144
+ setQuery(query) {
145
+ set((s) => ({ search: { ...s.search, query } }));
146
+ clearTimeout(searchTimer);
147
+ searchTimer = setTimeout(() => void runSearch(query), 300);
148
+ },
149
+ async create(form) {
150
+ const r = await post("/rooms", { ...form, workspaceId: form.workspaceId || snap.activeWorkspaceId });
151
+ set({ selected: r.roomId, dialog: { kind: "invite", roomId: r.roomId, name: r.name, invite: r.invite, public: r.public, listed: r.listed } });
152
+ void loadMessages(r.roomId);
153
+ return r;
154
+ },
155
+ async join(invite, info) {
156
+ const r = await post("/join", { invite, workspaceId: snap.activeWorkspaceId, ...info || {} });
157
+ set((s) => ({ selected: r.roomId, dialog: null, search: { ...s.search, results: s.search.results.map((x) => x.roomId === r.roomId ? { ...x, joined: true } : x) } }));
158
+ void loadMessages(r.roomId);
159
+ toast(`Joined ${r.name}`);
160
+ return r;
161
+ },
162
+ async leave(roomId) {
163
+ await post(`/rooms/${encodeURIComponent(roomId)}/leave`);
164
+ set((s) => {
165
+ const m = { ...s.messages };
166
+ delete m[roomId];
167
+ return { messages: m, selected: s.selected === roomId ? null : s.selected, dialog: null };
168
+ });
169
+ toast("Left the room");
170
+ },
171
+ async send(roomId, text) {
172
+ return post(`/rooms/${encodeURIComponent(roomId)}/send`, { text });
173
+ },
174
+ async task(roomId, title, assignee, body) {
175
+ return post(`/rooms/${encodeURIComponent(roomId)}/task`, { title, assignee, body });
176
+ },
177
+ async invite(roomId) {
178
+ return (await call(`/rooms/${encodeURIComponent(roomId)}/invite`)).invite;
179
+ },
180
+ async browseLan() {
181
+ return call("/lan/browse");
182
+ },
183
+ async ensureWorkspace(form) {
184
+ const w = await post("/workspaces", form);
185
+ set((s) => ({ activeWorkspaceId: w.id, workspaces: s.workspaces.some((x) => x.id === w.id) ? s.workspaces.map((x) => x.id === w.id ? w : x) : [w, ...s.workspaces] }));
186
+ return w;
187
+ },
188
+ async setActiveWorkspace(id) {
189
+ const w = await post("/workspaces/active", { id });
190
+ set({ activeWorkspaceId: id, selected: null });
191
+ return w;
192
+ },
193
+ async getConfig(roomId) {
194
+ return call(`/rooms/${encodeURIComponent(roomId)}/config`);
195
+ },
196
+ async saveConfig(roomId, patch) {
197
+ return post(`/rooms/${encodeURIComponent(roomId)}/config`, patch);
198
+ },
199
+ async decideApproval(requestId, decision) {
200
+ return post("/approvals/decide", { requestId, decision });
201
+ },
202
+ async mcp(roomId, tool, args, target) {
203
+ return post(`/rooms/${encodeURIComponent(roomId)}/mcp`, { tool, args, target });
204
+ }
205
+ };
206
+ return {
207
+ getSnapshot: () => snap,
208
+ subscribe(fn) {
209
+ subs.add(fn);
210
+ connect();
211
+ return () => subs.delete(fn);
212
+ },
213
+ actions,
214
+ dispose() {
215
+ if (es) es.close();
216
+ es = null;
217
+ subs.clear();
218
+ clearTimeout(searchTimer);
219
+ }
220
+ };
221
+ }
222
+
223
+ // src/client/index.jsx
224
+ var import_jsx_runtime = require("react/jsx-runtime");
225
+ var name = "dsh-room-client";
226
+ var inject = ["slots", "layout"];
227
+ var PANEL_ID = "collab";
228
+ var Svg = ({ size = 16, children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.15", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
229
+ var RoomsIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.5", y: "3", width: "11", height: "10", rx: "2" }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 6.5h5M5.5 9.5h3.5" })
232
+ ] });
233
+ var FolderIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 4.5h4l1.5 1.5H13.5v7.5H2.5z" }) });
234
+ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
235
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "4.25" }),
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.2 10.2 3.3 3.3" })
237
+ ] });
238
+ var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" }) });
239
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size: 14, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3.5 3.5l9 9M12.5 3.5l-9 9" }) });
240
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 13V3.5M4 7.5l4-4 4 4" }) });
241
+ var LinkIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6.5 9.5l3-3M7 4.75l1-1a2.5 2.5 0 0 1 3.5 3.5l-1 1M9 11.25l-1 1a2.5 2.5 0 0 1-3.5-3.5l1-1" }) });
242
+ var TaskIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
243
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "2.75", width: "10", height: "10.5", rx: "2" }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.75 7.75l1.5 1.5 3-3" })
245
+ ] });
246
+ var GlobeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 12, children: [
247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "5.5" }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 8h11M8 2.5c1.6 1.6 2.25 3.5 2.25 5.5S9.6 11.9 8 13.5C6.4 11.9 5.75 10 5.75 8S6.4 4.1 8 2.5z" })
249
+ ] });
250
+ var LockIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 12, children: [
251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3.5", y: "7", width: "9", height: "6.25", rx: "1.5" }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 7V5.25a2.5 2.5 0 0 1 5 0V7" })
253
+ ] });
254
+ var PeopleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "5.5", r: "2.25" }),
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 13c.4-2 1.8-3.25 3.5-3.25S9.1 11 9.5 13" }),
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.5 3.6a2.2 2.2 0 0 1 0 4.1M11.5 9.9c1 .4 1.7 1.5 2 3.1" })
258
+ ] });
259
+ var HUES = [212, 262, 152, 28, 340, 190, 48, 290];
260
+ function hueOf(s) {
261
+ let h = 0;
262
+ for (const c of s || "") h = h * 31 + c.charCodeAt(0) >>> 0;
263
+ return HUES[h % HUES.length];
264
+ }
265
+ function initials(s) {
266
+ const w = (s || "?").replace(/[^\p{L}\p{N} _-]/gu, "").split(/[\s_-]+/).filter(Boolean);
267
+ return ((w[0] || "?")[0] + (w[1] ? w[1][0] : (w[0] || "").slice(1, 2))).toUpperCase();
268
+ }
269
+ function Avatar({ name: name2, size = 32 }) {
270
+ const h = hueOf(name2);
271
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-avatar", style: { width: size, height: size, borderRadius: Math.round(size * 0.28), fontSize: Math.round(size * 0.36), background: `hsl(${h} 55% 90%)`, color: `hsl(${h} 40% 30%)` }, "aria-hidden": "true", children: initials(name2) });
272
+ }
273
+ function fmtTime(ts) {
274
+ const d = new Date(ts), now = /* @__PURE__ */ new Date();
275
+ return d.toDateString() === now.toDateString() ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString([], { month: "short", day: "numeric" });
276
+ }
277
+ function fmtDay(ts) {
278
+ return new Date(ts).toLocaleDateString([], { year: "numeric", month: "long", day: "numeric" });
279
+ }
280
+ var STATUS_TONE = { open: "info", claimed: "warning", done: "success", failed: "danger", cancelled: "quiet" };
281
+ function useStore(store) {
282
+ return (0, import_react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
283
+ }
284
+ function renderMentions(text) {
285
+ return text.split(/(@[A-Za-z0-9][\w.:-]*)/g).map((part, i) => part.startsWith("@") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention", children: part }, i) : part);
286
+ }
287
+ function PanelIcon({ size }) {
288
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
289
+ }
290
+ function RoomsSidebar({ store, ensurePanel }) {
291
+ const s = useStore(store);
292
+ const a = store.actions;
293
+ const [menu, setMenu] = (0, import_react.useState)(null);
294
+ const [searching, setSearching] = (0, import_react.useState)(false);
295
+ const [joining, setJoining] = (0, import_react.useState)(null);
296
+ const inputRef = (0, import_react.useRef)(null);
297
+ const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0] || null;
298
+ (0, import_react.useEffect)(() => {
299
+ if (!menu) return;
300
+ const close = () => setMenu(null);
301
+ window.addEventListener("click", close);
302
+ return () => window.removeEventListener("click", close);
303
+ }, [menu]);
304
+ (0, import_react.useEffect)(() => {
305
+ if (searching && inputRef.current) inputRef.current.focus();
306
+ }, [searching]);
307
+ const q = (s.search.query || "").trim().toLowerCase();
308
+ const mine = (s.rooms || []).filter((r) => {
309
+ if (activeWs && r.workspaceId && r.workspaceId !== activeWs.id) return false;
310
+ if (!q) return true;
311
+ return r.name.toLowerCase().includes(q) || (r.description || "").toLowerCase().includes(q) || (r.tags || []).some((t) => ("#" + t).includes(q));
312
+ });
313
+ const join = async (r) => {
314
+ setJoining(r.roomId);
315
+ try {
316
+ await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
317
+ ensurePanel();
318
+ } catch (e) {
319
+ a.toast(e.message);
320
+ } finally {
321
+ setJoining(null);
322
+ }
323
+ };
324
+ if (!activeWs) {
325
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-side-toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "Workspaces" }) }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: "Bind a local workspace first. Rooms you create inherit its path (MCP sandbox + agent cwd)." }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace" })
330
+ ] })
331
+ ] });
332
+ }
333
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-toolbar", children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "DSH Room" }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", "aria-label": searching ? "Close search" : "Search rooms", "aria-pressed": searching, onClick: () => {
337
+ setSearching(!searching);
338
+ a.setSearchOpen(!searching);
339
+ }, children: searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) }),
340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
341
+ "button",
342
+ {
343
+ type: "button",
344
+ className: "rm-icon-btn",
345
+ "data-rm-new": "",
346
+ "aria-label": "New",
347
+ "aria-haspopup": "menu",
348
+ "aria-expanded": !!menu,
349
+ onClick: (e) => {
350
+ e.stopPropagation();
351
+ const r = e.currentTarget.getBoundingClientRect();
352
+ setMenu(menu ? null : { top: r.bottom + 4, left: Math.max(8, r.right - 200) });
353
+ },
354
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
355
+ }
356
+ ),
357
+ menu ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-menu", role: "menu", style: { top: menu.top, left: menu.left }, onClick: (e) => e.stopPropagation(), children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
359
+ setMenu(null);
360
+ a.openDialog({ kind: "create" });
361
+ }, children: [
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
363
+ "Create room in workspace"
364
+ ] }),
365
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
366
+ setMenu(null);
367
+ a.openDialog({ kind: "join" });
368
+ }, children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
370
+ "Join by invite"
371
+ ] }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
373
+ setMenu(null);
374
+ a.openDialog({ kind: "workspace" });
375
+ }, children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
377
+ "Add / switch workspace"
378
+ ] }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
380
+ setMenu(null);
381
+ setSearching(true);
382
+ a.setSearchOpen(true);
383
+ }, children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
385
+ "Browse public rooms"
386
+ ] })
387
+ ] }) : null
388
+ ] }),
389
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-bar", children: [
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-ws-chip", onClick: () => a.openDialog({ kind: "workspace" }), title: activeWs.path, children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: activeWs.name })
393
+ ] }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-path", title: activeWs.path, children: activeWs.path })
395
+ ] }),
396
+ searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: inputRef, type: "text", placeholder: "Search rooms or #tags", value: s.search.query, onChange: (e) => a.setQuery(e.target.value), onKeyDown: (e) => e.key === "Escape" && a.setSearchOpen(false), "aria-label": "Search rooms" })
399
+ ] }) : null,
400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
403
+ "Rooms in ",
404
+ activeWs.name
405
+ ] }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-list", children: [
407
+ mine.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
408
+ "button",
409
+ {
410
+ type: "button",
411
+ className: "rm-room-item",
412
+ "aria-current": r.roomId === s.selected ? "true" : void 0,
413
+ "data-room-id": r.roomId,
414
+ onClick: () => {
415
+ a.select(r.roomId);
416
+ ensurePanel();
417
+ },
418
+ children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + r.status, style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
422
+ ] }),
423
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
425
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: r.last ? `${r.last.from}: ${r.last.text}` : r.description || (r.public ? "Public room" : "Private \xB7 invite only") })
426
+ ] }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-trail", children: [
428
+ r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
430
+ "Public"
431
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
433
+ "Private"
434
+ ] }),
435
+ r.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
436
+ r.last ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--rm-quiet)" }, children: fmtTime(r.last.ts) }) : null
437
+ ] })
438
+ ]
439
+ },
440
+ r.roomId
441
+ )),
442
+ !mine.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: "No rooms in this workspace yet. Create one or join by invite \u2014 peers should check out the same project." }) : null
443
+ ] })
444
+ ] }),
445
+ (s.workspaces || []).length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-group-label", children: "Workspaces" }),
447
+ (s.workspaces || []).map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-room-item" + (w.id === activeWs.id ? " is-active" : ""), onClick: () => a.setActiveWorkspace(w.id), children: [
448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: w.name }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: w.path })
452
+ ] })
453
+ ] }, w.id))
454
+ ] }) : null,
455
+ searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
457
+ "Public directory ",
458
+ s.search.loading ? "\xB7 searching\u2026" : ""
459
+ ] }),
460
+ s.search.results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item", "data-dir-room-id": r.roomId, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item-body", children: [
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-room-item-name", children: r.name }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-item-meta", children: [
465
+ r.description || "No description",
466
+ " \xB7 ",
467
+ r.members,
468
+ " online"
469
+ ] }),
470
+ r.tags.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-dir-tags", children: r.tags.map((tg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-tag", children: [
471
+ "#",
472
+ tg
473
+ ] }, tg)) }) : null
474
+ ] }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: r.joined ? "ghost" : "outline", disabled: r.joined || joining === r.roomId, onClick: () => join(r), children: r.joined ? "Joined" : joining === r.roomId ? "\u2026" : "Join" })
476
+ ] }, r.roomId)),
477
+ !s.search.loading && !s.search.results.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: s.search.error ? `Directory error: ${s.search.error}` : "No public rooms match." }) : null,
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
479
+ "Joining attaches the room to workspace \u201C",
480
+ activeWs.name,
481
+ "\u201D. Point your local path at the same project."
482
+ ] })
483
+ ] }) : null
484
+ ] })
485
+ ] });
486
+ }
487
+ function Message({ m, showAuthor }) {
488
+ if (m.kind === "task.create") {
489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg rm-msg-event", "data-message-id": m.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-card", children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
493
+ "Task \xB7 ",
494
+ m.from
495
+ ] }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: "open" }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
498
+ ] }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-task-title", children: m.title }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: 0 }, children: m.assignee ? `Assigned to @${m.assignee}` : "Unassigned \u2014 any agent may claim" })
501
+ ] }) });
502
+ }
503
+ if (m.kind === "task.claim" || m.kind === "task.update") {
504
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg rm-msg-event", "data-message-id": m.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
507
+ m.self ? "You" : m.from,
508
+ " ",
509
+ m.kind === "task.claim" ? "claimed" : "updated",
510
+ " task"
511
+ ] }),
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: STATUS_TONE[m.status] || "neutral", children: m.status }),
513
+ m.kind === "task.update" && m.text.includes(": ") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: m.text.slice(m.text.indexOf(": ") + 2) }) : null,
514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
515
+ ] }) });
516
+ }
517
+ if (m.self) {
518
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg rm-msg-self", "data-message-id": m.id, children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-time", style: { textAlign: "right", marginTop: 2 }, children: fmtTime(m.ts) })
521
+ ] });
522
+ }
523
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg", "data-message-id": m.id, children: [
524
+ showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.from, size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
526
+ showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg-head", children: [
527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: m.from }),
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
529
+ ] }) : null,
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) })
531
+ ] })
532
+ ] });
533
+ }
534
+ function ChatBody({ messages }) {
535
+ const ref = (0, import_react.useRef)(null);
536
+ (0, import_react.useEffect)(() => {
537
+ const el = ref.current;
538
+ if (el) el.scrollTop = el.scrollHeight;
539
+ }, [messages.length]);
540
+ const nodes = [];
541
+ let day = null, prev = null;
542
+ for (const m of messages) {
543
+ const d = fmtDay(m.ts);
544
+ if (d !== day) {
545
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-day", children: d }, "d" + m.ts));
546
+ day = d;
547
+ prev = null;
548
+ }
549
+ const showAuthor = !prev || prev.from !== m.from || m.ts - prev.ts > 5 * 6e4;
550
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor }, m.id));
551
+ prev = m;
552
+ }
553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-messages", ref, children: nodes.length ? nodes : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-hint", children: "No messages yet. Send a message, @mention an agent to wake it, or create a task from the composer." }) });
554
+ }
555
+ function mentionQueryAt(text, caret) {
556
+ const before = text.slice(0, caret);
557
+ const m = /(^|[\s([{])@([A-Za-z0-9._:-]*)$/.exec(before);
558
+ if (!m) return null;
559
+ const at = before.lastIndexOf("@");
560
+ return { start: at, end: caret, query: m[2] || "" };
561
+ }
562
+ function MentionMenu({ items, active, onPick, onHover }) {
563
+ if (!items.length) return null;
564
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-mention-menu", role: "listbox", "aria-label": "Mention someone", children: items.map((it, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
565
+ "button",
566
+ {
567
+ type: "button",
568
+ role: "option",
569
+ "aria-selected": i === active,
570
+ className: "rm-mention-item" + (i === active ? " is-active" : ""),
571
+ onMouseEnter: () => onHover(i),
572
+ onMouseDown: (e) => {
573
+ e.preventDefault();
574
+ onPick(it);
575
+ },
576
+ children: [
577
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.id, size: 22 }),
578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-body", children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-name", children: [
580
+ "@",
581
+ it.id
582
+ ] }),
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention-item-meta", children: it.hint })
584
+ ] }),
585
+ it.online === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "offline" }) : null
586
+ ]
587
+ },
588
+ it.id
589
+ )) });
590
+ }
591
+ function Composer({ room, store, draft: draft2, setDraft: setDraft2 }) {
592
+ const a = store.actions;
593
+ const [mode, setMode] = (0, import_react.useState)("chat");
594
+ const [busy, setBusy] = (0, import_react.useState)(false);
595
+ const [modelOpen, setModelOpen] = (0, import_react.useState)(false);
596
+ const [permOpen, setPermOpen] = (0, import_react.useState)(false);
597
+ const [peerOpen, setPeerOpen] = (0, import_react.useState)(false);
598
+ const [mentionActive, setMentionActive2] = (0, import_react.useState)(0);
599
+ const [caret, setCaret2] = (0, import_react.useState)(0);
600
+ const [mentionDismissed, setMentionDismissed] = (0, import_react.useState)(false);
601
+ const taRef2 = (0, import_react.useRef)(null);
602
+ const offline = room.status !== "online";
603
+ const mqRaw = mentionQueryAt(draft2, caret);
604
+ const mq2 = mentionDismissed ? null : mqRaw;
605
+ const mentionItems = (() => {
606
+ if (!mq2) return [];
607
+ const q = mq2.query.toLowerCase();
608
+ const members = (room.members || []).filter((m) => !m.self);
609
+ const known = members.map((m) => ({
610
+ id: m.agentId,
611
+ hint: m.online === false ? "member \xB7 offline" : "member \xB7 wake",
612
+ online: m.online !== false
613
+ }));
614
+ const all = { id: "all", hint: "Everyone \xB7 wake all members", online: true };
615
+ const list = [all, ...known];
616
+ return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || ("@" + it.id).toLowerCase().startsWith("@" + q));
617
+ })();
618
+ const mc = room.memberConfig || {};
619
+ const modelLabel = mc.model || "Default model";
620
+ const effort = mc.reasoningEffort || "medium";
621
+ const permSummary = (() => {
622
+ const p = mc.permissions || {};
623
+ const bits = [];
624
+ if (p.autoRead !== false) bits.push("read");
625
+ if (p.autoWrite) bits.push("write");
626
+ if (p.autoShell) bits.push("shell");
627
+ if (p.autoNetwork) bits.push("net");
628
+ return bits.length ? `Auto \xB7 ${bits.join("+")}` : "Ask to run";
629
+ })();
630
+ (0, import_react.useEffect)(() => {
631
+ setMentionActive2(0);
632
+ setMentionDismissed(false);
633
+ }, [mqRaw && mqRaw.query, mqRaw && mqRaw.start]);
634
+ const MODELS = ["deepseek-chat", "deepseek-reasoner", "deepseek-coder"];
635
+ const EFFORTS = ["low", "medium", "high", "max"];
636
+ const patchConfig = async (patch) => {
637
+ try {
638
+ const next = {
639
+ model: mc.model,
640
+ provider: mc.provider,
641
+ reasoningEffort: mc.reasoningEffort || "medium",
642
+ workspacePath: mc.workspacePath,
643
+ workspaceLabel: mc.workspaceLabel,
644
+ mcpEnabled: mc.mcpEnabled !== false,
645
+ ...patch,
646
+ permissions: { ...mc.permissions || { autoRead: true, autoWrite: false, autoShell: false, autoNetwork: false }, ...patch.permissions || {} },
647
+ peerGrants: patch.peerGrants !== void 0 ? patch.peerGrants : mc.peerGrants
648
+ };
649
+ await a.saveConfig(room.roomId, next);
650
+ } catch (e) {
651
+ a.toast(e.message);
652
+ }
653
+ };
654
+ const syncCaret = (el) => {
655
+ if (!el) return;
656
+ setCaret2(el.selectionStart ?? el.value.length);
657
+ };
658
+ const insertMention = (it) => {
659
+ if (!mq2) return;
660
+ const insert = `@${it.id} `;
661
+ const next = draft2.slice(0, mq2.start) + insert + draft2.slice(mq2.end);
662
+ setDraft2(next);
663
+ const pos = mq2.start + insert.length;
664
+ setCaret2(pos);
665
+ setMentionActive2(0);
666
+ requestAnimationFrame(() => {
667
+ const el = taRef2.current;
668
+ if (el) {
669
+ el.focus();
670
+ el.setSelectionRange(pos, pos);
671
+ }
672
+ });
673
+ };
674
+ const submit = async () => {
675
+ const text = draft2.trim();
676
+ if (!text || busy) return;
677
+ setBusy(true);
678
+ try {
679
+ if (mode === "task") {
680
+ const m = /^@([A-Za-z0-9][\w.:-]*)\s+/.exec(text);
681
+ await a.task(room.roomId, m ? text.slice(m[0].length) : text, m ? m[1] : void 0);
682
+ setMode("chat");
683
+ } else await a.send(room.roomId, text);
684
+ setDraft2("");
685
+ } catch (e) {
686
+ a.toast(e.message);
687
+ } finally {
688
+ setBusy(false);
689
+ }
690
+ };
691
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-card", "data-rm-composer": "", children: [
692
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-grow", children: [
693
+ mq2 && mentionItems.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MentionMenu, { items: mentionItems, active: mentionActive, onHover: setMentionActive2, onPick: insertMention }) : null,
694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
695
+ "textarea",
696
+ {
697
+ ref: taRef2,
698
+ rows: 1,
699
+ disabled: offline,
700
+ "aria-label": "Message the room",
701
+ placeholder: offline ? "Room is offline" : mode === "task" ? "Task: @assignee title (optional assignee)" : `Message ${room.name} \xB7 @agent or @all`,
702
+ value: draft2,
703
+ onChange: (e) => {
704
+ setDraft2(e.target.value);
705
+ syncCaret(e.target);
706
+ },
707
+ onClick: (e) => syncCaret(e.target),
708
+ onKeyUp: (e) => syncCaret(e.target),
709
+ onSelect: (e) => syncCaret(e.target),
710
+ onKeyDown: (e) => {
711
+ if (mq2 && mentionItems.length) {
712
+ if (e.key === "ArrowDown") {
713
+ e.preventDefault();
714
+ setMentionActive2((i) => (i + 1) % mentionItems.length);
715
+ return;
716
+ }
717
+ if (e.key === "ArrowUp") {
718
+ e.preventDefault();
719
+ setMentionActive2((i) => (i - 1 + mentionItems.length) % mentionItems.length);
720
+ return;
721
+ }
722
+ if (e.key === "Enter" || e.key === "Tab") {
723
+ e.preventDefault();
724
+ insertMention(mentionItems[mentionActive] || mentionItems[0]);
725
+ return;
726
+ }
727
+ if (e.key === "Escape") {
728
+ e.preventDefault();
729
+ setMentionDismissed(true);
730
+ return;
731
+ }
732
+ }
733
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
734
+ e.preventDefault();
735
+ void submit();
736
+ }
737
+ }
738
+ }
739
+ )
740
+ ] }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-row", children: [
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-tools", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-add", title: mode === "task" ? "Switch to chat" : "Create a task", "aria-pressed": mode === "task", onClick: () => setMode(mode === "task" ? "chat" : "task"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}) }) }),
743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-modes", children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-composer-select", disabled: offline, onClick: () => {
746
+ setModelOpen(!modelOpen);
747
+ setPermOpen(false);
748
+ setPeerOpen(false);
749
+ }, title: "Room agent model", children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-model-text", children: modelLabel }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-model-icon", "aria-hidden": true, children: "\u25C6" }),
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-effort", children: effort })
753
+ ] }),
754
+ modelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu", role: "menu", children: [
755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Model" }),
756
+ MODELS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", className: mc.model === m ? "is-active" : "", onClick: () => {
757
+ void patchConfig({ model: m });
758
+ setModelOpen(false);
759
+ }, children: m }, m)),
760
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Reasoning" }),
761
+ EFFORTS.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", className: effort === e ? "is-active" : "", onClick: () => {
762
+ void patchConfig({ reasoningEffort: e });
763
+ setModelOpen(false);
764
+ }, children: e }, e)),
765
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: () => {
766
+ setModelOpen(false);
767
+ a.openDialog({ kind: "config", roomId: room.roomId });
768
+ }, children: "More agent settings\u2026" })
769
+ ] }) : null
770
+ ] }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-select", disabled: offline, onClick: () => {
773
+ setPermOpen(!permOpen);
774
+ setModelOpen(false);
775
+ setPeerOpen(false);
776
+ }, title: "Your auto-permissions", children: permSummary }),
777
+ permOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu", role: "menu", children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Auto-allow (you)" }),
779
+ [["autoRead", "Read"], ["autoWrite", "Write"], ["autoShell", "Shell"], ["autoNetwork", "Network"]].map(([k, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-composer-check", children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
781
+ "input",
782
+ {
783
+ type: "checkbox",
784
+ checked: !!(mc.permissions || {})[k] || k === "autoRead" && mc.permissions?.autoRead !== false,
785
+ onChange: (e) => void patchConfig({ permissions: { [k]: e.target.checked } })
786
+ }
787
+ ),
788
+ label
789
+ ] }, k)),
790
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: () => {
791
+ setPermOpen(false);
792
+ a.openDialog({ kind: "config", roomId: room.roomId });
793
+ }, children: "Open full settings\u2026" })
794
+ ] }) : null
795
+ ] }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
797
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-select rm-composer-peers", disabled: offline, onClick: () => {
798
+ setPeerOpen(!peerOpen);
799
+ setModelOpen(false);
800
+ setPermOpen(false);
801
+ }, title: "Peer MCP limits on this workspace", children: "Peer MCP" }),
802
+ peerOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PeerGrantsMenu, { room, store, onClose: () => setPeerOpen(false), patchConfig }) : null
803
+ ] })
804
+ ] }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-trailing", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-send", "aria-label": mode === "task" ? "Create task" : "Send", disabled: !draft2.trim() || busy || offline, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {}) }) })
806
+ ] })
807
+ ] });
808
+ }
809
+ function PeerGrantsMenu({ room, store, onClose, patchConfig }) {
810
+ const mc = room.memberConfig || {};
811
+ const def = mc.peerGrants && mc.peerGrants.default || { read: "auto", write: "ask", exec: "ask", delete: "ask" };
812
+ const byAgent = mc.peerGrants && mc.peerGrants.byAgent || {};
813
+ const peers = (room.members || []).filter((m) => !m.self);
814
+ const setDefault = (cap, v) => {
815
+ void patchConfig({ peerGrants: { default: { ...def, [cap]: v }, byAgent: { ...byAgent } } });
816
+ };
817
+ const setPeer = (agentId, cap, v) => {
818
+ const cur = { ...byAgent[agentId] || {} };
819
+ cur[cap] = v;
820
+ void patchConfig({ peerGrants: { default: { ...def }, byAgent: { ...byAgent, [agentId]: cur } } });
821
+ };
822
+ const GRANT = ["auto", "ask", "deny"];
823
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu rm-composer-menu-wide", role: "menu", children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Default peer limits (this workspace)" }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: "0 8px 6px" }, children: "Controls what same-room peers may do via MCP on your path. Never shares API keys." }),
826
+ ["read", "write", "exec", "delete"].map((cap) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-grant-row", children: [
827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: cap }),
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: def[cap] || "ask", onChange: (e) => setDefault(cap, e.target.value), "aria-label": `Default ${cap}`, children: GRANT.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: g, children: g }, g)) })
829
+ ] }, cap)),
830
+ peers.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Per peer" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: 8 }, children: "No other peers online yet." }),
831
+ peers.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-peer-grant-block", children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-peer-grant-name", children: m.agentId }),
833
+ ["read", "write", "exec", "delete"].map((cap) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-grant-row", children: [
834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: cap }),
835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: byAgent[m.agentId] && byAgent[m.agentId][cap] || def[cap] || "ask", onChange: (e) => setPeer(m.agentId, cap, e.target.value), "aria-label": `${m.agentId} ${cap}`, children: GRANT.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: g, children: g }, g)) })
836
+ ] }, cap))
837
+ ] }, m.agentId)),
838
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: onClose, children: "Done" })
839
+ ] });
840
+ }
841
+ function RoomDetail({ room, messages, store, mention, assign }) {
842
+ const a = store.actions;
843
+ const tasks = (0, import_react.useMemo)(() => {
844
+ const map = /* @__PURE__ */ new Map();
845
+ for (const m of messages) {
846
+ if (!m.taskId) continue;
847
+ const t = map.get(m.taskId) || { taskId: m.taskId, title: m.taskId, status: "open" };
848
+ if (m.kind === "task.create") Object.assign(t, { title: m.title, assignee: m.assignee });
849
+ if (m.status) t.status = m.status;
850
+ map.set(m.taskId, t);
851
+ }
852
+ return [...map.values()].reverse();
853
+ }, [messages]);
854
+ const copyInvite = async () => {
855
+ const inv = await a.invite(room.roomId);
856
+ a.toast(await (0, import_dsh_client_ui_primitives.writeClipboard)(inv) ? "Invite copied" : inv);
857
+ };
858
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "rm-detail", "aria-label": "Room details", children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PeopleIcon, {}),
862
+ "Members \xB7 ",
863
+ room.members.length
864
+ ] }),
865
+ room.members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-member", "data-agent-id": m.agentId, children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
867
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.agentId, size: 26 }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + (m.online === false ? "offline" : "online"), style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
869
+ ] }),
870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.agentId }),
871
+ m.self ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "you" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-member-actions", children: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${m.agentId}`, onClick: () => mention(m.agentId), children: "@" }),
873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `Assign a task to ${m.agentId}`, onClick: () => assign(m.agentId), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}) })
874
+ ] })
875
+ ] }, m.agentId)),
876
+ !room.members.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.status === "online" ? "Only you are here." : "Offline." }) : null
877
+ ] }),
878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
881
+ "Tasks \xB7 ",
882
+ tasks.length
883
+ ] }),
884
+ tasks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-row", children: [
885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-task-row-title", children: t.title }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: STATUS_TONE[t.status] || "neutral", children: t.status }),
887
+ t.assignee ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-note", style: { margin: 0 }, children: [
888
+ "@",
889
+ t.assignee
890
+ ] }) : null
891
+ ] }, t.taskId)),
892
+ !tasks.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No tasks. Use the task button in the composer." }) : null
893
+ ] }),
894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
897
+ "Invite"
898
+ ] }),
899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.public ? "Public: listed in the relay directory with its key. Anyone can find, join, and read this room." : "Private: only agents holding the invite can join. The invite contains the room key \u2014 share it only with agents you trust." }),
900
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: copyInvite, icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}), children: "Copy invite" }) })
901
+ ] }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
903
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "About" }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "rm-about", children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Visibility" }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
908
+ " Public"
909
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
911
+ " Private"
912
+ ] }) }),
913
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Transport" }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
915
+ room.memberConfig?.workspaceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Workspace" }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.memberConfig.workspaceLabel })
918
+ ] }) : null,
919
+ room.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
920
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Description" }),
921
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.description })
922
+ ] }) : null,
923
+ room.tags?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
924
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Tags" }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.tags.map((t) => "#" + t).join(" ") })
926
+ ] }) : null,
927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Room id" }),
928
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "rm-mono", children: room.roomId }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Session" }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "rm-mono", children: room.sessionTitle }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Encryption" }),
932
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: "E2EE (AES-GCM); relay sees ciphertext only" })
933
+ ] }),
934
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-actions", children: [
935
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.openDialog({ kind: "config", roomId: room.roomId }), children: "Agent config" }),
936
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "ghost", className: "rm-danger", onClick: () => a.openDialog({ kind: "leave", roomId: room.roomId, name: room.name }), children: "Leave room" })
937
+ ] })
938
+ ] })
939
+ ] });
940
+ }
941
+ function EmptyMain({ store }) {
942
+ const s = useStore(store);
943
+ const a = store.actions;
944
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-root rm-welcome-wrap", "data-collab-main": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome", children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-badge", children: "Collab \xB7 E2EE \xB7 no accounts" }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Workspaces, then rooms" }),
947
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Bind a local workspace (project folder), then create rooms inside it. Peers join those rooms and point their agent at the same project. @mention or tasks wake agents. Relay or LAN transport; E2EE; optional public directory." }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome-actions", children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "workspace" }), children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Bind workspace" }),
953
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Local project path shared by agents in rooms" })
954
+ ] })
955
+ ] }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "create" }), children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Create room" }),
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "In the active workspace \xB7 Relay or LAN" })
961
+ ] })
962
+ ] }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "join" }), children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
965
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Join by invite" }),
967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://join/\u2026 invite" })
968
+ ] })
969
+ ] }),
970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.setSearchOpen(true), children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
972
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
973
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Browse public rooms" }),
974
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Search the relay directory by name or #tag" })
975
+ ] })
976
+ ] })
977
+ ] })
978
+ ] }) });
979
+ }
980
+ function RoomsMain({ store }) {
981
+ const s = useStore(store);
982
+ const [drafts, setDrafts] = (0, import_react.useState)({});
983
+ const room = s.rooms.find((r) => r.roomId === s.selected);
984
+ if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store });
985
+ const messages = s.messages[room.roomId] || [];
986
+ const draft2 = drafts[room.roomId] || "";
987
+ const setDraft2 = (v) => setDrafts((d) => ({ ...d, [room.roomId]: typeof v === "function" ? v(d[room.roomId] || "") : v }));
988
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
994
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
997
+ room.members.filter((m) => m.online !== false).length,
998
+ " online \xB7 ",
999
+ room.public ? "public" : "private",
1000
+ room.status !== "online" ? ` \xB7 room ${room.status}` : ""
1001
+ ] })
1002
+ ] }),
1003
+ room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
1004
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1005
+ "Public"
1006
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
1008
+ "Private"
1009
+ ] }),
1010
+ room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
1011
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
1012
+ ] }),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft: draft2, setDraft: setDraft2 })
1015
+ ] }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1017
+ RoomDetail,
1018
+ {
1019
+ room,
1020
+ messages,
1021
+ store,
1022
+ mention: (id) => setDraft2((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
1023
+ assign: (id) => {
1024
+ setDraft2(`@${id} `);
1025
+ store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
1026
+ }
1027
+ }
1028
+ )
1029
+ ] })
1030
+ ] });
1031
+ }
1032
+ function Field({ label, hint, children, htmlFor }) {
1033
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-field", children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "rm-field-label", htmlFor, children: label }),
1035
+ children,
1036
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: hint }) : null
1037
+ ] });
1038
+ }
1039
+ function CreateDialog({ store }) {
1040
+ const s = useStore(store);
1041
+ const a = store.actions;
1042
+ const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80" });
1043
+ const [err, setErr] = (0, import_react.useState)(null);
1044
+ const [busy, setBusy] = (0, import_react.useState)(false);
1045
+ const [lanPeers, setLanPeers] = (0, import_react.useState)([]);
1046
+ const ok = f.name.trim() && !busy;
1047
+ const syncCaret = (el) => {
1048
+ if (!el) return;
1049
+ setCaret(el.selectionStart ?? el.value.length);
1050
+ };
1051
+ const insertMention = (it) => {
1052
+ if (!mq) return;
1053
+ const insert = `@${it.id} `;
1054
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
1055
+ setDraft(next);
1056
+ const pos = mq.start + insert.length;
1057
+ setCaret(pos);
1058
+ setMentionActive(0);
1059
+ requestAnimationFrame(() => {
1060
+ const el = taRef.current;
1061
+ if (el) {
1062
+ el.focus();
1063
+ el.setSelectionRange(pos, pos);
1064
+ }
1065
+ });
1066
+ };
1067
+ const submit = async () => {
1068
+ if (!ok) return;
1069
+ setBusy(true);
1070
+ setErr(null);
1071
+ try {
1072
+ await a.create({
1073
+ name: f.name.trim(),
1074
+ description: f.description.trim(),
1075
+ tags: f.tags,
1076
+ public: f.public,
1077
+ mode: f.mode,
1078
+ lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0
1079
+ });
1080
+ } catch (e) {
1081
+ setErr(e.message);
1082
+ setBusy(false);
1083
+ }
1084
+ };
1085
+ const browseLan = async () => {
1086
+ try {
1087
+ const r = await a.browseLan();
1088
+ setLanPeers(r.peers || []);
1089
+ } catch (e) {
1090
+ setErr(e.message);
1091
+ }
1092
+ };
1093
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1094
+ import_dsh_client_ui_primitives.Modal,
1095
+ {
1096
+ open: true,
1097
+ onClose: a.closeDialog,
1098
+ title: "Create room",
1099
+ closeLabel: "Close",
1100
+ description: "Room is created inside the active workspace (path becomes MCP sandbox). Default transport is the VPS Node relay (62.234.113.56:80) with optional public directory. Cloudflare remains an advanced option. The room key never goes to the relay.",
1101
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !ok, onClick: submit, children: busy ? "Creating\u2026" : "Create" })
1104
+ ] }),
1105
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", onKeyDown: (e) => e.key === "Enter" && e.target.tagName === "INPUT" && submit(), children: [
1106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Transport", hint: "Default: VPS Node relay at 62.234.113.56:80 (invite-only or public directory). Blank host = embedded local LAN + mDNS. Cloudflare is optional.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
1107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.mode === "lan" ? " is-on" : ""), onClick: () => setF({ ...f, mode: "lan", lanHostPort: f.lanHostPort || "62.234.113.56:80" }), children: "LAN / VPS" }),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.mode === "relay" ? " is-on" : ""), onClick: () => setF({ ...f, mode: "relay" }), children: "Cloudflare (advanced)" })
1109
+ ] }) }),
1110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-create-name", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-name-row", children: [
1111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: f.name || "room", size: 36 }),
1112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-name", autoFocus: true, placeholder: "e.g. release-crew", maxLength: 60, value: f.name, onChange: (e) => setF({ ...f, name: e.target.value }) })
1113
+ ] }) }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Description (optional)", htmlFor: "rm-create-desc", hint: "Shown to members and, if public, in search results.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-desc", placeholder: "e.g. Agents that review and ship releases", maxLength: 280, value: f.description, onChange: (e) => setF({ ...f, description: e.target.value }) }) }),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Tags (optional)", htmlFor: "rm-create-tags", hint: "Spaces or commas, up to 8. Used by public room search.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-tags", placeholder: "e.g. review rust", value: f.tags, onChange: (e) => setF({ ...f, tags: e.target.value }) }) }),
1116
+ f.mode === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { label: "Relay host:port", htmlFor: "rm-lan-hp", hint: "Default VPS 62.234.113.56:80 (public directory works). Blank = this machine (LAN/Tailscale + mDNS). Invite: collab://lan/host:port/room#key.", children: [
1117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-lan-hp", placeholder: "62.234.113.56:80 or 100.x.x.x:7421", value: f.lanHostPort, onChange: (e) => setF({ ...f, lanHostPort: e.target.value }) }),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-lan-browse", children: [
1119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: browseLan, children: "Browse mDNS" }),
1120
+ lanPeers.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-lan-peer", onClick: () => setF({ ...f, lanHostPort: p.hostPort }), children: [
1121
+ p.hostPort,
1122
+ p.name ? ` \xB7 ${p.name}` : ""
1123
+ ] }, p.hostPort))
1124
+ ] })
1125
+ ] }) : null,
1126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "List in public directory" }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: f.public ? "Anyone can find this room by name or tag and join it. Its key is published with the listing \u2014 NOT confidential." : "Private: invite only. Nothing about the room is published." })
1130
+ ] }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.public, onChange: (v) => setF({ ...f, public: v }), label: "List in public directory" })
1132
+ ] }),
1133
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
1134
+ ] })
1135
+ }
1136
+ );
1137
+ }
1138
+ function JoinDialog({ store }) {
1139
+ const s = useStore(store);
1140
+ const a = store.actions;
1141
+ const [invite, setInvite] = (0, import_react.useState)("");
1142
+ const [err, setErr] = (0, import_react.useState)(null);
1143
+ const [busy, setBusy] = (0, import_react.useState)(false);
1144
+ const valid = /^collab:\/\/(join|lan|relay)\/.+#\S+$/.test(invite.trim());
1145
+ const syncCaret = (el) => {
1146
+ if (!el) return;
1147
+ setCaret(el.selectionStart ?? el.value.length);
1148
+ };
1149
+ const insertMention = (it) => {
1150
+ if (!mq) return;
1151
+ const insert = `@${it.id} `;
1152
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
1153
+ setDraft(next);
1154
+ const pos = mq.start + insert.length;
1155
+ setCaret(pos);
1156
+ setMentionActive(0);
1157
+ requestAnimationFrame(() => {
1158
+ const el = taRef.current;
1159
+ if (el) {
1160
+ el.focus();
1161
+ el.setSelectionRange(pos, pos);
1162
+ }
1163
+ });
1164
+ };
1165
+ const submit = async () => {
1166
+ if (!valid || busy) return;
1167
+ setBusy(true);
1168
+ setErr(null);
1169
+ try {
1170
+ await a.join(invite.trim());
1171
+ } catch (e) {
1172
+ setErr(e.message);
1173
+ setBusy(false);
1174
+ }
1175
+ };
1176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1177
+ import_dsh_client_ui_primitives.Modal,
1178
+ {
1179
+ open: true,
1180
+ onClose: a.closeDialog,
1181
+ title: "Join room",
1182
+ closeLabel: "Close",
1183
+ description: "Paste an invite. The room attaches to your active workspace \u2014 set the local path to your checkout of the same project before collaborating via MCP.",
1184
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !valid || busy, onClick: submit, children: busy ? "Joining\u2026" : "Join" })
1187
+ ] }),
1188
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { autoFocus: true, "aria-label": "Invite", placeholder: "collab://join/\u2026 or collab://lan/<host>:<port>/<roomId>#\u2026", value: invite, onChange: (e) => setInvite(e.target.value), onKeyDown: (e) => e.key === "Enter" && submit() }),
1190
+ invite && !valid ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Invites: collab://join/\u2026#key, collab://lan/host:port/room#key, or collab://relay/host/join/room#key" }) : null,
1191
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
1192
+ ] })
1193
+ }
1194
+ );
1195
+ }
1196
+ function InviteDialog({ store, d }) {
1197
+ const s = useStore(store);
1198
+ const a = store.actions;
1199
+ const [copied, setCopied] = (0, import_react.useState)(false);
1200
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1201
+ import_dsh_client_ui_primitives.Modal,
1202
+ {
1203
+ open: true,
1204
+ onClose: a.closeDialog,
1205
+ title: `Room "${d.name}" created`,
1206
+ closeLabel: "Close",
1207
+ description: d.public ? d.listed ? "Listed in the public directory: other agents can find it with search and join with one click." : "Directory listing failed; the room still works by invite." : "Private room. Share this invite with the agents you want in it.",
1208
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Done" }),
1210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", onClick: async () => setCopied(await (0, import_dsh_client_ui_primitives.writeClipboard)(d.invite)), children: copied ? "Copied" : "Copy invite" })
1211
+ ] }),
1212
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "rm-invite", "data-invite": "", children: d.invite }),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Anyone holding the invite can join and read the room." })
1215
+ ] })
1216
+ }
1217
+ );
1218
+ }
1219
+ function LeaveDialog({ store, d }) {
1220
+ const s = useStore(store);
1221
+ const a = store.actions;
1222
+ const [busy, setBusy] = (0, import_react.useState)(false);
1223
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1224
+ import_dsh_client_ui_primitives.Modal,
1225
+ {
1226
+ open: true,
1227
+ onClose: a.closeDialog,
1228
+ title: `Leave "${d.name}"?`,
1229
+ closeLabel: "Close",
1230
+ description: "This machine forgets the room and its invite. If you listed it publicly, the listing is removed.",
1231
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: async () => {
1234
+ setBusy(true);
1235
+ try {
1236
+ await a.leave(d.roomId);
1237
+ } catch (e) {
1238
+ a.toast(e.message);
1239
+ setBusy(false);
1240
+ }
1241
+ }, children: busy ? "Leaving\u2026" : "Leave" })
1242
+ ] })
1243
+ }
1244
+ );
1245
+ }
1246
+ function ConfigDialog({ store, d }) {
1247
+ const s = useStore(store);
1248
+ const a = store.actions;
1249
+ const room = (store.getSnapshot().rooms || []).find((r) => r.roomId === d.roomId);
1250
+ const mc = room?.memberConfig || {};
1251
+ const [f, setF] = (0, import_react.useState)({
1252
+ model: mc.model || "",
1253
+ provider: mc.provider || "",
1254
+ reasoningEffort: mc.reasoningEffort || "medium",
1255
+ workspacePath: "",
1256
+ workspaceLabel: mc.workspaceLabel || "",
1257
+ autoRead: mc.permissions?.autoRead !== false,
1258
+ autoWrite: !!mc.permissions?.autoWrite,
1259
+ autoShell: !!mc.permissions?.autoShell,
1260
+ autoNetwork: !!mc.permissions?.autoNetwork,
1261
+ mcpEnabled: mc.mcpEnabled !== false
1262
+ });
1263
+ const [err, setErr] = (0, import_react.useState)(null);
1264
+ const [busy, setBusy] = (0, import_react.useState)(false);
1265
+ (0, import_react.useEffect)(() => {
1266
+ a.getConfig(d.roomId).then((c) => setF((x) => ({ ...x, workspacePath: c.workspacePath || "", workspaceLabel: c.workspaceLabel || x.workspaceLabel, model: c.model || "", provider: c.provider || "", reasoningEffort: c.reasoningEffort || "medium", autoRead: c.permissions?.autoRead !== false, autoWrite: !!c.permissions?.autoWrite, autoShell: !!c.permissions?.autoShell, autoNetwork: !!c.permissions?.autoNetwork, mcpEnabled: c.mcpEnabled !== false }))).catch(() => {
1267
+ });
1268
+ }, [d.roomId]);
1269
+ const syncCaret = (el) => {
1270
+ if (!el) return;
1271
+ setCaret(el.selectionStart ?? el.value.length);
1272
+ };
1273
+ const insertMention = (it) => {
1274
+ if (!mq) return;
1275
+ const insert = `@${it.id} `;
1276
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
1277
+ setDraft(next);
1278
+ const pos = mq.start + insert.length;
1279
+ setCaret(pos);
1280
+ setMentionActive(0);
1281
+ requestAnimationFrame(() => {
1282
+ const el = taRef.current;
1283
+ if (el) {
1284
+ el.focus();
1285
+ el.setSelectionRange(pos, pos);
1286
+ }
1287
+ });
1288
+ };
1289
+ const submit = async () => {
1290
+ setBusy(true);
1291
+ setErr(null);
1292
+ try {
1293
+ await a.saveConfig(d.roomId, {
1294
+ model: f.model || void 0,
1295
+ provider: f.provider || void 0,
1296
+ reasoningEffort: f.reasoningEffort,
1297
+ workspacePath: f.workspacePath || void 0,
1298
+ workspaceLabel: f.workspaceLabel || void 0,
1299
+ mcpEnabled: f.mcpEnabled,
1300
+ permissions: { autoRead: f.autoRead, autoWrite: f.autoWrite, autoShell: f.autoShell, autoNetwork: f.autoNetwork }
1301
+ });
1302
+ a.closeDialog();
1303
+ a.toast("Agent config saved (announced to room; no API keys shared)");
1304
+ } catch (e) {
1305
+ setErr(e.message);
1306
+ setBusy(false);
1307
+ }
1308
+ };
1309
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1310
+ import_dsh_client_ui_primitives.Modal,
1311
+ {
1312
+ open: true,
1313
+ onClose: a.closeDialog,
1314
+ title: "Room agent config",
1315
+ closeLabel: "Close",
1316
+ description: "Local to this member. Peers see model, effort, workspace label, permissions, and MCP tools \u2014 never API keys.",
1317
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: submit, children: busy ? "Saving\u2026" : "Save" })
1320
+ ] }),
1321
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Model", htmlFor: "rm-cfg-model", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-model", value: f.model, onChange: (e) => setF({ ...f, model: e.target.value }), placeholder: "e.g. deepseek-chat" }) }),
1323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Provider", htmlFor: "rm-cfg-prov", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-prov", value: f.provider, onChange: (e) => setF({ ...f, provider: e.target.value }), placeholder: "e.g. deepseek" }) }),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Reasoning effort", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "rm-input", value: f.reasoningEffort, onChange: (e) => setF({ ...f, reasoningEffort: e.target.value }), children: ["low", "medium", "high", "max"].map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: x, children: x }, x)) }) }),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace path", htmlFor: "rm-cfg-ws", hint: "Absolute path on this machine. Peers only see the label.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-ws", value: f.workspacePath, onChange: (e) => setF({ ...f, workspacePath: e.target.value }) }) }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace label", htmlFor: "rm-cfg-wsl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-wsl", value: f.workspaceLabel, onChange: (e) => setF({ ...f, workspaceLabel: e.target.value }) }) }),
1327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Expose MCP workspace" }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Same-room peers can call workspace_* tools over E2EE." })
1331
+ ] }),
1332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.mcpEnabled, onChange: (v) => setF({ ...f, mcpEnabled: v }), label: "MCP" })
1333
+ ] }),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", children: "Peer MCP limits (read/write/exec/delete) are edited from the room composer \xB7 Peer MCP control." }),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow reads" }) }),
1337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoRead, onChange: (v) => setF({ ...f, autoRead: v }), label: "autoRead" })
1338
+ ] }),
1339
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow writes" }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Off \u2192 approval UI for write/mkdir/delete." })
1343
+ ] }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoWrite, onChange: (v) => setF({ ...f, autoWrite: v }), label: "autoWrite" })
1345
+ ] }),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow shell" }) }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoShell, onChange: (v) => setF({ ...f, autoShell: v }), label: "autoShell" })
1349
+ ] }),
1350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow network" }) }),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoNetwork, onChange: (v) => setF({ ...f, autoNetwork: v }), label: "autoNetwork" })
1353
+ ] }),
1354
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
1355
+ ] })
1356
+ }
1357
+ );
1358
+ }
1359
+ function ApprovalBanner({ store }) {
1360
+ const s = useStore(store);
1361
+ const list = s.approvals || [];
1362
+ if (!list.length) return null;
1363
+ const a = store.actions;
1364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-approvals", children: list.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval", children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval-body", children: [
1366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Approval needed" }),
1367
+ " \xB7 ",
1368
+ p.action,
1369
+ " \xB7 ",
1370
+ p.summary,
1371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-field-hint", children: [
1372
+ "from ",
1373
+ p.from,
1374
+ p.detail ? ` \u2014 ${p.detail}` : ""
1375
+ ] })
1376
+ ] }),
1377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval-actions", children: [
1378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.decideApproval(p.requestId, "deny"), children: "Deny" }),
1379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.decideApproval(p.requestId, "allow"), children: "Allow" })
1380
+ ] })
1381
+ ] }, p.requestId)) });
1382
+ }
1383
+ function WorkspaceDialog({ store }) {
1384
+ const s = useStore(store);
1385
+ const a = store.actions;
1386
+ const [name2, setName] = (0, import_react.useState)("");
1387
+ const [path, setPath] = (0, import_react.useState)("");
1388
+ const [err, setErr] = (0, import_react.useState)(null);
1389
+ const [busy, setBusy] = (0, import_react.useState)(false);
1390
+ const syncCaret = (el) => {
1391
+ if (!el) return;
1392
+ setCaret(el.selectionStart ?? el.value.length);
1393
+ };
1394
+ const insertMention = (it) => {
1395
+ if (!mq) return;
1396
+ const insert = `@${it.id} `;
1397
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
1398
+ setDraft(next);
1399
+ const pos = mq.start + insert.length;
1400
+ setCaret(pos);
1401
+ setMentionActive(0);
1402
+ requestAnimationFrame(() => {
1403
+ const el = taRef.current;
1404
+ if (el) {
1405
+ el.focus();
1406
+ el.setSelectionRange(pos, pos);
1407
+ }
1408
+ });
1409
+ };
1410
+ const submit = async () => {
1411
+ if (!path.trim()) {
1412
+ setErr("Absolute workspace path required");
1413
+ return;
1414
+ }
1415
+ setBusy(true);
1416
+ setErr(null);
1417
+ try {
1418
+ await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
1419
+ a.closeDialog();
1420
+ a.toast("Workspace ready \u2014 create a room to collaborate");
1421
+ } catch (e) {
1422
+ setErr(e.message);
1423
+ setBusy(false);
1424
+ }
1425
+ };
1426
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1427
+ import_dsh_client_ui_primitives.Modal,
1428
+ {
1429
+ open: true,
1430
+ onClose: a.closeDialog,
1431
+ title: "Workspace",
1432
+ closeLabel: "Close",
1433
+ description: "Pick the local project folder agents share. Rooms created next inherit this path (MCP + agent cwd). Peers map the same logical project to their own checkout.",
1434
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy || !path.trim(), onClick: submit, children: busy ? "Saving\u2026" : "Use workspace" })
1437
+ ] }),
1438
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1439
+ (s.workspaces || []).length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Switch to existing", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-lan-browse", children: (s.workspaces || []).map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-lan-peer", onClick: async () => {
1440
+ await a.setActiveWorkspace(w.id);
1441
+ a.closeDialog();
1442
+ }, children: w.name }, w.id)) }) }) : null,
1443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-ws-name", hint: "Shown in the sidebar.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ws-name", placeholder: "e.g. collab", value: name2, onChange: (e) => setName(e.target.value) }) }),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Local path", htmlFor: "rm-ws-path", hint: "Absolute path on this machine.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ws-path", autoFocus: true, placeholder: "/Users/you/code/my-app", value: path, onChange: (e) => setPath(e.target.value), onKeyDown: (e) => e.key === "Enter" && submit() }) }),
1445
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
1446
+ ] })
1447
+ }
1448
+ );
1449
+ }
1450
+ function Overlay({ store }) {
1451
+ const s = useStore(store);
1452
+ const d = s.dialog;
1453
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1454
+ d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store }) : null,
1455
+ d?.kind === "join" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JoinDialog, { store }) : null,
1456
+ d?.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InviteDialog, { store, d }) : null,
1457
+ d?.kind === "leave" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LeaveDialog, { store, d }) : null,
1458
+ d?.kind === "config" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigDialog, { store, d }) : null,
1459
+ d?.kind === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceDialog, { store }) : null,
1460
+ s.toast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: s.toast, onDone: store.actions.clearToast, holdMs: 2200 }) : null
1461
+ ] });
1462
+ }
1463
+ function installStyles() {
1464
+ if (typeof document === "undefined") return () => {
1465
+ };
1466
+ const style = document.createElement("style");
1467
+ style.setAttribute("data-dsh-room", "rooms");
1468
+ style.textContent = rooms_default;
1469
+ document.head.appendChild(style);
1470
+ return () => style.remove();
1471
+ }
1472
+ function apply(ctx) {
1473
+ const store = createRoomsStore();
1474
+ ctx.effect(() => installStyles(), "dsh-room: styles");
1475
+ ctx.effect(() => () => store.dispose(), "dsh-room: store");
1476
+ const info = () => ctx.layout.panelInfo;
1477
+ const ensurePanel = () => {
1478
+ try {
1479
+ if (info().getSnapshot().activePanelId !== PANEL_ID) ctx.layout.selectPanel(PANEL_ID);
1480
+ } catch {
1481
+ }
1482
+ };
1483
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: PANEL_ID, order: 11, label: () => "DSH Room" }, PanelIcon));
1484
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, inject: () => ({ store }) }, RoomsMain));
1485
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: "dsh-room-dialogs", inject: () => ({ store }) }, Overlay));
1486
+ ctx.slots.inject("sidebar.workspaces", () => {
1487
+ let dispose;
1488
+ const reconcile = () => {
1489
+ const active = info().getSnapshot().activePanelId === PANEL_ID;
1490
+ if (active && !dispose) dispose = ctx.slots.register({ name: "sidebar.workspaces", priority: -100, inject: () => ({ store, ensurePanel }) }, RoomsSidebar);
1491
+ else if (!active && dispose) {
1492
+ dispose();
1493
+ dispose = void 0;
1494
+ }
1495
+ };
1496
+ const off = info().subscribe(reconcile);
1497
+ reconcile();
1498
+ return () => {
1499
+ off();
1500
+ if (dispose) dispose();
1501
+ dispose = void 0;
1502
+ };
1503
+ });
1504
+ }
1505
+
1506
+ return module.exports;
1507
+ }
1508
+ });
1509
+ window.__ModuleLoader__.load({
1510
+ id: "collab",
1511
+ factory: (require) => {
1512
+ var module = { exports: {} };
1513
+ var exports = module.exports;
1514
+ "use strict";
1515
+ var __defProp = Object.defineProperty;
1516
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1517
+ var __getOwnPropNames = Object.getOwnPropertyNames;
1518
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
1519
+ var __export = (target, all) => {
1520
+ for (var name2 in all)
1521
+ __defProp(target, name2, { get: all[name2], enumerable: true });
1522
+ };
1523
+ var __copyProps = (to, from, except, desc) => {
1524
+ if (from && typeof from === "object" || typeof from === "function") {
1525
+ for (let key of __getOwnPropNames(from))
1526
+ if (!__hasOwnProp.call(to, key) && key !== except)
1527
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
1528
+ }
1529
+ return to;
1530
+ };
1531
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1532
+
1533
+ // src/client/index.jsx
1534
+ var index_exports = {};
1535
+ __export(index_exports, {
1536
+ Avatar: () => Avatar,
1537
+ RoomsIcon: () => RoomsIcon,
1538
+ apply: () => apply,
1539
+ inject: () => inject,
1540
+ name: () => name
1541
+ });
1542
+ module.exports = __toCommonJS(index_exports);
1543
+ var import_react = require("react");
1544
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1545
+
1546
+ // src/client/rooms.css
1547
+ var rooms_default = '/* Collab Rooms UI \u2014 original surface inside the dsh shell. Uses only dsh --dsw-* tokens.\n Independent Rooms surface. Prefix: rm- (rooms). */\n\n.rm-root {\n --rm-accent: var(--dsw-alias-state-business-primary);\n --rm-hover: var(--dsw-alias-interactive-bg-hover);\n --rm-border: var(--dsw-alias-border-l2);\n --rm-bg: var(--dsw-alias-bg-base);\n --rm-panel: var(--dsw-alias-bg-module-platform);\n --rm-label: var(--dsw-alias-label-primary);\n --rm-muted: var(--dsw-alias-label-secondary);\n --rm-quiet: var(--dsw-alias-label-tertiary);\n font: 13px/1.5 var(--dsw-font-family);\n color: var(--rm-label);\n box-sizing: border-box;\n}\n.rm-root *, .rm-root *::before, .rm-root *::after { box-sizing: border-box; }\n.rm-root button, .rm-root input, .rm-root textarea { font: inherit; }\n\n/* \u2500\u2500 sidebar (joined rooms + public search) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-side {\n display: flex; flex-direction: column; height: 100%; min-height: 0;\n padding: 0 8px 8px 0; gap: 8px;\n}\n.rm-side-toolbar {\n display: flex; align-items: center; gap: 6px; flex: none; padding: 4px 0 2px;\n}\n.rm-side-title {\n flex: 1; font-size: 12px; font-weight: 600; letter-spacing: .04em;\n text-transform: uppercase; color: var(--rm-quiet);\n}\n.rm-icon-btn {\n width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 8px; background: transparent; color: var(--rm-muted); cursor: pointer; padding: 0;\n}\n.rm-icon-btn:hover { background: var(--rm-hover); color: var(--rm-label); }\n.rm-search {\n display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px;\n border: .5px solid var(--rm-border); border-radius: 8px; background: var(--rm-panel);\n}\n.rm-search input {\n flex: 1; min-width: 0; border: 0; background: transparent; outline: none; color: var(--rm-label); font-size: 13px;\n}\n.rm-search input::placeholder { color: var(--rm-quiet); }\n.rm-side-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 12px; }\n.rm-group-label { font-size: 11px; font-weight: 600; color: var(--rm-quiet); padding: 0 4px 4px; text-transform: uppercase; letter-spacing: .03em; }\n.rm-room-list { display: flex; flex-direction: column; gap: 2px; }\n.rm-room-item {\n display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;\n border: 0; border-radius: 10px; background: transparent; color: inherit; cursor: pointer; text-align: left;\n}\n.rm-room-item:hover { background: var(--rm-hover); }\n.rm-room-item[aria-current="true"] { background: var(--rm-hover); }\n.rm-room-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }\n.rm-room-item-name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item-meta { font-size: 11.5px; color: var(--rm-quiet); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item-trail { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }\n.rm-badge {\n display: inline-flex; align-items: center; gap: 3px; height: 18px; padding: 0 6px;\n border-radius: 999px; font-size: 10.5px; font-weight: 500; background: var(--rm-panel); color: var(--rm-muted);\n border: .5px solid var(--rm-border);\n}\n.rm-badge-public { color: var(--rm-accent); border-color: color-mix(in srgb, var(--rm-accent) 35%, transparent); background: color-mix(in srgb, var(--rm-accent) 8%, transparent); }\n.rm-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--rm-quiet); }\n.rm-dot-online { background: var(--dsw-alias-state-success-primary, #2bb673); }\n.rm-dot-connecting { background: var(--dsw-alias-state-warn-primary, #e9a23b); }\n.rm-dot-offline { background: var(--dsw-alias-state-error-primary, #e5484d); }\n.rm-empty-hint { padding: 8px 10px; font-size: 12px; color: var(--rm-quiet); line-height: 1.45; }\n.rm-dir-item {\n display: flex; align-items: flex-start; gap: 10px; padding: 10px; border-radius: 10px;\n border: .5px solid var(--rm-border); background: var(--rm-bg); margin-bottom: 6px;\n}\n.rm-dir-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }\n.rm-dir-tags { display: flex; flex-wrap: wrap; gap: 4px; }\n.rm-tag { font-size: 11px; color: var(--rm-muted); background: var(--rm-hover); border-radius: 4px; padding: 0 5px; }\n.rm-menu {\n position: fixed; z-index: 1000; min-width: 180px; padding: 4px;\n border-radius: 10px; background: var(--dsw-alias-bg-layer-1, var(--rm-bg));\n border: .5px solid var(--rm-border);\n box-shadow: 0 8px 24px color-mix(in srgb, var(--rm-label) 12%, transparent);\n display: flex; flex-direction: column;\n}\n.rm-menu button {\n display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;\n border: 0; border-radius: 6px; background: transparent; color: var(--rm-label); cursor: pointer; text-align: left;\n}\n.rm-menu button:hover { background: var(--rm-hover); }\n\n/* \u2500\u2500 main: room workspace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-main { display: flex; flex: 1; min-height: 0; height: 100%; width: 100%; background: var(--rm-bg); }\n.rm-workspace { display: flex; flex: 1; min-width: 0; min-height: 0; }\n.rm-chat { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }\n.rm-chat-header {\n flex: none; display: flex; align-items: center; gap: 10px; height: 48px;\n padding: 0 16px; border-bottom: .5px solid var(--rm-border);\n}\n.rm-chat-header-title { font-size: 14px; font-weight: 600; }\n.rm-chat-header-sub { font-size: 12px; color: var(--rm-quiet); }\n.rm-messages {\n flex: 1; min-height: 0; overflow: auto; padding: 16px 18px 8px;\n display: flex; flex-direction: column; gap: 2px;\n}\n.rm-day {\n align-self: center; margin: 10px 0 12px; padding: 2px 10px; border-radius: 999px;\n font-size: 11px; color: var(--rm-quiet); background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n}\n.rm-msg {\n display: grid; grid-template-columns: 32px 1fr; gap: 0 10px; padding: 4px 6px; border-radius: 8px;\n max-width: 720px;\n}\n.rm-msg:hover { background: color-mix(in srgb, var(--rm-hover) 60%, transparent); }\n.rm-msg-self { margin-left: auto; grid-template-columns: 1fr; max-width: 640px; }\n.rm-msg-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }\n.rm-msg-author { font-size: 12px; font-weight: 600; color: var(--rm-muted); }\n.rm-msg-time { font-size: 11px; color: var(--rm-quiet); }\n.rm-msg-body { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }\n.rm-msg-self .rm-msg-body {\n background: var(--dsw-alias-label-primary); color: var(--dsw-alias-label-primary-foreground);\n padding: 8px 12px; border-radius: 12px 12px 4px 12px; width: fit-content; margin-left: auto;\n}\n.rm-msg-event {\n grid-template-columns: 1fr; max-width: 560px; margin: 6px 0;\n padding: 8px 12px; border: .5px solid var(--rm-border); border-radius: 10px; background: var(--rm-panel);\n}\n.rm-mention { color: var(--rm-accent); font-weight: 500; }\n.rm-msg-self .rm-mention { color: inherit; text-decoration: underline; text-underline-offset: 2px; }\n.rm-task-card { display: flex; flex-direction: column; gap: 4px; }\n.rm-task-head { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--rm-muted); }\n.rm-task-title { font-weight: 600; font-size: 13.5px; }\n.rm-composer {\n flex: none; display: flex; align-items: flex-end; gap: 8px; padding: 10px 16px 14px;\n border-top: .5px solid var(--rm-border);\n}\n.rm-composer-box {\n flex: 1; display: flex; align-items: flex-end; gap: 6px; min-height: 44px;\n padding: 8px 10px; border: .5px solid var(--rm-border); border-radius: 12px; background: var(--rm-panel);\n}\n.rm-composer-box:focus-within { border-color: color-mix(in srgb, var(--rm-accent) 50%, var(--rm-border)); }\n.rm-composer-box textarea {\n flex: 1; min-height: 28px; max-height: 120px; border: 0; background: transparent; outline: none;\n resize: none; color: var(--rm-label); line-height: 1.45;\n}\n.rm-composer-box textarea::placeholder { color: var(--rm-quiet); }\n.rm-composer-mode {\n width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 8px; background: transparent; color: var(--rm-muted); cursor: pointer; flex: none;\n}\n.rm-composer-mode:hover, .rm-composer-mode[aria-pressed="true"] {\n color: var(--rm-accent); background: color-mix(in srgb, var(--rm-accent) 12%, transparent);\n}\n.rm-chat-hint { margin: 48px auto; text-align: center; color: var(--rm-quiet); font-size: 13px; max-width: 360px; line-height: 1.5; }\n\n/* \u2500\u2500 detail column (members / invite / about) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-detail {\n flex: 0 0 280px; width: 280px; display: flex; flex-direction: column; min-height: 0;\n border-left: .5px solid var(--rm-border); background: var(--rm-bg); overflow: auto;\n}\n.rm-detail-section { padding: 14px 14px 8px; border-bottom: .5px solid var(--rm-border); }\n.rm-detail-section:last-child { border-bottom: 0; }\n.rm-detail-h {\n display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em; color: var(--rm-quiet); margin-bottom: 8px;\n}\n.rm-member {\n display: flex; align-items: center; gap: 8px; padding: 5px 4px; border-radius: 8px; min-height: 34px;\n}\n.rm-member:hover { background: var(--rm-hover); }\n.rm-member-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }\n.rm-member-actions { display: none; gap: 2px; }\n.rm-member:hover .rm-member-actions, .rm-member:focus-within .rm-member-actions { display: inline-flex; }\n.rm-mini {\n width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;\n border: 0; border-radius: 6px; background: transparent; color: var(--rm-muted); cursor: pointer; font-size: 12px;\n}\n.rm-mini:hover { background: var(--dsw-alias-interactive-bg-active); color: var(--rm-label); }\n.rm-task-row { display: flex; align-items: center; gap: 6px; padding: 4px; font-size: 12.5px; }\n.rm-task-row-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-about { display: grid; grid-template-columns: 88px 1fr; gap: 6px 8px; font-size: 12.5px; margin: 0; }\n.rm-about dt { color: var(--rm-quiet); margin: 0; }\n.rm-about dd { margin: 0; min-width: 0; overflow-wrap: anywhere; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n.rm-mono { font-family: var(--dsw-font-markdown-code-block-font-family, ui-monospace, monospace); font-size: 11.5px; }\n.rm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }\n.rm-danger { color: var(--dsw-alias-state-error-primary, #e5484d) !important; }\n.rm-note { font-size: 12px; color: var(--rm-quiet); line-height: 1.45; margin: 0 0 8px; }\n\n/* \u2500\u2500 empty / welcome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-welcome-wrap {\n flex: 1; display: flex; align-items: center; justify-content: center; padding: 32px; background: var(--rm-bg);\n}\n.rm-welcome {\n width: min(420px, 100%); display: flex; flex-direction: column; gap: 10px;\n padding: 20px; border-radius: 14px; border: .5px solid var(--rm-border); background: var(--rm-panel);\n}\n.rm-welcome h1 { margin: 0; font-size: 16px; font-weight: 600; }\n.rm-welcome p { margin: 0; font-size: 13px; color: var(--rm-muted); line-height: 1.5; }\n.rm-welcome-actions { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }\n.rm-welcome-action {\n display: flex; align-items: flex-start; gap: 10px; width: 100%; padding: 10px 12px;\n border: .5px solid var(--rm-border); border-radius: 10px; background: var(--rm-bg);\n color: var(--rm-label); cursor: pointer; text-align: left;\n}\n.rm-welcome-action:hover { background: var(--rm-hover); }\n.rm-welcome-action b { display: block; font-size: 13px; }\n.rm-welcome-action span { display: block; font-size: 12px; color: var(--rm-quiet); margin-top: 2px; }\n\n/* \u2500\u2500 avatars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-avatar {\n display: inline-flex; align-items: center; justify-content: center; flex: none;\n font-weight: 600; letter-spacing: .02em; user-select: none;\n}\n.rm-avatar-wrap { position: relative; display: inline-flex; flex: none; }\n\n/* \u2500\u2500 dialogs (inside dsh Modal) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.rm-form { display: flex; flex-direction: column; gap: 14px; }\n.rm-field { display: flex; flex-direction: column; gap: 6px; }\n.rm-field-label { font-size: 13px; font-weight: 500; }\n.rm-field-hint { font-size: 12px; color: var(--rm-quiet); }\n.rm-name-row { display: flex; align-items: center; gap: 10px; }\n.rm-name-row > :last-child { flex: 1; }\n.rm-switch-row {\n display: flex; align-items: center; justify-content: space-between; gap: 14px;\n padding: 10px 12px; border-radius: 10px; background: var(--rm-panel);\n}\n.rm-error { color: var(--dsw-alias-state-error-primary, #e5484d); font-size: 12.5px; }\n.rm-invite {\n display: block; padding: 10px 12px; border-radius: 8px; background: var(--rm-panel);\n font-family: var(--dsw-font-markdown-code-block-font-family, ui-monospace, monospace);\n font-size: 12px; word-break: break-all; user-select: all;\n}\n\n/* Dual transport + approvals + agent config */\n.rm-mode-row { display: flex; gap: 8px; }\n.rm-mode-btn {\n flex: 1; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--rm-border, #2a2f3a);\n background: transparent; color: inherit; cursor: pointer; font: inherit;\n}\n.rm-mode-btn.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 16%, transparent); }\n.rm-lan-browse { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; align-items: center; }\n.rm-lan-peer {\n font-size: 12px; padding: 4px 8px; border-radius: 999px; border: 1px solid var(--rm-border, #2a2f3a);\n background: transparent; color: inherit; cursor: pointer;\n}\n.rm-approvals { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--rm-border, #2a2f3a); background: color-mix(in srgb, #f5a524 12%, transparent); }\n.rm-approval { display: flex; gap: 12px; align-items: center; justify-content: space-between; }\n.rm-approval-body { font-size: 13px; }\n.rm-approval-actions { display: flex; gap: 6px; flex-shrink: 0; }\n.rm-input { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; font: inherit; }\n\n.rm-ws-bar { display: flex; flex-direction: column; gap: 4px; padding: 8px 12px; border-bottom: 1px solid var(--rm-border, #2a2f3a); }\n.rm-ws-chip { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; cursor: pointer; font: inherit; font-size: 12.5px; }\n.rm-ws-path { font-size: 11px; color: var(--rm-quiet, #8b93a7); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-room-item.is-active { background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); }\n\n/* Native-aligned room composer (mirrors dsh conversation control row) */\n.rm-composer-card {\n box-sizing: border-box;\n width: 100%;\n max-width: 920px;\n margin: 0 auto;\n border-radius: 16px;\n background: var(--rm-elev, var(--rm-panel, #12151c));\n border: 1px solid var(--rm-border, #2a2f3a);\n box-shadow: 0 1px 2px rgba(0,0,0,.06);\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 0 6px;\n}\n.rm-composer-grow { position: relative; padding: 0 4px; }\n.rm-composer-grow textarea {\n box-sizing: border-box; width: 100%; min-height: 36px; max-height: 160px;\n resize: none; border: none; outline: none; background: transparent;\n color: inherit; font: inherit; font-size: 14px; line-height: 22px;\n padding: 4px 12px 0 14px;\n}\n.rm-composer-grow textarea:disabled { opacity: .55; cursor: not-allowed; }\n.rm-composer-row {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;\n gap: 10px; min-width: 0; padding: 2px 8px 4px;\n}\n.rm-composer-tools, .rm-composer-modes, .rm-composer-trailing {\n display: flex; align-items: center; min-width: 0; gap: 8px;\n}\n.rm-composer-trailing { flex: none; margin-left: auto; }\n.rm-composer-add {\n width: 28px; height: 28px; border: none; border-radius: 999px;\n background: color-mix(in srgb, var(--rm-fg, #e8eaed) 8%, transparent);\n color: inherit; cursor: pointer; display: grid; place-items: center;\n}\n.rm-composer-add:hover, .rm-composer-add[aria-pressed="true"] {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 22%, transparent);\n}\n.rm-composer-select {\n max-width: 220px; height: 28px; border: none; border-radius: 8px;\n background: transparent; color: var(--rm-quiet, #8b93a7);\n font: inherit; font-size: 13px; font-weight: 500; line-height: 20px;\n padding: 0 8px; cursor: pointer; white-space: nowrap;\n}\n.rm-composer-select:hover:not(:disabled) {\n background: color-mix(in srgb, var(--rm-fg, #e8eaed) 8%, transparent);\n}\n.rm-composer-select:disabled { opacity: .5; cursor: default; }\n.rm-composer-model-text { display: inline; }\n.rm-composer-model-icon { display: none; font-size: 10px; }\n.rm-composer-effort {\n margin-left: 6px; font-size: 11px; opacity: .75;\n text-transform: uppercase; letter-spacing: .02em;\n}\n.rm-composer-send {\n width: 34px; height: 34px; border: none; border-radius: 999px;\n background: var(--rm-accent, #5b8cff); color: #fff; cursor: pointer;\n display: grid; place-items: center; transform: translateY(-2px);\n}\n.rm-composer-send:hover:not(:disabled) { filter: brightness(1.08); }\n.rm-composer-send:disabled { opacity: .4; cursor: default; }\n.rm-composer-popwrap { position: relative; }\n.rm-composer-menu {\n position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 40;\n min-width: 200px; max-height: 320px; overflow: auto;\n border: 1px solid var(--rm-border, #2a2f3a); border-radius: 12px;\n background: var(--rm-elev, #12151c); box-shadow: 0 8px 24px rgba(0,0,0,.28);\n padding: 6px; display: flex; flex-direction: column; gap: 2px;\n}\n.rm-composer-menu-wide { min-width: 280px; max-width: 340px; }\n.rm-composer-menu-label {\n font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;\n color: var(--rm-quiet, #8b93a7); padding: 6px 8px 2px;\n}\n.rm-composer-menu button[role="menuitem"] {\n text-align: left; border: none; background: transparent; color: inherit;\n font: inherit; font-size: 13px; padding: 6px 8px; border-radius: 8px; cursor: pointer;\n}\n.rm-composer-menu button[role="menuitem"]:hover,\n.rm-composer-menu button[role="menuitem"].is-active {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 16%, transparent);\n}\n.rm-composer-check {\n display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 8px; cursor: pointer;\n}\n.rm-grant-row {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 2px 8px; font-size: 12.5px;\n}\n.rm-grant-row select {\n font: inherit; font-size: 12px; border-radius: 6px;\n border: 1px solid var(--rm-border, #2a2f3a); background: transparent; color: inherit; padding: 2px 6px;\n}\n.rm-peer-grant-block { border-top: 1px solid var(--rm-border, #2a2f3a); padding-top: 4px; margin-top: 4px; }\n.rm-peer-grant-name { font-size: 12px; font-weight: 600; padding: 4px 8px; overflow: hidden; text-overflow: ellipsis; }\n\n/* Hide legacy tight composer if still referenced */\n.rm-composer { display: none; }\n\n.rm-composer-grow { position: relative; }\n.rm-mention-menu {\n position: absolute; left: 8px; right: 8px; bottom: calc(100% + 6px); z-index: 50;\n max-height: 240px; overflow: auto;\n border: 1px solid var(--rm-border, #2a2f3a); border-radius: 12px;\n background: var(--rm-elev, #12151c); box-shadow: 0 10px 28px rgba(0,0,0,.32);\n padding: 4px; display: flex; flex-direction: column; gap: 2px;\n}\n.rm-mention-item {\n display: flex; align-items: center; gap: 8px; text-align: left;\n border: none; background: transparent; color: inherit; font: inherit;\n padding: 6px 8px; border-radius: 8px; cursor: pointer;\n}\n.rm-mention-item:hover, .rm-mention-item.is-active {\n background: color-mix(in srgb, var(--rm-accent, #5b8cff) 18%, transparent);\n}\n.rm-mention-item-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }\n.rm-mention-item-name { font-size: 13px; font-weight: 600; }\n.rm-mention-item-meta { font-size: 11px; color: var(--rm-quiet, #8b93a7); }\n';
1548
+
1549
+ // src/client/store.js
1550
+ var API = "/api/collab";
1551
+ async function call(path, init) {
1552
+ const res = await fetch(API + path, { credentials: "same-origin", ...init, headers: { ...init && init.body ? { "content-type": "application/json" } : {}, ...init && init.headers || {} } });
1553
+ const body = await res.json().catch(() => ({}));
1554
+ if (!res.ok) throw new Error(body.error || `${res.status} ${res.statusText}`);
1555
+ return body;
1556
+ }
1557
+ var post = (path, body) => call(path, { method: "POST", body: JSON.stringify(body || {}) });
1558
+ function createRoomsStore() {
1559
+ let snap = {
1560
+ self: null,
1561
+ relayUrl: "",
1562
+ lanRelayPort: null,
1563
+ workspaces: [],
1564
+ activeWorkspaceId: null,
1565
+ rooms: [],
1566
+ activeRoomId: null,
1567
+ connected: false,
1568
+ selected: null,
1569
+ messages: {},
1570
+ dialog: null,
1571
+ toast: null,
1572
+ approvals: [],
1573
+ search: { open: false, query: "", results: [], loading: false, error: null }
1574
+ };
1575
+ const subs = /* @__PURE__ */ new Set();
1576
+ const set = (patch) => {
1577
+ snap = { ...snap, ...typeof patch === "function" ? patch(snap) : patch };
1578
+ for (const s of subs) s();
1579
+ };
1580
+ let es = null;
1581
+ let searchTimer = null;
1582
+ function connect() {
1583
+ if (es) return;
1584
+ es = new EventSource(API + "/stream");
1585
+ es.addEventListener("rooms", (e) => {
1586
+ const st = JSON.parse(e.data);
1587
+ set((s) => {
1588
+ const selected = s.selected && st.rooms.some((r) => r.roomId === s.selected) ? s.selected : st.activeRoomId || st.rooms[0] && st.rooms[0].roomId || null;
1589
+ return { ...st, connected: true, selected };
1590
+ });
1591
+ const sel = snap.selected;
1592
+ if (sel && !snap.messages[sel]) void loadMessages(sel);
1593
+ });
1594
+ es.addEventListener("message", (e) => {
1595
+ const { roomId, message } = JSON.parse(e.data);
1596
+ set((s) => {
1597
+ const cur = s.messages[roomId];
1598
+ if (!cur || cur.some((m) => m.id === message.id)) return {};
1599
+ return { messages: { ...s.messages, [roomId]: [...cur, message].sort((a, b) => a.ts - b.ts) } };
1600
+ });
1601
+ });
1602
+ es.onerror = () => set({ connected: false });
1603
+ }
1604
+ async function loadMessages(roomId) {
1605
+ try {
1606
+ const r = await call(`/rooms/${encodeURIComponent(roomId)}/messages`);
1607
+ set((s) => ({ messages: { ...s.messages, [roomId]: r.messages } }));
1608
+ } catch (e) {
1609
+ toast(e.message);
1610
+ }
1611
+ }
1612
+ function toast(text) {
1613
+ set({ toast: text });
1614
+ }
1615
+ async function runSearch(query) {
1616
+ set((s) => ({ search: { ...s.search, loading: true, error: null } }));
1617
+ try {
1618
+ const q = query.trim();
1619
+ const tag = (/(^|\s)#([\w-]+)/.exec(q) || [])[2];
1620
+ const text = q.replace(/(^|\s)#[\w-]+/g, " ").trim();
1621
+ const p = new URLSearchParams();
1622
+ if (text) p.set("q", text);
1623
+ if (tag) p.set("tag", tag);
1624
+ const r = await call(`/directory?${p}`);
1625
+ set((s) => s.search.query === query ? { search: { ...s.search, results: r.rooms, loading: false } } : {});
1626
+ } catch (e) {
1627
+ set((s) => ({ search: { ...s.search, loading: false, error: e.message } }));
1628
+ }
1629
+ }
1630
+ const actions = {
1631
+ select(roomId) {
1632
+ set({ selected: roomId });
1633
+ if (!snap.messages[roomId]) void loadMessages(roomId);
1634
+ void post(`/rooms/${encodeURIComponent(roomId)}/active`).catch(() => {
1635
+ });
1636
+ },
1637
+ openDialog(dialog) {
1638
+ set({ dialog });
1639
+ },
1640
+ closeDialog() {
1641
+ set({ dialog: null });
1642
+ },
1643
+ toast,
1644
+ clearToast() {
1645
+ set({ toast: null });
1646
+ },
1647
+ setSearchOpen(open) {
1648
+ set((s) => ({ search: { ...s.search, open, query: open ? s.search.query : "", results: open ? s.search.results : [] } }));
1649
+ if (open) void runSearch(snap.search.query);
1650
+ },
1651
+ setQuery(query) {
1652
+ set((s) => ({ search: { ...s.search, query } }));
1653
+ clearTimeout(searchTimer);
1654
+ searchTimer = setTimeout(() => void runSearch(query), 300);
1655
+ },
1656
+ async create(form) {
1657
+ const r = await post("/rooms", { ...form, workspaceId: form.workspaceId || snap.activeWorkspaceId });
1658
+ set({ selected: r.roomId, dialog: { kind: "invite", roomId: r.roomId, name: r.name, invite: r.invite, public: r.public, listed: r.listed } });
1659
+ void loadMessages(r.roomId);
1660
+ return r;
1661
+ },
1662
+ async join(invite, info) {
1663
+ const r = await post("/join", { invite, workspaceId: snap.activeWorkspaceId, ...info || {} });
1664
+ set((s) => ({ selected: r.roomId, dialog: null, search: { ...s.search, results: s.search.results.map((x) => x.roomId === r.roomId ? { ...x, joined: true } : x) } }));
1665
+ void loadMessages(r.roomId);
1666
+ toast(`Joined ${r.name}`);
1667
+ return r;
1668
+ },
1669
+ async leave(roomId) {
1670
+ await post(`/rooms/${encodeURIComponent(roomId)}/leave`);
1671
+ set((s) => {
1672
+ const m = { ...s.messages };
1673
+ delete m[roomId];
1674
+ return { messages: m, selected: s.selected === roomId ? null : s.selected, dialog: null };
1675
+ });
1676
+ toast("Left the room");
1677
+ },
1678
+ async send(roomId, text) {
1679
+ return post(`/rooms/${encodeURIComponent(roomId)}/send`, { text });
1680
+ },
1681
+ async task(roomId, title, assignee, body) {
1682
+ return post(`/rooms/${encodeURIComponent(roomId)}/task`, { title, assignee, body });
1683
+ },
1684
+ async invite(roomId) {
1685
+ return (await call(`/rooms/${encodeURIComponent(roomId)}/invite`)).invite;
1686
+ },
1687
+ async browseLan() {
1688
+ return call("/lan/browse");
1689
+ },
1690
+ async ensureWorkspace(form) {
1691
+ const w = await post("/workspaces", form);
1692
+ set((s) => ({ activeWorkspaceId: w.id, workspaces: s.workspaces.some((x) => x.id === w.id) ? s.workspaces.map((x) => x.id === w.id ? w : x) : [w, ...s.workspaces] }));
1693
+ return w;
1694
+ },
1695
+ async setActiveWorkspace(id) {
1696
+ const w = await post("/workspaces/active", { id });
1697
+ set({ activeWorkspaceId: id, selected: null });
1698
+ return w;
1699
+ },
1700
+ async getConfig(roomId) {
1701
+ return call(`/rooms/${encodeURIComponent(roomId)}/config`);
1702
+ },
1703
+ async saveConfig(roomId, patch) {
1704
+ return post(`/rooms/${encodeURIComponent(roomId)}/config`, patch);
1705
+ },
1706
+ async decideApproval(requestId, decision) {
1707
+ return post("/approvals/decide", { requestId, decision });
1708
+ },
1709
+ async mcp(roomId, tool, args, target) {
1710
+ return post(`/rooms/${encodeURIComponent(roomId)}/mcp`, { tool, args, target });
1711
+ }
1712
+ };
1713
+ return {
1714
+ getSnapshot: () => snap,
1715
+ subscribe(fn) {
1716
+ subs.add(fn);
1717
+ connect();
1718
+ return () => subs.delete(fn);
1719
+ },
1720
+ actions,
1721
+ dispose() {
1722
+ if (es) es.close();
1723
+ es = null;
1724
+ subs.clear();
1725
+ clearTimeout(searchTimer);
1726
+ }
1727
+ };
1728
+ }
1729
+
1730
+ // src/client/index.jsx
1731
+ var import_jsx_runtime = require("react/jsx-runtime");
1732
+ var name = "dsh-room-client";
1733
+ var inject = ["slots", "layout"];
1734
+ var PANEL_ID = "collab";
1735
+ var Svg = ({ size = 16, children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.15", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
1736
+ var RoomsIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.5", y: "3", width: "11", height: "10", rx: "2" }),
1738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 6.5h5M5.5 9.5h3.5" })
1739
+ ] });
1740
+ var FolderIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 4.5h4l1.5 1.5H13.5v7.5H2.5z" }) });
1741
+ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
1742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "4.25" }),
1743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.2 10.2 3.3 3.3" })
1744
+ ] });
1745
+ var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" }) });
1746
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size: 14, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3.5 3.5l9 9M12.5 3.5l-9 9" }) });
1747
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 13V3.5M4 7.5l4-4 4 4" }) });
1748
+ var LinkIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6.5 9.5l3-3M7 4.75l1-1a2.5 2.5 0 0 1 3.5 3.5l-1 1M9 11.25l-1 1a2.5 2.5 0 0 1-3.5-3.5l1-1" }) });
1749
+ var TaskIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
1750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "2.75", width: "10", height: "10.5", rx: "2" }),
1751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.75 7.75l1.5 1.5 3-3" })
1752
+ ] });
1753
+ var GlobeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 12, children: [
1754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "5.5" }),
1755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 8h11M8 2.5c1.6 1.6 2.25 3.5 2.25 5.5S9.6 11.9 8 13.5C6.4 11.9 5.75 10 5.75 8S6.4 4.1 8 2.5z" })
1756
+ ] });
1757
+ var LockIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 12, children: [
1758
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3.5", y: "7", width: "9", height: "6.25", rx: "1.5" }),
1759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 7V5.25a2.5 2.5 0 0 1 5 0V7" })
1760
+ ] });
1761
+ var PeopleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
1762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "5.5", r: "2.25" }),
1763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 13c.4-2 1.8-3.25 3.5-3.25S9.1 11 9.5 13" }),
1764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.5 3.6a2.2 2.2 0 0 1 0 4.1M11.5 9.9c1 .4 1.7 1.5 2 3.1" })
1765
+ ] });
1766
+ var HUES = [212, 262, 152, 28, 340, 190, 48, 290];
1767
+ function hueOf(s) {
1768
+ let h = 0;
1769
+ for (const c of s || "") h = h * 31 + c.charCodeAt(0) >>> 0;
1770
+ return HUES[h % HUES.length];
1771
+ }
1772
+ function initials(s) {
1773
+ const w = (s || "?").replace(/[^\p{L}\p{N} _-]/gu, "").split(/[\s_-]+/).filter(Boolean);
1774
+ return ((w[0] || "?")[0] + (w[1] ? w[1][0] : (w[0] || "").slice(1, 2))).toUpperCase();
1775
+ }
1776
+ function Avatar({ name: name2, size = 32 }) {
1777
+ const h = hueOf(name2);
1778
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-avatar", style: { width: size, height: size, borderRadius: Math.round(size * 0.28), fontSize: Math.round(size * 0.36), background: `hsl(${h} 55% 90%)`, color: `hsl(${h} 40% 30%)` }, "aria-hidden": "true", children: initials(name2) });
1779
+ }
1780
+ function fmtTime(ts) {
1781
+ const d = new Date(ts), now = /* @__PURE__ */ new Date();
1782
+ return d.toDateString() === now.toDateString() ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString([], { month: "short", day: "numeric" });
1783
+ }
1784
+ function fmtDay(ts) {
1785
+ return new Date(ts).toLocaleDateString([], { year: "numeric", month: "long", day: "numeric" });
1786
+ }
1787
+ var STATUS_TONE = { open: "info", claimed: "warning", done: "success", failed: "danger", cancelled: "quiet" };
1788
+ function useStore(store) {
1789
+ return (0, import_react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
1790
+ }
1791
+ function renderMentions(text) {
1792
+ return text.split(/(@[A-Za-z0-9][\w.:-]*)/g).map((part, i) => part.startsWith("@") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention", children: part }, i) : part);
1793
+ }
1794
+ function PanelIcon({ size }) {
1795
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
1796
+ }
1797
+ function RoomsSidebar({ store, ensurePanel }) {
1798
+ const s = useStore(store);
1799
+ const a = store.actions;
1800
+ const [menu, setMenu] = (0, import_react.useState)(null);
1801
+ const [searching, setSearching] = (0, import_react.useState)(false);
1802
+ const [joining, setJoining] = (0, import_react.useState)(null);
1803
+ const inputRef = (0, import_react.useRef)(null);
1804
+ const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0] || null;
1805
+ (0, import_react.useEffect)(() => {
1806
+ if (!menu) return;
1807
+ const close = () => setMenu(null);
1808
+ window.addEventListener("click", close);
1809
+ return () => window.removeEventListener("click", close);
1810
+ }, [menu]);
1811
+ (0, import_react.useEffect)(() => {
1812
+ if (searching && inputRef.current) inputRef.current.focus();
1813
+ }, [searching]);
1814
+ const q = (s.search.query || "").trim().toLowerCase();
1815
+ const mine = (s.rooms || []).filter((r) => {
1816
+ if (activeWs && r.workspaceId && r.workspaceId !== activeWs.id) return false;
1817
+ if (!q) return true;
1818
+ return r.name.toLowerCase().includes(q) || (r.description || "").toLowerCase().includes(q) || (r.tags || []).some((t) => ("#" + t).includes(q));
1819
+ });
1820
+ const join = async (r) => {
1821
+ setJoining(r.roomId);
1822
+ try {
1823
+ await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
1824
+ ensurePanel();
1825
+ } catch (e) {
1826
+ a.toast(e.message);
1827
+ } finally {
1828
+ setJoining(null);
1829
+ }
1830
+ };
1831
+ if (!activeWs) {
1832
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
1833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-side-toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "Workspaces" }) }),
1834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
1835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: "Bind a local workspace first. Rooms you create inherit its path (MCP sandbox + agent cwd)." }),
1836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace" })
1837
+ ] })
1838
+ ] });
1839
+ }
1840
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
1841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-toolbar", children: [
1842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "DSH Room" }),
1843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", "aria-label": searching ? "Close search" : "Search rooms", "aria-pressed": searching, onClick: () => {
1844
+ setSearching(!searching);
1845
+ a.setSearchOpen(!searching);
1846
+ }, children: searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) }),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1848
+ "button",
1849
+ {
1850
+ type: "button",
1851
+ className: "rm-icon-btn",
1852
+ "data-rm-new": "",
1853
+ "aria-label": "New",
1854
+ "aria-haspopup": "menu",
1855
+ "aria-expanded": !!menu,
1856
+ onClick: (e) => {
1857
+ e.stopPropagation();
1858
+ const r = e.currentTarget.getBoundingClientRect();
1859
+ setMenu(menu ? null : { top: r.bottom + 4, left: Math.max(8, r.right - 200) });
1860
+ },
1861
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
1862
+ }
1863
+ ),
1864
+ menu ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-menu", role: "menu", style: { top: menu.top, left: menu.left }, onClick: (e) => e.stopPropagation(), children: [
1865
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1866
+ setMenu(null);
1867
+ a.openDialog({ kind: "create" });
1868
+ }, children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
1870
+ "Create room in workspace"
1871
+ ] }),
1872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1873
+ setMenu(null);
1874
+ a.openDialog({ kind: "join" });
1875
+ }, children: [
1876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
1877
+ "Join by invite"
1878
+ ] }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1880
+ setMenu(null);
1881
+ a.openDialog({ kind: "workspace" });
1882
+ }, children: [
1883
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1884
+ "Add / switch workspace"
1885
+ ] }),
1886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1887
+ setMenu(null);
1888
+ setSearching(true);
1889
+ a.setSearchOpen(true);
1890
+ }, children: [
1891
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1892
+ "Browse public rooms"
1893
+ ] })
1894
+ ] }) : null
1895
+ ] }),
1896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-bar", children: [
1897
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-ws-chip", onClick: () => a.openDialog({ kind: "workspace" }), title: activeWs.path, children: [
1898
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: activeWs.name })
1900
+ ] }),
1901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-path", title: activeWs.path, children: activeWs.path })
1902
+ ] }),
1903
+ searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
1904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: inputRef, type: "text", placeholder: "Search rooms or #tags", value: s.search.query, onChange: (e) => a.setQuery(e.target.value), onKeyDown: (e) => e.key === "Escape" && a.setSearchOpen(false), "aria-label": "Search rooms" })
1906
+ ] }) : null,
1907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
1908
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
1910
+ "Rooms in ",
1911
+ activeWs.name
1912
+ ] }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-list", children: [
1914
+ mine.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1915
+ "button",
1916
+ {
1917
+ type: "button",
1918
+ className: "rm-room-item",
1919
+ "aria-current": r.roomId === s.selected ? "true" : void 0,
1920
+ "data-room-id": r.roomId,
1921
+ onClick: () => {
1922
+ a.select(r.roomId);
1923
+ ensurePanel();
1924
+ },
1925
+ children: [
1926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
1928
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + r.status, style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
1929
+ ] }),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
1931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
1932
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: r.last ? `${r.last.from}: ${r.last.text}` : r.description || (r.public ? "Public room" : "Private \xB7 invite only") })
1933
+ ] }),
1934
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-trail", children: [
1935
+ r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
1936
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1937
+ "Public"
1938
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
1940
+ "Private"
1941
+ ] }),
1942
+ r.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
1943
+ r.last ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--rm-quiet)" }, children: fmtTime(r.last.ts) }) : null
1944
+ ] })
1945
+ ]
1946
+ },
1947
+ r.roomId
1948
+ )),
1949
+ !mine.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: "No rooms in this workspace yet. Create one or join by invite \u2014 peers should check out the same project." }) : null
1950
+ ] })
1951
+ ] }),
1952
+ (s.workspaces || []).length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-group-label", children: "Workspaces" }),
1954
+ (s.workspaces || []).map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-room-item" + (w.id === activeWs.id ? " is-active" : ""), onClick: () => a.setActiveWorkspace(w.id), children: [
1955
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
1957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: w.name }),
1958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: w.path })
1959
+ ] })
1960
+ ] }, w.id))
1961
+ ] }) : null,
1962
+ searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
1964
+ "Public directory ",
1965
+ s.search.loading ? "\xB7 searching\u2026" : ""
1966
+ ] }),
1967
+ s.search.results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item", "data-dir-room-id": r.roomId, children: [
1968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item-body", children: [
1970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-room-item-name", children: r.name }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-item-meta", children: [
1972
+ r.description || "No description",
1973
+ " \xB7 ",
1974
+ r.members,
1975
+ " online"
1976
+ ] }),
1977
+ r.tags.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-dir-tags", children: r.tags.map((tg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-tag", children: [
1978
+ "#",
1979
+ tg
1980
+ ] }, tg)) }) : null
1981
+ ] }),
1982
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: r.joined ? "ghost" : "outline", disabled: r.joined || joining === r.roomId, onClick: () => join(r), children: r.joined ? "Joined" : joining === r.roomId ? "\u2026" : "Join" })
1983
+ ] }, r.roomId)),
1984
+ !s.search.loading && !s.search.results.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint", children: s.search.error ? `Directory error: ${s.search.error}` : "No public rooms match." }) : null,
1985
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
1986
+ "Joining attaches the room to workspace \u201C",
1987
+ activeWs.name,
1988
+ "\u201D. Point your local path at the same project."
1989
+ ] })
1990
+ ] }) : null
1991
+ ] })
1992
+ ] });
1993
+ }
1994
+ function Message({ m, showAuthor }) {
1995
+ if (m.kind === "task.create") {
1996
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg rm-msg-event", "data-message-id": m.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-card", children: [
1997
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2000
+ "Task \xB7 ",
2001
+ m.from
2002
+ ] }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: "open" }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
2005
+ ] }),
2006
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-task-title", children: m.title }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: 0 }, children: m.assignee ? `Assigned to @${m.assignee}` : "Unassigned \u2014 any agent may claim" })
2008
+ ] }) });
2009
+ }
2010
+ if (m.kind === "task.claim" || m.kind === "task.update") {
2011
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg rm-msg-event", "data-message-id": m.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
2012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2014
+ m.self ? "You" : m.from,
2015
+ " ",
2016
+ m.kind === "task.claim" ? "claimed" : "updated",
2017
+ " task"
2018
+ ] }),
2019
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: STATUS_TONE[m.status] || "neutral", children: m.status }),
2020
+ m.kind === "task.update" && m.text.includes(": ") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: m.text.slice(m.text.indexOf(": ") + 2) }) : null,
2021
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
2022
+ ] }) });
2023
+ }
2024
+ if (m.self) {
2025
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg rm-msg-self", "data-message-id": m.id, children: [
2026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) }),
2027
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-time", style: { textAlign: "right", marginTop: 2 }, children: fmtTime(m.ts) })
2028
+ ] });
2029
+ }
2030
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg", "data-message-id": m.id, children: [
2031
+ showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.from, size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
2032
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2033
+ showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg-head", children: [
2034
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: m.from }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
2036
+ ] }) : null,
2037
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) })
2038
+ ] })
2039
+ ] });
2040
+ }
2041
+ function ChatBody({ messages }) {
2042
+ const ref = (0, import_react.useRef)(null);
2043
+ (0, import_react.useEffect)(() => {
2044
+ const el = ref.current;
2045
+ if (el) el.scrollTop = el.scrollHeight;
2046
+ }, [messages.length]);
2047
+ const nodes = [];
2048
+ let day = null, prev = null;
2049
+ for (const m of messages) {
2050
+ const d = fmtDay(m.ts);
2051
+ if (d !== day) {
2052
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-day", children: d }, "d" + m.ts));
2053
+ day = d;
2054
+ prev = null;
2055
+ }
2056
+ const showAuthor = !prev || prev.from !== m.from || m.ts - prev.ts > 5 * 6e4;
2057
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor }, m.id));
2058
+ prev = m;
2059
+ }
2060
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-messages", ref, children: nodes.length ? nodes : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-hint", children: "No messages yet. Send a message, @mention an agent to wake it, or create a task from the composer." }) });
2061
+ }
2062
+ function mentionQueryAt(text, caret) {
2063
+ const before = text.slice(0, caret);
2064
+ const m = /(^|[\s([{])@([A-Za-z0-9._:-]*)$/.exec(before);
2065
+ if (!m) return null;
2066
+ const at = before.lastIndexOf("@");
2067
+ return { start: at, end: caret, query: m[2] || "" };
2068
+ }
2069
+ function MentionMenu({ items, active, onPick, onHover }) {
2070
+ if (!items.length) return null;
2071
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-mention-menu", role: "listbox", "aria-label": "Mention someone", children: items.map((it, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2072
+ "button",
2073
+ {
2074
+ type: "button",
2075
+ role: "option",
2076
+ "aria-selected": i === active,
2077
+ className: "rm-mention-item" + (i === active ? " is-active" : ""),
2078
+ onMouseEnter: () => onHover(i),
2079
+ onMouseDown: (e) => {
2080
+ e.preventDefault();
2081
+ onPick(it);
2082
+ },
2083
+ children: [
2084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.id, size: 22 }),
2085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-body", children: [
2086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-name", children: [
2087
+ "@",
2088
+ it.id
2089
+ ] }),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention-item-meta", children: it.hint })
2091
+ ] }),
2092
+ it.online === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "offline" }) : null
2093
+ ]
2094
+ },
2095
+ it.id
2096
+ )) });
2097
+ }
2098
+ function Composer({ room, store, draft: draft2, setDraft: setDraft2 }) {
2099
+ const a = store.actions;
2100
+ const [mode, setMode] = (0, import_react.useState)("chat");
2101
+ const [busy, setBusy] = (0, import_react.useState)(false);
2102
+ const [modelOpen, setModelOpen] = (0, import_react.useState)(false);
2103
+ const [permOpen, setPermOpen] = (0, import_react.useState)(false);
2104
+ const [peerOpen, setPeerOpen] = (0, import_react.useState)(false);
2105
+ const [mentionActive, setMentionActive2] = (0, import_react.useState)(0);
2106
+ const [caret, setCaret2] = (0, import_react.useState)(0);
2107
+ const [mentionDismissed, setMentionDismissed] = (0, import_react.useState)(false);
2108
+ const taRef2 = (0, import_react.useRef)(null);
2109
+ const offline = room.status !== "online";
2110
+ const mqRaw = mentionQueryAt(draft2, caret);
2111
+ const mq2 = mentionDismissed ? null : mqRaw;
2112
+ const mentionItems = (() => {
2113
+ if (!mq2) return [];
2114
+ const q = mq2.query.toLowerCase();
2115
+ const members = (room.members || []).filter((m) => !m.self);
2116
+ const known = members.map((m) => ({
2117
+ id: m.agentId,
2118
+ hint: m.online === false ? "member \xB7 offline" : "member \xB7 wake",
2119
+ online: m.online !== false
2120
+ }));
2121
+ const all = { id: "all", hint: "Everyone \xB7 wake all members", online: true };
2122
+ const list = [all, ...known];
2123
+ return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || ("@" + it.id).toLowerCase().startsWith("@" + q));
2124
+ })();
2125
+ const mc = room.memberConfig || {};
2126
+ const modelLabel = mc.model || "Default model";
2127
+ const effort = mc.reasoningEffort || "medium";
2128
+ const permSummary = (() => {
2129
+ const p = mc.permissions || {};
2130
+ const bits = [];
2131
+ if (p.autoRead !== false) bits.push("read");
2132
+ if (p.autoWrite) bits.push("write");
2133
+ if (p.autoShell) bits.push("shell");
2134
+ if (p.autoNetwork) bits.push("net");
2135
+ return bits.length ? `Auto \xB7 ${bits.join("+")}` : "Ask to run";
2136
+ })();
2137
+ (0, import_react.useEffect)(() => {
2138
+ setMentionActive2(0);
2139
+ setMentionDismissed(false);
2140
+ }, [mqRaw && mqRaw.query, mqRaw && mqRaw.start]);
2141
+ const MODELS = ["deepseek-chat", "deepseek-reasoner", "deepseek-coder"];
2142
+ const EFFORTS = ["low", "medium", "high", "max"];
2143
+ const patchConfig = async (patch) => {
2144
+ try {
2145
+ const next = {
2146
+ model: mc.model,
2147
+ provider: mc.provider,
2148
+ reasoningEffort: mc.reasoningEffort || "medium",
2149
+ workspacePath: mc.workspacePath,
2150
+ workspaceLabel: mc.workspaceLabel,
2151
+ mcpEnabled: mc.mcpEnabled !== false,
2152
+ ...patch,
2153
+ permissions: { ...mc.permissions || { autoRead: true, autoWrite: false, autoShell: false, autoNetwork: false }, ...patch.permissions || {} },
2154
+ peerGrants: patch.peerGrants !== void 0 ? patch.peerGrants : mc.peerGrants
2155
+ };
2156
+ await a.saveConfig(room.roomId, next);
2157
+ } catch (e) {
2158
+ a.toast(e.message);
2159
+ }
2160
+ };
2161
+ const syncCaret = (el) => {
2162
+ if (!el) return;
2163
+ setCaret2(el.selectionStart ?? el.value.length);
2164
+ };
2165
+ const insertMention = (it) => {
2166
+ if (!mq2) return;
2167
+ const insert = `@${it.id} `;
2168
+ const next = draft2.slice(0, mq2.start) + insert + draft2.slice(mq2.end);
2169
+ setDraft2(next);
2170
+ const pos = mq2.start + insert.length;
2171
+ setCaret2(pos);
2172
+ setMentionActive2(0);
2173
+ requestAnimationFrame(() => {
2174
+ const el = taRef2.current;
2175
+ if (el) {
2176
+ el.focus();
2177
+ el.setSelectionRange(pos, pos);
2178
+ }
2179
+ });
2180
+ };
2181
+ const submit = async () => {
2182
+ const text = draft2.trim();
2183
+ if (!text || busy) return;
2184
+ setBusy(true);
2185
+ try {
2186
+ if (mode === "task") {
2187
+ const m = /^@([A-Za-z0-9][\w.:-]*)\s+/.exec(text);
2188
+ await a.task(room.roomId, m ? text.slice(m[0].length) : text, m ? m[1] : void 0);
2189
+ setMode("chat");
2190
+ } else await a.send(room.roomId, text);
2191
+ setDraft2("");
2192
+ } catch (e) {
2193
+ a.toast(e.message);
2194
+ } finally {
2195
+ setBusy(false);
2196
+ }
2197
+ };
2198
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-card", "data-rm-composer": "", children: [
2199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-grow", children: [
2200
+ mq2 && mentionItems.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MentionMenu, { items: mentionItems, active: mentionActive, onHover: setMentionActive2, onPick: insertMention }) : null,
2201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2202
+ "textarea",
2203
+ {
2204
+ ref: taRef2,
2205
+ rows: 1,
2206
+ disabled: offline,
2207
+ "aria-label": "Message the room",
2208
+ placeholder: offline ? "Room is offline" : mode === "task" ? "Task: @assignee title (optional assignee)" : `Message ${room.name} \xB7 @agent or @all`,
2209
+ value: draft2,
2210
+ onChange: (e) => {
2211
+ setDraft2(e.target.value);
2212
+ syncCaret(e.target);
2213
+ },
2214
+ onClick: (e) => syncCaret(e.target),
2215
+ onKeyUp: (e) => syncCaret(e.target),
2216
+ onSelect: (e) => syncCaret(e.target),
2217
+ onKeyDown: (e) => {
2218
+ if (mq2 && mentionItems.length) {
2219
+ if (e.key === "ArrowDown") {
2220
+ e.preventDefault();
2221
+ setMentionActive2((i) => (i + 1) % mentionItems.length);
2222
+ return;
2223
+ }
2224
+ if (e.key === "ArrowUp") {
2225
+ e.preventDefault();
2226
+ setMentionActive2((i) => (i - 1 + mentionItems.length) % mentionItems.length);
2227
+ return;
2228
+ }
2229
+ if (e.key === "Enter" || e.key === "Tab") {
2230
+ e.preventDefault();
2231
+ insertMention(mentionItems[mentionActive] || mentionItems[0]);
2232
+ return;
2233
+ }
2234
+ if (e.key === "Escape") {
2235
+ e.preventDefault();
2236
+ setMentionDismissed(true);
2237
+ return;
2238
+ }
2239
+ }
2240
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
2241
+ e.preventDefault();
2242
+ void submit();
2243
+ }
2244
+ }
2245
+ }
2246
+ )
2247
+ ] }),
2248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-row", children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-tools", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-add", title: mode === "task" ? "Switch to chat" : "Create a task", "aria-pressed": mode === "task", onClick: () => setMode(mode === "task" ? "chat" : "task"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}) }) }),
2250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-modes", children: [
2251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
2252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-composer-select", disabled: offline, onClick: () => {
2253
+ setModelOpen(!modelOpen);
2254
+ setPermOpen(false);
2255
+ setPeerOpen(false);
2256
+ }, title: "Room agent model", children: [
2257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-model-text", children: modelLabel }),
2258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-model-icon", "aria-hidden": true, children: "\u25C6" }),
2259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-composer-effort", children: effort })
2260
+ ] }),
2261
+ modelOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu", role: "menu", children: [
2262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Model" }),
2263
+ MODELS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", className: mc.model === m ? "is-active" : "", onClick: () => {
2264
+ void patchConfig({ model: m });
2265
+ setModelOpen(false);
2266
+ }, children: m }, m)),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Reasoning" }),
2268
+ EFFORTS.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", className: effort === e ? "is-active" : "", onClick: () => {
2269
+ void patchConfig({ reasoningEffort: e });
2270
+ setModelOpen(false);
2271
+ }, children: e }, e)),
2272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: () => {
2273
+ setModelOpen(false);
2274
+ a.openDialog({ kind: "config", roomId: room.roomId });
2275
+ }, children: "More agent settings\u2026" })
2276
+ ] }) : null
2277
+ ] }),
2278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
2279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-select", disabled: offline, onClick: () => {
2280
+ setPermOpen(!permOpen);
2281
+ setModelOpen(false);
2282
+ setPeerOpen(false);
2283
+ }, title: "Your auto-permissions", children: permSummary }),
2284
+ permOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu", role: "menu", children: [
2285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Auto-allow (you)" }),
2286
+ [["autoRead", "Read"], ["autoWrite", "Write"], ["autoShell", "Shell"], ["autoNetwork", "Network"]].map(([k, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-composer-check", children: [
2287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2288
+ "input",
2289
+ {
2290
+ type: "checkbox",
2291
+ checked: !!(mc.permissions || {})[k] || k === "autoRead" && mc.permissions?.autoRead !== false,
2292
+ onChange: (e) => void patchConfig({ permissions: { [k]: e.target.checked } })
2293
+ }
2294
+ ),
2295
+ label
2296
+ ] }, k)),
2297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: () => {
2298
+ setPermOpen(false);
2299
+ a.openDialog({ kind: "config", roomId: room.roomId });
2300
+ }, children: "Open full settings\u2026" })
2301
+ ] }) : null
2302
+ ] }),
2303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-popwrap", children: [
2304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-select rm-composer-peers", disabled: offline, onClick: () => {
2305
+ setPeerOpen(!peerOpen);
2306
+ setModelOpen(false);
2307
+ setPermOpen(false);
2308
+ }, title: "Peer MCP limits on this workspace", children: "Peer MCP" }),
2309
+ peerOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PeerGrantsMenu, { room, store, onClose: () => setPeerOpen(false), patchConfig }) : null
2310
+ ] })
2311
+ ] }),
2312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-trailing", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-composer-send", "aria-label": mode === "task" ? "Create task" : "Send", disabled: !draft2.trim() || busy || offline, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {}) }) })
2313
+ ] })
2314
+ ] });
2315
+ }
2316
+ function PeerGrantsMenu({ room, store, onClose, patchConfig }) {
2317
+ const mc = room.memberConfig || {};
2318
+ const def = mc.peerGrants && mc.peerGrants.default || { read: "auto", write: "ask", exec: "ask", delete: "ask" };
2319
+ const byAgent = mc.peerGrants && mc.peerGrants.byAgent || {};
2320
+ const peers = (room.members || []).filter((m) => !m.self);
2321
+ const setDefault = (cap, v) => {
2322
+ void patchConfig({ peerGrants: { default: { ...def, [cap]: v }, byAgent: { ...byAgent } } });
2323
+ };
2324
+ const setPeer = (agentId, cap, v) => {
2325
+ const cur = { ...byAgent[agentId] || {} };
2326
+ cur[cap] = v;
2327
+ void patchConfig({ peerGrants: { default: { ...def }, byAgent: { ...byAgent, [agentId]: cur } } });
2328
+ };
2329
+ const GRANT = ["auto", "ask", "deny"];
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-composer-menu rm-composer-menu-wide", role: "menu", children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Default peer limits (this workspace)" }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: "0 8px 6px" }, children: "Controls what same-room peers may do via MCP on your path. Never shares API keys." }),
2333
+ ["read", "write", "exec", "delete"].map((cap) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-grant-row", children: [
2334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: cap }),
2335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: def[cap] || "ask", onChange: (e) => setDefault(cap, e.target.value), "aria-label": `Default ${cap}`, children: GRANT.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: g, children: g }, g)) })
2336
+ ] }, cap)),
2337
+ peers.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-composer-menu-label", children: "Per peer" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", style: { margin: 8 }, children: "No other peers online yet." }),
2338
+ peers.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-peer-grant-block", children: [
2339
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-peer-grant-name", children: m.agentId }),
2340
+ ["read", "write", "exec", "delete"].map((cap) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-grant-row", children: [
2341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: cap }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: byAgent[m.agentId] && byAgent[m.agentId][cap] || def[cap] || "ask", onChange: (e) => setPeer(m.agentId, cap, e.target.value), "aria-label": `${m.agentId} ${cap}`, children: GRANT.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: g, children: g }, g)) })
2343
+ ] }, cap))
2344
+ ] }, m.agentId)),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "menuitem", onClick: onClose, children: "Done" })
2346
+ ] });
2347
+ }
2348
+ function RoomDetail({ room, messages, store, mention, assign }) {
2349
+ const a = store.actions;
2350
+ const tasks = (0, import_react.useMemo)(() => {
2351
+ const map = /* @__PURE__ */ new Map();
2352
+ for (const m of messages) {
2353
+ if (!m.taskId) continue;
2354
+ const t = map.get(m.taskId) || { taskId: m.taskId, title: m.taskId, status: "open" };
2355
+ if (m.kind === "task.create") Object.assign(t, { title: m.title, assignee: m.assignee });
2356
+ if (m.status) t.status = m.status;
2357
+ map.set(m.taskId, t);
2358
+ }
2359
+ return [...map.values()].reverse();
2360
+ }, [messages]);
2361
+ const copyInvite = async () => {
2362
+ const inv = await a.invite(room.roomId);
2363
+ a.toast(await (0, import_dsh_client_ui_primitives.writeClipboard)(inv) ? "Invite copied" : inv);
2364
+ };
2365
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "rm-detail", "aria-label": "Room details", children: [
2366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
2368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PeopleIcon, {}),
2369
+ "Members \xB7 ",
2370
+ room.members.length
2371
+ ] }),
2372
+ room.members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-member", "data-agent-id": m.agentId, children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
2374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.agentId, size: 26 }),
2375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + (m.online === false ? "offline" : "online"), style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
2376
+ ] }),
2377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.agentId }),
2378
+ m.self ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "you" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-member-actions", children: [
2379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${m.agentId}`, onClick: () => mention(m.agentId), children: "@" }),
2380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `Assign a task to ${m.agentId}`, onClick: () => assign(m.agentId), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}) })
2381
+ ] })
2382
+ ] }, m.agentId)),
2383
+ !room.members.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.status === "online" ? "Only you are here." : "Offline." }) : null
2384
+ ] }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2386
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
2387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
2388
+ "Tasks \xB7 ",
2389
+ tasks.length
2390
+ ] }),
2391
+ tasks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-row", children: [
2392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-task-row-title", children: t.title }),
2393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: STATUS_TONE[t.status] || "neutral", children: t.status }),
2394
+ t.assignee ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-note", style: { margin: 0 }, children: [
2395
+ "@",
2396
+ t.assignee
2397
+ ] }) : null
2398
+ ] }, t.taskId)),
2399
+ !tasks.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No tasks. Use the task button in the composer." }) : null
2400
+ ] }),
2401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
2403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
2404
+ "Invite"
2405
+ ] }),
2406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.public ? "Public: listed in the relay directory with its key. Anyone can find, join, and read this room." : "Private: only agents holding the invite can join. The invite contains the room key \u2014 share it only with agents you trust." }),
2407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: copyInvite, icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}), children: "Copy invite" }) })
2408
+ ] }),
2409
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "About" }),
2411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "rm-about", children: [
2412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Visibility" }),
2413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
2415
+ " Public"
2416
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
2418
+ " Private"
2419
+ ] }) }),
2420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Transport" }),
2421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
2422
+ room.memberConfig?.workspaceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2423
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Workspace" }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.memberConfig.workspaceLabel })
2425
+ ] }) : null,
2426
+ room.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Description" }),
2428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.description })
2429
+ ] }) : null,
2430
+ room.tags?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Tags" }),
2432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.tags.map((t) => "#" + t).join(" ") })
2433
+ ] }) : null,
2434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Room id" }),
2435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "rm-mono", children: room.roomId }),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Session" }),
2437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "rm-mono", children: room.sessionTitle }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Encryption" }),
2439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: "E2EE (AES-GCM); relay sees ciphertext only" })
2440
+ ] }),
2441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-actions", children: [
2442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.openDialog({ kind: "config", roomId: room.roomId }), children: "Agent config" }),
2443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "ghost", className: "rm-danger", onClick: () => a.openDialog({ kind: "leave", roomId: room.roomId, name: room.name }), children: "Leave room" })
2444
+ ] })
2445
+ ] })
2446
+ ] });
2447
+ }
2448
+ function EmptyMain({ store }) {
2449
+ const s = useStore(store);
2450
+ const a = store.actions;
2451
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-root rm-welcome-wrap", "data-collab-main": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome", children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-badge", children: "Collab \xB7 E2EE \xB7 no accounts" }),
2453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Workspaces, then rooms" }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Bind a local workspace (project folder), then create rooms inside it. Peers join those rooms and point their agent at the same project. @mention or tasks wake agents. Relay or LAN transport; E2EE; optional public directory." }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome-actions", children: [
2456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "workspace" }), children: [
2457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
2458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Bind workspace" }),
2460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Local project path shared by agents in rooms" })
2461
+ ] })
2462
+ ] }),
2463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "create" }), children: [
2464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
2465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Create room" }),
2467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "In the active workspace \xB7 Relay or LAN" })
2468
+ ] })
2469
+ ] }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "join" }), children: [
2471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
2472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Join by invite" }),
2474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://join/\u2026 invite" })
2475
+ ] })
2476
+ ] }),
2477
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.setSearchOpen(true), children: [
2478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
2479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Browse public rooms" }),
2481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Search the relay directory by name or #tag" })
2482
+ ] })
2483
+ ] })
2484
+ ] })
2485
+ ] }) });
2486
+ }
2487
+ function RoomsMain({ store }) {
2488
+ const s = useStore(store);
2489
+ const [drafts, setDrafts] = (0, import_react.useState)({});
2490
+ const room = s.rooms.find((r) => r.roomId === s.selected);
2491
+ if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store });
2492
+ const messages = s.messages[room.roomId] || [];
2493
+ const draft2 = drafts[room.roomId] || "";
2494
+ const setDraft2 = (v) => setDrafts((d) => ({ ...d, [room.roomId]: typeof v === "function" ? v(d[room.roomId] || "") : v }));
2495
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
2496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
2497
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
2498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
2499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
2500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
2503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
2504
+ room.members.filter((m) => m.online !== false).length,
2505
+ " online \xB7 ",
2506
+ room.public ? "public" : "private",
2507
+ room.status !== "online" ? ` \xB7 room ${room.status}` : ""
2508
+ ] })
2509
+ ] }),
2510
+ room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
2511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
2512
+ "Public"
2513
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
2514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
2515
+ "Private"
2516
+ ] }),
2517
+ room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
2518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
2519
+ ] }),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages }),
2521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft: draft2, setDraft: setDraft2 })
2522
+ ] }),
2523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2524
+ RoomDetail,
2525
+ {
2526
+ room,
2527
+ messages,
2528
+ store,
2529
+ mention: (id) => setDraft2((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
2530
+ assign: (id) => {
2531
+ setDraft2(`@${id} `);
2532
+ store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
2533
+ }
2534
+ }
2535
+ )
2536
+ ] })
2537
+ ] });
2538
+ }
2539
+ function Field({ label, hint, children, htmlFor }) {
2540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-field", children: [
2541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "rm-field-label", htmlFor, children: label }),
2542
+ children,
2543
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: hint }) : null
2544
+ ] });
2545
+ }
2546
+ function CreateDialog({ store }) {
2547
+ const s = useStore(store);
2548
+ const a = store.actions;
2549
+ const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80" });
2550
+ const [err, setErr] = (0, import_react.useState)(null);
2551
+ const [busy, setBusy] = (0, import_react.useState)(false);
2552
+ const [lanPeers, setLanPeers] = (0, import_react.useState)([]);
2553
+ const ok = f.name.trim() && !busy;
2554
+ const syncCaret = (el) => {
2555
+ if (!el) return;
2556
+ setCaret(el.selectionStart ?? el.value.length);
2557
+ };
2558
+ const insertMention = (it) => {
2559
+ if (!mq) return;
2560
+ const insert = `@${it.id} `;
2561
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
2562
+ setDraft(next);
2563
+ const pos = mq.start + insert.length;
2564
+ setCaret(pos);
2565
+ setMentionActive(0);
2566
+ requestAnimationFrame(() => {
2567
+ const el = taRef.current;
2568
+ if (el) {
2569
+ el.focus();
2570
+ el.setSelectionRange(pos, pos);
2571
+ }
2572
+ });
2573
+ };
2574
+ const submit = async () => {
2575
+ if (!ok) return;
2576
+ setBusy(true);
2577
+ setErr(null);
2578
+ try {
2579
+ await a.create({
2580
+ name: f.name.trim(),
2581
+ description: f.description.trim(),
2582
+ tags: f.tags,
2583
+ public: f.public,
2584
+ mode: f.mode,
2585
+ lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0
2586
+ });
2587
+ } catch (e) {
2588
+ setErr(e.message);
2589
+ setBusy(false);
2590
+ }
2591
+ };
2592
+ const browseLan = async () => {
2593
+ try {
2594
+ const r = await a.browseLan();
2595
+ setLanPeers(r.peers || []);
2596
+ } catch (e) {
2597
+ setErr(e.message);
2598
+ }
2599
+ };
2600
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2601
+ import_dsh_client_ui_primitives.Modal,
2602
+ {
2603
+ open: true,
2604
+ onClose: a.closeDialog,
2605
+ title: "Create room",
2606
+ closeLabel: "Close",
2607
+ description: "Room is created inside the active workspace (path becomes MCP sandbox). Default transport is the VPS Node relay (62.234.113.56:80) with optional public directory. Cloudflare remains an advanced option. The room key never goes to the relay.",
2608
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !ok, onClick: submit, children: busy ? "Creating\u2026" : "Create" })
2611
+ ] }),
2612
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", onKeyDown: (e) => e.key === "Enter" && e.target.tagName === "INPUT" && submit(), children: [
2613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Transport", hint: "Default: VPS Node relay at 62.234.113.56:80 (invite-only or public directory). Blank host = embedded local LAN + mDNS. Cloudflare is optional.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.mode === "lan" ? " is-on" : ""), onClick: () => setF({ ...f, mode: "lan", lanHostPort: f.lanHostPort || "62.234.113.56:80" }), children: "LAN / VPS" }),
2615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.mode === "relay" ? " is-on" : ""), onClick: () => setF({ ...f, mode: "relay" }), children: "Cloudflare (advanced)" })
2616
+ ] }) }),
2617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-create-name", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-name-row", children: [
2618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: f.name || "room", size: 36 }),
2619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-name", autoFocus: true, placeholder: "e.g. release-crew", maxLength: 60, value: f.name, onChange: (e) => setF({ ...f, name: e.target.value }) })
2620
+ ] }) }),
2621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Description (optional)", htmlFor: "rm-create-desc", hint: "Shown to members and, if public, in search results.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-desc", placeholder: "e.g. Agents that review and ship releases", maxLength: 280, value: f.description, onChange: (e) => setF({ ...f, description: e.target.value }) }) }),
2622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Tags (optional)", htmlFor: "rm-create-tags", hint: "Spaces or commas, up to 8. Used by public room search.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-create-tags", placeholder: "e.g. review rust", value: f.tags, onChange: (e) => setF({ ...f, tags: e.target.value }) }) }),
2623
+ f.mode === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { label: "Relay host:port", htmlFor: "rm-lan-hp", hint: "Default VPS 62.234.113.56:80 (public directory works). Blank = this machine (LAN/Tailscale + mDNS). Invite: collab://lan/host:port/room#key.", children: [
2624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-lan-hp", placeholder: "62.234.113.56:80 or 100.x.x.x:7421", value: f.lanHostPort, onChange: (e) => setF({ ...f, lanHostPort: e.target.value }) }),
2625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-lan-browse", children: [
2626
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: browseLan, children: "Browse mDNS" }),
2627
+ lanPeers.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-lan-peer", onClick: () => setF({ ...f, lanHostPort: p.hostPort }), children: [
2628
+ p.hostPort,
2629
+ p.name ? ` \xB7 ${p.name}` : ""
2630
+ ] }, p.hostPort))
2631
+ ] })
2632
+ ] }) : null,
2633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "List in public directory" }),
2636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: f.public ? "Anyone can find this room by name or tag and join it. Its key is published with the listing \u2014 NOT confidential." : "Private: invite only. Nothing about the room is published." })
2637
+ ] }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.public, onChange: (v) => setF({ ...f, public: v }), label: "List in public directory" })
2639
+ ] }),
2640
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
2641
+ ] })
2642
+ }
2643
+ );
2644
+ }
2645
+ function JoinDialog({ store }) {
2646
+ const s = useStore(store);
2647
+ const a = store.actions;
2648
+ const [invite, setInvite] = (0, import_react.useState)("");
2649
+ const [err, setErr] = (0, import_react.useState)(null);
2650
+ const [busy, setBusy] = (0, import_react.useState)(false);
2651
+ const valid = /^collab:\/\/(join|lan|relay)\/.+#\S+$/.test(invite.trim());
2652
+ const syncCaret = (el) => {
2653
+ if (!el) return;
2654
+ setCaret(el.selectionStart ?? el.value.length);
2655
+ };
2656
+ const insertMention = (it) => {
2657
+ if (!mq) return;
2658
+ const insert = `@${it.id} `;
2659
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
2660
+ setDraft(next);
2661
+ const pos = mq.start + insert.length;
2662
+ setCaret(pos);
2663
+ setMentionActive(0);
2664
+ requestAnimationFrame(() => {
2665
+ const el = taRef.current;
2666
+ if (el) {
2667
+ el.focus();
2668
+ el.setSelectionRange(pos, pos);
2669
+ }
2670
+ });
2671
+ };
2672
+ const submit = async () => {
2673
+ if (!valid || busy) return;
2674
+ setBusy(true);
2675
+ setErr(null);
2676
+ try {
2677
+ await a.join(invite.trim());
2678
+ } catch (e) {
2679
+ setErr(e.message);
2680
+ setBusy(false);
2681
+ }
2682
+ };
2683
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2684
+ import_dsh_client_ui_primitives.Modal,
2685
+ {
2686
+ open: true,
2687
+ onClose: a.closeDialog,
2688
+ title: "Join room",
2689
+ closeLabel: "Close",
2690
+ description: "Paste an invite. The room attaches to your active workspace \u2014 set the local path to your checkout of the same project before collaborating via MCP.",
2691
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2692
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !valid || busy, onClick: submit, children: busy ? "Joining\u2026" : "Join" })
2694
+ ] }),
2695
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
2696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { autoFocus: true, "aria-label": "Invite", placeholder: "collab://join/\u2026 or collab://lan/<host>:<port>/<roomId>#\u2026", value: invite, onChange: (e) => setInvite(e.target.value), onKeyDown: (e) => e.key === "Enter" && submit() }),
2697
+ invite && !valid ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Invites: collab://join/\u2026#key, collab://lan/host:port/room#key, or collab://relay/host/join/room#key" }) : null,
2698
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
2699
+ ] })
2700
+ }
2701
+ );
2702
+ }
2703
+ function InviteDialog({ store, d }) {
2704
+ const s = useStore(store);
2705
+ const a = store.actions;
2706
+ const [copied, setCopied] = (0, import_react.useState)(false);
2707
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2708
+ import_dsh_client_ui_primitives.Modal,
2709
+ {
2710
+ open: true,
2711
+ onClose: a.closeDialog,
2712
+ title: `Room "${d.name}" created`,
2713
+ closeLabel: "Close",
2714
+ description: d.public ? d.listed ? "Listed in the public directory: other agents can find it with search and join with one click." : "Directory listing failed; the room still works by invite." : "Private room. Share this invite with the agents you want in it.",
2715
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Done" }),
2717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", onClick: async () => setCopied(await (0, import_dsh_client_ui_primitives.writeClipboard)(d.invite)), children: copied ? "Copied" : "Copy invite" })
2718
+ ] }),
2719
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
2720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "rm-invite", "data-invite": "", children: d.invite }),
2721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Anyone holding the invite can join and read the room." })
2722
+ ] })
2723
+ }
2724
+ );
2725
+ }
2726
+ function LeaveDialog({ store, d }) {
2727
+ const s = useStore(store);
2728
+ const a = store.actions;
2729
+ const [busy, setBusy] = (0, import_react.useState)(false);
2730
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2731
+ import_dsh_client_ui_primitives.Modal,
2732
+ {
2733
+ open: true,
2734
+ onClose: a.closeDialog,
2735
+ title: `Leave "${d.name}"?`,
2736
+ closeLabel: "Close",
2737
+ description: "This machine forgets the room and its invite. If you listed it publicly, the listing is removed.",
2738
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: async () => {
2741
+ setBusy(true);
2742
+ try {
2743
+ await a.leave(d.roomId);
2744
+ } catch (e) {
2745
+ a.toast(e.message);
2746
+ setBusy(false);
2747
+ }
2748
+ }, children: busy ? "Leaving\u2026" : "Leave" })
2749
+ ] })
2750
+ }
2751
+ );
2752
+ }
2753
+ function ConfigDialog({ store, d }) {
2754
+ const s = useStore(store);
2755
+ const a = store.actions;
2756
+ const room = (store.getSnapshot().rooms || []).find((r) => r.roomId === d.roomId);
2757
+ const mc = room?.memberConfig || {};
2758
+ const [f, setF] = (0, import_react.useState)({
2759
+ model: mc.model || "",
2760
+ provider: mc.provider || "",
2761
+ reasoningEffort: mc.reasoningEffort || "medium",
2762
+ workspacePath: "",
2763
+ workspaceLabel: mc.workspaceLabel || "",
2764
+ autoRead: mc.permissions?.autoRead !== false,
2765
+ autoWrite: !!mc.permissions?.autoWrite,
2766
+ autoShell: !!mc.permissions?.autoShell,
2767
+ autoNetwork: !!mc.permissions?.autoNetwork,
2768
+ mcpEnabled: mc.mcpEnabled !== false
2769
+ });
2770
+ const [err, setErr] = (0, import_react.useState)(null);
2771
+ const [busy, setBusy] = (0, import_react.useState)(false);
2772
+ (0, import_react.useEffect)(() => {
2773
+ a.getConfig(d.roomId).then((c) => setF((x) => ({ ...x, workspacePath: c.workspacePath || "", workspaceLabel: c.workspaceLabel || x.workspaceLabel, model: c.model || "", provider: c.provider || "", reasoningEffort: c.reasoningEffort || "medium", autoRead: c.permissions?.autoRead !== false, autoWrite: !!c.permissions?.autoWrite, autoShell: !!c.permissions?.autoShell, autoNetwork: !!c.permissions?.autoNetwork, mcpEnabled: c.mcpEnabled !== false }))).catch(() => {
2774
+ });
2775
+ }, [d.roomId]);
2776
+ const syncCaret = (el) => {
2777
+ if (!el) return;
2778
+ setCaret(el.selectionStart ?? el.value.length);
2779
+ };
2780
+ const insertMention = (it) => {
2781
+ if (!mq) return;
2782
+ const insert = `@${it.id} `;
2783
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
2784
+ setDraft(next);
2785
+ const pos = mq.start + insert.length;
2786
+ setCaret(pos);
2787
+ setMentionActive(0);
2788
+ requestAnimationFrame(() => {
2789
+ const el = taRef.current;
2790
+ if (el) {
2791
+ el.focus();
2792
+ el.setSelectionRange(pos, pos);
2793
+ }
2794
+ });
2795
+ };
2796
+ const submit = async () => {
2797
+ setBusy(true);
2798
+ setErr(null);
2799
+ try {
2800
+ await a.saveConfig(d.roomId, {
2801
+ model: f.model || void 0,
2802
+ provider: f.provider || void 0,
2803
+ reasoningEffort: f.reasoningEffort,
2804
+ workspacePath: f.workspacePath || void 0,
2805
+ workspaceLabel: f.workspaceLabel || void 0,
2806
+ mcpEnabled: f.mcpEnabled,
2807
+ permissions: { autoRead: f.autoRead, autoWrite: f.autoWrite, autoShell: f.autoShell, autoNetwork: f.autoNetwork }
2808
+ });
2809
+ a.closeDialog();
2810
+ a.toast("Agent config saved (announced to room; no API keys shared)");
2811
+ } catch (e) {
2812
+ setErr(e.message);
2813
+ setBusy(false);
2814
+ }
2815
+ };
2816
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2817
+ import_dsh_client_ui_primitives.Modal,
2818
+ {
2819
+ open: true,
2820
+ onClose: a.closeDialog,
2821
+ title: "Room agent config",
2822
+ closeLabel: "Close",
2823
+ description: "Local to this member. Peers see model, effort, workspace label, permissions, and MCP tools \u2014 never API keys.",
2824
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: submit, children: busy ? "Saving\u2026" : "Save" })
2827
+ ] }),
2828
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
2829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Model", htmlFor: "rm-cfg-model", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-model", value: f.model, onChange: (e) => setF({ ...f, model: e.target.value }), placeholder: "e.g. deepseek-chat" }) }),
2830
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Provider", htmlFor: "rm-cfg-prov", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-prov", value: f.provider, onChange: (e) => setF({ ...f, provider: e.target.value }), placeholder: "e.g. deepseek" }) }),
2831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Reasoning effort", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "rm-input", value: f.reasoningEffort, onChange: (e) => setF({ ...f, reasoningEffort: e.target.value }), children: ["low", "medium", "high", "max"].map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: x, children: x }, x)) }) }),
2832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace path", htmlFor: "rm-cfg-ws", hint: "Absolute path on this machine. Peers only see the label.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-ws", value: f.workspacePath, onChange: (e) => setF({ ...f, workspacePath: e.target.value }) }) }),
2833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace label", htmlFor: "rm-cfg-wsl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-cfg-wsl", value: f.workspaceLabel, onChange: (e) => setF({ ...f, workspaceLabel: e.target.value }) }) }),
2834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Expose MCP workspace" }),
2837
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Same-room peers can call workspace_* tools over E2EE." })
2838
+ ] }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.mcpEnabled, onChange: (v) => setF({ ...f, mcpEnabled: v }), label: "MCP" })
2840
+ ] }),
2841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-note", children: "Peer MCP limits (read/write/exec/delete) are edited from the room composer \xB7 Peer MCP control." }),
2842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow reads" }) }),
2844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoRead, onChange: (v) => setF({ ...f, autoRead: v }), label: "autoRead" })
2845
+ ] }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow writes" }),
2849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Off \u2192 approval UI for write/mkdir/delete." })
2850
+ ] }),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoWrite, onChange: (v) => setF({ ...f, autoWrite: v }), label: "autoWrite" })
2852
+ ] }),
2853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2854
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow shell" }) }),
2855
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoShell, onChange: (v) => setF({ ...f, autoShell: v }), label: "autoShell" })
2856
+ ] }),
2857
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
2858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Auto-allow network" }) }),
2859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.autoNetwork, onChange: (v) => setF({ ...f, autoNetwork: v }), label: "autoNetwork" })
2860
+ ] }),
2861
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
2862
+ ] })
2863
+ }
2864
+ );
2865
+ }
2866
+ function ApprovalBanner({ store }) {
2867
+ const s = useStore(store);
2868
+ const list = s.approvals || [];
2869
+ if (!list.length) return null;
2870
+ const a = store.actions;
2871
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-approvals", children: list.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval", children: [
2872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval-body", children: [
2873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Approval needed" }),
2874
+ " \xB7 ",
2875
+ p.action,
2876
+ " \xB7 ",
2877
+ p.summary,
2878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-field-hint", children: [
2879
+ "from ",
2880
+ p.from,
2881
+ p.detail ? ` \u2014 ${p.detail}` : ""
2882
+ ] })
2883
+ ] }),
2884
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-approval-actions", children: [
2885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.decideApproval(p.requestId, "deny"), children: "Deny" }),
2886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.decideApproval(p.requestId, "allow"), children: "Allow" })
2887
+ ] })
2888
+ ] }, p.requestId)) });
2889
+ }
2890
+ function WorkspaceDialog({ store }) {
2891
+ const s = useStore(store);
2892
+ const a = store.actions;
2893
+ const [name2, setName] = (0, import_react.useState)("");
2894
+ const [path, setPath] = (0, import_react.useState)("");
2895
+ const [err, setErr] = (0, import_react.useState)(null);
2896
+ const [busy, setBusy] = (0, import_react.useState)(false);
2897
+ const syncCaret = (el) => {
2898
+ if (!el) return;
2899
+ setCaret(el.selectionStart ?? el.value.length);
2900
+ };
2901
+ const insertMention = (it) => {
2902
+ if (!mq) return;
2903
+ const insert = `@${it.id} `;
2904
+ const next = draft.slice(0, mq.start) + insert + draft.slice(mq.end);
2905
+ setDraft(next);
2906
+ const pos = mq.start + insert.length;
2907
+ setCaret(pos);
2908
+ setMentionActive(0);
2909
+ requestAnimationFrame(() => {
2910
+ const el = taRef.current;
2911
+ if (el) {
2912
+ el.focus();
2913
+ el.setSelectionRange(pos, pos);
2914
+ }
2915
+ });
2916
+ };
2917
+ const submit = async () => {
2918
+ if (!path.trim()) {
2919
+ setErr("Absolute workspace path required");
2920
+ return;
2921
+ }
2922
+ setBusy(true);
2923
+ setErr(null);
2924
+ try {
2925
+ await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
2926
+ a.closeDialog();
2927
+ a.toast("Workspace ready \u2014 create a room to collaborate");
2928
+ } catch (e) {
2929
+ setErr(e.message);
2930
+ setBusy(false);
2931
+ }
2932
+ };
2933
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2934
+ import_dsh_client_ui_primitives.Modal,
2935
+ {
2936
+ open: true,
2937
+ onClose: a.closeDialog,
2938
+ title: "Workspace",
2939
+ closeLabel: "Close",
2940
+ description: "Pick the local project folder agents share. Rooms created next inherit this path (MCP + agent cwd). Peers map the same logical project to their own checkout.",
2941
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2942
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2943
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy || !path.trim(), onClick: submit, children: busy ? "Saving\u2026" : "Use workspace" })
2944
+ ] }),
2945
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
2946
+ (s.workspaces || []).length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Switch to existing", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-lan-browse", children: (s.workspaces || []).map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-lan-peer", onClick: async () => {
2947
+ await a.setActiveWorkspace(w.id);
2948
+ a.closeDialog();
2949
+ }, children: w.name }, w.id)) }) }) : null,
2950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-ws-name", hint: "Shown in the sidebar.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ws-name", placeholder: "e.g. collab", value: name2, onChange: (e) => setName(e.target.value) }) }),
2951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Local path", htmlFor: "rm-ws-path", hint: "Absolute path on this machine.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ws-path", autoFocus: true, placeholder: "/Users/you/code/my-app", value: path, onChange: (e) => setPath(e.target.value), onKeyDown: (e) => e.key === "Enter" && submit() }) }),
2952
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
2953
+ ] })
2954
+ }
2955
+ );
2956
+ }
2957
+ function Overlay({ store }) {
2958
+ const s = useStore(store);
2959
+ const d = s.dialog;
2960
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2961
+ d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store }) : null,
2962
+ d?.kind === "join" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JoinDialog, { store }) : null,
2963
+ d?.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InviteDialog, { store, d }) : null,
2964
+ d?.kind === "leave" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LeaveDialog, { store, d }) : null,
2965
+ d?.kind === "config" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigDialog, { store, d }) : null,
2966
+ d?.kind === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceDialog, { store }) : null,
2967
+ s.toast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: s.toast, onDone: store.actions.clearToast, holdMs: 2200 }) : null
2968
+ ] });
2969
+ }
2970
+ function installStyles() {
2971
+ if (typeof document === "undefined") return () => {
2972
+ };
2973
+ const style = document.createElement("style");
2974
+ style.setAttribute("data-dsh-room", "rooms");
2975
+ style.textContent = rooms_default;
2976
+ document.head.appendChild(style);
2977
+ return () => style.remove();
2978
+ }
2979
+ function apply(ctx) {
2980
+ const store = createRoomsStore();
2981
+ ctx.effect(() => installStyles(), "dsh-room: styles");
2982
+ ctx.effect(() => () => store.dispose(), "dsh-room: store");
2983
+ const info = () => ctx.layout.panelInfo;
2984
+ const ensurePanel = () => {
2985
+ try {
2986
+ if (info().getSnapshot().activePanelId !== PANEL_ID) ctx.layout.selectPanel(PANEL_ID);
2987
+ } catch {
2988
+ }
2989
+ };
2990
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: PANEL_ID, order: 11, label: () => "DSH Room" }, PanelIcon));
2991
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, inject: () => ({ store }) }, RoomsMain));
2992
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: "dsh-room-dialogs", inject: () => ({ store }) }, Overlay));
2993
+ ctx.slots.inject("sidebar.workspaces", () => {
2994
+ let dispose;
2995
+ const reconcile = () => {
2996
+ const active = info().getSnapshot().activePanelId === PANEL_ID;
2997
+ if (active && !dispose) dispose = ctx.slots.register({ name: "sidebar.workspaces", priority: -100, inject: () => ({ store, ensurePanel }) }, RoomsSidebar);
2998
+ else if (!active && dispose) {
2999
+ dispose();
3000
+ dispose = void 0;
3001
+ }
3002
+ };
3003
+ const off = info().subscribe(reconcile);
3004
+ reconcile();
3005
+ return () => {
3006
+ off();
3007
+ if (dispose) dispose();
3008
+ dispose = void 0;
3009
+ };
3010
+ });
3011
+ }
3012
+
3013
+ return module.exports;
3014
+ }
3015
+ });