@exoscient/control-panel 0.1.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 (77) hide show
  1. package/README.md +14 -0
  2. package/dist/AppControlPanel.d.ts +77 -0
  3. package/dist/AppControlPanel.d.ts.map +1 -0
  4. package/dist/AppControlPanel.js +1625 -0
  5. package/dist/AppControlPanel.js.map +1 -0
  6. package/dist/ControlPanelShell.d.ts +39 -0
  7. package/dist/ControlPanelShell.d.ts.map +1 -0
  8. package/dist/ControlPanelShell.js +152 -0
  9. package/dist/ControlPanelShell.js.map +1 -0
  10. package/dist/ExoLauncherSimulator.d.ts +36 -0
  11. package/dist/ExoLauncherSimulator.d.ts.map +1 -0
  12. package/dist/ExoLauncherSimulator.js +253 -0
  13. package/dist/ExoLauncherSimulator.js.map +1 -0
  14. package/dist/TaskDetail.d.ts +180 -0
  15. package/dist/TaskDetail.d.ts.map +1 -0
  16. package/dist/TaskDetail.js +889 -0
  17. package/dist/TaskDetail.js.map +1 -0
  18. package/dist/TaskListPage.d.ts +28 -0
  19. package/dist/TaskListPage.d.ts.map +1 -0
  20. package/dist/TaskListPage.js +16 -0
  21. package/dist/TaskListPage.js.map +1 -0
  22. package/dist/TaskWorkspace.d.ts +62 -0
  23. package/dist/TaskWorkspace.d.ts.map +1 -0
  24. package/dist/TaskWorkspace.js +592 -0
  25. package/dist/TaskWorkspace.js.map +1 -0
  26. package/dist/ai-plane.d.ts +75 -0
  27. package/dist/ai-plane.d.ts.map +1 -0
  28. package/dist/ai-plane.js +124 -0
  29. package/dist/ai-plane.js.map +1 -0
  30. package/dist/browser-icons.d.ts +25 -0
  31. package/dist/browser-icons.d.ts.map +1 -0
  32. package/dist/browser-icons.js +125 -0
  33. package/dist/browser-icons.js.map +1 -0
  34. package/dist/client-actions.d.ts +45 -0
  35. package/dist/client-actions.d.ts.map +1 -0
  36. package/dist/client-actions.js +48 -0
  37. package/dist/client-actions.js.map +1 -0
  38. package/dist/control-panel-shared.d.ts +58 -0
  39. package/dist/control-panel-shared.d.ts.map +1 -0
  40. package/dist/control-panel-shared.js +79 -0
  41. package/dist/control-panel-shared.js.map +1 -0
  42. package/dist/control-panel.css +4156 -0
  43. package/dist/index.d.ts +30 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +16 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/repository-workflow.d.ts +27 -0
  48. package/dist/repository-workflow.d.ts.map +1 -0
  49. package/dist/repository-workflow.js +24 -0
  50. package/dist/repository-workflow.js.map +1 -0
  51. package/dist/result.d.ts +6 -0
  52. package/dist/result.d.ts.map +1 -0
  53. package/dist/result.js +77 -0
  54. package/dist/result.js.map +1 -0
  55. package/dist/task-consistency.d.ts +28 -0
  56. package/dist/task-consistency.d.ts.map +1 -0
  57. package/dist/task-consistency.js +25 -0
  58. package/dist/task-consistency.js.map +1 -0
  59. package/dist/task-detail.browser.js +2709 -0
  60. package/dist/task-detail.css +1601 -0
  61. package/dist/task-state.d.ts +11 -0
  62. package/dist/task-state.d.ts.map +1 -0
  63. package/dist/task-state.js +103 -0
  64. package/dist/task-state.js.map +1 -0
  65. package/dist/telemetry.d.ts +39 -0
  66. package/dist/telemetry.d.ts.map +1 -0
  67. package/dist/telemetry.js +106 -0
  68. package/dist/telemetry.js.map +1 -0
  69. package/dist/trace.d.ts +80 -0
  70. package/dist/trace.d.ts.map +1 -0
  71. package/dist/trace.js +694 -0
  72. package/dist/trace.js.map +1 -0
  73. package/dist/updates.d.ts +72 -0
  74. package/dist/updates.d.ts.map +1 -0
  75. package/dist/updates.js +269 -0
  76. package/dist/updates.js.map +1 -0
  77. package/package.json +58 -0
