@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.
- package/README.md +14 -0
- package/dist/AppControlPanel.d.ts +77 -0
- package/dist/AppControlPanel.d.ts.map +1 -0
- package/dist/AppControlPanel.js +1625 -0
- package/dist/AppControlPanel.js.map +1 -0
- package/dist/ControlPanelShell.d.ts +39 -0
- package/dist/ControlPanelShell.d.ts.map +1 -0
- package/dist/ControlPanelShell.js +152 -0
- package/dist/ControlPanelShell.js.map +1 -0
- package/dist/ExoLauncherSimulator.d.ts +36 -0
- package/dist/ExoLauncherSimulator.d.ts.map +1 -0
- package/dist/ExoLauncherSimulator.js +253 -0
- package/dist/ExoLauncherSimulator.js.map +1 -0
- package/dist/TaskDetail.d.ts +180 -0
- package/dist/TaskDetail.d.ts.map +1 -0
- package/dist/TaskDetail.js +889 -0
- package/dist/TaskDetail.js.map +1 -0
- package/dist/TaskListPage.d.ts +28 -0
- package/dist/TaskListPage.d.ts.map +1 -0
- package/dist/TaskListPage.js +16 -0
- package/dist/TaskListPage.js.map +1 -0
- package/dist/TaskWorkspace.d.ts +62 -0
- package/dist/TaskWorkspace.d.ts.map +1 -0
- package/dist/TaskWorkspace.js +592 -0
- package/dist/TaskWorkspace.js.map +1 -0
- package/dist/ai-plane.d.ts +75 -0
- package/dist/ai-plane.d.ts.map +1 -0
- package/dist/ai-plane.js +124 -0
- package/dist/ai-plane.js.map +1 -0
- package/dist/browser-icons.d.ts +25 -0
- package/dist/browser-icons.d.ts.map +1 -0
- package/dist/browser-icons.js +125 -0
- package/dist/browser-icons.js.map +1 -0
- package/dist/client-actions.d.ts +45 -0
- package/dist/client-actions.d.ts.map +1 -0
- package/dist/client-actions.js +48 -0
- package/dist/client-actions.js.map +1 -0
- package/dist/control-panel-shared.d.ts +58 -0
- package/dist/control-panel-shared.d.ts.map +1 -0
- package/dist/control-panel-shared.js +79 -0
- package/dist/control-panel-shared.js.map +1 -0
- package/dist/control-panel.css +4156 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/repository-workflow.d.ts +27 -0
- package/dist/repository-workflow.d.ts.map +1 -0
- package/dist/repository-workflow.js +24 -0
- package/dist/repository-workflow.js.map +1 -0
- package/dist/result.d.ts +6 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +77 -0
- package/dist/result.js.map +1 -0
- package/dist/task-consistency.d.ts +28 -0
- package/dist/task-consistency.d.ts.map +1 -0
- package/dist/task-consistency.js +25 -0
- package/dist/task-consistency.js.map +1 -0
- package/dist/task-detail.browser.js +2709 -0
- package/dist/task-detail.css +1601 -0
- package/dist/task-state.d.ts +11 -0
- package/dist/task-state.d.ts.map +1 -0
- package/dist/task-state.js +103 -0
- package/dist/task-state.js.map +1 -0
- package/dist/telemetry.d.ts +39 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +106 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/trace.d.ts +80 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +694 -0
- package/dist/trace.js.map +1 -0
- package/dist/updates.d.ts +72 -0
- package/dist/updates.d.ts.map +1 -0
- package/dist/updates.js +269 -0
- package/dist/updates.js.map +1 -0
- 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
|
+
})();
|