@things-factory/ai-assistant 10.1.79 → 10.1.81
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/client/components/assistant-chat.ts +102 -13
- package/client/components/assistant-dock.ts +160 -18
- package/client/components/assistant-mode.test.ts +16 -2
- package/client/components/assistant-session-manager.ts +52 -25
- package/client/components/assistant-session-toolbar.ts +4 -2
- package/client/components/chat-echo-dedup.ts +4 -0
- package/client/components/proposal-outcome-line.ts +36 -0
- package/client/components/proposal-request.test.ts +52 -0
- package/client/utils/assistant-navigation.test.ts +38 -0
- package/client/utils/assistant-navigation.ts +6 -0
- package/client/utils/assistant-surface.ts +21 -0
- package/dist-client/components/assistant-chat.d.ts +15 -0
- package/dist-client/components/assistant-chat.js +106 -13
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +13 -0
- package/dist-client/components/assistant-dock.js +175 -20
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-mode.test.js +14 -2
- package/dist-client/components/assistant-mode.test.js.map +1 -1
- package/dist-client/components/assistant-session-manager.d.ts +4 -0
- package/dist-client/components/assistant-session-manager.js +68 -26
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/components/assistant-session-toolbar.d.ts +1 -0
- package/dist-client/components/assistant-session-toolbar.js +8 -2
- package/dist-client/components/assistant-session-toolbar.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.d.ts +2 -0
- package/dist-client/components/chat-echo-dedup.js +2 -0
- package/dist-client/components/chat-echo-dedup.js.map +1 -1
- package/dist-client/components/proposal-outcome-line.d.ts +29 -0
- package/dist-client/components/proposal-outcome-line.js +15 -0
- package/dist-client/components/proposal-outcome-line.js.map +1 -1
- package/dist-client/components/proposal-request.test.d.ts +1 -0
- package/dist-client/components/proposal-request.test.js +45 -0
- package/dist-client/components/proposal-request.test.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-navigation.d.ts +6 -0
- package/dist-client/utils/assistant-navigation.js +9 -0
- package/dist-client/utils/assistant-navigation.js.map +1 -0
- package/dist-client/utils/assistant-navigation.test.d.ts +1 -0
- package/dist-client/utils/assistant-navigation.test.js +37 -0
- package/dist-client/utils/assistant-navigation.test.js.map +1 -0
- package/dist-client/utils/assistant-surface.d.ts +19 -0
- package/dist-client/utils/assistant-surface.js.map +1 -1
- package/dist-server/service/assistant-chat-resolver.js +1 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/chat-message/chat-message.d.ts +10 -0
- package/dist-server/service/chat-message/chat-message.js +29 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-message/proposal-outcome.d.ts +8 -0
- package/dist-server/service/chat-message/proposal-outcome.js +35 -1
- package/dist-server/service/chat-message/proposal-outcome.js.map +1 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +7 -0
- package/dist-server/service/chat-session/session-inbox.js +7 -0
- package/dist-server/service/chat-session/session-inbox.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +1 -0
- package/dist-server/service/chat-session-resolver.js +63 -9
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/service/assistant-chat-resolver.ts +1 -0
- package/server/service/chat-message/chat-message.ts +32 -0
- package/server/service/chat-message/proposal-outcome.test.ts +53 -2
- package/server/service/chat-message/proposal-outcome.ts +33 -0
- package/server/service/chat-session/session-inbox.ts +7 -0
- package/server/service/chat-session-resolver.ts +71 -12
- package/test/assistant-dock.test.ts +8 -3
- package/test/assistant-session-toolbar.test.ts +7 -1
- package/test/open-doors.test.ts +2 -1
- package/test/session-discovery.test.ts +42 -0
- package/translations/en.json +26 -1
- package/translations/ja.json +26 -1
- package/translations/ko.json +26 -1
- package/translations/ms.json +26 -1
- package/translations/zh.json +26 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-session-manager.js","sourceRoot":"","sources":["../../client/components/assistant-session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\n\nimport {\n assistantSessionAdmin,\n sessionActions,\n type AssistantSessionAdmin,\n type AssistantSessionEntry,\n type AssistantSessionFilters\n} from '../utils/assistant-session-admin.js'\n\nconst PAGE = 30\n\n\nconst t = (key: string, defaultValue: string, options: Record<string, unknown> = {}) =>\n i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options })\n\n/**\n * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.\n *\n * Search, a filter (all / mine / including hidden), paging, and on each row: open, rename, hide or\n * unhide, private ⇄ public, delete. Taken from operato-twin's dock, where it was written first, so every\n * dock gets it by placing this element (typically behind the toolbar's \"All History\").\n *\n * ── What is the host's ──────────────────────────────────────────────────────────\n * Opening and starting a conversation belong to the dock's session controller, so this element asks\n * with the toolbar's own events and does not open anything itself:\n * assistant-session-select { sessionId } open that conversation\n * assistant-session-reset start a new one\n * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' }\n * a row changed - the host refreshes its tabs; if the open\n * conversation was removed or hidden, it moves off it\n *\n * ── What is the server's ────────────────────────────────────────────────────────\n * Who may do what (`sessionActions`): rename any participant, hide anyone for their own list, delete\n * the creator, visibility the owner. The buttons follow those hints; the doors decide again.\n *\n * ── Asking before what cannot be undone ─────────────────────────────────────────\n * Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].\n * Making it private, hiding and renaming do not: each can be undone from the same row.\n */\n@customElement('ox-assistant-session-manager')\nexport class AssistantSessionManager extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: 12.5px;\n color: var(--md-sys-color-on-surface);\n }\n\n .bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n .bar input,\n .bar select {\n min-width: 0;\n height: 28px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 8px;\n }\n\n .bar input {\n flex: 1;\n }\n\n .new {\n flex: none;\n border: 0;\n border-radius: 6px;\n padding: 4px 8px;\n background: transparent;\n color: var(--md-sys-color-primary);\n font: inherit;\n font-weight: 650;\n cursor: pointer;\n }\n\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 10px;\n padding: 6px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11.5px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .filters select {\n height: 26px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n }\n\n .filters label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n cursor: pointer;\n }\n\n .kind {\n color: var(--md-sys-color-primary);\n }\n\n .list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 9px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n cursor: pointer;\n }\n\n .row:hover,\n .row.current {\n background: var(--md-sys-color-surface-container);\n }\n\n .row.hidden {\n opacity: 0.6;\n }\n\n .dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n }\n\n .row.unread .dot {\n background: var(--md-sys-color-primary);\n }\n\n .row md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .body {\n flex: 1;\n min-width: 0;\n }\n\n .title,\n .prev {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .row.unread .title {\n font-weight: 700;\n }\n\n .prev {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 11px;\n }\n\n /* Row actions show on hover and on keyboard focus - the list's body is the names. */\n .acts {\n display: none;\n gap: 2px;\n }\n\n .row:hover .acts,\n .row:focus-within .acts {\n display: flex;\n }\n\n button.icon {\n display: inline-flex;\n padding: 3px;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n\n button.icon:hover {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n\n button.icon.danger:hover {\n color: var(--md-sys-color-error);\n }\n\n .rename {\n flex: 1;\n min-width: 0;\n height: 26px;\n border: 1px solid var(--md-sys-color-primary);\n border-radius: 6px;\n padding: 0 6px;\n font: inherit;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n }\n\n .ask {\n display: grid;\n gap: 6px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background: var(--md-sys-color-surface-container);\n }\n\n .ask .buttons {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .ask button {\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n\n .ask button.ok {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n .ask button.ok.danger {\n border-color: var(--md-sys-color-error);\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n }\n\n .note,\n .empty {\n padding: 14px 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [role='alert'] {\n color: var(--md-sys-color-error);\n }\n\n .more,\n .hiddenfilter {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .hiddenfilter md-icon {\n --md-icon-size: 16px;\n }\n `\n\n /** The anchor whose conversations are listed - the same one the dock's session controller opened. */\n @property({ type: String }) anchorType = ''\n @property({ type: String }) anchorId = ''\n /** The conversation the dock has open - marked in the list. */\n @property({ type: String }) sessionId = ''\n /** The doors. Replaceable for tests; a host normally leaves it. */\n @property({ attribute: false }) admin: AssistantSessionAdmin = assistantSessionAdmin\n /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */\n @property({ type: String }) kind = ''\n /** kind → the words people know it by (the modes' labels), for each row. */\n @property({ attribute: false }) kindLabels: Record<string, string> = {}\n\n @state() private entries: AssistantSessionEntry[] = []\n @state() private total = 0\n @state() private hiddenCount = 0\n @state() private search = ''\n @state() private allKinds = false\n @state() private whose: NonNullable<AssistantSessionFilters['whose']> = 'all'\n @state() private visibility: NonNullable<AssistantSessionFilters['visibility']> = 'all'\n @state() private includeHidden = false\n @state() private unreadOnly = false\n @state() private pageSize = PAGE\n @state() private loading = false\n @state() private error = ''\n @state() private renamingId = ''\n\n private generation = 0\n private searchTimer?: ReturnType<typeof setTimeout>\n\n /** Read the list again - the host calls this after it opened or started a conversation. */\n async reload(): Promise<void> {\n const generation = ++this.generation\n if (!this.anchorType || !this.anchorId) {\n this.entries = []\n this.total = this.hiddenCount = 0\n return\n }\n\n this.loading = true\n this.error = ''\n try {\n /*\n * Paging reads again from the start rather than appending - live activity reorders the list, and\n * appending a later page to an earlier order duplicates and drops rows.\n */\n const page = await this.admin.inbox({\n anchorType: this.anchorType,\n anchorId: this.anchorId,\n search: this.search,\n filters: {\n kind: this.allKinds ? '' : this.kind,\n whose: this.whose,\n visibility: this.visibility,\n includeHidden: this.includeHidden,\n unreadOnly: this.unreadOnly\n },\n limit: this.pageSize,\n offset: 0\n })\n if (generation !== this.generation) return\n this.entries = page.entries\n this.total = page.total\n this.hiddenCount = page.hiddenCount\n } catch (error) {\n if (generation !== this.generation) return\n this.entries = []\n this.total = this.hiddenCount = 0\n this.error = (error as Error).message || t('failed', 'could not load the conversation list.')\n } finally {\n if (generation === this.generation) this.loading = false\n }\n }\n\n protected updated(changed: PropertyValues) {\n if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind')) void this.reload()\n\n /*\n * The rename box takes the keys the moment it opens, with the name selected. `autofocus` works once\n * per document, so the second rename opened a box that ignored the typing (2026-09-24, operato-ecs).\n */\n if (changed.has('renamingId') && this.renamingId) {\n const input = this.renderRoot.querySelector('input.rename') as HTMLInputElement | null\n input?.focus()\n input?.select()\n }\n }\n\n disconnectedCallback() {\n this.generation++\n clearTimeout(this.searchTimer)\n super.disconnectedCallback()\n }\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n /* ── what the person does ────────────────────────────────────────────────────────── */\n\n private onSearch(value: string) {\n this.search = value\n clearTimeout(this.searchTimer)\n /* Not on every keystroke. A new search starts from the first page. */\n this.searchTimer = setTimeout(() => {\n this.pageSize = PAGE\n void this.reload()\n }, 200)\n }\n\n /** A filter changed - the list starts from its first page again. */\n private refilter(change: () => void) {\n change()\n this.pageSize = PAGE\n void this.reload()\n }\n\n private loadMore() {\n this.pageSize += PAGE\n void this.reload()\n }\n\n /** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */\n private async act(entry: AssistantSessionEntry, change: string, run: () => Promise<boolean>, refused: string) {\n this.renamingId = ''\n try {\n if (!(await run())) this.error = refused\n else this.emit('assistant-session-changed', { sessionId: entry.id, change })\n } catch (error) {\n /* Never pass a failure silently - a person must not believe a conversation was deleted when it was not. */\n this.error = (error as Error).message\n }\n const error = this.error\n await this.reload()\n if (error && !this.error) this.error = error\n }\n\n private rename(entry: AssistantSessionEntry, name: string) {\n const trimmed = name.trim()\n this.renamingId = ''\n if (!trimmed || trimmed === entry.name) return\n void this.act(entry, 'renamed', () => this.admin.rename(entry.id, trimmed), t('rename-refused', 'this conversation could not be renamed.'))\n }\n\n private setHidden(entry: AssistantSessionEntry, hidden: boolean) {\n void this.act(entry, hidden ? 'hidden' : 'unhidden', () => this.admin.hide(entry.id, hidden), t('hide-refused', 'this conversation could not be hidden.'))\n }\n\n private setVisibility(entry: AssistantSessionEntry, visibility: 'open' | 'private') {\n void this.act(\n entry,\n 'visibility',\n () => this.admin.setVisibility(entry.id, visibility),\n t('visibility-refused', 'only the owner of this conversation can change who reads it.')\n )\n }\n\n private _removeSession(entry: AssistantSessionEntry) {\n void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'))\n }\n\n /* ── drawing ─────────────────────────────────────────────────────────────────────── */\n\n render(): TemplateResult {\n const shown = this.entries.length\n\n return html`\n <div class=\"bar\">\n <input\n .value=${this.search}\n placeholder=${t('search', 'Search names and last messages')}\n aria-label=${t('search', 'Search names and last messages')}\n @input=${(e: Event) => this.onSearch((e.target as HTMLInputElement).value)}\n @keydown=${(e: KeyboardEvent) => e.key === 'Escape' && this.onSearch('')}\n />\n <button class=\"new\" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>\n </div>\n <div class=\"filters\">\n ${this.kind\n ? html`<select aria-label=${t('filter-kind', 'Which kind')} .value=${this.allKinds ? 'all' : 'this'}\n @change=${(e: Event) => this.refilter(() => (this.allKinds = (e.target as HTMLSelectElement).value === 'all'))}>\n <option value=\"this\">${this.kindLabels[this.kind] || t('filter-this-kind', 'This conversation')}</option>\n <option value=\"all\">${t('filter-all-kinds', 'All kinds here')}</option>\n </select>`\n : nothing}\n <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}\n @change=${(e: Event) => this.refilter(() => (this.whose = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-all', 'All')}</option>\n <option value=\"mine\">${t('filter-mine', 'Started by me')}</option>\n <option value=\"participant\">${t('filter-participant', \"I'm in\")}</option>\n </select>\n <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}\n @change=${(e: Event) => this.refilter(() => (this.visibility = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-any-visibility', 'Public and private')}</option>\n <option value=\"open\">${t('filter-open', 'Public')}</option>\n <option value=\"private\">${t('filter-private', 'Private')}</option>\n </select>\n <label><input type=\"checkbox\" .checked=${this.unreadOnly}\n @change=${(e: Event) => this.refilter(() => (this.unreadOnly = (e.target as HTMLInputElement).checked))} />${t('filter-unread', 'Unread')}</label>\n <label><input type=\"checkbox\" .checked=${this.includeHidden}\n @change=${(e: Event) => this.refilter(() => (this.includeHidden = (e.target as HTMLInputElement).checked))} />${t('filter-hidden', 'Including hidden')}</label>\n </div>\n <div class=\"list\">\n ${this.error ? html`<div class=\"note\" role=\"alert\">${this.error}</div>` : nothing}\n ${shown === 0 && !this.loading\n ? html`<div class=\"empty\">${this.search ? t('no-match', 'No conversation matches \"{q}\".', { q: this.search }) : t('none', 'No conversations yet.')}</div>`\n : this.entries.map(entry => this.renderRow(entry))}\n ${shown < this.total\n ? html`<button class=\"more\" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`\n : nothing}\n </div>\n <!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->\n ${this.hiddenCount > 0 && !this.includeHidden\n ? html`<button class=\"hiddenfilter\" @click=${() => this.refilter(() => (this.includeHidden = true))}>\n <md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}\n </button>`\n : nothing}\n `\n }\n\n private renderRow(entry: AssistantSessionEntry): TemplateResult {\n const name = entry.name || t('untitled', 'Untitled conversation')\n const actions = sessionActions(entry)\n const isPrivate = entry.visibility === 'private'\n\n if (this.renamingId === entry.id) {\n return html`<div class=\"row\">\n <span class=\"dot\"></span>\n <input\n class=\"rename\"\n .value=${entry.name || ''}\n aria-label=${t('rename', 'Rename')}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter') this.rename(entry, (e.target as HTMLInputElement).value)\n if (e.key === 'Escape') this.renamingId = ''\n }}\n @blur=${(e: Event) => this.rename(entry, (e.target as HTMLInputElement).value)}\n />\n </div>`\n }\n\n return html`<div\n class=\"row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''}\"\n @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}\n >\n <span class=\"dot\"></span>\n <md-icon title=${isPrivate ? t('private', 'Private - only its participants read it') : t('open', 'Public - everyone in this domain reads it')}\n >${isPrivate ? 'lock' : 'public'}</md-icon\n >\n <span class=\"body\">\n <div class=\"title\" @dblclick=${(e: Event) => { e.stopPropagation(); if (actions.rename) this.renamingId = entry.id }}>${name}</div>\n <div class=\"prev\">${this.allKinds && entry.kind ? html`<span class=\"kind\">${this.kindLabels[entry.kind] || entry.kind}</span> · ` : nothing}${entry.lastMessagePreview || ''}</div>\n </span>\n <span class=\"acts\" @click=${(e: Event) => e.stopPropagation()}>\n ${actions.rename\n ? html`<button class=\"icon\" title=${t('rename', 'Rename')} aria-label=${t('rename', 'Rename')} @click=${() => (this.renamingId = entry.id)}>\n <md-icon>edit</md-icon>\n </button>`\n : nothing}\n ${actions.setVisibility\n ? isPrivate\n ? html`<button class=\"icon\" title=${t('make-open', 'Make public')} aria-label=${t('make-open', 'Make public')}\n @click=${() => this.askOpen(entry, name)}><md-icon>public</md-icon></button>`\n : html`<button class=\"icon\" title=${t('make-private', 'Make private')} aria-label=${t('make-private', 'Make private')}\n @click=${() => this.setVisibility(entry, 'private')}><md-icon>lock</md-icon></button>`\n : nothing}\n <button\n class=\"icon\"\n title=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n aria-label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n @click=${() => this.setHidden(entry, !entry.hidden)}\n >\n <md-icon>${actions.unhide ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n ${actions.remove\n ? html`<button class=\"icon danger\" title=${t('remove', 'Delete')} aria-label=${t('remove', 'Delete')}\n @click=${() => this.askRemove(entry, name)}><md-icon>delete</md-icon></button>`\n : nothing}\n </span>\n </div>`\n }\n\n /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */\n private async ask(title: string, text: string, confirmText: string): Promise<boolean> {\n return !!(await OxPrompt.open({\n type: 'warning',\n title,\n text,\n confirmButton: { text: confirmText },\n cancelButton: { text: t('cancel', 'Cancel') }\n }))\n }\n\n private async askRemove(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('remove', 'Delete'),\n t('confirm-remove', 'Delete \"{name}\"? It disappears for every participant and cannot be brought back.', { name }),\n t('remove', 'Delete')\n )\n if (ok) this._removeSession(entry)\n }\n\n private async askOpen(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-open', 'Make public'),\n t('confirm-open', 'Make \"{name}\" public? Everyone in this domain will be able to read what is already written.', { name }),\n t('make-open', 'Make public')\n )\n if (ok) this.setVisibility(entry, 'open')\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assistant-session-manager.js","sourceRoot":"","sources":["../../client/components/assistant-session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\n\nimport {\n assistantSessionAdmin,\n sessionActions,\n type AssistantSessionAdmin,\n type AssistantSessionEntry,\n type AssistantSessionFilters\n} from '../utils/assistant-session-admin.js'\n\nconst PAGE = 30\n\n\nconst t = (key: string, defaultValue: string, options: Record<string, unknown> = {}) =>\n i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options })\n\n/**\n * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.\n *\n * Search, a filter (all / mine / including hidden), paging, and on each row: open, rename, hide or\n * unhide, private ⇄ public, delete. Taken from operato-twin's dock, where it was written first, so every\n * dock gets it by placing this element (typically behind the toolbar's \"All History\").\n *\n * ── What is the host's ──────────────────────────────────────────────────────────\n * Opening and starting a conversation belong to the dock's session controller, so this element asks\n * with the toolbar's own events and does not open anything itself:\n * assistant-session-select { sessionId } open that conversation\n * assistant-session-reset start a new one\n * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' }\n * a row changed - the host refreshes its tabs; if the open\n * conversation was removed or hidden, it moves off it\n *\n * ── What is the server's ────────────────────────────────────────────────────────\n * Who may do what (`sessionActions`): rename any participant, hide anyone for their own list, delete\n * the creator, visibility the owner. The buttons follow those hints; the doors decide again.\n *\n * ── Asking before what cannot be undone ─────────────────────────────────────────\n * Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].\n * Making it private, hiding and renaming do not: each can be undone from the same row.\n */\n@customElement('ox-assistant-session-manager')\nexport class AssistantSessionManager extends LitElement {\n static styles = css`\n .scope-label { padding: 12px 12px 0; font-size: 14px; font-weight: 600; }\n .filter-toggle, .reset { margin: 6px 10px; padding: 7px; font: inherit; background: transparent; color: inherit; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 6px; cursor: pointer; }\n .filter-toggle { align-self: flex-end; }\n .filters[hidden] { display: none; }\n .open-row { display: block; flex: 1; min-width: 0; text-align: start; border: 0; background: transparent; color: inherit; font: inherit; padding: 4px; cursor: pointer; }\n .meta { display: block; margin-top: 5px; font-size: 12px; color: var(--md-sys-color-on-surface-variant); }\n .actions summary { cursor: pointer; padding: 8px; font-size: 18px; list-style: none; }\n .actions[open] .acts { display: flex; flex-direction: column; position: absolute; right: 10px; top: 40px; z-index: 2; min-width: 180px; padding: 6px; background: var(--md-sys-color-surface-container-lowest); border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px; box-shadow: 0 4px 16px #0002; }\n .actions button.icon { min-height: 40px; padding: 8px 12px; font: inherit; text-align: start; }\n .actions button.danger { color: var(--md-sys-color-error, #b42318); }\n button:focus-visible, summary:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: 14px;\n color: var(--md-sys-color-on-surface);\n }\n\n .bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n .bar input,\n .bar select {\n min-width: 0;\n height: 28px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 8px;\n }\n\n .bar input {\n flex: 1;\n }\n\n .new {\n flex: none;\n border: 0;\n border-radius: 6px;\n padding: 4px 8px;\n background: transparent;\n color: var(--md-sys-color-primary);\n font: inherit;\n font-weight: 650;\n cursor: pointer;\n }\n\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 10px;\n padding: 6px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11.5px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .filters select {\n height: 26px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n }\n\n .filters label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n cursor: pointer;\n }\n\n .kind {\n color: var(--md-sys-color-primary);\n }\n\n .list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 9px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n cursor: pointer;\n }\n\n .row:hover,\n .row.current {\n background: var(--md-sys-color-surface-container);\n }\n\n .row.hidden {\n opacity: 0.6;\n }\n\n .dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n }\n\n .row.unread .dot {\n background: var(--md-sys-color-primary);\n }\n\n .row md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .body {\n flex: 1;\n min-width: 0;\n }\n\n .title,\n .prev {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .row.unread .title {\n font-weight: 700;\n }\n\n .prev {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 11px;\n }\n\n /* Row actions show on hover and on keyboard focus - the list's body is the names. */\n .acts {\n display: none;\n gap: 2px;\n }\n\n button.icon {\n display: inline-flex;\n padding: 3px;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n\n button.icon:hover {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n\n button.icon.danger:hover {\n color: var(--md-sys-color-error);\n }\n\n .rename {\n flex: 1;\n min-width: 0;\n height: 26px;\n border: 1px solid var(--md-sys-color-primary);\n border-radius: 6px;\n padding: 0 6px;\n font: inherit;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n }\n\n .ask {\n display: grid;\n gap: 6px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background: var(--md-sys-color-surface-container);\n }\n\n .ask .buttons {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .ask button {\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n\n .ask button.ok {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n .ask button.ok.danger {\n border-color: var(--md-sys-color-error);\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n }\n\n .note,\n .empty {\n padding: 14px 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [role='alert'] {\n color: var(--md-sys-color-error);\n }\n\n .more,\n .hiddenfilter {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .hiddenfilter md-icon {\n --md-icon-size: 16px;\n }\n `\n\n /** The anchor whose conversations are listed - the same one the dock's session controller opened. */\n @property({ type: String }) anchorType = ''\n @property({ type: String }) anchorId = ''\n /** The conversation the dock has open - marked in the list. */\n @property({ type: String }) sessionId = ''\n @property({ type: String }) scopeLabel = ''\n /** The doors. Replaceable for tests; a host normally leaves it. */\n @property({ attribute: false }) admin: AssistantSessionAdmin = assistantSessionAdmin\n /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */\n @property({ type: String }) kind = ''\n /** kind → the words people know it by (the modes' labels), for each row. */\n @property({ attribute: false }) kindLabels: Record<string, string> = {}\n\n @state() private entries: AssistantSessionEntry[] = []\n @state() private total = 0\n @state() private hiddenCount = 0\n @state() private search = ''\n @state() private allKinds = false\n @state() private whose: NonNullable<AssistantSessionFilters['whose']> = 'all'\n @state() private visibility: NonNullable<AssistantSessionFilters['visibility']> = 'all'\n @state() private includeHidden = false\n @state() private unreadOnly = false\n @state() private pageSize = PAGE\n @state() private loading = false\n @state() private error = ''\n @state() private renamingId = ''\n @state() private filtersOpen = false\n\n private get hasFilters() {\n return !!this.search.trim() || this.allKinds || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly\n }\n\n private resetFilters() {\n clearTimeout(this.searchTimer)\n this.refilter(() => {\n this.search = ''\n this.allKinds = false\n this.whose = 'all'\n this.visibility = 'all'\n this.includeHidden = this.unreadOnly = false\n })\n }\n\n private generation = 0\n private searchTimer?: ReturnType<typeof setTimeout>\n\n /** Read the list again - the host calls this after it opened or started a conversation. */\n async reload(): Promise<void> {\n const generation = ++this.generation\n if (!this.anchorType || !this.anchorId) {\n this.entries = []\n this.total = this.hiddenCount = 0\n return\n }\n\n this.loading = true\n this.error = ''\n try {\n /*\n * Paging reads again from the start rather than appending - live activity reorders the list, and\n * appending a later page to an earlier order duplicates and drops rows.\n */\n const page = await this.admin.inbox({\n anchorType: this.anchorType,\n anchorId: this.anchorId,\n search: this.search,\n filters: {\n kind: this.allKinds ? '' : this.kind,\n whose: this.whose,\n visibility: this.visibility,\n includeHidden: this.includeHidden,\n unreadOnly: this.unreadOnly\n },\n limit: this.pageSize,\n offset: 0\n })\n if (generation !== this.generation) return\n this.entries = page.entries\n this.total = page.total\n this.hiddenCount = page.hiddenCount\n } catch (error) {\n if (generation !== this.generation) return\n this.entries = []\n this.total = this.hiddenCount = 0\n this.error = (error as Error).message || t('failed', 'could not load the conversation list.')\n } finally {\n if (generation === this.generation) this.loading = false\n }\n }\n\n protected updated(changed: PropertyValues) {\n if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind')) void this.reload()\n\n /*\n * The rename box takes the keys the moment it opens, with the name selected. `autofocus` works once\n * per document, so the second rename opened a box that ignored the typing (2026-09-24, operato-ecs).\n */\n if (changed.has('renamingId') && this.renamingId) {\n const input = this.renderRoot.querySelector('input.rename') as HTMLInputElement | null\n input?.focus()\n input?.select()\n }\n }\n\n disconnectedCallback() {\n this.generation++\n clearTimeout(this.searchTimer)\n super.disconnectedCallback()\n }\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n /* ── what the person does ────────────────────────────────────────────────────────── */\n\n private onSearch(value: string) {\n this.search = value\n clearTimeout(this.searchTimer)\n /* Not on every keystroke. A new search starts from the first page. */\n this.searchTimer = setTimeout(() => {\n this.pageSize = PAGE\n void this.reload()\n }, 200)\n }\n\n /** A filter changed - the list starts from its first page again. */\n private refilter(change: () => void) {\n change()\n this.pageSize = PAGE\n void this.reload()\n }\n\n private loadMore() {\n this.pageSize += PAGE\n void this.reload()\n }\n\n /** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */\n private async act(entry: AssistantSessionEntry, change: string, run: () => Promise<boolean>, refused: string) {\n this.renamingId = ''\n try {\n if (!(await run())) this.error = refused\n else this.emit('assistant-session-changed', { sessionId: entry.id, change })\n } catch (error) {\n /* Never pass a failure silently - a person must not believe a conversation was deleted when it was not. */\n this.error = (error as Error).message\n }\n const error = this.error\n await this.reload()\n if (error && !this.error) this.error = error\n }\n\n private rename(entry: AssistantSessionEntry, name: string) {\n const trimmed = name.trim()\n this.renamingId = ''\n if (!trimmed || trimmed === entry.name) return\n void this.act(entry, 'renamed', () => this.admin.rename(entry.id, trimmed), t('rename-refused', 'this conversation could not be renamed.'))\n }\n\n private setHidden(entry: AssistantSessionEntry, hidden: boolean) {\n void this.act(entry, hidden ? 'hidden' : 'unhidden', () => this.admin.hide(entry.id, hidden), t('hide-refused', 'this conversation could not be hidden.'))\n }\n\n private setVisibility(entry: AssistantSessionEntry, visibility: 'open' | 'private') {\n void this.act(\n entry,\n 'visibility',\n () => this.admin.setVisibility(entry.id, visibility),\n t('visibility-refused', 'only the owner of this conversation can change who reads it.')\n )\n }\n\n private _removeSession(entry: AssistantSessionEntry) {\n void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'))\n }\n\n /* ── drawing ─────────────────────────────────────────────────────────────────────── */\n\n render(): TemplateResult {\n const shown = this.entries.length\n\n return html`\n <div class=\"scope-label\">${this.scopeLabel}</div>\n <div class=\"bar\">\n <input\n .value=${this.search}\n placeholder=${t('search', 'Search conversations')}\n aria-label=${t('search', 'Search conversations')}\n @input=${(e: Event) => this.onSearch((e.target as HTMLInputElement).value)}\n @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape' && this.search) { e.stopPropagation(); this.onSearch('') } }}\n />\n <button class=\"new\" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>\n </div>\n <button class=\"filter-toggle\" aria-expanded=${this.filtersOpen} @click=${() => (this.filtersOpen = !this.filtersOpen)}>${t('filters', 'Filters')}${this.hasFilters ? ' •' : ''}</button>\n <div class=\"filters\" ?hidden=${!this.filtersOpen}>\n ${this.kind\n ? html`<select aria-label=${t('filter-kind', 'Which kind')} .value=${this.allKinds ? 'all' : 'this'}\n @change=${(e: Event) => this.refilter(() => (this.allKinds = (e.target as HTMLSelectElement).value === 'all'))}>\n <option value=\"this\">${this.kindLabels[this.kind] || t('filter-this-kind', 'This conversation')}</option>\n <option value=\"all\">${t('filter-all-kinds', 'All kinds here')}</option>\n </select>`\n : nothing}\n <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}\n @change=${(e: Event) => this.refilter(() => (this.whose = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-all', 'All')}</option>\n <option value=\"mine\">${t('filter-mine', 'Started by me')}</option>\n <option value=\"participant\">${t('filter-participant', \"I'm in\")}</option>\n </select>\n <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}\n @change=${(e: Event) => this.refilter(() => (this.visibility = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-any-visibility', 'Public and private')}</option>\n <option value=\"open\">${t('filter-open', 'Public')}</option>\n <option value=\"private\">${t('filter-private', 'Private')}</option>\n </select>\n <label><input type=\"checkbox\" .checked=${this.unreadOnly}\n @change=${(e: Event) => this.refilter(() => (this.unreadOnly = (e.target as HTMLInputElement).checked))} />${t('filter-unread', 'Unread')}</label>\n <label><input type=\"checkbox\" .checked=${this.includeHidden}\n @change=${(e: Event) => this.refilter(() => (this.includeHidden = (e.target as HTMLInputElement).checked))} />${t('filter-hidden', 'Including hidden')}</label>\n </div>\n ${this.hasFilters ? html`<button class=\"reset\" @click=${() => this.resetFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}\n <div class=\"list\">\n ${this.error ? html`<div class=\"note\" role=\"alert\">${this.error}</div>` : nothing}\n ${this.loading ? html`<div class=\"note\" role=\"status\">${t('loading', 'Loading conversations…')}</div>` : nothing}\n ${shown === 0 && !this.loading && !this.error\n ? html`<div class=\"empty\" role=\"status\">${this.hasFilters ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations for this subject yet.')}</div>`\n : this.entries.map(entry => this.renderRow(entry))}\n ${shown < this.total\n ? html`<button class=\"more\" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`\n : nothing}\n </div>\n <!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->\n ${this.hiddenCount > 0 && !this.includeHidden\n ? html`<button class=\"hiddenfilter\" @click=${() => this.refilter(() => (this.includeHidden = true))}>\n <md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}\n </button>`\n : nothing}\n `\n }\n\n private renderRow(entry: AssistantSessionEntry): TemplateResult {\n const name = entry.name || t('untitled', 'Untitled conversation')\n const actions = sessionActions(entry)\n const isPrivate = entry.visibility === 'private'\n\n if (this.renamingId === entry.id) {\n return html`<div class=\"row\">\n <span class=\"dot\"></span>\n <input\n class=\"rename\"\n .value=${entry.name || ''}\n aria-label=${t('rename', 'Rename')}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter') this.rename(entry, (e.target as HTMLInputElement).value)\n if (e.key === 'Escape') { e.stopPropagation(); this.renamingId = '' }\n }}\n @blur=${(e: Event) => this.rename(entry, (e.target as HTMLInputElement).value)}\n />\n </div>`\n }\n\n return html`<div\n class=\"row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''}\"\n >\n <span class=\"dot\"></span>\n <button class=\"open-row\" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>\n <div class=\"title\">${name}</div>\n <div class=\"prev\">${this.allKinds && entry.kind ? html`<span class=\"kind\">${this.kindLabels[entry.kind] || entry.kind}</span> · ` : nothing}${entry.lastMessagePreview || ''}</div>\n <span class=\"meta\">${isPrivate ? t('audience-private', 'Participants only') : t('audience-open', 'Everyone in this domain')}${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html` · ${t('filter-mine', 'Started by me')}` : nothing}</span>\n </button>\n <details class=\"actions\" @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape') { e.stopPropagation(); const details = e.currentTarget as HTMLDetailsElement; details.open = false; details.querySelector('summary')?.focus() } }}>\n <summary aria-label=${t('manage', 'Manage conversation')}>⋯</summary>\n <span class=\"acts\" @click=${(e: Event) => e.stopPropagation()}>\n ${actions.rename\n ? html`<button class=\"icon\" title=${t('rename', 'Rename')} aria-label=${t('rename', 'Rename')} @click=${() => (this.renamingId = entry.id)}>\n ${t('rename', 'Rename')}\n </button>`\n : nothing}\n ${actions.setVisibility\n ? isPrivate\n ? html`<button class=\"icon\" title=${t('make-open', 'Make public')} aria-label=${t('make-open', 'Make public')}\n @click=${() => this.askOpen(entry, name)}>${t('make-open', 'Make public')}</button>`\n : html`<button class=\"icon\" title=${t('make-private', 'Make private')} aria-label=${t('make-private', 'Make private')}\n @click=${() => this.setVisibility(entry, 'private')}>${t('make-private', 'Make private')}</button>`\n : nothing}\n <button\n class=\"icon\"\n title=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n aria-label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n @click=${() => this.setHidden(entry, !entry.hidden)}\n >\n ${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n </button>\n ${actions.remove\n ? html`<button class=\"icon danger\" title=${t('remove', 'Delete')} aria-label=${t('remove', 'Delete')}\n @click=${() => this.askRemove(entry, name)}>${t('remove', 'Delete')}</button>`\n : nothing}\n </span>\n </details>\n </div>`\n }\n\n /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */\n private async ask(title: string, text: string, confirmText: string): Promise<boolean> {\n return !!(await OxPrompt.open({\n type: 'warning',\n title,\n text,\n confirmButton: { text: confirmText },\n cancelButton: { text: t('cancel', 'Cancel') }\n }))\n }\n\n private async askRemove(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('remove', 'Delete'),\n t('confirm-remove', 'Delete \"{name}\"? It disappears for every participant and cannot be brought back.', { name }),\n t('remove', 'Delete')\n )\n if (ok) this._removeSession(entry)\n }\n\n private async askOpen(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-open', 'Make public'),\n t('confirm-open', 'Make \"{name}\" public? Everyone in this domain will be able to read what is already written.', { name }),\n t('make-open', 'Make public')\n )\n if (ok) this.setVisibility(entry, 'open')\n }\n}\n"]}
|
|
@@ -14,6 +14,7 @@ let AssistantSessionToolbar = class AssistantSessionToolbar extends LitElement {
|
|
|
14
14
|
this.temporary = false;
|
|
15
15
|
this.error = '';
|
|
16
16
|
this.historyEnabled = false;
|
|
17
|
+
this.singleConversation = false;
|
|
17
18
|
this.maxTabs = 5;
|
|
18
19
|
/** 좁은 도크의 문맥 헤더 바로 아래에서 쓰는 밀도. 기능은 줄이지 않는다. */
|
|
19
20
|
this.compact = false;
|
|
@@ -48,7 +49,8 @@ let AssistantSessionToolbar = class AssistantSessionToolbar extends LitElement {
|
|
|
48
49
|
const label = i18next.t('ai-assistant.session.history', { defaultValue: 'Conversation history' });
|
|
49
50
|
return html `
|
|
50
51
|
<div class="controls">
|
|
51
|
-
${this.
|
|
52
|
+
${this.singleConversation ? html `<button @click=${() => this.emit('assistant-session-history')}>${i18next.t('ai-assistant.session.list', { defaultValue: 'Conversations' })}</button><span class="tabs">${this.sessions.find(session => session.id === this.sessionId)?.name || i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</span>` : nothing}
|
|
53
|
+
${this.temporary || this.singleConversation ? nothing : html `<div class="tabs" role="tablist" aria-label=${label}>
|
|
52
54
|
${!this.sessionId ? html `<button role="tab" aria-selected="true">${i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</button>` : nothing}
|
|
53
55
|
${assistantRecentSessions(this.sessions, this.sessionId, this.historyEnabled ? this.maxTabs : this.sessions.length).map((session, index) => html `<button role="tab" aria-selected=${session.id === this.sessionId ? 'true' : 'false'}
|
|
54
56
|
?disabled=${this.disabled || this.loading} title=${session.name || `${label} ${index + 1}`}
|
|
@@ -58,7 +60,7 @@ let AssistantSessionToolbar = class AssistantSessionToolbar extends LitElement {
|
|
|
58
60
|
title=${i18next.t('ai-assistant.session.reset-help', { defaultValue: 'Keep previous history and your work, and start a new conversation. Previous messages are excluded from subsequent requests.' })}
|
|
59
61
|
@click=${() => this.emit('assistant-session-reset')}>+ ${i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</button>
|
|
60
62
|
</div>
|
|
61
|
-
${this.historyEnabled ? html `<button class="history" ?disabled=${this.loading || this.disabled}
|
|
63
|
+
${this.historyEnabled && !this.singleConversation ? html `<button class="history" ?disabled=${this.loading || this.disabled}
|
|
62
64
|
@click=${() => this.emit('assistant-session-history')}>${i18next.t('ai-assistant.session.all', { defaultValue: 'All History' })}</button>` : nothing}
|
|
63
65
|
${this.loading ? html `<p role="status">${i18next.t('ai-assistant.session.loading', { defaultValue: 'Loading conversation history…' })}</p>` : nothing}
|
|
64
66
|
${this.error ? html `<p role="alert">${this.error} <button ?disabled=${this.loading || this.disabled}
|
|
@@ -94,6 +96,10 @@ __decorate([
|
|
|
94
96
|
property({ type: Boolean }),
|
|
95
97
|
__metadata("design:type", Object)
|
|
96
98
|
], AssistantSessionToolbar.prototype, "historyEnabled", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
property({ type: Boolean }),
|
|
101
|
+
__metadata("design:type", Object)
|
|
102
|
+
], AssistantSessionToolbar.prototype, "singleConversation", void 0);
|
|
97
103
|
__decorate([
|
|
98
104
|
property({ type: Number }),
|
|
99
105
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-session-toolbar.js","sourceRoot":"","sources":["../../client/components/assistant-session-toolbar.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAE/E,oGAAoG;AAE7F,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,UAAU;IAAhD;;QAC2B,aAAQ,GAAuB,EAAE,CAAA;QACrD,cAAS,GAAG,EAAE,CAAA;QACG,YAAO,GAAG,KAAK,CAAA;QACf,aAAQ,GAAG,KAAK,CAAA;QAChB,cAAS,GAAG,KAAK,CAAA;QAClC,UAAK,GAAG,EAAE,CAAA;QACO,mBAAc,GAAG,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"assistant-session-toolbar.js","sourceRoot":"","sources":["../../client/components/assistant-session-toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport type { AssistantSession } from '../utils/assistant-session-controller'\nimport { assistantRecentSessions } from '../utils/assistant-request-context.js'\n\n/** Product-neutral controls. The host/controller owns effects; this view never mutates sessions. */\n@customElement('ox-assistant-session-toolbar')\nexport class AssistantSessionToolbar extends LitElement {\n @property({ attribute: false }) sessions: AssistantSession[] = []\n @property() sessionId = ''\n @property({ type: Boolean }) loading = false\n @property({ type: Boolean }) disabled = false\n @property({ type: Boolean }) temporary = false\n @property() error = ''\n @property({ type: Boolean }) historyEnabled = false\n @property({ type: Boolean }) singleConversation = false\n @property({ type: Number }) maxTabs = 5\n /** 좁은 도크의 문맥 헤더 바로 아래에서 쓰는 밀도. 기능은 줄이지 않는다. */\n @property({ type: Boolean, reflect: true }) compact = false\n\n static styles = css`\n :host { display: block; min-width: 0; }\n .controls { display: flex; align-items: center; gap: 6px; }\n button { box-sizing: border-box; height: 28px; font: inherit; font-size: 12px;\n color: var(--secondary-color, #68747c); border: 0; border-radius: 4px;\n background: transparent; cursor: pointer; }\n .tabs { display:flex; flex:1; min-width:0; overflow-x:auto; gap:1px; scrollbar-width:thin; }\n [role=tab] { max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\n padding: 0 8px; border-bottom: 2px solid transparent; border-radius: 3px 3px 0 0; }\n [role=tab]:hover { background: color-mix(in srgb, var(--primary-color, #007f87) 7%, transparent); color: var(--primary-color, #007f87); }\n [aria-selected=true] { color:var(--primary-color, #007f87); border-bottom-color: currentColor; font-weight:600; }\n .session-new { flex: none; padding: 0 7px; font-weight: 500; white-space: nowrap; }\n .session-new:hover, .history:hover { color: var(--primary-color, #007f87); background: color-mix(in srgb, var(--primary-color, #007f87) 7%, transparent); }\n .history { height: 24px; margin: 1px 0 0 4px; padding: 0 5px; font-size: 11px; color: var(--tertiary-color, #88939a); }\n :host([compact]) .controls { gap: 3px; min-height: 24px; }\n :host([compact]) button { height: 24px; font-size: 11px; }\n :host([compact]) [role=tab] { max-width: 132px; padding: 0 6px; border-bottom-width: 1px; }\n :host([compact]) .session-new { padding: 0 5px; }\n :disabled { opacity: .5; cursor: default; }\n :focus-visible { outline: 2px solid var(--primary-color, #007f87); outline-offset: 2px; }\n p { margin: 6px 0 0; font-size: 12px; overflow-wrap: anywhere; }\n [role=alert] { color: var(--error-color, #b42318); }\n `\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n render() {\n const label = i18next.t('ai-assistant.session.history', { defaultValue: 'Conversation history' })\n return html`\n <div class=\"controls\">\n ${this.singleConversation ? html`<button @click=${() => this.emit('assistant-session-history')}>${i18next.t('ai-assistant.session.list', { defaultValue: 'Conversations' })}</button><span class=\"tabs\">${this.sessions.find(session => session.id === this.sessionId)?.name || i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</span>` : nothing}\n ${this.temporary || this.singleConversation ? nothing : html`<div class=\"tabs\" role=\"tablist\" aria-label=${label}>\n ${!this.sessionId ? html`<button role=\"tab\" aria-selected=\"true\">${i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</button>` : nothing}\n ${assistantRecentSessions(this.sessions, this.sessionId, this.historyEnabled ? this.maxTabs : this.sessions.length).map((session, index) => html`<button role=\"tab\" aria-selected=${session.id === this.sessionId ? 'true' : 'false'}\n ?disabled=${this.disabled || this.loading} title=${session.name || `${label} ${index + 1}`}\n @click=${() => this.emit('assistant-session-select', { sessionId: session.id })}>${session.name || `${label} ${index + 1}`}</button>`)}\n </div>`}\n <button class=\"session-new\" ?disabled=${this.disabled || this.loading || !!this.error}\n title=${i18next.t('ai-assistant.session.reset-help', { defaultValue: 'Keep previous history and your work, and start a new conversation. Previous messages are excluded from subsequent requests.' })}\n @click=${() => this.emit('assistant-session-reset')}>+ ${i18next.t('ai-assistant.session.new', { defaultValue: 'New conversation' })}</button>\n </div>\n ${this.historyEnabled && !this.singleConversation ? html`<button class=\"history\" ?disabled=${this.loading || this.disabled}\n @click=${() => this.emit('assistant-session-history')}>${i18next.t('ai-assistant.session.all', { defaultValue: 'All History' })}</button>` : nothing}\n ${this.loading ? html`<p role=\"status\">${i18next.t('ai-assistant.session.loading', { defaultValue: 'Loading conversation history…' })}</p>` : nothing}\n ${this.error ? html`<p role=\"alert\">${this.error} <button ?disabled=${this.loading || this.disabled}\n @click=${() => this.emit('assistant-session-retry')}>${i18next.t('ai-assistant.session.retry', { defaultValue: 'Retry' })}</button></p>` : nothing}\n `\n }\n}\n"]}
|
|
@@ -15,6 +15,8 @@ export interface RemoteMessage {
|
|
|
15
15
|
content: string;
|
|
16
16
|
relatedPatchId?: string;
|
|
17
17
|
toolUsagesJson?: any;
|
|
18
|
+
/** The answer's proposals, stored with it - the cards come back after a refresh (ADR-0097 Q-D4 ④). */
|
|
19
|
+
proposalsJson?: any;
|
|
18
20
|
/** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */
|
|
19
21
|
groundingWarningsJson?: any;
|
|
20
22
|
/** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */
|
|
@@ -16,6 +16,7 @@ export function remoteMessageToLine(m) {
|
|
|
16
16
|
content: m.content,
|
|
17
17
|
patchId: m.relatedPatchId ?? undefined,
|
|
18
18
|
toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,
|
|
19
|
+
proposals: Array.isArray(m.proposalsJson) && m.proposalsJson.length ? m.proposalsJson : undefined,
|
|
19
20
|
groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,
|
|
20
21
|
/* Files sent with the turn (shared dock §9) - opened through the conversation's own route when drawn. */
|
|
21
22
|
attachments: Array.isArray(m.attachmentsJson)
|
|
@@ -73,6 +74,7 @@ export function receiveRemoteMessage(lines, seen, m) {
|
|
|
73
74
|
content: m.content,
|
|
74
75
|
patchId: m.relatedPatchId ?? nextLines[idx].patchId,
|
|
75
76
|
toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : nextLines[idx].toolUsages,
|
|
77
|
+
proposals: nextLines[idx].proposals ?? (Array.isArray(m.proposalsJson) && m.proposalsJson.length ? m.proposalsJson : undefined),
|
|
76
78
|
groundingWarnings: Array.isArray(m.groundingWarningsJson)
|
|
77
79
|
? m.groundingWarningsJson
|
|
78
80
|
: nextLines[idx].groundingWarnings,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-echo-dedup.js","sourceRoot":"","sources":["../../client/components/chat-echo-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 그룹챗 메시지 에코 dedup — 순수 로직.\n *\n * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다\n * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을\n * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).\n *\n * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.\n * (C2 의 patch-apply-gate.ts 와 동일한 \"순수 게이트\" 컨벤션.)\n */\n\n/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */\nexport interface DedupLine {\n id?: string\n _localId?: string\n role: string\n content: string\n senderEmail?: string\n senderName?: string\n [k: string]: any\n}\n\n/** 서버 방송 메시지(필요 필드만). */\nexport interface RemoteMessage {\n id?: string\n role: string\n content: string\n relatedPatchId?: string\n toolUsagesJson?: any\n /** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */\n groundingWarningsJson?: any\n /** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */\n creator?: { name?: string; email?: string } | null\n /** 이 사람이 이 답의 문을 못 지나 본문이 한 줄로 바뀌어 왔나(ADR-0073 판정 1). */\n redacted?: boolean\n}\n\n/** 방송 메시지 → 라인. */\nexport function remoteMessageToLine(m: RemoteMessage): DedupLine {\n return {\n id: m.id,\n role: m.role,\n content: m.content,\n patchId: m.relatedPatchId ?? undefined,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,\n groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,\n /* Files sent with the turn (shared dock §9) - opened through the conversation's own route when drawn. */\n attachments: Array.isArray((m as any).attachmentsJson)\n ? (m as any).attachmentsJson.map((a: any) => ({ id: a.id, name: a.name, type: a.mimetype, size: a.size }))\n : undefined,\n senderEmail: m.creator?.email ?? undefined,\n senderName: m.creator?.name ?? undefined,\n /*\n * 서버가 이 사람에게 값을 안 내준 줄 — ADR-0073 판정 1. `content` 에 온 것은 어시스턴트가 쓴\n * 문장이 아니라 「권한이 필요합니다」 한 줄이다. 그대로 말풍선에 넣으면 어시스턴트가 그렇게 말한\n * 것처럼 읽힌다.\n */\n redacted: m.redacted === true\n }\n}\n\n/**\n * 원격(방송) 메시지 수신 결과를 계산.\n * - id 없음 / 이미 seen → 변화 없음(null).\n * - **내 발신 에코**이고 아직 서버 id 를 못 받은 optimistic 라인이 있으면 → 그 라인에 id 를 입힌다.\n * - 그 밖 → seen 에 추가 + 라인 append.\n *\n * 왜 '입히기'인가: 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 에코를 새 줄로 붙이고\n * 중복 제거를 mutation 응답 시점(reconcileSentLine)에 했는데, 그 응답은 **LLM 이 끝난 뒤** 오므로\n * 사용자는 대기 시간 내내 자기 질문을 두 줄로 본다. 그래서 붙이지 않고 대기 중인 줄에 흡수시킨다.\n *\n * `_localId` 는 유지한다 — 이후 mutation 응답이 patch(요약·patchId 등)를 그 줄에 병합할 수 있어야 한다.\n */\nexport function receiveRemoteMessage(\n lines: DedupLine[],\n seen: Set<string>,\n m: RemoteMessage | undefined | null\n): { lines: DedupLine[]; seen: Set<string> } | null {\n if (!m || !m.id) return null\n if (seen.has(m.id)) return null\n const nextSeen = new Set(seen)\n nextSeen.add(m.id)\n\n /* 내 발신 에코 흡수 — `_localId` 가 있는 줄은 이 클라이언트가 만든 것뿐이다.\n * 발신자 판정: 양쪽 email 이 있으면 같아야 하고, 방송에 발신자 email 이 없으면(단일 사용자·구형)\n * 내 것으로 본다. 다른 사용자의 메시지가 내 대기 줄을 삼키지 않도록 email 이 다르면 흡수하지 않는다. */\n /* AI 답변 에코 흡수 — 사용자 발화와 **같은 이유**로 붙이지 않고 대기 줄에 흡수한다.\n *\n * 왜 필요한가(실제 사고): 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 assistant 방송을\n * 새 줄로 붙였고, 뒤이어 mutation 응답이 \"같은 서버 id 를 가진 다른 줄이 있다\" 며 **대기 줄을 지웠다**.\n * 그런데 대기 줄에만 있는 것이 있다 — **영속되지 않는 응답 데이터**(조치 제안 카드·접지 경고).\n * 그래서 화면에서 실행 버튼이 사라졌다(제안 도구는 분명히 호출됐는데도).\n *\n * 흡수하면 서버가 보낸 본문(멘션 마커 포함)을 그 줄에 입히면서 대기 표시를 풀 수 있어, 답이 더\n * 빨리 보이는 효과도 있다. */\n if (m.role === 'assistant') {\n const idx = lines.findIndex(l => l.role === 'assistant' && !l.id && !!l._localId)\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n /* 본문·영속 데이터는 서버 것으로 갱신하고, 이 줄이 들고 있던 값(제안 등)은 유지한다. */\n content: m.content,\n patchId: m.relatedPatchId ?? nextLines[idx].patchId,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : nextLines[idx].toolUsages,\n groundingWarnings: Array.isArray(m.groundingWarningsJson)\n ? m.groundingWarningsJson\n : nextLines[idx].groundingWarnings,\n pending: false\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n if (m.role === 'user') {\n const echoEmail = m.creator?.email\n const idx = lines.findIndex(\n l =>\n l.role === 'user' &&\n !l.id &&\n !!l._localId &&\n (!echoEmail || l.senderEmail == null || l.senderEmail === echoEmail)\n )\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n senderEmail: echoEmail ?? nextLines[idx].senderEmail,\n senderName: m.creator?.name ?? nextLines[idx].senderName\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }\n}\n\n/**\n * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.\n * - **다른 줄**이 이미 그 serverId 를 갖고 있으면(옛 append 경로) optimistic 중복 라인 제거.\n * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.\n * - 해당 _localId 라인이 없으면 변화 없음.\n * 반환: 새 { lines, seen } (불변).\n */\nexport function reconcileSentLine(\n lines: DedupLine[],\n seen: Set<string>,\n localId: string,\n serverId: string | undefined,\n patch: Partial<DedupLine>\n): { lines: DedupLine[]; seen: Set<string> } {\n const idx = lines.findIndex(l => l._localId === localId)\n /* 같은 서버 id 를 든 **다른** 줄 — 에코가 흡수되지 못하고 새 줄로 붙은 경우다. */\n const echoed = serverId ? lines.findIndex((l, i) => i !== idx && l.id === serverId) : -1\n\n /*\n * 대기 줄이 없어졌다 — 보내는 동안 대화가 지워졌거나 이력으로 갈렸다. 그래도 **응답이 어느 메시지의\n * 것인지는 안다**(`assistantMessageId`). 그 줄에 합친다.\n *\n * 여기서 그냥 돌아가면, 저장되는 값(본문 · 도구 사용)은 에코가 들고 있으니 화면은 멀쩡해 보이고\n * **저장되지 않는 것만 사라진다** — 제안 카드다. 모델은 「카드를 눌러 적용하세요」라고 말하고 화면에는\n * 누를 것이 없다(2026-09-18 트윈에서 실제로 그랬다).\n */\n if (idx < 0) {\n if (echoed < 0) return { lines, seen }\n const merged = [...lines]\n merged[echoed] = { ...merged[echoed], ...patch, id: serverId ?? merged[echoed].id }\n return { lines: merged, seen: serverId ? new Set(seen).add(serverId) : seen }\n }\n\n const nextLines = [...lines]\n /* seen 만 보고 지우면, 에코를 이 줄에 **흡수**한 경우(id 가 이미 이 줄에 있다) 자기 줄을 지운다.\n * 실제 중복은 \"다른 줄이 같은 id 를 가진\" 상태일 때뿐이다. */\n if (echoed >= 0) {\n /* 남는 것은 에코 줄이다. 응답에만 있는 것(제안)을 그 줄에 옮기고 대기 줄을 지운다. */\n nextLines[echoed] = { ...nextLines[echoed], ...patch, id: serverId ?? nextLines[echoed].id }\n nextLines.splice(idx, 1)\n return { lines: nextLines, seen }\n }\n const nextSeen = serverId ? new Set(seen).add(serverId) : seen\n nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }\n return { lines: nextLines, seen: nextSeen }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chat-echo-dedup.js","sourceRoot":"","sources":["../../client/components/chat-echo-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 그룹챗 메시지 에코 dedup — 순수 로직.\n *\n * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다\n * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을\n * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).\n *\n * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.\n * (C2 의 patch-apply-gate.ts 와 동일한 \"순수 게이트\" 컨벤션.)\n */\n\n/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */\nexport interface DedupLine {\n id?: string\n _localId?: string\n role: string\n content: string\n senderEmail?: string\n senderName?: string\n [k: string]: any\n}\n\n/** 서버 방송 메시지(필요 필드만). */\nexport interface RemoteMessage {\n id?: string\n role: string\n content: string\n relatedPatchId?: string\n toolUsagesJson?: any\n /** The answer's proposals, stored with it - the cards come back after a refresh (ADR-0097 Q-D4 ④). */\n proposalsJson?: any\n /** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */\n groundingWarningsJson?: any\n /** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */\n creator?: { name?: string; email?: string } | null\n /** 이 사람이 이 답의 문을 못 지나 본문이 한 줄로 바뀌어 왔나(ADR-0073 판정 1). */\n redacted?: boolean\n}\n\n/** 방송 메시지 → 라인. */\nexport function remoteMessageToLine(m: RemoteMessage): DedupLine {\n return {\n id: m.id,\n role: m.role,\n content: m.content,\n patchId: m.relatedPatchId ?? undefined,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,\n proposals: Array.isArray(m.proposalsJson) && m.proposalsJson.length ? m.proposalsJson : undefined,\n groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,\n /* Files sent with the turn (shared dock §9) - opened through the conversation's own route when drawn. */\n attachments: Array.isArray((m as any).attachmentsJson)\n ? (m as any).attachmentsJson.map((a: any) => ({ id: a.id, name: a.name, type: a.mimetype, size: a.size }))\n : undefined,\n senderEmail: m.creator?.email ?? undefined,\n senderName: m.creator?.name ?? undefined,\n /*\n * 서버가 이 사람에게 값을 안 내준 줄 — ADR-0073 판정 1. `content` 에 온 것은 어시스턴트가 쓴\n * 문장이 아니라 「권한이 필요합니다」 한 줄이다. 그대로 말풍선에 넣으면 어시스턴트가 그렇게 말한\n * 것처럼 읽힌다.\n */\n redacted: m.redacted === true\n }\n}\n\n/**\n * 원격(방송) 메시지 수신 결과를 계산.\n * - id 없음 / 이미 seen → 변화 없음(null).\n * - **내 발신 에코**이고 아직 서버 id 를 못 받은 optimistic 라인이 있으면 → 그 라인에 id 를 입힌다.\n * - 그 밖 → seen 에 추가 + 라인 append.\n *\n * 왜 '입히기'인가: 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 에코를 새 줄로 붙이고\n * 중복 제거를 mutation 응답 시점(reconcileSentLine)에 했는데, 그 응답은 **LLM 이 끝난 뒤** 오므로\n * 사용자는 대기 시간 내내 자기 질문을 두 줄로 본다. 그래서 붙이지 않고 대기 중인 줄에 흡수시킨다.\n *\n * `_localId` 는 유지한다 — 이후 mutation 응답이 patch(요약·patchId 등)를 그 줄에 병합할 수 있어야 한다.\n */\nexport function receiveRemoteMessage(\n lines: DedupLine[],\n seen: Set<string>,\n m: RemoteMessage | undefined | null\n): { lines: DedupLine[]; seen: Set<string> } | null {\n if (!m || !m.id) return null\n if (seen.has(m.id)) return null\n const nextSeen = new Set(seen)\n nextSeen.add(m.id)\n\n /* 내 발신 에코 흡수 — `_localId` 가 있는 줄은 이 클라이언트가 만든 것뿐이다.\n * 발신자 판정: 양쪽 email 이 있으면 같아야 하고, 방송에 발신자 email 이 없으면(단일 사용자·구형)\n * 내 것으로 본다. 다른 사용자의 메시지가 내 대기 줄을 삼키지 않도록 email 이 다르면 흡수하지 않는다. */\n /* AI 답변 에코 흡수 — 사용자 발화와 **같은 이유**로 붙이지 않고 대기 줄에 흡수한다.\n *\n * 왜 필요한가(실제 사고): 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 assistant 방송을\n * 새 줄로 붙였고, 뒤이어 mutation 응답이 \"같은 서버 id 를 가진 다른 줄이 있다\" 며 **대기 줄을 지웠다**.\n * 그런데 대기 줄에만 있는 것이 있다 — **영속되지 않는 응답 데이터**(조치 제안 카드·접지 경고).\n * 그래서 화면에서 실행 버튼이 사라졌다(제안 도구는 분명히 호출됐는데도).\n *\n * 흡수하면 서버가 보낸 본문(멘션 마커 포함)을 그 줄에 입히면서 대기 표시를 풀 수 있어, 답이 더\n * 빨리 보이는 효과도 있다. */\n if (m.role === 'assistant') {\n const idx = lines.findIndex(l => l.role === 'assistant' && !l.id && !!l._localId)\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n /* 본문·영속 데이터는 서버 것으로 갱신하고, 이 줄이 들고 있던 값(제안 등)은 유지한다. */\n content: m.content,\n patchId: m.relatedPatchId ?? nextLines[idx].patchId,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : nextLines[idx].toolUsages,\n proposals: nextLines[idx].proposals ?? (Array.isArray(m.proposalsJson) && m.proposalsJson.length ? m.proposalsJson : undefined),\n groundingWarnings: Array.isArray(m.groundingWarningsJson)\n ? m.groundingWarningsJson\n : nextLines[idx].groundingWarnings,\n pending: false\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n if (m.role === 'user') {\n const echoEmail = m.creator?.email\n const idx = lines.findIndex(\n l =>\n l.role === 'user' &&\n !l.id &&\n !!l._localId &&\n (!echoEmail || l.senderEmail == null || l.senderEmail === echoEmail)\n )\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n senderEmail: echoEmail ?? nextLines[idx].senderEmail,\n senderName: m.creator?.name ?? nextLines[idx].senderName\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }\n}\n\n/**\n * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.\n * - **다른 줄**이 이미 그 serverId 를 갖고 있으면(옛 append 경로) optimistic 중복 라인 제거.\n * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.\n * - 해당 _localId 라인이 없으면 변화 없음.\n * 반환: 새 { lines, seen } (불변).\n */\nexport function reconcileSentLine(\n lines: DedupLine[],\n seen: Set<string>,\n localId: string,\n serverId: string | undefined,\n patch: Partial<DedupLine>\n): { lines: DedupLine[]; seen: Set<string> } {\n const idx = lines.findIndex(l => l._localId === localId)\n /* 같은 서버 id 를 든 **다른** 줄 — 에코가 흡수되지 못하고 새 줄로 붙은 경우다. */\n const echoed = serverId ? lines.findIndex((l, i) => i !== idx && l.id === serverId) : -1\n\n /*\n * 대기 줄이 없어졌다 — 보내는 동안 대화가 지워졌거나 이력으로 갈렸다. 그래도 **응답이 어느 메시지의\n * 것인지는 안다**(`assistantMessageId`). 그 줄에 합친다.\n *\n * 여기서 그냥 돌아가면, 저장되는 값(본문 · 도구 사용)은 에코가 들고 있으니 화면은 멀쩡해 보이고\n * **저장되지 않는 것만 사라진다** — 제안 카드다. 모델은 「카드를 눌러 적용하세요」라고 말하고 화면에는\n * 누를 것이 없다(2026-09-18 트윈에서 실제로 그랬다).\n */\n if (idx < 0) {\n if (echoed < 0) return { lines, seen }\n const merged = [...lines]\n merged[echoed] = { ...merged[echoed], ...patch, id: serverId ?? merged[echoed].id }\n return { lines: merged, seen: serverId ? new Set(seen).add(serverId) : seen }\n }\n\n const nextLines = [...lines]\n /* seen 만 보고 지우면, 에코를 이 줄에 **흡수**한 경우(id 가 이미 이 줄에 있다) 자기 줄을 지운다.\n * 실제 중복은 \"다른 줄이 같은 id 를 가진\" 상태일 때뿐이다. */\n if (echoed >= 0) {\n /* 남는 것은 에코 줄이다. 응답에만 있는 것(제안)을 그 줄에 옮기고 대기 줄을 지운다. */\n nextLines[echoed] = { ...nextLines[echoed], ...patch, id: serverId ?? nextLines[echoed].id }\n nextLines.splice(idx, 1)\n return { lines: nextLines, seen }\n }\n const nextSeen = serverId ? new Set(seen).add(serverId) : seen\n nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }\n return { lines: nextLines, seen: nextSeen }\n}\n"]}
|
|
@@ -7,3 +7,32 @@ export interface ParsedProposalOutcome {
|
|
|
7
7
|
}
|
|
8
8
|
/** Undefined when the row is any other system line — this must not guess. */
|
|
9
9
|
export declare function parseProposalOutcomeLine(content: string): ParsedProposalOutcome | undefined;
|
|
10
|
+
/** The key of a "requested" row (server `formatProposalRequestedLine`), or undefined for any other line. */
|
|
11
|
+
export declare function parseProposalRequestLine(content: string): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* What a proposal card says, from the conversation's rows (ADR-0097 Q-D4 ④).
|
|
14
|
+
*
|
|
15
|
+
* ready nobody has taken it - the run button
|
|
16
|
+
* applying this page asked the host and no answer came yet - elapsed time, no button
|
|
17
|
+
* unconfirmed it was requested (here before a refresh, or in another window) and no answer is
|
|
18
|
+
* recorded - "check result", which asks the host what happened and never runs it again
|
|
19
|
+
* answered the host's verdict
|
|
20
|
+
*
|
|
21
|
+
* Time never turns `applying` into a failure (ADR-0070 A12-a decision ②): all that is known is that
|
|
22
|
+
* no answer came.
|
|
23
|
+
*/
|
|
24
|
+
export type ProposalCardState = {
|
|
25
|
+
state: 'ready';
|
|
26
|
+
} | {
|
|
27
|
+
state: 'applying';
|
|
28
|
+
} | {
|
|
29
|
+
state: 'unconfirmed';
|
|
30
|
+
} | {
|
|
31
|
+
state: 'answered';
|
|
32
|
+
outcome: ParsedProposalOutcome;
|
|
33
|
+
};
|
|
34
|
+
export declare function proposalCardState(key: string, seen: {
|
|
35
|
+
outcomes: ReadonlyMap<string, ParsedProposalOutcome>;
|
|
36
|
+
requested: ReadonlySet<string>;
|
|
37
|
+
sentHere: boolean;
|
|
38
|
+
}): ProposalCardState;
|
|
@@ -12,4 +12,19 @@ export function parseProposalOutcomeLine(content) {
|
|
|
12
12
|
total: matched[5] === undefined ? undefined : Number(matched[5])
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
const REQUESTED = /^proposal (.+): requested$/;
|
|
16
|
+
/** The key of a "requested" row (server `formatProposalRequestedLine`), or undefined for any other line. */
|
|
17
|
+
export function parseProposalRequestLine(content) {
|
|
18
|
+
return REQUESTED.exec((content ?? '').trim())?.[1];
|
|
19
|
+
}
|
|
20
|
+
export function proposalCardState(key, seen) {
|
|
21
|
+
const outcome = seen.outcomes.get(key);
|
|
22
|
+
if (outcome)
|
|
23
|
+
return { state: 'answered', outcome };
|
|
24
|
+
if (seen.sentHere)
|
|
25
|
+
return { state: 'applying' };
|
|
26
|
+
if (seen.requested.has(key))
|
|
27
|
+
return { state: 'unconfirmed' };
|
|
28
|
+
return { state: 'ready' };
|
|
29
|
+
}
|
|
15
30
|
//# sourceMappingURL=proposal-outcome-line.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal-outcome-line.js","sourceRoot":"","sources":["../../client/components/proposal-outcome-line.ts"],"names":[],"mappings":"AAkBA,MAAM,IAAI,GAAG,6FAA6F,CAAA;AAE1G,6EAA6E;AAC7E,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAE9B,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QACf,OAAO,EAAE,OAAO,CAAC,CAAC,CAAqC;QACvD,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,SAAS;QAClC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAClE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACjE,CAAA;AACH,CAAC","sourcesContent":["/*\n * Reading back what the host did with a proposal (ADR-0070 A12-a).\n *\n * The outcome arrives as a `system` row in the conversation — broadcast while the panel is open,\n * and part of the history after a refresh. The panel reads the card's state from it, so a card\n * shows what happened rather than what was pressed.\n *\n * The shape is the server's (`server/service/chat-message/proposal-outcome.ts`). This parser is\n * tested against that formatter, so the two cannot drift apart without a test saying so.\n */\nexport interface ParsedProposalOutcome {\n key: string\n verdict: 'accepted' | 'refused' | 'unavailable' | 'declined'\n errorCode?: string\n applied?: number\n total?: number\n}\n\nconst LINE = /^proposal (.+): (accepted|refused|unavailable|declined)(?: \\(([^)]*)\\))?(?: (\\d+)\\/(\\d+))?$/\n\n/** Undefined when the row is any other system line — this must not guess. */\nexport function parseProposalOutcomeLine(content: string): ParsedProposalOutcome | undefined {\n const matched = LINE.exec((content ?? '').trim())\n if (!matched) return undefined\n\n return {\n key: matched[1],\n verdict: matched[2] as ParsedProposalOutcome['verdict'],\n errorCode: matched[3] || undefined,\n applied: matched[4] === undefined ? undefined : Number(matched[4]),\n total: matched[5] === undefined ? undefined : Number(matched[5])\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"proposal-outcome-line.js","sourceRoot":"","sources":["../../client/components/proposal-outcome-line.ts"],"names":[],"mappings":"AAkBA,MAAM,IAAI,GAAG,6FAA6F,CAAA;AAE1G,6EAA6E;AAC7E,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAE9B,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QACf,OAAO,EAAE,OAAO,CAAC,CAAC,CAAqC;QACvD,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,SAAS;QAClC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAClE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACjE,CAAA;AACH,CAAC;AAED,MAAM,SAAS,GAAG,4BAA4B,CAAA;AAE9C,4GAA4G;AAC5G,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACpD,CAAC;AAoBD,MAAM,UAAU,iBAAiB,CAC/B,GAAW,EACX,IAAiH;IAEjH,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACtC,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,CAAA;IAClD,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAA;IAC/C,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;IAC5D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC3B,CAAC","sourcesContent":["/*\n * Reading back what the host did with a proposal (ADR-0070 A12-a).\n *\n * The outcome arrives as a `system` row in the conversation — broadcast while the panel is open,\n * and part of the history after a refresh. The panel reads the card's state from it, so a card\n * shows what happened rather than what was pressed.\n *\n * The shape is the server's (`server/service/chat-message/proposal-outcome.ts`). This parser is\n * tested against that formatter, so the two cannot drift apart without a test saying so.\n */\nexport interface ParsedProposalOutcome {\n key: string\n verdict: 'accepted' | 'refused' | 'unavailable' | 'declined'\n errorCode?: string\n applied?: number\n total?: number\n}\n\nconst LINE = /^proposal (.+): (accepted|refused|unavailable|declined)(?: \\(([^)]*)\\))?(?: (\\d+)\\/(\\d+))?$/\n\n/** Undefined when the row is any other system line — this must not guess. */\nexport function parseProposalOutcomeLine(content: string): ParsedProposalOutcome | undefined {\n const matched = LINE.exec((content ?? '').trim())\n if (!matched) return undefined\n\n return {\n key: matched[1],\n verdict: matched[2] as ParsedProposalOutcome['verdict'],\n errorCode: matched[3] || undefined,\n applied: matched[4] === undefined ? undefined : Number(matched[4]),\n total: matched[5] === undefined ? undefined : Number(matched[5])\n }\n}\n\nconst REQUESTED = /^proposal (.+): requested$/\n\n/** The key of a \"requested\" row (server `formatProposalRequestedLine`), or undefined for any other line. */\nexport function parseProposalRequestLine(content: string): string | undefined {\n return REQUESTED.exec((content ?? '').trim())?.[1]\n}\n\n/**\n * What a proposal card says, from the conversation's rows (ADR-0097 Q-D4 ④).\n *\n * ready nobody has taken it - the run button\n * applying this page asked the host and no answer came yet - elapsed time, no button\n * unconfirmed it was requested (here before a refresh, or in another window) and no answer is\n * recorded - \"check result\", which asks the host what happened and never runs it again\n * answered the host's verdict\n *\n * Time never turns `applying` into a failure (ADR-0070 A12-a decision ②): all that is known is that\n * no answer came.\n */\nexport type ProposalCardState =\n | { state: 'ready' }\n | { state: 'applying' }\n | { state: 'unconfirmed' }\n | { state: 'answered'; outcome: ParsedProposalOutcome }\n\nexport function proposalCardState(\n key: string,\n seen: { outcomes: ReadonlyMap<string, ParsedProposalOutcome>; requested: ReadonlySet<string>; sentHere: boolean }\n): ProposalCardState {\n const outcome = seen.outcomes.get(key)\n if (outcome) return { state: 'answered', outcome }\n if (seen.sentHere) return { state: 'applying' }\n if (seen.requested.has(key)) return { state: 'unconfirmed' }\n return { state: 'ready' }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ADR-0097 Q-D4 ④ — the chat asks the server before it asks the host, and a card that was requested and
|
|
3
|
+
* never answered offers "check result", never "run".
|
|
4
|
+
*
|
|
5
|
+
* The component itself needs a browser; the order of its steps is read from the source, and the rules
|
|
6
|
+
* it follows are tested on their own (`proposal-outcome.test.ts`).
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { remoteMessageToLine } from './chat-echo-dedup';
|
|
11
|
+
const chat = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf8');
|
|
12
|
+
const dock = readFileSync(join(__dirname, 'assistant-dock.ts'), 'utf8');
|
|
13
|
+
const body = (source, start) => {
|
|
14
|
+
const from = source.indexOf(start);
|
|
15
|
+
expect(from).toBeGreaterThan(0);
|
|
16
|
+
return source.slice(from, source.indexOf('\n }\n', from));
|
|
17
|
+
};
|
|
18
|
+
it('★ the host is asked only after the server granted the request', () => {
|
|
19
|
+
const run = body(chat, 'private async executeProposal(');
|
|
20
|
+
const asked = run.indexOf('await this.requestProposal(key)');
|
|
21
|
+
const dispatched = run.indexOf("new CustomEvent('assistant-proposal-execute'");
|
|
22
|
+
expect(asked).toBeGreaterThan(0);
|
|
23
|
+
expect(dispatched).toBeGreaterThan(asked);
|
|
24
|
+
expect(run.slice(asked, dispatched)).toMatch(/if \(!granted\) \{[\s\S]*return\n/);
|
|
25
|
+
});
|
|
26
|
+
it('★ a requested card without an answer checks the result — it never runs again', () => {
|
|
27
|
+
const unconfirmed = chat.slice(chat.indexOf("card.state === 'unconfirmed'"), chat.indexOf(': html`', chat.indexOf("card.state === 'unconfirmed'")));
|
|
28
|
+
expect(unconfirmed).toMatch(/this\.checkProposal\(p, key\)/);
|
|
29
|
+
expect(unconfirmed).not.toMatch(/executeProposal/);
|
|
30
|
+
});
|
|
31
|
+
it('★ "check result" reports only what the host knows, and runs nothing', () => {
|
|
32
|
+
const check = body(dock, 'private onProposalCheck = async');
|
|
33
|
+
expect(check).toMatch(/this\.surface\?\.proposalOutcome/);
|
|
34
|
+
expect(check).toMatch(/if \(!outcome\?\.verdict\) \{[\s\S]*return/);
|
|
35
|
+
expect(check).not.toMatch(/runProposal|assistant-proposal-execute/);
|
|
36
|
+
});
|
|
37
|
+
it('★ a stored answer brings its cards back after a refresh', () => {
|
|
38
|
+
const line = remoteMessageToLine({ id: 'm1', role: 'assistant', content: 'here', proposalsJson: [{ tool: 'proposeFigureCandidate', key: 'k1' }] });
|
|
39
|
+
expect(line.proposals).toEqual([{ tool: 'proposeFigureCandidate', key: 'k1' }]);
|
|
40
|
+
expect(remoteMessageToLine({ id: 'm2', role: 'assistant', content: 'plain', proposalsJson: [] }).proposals).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
it('the chat reads stored proposals from history and from the broadcast', () => {
|
|
43
|
+
expect(chat.split('proposalsJson').length - 1).toBeGreaterThanOrEqual(2);
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=proposal-request.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal-request.test.js","sourceRoot":"","sources":["../../client/components/proposal-request.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * ADR-0097 Q-D4 ④ — the chat asks the server before it asks the host, and a card that was requested and\n * never answered offers \"check result\", never \"run\".\n *\n * The component itself needs a browser; the order of its steps is read from the source, and the rules\n * it follows are tested on their own (`proposal-outcome.test.ts`).\n */\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { remoteMessageToLine } from './chat-echo-dedup'\n\nconst chat = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf8')\nconst dock = readFileSync(join(__dirname, 'assistant-dock.ts'), 'utf8')\n\nconst body = (source: string, start: string) => {\n const from = source.indexOf(start)\n expect(from).toBeGreaterThan(0)\n return source.slice(from, source.indexOf('\\n }\\n', from))\n}\n\nit('★ the host is asked only after the server granted the request', () => {\n const run = body(chat, 'private async executeProposal(')\n const asked = run.indexOf('await this.requestProposal(key)')\n const dispatched = run.indexOf(\"new CustomEvent('assistant-proposal-execute'\")\n expect(asked).toBeGreaterThan(0)\n expect(dispatched).toBeGreaterThan(asked)\n expect(run.slice(asked, dispatched)).toMatch(/if \\(!granted\\) \\{[\\s\\S]*return\\n/)\n})\n\nit('★ a requested card without an answer checks the result — it never runs again', () => {\n const unconfirmed = chat.slice(chat.indexOf(\"card.state === 'unconfirmed'\"), chat.indexOf(': html`', chat.indexOf(\"card.state === 'unconfirmed'\")))\n expect(unconfirmed).toMatch(/this\\.checkProposal\\(p, key\\)/)\n expect(unconfirmed).not.toMatch(/executeProposal/)\n})\n\nit('★ \"check result\" reports only what the host knows, and runs nothing', () => {\n const check = body(dock, 'private onProposalCheck = async')\n expect(check).toMatch(/this\\.surface\\?\\.proposalOutcome/)\n expect(check).toMatch(/if \\(!outcome\\?\\.verdict\\) \\{[\\s\\S]*return/)\n expect(check).not.toMatch(/runProposal|assistant-proposal-execute/)\n})\n\nit('★ a stored answer brings its cards back after a refresh', () => {\n const line: any = remoteMessageToLine({ id: 'm1', role: 'assistant', content: 'here', proposalsJson: [{ tool: 'proposeFigureCandidate', key: 'k1' }] })\n expect(line.proposals).toEqual([{ tool: 'proposeFigureCandidate', key: 'k1' }])\n expect((remoteMessageToLine({ id: 'm2', role: 'assistant', content: 'plain', proposalsJson: [] }) as any).proposals).toBeUndefined()\n})\n\nit('the chat reads stored proposals from history and from the broadcast', () => {\n expect(chat.split('proposalsJson').length - 1).toBeGreaterThanOrEqual(2)\n})\n"]}
|