@cecwxf/dsh-room 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -37,7 +37,7 @@ var import_react = require("react");
37
37
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
38
38
 
39
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\n\n/* New Room shortcut under workspace (mirrors New Session affordance) */\n.rm-new-room {\n display: flex; align-items: center; justify-content: center; gap: 6px;\n width: 100%; height: 36px; margin-top: 8px;\n border: .5px solid var(--rm-border); border-radius: 10px;\n background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n color: var(--rm-label); cursor: pointer; font-weight: 500; font-size: 13.5px;\n}\n.rm-new-room:hover { background: var(--dsw-alias-button-floating-hover, var(--rm-hover)); }\n.rm-new-room svg { flex: none; }\n\n/* Wide / landscape create + browse modals (overrides default 380px Modal card) */\n.rm-modal-wide {\n width: min(720px, calc(100vw - 48px)) !important;\n max-height: min(640px, calc(100vh - 48px));\n}\n.rm-form-wide {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n align-items: start;\n}\n.rm-form-wide .rm-span-2 { grid-column: 1 / -1; }\n.rm-browse { display: flex; flex-direction: column; gap: 12px; min-height: 280px; max-height: 420px; }\n.rm-browse-search { flex: none; }\n.rm-browse-status { font-size: 11.5px; color: var(--rm-quiet); white-space: nowrap; }\n.rm-browse-list { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 6px; }\n@media (max-width: 640px) {\n .rm-form-wide { grid-template-columns: 1fr; }\n .rm-modal-wide { width: min(420px, calc(100vw - 32px)) !important; }\n}\n';
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\n\n/* New Room shortcut under workspace (mirrors New Session affordance) */\n.rm-new-room {\n display: flex; align-items: center; justify-content: center; gap: 6px;\n width: 100%; height: 36px; margin-top: 8px;\n border: .5px solid var(--rm-border); border-radius: 10px;\n background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n color: var(--rm-label); cursor: pointer; font-weight: 500; font-size: 13.5px;\n}\n.rm-new-room:hover { background: var(--dsw-alias-button-floating-hover, var(--rm-hover)); }\n.rm-new-room svg { flex: none; }\n\n/* Wide / landscape create + browse modals (overrides default 380px Modal card) */\n.rm-modal-wide {\n width: min(720px, calc(100vw - 48px)) !important;\n max-height: min(640px, calc(100vh - 48px));\n}\n.rm-form-wide {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n align-items: start;\n}\n.rm-form-wide .rm-span-2 { grid-column: 1 / -1; }\n.rm-browse { display: flex; flex-direction: column; gap: 12px; min-height: 280px; max-height: 420px; }\n.rm-browse-search { flex: none; }\n.rm-browse-status { font-size: 11.5px; color: var(--rm-quiet); white-space: nowrap; }\n.rm-browse-list { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 6px; }\n@media (max-width: 640px) {\n .rm-form-wide { grid-template-columns: 1fr; }\n .rm-modal-wide { width: min(420px, calc(100vw - 32px)) !important; }\n}\n\n\n/* \u2500\u2500 panel layout: rooms nav (grouped by native dsh workspace) + content \u2500\u2500 */\n.rm-main { flex-direction: row; }\n.rm-main-body { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }\n.rm-main-body > .rm-welcome-wrap { flex: 1; }\n.rm-main-body > .rm-workspace { flex: 1; }\n.rm-nav {\n flex: 0 0 248px; width: 248px; display: flex; flex-direction: column; gap: 8px; min-height: 0;\n padding: 10px 8px; border-right: .5px solid var(--rm-border); background: var(--rm-bg);\n}\n.rm-nav-head { display: flex; align-items: center; gap: 4px; padding: 0 4px; }\n.rm-nav-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 10px; }\n.rm-ws-group { display: flex; flex-direction: column; gap: 2px; }\n.rm-ws-head {\n display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 2px 0 8px;\n font-size: 12.5px; font-weight: 600; color: var(--rm-muted);\n}\n.rm-ws-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-ws-add { width: 24px; height: 24px; }\n.rm-ws-empty { padding: 2px 10px 6px 30px; font-size: 11.5px; }\n.rm-nav .rm-room-item { padding: 6px 8px 6px 14px; }\n.rm-session-mark { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; color: var(--rm-accent, var(--dsw-alias-state-business-primary)); }\n\n/* \u2500\u2500 local multi-agent (0.3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-dot-busy { background: var(--dsw-alias-state-warning-primary, #f0a020); }\n.rm-agent-tag { display: inline-flex; align-items: center; gap: 3px; margin-left: 6px; padding: 0 6px; border-radius: 999px; font-size: 10.5px; line-height: 17px; color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); white-space: nowrap; }\n.rm-msg-id { margin-left: 6px; font-size: 11px; color: var(--rm-quiet); font-family: var(--dsw-font-mono, ui-monospace, monospace); }\n.rm-agent-row { display: flex; align-items: center; gap: 8px; padding: 6px 4px; border-radius: 8px; }\n.rm-agent-row:hover { background: var(--rm-hover); }\n.rm-agent-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }\n.rm-agent-name { font-size: 13px; font-weight: 600; color: var(--rm-label); display: flex; align-items: center; }\n.rm-agent-sub { font-size: 11px; color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.rm-agent-row .rm-member-actions { display: inline-flex !important; opacity: 0.65; }\n.rm-agent-row:hover .rm-member-actions { opacity: 1; }\n.rm-num-row { display: flex; gap: 8px; margin: 4px 0 8px; }\n.rm-num-setting { flex: 1; display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--rm-muted); }\n.rm-num-setting input { width: 100%; box-sizing: border-box; padding: 5px 8px; border: 1px solid var(--rm-border); border-radius: 7px; background: var(--rm-bg); color: var(--rm-label); font: inherit; font-size: 13px; }\n.rm-runs { display: flex; flex-direction: column; gap: 4px; }\n.rm-run { display: grid; grid-template-columns: 22px auto 1fr auto; align-items: center; gap: 6px; padding: 5px 6px; border-radius: 7px; font-size: 12px; border: 1px solid var(--rm-border); }\n.rm-run-running { border-color: color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f0a020) 55%, transparent); background: color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f0a020) 8%, transparent); }\n.rm-run-queued, .rm-run-approval { border-style: dashed; }\n.rm-run-done, .rm-run-stopped, .rm-run-failed { opacity: 0.7; }\n.rm-run-pos { font-size: 11px; color: var(--rm-quiet); text-align: center; font-variant-numeric: tabular-nums; }\n.rm-run-who { font-weight: 600; color: var(--rm-label); white-space: nowrap; }\n.rm-run-what { color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }\n.rm-stop-btn { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 60%, transparent); background: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 10%, transparent); color: var(--dsw-alias-state-danger-primary, #e5484d); font-size: 12px; cursor: pointer; white-space: nowrap; }\n.rm-stop-btn:hover { background: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 18%, transparent); }\n.rm-badge-local { color: var(--dsw-alias-state-success-primary, #2bb673); }\n.rm-session-agent { color: var(--rm-accent, #5b8cff); }\n.rm-preset-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }\n.rm-preset { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 150px; flex: 1; padding: 8px 10px; border: 1px solid var(--rm-border); border-radius: 10px; background: transparent; color: var(--rm-label); text-align: left; cursor: pointer; font: inherit; font-size: 12px; }\n.rm-preset span { color: var(--rm-muted); font-size: 11px; line-height: 1.35; }\n.rm-preset.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); }\n.rm-preset-check { flex-direction: row; align-items: center; gap: 8px; min-width: 120px; flex: 0 1 auto; }\n.rm-local-box { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border: 1px dashed var(--rm-border); border-radius: 12px; }\n.rm-textarea { width: 100%; box-sizing: border-box; min-height: 84px; resize: vertical; font: inherit; font-size: 13px; padding: 8px 10px; }\n.rm-switch-col { display: flex; flex-direction: column; gap: 6px; }\n.rm-modal-xl { width: min(1180px, 96vw) !important; max-width: 96vw !important; }\n.rm-merge { display: grid; grid-template-columns: 260px 1fr; gap: 14px; min-height: 420px; }\n.rm-merge-list { display: flex; flex-direction: column; gap: 6px; border-right: 1px solid var(--rm-border); padding-right: 12px; }\n.rm-merge-agent { display: flex; align-items: center; gap: 8px; padding: 8px; border: 1px solid transparent; border-radius: 10px; background: transparent; color: var(--rm-label); cursor: pointer; text-align: left; font: inherit; }\n.rm-merge-agent:hover { background: var(--rm-hover); }\n.rm-merge-agent.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 10%, transparent); }\n.rm-merge-agent-body { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: 12px; }\n.rm-merge-agent-body .rm-mono { font-size: 10.5px; color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.rm-merge-main { min-width: 0; display: flex; flex-direction: column; gap: 8px; }\n.rm-merge-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; font-size: 13px; }\n.rm-merge-files { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; }\n.rm-confirm-text { font-size: 12px; color: var(--rm-label); margin-right: 4px; }\n.rm-diff { margin: 0; max-height: 52vh; overflow: auto; padding: 8px 0; border: 1px solid var(--rm-border); border-radius: 10px; background: var(--rm-panel); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 12px; line-height: 1.5; }\n.rm-diff > div { padding: 0 12px; white-space: pre; }\n.rm-diff-add { background: color-mix(in srgb, #2bb673 16%, transparent); }\n.rm-diff-del { background: color-mix(in srgb, #e5484d 16%, transparent); }\n.rm-diff-hunk { color: var(--rm-accent, #5b8cff); }\n.rm-diff-head, .rm-diff-file { color: var(--rm-muted); font-weight: 600; }\n.rm-diff-addn { color: #2bb673; }\n.rm-diff-deln { color: #e5484d; }\n.rm-ok { padding: 8px 10px; border-radius: 8px; font-size: 12.5px; color: var(--dsw-alias-state-success-primary, #2bb673); background: color-mix(in srgb, #2bb673 10%, transparent); }\n';
41
41
 
42
42
  // src/client/store.js
43
43
  var API = "/api/collab";
@@ -153,13 +153,21 @@ function createRoomsStore() {
153
153
  searchTimer = setTimeout(() => void runSearch(query), 300);
154
154
  },
155
155
  async create(form) {
156
- const r = await post("/rooms", { ...form, workspaceId: form.workspaceId || snap.activeWorkspaceId });
156
+ const r = await post("/rooms", { ...form, workspaceId: form.workspacePath ? void 0 : form.workspaceId || snap.activeWorkspaceId });
157
+ try {
158
+ api.onRoomOpened && api.onRoomOpened();
159
+ } catch {
160
+ }
157
161
  set({ selected: r.roomId, dialog: { kind: "invite", roomId: r.roomId, name: r.name, invite: r.invite, public: r.public, listed: r.listed } });
158
162
  void loadMessages(r.roomId);
159
163
  return r;
160
164
  },
161
165
  async join(invite, info) {
162
- const r = await post("/join", { invite, workspaceId: snap.activeWorkspaceId, ...info || {} });
166
+ const r = await post("/join", { invite, ...info && info.workspacePath ? {} : { workspaceId: snap.activeWorkspaceId }, ...info || {} });
167
+ try {
168
+ api.onRoomOpened && api.onRoomOpened();
169
+ } catch {
170
+ }
163
171
  set((s) => ({ selected: r.roomId, dialog: null, search: { ...s.search, results: s.search.results.map((x) => x.roomId === r.roomId ? { ...x, joined: true } : x) } }));
164
172
  void loadMessages(r.roomId);
165
173
  toast(`Joined ${r.name}`);
@@ -207,9 +215,40 @@ function createRoomsStore() {
207
215
  },
208
216
  async mcp(roomId, tool, args, target) {
209
217
  return post(`/rooms/${encodeURIComponent(roomId)}/mcp`, { tool, args, target });
218
+ },
219
+ // ── local multi-agent ──
220
+ async presets() {
221
+ return (await call("/presets")).presets;
222
+ },
223
+ async addAgent(roomId, body) {
224
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents`, body);
225
+ },
226
+ async updateAgent(roomId, id, body) {
227
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents/${encodeURIComponent(id)}`, body);
228
+ },
229
+ async removeAgent(roomId, id) {
230
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents/${encodeURIComponent(id)}?op=delete`);
231
+ },
232
+ async saveLocal(roomId, patch) {
233
+ return post(`/rooms/${encodeURIComponent(roomId)}/local`, patch);
234
+ },
235
+ async setTransport(roomId, target, force) {
236
+ return post(`/rooms/${encodeURIComponent(roomId)}/transport`, { target, force: !!force });
237
+ },
238
+ async stop(roomId, agentId) {
239
+ return post(`/rooms/${encodeURIComponent(roomId)}/stop`, agentId ? { agentId } : {});
240
+ },
241
+ async diffs(roomId) {
242
+ return call(`/rooms/${encodeURIComponent(roomId)}/diffs`);
243
+ },
244
+ async merge(roomId, agentId) {
245
+ return post(`/rooms/${encodeURIComponent(roomId)}/merge`, { agentId, approve: true });
246
+ },
247
+ async discard(roomId, agentId) {
248
+ return post(`/rooms/${encodeURIComponent(roomId)}/discard`, { agentId, approve: true });
210
249
  }
211
250
  };
212
- return {
251
+ const api = {
213
252
  getSnapshot: () => snap,
214
253
  subscribe(fn) {
215
254
  subs.add(fn);
@@ -222,14 +261,20 @@ function createRoomsStore() {
222
261
  es = null;
223
262
  subs.clear();
224
263
  clearTimeout(searchTimer);
225
- }
264
+ },
265
+ /** Native dsh workspaces service (set by apply). */
266
+ native: null,
267
+ nativeList: null,
268
+ /** Called after a room is created/joined (apply switches to the DSH Room panel). */
269
+ onRoomOpened: null
226
270
  };
271
+ return api;
227
272
  }
228
273
 
229
274
  // src/client/index.jsx
230
275
  var import_jsx_runtime = require("react/jsx-runtime");
231
276
  var name = "dsh-room-client";
232
- var inject = ["slots", "layout"];
277
+ var inject = ["slots", "layout", "workspaces"];
233
278
  var PANEL_ID = "collab";
234
279
  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 });
235
280
  var RoomsIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
@@ -242,7 +287,6 @@ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { child
242
287
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.2 10.2 3.3 3.3" })
243
288
  ] });
244
289
  var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" }) });
245
- 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" }) });
246
290
  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" }) });
247
291
  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" }) });
248
292
  var TaskIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
@@ -257,6 +301,17 @@ var LockIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 1
257
301
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3.5", y: "7", width: "9", height: "6.25", rx: "1.5" }),
258
302
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 7V5.25a2.5 2.5 0 0 1 5 0V7" })
259
303
  ] });
304
+ var BotIcon = ({ size = 14 }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "5", width: "10", height: "7.5", rx: "2" }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2.5V5M6 8.5h.01M10 8.5h.01M6.25 10.75h3.5" })
307
+ ] });
308
+ var StopIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size: 12, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5" }) });
309
+ var MergeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5", cy: "3.75", r: "1.5" }),
311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5", cy: "12.25", r: "1.5" }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "8", r: "1.5" }),
313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 5.25v5.5M5 5.5c0 2 1.5 2.5 4.5 2.5" })
314
+ ] });
260
315
  var PeopleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
