@unofficialbox/box-open-elements 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/components/actions/menu-item.d.ts +2 -0
  2. package/dist/components/actions/menu-item.js +4 -0
  3. package/dist/components/collections/card.js +6 -0
  4. package/dist/components/collections/check-list.d.ts +10 -0
  5. package/dist/components/collections/check-list.js +8 -0
  6. package/dist/components/collections/document-list.d.ts +10 -0
  7. package/dist/components/collections/document-list.js +8 -0
  8. package/dist/components/collections/fact-list.d.ts +10 -0
  9. package/dist/components/collections/fact-list.js +8 -0
  10. package/dist/components/collections/result-blocks.d.ts +54 -0
  11. package/dist/components/collections/result-blocks.js +74 -0
  12. package/dist/components/feedback/alert.d.ts +2 -0
  13. package/dist/components/feedback/alert.js +17 -13
  14. package/dist/components/feedback/progress-bar.d.ts +9 -0
  15. package/dist/components/feedback/progress-bar.js +29 -3
  16. package/dist/components/feedback/status-icon.d.ts +10 -0
  17. package/dist/components/feedback/status-icon.js +26 -0
  18. package/dist/components/feedback/toast.d.ts +10 -1
  19. package/dist/components/feedback/toast.js +69 -27
  20. package/dist/components/forms/number-input.d.ts +1 -0
  21. package/dist/components/forms/number-input.js +9 -6
  22. package/dist/components/forms/search-field.js +16 -0
  23. package/dist/components/overlays/context-menu.d.ts +2 -0
  24. package/dist/components/overlays/context-menu.js +7 -0
  25. package/dist/components/overlays/dialog.js +19 -3
  26. package/dist/components/overlays/popover.js +6 -1
  27. package/dist/element-maps.d.ts +49 -19
  28. package/dist/entries/agent-workspace.d.ts +1 -0
  29. package/dist/entries/agent-workspace.js +1 -0
  30. package/dist/entries/call-console.d.ts +1 -0
  31. package/dist/entries/call-console.js +1 -0
  32. package/dist/entries/check-list.d.ts +1 -0
  33. package/dist/entries/check-list.js +1 -0
  34. package/dist/entries/document-list.d.ts +1 -0
  35. package/dist/entries/document-list.js +1 -0
  36. package/dist/entries/fact-list.d.ts +1 -0
  37. package/dist/entries/fact-list.js +1 -0
  38. package/dist/entries/result-blocks.d.ts +1 -0
  39. package/dist/entries/result-blocks.js +1 -0
  40. package/dist/entries/run-summary.d.ts +1 -0
  41. package/dist/entries/run-summary.js +1 -0
  42. package/dist/entries/status-icon.d.ts +1 -0
  43. package/dist/entries/status-icon.js +1 -0
  44. package/dist/foundations/a11y/announcer.d.ts +12 -0
  45. package/dist/foundations/a11y/announcer.js +75 -0
  46. package/dist/foundations/a11y/focus.js +46 -25
  47. package/dist/foundations/a11y/index.d.ts +2 -0
  48. package/dist/foundations/a11y/index.js +1 -0
  49. package/dist/foundations/icons/box-iconography.d.ts +472 -472
  50. package/dist/foundations/icons/box-iconography.generated.d.ts +1151 -679
  51. package/dist/foundations/icons/box-iconography.generated.js +1006 -3588
  52. package/dist/foundations/icons/glyphs/index.d.ts +1 -0
  53. package/dist/foundations/icons/glyphs/index.js +2 -0
  54. package/dist/foundations/icons/glyphs/package.json +1 -0
  55. package/dist/foundations/motion/behaviors.d.ts +8 -0
  56. package/dist/foundations/motion/behaviors.js +24 -0
  57. package/dist/foundations/motion/index.d.ts +2 -0
  58. package/dist/foundations/motion/index.js +2 -0
  59. package/dist/foundations/motion/scroll-pin.d.ts +19 -0
  60. package/dist/foundations/motion/scroll-pin.js +60 -0
  61. package/dist/foundations/motion/tokens.d.ts +2 -0
  62. package/dist/foundations/motion/tokens.js +2 -0
  63. package/dist/foundations/overlay/positioning.js +7 -0
  64. package/dist/foundations/profiles/registry.d.ts +3 -0
  65. package/dist/foundations/profiles/registry.js +3 -0
  66. package/dist/foundations/profiles/types.d.ts +3 -0
  67. package/dist/foundations/status/index.d.ts +6 -0
  68. package/dist/foundations/status/index.js +35 -0
  69. package/dist/foundations/tokens/box-dark.js +4 -1
  70. package/dist/foundations/tokens/box-defaults.js +3 -0
  71. package/dist/foundations/tokens/registry.d.ts +3 -0
  72. package/dist/foundations/tokens/registry.js +3 -0
  73. package/dist/foundations/tokens/types.d.ts +3 -0
  74. package/dist/index.d.ts +12 -0
  75. package/dist/index.js +12 -0
  76. package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
  77. package/dist/patterns/agent-chat/agent-chat.js +146 -24
  78. package/dist/patterns/agent-chat/controller.d.ts +3 -1
  79. package/dist/patterns/agent-chat/controller.js +92 -16
  80. package/dist/patterns/agent-chat/index.d.ts +1 -0
  81. package/dist/patterns/agent-chat/index.js +1 -0
  82. package/dist/patterns/agent-chat/ndjson.d.ts +5 -0
  83. package/dist/patterns/agent-chat/ndjson.js +41 -0
  84. package/dist/patterns/agent-chat/types.d.ts +44 -1
  85. package/dist/patterns/agent-workspace/agent-workspace.d.ts +28 -0
  86. package/dist/patterns/agent-workspace/agent-workspace.js +215 -0
  87. package/dist/patterns/agent-workspace/controller.d.ts +38 -0
  88. package/dist/patterns/agent-workspace/controller.js +65 -0
  89. package/dist/patterns/agent-workspace/index.d.ts +2 -0
  90. package/dist/patterns/agent-workspace/index.js +2 -0
  91. package/dist/patterns/call-console/call-console.d.ts +20 -0
  92. package/dist/patterns/call-console/call-console.js +201 -0
  93. package/dist/patterns/call-console/controller.d.ts +23 -0
  94. package/dist/patterns/call-console/controller.js +50 -0
  95. package/dist/patterns/call-console/http-transport.d.ts +3 -0
  96. package/dist/patterns/call-console/http-transport.js +26 -0
  97. package/dist/patterns/call-console/index.d.ts +4 -0
  98. package/dist/patterns/call-console/index.js +4 -0
  99. package/dist/patterns/call-console/types.d.ts +32 -0
  100. package/dist/patterns/call-console/types.js +8 -0
  101. package/dist/patterns/content-explorer/adapters/action-menu.js +7 -5
  102. package/dist/patterns/content-explorer/adapters/toolbar.js +6 -2
  103. package/dist/patterns/content-uploader/content-uploader.js +2 -1
  104. package/dist/patterns/file-request/file-request-builder.js +3 -0
  105. package/dist/patterns/flow-builder/flow-builder.d.ts +48 -0
  106. package/dist/patterns/flow-builder/flow-builder.js +200 -0
  107. package/dist/patterns/flow-builder/index.d.ts +3 -0
  108. package/dist/patterns/flow-builder/index.js +3 -0
  109. package/dist/patterns/flow-builder/model.d.ts +33 -0
  110. package/dist/patterns/flow-builder/model.js +28 -0
  111. package/dist/patterns/flow-builder/primitives.d.ts +47 -0
  112. package/dist/patterns/flow-builder/primitives.js +192 -0
  113. package/dist/patterns/lineage/lineage-graph.js +26 -6
  114. package/dist/patterns/notifications/notification-inbox.js +19 -3
  115. package/dist/patterns/preview/annotation-inspector.js +2 -2
  116. package/dist/patterns/run/index.d.ts +2 -0
  117. package/dist/patterns/run/index.js +2 -0
  118. package/dist/patterns/run/progress.d.ts +24 -0
  119. package/dist/patterns/run/progress.js +32 -0
  120. package/dist/patterns/run/run-summary.d.ts +21 -0
  121. package/dist/patterns/run/run-summary.js +82 -0
  122. package/dist/patterns/run/run-trace.d.ts +1 -0
  123. package/dist/patterns/run/run-trace.js +14 -3
  124. package/dist/patterns/undo/index.d.ts +19 -0
  125. package/dist/patterns/undo/index.js +106 -0
  126. package/package.json +23 -2
