@oharness/server 0.9.3 → 0.11.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/analytics.d.ts +32 -29
- package/dist/analytics.js +225 -278
- package/dist/analytics.js.map +1 -1
- package/dist/i18n.js +500 -64
- package/dist/i18n.js.map +1 -1
- package/dist/icons.js +1 -0
- package/dist/icons.js.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/model-order.js +1 -0
- package/dist/model-order.js.map +1 -1
- package/dist/page/client/account.js +570 -27
- package/dist/page/client/account.js.map +1 -1
- package/dist/page/client/base.js +55 -12
- package/dist/page/client/base.js.map +1 -1
- package/dist/page/client/browser.js +1 -1
- package/dist/page/client/composer.js +802 -59
- 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 +168 -6
- package/dist/page/client/dialogs.js.map +1 -1
- package/dist/page/client/events.js +119 -9
- 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 +8 -0
- package/dist/page/client/index.js.map +1 -1
- package/dist/page/client/panels.js +14 -10
- package/dist/page/client/panels.js.map +1 -1
- package/dist/page/client/ripple.d.ts +13 -0
- package/dist/page/client/ripple.js +247 -0
- package/dist/page/client/ripple.js.map +1 -0
- package/dist/page/client/routines.js +432 -45
- package/dist/page/client/routines.js.map +1 -1
- package/dist/page/client/shell.js +386 -104
- package/dist/page/client/shell.js.map +1 -1
- package/dist/page/client/state.js +278 -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/terminal.js +34 -7
- package/dist/page/client/terminal.js.map +1 -1
- package/dist/page/client/theme.d.ts +17 -0
- package/dist/page/client/theme.js +365 -0
- package/dist/page/client/theme.js.map +1 -0
- package/dist/page/client/transcript.js +196 -8
- 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 +745 -76
- package/dist/page/style.js.map +1 -1
- package/dist/page.js +94 -16
- package/dist/page.js.map +1 -1
- package/dist/prefs.d.ts +31 -0
- package/dist/prefs.js +13 -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 +1738 -73
- 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/suggest.d.ts +42 -0
- package/dist/suggest.js +215 -0
- package/dist/suggest.js.map +1 -0
- package/dist/terminal.d.ts +28 -2
- package/dist/terminal.js +45 -3
- package/dist/terminal.js.map +1 -1
- package/dist/theme-assets.d.ts +39 -0
- package/dist/theme-assets.js +109 -0
- package/dist/theme-assets.js.map +1 -0
- package/dist/theme-store.d.ts +50 -0
- package/dist/theme-store.js +124 -0
- package/dist/theme-store.js.map +1 -0
- package/dist/theme-tools.d.ts +25 -0
- package/dist/theme-tools.js +243 -0
- package/dist/theme-tools.js.map +1 -0
- package/dist/turn-shots.d.ts +43 -0
- package/dist/turn-shots.js +80 -0
- package/dist/turn-shots.js.map +1 -0
- package/dist/user-theme.d.ts +143 -0
- package/dist/user-theme.js +510 -0
- package/dist/user-theme.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background tasks: their rows over the composer, and the sheet one opens
|
|
3
|
+
* into — what the subagent was told, what it did, what it said — with a way
|
|
4
|
+
* to stop it or tell it something.
|
|
5
|
+
*
|
|
6
|
+
* One part of the client script; see ./index.ts for how the parts join.
|
|
7
|
+
*/
|
|
8
|
+
export const TASKS = String.raw `
|
|
9
|
+
// --- background tasks -----------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const TASK_LIVE = new Set(["queued", "running"]);
|
|
12
|
+
/** How many task rows sit over the composer; the sheet lists every one. */
|
|
13
|
+
const TASK_ROWS = 4;
|
|
14
|
+
/** The task the sheet shows, while it is open. */
|
|
15
|
+
let taskOpen = null;
|
|
16
|
+
let taskSheet = null;
|
|
17
|
+
/** Bumped per paint, so a slow answer never draws over a newer one. */
|
|
18
|
+
let taskPaintSeq = 0;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Running ones first, then the newest. Ended tasks stay listed, quieter, so
|
|
22
|
+
* what they said is a click away and they can still be sent a follow-up.
|
|
23
|
+
*/
|
|
24
|
+
function sortedTasks(tasks) {
|
|
25
|
+
return (tasks || []).slice().sort((a, b) =>
|
|
26
|
+
(Number(TASK_LIVE.has(b.status)) - Number(TASK_LIVE.has(a.status))) || (b.queuedAt - a.queuedAt));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The rows over the composer, drawn by renderQueued with the queue. */
|
|
30
|
+
function appendTaskRows(container, here) {
|
|
31
|
+
// Put-away tasks leave the list; the transcript's card and the sheet's
|
|
32
|
+
// chips still reach them.
|
|
33
|
+
const tasks = sortedTasks(here && here.tasks).filter((task) => !task.dismissed);
|
|
34
|
+
for (const task of tasks.slice(0, TASK_ROWS)) {
|
|
35
|
+
const row = el("div", "q task task-" + task.status);
|
|
36
|
+
row.appendChild(el("span", "tag", t("tasks.status." + task.status)));
|
|
37
|
+
const name = el("span", "n", task.description + (task.activity ? " · " + task.activity : ""));
|
|
38
|
+
row.appendChild(name);
|
|
39
|
+
const open = el("button", "go", t("tasks.open"));
|
|
40
|
+
open.type = "button";
|
|
41
|
+
open.addEventListener("click", () => openTask(activeId, task.id));
|
|
42
|
+
row.appendChild(open);
|
|
43
|
+
// One × in the same place, saying what it does: running, it stops the
|
|
44
|
+
// task; ended, it only takes the row away.
|
|
45
|
+
const live = TASK_LIVE.has(task.status);
|
|
46
|
+
const close = el("button", "x", "×");
|
|
47
|
+
close.type = "button";
|
|
48
|
+
close.title = t(live ? "tasks.stop" : "tasks.dismiss");
|
|
49
|
+
close.setAttribute("aria-label", close.title);
|
|
50
|
+
close.addEventListener("click", () => void (live ? stopTask(activeId, task.id) : dismissTask(activeId, task.id)));
|
|
51
|
+
row.appendChild(close);
|
|
52
|
+
name.addEventListener("click", () => openTask(activeId, task.id));
|
|
53
|
+
container.appendChild(row);
|
|
54
|
+
}
|
|
55
|
+
if (tasks.length > TASK_ROWS) {
|
|
56
|
+
const more = el("button", "q task-more", t("tasks.more", { n: tasks.length - TASK_ROWS }));
|
|
57
|
+
more.type = "button";
|
|
58
|
+
more.addEventListener("click", () => openTask(activeId, tasks[TASK_ROWS].id));
|
|
59
|
+
container.appendChild(more);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Background results read into the conversation, as the transcript shows them:
|
|
65
|
+
* one line per task saying how it ended, each a way into its sheet. Where it
|
|
66
|
+
* sits is where the model read them.
|
|
67
|
+
*/
|
|
68
|
+
function taskCard(tasks) {
|
|
69
|
+
const card = el("div", "task-card");
|
|
70
|
+
card.appendChild(el("div", "task-card-head", t("tasks.cardTitle")));
|
|
71
|
+
for (const task of tasks) {
|
|
72
|
+
const row = el("div", "task-card-row task-" + task.status);
|
|
73
|
+
row.appendChild(el("span", "tag", t("tasks.status." + task.status)));
|
|
74
|
+
row.appendChild(el("span", "n", task.description));
|
|
75
|
+
const open = el("button", "go", t("tasks.open"));
|
|
76
|
+
open.type = "button";
|
|
77
|
+
// The conversation this card is in, which need not be the one on screen.
|
|
78
|
+
const session = P.id;
|
|
79
|
+
open.addEventListener("click", () => openTask(session, task.id));
|
|
80
|
+
row.appendChild(open);
|
|
81
|
+
card.appendChild(row);
|
|
82
|
+
}
|
|
83
|
+
// In the transcript's column, so it lines up with the messages around it.
|
|
84
|
+
const row = el("div", "column task-card-row-wrap");
|
|
85
|
+
row.appendChild(card);
|
|
86
|
+
P.node.appendChild(row);
|
|
87
|
+
follow();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A conversation's tasks changed: redraw what shows them. */
|
|
91
|
+
function tasksChanged(session, tasks) {
|
|
92
|
+
pane(session).tasks = tasks || [];
|
|
93
|
+
if (session === activeId) renderQueued();
|
|
94
|
+
if (taskOpen && taskOpen.session === session) void paintTask();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function openTask(session, id) {
|
|
98
|
+
taskOpen = { session, id, stick: true };
|
|
99
|
+
ensureTaskSheet();
|
|
100
|
+
taskSheet.hidden = false;
|
|
101
|
+
void paintTask();
|
|
102
|
+
taskSheet.querySelector(".task-input").focus();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function closeTask() {
|
|
106
|
+
taskOpen = null;
|
|
107
|
+
if (taskSheet) taskSheet.hidden = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function dismissTask(session, id) {
|
|
111
|
+
const body = await api("/task", { session, id, action: "dismiss" });
|
|
112
|
+
if (body.error) addNotice(body.error, "notice err");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function stopTask(session, id) {
|
|
116
|
+
const body = await api("/task", { session, id, action: "stop" });
|
|
117
|
+
if (body.error) addNotice(body.error, "notice err");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function ensureTaskSheet() {
|
|
121
|
+
if (taskSheet) return;
|
|
122
|
+
taskSheet = el("div");
|
|
123
|
+
taskSheet.id = "task-sheet";
|
|
124
|
+
taskSheet.hidden = true;
|
|
125
|
+
taskSheet.setAttribute("role", "dialog");
|
|
126
|
+
taskSheet.setAttribute("aria-label", t("tasks.title"));
|
|
127
|
+
|
|
128
|
+
const head = el("div", "task-head");
|
|
129
|
+
const titles = el("div", "task-titles");
|
|
130
|
+
titles.appendChild(el("div", "task-title"));
|
|
131
|
+
titles.appendChild(el("div", "task-meta"));
|
|
132
|
+
head.appendChild(titles);
|
|
133
|
+
const stop = el("button", "task-stop", t("tasks.stop"));
|
|
134
|
+
stop.type = "button";
|
|
135
|
+
stop.addEventListener("click", () => { if (taskOpen) void stopTask(taskOpen.session, taskOpen.id); });
|
|
136
|
+
head.appendChild(stop);
|
|
137
|
+
const close = el("button", "task-close", "×");
|
|
138
|
+
close.type = "button";
|
|
139
|
+
close.title = t("tasks.close");
|
|
140
|
+
close.setAttribute("aria-label", t("tasks.close"));
|
|
141
|
+
close.addEventListener("click", closeTask);
|
|
142
|
+
head.appendChild(close);
|
|
143
|
+
taskSheet.appendChild(head);
|
|
144
|
+
|
|
145
|
+
taskSheet.appendChild(el("div", "task-chips"));
|
|
146
|
+
const log = el("div", "task-log");
|
|
147
|
+
// Follow new lines only while the reader is at the bottom; scrolling up to
|
|
148
|
+
// read something earlier must not be undone by the next tool.
|
|
149
|
+
log.addEventListener("scroll", () => {
|
|
150
|
+
if (taskOpen) taskOpen.stick = log.scrollHeight - log.scrollTop - log.clientHeight < 40;
|
|
151
|
+
});
|
|
152
|
+
taskSheet.appendChild(log);
|
|
153
|
+
|
|
154
|
+
// The box is the field and its send button together, as in the composer.
|
|
155
|
+
const form = el("form", "task-form");
|
|
156
|
+
const box = el("div", "task-box");
|
|
157
|
+
const input = el("textarea", "task-input");
|
|
158
|
+
input.rows = 2;
|
|
159
|
+
input.setAttribute("aria-label", t("tasks.messageLabel"));
|
|
160
|
+
const send = el("button", "primary task-send");
|
|
161
|
+
send.type = "submit";
|
|
162
|
+
send.title = t("tasks.send");
|
|
163
|
+
send.setAttribute("aria-label", t("tasks.send"));
|
|
164
|
+
send.appendChild(svgIcon("send"));
|
|
165
|
+
box.appendChild(input);
|
|
166
|
+
box.appendChild(send);
|
|
167
|
+
form.appendChild(box);
|
|
168
|
+
form.addEventListener("submit", (e) => {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
void sendTask(input);
|
|
171
|
+
});
|
|
172
|
+
input.addEventListener("keydown", (e) => {
|
|
173
|
+
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
void sendTask(input);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
taskSheet.appendChild(form);
|
|
179
|
+
|
|
180
|
+
taskSheet.addEventListener("keydown", (e) => {
|
|
181
|
+
if (e.key === "Escape") closeTask();
|
|
182
|
+
});
|
|
183
|
+
document.body.appendChild(taskSheet);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function sendTask(input) {
|
|
187
|
+
const text = input.value.trim();
|
|
188
|
+
if (!text || !taskOpen) return;
|
|
189
|
+
const { session, id } = taskOpen;
|
|
190
|
+
input.disabled = true;
|
|
191
|
+
const body = await api("/task", { session, id, action: "message", text });
|
|
192
|
+
input.disabled = false;
|
|
193
|
+
if (body.error) {
|
|
194
|
+
taskSheet.querySelector(".task-log").appendChild(el("div", "task-error", body.error));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
input.value = "";
|
|
198
|
+
input.focus();
|
|
199
|
+
if (taskOpen) taskOpen.stick = true;
|
|
200
|
+
void paintTask();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function taskElapsed(task) {
|
|
204
|
+
if (!task.startedAt) return "";
|
|
205
|
+
const seconds = Math.max(0, Math.round(((task.endedAt || Date.now()) - task.startedAt) / 1000));
|
|
206
|
+
return seconds < 60 ? seconds + "s" : Math.floor(seconds / 60) + "m" + (seconds % 60) + "s";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function paintTask() {
|
|
210
|
+
if (!taskOpen || !taskSheet) return;
|
|
211
|
+
const { session, id } = taskOpen;
|
|
212
|
+
const seq = ++taskPaintSeq;
|
|
213
|
+
const body = await getJson("/task?session=" + encodeURIComponent(session) + "&id=" + encodeURIComponent(id));
|
|
214
|
+
if (!taskOpen || taskOpen.id !== id || taskOpen.session !== session || seq !== taskPaintSeq) return;
|
|
215
|
+
const log = taskSheet.querySelector(".task-log");
|
|
216
|
+
// Tool rows the reader opened stay open across a redraw: a running task
|
|
217
|
+
// redraws on every step, and an expanded result snapping shut is unreadable.
|
|
218
|
+
// Only for the task already on screen: another task's rows are other rows.
|
|
219
|
+
const opened = new Set();
|
|
220
|
+
if (taskSheet.dataset.painted === id) {
|
|
221
|
+
log.querySelectorAll(".task-tool").forEach((node, index) => { if (node.open) opened.add(index); });
|
|
222
|
+
}
|
|
223
|
+
taskSheet.dataset.painted = id;
|
|
224
|
+
if (body.error) {
|
|
225
|
+
log.replaceChildren(el("div", "task-error", body.error));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const task = body.task;
|
|
229
|
+
|
|
230
|
+
taskSheet.querySelector(".task-title").textContent = task.description;
|
|
231
|
+
const meta = [t("tasks.status." + task.status), task.agentType, t("tasks.turns", { n: task.turns }), taskElapsed(task)];
|
|
232
|
+
if (task.activity) meta.push(task.activity);
|
|
233
|
+
taskSheet.querySelector(".task-meta").textContent = meta.filter(Boolean).join(" · ");
|
|
234
|
+
taskSheet.querySelector(".task-stop").hidden = !TASK_LIVE.has(task.status);
|
|
235
|
+
|
|
236
|
+
// Every task of this conversation, so a sheet opened on one reaches the rest.
|
|
237
|
+
// One row that scrolls sideways: rebuilt on every change, so it keeps
|
|
238
|
+
// where it was scrolled to, and brings the open task's chip into view.
|
|
239
|
+
const chips = taskSheet.querySelector(".task-chips");
|
|
240
|
+
const scrolled = chips.scrollLeft;
|
|
241
|
+
chips.replaceChildren();
|
|
242
|
+
const all = sortedTasks(pane(session).tasks);
|
|
243
|
+
chips.hidden = all.length < 2;
|
|
244
|
+
let current = null;
|
|
245
|
+
for (const other of all) {
|
|
246
|
+
const chip = el("button", "task-chip task-" + other.status + (other.id === id ? " on" : ""), other.description);
|
|
247
|
+
chip.type = "button";
|
|
248
|
+
chip.addEventListener("click", () => openTask(session, other.id));
|
|
249
|
+
chips.appendChild(chip);
|
|
250
|
+
if (other.id === id) current = chip;
|
|
251
|
+
}
|
|
252
|
+
chips.scrollLeft = scrolled;
|
|
253
|
+
if (current && (current.offsetLeft < chips.scrollLeft || current.offsetLeft + current.offsetWidth > chips.scrollLeft + chips.clientWidth)) {
|
|
254
|
+
chips.scrollLeft = current.offsetLeft - 16;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
log.replaceChildren();
|
|
258
|
+
for (const entry of body.transcript || []) {
|
|
259
|
+
if (entry.role === "tool") {
|
|
260
|
+
const tool = el("details", "task-tool" + (entry.isError ? " err" : ""));
|
|
261
|
+
const summary = el("summary");
|
|
262
|
+
summary.appendChild(el("span", "task-tool-name", entry.name));
|
|
263
|
+
summary.appendChild(el("span", "task-tool-input", entry.text));
|
|
264
|
+
tool.appendChild(summary);
|
|
265
|
+
if (entry.result) tool.appendChild(el("pre", "task-tool-result", entry.result));
|
|
266
|
+
if (opened.has(log.querySelectorAll(".task-tool").length)) tool.open = true;
|
|
267
|
+
log.appendChild(tool);
|
|
268
|
+
} else if (entry.role === "assistant") {
|
|
269
|
+
const msg = el("div", "task-msg assistant");
|
|
270
|
+
msg.appendChild(renderMarkdown(entry.text));
|
|
271
|
+
log.appendChild(msg);
|
|
272
|
+
} else {
|
|
273
|
+
log.appendChild(el("div", "task-msg user", entry.text));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!log.childElementCount) log.appendChild(el("div", "task-empty", t("tasks.empty")));
|
|
277
|
+
if (task.worktree) log.appendChild(el("div", "task-note", task.worktree));
|
|
278
|
+
if (task.error) log.appendChild(el("div", "task-error", task.error));
|
|
279
|
+
if (TASK_LIVE.has(task.status)) {
|
|
280
|
+
const working = el("div", "task-working");
|
|
281
|
+
working.appendChild(el("span", "spinner"));
|
|
282
|
+
log.appendChild(working);
|
|
283
|
+
}
|
|
284
|
+
if (taskOpen.stick) log.scrollTop = log.scrollHeight;
|
|
285
|
+
|
|
286
|
+
const input = taskSheet.querySelector(".task-input");
|
|
287
|
+
const stopped = task.status === "cancelled";
|
|
288
|
+
input.disabled = stopped;
|
|
289
|
+
taskSheet.querySelector(".task-send").disabled = stopped;
|
|
290
|
+
input.placeholder = t(stopped ? "tasks.placeholder.stopped" : TASK_LIVE.has(task.status) ? "tasks.placeholder.running" : "tasks.placeholder.ended");
|
|
291
|
+
}
|
|
292
|
+
`;
|
|
293
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../../src/page/client/tasks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4R9B,CAAC"}
|
|
@@ -84,7 +84,7 @@ function terminalActive() {
|
|
|
84
84
|
* Asked again for the project already on show it hides, which is what the
|
|
85
85
|
* tab's button wants; force keeps it open, for the menus.
|
|
86
86
|
*/
|
|
87
|
-
async function openTerminalIn(cwd, force) {
|
|
87
|
+
async function openTerminalIn(cwd, force, opts) {
|
|
88
88
|
if (!cwd) return;
|
|
89
89
|
if (terminalShownFor(cwd) && !force) return hideTerminal();
|
|
90
90
|
track("panel_opened", { panel: "terminal" });
|
|
@@ -115,12 +115,28 @@ async function openTerminalIn(cwd, force) {
|
|
|
115
115
|
const entry = terminalCreate(lib, cwd);
|
|
116
116
|
void terminalAttach(entry, found, true);
|
|
117
117
|
}
|
|
118
|
-
if (!group.tabs.length) await terminalNew(cwd);
|
|
119
|
-
else group.active = group.tabs[group.tabs.length - 1];
|
|
120
|
-
} else if (!group.tabs.length) {
|
|
118
|
+
if (!group.tabs.length && !(opts && opts.bare)) await terminalNew(cwd);
|
|
119
|
+
else if (group.tabs.length) group.active = group.tabs[group.tabs.length - 1];
|
|
120
|
+
} else if (!group.tabs.length && !(opts && opts.bare)) {
|
|
121
121
|
await terminalNew(cwd);
|
|
122
122
|
}
|
|
123
|
-
if (terminalCwd === cwd) terminalSelect(group.active);
|
|
123
|
+
if (terminalCwd === cwd && group.active) terminalSelect(group.active);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Hand the project to Claude Code: a new terminal tab running the user's own
|
|
128
|
+
* claude, signed in however they signed it in. OHarness starts it and stays
|
|
129
|
+
* out of the way — no login of ours, no token read — which is what keeps a
|
|
130
|
+
* Claude plan usable from here at all.
|
|
131
|
+
*/
|
|
132
|
+
async function openClaudeCode(cwd, resume) {
|
|
133
|
+
if (!cwd) return;
|
|
134
|
+
track("panel_opened", { panel: "claude-code" });
|
|
135
|
+
// bare: the panel without the plain shell it would otherwise start, since
|
|
136
|
+
// the tab being opened is the one that was asked for.
|
|
137
|
+
await openTerminalIn(cwd, true, { bare: true });
|
|
138
|
+
// resume: a session an @claude reply ran in, opened in Claude Code itself.
|
|
139
|
+
if (terminalCwd === cwd) await terminalNew(cwd, "claude", resume);
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
function hideTerminal() {
|
|
@@ -135,9 +151,13 @@ function terminalFollow(cwd) {
|
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
/** Another shell in the project on show, as a new tab in front. */
|
|
138
|
-
async function terminalNew(cwd) {
|
|
154
|
+
async function terminalNew(cwd, run, resume) {
|
|
139
155
|
const lib = await loadTerminalLib();
|
|
140
156
|
const entry = terminalCreate(lib, cwd);
|
|
157
|
+
// Only the first shell runs it: a tab restarted by typing after an exit is
|
|
158
|
+
// an ordinary shell, not a second Claude Code the user did not ask for.
|
|
159
|
+
if (run) entry.run = run;
|
|
160
|
+
if (run && resume) entry.resume = resume;
|
|
141
161
|
terminalGroup(cwd).active = entry;
|
|
142
162
|
terminalSelect(entry);
|
|
143
163
|
await terminalStart(entry);
|
|
@@ -266,8 +286,15 @@ async function terminalStart(entry) {
|
|
|
266
286
|
entry.id = null;
|
|
267
287
|
if (replace) entry.term.reset();
|
|
268
288
|
terminalFit(entry);
|
|
289
|
+
const run = entry.run;
|
|
290
|
+
const resume = entry.resume;
|
|
291
|
+
entry.run = undefined;
|
|
292
|
+
entry.resume = undefined;
|
|
269
293
|
const body = await api("/term/open", {
|
|
270
|
-
cwd: entry.cwd, cols: entry.term.cols, rows: entry.term.rows,
|
|
294
|
+
cwd: entry.cwd, cols: entry.term.cols, rows: entry.term.rows,
|
|
295
|
+
...(replace ? { replace } : {}),
|
|
296
|
+
...(run ? { run } : {}),
|
|
297
|
+
...(resume ? { resume } : {}),
|
|
271
298
|
});
|
|
272
299
|
entry.starting = false;
|
|
273
300
|
if (!body || body.error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../../src/page/client/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../../src/page/client/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8WjC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Themes the agent proposes, and the one in use.
|
|
3
|
+
*
|
|
4
|
+
* A proposal arrives as a card in the conversation that asked for it. Nothing
|
|
5
|
+
* changes until the person acts on the card: Preview tries it on this page
|
|
6
|
+
* only, Apply makes it the theme everywhere, Discard drops it. The applied
|
|
7
|
+
* theme is printed into the page by the server and replaced here when another
|
|
8
|
+
* window changes it.
|
|
9
|
+
*
|
|
10
|
+
* Safe mode is the way out of a theme that made the page unreadable, so it is
|
|
11
|
+
* the one thing here that needs no theme, no card and no conversation: the
|
|
12
|
+
* page loaded with ?theme=default, or the shortcut below, and custom colours
|
|
13
|
+
* are off in this window until it is pressed again.
|
|
14
|
+
*
|
|
15
|
+
* One part of the client script; see ./index.ts for how the parts join.
|
|
16
|
+
*/
|
|
17
|
+
export declare const THEME: string;
|