261
316
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "5.5", r: "2.25" }),
262
317
  /* @__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" }),
@@ -287,220 +342,133 @@ var STATUS_TONE = { open: "info", claimed: "warning", done: "success", failed: "
287
342
  function useStore(store) {
288
343
  return (0, import_react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
289
344
  }
290
- function renderMentions(text) {
291
- 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);
292
- }
293
- function PanelIcon({ size }) {
294
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
345
+ var EMPTY_WS = { items: [] };
346
+ var NO_SUB = () => () => {
347
+ };
348
+ var NO_SNAP = () => EMPTY_WS;
349
+ function useNativeWorkspaces(store) {
350
+ const src = store.nativeList;
351
+ const snap = (0, import_react.useSyncExternalStore)(src ? src.subscribe : NO_SUB, src ? src.getSnapshot : NO_SNAP);
352
+ return snap && snap.items || [];
353
+ }
354
+ var samePath = (a, b) => !!a && !!b && a.replace(/[\\/]+$/, "") === b.replace(/[\\/]+$/, "");
355
+ function groupRooms(rooms, workspaces) {
356
+ const groups = workspaces.map((w) => ({ ws: w, rooms: [] }));
357
+ const other = [];
358
+ for (const r of rooms) {
359
+ const path = r.memberConfig && r.memberConfig.workspacePath;
360
+ const g = groups.find((x) => samePath(x.ws.path, path));
361
+ if (g) g.rooms.push(r);
362
+ else other.push(r);
363
+ }
364
+ return { groups, other };
365
+ }
366
+ var wsTitle = (w) => w.title || (w.path || "").split(/[\\/]/).filter(Boolean).pop() || w.path;
367
+ function RoomItem({ r, selected, onClick }) {
368
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-room-item", "aria-current": selected ? "true" : void 0, "data-room-id": r.roomId, onClick, children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 26 }),
371
+ /* @__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)" } })
372
+ ] }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
375
+ /* @__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") })
376
+ ] }),
377
+ r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge rm-badge-public", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}) })
378
+ ] });
295
379
  }
296
- function RoomsSidebar({ store, ensurePanel }) {
380
+ function RoomsNav({ store }) {
297
381
  const s = useStore(store);
298
382
  const a = store.actions;
299
- const [menu, setMenu] = (0, import_react.useState)(null);
300
- const [joining, setJoining] = (0, import_react.useState)(null);
301
- const inputRef = (0, import_react.useRef)(null);
302
- const searching = !!s.search.open;
303
- const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0] || null;
304
- (0, import_react.useEffect)(() => {
305
- if (!menu) return;
306
- const close = () => setMenu(null);
307
- window.addEventListener("click", close);
308
- return () => window.removeEventListener("click", close);
309
- }, [menu]);
310
- (0, import_react.useEffect)(() => {
311
- if (searching && inputRef.current) inputRef.current.focus();
312
- }, [searching]);
313
- const q = (s.search.query || "").trim().toLowerCase();
314
- const mine = (s.rooms || []).filter((r) => {
315
- if (activeWs && r.workspaceId && r.workspaceId !== activeWs.id) return false;
316
- if (!q) return true;
317
- return r.name.toLowerCase().includes(q) || (r.description || "").toLowerCase().includes(q) || (r.tags || []).some((t) => ("#" + t).includes(q));
318
- });
319
- const join = async (r) => {
320
- setJoining(r.roomId);
321
- try {
322
- await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
323
- ensurePanel();
324
- } catch (e) {
325
- a.toast(e.message);
326
- } finally {
327
- setJoining(null);
328
- }
329
- };
330
- if (!activeWs) {
331
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
332
- /* @__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" }) }),
333
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
334
- /* @__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)." }),
335
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace" })
336
- ] })
337
- ] });
338
- }
339
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
340
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-toolbar", children: [
341
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "DSH Room" }),
342
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", "aria-label": searching ? "Close search" : "Search rooms", "aria-pressed": searching, onClick: () => a.setSearchOpen(!searching), children: searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) }),
343
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
344
- "button",
345
- {
346
- type: "button",
347
- className: "rm-icon-btn",
348
- "data-rm-new": "",
349
- "aria-label": "New",
350
- "aria-haspopup": "menu",
351
- "aria-expanded": !!menu,
352
- onClick: (e) => {
353
- e.stopPropagation();
354
- const r = e.currentTarget.getBoundingClientRect();
355
- setMenu(menu ? null : { top: r.bottom + 4, left: Math.max(8, r.right - 200) });
356
- },
357
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
358
- }
359
- ),
360
- 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: [
361
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
362
- setMenu(null);
363
- a.openDialog({ kind: "create" });
364
- }, children: [
365
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
366
- "Create room in workspace"
367
- ] }),
368
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
369
- setMenu(null);
370
- a.openDialog({ kind: "join" });
371
- }, children: [
372
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
373
- "Join by invite"
374
- ] }),
375
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
376
- setMenu(null);
377
- a.openDialog({ kind: "workspace" });
378
- }, children: [
379
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
380
- "Add / switch workspace"
381
- ] }),
382
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
383
- setMenu(null);
384
- a.openDialog({ kind: "browse" });
385
- }, children: [
386
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
387
- "Browse public rooms"
388
- ] })
389
- ] }) : null
390
- ] }),
391
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-bar", children: [
392
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-ws-chip", onClick: () => a.openDialog({ kind: "workspace" }), title: activeWs.path, children: [
393
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
394
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: activeWs.name })
395
- ] }),
396
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-path", title: activeWs.path, children: activeWs.path }),
397
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-new-room", "data-rm-new-room": "", onClick: () => {
398
- a.openDialog({ kind: "create" });
399
- ensurePanel();
400
- }, children: [
401
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
402
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "New Room" })
403
- ] })
383
+ const workspaces = useNativeWorkspaces(store);
384
+ const [q, setQ] = (0, import_react.useState)("");
385
+ const qq = q.trim().toLowerCase();
386
+ const match = (r) => !qq || r.name.toLowerCase().includes(qq) || (r.description || "").toLowerCase().includes(qq) || (r.tags || []).some((t) => ("#" + t).includes(qq));
387
+ const { groups, other } = groupRooms((s.rooms || []).filter(match), workspaces);
388
+ const sel = (r) => a.select(r.roomId);
389
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", { className: "rm-nav", "aria-label": "Rooms by workspace", "data-rm-nav": "", children: [
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-nav-head", children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "Rooms" }),
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", title: "Browse public rooms", "aria-label": "Browse public rooms", onClick: () => a.openDialog({ kind: "browse" }), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}) }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", title: "Join by invite", "aria-label": "Join by invite", onClick: () => a.openDialog({ kind: "join" }), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}) })
404
394
  ] }),
405
- searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
406
396
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
407
- /* @__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" })
408
- ] }) : null,
409
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
410
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
411
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
412
- "Rooms in ",
413
- activeWs.name
414
- ] }),
415
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-list", children: [
416
- mine.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "text", placeholder: "Filter rooms or #tags", value: q, onChange: (e) => setQ(e.target.value), "aria-label": "Filter rooms" })
398
+ ] }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-nav-scroll", children: [
400
+ groups.map(({ ws, rooms }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-group", "data-rm-ws-group": ws.workspaceId, children: [
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-head", title: ws.path, children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-name", children: wsTitle(ws) }),
404
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
405
  "button",
418
406
  {
419
407
  type: "button",
420
- className: "rm-room-item",
421
- "aria-current": r.roomId === s.selected ? "true" : void 0,
422
- "data-room-id": r.roomId,
423
- onClick: () => {
424
- a.select(r.roomId);
425
- ensurePanel();
426
- },
427
- children: [
428
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
429
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
430
- /* @__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)" } })
431
- ] }),
432
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
433
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
434
- /* @__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") })
435
- ] }),
436
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-trail", children: [
437
- r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
438
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
439
- "Public"
440
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
441
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
442
- "Private"
443
- ] }),
444
- r.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
445
- r.last ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--rm-quiet)" }, children: fmtTime(r.last.ts) }) : null
446
- ] })
447
- ]
448
- },
449
- r.roomId
450
- )),
451
- !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
452
- ] })
453
- ] }),
454
- (s.workspaces || []).length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
455
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-group-label", children: "Workspaces" }),
456
- (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: [
408
+ className: "rm-icon-btn rm-ws-add",
409
+ title: `New room in ${wsTitle(ws)}`,
410
+ "aria-label": `New room in ${wsTitle(ws)}`,
411
+ onClick: () => a.openDialog({ kind: "create", workspace: { id: ws.workspaceId, path: ws.path, title: wsTitle(ws) } }),
412
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
413
+ }
414
+ )
415
+ ] }),
416
+ rooms.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomItem, { r, selected: r.roomId === s.selected, onClick: () => sel(r) }, r.roomId)),
417
+ !rooms.length && !qq ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint rm-ws-empty", children: "No rooms \xB7 use + to create one here" }) : null
418
+ ] }, ws.workspaceId)),
419
+ other.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-group", children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-head", children: [
457
421
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
458
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
459
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: w.name }),
460
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: w.path })
461
- ] })
462
- ] }, w.id))
463
- ] }) : null,
464
- searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
465
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
466
- "Public directory ",
467
- s.search.loading ? "\xB7 searching\u2026" : ""
422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-name", children: "Other rooms" })
468
423
  ] }),
469
- s.search.results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item", "data-dir-room-id": r.roomId, children: [
470
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
471
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item-body", children: [
472
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-room-item-name", children: r.name }),
473
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-item-meta", children: [
474
- r.description || "No description",
475
- " \xB7 ",
476
- r.members,
477
- " online"
478
- ] }),
479
- (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: [
480
- "#",
481
- tg
482
- ] }, tg)) }) : null
483
- ] }),
484
- /* @__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" })
485
- ] }, r.roomId)),
486
- !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,
487
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
488
- "Joining attaches the room to workspace \u201C",
489
- activeWs.name,
490
- "\u201D. Point your local path at the same project."
491
- ] })
424
+ other.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomItem, { r, selected: r.roomId === s.selected, onClick: () => sel(r) }, r.roomId))
425
+ ] }) : null,
426
+ !workspaces.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
427
+ "No workspaces yet. Add one with the native \u201CAdd workspace\u201D in the left sidebar, then use \u201CNew room\u201D on it.",
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace\u2026" }) })
492
429
  ] }) : null
493
430
  ] })
494
431
  ] });
495
432
  }
496
- function Message({ m, showAuthor }) {
433
+ function RoomSessionMark({ store, sessionId }) {
434
+ const s = useStore(store);
435
+ for (const r of s.rooms || []) {
436
+ const ag = (r.local && r.local.agents || []).find((x) => x.sessionId === sessionId);
437
+ if (ag) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-session-mark rm-session-agent", title: `DSH Room \xB7 ${r.name} \xB7 ${ag.name} (${ag.agentId})`, "aria-label": `DSH Room ${r.name} agent ${ag.name}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 13 }) });
438
+ }
439
+ const room = (s.rooms || []).find((r) => r.sessionId === sessionId);
440
+ if (!room) return null;
441
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-session-mark", title: `DSH Room \xB7 ${room.name}`, "aria-label": `DSH Room ${room.name}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size: 13 }) });
442
+ }
443
+ function WorkspaceSelect({ store, value, onChange, id }) {
444
+ const workspaces = useNativeWorkspaces(store);
445
+ (0, import_react.useEffect)(() => {
446
+ if (!value && workspaces[0]) onChange({ id: workspaces[0].workspaceId, path: workspaces[0].path, title: wsTitle(workspaces[0]) });
447
+ }, [workspaces.length]);
448
+ if (!workspaces.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "No dsh workspace yet \u2014 add one in the left sidebar (Add workspace)." });
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { id, className: "rm-input", value: value ? value.path : "", onChange: (e) => {
450
+ const w = workspaces.find((x) => x.path === e.target.value);
451
+ if (w) onChange({ id: w.workspaceId, path: w.path, title: wsTitle(w) });
452
+ }, children: workspaces.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: w.path, children: [
453
+ wsTitle(w),
454
+ " \u2014 ",
455
+ w.path
456
+ ] }, w.workspaceId)) });
457
+ }
458
+ function renderMentions(text) {
459
+ 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);
460
+ }
461
+ function PanelIcon({ size }) {
462
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
463
+ }
464
+ function Message({ m, showAuthor, who }) {
497
465
  if (m.kind === "task.create") {
498
466
  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: [
499
467
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
500
468
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
501
469
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
502
470
  "Task \xB7 ",
503
- m.from
471
+ who && who.name || m.from
504
472
  ] }),
505
473
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: "open" }),
506
474
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
@@ -513,7 +481,7 @@ function Message({ m, showAuthor }) {
513
481
  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: [
514
482
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
515
483
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
516
- m.self ? "You" : m.from,
484
+ m.self ? "You" : who && who.name || m.from,
517
485
  " ",
518
486
  m.kind === "task.claim" ? "claimed" : "updated",
519
487
  " task"
@@ -533,14 +501,20 @@ function Message({ m, showAuthor }) {
533
501
  showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.from, size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
534
502
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
535
503
  showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg-head", children: [
536
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: m.from }),
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: who && who.name ? who.name : m.from }),
505
+ who && who.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-id", children: m.from }) : null,
506
+ who && who.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
508
+ who.moderator ? "moderator" : "local agent"
509
+ ] }) : null,
537
510
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
538
511
  ] }) : null,
539
512
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) })
540
513
  ] })
541
514
  ] });
542
515
  }
543
- function ChatBody({ messages }) {
516
+ function ChatBody({ messages, members }) {
517
+ const byId = (0, import_react.useMemo)(() => Object.fromEntries((members || []).map((x) => [x.agentId, x])), [members]);
544
518
  const ref = (0, import_react.useRef)(null);
545
519
  (0, import_react.useEffect)(() => {
546
520
  const el = ref.current;
@@ -556,7 +530,7 @@ function ChatBody({ messages }) {
556
530
  prev = null;
557
531
  }
558
532
  const showAuthor = !prev || prev.from !== m.from || m.ts - prev.ts > 5 * 6e4;
559
- nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor }, m.id));
533
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor, who: byId[m.from] }, m.id));
560
534
  prev = m;
561
535
  }
562
536
  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." }) });
@@ -583,7 +557,7 @@ function MentionMenu({ items, active, onPick, onHover }) {
583
557
  onPick(it);
584
558
  },
585
559
  children: [
586
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.id, size: 22 }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.name || it.id, size: 22 }),
587
561
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-body", children: [
588
562
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-name", children: [
589
563
  "@",
@@ -591,6 +565,10 @@ function MentionMenu({ items, active, onPick, onHover }) {
591
565
  ] }),
592
566
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention-item-meta", children: it.hint })
593
567
  ] }),
568
+ it.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
570
+ "local"
571
+ ] }) : null,
594
572
  it.online === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "offline" }) : null
595
573
  ]
596
574
  },
@@ -615,14 +593,20 @@ function Composer({ room, store, draft, setDraft }) {
615
593
  if (!mq) return [];
616
594
  const q = mq.query.toLowerCase();
617
595
  const members = (room.members || []).filter((m) => !m.self);
618
- const known = members.map((m) => ({
619
- id: m.agentId,
620
- hint: m.online === false ? "member \xB7 offline" : "member \xB7 wake",
621
- online: m.online !== false
622
- }));
623
- const all = { id: "all", hint: "Everyone \xB7 wake all members", online: true };
596
+ const localAgents = room.local && room.local.agents || [];
597
+ const seen = /* @__PURE__ */ new Set();
598
+ const known = [];
599
+ for (const la of localAgents) {
600
+ seen.add(la.agentId);
601
+ known.push({ id: la.agentId, name: la.name, local: true, hint: `${la.name} \xB7 local agent${la.moderator ? " \xB7 moderator" : ""}${la.state && la.state !== "idle" ? ` \xB7 ${la.state}` : ""}`, online: la.online !== false });
602
+ }
603
+ for (const m of members) {
604
+ if (seen.has(m.agentId)) continue;
605
+ known.push({ id: m.agentId, name: m.name, hint: `${m.name ? m.name + " \xB7 " : ""}${m.hostedBy ? "remote agent" : "member"}${m.online === false ? " \xB7 offline" : " \xB7 wake"}`, online: m.online !== false });
606
+ }
607
+ const all = { id: "all", hint: localAgents.length ? "Everyone \xB7 wakes all local agents in parallel" : "Everyone \xB7 wake all members", online: true };
624
608
  const list = [all, ...known];
625
- return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || ("@" + it.id).toLowerCase().startsWith("@" + q));
609
+ return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || it.name && it.name.toLowerCase().startsWith(q));
626
610
  })();
627
611
  const mc = room.memberConfig || {};
628
612
  const modelLabel = mc.model || "Default model";
@@ -660,6 +644,23 @@ function Composer({ room, store, draft, setDraft }) {
660
644
  a.toast(e.message);
661
645
  }
662
646
  };