@@ -0,0 +1,41 @@
1
+ /** Incremental UTF-8 decoding with a bounded line buffer and early-reader cleanup. */
2
+ export async function* readNdjson(stream, options = {}) {
3
+ const reader = stream.getReader();
4
+ const decoder = new TextDecoder("utf-8", { fatal: true });
5
+ const limit = options.maxLineLength ?? 1048576;
6
+ let buffer = "";
7
+ let finished = false;
8
+ const abort = () => { void reader.cancel(options.signal?.reason); };
9
+ options.signal?.addEventListener("abort", abort, { once: true });
10
+ try {
11
+ while (true) {
12
+ options.signal?.throwIfAborted();
13
+ const { value, done } = await reader.read();
14
+ options.signal?.throwIfAborted();
15
+ buffer += decoder.decode(value, { stream: !done });
16
+ let newline;
17
+ while ((newline = buffer.indexOf("\n")) !== -1) {
18
+ const line = buffer.slice(0, newline).trim();
19
+ buffer = buffer.slice(newline + 1);
20
+ if (line.length > limit)
21
+ throw new Error("NDJSON line exceeds limit");
22
+ if (line)
23
+ yield JSON.parse(line);
24
+ }
25
+ if (buffer.length > limit)
26
+ throw new Error("NDJSON line exceeds limit");
27
+ if (done) {
28
+ finished = true;
29
+ if (buffer.trim())
30
+ yield JSON.parse(buffer);
31
+ break;
32
+ }
33
+ }
34
+ }
35
+ finally {
36
+ options.signal?.removeEventListener("abort", abort);
37
+ if (!finished)
38
+ await reader.cancel().catch(() => { });
39
+ reader.releaseLock();
40
+ }
41
+ }
@@ -1,4 +1,15 @@
1
+ import type { ResultBlock } from "../../components/collections/result-blocks.js";
2
+ import type { RunStep } from "../run/types.js";
3
+ import type { AgentTodo } from "../run/progress.js";
1
4
  export type AgentRole = "user" | "agent";
