@unofficialbox/box-open-elements 0.18.0 → 0.20.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 (217) hide show
  1. package/dist/components/actions/button-group.js +6 -0
  2. package/dist/components/actions/button.js +6 -0
  3. package/dist/components/actions/icon-button.js +6 -0
  4. package/dist/components/actions/link-button.js +6 -0
  5. package/dist/components/actions/menu-item.js +6 -0
  6. package/dist/components/actions/menu.js +6 -0
  7. package/dist/components/actions/segmented-control.js +6 -0
  8. package/dist/components/actions/toolbar.js +6 -0
  9. package/dist/components/collections/card.js +6 -0
  10. package/dist/components/collections/carousel.js +6 -0
  11. package/dist/components/collections/check-list.d.ts +10 -0
  12. package/dist/components/collections/check-list.js +8 -0
  13. package/dist/components/collections/datalist-item.js +6 -0
  14. package/dist/components/collections/document-list.d.ts +10 -0
  15. package/dist/components/collections/document-list.js +8 -0
  16. package/dist/components/collections/draggable-list.js +6 -0
  17. package/dist/components/collections/fact-list.d.ts +10 -0
  18. package/dist/components/collections/fact-list.js +8 -0
  19. package/dist/components/collections/grid-view.js +6 -0
  20. package/dist/components/collections/pagination.js +6 -0
  21. package/dist/components/collections/result-blocks.d.ts +54 -0
  22. package/dist/components/collections/result-blocks.js +71 -0
  23. package/dist/components/collections/table.js +6 -0
  24. package/dist/components/collections/thumbnail-card.js +6 -0
  25. package/dist/components/collections/tree-grid.js +6 -0
  26. package/dist/components/collections/tree.js +6 -0
  27. package/dist/components/feedback/alert.js +1 -1
  28. package/dist/components/feedback/badge.js +1 -1
  29. package/dist/components/feedback/badgeable.js +6 -0
  30. package/dist/components/feedback/chip.js +6 -0
  31. package/dist/components/feedback/due-badge.js +6 -0
  32. package/dist/components/feedback/empty-state.js +6 -0
  33. package/dist/components/feedback/error-mask.js +6 -0
  34. package/dist/components/feedback/help-text.js +6 -0
  35. package/dist/components/feedback/indicator.js +1 -1
  36. package/dist/components/feedback/nudge.js +6 -0
  37. package/dist/components/feedback/path.js +6 -0
  38. package/dist/components/feedback/progress-bar.d.ts +9 -0
  39. package/dist/components/feedback/progress-bar.js +35 -3
  40. package/dist/components/feedback/progress-ring.js +6 -0
  41. package/dist/components/feedback/progress-steps.js +6 -0
  42. package/dist/components/feedback/skeleton.js +6 -0
  43. package/dist/components/feedback/spinner.js +6 -0
  44. package/dist/components/feedback/status-icon.d.ts +10 -0
  45. package/dist/components/feedback/status-icon.js +26 -0
  46. package/dist/components/feedback/toast.js +1 -1
  47. package/dist/components/files/drop-zone.js +1 -1
  48. package/dist/components/forms/calendar.js +6 -0
  49. package/dist/components/forms/category-selector.js +6 -0
  50. package/dist/components/forms/checkbox-group.js +6 -0
  51. package/dist/components/forms/checkbox.js +6 -0
  52. package/dist/components/forms/color-picker.js +6 -0
  53. package/dist/components/forms/combobox.js +6 -0
  54. package/dist/components/forms/date-field.js +6 -0
  55. package/dist/components/forms/dropdown.js +6 -0
  56. package/dist/components/forms/dual-listbox.js +6 -0
  57. package/dist/components/forms/fieldset.js +6 -0
  58. package/dist/components/forms/multi-select.js +6 -0
  59. package/dist/components/forms/number-input.js +6 -0
  60. package/dist/components/forms/pill-cloud.js +6 -0
  61. package/dist/components/forms/pill-selector-dropdown.js +6 -0
  62. package/dist/components/forms/radio-group.js +6 -0
  63. package/dist/components/forms/range-slider.js +6 -0
  64. package/dist/components/forms/rating.js +6 -0
  65. package/dist/components/forms/rich-text-input.js +6 -0
  66. package/dist/components/forms/search-field.js +6 -0
  67. package/dist/components/forms/select.js +6 -0
  68. package/dist/components/forms/slider.js +6 -0
  69. package/dist/components/forms/spin-button.js +6 -0
  70. package/dist/components/forms/switch.js +6 -0
  71. package/dist/components/forms/tag-input.js +6 -0
  72. package/dist/components/forms/text-area.js +6 -0
  73. package/dist/components/forms/text-field.js +6 -0
  74. package/dist/components/forms/tile-group.js +1 -1
  75. package/dist/components/forms/time-field.js +6 -0
  76. package/dist/components/identity/avatar.js +6 -0
  77. package/dist/components/identity/contact-datalist-item.js +6 -0
  78. package/dist/components/identity/persona.js +6 -0
  79. package/dist/components/layout/app-shell.js +6 -0
  80. package/dist/components/layout/divider.js +6 -0
  81. package/dist/components/layout/grid.js +1 -1
  82. package/dist/components/layout/nav-sidebar.js +6 -0
  83. package/dist/components/layout/section.js +6 -0
  84. package/dist/components/layout/sidebar-toggle-button.js +6 -0
  85. package/dist/components/layout/split-view.js +6 -0
  86. package/dist/components/navigation/accordion.js +6 -0
  87. package/dist/components/navigation/breadcrumb.js +6 -0
  88. package/dist/components/navigation/tabs.js +6 -0
  89. package/dist/components/output/code-block.js +1 -1
  90. package/dist/components/output/formatted-value.js +1 -1
  91. package/dist/components/overlays/command-palette.js +6 -0
  92. package/dist/components/overlays/context-menu.js +6 -0
  93. package/dist/components/overlays/guide-tooltip.js +6 -0
  94. package/dist/components/overlays/popover.js +6 -0
  95. package/dist/components/overlays/shortcuts-overlay.js +6 -0
  96. package/dist/components/overlays/tooltip.js +6 -0
  97. package/dist/components/visuals/illustration.js +6 -0
  98. package/dist/element-maps.d.ts +39 -19
  99. package/dist/entries/agent-workspace.d.ts +1 -0
  100. package/dist/entries/agent-workspace.js +1 -0
  101. package/dist/entries/call-console.d.ts +1 -0
  102. package/dist/entries/call-console.js +1 -0
  103. package/dist/entries/check-list.d.ts +1 -0
  104. package/dist/entries/check-list.js +1 -0
  105. package/dist/entries/document-list.d.ts +1 -0
  106. package/dist/entries/document-list.js +1 -0
  107. package/dist/entries/fact-list.d.ts +1 -0
  108. package/dist/entries/fact-list.js +1 -0
  109. package/dist/entries/result-blocks.d.ts +1 -0
  110. package/dist/entries/result-blocks.js +1 -0
  111. package/dist/entries/run-summary.d.ts +1 -0
  112. package/dist/entries/run-summary.js +1 -0
  113. package/dist/entries/status-icon.d.ts +1 -0
  114. package/dist/entries/status-icon.js +1 -0
  115. package/dist/foundations/motion/behaviors.d.ts +8 -0
  116. package/dist/foundations/motion/behaviors.js +24 -0
  117. package/dist/foundations/motion/index.d.ts +2 -0
  118. package/dist/foundations/motion/index.js +2 -0
  119. package/dist/foundations/motion/scroll-pin.d.ts +19 -0
  120. package/dist/foundations/motion/scroll-pin.js +60 -0
  121. package/dist/foundations/motion/tokens.d.ts +2 -0
  122. package/dist/foundations/motion/tokens.js +2 -0
  123. package/dist/foundations/profiles/registry.d.ts +3 -0
  124. package/dist/foundations/profiles/registry.js +3 -0
  125. package/dist/foundations/profiles/types.d.ts +3 -0
  126. package/dist/foundations/status/index.d.ts +6 -0
  127. package/dist/foundations/status/index.js +33 -0
  128. package/dist/index.d.ts +10 -0
  129. package/dist/index.js +10 -0
  130. package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
  131. package/dist/patterns/agent-chat/agent-chat.js +141 -21
  132. package/dist/patterns/agent-chat/controller.d.ts +3 -1
  133. package/dist/patterns/agent-chat/controller.js +92 -16
  134. package/dist/patterns/agent-chat/index.d.ts +1 -0
  135. package/dist/patterns/agent-chat/index.js +1 -0
  136. package/dist/patterns/agent-chat/ndjson.d.ts +5 -0
  137. package/dist/patterns/agent-chat/ndjson.js +41 -0
  138. package/dist/patterns/agent-chat/types.d.ts +44 -1
  139. package/dist/patterns/agent-workspace/agent-workspace.d.ts +28 -0
  140. package/dist/patterns/agent-workspace/agent-workspace.js +215 -0
  141. package/dist/patterns/agent-workspace/controller.d.ts +38 -0
  142. package/dist/patterns/agent-workspace/controller.js +65 -0
  143. package/dist/patterns/agent-workspace/index.d.ts +2 -0
  144. package/dist/patterns/agent-workspace/index.js +2 -0
  145. package/dist/patterns/audit/activity-density.js +6 -0
  146. package/dist/patterns/audit/audit-log.js +6 -0
  147. package/dist/patterns/call-console/call-console.d.ts +17 -0
  148. package/dist/patterns/call-console/call-console.js +107 -0
  149. package/dist/patterns/call-console/controller.d.ts +23 -0
  150. package/dist/patterns/call-console/controller.js +50 -0
  151. package/dist/patterns/call-console/http-transport.d.ts +3 -0
  152. package/dist/patterns/call-console/http-transport.js +26 -0
  153. package/dist/patterns/call-console/index.d.ts +4 -0
  154. package/dist/patterns/call-console/index.js +4 -0
  155. package/dist/patterns/call-console/types.d.ts +32 -0
  156. package/dist/patterns/call-console/types.js +8 -0
  157. package/dist/patterns/comments/comment-thread.js +6 -0
  158. package/dist/patterns/content-explorer/adapters/action-menu.js +6 -0
  159. package/dist/patterns/content-explorer/adapters/breadcrumbs.js +6 -0
  160. package/dist/patterns/content-explorer/adapters/list.js +6 -0
  161. package/dist/patterns/content-explorer/adapters/table.js +6 -0
  162. package/dist/patterns/content-explorer/adapters/toolbar.js +6 -0
  163. package/dist/patterns/content-explorer/content-explorer.js +6 -0
  164. package/dist/patterns/content-picker/content-picker.js +6 -0
  165. package/dist/patterns/content-sidebar/content-sidebar.js +6 -0
  166. package/dist/patterns/content-uploader/content-uploader.js +8 -1
  167. package/dist/patterns/diff/compare-view.js +6 -0
  168. package/dist/patterns/diff/diff-viewer.js +6 -0
  169. package/dist/patterns/file-request/file-request-builder.js +6 -0
  170. package/dist/patterns/form-wizard/form-wizard.js +6 -0
  171. package/dist/patterns/form-wizard/wizard-summary.js +6 -0
  172. package/dist/patterns/governance/governance-panel.js +6 -0
  173. package/dist/patterns/insights/bar-chart.js +6 -0
  174. package/dist/patterns/insights/chart-panel.js +6 -0
  175. package/dist/patterns/insights/donut-chart.js +6 -0
  176. package/dist/patterns/insights/line-chart.js +6 -0
  177. package/dist/patterns/insights/metric-card.js +6 -0
  178. package/dist/patterns/item/bulk-action-bar.js +6 -0
  179. package/dist/patterns/item/item-details-panel.js +6 -0
  180. package/dist/patterns/item/item-form.js +6 -0
  181. package/dist/patterns/item/preview-header.js +6 -0
  182. package/dist/patterns/lineage/lineage-graph.js +6 -0
  183. package/dist/patterns/lineage/provenance-strip.js +6 -0
  184. package/dist/patterns/metadata/metadata-filter-builder.js +6 -0
  185. package/dist/patterns/metadata/metadata-inspector.js +6 -0
  186. package/dist/patterns/notifications/notification-bell.js +6 -0
  187. package/dist/patterns/notifications/notification-inbox.js +6 -0
  188. package/dist/patterns/preview/annotation-inspector.js +6 -0
  189. package/dist/patterns/preview/annotation-toolbar.js +6 -0
  190. package/dist/patterns/preview/preview-element.js +6 -0
  191. package/dist/patterns/run/index.d.ts +2 -0
  192. package/dist/patterns/run/index.js +2 -0
  193. package/dist/patterns/run/progress.d.ts +24 -0
  194. package/dist/patterns/run/progress.js +32 -0
  195. package/dist/patterns/run/run-summary.d.ts +21 -0
  196. package/dist/patterns/run/run-summary.js +82 -0
  197. package/dist/patterns/run/run-trace.d.ts +1 -0
  198. package/dist/patterns/run/run-trace.js +20 -3
  199. package/dist/patterns/search/filter-bar.js +6 -0
  200. package/dist/patterns/search/saved-view-picker.js +6 -0
  201. package/dist/patterns/search/search-results-header.js +6 -0
  202. package/dist/patterns/share/access-stats.js +6 -0
  203. package/dist/patterns/share/collaborator-avatars.js +6 -0
  204. package/dist/patterns/share/invite-collaborators-modal.js +6 -0
  205. package/dist/patterns/share/permission-matrix.js +6 -0
  206. package/dist/patterns/share/presence.js +6 -0
  207. package/dist/patterns/share/share-panel.js +6 -0
  208. package/dist/patterns/share/unified-share-modal.js +6 -0
  209. package/dist/patterns/signature/signature-ceremony.js +6 -0
  210. package/dist/patterns/task/review-queue-item.js +6 -0
  211. package/dist/patterns/task/task-assignment-panel.js +6 -0
  212. package/dist/patterns/timeline/timeline.js +6 -0
  213. package/dist/patterns/versions/version-graph.js +6 -0
  214. package/dist/patterns/versions/version-list.js +6 -0
  215. package/dist/patterns/work-queue/work-queue.js +6 -0
  216. package/dist/patterns/work-queue/workload-board.js +6 -0
  217. package/package.json +13 -1