647
+ const syncCaret = (el) => {
648
+ if (el) setCaret(el.selectionStart == null ? el.value.length : el.selectionStart);
649
+ };
650
+ const insertMention = (it) => {
651
+ if (!mq || !it) return;
652
+ const next = draft.slice(0, mq.start) + "@" + it.id + " " + draft.slice(mq.end);
653
+ const pos = mq.start + it.id.length + 2;
654
+ setDraft(next);
655
+ setCaret(pos);
656
+ requestAnimationFrame(() => {
657
+ const el = taRef.current;
658
+ if (el) {
659
+ el.focus();
660
+ el.setSelectionRange(pos, pos);
661
+ }
662
+ });
663
+ };
663
664
  const submit = async () => {
664
665
  const text = draft.trim();
665
666
  if (!text || busy) return;
@@ -856,7 +857,15 @@ function RoomDetail({ room, messages, store, mention, assign }) {
856
857
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.agentId, size: 26 }),
857
858
  /* @__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)" } })
858
859
  ] }),
859
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.agentId }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
861
+ m.name,
862
+ " ",
863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-id", children: m.agentId })
864
+ ] }) : m.agentId }),
865
+ m.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
867
+ m.moderator ? "moderator" : "local"
868
+ ] }) : m.hostedBy && !m.self ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-agent-tag", children: "remote agent" }) : null,
860
869
  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: [
861
870
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${m.agentId}`, onClick: () => mention(m.agentId), children: "@" }),
862
871
  /* @__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, {}) })
@@ -864,6 +873,9 @@ function RoomDetail({ room, messages, store, mention, assign }) {
864
873
  ] }, m.agentId)),
865
874
  !room.members.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.status === "online" ? "Only you are here." : "Offline." }) : null
866
875
  ] }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocalAgentsSection, { room, store, mention }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrchestrationSection, { room, store }),
878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IsolationSection, { room, store }),
867
879
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
868
880
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
869
881
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
@@ -886,8 +898,10 @@ function RoomDetail({ room, messages, store, mention, assign }) {
886
898
  "Invite"
887
899
  ] }),
888
900
  /* @__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." }),
901
+ room.transport === "local" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Local-only room: switch Transport to Relay or LAN before inviting remote hosts." }) : null,
889
902
  /* @__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" }) })
890
903
  ] }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransportSection, { room, store }),
891
905
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
892
906
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "About" }),
893
907
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "rm-about", children: [
@@ -900,7 +914,7 @@ function RoomDetail({ room, messages, store, mention, assign }) {
900
914
  " Private"
901
915
  ] }) }),
902
916
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Transport" }),
903
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "local" ? "Local only (in-process loopback)" : room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
904
918
  room.memberConfig?.workspaceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
905
919
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Workspace" }),
906
920
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.memberConfig.workspaceLabel })
@@ -927,33 +941,392 @@ function RoomDetail({ room, messages, store, mention, assign }) {
927
941
  ] })
928
942
  ] });
929
943
  }
930
- function EmptyMain({ store }) {
944
+ var RUN_TONE = { running: "warning", queued: "info", approval: "info", done: "success", failed: "danger", stopped: "quiet" };
945
+ var MODEL_CHOICES = ["", "deepseek-chat", "deepseek-reasoner"];
946
+ function LocalAgentsSection({ room, store, mention }) {
947
+ const a = store.actions;
948
+ const local = room.local || { agents: [] };
949
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-local-agents": "", children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, {}),
952
+ "Local agents \xB7 ",
953
+ local.agents.length
954
+ ] }),
955
+ local.agents.map((ag) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-agent-row", "data-agent-id": ag.agentId, children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: ag.name, size: 26 }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + (ag.state === "running" ? "busy" : ag.online ? "online" : "offline"), style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
959
+ ] }),
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-main", children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-name", children: [
962
+ ag.name,
963
+ ag.moderator ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-agent-tag", children: "moderator" }) : null
964
+ ] }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-sub rm-mono", children: [
966
+ "@",
967
+ ag.agentId
968
+ ] }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-sub", children: [
970
+ ag.scope === "write" ? ag.isolation === "worktree" ? "writes \xB7 own worktree" : ag.isolation === "locks" ? "writes \xB7 file locks" : "writes" : "read-only",
971
+ ag.approvalRequired ? " \xB7 approval" : "",
972
+ ag.model ? ` \xB7 ${ag.model}` : "",
973
+ ag.reasoningEffort ? ` \xB7 ${ag.reasoningEffort}` : ""
974
+ ] })
975
+ ] }),
976
+ ag.state !== "idle" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[ag.state] || "neutral", children: ag.state }) : null,
977
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-member-actions", children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${ag.name}`, onClick: () => mention(ag.agentId), children: "@" }),
979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `Edit ${ag.name}`, "aria-label": `Edit ${ag.name}`, onClick: () => a.openDialog({ kind: "agent", roomId: room.roomId, agentId: ag.agentId }), children: "\u270E" })
980
+ ] })
981
+ ] }, ag.agentId)),
982
+ !local.agents.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Host several agents from this dsh as separate members \u2014 each with its own identity, session and scope." }) : null,
983
+ /* @__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", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}), onClick: () => a.openDialog({ kind: "agent", roomId: room.roomId }), children: "Add local agent" }) })
984
+ ] });
985
+ }
986
+ function NumberSetting({ label, value, min, max, onSave, hint }) {
987
+ const [v, setV] = (0, import_react.useState)(String(value));
988
+ (0, import_react.useEffect)(() => setV(String(value)), [value]);
989
+ const commit = () => {
990
+ const n = Number(v);
991
+ if (Number.isFinite(n) && n !== value) onSave(Math.min(max, Math.max(min, Math.round(n))));
992
+ else setV(String(value));
993
+ };
994
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-num-setting", title: hint, children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", min, max, value: v, "aria-label": label, onChange: (e) => setV(e.target.value), onBlur: commit, onKeyDown: (e) => e.key === "Enter" && e.target.blur() })
997
+ ] });
998
+ }
999
+ function RunQueue({ room, store, compact }) {
1000
+ const a = store.actions;
1001
+ const runs = room.runs || [];
1002
+ const nameOf = (id) => (room.local && room.local.agents || []).find((x) => x.agentId === id)?.name || id;
1003
+ const active = runs.filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
1004
+ const recent = runs.filter((r) => !active.includes(r)).slice(-3).reverse();
1005
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-runs", "data-rm-runs": "", children: [
1006
+ active.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-run rm-run-" + r.state, children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-pos", children: r.state === "running" ? "\u25B6" : r.state === "approval" ? "?" : `#${i + 1 - active.filter((x) => x.state === "running").length}` }),
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-who", children: nameOf(r.agentId) }),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-what", title: r.summary, children: r.summary }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[r.state], children: r.state === "approval" ? "needs approval" : r.state })
1011
+ ] }, r.id)),
1012
+ !compact ? recent.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-run rm-run-" + r.state, children: [
1013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-pos", children: "\u2713" }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-who", children: nameOf(r.agentId) }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-what", title: r.error || r.summary, children: r.error || r.summary }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[r.state], children: r.state })
1017
+ ] }, r.id)) : null,
1018
+ !active.length && !recent.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No agent runs yet." }) : null,
1019
+ active.length ? /* @__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", className: "rm-danger", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}), onClick: () => a.stop(room.roomId).then((r) => a.toast(`Stopped ${r.stopped} run(s)`)).catch((e) => a.toast(e.message)), children: "Stop all agents" }) }) : null
1020
+ ] });
1021
+ }
1022
+ function OrchestrationSection({ room, store }) {
1023
+ const a = store.actions;
1024
+ const local = room.local;
1025
+ if (!local || !local.agents.length) return null;
1026
+ const save = (patch) => a.saveLocal(room.roomId, patch).catch((e) => a.toast(e.message));
1027
+ const active = (room.runs || []).filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
1028
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-orchestration": "", children: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
1030
+ "Runs \xB7 ",
1031
+ active.filter((r) => r.state === "running").length,
1032
+ "/",
1033
+ local.concurrency,
1034
+ " running",
1035
+ active.length > local.concurrency ? ` \xB7 ${active.length - Math.min(local.concurrency, active.filter((r) => r.state === "running").length)} queued` : ""
1036
+ ] }),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-num-row", children: [
1038
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NumberSetting, { label: "Parallel runs", value: local.concurrency, min: 1, max: 16, onSave: (n) => save({ concurrency: n }), hint: "Max local agent runs at once; the rest wait in the queue." }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NumberSetting, { label: "Turn limit", value: local.turnLimit, min: 1, max: 100, onSave: (n) => save({ turnLimit: n }), hint: "Max agent-to-agent hand-offs per task/thread. A human message resets it." })
1040
+ ] }),
1041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunQueue, { room, store })
1042
+ ] });
1043
+ }
1044
+ function IsolationSection({ room, store }) {
1045
+ const a = store.actions;
1046
+ const local = room.local;
1047
+ if (!local || !local.agents.some((x) => x.scope === "write")) return null;
1048
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-isolation": "", children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}),
1051
+ "Workspace isolation"
1052
+ ] }),
1053
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "rm-input", "aria-label": "Isolation mode", value: local.isolation, onChange: (e) => a.saveLocal(room.roomId, { isolation: e.target.value }).catch((x) => a.toast(x.message)), children: [
1054
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "auto", children: "Auto \u2014 git worktree per writing agent, else file locks" }),
1055
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "worktree", children: "Git worktree per agent" }),
1056
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "locks", children: "File locks (shared workspace)" }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "off", children: "Off (shared, no locks)" })
1058
+ ] }),
1059
+ local.locks && local.locks.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-note", children: [
1060
+ local.locks.length,
1061
+ " file lock(s): ",
1062
+ local.locks.slice(0, 4).map((l) => `${l.path} (${l.owner})`).join(", ")
1063
+ ] }) : null,
1064
+ /* @__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", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}), onClick: () => a.openDialog({ kind: "merge", roomId: room.roomId }), children: "Review & merge" }) })
1065
+ ] });
1066
+ }
1067
+ function TransportSection({ room, store }) {
1068
+ const a = store.actions;
1069
+ const [busy, setBusy] = (0, import_react.useState)(null);
1070
+ const cur = room.transport === "local" ? "local" : room.transport === "lan" ? "relay" : "relay";
1071
+ const go = async (target) => {
1072
+ setBusy(target);
1073
+ try {
1074
+ await a.setTransport(room.roomId, target);
1075
+ a.toast(target === "local" ? "Local only \u2014 in-process, no network" : target === "lan" ? "Now on this machine's LAN relay \u2014 invite remote hosts" : "Now on the relay \u2014 invite remote hosts");
1076
+ } catch (e) {
1077
+ a.toast(e.message);
1078
+ } finally {
1079
+ setBusy(null);
1080
+ }
1081
+ };
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-transport": "", children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "Transport" }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy || room.public, className: "rm-mode-btn" + (cur === "local" ? " is-on" : ""), onClick: () => go("local"), title: room.public ? "Public rooms need a relay" : "In-process loopback: no network", children: busy === "local" ? "\u2026" : "Local only" }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy, className: "rm-mode-btn" + (cur === "relay" && room.transport !== "local" ? " is-on" : ""), onClick: () => go("relay"), children: busy === "relay" ? "\u2026" : "Relay (VPS)" }),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy, className: "rm-mode-btn", onClick: () => go("lan"), title: "Embedded LAN relay on this machine (mDNS)", children: busy === "lan" ? "\u2026" : "LAN" })
1088
+ ] }),
1089
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.transport === "local" ? "All members are on this dsh \u2014 nothing leaves the process. Switch to Relay or LAN to invite remote hosts; local agents stay members." : "Local agents connect as separate members over this transport, alongside remote hosts." })
1090
+ ] });
1091
+ }
1092
+ function AgentDialog({ store, d }) {
1093
+ const a = store.actions;
931
1094
  const s = useStore(store);
1095
+ const room = (s.rooms || []).find((r) => r.roomId === d.roomId);
1096
+ const existing = room && room.local ? room.local.agents.find((x) => x.agentId === d.agentId) : null;
1097
+ const [presets, setPresets] = (0, import_react.useState)([]);
1098
+ const [f, setF] = (0, import_react.useState)(() => existing ? { name: existing.name, persona: existing.persona || "", systemPrompt: existing.systemPrompt || "", model: existing.model || "", reasoningEffort: existing.reasoningEffort || "medium", scope: existing.scope, approvalRequired: !!existing.approvalRequired, moderator: !!existing.moderator } : { preset: "", name: "", persona: "", systemPrompt: "", model: "", reasoningEffort: "medium", scope: "read", approvalRequired: false, moderator: false });
1099
+ const [err, setErr] = (0, import_react.useState)(null);
1100
+ const [busy, setBusy] = (0, import_react.useState)(false);
1101
+ (0, import_react.useEffect)(() => {
1102
+ a.presets().then(setPresets).catch(() => {
1103
+ });
1104
+ }, []);
1105
+ const pick = (p) => setF({ ...f, preset: p.key, name: p.name, persona: p.persona, systemPrompt: p.systemPrompt, scope: p.scope, moderator: !!p.moderator, reasoningEffort: p.reasoningEffort || "medium" });
1106
+ const submit = async () => {
1107
+ if (!f.name.trim()) {
1108
+ setErr("Name required");
1109
+ return;
1110
+ }
1111
+ setBusy(true);
1112
+ setErr(null);
1113
+ try {
1114
+ const body = { name: f.name, persona: f.persona, systemPrompt: f.systemPrompt, model: f.model, reasoningEffort: f.reasoningEffort, scope: f.scope, approvalRequired: f.approvalRequired, moderator: f.moderator, preset: f.preset || void 0 };
1115
+ if (existing) await a.updateAgent(d.roomId, existing.agentId, body);
1116
+ else {
1117
+ const r = await a.addAgent(d.roomId, body);
1118
+ a.toast(`${r.name} joined as @${r.agentId}`);
1119
+ }
1120
+ a.closeDialog();
1121
+ } catch (e) {
1122
+ setErr(e.message);
1123
+ setBusy(false);
1124
+ }
1125
+ };
1126
+ const remove = async () => {
1127
+ setBusy(true);
1128
+ try {
1129
+ const r = await a.removeAgent(d.roomId, existing.agentId);
1130
+ a.toast(`Removed ${existing.name}${r.branchKept ? ` \xB7 branch ${r.branchKept} kept` : ""}`);
1131
+ a.closeDialog();
1132
+ } catch (e) {
1133
+ setErr(e.message);
1134
+ setBusy(false);
1135
+ }
1136
+ };
1137
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1138
+ import_dsh_client_ui_primitives.Modal,
1139
+ {
1140
+ open: true,
1141
+ onClose: a.closeDialog,
1142
+ title: existing ? `Edit ${existing.name}` : "Add local agent",
1143
+ closeLabel: "Close",
1144
+ className: "rm-modal-wide",
1145
+ description: existing ? `Member @${existing.agentId} \xB7 own identity and dsh session in this room's workspace.` : "Runs on this dsh as a separate room member: own keypair, own session, own scope. Remote peers see it as a member.",
1146
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1147
+ existing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", className: "rm-danger", disabled: busy, onClick: remove, children: "Remove" }) : null,
1148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: submit, children: busy ? "Saving\u2026" : existing ? "Save" : "Add agent" })
1151
+ ] }),
1152
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form rm-form-wide", "data-rm-agent-form": "", children: [
1153
+ !existing ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2", children: [
1154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Preset" }),
1155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-preset-row", children: presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-preset" + (f.preset === p.key ? " is-on" : ""), onClick: () => pick(p), children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: p.name }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.persona })
1158
+ ] }, p.key)) })
1159
+ ] }) : null,
1160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-ag-name", hint: "Member id is derived from it (e.g. coder.ab12).", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ag-name", value: f.name, maxLength: 40, onChange: (e) => setF({ ...f, name: e.target.value }), placeholder: "e.g. Coder" }) }),
1161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Persona", htmlFor: "rm-ag-persona", hint: "One line other members see.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ag-persona", value: f.persona, maxLength: 200, onChange: (e) => setF({ ...f, persona: e.target.value }), placeholder: "e.g. Implements changes" }) }),
1162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "System prompt", htmlFor: "rm-ag-sp", hint: "Prepended to every wake of this agent.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { id: "rm-ag-sp", className: "rm-input rm-textarea", rows: 4, value: f.systemPrompt, maxLength: 4e3, onChange: (e) => setF({ ...f, systemPrompt: e.target.value }) }) }) }),
1163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Model", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "rm-input", "aria-label": "Model", value: f.model, onChange: (e) => setF({ ...f, model: e.target.value }), children: [
1164
+ MODEL_CHOICES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: m, children: m || "dsh default model" }, m)),
1165
+ f.model && !MODEL_CHOICES.includes(f.model) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: f.model, children: f.model }) : null
1166
+ ] }) }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Reasoning effort", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "rm-input", "aria-label": "Reasoning effort", 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)) }) }),
1168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace scope", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
1169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.scope === "read" ? " is-on" : ""), onClick: () => setF({ ...f, scope: "read" }), children: "Read-only" }),
1170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.scope === "write" ? " is-on" : ""), onClick: () => setF({ ...f, scope: "write" }), children: "Read + write" })
1171
+ ] }) }),
1172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-col", children: [
1173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Approval required" }),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Each run waits for your OK." })
1177
+ ] }),
1178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.approvalRequired, onChange: (v) => setF({ ...f, approvalRequired: v }), label: "Approval required" })
1179
+ ] }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Moderator" }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Splits requests into tasks and summarizes. One per room." })
1184
+ ] }),
1185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.moderator, onChange: (v) => setF({ ...f, moderator: v }), label: "Moderator" })
1186
+ ] })
1187
+ ] }),
1188
+ f.scope === "write" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-note", children: "Writes go to its own git worktree (branch room/<room>/<agent>) when the workspace is a git repo; you merge from \u201CReview & merge\u201D. Non-git workspaces use file locks." }) : null,
1189
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-error", children: err }) : null
1190
+ ] })
1191
+ }
1192
+ );
1193
+ }
1194
+ function DiffView({ patch }) {
1195
+ if (!patch) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No changes." });
1196
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "rm-diff", "data-rm-diff": "", children: patch.split("\n").map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: l.startsWith("+++") || l.startsWith("---") ? "rm-diff-file" : l.startsWith("+") ? "rm-diff-add" : l.startsWith("-") ? "rm-diff-del" : l.startsWith("@@") ? "rm-diff-hunk" : l.startsWith("diff ") ? "rm-diff-head" : "", children: l || " " }, i)) });
1197
+ }
1198
+ function MergeDialog({ store, d }) {
1199
+ const a = store.actions;
1200
+ const [data, setData] = (0, import_react.useState)(null);
1201
+ const [sel, setSel] = (0, import_react.useState)(null);
1202
+ const [confirm, setConfirm] = (0, import_react.useState)(null);
1203
+ const [busy, setBusy] = (0, import_react.useState)(false);
1204
+ const [result, setResult] = (0, import_react.useState)(null);
1205
+ const load = async () => {
1206
+ try {
1207
+ const r = await a.diffs(d.roomId);
1208
+ setData(r);
1209
+ setSel((cur) => cur && r.agents.some((x) => x.agentId === cur) ? cur : (r.agents.find((x) => x.ahead) || r.agents[0] || {}).agentId || null);
1210
+ } catch (e) {
1211
+ setData({ error: e.message, agents: [] });
1212
+ }
1213
+ };
1214
+ (0, import_react.useEffect)(() => {
1215
+ void load();
1216
+ }, [d.roomId]);
1217
+ const ag = data && data.agents.find((x) => x.agentId === sel);
1218
+ const run = async (kind) => {
1219
+ setBusy(true);
1220
+ setResult(null);
1221
+ try {
1222
+ const r = kind === "merge" ? await a.merge(d.roomId, sel) : await a.discard(d.roomId, sel);
1223
+ setResult(kind === "merge" ? r.merged ? { ok: true, text: `Merged into the main workspace (${r.commit}).` } : { ok: false, text: `${r.error || "Not merged"}${r.conflicts && r.conflicts.length ? ": " + r.conflicts.join(", ") : ""}` } : { ok: true, text: r.discarded ? `Discarded ${r.branch}.` : "Nothing to discard." });
1224
+ await load();
1225
+ } catch (e) {
1226
+ setResult({ ok: false, text: e.message });
1227
+ } finally {
1228
+ setBusy(false);
1229
+ setConfirm(null);
1230
+ }
1231
+ };
1232
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1233
+ import_dsh_client_ui_primitives.Modal,
1234
+ {
1235
+ open: true,
1236
+ onClose: a.closeDialog,
1237
+ title: "Review & merge agent changes",
1238
+ closeLabel: "Close",
1239
+ className: "rm-modal-wide rm-modal-xl",
1240
+ description: "Each writing agent works on its own branch. Review the diff, then merge into (or discard from) your main workspace. Nothing is merged without your confirmation.",
1241
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Close" }),
1242
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-root rm-merge", "data-rm-merge": "", children: !data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Loading diffs\u2026" }) : data.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: data.error }) : !data.agents.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No writing agents in this room." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1243
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-list", children: data.agents.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-merge-agent" + (x.agentId === sel ? " is-on" : ""), onClick: () => {
1244
+ setSel(x.agentId);
1245
+ setConfirm(null);
1246
+ setResult(null);
1247
+ }, children: [
1248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: x.name, size: 24 }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-merge-agent-body", children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: x.name }),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: x.branch || (x.isolation === "locks" ? "shared workspace \xB7 locks" : "no worktree yet") })
1252
+ ] }),
1253
+ x.conflicts && x.conflicts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "danger", children: "conflict" }) : x.ahead ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: [
1254
+ x.files.length,
1255
+ " file",
1256
+ x.files.length === 1 ? "" : "s"
1257
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "clean" })
1258
+ ] }, x.agentId)) }),
1259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-main", children: ag ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-merge-head", children: [
1261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: ag.name }),
1263
+ " ",
1264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: ag.branch || "" }),
1265
+ ag.ahead ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-note", style: { margin: "0 0 0 8px" }, children: [
1266
+ ag.ahead,
1267
+ " commit(s) ahead"
1268
+ ] }) : null
1269
+ ] }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-actions", style: { margin: 0 }, children: confirm ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-confirm-text", children: confirm === "merge" ? `Merge ${ag.files.length} file(s) into the main workspace?` : `Discard ${ag.name}'s branch and worktree?` }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => setConfirm(null), children: "Cancel" }),
1273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", disabled: busy, onClick: () => run(confirm), children: busy ? "Working\u2026" : confirm === "merge" ? "Confirm merge" : "Confirm discard" })
1274
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "ghost", className: "rm-danger", disabled: !ag.branch || busy, onClick: () => setConfirm("discard"), children: "Discard" }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}), disabled: !ag.ahead || busy, onClick: () => setConfirm("merge"), children: "Merge into main" })
1277
+ ] }) })
1278
+ ] }),
1279
+ result ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: result.ok ? "rm-ok" : "rm-error", children: result.text }) : null,
1280
+ ag.conflicts && ag.conflicts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-error", children: [
1281
+ "Would conflict with the main workspace: ",
1282
+ ag.conflicts.join(", "),
1283
+ " \u2014 resolve on the branch or discard."
1284
+ ] }) : null,
1285
+ ag.mainDirty && ag.mainDirty.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-error", children: [
1286
+ "Uncommitted changes in the main workspace touch: ",
1287
+ ag.mainDirty.join(", ")
1288
+ ] }) : null,
1289
+ ag.files && ag.files.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-files", children: ag.files.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-merge-file", children: [
1290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: f.path }),
1291
+ " ",
1292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-diff-addn", children: [
1293
+ "+",
1294
+ f.adds
1295
+ ] }),
1296
+ " ",
1297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-diff-deln", children: [
1298
+ "\u2212",
1299
+ f.dels
1300
+ ] })
1301
+ ] }, f.path)) }) : null,
1302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffView, { patch: ag.patch }),
1303
+ ag.truncated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Diff truncated." }) : null
1304
+ ] }) : null })
1305
+ ] }) })
1306
+ }
1307
+ );
1308
+ }
1309
+ function EmptyMain({ store }) {
932
1310
  const a = store.actions;
933
- 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: [
1311
+ const workspaces = useNativeWorkspaces(store);
1312
+ const first = workspaces[0];
1313
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-welcome-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome", children: [
934
1314
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-badge", children: "Collab \xB7 E2EE \xB7 no accounts" }),
935
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Workspaces, then rooms" }),
936
- /* @__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." }),
1315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Rooms live in your workspaces" }),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Your dsh workspaces stay in the left sidebar as usual. Hover a workspace there and click the room icon (next to \u201CNew session\u201D), or use its \u201C\u2026\u201D menu \u2192 New room. Peers join and point their agent at the same project; @mention or tasks wake agents." }),
937
1317
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome-actions", children: [
938
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "workspace" }), children: [
939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
940
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
941
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Bind workspace" }),
942
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Local project path shared by agents in rooms" })
943
- ] })
944
- ] }),
945
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "create" }), children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", disabled: !first, onClick: () => first && a.openDialog({ kind: "create", workspace: { id: first.workspaceId, path: first.path, title: wsTitle(first) } }), children: [
946
1319
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
947
1320
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
948
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Create room" }),
949
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "In the active workspace \xB7 Relay or LAN" })
1321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "New room" }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: first ? `In ${wsTitle(first)} \xB7 default relay 62.234.113.56:80` : "Add a workspace in the left sidebar first" })
950
1323
  ] })
951
1324
  ] }),
952
1325
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "join" }), children: [
953
1326
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
954
1327
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
955
1328
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Join by invite" }),
956
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://join/\u2026 invite" })
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://\u2026 invite" })
957
1330
  ] })
958
1331
  ] }),
959
1332
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "browse" }), children: [
@@ -970,51 +1343,74 @@ function RoomsMain({ store }) {
970
1343
  const s = useStore(store);
971
1344
  const [drafts, setDrafts] = (0, import_react.useState)({});
972
1345
  const room = s.rooms.find((r) => r.roomId === s.selected);
973
- if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store });
1346
+ if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
1347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsNav, { store }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-main-body", children: [
1349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
1350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store })
1351
+ ] })
1352
+ ] });
974
1353
  const messages = s.messages[room.roomId] || [];
975
1354
  const draft = drafts[room.roomId] || "";
976
1355
  const setDraft = (v) => setDrafts((d) => ({ ...d, [room.roomId]: typeof v === "function" ? v(d[room.roomId] || "") : v }));
977
1356
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
978
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
979
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
980
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
981
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
982
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
983
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
984
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
985
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
986
- room.members.filter((m) => m.online !== false).length,
987
- " online \xB7 ",
988
- room.public ? "public" : "private",
989
- room.status !== "online" ? ` \xB7 room ${room.status}` : ""
990
- ] })
991
- ] }),
992
- room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
993
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
994
- "Public"
995
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
996
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
997
- "Private"
1357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsNav, { store }),
1358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-main-body", children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
1360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
1361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
1362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
1363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
1364
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
1367
+ room.members.filter((m) => m.online !== false).length,
1368
+ " online",
1369
+ room.local && room.local.agents.length ? ` \xB7 ${room.local.agents.length} local agent${room.local.agents.length === 1 ? "" : "s"}` : "",
1370
+ " \xB7 ",
1371
+ room.public ? "public" : "private",
1372
+ room.status !== "online" ? ` \xB7 room ${room.status}` : ""
1373
+ ] })
1374
+ ] }),
1375
+ (() => {
1376
+ const act = (room.runs || []).filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
1377
+ if (!act.length) return null;
1378
+ const running = act.filter((r) => r.state === "running").length;
1379
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-stop-btn", "data-rm-stop": "", title: "Stop all local agent runs", onClick: () => store.actions.stop(room.roomId).then((r) => store.actions.toast(`Stopped ${r.stopped} run(s)`)).catch((e) => store.actions.toast(e.message)), children: [
1380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}),
1381
+ "Stop \xB7 ",
1382
+ running,
1383
+ " running",
1384
+ act.length > running ? `, ${act.length - running} queued` : ""
1385
+ ] });
1386
+ })(),
1387
+ room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
1388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1389
+ "Public"
1390
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
1391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
1392
+ "Private"
1393
+ ] }),
1394
+ room.transport === "local" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge rm-badge-local", children: "Local only" }) : room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
1395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
998
1396
  ] }),
999
- room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
1000
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
1397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages, members: room.members }),
1398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft, setDraft })
1001
1399
  ] }),
1002
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages }),
1003
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft, setDraft })
1004
- ] }),
1005
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1006
- RoomDetail,
1007
- {
1008
- room,
1009
- messages,
1010
- store,
1011
- mention: (id) => setDraft((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
1012
- assign: (id) => {
1013
- setDraft(`@${id} `);
1014
- store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
1400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1401
+ RoomDetail,
1402
+ {
1403
+ room,
1404
+ messages,
1405
+ store,
1406
+ mention: (id) => setDraft((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
1407
+ assign: (id) => {
1408
+ setDraft(`@${id} `);
1409
+ store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
1410
+ }
1015
1411
  }
1016
- }
1017
- )
1412
+ )
1413
+ ] })
1018
1414
  ] })
1019
1415
  ] });
1020
1416
  }
@@ -1025,17 +1421,21 @@ function Field({ label, hint, children, htmlFor }) {
1025
1421
  hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: hint }) : null
1026
1422
  ] });
1027
1423
  }
1028
- function CreateDialog({ store }) {
1424
+ function CreateDialog({ store, d }) {
1029
1425
  const s = useStore(store);
1030
1426
  const a = store.actions;
1031
- const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80" });
1427
+ const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80", localOnly: false, agents: [] });
1032
1428
  const [err, setErr] = (0, import_react.useState)(null);
1033
1429
  const [busy, setBusy] = (0, import_react.useState)(false);
1034
1430
  const [lanPeers, setLanPeers] = (0, import_react.useState)([]);
1035
1431
  const ok = f.name.trim() && !busy;
1036
- const ws = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0];
1432
+ const [ws, setWs] = (0, import_react.useState)(d && d.workspace ? d.workspace : null);
1037
1433
  const submit = async () => {
1038
1434
  if (!ok) return;
1435
+ if (!ws) {
1436
+ setErr("Pick a workspace");
1437
+ return;
1438
+ }
1039
1439
  setBusy(true);
1040
1440
  setErr(null);
1041
1441
  try {
@@ -1045,7 +1445,11 @@ function CreateDialog({ store }) {
1045
1445
  tags: f.tags,
1046
1446
  public: f.public,
1047
1447
  mode: f.mode,
1048
- lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0
1448
+ lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0,
1449
+ workspacePath: ws.path,
1450
+ workspaceName: ws.title,
1451
+ localOnly: f.localOnly,
1452
+ agents: f.agents
1049
1453
  });
1050
1454
  } catch (e) {
1051
1455
  setErr(e.message);
@@ -1068,12 +1472,13 @@ function CreateDialog({ store }) {
1068
1472
  title: "Create room",
1069
1473
  closeLabel: "Close",
1070
1474
  className: "rm-modal-wide",
1071
- description: ws ? `In workspace \u201C${ws.name}\u201D. Default relay 62.234.113.56:80 \xB7 E2EE \xB7 keys stay local.` : "Bind a workspace first. Default relay 62.234.113.56:80.",
1475
+ description: ws ? `In workspace \u201C${ws.title}\u201D (${ws.path}). Agent cwd + MCP sandbox = this path. Default relay 62.234.113.56:80 \xB7 E2EE.` : "Pick a dsh workspace. Default relay 62.234.113.56:80 \xB7 E2EE.",
1072
1476
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1073
1477
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1074
1478
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !ok, onClick: submit, children: busy ? "Creating\u2026" : "Create" })
1075
1479
  ] }),
1076
1480
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form rm-form-wide", onKeyDown: (e) => e.key === "Enter" && e.target.tagName === "INPUT" && submit(), children: [
1481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace", htmlFor: "rm-create-ws", hint: "Native dsh workspace this room belongs to.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, id: "rm-create-ws", value: ws, onChange: setWs }) }),
1077
1482
  /* @__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: [
1078
1483
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: f.name || "room", size: 36 }),
1079
1484
  /* @__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 }) })
@@ -1094,12 +1499,29 @@ function CreateDialog({ store }) {
1094
1499
  ] }, p.hostPort))
1095
1500
  ] })
1096
1501
  ] }) }) : null,
1502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2 rm-local-box", children: [
1503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-switch-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Local agents" }),
1505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Host several agents from this dsh as separate members (own identity, session, scope). Edit them later in room details." })
1506
+ ] }) }),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-preset-row", children: [["moderator", "Moderator"], ["architect", "Architect"], ["coder", "Coder"], ["reviewer", "Reviewer"]].map(([k, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-preset rm-preset-check" + (f.agents.includes(k) ? " is-on" : ""), children: [
1508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: f.agents.includes(k), onChange: (e) => setF({ ...f, agents: e.target.checked ? [...f.agents, k] : f.agents.filter((x) => x !== k) }) }),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: label })
1510
+ ] }, k)) }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
1512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Local only (no network)" }),
1514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "In-process loopback \u2014 nothing leaves this machine. One click to switch to Relay/LAN later." })
1515
+ ] }),
1516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.localOnly, onChange: (v) => setF({ ...f, localOnly: v, public: v ? false : f.public }), label: "Local only" })
1517
+ ] })
1518
+ ] }),
1097
1519
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2 rm-switch-row", children: [
1098
1520
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1099
1521
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "List in public directory" }),
1100
1522
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: f.public ? "Anyone can find and join \u2014 key is published with the listing." : "Private: invite only." })
1101
1523
  ] }),
1102
- /* @__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" })
1524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.public, onChange: (v) => setF({ ...f, public: v, localOnly: v ? false : f.localOnly }), label: "List in public directory" })
1103
1525
  ] }),
1104
1526
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-error", children: err }) : null
1105
1527
  ] })
@@ -1113,12 +1535,13 @@ function JoinDialog({ store }) {
1113
1535
  const [err, setErr] = (0, import_react.useState)(null);
1114
1536
  const [busy, setBusy] = (0, import_react.useState)(false);
1115
1537
  const valid = /^collab:\/\/(join|lan|relay)\/.+#\S+$/.test(invite.trim());
1538
+ const [ws, setWs] = (0, import_react.useState)(null);
1116
1539
  const submit = async () => {
1117
1540
  if (!valid || busy) return;
1118
1541
  setBusy(true);
1119
1542
  setErr(null);
1120
1543
  try {
1121
- await a.join(invite.trim());
1544
+ await a.join(invite.trim(), ws ? { workspacePath: ws.path, workspaceName: ws.title } : void 0);
1122
1545
  } catch (e) {
1123
1546
  setErr(e.message);
1124
1547
  setBusy(false);
@@ -1131,12 +1554,13 @@ function JoinDialog({ store }) {
1131
1554
  onClose: a.closeDialog,
1132
1555
  title: "Join room",
1133
1556
  closeLabel: "Close",
1134
- 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.",
1557
+ description: "Paste an invite and pick the dsh workspace (your checkout of the same project) the room attaches to.",
1135
1558
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1136
1559
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1137
1560
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !valid || busy, onClick: submit, children: busy ? "Joining\u2026" : "Join" })
1138
1561
  ] }),
1139
1562
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, value: ws, onChange: setWs }),
1140
1564
  /* @__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() }),
1141
1565
  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,
1142
1566
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
@@ -1326,9 +1750,10 @@ function WorkspaceDialog({ store }) {
1326
1750
  setBusy(true);
1327
1751
  setErr(null);
1328
1752
  try {
1329
- await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
1753
+ if (store.native && store.native.create) await store.native.create({ path: path.trim() });
1754
+ else await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
1330
1755
  a.closeDialog();
1331
- a.toast("Workspace ready \u2014 create a room to collaborate");
1756
+ a.toast("Workspace added \u2014 it appears in the left sidebar; use New room on it");
1332
1757
  } catch (e) {
1333
1758
  setErr(e.message);
1334
1759
  setBusy(false);
@@ -1339,18 +1764,14 @@ function WorkspaceDialog({ store }) {
1339
1764
  {
1340
1765
  open: true,
1341
1766
  onClose: a.closeDialog,
1342
- title: "Workspace",
1767
+ title: "Add workspace",
1343
1768
  closeLabel: "Close",
1344
- 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.",
1769
+ description: "Fallback: registers a dsh workspace (same as the native \u201CAdd workspace\u201D). It shows up in the left sidebar.",
1345
1770
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1346
1771
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
1347
1772
  /* @__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" })
1348
1773
  ] }),
1349
1774
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
1350
- (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 () => {
1351
- await a.setActiveWorkspace(w.id);
1352
- a.closeDialog();
1353
- }, children: w.name }, w.id)) }) }) : null,
1354
1775
  /* @__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) }) }),
1355
1776
  /* @__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() }) }),
1356
1777
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
@@ -1363,7 +1784,7 @@ function BrowseDialog({ store }) {
1363
1784
  const a = store.actions;
1364
1785
  const [joining, setJoining] = (0, import_react.useState)(null);
1365
1786
  const inputRef = (0, import_react.useRef)(null);
1366
- const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0];
1787
+ const [activeWs, setActiveWs] = (0, import_react.useState)(null);
1367
1788
  (0, import_react.useEffect)(() => {
1368
1789
  a.setSearchOpen(true);
1369
1790
  requestAnimationFrame(() => inputRef.current?.focus());
@@ -1373,7 +1794,7 @@ function BrowseDialog({ store }) {
1373
1794
  const join = async (r) => {
1374
1795
  setJoining(r.roomId);
1375
1796
  try {
1376
- await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
1797
+ await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspacePath: activeWs?.path, workspaceName: activeWs?.title });
1377
1798
  } catch (e) {
1378
1799
  a.toast(e.message);
1379
1800
  } finally {
@@ -1388,9 +1809,10 @@ function BrowseDialog({ store }) {
1388
1809
  title: "Browse public rooms",
1389
1810
  closeLabel: "Close",
1390
1811
  className: "rm-modal-wide",
1391
- description: "Directory on VPS 62.234.113.56:80. Search by name or #tag, then join into the active workspace.",
1812
+ description: "Directory on VPS 62.234.113.56:80. Search by name or #tag; joined rooms attach to the workspace below.",
1392
1813
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Close" }),
1393
1814
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-browse", children: [
1815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, value: activeWs, onChange: setActiveWs }),
1394
1816
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search rm-browse-search", children: [
1395
1817
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
1396
1818
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1434,13 +1856,15 @@ function Overlay({ store }) {
1434
1856
  const s = useStore(store);
1435
1857
  const d = s.dialog;
1436
1858
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1437
- d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store }) : null,
1859
+ d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store, d }) : null,
1438
1860
  d?.kind === "join" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JoinDialog, { store }) : null,
1439
1861
  d?.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InviteDialog, { store, d }) : null,
1440
1862
  d?.kind === "leave" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LeaveDialog, { store, d }) : null,
1441
1863
  d?.kind === "config" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigDialog, { store, d }) : null,
1442
1864
  d?.kind === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceDialog, { store }) : null,
1443
1865
  d?.kind === "browse" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BrowseDialog, { store }) : null,
1866
+ d?.kind === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AgentDialog, { store, d }) : null,
1867
+ d?.kind === "merge" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeDialog, { store, d }) : null,
1444
1868
  s.toast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: s.toast, onDone: store.actions.clearToast, holdMs: 2200 }) : null
1445
1869
  ] });
1446
1870
  }
@@ -1453,8 +1877,118 @@ function installStyles() {
1453
1877
  document.head.appendChild(style);
1454
1878
  return () => style.remove();
1455
1879
  }
1880
+ var ROOM_SVG = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.15" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="3" width="11" height="10" rx="2"/><path d="M5.5 6.5h5M5.5 9.5h3.5"/></svg>';
1881
+ function installWorkspaceDecorator(store, native, onNewRoom) {
1882
+ if (typeof document === "undefined" || !native) return () => {
1883
+ };
1884
+ const wsFor = (key) => (native.list.getSnapshot().items || []).find((w) => w.workspaceId === key);
1885
+ const rowKey = (row) => (row.getAttribute("data-row-key") || "").slice("workspace:".length);
1886
+ const open = (key) => {
1887
+ const w = wsFor(key);
1888
+ if (w) onNewRoom({ id: w.workspaceId, path: w.path, title: wsTitle(w) });
1889
+ };
1890
+ const decorate = () => {
1891
+ for (const row of document.querySelectorAll('[data-row-key^="workspace:"]')) {
1892
+ const key = rowKey(row);
1893
+ if (!key || row.querySelector("[data-rm-ws-new-room]")) continue;
1894
+ const newSess = row.querySelector('button[aria-label^="New session in"]');
1895
+ if (!newSess) continue;
1896
+ let anchor = newSess;
1897
+ while (anchor.parentElement && anchor.parentElement !== row && !anchor.parentElement.querySelector('button[aria-label^="Workspace actions"]')) anchor = anchor.parentElement;
1898
+ if (!anchor.parentElement) continue;
1899
+ const label = (newSess.getAttribute("aria-label") || "").replace(/^New session in /, "");
1900
+ const btn = document.createElement("button");
1901
+ btn.type = "button";
1902
+ btn.className = newSess.className;
1903
+ btn.setAttribute("data-rm-ws-new-room", key);
1904
+ btn.setAttribute("aria-label", `New room in ${label}`);
1905
+ btn.title = "New room";
1906
+ btn.innerHTML = ROOM_SVG;
1907
+ btn.addEventListener("click", (e) => {
1908
+ e.preventDefault();
1909
+ e.stopPropagation();
1910
+ open(key);
1911
+ });
1912
+ anchor.parentElement.insertBefore(btn, anchor);
1913
+ }
1914
+ };
1915
+ let pendingKey = null;
1916
+ const onClickCapture = (e) => {
1917
+ const trigger = e.target instanceof Element ? e.target.closest('button[aria-label^="Workspace actions for"]') : null;
1918
+ if (!trigger) return;
1919
+ const row = trigger.closest('[data-row-key^="workspace:"]');
1920
+ pendingKey = row ? rowKey(row) : null;
1921
+ if (!pendingKey) return;
1922
+ const key = pendingKey;
1923
+ let tries = 0;
1924
+ const augment = () => {
1925
+ const menus = [...document.querySelectorAll('[role="menu"]')].filter((m) => m.querySelector('[role="menuitem"]'));
1926
+ const menu = menus[menus.length - 1];
1927
+ if (!menu) {
1928
+ if (++tries < 10) requestAnimationFrame(augment);
1929
+ return;
1930
+ }
1931
+ if (menu.querySelector("[data-rm-ws-menu]")) return;
1932
+ const tpl = menu.querySelector('[role="menuitem"]');
1933
+ const mk = (text, svg, onPick) => {
1934
+ const item = document.createElement("button");
1935
+ item.type = "button";
1936
+ item.setAttribute("role", "menuitem");
1937
+ item.setAttribute("data-rm-ws-menu", text);
1938
+ item.className = tpl.className;
1939
+ item.innerHTML = `<span style="display:inline-flex;width:16px;justify-content:center">${svg}</span><span>${text}</span>`;
1940
+ item.style.display = "flex";
1941
+ item.style.alignItems = "center";
1942
+ item.style.gap = "8px";
1943
+ item.addEventListener("click", (ev) => {
1944
+ ev.preventDefault();
1945
+ ev.stopPropagation();
1946
+ trigger.click();
1947
+ onPick();
1948
+ });
1949
+ return item;
1950
+ };
1951
+ const newSessSvg = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.15" stroke-linecap="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8h9"/></svg>';
1952
+ const first = menu.firstElementChild;
1953
+ menu.insertBefore(mk("New session", newSessSvg, () => {
1954
+ const row2 = document.querySelector(`[data-row-key="workspace:${CSS.escape(key)}"]`);
1955
+ row2?.querySelector('button[aria-label^="New session in"]')?.click();
1956
+ }), first);
1957
+ menu.insertBefore(mk("New room", ROOM_SVG, () => open(key)), first);
1958
+ };
1959
+ requestAnimationFrame(augment);
1960
+ };
1961
+ let raf = 0;
1962
+ const schedule = () => {
1963
+ if (!raf) raf = requestAnimationFrame(() => {
1964
+ raf = 0;
1965
+ decorate();
1966
+ });
1967
+ };
1968
+ const mo = new MutationObserver(schedule);
1969
+ mo.observe(document.body, { childList: true, subtree: true });
1970
+ document.addEventListener("click", onClickCapture, true);
1971
+ schedule();
1972
+ return () => {
1973
+ mo.disconnect();
1974
+ document.removeEventListener("click", onClickCapture, true);
1975
+ if (raf) cancelAnimationFrame(raf);
1976
+ for (const b of document.querySelectorAll("[data-rm-ws-new-room]")) b.remove();
1977
+ };
1978
+ }
1456
1979
  function apply(ctx) {
1457
1980
  const store = createRoomsStore();
1981
+ let native = null;
1982
+ try {
1983
+ native = ctx.get ? ctx.get("workspaces") : ctx.workspaces;
1984
+ } catch {
1985
+ native = ctx.workspaces || null;
1986
+ }
1987
+ store.native = native || null;
1988
+ store.nativeList = native && native.list ? {
1989
+ subscribe: (listener) => native.list.subscribe(listener),
1990
+ getSnapshot: () => native.list.getSnapshot()
1991
+ } : null;
1458
1992
  ctx.effect(() => installStyles(), "dsh-room: styles");
1459
1993
  ctx.effect(() => () => store.dispose(), "dsh-room: store");
1460
1994
  const info = () => ctx.layout.panelInfo;
@@ -1464,27 +1998,12 @@ function apply(ctx) {
1464
1998
  } catch {
1465
1999
  }
1466
2000
  };
2001
+ store.onRoomOpened = ensurePanel;
1467
2002
  ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: PANEL_ID, order: 11, label: () => "DSH Room" }, PanelIcon));
1468
2003
  ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, inject: () => ({ store }) }, RoomsMain));
1469
2004
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: "dsh-room-dialogs", inject: () => ({ store }) }, Overlay));
1470
- ctx.slots.inject("sidebar.workspaces", () => {
1471
- let dispose;
1472
- const reconcile = () => {
1473
- const active = info().getSnapshot().activePanelId === PANEL_ID;
1474
- if (active && !dispose) dispose = ctx.slots.register({ name: "sidebar.workspaces", priority: -100, inject: () => ({ store, ensurePanel }) }, RoomsSidebar);
1475
- else if (!active && dispose) {
1476
- dispose();
1477
- dispose = void 0;
1478
- }
1479
- };
1480
- const off = info().subscribe(reconcile);
1481
- reconcile();
1482
- return () => {
1483
- off();
1484
- if (dispose) dispose();
1485
- dispose = void 0;
1486
- };
1487
- });
2005
+ ctx.slots.inject("sidebar.session.row.leading", () => ctx.slots.register({ name: "sidebar.session.row.leading", id: "dsh-room-session-mark", inject: () => ({ store }) }, RoomSessionMark));
2006
+ ctx.effect(() => installWorkspaceDecorator(store, native, (workspace) => store.actions.openDialog({ kind: "create", workspace })), "dsh-room: workspace New room");
1488
2007
  }
1489
2008
 
1490
2009
  return module.exports;
@@ -1528,7 +2047,7 @@ var import_react = require("react");
1528
2047
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1529
2048
 
1530
2049
  // src/client/rooms.css
1531
- 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\n\n/* New Room shortcut under workspace (mirrors New Session affordance) */\n.rm-new-room {\n display: flex; align-items: center; justify-content: center; gap: 6px;\n width: 100%; height: 36px; margin-top: 8px;\n border: .5px solid var(--rm-border); border-radius: 10px;\n background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n color: var(--rm-label); cursor: pointer; font-weight: 500; font-size: 13.5px;\n}\n.rm-new-room:hover { background: var(--dsw-alias-button-floating-hover, var(--rm-hover)); }\n.rm-new-room svg { flex: none; }\n\n/* Wide / landscape create + browse modals (overrides default 380px Modal card) */\n.rm-modal-wide {\n width: min(720px, calc(100vw - 48px)) !important;\n max-height: min(640px, calc(100vh - 48px));\n}\n.rm-form-wide {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n align-items: start;\n}\n.rm-form-wide .rm-span-2 { grid-column: 1 / -1; }\n.rm-browse { display: flex; flex-direction: column; gap: 12px; min-height: 280px; max-height: 420px; }\n.rm-browse-search { flex: none; }\n.rm-browse-status { font-size: 11.5px; color: var(--rm-quiet); white-space: nowrap; }\n.rm-browse-list { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 6px; }\n@media (max-width: 640px) {\n .rm-form-wide { grid-template-columns: 1fr; }\n .rm-modal-wide { width: min(420px, calc(100vw - 32px)) !important; }\n}\n';
2050
+ 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\n\n/* New Room shortcut under workspace (mirrors New Session affordance) */\n.rm-new-room {\n display: flex; align-items: center; justify-content: center; gap: 6px;\n width: 100%; height: 36px; margin-top: 8px;\n border: .5px solid var(--rm-border); border-radius: 10px;\n background: var(--dsw-alias-button-elevated-fill, var(--rm-panel));\n color: var(--rm-label); cursor: pointer; font-weight: 500; font-size: 13.5px;\n}\n.rm-new-room:hover { background: var(--dsw-alias-button-floating-hover, var(--rm-hover)); }\n.rm-new-room svg { flex: none; }\n\n/* Wide / landscape create + browse modals (overrides default 380px Modal card) */\n.rm-modal-wide {\n width: min(720px, calc(100vw - 48px)) !important;\n max-height: min(640px, calc(100vh - 48px));\n}\n.rm-form-wide {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n align-items: start;\n}\n.rm-form-wide .rm-span-2 { grid-column: 1 / -1; }\n.rm-browse { display: flex; flex-direction: column; gap: 12px; min-height: 280px; max-height: 420px; }\n.rm-browse-search { flex: none; }\n.rm-browse-status { font-size: 11.5px; color: var(--rm-quiet); white-space: nowrap; }\n.rm-browse-list { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 6px; }\n@media (max-width: 640px) {\n .rm-form-wide { grid-template-columns: 1fr; }\n .rm-modal-wide { width: min(420px, calc(100vw - 32px)) !important; }\n}\n\n\n/* \u2500\u2500 panel layout: rooms nav (grouped by native dsh workspace) + content \u2500\u2500 */\n.rm-main { flex-direction: row; }\n.rm-main-body { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }\n.rm-main-body > .rm-welcome-wrap { flex: 1; }\n.rm-main-body > .rm-workspace { flex: 1; }\n.rm-nav {\n flex: 0 0 248px; width: 248px; display: flex; flex-direction: column; gap: 8px; min-height: 0;\n padding: 10px 8px; border-right: .5px solid var(--rm-border); background: var(--rm-bg);\n}\n.rm-nav-head { display: flex; align-items: center; gap: 4px; padding: 0 4px; }\n.rm-nav-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 10px; }\n.rm-ws-group { display: flex; flex-direction: column; gap: 2px; }\n.rm-ws-head {\n display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 2px 0 8px;\n font-size: 12.5px; font-weight: 600; color: var(--rm-muted);\n}\n.rm-ws-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.rm-ws-add { width: 24px; height: 24px; }\n.rm-ws-empty { padding: 2px 10px 6px 30px; font-size: 11.5px; }\n.rm-nav .rm-room-item { padding: 6px 8px 6px 14px; }\n.rm-session-mark { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; color: var(--rm-accent, var(--dsw-alias-state-business-primary)); }\n\n/* \u2500\u2500 local multi-agent (0.3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-dot-busy { background: var(--dsw-alias-state-warning-primary, #f0a020); }\n.rm-agent-tag { display: inline-flex; align-items: center; gap: 3px; margin-left: 6px; padding: 0 6px; border-radius: 999px; font-size: 10.5px; line-height: 17px; color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); white-space: nowrap; }\n.rm-msg-id { margin-left: 6px; font-size: 11px; color: var(--rm-quiet); font-family: var(--dsw-font-mono, ui-monospace, monospace); }\n.rm-agent-row { display: flex; align-items: center; gap: 8px; padding: 6px 4px; border-radius: 8px; }\n.rm-agent-row:hover { background: var(--rm-hover); }\n.rm-agent-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }\n.rm-agent-name { font-size: 13px; font-weight: 600; color: var(--rm-label); display: flex; align-items: center; }\n.rm-agent-sub { font-size: 11px; color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.rm-agent-row .rm-member-actions { display: inline-flex !important; opacity: 0.65; }\n.rm-agent-row:hover .rm-member-actions { opacity: 1; }\n.rm-num-row { display: flex; gap: 8px; margin: 4px 0 8px; }\n.rm-num-setting { flex: 1; display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--rm-muted); }\n.rm-num-setting input { width: 100%; box-sizing: border-box; padding: 5px 8px; border: 1px solid var(--rm-border); border-radius: 7px; background: var(--rm-bg); color: var(--rm-label); font: inherit; font-size: 13px; }\n.rm-runs { display: flex; flex-direction: column; gap: 4px; }\n.rm-run { display: grid; grid-template-columns: 22px auto 1fr auto; align-items: center; gap: 6px; padding: 5px 6px; border-radius: 7px; font-size: 12px; border: 1px solid var(--rm-border); }\n.rm-run-running { border-color: color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f0a020) 55%, transparent); background: color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f0a020) 8%, transparent); }\n.rm-run-queued, .rm-run-approval { border-style: dashed; }\n.rm-run-done, .rm-run-stopped, .rm-run-failed { opacity: 0.7; }\n.rm-run-pos { font-size: 11px; color: var(--rm-quiet); text-align: center; font-variant-numeric: tabular-nums; }\n.rm-run-who { font-weight: 600; color: var(--rm-label); white-space: nowrap; }\n.rm-run-what { color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }\n.rm-stop-btn { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 60%, transparent); background: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 10%, transparent); color: var(--dsw-alias-state-danger-primary, #e5484d); font-size: 12px; cursor: pointer; white-space: nowrap; }\n.rm-stop-btn:hover { background: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #e5484d) 18%, transparent); }\n.rm-badge-local { color: var(--dsw-alias-state-success-primary, #2bb673); }\n.rm-session-agent { color: var(--rm-accent, #5b8cff); }\n.rm-preset-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }\n.rm-preset { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 150px; flex: 1; padding: 8px 10px; border: 1px solid var(--rm-border); border-radius: 10px; background: transparent; color: var(--rm-label); text-align: left; cursor: pointer; font: inherit; font-size: 12px; }\n.rm-preset span { color: var(--rm-muted); font-size: 11px; line-height: 1.35; }\n.rm-preset.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 12%, transparent); }\n.rm-preset-check { flex-direction: row; align-items: center; gap: 8px; min-width: 120px; flex: 0 1 auto; }\n.rm-local-box { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border: 1px dashed var(--rm-border); border-radius: 12px; }\n.rm-textarea { width: 100%; box-sizing: border-box; min-height: 84px; resize: vertical; font: inherit; font-size: 13px; padding: 8px 10px; }\n.rm-switch-col { display: flex; flex-direction: column; gap: 6px; }\n.rm-modal-xl { width: min(1180px, 96vw) !important; max-width: 96vw !important; }\n.rm-merge { display: grid; grid-template-columns: 260px 1fr; gap: 14px; min-height: 420px; }\n.rm-merge-list { display: flex; flex-direction: column; gap: 6px; border-right: 1px solid var(--rm-border); padding-right: 12px; }\n.rm-merge-agent { display: flex; align-items: center; gap: 8px; padding: 8px; border: 1px solid transparent; border-radius: 10px; background: transparent; color: var(--rm-label); cursor: pointer; text-align: left; font: inherit; }\n.rm-merge-agent:hover { background: var(--rm-hover); }\n.rm-merge-agent.is-on { border-color: var(--rm-accent, #5b8cff); background: color-mix(in srgb, var(--rm-accent, #5b8cff) 10%, transparent); }\n.rm-merge-agent-body { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: 12px; }\n.rm-merge-agent-body .rm-mono { font-size: 10.5px; color: var(--rm-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.rm-merge-main { min-width: 0; display: flex; flex-direction: column; gap: 8px; }\n.rm-merge-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; font-size: 13px; }\n.rm-merge-files { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; }\n.rm-confirm-text { font-size: 12px; color: var(--rm-label); margin-right: 4px; }\n.rm-diff { margin: 0; max-height: 52vh; overflow: auto; padding: 8px 0; border: 1px solid var(--rm-border); border-radius: 10px; background: var(--rm-panel); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 12px; line-height: 1.5; }\n.rm-diff > div { padding: 0 12px; white-space: pre; }\n.rm-diff-add { background: color-mix(in srgb, #2bb673 16%, transparent); }\n.rm-diff-del { background: color-mix(in srgb, #e5484d 16%, transparent); }\n.rm-diff-hunk { color: var(--rm-accent, #5b8cff); }\n.rm-diff-head, .rm-diff-file { color: var(--rm-muted); font-weight: 600; }\n.rm-diff-addn { color: #2bb673; }\n.rm-diff-deln { color: #e5484d; }\n.rm-ok { padding: 8px 10px; border-radius: 8px; font-size: 12.5px; color: var(--dsw-alias-state-success-primary, #2bb673); background: color-mix(in srgb, #2bb673 10%, transparent); }\n';
1532
2051
 
1533
2052
  // src/client/store.js
1534
2053
  var API = "/api/collab";
@@ -1644,13 +2163,21 @@ function createRoomsStore() {
1644
2163
  searchTimer = setTimeout(() => void runSearch(query), 300);
1645
2164
  },
1646
2165
  async create(form) {
1647
- const r = await post("/rooms", { ...form, workspaceId: form.workspaceId || snap.activeWorkspaceId });
2166
+ const r = await post("/rooms", { ...form, workspaceId: form.workspacePath ? void 0 : form.workspaceId || snap.activeWorkspaceId });
2167
+ try {
2168
+ api.onRoomOpened && api.onRoomOpened();
2169
+ } catch {
2170
+ }
1648
2171
  set({ selected: r.roomId, dialog: { kind: "invite", roomId: r.roomId, name: r.name, invite: r.invite, public: r.public, listed: r.listed } });
1649
2172
  void loadMessages(r.roomId);
1650
2173
  return r;
1651
2174
  },
1652
2175
  async join(invite, info) {
1653
- const r = await post("/join", { invite, workspaceId: snap.activeWorkspaceId, ...info || {} });
2176
+ const r = await post("/join", { invite, ...info && info.workspacePath ? {} : { workspaceId: snap.activeWorkspaceId }, ...info || {} });
2177
+ try {
2178
+ api.onRoomOpened && api.onRoomOpened();
2179
+ } catch {
2180
+ }
1654
2181
  set((s) => ({ selected: r.roomId, dialog: null, search: { ...s.search, results: s.search.results.map((x) => x.roomId === r.roomId ? { ...x, joined: true } : x) } }));
1655
2182
  void loadMessages(r.roomId);
1656
2183
  toast(`Joined ${r.name}`);
@@ -1698,9 +2225,40 @@ function createRoomsStore() {
1698
2225
  },
1699
2226
  async mcp(roomId, tool, args, target) {
1700
2227
  return post(`/rooms/${encodeURIComponent(roomId)}/mcp`, { tool, args, target });
2228
+ },
2229
+ // ── local multi-agent ──
2230
+ async presets() {
2231
+ return (await call("/presets")).presets;
2232
+ },
2233
+ async addAgent(roomId, body) {
2234
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents`, body);
2235
+ },
2236
+ async updateAgent(roomId, id, body) {
2237
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents/${encodeURIComponent(id)}`, body);
2238
+ },
2239
+ async removeAgent(roomId, id) {
2240
+ return post(`/rooms/${encodeURIComponent(roomId)}/agents/${encodeURIComponent(id)}?op=delete`);
2241
+ },
2242
+ async saveLocal(roomId, patch) {
2243
+ return post(`/rooms/${encodeURIComponent(roomId)}/local`, patch);
2244
+ },
2245
+ async setTransport(roomId, target, force) {
2246
+ return post(`/rooms/${encodeURIComponent(roomId)}/transport`, { target, force: !!force });
2247
+ },
2248
+ async stop(roomId, agentId) {
2249
+ return post(`/rooms/${encodeURIComponent(roomId)}/stop`, agentId ? { agentId } : {});
2250
+ },
2251
+ async diffs(roomId) {
2252
+ return call(`/rooms/${encodeURIComponent(roomId)}/diffs`);
2253
+ },
2254
+ async merge(roomId, agentId) {
2255
+ return post(`/rooms/${encodeURIComponent(roomId)}/merge`, { agentId, approve: true });
2256
+ },
2257
+ async discard(roomId, agentId) {
2258
+ return post(`/rooms/${encodeURIComponent(roomId)}/discard`, { agentId, approve: true });
1701
2259
  }
1702
2260
  };
1703
- return {
2261
+ const api = {
1704
2262
  getSnapshot: () => snap,
1705
2263
  subscribe(fn) {
1706
2264
  subs.add(fn);
@@ -1713,14 +2271,20 @@ function createRoomsStore() {
1713
2271
  es = null;
1714
2272
  subs.clear();
1715
2273
  clearTimeout(searchTimer);
1716
- }
2274
+ },
2275
+ /** Native dsh workspaces service (set by apply). */
2276
+ native: null,
2277
+ nativeList: null,
2278
+ /** Called after a room is created/joined (apply switches to the DSH Room panel). */
2279
+ onRoomOpened: null
1717
2280
  };
