teapot-coding-agent 0.26.14 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/agent/agent.js +85 -14
- package/dist/agent/tools.js +11 -1
- package/dist/log/events.js +41 -7
- package/dist/master.js +31 -0
- package/dist/server/api.js +45 -25
- package/package.json +1 -1
- package/public/assets/index-B7AL8hNs.css +1 -0
- package/public/assets/{index-C1UUe1T-.js → index-CaKIcOkZ.js} +19 -19
- package/public/assets/index-CaKIcOkZ.js.map +1 -0
- package/public/assets/{shiki-BxZS8Zdw.js → shiki-B6zAhgc_.js} +2 -2
- package/public/assets/{shiki-BxZS8Zdw.js.map → shiki-B6zAhgc_.js.map} +1 -1
- package/public/index.html +2 -2
- package/README.ja.md +0 -243
- package/public/assets/index-C1UUe1T-.js.map +0 -1
- package/public/assets/index-DBx_UTjS.css +0 -1
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://deepwiki.com/akku1139/teapot)
|
|
8
8
|
|
|
9
|
-
[日本語版はこちら](README.ja.md)
|
|
10
|
-
|
|
11
9
|
---
|
|
12
10
|
|
|
13
11
|
## 🚀 Quick start
|
|
@@ -167,7 +165,7 @@ Agents can run `bash` and rewrite files — including `rm` and `npm install`.
|
|
|
167
165
|
|
|
168
166
|
- Designed to run under a **dedicated Linux user**
|
|
169
167
|
- `git commit` before handing over a repo you care about
|
|
170
|
-
- File operations outside the workspace are rejected (`safeJoin`)
|
|
168
|
+
- File operations outside the workspace are rejected (`safeJoin`), but agents can use `bash` outside of the worktree
|
|
171
169
|
- teapot listens on localhost only by default. If you expose it, set **both** `--host 0.0.0.0` and an API token (`TEAPOT_API_TOKEN` env or config `password`):
|
|
172
170
|
|
|
173
171
|
```sh
|
package/dist/agent/agent.js
CHANGED
|
@@ -1227,23 +1227,48 @@ export class Agent {
|
|
|
1227
1227
|
}
|
|
1228
1228
|
if (p.source === "user") {
|
|
1229
1229
|
const id = p.id ?? `u${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
1230
|
-
|
|
1231
|
-
//
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
void this.log
|
|
1235
|
-
.append("system_note", this.currentSession, this.currentBranch, {
|
|
1236
|
-
event: "prompt-delivered",
|
|
1237
|
-
promptId: id,
|
|
1238
|
-
preview: p.text.slice(0, 80),
|
|
1239
|
-
})
|
|
1240
|
-
.then(() => bus.emit("update", { kind: "agent-update", agentId: this.opts.id }))
|
|
1241
|
-
.catch(() => { });
|
|
1230
|
+
// #37: queued here, MARKED at the request boundary instead — see
|
|
1231
|
+
// markDeliveredPrompts(). Text is kept so the marker can carry the same
|
|
1232
|
+
// preview the UI shows.
|
|
1233
|
+
this.undeliveredPrompts.push({ id, preview: p.text.slice(0, 80) });
|
|
1242
1234
|
}
|
|
1243
1235
|
}
|
|
1244
1236
|
}
|
|
1245
1237
|
/** ids of user prompts that have entered an LLM call payload (recent first-capped) */
|
|
1246
1238
|
deliveredPrompts = [];
|
|
1239
|
+
/**
|
|
1240
|
+
* User prompts consumed from the queue but not yet MARKED as delivered (#37).
|
|
1241
|
+
*
|
|
1242
|
+
* They are drained at a turn boundary, but the `prompt-delivered` marker must
|
|
1243
|
+
* wait until the request that carries them actually starts — which is later,
|
|
1244
|
+
* and after `maybeCompact()`, which can itself call the LLM.
|
|
1245
|
+
*/
|
|
1246
|
+
undeliveredPrompts = [];
|
|
1247
|
+
/**
|
|
1248
|
+
* Log `prompt-delivered` for every prompt whose outbound request is starting.
|
|
1249
|
+
*
|
|
1250
|
+
* #37: the marker's meaning is "the request carrying this prompt has begun".
|
|
1251
|
+
* It used to fire in drainPendingPrompts, i.e. before the request, so the
|
|
1252
|
+
* timeline reordered by a moment the API never saw.
|
|
1253
|
+
*/
|
|
1254
|
+
async markDeliveredPrompts() {
|
|
1255
|
+
if (this.undeliveredPrompts.length === 0)
|
|
1256
|
+
return;
|
|
1257
|
+
const pending = this.undeliveredPrompts;
|
|
1258
|
+
this.undeliveredPrompts = [];
|
|
1259
|
+
for (const { id, preview } of pending) {
|
|
1260
|
+
this.deliveredPrompts.push(id);
|
|
1261
|
+
// cap: the UI only needs recent confirmations
|
|
1262
|
+
if (this.deliveredPrompts.length > 64)
|
|
1263
|
+
this.deliveredPrompts.shift();
|
|
1264
|
+
await this.log.append("system_note", this.currentSession, this.currentBranch, {
|
|
1265
|
+
event: "prompt-delivered",
|
|
1266
|
+
promptId: id,
|
|
1267
|
+
preview,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
bus.emit("update", { kind: "agent-update", agentId: this.opts.id });
|
|
1271
|
+
}
|
|
1247
1272
|
/**
|
|
1248
1273
|
* Background jobs that exited since the last LLM call. Their outcomes are
|
|
1249
1274
|
* folded into ONE harness message at the turn boundary — the agent learns
|
|
@@ -1366,9 +1391,25 @@ export class Agent {
|
|
|
1366
1391
|
stopSeq = 0;
|
|
1367
1392
|
/** #95: set when this round already injected a harness progress request */
|
|
1368
1393
|
progressRequestedThisRound = false;
|
|
1394
|
+
/**
|
|
1395
|
+
* CONTROL PLANE: fired on every status change, immediately, independent of the
|
|
1396
|
+
* log.
|
|
1397
|
+
*
|
|
1398
|
+
* #126: `wait_children` wakeups used to ride `EventLog.onEvent`, so a status
|
|
1399
|
+
* change reached the parent only AFTER the write completed. After the
|
|
1400
|
+
* write-before-notify fix that meant a FAILED or slow write left a parked parent
|
|
1401
|
+
* waiting forever — persistence latency became control flow. Measured with every
|
|
1402
|
+
* write failing: `onEvent fired: 0`, so no wakeup at all.
|
|
1403
|
+
*
|
|
1404
|
+
* Control flow must not depend on durability, so this is separate from the
|
|
1405
|
+
* event pipeline. The log's `onEvent` now means "persisted, tell the world";
|
|
1406
|
+
* this means "my state changed, react now".
|
|
1407
|
+
*/
|
|
1408
|
+
onControlStatusChange = null;
|
|
1369
1409
|
setStatus(s, reason = "") {
|
|
1370
|
-
// Update the field FIRST.
|
|
1371
|
-
//
|
|
1410
|
+
// Update the field FIRST. The control-plane hook fires synchronously below
|
|
1411
|
+
// (see onControlStatusChange), and it is what wakes parked wait_children
|
|
1412
|
+
// callers — those
|
|
1372
1413
|
// re-check agent.status at that instant. Updating after the append made
|
|
1373
1414
|
// every waiter observe the OLD status ("running"), conclude "someone is
|
|
1374
1415
|
// still working", and stay parked until timeout even though the child had
|
|
@@ -1377,6 +1418,15 @@ export class Agent {
|
|
|
1377
1418
|
this.status = s;
|
|
1378
1419
|
this.statusReason = reason;
|
|
1379
1420
|
if (prev !== s) {
|
|
1421
|
+
// #126: control plane FIRST, and not via the log. A parent's wait_children
|
|
1422
|
+
// must wake on the status change itself, not on the durability of the write
|
|
1423
|
+
// that records it.
|
|
1424
|
+
try {
|
|
1425
|
+
this.onControlStatusChange?.(this.opts.id, s);
|
|
1426
|
+
}
|
|
1427
|
+
catch {
|
|
1428
|
+
/* a control-plane listener must never break a status change */
|
|
1429
|
+
}
|
|
1380
1430
|
void this.log.append("state", this.currentSession, this.currentBranch, {
|
|
1381
1431
|
from: prev,
|
|
1382
1432
|
to: s,
|
|
@@ -1591,6 +1641,27 @@ export class Agent {
|
|
|
1591
1641
|
text: "",
|
|
1592
1642
|
reasoning: "",
|
|
1593
1643
|
});
|
|
1644
|
+
// #37: `prompt-delivered` must mean "the outbound request CARRYING this
|
|
1645
|
+
// prompt has started", not "the prompt moved from the queue into
|
|
1646
|
+
// `this.messages`".
|
|
1647
|
+
//
|
|
1648
|
+
// It used to be logged in drainPendingPrompts, which runs BEFORE
|
|
1649
|
+
// refreshSkills, maybeRequestProgress and — critically — maybeCompact. So
|
|
1650
|
+
// the marker preceded the request, and `maybeCompact()` can itself make an
|
|
1651
|
+
// LLM call, which meant the timeline claimed a delivery order the API never
|
|
1652
|
+
// saw. Measured before the fix:
|
|
1653
|
+
//
|
|
1654
|
+
// LLM CALL #1
|
|
1655
|
+
// seq=5 prompt-delivered("first prompt")
|
|
1656
|
+
// seq=6 llm turn start
|
|
1657
|
+
//
|
|
1658
|
+
// The UI reorders by this marker (resequenceToDelivery), so the semantics
|
|
1659
|
+
// were the bug — not timeline-order.ts.
|
|
1660
|
+
//
|
|
1661
|
+
// Emitted ONCE per request, not per attempt: a retry sends the same payload,
|
|
1662
|
+
// so re-emitting would move the marker after a later event on each try.
|
|
1663
|
+
if (attempt === 1)
|
|
1664
|
+
await this.markDeliveredPrompts();
|
|
1594
1665
|
try {
|
|
1595
1666
|
return await this.callLlm(messages, tools, onDelta);
|
|
1596
1667
|
}
|
package/dist/agent/tools.js
CHANGED
|
@@ -1192,8 +1192,18 @@ export const TOOLS = [
|
|
|
1192
1192
|
return stale;
|
|
1193
1193
|
let oldText = str(args.old_text);
|
|
1194
1194
|
const newText = str(args.new_text);
|
|
1195
|
+
// #126: the single most common edit_file failure in the real logs (22
|
|
1196
|
+
// occurrences) said only "old_text is required", while the tool's own
|
|
1197
|
+
// schema already documents the better form. Name it and say what to do —
|
|
1198
|
+
// the pattern master.ts uses for "not your sub-agent" (master.ts:979),
|
|
1199
|
+
// which turned a confusing refusal into a re-addressable one.
|
|
1195
1200
|
if (!oldText)
|
|
1196
|
-
return {
|
|
1201
|
+
return {
|
|
1202
|
+
ok: false,
|
|
1203
|
+
result: "old_text is required — pass the exact text to replace, or read with " +
|
|
1204
|
+
'`read_file({line_ids:"hash"})` and pass that block VERBATIM as old_text. ' +
|
|
1205
|
+
"Do NOT copy read_file's `N| ` line-number prefixes.",
|
|
1206
|
+
};
|
|
1197
1207
|
const replaceAll = args.replace_all === true;
|
|
1198
1208
|
// #7/#4: accept a block copied straight out of read_file's hashline
|
|
1199
1209
|
// output. When the anchors do not verify we FAIL with the specific reason
|
package/dist/log/events.js
CHANGED
|
@@ -89,12 +89,25 @@ export class EventLog {
|
|
|
89
89
|
data,
|
|
90
90
|
};
|
|
91
91
|
this.lastByBranch.set(branch, evt.id);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
// #54: WRITE FIRST, then notify.
|
|
93
|
+
//
|
|
94
|
+
// The observer used to fire BEFORE the write. The frontend receives a
|
|
95
|
+
// `kind:"event"` frame and — by design — treats it as "something changed"
|
|
96
|
+
// and re-reads `/events`. So the reload could read the log BEFORE this
|
|
97
|
+
// event reached disk, and the reload would simply not contain it.
|
|
98
|
+
//
|
|
99
|
+
// Measured against a real EventLog, reloading on every notification:
|
|
100
|
+
//
|
|
101
|
+
// appends: 30 WS-triggered reloads that MISSED the event: 30
|
|
102
|
+
//
|
|
103
|
+
// The 120ms debounce usually hides this, which is exactly why #54 felt
|
|
104
|
+
// random: a slow write misses, a fast one lands. Notifying after the write
|
|
105
|
+
// makes the event visible to any reader the observer triggers.
|
|
106
|
+
// `written` distinguishes a REAL write from the post-close drop path, which
|
|
107
|
+
// also resolves. #54: announcing a dropped event would tell the frontend to
|
|
108
|
+
// reload for something that will never appear — the row would flicker in and
|
|
109
|
+
// straight back out.
|
|
110
|
+
let written = false;
|
|
98
111
|
const p = new Promise((resolve, reject) => {
|
|
99
112
|
this.chain = this.chain.then(() => {
|
|
100
113
|
if (!this.stream) {
|
|
@@ -103,10 +116,31 @@ export class EventLog {
|
|
|
103
116
|
console.error(`[teapot] event dropped after log close (${this.agentId}): ${type}`);
|
|
104
117
|
return resolve(evt);
|
|
105
118
|
}
|
|
106
|
-
this.stream.write(JSON.stringify(evt) + "\n", "utf8", (err) =>
|
|
119
|
+
this.stream.write(JSON.stringify(evt) + "\n", "utf8", (err) => {
|
|
120
|
+
if (err)
|
|
121
|
+
return reject(err);
|
|
122
|
+
written = true;
|
|
123
|
+
resolve(evt);
|
|
124
|
+
});
|
|
107
125
|
}, () => resolve(evt));
|
|
108
126
|
});
|
|
109
127
|
this.chain = this.chain.then(() => { }, () => { });
|
|
128
|
+
// notify once the write is handed to the OS, so a reload the observer
|
|
129
|
+
// triggers can actually see this event
|
|
130
|
+
void p
|
|
131
|
+
.then(() => {
|
|
132
|
+
if (!written)
|
|
133
|
+
return; // dropped after close: nothing on disk to announce
|
|
134
|
+
try {
|
|
135
|
+
this.onEvent?.(evt);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* observer must never break the log */
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.catch(() => {
|
|
142
|
+
/* a failed write must not notify — the event is not on disk */
|
|
143
|
+
});
|
|
110
144
|
return p;
|
|
111
145
|
}
|
|
112
146
|
lastEventId(branch) {
|
package/dist/master.js
CHANGED
|
@@ -524,6 +524,28 @@ export class Master {
|
|
|
524
524
|
bus.emit("update", { kind: "event", agentId: e.agent, event: e });
|
|
525
525
|
this.onChildEvent(ac, e);
|
|
526
526
|
};
|
|
527
|
+
// #126: the CONTROL PLANE, separate from the event pipeline above.
|
|
528
|
+
//
|
|
529
|
+
// `onChildEvent` used to be what woke a parent's `wait_children`, and it runs
|
|
530
|
+
// after the event is persisted. So a failed or slow write left the parent
|
|
531
|
+
// parked forever — measured with every write failing, the wake never fired.
|
|
532
|
+
//
|
|
533
|
+
// A status change is CONTROL FLOW, not an observation, so it gets its own
|
|
534
|
+
// immediate path. `onChildEvent` keeps its other job (mirroring the child's
|
|
535
|
+
// activity into the parent's timeline, forwarding its final report), which
|
|
536
|
+
// legitimately belongs to the event stream.
|
|
537
|
+
agent.onControlStatusChange = (agentId, status) => {
|
|
538
|
+
if (!ac.parent)
|
|
539
|
+
return;
|
|
540
|
+
const parent = this.agents.get(ac.parent);
|
|
541
|
+
if (!parent)
|
|
542
|
+
return;
|
|
543
|
+
// the waiter re-checks whether ITS targets are all settled, so waking on
|
|
544
|
+
// every movement is correct and cheap
|
|
545
|
+
this.wakeParentWaiters(ac.parent);
|
|
546
|
+
if (status === "idle" || status === "stopped" || status === "waiting" || status === "error")
|
|
547
|
+
parent.enqueuePrompt(`[harness] @${agentId} is now ${status}. Continue your task, or finish() if the work is done.`, "harness");
|
|
548
|
+
};
|
|
527
549
|
// sub-agent management hooks — only when this agent can legally spawn
|
|
528
550
|
if (myDepth < this.maxSpawnDepth()) {
|
|
529
551
|
const self = agent;
|
|
@@ -842,9 +864,18 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
842
864
|
// silently dropped exactly the details the operator spawned the sub
|
|
843
865
|
// to gather. Context management is compaction's job, not the pipe's.
|
|
844
866
|
`[harness] Sub-agent ${ac.id} finished. Final report:\n${report || "(no summary)"}`, "harness");
|
|
867
|
+
// #123: a parent's report is delivered INTO a live agent — queueing it is
|
|
868
|
+
// not enough. If the parent is idle (finished, or parked) the report sat in
|
|
869
|
+
// the queue until something else started the loop, so the operator saw a
|
|
870
|
+
// finished sub-agent and a parent that never reacted.
|
|
871
|
+
if (parent.status !== "running")
|
|
872
|
+
parent.start(`sub-agent ${ac.id} finished`);
|
|
845
873
|
}
|
|
846
874
|
else if (e.type === "error") {
|
|
847
875
|
parent.enqueuePrompt(`[harness] Sub-agent ${ac.id} hit an error: ${String(e.data.message ?? "").slice(0, 500)}`, "harness");
|
|
876
|
+
// #123: same for an error report
|
|
877
|
+
if (parent.status !== "running")
|
|
878
|
+
parent.start(`sub-agent ${ac.id} error`);
|
|
848
879
|
}
|
|
849
880
|
// #63: a child's activity is NOT mirrored into the parent's log.
|
|
850
881
|
//
|
package/dist/server/api.js
CHANGED
|
@@ -525,14 +525,18 @@ export function buildApp(master) {
|
|
|
525
525
|
app.get("/api/agents/:id/tree", async (c) => {
|
|
526
526
|
const a = master.agents.get(c.req.param("id"));
|
|
527
527
|
if (!a)
|
|
528
|
-
return c
|
|
528
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
529
529
|
const rel = c.req.query("path") || ".";
|
|
530
530
|
let abs;
|
|
531
531
|
try {
|
|
532
532
|
abs = safeJoin(a.workspace, rel);
|
|
533
533
|
}
|
|
534
534
|
catch (err) {
|
|
535
|
-
|
|
535
|
+
// #126: safeJoin's message embeds the RESOLVED absolute path
|
|
536
|
+
// ("path escapes workspace: /home/user/secret/project/…"), so returning it
|
|
537
|
+
// verbatim hands the caller the server's directory layout. The rejection is
|
|
538
|
+
// correct; the disclosure was not. Echo back the path AS GIVEN.
|
|
539
|
+
return c.json({ error: `path is outside the workspace: ${rel}` }, 400);
|
|
536
540
|
}
|
|
537
541
|
let dirents;
|
|
538
542
|
try {
|
|
@@ -580,7 +584,7 @@ export function buildApp(master) {
|
|
|
580
584
|
app.get("/api/agents/:id/file", async (c) => {
|
|
581
585
|
const a = master.agents.get(c.req.param("id"));
|
|
582
586
|
if (!a)
|
|
583
|
-
return c
|
|
587
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
584
588
|
const rel = c.req.query("path") ?? "";
|
|
585
589
|
if (!rel.trim())
|
|
586
590
|
return c.json({ error: "path required" }, 400);
|
|
@@ -611,7 +615,7 @@ export function buildApp(master) {
|
|
|
611
615
|
app.get("/api/agents/:id/raw", async (c) => {
|
|
612
616
|
const a = master.agents.get(c.req.param("id"));
|
|
613
617
|
if (!a)
|
|
614
|
-
return c
|
|
618
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
615
619
|
const rel = c.req.query("path") ?? "";
|
|
616
620
|
if (!rel.trim())
|
|
617
621
|
return c.json({ error: "path required" }, 400);
|
|
@@ -649,7 +653,7 @@ export function buildApp(master) {
|
|
|
649
653
|
app.put("/api/agents/:id/file", async (c) => {
|
|
650
654
|
const a = master.agents.get(c.req.param("id"));
|
|
651
655
|
if (!a)
|
|
652
|
-
return c
|
|
656
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
653
657
|
const rel = c.req.query("path") ?? "";
|
|
654
658
|
if (!rel.trim())
|
|
655
659
|
return c.json({ error: "path required" }, 400);
|
|
@@ -755,20 +759,20 @@ export function buildApp(master) {
|
|
|
755
759
|
});
|
|
756
760
|
app.get("/api/agents/:id", (c) => {
|
|
757
761
|
const a = master.agents.get(c.req.param("id"));
|
|
758
|
-
return a ? c.json(a.snapshot()) : c
|
|
762
|
+
return a ? c.json(a.snapshot()) : notFound(c, "agent", c.req.param("id"));
|
|
759
763
|
});
|
|
760
764
|
// lazy session load: stopped (not restored) → idle, history rebuilt from the log
|
|
761
765
|
app.post("/api/agents/:id/load", async (c) => {
|
|
762
766
|
const a = master.agents.get(c.req.param("id"));
|
|
763
767
|
if (!a)
|
|
764
|
-
return c
|
|
768
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
765
769
|
await a.load();
|
|
766
770
|
return c.json({ ok: true, agent: a.snapshot() });
|
|
767
771
|
});
|
|
768
772
|
app.post("/api/agents/:id/prompt", async (c) => {
|
|
769
773
|
const a = master.agents.get(c.req.param("id"));
|
|
770
774
|
if (!a)
|
|
771
|
-
return c
|
|
775
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
772
776
|
const body = await c.req
|
|
773
777
|
.json()
|
|
774
778
|
.catch(() => null); // #110
|
|
@@ -797,7 +801,7 @@ export function buildApp(master) {
|
|
|
797
801
|
app.post("/api/agents/:id/prompt/cancel", async (c) => {
|
|
798
802
|
const a = master.agents.get(c.req.param("id"));
|
|
799
803
|
if (!a)
|
|
800
|
-
return c
|
|
804
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
801
805
|
const body = await c.req.json().catch(() => null); // #110
|
|
802
806
|
if (!body)
|
|
803
807
|
return c.json({ error: "invalid JSON" }, 400);
|
|
@@ -811,7 +815,7 @@ export function buildApp(master) {
|
|
|
811
815
|
app.post("/api/agents/:id/start", (c) => {
|
|
812
816
|
const a = master.agents.get(c.req.param("id"));
|
|
813
817
|
if (!a)
|
|
814
|
-
return c
|
|
818
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
815
819
|
a.start("api start");
|
|
816
820
|
return c.json({ ok: true });
|
|
817
821
|
});
|
|
@@ -874,14 +878,14 @@ export function buildApp(master) {
|
|
|
874
878
|
app.post("/api/agents/:id/stop", (c) => {
|
|
875
879
|
const a = master.agents.get(c.req.param("id"));
|
|
876
880
|
if (!a)
|
|
877
|
-
return c
|
|
881
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
878
882
|
a.stop("stopped via api");
|
|
879
883
|
return c.json({ ok: true });
|
|
880
884
|
});
|
|
881
885
|
app.post("/api/agents/:id/goal", async (c) => {
|
|
882
886
|
const a = master.agents.get(c.req.param("id"));
|
|
883
887
|
if (!a)
|
|
884
|
-
return c
|
|
888
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
885
889
|
const body = await c.req.json()
|
|
886
890
|
.catch(() => null); // #110
|
|
887
891
|
if (!body)
|
|
@@ -911,7 +915,7 @@ export function buildApp(master) {
|
|
|
911
915
|
app.get("/api/agents/:id/skills", async (c) => {
|
|
912
916
|
const a = master.agents.get(c.req.param("id"));
|
|
913
917
|
if (!a)
|
|
914
|
-
return c
|
|
918
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
915
919
|
try {
|
|
916
920
|
const list = await currentSkills(a.toolCtx);
|
|
917
921
|
return c.json({
|
|
@@ -926,7 +930,7 @@ export function buildApp(master) {
|
|
|
926
930
|
app.post("/api/agents/:id/compact", async (c) => {
|
|
927
931
|
const a = master.agents.get(c.req.param("id"));
|
|
928
932
|
if (!a)
|
|
929
|
-
return c
|
|
933
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
930
934
|
try {
|
|
931
935
|
return c.json({ ok: true, ...(await a.compactNow()) });
|
|
932
936
|
}
|
|
@@ -954,7 +958,7 @@ export function buildApp(master) {
|
|
|
954
958
|
app.post("/api/agents/:id/auto-continue", async (c) => {
|
|
955
959
|
const a = master.agents.get(c.req.param("id"));
|
|
956
960
|
if (!a)
|
|
957
|
-
return c
|
|
961
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
958
962
|
const body = await c.req.json().catch(() => null);
|
|
959
963
|
if (!body || typeof body.value !== "boolean")
|
|
960
964
|
return c.json({ error: "boolean value required" }, 400);
|
|
@@ -970,7 +974,7 @@ export function buildApp(master) {
|
|
|
970
974
|
app.post("/api/agents/:id/auto-compact", async (c) => {
|
|
971
975
|
const a = master.agents.get(c.req.param("id"));
|
|
972
976
|
if (!a)
|
|
973
|
-
return c
|
|
977
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
974
978
|
const body = await c.req.json().catch(() => null);
|
|
975
979
|
if (!body || typeof body.value !== "boolean")
|
|
976
980
|
return c.json({ error: "boolean value required" }, 400);
|
|
@@ -990,7 +994,7 @@ export function buildApp(master) {
|
|
|
990
994
|
app.post("/api/agents/:id/spawn", async (c) => {
|
|
991
995
|
const a = master.agents.get(c.req.param("id"));
|
|
992
996
|
if (!a)
|
|
993
|
-
return c
|
|
997
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
994
998
|
const body = await c.req
|
|
995
999
|
.json()
|
|
996
1000
|
.catch(() => null);
|
|
@@ -1013,7 +1017,7 @@ export function buildApp(master) {
|
|
|
1013
1017
|
app.post("/api/agents/:id/stop-children", async (c) => {
|
|
1014
1018
|
const a = master.agents.get(c.req.param("id"));
|
|
1015
1019
|
if (!a)
|
|
1016
|
-
return c
|
|
1020
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1017
1021
|
const body = await c.req.json().catch(() => ({ ids: undefined }));
|
|
1018
1022
|
try {
|
|
1019
1023
|
const r = await master.stopChildrenFor(c.req.param("id"), body.ids);
|
|
@@ -1027,20 +1031,26 @@ export function buildApp(master) {
|
|
|
1027
1031
|
app.post("/api/agents/:id/todo", async (c) => {
|
|
1028
1032
|
const a = master.agents.get(c.req.param("id"));
|
|
1029
1033
|
if (!a)
|
|
1030
|
-
return c
|
|
1034
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1031
1035
|
const body = await c.req.json().catch(() => null);
|
|
1032
1036
|
if (!body)
|
|
1033
1037
|
return c.json({ error: "invalid JSON" }, 400);
|
|
1034
1038
|
await a.setTodo(body.text ?? "");
|
|
1035
1039
|
if (body.notify !== false && body.text?.trim())
|
|
1036
1040
|
a.enqueuePrompt(`[harness] The operator updated the task list:\n\n${body.text}\n\nWork through it (get_todo() always has the latest).`, "harness");
|
|
1041
|
+
// #123: an idle agent must actually START on the queued prompt. The goal
|
|
1042
|
+
// route above learned this (the queued prompt sat forever otherwise); the
|
|
1043
|
+
// todo route did not, so saving a task list on an idle session looked like
|
|
1044
|
+
// it had been ignored.
|
|
1045
|
+
if (a.status !== "running")
|
|
1046
|
+
a.start("todo set");
|
|
1037
1047
|
return c.json({ ok: true });
|
|
1038
1048
|
});
|
|
1039
1049
|
// edit a previously-sent prompt: forks there, optionally summarizes the tail
|
|
1040
1050
|
app.post("/api/agents/:id/edit-prompt", async (c) => {
|
|
1041
1051
|
const a = master.agents.get(c.req.param("id"));
|
|
1042
1052
|
if (!a)
|
|
1043
|
-
return c
|
|
1053
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1044
1054
|
const body = await c.req
|
|
1045
1055
|
.json()
|
|
1046
1056
|
.catch(() => null);
|
|
@@ -1066,7 +1076,7 @@ export function buildApp(master) {
|
|
|
1066
1076
|
app.post("/api/agents/:id/fork", async (c) => {
|
|
1067
1077
|
const a = master.agents.get(c.req.param("id"));
|
|
1068
1078
|
if (!a)
|
|
1069
|
-
return c
|
|
1079
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1070
1080
|
const body = await c.req.json().catch(() => ({ fromEvent: null }));
|
|
1071
1081
|
const r = await a.fork(body.fromEvent ?? null);
|
|
1072
1082
|
return c.json({ ok: true, ...r });
|
|
@@ -1096,7 +1106,7 @@ export function buildApp(master) {
|
|
|
1096
1106
|
app.get("/api/agents/:id/events", async (c) => {
|
|
1097
1107
|
const a = master.agents.get(c.req.param("id"));
|
|
1098
1108
|
if (!a)
|
|
1099
|
-
return c
|
|
1109
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1100
1110
|
// #54: the frontend pairs a tool_call with its tool_result by walking the
|
|
1101
1111
|
// events it holds, so a window that can contain a result WITHOUT its call
|
|
1102
1112
|
// strands the bash row at "waiting for output…" forever — the call scrolled
|
|
@@ -1113,7 +1123,7 @@ export function buildApp(master) {
|
|
|
1113
1123
|
if (sessionId) {
|
|
1114
1124
|
const rec = master.sessionById(sessionId);
|
|
1115
1125
|
if (!rec || rec.agentId !== c.req.param("id"))
|
|
1116
|
-
return c
|
|
1126
|
+
return notFound(c, "session (or it belongs to another agent)", sessionId);
|
|
1117
1127
|
filePath = path.join(rec.dir, "chat.jsonl");
|
|
1118
1128
|
}
|
|
1119
1129
|
// #59: the plain "latest N" request is what the UI polls every ~120 ms for
|
|
@@ -1152,13 +1162,13 @@ export function buildApp(master) {
|
|
|
1152
1162
|
const known = master.agents.has(id) || master.config.agents.some((a) => a.id === id);
|
|
1153
1163
|
const owned = master.sessionsOf(id);
|
|
1154
1164
|
if (!known && owned.length === 0)
|
|
1155
|
-
return c
|
|
1165
|
+
return notFound(c, "agent", id);
|
|
1156
1166
|
return c.json({ sessions: owned });
|
|
1157
1167
|
});
|
|
1158
1168
|
app.get("/api/agents/:id/branches", async (c) => {
|
|
1159
1169
|
const a = master.agents.get(c.req.param("id"));
|
|
1160
1170
|
if (!a)
|
|
1161
|
-
return c
|
|
1171
|
+
return notFound(c, "agent", c.req.param("id"));
|
|
1162
1172
|
// cached like /events — this used to bypass it and re-read the whole log
|
|
1163
1173
|
const events = await readEventsCached(a.log.filePath);
|
|
1164
1174
|
const branches = new Map();
|
|
@@ -1190,6 +1200,16 @@ export function buildApp(master) {
|
|
|
1190
1200
|
// ---- metrics / SSE ----
|
|
1191
1201
|
app.get("/api/metrics", (c) => c.json(master.metrics()));
|
|
1192
1202
|
// current server version (for live-update polling)
|
|
1203
|
+
/**
|
|
1204
|
+
* A 404 that says WHICH thing was missing.
|
|
1205
|
+
*
|
|
1206
|
+
* #126: there were 24 hand-copied `c.json({ error: "not found" }, 404)` lines.
|
|
1207
|
+
* From a client they are indistinguishable — a typo in an agent id, a deleted
|
|
1208
|
+
* session, or a bad route all produce the same three words, and none carries
|
|
1209
|
+
* the id that was looked up. The id is the first thing anyone debugging this
|
|
1210
|
+
* needs, and it is already in hand.
|
|
1211
|
+
*/
|
|
1212
|
+
const notFound = (c, what, id) => c.json({ error: `${what} not found: ${id || "(no id given)"}` }, 404);
|
|
1193
1213
|
app.get("/api/version", (c) => c.json({ version: master.getVersion() }));
|
|
1194
1214
|
// trigger a full server restart (agents stopped gracefully, new process spawned)
|
|
1195
1215
|
app.post("/api/update/restart", async (c) => {
|
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}.agent-item{cursor:pointer;color:var(--fg);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.collapsed{cursor:pointer}.agent-item.sel{background:var(--bg-mid)}.agent-item.sub-row{color:var(--dim)}.agent-item.sub-row.sel{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 pre>code{white-space:pre-wrap;overflow-wrap:anywhere}.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 18px;position:absolute;inset:0}.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)}input[type=checkbox]{background-color:var(--bg-darkest);border:1px solid var(--dim);border-radius:3px}input[type=number]{appearance:textfield}input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}.modal>button,.modal button:not(.iconbtn):not([class]){background:var(--bg-light);color:var(--fg);border:1px solid var(--dim);font:inherit;cursor:pointer;border-radius:6px;padding:6px 10px}.modal>button:hover,.modal button:not(.iconbtn):not([class]):hover{background:var(--bg-mid);border-color:var(--acc)}.modal>button:focus-visible,.modal button:not(.iconbtn):not([class]):focus-visible{outline:2px solid var(--acc);outline-offset:1px}.todopane,.todopane-edit{box-sizing:border-box;background:var(--bg-darkest);width:100%;height:11.5em;min-height:7em;max-height:40vh;color:var(--fg);resize:vertical;border:none;border-radius:6px;padding:6px 8px;font-family:ui-monospace,Menlo,monospace;font-size:12.5px;line-height:1.45;display:block;overflow-y:auto}.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}
|