@@ -16,6 +16,8 @@ export class AgentChatController extends Controller {
16
16
  config;
17
17
  sendCounter = 0;
18
18
  activeAbortController = null;
19
+ finishActive;
20
+ resolving = new Set();
19
21
  constructor(config) {
20
22
  super(createInitialState());
21
23
  this.config = config;
@@ -31,7 +33,7 @@ export class AgentChatController extends Controller {
31
33
  if (!this.state.connected) {
32
34
  return;
33
35
  }
34
- this.activeAbortController?.abort();
36
+ this.stop();
35
37
  this.activeAbortController = null;
36
38
  this.setState(createInitialState());
37
39
  this.emit("disconnected", undefined);
@@ -42,6 +44,7 @@ export class AgentChatController extends Controller {
42
44
  /** Abort the in-flight generation; whatever already streamed is kept. */
43
45
  stop() {
44
46
  this.activeAbortController?.abort();
47
+ this.finishActive?.();
45
48
  }
46
49
  /**
47
50
  * Send a user turn and stream the agent's reply. One send at a time: a
@@ -52,7 +55,7 @@ export class AgentChatController extends Controller {
52
55
  if (!this.state.connected || trimmed.length === 0) {
53
56
  return null;
54
57
  }
55
- this.activeAbortController?.abort();
58
+ this.stop();
56
59
  const abortController = typeof AbortController === "function" ? new AbortController() : null;
57
60
  this.activeAbortController = abortController;
58
61
  this.sendCounter += 1;
@@ -70,6 +73,9 @@ export class AgentChatController extends Controller {
70
73
  role: "agent",
71
74
  body: "",
72
75
  status: "streaming",
76
+ startedAt: Date.now(),
77
+ blocks: [], trace: [], todos: [], options: [],
78
+ completeness: { status: "streaming", missing: [] },
73
79
  ...(this.config.agentName ? { actor: { name: this.config.agentName } } : {}),
74
80
  citations: [],
75
81
  proposals: [],
@@ -89,37 +95,81 @@ export class AgentChatController extends Controller {
89
95
  });
90
96
  this.emit("messagesChanged", { messages: this.state.messages });
91
97
  };
98
+ const sequences = new Set();
99
+ let highSequence = 0;
100
+ let done;
101
+ const missing = () => Array.from({ length: highSequence }, (_, i) => i + 1).filter(i => !sequences.has(i));
102
+ const upsert = (items, item) => item.id && items.some(i => i.id === item.id)
103
+ ? items.map(i => i.id === item.id ? item : i) : [...items, item];
92
104
  const onEvent = (event) => {
93
105
  const current = this.getMessage(agentMessage.id);
94
- if (!current || current.status !== "streaming") {
106
+ if (!current || current.status !== "streaming" || abortController?.signal.aborted || turn !== this.sendCounter || done) {
95
107
  return;
96
108
  }
109
+ if (event.seq !== undefined) {
110
+ if (!Number.isSafeInteger(event.seq) || event.seq < 1 || event.seq > 100000)
111
+ throw new Error("Invalid stream sequence");
112
+ if (sequences.has(event.seq))
113
+ return;
114
+ sequences.add(event.seq);
115
+ highSequence = Math.max(highSequence, event.seq);
116
+ }
117
+ let patch = {};
97
118
  if (event.kind === "delta") {
98
- patchAgentMessage({ body: current.body + event.text });
119
+ patch = { body: current.body + event.text };
99
120
  }
100
121
  else if (event.kind === "citation") {
101
- patchAgentMessage({ citations: [...current.citations, event.citation] });
122
+ patch = { citations: upsert(current.citations, event.citation) };
102
123
  }
103
- else {
104
- patchAgentMessage({ proposals: [...current.proposals, event.proposal] });
124
+ else if (event.kind === "proposal") {
125
+ patch = { proposals: upsert(current.proposals, event.proposal) };
126
+ }
127
+ else if (event.kind === "block") {
128
+ patch = { blocks: upsert(current.blocks ?? [], event.block) };
129
+ }
130
+ else if (event.kind === "trace") {
131
+ patch = { trace: upsert(current.trace ?? [], event.step) };
132
+ }
133
+ else if (event.kind === "todos") {
134
+ patch = { todos: event.todos };
135
+ }
136
+ else if (event.kind === "options") {
137
+ patch = { options: event.options };
105
138
  }
139
+ else if (event.kind === "context") {
140
+ patch = { context: event.context };
141
+ }
142
+ else if (event.kind === "done") {
143
+ done = event.status;
144
+ }
145
+ patchAgentMessage({ ...patch, completeness: { status: "streaming", missing: missing() } });
106
146
  };
147
+ let finished = false;
107
148
  const finish = (patch) => {
108
- patchAgentMessage(patch);
149
+ if (finished)
150
+ return;
151
+ finished = true;
152
+ const gaps = missing();
153
+ patchAgentMessage({ ...patch, endedAt: Date.now(), completeness: {
154
+ status: patch.status === "error" ? "error" : !done || gaps.length ? "incomplete" : done,
155
+ missing: gaps,
156
+ } });
109
157
  // A superseded send must not clear the newer send's streaming flag.
110
158
  if (turn === this.sendCounter) {
111
159
  this.setState({ ...this.state, streaming: false });
112
160
  this.emit("streamingChanged", { streaming: false });
113
161
  }
114
162
  };
163
+ this.finishActive = () => finish({ status: "complete" });
115
164
  try {
116
165
  await this.config.transport.sendMessage({
117
166
  body: trimmed,
167
+ messageId: agentMessage.id,
118
168
  token: this.config.token,
119
169
  onEvent,
120
170
  ...(abortController ? { signal: abortController.signal } : {}),
121
171
  });
122
- finish({ status: "complete" });
172
+ finish({ status: done === "error" ? "error" : "complete" });
123
173
  }
124
174
  catch (error) {
125
175
  if (abortController?.signal.aborted) {
@@ -129,42 +179,58 @@ export class AgentChatController extends Controller {
129
179
  else {
130
180
  const message = error instanceof Error ? error.message : "The agent reply failed";
131
181
  finish({ status: "error", errorMessage: message });
132
- this.setState({ ...this.state, error: message });
133
- this.emit("sendFailed", { message });
182
+ if (turn === this.sendCounter) {
183
+ this.setState({ ...this.state, error: message });
184
+ this.emit("sendFailed", { message });
185
+ }
134
186
  }
135
187
  }
136
188
  finally {
137
189
  if (this.activeAbortController === abortController) {
138
190
  this.activeAbortController = null;
191
+ this.finishActive = undefined;
139
192
  }
140
193
  }
141
194
  return this.getMessage(agentMessage.id) ?? null;
142
195
  }
143
196
  /** Resolve a HITL proposal through the transport capability. */
144
- async resolveAction(proposalId, decision, note) {
197
+ async resolveAction(proposalId, decision, note, messageId) {
145
198
  const transport = this.config.transport;
146
199
  if (!transport.resolveAction) {
147
200
  throw new Error("Agent-chat transport does not support resolveAction");
148
201
  }
149
- const holder = this.state.messages.find(message => message.proposals.some(proposal => proposal.id === proposalId));
202
+ const holder = this.state.messages.find(message => (!messageId || message.id === messageId) &&
203
+ message.proposals.some(proposal => proposal.id === proposalId));
150
204
  const pending = holder?.proposals.find(proposal => proposal.id === proposalId);
151
- if (!this.state.connected || !holder || !pending || pending.decision) {
205
+ const key = `${holder?.id}:${proposalId}`;
206
+ if (!this.state.connected || !holder || !pending || pending.decision || this.resolving.has(key)) {
152
207
  return null;
153
208
  }
209
+ const patch = (value) => {
210
+ this.setState({ ...this.state, messages: this.state.messages.map(m => m.id === holder.id ? {
211
+ ...m, proposals: m.proposals.map(p => p.id === proposalId ? { ...p, ...value } : p),
212
+ } : m) });
213
+ this.emit("messagesChanged", { messages: this.state.messages });
214
+ };
215
+ this.resolving.add(key);
216
+ patch({ resolving: decision });
154
217
  try {
155
218
  // Called on the transport so class-based implementations keep `this`.
156
- const resolved = await transport.resolveAction({
219
+ const response = await transport.resolveAction({
157
220
  proposalId,
221
+ messageId: holder.id,
158
222
  decision,
159
223
  ...(note !== undefined ? { note } : {}),
160
224
  token: this.config.token,
161
225
  });
226
+ const resolved = { ...pending, ...response, decision };
227
+ delete resolved.resolving;
162
228
  this.setState({
163
229
  ...this.state,
164
230
  messages: this.state.messages.map(message => message.id === holder.id
165
231
  ? {
166
232
  ...message,
167
- proposals: message.proposals.map(proposal => proposal.id === proposalId ? { ...proposal, ...resolved } : proposal),
233
+ proposals: message.proposals.map(proposal => proposal.id === proposalId ? { ...pending, ...resolved, decision } : proposal),
168
234
  }
169
235
  : message),
170
236
  });
@@ -178,5 +244,15 @@ export class AgentChatController extends Controller {
178
244
  this.emit("sendFailed", { message });
179
245
  return null;
180
246
  }
247
+ finally {
248
+ this.resolving.delete(key);
249
+ const current = this.getMessage(holder.id)?.proposals.find(p => p.id === proposalId);
250
+ if (current?.resolving) {
251
+ const clean = { ...current };
252
+ delete clean.resolving;
253
+ this.setState({ ...this.state, messages: this.state.messages.map(m => m.id === holder.id ? { ...m, proposals: m.proposals.map(p => p.id === proposalId ? clean : p) } : m) });
254
+ this.emit("messagesChanged", { messages: this.state.messages });
255
+ }
256
+ }
181
257
  }
182
258
  }
@@ -1,3 +1,4 @@
1
1
  export * from "./types.js";
2
2
  export * from "./controller.js";
3
3
  export * from "./agent-chat.js";
4
+ export * from "./ndjson.js";
@@ -4,3 +4,4 @@ export * from "./types.js";
4
4
  export * from "./controller.js";
5
5
  // Composed conversation surface
6
6
  export * from "./agent-chat.js";
7
+ export * from "./ndjson.js";
@@ -0,0 +1,5 @@
1
+ /** Incremental UTF-8 decoding with a bounded line buffer and early-reader cleanup. */
2
+ export declare function readNdjson<T = unknown>(stream: ReadableStream<Uint8Array>, options?: {
3
+ maxLineLength?: number;
4
+ signal?: AbortSignal;
5
+ }): AsyncGenerator<T>;
@@ -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
+ }