2281
+ return api;
1718
2282
  }
1719
2283
 
1720
2284
  // src/client/index.jsx
1721
2285
  var import_jsx_runtime = require("react/jsx-runtime");
1722
2286
  var name = "dsh-room-client";
1723
- var inject = ["slots", "layout"];
2287
+ var inject = ["slots", "layout", "workspaces"];
1724
2288
  var PANEL_ID = "collab";
1725
2289
  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 });
1726
2290
  var RoomsIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
@@ -1733,7 +2297,6 @@ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { child
1733
2297
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.2 10.2 3.3 3.3" })
1734
2298
  ] });
1735
2299
  var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" }) });
1736
- 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" }) });
1737
2300
  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" }) });
1738
2301
  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" }) });
1739
2302
  var TaskIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
@@ -1748,6 +2311,17 @@ var LockIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 1
1748
2311
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3.5", y: "7", width: "9", height: "6.25", rx: "1.5" }),
1749
2312
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 7V5.25a2.5 2.5 0 0 1 5 0V7" })
1750
2313
  ] });
2314
+ var BotIcon = ({ size = 14 }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size, children: [
2315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "5", width: "10", height: "7.5", rx: "2" }),
2316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2.5V5M6 8.5h.01M10 8.5h.01M6.25 10.75h3.5" })
2317
+ ] });
2318
+ var StopIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Svg, { size: 12, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5" }) });
2319
+ var MergeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
2320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5", cy: "3.75", r: "1.5" }),
2321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5", cy: "12.25", r: "1.5" }),
2322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "8", r: "1.5" }),
2323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 5.25v5.5M5 5.5c0 2 1.5 2.5 4.5 2.5" })
2324
+ ] });
1751
2325
  var PeopleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { size: 14, children: [
1752
2326
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "5.5", r: "2.25" }),
1753
2327
  /* @__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" }),
@@ -1778,220 +2352,133 @@ var STATUS_TONE = { open: "info", claimed: "warning", done: "success", failed: "
1778
2352
  function useStore(store) {
1779
2353
  return (0, import_react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
1780
2354
  }
1781
- function renderMentions(text) {
1782
- 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);
1783
- }
1784
- function PanelIcon({ size }) {
1785
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
2355
+ var EMPTY_WS = { items: [] };
2356
+ var NO_SUB = () => () => {
2357
+ };
2358
+ var NO_SNAP = () => EMPTY_WS;
2359
+ function useNativeWorkspaces(store) {
2360
+ const src = store.nativeList;
2361
+ const snap = (0, import_react.useSyncExternalStore)(src ? src.subscribe : NO_SUB, src ? src.getSnapshot : NO_SNAP);
2362
+ return snap && snap.items || [];
2363
+ }
2364
+ var samePath = (a, b) => !!a && !!b && a.replace(/[\\/]+$/, "") === b.replace(/[\\/]+$/, "");
2365
+ function groupRooms(rooms, workspaces) {
2366
+ const groups = workspaces.map((w) => ({ ws: w, rooms: [] }));
2367
+ const other = [];
2368
+ for (const r of rooms) {
2369
+ const path = r.memberConfig && r.memberConfig.workspacePath;
2370
+ const g = groups.find((x) => samePath(x.ws.path, path));
2371
+ if (g) g.rooms.push(r);
2372
+ else other.push(r);
2373
+ }
2374
+ return { groups, other };
2375
+ }
2376
+ var wsTitle = (w) => w.title || (w.path || "").split(/[\\/]/).filter(Boolean).pop() || w.path;
2377
+ function RoomItem({ r, selected, onClick }) {
2378
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-room-item", "aria-current": selected ? "true" : void 0, "data-room-id": r.roomId, onClick, children: [
2379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
2380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 26 }),
2381
+ /* @__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)" } })
2382
+ ] }),
2383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
2385
+ /* @__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") })
2386
+ ] }),
2387
+ r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge rm-badge-public", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}) })
2388
+ ] });
1786
2389
  }
1787
- function RoomsSidebar({ store, ensurePanel }) {
2390
+ function RoomsNav({ store }) {
1788
2391
  const s = useStore(store);
1789
2392
  const a = store.actions;
1790
- const [menu, setMenu] = (0, import_react.useState)(null);
1791
- const [joining, setJoining] = (0, import_react.useState)(null);
1792
- const inputRef = (0, import_react.useRef)(null);
1793
- const searching = !!s.search.open;
1794
- const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0] || null;
1795
- (0, import_react.useEffect)(() => {
1796
- if (!menu) return;
1797
- const close = () => setMenu(null);
1798
- window.addEventListener("click", close);
1799
- return () => window.removeEventListener("click", close);
1800
- }, [menu]);
1801
- (0, import_react.useEffect)(() => {
1802
- if (searching && inputRef.current) inputRef.current.focus();
1803
- }, [searching]);
1804
- const q = (s.search.query || "").trim().toLowerCase();
1805
- const mine = (s.rooms || []).filter((r) => {
1806
- if (activeWs && r.workspaceId && r.workspaceId !== activeWs.id) return false;
1807
- if (!q) return true;
1808
- return r.name.toLowerCase().includes(q) || (r.description || "").toLowerCase().includes(q) || (r.tags || []).some((t) => ("#" + t).includes(q));
1809
- });
1810
- const join = async (r) => {
1811
- setJoining(r.roomId);
1812
- try {
1813
- await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
1814
- ensurePanel();
1815
- } catch (e) {
1816
- a.toast(e.message);
1817
- } finally {
1818
- setJoining(null);
1819
- }
1820
- };
1821
- if (!activeWs) {
1822
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
1823
- /* @__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" }) }),
1824
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
1825
- /* @__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)." }),
1826
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace" })
1827
- ] })
1828
- ] });
1829
- }
1830
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-side", "data-collab-sidebar": "", children: [
1831
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-toolbar", children: [
1832
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "DSH Room" }),
1833
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", "aria-label": searching ? "Close search" : "Search rooms", "aria-pressed": searching, onClick: () => a.setSearchOpen(!searching), children: searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) }),
1834
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1835
- "button",
1836
- {
1837
- type: "button",
1838
- className: "rm-icon-btn",
1839
- "data-rm-new": "",
1840
- "aria-label": "New",
1841
- "aria-haspopup": "menu",
1842
- "aria-expanded": !!menu,
1843
- onClick: (e) => {
1844
- e.stopPropagation();
1845
- const r = e.currentTarget.getBoundingClientRect();
1846
- setMenu(menu ? null : { top: r.bottom + 4, left: Math.max(8, r.right - 200) });
1847
- },
1848
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
1849
- }
1850
- ),
1851
- 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: [
1852
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1853
- setMenu(null);
1854
- a.openDialog({ kind: "create" });
1855
- }, children: [
1856
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
1857
- "Create room in workspace"
1858
- ] }),
1859
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1860
- setMenu(null);
1861
- a.openDialog({ kind: "join" });
1862
- }, children: [
1863
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
1864
- "Join by invite"
1865
- ] }),
1866
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1867
- setMenu(null);
1868
- a.openDialog({ kind: "workspace" });
1869
- }, children: [
1870
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1871
- "Add / switch workspace"
1872
- ] }),
1873
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", role: "menuitem", onClick: () => {
1874
- setMenu(null);
1875
- a.openDialog({ kind: "browse" });
1876
- }, children: [
1877
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1878
- "Browse public rooms"
1879
- ] })
1880
- ] }) : null
1881
- ] }),
1882
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-bar", children: [
1883
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-ws-chip", onClick: () => a.openDialog({ kind: "workspace" }), title: activeWs.path, children: [
1884
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1885
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: activeWs.name })
1886
- ] }),
1887
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-path", title: activeWs.path, children: activeWs.path }),
1888
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-new-room", "data-rm-new-room": "", onClick: () => {
1889
- a.openDialog({ kind: "create" });
1890
- ensurePanel();
1891
- }, children: [
1892
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
1893
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "New Room" })
1894
- ] })
2393
+ const workspaces = useNativeWorkspaces(store);
2394
+ const [q, setQ] = (0, import_react.useState)("");
2395
+ const qq = q.trim().toLowerCase();
2396
+ const match = (r) => !qq || r.name.toLowerCase().includes(qq) || (r.description || "").toLowerCase().includes(qq) || (r.tags || []).some((t) => ("#" + t).includes(qq));
2397
+ const { groups, other } = groupRooms((s.rooms || []).filter(match), workspaces);
2398
+ const sel = (r) => a.select(r.roomId);
2399
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", { className: "rm-nav", "aria-label": "Rooms by workspace", "data-rm-nav": "", children: [
2400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-nav-head", children: [
2401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-side-title", children: "Rooms" }),
2402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", title: "Browse public rooms", "aria-label": "Browse public rooms", onClick: () => a.openDialog({ kind: "browse" }), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}) }),
2403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-icon-btn", title: "Join by invite", "aria-label": "Join by invite", onClick: () => a.openDialog({ kind: "join" }), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}) })
1895
2404
  ] }),
1896
- searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
2405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search", children: [
1897
2406
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
1898
- /* @__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" })
1899
- ] }) : null,
1900
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-side-scroll", children: [
1901
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1902
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
1903
- "Rooms in ",
1904
- activeWs.name
1905
- ] }),
1906
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-list", children: [
1907
- mine.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "text", placeholder: "Filter rooms or #tags", value: q, onChange: (e) => setQ(e.target.value), "aria-label": "Filter rooms" })
2408
+ ] }),
2409
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-nav-scroll", children: [
2410
+ groups.map(({ ws, rooms }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-group", "data-rm-ws-group": ws.workspaceId, children: [
2411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-head", title: ws.path, children: [
2412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
2413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-name", children: wsTitle(ws) }),
2414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1908
2415
  "button",
1909
2416
  {
1910
2417
  type: "button",
1911
- className: "rm-room-item",
1912
- "aria-current": r.roomId === s.selected ? "true" : void 0,
1913
- "data-room-id": r.roomId,
1914
- onClick: () => {
1915
- a.select(r.roomId);
1916
- ensurePanel();
1917
- },
1918
- children: [
1919
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
1920
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
1921
- /* @__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)" } })
1922
- ] }),
1923
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
1924
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: r.name }),
1925
- /* @__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") })
1926
- ] }),
1927
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-trail", children: [
1928
- r.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
1929
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
1930
- "Public"
1931
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
1932
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
1933
- "Private"
1934
- ] }),
1935
- r.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
1936
- r.last ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--rm-quiet)" }, children: fmtTime(r.last.ts) }) : null
1937
- ] })
1938
- ]
1939
- },
1940
- r.roomId
1941
- )),
1942
- !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
1943
- ] })
1944
- ] }),
1945
- (s.workspaces || []).length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1946
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-group-label", children: "Workspaces" }),
1947
- (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: [
2418
+ className: "rm-icon-btn rm-ws-add",
2419
+ title: `New room in ${wsTitle(ws)}`,
2420
+ "aria-label": `New room in ${wsTitle(ws)}`,
2421
+ onClick: () => a.openDialog({ kind: "create", workspace: { id: ws.workspaceId, path: ws.path, title: wsTitle(ws) } }),
2422
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
2423
+ }
2424
+ )
2425
+ ] }),
2426
+ rooms.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomItem, { r, selected: r.roomId === s.selected, onClick: () => sel(r) }, r.roomId)),
2427
+ !rooms.length && !qq ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-empty-hint rm-ws-empty", children: "No rooms \xB7 use + to create one here" }) : null
2428
+ ] }, ws.workspaceId)),
2429
+ other.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-group", children: [
2430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-ws-head", children: [
1948
2431
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
1949
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-room-item-body", children: [
1950
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-name", children: w.name }),
1951
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-room-item-meta", children: w.path })
1952
- ] })
1953
- ] }, w.id))
1954
- ] }) : null,
1955
- searching ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1956
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-group-label", children: [
1957
- "Public directory ",
1958
- s.search.loading ? "\xB7 searching\u2026" : ""
2432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-ws-name", children: "Other rooms" })
1959
2433
  ] }),
1960
- s.search.results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item", "data-dir-room-id": r.roomId, children: [
1961
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: r.name, size: 30 }),
1962
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-dir-item-body", children: [
1963
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-room-item-name", children: r.name }),
1964
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-room-item-meta", children: [
1965
- r.description || "No description",
1966
- " \xB7 ",
1967
- r.members,
1968
- " online"
1969
- ] }),
1970
- (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: [
1971
- "#",
1972
- tg
1973
- ] }, tg)) }) : null
1974
- ] }),
1975
- /* @__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" })
1976
- ] }, r.roomId)),
1977
- !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,
1978
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
1979
- "Joining attaches the room to workspace \u201C",
1980
- activeWs.name,
1981
- "\u201D. Point your local path at the same project."
1982
- ] })
2434
+ other.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomItem, { r, selected: r.roomId === s.selected, onClick: () => sel(r) }, r.roomId))
2435
+ ] }) : null,
2436
+ !workspaces.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-empty-hint", children: [
2437
+ "No workspaces yet. Add one with the native \u201CAdd workspace\u201D in the left sidebar, then use \u201CNew room\u201D on it.",
2438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => a.openDialog({ kind: "workspace" }), children: "Add workspace\u2026" }) })
1983
2439
  ] }) : null
1984
2440
  ] })
1985
2441
  ] });
1986
2442
  }
1987
- function Message({ m, showAuthor }) {
2443
+ function RoomSessionMark({ store, sessionId }) {
2444
+ const s = useStore(store);
2445
+ for (const r of s.rooms || []) {
2446
+ const ag = (r.local && r.local.agents || []).find((x) => x.sessionId === sessionId);
2447
+ if (ag) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-session-mark rm-session-agent", title: `DSH Room \xB7 ${r.name} \xB7 ${ag.name} (${ag.agentId})`, "aria-label": `DSH Room ${r.name} agent ${ag.name}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 13 }) });
2448
+ }
2449
+ const room = (s.rooms || []).find((r) => r.sessionId === sessionId);
2450
+ if (!room) return null;
2451
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-session-mark", title: `DSH Room \xB7 ${room.name}`, "aria-label": `DSH Room ${room.name}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size: 13 }) });
2452
+ }
2453
+ function WorkspaceSelect({ store, value, onChange, id }) {
2454
+ const workspaces = useNativeWorkspaces(store);
2455
+ (0, import_react.useEffect)(() => {
2456
+ if (!value && workspaces[0]) onChange({ id: workspaces[0].workspaceId, path: workspaces[0].path, title: wsTitle(workspaces[0]) });
2457
+ }, [workspaces.length]);
2458
+ if (!workspaces.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "No dsh workspace yet \u2014 add one in the left sidebar (Add workspace)." });
2459
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { id, className: "rm-input", value: value ? value.path : "", onChange: (e) => {
2460
+ const w = workspaces.find((x) => x.path === e.target.value);
2461
+ if (w) onChange({ id: w.workspaceId, path: w.path, title: wsTitle(w) });
2462
+ }, children: workspaces.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: w.path, children: [
2463
+ wsTitle(w),
2464
+ " \u2014 ",
2465
+ w.path
2466
+ ] }, w.workspaceId)) });
2467
+ }
2468
+ function renderMentions(text) {
2469
+ 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);
2470
+ }
2471
+ function PanelIcon({ size }) {
2472
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsIcon, { size });
2473
+ }
2474
+ function Message({ m, showAuthor, who }) {
1988
2475
  if (m.kind === "task.create") {
1989
2476
  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: [
1990
2477
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-task-head", children: [
1991
2478
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
1992
2479
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1993
2480
  "Task \xB7 ",
1994
- m.from
2481
+ who && who.name || m.from
1995
2482
  ] }),
1996
2483
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: "open" }),
1997
2484
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
@@ -2004,7 +2491,7 @@ function Message({ m, showAuthor }) {
2004
2491
  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: [
2005
2492
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
2006
2493
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2007
- m.self ? "You" : m.from,
2494
+ m.self ? "You" : who && who.name || m.from,
2008
2495
  " ",
2009
2496
  m.kind === "task.claim" ? "claimed" : "updated",
2010
2497
  " task"
@@ -2024,14 +2511,20 @@ function Message({ m, showAuthor }) {
2024
2511
  showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.from, size: 28 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
2025
2512
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2026
2513
  showAuthor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-msg-head", children: [
2027
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: m.from }),
2514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-author", children: who && who.name ? who.name : m.from }),
2515
+ who && who.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-id", children: m.from }) : null,
2516
+ who && who.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
2517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
2518
+ who.moderator ? "moderator" : "local agent"
2519
+ ] }) : null,
2028
2520
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-time", children: fmtTime(m.ts) })
2029
2521
  ] }) : null,
2030
2522
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-msg-body", children: renderMentions(m.text) })
2031
2523
  ] })
2032
2524
  ] });
2033
2525
  }
2034
- function ChatBody({ messages }) {
2526
+ function ChatBody({ messages, members }) {
2527
+ const byId = (0, import_react.useMemo)(() => Object.fromEntries((members || []).map((x) => [x.agentId, x])), [members]);
2035
2528
  const ref = (0, import_react.useRef)(null);
2036
2529
  (0, import_react.useEffect)(() => {
2037
2530
  const el = ref.current;
@@ -2047,7 +2540,7 @@ function ChatBody({ messages }) {
2047
2540
  prev = null;
2048
2541
  }
2049
2542
  const showAuthor = !prev || prev.from !== m.from || m.ts - prev.ts > 5 * 6e4;
2050
- nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor }, m.id));
2543
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { m, showAuthor, who: byId[m.from] }, m.id));
2051
2544
  prev = m;
2052
2545
  }
2053
2546
  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." }) });
@@ -2074,7 +2567,7 @@ function MentionMenu({ items, active, onPick, onHover }) {
2074
2567
  onPick(it);
2075
2568
  },
2076
2569
  children: [
2077
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.id, size: 22 }),
2570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: it.id === "all" ? "all" : it.name || it.id, size: 22 }),
2078
2571
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-body", children: [
2079
2572
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-mention-item-name", children: [
2080
2573
  "@",
@@ -2082,6 +2575,10 @@ function MentionMenu({ items, active, onPick, onHover }) {
2082
2575
  ] }),
2083
2576
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mention-item-meta", children: it.hint })
2084
2577
  ] }),
2578
+ it.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
2579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
2580
+ "local"
2581
+ ] }) : null,
2085
2582
  it.online === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "offline" }) : null
2086
2583
  ]
2087
2584
  },
@@ -2106,14 +2603,20 @@ function Composer({ room, store, draft, setDraft }) {
2106
2603
  if (!mq) return [];
2107
2604
  const q = mq.query.toLowerCase();
2108
2605
  const members = (room.members || []).filter((m) => !m.self);
2109
- const known = members.map((m) => ({
2110
- id: m.agentId,
2111
- hint: m.online === false ? "member \xB7 offline" : "member \xB7 wake",
2112
- online: m.online !== false
2113
- }));
2114
- const all = { id: "all", hint: "Everyone \xB7 wake all members", online: true };
2606
+ const localAgents = room.local && room.local.agents || [];
2607
+ const seen = /* @__PURE__ */ new Set();
2608
+ const known = [];
2609
+ for (const la of localAgents) {
2610
+ seen.add(la.agentId);
2611
+ known.push({ id: la.agentId, name: la.name, local: true, hint: `${la.name} \xB7 local agent${la.moderator ? " \xB7 moderator" : ""}${la.state && la.state !== "idle" ? ` \xB7 ${la.state}` : ""}`, online: la.online !== false });
2612
+ }
2613
+ for (const m of members) {
2614
+ if (seen.has(m.agentId)) continue;
2615
+ known.push({ id: m.agentId, name: m.name, hint: `${m.name ? m.name + " \xB7 " : ""}${m.hostedBy ? "remote agent" : "member"}${m.online === false ? " \xB7 offline" : " \xB7 wake"}`, online: m.online !== false });
2616
+ }
2617
+ const all = { id: "all", hint: localAgents.length ? "Everyone \xB7 wakes all local agents in parallel" : "Everyone \xB7 wake all members", online: true };
2115
2618
  const list = [all, ...known];