5
+ export interface AgentPromptOption {
6
+ label: string;
7
+ prompt: string;
8
+ }
9
+ export interface AgentCompleteness {
10
+ status: "streaming" | "complete" | "needs_input" | "error" | "incomplete";
11
+ missing: number[];
12
+ }
2
13
  export type AgentMessageStatus = "streaming" | "complete" | "error";
3
14
  /** Same shape as the timeline's evidence chips — deep-linkable supporting content. */
4
15
  export interface AgentCitation {
@@ -25,6 +36,8 @@ export interface AgentActionProposal {
25
36
  }>;
26
37
  /** Present once a decision has landed. */
27
38
  decision?: AgentActionDecision;
39
+ outcome?: "done" | "failed";
40
+ resolving?: AgentActionDecision;
28
41
  note?: string;
29
42
  }
30
43
  export interface AgentChatMessage {
@@ -39,11 +52,21 @@ export interface AgentChatMessage {
39
52
  timestamp?: string;
40
53
  citations: AgentCitation[];
41
54
  proposals: AgentActionProposal[];
55
+ blocks?: ResultBlock[];
56
+ trace?: RunStep[];
57
+ todos?: AgentTodo[];
58
+ options?: AgentPromptOption[];
59
+ context?: Record<string, unknown>;
60
+ completeness?: AgentCompleteness;
61
+ startedAt?: number;
62
+ endedAt?: number;
42
63
  /** Human-readable failure detail when `status` is "error". */
43
64
  errorMessage?: string;
44
65
  }
45
66
  /** Typed stream events a transport delivers while a reply generates. */
46
67
  export type AgentStreamEvent = {
68
+ seq?: number;
69
+ } & ({
47
70
  kind: "delta";
48
71
  text: string;
49
72
  } | {
@@ -52,8 +75,27 @@ export type AgentStreamEvent = {
52
75
  } | {
53
76
  kind: "proposal";
54
77
  proposal: AgentActionProposal;
55
- };
78
+ } | {
79
+ kind: "block";
80
+ block: ResultBlock;
81
+ } | {
82
+ kind: "trace";
83
+ step: RunStep;
84
+ } | {
85
+ kind: "todos";
86
+ todos: AgentTodo[];
87
+ } | {
88
+ kind: "options";
89
+ options: AgentPromptOption[];
90
+ } | {
91
+ kind: "context";
92
+ context: Record<string, unknown>;
93
+ } | {
94
+ kind: "done";
95
+ status: "complete" | "needs_input" | "error";
96
+ });
56
97
  export interface AgentSendRequest {
98
+ messageId: string;
57
99
  body: string;
58
100
  token: string;
59
101
  signal?: AbortSignal;
@@ -61,6 +103,7 @@ export interface AgentSendRequest {
61
103
  onEvent: (event: AgentStreamEvent) => void;
62
104
  }
63
105
  export interface AgentResolveActionRequest {
106
+ messageId?: string;
64
107
  proposalId: string;
65
108
  decision: AgentActionDecision;
66
109
  note?: string;
@@ -0,0 +1,28 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import type { AgentWorkspaceController } from "./controller.js";
3
+ export declare class AgentWorkspace extends BaseElement {
4
+ static readonly tagName = "box-agent-workspace";
5
+ static get observedAttributes(): string[];
6
+ private model;
7
+ private unsubscribe?;
8
+ private observer?;
9
+ private mobile;
10
+ private chatsOpen;
11
+ private detailsOpen;
12
+ private initialized;
13
+ private renderedActive;
14
+ private sessions;
15
+ get workspaceController(): AgentWorkspaceController | null;
16
+ set workspaceController(value: AgentWorkspaceController | null);
17
+ connectedCallback(): void;
18
+ disconnectedCallback(): void;
19
+ private listen;
20
+ private resize;
21
+ private storageKey;
22
+ private toggle;
23
+ private closeDrawers;
24
+ private syncPanes;
25
+ protected renderTemplate(): void;
26
+ protected setupListeners(): void;
27
+ protected update(): void;
28
+ }
@@ -0,0 +1,215 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { AgentChat } from "../agent-chat/agent-chat.js";
3
+ import { isSafeHref } from "../internal/safe-href.js";
4
+ import { boeReducedMotionPolicy } from "../../foundations/motion/index.js";
5
+ import { trapTabKey } from "../../foundations/a11y/focus.js";
6
+ const DEFAULT_TAG_NAME = "box-agent-workspace";
7
+ export class AgentWorkspace extends BaseElement {
8
+ static tagName = DEFAULT_TAG_NAME;
9
+ static get observedAttributes() { return ["viewer-key"]; }
10
+ model = null;
11
+ unsubscribe;
12
+ observer;
13
+ mobile = false;
14
+ chatsOpen = true;
15
+ detailsOpen = false;
16
+ initialized = false;
17
+ renderedActive = null;
18
+ sessions = new Map();
19
+ get workspaceController() { return this.model; }
20
+ set workspaceController(value) {
21
+ this.unsubscribe?.();
22
+ this.model = value;
23
+ if (this.isConnected)
24
+ this.listen();
25
+ if (this.isRendered)
26
+ this.update();
27
+ }
28
+ connectedCallback() {
29
+ super.connectedCallback();
30
+ this.listen();
31
+ this.resize();
32
+ if (typeof ResizeObserver !== "undefined") {
33
+ this.observer = new ResizeObserver(() => this.resize());
34
+ this.observer.observe(this);
35
+ }
36
+ }
37
+ disconnectedCallback() { this.unsubscribe?.(); this.observer?.disconnect(); }
38
+ listen() { this.unsubscribe?.(); this.unsubscribe = this.model?.subscribe(() => this.update()); }
39
+ resize() {
40
+ const width = this.getBoundingClientRect().width || 1200;
41
+ const mobile = width < 900;
42
+ if (!this.initialized || mobile !== this.mobile) {
43
+ this.chatsOpen = !mobile;
44
+ this.detailsOpen = width >= 1200;
45
+ if (!mobile) {
46
+ try {
47
+ const saved = JSON.parse(localStorage.getItem(this.storageKey()) ?? "null");
48
+ if (saved && typeof saved.chats === "boolean" && typeof saved.details === "boolean") {
49
+ this.chatsOpen = saved.chats;
50
+ this.detailsOpen = saved.details;
51
+ }
52
+ }
53
+ catch { }
54
+ }
55
+ this.mobile = mobile;
56
+ this.initialized = true;
57
+ }
58
+ this.syncPanes();
59
+ }
60
+ storageKey() { return `boe-workspace:${this.getAttribute("viewer-key") ?? "default"}`; }
61
+ toggle(pane) {
62
+ if (pane === "chats") {
63
+ this.chatsOpen = !this.chatsOpen;
64
+ if (this.mobile)
65
+ this.detailsOpen = false;
66
+ }
67
+ else {
68
+ this.detailsOpen = !this.detailsOpen;
69
+ if (this.mobile)
70
+ this.chatsOpen = false;
71
+ }
72
+ if (!this.mobile)
73
+ try {
74
+ localStorage.setItem(this.storageKey(), JSON.stringify({ chats: this.chatsOpen, details: this.detailsOpen }));
75
+ }
76
+ catch { }
77
+ this.syncPanes();
78
+ if (this.mobile && (this.chatsOpen || this.detailsOpen))
79
+ this.shadowRoot.querySelector(`#${pane} button`)?.focus({ preventScroll: true });
80
+ }
81
+ closeDrawers() { this.chatsOpen = false; this.detailsOpen = false; this.syncPanes(); this.sessions.get(this.model?.activeId ?? "")?.focusComposer(); }
82
+ syncPanes() {
83
+ if (!this.isRendered)
84
+ return;
85
+ this.toggleAttribute("data-mobile", this.mobile);
86
+ for (const [pane, open] of [["chats", this.chatsOpen], ["details", this.detailsOpen]]) {
87
+ const el = this.shadowRoot.getElementById(pane);
88
+ el.inert = !open;
89
+ el.toggleAttribute("data-open", open);
90
+ if (this.mobile && open) {
91
+ el.setAttribute("role", "dialog");
92
+ el.setAttribute("aria-modal", "true");
93
+ el.setAttribute("aria-label", pane === "chats" ? "Conversations" : "Conversation details");
94
+ }
95
+ else {
96
+ el.removeAttribute("role");
97
+ el.removeAttribute("aria-modal");
98
+ }
99
+ this.shadowRoot.querySelector(`[data-toggle="${pane}"]`).setAttribute("aria-expanded", String(open));
100
+ }
101
+ const overlaid = this.mobile && (this.chatsOpen || this.detailsOpen);
102
+ this.shadowRoot.querySelector("#conversation").inert = overlaid;
103
+ this.shadowRoot.querySelector("#scrim").hidden = !overlaid;
104
+ for (const chat of this.sessions.values())
105
+ chat.toggleAttribute("hide-citations", this.detailsOpen);
106
+ }
107
+ renderTemplate() {
108
+ AgentChat.register();
109
+ this.shadowRoot.innerHTML = `<style>
110
+ :host{display:block;height:100%;min-height:30rem;color:var(--boe-token-text-text,#222);font:inherit}:host([hidden]){display:none !important}
111
+ *{box-sizing:border-box} section{position:relative;overflow:clip;height:100%;display:flex;min-width:0}
112
+ aside{width:0;overflow:clip;transition:width var(--boe-profile-motion-panel,320ms) ease-out;background:var(--boe-token-surface-surface,#fff)} aside[data-open]{width:280px;flex-shrink:0}
113
+ aside>div{width:280px;height:100%;padding:1rem;overflow:auto} main{min-width:0;flex:1;overflow:clip;display:flex;flex-direction:column;background:var(--boe-token-surface-surface,#fff)} nav{display:flex;justify-content:space-between;padding:.5rem}
114
+ #conversation{flex:1;min-height:0} #sessions,box-agent-chat{height:100%}
115
+ box-agent-chat::part(panel){height:100%;box-sizing:border-box;display:flex;flex-direction:column;border:0;border-radius:0}
116
+ box-agent-chat::part(thread){flex:1;min-height:0;max-block-size:none;align-content:start}
117
+ button,a{font:inherit;color:inherit} button{cursor:pointer;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:999px;background:var(--boe-token-surface-surface,#fff);min-height:32px;padding:.3rem .8rem;font-size:13px;font-weight:600}
118
+ button:focus-visible,a:focus-visible{outline:2px solid var(--boe-token-surface-surface-brand,#0061d5);outline-offset:2px}
119
+ [part="chat"]{display:block;text-align:left;width:100%;border:0;background:none;padding:.65rem;border-radius:6px}
120
+ [aria-current="page"]{background:var(--boe-token-surface-surface-secondary,#f3f3f3)} small{display:block;color:var(--boe-token-text-text-secondary,#666)}
121
+ #scrim{position:absolute;inset:0;background:#0005;border:0;border-radius:0;z-index:2} [hidden]{display:none!important}
122
+ [data-close]{display:none} :host([data-mobile]) [data-close]{display:block;margin-bottom:.5rem}
123
+ :host([data-mobile]) aside{position:absolute;inset-block:0;z-index:3} :host([data-mobile]) #details{right:0}
124
+ #approvals button,#sources a{display:block;margin:.5rem 0;text-align:left} #context{white-space:pre-wrap;overflow-wrap:anywhere}
125
+ ${boeReducedMotionPolicy}</style><section part="workspace">
126
+ <aside id="chats" part="chats"><div><button data-close>Close conversations</button><slot name="chats"><button id="new">New chat</button><div id="list"></div></slot></div></aside>
127
+ <main><nav><button data-toggle="chats" aria-controls="chats">Conversations</button><button data-toggle="details" aria-controls="details">Details</button></nav><div id="conversation" part="conversation"><slot name="conversation"><div id="sessions"></div></slot></div></main>
128
+ <button id="scrim" aria-label="Close panel" hidden></button>
129
+ <aside id="details" part="details"><div><button data-close>Close details</button><slot name="details"><h2>Conversation details</h2><div id="context"></div><h3>Approvals</h3><div id="approvals"></div><h3>Sources</h3><div id="sources"></div></slot></div></aside></section>`;
130
+ }
131
+ setupListeners() {
132
+ this.shadowRoot.querySelector("#new").addEventListener("click", () => { this.model?.newChat(); if (this.mobile)
133
+ this.closeDrawers(); });
134
+ this.shadowRoot.querySelector("#scrim").addEventListener("click", () => this.closeDrawers());
135
+ this.shadowRoot.querySelectorAll("[data-close]").forEach(b => b.addEventListener("click", () => this.closeDrawers()));
136
+ this.shadowRoot.querySelectorAll("[data-toggle]").forEach(b => b.addEventListener("click", () => this.toggle(b.dataset.toggle)));
137
+ this.shadowRoot.addEventListener("keydown", event => {
138
+ const e = event;
139
+ if (e.key === "Escape" && this.mobile) {
140
+ e.preventDefault();
141
+ this.closeDrawers();
142
+ }
143
+ if (e.key === "Tab" && this.mobile && (this.chatsOpen || this.detailsOpen))
144
+ trapTabKey(e, this.shadowRoot.getElementById(this.chatsOpen ? "chats" : "details"));
145
+ });
146
+ }
147
+ update() {
148
+ if (!this.isRendered)
149
+ return;
150
+ const list = this.shadowRoot.querySelector("#list");
151
+ const activeRow = this.shadowRoot.activeElement?.getAttribute("data-chat-id");
152
+ list.replaceChildren();
153
+ for (const summary of this.model?.summaries ?? []) {
154
+ const b = document.createElement("button");
155
+ b.setAttribute("part", "chat");
156
+ b.dataset.chatId = summary.id;
157
+ b.textContent = summary.title;
158
+ if (summary.id === this.model?.activeId)
159
+ b.setAttribute("aria-current", "page");
160
+ const small = document.createElement("small");
161
+ small.textContent = summary.status;
162
+ b.append(small);
163
+ b.addEventListener("click", () => { this.model?.select(summary.id); if (this.mobile)
164
+ this.closeDrawers(); });
165
+ list.append(b);
166
+ if (summary.id === activeRow)
167
+ b.focus({ preventScroll: true });
168
+ }
169
+ for (const [id, element] of this.sessions)
170
+ if (!this.model?.chats.some(c => c.id === id && c.controller === element.chatController)) {
171
+ element.remove();
172
+ this.sessions.delete(id);
173
+ }
174
+ for (const chat of this.model?.chats ?? []) {
175
+ let element = this.sessions.get(chat.id);
176
+ if (!element) {
177
+ element = new AgentChat();
178
+ element.chatController = chat.controller;
179
+ this.sessions.set(chat.id, element);
180
+ this.shadowRoot.querySelector("#sessions").append(element);
181
+ }
182
+ element.hidden = chat.id !== this.model?.activeId;
183
+ element.inert = element.hidden;
184
+ }
185
+ const details = this.model?.details;
186
+ this.shadowRoot.querySelector("#context").textContent = details?.context ? Object.entries(details.context).map(([key, value]) => `${key}: ${typeof value === "object" ? JSON.stringify(value) : String(value)}`).join("\n") : "";
187
+ const approvals = this.shadowRoot.querySelector("#approvals");
188
+ approvals.replaceChildren();
189
+ for (const { messageId, proposal } of details?.approvals ?? []) {
190
+ const b = document.createElement("button");
191
+ b.textContent = `${proposal.title} — ${proposal.outcome === "failed" ? "Approved · didn't complete" : proposal.decision ?? "Needs your approval"}`;
192
+ b.addEventListener("click", () => { if (this.mobile)
193
+ this.closeDrawers(); this.sessions.get(this.model?.activeId ?? "")?.focusProposal(messageId, proposal.id); });
194
+ approvals.append(b);
195
+ }
196
+ const sources = this.shadowRoot.querySelector("#sources");
197
+ sources.replaceChildren();
198
+ for (const source of details?.sources ?? []) {
199
+ const el = document.createElement(source.href && isSafeHref(source.href) ? "a" : "span");
200
+ el.textContent = source.label;
201
+ if (el instanceof HTMLAnchorElement) {
202
+ el.href = source.href;
203
+ el.target = "_blank";
204
+ el.rel = "noopener noreferrer";
205
+ }
206
+ sources.append(el);
207
+ }
208
+ this.syncPanes();
209
+ if (this.renderedActive !== this.model?.activeId) {
210
+ this.renderedActive = this.model?.activeId ?? null;
211
+ this.sessions.get(this.renderedActive ?? "")?.focusComposer();
212
+ }
213
+ }
214
+ }
215
+ AgentWorkspace.register();
@@ -0,0 +1,38 @@
1
+ import { AgentChatController } from "../agent-chat/controller.js";
2
+ import type { AgentActionProposal, AgentCitation, AgentChatState } from "../agent-chat/types.js";
3
+ export interface WorkspaceChat {
4
+ id: string;
5
+ controller: AgentChatController;
6
+ }
7
+ export interface WorkspaceSummary {
8
+ id: string;
9
+ title: string;
10
+ status: string;
11
+ }
12
+ export interface WorkspaceDetails {
13
+ context?: Record<string, unknown>;
14
+ approvals: {
15
+ messageId: string;
16
+ proposal: AgentActionProposal;
17
+ }[];
18
+ sources: AgentCitation[];
19
+ }
20
+ export declare const conversationTitle: (body: string, max?: number) => string;
21
+ export declare function conversationDetails(state: Readonly<AgentChatState>): WorkspaceDetails;
22
+ /** Owns independent sessions. Selecting a chat never disconnects another one. */
23
+ export declare class AgentWorkspaceController {
24
+ private readonly createSession;
25
+ readonly chats: WorkspaceChat[];
26
+ activeId: string | null;
27
+ private seq;
28
+ private listeners;
29
+ private subscriptions;
30
+ constructor(createSession: () => AgentChatController);
31
+ subscribe(listener: () => void): () => void;
32
+ private emit;
33
+ newChat(): WorkspaceChat;
34
+ select(id: string): void;
35
+ get summaries(): WorkspaceSummary[];
36
+ get details(): WorkspaceDetails;
37
+ destroy(): void;
38
+ }
@@ -0,0 +1,65 @@
1
+ export const conversationTitle = (body, max = 48) => {
2
+ const text = body.trim().replace(/\s+/g, " ");
3
+ if (text.length <= max)
4
+ return text || "New chat";
5
+ const cut = text.slice(0, max);
6
+ const space = cut.lastIndexOf(" ");
7
+ return `${space > 0 ? cut.slice(0, space) : cut}…`;
8
+ };
9
+ export function conversationDetails(state) {
10
+ const sources = new Map();
11
+ const approvals = [];
12
+ let context;
13
+ for (const message of state.messages) {
14
+ if (message.context)
15
+ context = message.context;
16
+ for (const citation of message.citations)
17
+ if (!sources.has(citation.id))
18
+ sources.set(citation.id, citation);
19
+ for (const block of message.blocks ?? [])
20
+ if (block.type === "documents")
21
+ for (const item of block.items) {
22
+ if (!sources.has(item.id))
23
+ sources.set(item.id, { id: item.id, label: item.name, ...(item.href ? { href: item.href } : {}) });
24
+ }
25
+ for (const proposal of message.proposals)
26
+ approvals.push({ messageId: message.id, proposal });
27
+ }
28
+ return { ...(context ? { context } : {}), approvals, sources: [...sources.values()] };
29
+ }
30
+ /** Owns independent sessions. Selecting a chat never disconnects another one. */
31
+ export class AgentWorkspaceController {
32
+ createSession;
33
+ chats = [];
34
+ activeId = null;
35
+ seq = 0;
36
+ listeners = new Set();
37
+ subscriptions = [];
38
+ constructor(createSession) {
39
+ this.createSession = createSession;
40
+ }
41
+ subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
42
+ emit() { this.listeners.forEach(fn => fn()); }
43
+ newChat() {
44
+ let chat = this.chats.find(c => c.controller.getState().messages.length === 0);
45
+ if (!chat) {
46
+ chat = { id: `chat-${++this.seq}`, controller: this.createSession() };
47
+ chat.controller.connect();
48
+ this.chats.push(chat);
49
+ this.subscriptions.push(chat.controller.subscribe("messagesChanged", () => this.emit()), chat.controller.subscribe("streamingChanged", () => this.emit()));
50
+ }
51
+ this.select(chat.id);
52
+ return chat;
53
+ }
54
+ select(id) { if (!this.chats.some(c => c.id === id))
55
+ return; this.activeId = id; this.emit(); }
56
+ get summaries() {
57
+ return this.chats.map(c => {
58
+ const state = c.controller.getState();
59
+ return { id: c.id, title: conversationTitle(state.messages.find(m => m.role === "user")?.body ?? ""),
60
+ status: state.messages.some(m => m.proposals.some(p => !p.decision)) ? "Needs your approval" : state.streaming ? "Working…" : "" };
61
+ });
62
+ }
63
+ get details() { const chat = this.chats.find(c => c.id === this.activeId); return chat ? conversationDetails(chat.controller.getState()) : { approvals: [], sources: [] }; }
64
+ destroy() { this.subscriptions.forEach(fn => fn()); this.subscriptions = []; this.chats.forEach(c => { c.controller.disconnect(); c.controller.destroy(); }); this.listeners.clear(); }
65
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./controller.js";
2
+ export * from "./agent-workspace.js";
@@ -0,0 +1,2 @@
1
+ export * from "./controller.js";
2
+ export * from "./agent-workspace.js";
@@ -0,0 +1,20 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import type { CallConsoleController } from "./controller.js";
3
+ export declare function copyCallText(text: string, doc?: Document): Promise<boolean>;
4
+ export declare class CallConsole extends BaseElement {
5
+ static readonly tagName = "box-call-console";
6
+ private model;
7
+ private unsubscribe?;
8
+ private selected;
9
+ private clearing;
10
+ private rowSignatures;
11
+ get callController(): CallConsoleController | null;
12
+ set callController(value: CallConsoleController | null);
13
+ private listen;
14
+ connectedCallback(): void;
15
+ disconnectedCallback(): void;
16
+ protected renderTemplate(): void;
17
+ protected setupListeners(): void;
18
+ private get filters();
19
+ protected update(): void;
20
+ }