@oharness/server 0.9.3 → 0.10.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/dist/i18n.js +354 -62
- package/dist/i18n.js.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/page/client/account.js +430 -23
- package/dist/page/client/account.js.map +1 -1
- package/dist/page/client/base.js +20 -1
- package/dist/page/client/base.js.map +1 -1
- package/dist/page/client/composer.js +693 -54
- package/dist/page/client/composer.js.map +1 -1
- package/dist/page/client/computer.d.ts +27 -0
- package/dist/page/client/computer.js +358 -0
- package/dist/page/client/computer.js.map +1 -0
- package/dist/page/client/dialogs.js +93 -6
- package/dist/page/client/dialogs.js.map +1 -1
- package/dist/page/client/events.js +94 -7
- package/dist/page/client/events.js.map +1 -1
- package/dist/page/client/files.js +86 -1
- package/dist/page/client/files.js.map +1 -1
- package/dist/page/client/index.js +4 -0
- package/dist/page/client/index.js.map +1 -1
- package/dist/page/client/panels.js +10 -6
- package/dist/page/client/panels.js.map +1 -1
- package/dist/page/client/routines.js +296 -41
- package/dist/page/client/routines.js.map +1 -1
- package/dist/page/client/shell.js +187 -51
- package/dist/page/client/shell.js.map +1 -1
- package/dist/page/client/state.js +273 -19
- package/dist/page/client/state.js.map +1 -1
- package/dist/page/client/tasks.d.ts +8 -0
- package/dist/page/client/tasks.js +293 -0
- package/dist/page/client/tasks.js.map +1 -0
- package/dist/page/client/transcript.js +137 -5
- package/dist/page/client/transcript.js.map +1 -1
- package/dist/page/client/usage.js +1 -11
- package/dist/page/client/usage.js.map +1 -1
- package/dist/page/style.js +568 -60
- package/dist/page/style.js.map +1 -1
- package/dist/page.js +90 -11
- package/dist/page.js.map +1 -1
- package/dist/prefs.d.ts +14 -0
- package/dist/prefs.js +8 -1
- package/dist/prefs.js.map +1 -1
- package/dist/remote-crypto.d.ts +78 -0
- package/dist/remote-crypto.js +216 -0
- package/dist/remote-crypto.js.map +1 -0
- package/dist/remote.d.ts +103 -0
- package/dist/remote.js +498 -0
- package/dist/remote.js.map +1 -0
- package/dist/server.d.ts +70 -0
- package/dist/server.js +1291 -49
- package/dist/server.js.map +1 -1
- package/dist/starters.d.ts +92 -0
- package/dist/starters.js +213 -0
- package/dist/starters.js.map +1 -0
- package/dist/turn-shots.d.ts +27 -0
- package/dist/turn-shots.js +71 -0
- package/dist/turn-shots.js.map +1 -0
- package/package.json +2 -2
|
@@ -22,55 +22,180 @@ async function newRoutine() {
|
|
|
22
22
|
input.setSelectionRange(input.value.length, input.value.length);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
function
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
if (run.costUsd) parts.push("$" + run.costUsd.toFixed(4));
|
|
33
|
-
return parts.join(" · ");
|
|
25
|
+
/** A span of time as someone reads it: 42s, 3m 05s, 1h 12m. */
|
|
26
|
+
function runDuration(ms) {
|
|
27
|
+
const sec = Math.max(0, Math.round(ms / 1000));
|
|
28
|
+
if (sec < 60) return sec + "s";
|
|
29
|
+
const min = Math.floor(sec / 60);
|
|
30
|
+
if (min < 60) return min + "m " + String(sec % 60).padStart(2, "0") + "s";
|
|
31
|
+
return Math.floor(min / 60) + "h " + (min % 60) + "m";
|
|
34
32
|
}
|
|
35
33
|
|
|
34
|
+
/** The first line worth reading out of what a run said, or why it stopped. */
|
|
35
|
+
function runExcerpt(run) {
|
|
36
|
+
const text = run.error || run.output || "";
|
|
37
|
+
const line = text.split("\n").map((one) => one.replace(/^[#>*\-\s]+/, "").trim()).find(Boolean) || "";
|
|
38
|
+
return line.length > 160 ? line.slice(0, 159) + "…" : line;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* What the page remembers across repaints: which histories are folded away
|
|
43
|
+
* and which runs are open. The panel is rebuilt whenever a run starts or
|
|
44
|
+
* ends, and losing what someone had opened each time would make it unusable
|
|
45
|
+
* while a routine is going.
|
|
46
|
+
*/
|
|
47
|
+
const foldedHistories = new Set();
|
|
48
|
+
const openRuns = new Set();
|
|
49
|
+
const runKey = (routine, run) => routine.name + "\0" + run.startedAt;
|
|
50
|
+
|
|
36
51
|
/**
|
|
37
|
-
*
|
|
52
|
+
* One run in the history: a status, when, how long, what it cost, and one
|
|
53
|
+
* line under it — what it is doing, while it runs; the start of its answer or
|
|
54
|
+
* its error, after. Clicking opens it in place to the whole of that text,
|
|
55
|
+
* with a way into its conversation.
|
|
56
|
+
*/
|
|
57
|
+
function routineRunRow(run, routine) {
|
|
58
|
+
const state = run.running ? "running" : run.skipped ? "skipped" : run.ok ? "ok" : "failed";
|
|
59
|
+
const key = runKey(routine, run);
|
|
60
|
+
const row = el("div", "routine-run " + state);
|
|
61
|
+
row.dataset.routine = routine.name;
|
|
62
|
+
const top = el("div", "routine-run-top");
|
|
63
|
+
top.appendChild(el("span", "run-status " + state, t("routines.status." + state)));
|
|
64
|
+
top.appendChild(el("span", null, new Date(run.startedAt).toLocaleString()));
|
|
65
|
+
const facts = el("span", "muted routine-run-facts");
|
|
66
|
+
if (run.running) {
|
|
67
|
+
const tick = () => {
|
|
68
|
+
if (!facts.isConnected && facts.dataset.painted) return clearInterval(timer);
|
|
69
|
+
facts.dataset.painted = "1";
|
|
70
|
+
const bits = [t("routines.elapsed", { d: runDuration(Date.now() - run.startedAt) })];
|
|
71
|
+
const turns = Number(row.dataset.turns || run.turns || 0);
|
|
72
|
+
if (turns) bits.push(t("routines.turns", { n: turns }));
|
|
73
|
+
facts.textContent = bits.join(" · ");
|
|
74
|
+
};
|
|
75
|
+
const timer = setInterval(tick, 1000);
|
|
76
|
+
tick();
|
|
77
|
+
} else if (!run.skipped) {
|
|
78
|
+
const bits = [runDuration(run.finishedAt - run.startedAt), t("routines.turns", { n: run.turns })];
|
|
79
|
+
if (run.costUsd) bits.push("$" + run.costUsd.toFixed(4));
|
|
80
|
+
if (run.catchUp) bits.push(t("routines.catchUp"));
|
|
81
|
+
facts.textContent = bits.join(" · ");
|
|
82
|
+
} else if (run.reason === "still-running") {
|
|
83
|
+
facts.textContent = t("routines.stillRunning");
|
|
84
|
+
}
|
|
85
|
+
top.appendChild(facts);
|
|
86
|
+
row.appendChild(top);
|
|
87
|
+
|
|
88
|
+
const full = run.running ? run.activity || "" : run.error || run.output || "";
|
|
89
|
+
const line = el("div", "routine-run-text" + (run.error ? " err" : " muted"),
|
|
90
|
+
run.running ? full || t("routines.starting") : runExcerpt(run));
|
|
91
|
+
row.appendChild(line);
|
|
92
|
+
if (!line.textContent) line.remove();
|
|
93
|
+
|
|
94
|
+
const detail = el("div", "routine-run-detail");
|
|
95
|
+
if (full && !run.running) {
|
|
96
|
+
// The answer as the conversation shows it; an error stays plain text.
|
|
97
|
+
const body = el("div", "routine-run-full" + (run.error ? " err" : " md"));
|
|
98
|
+
if (run.error) body.textContent = full;
|
|
99
|
+
else body.appendChild(renderMarkdown(full));
|
|
100
|
+
detail.appendChild(body);
|
|
101
|
+
}
|
|
102
|
+
if (run.sessionId) {
|
|
103
|
+
const open = el("button", "ghost small", t("routines.openRun"));
|
|
104
|
+
open.type = "button";
|
|
105
|
+
open.addEventListener("click", (event) => {
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
void openSession(run.sessionId, routine.cwd);
|
|
108
|
+
});
|
|
109
|
+
detail.appendChild(open);
|
|
110
|
+
}
|
|
111
|
+
if (!detail.childNodes.length) return row;
|
|
112
|
+
|
|
113
|
+
row.appendChild(detail);
|
|
114
|
+
row.tabIndex = 0;
|
|
115
|
+
row.setAttribute("role", "button");
|
|
116
|
+
const setOpen = (on) => {
|
|
117
|
+
row.classList.toggle("open", on);
|
|
118
|
+
row.setAttribute("aria-expanded", on ? "true" : "false");
|
|
119
|
+
if (on) openRuns.add(key); else openRuns.delete(key);
|
|
120
|
+
};
|
|
121
|
+
setOpen(openRuns.has(key));
|
|
122
|
+
row.addEventListener("click", (event) => {
|
|
123
|
+
// Selecting text in the full answer is reading it, not closing it.
|
|
124
|
+
if (String(getSelection() || "")) return;
|
|
125
|
+
if (event.target.closest(".routine-run-full") && row.classList.contains("open")) return;
|
|
126
|
+
setOpen(!row.classList.contains("open"));
|
|
127
|
+
});
|
|
128
|
+
row.addEventListener("keydown", (event) => {
|
|
129
|
+
if (event.target !== row || (event.key !== "Enter" && event.key !== " ")) return;
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
setOpen(!row.classList.contains("open"));
|
|
132
|
+
});
|
|
133
|
+
return row;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** How many runs a card shows before "Show all". */
|
|
137
|
+
const RUNS_SHOWN = 5;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A routine's runs, newest first, the one going now on top.
|
|
38
141
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the
|
|
142
|
+
* Open unless folded: whether it ran, and what came of it, is what anyone
|
|
143
|
+
* opening this page wants to know. Repainted whenever a run starts or ends,
|
|
144
|
+
* and patched in place as a running one moves on, so a run started here is
|
|
145
|
+
* on the page at once and turns into its result when it finishes.
|
|
43
146
|
*/
|
|
44
147
|
function routineHistory(routine) {
|
|
45
|
-
const box = el("
|
|
46
|
-
|
|
148
|
+
const box = el("div", "routine-history");
|
|
149
|
+
const head = el("button", "routine-history-head muted small");
|
|
150
|
+
head.type = "button";
|
|
151
|
+
const label = el("span", null, t("routines.history"));
|
|
152
|
+
head.append(el("span", "chev", "▾"), label);
|
|
153
|
+
box.appendChild(head);
|
|
47
154
|
const list = el("div", "routine-runs");
|
|
48
155
|
box.appendChild(list);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
156
|
+
const fold = (on) => {
|
|
157
|
+
box.classList.toggle("folded", on);
|
|
158
|
+
head.setAttribute("aria-expanded", on ? "false" : "true");
|
|
159
|
+
if (on) foldedHistories.add(routine.name); else foldedHistories.delete(routine.name);
|
|
160
|
+
};
|
|
161
|
+
fold(foldedHistories.has(routine.name));
|
|
162
|
+
head.addEventListener("click", () => fold(!box.classList.contains("folded")));
|
|
163
|
+
|
|
164
|
+
list.appendChild(el("div", "muted small", t("routines.loading")));
|
|
165
|
+
void getJson("/routine/history?name=" + encodeURIComponent(routine.name)).then((body) => {
|
|
166
|
+
list.replaceChildren();
|
|
167
|
+
const runs = (body && body.runs) || [];
|
|
168
|
+
if (runs.length) label.textContent = t("routines.historyCount", { n: runs.filter((run) => !run.running).length });
|
|
169
|
+
if (!runs.length) return void list.appendChild(el("div", "muted small", t("routines.noRuns")));
|
|
170
|
+
const paint = (all) => {
|
|
54
171
|
list.replaceChildren();
|
|
55
|
-
const runs
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
line.appendChild(el("span", "muted", t("routines.stillRunning")));
|
|
62
|
-
}
|
|
63
|
-
if (run.sessionId) {
|
|
64
|
-
line.type = "button";
|
|
65
|
-
line.addEventListener("click", () => void openSession(run.sessionId, routine.cwd));
|
|
66
|
-
}
|
|
67
|
-
list.appendChild(line);
|
|
172
|
+
for (const run of all ? runs : runs.slice(0, RUNS_SHOWN)) list.appendChild(routineRunRow(run, routine));
|
|
173
|
+
if (runs.length > RUNS_SHOWN) {
|
|
174
|
+
const more = el("button", "ghost small routine-more", all ? t("routines.showFewer") : t("routines.showAll", { n: runs.length }));
|
|
175
|
+
more.type = "button";
|
|
176
|
+
more.addEventListener("click", () => paint(!all));
|
|
177
|
+
list.appendChild(more);
|
|
68
178
|
}
|
|
69
|
-
}
|
|
179
|
+
};
|
|
180
|
+
paint(false);
|
|
70
181
|
});
|
|
71
182
|
return box;
|
|
72
183
|
}
|
|
73
184
|
|
|
185
|
+
/** A running routine moved on: say so in its row, without a repaint. */
|
|
186
|
+
function paintRoutineActivity(event) {
|
|
187
|
+
for (const row of document.querySelectorAll(".routine-run.running")) {
|
|
188
|
+
if (row.dataset.routine !== event.name) continue;
|
|
189
|
+
row.dataset.turns = String(event.turns || 0);
|
|
190
|
+
let line = row.querySelector(".routine-run-text");
|
|
191
|
+
if (!line) {
|
|
192
|
+
line = el("div", "routine-run-text muted");
|
|
193
|
+
row.querySelector(".routine-run-top").after(line);
|
|
194
|
+
}
|
|
195
|
+
if (event.activity) line.textContent = event.activity;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
74
199
|
/**
|
|
75
200
|
* Scheduled runs, read from the scheduler rather than kept here.
|
|
76
201
|
*
|
|
@@ -103,6 +228,10 @@ async function showRoutines() {
|
|
|
103
228
|
const head = el("div", "row-head");
|
|
104
229
|
head.appendChild(el("span", "row-title", routine.name));
|
|
105
230
|
head.appendChild(el("span", "mono row-cron", routine.cron));
|
|
231
|
+
// What it runs on, and what it needs connected: the two things a run
|
|
232
|
+
// fails on without anyone there to fix them.
|
|
233
|
+
if (routine.model) head.appendChild(el("span", "muted small routine-where", routine.model.split("/").pop()));
|
|
234
|
+
if (routine.apps && routine.apps.length) head.appendChild(el("span", "muted small routine-where", routine.apps.join(", ")));
|
|
106
235
|
if (!routine.enabled) head.appendChild(el("span", "tag", t("routines.paused")));
|
|
107
236
|
// Where it runs. A routine is made in one project and runs there, from
|
|
108
237
|
// whichever project is open when its time comes, so the card says which.
|
|
@@ -112,8 +241,9 @@ async function showRoutines() {
|
|
|
112
241
|
head.appendChild(where);
|
|
113
242
|
}
|
|
114
243
|
|
|
115
|
-
const run = el("button", "ghost", t("routines.run"));
|
|
244
|
+
const run = el("button", "ghost", t(routine.running ? "routines.running" : "routines.run"));
|
|
116
245
|
run.type = "button";
|
|
246
|
+
run.disabled = Boolean(routine.running);
|
|
117
247
|
run.addEventListener("click", () => {
|
|
118
248
|
run.disabled = true;
|
|
119
249
|
run.textContent = t("routines.running");
|
|
@@ -161,11 +291,9 @@ async function showRoutines() {
|
|
|
161
291
|
if (routine.enabled && routine.nextRunAt) {
|
|
162
292
|
bits.push(t("routines.next", { when: new Date(routine.nextRunAt).toLocaleString() }));
|
|
163
293
|
}
|
|
164
|
-
|
|
294
|
+
// The last run is the first line of the history below, so it is not
|
|
295
|
+
// repeated here.
|
|
165
296
|
if (bits.length) row.appendChild(el("div", "muted small", bits.join(" · ")));
|
|
166
|
-
if (routine.lastRun && routine.lastRun.error) {
|
|
167
|
-
row.appendChild(el("div", "err small", routine.lastRun.error));
|
|
168
|
-
}
|
|
169
297
|
row.appendChild(routineHistory(routine));
|
|
170
298
|
panel.appendChild(row);
|
|
171
299
|
}
|
|
@@ -197,7 +325,7 @@ async function showSettings() {
|
|
|
197
325
|
|
|
198
326
|
// Rows are grouped by what they are about. Each group is filled as the page
|
|
199
327
|
// is built and drawn at the end under its heading, and only when it has rows.
|
|
200
|
-
const groups = { workspace: el("div"), sync: el("div"), memory: el("div"), activity: el("div"), advanced: el("div"), about: el("div") };
|
|
328
|
+
const groups = { workspace: el("div"), remote: el("div"), sync: el("div"), memory: el("div"), activity: el("div"), advanced: el("div"), about: el("div") };
|
|
201
329
|
let target = groups.workspace;
|
|
202
330
|
|
|
203
331
|
// A click on a button inside a summary toggles the disclosure as well as
|
|
@@ -674,6 +802,132 @@ async function showSettings() {
|
|
|
674
802
|
}),
|
|
675
803
|
);
|
|
676
804
|
}
|
|
805
|
+
// Remote control: a phone on this account driving this machine through the
|
|
806
|
+
// relay. Offered only where the server was given a relay to use, and off
|
|
807
|
+
// until somebody at the machine turns it on.
|
|
808
|
+
const remote = await getJson("/remote");
|
|
809
|
+
if (remote && !remote.error && remote.available) {
|
|
810
|
+
target = groups.remote;
|
|
811
|
+
const stateKey = (view) =>
|
|
812
|
+
view.state === "online" ? "online" : view.state === "connecting" ? "connecting" : view.state === "signed-out" ? "signedOut" : "off";
|
|
813
|
+
const body = field(
|
|
814
|
+
t("settings.remote"),
|
|
815
|
+
t("settings.remote." + stateKey(remote)),
|
|
816
|
+
t("settings.remoteHint"),
|
|
817
|
+
toggle(remote.on, t("settings.remote"), (next) => {
|
|
818
|
+
void api("/remote", { enabled: next }).then((result) => {
|
|
819
|
+
if (result && result.error) addNotice(result.error, "err");
|
|
820
|
+
void showSettings();
|
|
821
|
+
});
|
|
822
|
+
}),
|
|
823
|
+
);
|
|
824
|
+
if (remote.keepsAwake) body.appendChild(el("div", "muted small", t("settings.remoteAwake")));
|
|
825
|
+
|
|
826
|
+
// Where it stands, said in a sentence: off, waiting on an account,
|
|
827
|
+
// reaching for the relay, or there.
|
|
828
|
+
const status = el("div", "setting-status small");
|
|
829
|
+
const dot = el("span", "status-dot");
|
|
830
|
+
const said = el("span");
|
|
831
|
+
status.append(dot, said);
|
|
832
|
+
body.appendChild(status);
|
|
833
|
+
let signIn = null;
|
|
834
|
+
if (typeof openConnect === "function") {
|
|
835
|
+
signIn = el("button", "ghost", t("settings.remoteSignIn"));
|
|
836
|
+
signIn.type = "button";
|
|
837
|
+
signIn.addEventListener("click", () => openConnect("official"));
|
|
838
|
+
body.appendChild(signIn);
|
|
839
|
+
}
|
|
840
|
+
const row = body.parentNode;
|
|
841
|
+
const shownValue = row.querySelector(".setting-value");
|
|
842
|
+
const paint = (view) => {
|
|
843
|
+
const key = stateKey(view);
|
|
844
|
+
dot.className = "status-dot" + (key === "online" ? " ok" : "");
|
|
845
|
+
said.textContent = t("settings.remoteStatus." + key);
|
|
846
|
+
if (shownValue) shownValue.textContent = t("settings.remote." + key);
|
|
847
|
+
if (signIn) signIn.hidden = key !== "signedOut";
|
|
848
|
+
};
|
|
849
|
+
paint(remote);
|
|
850
|
+
// Connecting and signing in finish on their own time, so the row looks
|
|
851
|
+
// again while it is on screen and not yet online.
|
|
852
|
+
if (remote.on) {
|
|
853
|
+
const timer = setInterval(() => {
|
|
854
|
+
if (!document.body.contains(row)) return clearInterval(timer);
|
|
855
|
+
void getJson("/remote").then((view) => {
|
|
856
|
+
if (!view || view.error || !document.body.contains(row)) return;
|
|
857
|
+
paint(view);
|
|
858
|
+
if (!view.on || view.state === "online") clearInterval(timer);
|
|
859
|
+
});
|
|
860
|
+
}, 3000);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// The phones allowed in so far. Forgetting one ends its connection now
|
|
864
|
+
// and sends it back to the Allow card next time.
|
|
865
|
+
const phones = el("div", "setting-list");
|
|
866
|
+
phones.appendChild(el("div", "muted small", t("settings.remotePhones")));
|
|
867
|
+
const devices = remote.devices || [];
|
|
868
|
+
if (!devices.length) phones.appendChild(el("div", "small", t("settings.remoteNoPhones")));
|
|
869
|
+
for (const device of devices) {
|
|
870
|
+
const line = el("div", "remote-phone small");
|
|
871
|
+
line.appendChild(el("span", "remote-phone-name", device.name || device.id));
|
|
872
|
+
if (device.pairedAt) {
|
|
873
|
+
line.appendChild(el("span", "muted", t("settings.remotePairedOn", { date: new Date(device.pairedAt).toLocaleDateString() })));
|
|
874
|
+
}
|
|
875
|
+
const forget = el("button", "ghost", t("settings.remoteForget"));
|
|
876
|
+
forget.type = "button";
|
|
877
|
+
forget.addEventListener("click", () => {
|
|
878
|
+
forget.disabled = true;
|
|
879
|
+
void api("/remote/revoke", { id: device.id }).then((result) => {
|
|
880
|
+
if (result && result.error) {
|
|
881
|
+
forget.disabled = false;
|
|
882
|
+
return void addNotice(result.error, "err");
|
|
883
|
+
}
|
|
884
|
+
addNotice(t("settings.remoteForgotten", { name: device.name || device.id }));
|
|
885
|
+
void showSettings();
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
line.appendChild(forget);
|
|
889
|
+
phones.appendChild(line);
|
|
890
|
+
}
|
|
891
|
+
body.appendChild(phones);
|
|
892
|
+
}
|
|
893
|
+
// Computer use: only in the desktop app, which ships the helper. The switch
|
|
894
|
+
// is whether it may ever take the real mouse and keyboard; the list below
|
|
895
|
+
// it is the person's own additions to the apps it refuses.
|
|
896
|
+
const computer = await getJson("/computer-use/settings");
|
|
897
|
+
if (computer && !computer.error && computer.available) {
|
|
898
|
+
target = groups.workspace;
|
|
899
|
+
const takeover = computer.takeover !== "never";
|
|
900
|
+
const body = field(
|
|
901
|
+
t("settings.computerTakeover"),
|
|
902
|
+
t(takeover ? "settings.computerTakeoverAsk" : "settings.computerTakeoverNever"),
|
|
903
|
+
t("settings.computerTakeoverHint"),
|
|
904
|
+
toggle(takeover, t("settings.computerTakeover"), (next) => {
|
|
905
|
+
void api("/computer-use/settings", { takeover: next ? "ask" : "never" }).then(showSettings);
|
|
906
|
+
}),
|
|
907
|
+
);
|
|
908
|
+
const blocked = el("div", "setting-list");
|
|
909
|
+
blocked.appendChild(el("div", "muted small", t("settings.computerBlocked")));
|
|
910
|
+
const box = el("textarea", "boxed");
|
|
911
|
+
box.value = (computer.blockedApps || []).join("\n");
|
|
912
|
+
box.rows = Math.max(2, (computer.blockedApps || []).length + 1);
|
|
913
|
+
box.spellcheck = false;
|
|
914
|
+
box.style.width = "100%";
|
|
915
|
+
box.placeholder = t("settings.computerBlockedPlaceholder");
|
|
916
|
+
blocked.appendChild(box);
|
|
917
|
+
const save = el("button", "ghost", t("perms.save"));
|
|
918
|
+
save.type = "button";
|
|
919
|
+
save.addEventListener("click", () => {
|
|
920
|
+
save.disabled = true;
|
|
921
|
+
const apps = box.value.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
922
|
+
void api("/computer-use/settings", { blockedApps: apps }).then((result) => {
|
|
923
|
+
save.disabled = false;
|
|
924
|
+
if (result && result.error) return void addNotice(result.error, "err");
|
|
925
|
+
addNotice(t("perms.saved"));
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
blocked.appendChild(save);
|
|
929
|
+
body.appendChild(blocked);
|
|
930
|
+
}
|
|
677
931
|
// Which browsers the agent may use. Three choices, so a segmented control
|
|
678
932
|
// rather than a switch; absent when the browser tools are off altogether,
|
|
679
933
|
// since then there is nothing for it to move.
|
|
@@ -726,6 +980,7 @@ async function showSettings() {
|
|
|
726
980
|
}
|
|
727
981
|
const sections = [
|
|
728
982
|
["settings.section.workspace", [groups.workspace]],
|
|
983
|
+
["settings.section.remote", [groups.remote]],
|
|
729
984
|
["settings.section.sync", [groups.sync, groups.memory]],
|
|
730
985
|
["settings.section.activity", [groups.activity]],
|
|
731
986
|
["settings.section.advanced", [groups.advanced]],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routines.js","sourceRoot":"","sources":["../../../src/page/client/routines.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"routines.js","sourceRoot":"","sources":["../../../src/page/client/routines.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kFAwpCgD,CAAC"}
|