2116
- return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || ("@" + it.id).toLowerCase().startsWith("@" + q));
2619
+ return list.filter((it) => !q || it.id.toLowerCase().startsWith(q) || it.name && it.name.toLowerCase().startsWith(q));
2117
2620
  })();
2118
2621
  const mc = room.memberConfig || {};
2119
2622
  const modelLabel = mc.model || "Default model";
@@ -2151,6 +2654,23 @@ function Composer({ room, store, draft, setDraft }) {
2151
2654
  a.toast(e.message);
2152
2655
  }
2153
2656
  };
2657
+ const syncCaret = (el) => {
2658
+ if (el) setCaret(el.selectionStart == null ? el.value.length : el.selectionStart);
2659
+ };
2660
+ const insertMention = (it) => {
2661
+ if (!mq || !it) return;
2662
+ const next = draft.slice(0, mq.start) + "@" + it.id + " " + draft.slice(mq.end);
2663
+ const pos = mq.start + it.id.length + 2;
2664
+ setDraft(next);
2665
+ setCaret(pos);
2666
+ requestAnimationFrame(() => {
2667
+ const el = taRef.current;
2668
+ if (el) {
2669
+ el.focus();
2670
+ el.setSelectionRange(pos, pos);
2671
+ }
2672
+ });
2673
+ };
2154
2674
  const submit = async () => {
2155
2675
  const text = draft.trim();
2156
2676
  if (!text || busy) return;
@@ -2347,7 +2867,15 @@ function RoomDetail({ room, messages, store, mention, assign }) {
2347
2867
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: m.agentId, size: 26 }),
2348
2868
  /* @__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)" } })
2349
2869
  ] }),
2350
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.agentId }),
2870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-member-name", children: m.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2871
+ m.name,
2872
+ " ",
2873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-msg-id", children: m.agentId })
2874
+ ] }) : m.agentId }),
2875
+ m.local ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-tag", children: [
2876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, { size: 11 }),
2877
+ m.moderator ? "moderator" : "local"
2878
+ ] }) : m.hostedBy && !m.self ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-agent-tag", children: "remote agent" }) : null,
2351
2879
  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: [
2352
2880
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${m.agentId}`, onClick: () => mention(m.agentId), children: "@" }),
2353
2881
  /* @__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, {}) })
@@ -2355,6 +2883,9 @@ function RoomDetail({ room, messages, store, mention, assign }) {
2355
2883
  ] }, m.agentId)),
2356
2884
  !room.members.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.status === "online" ? "Only you are here." : "Offline." }) : null
2357
2885
  ] }),
2886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocalAgentsSection, { room, store, mention }),
2887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrchestrationSection, { room, store }),
2888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IsolationSection, { room, store }),
2358
2889
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2359
2890
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
2360
2891
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskIcon, {}),
@@ -2377,8 +2908,10 @@ function RoomDetail({ room, messages, store, mention, assign }) {
2377
2908
  "Invite"
2378
2909
  ] }),
2379
2910
  /* @__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." }),
2911
+ room.transport === "local" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Local-only room: switch Transport to Relay or LAN before inviting remote hosts." }) : null,
2380
2912
  /* @__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" }) })
2381
2913
  ] }),
2914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransportSection, { room, store }),
2382
2915
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", children: [
2383
2916
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "About" }),
2384
2917
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "rm-about", children: [
@@ -2391,7 +2924,7 @@ function RoomDetail({ room, messages, store, mention, assign }) {
2391
2924
  " Private"
2392
2925
  ] }) }),
2393
2926
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Transport" }),
2394
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
2927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.transport === "local" ? "Local only (in-process loopback)" : room.transport === "lan" ? "LAN / VPS (Node relay)" : "Relay (Cloudflare)" }),
2395
2928
  room.memberConfig?.workspaceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2396
2929
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Workspace" }),
2397
2930
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: room.memberConfig.workspaceLabel })
@@ -2418,33 +2951,392 @@ function RoomDetail({ room, messages, store, mention, assign }) {
2418
2951
  ] })
2419
2952
  ] });
2420
2953
  }
2421
- function EmptyMain({ store }) {
2954
+ var RUN_TONE = { running: "warning", queued: "info", approval: "info", done: "success", failed: "danger", stopped: "quiet" };
2955
+ var MODEL_CHOICES = ["", "deepseek-chat", "deepseek-reasoner"];
2956
+ function LocalAgentsSection({ room, store, mention }) {
2957
+ const a = store.actions;
2958
+ const local = room.local || { agents: [] };
2959
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-local-agents": "", children: [
2960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
2961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BotIcon, {}),
2962
+ "Local agents \xB7 ",
2963
+ local.agents.length
2964
+ ] }),
2965
+ local.agents.map((ag) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-agent-row", "data-agent-id": ag.agentId, children: [
2966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-avatar-wrap", children: [
2967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: ag.name, size: 26 }),
2968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-dot rm-dot-" + (ag.state === "running" ? "busy" : ag.online ? "online" : "offline"), style: { position: "absolute", right: -1, bottom: -1, border: "2px solid var(--rm-bg)" } })
2969
+ ] }),
2970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-main", children: [
2971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-name", children: [
2972
+ ag.name,
2973
+ ag.moderator ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-agent-tag", children: "moderator" }) : null
2974
+ ] }),
2975
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-sub rm-mono", children: [
2976
+ "@",
2977
+ ag.agentId
2978
+ ] }),
2979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-agent-sub", children: [
2980
+ ag.scope === "write" ? ag.isolation === "worktree" ? "writes \xB7 own worktree" : ag.isolation === "locks" ? "writes \xB7 file locks" : "writes" : "read-only",
2981
+ ag.approvalRequired ? " \xB7 approval" : "",
2982
+ ag.model ? ` \xB7 ${ag.model}` : "",
2983
+ ag.reasoningEffort ? ` \xB7 ${ag.reasoningEffort}` : ""
2984
+ ] })
2985
+ ] }),
2986
+ ag.state !== "idle" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[ag.state] || "neutral", children: ag.state }) : null,
2987
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-member-actions", children: [
2988
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `@mention ${ag.name}`, onClick: () => mention(ag.agentId), children: "@" }),
2989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mini", title: `Edit ${ag.name}`, "aria-label": `Edit ${ag.name}`, onClick: () => a.openDialog({ kind: "agent", roomId: room.roomId, agentId: ag.agentId }), children: "\u270E" })
2990
+ ] })
2991
+ ] }, ag.agentId)),
2992
+ !local.agents.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Host several agents from this dsh as separate members \u2014 each with its own identity, session and scope." }) : null,
2993
+ /* @__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", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}), onClick: () => a.openDialog({ kind: "agent", roomId: room.roomId }), children: "Add local agent" }) })
2994
+ ] });
2995
+ }
2996
+ function NumberSetting({ label, value, min, max, onSave, hint }) {
2997
+ const [v, setV] = (0, import_react.useState)(String(value));
2998
+ (0, import_react.useEffect)(() => setV(String(value)), [value]);
2999
+ const commit = () => {
3000
+ const n = Number(v);
3001
+ if (Number.isFinite(n) && n !== value) onSave(Math.min(max, Math.max(min, Math.round(n))));
3002
+ else setV(String(value));
3003
+ };
3004
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-num-setting", title: hint, children: [
3005
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label }),
3006
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", min, max, value: v, "aria-label": label, onChange: (e) => setV(e.target.value), onBlur: commit, onKeyDown: (e) => e.key === "Enter" && e.target.blur() })
3007
+ ] });
3008
+ }
3009
+ function RunQueue({ room, store, compact }) {
3010
+ const a = store.actions;
3011
+ const runs = room.runs || [];
3012
+ const nameOf = (id) => (room.local && room.local.agents || []).find((x) => x.agentId === id)?.name || id;
3013
+ const active = runs.filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
3014
+ const recent = runs.filter((r) => !active.includes(r)).slice(-3).reverse();
3015
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-runs", "data-rm-runs": "", children: [
3016
+ active.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-run rm-run-" + r.state, children: [
3017
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-pos", children: r.state === "running" ? "\u25B6" : r.state === "approval" ? "?" : `#${i + 1 - active.filter((x) => x.state === "running").length}` }),
3018
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-who", children: nameOf(r.agentId) }),
3019
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-what", title: r.summary, children: r.summary }),
3020
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[r.state], children: r.state === "approval" ? "needs approval" : r.state })
3021
+ ] }, r.id)),
3022
+ !compact ? recent.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-run rm-run-" + r.state, children: [
3023
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-pos", children: "\u2713" }),
3024
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-who", children: nameOf(r.agentId) }),
3025
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-run-what", title: r.error || r.summary, children: r.error || r.summary }),
3026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: RUN_TONE[r.state], children: r.state })
3027
+ ] }, r.id)) : null,
3028
+ !active.length && !recent.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No agent runs yet." }) : null,
3029
+ active.length ? /* @__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", className: "rm-danger", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}), onClick: () => a.stop(room.roomId).then((r) => a.toast(`Stopped ${r.stopped} run(s)`)).catch((e) => a.toast(e.message)), children: "Stop all agents" }) }) : null
3030
+ ] });
3031
+ }
3032
+ function OrchestrationSection({ room, store }) {
3033
+ const a = store.actions;
3034
+ const local = room.local;
3035
+ if (!local || !local.agents.length) return null;
3036
+ const save = (patch) => a.saveLocal(room.roomId, patch).catch((e) => a.toast(e.message));
3037
+ const active = (room.runs || []).filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
3038
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-orchestration": "", children: [
3039
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
3040
+ "Runs \xB7 ",
3041
+ active.filter((r) => r.state === "running").length,
3042
+ "/",
3043
+ local.concurrency,
3044
+ " running",
3045
+ active.length > local.concurrency ? ` \xB7 ${active.length - Math.min(local.concurrency, active.filter((r) => r.state === "running").length)} queued` : ""
3046
+ ] }),
3047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-num-row", children: [
3048
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NumberSetting, { label: "Parallel runs", value: local.concurrency, min: 1, max: 16, onSave: (n) => save({ concurrency: n }), hint: "Max local agent runs at once; the rest wait in the queue." }),
3049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NumberSetting, { label: "Turn limit", value: local.turnLimit, min: 1, max: 100, onSave: (n) => save({ turnLimit: n }), hint: "Max agent-to-agent hand-offs per task/thread. A human message resets it." })
3050
+ ] }),
3051
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunQueue, { room, store })
3052
+ ] });
3053
+ }
3054
+ function IsolationSection({ room, store }) {
3055
+ const a = store.actions;
3056
+ const local = room.local;
3057
+ if (!local || !local.agents.some((x) => x.scope === "write")) return null;
3058
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-isolation": "", children: [
3059
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-h", children: [
3060
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}),
3061
+ "Workspace isolation"
3062
+ ] }),
3063
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "rm-input", "aria-label": "Isolation mode", value: local.isolation, onChange: (e) => a.saveLocal(room.roomId, { isolation: e.target.value }).catch((x) => a.toast(x.message)), children: [
3064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "auto", children: "Auto \u2014 git worktree per writing agent, else file locks" }),
3065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "worktree", children: "Git worktree per agent" }),
3066
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "locks", children: "File locks (shared workspace)" }),
3067
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "off", children: "Off (shared, no locks)" })
3068
+ ] }),
3069
+ local.locks && local.locks.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-note", children: [
3070
+ local.locks.length,
3071
+ " file lock(s): ",
3072
+ local.locks.slice(0, 4).map((l) => `${l.path} (${l.owner})`).join(", ")
3073
+ ] }) : null,
3074
+ /* @__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", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}), onClick: () => a.openDialog({ kind: "merge", roomId: room.roomId }), children: "Review & merge" }) })
3075
+ ] });
3076
+ }
3077
+ function TransportSection({ room, store }) {
3078
+ const a = store.actions;
3079
+ const [busy, setBusy] = (0, import_react.useState)(null);
3080
+ const cur = room.transport === "local" ? "local" : room.transport === "lan" ? "relay" : "relay";
3081
+ const go = async (target) => {
3082
+ setBusy(target);
3083
+ try {
3084
+ await a.setTransport(room.roomId, target);
3085
+ a.toast(target === "local" ? "Local only \u2014 in-process, no network" : target === "lan" ? "Now on this machine's LAN relay \u2014 invite remote hosts" : "Now on the relay \u2014 invite remote hosts");
3086
+ } catch (e) {
3087
+ a.toast(e.message);
3088
+ } finally {
3089
+ setBusy(null);
3090
+ }
3091
+ };
3092
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-detail-section", "data-rm-transport": "", children: [
3093
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-detail-h", children: "Transport" }),
3094
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
3095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy || room.public, className: "rm-mode-btn" + (cur === "local" ? " is-on" : ""), onClick: () => go("local"), title: room.public ? "Public rooms need a relay" : "In-process loopback: no network", children: busy === "local" ? "\u2026" : "Local only" }),
3096
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy, className: "rm-mode-btn" + (cur === "relay" && room.transport !== "local" ? " is-on" : ""), onClick: () => go("relay"), children: busy === "relay" ? "\u2026" : "Relay (VPS)" }),
3097
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !!busy, className: "rm-mode-btn", onClick: () => go("lan"), title: "Embedded LAN relay on this machine (mDNS)", children: busy === "lan" ? "\u2026" : "LAN" })
3098
+ ] }),
3099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: room.transport === "local" ? "All members are on this dsh \u2014 nothing leaves the process. Switch to Relay or LAN to invite remote hosts; local agents stay members." : "Local agents connect as separate members over this transport, alongside remote hosts." })
3100
+ ] });
3101
+ }
3102
+ function AgentDialog({ store, d }) {
3103
+ const a = store.actions;
2422
3104
  const s = useStore(store);
3105
+ const room = (s.rooms || []).find((r) => r.roomId === d.roomId);
3106
+ const existing = room && room.local ? room.local.agents.find((x) => x.agentId === d.agentId) : null;
3107
+ const [presets, setPresets] = (0, import_react.useState)([]);
3108
+ const [f, setF] = (0, import_react.useState)(() => existing ? { name: existing.name, persona: existing.persona || "", systemPrompt: existing.systemPrompt || "", model: existing.model || "", reasoningEffort: existing.reasoningEffort || "medium", scope: existing.scope, approvalRequired: !!existing.approvalRequired, moderator: !!existing.moderator } : { preset: "", name: "", persona: "", systemPrompt: "", model: "", reasoningEffort: "medium", scope: "read", approvalRequired: false, moderator: false });
3109
+ const [err, setErr] = (0, import_react.useState)(null);
3110
+ const [busy, setBusy] = (0, import_react.useState)(false);
3111
+ (0, import_react.useEffect)(() => {
3112
+ a.presets().then(setPresets).catch(() => {
3113
+ });
3114
+ }, []);
3115
+ const pick = (p) => setF({ ...f, preset: p.key, name: p.name, persona: p.persona, systemPrompt: p.systemPrompt, scope: p.scope, moderator: !!p.moderator, reasoningEffort: p.reasoningEffort || "medium" });
3116
+ const submit = async () => {
3117
+ if (!f.name.trim()) {
3118
+ setErr("Name required");
3119
+ return;
3120
+ }
3121
+ setBusy(true);
3122
+ setErr(null);
3123
+ try {
3124
+ const body = { name: f.name, persona: f.persona, systemPrompt: f.systemPrompt, model: f.model, reasoningEffort: f.reasoningEffort, scope: f.scope, approvalRequired: f.approvalRequired, moderator: f.moderator, preset: f.preset || void 0 };
3125
+ if (existing) await a.updateAgent(d.roomId, existing.agentId, body);
3126
+ else {
3127
+ const r = await a.addAgent(d.roomId, body);
3128
+ a.toast(`${r.name} joined as @${r.agentId}`);
3129
+ }
3130
+ a.closeDialog();
3131
+ } catch (e) {
3132
+ setErr(e.message);
3133
+ setBusy(false);
3134
+ }
3135
+ };
3136
+ const remove = async () => {
3137
+ setBusy(true);
3138
+ try {
3139
+ const r = await a.removeAgent(d.roomId, existing.agentId);
3140
+ a.toast(`Removed ${existing.name}${r.branchKept ? ` \xB7 branch ${r.branchKept} kept` : ""}`);
3141
+ a.closeDialog();
3142
+ } catch (e) {
3143
+ setErr(e.message);
3144
+ setBusy(false);
3145
+ }
3146
+ };
3147
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3148
+ import_dsh_client_ui_primitives.Modal,
3149
+ {
3150
+ open: true,
3151
+ onClose: a.closeDialog,
3152
+ title: existing ? `Edit ${existing.name}` : "Add local agent",
3153
+ closeLabel: "Close",
3154
+ className: "rm-modal-wide",
3155
+ description: existing ? `Member @${existing.agentId} \xB7 own identity and dsh session in this room's workspace.` : "Runs on this dsh as a separate room member: own keypair, own session, own scope. Remote peers see it as a member.",
3156
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3157
+ existing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", className: "rm-danger", disabled: busy, onClick: remove, children: "Remove" }) : null,
3158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
3159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
3160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: submit, children: busy ? "Saving\u2026" : existing ? "Save" : "Add agent" })
3161
+ ] }),
3162
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form rm-form-wide", "data-rm-agent-form": "", children: [
3163
+ !existing ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2", children: [
3164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Preset" }),
3165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-preset-row", children: presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-preset" + (f.preset === p.key ? " is-on" : ""), onClick: () => pick(p), children: [
3166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: p.name }),
3167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.persona })
3168
+ ] }, p.key)) })
3169
+ ] }) : null,
3170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name", htmlFor: "rm-ag-name", hint: "Member id is derived from it (e.g. coder.ab12).", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ag-name", value: f.name, maxLength: 40, onChange: (e) => setF({ ...f, name: e.target.value }), placeholder: "e.g. Coder" }) }),
3171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Persona", htmlFor: "rm-ag-persona", hint: "One line other members see.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Input, { id: "rm-ag-persona", value: f.persona, maxLength: 200, onChange: (e) => setF({ ...f, persona: e.target.value }), placeholder: "e.g. Implements changes" }) }),
3172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "System prompt", htmlFor: "rm-ag-sp", hint: "Prepended to every wake of this agent.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { id: "rm-ag-sp", className: "rm-input rm-textarea", rows: 4, value: f.systemPrompt, maxLength: 4e3, onChange: (e) => setF({ ...f, systemPrompt: e.target.value }) }) }) }),
3173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Model", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "rm-input", "aria-label": "Model", value: f.model, onChange: (e) => setF({ ...f, model: e.target.value }), children: [
3174
+ MODEL_CHOICES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: m, children: m || "dsh default model" }, m)),
3175
+ f.model && !MODEL_CHOICES.includes(f.model) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: f.model, children: f.model }) : null
3176
+ ] }) }),
3177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Reasoning effort", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: "rm-input", "aria-label": "Reasoning effort", 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)) }) }),
3178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace scope", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-mode-row", children: [
3179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.scope === "read" ? " is-on" : ""), onClick: () => setF({ ...f, scope: "read" }), children: "Read-only" }),
3180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "rm-mode-btn" + (f.scope === "write" ? " is-on" : ""), onClick: () => setF({ ...f, scope: "write" }), children: "Read + write" })
3181
+ ] }) }),
3182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-col", children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
3184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Approval required" }),
3186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Each run waits for your OK." })
3187
+ ] }),
3188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.approvalRequired, onChange: (v) => setF({ ...f, approvalRequired: v }), label: "Approval required" })
3189
+ ] }),
3190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
3191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Moderator" }),
3193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Splits requests into tasks and summarizes. One per room." })
3194
+ ] }),
3195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.moderator, onChange: (v) => setF({ ...f, moderator: v }), label: "Moderator" })
3196
+ ] })
3197
+ ] }),
3198
+ f.scope === "write" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-note", children: "Writes go to its own git worktree (branch room/<room>/<agent>) when the workspace is a git repo; you merge from \u201CReview & merge\u201D. Non-git workspaces use file locks." }) : null,
3199
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-error", children: err }) : null
3200
+ ] })
3201
+ }
3202
+ );
3203
+ }
3204
+ function DiffView({ patch }) {
3205
+ if (!patch) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No changes." });
3206
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "rm-diff", "data-rm-diff": "", children: patch.split("\n").map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: l.startsWith("+++") || l.startsWith("---") ? "rm-diff-file" : l.startsWith("+") ? "rm-diff-add" : l.startsWith("-") ? "rm-diff-del" : l.startsWith("@@") ? "rm-diff-hunk" : l.startsWith("diff ") ? "rm-diff-head" : "", children: l || " " }, i)) });
3207
+ }
3208
+ function MergeDialog({ store, d }) {
3209
+ const a = store.actions;
3210
+ const [data, setData] = (0, import_react.useState)(null);
3211
+ const [sel, setSel] = (0, import_react.useState)(null);
3212
+ const [confirm, setConfirm] = (0, import_react.useState)(null);
3213
+ const [busy, setBusy] = (0, import_react.useState)(false);
3214
+ const [result, setResult] = (0, import_react.useState)(null);
3215
+ const load = async () => {
3216
+ try {
3217
+ const r = await a.diffs(d.roomId);
3218
+ setData(r);
3219
+ setSel((cur) => cur && r.agents.some((x) => x.agentId === cur) ? cur : (r.agents.find((x) => x.ahead) || r.agents[0] || {}).agentId || null);
3220
+ } catch (e) {
3221
+ setData({ error: e.message, agents: [] });
3222
+ }
3223
+ };
3224
+ (0, import_react.useEffect)(() => {
3225
+ void load();
3226
+ }, [d.roomId]);
3227
+ const ag = data && data.agents.find((x) => x.agentId === sel);
3228
+ const run = async (kind) => {
3229
+ setBusy(true);
3230
+ setResult(null);
3231
+ try {
3232
+ const r = kind === "merge" ? await a.merge(d.roomId, sel) : await a.discard(d.roomId, sel);
3233
+ setResult(kind === "merge" ? r.merged ? { ok: true, text: `Merged into the main workspace (${r.commit}).` } : { ok: false, text: `${r.error || "Not merged"}${r.conflicts && r.conflicts.length ? ": " + r.conflicts.join(", ") : ""}` } : { ok: true, text: r.discarded ? `Discarded ${r.branch}.` : "Nothing to discard." });
3234
+ await load();
3235
+ } catch (e) {
3236
+ setResult({ ok: false, text: e.message });
3237
+ } finally {
3238
+ setBusy(false);
3239
+ setConfirm(null);
3240
+ }
3241
+ };
3242
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3243
+ import_dsh_client_ui_primitives.Modal,
3244
+ {
3245
+ open: true,
3246
+ onClose: a.closeDialog,
3247
+ title: "Review & merge agent changes",
3248
+ closeLabel: "Close",
3249
+ className: "rm-modal-wide rm-modal-xl",
3250
+ description: "Each writing agent works on its own branch. Review the diff, then merge into (or discard from) your main workspace. Nothing is merged without your confirmation.",
3251
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Close" }),
3252
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-root rm-merge", "data-rm-merge": "", children: !data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Loading diffs\u2026" }) : data.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: data.error }) : !data.agents.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "No writing agents in this room." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-list", children: data.agents.map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-merge-agent" + (x.agentId === sel ? " is-on" : ""), onClick: () => {
3254
+ setSel(x.agentId);
3255
+ setConfirm(null);
3256
+ setResult(null);
3257
+ }, children: [
3258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: x.name, size: 24 }),
3259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-merge-agent-body", children: [
3260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: x.name }),
3261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: x.branch || (x.isolation === "locks" ? "shared workspace \xB7 locks" : "no worktree yet") })
3262
+ ] }),
3263
+ x.conflicts && x.conflicts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "danger", children: "conflict" }) : x.ahead ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Tag, { tone: "info", children: [
3264
+ x.files.length,
3265
+ " file",
3266
+ x.files.length === 1 ? "" : "s"
3267
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "quiet", children: "clean" })
3268
+ ] }, x.agentId)) }),
3269
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-main", children: ag ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-merge-head", children: [
3271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: ag.name }),
3273
+ " ",
3274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: ag.branch || "" }),
3275
+ ag.ahead ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-note", style: { margin: "0 0 0 8px" }, children: [
3276
+ ag.ahead,
3277
+ " commit(s) ahead"
3278
+ ] }) : null
3279
+ ] }),
3280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-actions", style: { margin: 0 }, children: confirm ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-confirm-text", children: confirm === "merge" ? `Merge ${ag.files.length} file(s) into the main workspace?` : `Discard ${ag.name}'s branch and worktree?` }),
3282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "outline", onClick: () => setConfirm(null), children: "Cancel" }),
3283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", disabled: busy, onClick: () => run(confirm), children: busy ? "Working\u2026" : confirm === "merge" ? "Confirm merge" : "Confirm discard" })
3284
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "ghost", className: "rm-danger", disabled: !ag.branch || busy, onClick: () => setConfirm("discard"), children: "Discard" }),
3286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeIcon, {}), disabled: !ag.ahead || busy, onClick: () => setConfirm("merge"), children: "Merge into main" })
3287
+ ] }) })
3288
+ ] }),
3289
+ result ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: result.ok ? "rm-ok" : "rm-error", children: result.text }) : null,
3290
+ ag.conflicts && ag.conflicts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-error", children: [
3291
+ "Would conflict with the main workspace: ",
3292
+ ag.conflicts.join(", "),
3293
+ " \u2014 resolve on the branch or discard."
3294
+ ] }) : null,
3295
+ ag.mainDirty && ag.mainDirty.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-error", children: [
3296
+ "Uncommitted changes in the main workspace touch: ",
3297
+ ag.mainDirty.join(", ")
3298
+ ] }) : null,
3299
+ ag.files && ag.files.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-merge-files", children: ag.files.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-merge-file", children: [
3300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-mono", children: f.path }),
3301
+ " ",
3302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-diff-addn", children: [
3303
+ "+",
3304
+ f.adds
3305
+ ] }),
3306
+ " ",
3307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-diff-deln", children: [
3308
+ "\u2212",
3309
+ f.dels
3310
+ ] })
3311
+ ] }, f.path)) }) : null,
3312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffView, { patch: ag.patch }),
3313
+ ag.truncated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rm-note", children: "Diff truncated." }) : null
3314
+ ] }) : null })
3315
+ ] }) })
3316
+ }
3317
+ );
3318
+ }
3319
+ function EmptyMain({ store }) {
2423
3320
  const a = store.actions;
2424
- 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: [
3321
+ const workspaces = useNativeWorkspaces(store);
3322
+ const first = workspaces[0];
3323
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-welcome-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome", children: [
2425
3324
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-badge", children: "Collab \xB7 E2EE \xB7 no accounts" }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Workspaces, then rooms" }),
2427
- /* @__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." }),
3325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "Rooms live in your workspaces" }),
3326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Your dsh workspaces stay in the left sidebar as usual. Hover a workspace there and click the room icon (next to \u201CNew session\u201D), or use its \u201C\u2026\u201D menu \u2192 New room. Peers join and point their agent at the same project; @mention or tasks wake agents." }),
2428
3327
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-welcome-actions", children: [
2429
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "workspace" }), children: [
2430
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderIcon, {}),
2431
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2432
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Bind workspace" }),
2433
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Local project path shared by agents in rooms" })
2434
- ] })
2435
- ] }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "create" }), children: [
3328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", disabled: !first, onClick: () => first && a.openDialog({ kind: "create", workspace: { id: first.workspaceId, path: first.path, title: wsTitle(first) } }), children: [
2437
3329
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {}),
2438
3330
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2439
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Create room" }),
2440
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "In the active workspace \xB7 Relay or LAN" })
3331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "New room" }),
3332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: first ? `In ${wsTitle(first)} \xB7 default relay 62.234.113.56:80` : "Add a workspace in the left sidebar first" })
2441
3333
  ] })
2442
3334
  ] }),
2443
3335
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "join" }), children: [
2444
3336
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkIcon, {}),
2445
3337
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2446
3338
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Join by invite" }),
2447
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://join/\u2026 invite" })
3339
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Paste a collab://\u2026 invite" })
2448
3340
  ] })
2449
3341
  ] }),
2450
3342
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-welcome-action", onClick: () => a.openDialog({ kind: "browse" }), children: [
@@ -2461,51 +3353,74 @@ function RoomsMain({ store }) {
2461
3353
  const s = useStore(store);
2462
3354
  const [drafts, setDrafts] = (0, import_react.useState)({});
2463
3355
  const room = s.rooms.find((r) => r.roomId === s.selected);
2464
- if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store });
3356
+ if (!room) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
3357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsNav, { store }),
3358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-main-body", children: [
3359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
3360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyMain, { store })
3361
+ ] })
3362
+ ] });
2465
3363
  const messages = s.messages[room.roomId] || [];
2466
3364
  const draft = drafts[room.roomId] || "";
2467
3365
  const setDraft = (v) => setDrafts((d) => ({ ...d, [room.roomId]: typeof v === "function" ? v(d[room.roomId] || "") : v }));
2468
3366
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-main", "data-collab-main": "", children: [
2469
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
2470
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
2471
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
2472
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
2473
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
2474
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2475
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
2476
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
2477
- room.members.filter((m) => m.online !== false).length,
2478
- " online \xB7 ",
2479
- room.public ? "public" : "private",
2480
- room.status !== "online" ? ` \xB7 room ${room.status}` : ""
2481
- ] })
2482
- ] }),
2483
- room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
2484
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
2485
- "Public"
2486
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
2487
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
2488
- "Private"
3367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoomsNav, { store }),
3368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-main-body", children: [
3369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApprovalBanner, { store }),
3370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-workspace", children: [
3371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rm-chat", children: [
3372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rm-chat-header", children: [
3373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: room.name, size: 28 }),
3374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
3375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-chat-header-title", children: room.name }),
3376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-chat-header-sub", children: [
3377
+ room.members.filter((m) => m.online !== false).length,
3378
+ " online",
3379
+ room.local && room.local.agents.length ? ` \xB7 ${room.local.agents.length} local agent${room.local.agents.length === 1 ? "" : "s"}` : "",
3380
+ " \xB7 ",
3381
+ room.public ? "public" : "private",
3382
+ room.status !== "online" ? ` \xB7 room ${room.status}` : ""
3383
+ ] })
3384
+ ] }),
3385
+ (() => {
3386
+ const act = (room.runs || []).filter((r) => r.state === "running" || r.state === "queued" || r.state === "approval");
3387
+ if (!act.length) return null;
3388
+ const running = act.filter((r) => r.state === "running").length;
3389
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rm-stop-btn", "data-rm-stop": "", title: "Stop all local agent runs", onClick: () => store.actions.stop(room.roomId).then((r) => store.actions.toast(`Stopped ${r.stopped} run(s)`)).catch((e) => store.actions.toast(e.message)), children: [
3390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}),
3391
+ "Stop \xB7 ",
3392
+ running,
3393
+ " running",
3394
+ act.length > running ? `, ${act.length - running} queued` : ""
3395
+ ] });
3396
+ })(),
3397
+ room.public ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge rm-badge-public", children: [
3398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlobeIcon, {}),
3399
+ "Public"
3400
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rm-badge", children: [
3401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LockIcon, {}),
3402
+ "Private"
3403
+ ] }),
3404
+ room.transport === "local" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge rm-badge-local", children: "Local only" }) : room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
3405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
2489
3406
  ] }),
2490
- room.transport === "lan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rm-badge", children: "LAN/VPS" }) : null,
2491
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: room.status === "online" ? "done" : room.status === "connecting" ? "ongoing" : "error", size: 8 })
3407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages, members: room.members }),
3408
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft, setDraft })
2492
3409
  ] }),
2493
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBody, { messages }),
2494
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Composer, { room, store, draft, setDraft })
2495
- ] }),
2496
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2497
- RoomDetail,
2498
- {
2499
- room,
2500
- messages,
2501
- store,
2502
- mention: (id) => setDraft((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
2503
- assign: (id) => {
2504
- setDraft(`@${id} `);
2505
- store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
3410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3411
+ RoomDetail,
3412
+ {
3413
+ room,
3414
+ messages,
3415
+ store,
3416
+ mention: (id) => setDraft((d) => `${d}${d && !d.endsWith(" ") ? " " : ""}@${id} `),
3417
+ assign: (id) => {
3418
+ setDraft(`@${id} `);
3419
+ store.actions.toast(`Switch to task mode, then describe the task for @${id}`);
3420
+ }
2506
3421
  }
2507
- }
2508
- )
3422
+ )
3423
+ ] })
2509
3424
  ] })
2510
3425
  ] });
2511
3426
  }
@@ -2516,17 +3431,21 @@ function Field({ label, hint, children, htmlFor }) {
2516
3431
  hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: hint }) : null
2517
3432
  ] });
2518
3433
  }
2519
- function CreateDialog({ store }) {
3434
+ function CreateDialog({ store, d }) {
2520
3435
  const s = useStore(store);
2521
3436
  const a = store.actions;
2522
- const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80" });
3437
+ const [f, setF] = (0, import_react.useState)({ name: "", description: "", tags: "", public: false, mode: "lan", lanHostPort: "62.234.113.56:80", localOnly: false, agents: [] });
2523
3438
  const [err, setErr] = (0, import_react.useState)(null);
2524
3439
  const [busy, setBusy] = (0, import_react.useState)(false);
2525
3440
  const [lanPeers, setLanPeers] = (0, import_react.useState)([]);
2526
3441
  const ok = f.name.trim() && !busy;
2527
- const ws = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0];
3442
+ const [ws, setWs] = (0, import_react.useState)(d && d.workspace ? d.workspace : null);
2528
3443
  const submit = async () => {
2529
3444
  if (!ok) return;
3445
+ if (!ws) {
3446
+ setErr("Pick a workspace");
3447
+ return;
3448
+ }
2530
3449
  setBusy(true);
2531
3450
  setErr(null);
2532
3451
  try {
@@ -2536,7 +3455,11 @@ function CreateDialog({ store }) {
2536
3455
  tags: f.tags,
2537
3456
  public: f.public,
2538
3457
  mode: f.mode,
2539
- lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0
3458
+ lanHostPort: f.mode === "lan" ? f.lanHostPort.trim() : void 0,
3459
+ workspacePath: ws.path,
3460
+ workspaceName: ws.title,
3461
+ localOnly: f.localOnly,
3462
+ agents: f.agents
2540
3463
  });
2541
3464
  } catch (e) {
2542
3465
  setErr(e.message);
@@ -2559,12 +3482,13 @@ function CreateDialog({ store }) {
2559
3482
  title: "Create room",
2560
3483
  closeLabel: "Close",
2561
3484
  className: "rm-modal-wide",
2562
- description: ws ? `In workspace \u201C${ws.name}\u201D. Default relay 62.234.113.56:80 \xB7 E2EE \xB7 keys stay local.` : "Bind a workspace first. Default relay 62.234.113.56:80.",
3485
+ description: ws ? `In workspace \u201C${ws.title}\u201D (${ws.path}). Agent cwd + MCP sandbox = this path. Default relay 62.234.113.56:80 \xB7 E2EE.` : "Pick a dsh workspace. Default relay 62.234.113.56:80 \xB7 E2EE.",
2563
3486
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2564
3487
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2565
3488
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !ok, onClick: submit, children: busy ? "Creating\u2026" : "Create" })
2566
3489
  ] }),
2567
3490
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form rm-form-wide", onKeyDown: (e) => e.key === "Enter" && e.target.tagName === "INPUT" && submit(), children: [
3491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Workspace", htmlFor: "rm-create-ws", hint: "Native dsh workspace this room belongs to.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, id: "rm-create-ws", value: ws, onChange: setWs }) }),
2568
3492
  /* @__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: [
2569
3493
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Avatar, { name: f.name || "room", size: 36 }),
2570
3494
  /* @__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 }) })
@@ -2585,12 +3509,29 @@ function CreateDialog({ store }) {
2585
3509
  ] }, p.hostPort))
2586
3510
  ] })
2587
3511
  ] }) }) : null,
3512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2 rm-local-box", children: [
3513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-switch-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Local agents" }),
3515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "Host several agents from this dsh as separate members (own identity, session, scope). Edit them later in room details." })
3516
+ ] }) }),
3517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-preset-row", children: [["moderator", "Moderator"], ["architect", "Architect"], ["coder", "Coder"], ["reviewer", "Reviewer"]].map(([k, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rm-preset rm-preset-check" + (f.agents.includes(k) ? " is-on" : ""), children: [
3518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: f.agents.includes(k), onChange: (e) => setF({ ...f, agents: e.target.checked ? [...f.agents, k] : f.agents.filter((x) => x !== k) }) }),
3519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: label })
3520
+ ] }, k)) }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-switch-row", children: [
3522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "Local only (no network)" }),
3524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: "In-process loopback \u2014 nothing leaves this machine. One click to switch to Relay/LAN later." })
3525
+ ] }),
3526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.localOnly, onChange: (v) => setF({ ...f, localOnly: v, public: v ? false : f.public }), label: "Local only" })
3527
+ ] })
3528
+ ] }),
2588
3529
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-span-2 rm-switch-row", children: [
2589
3530
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2590
3531
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-label", children: "List in public directory" }),
2591
3532
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-field-hint", children: f.public ? "Anyone can find and join \u2014 key is published with the listing." : "Private: invite only." })
2592
3533
  ] }),
2593
- /* @__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" })
3534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Switch, { checked: f.public, onChange: (v) => setF({ ...f, public: v, localOnly: v ? false : f.localOnly }), label: "List in public directory" })
2594
3535
  ] }),
2595
3536
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-span-2 rm-error", children: err }) : null
2596
3537
  ] })
@@ -2604,12 +3545,13 @@ function JoinDialog({ store }) {
2604
3545
  const [err, setErr] = (0, import_react.useState)(null);
2605
3546
  const [busy, setBusy] = (0, import_react.useState)(false);
2606
3547
  const valid = /^collab:\/\/(join|lan|relay)\/.+#\S+$/.test(invite.trim());
3548
+ const [ws, setWs] = (0, import_react.useState)(null);
2607
3549
  const submit = async () => {
2608
3550
  if (!valid || busy) return;
2609
3551
  setBusy(true);
2610
3552
  setErr(null);
2611
3553
  try {
2612
- await a.join(invite.trim());
3554
+ await a.join(invite.trim(), ws ? { workspacePath: ws.path, workspaceName: ws.title } : void 0);
2613
3555
  } catch (e) {
2614
3556
  setErr(e.message);
2615
3557
  setBusy(false);
@@ -2622,12 +3564,13 @@ function JoinDialog({ store }) {
2622
3564
  onClose: a.closeDialog,
2623
3565
  title: "Join room",
2624
3566
  closeLabel: "Close",
2625
- 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.",
3567
+ description: "Paste an invite and pick the dsh workspace (your checkout of the same project) the room attaches to.",
2626
3568
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2627
3569
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2628
3570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: !valid || busy, onClick: submit, children: busy ? "Joining\u2026" : "Join" })
2629
3571
  ] }),
2630
3572
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
3573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, value: ws, onChange: setWs }),
2631
3574
  /* @__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() }),
2632
3575
  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,
2633
3576
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
@@ -2817,9 +3760,10 @@ function WorkspaceDialog({ store }) {
2817
3760
  setBusy(true);
2818
3761
  setErr(null);
2819
3762
  try {
2820
- await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
3763
+ if (store.native && store.native.create) await store.native.create({ path: path.trim() });
3764
+ else await a.ensureWorkspace({ name: name2.trim() || void 0, path: path.trim() });
2821
3765
  a.closeDialog();
2822
- a.toast("Workspace ready \u2014 create a room to collaborate");
3766
+ a.toast("Workspace added \u2014 it appears in the left sidebar; use New room on it");
2823
3767
  } catch (e) {
2824
3768
  setErr(e.message);
2825
3769
  setBusy(false);
@@ -2830,18 +3774,14 @@ function WorkspaceDialog({ store }) {
2830
3774
  {
2831
3775
  open: true,
2832
3776
  onClose: a.closeDialog,
2833
- title: "Workspace",
3777
+ title: "Add workspace",
2834
3778
  closeLabel: "Close",
2835
- 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.",
3779
+ description: "Fallback: registers a dsh workspace (same as the native \u201CAdd workspace\u201D). It shows up in the left sidebar.",
2836
3780
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2837
3781
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Cancel" }),
2838
3782
  /* @__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" })
2839
3783
  ] }),
2840
3784
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-form", children: [
2841
- (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 () => {
2842
- await a.setActiveWorkspace(w.id);
2843
- a.closeDialog();
2844
- }, children: w.name }, w.id)) }) }) : null,
2845
3785
  /* @__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) }) }),
2846
3786
  /* @__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() }) }),
2847
3787
  err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rm-error", children: err }) : null
@@ -2854,7 +3794,7 @@ function BrowseDialog({ store }) {
2854
3794
  const a = store.actions;
2855
3795
  const [joining, setJoining] = (0, import_react.useState)(null);
2856
3796
  const inputRef = (0, import_react.useRef)(null);
2857
- const activeWs = (s.workspaces || []).find((w) => w.id === s.activeWorkspaceId) || (s.workspaces || [])[0];
3797
+ const [activeWs, setActiveWs] = (0, import_react.useState)(null);
2858
3798
  (0, import_react.useEffect)(() => {
2859
3799
  a.setSearchOpen(true);
2860
3800
  requestAnimationFrame(() => inputRef.current?.focus());
@@ -2864,7 +3804,7 @@ function BrowseDialog({ store }) {
2864
3804
  const join = async (r) => {
2865
3805
  setJoining(r.roomId);
2866
3806
  try {
2867
- await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspaceId: activeWs?.id });
3807
+ await a.join(r.invite, { name: r.name, description: r.description, tags: r.tags, public: true, workspacePath: activeWs?.path, workspaceName: activeWs?.title });
2868
3808
  } catch (e) {
2869
3809
  a.toast(e.message);
2870
3810
  } finally {
@@ -2879,9 +3819,10 @@ function BrowseDialog({ store }) {
2879
3819
  title: "Browse public rooms",
2880
3820
  closeLabel: "Close",
2881
3821
  className: "rm-modal-wide",
2882
- description: "Directory on VPS 62.234.113.56:80. Search by name or #tag, then join into the active workspace.",
3822
+ description: "Directory on VPS 62.234.113.56:80. Search by name or #tag; joined rooms attach to the workspace below.",
2883
3823
  footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", onClick: a.closeDialog, children: "Close" }),
2884
3824
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-root rm-browse", children: [
3825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSelect, { store, value: activeWs, onChange: setActiveWs }),
2885
3826
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rm-search rm-browse-search", children: [
2886
3827
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
2887
3828
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -2925,13 +3866,15 @@ function Overlay({ store }) {
2925
3866
  const s = useStore(store);
2926
3867
  const d = s.dialog;
2927
3868
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2928
- d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store }) : null,
3869
+ d?.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CreateDialog, { store, d }) : null,
2929
3870
  d?.kind === "join" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JoinDialog, { store }) : null,
2930
3871
  d?.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InviteDialog, { store, d }) : null,
2931
3872
  d?.kind === "leave" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LeaveDialog, { store, d }) : null,
2932
3873
  d?.kind === "config" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigDialog, { store, d }) : null,
2933
3874
  d?.kind === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceDialog, { store }) : null,
2934
3875
  d?.kind === "browse" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BrowseDialog, { store }) : null,
3876
+ d?.kind === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AgentDialog, { store, d }) : null,
3877
+ d?.kind === "merge" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MergeDialog, { store, d }) : null,
2935
3878
  s.toast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: s.toast, onDone: store.actions.clearToast, holdMs: 2200 }) : null
2936
3879
  ] });
2937
3880
  }
@@ -2944,8 +3887,118 @@ function installStyles() {
2944
3887
  document.head.appendChild(style);
2945
3888
  return () => style.remove();
2946
3889
  }
3890
+ var ROOM_SVG = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.15" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="3" width="11" height="10" rx="2"/><path d="M5.5 6.5h5M5.5 9.5h3.5"/></svg>';
3891
+ function installWorkspaceDecorator(store, native, onNewRoom) {
3892
+ if (typeof document === "undefined" || !native) return () => {
3893
+ };
3894
+ const wsFor = (key) => (native.list.getSnapshot().items || []).find((w) => w.workspaceId === key);
3895
+ const rowKey = (row) => (row.getAttribute("data-row-key") || "").slice("workspace:".length);
3896
+ const open = (key) => {
3897
+ const w = wsFor(key);
3898
+ if (w) onNewRoom({ id: w.workspaceId, path: w.path, title: wsTitle(w) });
3899
+ };
3900
+ const decorate = () => {
3901
+ for (const row of document.querySelectorAll('[data-row-key^="workspace:"]')) {
3902
+ const key = rowKey(row);
3903
+ if (!key || row.querySelector("[data-rm-ws-new-room]")) continue;
3904
+ const newSess = row.querySelector('button[aria-label^="New session in"]');
3905
+ if (!newSess) continue;
3906
+ let anchor = newSess;
3907
+ while (anchor.parentElement && anchor.parentElement !== row && !anchor.parentElement.querySelector('button[aria-label^="Workspace actions"]')) anchor = anchor.parentElement;
3908
+ if (!anchor.parentElement) continue;
3909
+ const label = (newSess.getAttribute("aria-label") || "").replace(/^New session in /, "");
3910
+ const btn = document.createElement("button");
3911
+ btn.type = "button";
3912
+ btn.className = newSess.className;
3913
+ btn.setAttribute("data-rm-ws-new-room", key);
3914
+ btn.setAttribute("aria-label", `New room in ${label}`);
3915
+ btn.title = "New room";
3916
+ btn.innerHTML = ROOM_SVG;
3917
+ btn.addEventListener("click", (e) => {
3918
+ e.preventDefault();
3919
+ e.stopPropagation();
3920
+ open(key);
3921
+ });
3922
+ anchor.parentElement.insertBefore(btn, anchor);
3923
+ }
3924
+ };
3925
+ let pendingKey = null;
3926
+ const onClickCapture = (e) => {
3927
+ const trigger = e.target instanceof Element ? e.target.closest('button[aria-label^="Workspace actions for"]') : null;
3928
+ if (!trigger) return;
3929
+ const row = trigger.closest('[data-row-key^="workspace:"]');
3930
+ pendingKey = row ? rowKey(row) : null;
3931
+ if (!pendingKey) return;
3932
+ const key = pendingKey;
3933
+ let tries = 0;
3934
+ const augment = () => {
3935
+ const menus = [...document.querySelectorAll('[role="menu"]')].filter((m) => m.querySelector('[role="menuitem"]'));
3936
+ const menu = menus[menus.length - 1];
3937
+ if (!menu) {
3938
+ if (++tries < 10) requestAnimationFrame(augment);
3939
+ return;
3940
+ }
3941
+ if (menu.querySelector("[data-rm-ws-menu]")) return;
3942
+ const tpl = menu.querySelector('[role="menuitem"]');
3943
+ const mk = (text, svg, onPick) => {
3944
+ const item = document.createElement("button");
3945
+ item.type = "button";
3946
+ item.setAttribute("role", "menuitem");
3947
+ item.setAttribute("data-rm-ws-menu", text);
3948
+ item.className = tpl.className;
3949
+ item.innerHTML = `<span style="display:inline-flex;width:16px;justify-content:center">${svg}</span><span>${text}</span>`;
3950
+ item.style.display = "flex";
3951
+ item.style.alignItems = "center";
3952
+ item.style.gap = "8px";
3953
+ item.addEventListener("click", (ev) => {
3954
+ ev.preventDefault();
3955
+ ev.stopPropagation();
3956
+ trigger.click();
3957
+ onPick();
3958
+ });
3959
+ return item;
3960
+ };
3961
+ const newSessSvg = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.15" stroke-linecap="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8h9"/></svg>';
3962
+ const first = menu.firstElementChild;
3963
+ menu.insertBefore(mk("New session", newSessSvg, () => {
3964
+ const row2 = document.querySelector(`[data-row-key="workspace:${CSS.escape(key)}"]`);
3965
+ row2?.querySelector('button[aria-label^="New session in"]')?.click();
3966
+ }), first);
3967
+ menu.insertBefore(mk("New room", ROOM_SVG, () => open(key)), first);
3968
+ };
3969
+ requestAnimationFrame(augment);
3970
+ };
3971
+ let raf = 0;
3972
+ const schedule = () => {
3973
+ if (!raf) raf = requestAnimationFrame(() => {
3974
+ raf = 0;
3975
+ decorate();
3976
+ });
3977
+ };
3978
+ const mo = new MutationObserver(schedule);
3979
+ mo.observe(document.body, { childList: true, subtree: true });
3980
+ document.addEventListener("click", onClickCapture, true);
3981
+ schedule();
3982
+ return () => {
3983
+ mo.disconnect();
3984
+ document.removeEventListener("click", onClickCapture, true);
3985
+ if (raf) cancelAnimationFrame(raf);
3986
+ for (const b of document.querySelectorAll("[data-rm-ws-new-room]")) b.remove();
3987
+ };
3988
+ }
2947
3989
  function apply(ctx) {
2948
3990
  const store = createRoomsStore();
3991
+ let native = null;
3992
+ try {
3993
+ native = ctx.get ? ctx.get("workspaces") : ctx.workspaces;
3994
+ } catch {
3995
+ native = ctx.workspaces || null;
3996
+ }
3997
+ store.native = native || null;
3998
+ store.nativeList = native && native.list ? {
3999
+ subscribe: (listener) => native.list.subscribe(listener),
4000
+ getSnapshot: () => native.list.getSnapshot()
4001
+ } : null;
2949
4002
  ctx.effect(() => installStyles(), "dsh-room: styles");
2950
4003
  ctx.effect(() => () => store.dispose(), "dsh-room: store");
2951
4004
  const info = () => ctx.layout.panelInfo;
@@ -2955,27 +4008,12 @@ function apply(ctx) {
2955
4008
  } catch {
2956
4009
  }
2957
4010
  };
4011
+ store.onRoomOpened = ensurePanel;
2958
4012
  ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: PANEL_ID, order: 11, label: () => "DSH Room" }, PanelIcon));
2959
4013
  ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, inject: () => ({ store }) }, RoomsMain));
2960
4014
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: "dsh-room-dialogs", inject: () => ({ store }) }, Overlay));
2961
- ctx.slots.inject("sidebar.workspaces", () => {
2962
- let dispose;
2963
- const reconcile = () => {
2964
- const active = info().getSnapshot().activePanelId === PANEL_ID;
2965
- if (active && !dispose) dispose = ctx.slots.register({ name: "sidebar.workspaces", priority: -100, inject: () => ({ store, ensurePanel }) }, RoomsSidebar);
2966
- else if (!active && dispose) {
2967
- dispose();
2968
- dispose = void 0;
2969
- }
2970
- };
2971
- const off = info().subscribe(reconcile);
2972
- reconcile();
2973
- return () => {
2974
- off();
2975
- if (dispose) dispose();
2976
- dispose = void 0;
2977
- };
2978
- });
4015
+ ctx.slots.inject("sidebar.session.row.leading", () => ctx.slots.register({ name: "sidebar.session.row.leading", id: "dsh-room-session-mark", inject: () => ({ store }) }, RoomSessionMark));
4016
+ ctx.effect(() => installWorkspaceDecorator(store, native, (workspace) => store.actions.openDialog({ kind: "create", workspace })), "dsh-room: workspace New room");
2979
4017
  }
2980
4018
 
2981
4019
  return module.exports;