@agimon-ai/po-computer-use 0.0.0-alpha.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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/apiContracts.cjs +242 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +237 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensionService-Bp0q295i.cjs +504 -0
- package/dist/extensionService-Bp0q295i.cjs.map +1 -0
- package/dist/extensionService-DXvF7esO.mjs +434 -0
- package/dist/extensionService-DXvF7esO.mjs.map +1 -0
- package/dist/extensions/mcp-ui.mjs +47 -0
- package/dist/extensions/mcp-ui.mjs.map +1 -0
- package/dist/extensions/mcp.cjs +66 -0
- package/dist/extensions/mcp.cjs.map +1 -0
- package/dist/extensions/mcp.d.mts +5 -0
- package/dist/extensions/mcp.d.mts.map +1 -0
- package/dist/extensions/mcp.mjs +61 -0
- package/dist/extensions/mcp.mjs.map +1 -0
- package/dist/extensions/server.cjs +1394 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +1388 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +771 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +325 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +129 -0
- package/dist/index.mjs.map +1 -0
- package/docs/computer-scripts.md +72 -0
- package/llms.txt +12 -0
- package/package.json +147 -0
- package/src/prompts/po-use-computer-use/SKILL.md +34 -0
- package/src/types/computerUse.ts +132 -0
- package/src/types/computerUseApi.ts +83 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
import { apiResponse, createApiClient, defineActivityGroup, defineApiRoutes, defineChannelFile, defineFill, defineMinorModeFile, defineSessionStore, defineSettingsSection, defineToolRenderer, defineWebPlugin, sessionApiAddress, toolResultTextLines } from "@agimon-ai/po-core/web";
|
|
2
|
+
import { Button, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, MessageLines, collapseLines, toolTone } from "@agimon-ai/po-web-components";
|
|
3
|
+
import { useStore } from "@tanstack/react-store";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { sealedTransport } from "@agimon-ai/po-web-security/browser";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/extensions/(frontend)/_lib/computerUseSettings.ts
|
|
8
|
+
const computerUseSettingsSection = {
|
|
9
|
+
id: "computer-use",
|
|
10
|
+
desktopOnly: true,
|
|
11
|
+
label: "computer use",
|
|
12
|
+
detail: "control whether Po Desktop may expose session-scoped computer control",
|
|
13
|
+
order: 45,
|
|
14
|
+
fields: [{
|
|
15
|
+
id: "enabled",
|
|
16
|
+
label: "enable computer use",
|
|
17
|
+
kind: "toggle",
|
|
18
|
+
keyPath: ["computerUse", "enabled"],
|
|
19
|
+
detail: "Makes the computer-use minor mode available. Each control session still requires native confirmation."
|
|
20
|
+
}]
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/extensions/(frontend)/setting/computer-use.web.ts
|
|
24
|
+
var computer_use_web_default$3 = defineSettingsSection({ settingsSections: [computerUseSettingsSection] }.settingsSections[0]);
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/extensions/workspaces/(frontend)/setting/computer-use.web.ts
|
|
27
|
+
var computer_use_web_default$2 = defineSettingsSection({ settingsSections: [computerUseSettingsSection] }.settingsSections[0]);
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/types/computerUseApi.ts
|
|
30
|
+
const computerUseChannelType = "computer_use_state";
|
|
31
|
+
const COMPUTER_USE_MODE_ID = "computer-use";
|
|
32
|
+
const COMPUTER_USE_STATUS_KEY = "doom-computer-use";
|
|
33
|
+
const COMPUTER_USE_MODE_STATUS_KEY = "doom-computer-use-mode";
|
|
34
|
+
const COMPUTER_USE_DEFAULT_DURATION_MS = 3e5;
|
|
35
|
+
/**
|
|
36
|
+
* Every path this API answers on, below its mount.
|
|
37
|
+
*
|
|
38
|
+
* The typed route table in `./apiRoutes` is built from this, the dispatcher
|
|
39
|
+
* reads that table, and the hub channel addresses the broker with these same
|
|
40
|
+
* strings, so a route cannot move on one side alone.
|
|
41
|
+
*/
|
|
42
|
+
const COMPUTER_USE_ROUTES = {
|
|
43
|
+
activate: "/activate",
|
|
44
|
+
artifact: "/artifact",
|
|
45
|
+
agentState: "/agent/state",
|
|
46
|
+
agentObserve: "/agent/observe",
|
|
47
|
+
agentAction: "/agent/action",
|
|
48
|
+
agentStop: "/agent/stop",
|
|
49
|
+
hubState: "/hub/state",
|
|
50
|
+
hubActivation: "/hub/activation",
|
|
51
|
+
hubAuthorization: "/hub/authorization",
|
|
52
|
+
hubNext: "/hub/next",
|
|
53
|
+
hubComplete: "/hub/complete",
|
|
54
|
+
hubStop: "/hub/stop"
|
|
55
|
+
};
|
|
56
|
+
const computerUse = defineSessionStore({
|
|
57
|
+
state: {
|
|
58
|
+
sessionId: "",
|
|
59
|
+
revision: 0,
|
|
60
|
+
wake: 0,
|
|
61
|
+
phase: "inactive"
|
|
62
|
+
},
|
|
63
|
+
targets: []
|
|
64
|
+
});
|
|
65
|
+
function isRecord(value) {
|
|
66
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
67
|
+
}
|
|
68
|
+
const computerUseChannel = computerUse.channel({
|
|
69
|
+
channel: computerUseChannelType,
|
|
70
|
+
parse(input) {
|
|
71
|
+
if (!isRecord(input) || !isRecord(input.state) || !Array.isArray(input.targets)) return null;
|
|
72
|
+
if (typeof input.state.phase !== "string" || typeof input.state.revision !== "number") return null;
|
|
73
|
+
return input;
|
|
74
|
+
},
|
|
75
|
+
reduce: (_current, payload) => payload
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/types/apiRoutes.ts
|
|
79
|
+
/**
|
|
80
|
+
* This package's routes, as data.
|
|
81
|
+
*
|
|
82
|
+
* No scope and no base path: the build reads both off
|
|
83
|
+
* `src/extensions/workspaces/sessions/(backend)/api/computer-use/`, so the
|
|
84
|
+
* mount is stated once, by the folder that creates it.
|
|
85
|
+
*
|
|
86
|
+
* The paths come from `COMPUTER_USE_ROUTES` rather than being written out
|
|
87
|
+
* again. That table already addressed the broker from the hub channel and from
|
|
88
|
+
* the broker's own dispatcher, and a second copy here is exactly the
|
|
89
|
+
* disagreement this file exists to prevent. The dispatcher now reads this
|
|
90
|
+
* table, so the page, the broker and the hub all resolve one set of strings.
|
|
91
|
+
*
|
|
92
|
+
* Activation, status, cancellation and recording reads are reachable from a browser. The `/agent/` and `/hub/` routes
|
|
93
|
+
* each demand a host-issued bearer token and answer 404 without one; they are
|
|
94
|
+
* declared here because they are this API's surface, not because a page calls
|
|
95
|
+
* them.
|
|
96
|
+
*/
|
|
97
|
+
var apiRoutes_default = defineApiRoutes({
|
|
98
|
+
artifact: {
|
|
99
|
+
method: "GET",
|
|
100
|
+
path: COMPUTER_USE_ROUTES.artifact,
|
|
101
|
+
query: ["artifactId", "offset"],
|
|
102
|
+
raw: true
|
|
103
|
+
},
|
|
104
|
+
activationState: {
|
|
105
|
+
method: "GET",
|
|
106
|
+
path: COMPUTER_USE_ROUTES.activate,
|
|
107
|
+
response: apiResponse()
|
|
108
|
+
},
|
|
109
|
+
cancelActivation: {
|
|
110
|
+
method: "DELETE",
|
|
111
|
+
path: COMPUTER_USE_ROUTES.activate,
|
|
112
|
+
response: apiResponse()
|
|
113
|
+
},
|
|
114
|
+
activate: {
|
|
115
|
+
method: "POST",
|
|
116
|
+
path: COMPUTER_USE_ROUTES.activate,
|
|
117
|
+
response: apiResponse()
|
|
118
|
+
},
|
|
119
|
+
agentState: {
|
|
120
|
+
method: "GET",
|
|
121
|
+
path: COMPUTER_USE_ROUTES.agentState,
|
|
122
|
+
response: apiResponse()
|
|
123
|
+
},
|
|
124
|
+
/** Answers whatever the admitted Desktop provider returned, which is runtime-defined JSON. */
|
|
125
|
+
agentObserve: {
|
|
126
|
+
method: "POST",
|
|
127
|
+
path: COMPUTER_USE_ROUTES.agentObserve,
|
|
128
|
+
response: apiResponse()
|
|
129
|
+
},
|
|
130
|
+
agentAction: {
|
|
131
|
+
method: "POST",
|
|
132
|
+
path: COMPUTER_USE_ROUTES.agentAction,
|
|
133
|
+
response: apiResponse()
|
|
134
|
+
},
|
|
135
|
+
agentStop: {
|
|
136
|
+
method: "POST",
|
|
137
|
+
path: COMPUTER_USE_ROUTES.agentStop,
|
|
138
|
+
response: apiResponse()
|
|
139
|
+
},
|
|
140
|
+
hubState: {
|
|
141
|
+
method: "GET",
|
|
142
|
+
path: COMPUTER_USE_ROUTES.hubState,
|
|
143
|
+
response: apiResponse()
|
|
144
|
+
},
|
|
145
|
+
/** Null until a reader has confirmed one; reading it moves the session to `activating`. */
|
|
146
|
+
hubActivation: {
|
|
147
|
+
method: "GET",
|
|
148
|
+
path: COMPUTER_USE_ROUTES.hubActivation,
|
|
149
|
+
response: apiResponse()
|
|
150
|
+
},
|
|
151
|
+
hubAuthorization: {
|
|
152
|
+
method: "GET",
|
|
153
|
+
path: COMPUTER_USE_ROUTES.hubAuthorization,
|
|
154
|
+
response: apiResponse()
|
|
155
|
+
},
|
|
156
|
+
hubNext: {
|
|
157
|
+
method: "GET",
|
|
158
|
+
path: COMPUTER_USE_ROUTES.hubNext,
|
|
159
|
+
response: apiResponse()
|
|
160
|
+
},
|
|
161
|
+
hubComplete: {
|
|
162
|
+
method: "POST",
|
|
163
|
+
path: COMPUTER_USE_ROUTES.hubComplete,
|
|
164
|
+
response: apiResponse()
|
|
165
|
+
},
|
|
166
|
+
hubStop: {
|
|
167
|
+
method: "POST",
|
|
168
|
+
path: COMPUTER_USE_ROUTES.hubStop,
|
|
169
|
+
response: apiResponse()
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
const api = createApiClient(apiRoutes_default, {
|
|
173
|
+
scopes: ["session"],
|
|
174
|
+
basePath: "computer-use",
|
|
175
|
+
transport: (input, init) => sealedTransport.fetch(input, init),
|
|
176
|
+
sessionAddress: sessionApiAddress
|
|
177
|
+
});
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/computerUseRecording.ts
|
|
180
|
+
const MAX_RECORDING_BYTES = 134217728;
|
|
181
|
+
const CHUNK_BYTES = 1048576;
|
|
182
|
+
/** Owns only a bounded sealed download and its disposable browser URL. */
|
|
183
|
+
async function loadComputerUseRecording(sessionId, artifactId, sizeBytes, signal) {
|
|
184
|
+
if (!Number.isSafeInteger(sizeBytes) || sizeBytes < 1 || sizeBytes > MAX_RECORDING_BYTES) throw new Error("Recording exceeds the permitted size.");
|
|
185
|
+
const parts = [];
|
|
186
|
+
for (let offset = 0; offset < sizeBytes; offset += CHUNK_BYTES) {
|
|
187
|
+
signal.throwIfAborted();
|
|
188
|
+
const result = await api.session(sessionId).artifact({
|
|
189
|
+
query: {
|
|
190
|
+
artifactId,
|
|
191
|
+
offset
|
|
192
|
+
},
|
|
193
|
+
signal
|
|
194
|
+
});
|
|
195
|
+
if (!result.ok || result.status !== 206) throw new Error("The recording is unavailable or access was revoked.");
|
|
196
|
+
const end = Math.min(offset + CHUNK_BYTES, sizeBytes) - 1;
|
|
197
|
+
if (result.response.headers.get("content-range") !== `bytes ${offset}-${end}/${sizeBytes}`) throw new Error("The recording response is invalid.");
|
|
198
|
+
const bytes = await result.response.arrayBuffer();
|
|
199
|
+
signal.throwIfAborted();
|
|
200
|
+
if (bytes.byteLength !== end - offset + 1) throw new Error("The recording response is incomplete.");
|
|
201
|
+
parts.push(bytes);
|
|
202
|
+
}
|
|
203
|
+
signal.throwIfAborted();
|
|
204
|
+
const url = URL.createObjectURL(new Blob(parts, { type: "video/mp4" }));
|
|
205
|
+
let disposed = false;
|
|
206
|
+
return {
|
|
207
|
+
url,
|
|
208
|
+
dispose() {
|
|
209
|
+
if (!disposed) {
|
|
210
|
+
disposed = true;
|
|
211
|
+
URL.revokeObjectURL(url);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/ComputerUsePanel.tsx
|
|
218
|
+
function text(value, fallback) {
|
|
219
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : fallback;
|
|
220
|
+
}
|
|
221
|
+
function ComputerUsePanel({ sessionId, sendSessionFrame }) {
|
|
222
|
+
const session = useStore(computerUse.store, (state) => computerUse.select(state, sessionId));
|
|
223
|
+
const [selected, setSelected] = useState(0);
|
|
224
|
+
const [confirmationTarget, setConfirmationTarget] = useState();
|
|
225
|
+
const [error, setError] = useState();
|
|
226
|
+
const [recordingUrl, setRecordingUrl] = useState();
|
|
227
|
+
const [recordingAccessDenied, setRecordingAccessDenied] = useState(false);
|
|
228
|
+
const send = (payload) => {
|
|
229
|
+
if (sessionId !== null) sendSessionFrame(sessionId, {
|
|
230
|
+
type: computerUseChannelType,
|
|
231
|
+
payload
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (sessionId === null) return;
|
|
236
|
+
sendSessionFrame(sessionId, {
|
|
237
|
+
type: computerUseChannelType,
|
|
238
|
+
payload: { action: "status" }
|
|
239
|
+
});
|
|
240
|
+
sendSessionFrame(sessionId, {
|
|
241
|
+
type: computerUseChannelType,
|
|
242
|
+
payload: { action: "targets" }
|
|
243
|
+
});
|
|
244
|
+
}, [sendSessionFrame, sessionId]);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (sessionId === null) return;
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
let timer;
|
|
249
|
+
const poll = async () => {
|
|
250
|
+
try {
|
|
251
|
+
const result = await api.session(sessionId).activationState({ signal: controller.signal });
|
|
252
|
+
if (controller.signal.aborted) return;
|
|
253
|
+
setRecordingAccessDenied(!result.ok);
|
|
254
|
+
if (result.ok) computerUse.update(sessionId, (current) => result.data.revision >= current.state.revision ? {
|
|
255
|
+
...current,
|
|
256
|
+
state: result.data
|
|
257
|
+
} : current);
|
|
258
|
+
} catch {
|
|
259
|
+
if (!controller.signal.aborted) setRecordingAccessDenied(true);
|
|
260
|
+
}
|
|
261
|
+
if (!controller.signal.aborted) timer = setTimeout(() => void poll(), 2e3);
|
|
262
|
+
};
|
|
263
|
+
poll();
|
|
264
|
+
return () => {
|
|
265
|
+
controller.abort();
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
};
|
|
268
|
+
}, [sessionId]);
|
|
269
|
+
const artifactId = session.state.artifact?.artifactId;
|
|
270
|
+
const artifactSize = session.state.artifact?.sizeBytes;
|
|
271
|
+
const artifactStatus = session.state.artifact?.status;
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
setRecordingUrl(void 0);
|
|
274
|
+
if (recordingAccessDenied || sessionId === null || artifactId === void 0 || artifactSize === void 0 || artifactStatus !== "ready") return;
|
|
275
|
+
const controller = new AbortController();
|
|
276
|
+
let asset;
|
|
277
|
+
loadComputerUseRecording(sessionId, artifactId, artifactSize, controller.signal).then((loaded) => {
|
|
278
|
+
if (controller.signal.aborted) loaded.dispose();
|
|
279
|
+
else {
|
|
280
|
+
asset = loaded;
|
|
281
|
+
setRecordingUrl(loaded.url);
|
|
282
|
+
}
|
|
283
|
+
}).catch(() => {
|
|
284
|
+
if (!controller.signal.aborted) setError("The recording is unavailable or access was revoked.");
|
|
285
|
+
});
|
|
286
|
+
return () => {
|
|
287
|
+
controller.abort();
|
|
288
|
+
asset?.dispose();
|
|
289
|
+
};
|
|
290
|
+
}, [
|
|
291
|
+
sessionId,
|
|
292
|
+
artifactId,
|
|
293
|
+
artifactSize,
|
|
294
|
+
artifactStatus,
|
|
295
|
+
recordingAccessDenied
|
|
296
|
+
]);
|
|
297
|
+
if (sessionId === null) return /* @__PURE__ */ jsx("p", {
|
|
298
|
+
className: "px-1 text-sm text-doom-dim",
|
|
299
|
+
children: "Select a session."
|
|
300
|
+
});
|
|
301
|
+
const state = session.state;
|
|
302
|
+
const target = session.targets[selected];
|
|
303
|
+
const artifact = state.artifact;
|
|
304
|
+
const confirm = async (chosenTarget = confirmationTarget) => {
|
|
305
|
+
setError(void 0);
|
|
306
|
+
try {
|
|
307
|
+
const result = await api.session(sessionId).activate({ body: {
|
|
308
|
+
...chosenTarget === void 0 ? {} : { target: chosenTarget },
|
|
309
|
+
durationMs: COMPUTER_USE_DEFAULT_DURATION_MS
|
|
310
|
+
} });
|
|
311
|
+
if (!result.ok) {
|
|
312
|
+
if (result.status === 0) setError("The session is unreachable.");
|
|
313
|
+
else setError(result.error === "" ? `Activation failed with HTTP ${result.status}.` : result.error);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
setConfirmationTarget(void 0);
|
|
317
|
+
computerUse.update(sessionId, (current) => ({
|
|
318
|
+
...current,
|
|
319
|
+
state: result.data
|
|
320
|
+
}));
|
|
321
|
+
send({ action: "status" });
|
|
322
|
+
} catch {
|
|
323
|
+
setError("The activation request is unavailable.");
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
327
|
+
"data-testid": "computer-use-panel",
|
|
328
|
+
className: "flex flex-col gap-2 text-base text-doom-text",
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsxs("header", {
|
|
331
|
+
className: "flex items-center justify-between gap-2",
|
|
332
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
333
|
+
className: "text-doom-dim",
|
|
334
|
+
children: state.phase.replaceAll("_", " ")
|
|
335
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
336
|
+
size: "sm",
|
|
337
|
+
type: "button",
|
|
338
|
+
className: "min-h-7 rounded border border-doom-border bg-doom-panel px-2 py-1",
|
|
339
|
+
onClick: () => send({ action: "targets" }),
|
|
340
|
+
children: "Refresh targets"
|
|
341
|
+
})]
|
|
342
|
+
}),
|
|
343
|
+
session.busy ? /* @__PURE__ */ jsx("p", {
|
|
344
|
+
className: "rounded border border-doom-yellow px-2 py-1",
|
|
345
|
+
children: "Computer use is busy in another session"
|
|
346
|
+
}) : null,
|
|
347
|
+
state.failure ? /* @__PURE__ */ jsx("p", {
|
|
348
|
+
className: "text-doom-red",
|
|
349
|
+
children: state.failure.message
|
|
350
|
+
}) : null,
|
|
351
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
352
|
+
className: "text-doom-red",
|
|
353
|
+
children: error
|
|
354
|
+
}) : null,
|
|
355
|
+
(state.phase === "inactive" || state.phase === "failed") && confirmationTarget === void 0 ? /* @__PURE__ */ jsxs("div", {
|
|
356
|
+
className: "flex flex-col gap-2",
|
|
357
|
+
children: [
|
|
358
|
+
/* @__PURE__ */ jsx("label", {
|
|
359
|
+
htmlFor: "computer-use-target",
|
|
360
|
+
children: "Application window"
|
|
361
|
+
}),
|
|
362
|
+
/* @__PURE__ */ jsx("select", {
|
|
363
|
+
id: "computer-use-target",
|
|
364
|
+
value: selected,
|
|
365
|
+
onChange: (event) => setSelected(Number(event.target.value)),
|
|
366
|
+
className: "min-h-7 rounded border border-doom-border bg-doom-panel px-2 py-1",
|
|
367
|
+
children: session.targets.map((item, index) => /* @__PURE__ */ jsx("option", {
|
|
368
|
+
value: index,
|
|
369
|
+
children: text(item.applicationName, text(item.windowTitle, text(item.windowId, "Window")))
|
|
370
|
+
}, text(item.windowId, String(index))))
|
|
371
|
+
}),
|
|
372
|
+
/* @__PURE__ */ jsx(Button, {
|
|
373
|
+
size: "sm",
|
|
374
|
+
type: "button",
|
|
375
|
+
disabled: Boolean(session.busy),
|
|
376
|
+
variant: "primary",
|
|
377
|
+
onClick: () => {
|
|
378
|
+
if (target === void 0) confirm();
|
|
379
|
+
else setConfirmationTarget(Object.freeze({ ...target }));
|
|
380
|
+
},
|
|
381
|
+
children: target === void 0 ? "Choose window on Mac" : "Request activation"
|
|
382
|
+
})
|
|
383
|
+
]
|
|
384
|
+
}) : null,
|
|
385
|
+
confirmationTarget !== void 0 ? /* @__PURE__ */ jsxs("div", {
|
|
386
|
+
className: "rounded border border-doom-blue p-3",
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ jsx("strong", { children: "Confirm computer control" }),
|
|
389
|
+
/* @__PURE__ */ jsxs("p", {
|
|
390
|
+
className: "my-2 text-doom-text",
|
|
391
|
+
children: [
|
|
392
|
+
text(confirmationTarget.applicationName, "Application"),
|
|
393
|
+
":",
|
|
394
|
+
" ",
|
|
395
|
+
text(confirmationTarget.windowTitle, text(confirmationTarget.windowId, "Window"))
|
|
396
|
+
]
|
|
397
|
+
}),
|
|
398
|
+
/* @__PURE__ */ jsx("p", {
|
|
399
|
+
className: "my-2 text-doom-dim",
|
|
400
|
+
children: "Confirm a 5 minute session for this exact application window."
|
|
401
|
+
}),
|
|
402
|
+
/* @__PURE__ */ jsxs("div", {
|
|
403
|
+
className: "flex gap-2",
|
|
404
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
405
|
+
size: "sm",
|
|
406
|
+
type: "button",
|
|
407
|
+
variant: "primary",
|
|
408
|
+
onClick: () => void confirm(),
|
|
409
|
+
children: "Confirm"
|
|
410
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
411
|
+
size: "sm",
|
|
412
|
+
type: "button",
|
|
413
|
+
className: "min-h-7 rounded border border-doom-border bg-doom-panel px-2 py-1",
|
|
414
|
+
onClick: () => setConfirmationTarget(void 0),
|
|
415
|
+
children: "Cancel"
|
|
416
|
+
})]
|
|
417
|
+
})
|
|
418
|
+
]
|
|
419
|
+
}) : null,
|
|
420
|
+
state.phase === "awaiting_confirmation" || state.phase === "activating" ? /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: "Desktop activation is pending." }), /* @__PURE__ */ jsx(Button, {
|
|
421
|
+
size: "sm",
|
|
422
|
+
type: "button",
|
|
423
|
+
onClick: () => {
|
|
424
|
+
(async () => {
|
|
425
|
+
try {
|
|
426
|
+
const result = await api.session(sessionId).cancelActivation();
|
|
427
|
+
if (!result.ok) setError("The activation could not be cancelled.");
|
|
428
|
+
else computerUse.update(sessionId, (current) => ({
|
|
429
|
+
...current,
|
|
430
|
+
state: result.data
|
|
431
|
+
}));
|
|
432
|
+
} catch {
|
|
433
|
+
setError("The activation could not be cancelled.");
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
436
|
+
},
|
|
437
|
+
children: "Cancel activation"
|
|
438
|
+
})] }) : null,
|
|
439
|
+
state.phase === "active" || state.phase === "stopping" ? /* @__PURE__ */ jsx(Button, {
|
|
440
|
+
size: "sm",
|
|
441
|
+
type: "button",
|
|
442
|
+
disabled: state.phase === "stopping",
|
|
443
|
+
className: "min-h-7 rounded border border-doom-border bg-doom-panel px-2 py-1",
|
|
444
|
+
onClick: () => send({ action: "stop" }),
|
|
445
|
+
children: "Stop computer use"
|
|
446
|
+
}) : null,
|
|
447
|
+
artifact ? /* @__PURE__ */ jsxs("div", {
|
|
448
|
+
"data-testid": "computer-use-artifact",
|
|
449
|
+
className: "rounded bg-doom-panel p-2",
|
|
450
|
+
children: [
|
|
451
|
+
/* @__PURE__ */ jsx("strong", { children: artifact.status === "ready" ? "Completed recording" : "Recording unavailable" }),
|
|
452
|
+
/* @__PURE__ */ jsxs("p", { children: ["ID: ", artifact.artifactId] }),
|
|
453
|
+
/* @__PURE__ */ jsxs("p", { children: ["Status: ", artifact.status] }),
|
|
454
|
+
artifact.failure ? /* @__PURE__ */ jsx("p", {
|
|
455
|
+
className: "text-doom-red",
|
|
456
|
+
children: artifact.failure.message
|
|
457
|
+
}) : null,
|
|
458
|
+
artifact.completedAt ? /* @__PURE__ */ jsxs("p", { children: ["Completed: ", artifact.completedAt] }) : null,
|
|
459
|
+
artifact.actionCount !== void 0 ? /* @__PURE__ */ jsxs("p", { children: ["Actions: ", artifact.actionCount] }) : null,
|
|
460
|
+
artifact.status === "ready" && artifact.sizeBytes && !recordingUrl ? /* @__PURE__ */ jsx("p", { children: "Loading recording through the secure channel." }) : null,
|
|
461
|
+
artifact.status === "ready" && recordingUrl ? /* @__PURE__ */ jsx("video", {
|
|
462
|
+
className: "mt-2 w-full",
|
|
463
|
+
controls: true,
|
|
464
|
+
preload: "metadata",
|
|
465
|
+
src: recordingUrl,
|
|
466
|
+
children: /* @__PURE__ */ jsx("track", { kind: "captions" })
|
|
467
|
+
}) : null,
|
|
468
|
+
artifact.status === "ready" && recordingUrl ? /* @__PURE__ */ jsx("a", {
|
|
469
|
+
className: "mt-2 inline-block text-doom-blue",
|
|
470
|
+
href: recordingUrl,
|
|
471
|
+
download: "Po-recording.mp4",
|
|
472
|
+
children: "Download recording"
|
|
473
|
+
}) : null
|
|
474
|
+
]
|
|
475
|
+
}) : null
|
|
476
|
+
]
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/computerActionToolRender.ts
|
|
481
|
+
const computerActionToolName = "computer_action";
|
|
482
|
+
function computerActionCallSummary(args) {
|
|
483
|
+
return [typeof args.kind === "string" ? args.kind : "", typeof args.elementRef === "string" ? args.elementRef : ""].filter(Boolean).join(" ");
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
487
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
488
|
+
*/
|
|
489
|
+
function computerActionResultLines(result) {
|
|
490
|
+
if (result === null) return [];
|
|
491
|
+
return toolResultTextLines(result.content).map((text) => ({
|
|
492
|
+
text,
|
|
493
|
+
tone: "dim"
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/ComputerActionToolCard.tsx
|
|
498
|
+
/**
|
|
499
|
+
* The computer_action tool's timeline item: Perform one constrained semantic action in the authorized application window. The shell owns
|
|
500
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
501
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
502
|
+
* hides any so the toggle appears only when it reveals something.
|
|
503
|
+
*/
|
|
504
|
+
function ComputerActionToolCard({ toolName, args, result, running, isError }) {
|
|
505
|
+
const lines = computerActionResultLines(result);
|
|
506
|
+
const collapsed = collapseLines(lines, 8, false);
|
|
507
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
508
|
+
tone: toolTone({
|
|
509
|
+
running,
|
|
510
|
+
isError
|
|
511
|
+
}),
|
|
512
|
+
expandable: collapsed.hidden > 0,
|
|
513
|
+
children: ({ expanded }) => {
|
|
514
|
+
const view = expanded ? collapseLines(lines, 8, true) : collapsed;
|
|
515
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
516
|
+
title: toolName,
|
|
517
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
518
|
+
"data-testid": `tool-call-${toolName}`,
|
|
519
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
520
|
+
children: computerActionCallSummary(args)
|
|
521
|
+
})
|
|
522
|
+
}), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
523
|
+
"data-testid": `tool-result-${toolName}`,
|
|
524
|
+
className: "flex flex-col gap-1",
|
|
525
|
+
children: [
|
|
526
|
+
view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
|
|
527
|
+
view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
|
|
528
|
+
expands: true,
|
|
529
|
+
children: [view.hidden, " more line(s)"]
|
|
530
|
+
}) : null,
|
|
531
|
+
running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
532
|
+
tone: "running",
|
|
533
|
+
children: "running"
|
|
534
|
+
}) : null,
|
|
535
|
+
isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
536
|
+
tone: "error",
|
|
537
|
+
children: "failed"
|
|
538
|
+
}) : null
|
|
539
|
+
]
|
|
540
|
+
}) : null] });
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/computerExecToolRender.ts
|
|
546
|
+
const computerExecToolName = "computer_exec";
|
|
547
|
+
function computerExecCallSummary(args) {
|
|
548
|
+
return typeof args.scriptPath === "string" ? args.scriptPath : "";
|
|
549
|
+
}
|
|
550
|
+
function computerExecResultLines(result) {
|
|
551
|
+
if (result === null) return [];
|
|
552
|
+
return toolResultTextLines(result.content).map((text) => ({
|
|
553
|
+
text,
|
|
554
|
+
tone: "dim"
|
|
555
|
+
}));
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/ComputerExecToolCard.tsx
|
|
559
|
+
function ComputerExecToolCard({ toolName, args, result, running, isError }) {
|
|
560
|
+
const lines = computerExecResultLines(result);
|
|
561
|
+
const collapsed = collapseLines(lines, 8, false);
|
|
562
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
563
|
+
tone: toolTone({
|
|
564
|
+
running,
|
|
565
|
+
isError
|
|
566
|
+
}),
|
|
567
|
+
expandable: collapsed.hidden > 0,
|
|
568
|
+
children: ({ expanded }) => {
|
|
569
|
+
const view = expanded ? collapseLines(lines, 8, true) : collapsed;
|
|
570
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
571
|
+
title: toolName,
|
|
572
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
573
|
+
"data-testid": `tool-call-${toolName}`,
|
|
574
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
575
|
+
children: computerExecCallSummary(args)
|
|
576
|
+
})
|
|
577
|
+
}), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
578
|
+
"data-testid": `tool-result-${toolName}`,
|
|
579
|
+
className: "flex flex-col gap-1",
|
|
580
|
+
children: [
|
|
581
|
+
view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
|
|
582
|
+
view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
|
|
583
|
+
expands: true,
|
|
584
|
+
children: [view.hidden, " more line(s)"]
|
|
585
|
+
}) : null,
|
|
586
|
+
running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
587
|
+
tone: "running",
|
|
588
|
+
children: "running"
|
|
589
|
+
}) : null,
|
|
590
|
+
isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
591
|
+
tone: "error",
|
|
592
|
+
children: "failed"
|
|
593
|
+
}) : null
|
|
594
|
+
]
|
|
595
|
+
}) : null] });
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/computerStateToolRender.ts
|
|
601
|
+
const computerStateToolName = "computer_state";
|
|
602
|
+
function computerStateCallSummary(_args) {
|
|
603
|
+
return "authorized window";
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
607
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
608
|
+
*/
|
|
609
|
+
function computerStateResultLines(result) {
|
|
610
|
+
if (result === null) return [];
|
|
611
|
+
return toolResultTextLines(result.content).map((text) => ({
|
|
612
|
+
text,
|
|
613
|
+
tone: "dim"
|
|
614
|
+
}));
|
|
615
|
+
}
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/ComputerStateToolCard.tsx
|
|
618
|
+
/**
|
|
619
|
+
* The computer_state tool's timeline item: Observe the authorized application window and semantic accessibility state. The shell owns
|
|
620
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
621
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
622
|
+
* hides any so the toggle appears only when it reveals something.
|
|
623
|
+
*/
|
|
624
|
+
function ComputerStateToolCard({ toolName, args, result, running, isError }) {
|
|
625
|
+
const lines = computerStateResultLines(result);
|
|
626
|
+
const collapsed = collapseLines(lines, 8, false);
|
|
627
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
628
|
+
tone: toolTone({
|
|
629
|
+
running,
|
|
630
|
+
isError
|
|
631
|
+
}),
|
|
632
|
+
expandable: collapsed.hidden > 0,
|
|
633
|
+
children: ({ expanded }) => {
|
|
634
|
+
const view = expanded ? collapseLines(lines, 8, true) : collapsed;
|
|
635
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
636
|
+
title: toolName,
|
|
637
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
638
|
+
"data-testid": `tool-call-${toolName}`,
|
|
639
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
640
|
+
children: computerStateCallSummary(args)
|
|
641
|
+
})
|
|
642
|
+
}), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
643
|
+
"data-testid": `tool-result-${toolName}`,
|
|
644
|
+
className: "flex flex-col gap-1",
|
|
645
|
+
children: [
|
|
646
|
+
view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
|
|
647
|
+
view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
|
|
648
|
+
expands: true,
|
|
649
|
+
children: [view.hidden, " more line(s)"]
|
|
650
|
+
}) : null,
|
|
651
|
+
running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
652
|
+
tone: "running",
|
|
653
|
+
children: "running"
|
|
654
|
+
}) : null,
|
|
655
|
+
isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
656
|
+
tone: "error",
|
|
657
|
+
children: "failed"
|
|
658
|
+
}) : null
|
|
659
|
+
]
|
|
660
|
+
}) : null] });
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_legacy/index.ts
|
|
666
|
+
var _legacy_default = {
|
|
667
|
+
minorModes: [{
|
|
668
|
+
name: "computer use",
|
|
669
|
+
modeId: COMPUTER_USE_MODE_ID,
|
|
670
|
+
keys: "c e",
|
|
671
|
+
statusKey: COMPUTER_USE_MODE_STATUS_KEY,
|
|
672
|
+
hideWhenMissing: true,
|
|
673
|
+
desktopOnly: true,
|
|
674
|
+
order: 70
|
|
675
|
+
}],
|
|
676
|
+
activityGroups: [{
|
|
677
|
+
name: "computer-use",
|
|
678
|
+
keys: "c e",
|
|
679
|
+
order: 70,
|
|
680
|
+
statusKey: COMPUTER_USE_STATUS_KEY,
|
|
681
|
+
hideWhenEmpty: true,
|
|
682
|
+
desktopOnly: true,
|
|
683
|
+
marksBackgroundWork: false
|
|
684
|
+
}],
|
|
685
|
+
activitySections: [{
|
|
686
|
+
id: "computer-use",
|
|
687
|
+
component: ComputerUsePanel
|
|
688
|
+
}],
|
|
689
|
+
channels: [computerUseChannel],
|
|
690
|
+
toolRenderers: [
|
|
691
|
+
{
|
|
692
|
+
tools: [computerStateToolName],
|
|
693
|
+
message: ComputerStateToolCard
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
tools: [computerActionToolName],
|
|
697
|
+
message: ComputerActionToolCard
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
tools: [computerExecToolName],
|
|
701
|
+
message: ComputerExecToolCard
|
|
702
|
+
}
|
|
703
|
+
]
|
|
704
|
+
};
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/computer-use.web.ts
|
|
707
|
+
var computer_use_web_default$1 = defineActivityGroup(_legacy_default.activityGroups[0]);
|
|
708
|
+
//#endregion
|
|
709
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/computer-use.web.ts
|
|
710
|
+
var computer_use_web_default = defineChannelFile(_legacy_default.channels[0]);
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/ComputerUse.activity.computer-use.web.tsx
|
|
713
|
+
var ComputerUse_activity_computer_use_web_default = defineFill({
|
|
714
|
+
slot: "activity.computer-use",
|
|
715
|
+
..._legacy_default.activitySections[0]
|
|
716
|
+
});
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/extensions/workspaces/sessions/(frontend)/mode/computer-use/mode.web.ts
|
|
719
|
+
var mode_web_default = defineMinorModeFile(_legacy_default.minorModes[0]);
|
|
720
|
+
//#endregion
|
|
721
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/computer-action.web.tsx
|
|
722
|
+
var computer_action_web_default = defineToolRenderer(_legacy_default.toolRenderers[1]);
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/computer-exec.web.tsx
|
|
725
|
+
var computer_exec_web_default = defineToolRenderer(_legacy_default.toolRenderers[2]);
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/computer-state.web.tsx
|
|
728
|
+
var computer_state_web_default = defineToolRenderer(_legacy_default.toolRenderers[0]);
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region generated/web.ts
|
|
731
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
732
|
+
...identity,
|
|
733
|
+
...value
|
|
734
|
+
};
|
|
735
|
+
const webPlugin = defineWebPlugin({
|
|
736
|
+
id: "computer-use",
|
|
737
|
+
global: { get settingsSections() {
|
|
738
|
+
return [via({ id: "computer-use" }, computer_use_web_default$3)];
|
|
739
|
+
} },
|
|
740
|
+
workspace: { get settingsSections() {
|
|
741
|
+
return [via({ id: "computer-use" }, computer_use_web_default$2)];
|
|
742
|
+
} },
|
|
743
|
+
session: {
|
|
744
|
+
get activityGroups() {
|
|
745
|
+
return [via({ name: "computer-use" }, computer_use_web_default$1)];
|
|
746
|
+
},
|
|
747
|
+
get channels() {
|
|
748
|
+
return [via({ channel: "computer_use" }, computer_use_web_default)];
|
|
749
|
+
},
|
|
750
|
+
get fills() {
|
|
751
|
+
return [via({
|
|
752
|
+
slot: "activity.computer-use",
|
|
753
|
+
id: "computer-use"
|
|
754
|
+
}, ComputerUse_activity_computer_use_web_default)];
|
|
755
|
+
},
|
|
756
|
+
get minorModes() {
|
|
757
|
+
return [via({ name: "computer-use" }, mode_web_default)];
|
|
758
|
+
},
|
|
759
|
+
get toolRenderers() {
|
|
760
|
+
return [
|
|
761
|
+
via({ tools: ["computer_action"] }, computer_action_web_default),
|
|
762
|
+
via({ tools: ["computer_exec"] }, computer_exec_web_default),
|
|
763
|
+
via({ tools: ["computer_state"] }, computer_state_web_default)
|
|
764
|
+
];
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
//#endregion
|
|
769
|
+
export { webPlugin };
|
|
770
|
+
|
|
771
|
+
//# sourceMappingURL=web.mjs.map
|