@@ -0,0 +1,2709 @@
1
+ var require = function(name) { if (name === 'react') return window.React; if (name === 'react/jsx-runtime') { var jsx = function(type, props, key) { props = props || {}; if (key !== undefined) props.key = key; var children = props.children; var nextProps = Object.assign({}, props); delete nextProps.children; return Array.isArray(children) ? window.React.createElement.apply(window.React, [type, nextProps].concat(children)) : window.React.createElement(type, nextProps, children); }; return { jsx: jsx, jsxs: jsx, Fragment: window.React.Fragment }; } throw new Error('Unsupported external ' + name); };
2
+ "use strict";
3
+ var ExoscientCpLibBundle = (() => {
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // src/TaskDetail.tsx
34
+ var import_react2 = __require("react");
35
+
36
+ // src/browser-icons.tsx
37
+ var import_react = __toESM(__require("react"), 1);
38
+ function icon(label, children) {
39
+ return function BrowserIcon(props) {
40
+ return import_react.default.createElement(
41
+ "svg",
42
+ {
43
+ viewBox: "0 0 24 24",
44
+ width: "1em",
45
+ height: "1em",
46
+ fill: "none",
47
+ stroke: "currentColor",
48
+ strokeWidth: "2",
49
+ strokeLinecap: "round",
50
+ strokeLinejoin: "round",
51
+ role: "img",
52
+ "aria-label": label,
53
+ ...props
54
+ },
55
+ children
56
+ );
57
+ };
58
+ }
59
+ var Check = icon("granted", [
60
+ import_react.default.createElement("path", { key: "a", d: "M20 6 9 17l-5-5" })
61
+ ]);
62
+ var CircleX = icon("denied", [
63
+ import_react.default.createElement("circle", { key: "a", cx: "12", cy: "12", r: "10" }),
64
+ import_react.default.createElement("path", { key: "b", d: "m15 9-6 6" }),
65
+ import_react.default.createElement("path", { key: "c", d: "m9 9 6 6" })
66
+ ]);
67
+ var Hourglass = icon("waiting", [
68
+ import_react.default.createElement("path", { key: "a", d: "M5 22h14" }),
69
+ import_react.default.createElement("path", { key: "b", d: "M5 2h14" }),
70
+ import_react.default.createElement("path", { key: "c", d: "M17 22v-4.2a4 4 0 0 0-1.2-2.8L13 12l2.8-3a4 4 0 0 0 1.2-2.8V2" }),
71
+ import_react.default.createElement("path", { key: "d", d: "M7 2v4.2A4 4 0 0 0 8.2 9L11 12l-2.8 3A4 4 0 0 0 7 17.8V22" })
72
+ ]);
73
+ var Maximize2 = icon("maximize", [
74
+ import_react.default.createElement("path", { key: "a", d: "M15 3h6v6" }),
75
+ import_react.default.createElement("path", { key: "b", d: "m21 3-7 7" }),
76
+ import_react.default.createElement("path", { key: "c", d: "M9 21H3v-6" }),
77
+ import_react.default.createElement("path", { key: "d", d: "m3 21 7-7" })
78
+ ]);
79
+ var Minimize2 = icon("minimize", [
80
+ import_react.default.createElement("path", { key: "a", d: "M4 14h6v6" }),
81
+ import_react.default.createElement("path", { key: "b", d: "m10 14-7 7" }),
82
+ import_react.default.createElement("path", { key: "c", d: "M20 10h-6V4" }),
83
+ import_react.default.createElement("path", { key: "d", d: "m14 10 7-7" })
84
+ ]);
85
+ var PanelBottomClose = icon("minimize panel", [
86
+ import_react.default.createElement("rect", { key: "a", x: "3", y: "3", width: "18", height: "18", rx: "2" }),
87
+ import_react.default.createElement("path", { key: "b", d: "M3 15h18" }),
88
+ import_react.default.createElement("path", { key: "c", d: "m9 10 3 3 3-3" })
89
+ ]);
90
+ var PanelBottomOpen = icon("restore panel", [
91
+ import_react.default.createElement("rect", { key: "a", x: "3", y: "3", width: "18", height: "18", rx: "2" }),
92
+ import_react.default.createElement("path", { key: "b", d: "M3 15h18" }),
93
+ import_react.default.createElement("path", { key: "c", d: "m9 13 3-3 3 3" })
94
+ ]);
95
+ var Paperclip = icon("attachment", [
96
+ import_react.default.createElement("path", { key: "a", d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
97
+ ]);
98
+ var RefreshCw = icon("refresh", [
99
+ import_react.default.createElement("path", { key: "a", d: "M3 12a9 9 0 0 1 15-6.7L21 8" }),
100
+ import_react.default.createElement("path", { key: "b", d: "M21 3v5h-5" }),
101
+ import_react.default.createElement("path", { key: "c", d: "M21 12a9 9 0 0 1-15 6.7L3 16" }),
102
+ import_react.default.createElement("path", { key: "d", d: "M3 21v-5h5" })
103
+ ]);
104
+ var Send = icon("send", [
105
+ import_react.default.createElement("path", { key: "a", d: "m22 2-7 20-4-9-9-4Z" }),
106
+ import_react.default.createElement("path", { key: "b", d: "M22 2 11 13" })
107
+ ]);
108
+ var Square = icon("stop", [
109
+ import_react.default.createElement("rect", { key: "a", x: "6", y: "6", width: "12", height: "12", rx: "1" })
110
+ ]);
111
+ var Waypoints = icon("trace", [
112
+ import_react.default.createElement("circle", { key: "a", cx: "12", cy: "4.5", r: "2.5" }),
113
+ import_react.default.createElement("path", { key: "b", d: "m10.2 6.3-3.9 3.9" }),
114
+ import_react.default.createElement("circle", { key: "c", cx: "4.5", cy: "12", r: "2.5" }),
115
+ import_react.default.createElement("path", { key: "d", d: "m6.3 13.8 3.9 3.9" }),
116
+ import_react.default.createElement("circle", { key: "e", cx: "12", cy: "19.5", r: "2.5" }),
117
+ import_react.default.createElement("path", { key: "f", d: "m13.8 17.7 3.9-3.9" }),
118
+ import_react.default.createElement("circle", { key: "g", cx: "19.5", cy: "12", r: "2.5" }),
119
+ import_react.default.createElement("path", { key: "h", d: "m17.7 10.2-3.9-3.9" })
120
+ ]);
121
+ var X = icon("close", [
122
+ import_react.default.createElement("path", { key: "a", d: "M18 6 6 18" }),
123
+ import_react.default.createElement("path", { key: "b", d: "m6 6 12 12" })
124
+ ]);
125
+ var Share2 = icon("share", [
126
+ import_react.default.createElement("circle", { key: "a", cx: "18", cy: "5", r: "3" }),
127
+ import_react.default.createElement("circle", { key: "b", cx: "6", cy: "12", r: "3" }),
128
+ import_react.default.createElement("circle", { key: "c", cx: "18", cy: "19", r: "3" }),
129
+ import_react.default.createElement("line", { key: "d", x1: "8.59", y1: "13.51", x2: "15.42", y2: "17.49" }),
130
+ import_react.default.createElement("line", { key: "e", x1: "15.41", y1: "6.51", x2: "8.59", y2: "10.49" })
131
+ ]);
132
+ var Trash2 = icon("delete", [
133
+ import_react.default.createElement("path", { key: "a", d: "M3 6h18" }),
134
+ import_react.default.createElement("path", { key: "b", d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
135
+ import_react.default.createElement("path", { key: "c", d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
136
+ import_react.default.createElement("line", { key: "d", x1: "10", y1: "11", x2: "10", y2: "17" }),
137
+ import_react.default.createElement("line", { key: "e", x1: "14", y1: "11", x2: "14", y2: "17" })
138
+ ]);
139
+ var ChevronLeft = icon("previous", [
140
+ import_react.default.createElement("path", { key: "a", d: "m15 18-6-6 6-6" })
141
+ ]);
142
+ var ChevronRight = icon("next", [
143
+ import_react.default.createElement("path", { key: "a", d: "m9 18 6-6-6-6" })
144
+ ]);
145
+ var ClipboardList = icon("tasks", [
146
+ import_react.default.createElement("rect", { key: "a", x: "8", y: "2", width: "8", height: "4", rx: "1", ry: "1" }),
147
+ import_react.default.createElement("path", { key: "b", d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }),
148
+ import_react.default.createElement("path", { key: "c", d: "M12 11h4" }),
149
+ import_react.default.createElement("path", { key: "d", d: "M12 16h4" }),
150
+ import_react.default.createElement("path", { key: "e", d: "M8 11h.01" }),
151
+ import_react.default.createElement("path", { key: "f", d: "M8 16h.01" })
152
+ ]);
153
+ var Filter = icon("filter", [
154
+ import_react.default.createElement("path", { key: "a", d: "M22 3H2l8 9.46V19l4 2v-8.54L22 3z" })
155
+ ]);
156
+ var Menu = icon("menu", [
157
+ import_react.default.createElement("line", { key: "a", x1: "4", y1: "6", x2: "20", y2: "6" }),
158
+ import_react.default.createElement("line", { key: "b", x1: "4", y1: "12", x2: "20", y2: "12" }),
159
+ import_react.default.createElement("line", { key: "c", x1: "4", y1: "18", x2: "20", y2: "18" })
160
+ ]);
161
+ var Plus = icon("add", [
162
+ import_react.default.createElement("path", { key: "a", d: "M5 12h14" }),
163
+ import_react.default.createElement("path", { key: "b", d: "M12 5v14" })
164
+ ]);
165
+
166
+ // src/client-actions.ts
167
+ function isReloadClientAppAction(action) {
168
+ return isObject(action) && action.type === "reloadClientApp" && typeof action.id === "string" && action.id.trim().length > 0;
169
+ }
170
+ function isUserDecisionAction(action) {
171
+ return isObject(action) && action.type === "userDecision" && typeof action.id === "string" && action.id.trim().length > 0 && typeof action.prompt === "string" && Array.isArray(action.options);
172
+ }
173
+ function userDecisionSelectionMode(action) {
174
+ return action.selectionMode === "multiple" ? "multiple" : "single";
175
+ }
176
+ function userDecisionAllowsOther(action) {
177
+ if (typeof action.other === "boolean") return action.other;
178
+ if (isObject(action.other)) return action.other.enabled !== false;
179
+ return false;
180
+ }
181
+ function userDecisionOtherLabel(action) {
182
+ return isObject(action.other) && typeof action.other.label === "string" && action.other.label.trim() ? action.other.label.trim() : "Other";
183
+ }
184
+ function userDecisionOtherPlaceholder(action) {
185
+ return isObject(action.other) && typeof action.other.placeholder === "string" ? action.other.placeholder : "";
186
+ }
187
+ function externalContinuation(action) {
188
+ return isObject(action.continuation) && action.continuation.kind === "externalContinuation" ? action.continuation : null;
189
+ }
190
+ function isObject(value) {
191
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
192
+ }
193
+
194
+ // src/result.ts
195
+ var ownerHeadings = ["Owner Result", "Owner Message", "Owner Summary", "Result"];
196
+ var technicalHeadings = [
197
+ "Technical Change Summary",
198
+ "Technical Summary",
199
+ "Change Summary",
200
+ "What changed",
201
+ "Changed",
202
+ "Changes",
203
+ "Verification",
204
+ "Verified",
205
+ "Tests",
206
+ "Deployment",
207
+ "Known issues",
208
+ "Skipped checks"
209
+ ];
210
+ function splitAgentResult(text) {
211
+ const normalized = text.trim();
212
+ if (!normalized) return { owner: "", technical: "" };
213
+ const ownerHeading = findFirstResultHeading(normalized, ownerHeadings);
214
+ const technicalHeading = findFirstResultHeading(
215
+ normalized,
216
+ technicalHeadings,
217
+ ownerHeading ? ownerHeading.contentStart : 0
218
+ );
219
+ if (ownerHeading && technicalHeading && ownerHeading.index <= technicalHeading.index) {
220
+ return {
221
+ owner: normalized.slice(ownerHeading.contentStart, technicalHeading.index).trim(),
222
+ technical: normalized.slice(technicalHeading.contentStart).trim()
223
+ };
224
+ }
225
+ if (technicalHeading) {
226
+ return {
227
+ owner: normalized.slice(0, technicalHeading.index).trim(),
228
+ technical: normalized.slice(technicalHeading.contentStart).trim()
229
+ };
230
+ }
231
+ if (ownerHeading) {
232
+ return { owner: normalized.slice(ownerHeading.contentStart).trim(), technical: "" };
233
+ }
234
+ return { owner: normalized, technical: "" };
235
+ }
236
+ function findFirstResultHeading(text, headings, minIndex = 0) {
237
+ return headings.flatMap((heading) => findResultHeadings(text, heading)).filter((heading) => heading.index >= minIndex).sort((left, right) => left.index - right.index)[0] ?? null;
238
+ }
239
+ function findResultHeadings(text, heading) {
240
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
241
+ const matches = [];
242
+ const blockPattern = new RegExp(`(^|\\n)\\s*(?:#{1,6}\\s*)?\\*{0,2}${escapedHeading}\\*{0,2}:?\\s*(?:\\n|$)`, "gi");
243
+ collectHeadingMatches(text, blockPattern, matches);
244
+ const lineInlinePattern = new RegExp(`(^|\\n)\\s*(?:#{1,6}\\s*)?\\*{0,2}${escapedHeading}\\*{0,2}:\\s*`, "gi");
245
+ collectHeadingMatches(text, lineInlinePattern, matches);
246
+ if (heading !== "Result") {
247
+ const markdownInlinePattern = new RegExp(`(^|\\s)(#{1,6}\\s+)\\*{0,2}${escapedHeading}\\*{0,2}:?\\s+`, "gi");
248
+ collectHeadingMatches(text, markdownInlinePattern, matches);
249
+ }
250
+ return dedupeHeadingMatches(matches);
251
+ }
252
+ function collectHeadingMatches(text, pattern, matches) {
253
+ for (const match of text.matchAll(pattern)) {
254
+ const prefix = match[1] ?? "";
255
+ matches.push({
256
+ index: match.index + prefix.length,
257
+ contentStart: match.index + match[0].length
258
+ });
259
+ }
260
+ }
261
+ function dedupeHeadingMatches(matches) {
262
+ const seen = /* @__PURE__ */ new Set();
263
+ return matches.filter((match) => {
264
+ const key = `${match.index}:${match.contentStart}`;
265
+ if (seen.has(key)) return false;
266
+ seen.add(key);
267
+ return true;
268
+ });
269
+ }
270
+
271
+ // src/trace.ts
272
+ function buildTraceItems(events) {
273
+ const items = [];
274
+ let assistantBuffer = "";
275
+ let assistantAt = "";
276
+ function flushAssistant() {
277
+ if (assistantBuffer) {
278
+ items.push({ kind: "assistant", at: assistantAt, text: assistantBuffer });
279
+ assistantBuffer = "";
280
+ assistantAt = "";
281
+ }
282
+ }
283
+ for (const rawEvent of events) {
284
+ const event = normalizeTraceEvent(rawEvent);
285
+ if (event.type === "assistant-delta") {
286
+ assistantAt ||= event.at;
287
+ assistantBuffer += event.message;
288
+ continue;
289
+ }
290
+ if (event.type === "assistant-message") {
291
+ if (!assistantBuffer) {
292
+ items.push({ kind: "assistant", at: event.at, text: event.message });
293
+ }
294
+ continue;
295
+ }
296
+ if (event.type === "assistant") {
297
+ flushAssistant();
298
+ items.push({ kind: "assistant", at: event.at, text: event.message });
299
+ continue;
300
+ }
301
+ flushAssistant();
302
+ if (event.type === "codex-event" || isCodexJsonEvent(event)) {
303
+ const item = parseCodexEvent(event);
304
+ if (item) items.push(item);
305
+ continue;
306
+ }
307
+ if (event.type === "turn-completed") {
308
+ items.push(parseTurnCompleted(event));
309
+ continue;
310
+ }
311
+ if (event.type === "thread") {
312
+ items.push({ kind: "status", at: event.at, text: "Conversation thread started.", raw: readableJson(event.message) });
313
+ continue;
314
+ }
315
+ if (event.type === "status") {
316
+ items.push({ kind: "status", at: event.at, text: event.message });
317
+ continue;
318
+ }
319
+ if (event.type === "output") {
320
+ items.push({ kind: "output", at: event.at, stream: event.stream, text: event.message });
321
+ continue;
322
+ }
323
+ if (event.type === "tokens") {
324
+ items.push({ kind: "tokens", at: event.at, text: tokenUsageText(parseJson(event.message)), raw: readableJson(event.message) });
325
+ continue;
326
+ }
327
+ if (event.type === "error") {
328
+ items.push({ kind: "error", at: event.at, text: plainError(event.message), raw: readableJson(event.message) });
329
+ continue;
330
+ }
331
+ if (event.type === "result") {
332
+ items.push({ kind: "result", at: event.at, text: event.message });
333
+ continue;
334
+ }
335
+ items.push({ kind: "raw", at: event.at, label: event.type, text: readableJson(event.message) });
336
+ }
337
+ flushAssistant();
338
+ return compactTraceItems(items);
339
+ }
340
+ function traceItemMatchesFilter(item, filters) {
341
+ if (filters.length === 0) return true;
342
+ return filters.includes(traceItemCategory(item));
343
+ }
344
+ function latestContextPressure(events) {
345
+ for (let index = events.length - 1; index >= 0; index -= 1) {
346
+ const pressure = contextPressureFromUsage(tokenUsageFromEvent(normalizeTraceEvent(events[index])));
347
+ if (pressure) return pressure;
348
+ }
349
+ return null;
350
+ }
351
+ function traceItemCategory(item) {
352
+ if (item.kind === "assistant") return "narration";
353
+ if (item.kind === "file" || item.kind === "tool") return "coding";
354
+ if (item.kind === "command" || item.kind === "output") return "shell";
355
+ if (item.kind === "tokens") return "tokens";
356
+ if (item.kind === "status" || item.kind === "result") return "status";
357
+ if (item.kind === "error") return "errors";
358
+ return "raw";
359
+ }
360
+ function normalizeTraceEvent(event) {
361
+ const source = isObject2(event) ? event : {};
362
+ const messageValue = valueAtAny(source, ["message", "Message", "text", "Text", "payload", "Payload", "data", "Data"]);
363
+ const message = traceMessageFromValue(messageValue, source);
364
+ const traceLine = parseTraceLine(message);
365
+ const explicitTimestamp = firstString(
366
+ stringValue(source, "at"),
367
+ stringValue(source, "At"),
368
+ stringValue(source, "timestamp"),
369
+ stringValue(source, "Timestamp"),
370
+ stringValue(source, "time"),
371
+ stringValue(source, "Time"),
372
+ stringValue(source, "createdAt"),
373
+ stringValue(source, "CreatedAt")
374
+ );
375
+ const explicitType = firstString(
376
+ stringValue(source, "type"),
377
+ stringValue(source, "Type"),
378
+ stringValue(source, "kind"),
379
+ stringValue(source, "Kind")
380
+ );
381
+ const lineEvent = traceLine && (!explicitType || explicitType === "event") ? eventFromTraceLine(traceLine) : null;
382
+ return {
383
+ type: lineEvent?.type ?? explicitType ?? "event",
384
+ at: explicitTimestamp ?? traceLine?.at ?? "",
385
+ stream: lineEvent?.stream ?? firstString(stringValue(source, "stream"), stringValue(source, "Stream")),
386
+ message: lineEvent?.message ?? message
387
+ };
388
+ }
389
+ function parseTraceLine(message) {
390
+ const match = message.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))\s+#\d+\s+([A-Za-z0-9._/-]+)(?:\s+([\s\S]*))?$/);
391
+ if (!match) return null;
392
+ const payloadText = match[3]?.trim() ?? "";
393
+ const payload = parseJson(payloadText);
394
+ return { at: match[1], label: match[2], payloadText, payload };
395
+ }
396
+ function eventFromTraceLine(line) {
397
+ const payloadMessage = objectValue(line.payload, "message");
398
+ if (line.label === "agent.notification" && payloadMessage) {
399
+ return { type: "codex-event", at: line.at, message: stringifyCompact(payloadMessage) ?? line.payloadText };
400
+ }
401
+ if (line.payload && (stringValue(line.payload, "method") || stringValue(line.payload, "type"))) {
402
+ return { type: "codex-event", at: line.at, message: stringifyCompact(line.payload) ?? line.payloadText };
403
+ }
404
+ if (line.label === "agent.stderr" || line.label === "agent.stdout") {
405
+ return {
406
+ type: "output",
407
+ at: line.at,
408
+ stream: line.label === "agent.stderr" ? "stderr" : "stdout",
409
+ message: firstString(stringValue(line.payload, "line"), line.payloadText) ?? ""
410
+ };
411
+ }
412
+ if (line.label.includes("error") || line.label.includes("failed")) {
413
+ return { type: "error", at: line.at, message: firstString(stringValue(line.payload, "message"), line.payloadText, titleFromTraceLabel(line.label)) ?? titleFromTraceLabel(line.label) };
414
+ }
415
+ return {
416
+ type: "status",
417
+ at: line.at,
418
+ message: firstString(stringValue(line.payload, "message"), titleFromTraceLabel(line.label)) ?? titleFromTraceLabel(line.label)
419
+ };
420
+ }
421
+ function titleFromTraceLabel(label) {
422
+ const text = label.replace(/[._/-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim();
423
+ return text ? `${text.charAt(0).toLocaleUpperCase()}${text.slice(1)}.` : "Trace event.";
424
+ }
425
+ function traceMessageFromValue(value, source) {
426
+ if (typeof value === "string") return value;
427
+ if (value !== void 0) return stringifyCompact(value) ?? "";
428
+ return stringifyCompact(source) ?? "";
429
+ }
430
+ function valueAtAny(value, keys) {
431
+ if (!isObject2(value)) return void 0;
432
+ for (const key of keys) {
433
+ const child = value[key];
434
+ if (child !== void 0 && child !== null) return child;
435
+ }
436
+ return void 0;
437
+ }
438
+ function parseCodexEvent(event) {
439
+ const json = parseJson(event.message);
440
+ const cliType = stringValue(json, "type");
441
+ const method = stringValue(json, "method") || (cliType ? cliType.replace(/\./g, "/") : void 0) || event.stream || "AI agent event";
442
+ const params = objectValue(json, "params") ?? json;
443
+ const item = objectValue(params, "item") || objectValue(params, "entry") || objectValue(json, "item") || params || json;
444
+ const itemType = stringValue(item, "type") || stringValue(item, "kind") || "";
445
+ const title = titleFromMethod(method, itemType);
446
+ const raw = readableJson(event.message);
447
+ if (method === "item/completed" && itemType === "agent_message") {
448
+ return {
449
+ kind: "assistant",
450
+ at: event.at,
451
+ text: stringValue(item, "text") ?? stringValue(item, "message") ?? ""
452
+ };
453
+ }
454
+ if (method.includes("error")) {
455
+ return { kind: "error", at: event.at, text: plainError(event.message), raw };
456
+ }
457
+ if (method === "configWarning") {
458
+ return {
459
+ kind: "status",
460
+ at: event.at,
461
+ text: firstString(stringValue(params, "summary"), stringValue(params, "details"), "Configuration warning.") ?? "Configuration warning.",
462
+ raw
463
+ };
464
+ }
465
+ if (method === "thread/started") {
466
+ const thread = objectValue(params, "thread");
467
+ const cliVersion = stringValue(thread, "cliVersion");
468
+ return {
469
+ kind: "status",
470
+ at: event.at,
471
+ text: `Thread started${cliVersion ? ` with AI agent runtime ${cliVersion}` : ""}.`,
472
+ raw
473
+ };
474
+ }
475
+ if (method === "thread/status/changed") {
476
+ const status = objectValue(params, "status");
477
+ return {
478
+ kind: "status",
479
+ at: event.at,
480
+ text: `Thread status: ${stringValue(status, "type") ?? "updated"}.`,
481
+ raw
482
+ };
483
+ }
484
+ if (method === "thread/tokenUsage/updated") {
485
+ return {
486
+ kind: "tokens",
487
+ at: event.at,
488
+ text: tokenUsageText(objectValue(params, "tokenUsage")),
489
+ raw
490
+ };
491
+ }
492
+ if (method === "turn/started") {
493
+ const turn = objectValue(params, "turn");
494
+ return {
495
+ kind: "status",
496
+ at: event.at,
497
+ text: `Turn ${stringValue(turn, "status") ?? "started"}.`,
498
+ raw
499
+ };
500
+ }
501
+ if (method === "turn/diff/updated") {
502
+ return {
503
+ kind: "file",
504
+ at: event.at,
505
+ title: "Diff updated",
506
+ text: stringValue(params, "diff"),
507
+ raw
508
+ };
509
+ }
510
+ if (method === "item/commandExecution/outputDelta") {
511
+ return parseCommandOutputDelta(event.at, params, raw);
512
+ }
513
+ if (method === "item/fileChange/outputDelta") {
514
+ return {
515
+ kind: "file",
516
+ at: event.at,
517
+ title: "File change output",
518
+ text: stringValue(params, "delta"),
519
+ raw
520
+ };
521
+ }
522
+ if (method === "rawResponseItem/completed") {
523
+ return parseRawResponseItem(event.at, item, raw);
524
+ }
525
+ if (isCommandEvent(method, itemType, item)) {
526
+ return {
527
+ kind: "command",
528
+ at: event.at,
529
+ title: itemType === "commandExecution" ? "Command Execution" : title,
530
+ itemId: stringValue(item, "id") ?? stringValue(params, "itemId"),
531
+ command: firstString(
532
+ stringValue(item, "command"),
533
+ stringValue(item, "cmd"),
534
+ stringValue(item, "script"),
535
+ commandFromToolArguments(item),
536
+ stringValue(objectValue(item, "args"), "cmd"),
537
+ stringValue(objectValue(item, "arguments"), "cmd"),
538
+ stringValue(params, "command")
539
+ ),
540
+ output: firstString(
541
+ stringValue(item, "aggregatedOutput"),
542
+ stringValue(item, "aggregated_output"),
543
+ stringValue(item, "output"),
544
+ stringValue(item, "stdout"),
545
+ stringValue(item, "stderr"),
546
+ stringValue(params, "output"),
547
+ stringValue(params, "delta")
548
+ ),
549
+ status: commandStatus(item) ?? statusFromEvent(method, item),
550
+ raw
551
+ };
552
+ }
553
+ if (isFileEvent(method, itemType, item)) {
554
+ return {
555
+ kind: "file",
556
+ at: event.at,
557
+ title,
558
+ path: firstString(
559
+ stringValue(item, "path"),
560
+ stringValue(item, "file"),
561
+ stringValue(item, "filename"),
562
+ pathFromChanges(item),
563
+ stringValue(objectValue(item, "changes"), "path")
564
+ ),
565
+ text: firstString(fileChangesText(item), stringValue(item, "text"), stringValue(item, "summary"), stringValue(params, "delta")),
566
+ raw
567
+ };
568
+ }
569
+ if (isToolEvent(method, itemType, item)) {
570
+ return {
571
+ kind: "tool",
572
+ at: event.at,
573
+ title,
574
+ input: stringifyCompact(
575
+ valueAt(item, "input") ?? valueAt(item, "arguments") ?? valueAt(item, "args") ?? valueAt(params, "input") ?? valueAt(params, "arguments")
576
+ ),
577
+ output: firstString(stringValue(item, "output"), stringValue(item, "result"), stringValue(params, "delta")),
578
+ status: statusFromEvent(method, item),
579
+ raw
580
+ };
581
+ }
582
+ if (method.includes("started") || method.includes("completed") || method.includes("turn")) {
583
+ return { kind: "status", at: event.at, text: title, raw };
584
+ }
585
+ return { kind: "raw", at: event.at, label: title, text: raw };
586
+ }
587
+ function isCodexJsonEvent(event) {
588
+ const json = parseJson(event.message);
589
+ return Boolean(json && (stringValue(json, "type") || stringValue(json, "method")));
590
+ }
591
+ function parseRawResponseItem(at, item, raw) {
592
+ const itemType = stringValue(item, "type") ?? "response item";
593
+ const name = stringValue(item, "name");
594
+ if (itemType === "function_call") {
595
+ const args = parseJson(stringValue(item, "arguments") ?? "");
596
+ if (isCommandToolName(name) && !args) return null;
597
+ return {
598
+ kind: isCommandToolName(name) ? "command" : "tool",
599
+ at,
600
+ title: name ?? "Tool call",
601
+ command: isCommandToolName(name) ? commandFromArguments(args) : void 0,
602
+ input: !isCommandToolName(name) ? stringifyCompact(args ?? stringValue(item, "arguments")) : void 0,
603
+ status: stringValue(item, "status") ?? "called",
604
+ raw
605
+ };
606
+ }
607
+ if (itemType === "function_call_output") {
608
+ const output = stringValue(item, "output");
609
+ if (!output) return null;
610
+ return {
611
+ kind: "tool",
612
+ at,
613
+ title: "Tool output",
614
+ output,
615
+ status: outputStatus(output),
616
+ raw
617
+ };
618
+ }
619
+ if (itemType === "custom_tool_call") {
620
+ const toolName = name ?? "Custom tool";
621
+ return {
622
+ kind: toolName === "apply_patch" ? "file" : "tool",
623
+ at,
624
+ title: toolName,
625
+ text: toolName === "apply_patch" ? stringValue(item, "input") : void 0,
626
+ input: toolName === "apply_patch" ? void 0 : stringValue(item, "input"),
627
+ status: stringValue(item, "status") ?? "called",
628
+ raw
629
+ };
630
+ }
631
+ if (itemType === "custom_tool_call_output") {
632
+ return {
633
+ kind: "tool",
634
+ at,
635
+ title: "Custom tool output",
636
+ output: decodedToolOutput(stringValue(item, "output")),
637
+ status: outputStatus(stringValue(item, "output")),
638
+ raw
639
+ };
640
+ }
641
+ if (itemType === "message") {
642
+ const text = contentText(valueAt(item, "content"));
643
+ if (!text) return null;
644
+ return {
645
+ kind: stringValue(item, "role") === "assistant" ? "assistant" : "status",
646
+ at,
647
+ text,
648
+ raw
649
+ };
650
+ }
651
+ if (itemType === "reasoning") {
652
+ const summary = contentText(valueAt(item, "summary"));
653
+ return {
654
+ kind: "status",
655
+ at,
656
+ text: summary ? `Reasoning summary: ${summary}` : "Reasoning step completed.",
657
+ raw
658
+ };
659
+ }
660
+ return { kind: "raw", at, label: titleFromMethod("rawResponseItem/completed", itemType), text: raw };
661
+ }
662
+ function parseCommandOutputDelta(at, params, raw) {
663
+ const itemId = stringValue(params, "itemId");
664
+ const delta = stringValue(params, "delta") ?? "";
665
+ const parsedDelta = parseJson(delta);
666
+ if (parsedDelta) {
667
+ const stdout = stringValue(parsedDelta, "stdout");
668
+ const stderr = stringValue(parsedDelta, "stderr");
669
+ return {
670
+ kind: "command",
671
+ at,
672
+ title: "Command Execution",
673
+ itemId,
674
+ command: commandFromDelta(parsedDelta),
675
+ output: firstString(
676
+ stringValue(parsedDelta, "output"),
677
+ stdout || stderr ? `${stdout ?? ""}${stderr ?? ""}` : void 0
678
+ ),
679
+ status: commandDeltaStatus(parsedDelta),
680
+ raw
681
+ };
682
+ }
683
+ return {
684
+ kind: "command",
685
+ at,
686
+ title: "Command Execution",
687
+ itemId,
688
+ output: delta,
689
+ raw
690
+ };
691
+ }
692
+ function parseTurnCompleted(event) {
693
+ const json = parseJson(event.message);
694
+ const usage = objectValue(objectValue(json, "params"), "usage") || objectValue(json, "usage");
695
+ const usageText = usage ? ` Token use: ${stringifyCompact(usage)}.` : "";
696
+ return {
697
+ kind: usage ? "tokens" : "status",
698
+ at: event.at,
699
+ text: `Turn completed.${usageText}`,
700
+ raw: readableJson(event.message)
701
+ };
702
+ }
703
+ function compactTraceItems(items) {
704
+ const compacted = [];
705
+ for (const item of items) {
706
+ const previous = compacted[compacted.length - 1];
707
+ if (previous?.kind === "output" && item.kind === "output" && previous.stream === item.stream) {
708
+ previous.text += item.text;
709
+ continue;
710
+ }
711
+ if (item.kind === "command") {
712
+ const command = findCommandToMerge(compacted, item);
713
+ if (command) {
714
+ command.title = item.title || command.title;
715
+ command.command = item.command ?? command.command;
716
+ command.output = item.command ? item.output ?? command.output : `${command.output ?? ""}${item.output ?? ""}`;
717
+ command.status = item.status ?? command.status;
718
+ command.raw = item.raw ?? command.raw;
719
+ continue;
720
+ }
721
+ }
722
+ if (previous?.kind === "command" && item.kind === "command" && previous.title === item.title && !item.command) {
723
+ previous.output = `${previous.output ?? ""}${item.output ?? ""}`;
724
+ previous.status = item.status ?? previous.status;
725
+ previous.raw = item.raw ?? previous.raw;
726
+ continue;
727
+ }
728
+ if (previous?.kind === "file" && item.kind === "file" && previous.title === item.title && !item.path) {
729
+ if (previous.text === item.text) {
730
+ previous.raw = item.raw ?? previous.raw;
731
+ continue;
732
+ }
733
+ previous.text = `${previous.text ?? ""}${item.text ?? ""}`;
734
+ previous.raw = item.raw ?? previous.raw;
735
+ continue;
736
+ }
737
+ compacted.push(item);
738
+ }
739
+ return compacted;
740
+ }
741
+ function findCommandToMerge(items, item) {
742
+ if (item.itemId) {
743
+ for (let index = items.length - 1; index >= 0; index -= 1) {
744
+ const candidate = items[index];
745
+ if (candidate.kind === "command" && candidate.itemId === item.itemId) return candidate;
746
+ }
747
+ }
748
+ const previous = items[items.length - 1];
749
+ if (previous?.kind === "command" && !item.command && previous.title === item.title) return previous;
750
+ return null;
751
+ }
752
+ function isCommandEvent(method, itemType, item) {
753
+ const text = `${method} ${itemType} ${stringValue(item, "name") ?? ""}`.toLowerCase();
754
+ return text.includes("command") || text.includes("shell") || text.includes("exec");
755
+ }
756
+ function isFileEvent(method, itemType, item) {
757
+ const text = `${method} ${itemType} ${stringValue(item, "name") ?? ""}`.toLowerCase();
758
+ return text.includes("file") || text.includes("patch") || text.includes("apply_patch") || Boolean(stringValue(item, "path"));
759
+ }
760
+ function isToolEvent(method, itemType, item) {
761
+ const text = `${method} ${itemType} ${stringValue(item, "name") ?? ""}`.toLowerCase();
762
+ return text.includes("tool") || Boolean(stringValue(item, "name"));
763
+ }
764
+ function titleFromMethod(method, itemType) {
765
+ const base = itemType || method.split("/").filter(Boolean).pop() || method;
766
+ return base.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
767
+ }
768
+ function statusFromEvent(method, item) {
769
+ return firstString(
770
+ stringValue(item, "status"),
771
+ method.includes("completed") ? "completed" : void 0,
772
+ method.includes("started") ? "started" : void 0
773
+ );
774
+ }
775
+ function commandStatus(item) {
776
+ const status = stringValue(item, "status");
777
+ const exitCode = valueAt(item, "exitCode") ?? valueAt(item, "exit_code");
778
+ const durationMs = valueAt(item, "durationMs") ?? valueAt(item, "duration_ms");
779
+ const parts = [
780
+ status,
781
+ typeof exitCode === "number" ? `exit ${exitCode}` : void 0,
782
+ typeof durationMs === "number" ? `${durationMs} ms` : void 0
783
+ ].filter(Boolean);
784
+ return parts.length > 0 ? parts.join(" / ") : void 0;
785
+ }
786
+ function commandFromToolArguments(item) {
787
+ return commandFromArguments(parseJson(stringValue(item, "arguments") ?? ""));
788
+ }
789
+ function commandFromArguments(args) {
790
+ return firstString(
791
+ stringValue(args, "cmd"),
792
+ stringValue(args, "command"),
793
+ stringValue(args, "chars")
794
+ );
795
+ }
796
+ function commandFromDelta(delta) {
797
+ const command = valueAt(delta, "command");
798
+ if (Array.isArray(command)) return command.map((part) => typeof part === "string" ? part : String(part)).join(" ");
799
+ return firstString(stringValue(delta, "command"), stringValue(delta, "cmd"));
800
+ }
801
+ function commandDeltaStatus(delta) {
802
+ const exitCode = valueAt(delta, "exitCode") ?? valueAt(delta, "exit_code");
803
+ const status = stringValue(delta, "status");
804
+ const parts = [
805
+ status,
806
+ typeof exitCode === "number" ? `exit ${exitCode}` : void 0
807
+ ].filter(Boolean);
808
+ return parts.length > 0 ? parts.join(" / ") : void 0;
809
+ }
810
+ function fileChangesText(item) {
811
+ const changes = valueAt(item, "changes");
812
+ if (!Array.isArray(changes)) return void 0;
813
+ const seen = /* @__PURE__ */ new Set();
814
+ return changes.map((change) => {
815
+ if (!isObject2(change)) return "";
816
+ const path = stringValue(change, "path");
817
+ const kind = stringValue(change, "kind");
818
+ const diff = stringValue(change, "diff");
819
+ const text = [path, kind, diff].filter(Boolean).join("\n");
820
+ if (!text || seen.has(text)) return "";
821
+ seen.add(text);
822
+ return text;
823
+ }).filter(Boolean).join("\n\n");
824
+ }
825
+ function pathFromChanges(item) {
826
+ const changes = valueAt(item, "changes");
827
+ if (!Array.isArray(changes)) return void 0;
828
+ const paths = [...new Set(changes.map((change) => isObject2(change) ? stringValue(change, "path") : void 0).filter(Boolean))];
829
+ return paths.length === 1 ? paths[0] : void 0;
830
+ }
831
+ function tokenUsageFromEvent(event) {
832
+ const json = parseJson(event.message);
833
+ if (!json) return void 0;
834
+ if (event.type === "tokens") {
835
+ return normalizeTokenUsage(json);
836
+ }
837
+ if (event.type !== "codex-event" && !isCodexJsonEvent(event)) {
838
+ return void 0;
839
+ }
840
+ const cliType = stringValue(json, "type");
841
+ const method = stringValue(json, "method") || (cliType ? cliType.replace(/\./g, "/") : void 0) || event.stream;
842
+ if (method !== "thread/tokenUsage/updated") return void 0;
843
+ const params = objectValue(json, "params") ?? json;
844
+ return normalizeTokenUsage(params);
845
+ }
846
+ function normalizeTokenUsage(tokenUsage) {
847
+ return objectValue(tokenUsage, "tokenUsage") ?? tokenUsage;
848
+ }
849
+ function contextPressureFromUsage(tokenUsage) {
850
+ const usage = normalizeTokenUsage(tokenUsage);
851
+ const lastUsage = objectValue(usage, "last");
852
+ const last = lastUsage ?? usage;
853
+ const contextWindow = numberValue(usage, "modelContextWindow") ?? numberValue(usage, "model_context_window");
854
+ const lastInputTokens = numberValue(last, "inputTokens") ?? numberValue(last, "input_tokens");
855
+ const flatInputTokens = lastUsage ? void 0 : numberValue(usage, "inputTokens") ?? numberValue(usage, "input_tokens");
856
+ const usedTokens = lastInputTokens ?? flatInputTokens;
857
+ if (!usedTokens || !contextWindow || contextWindow <= 0) return null;
858
+ const ratio = usedTokens / contextWindow;
859
+ const percent = Math.round(ratio * 100);
860
+ const tone = ratio >= 0.9 ? "critical" : ratio >= 0.75 ? "high" : ratio >= 0.5 ? "medium" : "low";
861
+ const source = lastUsage ? "last-input" : "reported-input";
862
+ return {
863
+ usedTokens,
864
+ contextWindow,
865
+ ratio,
866
+ percent,
867
+ tone,
868
+ source,
869
+ label: `Latest request context: ${percent}% (${usedTokens.toLocaleString()} / ${contextWindow.toLocaleString()} input tokens).`
870
+ };
871
+ }
872
+ function tokenUsageText(tokenUsage) {
873
+ const usage = normalizeTokenUsage(tokenUsage);
874
+ const total = objectValue(usage, "total") ?? usage;
875
+ const last = objectValue(usage, "last") ?? usage;
876
+ const contextWindow = numberValue(usage, "modelContextWindow") ?? numberValue(usage, "model_context_window");
877
+ const totalTokens = numberValue(total, "totalTokens") ?? numberValue(total, "total_tokens");
878
+ const inputTokens = numberValue(last, "inputTokens") ?? numberValue(last, "input_tokens");
879
+ const cachedInputTokens = numberValue(last, "cachedInputTokens") ?? numberValue(last, "cached_input_tokens");
880
+ const outputTokens = numberValue(last, "outputTokens") ?? numberValue(last, "output_tokens");
881
+ const reasoningOutputTokens = numberValue(last, "reasoningOutputTokens") ?? numberValue(last, "reasoning_output_tokens");
882
+ const parts = [
883
+ totalTokens !== void 0 ? `${totalTokens.toLocaleString()} total tokens` : void 0,
884
+ inputTokens !== void 0 ? `${inputTokens.toLocaleString()} input` : void 0,
885
+ cachedInputTokens !== void 0 ? `${cachedInputTokens.toLocaleString()} cached input` : void 0,
886
+ outputTokens !== void 0 ? `${outputTokens.toLocaleString()} output` : void 0,
887
+ reasoningOutputTokens !== void 0 ? `${reasoningOutputTokens.toLocaleString()} reasoning` : void 0,
888
+ contextWindow !== void 0 ? `${contextWindow.toLocaleString()} context` : void 0
889
+ ].filter(Boolean);
890
+ return parts.length > 0 ? `Token usage: ${parts.join(" / ")}.` : "Token usage updated.";
891
+ }
892
+ function contentText(value) {
893
+ if (!Array.isArray(value)) return void 0;
894
+ return value.map((entry) => isObject2(entry) ? firstString(stringValue(entry, "text"), stringValue(entry, "summary")) : void 0).filter(Boolean).join("\n");
895
+ }
896
+ function decodedToolOutput(value) {
897
+ const json = parseJson(value ?? "");
898
+ return firstString(
899
+ stringValue(json, "output"),
900
+ stringValue(json, "message"),
901
+ value
902
+ );
903
+ }
904
+ function outputStatus(value) {
905
+ const metadata = objectValue(parseJson(value ?? ""), "metadata");
906
+ const exitCode = valueAt(metadata, "exit_code");
907
+ return typeof exitCode === "number" ? `exit ${exitCode}` : void 0;
908
+ }
909
+ function isCommandToolName(name) {
910
+ return name === "exec_command" || name === "write_stdin";
911
+ }
912
+ function readableJson(value) {
913
+ try {
914
+ return JSON.stringify(JSON.parse(value), null, 2);
915
+ } catch {
916
+ return value;
917
+ }
918
+ }
919
+ function plainError(value) {
920
+ const json = parseJson(value);
921
+ return firstString(
922
+ stringValue(json, "message"),
923
+ stringValue(json, "error"),
924
+ stringValue(objectValue(json, "error"), "message"),
925
+ value
926
+ ) ?? value;
927
+ }
928
+ function parseJson(value) {
929
+ try {
930
+ const parsed = JSON.parse(value);
931
+ return isObject2(parsed) ? parsed : void 0;
932
+ } catch {
933
+ return void 0;
934
+ }
935
+ }
936
+ function objectValue(value, key) {
937
+ if (!isObject2(value)) return void 0;
938
+ const child = value[key];
939
+ return isObject2(child) ? child : void 0;
940
+ }
941
+ function valueAt(value, key) {
942
+ return isObject2(value) ? value[key] : void 0;
943
+ }
944
+ function numberValue(value, key) {
945
+ const child = valueAt(value, key);
946
+ return typeof child === "number" && Number.isFinite(child) ? child : void 0;
947
+ }
948
+ function stringValue(value, key) {
949
+ if (!isObject2(value)) return void 0;
950
+ const child = value[key];
951
+ return typeof child === "string" && child.length > 0 ? child : void 0;
952
+ }
953
+ function firstString(...values) {
954
+ return values.find((value) => value && value.length > 0);
955
+ }
956
+ function stringifyCompact(value) {
957
+ if (value === void 0 || value === null) return void 0;
958
+ if (typeof value === "string") return value;
959
+ try {
960
+ return JSON.stringify(value, null, 2);
961
+ } catch {
962
+ return String(value);
963
+ }
964
+ }
965
+ function isObject2(value) {
966
+ return typeof value === "object" && value !== null && !Array.isArray(value);
967
+ }
968
+
969
+ // src/TaskDetail.tsx
970
+ var import_jsx_runtime = __require("react/jsx-runtime");
971
+ function TaskDetail({
972
+ task,
973
+ maximized,
974
+ minimized,
975
+ platformStatus,
976
+ elapsedNow,
977
+ traceLoadingTurnId,
978
+ selectedTraceTurn,
979
+ selectedTraceTurnIndex,
980
+ turnRefs,
981
+ followUp,
982
+ followUpAttachments,
983
+ followUpSubmitting,
984
+ followUpError = null,
985
+ interruptMessage,
986
+ interruptSubmitting,
987
+ steerSubmitting,
988
+ taskCommandStatus = null,
989
+ refreshSubmitting = false,
990
+ onClose,
991
+ onMinimize,
992
+ onRestore,
993
+ onToggleMaximized,
994
+ onRefresh,
995
+ onOpenTrace,
996
+ onCloseTrace,
997
+ onSubmitFollowUp,
998
+ onSubmitSteer,
999
+ onFollowUpChange,
1000
+ onFollowUpAttachmentsChange,
1001
+ uploadAttachment,
1002
+ onSubmitInterrupt,
1003
+ onInterruptMessageChange,
1004
+ onSubmitClientAction,
1005
+ currentUserId,
1006
+ shareTask,
1007
+ unshareTask
1008
+ }) {
1009
+ const [followUpAttachmentsProcessing, setFollowUpAttachmentsProcessing] = (0, import_react2.useState)(false);
1010
+ const [accessDetailsOpen, setAccessDetailsOpen] = (0, import_react2.useState)(false);
1011
+ const [shareUser, setShareUser] = (0, import_react2.useState)("");
1012
+ const [shareLevel, setShareLevel] = (0, import_react2.useState)("read");
1013
+ const [shareSubmitting, setShareSubmitting] = (0, import_react2.useState)(false);
1014
+ const [shareError, setShareError] = (0, import_react2.useState)(null);
1015
+ const [clientActionSubmitting, setClientActionSubmitting] = (0, import_react2.useState)(false);
1016
+ const [clientActionError, setClientActionError] = (0, import_react2.useState)(null);
1017
+ const previousTaskStateRef = (0, import_react2.useRef)(null);
1018
+ const taskInputPlaceholder = "Send instructions to the AI agent";
1019
+ const taskQueued = task ? isTaskQueued(task) : false;
1020
+ (0, import_react2.useEffect)(() => {
1021
+ setAccessDetailsOpen(false);
1022
+ setShareUser("");
1023
+ setShareLevel("read");
1024
+ setShareError(null);
1025
+ setClientActionError(null);
1026
+ }, [task?.id]);
1027
+ const canManageSharing = Boolean(task && shareTask && unshareTask && (!currentUserId || task.createdByUserId === currentUserId));
1028
+ async function submitShare(event) {
1029
+ event.preventDefault();
1030
+ if (!task || !shareTask || !shareUser.trim() || shareSubmitting) return;
1031
+ setShareSubmitting(true);
1032
+ setShareError(null);
1033
+ try {
1034
+ const updated = await shareTask(task.id, {
1035
+ userId: shareUser.trim(),
1036
+ email: shareUser.includes("@") ? shareUser.trim() : void 0,
1037
+ accessLevel: shareLevel
1038
+ });
1039
+ if (updated) setShareUser("");
1040
+ else setShareError("Task sharing failed.");
1041
+ } catch (error) {
1042
+ setShareError(error instanceof Error ? error.message : "Task sharing failed.");
1043
+ } finally {
1044
+ setShareSubmitting(false);
1045
+ }
1046
+ }
1047
+ async function removeShare(userId) {
1048
+ if (!task || !unshareTask || shareSubmitting) return;
1049
+ setShareSubmitting(true);
1050
+ setShareError(null);
1051
+ try {
1052
+ const updated = await unshareTask(task.id, userId);
1053
+ if (!updated) setShareError("Task sharing failed.");
1054
+ } catch (error) {
1055
+ setShareError(error instanceof Error ? error.message : "Task sharing failed.");
1056
+ } finally {
1057
+ setShareSubmitting(false);
1058
+ }
1059
+ }
1060
+ (0, import_react2.useEffect)(() => {
1061
+ const previous = previousTaskStateRef.current;
1062
+ if (!task) {
1063
+ previousTaskStateRef.current = null;
1064
+ return;
1065
+ }
1066
+ if (previous?.id === task.id && previous.busy && !isTaskBusy(task) && isTaskFinishedStatus(task.status)) {
1067
+ notifyTaskFinished(task);
1068
+ }
1069
+ previousTaskStateRef.current = { id: task.id, busy: isTaskBusy(task), status: task.status };
1070
+ }, [task]);
1071
+ (0, import_react2.useEffect)(() => {
1072
+ if (!task || !isReloadClientAppAction(task.clientAction)) return;
1073
+ if (typeof window === "undefined") return;
1074
+ const action = task.clientAction;
1075
+ const key = `exo-client-action:${task.id}:${action.id}`;
1076
+ try {
1077
+ if (window.sessionStorage.getItem(key)) return;
1078
+ window.sessionStorage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
1079
+ } catch {
1080
+ }
1081
+ const delay = typeof action.delayMs === "number" && Number.isFinite(action.delayMs) ? Math.max(0, Math.min(3e4, action.delayMs)) : 0;
1082
+ const timer = window.setTimeout(() => window.location.reload(), delay);
1083
+ return () => window.clearTimeout(timer);
1084
+ }, [task?.id, task?.clientAction]);
1085
+ async function submitClientAction(action, message) {
1086
+ const continuation = externalContinuation(action);
1087
+ if (!task || !onSubmitClientAction || !continuation?.token || clientActionSubmitting) return;
1088
+ setClientActionSubmitting(true);
1089
+ setClientActionError(null);
1090
+ try {
1091
+ const updated = await onSubmitClientAction(task.id, { actionId: action.id, token: continuation.token, message });
1092
+ if (!updated) setClientActionError("Decision could not be submitted.");
1093
+ } catch (error) {
1094
+ setClientActionError(error instanceof Error ? error.message : "Decision could not be submitted.");
1095
+ } finally {
1096
+ setClientActionSubmitting(false);
1097
+ }
1098
+ }
1099
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1100
+ task && minimized && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "task-detail-dock", role: "region", "aria-label": "Minimized task detail", children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "task-detail-dock-main", children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { title: formatTaskFullTitle(task), children: formatTaskDetailTitle(task) }),
1103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: taskStatusContent(task, platformStatus) })
1104
+ ] }),
1105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "task-detail-dock-actions", children: [
1106
+ onRefresh && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onRefresh, disabled: refreshSubmitting, "aria-label": "Refresh task detail", title: "Refresh", children: refreshSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { "aria-hidden": "true" }) }),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onRestore, "aria-label": "Restore task detail", title: "Restore", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PanelBottomOpen, { "aria-hidden": "true" }) }),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "aria-hidden": "true" }) })
1109
+ ] })
1110
+ ] }),
1111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", { className: `drawer${task && !minimized ? " open" : ""}${maximized ? " maximized" : ""}`, "aria-hidden": !task || minimized, children: task && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [
1113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-heading", children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-status-row", children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: taskStatusContent(task, platformStatus) }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1117
+ TaskAccessSummary,
1118
+ {
1119
+ task,
1120
+ detailsOpen: accessDetailsOpen,
1121
+ onToggle: () => setAccessDetailsOpen((open) => !open)
1122
+ }
1123
+ )
1124
+ ] }),
1125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-tools", children: [
1126
+ onRefresh && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onRefresh, disabled: refreshSubmitting, "aria-label": "Refresh task detail", title: "Refresh", children: refreshSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { "aria-hidden": "true" }) }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onMinimize, "aria-label": "Minimize task detail", title: "Minimize", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PanelBottomClose, { "aria-hidden": "true" }) }),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1129
+ "button",
1130
+ {
1131
+ className: "icon-button",
1132
+ onClick: onToggleMaximized,
1133
+ "aria-label": maximized ? "Restore task detail" : "Maximize task detail",
1134
+ title: maximized ? "Restore" : "Maximize",
1135
+ children: maximized ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minimize2, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Maximize2, { "aria-hidden": "true" })
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "icon-button", onClick: onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "aria-hidden": "true" }) })
1139
+ ] })
1140
+ ] }),
1141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { title: formatTaskFullTitle(task), children: formatTaskDetailTitle(task) }),
1142
+ accessDetailsOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskDetailDetails, { task }),
1143
+ canManageSharing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1144
+ TaskSharingPanel,
1145
+ {
1146
+ task,
1147
+ shareUser,
1148
+ shareLevel,
1149
+ submitting: shareSubmitting,
1150
+ error: shareError,
1151
+ onShareUserChange: setShareUser,
1152
+ onShareLevelChange: setShareLevel,
1153
+ onSubmit: submitShare,
1154
+ onRemove: removeShare
1155
+ }
1156
+ )
1157
+ ] }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-content", children: [
1159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-body", children: [
1160
+ task.updatePublicationFailures?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskUpdatePublicationWarning, { failures: task.updatePublicationFailures }) : null,
1161
+ task.turns.map((turn, index) => {
1162
+ const result = taskTurnResult(task, turn, index);
1163
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1164
+ "article",
1165
+ {
1166
+ className: "turn",
1167
+ ref: (element) => {
1168
+ if (element) {
1169
+ turnRefs.current.set(turn.id, element);
1170
+ } else {
1171
+ turnRefs.current.delete(turn.id);
1172
+ }
1173
+ },
1174
+ children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "turn-panel request-panel", "aria-label": `Request ${index + 1}`, children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnTitle, { index, timestamp: turn.createdAt }),
1177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: turn.content })
1178
+ ] }),
1179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SteeringMessages, { messages: taskSteeringMessages(task, index) }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "turn-panel output-panel", "aria-label": `AI agent output for request ${index + 1}`, children: [
1181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "turn-title output-title", children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: formatAgentTitle(turn, elapsedNow) }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { children: formatCompletionTime(turn) })
1184
+ ] }),
1185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "result", children: isTurnQueued(turn) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "working-result-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `status-text ${statusToneClass(turn.status)}`, children: "Queued" }) }) : isTurnWorking(turn) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "working-result-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkingIndicator, { label: platformStatus.available ? "Working" : "The platform is down. Wait until it is back up", animate: platformStatus.available }) }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "output-panel-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceButton, { loading: traceLoadingTurnId === turn.id, onClick: () => onOpenTrace(turn.id) }) })
1188
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormattedText, { text: result.owner || "No output recorded." }),
1190
+ result.technical && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "technical-summary", children: [
1191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
1192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "technical-summary-title", children: "Technical summary" }),
1193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1194
+ TraceButton,
1195
+ {
1196
+ loading: traceLoadingTurnId === turn.id,
1197
+ onClick: () => onOpenTrace(turn.id),
1198
+ stopPropagation: true
1199
+ }
1200
+ )
1201
+ ] }),
1202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "technical-summary-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormattedText, { text: result.technical }) })
1203
+ ] }),
1204
+ !result.technical && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "output-panel-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceButton, { loading: traceLoadingTurnId === turn.id, onClick: () => onOpenTrace(turn.id) }) }),
1205
+ index === task.turns.length - 1 && isUserDecisionAction(task.clientAction) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1206
+ ClientActionPanel,
1207
+ {
1208
+ action: task.clientAction,
1209
+ submitting: clientActionSubmitting,
1210
+ error: clientActionError,
1211
+ canSubmit: Boolean(onSubmitClientAction),
1212
+ onSubmit: (message) => void submitClientAction(task.clientAction, message)
1213
+ }
1214
+ )
1215
+ ] }) })
1216
+ ] })
1217
+ ]
1218
+ },
1219
+ turn.id
1220
+ );
1221
+ })
1222
+ ] }),
1223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { className: "drawer-form", onSubmit: followUpAttachmentsProcessing ? (event) => event.preventDefault() : onSubmitFollowUp, children: task.busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "interrupt-panel", children: [
1224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-form-toolbar", children: [
1225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandStatus, { status: taskCommandStatus }),
1226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-form-buttons", children: [
1227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary icon-action-button", type: "button", disabled: taskQueued || interruptSubmitting || steerSubmitting || !interruptMessage.trim(), onClick: onSubmitSteer, "aria-label": "Send steering message", title: "Steer", children: steerSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Send, { "aria-hidden": "true" }) }),
1228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "danger icon-action-button", type: "button", disabled: interruptSubmitting || steerSubmitting, onClick: onSubmitInterrupt, "aria-label": "Interrupt task", title: "Interrupt", children: interruptSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Square, { "aria-hidden": "true" }) })
1229
+ ] })
1230
+ ] }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { disabled: taskQueued || interruptSubmitting || steerSubmitting, value: interruptMessage, onChange: (event) => onInterruptMessageChange(event.target.value), placeholder: taskQueued ? "Task is queued" : taskInputPlaceholder })
1232
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-form-toolbar", children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1235
+ AttachmentPicker,
1236
+ {
1237
+ attachments: followUpAttachments,
1238
+ disabled: followUpSubmitting,
1239
+ onChange: onFollowUpAttachmentsChange,
1240
+ onProcessingChange: setFollowUpAttachmentsProcessing,
1241
+ uploadAttachment,
1242
+ compact: true
1243
+ }
1244
+ ),
1245
+ followUpError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "command-status error", role: "alert", children: followUpError }),
1246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "drawer-form-buttons", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary icon-action-button", disabled: followUpAttachmentsProcessing || followUpSubmitting || !followUp.trim(), "aria-label": "Continue task", title: "Continue", children: followUpAttachmentsProcessing || followUpSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Send, { "aria-hidden": "true" }) }) })
1247
+ ] }),
1248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { disabled: followUpSubmitting, value: followUp, onChange: (event) => onFollowUpChange(event.target.value), placeholder: taskInputPlaceholder })
1249
+ ] }) }),
1250
+ selectedTraceTurn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "task-trace-overlay", role: "dialog", "aria-modal": "false", "aria-label": `Trace for request ${selectedTraceTurnIndex + 1}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1251
+ TracePanel,
1252
+ {
1253
+ turn: selectedTraceTurn,
1254
+ turnIndex: selectedTraceTurnIndex,
1255
+ loading: traceLoadingTurnId === selectedTraceTurn.id && (selectedTraceTurn.trace?.events?.length ?? 0) === 0,
1256
+ onCollapse: onCloseTrace
1257
+ }
1258
+ ) })
1259
+ ] })
1260
+ ] }) })
1261
+ ] });
1262
+ }
1263
+ function TaskUpdatePublicationWarning({ failures }) {
1264
+ const latest = failures[failures.length - 1];
1265
+ const previousCount = failures.length - 1;
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "task-publication-warning", "aria-label": "Task update publication warning", children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Update publication degraded" }),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1269
+ latest.updateType,
1270
+ " could not be published. Retry status: ",
1271
+ formatRetryStatus(latest.retryStatus, latest.retryAttempts),
1272
+ ".",
1273
+ previousCount > 0 ? ` ${previousCount} earlier update${previousCount === 1 ? "" : "s"} also failed.` : ""
1274
+ ] })
1275
+ ] });
1276
+ }
1277
+ function ClientActionPanel({
1278
+ action,
1279
+ submitting,
1280
+ error,
1281
+ canSubmit,
1282
+ onSubmit
1283
+ }) {
1284
+ const mode = userDecisionSelectionMode(action);
1285
+ const options = action.options.filter((option) => option && typeof option.id === "string" && typeof option.label === "string");
1286
+ const [selectedIds, setSelectedIds] = (0, import_react2.useState)(() => options[0]?.id ? [options[0].id] : []);
1287
+ const [otherText, setOtherText] = (0, import_react2.useState)("");
1288
+ const allowsOther = userDecisionAllowsOther(action);
1289
+ const continuation = externalContinuation(action);
1290
+ const canContinue = canSubmit && Boolean(continuation?.token) && (selectedIds.length > 0 || allowsOther && Boolean(otherText.trim()));
1291
+ (0, import_react2.useEffect)(() => {
1292
+ setSelectedIds(options[0]?.id ? [options[0].id] : []);
1293
+ setOtherText("");
1294
+ }, [action.id]);
1295
+ function toggleOption(optionId) {
1296
+ if (mode === "single") {
1297
+ setSelectedIds([optionId]);
1298
+ return;
1299
+ }
1300
+ setSelectedIds((current) => current.includes(optionId) ? current.filter((id) => id !== optionId) : [...current, optionId]);
1301
+ }
1302
+ function submit(event) {
1303
+ event.preventDefault();
1304
+ if (!canContinue || submitting) return;
1305
+ onSubmit(`User decision result:
1306
+ ${JSON.stringify({
1307
+ clientActionResult: {
1308
+ type: "userDecision",
1309
+ actionId: action.id,
1310
+ selectedOptionIds: selectedIds,
1311
+ otherText: allowsOther && otherText.trim() ? otherText.trim() : null,
1312
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString()
1313
+ }
1314
+ }, null, 2)}`);
1315
+ }
1316
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: "client-action-panel", onSubmit: submit, children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "client-action-heading", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: action.prompt }) }),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "client-action-options", children: options.map((option) => {
1319
+ const checked = selectedIds.includes(option.id);
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "client-action-option", children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1322
+ "input",
1323
+ {
1324
+ type: mode === "multiple" ? "checkbox" : "radio",
1325
+ name: `client-action-${action.id}`,
1326
+ checked,
1327
+ onChange: () => toggleOption(option.id)
1328
+ }
1329
+ ),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: option.label }),
1332
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: option.description })
1333
+ ] })
1334
+ ] }, option.id);
1335
+ }) }),
1336
+ allowsOther && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "client-action-other", children: [
1337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: userDecisionOtherLabel(action) }),
1338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1339
+ "textarea",
1340
+ {
1341
+ value: otherText,
1342
+ onChange: (event) => setOtherText(event.target.value),
1343
+ placeholder: userDecisionOtherPlaceholder(action),
1344
+ rows: 2
1345
+ }
1346
+ )
1347
+ ] }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "client-action-footer", children: [
1349
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "command-status error", role: "alert", children: error }),
1350
+ !continuation?.token && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "command-status muted", children: "Waiting for continuation access." }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary icon-action-button", type: "submit", disabled: !canContinue || submitting, "aria-label": "Submit decision", title: "Submit decision", children: submitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { "aria-hidden": "true" }) })
1352
+ ] })
1353
+ ] });
1354
+ }
1355
+ function formatRetryStatus(status, attempts) {
1356
+ const label = status.trim().replace(/[-_]+/g, " ") || "unknown";
1357
+ return attempts > 0 ? `${label}, ${attempts} attempt${attempts === 1 ? "" : "s"}` : label;
1358
+ }
1359
+ function TaskSharingPanel({
1360
+ task,
1361
+ shareUser,
1362
+ shareLevel,
1363
+ submitting,
1364
+ error,
1365
+ onShareUserChange,
1366
+ onShareLevelChange,
1367
+ onSubmit,
1368
+ onRemove
1369
+ }) {
1370
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "task-sharing-panel", "aria-label": "Task sharing", children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: "task-sharing-form", onSubmit, children: [
1372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Share2, { "aria-hidden": "true" }),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1374
+ "input",
1375
+ {
1376
+ value: shareUser,
1377
+ onChange: (event) => onShareUserChange(event.target.value),
1378
+ placeholder: "User ID or email",
1379
+ disabled: submitting
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: shareLevel, onChange: (event) => onShareLevelChange(event.target.value), disabled: submitting, children: [
1383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "read", children: "Read only" }),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "full", children: "Full" })
1385
+ ] }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", disabled: submitting || !shareUser.trim(), children: submitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : "Share" })
1387
+ ] }),
1388
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "task-sharing-error", children: error }),
1389
+ (task.shares?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "task-share-list", children: task.shares.map((share) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "task-share-row", children: [
1390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: share.displayName || share.email || share.userId }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: share.accessLevel === "full" ? "Full" : "Read only" }),
1392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => onRemove(share.userId), disabled: submitting, "aria-label": `Remove ${share.displayName || share.email || share.userId}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { "aria-hidden": "true" }) })
1393
+ ] }, share.userId)) })
1394
+ ] });
1395
+ }
1396
+ function TaskDetailDetails({ task }) {
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "task-detail-details", children: [
1398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskDetailMeta, { task }),
1399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessChips, { task })
1400
+ ] });
1401
+ }
1402
+ function TaskDetailMeta({ task }) {
1403
+ const startedAt = task.startedAt ?? task.createdAt;
1404
+ const completedAt = task.completedAt ?? null;
1405
+ const updatedAt = task.updatedAt ?? completedAt ?? startedAt;
1406
+ const turnsCount = task.turns.length;
1407
+ const codexModel = normalizeMetadataValue(task.codexModel);
1408
+ const codexReasoningEffort = normalizeMetadataValue(task.codexReasoningEffort);
1409
+ const creator = formatCreatorName(task);
1410
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "drawer-meta", "aria-label": "Task metadata", children: [
1411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "ID" }),
1413
+ "\xA0",
1414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: task.id })
1415
+ ] }),
1416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Creator" }),
1418
+ "\xA0",
1419
+ creator
1420
+ ] }),
1421
+ codexModel && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Model" }),
1423
+ "\xA0",
1424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: codexModel })
1425
+ ] }),
1426
+ codexReasoningEffort && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Effort" }),
1428
+ "\xA0",
1429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: codexReasoningEffort })
1430
+ ] }),
1431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Started" }),
1433
+ "\xA0",
1434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { dateTime: startedAt, children: formatDateTime(startedAt) })
1435
+ ] }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Updated" }),
1438
+ "\xA0",
1439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { dateTime: updatedAt, children: formatDateTime(updatedAt) })
1440
+ ] }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "Turns" }),
1443
+ "\xA0",
1444
+ turnsCount
1445
+ ] })
1446
+ ] });
1447
+ }
1448
+ function formatCreatorName(task) {
1449
+ return normalizeMetadataValue(task.createdByUserDisplayName) ?? normalizeMetadataValue(task.createdByUserEmail) ?? normalizeMetadataValue(task.createdByUserId) ?? "Unknown";
1450
+ }
1451
+ function normalizeMetadataValue(value) {
1452
+ const normalized = value?.trim();
1453
+ return normalized ? normalized : null;
1454
+ }
1455
+ function TraceButton({
1456
+ loading,
1457
+ onClick,
1458
+ stopPropagation = false
1459
+ }) {
1460
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1461
+ "button",
1462
+ {
1463
+ className: `trace-open-button${loading ? " loading" : ""}`,
1464
+ type: "button",
1465
+ onClick: (event) => {
1466
+ if (stopPropagation) event.stopPropagation();
1467
+ onClick();
1468
+ },
1469
+ disabled: loading,
1470
+ "aria-busy": loading,
1471
+ children: [
1472
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Waypoints, { "aria-hidden": "true" }),
1473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: loading ? "Loading trace" : "Trace" })
1474
+ ]
1475
+ }
1476
+ );
1477
+ }
1478
+ function TaskAccessSummary({
1479
+ task,
1480
+ detailsOpen,
1481
+ onToggle
1482
+ }) {
1483
+ const counts = accessRequestCounts(groupedAccessRequests(task.accessRequests ?? []));
1484
+ const total = counts.granted + counts.released + counts.waiting + counts.denied;
1485
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1486
+ "button",
1487
+ {
1488
+ className: `task-access-summary${total === 0 ? " task-details-summary" : ""}`,
1489
+ type: "button",
1490
+ onClick: onToggle,
1491
+ "aria-expanded": detailsOpen,
1492
+ "aria-label": accessSummaryLabel(counts, detailsOpen),
1493
+ title: detailsOpen ? "Hide access details" : "Show access details",
1494
+ children: total === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "task-details-summary-label", children: "Details" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1495
+ counts.granted > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessSummaryItem, { state: "granted", count: counts.granted }),
1496
+ counts.released > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessSummaryItem, { state: "released", count: counts.released }),
1497
+ counts.waiting > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessSummaryItem, { state: "waiting", count: counts.waiting }),
1498
+ counts.denied > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessSummaryItem, { state: "denied", count: counts.denied })
1499
+ ] })
1500
+ }
1501
+ );
1502
+ }
1503
+ function TaskAccessSummaryItem({ state, count }) {
1504
+ const Icon = accessRequestIcon(state);
1505
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: `task-access-summary-item ${state}`, children: [
1506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: "task-access-summary-icon", "aria-hidden": "true" }),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: count })
1508
+ ] });
1509
+ }
1510
+ function TaskAccessChips({ task }) {
1511
+ const requests = groupedAccessRequests(task.accessRequests ?? []);
1512
+ if (requests.length === 0) {
1513
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "task-access-chips", "aria-label": "Agent access", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "task-access-chip muted", children: "No access requests" }) });
1514
+ }
1515
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "task-access-chips", "aria-label": "Agent access", children: requests.map((request, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskAccessChip, { request, index }, accessRequestKey(request, index))) });
1516
+ }
1517
+ function TaskAccessChip({ request, index }) {
1518
+ const state = accessRequestState(request);
1519
+ const label = accessRequestLabel(request);
1520
+ const Icon = accessRequestIcon(state);
1521
+ const occurrences = accessRequestOccurrences(request);
1522
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1523
+ "span",
1524
+ {
1525
+ className: `task-access-chip ${state}`,
1526
+ title: `${label} ${request.level} access to ${request.target} at ${formatDateTime(accessRequestTime(request))}${occurrences > 1 ? ` (${occurrences} requests)` : ""}`,
1527
+ "aria-label": `${label} ${request.target} ${request.level}`,
1528
+ "data-access-index": index,
1529
+ children: [
1530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: "task-access-icon", "aria-hidden": "true" }),
1531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "task-access-text", children: [
1532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: request.target }),
1533
+ " ",
1534
+ request.level,
1535
+ occurrences > 1 ? ` x${occurrences}` : ""
1536
+ ] })
1537
+ ]
1538
+ }
1539
+ );
1540
+ }
1541
+ function groupedAccessRequests(requests) {
1542
+ const groups = /* @__PURE__ */ new Map();
1543
+ for (const request of requests) {
1544
+ const key = `${request.target.trim().toLowerCase()}\0${request.level.trim().toLowerCase()}`;
1545
+ groups.set(key, [...groups.get(key) ?? [], request]);
1546
+ }
1547
+ return Array.from(groups.values()).map((group) => {
1548
+ const ordered = [...group].sort((left, right) => Date.parse(left.requestedAt) - Date.parse(right.requestedAt));
1549
+ const active = ordered.filter((request) => accessRequestState(request) !== "released");
1550
+ const selected = latestAccessRequest(active.length > 0 ? active : ordered);
1551
+ return {
1552
+ ...selected,
1553
+ occurrences: ordered.length,
1554
+ firstRequestedAt: ordered[0]?.requestedAt,
1555
+ lastRequestedAt: ordered[ordered.length - 1]?.requestedAt
1556
+ };
1557
+ }).sort((left, right) => Date.parse(accessRequestTime(right)) - Date.parse(accessRequestTime(left)));
1558
+ }
1559
+ function latestAccessRequest(requests) {
1560
+ return requests.reduce((latest, request) => Date.parse(accessRequestTime(request)) >= Date.parse(accessRequestTime(latest)) ? request : latest, requests[0]);
1561
+ }
1562
+ function accessRequestState(request) {
1563
+ if (request.status?.toLowerCase() === "released" || request.releasedAt) return "released";
1564
+ if (request.granted) return "granted";
1565
+ const status = `${request.status ?? ""} ${request.message ?? ""}`.toLowerCase();
1566
+ if (status.includes("wait") || status.includes("queue")) return "waiting";
1567
+ return "denied";
1568
+ }
1569
+ function accessRequestIcon(state) {
1570
+ return state === "granted" || state === "released" ? Check : state === "waiting" ? Hourglass : Square;
1571
+ }
1572
+ function accessRequestCounts(requests) {
1573
+ return requests.reduce(
1574
+ (counts, request) => {
1575
+ counts[accessRequestState(request)] += 1;
1576
+ return counts;
1577
+ },
1578
+ { granted: 0, released: 0, waiting: 0, denied: 0 }
1579
+ );
1580
+ }
1581
+ function accessSummaryLabel(counts, detailsOpen) {
1582
+ const parts = [
1583
+ counts.granted > 0 ? `${counts.granted} granted` : null,
1584
+ counts.released > 0 ? `${counts.released} released` : null,
1585
+ counts.waiting > 0 ? `${counts.waiting} waiting` : null,
1586
+ counts.denied > 0 ? `${counts.denied} denied` : null
1587
+ ].filter(Boolean);
1588
+ if (parts.length === 0) return `${detailsOpen ? "Hide" : "Show"} task details`;
1589
+ return `${detailsOpen ? "Hide" : "Show"} access details: ${parts.join(", ")}`;
1590
+ }
1591
+ function accessRequestLabel(request) {
1592
+ const state = accessRequestState(request);
1593
+ return state === "granted" ? "Granted" : state === "released" ? "Released" : state === "waiting" ? "Waiting" : "Denied";
1594
+ }
1595
+ function accessRequestTime(request) {
1596
+ return request.releasedAt ?? request.requestedAt;
1597
+ }
1598
+ function accessRequestOccurrences(request) {
1599
+ return request.occurrences ?? 1;
1600
+ }
1601
+ function accessRequestKey(request, index) {
1602
+ return request.requestId || `${request.requestedAt}-${request.target}-${request.level}-${index}`;
1603
+ }
1604
+ function AttachmentPicker({
1605
+ attachments,
1606
+ disabled,
1607
+ compact,
1608
+ onChange,
1609
+ onProcessingChange,
1610
+ uploadAttachment
1611
+ }) {
1612
+ const inputRef = (0, import_react2.useRef)(null);
1613
+ const [processingFiles, setProcessingFiles] = (0, import_react2.useState)([]);
1614
+ const hasUploadingAttachments = attachments.some((attachment) => attachment.status === "uploading");
1615
+ const hasFailedAttachments = attachments.some((attachment) => attachment.status === "failed");
1616
+ const hasBlockingAttachments = hasUploadingAttachments || hasFailedAttachments;
1617
+ const isProcessing = processingFiles.length > 0 || hasBlockingAttachments;
1618
+ const preparingCount = Math.max(processingFiles.length, attachments.filter((attachment) => attachment.status === "uploading").length);
1619
+ const statusLabel = hasFailedAttachments && !hasUploadingAttachments && processingFiles.length === 0 ? "Remove failed attachment before sending" : compact ? "Preparing attachments" : `Preparing ${preparingCount} attachment${preparingCount === 1 ? "" : "s"}`;
1620
+ (0, import_react2.useEffect)(() => {
1621
+ onProcessingChange?.(isProcessing);
1622
+ }, [isProcessing, onProcessingChange]);
1623
+ async function addFiles(files) {
1624
+ if (!files || disabled || isProcessing) return;
1625
+ const selectedFiles = Array.from(files).slice(0, 8);
1626
+ const batchId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1627
+ const pendingAttachments = selectedFiles.map((file) => ({
1628
+ name: file.name,
1629
+ contentType: file.type || "text/plain",
1630
+ content: "",
1631
+ uploadId: `pending-${batchId}-${file.name}-${file.size}-${file.lastModified}`,
1632
+ status: "uploading",
1633
+ progress: 0,
1634
+ sizeBytes: file.size
1635
+ }));
1636
+ let nextAttachments = [...attachments, ...pendingAttachments];
1637
+ onChange(nextAttachments);
1638
+ setProcessingFiles(selectedFiles.map((file) => file.name));
1639
+ try {
1640
+ for (let index = 0; index < selectedFiles.length; index += 1) {
1641
+ const file = selectedFiles[index];
1642
+ const pending = pendingAttachments[index];
1643
+ const pendingId = pending.uploadId;
1644
+ const setProgress = (progress) => {
1645
+ nextAttachments = nextAttachments.map((attachment) => attachment.uploadId === pendingId ? { ...attachment, progress } : attachment);
1646
+ onChange(nextAttachments);
1647
+ };
1648
+ try {
1649
+ const attachment = uploadAttachment ? await uploadAttachment(file, setProgress) : await readAttachment(file);
1650
+ nextAttachments = nextAttachments.map((item) => item.uploadId === pendingId ? {
1651
+ ...attachment,
1652
+ name: file.name,
1653
+ contentType: file.type || "text/plain",
1654
+ sizeBytes: file.size,
1655
+ status: "uploaded",
1656
+ progress: 100
1657
+ } : item);
1658
+ onChange(nextAttachments);
1659
+ } catch (error) {
1660
+ nextAttachments = nextAttachments.map((item) => item.uploadId === pendingId ? {
1661
+ ...item,
1662
+ status: "failed",
1663
+ progress: 0,
1664
+ error: error instanceof Error ? error.message : "Upload failed."
1665
+ } : item);
1666
+ onChange(nextAttachments);
1667
+ }
1668
+ setProcessingFiles(selectedFiles.slice(index + 1).map((item) => item.name));
1669
+ }
1670
+ } finally {
1671
+ setProcessingFiles([]);
1672
+ if (inputRef.current) inputRef.current.value = "";
1673
+ }
1674
+ }
1675
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `attachment-picker${compact ? " compact" : ""}`, children: [
1676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: inputRef, type: "file", multiple: true, onChange: (event) => void addFiles(event.target.files), disabled: disabled || isProcessing }),
1677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "attachment-button", disabled: disabled || isProcessing, onClick: () => inputRef.current?.click(), "aria-label": compact ? "Attach files" : void 0, title: compact ? "Attach files" : void 0, children: [
1678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Paperclip, { "aria-hidden": "true" }),
1679
+ !compact && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Attach files" })
1680
+ ] }),
1681
+ isProcessing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "attachment-status", role: "status", "aria-live": "polite", children: [
1682
+ !hasFailedAttachments && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}),
1683
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: statusLabel })
1684
+ ] }),
1685
+ attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "attachment-list", children: attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "attachment-chip", children: [
1686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: attachment.name }),
1687
+ attachment.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1688
+ Math.max(0, Math.min(100, Math.round(attachment.progress ?? 0))),
1689
+ "%"
1690
+ ] }),
1691
+ attachment.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: attachment.error ?? "Upload failed" }),
1692
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => onChange(attachments.filter((_, itemIndex) => itemIndex !== index)), "aria-label": `Remove ${attachment.name}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "aria-hidden": "true" }) })
1693
+ ] }, `${attachment.name}-${index}`)) })
1694
+ ] });
1695
+ }
1696
+ function CommandStatus({ status }) {
1697
+ if (!status) {
1698
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true" });
1699
+ }
1700
+ const label = {
1701
+ interrupting: "Requesting interrupt",
1702
+ "interrupt-accepted": "Interrupt accepted",
1703
+ steering: "Sending steering",
1704
+ "steer-accepted": "Steering accepted",
1705
+ failed: "Command was not accepted"
1706
+ }[status];
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `command-status ${status === "failed" ? "error" : ""}`, role: "status", children: label });
1708
+ }
1709
+ async function readAttachment(file) {
1710
+ const maxAttachmentBytes = 10 * 1024 * 1024;
1711
+ if (file.size > maxAttachmentBytes) {
1712
+ return { content: `[Attachment omitted: ${file.name} is larger than 10 MB.]` };
1713
+ }
1714
+ const dataUrl = await readFileAsDataUrl(file);
1715
+ return { content: dataUrl.split(",", 2)[1] ?? "", contentEncoding: "base64" };
1716
+ }
1717
+ function readFileAsDataUrl(file) {
1718
+ return new Promise((resolve, reject) => {
1719
+ const reader = new FileReader();
1720
+ reader.onload = () => resolve(String(reader.result));
1721
+ reader.onerror = () => reject(reader.error ?? Error("Unable to read attachment."));
1722
+ reader.readAsDataURL(file);
1723
+ });
1724
+ }
1725
+ function formatTaskDetailTitle(task) {
1726
+ return truncateWords(taskTitleText(task), 15) || formatTaskLabel(task);
1727
+ }
1728
+ function formatTaskFullTitle(task) {
1729
+ return taskTitleText(task) || formatTaskLabel(task);
1730
+ }
1731
+ function formatTaskLabel(task) {
1732
+ return `Task: created: ${formatDateTime(task.createdAt)}, modified: ${formatDateTime(task.updatedAt ?? task.completedAt ?? task.startedAt ?? task.createdAt)}`;
1733
+ }
1734
+ function taskTitleText(task) {
1735
+ return collapseWhitespace(task.turns[0]?.content || task.message);
1736
+ }
1737
+ function truncateWords(value, maxWords) {
1738
+ const words = collapseWhitespace(value).split(" ").filter(Boolean);
1739
+ if (words.length <= maxWords) return words.join(" ");
1740
+ return `${words.slice(0, maxWords).join(" ")}...`;
1741
+ }
1742
+ function isTaskFinishedStatus(status) {
1743
+ const normalized = status.toLocaleLowerCase();
1744
+ return normalized === "closed" || normalized === "failed" || normalized === "error" || normalized === "cancelled" || normalized === "canceled";
1745
+ }
1746
+ function notifyTaskFinished(task) {
1747
+ if (typeof window === "undefined" || !("Notification" in window)) return;
1748
+ const title = taskTitleText(task) || "Task finished";
1749
+ const body = `${capitalize(task.status)}.`;
1750
+ const showNotification = () => {
1751
+ try {
1752
+ new Notification(title, { body, tag: `exoscient-task-${task.id}` });
1753
+ } catch {
1754
+ }
1755
+ };
1756
+ if (Notification.permission === "granted") {
1757
+ showNotification();
1758
+ return;
1759
+ }
1760
+ if (Notification.permission === "default") {
1761
+ void Notification.requestPermission().then((permission) => {
1762
+ if (permission === "granted") showNotification();
1763
+ }).catch(() => {
1764
+ });
1765
+ }
1766
+ }
1767
+ function taskTurnResult(task, turn, index) {
1768
+ const split = splitAgentResult(taskTurnResultText(task, turn, index));
1769
+ const turnTechnical = turn.technicalMessage?.trim();
1770
+ if (turnTechnical) {
1771
+ return {
1772
+ owner: split.owner,
1773
+ technical: turnTechnical
1774
+ };
1775
+ }
1776
+ if (index === task.turns.length - 1 && task.technicalMessage) {
1777
+ return {
1778
+ owner: split.owner || task.ownerMessage || "",
1779
+ technical: task.technicalMessage
1780
+ };
1781
+ }
1782
+ return split;
1783
+ }
1784
+ function taskTurnResultText(task, turn, index) {
1785
+ if (turn.output || turn.error) return turn.output || turn.error || "";
1786
+ if (index === task.turns.length - 1 && (task.ownerMessage || task.technicalMessage)) return task.ownerMessage || task.technicalMessage || "";
1787
+ return index === task.turns.length - 1 ? task.latestResult || "" : "";
1788
+ }
1789
+ function taskSteeringMessages(task, index) {
1790
+ if (index !== task.turns.length - 1) return [];
1791
+ return (task.steeringMessages ?? []).filter((item) => item.message.trim().length > 0);
1792
+ }
1793
+ function collapseWhitespace(value) {
1794
+ return value.trim().replace(/\s+/g, " ");
1795
+ }
1796
+ function SteeringMessages({ messages }) {
1797
+ if (messages.length === 0) return null;
1798
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { className: "turn-panel steering-panel", "aria-label": "Steering messages", children: messages.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "steering-message", children: [
1799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Send, { "aria-hidden": "true" }),
1800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "steering-message-title", children: [
1802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Steering" }),
1803
+ item.at && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { children: formatDateTime(item.at) })
1804
+ ] }),
1805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: item.message })
1806
+ ] })
1807
+ ] }, `${item.at ?? "steering"}-${index}`)) });
1808
+ }
1809
+ function TurnTitle({ index, timestamp }) {
1810
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "turn-title", children: [
1811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
1812
+ "#",
1813
+ index + 1
1814
+ ] }),
1815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { children: formatDateTime(timestamp) })
1816
+ ] });
1817
+ }
1818
+ function formatAgentTitle(turn, now) {
1819
+ const duration = formatTurnDuration(turn, now);
1820
+ return duration ? `AI agent, ${duration}` : "AI agent";
1821
+ }
1822
+ function taskStatusContent(task, platformStatus) {
1823
+ if (isTaskQueued(task)) {
1824
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `status-text ${statusToneClass(task.status)}`, children: "Queued" });
1825
+ }
1826
+ if (task.busy) {
1827
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkingIndicator, { label: platformStatus.available ? "Working" : "The platform is down. Wait until it is back up", animate: platformStatus.available });
1828
+ }
1829
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `status-text ${statusToneClass(task.status)}`, children: taskStatusLabel(task.status) });
1830
+ }
1831
+ function formatCompletionTime(turn) {
1832
+ const completedAt = turn.completedAt ?? turn.trace?.completedAt;
1833
+ return completedAt ? formatDateTime(completedAt) : "In progress";
1834
+ }
1835
+ function formatTurnDuration(turn, now = Date.now()) {
1836
+ const completedAt = turn.completedAt ?? turn.trace?.completedAt;
1837
+ if (!completedAt && !isTurnWorking(turn)) return "";
1838
+ const startedAt = turn.startedAt ?? turn.trace?.startedAt ?? turn.createdAt;
1839
+ const elapsedMs = (completedAt ? new Date(completedAt).getTime() : now) - new Date(startedAt).getTime();
1840
+ if (!Number.isFinite(elapsedMs) || elapsedMs < 0) return "";
1841
+ const totalSeconds = Math.max(1, Math.round(elapsedMs / 1e3));
1842
+ const hours = Math.floor(totalSeconds / 3600);
1843
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1844
+ const seconds = totalSeconds % 60;
1845
+ if (hours > 0) return `${hours}h ${minutes}m`;
1846
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
1847
+ return `${seconds}s`;
1848
+ }
1849
+ function formatDateTime(value) {
1850
+ if (!value) return "";
1851
+ const date = new Date(value);
1852
+ return Number.isFinite(date.getTime()) ? date.toLocaleString() : "";
1853
+ }
1854
+ function isTurnQueued(turn) {
1855
+ return turn.status.trim().toLocaleLowerCase() === "queued";
1856
+ }
1857
+ function isTurnWorking(turn) {
1858
+ const normalized = turn.status.trim().toLocaleLowerCase();
1859
+ return normalized === "running" || normalized === "working" || isTurnQueued(turn);
1860
+ }
1861
+ function isTaskBusy(task) {
1862
+ const normalized = task.status.trim().toLocaleLowerCase();
1863
+ return task.busy || normalized === "running" || normalized === "working" || isTaskQueued(task);
1864
+ }
1865
+ function isTaskQueued(task) {
1866
+ return task.status.trim().toLocaleLowerCase() === "queued";
1867
+ }
1868
+ function capitalize(value) {
1869
+ return value ? `${value.charAt(0).toLocaleUpperCase()}${value.slice(1)}` : value;
1870
+ }
1871
+ function taskStatusLabel(status) {
1872
+ const normalized = status.trim().toLocaleLowerCase();
1873
+ if (normalized === "waiting_for_user_continuation") return "Waiting for user continuation";
1874
+ if (normalized === "waiting_for_external_continuation") return "Waiting for external continuation";
1875
+ if (normalized === "closed") return "Closed";
1876
+ if (normalized === "working" || normalized === "running" || normalized === "in_progress") return "Working";
1877
+ return capitalize(status.replace(/[_-]+/g, " "));
1878
+ }
1879
+ function statusToneClass(status) {
1880
+ const normalized = status.trim().toLocaleLowerCase();
1881
+ if (normalized === "closed") return "status-success";
1882
+ if (normalized === "running" || normalized === "working" || normalized === "queued" || normalized === "in_progress") return "status-active";
1883
+ if (normalized === "failed" || normalized === "error" || normalized === "cancelled" || normalized === "canceled") return "status-danger";
1884
+ return "status-muted";
1885
+ }
1886
+ function WorkingIndicator({ label = "Working", animate = true }) {
1887
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "working-indicator", role: "status", "aria-label": label, children: [
1888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label }),
1889
+ animate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "working-dots", "aria-hidden": "true", children: [
1890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1891
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
1893
+ ] })
1894
+ ] });
1895
+ }
1896
+ function InlineSpinner() {
1897
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-spinner", "aria-hidden": "true" });
1898
+ }
1899
+ function LoadingState({ label }) {
1900
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "loading-state", role: "status", "aria-live": "polite", children: [
1901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineSpinner, {}),
1902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })
1903
+ ] });
1904
+ }
1905
+ function TracePanel({
1906
+ turn,
1907
+ turnIndex,
1908
+ loading,
1909
+ onCollapse
1910
+ }) {
1911
+ const isWorking = isTurnWorking(turn);
1912
+ const [follow, setFollow] = (0, import_react2.useState)(isWorking);
1913
+ const [filters, setFilters] = (0, import_react2.useState)(["narration"]);
1914
+ const scrollRef = (0, import_react2.useRef)(null);
1915
+ const events = turn.trace?.events ?? [];
1916
+ const items = (0, import_react2.useMemo)(() => buildTraceItems(events), [events]);
1917
+ const contextPressure = (0, import_react2.useMemo)(() => latestContextPressure(events), [events]);
1918
+ const visibleItems = (0, import_react2.useMemo)(() => items.filter((item) => traceItemMatchesFilter(item, filters)), [filters, items]);
1919
+ (0, import_react2.useEffect)(() => {
1920
+ if (follow && scrollRef.current) {
1921
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
1922
+ }
1923
+ }, [follow, visibleItems.length, events.length]);
1924
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-shell", children: [
1925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-toolbar", children: [
1926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-toolbar-summary", children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1928
+ "Request #",
1929
+ turnIndex + 1,
1930
+ " trace - ",
1931
+ events.length,
1932
+ " events"
1933
+ ] }),
1934
+ contextPressure && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `context-pressure context-pressure-${contextPressure.tone}`, title: contextPressure.label, children: [
1935
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1936
+ "Context ",
1937
+ contextPressure.percent,
1938
+ "%"
1939
+ ] }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "context-pressure-meter", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "context-pressure-fill", style: { width: `${Math.min(contextPressure.percent, 100)}%` } }) })
1941
+ ] })
1942
+ ] }),
1943
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-toolbar-actions", children: [
1944
+ isWorking && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1945
+ "button",
1946
+ {
1947
+ className: `trace-toggle-button${follow ? " active" : ""}`,
1948
+ type: "button",
1949
+ onClick: () => setFollow((current) => !current),
1950
+ "aria-pressed": follow,
1951
+ children: "Follow"
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "trace-collapse-button", type: "button", onClick: onCollapse, "aria-label": "Close trace", title: "Close trace", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "aria-hidden": "true" }) })
1955
+ ] })
1956
+ ] }),
1957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-filter-bar", "aria-label": "Trace filters", children: traceFilterOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1958
+ "button",
1959
+ {
1960
+ className: `trace-filter-button${filters.includes(option.value) ? " active" : ""}`,
1961
+ type: "button",
1962
+ onClick: () => setFilters((current) => current.includes(option.value) ? current.filter((item) => item !== option.value) : [...current, option.value]),
1963
+ "aria-pressed": filters.includes(option.value),
1964
+ children: option.label
1965
+ },
1966
+ option.value
1967
+ )) }),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-list", ref: scrollRef, children: [
1969
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingState, { label: "Loading trace" }),
1970
+ !loading && items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "No trace events yet" }),
1971
+ !loading && items.length > 0 && visibleItems.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "No events match this filter" }),
1972
+ visibleItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceItemView, { item }, `${item.kind}-${item.at}-${index}`))
1973
+ ] })
1974
+ ] });
1975
+ }
1976
+ var traceFilterOptions = [
1977
+ { value: "narration", label: "Assistant Remarks" },
1978
+ { value: "coding", label: "Coding" },
1979
+ { value: "shell", label: "Shell Commands" },
1980
+ { value: "tokens", label: "Token Usage" },
1981
+ { value: "status", label: "Status" },
1982
+ { value: "errors", label: "Errors" },
1983
+ { value: "raw", label: "Raw" }
1984
+ ];
1985
+ function TraceItemView({ item }) {
1986
+ const label = item.kind === "output" ? item.stream ?? item.kind : item.kind === "raw" ? item.label : "title" in item ? item.title : item.kind;
1987
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `trace-event ${item.kind}`, children: [
1988
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-event-header", children: [
1989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatDateTime(item.at) || "Unknown time" }),
1990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: label })
1991
+ ] }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceItemBody, { item })
1993
+ ] });
1994
+ }
1995
+ function TraceItemBody({ item }) {
1996
+ if (item.kind === "assistant" || item.kind === "result") {
1997
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormattedText, { text: item.text });
1998
+ }
1999
+ if (item.kind === "command") {
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-body", children: [
2001
+ item.status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "trace-meta", children: item.status }),
2002
+ item.command && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "trace-command", children: item.command }),
2003
+ item.output && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: item.output }),
2004
+ item.raw && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RawDetails, { text: item.raw })
2005
+ ] });
2006
+ }
2007
+ if (item.kind === "tool") {
2008
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-body", children: [
2009
+ item.status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "trace-meta", children: item.status }),
2010
+ item.input && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: item.input }),
2011
+ item.output && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: item.output }),
2012
+ item.raw && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RawDetails, { text: item.raw })
2013
+ ] });
2014
+ }
2015
+ if (item.kind === "file") {
2016
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-body", children: [
2017
+ item.path && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "trace-meta", children: item.path }),
2018
+ item.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: item.text }),
2019
+ item.raw && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RawDetails, { text: item.raw })
2020
+ ] });
2021
+ }
2022
+ if (item.kind === "status" || item.kind === "error" || item.kind === "tokens") {
2023
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-body", children: [
2024
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceTextOrJson, { text: item.text }),
2025
+ item.raw && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RawDetails, { text: item.raw })
2026
+ ] });
2027
+ }
2028
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceTextOrJson, { text: item.text });
2029
+ }
2030
+ function RawDetails({ text }) {
2031
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "raw-details", children: [
2032
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "Raw event" }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceTextOrJson, { text })
2034
+ ] });
2035
+ }
2036
+ function TraceTextOrJson({ text }) {
2037
+ const json = parseTraceJson(text);
2038
+ if (json !== void 0) {
2039
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonTree, { value: json });
2040
+ }
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: text });
2042
+ }
2043
+ function JsonTree({ value }) {
2044
+ if (Array.isArray(value)) {
2045
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "json-tree json-array", children: value.map((entry, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
2046
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "json-key", children: index }),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonTree, { value: entry })
2048
+ ] }, index)) });
2049
+ }
2050
+ if (isJsonObject(value)) {
2051
+ const entries = Object.entries(value);
2052
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "json-tree", children: entries.map(([key, entry]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
2053
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "json-key", children: key }),
2054
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonTree, { value: entry })
2055
+ ] }, key)) });
2056
+ }
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `json-value ${value === null ? "null" : typeof value}`, children: formatJsonScalar(value) });
2058
+ }
2059
+ function parseTraceJson(text) {
2060
+ const trimmed = text.trim();
2061
+ if (!trimmed || !trimmed.startsWith("{") && !trimmed.startsWith("[")) return void 0;
2062
+ try {
2063
+ return normalizeTraceJson(JSON.parse(trimmed));
2064
+ } catch {
2065
+ return void 0;
2066
+ }
2067
+ }
2068
+ function normalizeTraceJson(value, depth = 0) {
2069
+ if (depth > 6) return value;
2070
+ if (typeof value === "string") {
2071
+ const trimmed = value.trim();
2072
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
2073
+ try {
2074
+ return normalizeTraceJson(JSON.parse(trimmed), depth + 1);
2075
+ } catch {
2076
+ return value;
2077
+ }
2078
+ }
2079
+ return value;
2080
+ }
2081
+ if (Array.isArray(value)) {
2082
+ return value.map((entry) => normalizeTraceJson(entry, depth + 1));
2083
+ }
2084
+ if (isJsonObject(value)) {
2085
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, normalizeTraceJson(entry, depth + 1)]));
2086
+ }
2087
+ return value;
2088
+ }
2089
+ function formatJsonScalar(value) {
2090
+ if (value === null) return "null";
2091
+ if (typeof value === "string") return value;
2092
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2093
+ return "";
2094
+ }
2095
+ function isJsonObject(value) {
2096
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2097
+ }
2098
+ function FormattedText({ text }) {
2099
+ const blocks = parseMarkdownBlocks(text);
2100
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "formatted-text", children: blocks.map((block, index) => renderMarkdownBlock(block, index)) });
2101
+ }
2102
+ function parseMarkdownBlocks(text) {
2103
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
2104
+ const blocks = [];
2105
+ let index = 0;
2106
+ while (index < lines.length) {
2107
+ const line = lines[index];
2108
+ if (line.trim() === "") {
2109
+ index += 1;
2110
+ continue;
2111
+ }
2112
+ const fence = line.match(/^```([a-zA-Z0-9_-]+)?\s*$/);
2113
+ if (fence) {
2114
+ const codeLines = [];
2115
+ index += 1;
2116
+ while (index < lines.length && !/^```\s*$/.test(lines[index])) {
2117
+ codeLines.push(lines[index]);
2118
+ index += 1;
2119
+ }
2120
+ if (index < lines.length) index += 1;
2121
+ blocks.push({ type: "code", text: codeLines.join("\n"), language: fence[1] });
2122
+ continue;
2123
+ }
2124
+ const heading = line.match(/^(#{1,6})\s+(.+)$/);
2125
+ if (heading) {
2126
+ blocks.push({ type: "heading", depth: heading[1].length, text: heading[2].trim() });
2127
+ index += 1;
2128
+ continue;
2129
+ }
2130
+ const unordered = line.match(/^\s*[-*]\s+(.+)$/);
2131
+ if (unordered) {
2132
+ const items = [];
2133
+ while (index < lines.length) {
2134
+ const item = lines[index].match(/^\s*[-*]\s+(.+)$/);
2135
+ if (!item) break;
2136
+ items.push(item[1].trim());
2137
+ index += 1;
2138
+ }
2139
+ blocks.push({ type: "unordered-list", items });
2140
+ continue;
2141
+ }
2142
+ const ordered = line.match(/^\s*\d+[.)]\s+(.+)$/);
2143
+ if (ordered) {
2144
+ const items = [];
2145
+ while (index < lines.length) {
2146
+ const item = lines[index].match(/^\s*\d+[.)]\s+(.+)$/);
2147
+ if (!item) break;
2148
+ items.push(item[1].trim());
2149
+ index += 1;
2150
+ }
2151
+ blocks.push({ type: "ordered-list", items });
2152
+ continue;
2153
+ }
2154
+ const paragraphLines = [];
2155
+ while (index < lines.length) {
2156
+ const current = lines[index];
2157
+ if (current.trim() === "" || /^```([a-zA-Z0-9_-]+)?\s*$/.test(current) || /^(#{1,6})\s+(.+)$/.test(current) || /^\s*[-*]\s+(.+)$/.test(current) || /^\s*\d+[.)]\s+(.+)$/.test(current)) {
2158
+ break;
2159
+ }
2160
+ paragraphLines.push(current);
2161
+ index += 1;
2162
+ }
2163
+ blocks.push({ type: "paragraph", text: paragraphLines.join("\n").trimEnd() });
2164
+ }
2165
+ return blocks.length === 0 ? [{ type: "paragraph", text }] : blocks;
2166
+ }
2167
+ function renderMarkdownBlock(block, index) {
2168
+ switch (block.type) {
2169
+ case "code":
2170
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "code-block", "data-language": block.language, children: block.text }, index);
2171
+ case "heading":
2172
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownHeading, { depth: block.depth, text: block.text }, index);
2173
+ case "unordered-list":
2174
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: block.items.map((item, itemIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: renderInlineMarkdown(item, `u-${index}-${itemIndex}`) }, itemIndex)) }, index);
2175
+ case "ordered-list":
2176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { children: block.items.map((item, itemIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: renderInlineMarkdown(item, `o-${index}-${itemIndex}`) }, itemIndex)) }, index);
2177
+ case "paragraph":
2178
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: renderInlineMarkdown(block.text, `p-${index}`) }, index);
2179
+ }
2180
+ }
2181
+ function MarkdownHeading({ depth, text }) {
2182
+ const children = renderInlineMarkdown(text, `h-${depth}-${text}`);
2183
+ if (depth <= 2) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children });
2184
+ if (depth === 3) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", { children });
2185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h6", { children });
2186
+ }
2187
+ function renderInlineMarkdown(text, keyPrefix) {
2188
+ const nodes = [];
2189
+ let index = 0;
2190
+ let nodeIndex = 0;
2191
+ const pushText = (value) => {
2192
+ if (value) nodes.push(value);
2193
+ };
2194
+ while (index < text.length) {
2195
+ if (text[index] === "`") {
2196
+ const end = text.indexOf("`", index + 1);
2197
+ if (end > index + 1) {
2198
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: text.slice(index + 1, end) }, `${keyPrefix}-code-${nodeIndex++}`));
2199
+ index = end + 1;
2200
+ continue;
2201
+ }
2202
+ }
2203
+ if (text.startsWith("**", index)) {
2204
+ const end = text.indexOf("**", index + 2);
2205
+ if (end > index + 2) {
2206
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: renderInlineMarkdown(text.slice(index + 2, end), `${keyPrefix}-strong-${nodeIndex}`) }, `${keyPrefix}-strong-${nodeIndex++}`));
2207
+ index = end + 2;
2208
+ continue;
2209
+ }
2210
+ }
2211
+ if (text[index] === "*") {
2212
+ const end = text.indexOf("*", index + 1);
2213
+ if (end > index + 1) {
2214
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { children: renderInlineMarkdown(text.slice(index + 1, end), `${keyPrefix}-em-${nodeIndex}`) }, `${keyPrefix}-em-${nodeIndex++}`));
2215
+ index = end + 1;
2216
+ continue;
2217
+ }
2218
+ }
2219
+ if (text[index] === "[") {
2220
+ const textEnd = text.indexOf("]", index + 1);
2221
+ const hrefStart = textEnd >= 0 ? text.indexOf("(", textEnd) : -1;
2222
+ const hrefEnd = hrefStart === textEnd + 1 ? text.indexOf(")", hrefStart + 1) : -1;
2223
+ if (textEnd > index + 1 && hrefEnd > hrefStart + 1) {
2224
+ const label = text.slice(index + 1, textEnd);
2225
+ const href = text.slice(hrefStart + 1, hrefEnd).trim();
2226
+ nodes.push(isSafeMarkdownHref(href) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href, rel: "noreferrer", target: "_blank", children: renderInlineMarkdown(label, `${keyPrefix}-link-${nodeIndex}`) }, `${keyPrefix}-link-${nodeIndex++}`) : label);
2227
+ index = hrefEnd + 1;
2228
+ continue;
2229
+ }
2230
+ }
2231
+ const nextSpecial = nextMarkdownSpecialIndex(text, index + 1);
2232
+ pushText(text.slice(index, nextSpecial));
2233
+ index = nextSpecial;
2234
+ }
2235
+ return nodes;
2236
+ }
2237
+ function nextMarkdownSpecialIndex(text, start) {
2238
+ const indexes = ["`", "*", "["].map((character) => text.indexOf(character, start)).filter((value) => value >= 0);
2239
+ return indexes.length > 0 ? Math.min(...indexes) : text.length;
2240
+ }
2241
+ function isSafeMarkdownHref(href) {
2242
+ return /^(https?:|mailto:|tel:|\/|#)/i.test(href);
2243
+ }
2244
+
2245
+ // src/updates.ts
2246
+ var ClientUpdateService = class {
2247
+ constructor(baseUrl, accessToken = () => null, eventSourceFactory = (url) => new EventSource(url)) {
2248
+ this.baseUrl = baseUrl;
2249
+ this.accessToken = accessToken;
2250
+ this.eventSourceFactory = eventSourceFactory;
2251
+ this.defaultCursor = 0;
2252
+ this.streamCursor = 0;
2253
+ this.source = null;
2254
+ this.subscriptions = /* @__PURE__ */ new Map();
2255
+ this.nextSubscriptionId = 1;
2256
+ this.signature = "";
2257
+ }
2258
+ setCursor(cursor) {
2259
+ this.defaultCursor = Math.max(this.defaultCursor, validCursor(cursor));
2260
+ }
2261
+ subscribe(subscription) {
2262
+ const id = this.nextSubscriptionId++;
2263
+ this.subscriptions.set(id, {
2264
+ subscription,
2265
+ cursor: validCursor(subscription.after ?? this.defaultCursor)
2266
+ });
2267
+ this.reconnectIfNeeded();
2268
+ return () => {
2269
+ this.subscriptions.delete(id);
2270
+ this.reconnectIfNeeded();
2271
+ };
2272
+ }
2273
+ reconnectIfNeeded() {
2274
+ const nextSignature = this.buildSignature();
2275
+ if (nextSignature === this.signature) {
2276
+ return;
2277
+ }
2278
+ this.source?.close();
2279
+ this.source = null;
2280
+ this.signature = nextSignature;
2281
+ if (this.subscriptions.size === 0) {
2282
+ return;
2283
+ }
2284
+ const { filterType, filter } = combinedServerFilter(this.activeSubscriptions());
2285
+ const after = this.minimumCursor();
2286
+ this.streamCursor = after;
2287
+ const url = this.withAccessToken(`${this.baseUrl}/api/updates?after=${after}&filterType=${encodeURIComponent(filterType)}&filter=${encodeURIComponent(JSON.stringify(filter))}`);
2288
+ const source = this.eventSourceFactory(url);
2289
+ this.source = source;
2290
+ source.onmessage = (event) => {
2291
+ if (this.source !== source) {
2292
+ return;
2293
+ }
2294
+ const result = parseUpdateStreamItem(event.data);
2295
+ if (!result.ok) {
2296
+ this.resyncFromServer();
2297
+ return;
2298
+ }
2299
+ const item = result.item;
2300
+ const high = item.kind === "skip" ? item.to : item.update?.updateNumber;
2301
+ const low = item.kind === "skip" ? item.from : item.update?.updateNumber;
2302
+ if (high === void 0 || high <= this.streamCursor) {
2303
+ return;
2304
+ }
2305
+ if (low === void 0 || low > this.streamCursor + 1) {
2306
+ this.resyncFromServer();
2307
+ return;
2308
+ }
2309
+ this.dispatchItem(item);
2310
+ this.streamCursor = advanceCursor(this.streamCursor, item);
2311
+ };
2312
+ source.addEventListener("ephemeral", () => {
2313
+ });
2314
+ source.onerror = () => {
2315
+ if (this.source !== source) {
2316
+ return;
2317
+ }
2318
+ };
2319
+ }
2320
+ // Tear down the live connection and re-establish it from the lowest active cursor. Used to recover
2321
+ // from stream corruption (malformed event, out-of-order gap) without surfacing an error: the server
2322
+ // replays from the requested cursor and the onmessage replay-dedup ignores anything already seen.
2323
+ resyncFromServer() {
2324
+ this.signature = "";
2325
+ this.reconnectIfNeeded();
2326
+ }
2327
+ buildSignature() {
2328
+ if (this.subscriptions.size === 0) return "";
2329
+ const { filterType, filter } = combinedServerFilter(this.activeSubscriptions());
2330
+ return `${this.minimumCursor()}:${filterType}:${JSON.stringify(filter)}`;
2331
+ }
2332
+ withAccessToken(url) {
2333
+ const token = this.accessToken();
2334
+ if (!token) return url;
2335
+ const separator = url.includes("?") ? "&" : "?";
2336
+ return `${url}${separator}accessToken=${encodeURIComponent(token)}`;
2337
+ }
2338
+ dispatchItem(item) {
2339
+ if (item.kind === "skip") {
2340
+ this.dispatchSkip(item);
2341
+ return;
2342
+ }
2343
+ const update = item.update;
2344
+ if (!update) {
2345
+ return;
2346
+ }
2347
+ for (const active of this.subscriptions.values()) {
2348
+ const updateNumber = update.updateNumber;
2349
+ if (updateNumber <= active.cursor) {
2350
+ continue;
2351
+ }
2352
+ if (subscriptionMatches(active.subscription, item)) {
2353
+ this.emitSkip(active, active.cursor + 1, updateNumber - 1);
2354
+ active.subscription.onItem(item);
2355
+ active.cursor = updateNumber;
2356
+ } else {
2357
+ this.emitSkip(active, active.cursor + 1, updateNumber);
2358
+ }
2359
+ }
2360
+ }
2361
+ dispatchSkip(item) {
2362
+ if (item.to === void 0) {
2363
+ return;
2364
+ }
2365
+ for (const active of this.subscriptions.values()) {
2366
+ const from = active.cursor + 1;
2367
+ const to = item.to;
2368
+ if (to < from) {
2369
+ continue;
2370
+ }
2371
+ active.subscription.onItem({ kind: "skip", from, to });
2372
+ active.cursor = Math.max(active.cursor, to);
2373
+ }
2374
+ }
2375
+ emitSkip(active, from, to) {
2376
+ if (to < from) {
2377
+ return;
2378
+ }
2379
+ active.subscription.onItem({ kind: "skip", from, to });
2380
+ active.cursor = Math.max(active.cursor, to);
2381
+ }
2382
+ activeSubscriptions() {
2383
+ return [...this.subscriptions.values()].map((active) => active.subscription);
2384
+ }
2385
+ minimumCursor() {
2386
+ let cursor = null;
2387
+ for (const active of this.subscriptions.values()) {
2388
+ cursor = cursor === null ? active.cursor : Math.min(cursor, active.cursor);
2389
+ }
2390
+ return cursor ?? this.defaultCursor;
2391
+ }
2392
+ };
2393
+ function combinedServerFilter(subscriptions) {
2394
+ const filterTypes = new Set(subscriptions.map((subscription) => subscription.filterType));
2395
+ return {
2396
+ filterType: filterTypes.size === 1 ? subscriptions[0]?.filterType ?? "all" : "all",
2397
+ filter: {
2398
+ any: subscriptions.map((subscription) => normalizeFilter(subscription.filter))
2399
+ }
2400
+ };
2401
+ }
2402
+ function subscriptionMatches(subscription, item) {
2403
+ const update = item.update;
2404
+ if (!update) return false;
2405
+ return filterValueMatches(subscription.filter.objectIds, update.objectId) && filterValueMatches(subscription.filter.objectTypes, update.objectType) && filterValueMatches(subscription.filter.updateTypes, update.type);
2406
+ }
2407
+ function parseUpdateStreamItem(data) {
2408
+ let parsed;
2409
+ try {
2410
+ parsed = JSON.parse(data);
2411
+ } catch {
2412
+ return { ok: false, message: "The update stream sent an invalid event." };
2413
+ }
2414
+ if (!isRecord(parsed)) {
2415
+ return { ok: false, message: "The update stream sent an invalid event." };
2416
+ }
2417
+ if (parsed.kind === "skip") {
2418
+ return {
2419
+ ok: true,
2420
+ item: {
2421
+ kind: "skip",
2422
+ from: numberValue2(parsed.from),
2423
+ to: numberValue2(parsed.to)
2424
+ }
2425
+ };
2426
+ }
2427
+ if (parsed.kind !== "update" || !isRecord(parsed.update)) {
2428
+ return { ok: false, message: "The update stream sent an unsupported event." };
2429
+ }
2430
+ const updateNumber = numberValue2(parsed.update.updateNumber);
2431
+ const type = stringValue2(parsed.update.type);
2432
+ const objectType = stringValue2(parsed.update.objectType);
2433
+ if (updateNumber === void 0 || !type || !objectType) {
2434
+ return { ok: false, message: "The update stream sent an incomplete update." };
2435
+ }
2436
+ return {
2437
+ ok: true,
2438
+ item: {
2439
+ kind: "update",
2440
+ update: {
2441
+ updateNumber,
2442
+ type,
2443
+ objectType,
2444
+ objectId: stringValue2(parsed.update.objectId),
2445
+ payload: isRecord(parsed.update.payload) ? parsed.update.payload : void 0
2446
+ }
2447
+ }
2448
+ };
2449
+ }
2450
+ function advanceCursor(current, item) {
2451
+ return Math.max(current, item.kind === "skip" ? item.to ?? current : item.update?.updateNumber ?? current);
2452
+ }
2453
+ function normalizeFilter(filter) {
2454
+ return {
2455
+ objectIds: filterValues(filter.objectIds),
2456
+ objectTypes: filterValues(filter.objectTypes),
2457
+ updateTypes: filterValues(filter.updateTypes)
2458
+ };
2459
+ }
2460
+ function filterValueMatches(filter, value) {
2461
+ const values = filterValues(filter);
2462
+ return values.length === 0 || Boolean(value && values.some((item) => item.toLocaleLowerCase() === value.toLocaleLowerCase()));
2463
+ }
2464
+ function filterValues(value) {
2465
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
2466
+ }
2467
+ function isRecord(value) {
2468
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2469
+ }
2470
+ function stringValue2(value) {
2471
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2472
+ }
2473
+ function numberValue2(value) {
2474
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2475
+ }
2476
+ function validCursor(value) {
2477
+ return Number.isFinite(value) && value > 0 ? value : 0;
2478
+ }
2479
+
2480
+ // src/telemetry.ts
2481
+ var ClientTelemetryService = class {
2482
+ constructor(options = {}) {
2483
+ this.queue = [];
2484
+ this.flushTimer = null;
2485
+ this.flushing = false;
2486
+ this.baseUrl = options.baseUrl ?? "";
2487
+ this.accessToken = options.accessToken ?? (() => null);
2488
+ this.fetchImpl = options.fetch ?? fetch;
2489
+ this.endpoint = options.endpoint ?? "/api/client-telemetry";
2490
+ this.flushIntervalMs = options.flushIntervalMs ?? 5e3;
2491
+ this.maxBatchSize = options.maxBatchSize ?? 20;
2492
+ this.maxQueueSize = options.maxQueueSize ?? 200;
2493
+ if (typeof window !== "undefined") {
2494
+ window.addEventListener("pagehide", () => this.flushWithBeacon());
2495
+ }
2496
+ }
2497
+ record(event) {
2498
+ if (this.queue.length >= this.maxQueueSize) {
2499
+ this.queue.shift();
2500
+ }
2501
+ this.queue.push({
2502
+ ...event,
2503
+ observedAt: event.observedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2504
+ route: event.route ?? currentRoute()
2505
+ });
2506
+ this.scheduleFlush();
2507
+ }
2508
+ async flush() {
2509
+ if (this.flushing || this.queue.length === 0) {
2510
+ return;
2511
+ }
2512
+ this.flushing = true;
2513
+ if (this.flushTimer !== null) {
2514
+ clearTimeout(this.flushTimer);
2515
+ this.flushTimer = null;
2516
+ }
2517
+ const batch = this.queue.splice(0, this.maxBatchSize);
2518
+ try {
2519
+ const response = await this.fetchImpl(this.url(), {
2520
+ method: "POST",
2521
+ headers: this.headers({ "content-type": "application/json" }),
2522
+ body: JSON.stringify({ events: batch })
2523
+ });
2524
+ if (!response.ok) {
2525
+ this.requeue(batch);
2526
+ }
2527
+ } catch {
2528
+ this.requeue(batch);
2529
+ } finally {
2530
+ this.flushing = false;
2531
+ if (this.queue.length > 0) {
2532
+ this.scheduleFlush();
2533
+ }
2534
+ }
2535
+ }
2536
+ scheduleFlush() {
2537
+ if (this.flushTimer !== null) {
2538
+ return;
2539
+ }
2540
+ this.flushTimer = setTimeout(() => {
2541
+ void this.flush();
2542
+ }, this.flushIntervalMs);
2543
+ }
2544
+ flushWithBeacon() {
2545
+ if (this.queue.length === 0 || typeof navigator === "undefined" || !navigator.sendBeacon) {
2546
+ return;
2547
+ }
2548
+ const batch = this.queue.splice(0, this.maxBatchSize);
2549
+ const body = JSON.stringify({ events: batch });
2550
+ if (!navigator.sendBeacon(this.beaconUrl(), new Blob([body], { type: "application/json" }))) {
2551
+ this.requeue(batch);
2552
+ }
2553
+ }
2554
+ requeue(events) {
2555
+ this.queue.unshift(...events);
2556
+ if (this.queue.length > this.maxQueueSize) {
2557
+ this.queue.splice(0, this.queue.length - this.maxQueueSize);
2558
+ }
2559
+ }
2560
+ headers(init = {}) {
2561
+ const headers = new Headers(init);
2562
+ const token = this.accessToken();
2563
+ if (token) headers.set("authorization", `Bearer ${token}`);
2564
+ return headers;
2565
+ }
2566
+ url() {
2567
+ return `${this.baseUrl}${this.endpoint}`;
2568
+ }
2569
+ beaconUrl() {
2570
+ const token = this.accessToken();
2571
+ if (!token) return this.url();
2572
+ const separator = this.endpoint.includes("?") ? "&" : "?";
2573
+ return `${this.baseUrl}${this.endpoint}${separator}accessToken=${encodeURIComponent(token)}`;
2574
+ }
2575
+ };
2576
+ function currentRoute() {
2577
+ if (typeof window === "undefined") {
2578
+ return void 0;
2579
+ }
2580
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
2581
+ }
2582
+
2583
+ // src/TaskListPage.tsx
2584
+ var import_react3 = __require("react");
2585
+ var import_jsx_runtime2 = __require("react/jsx-runtime");
2586
+
2587
+ // src/ai-plane.ts
2588
+ var aiPlaneEndpoints = {
2589
+ tasks: "/api/tasks",
2590
+ task: (taskId) => `/api/tasks/${encodeURIComponent(taskId)}`,
2591
+ taskMessages: (taskId) => `/api/tasks/${encodeURIComponent(taskId)}/messages`,
2592
+ taskInterrupt: (taskId) => `/api/tasks/${encodeURIComponent(taskId)}/interrupt`,
2593
+ taskSteer: (taskId) => `/api/tasks/${encodeURIComponent(taskId)}/steer`,
2594
+ taskAttachments: "/api/task-attachments",
2595
+ taskState: "/api/state/tasks",
2596
+ taskStateDetail: (taskId) => `/api/state/tasks/${encodeURIComponent(taskId)}`,
2597
+ taskTrace: (taskId, turnId) => `/api/state/tasks/${encodeURIComponent(taskId)}/turns/${encodeURIComponent(turnId)}/trace`,
2598
+ updates: "/api/updates",
2599
+ clientTelemetry: "/api/client-telemetry",
2600
+ platformStatus: "/api/platform-status"
2601
+ };
2602
+ var AiPlaneClient = class {
2603
+ constructor(options = {}) {
2604
+ this.baseUrl = options.baseUrl ?? "";
2605
+ this.accessToken = options.accessToken ?? (() => null);
2606
+ this.fetchImpl = options.fetch ?? fetch;
2607
+ this.updates = new ClientUpdateService(this.baseUrl, this.accessToken);
2608
+ this.telemetry = new ClientTelemetryService({ baseUrl: this.baseUrl, accessToken: this.accessToken, fetch: this.fetchImpl });
2609
+ }
2610
+ async loadTaskState() {
2611
+ return this.getJson(aiPlaneEndpoints.taskState);
2612
+ }
2613
+ async loadTask(taskId) {
2614
+ return this.getJson(aiPlaneEndpoints.taskStateDetail(taskId));
2615
+ }
2616
+ async loadTrace(taskId, turnId) {
2617
+ return this.getJson(aiPlaneEndpoints.taskTrace(taskId, turnId));
2618
+ }
2619
+ async loadPlatformStatus() {
2620
+ return this.getJson(aiPlaneEndpoints.platformStatus);
2621
+ }
2622
+ async createTask(request) {
2623
+ return this.postJson(aiPlaneEndpoints.tasks, request);
2624
+ }
2625
+ async continueTask(taskId, request) {
2626
+ return this.postJson(aiPlaneEndpoints.taskMessages(taskId), request);
2627
+ }
2628
+ async interruptTask(taskId, message) {
2629
+ return this.postJson(aiPlaneEndpoints.taskInterrupt(taskId), { message });
2630
+ }
2631
+ async steerTask(taskId, message) {
2632
+ return this.postJson(aiPlaneEndpoints.taskSteer(taskId), { message });
2633
+ }
2634
+ async uploadAttachment(file, onProgress) {
2635
+ return new Promise((resolve, reject) => {
2636
+ const request = new XMLHttpRequest();
2637
+ const body = new FormData();
2638
+ body.append("file", file);
2639
+ request.open("POST", this.url(aiPlaneEndpoints.taskAttachments));
2640
+ const token = this.accessToken();
2641
+ if (token) request.setRequestHeader("authorization", `Bearer ${token}`);
2642
+ request.upload.onprogress = (event) => {
2643
+ if (event.lengthComputable) onProgress?.(Math.round(event.loaded / event.total * 100));
2644
+ };
2645
+ request.onload = () => {
2646
+ if (request.status >= 200 && request.status < 300) {
2647
+ resolve(JSON.parse(request.responseText));
2648
+ } else {
2649
+ reject(new Error(request.responseText || `Upload failed: ${request.status}`));
2650
+ }
2651
+ };
2652
+ request.onerror = () => reject(new Error("Upload failed."));
2653
+ request.send(body);
2654
+ });
2655
+ }
2656
+ async getJson(path) {
2657
+ const response = await this.fetchImpl(this.url(path), { headers: this.headers() });
2658
+ return this.readJson(response);
2659
+ }
2660
+ async postJson(path, body) {
2661
+ const response = await this.fetchImpl(this.url(path), {
2662
+ method: "POST",
2663
+ headers: this.headers({ "content-type": "application/json" }),
2664
+ body: JSON.stringify(body)
2665
+ });
2666
+ return this.readJson(response);
2667
+ }
2668
+ async readJson(response) {
2669
+ const json = await response.json().catch(() => null);
2670
+ if (!response.ok) {
2671
+ throw new Error(errorMessage(json) ?? `Request failed: ${response.status}`);
2672
+ }
2673
+ return json;
2674
+ }
2675
+ headers(init = {}) {
2676
+ const headers = new Headers(init);
2677
+ const token = this.accessToken();
2678
+ if (token) headers.set("authorization", `Bearer ${token}`);
2679
+ return headers;
2680
+ }
2681
+ url(path) {
2682
+ return `${this.baseUrl}${path}`;
2683
+ }
2684
+ };
2685
+ function errorMessage(value) {
2686
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
2687
+ const error = value.error;
2688
+ if (typeof error === "string") return error;
2689
+ if (typeof error === "object" && error !== null && !Array.isArray(error)) {
2690
+ const message = error.message;
2691
+ if (typeof message === "string") return message;
2692
+ }
2693
+ return null;
2694
+ }
2695
+
2696
+ // browser-entry.tsx
2697
+ globalThis.ExoscientCpLib = {
2698
+ AiPlaneClient,
2699
+ ClientUpdateService,
2700
+ TaskDetail,
2701
+ WorkingIndicator,
2702
+ capitalize,
2703
+ formatDateTime,
2704
+ formatTaskLabel,
2705
+ isTaskBusy,
2706
+ statusToneClass,
2707
+ taskTitleText
2708
+ };
2709
+ })();