teapot-coding-agent 0.24.1 → 0.24.2
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/agent/agent.js +50 -3
- package/dist/agent/llm.js +130 -6
- package/dist/agent/tools.js +63 -6
- package/dist/log/events.js +47 -0
- package/dist/server/api.js +2 -2
- package/package.json +1 -1
- package/public/assets/index-BKQtrd-0.css +1 -0
- package/public/assets/{index-sfdzqjpM.js → index-H2_fgmAt.js} +22 -22
- package/public/assets/index-H2_fgmAt.js.map +1 -0
- package/public/assets/{shiki-D1TWrY4w.js → shiki-DcpfEu1c.js} +2 -2
- package/public/assets/{shiki-D1TWrY4w.js.map → shiki-DcpfEu1c.js.map} +1 -1
- package/public/index.html +2 -2
- package/public/assets/index-ClyCarLt.css +0 -1
- package/public/assets/index-sfdzqjpM.js.map +0 -1
package/dist/agent/agent.js
CHANGED
|
@@ -346,10 +346,23 @@ export class Agent {
|
|
|
346
346
|
return this.manualCompactBudget;
|
|
347
347
|
}
|
|
348
348
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
349
|
+
* Is this agent doing work that `stop()` would actually stop?
|
|
350
|
+
*
|
|
351
|
+
* `status` cannot answer this on its own. Three states count as live:
|
|
352
|
+
* - `running`: the obvious one;
|
|
353
|
+
* - a PARKED TOOL: `parkForTool()` flips the visible status to "idle" so a
|
|
354
|
+
* multi-minute `wait_children` doesn't look like a running spinner, but
|
|
355
|
+
* the loop is very much alive inside the tool;
|
|
356
|
+
* - QUEUED PROMPTS: prompts handed to a stopped agent are accepted and held,
|
|
357
|
+
* and a later `start()` will consume them.
|
|
358
|
+
*
|
|
359
|
+
* Anything keying "should this button stop or start?" off `status` alone
|
|
360
|
+
* therefore offers **start** on a working agent, and the user cannot stop it
|
|
361
|
+
* (#59) — the same trap #38 documented for editing history.
|
|
352
362
|
*/
|
|
363
|
+
isLive() {
|
|
364
|
+
return this.status === "running" || this.parkedByTool || this.pendingPrompts.length > 0;
|
|
365
|
+
}
|
|
353
366
|
/**
|
|
354
367
|
* A background shell (bash background=true) finished. Two channels:
|
|
355
368
|
* 1. a system_note row in the timeline — the operator sees the outcome
|
|
@@ -614,6 +627,15 @@ export class Agent {
|
|
|
614
627
|
if (msgs.length > 0) {
|
|
615
628
|
this.messages = msgs;
|
|
616
629
|
this.currentBranch = last.branch;
|
|
630
|
+
// #50: re-apply pruning to the restored history. `maybePrune` clips
|
|
631
|
+
// oversized tool output IN PLACE, so a live session keeps its clipped
|
|
632
|
+
// text while the log still holds the full result — a restart therefore
|
|
633
|
+
// handed the provider a strictly LARGER prompt than the one that had just
|
|
634
|
+
// succeeded, which is a size regression that only shows up after a
|
|
635
|
+
// reload. It is a size fix, not a semantic one: the model has already
|
|
636
|
+
// seen and acted on that output, and the pruned form keeps the head plus
|
|
637
|
+
// a marker, exactly as it did live.
|
|
638
|
+
this.maybePrune();
|
|
617
639
|
await this.log.append("system_note", this.currentSession, this.currentBranch, {
|
|
618
640
|
event: "session-restored",
|
|
619
641
|
branch: last.branch,
|
|
@@ -876,6 +898,17 @@ export class Agent {
|
|
|
876
898
|
id: this.opts.id,
|
|
877
899
|
status: this.status,
|
|
878
900
|
statusReason: this.statusReason,
|
|
901
|
+
/**
|
|
902
|
+
* Is there work in flight that `stop` would actually stop?
|
|
903
|
+
*
|
|
904
|
+
* `status` alone is not enough to answer that, and the UI needs it: a
|
|
905
|
+
* parked tool (`wait_children`) rewrites the status to "idle" while the
|
|
906
|
+
* loop is very much alive, so a client keying "stop vs start" off
|
|
907
|
+
* `status === "running"` offered **start** on a running agent and the
|
|
908
|
+
* user could not stop it (#59). This is the same "running is not the
|
|
909
|
+
* only live state" fact #38 had to be taught about editing.
|
|
910
|
+
*/
|
|
911
|
+
live: this.isLive(),
|
|
879
912
|
workspace: this.workspace,
|
|
880
913
|
workspaceMissing: this.workspaceMissing,
|
|
881
914
|
session: this.currentSession,
|
|
@@ -2171,6 +2204,20 @@ export class Agent {
|
|
|
2171
2204
|
phase: "summarizing",
|
|
2172
2205
|
summarized: oldCount,
|
|
2173
2206
|
});
|
|
2207
|
+
// #56: the START belongs on the timeline too. Only the right panel showed
|
|
2208
|
+
// compaction, and it reports the *outcome*; a long summarize (hundreds of
|
|
2209
|
+
// thousands of tokens) looks exactly like a hung agent on the timeline,
|
|
2210
|
+
// because the first `context-compacted` event is only appended once the
|
|
2211
|
+
// whole pass is over. Log the announcement as a visible system_note so
|
|
2212
|
+
// the operator sees WHERE the conversation was rewritten, even if the
|
|
2213
|
+
// pass later fails — `before`/`oldCount` are known at this point, and the
|
|
2214
|
+
// completion divider still carries the authoritative after-figures.
|
|
2215
|
+
await this.log.append("system_note", this.currentSession, this.currentBranch, {
|
|
2216
|
+
event: "context-compaction-started",
|
|
2217
|
+
tokensBefore: before,
|
|
2218
|
+
messages: oldCount,
|
|
2219
|
+
reason: force ? "manual" : "auto",
|
|
2220
|
+
});
|
|
2174
2221
|
this.compactPhase = "summarizing";
|
|
2175
2222
|
try {
|
|
2176
2223
|
summary = await this.summarize(old, (text) => {
|
package/dist/agent/llm.js
CHANGED
|
@@ -128,6 +128,75 @@ function sanitize(messages) {
|
|
|
128
128
|
return { ...m };
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Why did the response carry no usable message?
|
|
133
|
+
*
|
|
134
|
+
* #50 — this used to be a bare "LLM API returned no choices", which is what
|
|
135
|
+
* made the report undiagnosable: an empty `choices` array, a 200 carrying an
|
|
136
|
+
* `error` object, a `delta`-shaped body, and an SSE stream that the SDK
|
|
137
|
+
* handed back as a plain String all produce the identical line, and none of
|
|
138
|
+
* them is a useful thing to read in a log.
|
|
139
|
+
*
|
|
140
|
+
* The leading text is kept stable at "LLM API returned no choices" so any
|
|
141
|
+
* existing matching (and the retry notes operators have learned to read)
|
|
142
|
+
* still works; the diagnosis follows after a colon.
|
|
143
|
+
*/
|
|
144
|
+
function noChoicesDetail(res) {
|
|
145
|
+
const base = "LLM API returned no choices";
|
|
146
|
+
// a gateway that answers 200 with an error object instead of failing: the
|
|
147
|
+
// actionable message is right there and used to be thrown away
|
|
148
|
+
const err = res?.error;
|
|
149
|
+
if (err && typeof err === "object") {
|
|
150
|
+
const msg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
151
|
+
const code = err.code ?? err.type;
|
|
152
|
+
return `${base}: provider returned an error in a 200 response${code ? ` (${String(code)})` : ""} — ${String(msg).slice(0, 300)}`;
|
|
153
|
+
}
|
|
154
|
+
// the SDK returned raw text — e.g. an SSE body served to a non-streaming
|
|
155
|
+
// request. JSON.parse succeeds for a real object, so a failure here means we
|
|
156
|
+
// were handed something that is not a completion at all.
|
|
157
|
+
if (typeof res === "string") {
|
|
158
|
+
const head = res.slice(0, 160).replace(/\s+/g, " ").trim();
|
|
159
|
+
return `${base}: response was not a completion object (${res.length} bytes of ${res.trimStart().startsWith("data:") || res.trimStart().startsWith("event:") ? "SSE" : "text"})${head ? ` — ${head}` : ""}`;
|
|
160
|
+
}
|
|
161
|
+
const choices = res?.choices;
|
|
162
|
+
if (Array.isArray(choices)) {
|
|
163
|
+
const first = choices[0];
|
|
164
|
+
if (!first)
|
|
165
|
+
return `${base}: the provider returned an empty choices array (0 candidates)`;
|
|
166
|
+
if (first.delta && !first.message) {
|
|
167
|
+
return `${base}: the provider returned a streaming delta on a non-streaming request (finish_reason=${String(first.finish_reason)})`;
|
|
168
|
+
}
|
|
169
|
+
if (first.message === null) {
|
|
170
|
+
return `${base}: the provider returned a choice with a null message (finish_reason=${String(first.finish_reason)})`;
|
|
171
|
+
}
|
|
172
|
+
return `${base}: the provider returned a choice with no message field (finish_reason=${String(first.finish_reason)})`;
|
|
173
|
+
}
|
|
174
|
+
return `${base}: the response had no choices array (${safeShape(res)})`;
|
|
175
|
+
}
|
|
176
|
+
/** a short type summary of an unexpected response, for the log line */
|
|
177
|
+
function safeShape(res) {
|
|
178
|
+
try {
|
|
179
|
+
const o = res;
|
|
180
|
+
if (!o || typeof o !== "object")
|
|
181
|
+
return `got ${typeof res}`;
|
|
182
|
+
return `keys: ${Object.keys(o).slice(0, 8).join(", ") || "(none)"}`;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return "unreadable response";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* HTTP statuses that mean "this request shape is not supported here", so
|
|
190
|
+
* re-issuing it without `stream: true` is a genuinely different request.
|
|
191
|
+
*
|
|
192
|
+
* Deliberately an allow-list rather than the whole 4xx range. 408/425/429 are
|
|
193
|
+
* TRANSIENT: replaying them immediately against an already-saturated provider
|
|
194
|
+
* just doubles the request count without changing anything. 401/403 mean the
|
|
195
|
+
* credentials are wrong, which is not fixed by dropping `stream` either — they
|
|
196
|
+
* are excluded on purpose, since a fallback there would replace one honest
|
|
197
|
+
* auth error with the same one and a wasted round trip.
|
|
198
|
+
*/
|
|
199
|
+
const STREAM_REJECTED_STATUS = new Set([400, 404, 405, 406, 415, 422, 501]);
|
|
131
200
|
export async function chat(cfg, messages, tools, signal, onDelta) {
|
|
132
201
|
try {
|
|
133
202
|
const res = await client(cfg).chat.completions.create({
|
|
@@ -136,10 +205,14 @@ export async function chat(cfg, messages, tools, signal, onDelta) {
|
|
|
136
205
|
...(tools.length ? { tools } : {}),
|
|
137
206
|
...effortForRequest(cfg),
|
|
138
207
|
}, { signal });
|
|
139
|
-
|
|
208
|
+
// #50: a gateway may answer with `null` (or any non-object) on some proxy
|
|
209
|
+
// error paths, so the guard is on `rm`, not on `res` being truthy —
|
|
210
|
+
// reading `res.choices` unguarded threw a bare TypeError that named neither
|
|
211
|
+
// the provider nor the shape.
|
|
212
|
+
const raw = res?.choices?.[0];
|
|
140
213
|
const rm = raw?.message;
|
|
141
214
|
if (!rm)
|
|
142
|
-
throw new Error(
|
|
215
|
+
throw new Error(noChoicesDetail(res));
|
|
143
216
|
// normalize: providers attach extra fields (reasoning, refusal, ...) and
|
|
144
217
|
// nullable content — keep only what our protocol understands
|
|
145
218
|
const message = {
|
|
@@ -198,6 +271,13 @@ export async function chat(cfg, messages, tools, signal, onDelta) {
|
|
|
198
271
|
}
|
|
199
272
|
catch (err) {
|
|
200
273
|
const e = err;
|
|
274
|
+
// #50: our own shape-diagnosis already reads as a full sentence and names
|
|
275
|
+
// the response it saw. It carries no HTTP status (the request SUCCEEDED —
|
|
276
|
+
// that is the whole problem), so the generic wrapper below would have
|
|
277
|
+
// relabelled it "LLM API error ?: …", hiding both the diagnosis and the
|
|
278
|
+
// fact that no status was ever involved.
|
|
279
|
+
if (String(err.message ?? "").startsWith("LLM API returned no choices"))
|
|
280
|
+
throw err;
|
|
201
281
|
if (e.status === undefined && !String(err.message).includes("provider returned"))
|
|
202
282
|
throw err; // not an API error (abort, bug, ...)
|
|
203
283
|
const detail = e.error?.message ?? e.message ?? "unknown provider error";
|
|
@@ -284,14 +364,58 @@ export async function chatStream(cfg, messages, tools, signal, onDelta) {
|
|
|
284
364
|
function: { name: c.name, arguments: c.args || "{}" },
|
|
285
365
|
}];
|
|
286
366
|
});
|
|
287
|
-
if (!message.content && !message.tool_calls)
|
|
288
|
-
|
|
367
|
+
if (!message.content && !message.tool_calls) {
|
|
368
|
+
// #50: a stream that opened, produced NOTHING, and closed cleanly. The
|
|
369
|
+
// throw carries a marker so the handler below can tell this apart from a
|
|
370
|
+
// request the provider actively rejected: this one is a broken/flaky
|
|
371
|
+
// proxy, and re-issuing it without `stream` is a genuinely different
|
|
372
|
+
// request that usually succeeds. Throwing a bare Error would make the two
|
|
373
|
+
// indistinguishable and cost us that safety net.
|
|
374
|
+
const e = new Error("LLM API error: empty completion");
|
|
375
|
+
e.emptyStream = true;
|
|
376
|
+
throw e;
|
|
377
|
+
}
|
|
289
378
|
return { message, reasoning: reasoning || undefined, usage };
|
|
290
379
|
}
|
|
291
380
|
catch (err) {
|
|
292
|
-
// provider
|
|
293
|
-
|
|
381
|
+
// #50 — a provider that doesn't support streaming at all still gets ONE
|
|
382
|
+
// clean fallback, but ONLY when the streaming attempt produced nothing for
|
|
383
|
+
// a reason that could plausibly be the stream itself.
|
|
384
|
+
//
|
|
385
|
+
// The old condition was `!gotChunk && !signal?.aborted`, which also matched
|
|
386
|
+
// a request that FAILED — a gateway answering 200 with an SSE `error`
|
|
387
|
+
// frame, or a context-length rejection. In those cases the fallback
|
|
388
|
+
// re-issued the same oversized request in a different shape, and the
|
|
389
|
+
// resulting "LLM API returned no choices" replaced the real diagnosis.
|
|
390
|
+
// That is why a restored 1500-message session reported "no choices" three
|
|
391
|
+
// times and then errored: the genuine "context length exceeded" was
|
|
392
|
+
// discarded on the first attempt and could never be recognised by
|
|
393
|
+
// isContextOverflow(), so the compact-and-retry recovery never ran.
|
|
394
|
+
//
|
|
395
|
+
// Two exclusions, and the second one matters as much as the first:
|
|
396
|
+
//
|
|
397
|
+
// 1. A status that means the REQUEST ITSELF was rejected. Only then is
|
|
398
|
+
// re-issuing it without `stream` a different request rather than the
|
|
399
|
+
// same failing one. `400`/`404`/`405`/`422` are the shapes a provider
|
|
400
|
+
// uses to say "no streaming here" — deliberately NOT the whole 4xx
|
|
401
|
+
// range, because 408/425/429 are transient: replaying those against an
|
|
402
|
+
// already-saturated provider just doubles the request count.
|
|
403
|
+
// 2. A THROWN error, as opposed to a request that simply produced no
|
|
404
|
+
// chunks. A 200 that streams zero chunks and closes cleanly is a
|
|
405
|
+
// broken/flaky proxy, and that is precisely the case the non-streaming
|
|
406
|
+
// retry rescues. Requiring a 4xx here would have deleted that safety
|
|
407
|
+
// net — a regression this file's own tests caught.
|
|
408
|
+
//
|
|
409
|
+
// So the fallback runs when the request was rejected outright, OR when it
|
|
410
|
+
// completed without error and without a single chunk. Everything else —
|
|
411
|
+
// 5xx, an error frame, a mid-stream failure — is the ANSWER, rethrown
|
|
412
|
+
// untouched so overflow recovery can see it.
|
|
413
|
+
const status = err?.status;
|
|
414
|
+
const rejectedByProvider = typeof status === "number" && STREAM_REJECTED_STATUS.has(status);
|
|
415
|
+
const emptyStream = err?.emptyStream === true;
|
|
416
|
+
if (!gotChunk && !signal?.aborted && (rejectedByProvider || emptyStream)) {
|
|
294
417
|
return chat(cfg, messages, tools, signal, onDelta);
|
|
418
|
+
}
|
|
295
419
|
// user interrupt: hand back whatever streamed so far so the harness can
|
|
296
420
|
// keep the partial output visible instead of losing it
|
|
297
421
|
if (signal?.aborted && (text || reasoning)) {
|
package/dist/agent/tools.js
CHANGED
|
@@ -182,7 +182,13 @@ function startBackgroundShell(cmd, ctx) {
|
|
|
182
182
|
* Wording varies by provider (OpenRouter/OpenAI/Anthropic/local servers). */
|
|
183
183
|
export function isContextOverflow(err) {
|
|
184
184
|
const msg = String(err?.message ?? "").toLowerCase();
|
|
185
|
-
return (/context (length|window|limit)|too many tokens|prompt is too long|max.{0,12}tokens.{0,20}(exceed|surpass)|request too large|payload too large|exceeds.{0,20}limit/.test(msg) ||
|
|
185
|
+
return (/context (length|window|limit)|too many tokens|prompt is too long|max.{0,12}tokens.{0,20}(exceed|surpass)|request too large|payload too large|exceeds.{0,20}limit/.test(msg) ||
|
|
186
|
+
/\b(400|413)\b.*token/.test(msg) ||
|
|
187
|
+
// #50: some gateways answer an over-long request with HTTP 200 and put the
|
|
188
|
+
// real diagnosis in the body, which llm.ts now surfaces after the "no
|
|
189
|
+
// choices" prefix. Without this the compact-and-retry recovery never
|
|
190
|
+
// recognised it and the turn simply failed.
|
|
191
|
+
/no choices:[\s\S]{0,300}context (length|window|limit)/.test(msg));
|
|
186
192
|
}
|
|
187
193
|
/** Any background shell still running for this workspace? The auto-continue
|
|
188
194
|
* loop consults this before nudging a finished round: an agent that parked
|
|
@@ -709,10 +715,43 @@ export function hashLine(content) {
|
|
|
709
715
|
}
|
|
710
716
|
/** `LINE:HASH| content` for each line, 1-indexed */
|
|
711
717
|
export function renderHashlines(lines, startLine = 1) {
|
|
712
|
-
|
|
718
|
+
// same trailing-newline rule as renderGutter (#7): a file ending in "\n"
|
|
719
|
+
// splits into a final "" that is not a line, and an anchor for it is a lie
|
|
720
|
+
// the model can copy into an edit that then never verifies
|
|
721
|
+
const body = lines.length && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
|
|
722
|
+
return body
|
|
713
723
|
.map((l, i) => `${startLine + i}:${hashLine(l)}| ${l}`)
|
|
714
724
|
.join("\n");
|
|
715
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Width of the `N| ` gutter for a page, so every line in it lines up.
|
|
728
|
+
*
|
|
729
|
+
* #7: the prefix had no padding, so the gutter silently widened at line 1000
|
|
730
|
+
* and the whole block jumped sideways mid-file — the exact "unfriendly line
|
|
731
|
+
* numbers" complaint. Padding to the widest number IN THE PAGE is enough: a
|
|
732
|
+
* page is a bounded window, and aligning to the file's total length would
|
|
733
|
+
* push every line of a 20-line read of a 500k-line file 6 characters right.
|
|
734
|
+
*/
|
|
735
|
+
export function gutterWidth(startLine, count) {
|
|
736
|
+
return String(Math.max(0, startLine - 1) + count).length;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* The default `N| ` display, with a stable gutter width and no phantom line.
|
|
740
|
+
*
|
|
741
|
+
* #7, second half: the prefix was added AFTER `text.split("\n")`, so a file
|
|
742
|
+
* ending in a newline produced a final numbered row for a line that does not
|
|
743
|
+
* exist. A model that copies the tail verbatim then hands `edit_file` an
|
|
744
|
+
* old_text with a phantom line in it, and the edit fails for no visible reason.
|
|
745
|
+
* A trailing empty element is dropped, which is what the reader means.
|
|
746
|
+
*/
|
|
747
|
+
export function renderGutter(lines, startLine = 1) {
|
|
748
|
+
// a trailing newline splits into a final "" — not a line of the file
|
|
749
|
+
const body = lines.length && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
|
|
750
|
+
if (!body.length)
|
|
751
|
+
return "";
|
|
752
|
+
const w = gutterWidth(startLine, body.length);
|
|
753
|
+
return body.map((l, i) => `${String(startLine + i).padStart(w)}| ${l}`).join("\n");
|
|
754
|
+
}
|
|
716
755
|
/** parse a `LINE:HASH` anchor; returns null when the text is not an anchor */
|
|
717
756
|
export function parseAnchor(anchor) {
|
|
718
757
|
const m = /^\s*(\d+):([0-9a-z]{4,8}| {4})\s*$/.exec(anchor);
|
|
@@ -913,7 +952,7 @@ export const TOOLS = [
|
|
|
913
952
|
}
|
|
914
953
|
const parts = regions.map(([s, e]) => idMode === "hash"
|
|
915
954
|
? renderHashlines(lines.slice(s, e + 1), s + 1)
|
|
916
|
-
: lines.slice(s, e + 1)
|
|
955
|
+
: renderGutter(lines.slice(s, e + 1), s + 1));
|
|
917
956
|
let result = parts.join("\n--\n");
|
|
918
957
|
if (idxs.length > page.length || off > 0)
|
|
919
958
|
result += `\n(${off + 1}–${off + page.length} of ${idxs.length} matches)`;
|
|
@@ -924,8 +963,11 @@ export const TOOLS = [
|
|
|
924
963
|
off = off < 0 ? Math.max(0, lines.length + off) : Math.max(0, off - 1);
|
|
925
964
|
const lim = num(args.limit, 2000);
|
|
926
965
|
const pageLines = lines.slice(off, off + lim);
|
|
927
|
-
const slice = idMode === "hash" ? renderHashlines(pageLines, off + 1) : pageLines
|
|
928
|
-
|
|
966
|
+
const slice = idMode === "hash" ? renderHashlines(pageLines, off + 1) : renderGutter(pageLines, off + 1);
|
|
967
|
+
// the phantom trailing line is dropped for the count too, or a file that
|
|
968
|
+
// ends in a newline always reports one line more than it has
|
|
969
|
+
const total = lines.length && lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
970
|
+
const more = off + lim < total ? `\n... (${total - off - lim} more lines)` : "";
|
|
929
971
|
return { ok: true, result: slice + more };
|
|
930
972
|
},
|
|
931
973
|
},
|
|
@@ -1090,12 +1132,27 @@ export const TOOLS = [
|
|
|
1090
1132
|
// already proved which lines it means, so a block whose content occurs
|
|
1091
1133
|
// twice must still edit the anchored occurrence rather than fail the
|
|
1092
1134
|
// uniqueness check (#4).
|
|
1135
|
+
// The hashline path splits on "\n" to index by resolved line number, which
|
|
1136
|
+
// leaves a trailing "\r" on every line of a CRLF file. Rejoining with
|
|
1137
|
+
// "\n" then DROPS all of them, so a single edit silently rewrote a
|
|
1138
|
+
// Windows file's line endings: the replaced line arrived with the model's
|
|
1139
|
+
// "\n" while every untouched line kept its "\r\n". The file still parsed
|
|
1140
|
+
// and the edit "succeeded", so nothing reported it — but the next
|
|
1141
|
+
// checkout diff shows the whole file changed, and tools that normalise on
|
|
1142
|
+
// EOL (git, prettier, formatters) now rewrite it.
|
|
1143
|
+
//
|
|
1144
|
+
// So the EOL is detected ONCE from the untouched text and re-applied to
|
|
1145
|
+
// the replacement, which also keeps a CRLF file CRLF when the model sends
|
|
1146
|
+
// LF-separated new_text (the overwhelmingly common case — models never
|
|
1147
|
+
// emit \r).
|
|
1148
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
1093
1149
|
const out = hashSpan
|
|
1094
1150
|
? text
|
|
1095
1151
|
.split("\n")
|
|
1096
1152
|
.slice(0, hashSpan.start)
|
|
1097
|
-
.concat(newText.split("\n"), text.split("\n").slice(hashSpan.end))
|
|
1153
|
+
.concat(newText.replace(/\r\n/g, "\n").split("\n"), text.split("\n").slice(hashSpan.end))
|
|
1098
1154
|
.join("\n")
|
|
1155
|
+
.replace(/(?<!\r)\n/g, eol)
|
|
1099
1156
|
: replaceAll
|
|
1100
1157
|
? text.split(oldText).join(newText)
|
|
1101
1158
|
: text.replace(oldText, newText);
|
package/dist/log/events.js
CHANGED
|
@@ -238,3 +238,50 @@ async function readWindow(fh, size, bytes) {
|
|
|
238
238
|
}
|
|
239
239
|
return { events, bytes: buf.length };
|
|
240
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Keep one branch's rows out of a timeline, optionally including the history it
|
|
243
|
+
* inherited before it forked.
|
|
244
|
+
*
|
|
245
|
+
* #38. A fork is a new timeline, but the agent's CONTEXT on it still includes
|
|
246
|
+
* everything before the fork point — `rebuildMessagesFrom` walks the parent
|
|
247
|
+
* chain across branch boundaries precisely so a restarted fork keeps its
|
|
248
|
+
* history. Filtering the view by strict equality therefore showed *less* than
|
|
249
|
+
* the model actually reasons over, which is why a filtered fork looked like
|
|
250
|
+
* data loss even though nothing was missing from the log.
|
|
251
|
+
*
|
|
252
|
+
* `includeInherited` walks the parent links out of the branch's own events and
|
|
253
|
+
* keeps everything they reach, so the view matches the model's context. Strict
|
|
254
|
+
* remains the default: a caller that genuinely wants only a branch's own rows
|
|
255
|
+
* (counting, auditing) should not be handed inherited history silently.
|
|
256
|
+
*
|
|
257
|
+
* Pure and exported so the rule can be tested without an HTTP server.
|
|
258
|
+
*/
|
|
259
|
+
export function filterByBranch(events, branch, includeInherited = false) {
|
|
260
|
+
if (!includeInherited)
|
|
261
|
+
return events.filter((e) => e.branch === branch);
|
|
262
|
+
const byId = new Map(events.map((e) => [e.id, e]));
|
|
263
|
+
// every event belonging to the branch, plus everything reachable by `parent`
|
|
264
|
+
const wanted = new Set();
|
|
265
|
+
// A WORKLIST, not a loop over the growing set: iterating `wanted` while
|
|
266
|
+
// adding to it inside the same pass re-visits nodes and, on a parent cycle,
|
|
267
|
+
// never terminates.
|
|
268
|
+
const queue = [];
|
|
269
|
+
for (const e of events) {
|
|
270
|
+
if (e.branch !== branch)
|
|
271
|
+
continue;
|
|
272
|
+
if (wanted.has(e.id))
|
|
273
|
+
continue;
|
|
274
|
+
wanted.add(e.id);
|
|
275
|
+
queue.push(e.id);
|
|
276
|
+
}
|
|
277
|
+
while (queue.length) {
|
|
278
|
+
const cur = byId.get(queue.pop());
|
|
279
|
+
// walk up through the fork boundary — a fork's parent is the last event of
|
|
280
|
+
// the SOURCE branch, and that branch's history is inherited too
|
|
281
|
+
if (cur?.parent && !wanted.has(cur.parent)) {
|
|
282
|
+
wanted.add(cur.parent);
|
|
283
|
+
queue.push(cur.parent);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return events.filter((e) => wanted.has(e.id));
|
|
287
|
+
}
|
package/dist/server/api.js
CHANGED
|
@@ -51,7 +51,7 @@ async function listModels(baseUrl, apiKey) {
|
|
|
51
51
|
}
|
|
52
52
|
import path from "node:path";
|
|
53
53
|
import { bus } from "../bus.js";
|
|
54
|
-
import { readEvents, readEventsTail } from "../log/events.js";
|
|
54
|
+
import { readEvents, readEventsTail, filterByBranch } from "../log/events.js";
|
|
55
55
|
/** media categories the file tree can preview inline */
|
|
56
56
|
const IMAGE_EXT = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico", "avif"]);
|
|
57
57
|
const VIDEO_EXT = new Set(["mp4", "webm", "mov", "mkv", "avi", "m4v"]);
|
|
@@ -976,7 +976,7 @@ export function buildApp(master) {
|
|
|
976
976
|
}
|
|
977
977
|
let events = await readEventsCached(filePath);
|
|
978
978
|
if (branch)
|
|
979
|
-
events = events.
|
|
979
|
+
events = filterByBranch(events, branch, c.req.query("lineage") === "true");
|
|
980
980
|
// cursor pagination for older pages: everything strictly BEFORE this id
|
|
981
981
|
if (before) {
|
|
982
982
|
const idx = events.findIndex((e) => e.id === before);
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg-darkest:#1a1c22;--bg-dark:#232630;--bg-mid:#2b2e39;--bg-light:#343845;--fg:#dcdee4;--dim:#9298a5;--line:#1d2027;--acc:#5865f2;--ok:#3ba55d;--warn:#faa81a;--err:#ed4245;--tool:#3ba0c9;--term-bg:#0d0e12;--term-fg:#dcdee4;font-family:gg sans,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}[data-theme=midnight]{--bg-darkest:#0d0f18;--bg-dark:#12141f;--bg-mid:#1b1f31;--bg-light:#252a42;--fg:#dfe2f2;--dim:#8a90ad;--line:#0a0c14;--acc:#8b7cf7;--ok:#54c08a;--warn:#e5b567;--err:#ef6a6a;--tool:#6aa9e9;--term-bg:#0a0c13;--term-fg:#dfe2f2}[data-theme=tea]{--bg-darkest:#241c16;--bg-dark:#2e241c;--bg-mid:#3b2f24;--bg-light:#493b2d;--fg:#f0e4d2;--dim:#b39d82;--line:#1b140f;--acc:#d98e32;--ok:#7fa653;--warn:#e0b058;--err:#cf5b47;--tool:#c9975f;--term-bg:#1d1712;--term-fg:#ecdcc3}[data-theme=coffee]{--bg-darkest:#191410;--bg-dark:#211a14;--bg-mid:#30271e;--bg-light:#403527;--fg:#ede3d0;--dim:#a89478;--line:#120d07;--acc:#c68a4b;--ok:#8aa15c;--warn:#d9a441;--err:#c25450;--tool:#a67c52;--term-bg:#15100c;--term-fg:#e8dcc5}[data-theme=matcha]{--bg-darkest:#1b2217;--bg-dark:#232b1e;--bg-mid:#2f3a28;--bg-light:#3c4a33;--fg:#e8eeda;--dim:#9aab84;--line:#131a10;--acc:#8db64e;--ok:#6fae4e;--warn:#d8b13c;--err:#cd5c50;--tool:#5da88f;--term-bg:#151b11;--term-fg:#dfe8cb}[data-theme=matrix]{--bg-darkest:#060a06;--bg-dark:#0a0f0a;--bg-mid:#122116;--bg-light:#18301d;--fg:#c9f2cf;--dim:#6fa075;--line:#030603;--acc:#2fd558;--ok:#39c95f;--warn:#b8d977;--err:#ff6b6b;--tool:#57c99b;--term-bg:#030503;--term-fg:#3f7}[data-theme=sunset]{--bg-darkest:#1c1422;--bg-dark:#241b2b;--bg-mid:#33253e;--bg-light:#452f52;--fg:#f2e6ea;--dim:#ab93a8;--line:#150e1b;--acc:#f2784b;--ok:#63b46f;--warn:#eda63d;--err:#e25563;--tool:#b06ac9;--term-bg:#170f1d;--term-fg:#f0dfe4}[data-theme=light]{--bg-darkest:#eceef4;--bg-dark:#f6f7fa;--bg-mid:#e2e5ee;--bg-light:#fff;--fg:#24272f;--dim:#5d6470;--line:#d8dbe4;--acc:#4757d8;--ok:#2b8a4b;--warn:#b97a0f;--err:#d83c3f;--tool:#2278a8;--term-bg:#fff;--term-fg:#24272f}[data-theme=strawberry]{--bg-darkest:#fbe4ea;--bg-dark:#fdf0f3;--bg-mid:#f7d4de;--bg-light:#fffafb;--fg:#4a2733;--dim:#96707e;--line:#f0ccd6;--acc:#e05575;--ok:#4d9e63;--warn:#c98a2d;--err:#d43d51;--tool:#b05687;--term-bg:#fff5f8;--term-fg:#4a2733}[data-theme=ramune]{--bg-darkest:#ddeff7;--bg-dark:#ecf7fb;--bg-mid:#c9e7f4;--bg-light:#fbfeff;--fg:#173342;--dim:#5b7d90;--line:#c2e2ef;--acc:#1d86ae;--ok:#3a9e6e;--warn:#cf9a2a;--err:#d64f5c;--tool:#3a9db8;--term-bg:#f4fbfd;--term-fg:#173342}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg-dark);color:var(--fg);font-size:15px}.layout{grid-template-columns:250px 1fr 370px;height:100dvh;display:grid;overflow:hidden}.layout.right-hidden{grid-template-columns:250px 1fr}.layout.right-hidden .rightbar{display:none}@media (width<=1100px){.layout,.layout.right-hidden{grid-template-columns:220px 1fr}.layout.right-hidden .rightbar,.layout .rightbarbackdrop{display:block}.rightbar .rightbarhead{z-index:1;display:flex;position:sticky;top:0}.rightbar{z-index:5;width:min(420px,92vw);box-shadow:none;transition:transform .18s,box-shadow .18s;position:fixed;top:0;bottom:0;right:0;transform:translate(100%)}.rightbar.open{transform:none;box-shadow:-8px 0 24px #0007}}.sidebar{background:var(--bg-darkest);flex-direction:column;padding:10px 8px;display:flex;overflow:hidden auto}.themepop{background:var(--bg-mid);border:1px solid var(--bg-light);border-radius:8px;flex-direction:column;gap:8px;padding:10px;display:flex;position:static}.trow{color:var(--fg);cursor:pointer;align-items:center;gap:6px;font-size:12.5px;display:flex}.trow select{background:var(--bg-darkest);color:var(--fg);border:none;border-radius:5px;outline:none;padding:3px 6px}.themegrid{grid-template-columns:1fr 1fr;gap:4px;display:grid}.themebtn{background:var(--bg-light);color:var(--fg);cursor:pointer;text-align:left;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;min-width:0;padding:5px 8px;font-size:12px;display:flex;overflow:hidden}.themebtn>:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.themebtn:hover{filter:brightness(1.15)}.themebtn.on{border-color:var(--acc)}.swdots{flex-shrink:0;gap:2px;display:inline-flex}.swdots i{border-radius:50%;width:9px;height:9px;display:inline-block;box-shadow:inset 0 0 0 1px #0004}.agent-list{flex:1;min-width:0;min-height:0;overflow-y:auto}.wsheader{letter-spacing:.04em;text-transform:uppercase;color:var(--dim);cursor:pointer;-webkit-user-select:none;user-select:none;border-top:1px solid var(--line);align-items:center;gap:6px;margin-top:6px;padding:10px 10px 4px;font-size:11px;font-weight:600;display:flex}.wsheader:first-child{border-top:none;margin-top:0}.wsheader:hover{color:var(--fg)}.wsheader .wscaret{flex-shrink:0;width:12px;font-size:9px}.wsheader .wscount{background:var(--bg-light);color:var(--dim);letter-spacing:0;border-radius:999px;flex-shrink:0;margin-left:auto;padding:0 6px;font-size:10px;font-weight:600;line-height:15px}.chatheader{letter-spacing:.03em;color:var(--dim);cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:6px;padding:4px 10px 2px 22px;font-size:11px;font-weight:600;display:flex}.chatheader:hover{color:var(--fg)}.chatheader .wscaret{flex-shrink:0;width:12px;font-size:9px}.agent-item{cursor:pointer;color:var(--dim);border-radius:6px;align-items:center;gap:8px;min-width:0;margin-bottom:2px;padding:7px 10px;display:flex}.agent-item>span:not(.dot):not(.caret):not(.caret-spacer):not(.notifbadge):not(.subcount):not(.subtag):not(.ghosttag):not(.mini-cron):not(.goaldone){text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.agent-item .subtag{color:var(--dim);font-size:10px}.agent-item .goaldone{flex-shrink:0}.caret{cursor:pointer;width:12px;color:var(--dim);text-align:center;-webkit-user-select:none;user-select:none;flex-shrink:0;font-size:9px}.caret:hover{color:var(--fg)}.caret-spacer{flex-shrink:0;width:12px}.agent-item:hover{background:var(--bg-mid)}.agent-item.sel{background:var(--bg-mid);color:var(--fg)}.dot{border-radius:50%;flex-shrink:0;width:9px;height:9px}.dot.running{background:var(--ok);box-shadow:0 0 6px var(--ok)}.dot.waiting{background:var(--acc);box-shadow:0 0 6px var(--acc);animation:1.6s ease-in-out infinite pulse}.badge.waiting{color:var(--acc)}.dot.idle{background:var(--warn)}.dot.stopped{background:var(--dim)}.dot.error{background:var(--err);box-shadow:0 0 6px var(--err)}.sidebar .metrics{color:var(--dim);border-top:1px solid var(--line);margin-top:12px;padding:8px 10px;font-size:11px;line-height:1.7}.sidebar-footer{border-top:1px solid var(--line);flex-direction:column;gap:2px;margin-top:auto;padding-top:8px;display:flex}.footer-row{align-items:center;gap:8px;display:flex}.footer-row .conn{margin-left:0}.sidebar-footer .metrics{border-top:none;margin-top:4px;padding:0}.brand{color:var(--fg);align-items:center;gap:8px;font-size:14px;display:flex}.version{color:var(--dim);font-family:ui-monospace,Menlo,monospace;font-size:11px}.channel{flex-direction:column;min-width:0;min-height:0;display:flex;position:relative;overflow:hidden}.chan-head{border-bottom:2px solid var(--line);background:var(--bg-dark);flex-shrink:0;align-items:center;gap:10px;padding:10px 16px;display:flex}.chan-head .hash{color:var(--dim);font-size:20px}.chan-head .title{font-weight:700}.chan-head .sub{color:var(--dim);text-overflow:ellipsis;white-space:nowrap;margin-left:8px;font-size:12px;overflow:hidden}.badge{background:var(--bg-light);vertical-align:middle;border-radius:10px;padding:1px 8px;font-size:11px}.badge.running{color:var(--ok)}.badge.error{color:var(--err)}.badge.idle{color:var(--warn)}.badge.done{color:var(--acc)}.badge.queued{color:var(--warn);white-space:nowrap;background:#faa81a1a;border:1px solid #faa81a44}.feed{overscroll-behavior:contain;flex:1;min-height:0;padding:14px 0 8px;overflow-y:auto}.msg{gap:14px;padding:3px 18px;display:flex}.msg:hover{background:#ffffff08}.msg.pending{opacity:.75}.msg.pending .ts{color:var(--warn);font-weight:600}.msg.pending .avatar{animation:1.6s ease-in-out infinite pulse}.msg.pending .editbtn.cancel{color:var(--err)}.msg.withdrawn{opacity:.55}.msg.withdrawn .msg-body{text-decoration:line-through;-webkit-text-decoration-color:var(--muted);text-decoration-color:var(--muted)}.msg.withdrawn .msg-head{text-decoration:none}.msg.withdrawn .ts{color:var(--muted);font-weight:600}.msg.grouped{padding-top:0}.avatar{border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;width:38px;height:38px;margin-top:2px;font-size:17px;display:flex}.msg-body{flex:1;min-width:0}.msg-head{align-items:baseline;gap:8px;display:flex}.actor{color:var(--warn);white-space:nowrap;font-size:11px}.runcard{flex-direction:column;gap:8px;display:flex}.statgrid{grid-template-columns:repeat(3,1fr);gap:6px;display:grid}.stat{background:var(--bg-mid);border-radius:6px;flex-direction:column;gap:1px;min-width:0;padding:5px 8px;display:flex}.stat span{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;font-size:10px}.stat b{text-overflow:ellipsis;font-size:15px;overflow:hidden}.statrow{align-items:center;gap:8px;min-width:0;font-size:12.5px;display:flex}.statrow .k{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;flex-shrink:0;width:auto;min-width:58px;font-size:10px}.pill{background:var(--bg-mid);white-space:nowrap;border-radius:999px;margin-left:auto;padding:1px 8px;font-size:11px}.pill.ok{color:var(--ok)}.pill.warn{color:var(--warn)}.pill.crit{color:var(--err);background:#ed42451a}.bartrack{background:var(--bg-light);border-radius:3px;height:6px;margin-top:2px;overflow:hidden}.barfill{background:var(--ok);border-radius:3px;min-width:2px;height:100%;transition:width .3s}.barfill.warn{background:var(--warn)}.barfill.crit{background:var(--err)}.author{font-size:14.5px;font-weight:600}.ts{color:var(--dim);font-size:11px}.content{white-space:pre-wrap;word-break:break-word;line-height:1.45}.content p{margin:2px 0}.content pre{background:var(--bg-darkest);border-radius:6px;padding:8px;overflow-x:auto}.content code{background:var(--bg-darkest);border-radius:4px;padding:1px 4px;font-size:13px}.content pre code{background:0 0;padding:0}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{margin:8px 0 2px;font-size:15px}.content h1{font-size:16.5px}.content h2{font-size:15.5px}.content blockquote{border-left:3px solid var(--bg-light);color:var(--dim);background:#ffffff05;border-radius:0 4px 4px 0;margin:4px 0;padding:2px 10px}.content blockquote p{margin:2px 0}.content hr{border:none;border-top:1px solid var(--bg-light);margin:8px 0}.content del{color:var(--dim)}.content img{border-radius:6px;max-width:100%}.content li.task{margin-left:-18px;list-style:none}.content li:has(>input[type=checkbox]){margin-left:-18px;list-style:none}.content li input[type=checkbox]{accent-color:var(--ok);margin-right:6px}input[type=checkbox]{accent-color:var(--acc)}.content a{color:var(--tool);text-decoration:underline;-webkit-text-decoration-color:color-mix(in srgb, var(--tool) 45%, transparent);text-decoration-color:color-mix(in srgb, var(--tool) 45%, transparent);text-underline-offset:2px}.content a:hover{filter:brightness(1.15)}.themebtn.on{background:var(--acc);color:#fff}.trow select:focus{outline:1px solid var(--acc)}.tbl{margin:6px 0;overflow-x:auto}.tbl table{border-collapse:collapse;font-size:13px}.tbl th,.tbl td{border:1px solid var(--bg-light);text-align:left;padding:3px 9px}.tbl th{background:var(--bg-darkest);font-weight:600}.embed{border-left:3px solid var(--tool);background:var(--bg-darkest);border-radius:4px;margin-top:3px;padding:6px 10px;font-size:13.5px}.embed.fail{border-color:var(--err)}.embed.warn-embed{border-color:var(--warn)}.embed.warn-embed .mono{color:var(--warn)}.embed.done{border-color:var(--ok)}.embed.running{border-color:var(--warn)}.embed.running summary .meta:after{content:"⋯";color:var(--warn);margin-left:6px;animation:1s step-end infinite blink;display:inline-block}.embed summary{gap:8px}.embed summary .mono{font-family:ui-monospace,Menlo,monospace}.toolbody{background:var(--bg-darkest);white-space:pre-wrap;word-break:break-word;border-radius:4px;max-height:340px;margin:4px 0;padding:6px 8px;font-size:12.5px;overflow-y:auto}.toolbody.del{border-left:2px solid var(--err);color:#f0a5a6;background:#ed424514}.toolbody.add{border-left:2px solid var(--ok);color:#9fd8ae;background:#3ba55d14}.toolbody.patch{padding:4px 8px}.toolbody.patch .pline{white-space:pre-wrap;word-break:break-word}.toolbody.patch .pline.add{color:#9fd8ae;border-left:2px solid var(--ok);background:#3ba55d14;padding-left:4px}.toolbody.patch .pline.del{color:#f0a5a6;border-left:2px solid var(--err);background:#ed424514;padding-left:4px}.toolbody.patch .pline.meta{color:var(--dim)}.embed.question{border-color:var(--acc);background:#5865f20f}.qopts{flex-wrap:wrap;gap:6px;margin-top:6px;display:flex}.qopt{background:var(--acc);color:#fff;cursor:pointer;border:none;border-radius:999px;padding:3px 12px;font-size:12.5px}.qopt:hover{filter:brightness(1.15)}.qopt:disabled{opacity:.45;cursor:default;filter:none}.embed.question.answered{opacity:.75}.qfree{gap:6px;margin-top:8px;display:flex}.qfree input{background:var(--bg-mid);border:1px solid var(--bg-light);color:var(--fg);border-radius:6px;outline:none;flex:1;padding:4px 10px;font-size:13px}.qfree input:focus{border-color:var(--acc)}.qfree button{background:var(--bg-light);color:var(--fg);cursor:pointer;border:none;border-radius:6px;padding:4px 12px;font-size:12.5px}.qfree button:disabled{opacity:.45;cursor:default}.bashelapsed{color:var(--warn);margin-left:8px;font-size:11px}.embed.question .meta{margin-top:6px}.embed.decision{border-color:var(--acc);background:#5865f20f}.embed.decision summary .meta{margin-left:6px}.embed.decision .content{margin-top:3px;font-size:13px}.embed summary{cursor:pointer;-webkit-user-select:none;user-select:none;list-style-position:outside}.embed summary::marker{color:var(--dim)}.embed[open] summary{margin-bottom:4px}.embed .mono{white-space:pre-wrap;word-break:break-word;max-height:340px;font-family:ui-monospace,Menlo,monospace;font-size:12.5px;overflow-y:auto}.embed .meta{color:var(--dim);margin-top:3px;font-size:11px}.reasoning{border-left:3px dashed var(--bg-light);color:var(--dim);background:#ffffff05;border-radius:4px;margin:2px 0;padding:2px 10px;font-size:12.5px}.reasoning summary{cursor:pointer;-webkit-user-select:none;user-select:none;opacity:.75}.reasoning summary:hover{opacity:1;color:var(--fg)}.reasoning[open] summary{margin-bottom:4px}.reasoning .mono{white-space:pre-wrap;word-break:break-word;max-height:260px;overflow-y:auto}.msg.live .avatar{animation:1.6s ease-in-out infinite pulse}.cursor{color:var(--acc);animation:1s step-end infinite blink}.interrupted{color:var(--warn);margin-top:3px;font-size:11.5px}@keyframes blink{50%{opacity:0}}@keyframes pulse{50%{opacity:.55}}.conn{background:var(--err);vertical-align:middle;width:8px;height:8px;box-shadow:0 0 6px var(--err);border-radius:50%;display:inline-block}.conn.ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.copybtn{border:1px solid var(--bg-light);color:var(--dim);cursor:pointer;background:0 0;border-radius:4px;flex-shrink:0;padding:0 5px;font-size:11px;line-height:16px}.copybtn:hover{color:var(--fg);filter:brightness(1.3)}.embed summary{align-items:center;gap:6px;display:flex}.embed summary .iconmeta{flex-shrink:0;font-size:11px}.toolcopy{flex-shrink:0;gap:4px;margin-left:auto;display:flex}.msgfoot{color:var(--dim);align-items:center;gap:6px;margin-top:4px;font-size:11px;display:flex}.imgstage{flex-wrap:wrap;gap:6px;margin-bottom:6px;display:flex}.imgchip{display:inline-flex;position:relative}.imgchip img{object-fit:cover;border:1px solid var(--line);border-radius:6px;width:64px;height:64px}.imgchip .imgx{background:var(--err);color:#fff;cursor:pointer;border:none;border-radius:50%;justify-content:center;align-items:center;width:16px;height:16px;padding:0;font-size:9px;line-height:1;display:inline-flex;position:absolute;top:-5px;right:-5px}.promptimgs{flex-wrap:wrap;gap:6px;margin-top:4px;display:flex}img.promptimg{border:1px solid var(--line);border-radius:6px;max-width:180px;max-height:120px}.bgjob-note{color:var(--warn)}.bgjob-note b{color:var(--fg);font-family:ui-monospace,Menlo,monospace}details.reasoning .thinkscroll{white-space:pre-wrap;word-break:break-word;scrollbar-gutter:stable;max-height:220px;overflow-y:auto}.thinktimer{color:var(--dim);margin-left:6px;font-size:11px;font-weight:400}.termdrawer{background:var(--term-bg);border-top:2px solid var(--line);flex-direction:column;display:flex}.termgrip{cursor:ns-resize;background:var(--bg-light);opacity:.6;border-bottom:1px solid var(--line);height:6px}.termgrip:hover{opacity:1}.termbar{color:var(--dim);border-bottom:1px solid var(--line);background:var(--bg-darkest);justify-content:space-between;align-items:center;padding:4px 10px;font-size:11.5px;display:flex}.termtabs{flex:1;align-items:center;gap:2px;min-width:0;display:flex;overflow-x:auto}.termtab{cursor:pointer;color:var(--dim);white-space:nowrap;background:var(--bg-mid);border-radius:5px;align-items:center;gap:6px;padding:2px 8px;font-size:11.5px;display:inline-flex}.termtab.active{color:var(--fg);outline:1px solid var(--acc)}.termtab:hover{filter:brightness(1.15)}.tabx{color:inherit;opacity:.5;cursor:pointer;background:0 0;border:none;padding:0;font-size:10px}.tabx:hover{opacity:1;color:var(--err)}.termbody{background:var(--term-bg);flex:1;gap:4px;min-height:0;padding:4px;display:flex}.termbody.split .termpane{width:50%}.termpane{background:var(--term-bg);border-radius:6px;flex:1;min-width:0;position:relative;overflow:hidden}.termpane:not(.focused){opacity:.9}.termpane.focused{box-shadow:inset 0 0 0 2px var(--acc)}.termhost{position:absolute;inset:0}.termsession{padding:4px 6px;position:absolute;inset:0}.termsplit{background:var(--line);cursor:col-resize;border-radius:2px;flex-shrink:0;width:3px}.termempty{color:var(--dim);place-items:center;font-size:12px;display:grid;position:absolute;inset:0}.jump{background:var(--acc);color:#fff;cursor:pointer;z-index:2;border:none;border-radius:999px;padding:6px 14px;font-size:12.5px;font-weight:600;position:absolute;top:-34px;left:50%;transform:translate(-50%);box-shadow:0 4px 14px #0008}body:has(.termdrawer) .jump{top:auto;bottom:calc(var(--termh,300px) + 56px);left:50%}.divider-msg{color:var(--dim);align-items:center;gap:10px;padding:4px 18px;font-size:11.5px;display:flex}.divider-msg:before,.divider-msg:after{content:"";background:var(--line);flex:1;height:1px}.divider-msg.err{color:var(--err)}.divider-msg.compacting{color:var(--acc);animation:1.6s ease-in-out infinite pulse}.goalline{color:var(--dim);padding:4px 18px;font-size:11.5px}.goalline-label{color:var(--dim);display:block}.goalline.warn .goalline-label{color:var(--warn)}.goalline.err .goalline-label{color:var(--err)}.goalline .card{text-align:left;margin-top:6px}.day-divider{align-items:center;gap:10px;padding:14px 18px 6px;display:flex}.day-divider:before,.day-divider:after{content:"";background:var(--bg-light);flex:1;height:1px}.day-divider span{color:var(--dim);font-size:11px}.composer{padding:0 16px 18px;position:relative}.composer form{background:var(--bg-light);border-radius:10px;align-items:flex-end;gap:8px;padding:10px 12px;display:flex}.composer textarea{color:var(--fg);font:inherit;resize:none;background:0 0;border:none;outline:none;flex:1;max-height:160px;line-height:1.4;overflow-y:auto}.cmds{background:var(--bg-mid);border:1px solid var(--bg-light);z-index:3;border-radius:8px;position:absolute;bottom:calc(100% - 8px);left:16px;right:16px;overflow:hidden;box-shadow:0 -6px 20px #0007}.cmdrow{cursor:pointer;align-items:baseline;gap:10px;padding:7px 12px;font-size:13.5px;display:flex}.cmdrow:hover{background:var(--bg-light)}.cmdrow.active{background:var(--acc);color:#fff}.cmdrow.active .muted{color:#e6e9ff}.cmdrow .muted{font-size:12px}.composer button{background:var(--acc);color:#fff;cursor:pointer;border:none;border-radius:8px;padding:7px 14px;font-weight:600}.composer button:hover{opacity:.9}.composer label{color:var(--dim);white-space:nowrap;align-items:center;gap:4px;font-size:12px;display:flex}.hint{color:var(--dim);margin-top:5px;font-size:11px}.rightbarbackdrop{z-index:4;background:#0004;display:none;position:fixed;inset:0}.rightbarhead{background:var(--bg-dark);border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:10px 14px;display:none}.rightbarhead .muted{text-transform:uppercase;letter-spacing:.04em;font-size:11px}.rightbar{background:var(--bg-dark);border-left:2px solid var(--line);scrollbar-gutter:stable;flex-direction:column;min-height:0;padding:14px;font-size:13px;display:flex;overflow-y:auto}.rightbar>*{flex-shrink:0}.rightbar h3{text-transform:uppercase;letter-spacing:.04em;color:var(--dim);margin:14px 0 6px;font-size:11px}.rightbar h3:first-of-type{margin-top:0}.card{background:var(--bg-darkest);white-space:pre-wrap;word-break:break-word;border-radius:8px;max-height:200px;padding:10px;overflow-y:auto}.card.runcard{max-height:none;overflow-y:visible}.muted{color:var(--dim)}.prog .progrow{gap:8px;margin-bottom:4px;display:flex}.prog .progrow b{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;flex-shrink:0;min-width:62px;padding-top:2px;font-size:10px}.embed .progrow{gap:8px;margin-bottom:4px;display:flex}.embed .progrow b{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;flex-shrink:0;min-width:62px;padding-top:2px;font-size:10px}.embed .progrow span{min-width:0}.inline-md{display:inline}.inline-md>:first-child{margin-top:0}.inline-md>:last-child{margin-bottom:0}.prog .progrow.warn span{color:var(--err)}.msg-head .editbtn{color:var(--dim);cursor:pointer;opacity:0;background:0 0;border:none;padding:0 2px;font-size:11px;transition:opacity .12s}.msg:hover .msg-head .editbtn,.msg.pending .msg-head .editbtn{opacity:1}.msg-head .editbtn:focus-visible{opacity:1;outline:2px solid var(--acc);outline-offset:1px}.msg-head .editbtn:hover{color:var(--acc)}.branch-row{color:var(--dim);cursor:pointer;justify-content:space-between;padding:3px 0;font-size:12px;display:flex}.branch-row:hover,.branch-row.cur{color:var(--fg)}.mini-cron{opacity:.75;margin-left:auto;font-size:10px}.sched-row{cursor:pointer;border-radius:6px;margin-bottom:2px;padding:6px 8px}.sched-row:hover,.sched-row.cur{background:var(--bg-mid)}.sched-top{align-items:baseline;gap:6px;font-size:12.5px;display:flex}.sched-row .sched-meta{color:var(--dim);margin-top:2px;font-size:11px}.sched-row .sched-prompt{color:var(--dim);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:11.5px;overflow:hidden}.badge.cron{color:var(--tool);white-space:nowrap;background:#3ba0c91a;border:1px solid #3ba0c944}.sesscard{flex-direction:column;gap:6px;font-size:12.5px;display:flex}.sessrow{align-items:center;gap:8px;display:flex}.filebox{background:var(--bg-darkest);border-radius:8px;height:260px;padding:4px;overflow-y:auto}.filerow{cursor:pointer;color:var(--fg);border-radius:4px;align-items:center;gap:6px;padding:3px 8px;font-size:12.5px;display:flex}.filerow:hover{background:var(--bg-mid)}.filerow .ficon{width:10px;color:var(--dim);text-align:center;flex-shrink:0;font-size:9px}.filerow.isdir .fname{font-weight:600}.fname{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.fsize{color:var(--dim);flex-shrink:0;margin-left:auto;font-size:10px}.sessrow .k{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;flex-shrink:0;width:74px;font-size:10px}.ellip{text-overflow:ellipsis;white-space:nowrap;text-align:left;direction:rtl;overflow:hidden}.modelbox{background:var(--bg-darkest);border-radius:8px;flex-direction:column;gap:6px;padding:10px;display:flex}.modelbox select,.modelbox input[type=text]{background:var(--bg-mid);color:var(--fg);font:inherit;border:none;border-radius:6px;outline:none;min-width:0;padding:6px 8px}.modelbox button{background:var(--acc);color:#fff;cursor:pointer;white-space:nowrap;border:none;border-radius:6px;padding:6px 10px;font-size:12.5px;font-weight:600}.modelbox button:hover{opacity:.9}.modelbox .meta{color:var(--dim);font-size:11px}.btnrow{gap:6px;margin:8px 0;display:flex}.btnrow button{background:var(--bg-light);color:var(--fg);cursor:pointer;border:none;border-radius:6px;padding:6px 10px;font-size:13px}.btnrow button:hover{filter:brightness(1.2)}.btnrow .runbtn{background:var(--ok);color:#fff;font-weight:600}.btnrow .runbtn,.btnrow .danger{min-width:74px}.btnrow .danger{background:var(--err);color:#fff;font-weight:600}.ctrlrow{align-items:center;gap:8px;margin-top:6px;font-size:12.5px;display:flex}.ctrlrow label{cursor:pointer;color:var(--fg);white-space:nowrap;align-items:center;gap:5px;display:flex}.ctrlrow .muted{font-size:11.5px}.goalstate{background:var(--bg-light);color:var(--fg);cursor:pointer;border:none;border-radius:6px;padding:3px 10px;font-size:12px}.goalstate.on.active{background:var(--ok);color:#fff;font-weight:600}.goalstate.on.done{background:var(--acc);color:#fff;font-weight:600}.goalstate.on.paused{background:var(--warn);color:#fff;font-weight:600}.goalstate:disabled{cursor:default;filter:none}.verifycard{border-left:3px solid var(--tool);margin-top:6px}.auditcard{border-left:3px solid var(--warn);margin-top:6px}.auditcard.ok{border-left-color:var(--ok)}.vtitle{color:var(--dim);text-transform:uppercase;letter-spacing:.04em;align-items:center;gap:6px;margin-bottom:4px;font-size:12px;font-weight:600;display:flex}.iconbtn{background:var(--bg-light);color:var(--fg);cursor:pointer;border:none;border-radius:6px;flex:none;justify-content:center;align-self:flex-end;align-items:center;width:26px;height:24px;padding:0;font-size:13px;display:inline-flex}.iconbtn-glyph{justify-content:center;align-items:center;width:100%;height:100%;line-height:1;display:inline-flex}.iconbtn:hover{filter:brightness(1.3)}.iconbtn.active{background:var(--acc);color:#fff}.overlay{z-index:10;background:#0009;place-items:center;display:grid;position:fixed;inset:0}.modal{background:var(--bg-mid);border-radius:10px;width:min(620px,92vw);max-height:88vh;padding:16px 18px;overflow-y:auto}.modal-head{justify-content:space-between;align-items:center;margin-bottom:12px;font-size:16px;display:flex}.modal label{color:var(--dim);flex-direction:column;gap:4px;font-size:12.5px;display:flex}.modal input[type=text],.modal input[type=number],.modal input[type=password],.modal input[type=url],.modal input[type=email],.modal select,.modal textarea{background:var(--bg-darkest);color:var(--fg);font:inherit;border:1px solid #0000;border-radius:6px;outline:none;min-width:0;padding:7px 9px}.modal input:focus,.modal select:focus,.modal textarea:focus{border-color:var(--acc)}.modal ::placeholder{color:#7c818f;opacity:1}.modal select option{background:var(--bg-darkest);color:var(--fg)}.modal input.compact{padding:5px 8px;font-size:12.5px}.modal textarea{resize:vertical}.w100{width:100%}.mono{font-family:ui-monospace,Menlo,monospace;font-size:13px}.modal fieldset{border:1px solid var(--bg-light);border-radius:8px;margin:0 0 4px;padding:10px}.modal fieldset legend{color:var(--dim);text-transform:uppercase;letter-spacing:.05em;padding:0 6px;font-size:11px}.cfgrow{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px;display:flex}.cfgrow input{background:var(--bg-darkest);border:1px solid var(--bg-light);color:var(--fg);border-radius:5px;flex:1;min-width:0;padding:5px 8px;font-size:12.5px}.cfgrow select{background:var(--bg-darkest);border:1px solid var(--bg-light);color:var(--fg);border-radius:5px;padding:5px 8px;font-size:12.5px}.cfgrow .cfgname{width:90px;color:var(--warn);flex:none;font-weight:600}.cfgrow button{background:var(--bg-mid);border:1px solid var(--bg-light);color:var(--fg);cursor:pointer;border-radius:5px;padding:4px 8px;font-size:12px}.cfgrow button:hover{background:var(--bg-light)}.cfgrow button.danger{background:var(--err);border-color:var(--err);color:#fff}.cfggrid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;display:grid}.cfggrid label,.cfgcol label{color:var(--dim);flex-direction:column;gap:3px;font-size:11px;display:flex}.cfggrid input{background:var(--bg-darkest);border:1px solid var(--bg-light);color:var(--fg);border-radius:5px;width:100%;padding:5px 8px}.cfgcol{margin-bottom:8px}.cfgcol textarea{background:var(--bg-darkest);border:1px solid var(--bg-light);color:var(--fg);font:inherit;border-radius:5px;width:100%;margin-top:4px;padding:5px 8px;font-size:12.5px}.presetbtn{background:var(--bg-light);color:var(--fg);cursor:pointer;border:1px solid #0000;border-radius:6px;flex:1;padding:7px 10px;font-size:13px}.presetbtn.active{background:var(--acc);color:#fff}.forkchip{white-space:nowrap;color:var(--dim);cursor:pointer;align-items:center;gap:4px;font-size:11.5px;display:flex}.dirlist{background:var(--bg-darkest);border-radius:6px;max-height:160px;padding:4px;overflow-y:auto}.direntry{cursor:pointer;border-radius:4px;padding:4px 8px;font-size:14px}.direntry:hover{background:var(--bg-light)}.filepreview{border-radius:8px;max-height:62vh;overflow:auto}.filetoolbar{gap:6px;margin-bottom:8px;display:flex}.filetoolbar button{background:var(--bg-light);color:var(--dim);cursor:pointer;border:none;border-radius:6px;padding:4px 12px;font-size:12.5px}.filetoolbar button:hover{color:var(--fg);filter:brightness(1.15)}.filetoolbar button.on{background:var(--acc);color:#fff;font-weight:600}.fileeditor{flex-direction:column;gap:8px;display:flex}.fileeditor textarea{background:var(--bg-darkest);min-height:55vh;max-height:65vh;color:var(--fg);border:1px solid var(--bg-light);white-space:pre;border-radius:8px;padding:10px;font-size:12.5px;line-height:1.45;overflow:auto}.filerow-actions{justify-content:flex-end;align-items:center;gap:10px;display:flex}.savebtn{background:var(--ok);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:5px 14px;font-size:13px;font-weight:600}.savebtn:disabled{opacity:.4;cursor:default}.mdpreview{background:var(--bg-darkest);border-radius:8px;max-height:62vh;padding:14px 16px;overflow:auto}.filepreview .shiki,.filepreview .shiki span{color:var(--shiki-light);font-family:ui-monospace,Menlo,monospace;font-size:12.5px}html[data-appearance=dark] .filepreview .shiki,html[data-appearance=dark] .filepreview .shiki span{color:var(--shiki-dark)}.filepreview .shiki{background-color:var(--shiki-dark-bg);white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;margin:0;padding:10px 12px;font-size:12.5px}html[data-appearance=light] .filepreview .shiki{background-color:var(--shiki-light-bg)}.notifbtn{position:relative}.notifbtn.has-unread{color:var(--acc)}.notifdot{background:var(--err);color:#fff;border-radius:999px;padding:2px 4px;font-size:9px;font-style:normal;font-weight:700;line-height:1;position:absolute;top:-4px;right:-4px}.brand-row{align-items:center;margin-top:2px;display:flex}.notifbackdrop{z-index:7;position:fixed;inset:0}.notifpanel{z-index:8;background:var(--bg-mid);border:1px solid var(--bg-light);border-radius:10px;width:min(360px,88vw);position:absolute;bottom:8px;left:8px;overflow:hidden;box-shadow:0 8px 30px #000a}.notifhead{border-bottom:1px solid var(--line);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);justify-content:space-between;align-items:center;padding:8px 12px;font-size:11px;display:flex}.notiflist{max-height:46vh;overflow-y:auto}.notifitem{border-bottom:1px solid var(--line);cursor:pointer;padding:8px 12px}.notifitem:last-child{border-bottom:none}.notifitem:hover{background:var(--bg-light)}.notifitem .nhead{align-items:baseline;gap:6px;min-width:0;display:flex}.notifitem .ntitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:13px;font-weight:600;overflow:hidden}.notifitem.error .ntitle{color:var(--err)}.notifitem.finish .ntitle{color:var(--ok)}.notifitem.question .ntitle{color:var(--acc)}.notifitem .nbody{color:var(--dim);word-break:break-word;margin-top:3px;font-size:12px}.subcount{color:var(--dim);background:var(--bg-light);white-space:nowrap;border-radius:999px;flex-shrink:0;padding:1px 7px;font-size:10px}.subcount.live{color:var(--ok)}.mediapreview{flex-direction:column;align-items:center;gap:8px;display:flex}.mediapreview img{background:var(--bg-darkest);border-radius:8px;max-width:100%;max-height:65vh}.mediapreview video{background:#000;border-radius:8px;max-width:100%;max-height:65vh}.mediapreview audio{width:100%}.composer{flex-direction:column;display:flex}.composer .hint{flex-shrink:0}.composer.maximized{z-index:5;background:var(--bg-dark);padding:16px 16px 10px;position:absolute;inset:0}.composer.maximized .hint{white-space:nowrap;text-overflow:ellipsis;flex-shrink:0;margin-top:8px;line-height:1.4;overflow:hidden}.composer.maximized:before{content:"";background:var(--bg-dark);z-index:-1;position:absolute;inset:0}.composer.maximized form{flex:1;align-items:stretch;min-height:0}.composer.maximized textarea{resize:none;flex:1;max-height:none;height:auto!important}.composer.maximized .maxbtn,.composer.maximized form>button[type=submit]{align-self:flex-end}.maxbtn{justify-content:center;align-items:center;font-size:12px;line-height:1;display:inline-flex;padding:0!important}.filerow.gitignored .fname,.filerow.gitignored .ficon,.filerow.gitignored .fsize{opacity:.38}.embed.compaction{border-left-color:var(--tool)}.embed.compaction summary .meta{color:var(--tool)}.avatar.ghosted{opacity:.35;transition:opacity .15s}.msg:hover .avatar.ghosted{opacity:1}.grouptime{display:none}.notifitem{opacity:1}.notifitem.read{opacity:.55}.notifitem.read .ntitle{font-weight:400}.nreadbtn{flex-shrink:0;width:auto;padding:0 6px;font-size:11px}.flash-target{outline:2px solid var(--acc);border-radius:8px;transition:outline-color .6s}.msg.flash-target{background:color-mix(in srgb, var(--acc) 12%, transparent)}.agent-item .ghost{opacity:.55}.ghosttag{flex-shrink:0;font-size:10px}.filetoggle{text-align:center;flex-shrink:0;width:26px;padding:0}h3 .filetoggle{margin-left:0}.notifbadge{color:var(--acc);background:var(--bg-light);white-space:nowrap;border-radius:999px;flex-shrink:0;margin-left:auto;padding:1px 6px;font-size:10px}.agent-item.sel .notifbadge{background:var(--bg-darkest)}.xterm{cursor:text;-webkit-user-select:none;user-select:none;position:relative}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{z-index:5;position:absolute;top:0}.xterm .xterm-helper-textarea{opacity:0;z-index:-5;white-space:nowrap;resize:none;border:0;width:0;height:0;margin:0;padding:0;position:absolute;top:0;left:-9999em;overflow:hidden}.xterm .composition-view{color:#fff;white-space:nowrap;z-index:1;background:#000;display:none;position:absolute}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{cursor:default;background-color:#000;position:absolute;inset:0;overflow-y:scroll}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;top:0;left:0}.xterm-char-measure-element{visibility:hidden;line-height:normal;display:inline-block;position:absolute;top:0;left:-9999em}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{z-index:10;color:#0000;pointer-events:none;position:absolute;inset:0}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:#0000}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre;font-family:monospace}.xterm .xterm-accessibility-tree>div{transform-origin:0;width:fit-content}.xterm .live-region{width:1px;height:1px;position:absolute;left:-9999px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:underline double;text-decoration:underline double}.xterm-underline-3{-webkit-text-decoration:underline wavy;text-decoration:underline wavy}.xterm-underline-4{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.xterm-underline-5{-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:underline overline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;pointer-events:none;position:absolute;top:0;right:0}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;z-index:11;background:0 0;transition:opacity .1s linear}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{display:none;position:absolute}.xterm .xterm-scrollable-element>.shadow.top{width:100%;height:3px;box-shadow:var(--vscode-scrollbar-shadow,#000) 0 6px 6px -6px inset;display:block;top:0;left:3px}.xterm .xterm-scrollable-element>.shadow.left{width:3px;height:100%;box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset;display:block;top:3px;left:0}.xterm .xterm-scrollable-element>.shadow.top-left-corner{width:3px;height:3px;display:block;top:0;left:0}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset}
|