@vincemakes/kiso-web 0.52.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main/host.js CHANGED
@@ -28,6 +28,8 @@ export async function startHost(options) {
28
28
  sanitizer: options.sanitizer ?? "web",
29
29
  ...(options.keepaliveMs !== undefined ? { keepaliveMs: options.keepaliveMs } : {}),
30
30
  ...(options.toolLimits !== undefined ? { toolLimits: options.toolLimits } : {}),
31
+ followUps: options.followUps ?? true,
32
+ titles: options.titles ?? true,
31
33
  });
32
34
  // the folder in use: its directory is claimed (named after its path) the way the terminal claims it
33
35
  const workspace = canonicalPath(options.workspace);
@@ -52,7 +54,7 @@ export async function startHost(options) {
52
54
  if (space === undefined || !space.service.isRunning(sessionId))
53
55
  return "idle";
54
56
  return (await space.service.pendingApprovals(sessionId)).length > 0 ? "waiting" : "running";
55
- });
57
+ }, (id) => options.settings.marksOf(id));
56
58
  const rendererDir = fileURLToPath(new URL("../../out/renderer/", import.meta.url));
57
59
  const tokenBytes = Buffer.from(token);
58
60
  let auth;
@@ -0,0 +1,15 @@
1
+ import { readSessionName, writeSessionName } from "@vincemakes/kiso-runtime/internal";
2
+ /** A Work's name: the runtime's own session-name store, the one the terminal's rename writes (a file beside the log, never the log).
3
+ * The runtime's titles read it (the listing re-reads a folder whose name file changed); empty clears it; at most 120 characters. */
4
+ export function rename(dir, sessionId, name) {
5
+ const clean = name.replace(/\s+/g, " ").trim().slice(0, 120);
6
+ writeSessionName(dir, sessionId, clean === "" ? null : clean);
7
+ return clean === "" ? null : clean;
8
+ }
9
+ /** Whether a Work has a name: the person's, or a title written before. */
10
+ export const named = (dir, sessionId) => readSessionName(dir, sessionId) !== null;
11
+ /** A title made from the Work's first message, kept only while the Work is still unnamed: the person's name wins. */
12
+ export function titleIfUnnamed(dir, sessionId, title) {
13
+ if (title !== "" && !named(dir, sessionId))
14
+ writeSessionName(dir, sessionId, title);
15
+ }
@@ -2,9 +2,13 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { applyEffortDeclarations, contextWindowOf, demoScript, authForProfile, effortOptions, loadUserConfig, mergeConfigs, preferences, profileAvailable, rememberEffort, startingEffort, } from "@vincemakes/kiso-code/config";
4
4
  import { MODE_LABEL, OFFERED_MODES } from "@vincemakes/kiso-code/policy";
5
+ import { lookupModelMetadata } from "@vincemakes/kiso-runtime/internal";
5
6
  import { fauxSkip } from "./faux.js";
6
7
  import { storeKey } from "./login.js";
7
8
  const MAX_RECENT = 10;
9
+ const SAFE_SESSION = /^[A-Za-z0-9._-]+$/;
10
+ /** Whether the model table says the profile's model reads images; unknown is no (nothing is sent that might be dropped). */
11
+ const readsImages = (p) => lookupModelMetadata(p.model, p.baseUrl)?.capabilities.inputModalities?.includes("image") === true;
8
12
  const isTier = (value) => OFFERED_MODES.some((m) => m === value);
9
13
  export class Settings {
10
14
  #file;
@@ -63,9 +67,12 @@ export class Settings {
63
67
  applyEffortDeclarations(merged);
64
68
  return merged;
65
69
  }
66
- /** The profile in use: the person's pick if it still exists, else the configuration's own default. */
67
- #chosen(config) {
70
+ /** The profile in use: the Work's own pick, else the person's pick for new Works, if it still exists, else the configuration's own default. */
71
+ #chosen(config, session) {
68
72
  const models = config.models ?? {};
73
+ const own = session === undefined ? undefined : this.#read().works?.[session]?.profile;
74
+ if (own !== undefined && models[own] !== undefined)
75
+ return own;
69
76
  const picked = this.#read().profile;
70
77
  if (picked !== undefined && models[picked] !== undefined)
71
78
  return picked;
@@ -78,12 +85,12 @@ export class Settings {
78
85
  if (this.#demo !== null)
79
86
  return this.#demo(ctx === undefined ? 0 : fauxSkip(ctx.sessionsDir, ctx.sessionId));
80
87
  const config = this.#config();
81
- const name = this.#chosen(config);
88
+ const name = this.#chosen(config, ctx?.sessionId);
82
89
  if (name === null)
83
90
  throw new Error("no model is configured: add a profile to ~/.kiso/config.json (see the terminal's docs) and choose it in Settings");
84
91
  const profile = config.models[name];
85
92
  const auth = authForProfile(name, profile);
86
- const effort = startingEffort(profile, preferences().effort?.[name]);
93
+ const effort = startingEffort(profile, this.#effortOf(name, ctx?.sessionId));
87
94
  return {
88
95
  kind: "profile",
89
96
  name,
@@ -93,6 +100,51 @@ export class Settings {
93
100
  ...(effort !== null ? { reasoning: { thinking: "default", effort } } : {}),
94
101
  };
95
102
  }
103
+ /** The effort a Work asked for (when it is one of the profile's levels), else the terminal's remembered one for the profile. */
104
+ #effortOf(name, session) {
105
+ const own = session === undefined ? undefined : this.#read().works?.[session]?.effort;
106
+ return own ?? preferences().effort?.[name];
107
+ }
108
+ /** A Work's own model choice (to put back when its rebuild is refused). */
109
+ workOf(session) {
110
+ return this.#read().works?.[session];
111
+ }
112
+ /** A fork starts with its parent's own tier, model and effort, and remembers where it came from. */
113
+ forked(parent, child, fork) {
114
+ const own = this.#read();
115
+ const tier = own.tiers?.[parent];
116
+ const work = own.works?.[parent];
117
+ this.write({
118
+ ...(tier !== undefined ? { tiers: { ...own.tiers, [child]: tier } } : {}),
119
+ ...(work !== undefined ? { works: { ...own.works, [child]: work } } : {}),
120
+ forks: { ...own.forks, [child]: fork },
121
+ });
122
+ }
123
+ forkOf(session) {
124
+ return this.#read().forks?.[session];
125
+ }
126
+ /** Pin or archive a Work (a pin goes first), or take it off. */
127
+ mark(session, list, on) {
128
+ const rest = (this.#read()[list] ?? []).filter((id) => id !== session);
129
+ this.write({ [list]: on ? [session, ...rest] : rest });
130
+ }
131
+ /** What the sidebar shows of a Work beyond its log: the fork's parent, its pin's place, whether it is archived. */
132
+ marksOf(session) {
133
+ const own = this.#read();
134
+ const parent = own.forks?.[session]?.parent;
135
+ const pinned = own.pins?.indexOf(session) ?? -1;
136
+ return { ...(parent !== undefined ? { parent } : {}), ...(pinned >= 0 ? { pinned } : {}), ...(own.archived?.includes(session) ? { archived: true } : {}) };
137
+ }
138
+ /** A deleted Work leaves nothing here; a fork's record of it stays (its notice still names the parent). */
139
+ forget(session) {
140
+ const own = this.#read();
141
+ const drop = (m) => Object.fromEntries(Object.entries(m ?? {}).filter(([k]) => k !== session));
142
+ this.write({ tiers: drop(own.tiers), works: drop(own.works), forks: drop(own.forks), pins: (own.pins ?? []).filter((id) => id !== session), archived: (own.archived ?? []).filter((id) => id !== session) });
143
+ }
144
+ restoreWork(session, work) {
145
+ const { [session]: _, ...rest } = this.#read().works ?? {};
146
+ this.write({ works: work === undefined ? rest : { ...rest, [session]: work } });
147
+ }
96
148
  /** The person's own test-runner commands (`checks` in the configuration), for the compaction phase rule. */
97
149
  checks() {
98
150
  return Object.values(this.#config().checks ?? {});
@@ -122,21 +174,35 @@ export class Settings {
122
174
  snapshot(session) {
123
175
  const config = this.#config();
124
176
  const models = config.models ?? {};
125
- const name = this.#chosen(config);
177
+ const name = this.#chosen(config, session);
126
178
  const profile = name === null ? undefined : models[name];
127
179
  const levels = profile === undefined ? null : effortOptions(profile);
128
180
  return {
129
- profiles: Object.entries(models).map(([n, p]) => ({ name: n, model: p.model, available: profileAvailable(p) })),
181
+ profiles: Object.entries(models).map(([n, p]) => ({ name: n, model: p.model, available: profileAvailable(p), images: readsImages(p) })),
130
182
  profile: name,
131
- effort: levels === null || name === null ? null : { ...levels, current: startingEffort(profile, preferences().effort?.[name]) },
183
+ effort: levels === null || name === null ? null : { ...levels, current: startingEffort(profile, this.#effortOf(name, session)) },
132
184
  mode: this.tierOf(session),
133
185
  setup: this.#setup(config, name),
134
186
  modes: OFFERED_MODES.map((value) => ({ value, label: MODE_LABEL[value] })),
135
187
  signIn: name === null ? { ok: false, text: "No model is configured." } : signInOf(name, profile),
136
188
  };
137
189
  }
190
+ /** Apply a patch; returns the Work whose model binding it changed (the host rebuilds that Work), else null. */
138
191
  apply(patch) {
139
192
  const config = this.#config();
193
+ const session = patch.session !== undefined && SAFE_SESSION.test(patch.session) ? patch.session : undefined;
194
+ if (session !== undefined && (patch.profile !== undefined || patch.effort !== undefined)) {
195
+ // one Work's model: its own profile and effort, the new-Work default untouched
196
+ const was = this.workOf(session) ?? {};
197
+ const profileName = patch.profile !== undefined && config.models?.[patch.profile] !== undefined ? patch.profile : (was.profile ?? this.#chosen(config, session) ?? undefined);
198
+ const profile = profileName === undefined ? undefined : config.models?.[profileName];
199
+ const effort = patch.effort !== undefined && profile !== undefined && effortOptions(profile)?.levels.includes(patch.effort) === true ? patch.effort : patch.profile !== undefined ? undefined : was.effort;
200
+ const next = { ...(profileName !== undefined ? { profile: profileName } : {}), ...(effort !== undefined ? { effort } : {}) };
201
+ if (next.profile === was.profile && next.effort === was.effort)
202
+ return null;
203
+ this.write({ works: { ...this.#read().works, [session]: next } });
204
+ return session;
205
+ }
140
206
  if (patch.profile !== undefined && config.models?.[patch.profile] !== undefined) {
141
207
  this.write({ profile: patch.profile });
142
208
  // choosing a profile that can run ends the tour
@@ -152,9 +218,10 @@ export class Settings {
152
218
  this.#default = patch.mode;
153
219
  this.write({ mode: patch.mode });
154
220
  }
155
- else if (/^[A-Za-z0-9._-]+$/.test(patch.session))
221
+ else if (SAFE_SESSION.test(patch.session))
156
222
  this.write({ tiers: { ...this.#read().tiers, [patch.session]: patch.mode } });
157
223
  }
224
+ return null;
158
225
  }
159
226
  }
160
227
  /** Where a profile's sign-in comes from, in words; the secret is never part of it. */
@@ -1,10 +1,11 @@
1
1
  import { realpathSync } from "node:fs";
2
2
  import { SessionStore } from "@vincemakes/kiso-runtime";
3
+ import { named, titleIfUnnamed } from "./names.js";
3
4
  import { createSessionService, toWireEvent } from "@vincemakes/kiso-server";
4
5
  import { createHttpHandler } from "@vincemakes/kiso-server/http";
5
6
  import { createWebAgent, routeOf } from "./agent.js";
6
7
  import { createTaskHub, rowsOf } from "./tasks.js";
7
- import { runTotals, usageFrameAt } from "./usage.js";
8
+ import { runRate, runTotals, usageFrameAt } from "./usage.js";
8
9
  import { webSanitize } from "./sanitize.js";
9
10
  import { TimeIndex } from "./times.js";
10
11
  import { artifactsOf, LogImages } from "./artifacts.js";
@@ -13,15 +14,42 @@ const DELTAS = new Set(["thinking", "text_delta"]);
13
14
  export function createSpace(options) {
14
15
  const store = new SessionStore(options.dir);
15
16
  const tasks = createTaskHub(options.dir);
16
- const route = () => {
17
+ // the Work's own route (a Work can switch its model in the composer): what its usage is canonicalized and priced on
18
+ const route = (sessionId) => {
17
19
  try {
18
- return routeOf(options.model());
20
+ return routeOf(options.model({ sessionId, sessionsDir: options.dir }));
19
21
  }
20
22
  catch {
21
23
  return "unknown";
22
24
  }
23
25
  };
24
26
  const opened = new Set();
27
+ const sessions = new Map();
28
+ const followUps = new Map();
29
+ // owner 2026-11-03: a Work is titled by a summary of its first message, once, beside its first run; the person's
30
+ // own name wins, before or after, and a failure leaves the first request's title. A first message sent in the last
31
+ // minute, not "a run is live": a quick run can end before its first event is seen here, and a replayed old one is old
32
+ const titled = new Set();
33
+ const title = (sessionId, event) => {
34
+ const session = sessions.get(sessionId);
35
+ if (options.titles !== true || session === undefined || titled.has(sessionId) || Date.now() - times.at(sessionId, event.seq) > 60_000)
36
+ return;
37
+ if (service.events(sessionId).some((e) => e.type === "user_input" && e.seq < event.seq) || named(options.dir, sessionId))
38
+ return;
39
+ titled.add(sessionId);
40
+ const text = typeof event.content === "string" ? event.content : event.content.map((b) => (b.type === "text" ? b.text : "")).join(" ");
41
+ void session.title(text, { signal: AbortSignal.timeout(15_000) }).then((name) => titleIfUnnamed(options.dir, sessionId, name), () => undefined);
42
+ };
43
+ const followUp = (sessionId, seq) => {
44
+ const session = sessions.get(sessionId);
45
+ const end = [...service.events(sessionId)].reverse().find((e) => e.type === "terminal");
46
+ if (options.followUps !== true || session === undefined || service.isRunning(sessionId) || end?.seq !== seq || end.outcome.kind !== "completed")
47
+ return Promise.resolve("");
48
+ const at = `${sessionId}:${seq}`;
49
+ if (!followUps.has(at))
50
+ followUps.set(at, session.followUp({ maxTokens: 200 }).catch(() => ""));
51
+ return followUps.get(at);
52
+ };
25
53
  const service = createSessionService({
26
54
  store,
27
55
  open: async (sessionId) => {
@@ -37,15 +65,17 @@ export function createSpace(options) {
37
65
  ...(options.secretEnvNames !== undefined ? { secretEnvNames: options.secretEnvNames } : {}),
38
66
  ...(options.toolLimits !== undefined ? { toolLimits: options.toolLimits } : {}),
39
67
  onSession: (sessionId, session) => {
68
+ sessions.set(sessionId, session);
40
69
  // the model hears how a task ended; nothing wakes an idle session
41
70
  session.useTasks(tasks.managerFor(sessionId), { wake: false });
42
71
  },
43
72
  });
44
73
  },
45
74
  });
46
- const windowOf = () => {
75
+ // the Work's own model (a Work can switch it in the composer), for its context window
76
+ const windowOf = (sessionId) => {
47
77
  try {
48
- const m = options.model();
78
+ const m = options.model({ sessionId, sessionsDir: options.dir });
49
79
  return m.kind === "profile" ? (m.contextWindow ?? null) : null;
50
80
  }
51
81
  catch {
@@ -60,6 +90,8 @@ export function createSpace(options) {
60
90
  const at = { seq: event.seq, ts: times.at(sessionId, event.seq) };
61
91
  frames.push({ event: "at", data: at });
62
92
  }
93
+ if (event.type === "user_input")
94
+ title(sessionId, event);
63
95
  frames.push(...derived(event, sessionId));
64
96
  return frames;
65
97
  };
@@ -85,15 +117,16 @@ export function createSpace(options) {
85
117
  };
86
118
  const derived = (event, sessionId) => {
87
119
  if (event.type === "usage") {
88
- const frame = usageFrameAt(service.events(sessionId), event.seq, route());
89
- return frame === null ? [] : [{ event: "usage", data: { ...frame, window: windowOf() } }];
120
+ const frame = usageFrameAt(service.events(sessionId), event.seq, route(sessionId));
121
+ return frame === null ? [] : [{ event: "usage", data: { ...frame, window: windowOf(sessionId) } }];
90
122
  }
91
123
  if (event.type === "terminal") {
92
- const totals = runTotals(service.events(sessionId), event.seq, route());
93
- return totals === null ? [] : [{ event: "run_usage", data: { seq: event.seq, ...totals } }];
124
+ const events = service.events(sessionId);
125
+ const totals = runTotals(events, event.seq, route(sessionId));
126
+ return totals === null ? [] : [{ event: "run_usage", data: { seq: event.seq, ...totals, tokPerSec: runRate(events, event.seq, totals.output, (seq) => times.at(sessionId, seq)) } }];
94
127
  }
95
128
  if (event.type === "tool_result") {
96
- const artifacts = artifactsOf(event, sessionId, callOf(sessionId, event.callId), realWorkspace());
129
+ const artifacts = artifactsOf(event, sessionId, callOf(sessionId, event.callId), realWorkspace(), options.dir);
97
130
  const text = typeof event.content === "string" ? event.content : event.content.map((b) => (b.type === "text" ? b.text : `[${b.type}]`)).join("\n");
98
131
  const lines = text.split("\n");
99
132
  const frame = {
@@ -155,6 +188,7 @@ export function createSpace(options) {
155
188
  images: new LogImages(options.dir),
156
189
  handler,
157
190
  opened: () => opened,
191
+ followUp,
158
192
  drain: (graceMs) => service.drain(graceMs),
159
193
  close: async () => {
160
194
  await tasks.close();
@@ -28,6 +28,10 @@ export function createTaskHub(storeRoot) {
28
28
  // the manager satisfies ShellTasks structurally (tools-node takes no runtime dependency)
29
29
  shellTasks: (sessionId) => (sessionId === undefined ? undefined : managerFor(sessionId)),
30
30
  managerFor,
31
+ forget: (sessionId) => {
32
+ managers.get(sessionId)?.close();
33
+ managers.delete(sessionId);
34
+ },
31
35
  close: async () => {
32
36
  for (const manager of managers.values()) {
33
37
  await manager.stopAll("exit", 2_000, "moved");
@@ -81,3 +81,22 @@ export function runTotals(events, terminalSeq, route) {
81
81
  }
82
82
  return null;
83
83
  }
84
+ const CALL_OUTPUT = new Set(["thinking", "text_start", "text_delta", "tool_call_start", "tool_call_end"]);
85
+ /** The run's decode rate as the terminal times one call (TPS-1), summed: each call from its first output event to its last usage
86
+ * report (tool time falls between calls), output over the summed time; null under 0.5 s or with no output ("0 tok/s" is no measurement). */
87
+ export function runRate(events, terminalSeq, output, at) {
88
+ const start = events.reduce((n, ev) => (ev.type === "terminal" && ev.seq < terminalSeq ? ev.seq : n), -1);
89
+ let [ms, first, last] = [0, -1, -1];
90
+ for (const ev of events) {
91
+ if (ev.seq <= start || ev.seq > terminalSeq)
92
+ continue;
93
+ if (CALL_OUTPUT.has(ev.type) && first < 0)
94
+ first = ev.seq;
95
+ else if (ev.type === "usage" && first >= 0)
96
+ last = ev.seq;
97
+ if (ev.type === "stop" || ev.seq === terminalSeq)
98
+ [ms, first, last] = [ms + (first >= 0 && last >= 0 ? Math.max(0, at(last) - at(first)) : 0), -1, -1];
99
+ }
100
+ const rate = output === null || ms < 500 ? 0 : Math.round(output / (ms / 1000));
101
+ return rate > 0 ? rate : null;
102
+ }
@@ -0,0 +1,21 @@
1
+ import { rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { Refusal } from "./files.js";
4
+ export const WORK_ACTS = ["pin", "unpin", "archive", "unarchive", "delete"];
5
+ export function workAct(space, settings, session, act) {
6
+ if (!space.store.ids().includes(session))
7
+ throw new Refusal(404, "no such Work in this folder");
8
+ if (act === "pin" || act === "unpin")
9
+ return settings.mark(session, "pins", act === "pin");
10
+ const busy = space.service.isRunning(session) ? "it is running" : space.tasks.managerFor(session).list().some((t) => t.endedAt === undefined) ? "a background task of it is still running" : null;
11
+ if (busy !== null && act !== "unarchive")
12
+ throw new Refusal(409, `${act === "delete" ? "This Work cannot be deleted" : "This Work cannot be archived"} while ${busy}: stop it first`);
13
+ if (act === "archive" || act === "unarchive")
14
+ return settings.mark(session, "archived", act === "archive");
15
+ // closed first: the store refuses any later write to it, so nothing brings the files back
16
+ space.store.close(session);
17
+ space.tasks.forget(session);
18
+ for (const path of [`${session}.jsonl`, `${session}.meta.json`, `${session}.tasks`, `${session}.files`])
19
+ rmSync(join(space.dir, path), { recursive: true, force: true });
20
+ settings.forget(session);
21
+ }
@@ -0,0 +1,2 @@
1
+ /** What `GET /desk/git` answers: the rail's Changes from git (main/git.ts reads it). */
2
+ export {};
@@ -0,0 +1 @@
1
+ :root{--ink:#1c1c1e;--mute:#8a8a8e;--faint:#b4b4ba;--line:#ececee;--blue:#2f6bff;--green:#1f9d57;--red:#d94a4a;--amber:#d98a1a;--sunk:#f6f6f8;--ease:cubic-bezier(.22, .9, .24, 1);--spring:cubic-bezier(.34, 1.4, .5, 1);--mono:ui-monospace, SFMono-Regular, Menlo, monospace;--sans:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{font:14px/1.6 var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased;background:#fff;overflow:hidden}button{font:inherit;color:inherit;cursor:pointer;background:0 0;border:0;padding:0}textarea{font:inherit;color:inherit}svg{display:block}.ico{fill:none;stroke:currentColor;stroke-width:1.8px;stroke-linecap:round;stroke-linejoin:round;width:15px;height:15px}.i16{fill:none;stroke:currentColor;stroke-width:1.75px;stroke-linecap:round;stroke-linejoin:round;flex:none;width:16px;height:16px}.scroll{overflow-anchor:none;scrollbar-width:none;padding:0 0 40px;position:absolute;inset:54px 0 112px;overflow-y:auto;mask-image:linear-gradient(#0000,#000 14px)}.col{flex-direction:column;gap:14px;width:680px;max-width:calc(100% - 40px);margin:0 auto;display:flex}.empty{text-align:center;color:var(--mute);margin:22vh auto 0;font-size:15px}.empty b{color:var(--ink);letter-spacing:-.01em;margin-bottom:6px;font-size:22px;font-weight:600;display:block}.me{white-space:pre-wrap;max-width:82%;animation:in .5s var(--ease) both;background:#f3f3f5;border-radius:18px;align-self:flex-end;padding:9px 14px}@keyframes in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}.comp{width:min(680px,100% - 40px);position:absolute;bottom:20px;left:50%;transform:translate(-50%)}.box{background:#fff;border-radius:22px;flex-direction:column;gap:4px;padding:8px 8px 8px 14px;transition:box-shadow .15s;display:flex;position:relative;box-shadow:0 8px 30px #00000012,0 0 0 1px #0000000a}.box textarea{resize:none;background:0 0;border:0;outline:0;width:100%;max-height:160px;padding:6px 4px 2px 2px;line-height:1.5}.box textarea::placeholder{color:#a5a5a8}.sendb{color:#fff;background:#d7d7d5;border-radius:50%;flex:none;place-items:center;width:32px;height:32px;transition:all .3s;display:grid}.sendb.ready,.sendb.run{background:var(--ink)}.sendb i{background:#fff;border-radius:2px;width:10px;height:10px;display:block}.proc{animation:in .5s var(--ease) both;flex-direction:column;gap:10px;display:flex}.ph{text-align:left;width:100%;color:var(--mute);align-items:center;gap:8px;padding:4px 0;font-size:13px;display:flex}.chev{color:var(--faint);flex:none;transition:transform .15s}.grp:not(.folded)>.ph .chev,.think.open .th-h .chev{transform:rotate(90deg)}.ph .lab{color:#555;font-weight:500}.ph .clk{font-variant-numeric:tabular-nums}.delta{font-variant-numeric:tabular-nums;gap:4px;font-size:12.5px;display:inline-flex}.delta .plus{color:var(--green);font-style:normal}.delta .minus{color:var(--red);font-style:normal}.delta.mod{color:var(--faint)}.pw{transition:grid-template-rows .3s var(--ease);grid-template-rows:1fr;grid-template-columns:minmax(0,1fr);display:grid}.grp.folded>.pw{grid-template-rows:0fr}.pb{min-width:0;min-height:0;overflow:clip}.pb-in{flex-direction:column;gap:12px;padding:6px 0 4px 2px;display:flex}.dotpulse{background:var(--blue);border-radius:50%;flex:none;width:8px;height:8px;position:relative}.dotpulse:after{content:"";border:2px solid var(--blue);opacity:0;border-radius:50%;animation:1.5s ease-out infinite rg;position:absolute;inset:-4px}@keyframes rg{0%{opacity:.6;transform:scale(.5)}to{opacity:0;transform:scale(1.4)}}.think{animation:in .5s var(--ease) both}.th-h{color:var(--mute);text-align:left;align-items:center;gap:8px;width:100%;font-size:13px;display:flex}.th-h .spark{color:#9a8cff;width:14px;height:14px}.ph .lab.shimmer,.th-h .lab.shimmer{background:linear-gradient(90deg,#8a8a8e 20%,#1c1c1e 50%,#8a8a8e 80%) 0 0/200% 100%;color:#0000;background-clip:text;animation:1.8s linear infinite sh}@keyframes sh{to{background-position:-200% 0}}.th-h .t{font-variant-numeric:tabular-nums;color:var(--faint)}.th-full{transition:grid-template-rows .3s var(--ease);grid-template-rows:0fr;grid-template-columns:minmax(0,1fr);display:grid}.think.open .th-full{grid-template-rows:1fr}.th-full>div{min-width:0;min-height:0;overflow:clip}.th-text{color:#7c7c84;white-space:pre-wrap;border-left:2px solid #e8e6f6;margin:6px 0 2px 7px;padding:2px 0 2px 14px;font-size:12.5px;line-height:1.75}.say{color:#2b2b2e;animation:in .4s var(--ease) both;line-height:1.65}.say p{margin:0}.say p+p,.say ul,.say ol,.say pre,.say .tbl{margin-top:6px}.say code{font:12.5px var(--mono);background:#f3f3f5;border-radius:5px;padding:1px 5px}.say ul,.say ol{margin-bottom:0;padding-left:20px}.caret>:last-child:not(ul,ol,pre,hr,.tbl):after,.caret>:is(ul,ol):last-child>li:last-child:after{content:"";background:var(--ink);vertical-align:-2px;width:6px;height:14px;margin-left:2px;animation:.9s step-end infinite bl;display:inline-block}@keyframes bl{50%{opacity:0}}.steer{background:#f3f3f5;border-radius:14px;align-self:flex-end;padding:5px 12px;font-size:13px}.step{animation:in .45s var(--ease) both}.s-row{align-items:center;gap:9px;min-height:26px;display:flex}.s-ic{color:#777;flex:none;place-items:center;width:16px;height:16px;display:grid}.s-ic .sp{border:2px solid #dcdce0;border-top-color:var(--blue);border-radius:50%;width:12px;height:12px;animation:1s linear infinite spn}@keyframes spn{to{transform:rotate(360deg)}}.s-verb{font-size:12.5px;font-weight:600}.s-obj{font:12px var(--mono);color:#555;white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.s-sp{flex:1}.s-time{font:11px var(--mono);color:var(--mute);font-variant-numeric:tabular-nums;flex:none}.step.failed .s-verb,.step.denied .s-verb{color:var(--red)}.bgtag{color:var(--blue);animation:in .4s var(--ease) both;background:#eef3ff;border-radius:8px;flex:none;padding:1px 8px;font-size:11px}.s-bar{background:#ececef;border-radius:3px;height:3px;margin:4px 0 0 25px;position:relative;overflow:hidden}.s-bar b{background:var(--green);width:100%;height:100%;display:block}.s-bar.live b{background:var(--blue);width:35%;animation:slide 1.4s var(--ease) infinite}@keyframes slide{0%{transform:translate(-100%)}to{transform:translate(300%)}}.s-out{background:var(--sunk);font:11.5px/1.6 var(--mono);color:#4a4a52;border-radius:8px;margin:4px 0 0 25px;padding:7px 11px}.s-lines{white-space:pre;overflow:hidden}.s-lines[data-wide=true]{mask-image:linear-gradient(90deg,#000 calc(100% - 36px),#0000)}.s-out .more{color:var(--blue);font:11px var(--sans);margin-top:3px}.s-out .a{color:#176b3a}.s-out .d{color:#a33636}.s-req{font:11px var(--mono);color:var(--mute);font-variant-numeric:tabular-nums;margin:3px 0 0 25px}.s-err{color:var(--red);margin:3px 0 0 25px;font-size:12px}.ask{animation:in .45s var(--ease) both, pl 1.6s ease-in-out .5s infinite;background:#fff;border-radius:16px;padding:13px 15px;box-shadow:0 0 0 1px #f0d9a8,0 8px 30px #d98a1a1f}@keyframes pl{50%{box-shadow:0 0 0 1px #e8c47a,0 8px 36px #d98a1a47}}.ask .h{align-items:center;gap:8px;font-weight:600;display:flex}.ask .h svg{color:var(--amber)}.ask pre{font:12.5px var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;background:#f5f5f6;border-radius:10px;max-height:220px;margin:9px 0;padding:8px 12px;overflow:auto}.ask pre .a{color:#176b3a}.ask pre .d{color:#a33636}.btn{background:#f1f1f3;border-radius:9px;height:30px;margin-right:6px;padding:0 13px;transition:all .2s}.btn.pri{background:var(--ink);color:#fff}.btn:disabled{opacity:.5;cursor:default}.note{color:var(--mute);animation:in .4s var(--ease) both;align-items:center;gap:8px;font-size:12.5px;display:flex}.note .ok{background:var(--green);border-radius:50%;flex:none;width:14px;height:14px;position:relative}.note .ok:after{content:"";border:1.6px solid #fff;border-width:0 1.6px 1.6px 0;width:3px;height:6px;position:absolute;top:2px;left:4.5px;transform:rotate(45deg)}.note .no{background:var(--red);border-radius:50%;flex:none;width:14px;height:14px}.ans{animation:in .4s var(--ease) both;color:#222;font-size:14.5px;line-height:1.75}.ans h1,.ans h2,.ans h3{letter-spacing:-.01em;margin:2px 0 8px;font-size:18px}.ans p{margin:8px 0}.ans ul,.ans ol{margin:8px 0;padding-left:20px}.ans li{margin:4px 0}.ans code{font:13px var(--mono);background:#f3f3f5;border-radius:6px;padding:1px 6px}.ans pre{background:var(--sunk);font:12.5px/1.6 var(--mono);border-radius:10px;padding:10px 14px;overflow:auto}.ans pre code{background:0 0;padding:0}.lk{color:var(--blue);cursor:pointer;border-bottom:1px dashed #0000;text-decoration:none;display:inline;position:relative}.lk:hover{border-bottom-color:var(--blue)}.lk.plain{cursor:default;color:inherit;text-decoration:underline;-webkit-text-decoration-color:var(--line);text-decoration-color:var(--line);border-bottom:0}.fic{vertical-align:-.15em;width:14px;height:14px;margin-right:4px;display:inline}.hov{z-index:5;border:1px solid var(--line);min-width:240px;color:var(--mute);white-space:nowrap;background:#fff;border-radius:12px;align-items:center;gap:10px;padding:8px 12px;font-size:13px;display:none;position:absolute;top:calc(100% + 4px);left:0;box-shadow:0 8px 28px #0000001a}.lk:hover .hov,.lk:focus-visible .hov{display:flex}.hov .o{color:var(--ink);margin-left:auto}.fchip{overflow-wrap:anywhere;vertical-align:baseline;max-width:100%;color:var(--ink);cursor:pointer;background:#f3f4f6;border-radius:6px;padding:1px 6px;display:inline-block}.fchip:hover{background:#e9ebef}.ans .tbl{max-width:100%;margin:10px 0;overflow-x:auto}.ans table{border-collapse:collapse;font-size:13.5px;line-height:1.5}.ans th,.ans td{border:1px solid var(--line);vertical-align:top;padding:5px 10px}.ans th{white-space:nowrap;background:#f7f7f8;font-weight:600}.ans blockquote{border-left:3px solid var(--line);color:#555;margin:8px 0;padding:2px 0 2px 14px}.ans hr{border:0;border-top:1px solid var(--line);margin:14px 0}.acts{color:var(--mute);font:11.5px var(--mono);animation:in .4s var(--ease) both}.run-end{color:#8a5300;overflow-wrap:anywhere;animation:in .3s var(--ease) both;background:#fff7e6;border-radius:10px;margin:6px 0 10px;padding:8px 12px;font-size:13px;line-height:1.5}.run-end.bad{color:#b42318;background:#fef3f2}.racts{color:var(--mute);opacity:0;align-items:center;gap:2px;margin:2px 0 10px -6px;font-size:12px;transition:opacity .15s;display:flex}.racts.me-acts{justify-content:flex-end;margin:-2px 0 0}.turn{display:contents}.turn:hover>.racts:not(.me-acts),.me:hover+.me-acts,.racts:hover,.racts:focus-within{opacity:1}.racts .ab{width:26px;height:26px;color:var(--mute);border-radius:7px;place-items:center;display:grid}.racts .ab:hover{color:var(--ink);background:#0000000d}.racts .ago{color:var(--mute);margin-left:6px;font-size:11.5px}.forknote{color:var(--mute);background:#f6f6f4;border-radius:10px;margin:0 0 14px;padding:8px 12px;font-size:12.5px;line-height:1.5}.forknote.err{color:#b42318;background:#fef3f2}.forknote .flink{color:var(--ink);text-underline-offset:2px;text-decoration:underline}.ask.resume .btn,.verdict{margin-top:8px}.verdict pre{margin:0 0 8px}.toast{color:#fff;animation:in .3s var(--ease) both;background:#1c1c1ee6;border-radius:14px;padding:6px 14px;font-size:12.5px;position:absolute;bottom:92px;left:50%;transform:translate(-50%)}.latest{color:var(--ink);box-shadow:0 0 0 1px var(--line), 0 6px 18px #0000001a;animation:in .25s var(--ease) both;background:#fff;border-radius:15px;padding:5px 13px;font-size:12.5px;position:absolute;bottom:124px;left:50%;transform:translate(-50%)}.loading{color:var(--mute);animation:in .3s var(--ease) .15s both;flex-direction:column;gap:10px;font-size:13px;display:flex}.loading i{background:#f1f1f3;border-radius:6px;height:12px;display:block}.loading i:nth-child(2){width:70%}.loading i:nth-child(3){width:45%}@media (prefers-reduced-motion:reduce){.ph .lab.shimmer,.th-h .lab.shimmer{color:var(--mute);background:0 0;animation:none}.dotpulse:after,.ask{animation:none!important}.pw,.th-full,.chev{transition:none}.brand{animation:none}}.tns{flex:none;align-items:center;gap:6px;margin-left:auto;display:inline-flex}.tn{width:66px;height:42px;box-shadow:0 0 0 1px var(--line), 0 1px 4px #00000014;cursor:zoom-in;background:#fff;border-radius:7px;flex:none;place-items:center;display:inline-grid;overflow:hidden}.tn .fic{width:18px;height:18px;margin:0}.tn-img{object-fit:cover;width:100%;height:100%;display:block}.tn-img.gone,.ip-img.gone{color:var(--faint);text-align:center;place-items:center;font-size:10.5px;line-height:1.2;display:grid}.tpage{color:var(--mute);text-align:center;flex-direction:column;align-items:center;gap:2px;padding:0 4px;font-size:10px;line-height:1.2;display:flex;overflow:hidden}.tpage>span{white-space:nowrap;text-overflow:ellipsis;max-width:58px;overflow:hidden}.tn-more{color:var(--mute);font-size:12px}.s-row.click{cursor:pointer}.step.shown .s-row .chev{transform:rotate(90deg)}.inplace{flex-direction:column;gap:8px;margin:6px 0 2px 25px;display:flex}.ip-img{width:100%;box-shadow:0 0 0 1px var(--line);cursor:zoom-in;border-radius:10px;display:block}.made{flex-wrap:wrap;gap:6px;margin:10px 0 2px;display:flex}.made-c{border:1px solid var(--line);max-width:260px;height:32px;color:var(--ink);background:#fff;border-radius:10px;align-items:center;gap:8px;padding:0 12px 0 10px;font-size:13px;display:inline-flex}.made-c:hover{background:var(--sunk)}.made-c .i16{color:var(--mute)}.made-c span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.lightbox{z-index:200;animation:in .15s var(--ease) both;background:#111113d6;flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:56px 24px 24px;display:flex;position:fixed;inset:0}.lb-img{object-fit:contain;background:#fff;border-radius:10px;max-width:min(1200px,100%);max-height:calc(100vh - 120px);box-shadow:0 20px 60px #00000073}.lb-cap{color:#d9d9dc;font-size:12.5px}.lb-acts{gap:6px;display:flex;position:absolute;top:14px;right:14px}.lb-b{color:#fff;background:#ffffff1f;border-radius:9px;place-items:center;width:34px;height:34px;display:grid}.lb-b:hover{background:#ffffff38}.box.drag{box-shadow:0 0 0 2px var(--blue), 0 8px 30px #2f6bff26}.cbar{align-items:center;gap:4px;margin-left:-6px;display:flex}.cm{display:inline-flex;position:relative}.cpill{color:#44444a;white-space:nowrap;border-radius:999px;align-items:center;gap:6px;height:30px;padding:0 10px;font-size:13px;display:inline-flex}.cpill:hover:not(:disabled){background:var(--sunk)}.cpill:disabled{opacity:.55;cursor:default}.cpill.round{justify-content:center;width:30px;padding:0}.cpill.plan{color:#6d4fd8}.cpill.full{color:#c2410c}.cpill .sub{color:var(--faint)}.cpill [data-field=model-pill]{text-overflow:ellipsis;max-width:180px;overflow:hidden}.cmenu{z-index:90;min-width:260px;max-width:340px;box-shadow:0 14px 50px #00000029, 0 0 0 1px var(--line);animation:in .15s var(--ease) both;background:#fff;border-radius:14px;padding:6px;position:absolute;bottom:calc(100% + 8px);left:0}.cbar>.cm:last-of-type .cmenu,.cm .cmenu[data-menu=model]{left:auto;right:0}.cm-h{color:var(--mute);padding:6px 10px 4px;font-size:11.5px}.cm-i{text-align:left;width:100%;color:var(--ink);border-radius:9px;align-items:flex-start;gap:10px;padding:7px 10px;display:flex}.cm-i:hover:not(:disabled),.cm-i.on{background:var(--sunk)}.cm-i:disabled{opacity:.5;cursor:default}.cm-i>.i16{color:var(--mute);margin-top:2px}.cm-i>.i16:last-child{color:var(--blue);margin-left:auto}.cm-t{flex-direction:column;min-width:0;display:flex}.cm-t b{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.cm-t small{color:var(--mute);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.cm-at{text-align:center;width:16px;color:var(--mute);font-weight:600}.cm-dot{background:#9a9aa0;border-radius:50%;flex:none;width:8px;height:8px;margin:6px 4px 0}.cm-dot.plan{background:#6d4fd8}.cm-dot.full{background:#c2410c}.cm-tag{border:1px solid var(--line);color:var(--mute);border-radius:6px;margin-left:6px;padding:0 5px;font-size:11px;font-weight:400}.cm-seg{gap:4px;padding:2px 8px 8px;display:flex}.cm-seg button{background:var(--sunk);color:var(--mute);border-radius:7px;flex:1;padding:4px 0;font-size:12.5px}.cm-seg button.on{background:var(--ink);color:#fff}.cmenu[data-menu=model]{width:340px;max-width:calc(100vw - 32px)}.cm-list{scrollbar-width:thin;max-height:264px;overflow-y:auto}.cm-i.one{align-items:center;gap:8px;padding:6px 10px}.cm-i.one>b{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:13.5px;font-weight:450;overflow:hidden}.cm-i.one>small{max-width:42%;color:var(--faint);text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:0 auto;font-size:12px;overflow:hidden}.cm-i.one>.i16,.cm-nock{flex:none;width:16px;margin:0}.cm-i.one>.i16:last-child{color:var(--blue)}.cmenu .sep{background:var(--line);height:1px;margin:5px 4px}.cm-fly{position:relative}.cm-row>.cm-v{color:var(--mute);text-transform:capitalize}.cm-i.one.cm-row>.i16:last-child{color:var(--mute);transform:rotate(-90deg)}.cm .cmenu.cm-sub[data-menu]{width:220px;min-width:200px;bottom:-6px;left:auto;right:calc(100% + 12px)}.cm-sub .cm-i.one>b{text-transform:capitalize}.cm-err{color:#9a3412;background:#fff4ec;border-radius:8px;margin:4px 8px 6px;padding:6px 8px;font-size:12.5px}.atp{bottom:calc(100% - 4px);left:8px}.atts{flex-wrap:wrap;gap:8px;padding:4px 0 2px;display:flex}.att{border:1px solid var(--line);background:var(--sunk);border-radius:10px;align-items:center;gap:8px;height:56px;display:inline-flex;position:relative;overflow:hidden}.att.image{width:56px}.att.image img{object-fit:cover;width:100%;height:100%}.att.file{max-width:220px;padding:0 30px 0 10px;font-size:12.5px}.att.file span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.att-x{color:#fff;background:#0000008c;border-radius:50%;place-items:center;width:18px;height:18px;display:grid;position:absolute;top:3px;right:3px}.att-x .i16{stroke-width:2.2px;width:11px;height:11px}.cwarn{color:#92400e;background:#fffbeb;border-radius:10px;align-items:center;gap:8px;margin:2px 0;padding:7px 10px;font-size:12.5px;display:flex}.cnote{color:var(--red);padding:0 2px;font-size:12.5px}.me-pics{--att:64px;flex-wrap:wrap;justify-content:flex-end;align-items:stretch;gap:6px;margin:18px 0 -12px;display:flex}.me-pic{width:var(--att);height:var(--att);border:1px solid var(--line);border-radius:12px;overflow:hidden}.me-img{object-fit:cover;width:100%;height:100%;display:block}.me-file{height:var(--att);box-sizing:border-box;border:1px solid var(--line);text-align:left;background:#fff;border-radius:12px;align-items:center;gap:10px;max-width:260px;padding:0 14px 0 11px;display:flex}.me-file:hover{background:#fafafb}.me-ft{background:var(--sunk);width:36px;height:42px;font:600 10px var(--mono);color:#6b6b70;border-radius:7px;flex:none;place-items:center;display:grid}.me-fn{flex-direction:column;min-width:0;display:flex}.me-fn b{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.me-fn small{color:var(--mute);font-size:12px}.fviewer{z-index:200;animation:in .15s var(--ease) both;background:#11111352;place-items:center;padding:24px;display:grid;position:fixed;inset:0}.fv-box{background:#fff;border-radius:14px;flex-direction:column;width:min(760px,100%);max-height:min(80vh,900px);display:flex;overflow:hidden;box-shadow:0 18px 60px #0000002e}.fv-h{border-bottom:1px solid var(--line);align-items:center;gap:10px;padding:10px 12px 10px 16px;font-size:13px;display:flex}.fv-h b{text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.fv-h span{color:var(--mute);margin-left:auto}.fv-text{font:12.5px/1.6 var(--mono);color:#333;white-space:pre;margin:0;padding:12px 16px;overflow:auto}.me.clamp{max-height:calc(9.6em + 18px);position:relative;overflow:hidden}.me.folded:after{content:"";pointer-events:none;background:linear-gradient(#f3f3f500,#f3f3f5);height:3.2em;position:absolute;bottom:0;left:0;right:0}.me-more{color:#3a3a3e;align-self:flex-end;margin:-2px 12px 0 0;font-size:13px}.me-more:hover{color:var(--ink);text-decoration:underline}:root{--sw:264px;--side:#f5f5f6}.win{background:#fff;display:flex;position:fixed;inset:0;overflow:clip}.sidetoggle{z-index:70;width:34px;height:34px;color:var(--mute);transition:left .6s var(--ease);border-radius:10px;place-items:center;display:grid;position:absolute;top:10px;left:12px}.win[data-side="1"] .sidetoggle{left:calc(var(--sw) - 46px)}.brand{z-index:70;height:34px;color:var(--ink);pointer-events:none;animation:brandIn .3s .2s var(--ease) both;align-items:center;gap:8px;display:flex;position:absolute;top:10px;left:22px}@keyframes brandIn{0%{opacity:0}}.brand .mark{flex:none}.brand .word{letter-spacing:-.02em;font-size:17px;font-weight:600;line-height:1}.sidetoggle:hover{color:var(--ink);background:#0000000f}.main{flex:1 1 0;min-width:0;display:flex;position:relative;container-type:inline-size}.side{background:var(--side);width:0;transition:width .6s var(--ease);flex:none;overflow:hidden}.win[data-side="1"] .side{width:var(--sw)}.side-in{width:var(--sw);flex-direction:column;height:100%;padding:54px 10px 10px;display:flex}.newbtn{background:#ffffffbf;border-radius:11px;align-items:center;gap:8px;width:100%;height:38px;margin-bottom:8px;padding:0 12px;transition:all .2s;display:flex;box-shadow:0 0 0 1px #0000000a}.newbtn:hover{background:#fff}.hlist{scrollbar-width:thin;scrollbar-color:#cfcfcb transparent;flex:1;margin:0 -6px;padding:0 6px;overflow-y:auto}.grp{color:var(--mute);padding:12px 8px 4px;font-size:12px}.hlist section+section{margin-top:10px}.grp.gh{text-align:left;align-items:center;gap:5px;width:100%;display:flex}.grp.gh:hover{color:var(--ink)}.grp.gh .tri{fill:currentColor;width:10px;height:10px;transition:transform .2s var(--ease)}.shut>.grp.gh .tri{transform:rotate(-90deg)}.item{text-align:left;border-radius:10px;align-items:center;gap:8px;width:100%;padding:7px 9px;transition:background .15s;display:flex}.item.child{padding-left:30px;position:relative}.item.child:before{content:"";border-bottom:1.5px solid #d6d6d2;border-left:1.5px solid #d6d6d2;border-bottom-left-radius:6px;width:8px;height:calc(50% - 1px);position:absolute;top:-2px;left:16px}.item.child.more:after{content:"";border-left:1.5px solid #d6d6d2;position:absolute;top:-2px;bottom:-2px;left:16px}.item.child.more:before{display:none}.item .kids{color:var(--mute);border-radius:6px;flex:none;align-items:center;gap:3px;margin:-2px 0;padding:2px 4px;display:inline-flex}.item .kids:hover{color:var(--ink);background:#0000000f}.item .kids .tri{fill:currentColor;width:13px;height:13px;transition:transform .2s var(--ease);transform:rotate(-90deg)}.item .kids.open .tri{transform:none}.item .kids b{font-size:11.5px;font-weight:400}.item:hover{background:#0000000b}.item.on{background:#ffffffe6;box-shadow:0 0 0 1px #0000000a}.item .tt{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.item small{color:var(--mute);white-space:nowrap;text-overflow:ellipsis;flex:none;max-width:38%;font-size:11.5px;overflow:hidden}.dot{border-radius:50%;flex:none;width:8px;height:8px}.dot.amber{background:var(--amber)}.item .sp{border:2px solid #d8d8d5;border-top-color:var(--blue);border-radius:50%;flex:none;width:12px;height:12px;animation:1s linear infinite sp}@keyframes sp{to{transform:rotate(360deg)}}.fn{border-top:1px solid #0000000f;margin:8px -4px 0;padding-top:6px}.fn-h{width:100%;color:var(--mute);border-radius:10px;align-items:center;gap:6px;padding:7px 10px;display:flex}.fn-h:hover{color:var(--ink)}.fn-h .chev{transition:transform .3s var(--ease);margin-left:auto}.fn.open .fn-h .chev{transform:rotate(90deg)}.fn-b{transition:grid-template-rows .4s var(--ease);grid-template-rows:0fr;display:grid}.fn.open .fn-b{grid-template-rows:1fr}.fn-b>div{overflow:hidden}.fn-b button{color:#444;border-radius:10px;align-items:center;gap:10px;width:100%;padding:7px 10px;display:flex}.fn-b button:hover,.fn-b button.on{background:#0000000b}.row{position:relative}.row .dots{color:#555;border-radius:7px;place-items:center;width:26px;height:24px;display:none;position:absolute;top:50%;right:6px;transform:translateY(-50%)}.row:hover .dots,.row.menu-open .dots{display:grid}.row:hover .item small,.row.menu-open .item small{visibility:hidden}.row .dots:hover{background:#00000012}.row-in{min-width:0;font:inherit;color:var(--ink);background:0 0;border:0;outline:0;flex:1;padding:0}.row-in::selection{background:#cfe0ff}.menu.smenu{z-index:140;min-width:200px;position:fixed;right:auto}.menu .sep{background:var(--line);height:1px;margin:5px 4px}.menu button.danger{color:#c0392b}.menu.smenu kbd{font:inherit;color:var(--mute);margin-left:auto;font-size:12px}.side-err{color:#b42318;background:#fef3f2;border-radius:9px;margin:6px 4px;padding:7px 10px;font-size:12.5px;line-height:1.45}.archived-h{text-align:left;width:100%;display:block}.archived-h:hover{color:var(--ink)}.settings{padding:72px 0 40px;position:absolute;inset:0;overflow-y:auto}.settings>*{width:560px;max-width:calc(100% - 40px);margin-left:auto;margin-right:auto}.settings h1{letter-spacing:-.01em;margin-top:0;margin-bottom:20px;font-size:22px;font-weight:600}.srow{border-top:1px solid var(--line);align-items:center;gap:16px;padding:14px 0;display:flex}.slabel{flex:1;min-width:45%}.slabel small{color:var(--mute);font-size:12px;display:block}.sfield .cpill{border:1px solid var(--line);background:#fff;max-width:280px}.sfield{text-align:right;max-width:55%}.sfield .cpill>span{text-overflow:ellipsis;overflow:hidden}.sfield [data-field=effort],.sfield [data-effort] b{text-transform:capitalize}.sfield .cmenu.down{text-align:left;width:340px;max-height:min(420px,60vh);inset:calc(100% + 6px) 0 auto auto;overflow-y:auto}.muted{color:var(--mute)}.okline{color:var(--green)}.warnline{color:var(--amber)}.folders{border:0;border-radius:16px;flex-direction:column;gap:10px;width:min(560px,100vw - 32px);max-height:min(620px,100vh - 48px);padding:18px;display:flex;box-shadow:0 18px 60px #0000002e}.folders:not([open]){display:none}.folders::backdrop{background:#0000002e}.folders h2{margin:0;font-size:16px;font-weight:600}.folders .frow{align-items:center;gap:8px;display:flex}.folders .frow input{border:1px solid var(--line,#e5e5e2);min-width:0;height:32px;font:inherit;border-radius:9px;flex:1;padding:0 10px}.folders .frow.end{justify-content:flex-end}.folders .full{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;font-size:12px;overflow:hidden}.folders .chip.on{background:#00000012}.folders .chip.primary{background:var(--ink);color:#fff}.folders .chip.primary:disabled{opacity:.4}.folders .crumbs{color:var(--mute);flex-wrap:wrap;gap:2px;font-size:13px;display:flex}.folders .crumbs button+button:before{content:"/";color:#c4c4c0;margin-right:4px}.folders .dirs{border-top:1px solid #0000000f;border-bottom:1px solid #0000000f;flex-direction:column;flex:1;min-height:160px;padding:4px 0;display:flex;overflow-y:auto}.folders .dir{text-align:left;border-radius:8px;padding:6px 8px}.folders button.dir:hover{background:#0000000b}.folders .dir.off{color:var(--mute)}.signedout{color:var(--mute);text-align:center;background:#fff;place-items:center;padding:16px;font-size:14px;display:grid;position:fixed;inset:0}.railtoggle{width:34px;height:34px;color:var(--mute);border-radius:10px;flex:none;place-items:center;display:grid;position:relative}.railtoggle:hover{color:var(--ink);background:#0000000f}.railtoggle.on{color:var(--ink);background:#efeff1}.rail{width:0;transition:width .5s var(--ease);background:#fbfbfc;flex:none;overflow:hidden}.win[data-rail="1"] .rail{width:280px}.rail-in{border-left:1px solid var(--line);flex-direction:column;width:280px;min-width:0;height:100%;display:flex}.rail-h{border-bottom:1px solid var(--line);height:54px;color:var(--mute);flex:none;align-items:center;padding:0 16px;font-size:12.5px;display:flex}.rail-b{scrollbar-width:thin;flex:1;overflow-y:auto}.grow{flex:1}.mono{color:var(--mute);font:11.5px ui-monospace,Menlo,monospace}.r-sec{border-bottom:1px solid var(--line);padding:2px 0 6px}.r-sec.closed{padding-bottom:2px}.r-sh{text-align:left;width:100%;height:36px;color:var(--ink);align-items:center;gap:10px;padding:0 12px 0 16px;font-size:13px;display:flex}.r-sh b{font-weight:600}.r-sh:hover .r-chev{color:var(--ink)}.r-cnt{color:var(--faint);font-size:12.5px}.r-ic{width:16px;height:16px;color:var(--mute);flex:none;place-items:center;display:grid}.r-chev{transition:transform .15s var(--ease)}.closed>.r-sh .r-chev,.r-chev.side{transform:rotate(-90deg)}.r-row{width:100%;height:32px;color:var(--ink);text-align:left;cursor:pointer;align-items:center;gap:10px;padding:0 12px 0 16px;font-size:13px;display:flex}.r-row:hover{background:#f1f1f4}.r-row.dim{color:var(--mute)}.r-row.r-br{cursor:default}.r-row.r-br:hover{background:0 0}.r-nm{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.r-nm.mono{color:inherit;font-size:12px}.r-mt{color:var(--faint);font-variant-numeric:tabular-nums;flex:none;font:11.5px ui-monospace,Menlo,monospace}.r-mt .add,.r-sh .add{color:var(--green)}.r-mt .del,.r-sh .del{color:var(--red)}.r-stop{opacity:.7;border-radius:6px;flex:none;place-items:center;width:20px;height:20px;display:grid}.r-stop:hover{opacity:1;background:#e5e5ea}.r-stop i{background:var(--ink);border-radius:2px;width:8px;height:8px}.r-ctx .r-sh{height:38px}.r-ctxv{color:var(--mute);white-space:nowrap;font:12px ui-monospace,Menlo,monospace}.r-ctxv b{color:var(--ink);font-weight:600}.r-meter{background:#ebebef;border-radius:999px;gap:1px;height:4px;margin:-2px 12px 8px 42px;display:flex;position:relative}.r-meter b{background:var(--blue);height:100%;transition:width .8s var(--ease);border-radius:999px;flex:none;display:block}.r-meter .tick{background:#8e8e96;border-radius:1px;width:2px;height:10px;margin-left:-1px;position:absolute;top:-3px}.r-meter .tick.soft{background:#c9c9cf}.seg-conversation{background:var(--blue)!important}.seg-system{background:#8b5cf6!important}.seg-instructions{background:#14b8a6!important}.seg-tools{background:#f97316!important}.seg-free{background:#ebebef;box-shadow:inset 0 0 0 1px #dcdce1}.r-parts{flex-direction:column;grid-column:1/-1;gap:2px;margin-bottom:8px;display:flex}.r-part{color:#444;grid-template-columns:10px 1fr auto 44px;align-items:center;gap:8px;height:22px;display:grid}.r-part .sw{border-radius:2px;width:9px;height:9px}.r-part span{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.r-part b{color:var(--mute);text-align:right;font:500 11.5px ui-monospace,Menlo,monospace}.r-part em{color:var(--ink);text-align:right;font:11.5px ui-monospace,Menlo,monospace}.r-parts small{color:var(--faint);margin-top:2px;font-size:11.5px}.r-act{color:var(--ink);white-space:nowrap;background:#fff;border:1px solid #d5d5da;border-radius:7px;flex:none;margin-left:4px;padding:2px 9px;font-size:12px}.r-act:hover{background:var(--sunk)}.r-pr{text-decoration:none}.r-ci{color:var(--green);flex:none;font:11.5px ui-monospace,Menlo,monospace}.r-ci.run{color:var(--amber)}.r-ci.bad{color:var(--red)}.dlg-veil{z-index:210;background:#14141852;place-items:center;padding:16px;display:grid;position:fixed;inset:0}.dlg{width:min(520px,100%);max-height:calc(100vh - 32px);animation:in .15s var(--ease) both;background:#fff;border-radius:14px;padding:18px 20px;overflow:auto;box-shadow:0 24px 70px #00000040}.dlg h3{margin:0 0 4px;font-size:15px}.dlg-sub{margin-bottom:10px}.dlg-l{color:var(--mute);margin:10px 0 4px;font-size:12px;display:block}.dlg-in{width:100%;font:inherit;resize:vertical;border:1px solid #dfdfe4;border-radius:9px;padding:8px 10px;font-size:13px}.dlg-files{flex-direction:column;gap:2px;max-height:200px;padding:4px 0;display:flex;overflow:auto}.dlg-file{align-items:center;gap:8px;font-size:12.5px;display:flex}.dlg-file small{color:var(--mute)}.dlg-err{color:#9a3412;background:#fff4ec;border-radius:8px;margin-top:10px;padding:7px 10px;font-size:12.5px}.dlg-btns{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.dlg-btns button{background:#fff;border:1px solid #d5d5da;border-radius:9px;padding:6px 14px;font-size:13px}.dlg-btns .go{background:var(--ink);border-color:var(--ink);color:#fff}.dlg-btns button:disabled{opacity:.6}.r-kv{color:#555;grid-template-columns:1fr auto;gap:3px 10px;margin:0 12px 6px 42px;font-size:12.5px;display:grid}.r-kv b{color:var(--ink);font-variant-numeric:tabular-nums;text-align:right;font-weight:500}.r-kv small{color:var(--faint);grid-column:1/-1;margin-top:4px;font-size:11.5px}.r-env{border-top:1px solid var(--line);color:#555;white-space:nowrap;flex:none;align-items:center;gap:8px;min-width:0;height:40px;padding:0 12px 0 16px;font-size:12.5px;display:flex}.r-env-f{text-overflow:ellipsis;flex:0 auto;min-width:3ch;overflow:hidden}.r-env-b{text-overflow:ellipsis;color:#555;flex:0 auto;min-width:3ch;overflow:hidden}.chip2{color:#555;background:#efeff1;border-radius:10px;flex:none;margin-left:auto;padding:2px 9px;font-size:11.5px}.tip{z-index:150;width:236px;box-shadow:0 14px 50px #0000002e, 0 0 0 1px var(--line);color:#444;pointer-events:none;animation:in .18s var(--ease) both;background:#fff;border-radius:14px;padding:12px 14px;font-size:12.5px;position:fixed;right:290px}.tip h6{color:var(--mute);letter-spacing:.04em;word-break:break-all;margin:0 0 8px;font-size:12px;font-weight:500}.tip .kv{justify-content:space-between;gap:12px;padding:3px 0;display:flex}.tip .kv b{color:var(--ink);font-variant-numeric:tabular-nums;text-align:right;word-break:break-all;font-weight:600}.tip .lg{color:#d9d9dc;white-space:pre-wrap;word-break:break-all;background:#161618;border-radius:8px;margin-top:6px;padding:8px 10px;font:11.5px/1.6 ui-monospace,Menlo,monospace}.tip .tnote{color:var(--mute);margin-top:6px}.dlg-p{color:#444;margin:4px 0 6px;font-size:13px;line-height:1.55}.dlg-btns .go.danger{background:#c0392b;border-color:#c0392b}.work{flex:1 1 0;min-width:0;display:flex;position:relative}.chat{flex:320px;min-width:320px;position:relative}.panel{border-left:1px solid var(--line);min-width:320px;animation:in .4s var(--ease) both;background:#fff;flex-direction:column;flex:0 50%;display:flex;position:relative;overflow:hidden}.panel.full{flex:100%}.work.paneled:has(.panel.full) .chat{flex:0 0 0;min-width:0;overflow:hidden}@container (width<=639px){.work.paneled .chat{flex:0 0 0;min-width:0;overflow:hidden}.work.paneled .panel{flex:auto;min-width:0}}.panel.full.clear .pv-h{padding-left:132px}.pv-h{border-bottom:1px solid var(--line);flex:none;align-items:center;gap:2px;height:54px;padding:0 12px;display:flex}.pv-h .name{text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:7px;min-width:0;margin-right:auto;font-weight:600;display:inline-flex;overflow:hidden}.sbtn{width:34px;height:34px;color:var(--mute);border-radius:10px;place-items:center;display:grid}.sbtn:hover{color:var(--ink);background:#0000000f}.pv-b{flex:1;padding:12px;overflow:auto}.pv-b.flush{flex-direction:column;padding:0;display:flex}.pnote{color:var(--mute);padding:6px 2px;font-size:12px}.pnote.warn{color:var(--amber);padding:6px 10px}.pv-b .empty{color:var(--mute);text-align:left;margin:0;padding:4px 0;font-size:13.5px}.pad{padding:20px 16px!important}.morebtn{width:34px;height:34px;color:var(--mute);border-radius:10px;flex:none;place-items:center;display:grid;position:relative}.morebtn:hover{color:var(--ink);background:#0000000f}.menu{z-index:120;min-width:210px;box-shadow:0 12px 44px #00000029, 0 0 0 1px var(--line);animation:in .2s var(--ease) both;background:#fff;border-radius:14px;padding:6px;position:absolute;top:50px;right:12px}.menu button{text-align:left;border-radius:9px;align-items:center;gap:10px;width:100%;padding:8px 10px;display:flex}.menu button:hover{background:#f4f4f6}.menu .ico{color:#777}.diff{border:1px solid var(--line);background:#fafaf9;border-radius:10px;margin-bottom:10px;font:12.5px/1.7 ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.diff div{white-space:pre-wrap;word-break:break-all;padding:0 12px}.diff .a{color:#176b3a;background:#e8f6ec}.diff .d{color:#a33636;background:#fbe9e9}.diff .h{color:var(--mute);background:#f1f1ef;padding:4px 12px}.log{color:#d9d9dc;white-space:pre-wrap;word-break:break-all;background:#161618;border-radius:12px;min-height:120px;padding:14px;font:12.5px/1.7 ui-monospace,Menlo,monospace}.log.txt{color:#333;border:1px solid var(--line);background:#fafaf9}.tmeta{padding:4px 0 8px}.cmd{margin-bottom:12px}.crumbs{color:var(--mute);flex-wrap:wrap;gap:2px;margin-bottom:8px;font-size:12.5px;display:flex}.crumbs button:hover,.crumb:hover{color:var(--ink)}.crumb{color:var(--mute);margin-bottom:10px;font-size:12.5px}.tree{font-size:13.5px}.tree button{text-align:left;border-radius:8px;justify-content:space-between;gap:10px;width:100%;padding:7px 8px;display:flex}.tree button:hover{background:#f4f4f2}.tree button:disabled{opacity:.6;cursor:default}.tree span{color:var(--mute);flex:none}.tree span.plus{color:var(--green)}.imgprev{max-width:100%;box-shadow:0 0 0 1px var(--line);border-radius:10px}[data-panel-body=open] .imgprev{cursor:zoom-in}[data-panel-body=open] .imgprev.actual{cursor:zoom-out;max-width:none}.modes{gap:4px;padding:0 0 10px;display:flex}.modes button{color:var(--mute);border-radius:7px;padding:3px 10px;font-size:12.5px}.modes button:hover,.modes button.on{color:var(--ink);background:#f3f3f5}.mdprev{padding:4px 6px}.browser{flex-direction:column;height:100%;display:flex}.urlbar{border-bottom:1px solid var(--line);background:#f7f7f8;align-items:center;gap:8px;padding:8px 10px;display:flex}.urlbar .u{color:#555;font:inherit;box-shadow:0 0 0 1px var(--line);background:#fff;border:0;border-radius:8px;outline:0;flex:1;padding:4px 10px;font-size:13px}.chips{flex-wrap:wrap;gap:6px;padding:6px 10px;display:flex}.chips button{background:#f3f3f5;border-radius:10px;padding:2px 10px;font-size:12px}.browser iframe{background:#fff;border:0;flex:1;width:100%}.click{cursor:pointer}.homebox{flex-direction:column;justify-content:center;align-items:center;gap:28px;padding:0 20px 60px;display:flex;position:absolute;inset:0}.homebox h1{letter-spacing:-.02em;text-align:center;margin:0;font-size:40px;font-weight:300}.comp.inline{width:min(680px,100%);position:static;transform:none}.tray{align-items:center;gap:8px;padding:10px 4px 0;display:flex}.trayfold{position:relative}.chip{color:#444;background:#f3f3f5;border:0;border-radius:15px;align-items:center;gap:6px;height:30px;padding:0 12px;font-size:13px;display:inline-flex}.chip:hover{background:#ebebee}select.chip{appearance:none;cursor:pointer;font:inherit;padding-right:14px;font-size:13px}.tray .hint{color:var(--mute);margin-left:auto;font-size:12px}.fmenu{min-width:220px;inset:auto auto 36px 0}.mlabel{color:var(--mute);padding:6px 10px 2px;font-size:11.5px}.guide{text-align:left;background:#fff;border-radius:18px;flex-direction:column;gap:10px;width:min(560px,100%);padding:22px 24px;display:flex;box-shadow:0 8px 30px #00000012,0 0 0 1px #0000000a}.guide h2{margin:0;font-size:18px;font-weight:600}.guide p{color:#555;margin:0 0 6px}.guide label{color:var(--mute);font-size:12.5px}.guide input{font:inherit;border:1px solid var(--line);border-radius:10px;outline:0;width:100%;padding:8px 12px}.gprofile{text-align:left;background:#f6f6f8;border-radius:10px;align-items:baseline;gap:8px;padding:9px 12px;display:flex}.gprofile:hover{background:#ededf1}.gprofile span{color:var(--mute);font-size:12.5px}.gprofile small{color:var(--amber);margin-left:auto}.gdemo{color:var(--blue);align-self:flex-start;margin-top:4px}.grow-row{gap:8px;display:flex}[data-panel-body=open].served{flex-direction:column;height:100%;display:flex}.pageview{width:100%;min-height:60vh;box-shadow:0 0 0 1px var(--line);background:#fff;border:0;border-radius:8px;flex:1}.mediaview{width:100%}.hdr{z-index:60;border-bottom:1px solid var(--line);background:#fff;align-items:center;gap:4px;min-width:0;height:54px;padding:0 10px 0 14px;display:flex;position:absolute;top:0;left:0;right:0}.win[data-side="0"] .hdr{padding-left:54px}.hdr-title{min-width:0;max-width:55%;color:var(--ink);white-space:nowrap;text-overflow:ellipsis;text-align:left;border-radius:8px;padding:5px 8px;font-size:14px;font-weight:600;overflow:hidden}.hdr-title:hover{background:var(--sunk)}input.hdr-title.edit{width:min(440px,55%);max-width:none;font:inherit;background:0 0;border:0;outline:0;font-size:14px;font-weight:600}input.hdr-title.edit:hover{background:0 0}.hdr-title.edit::selection{background:#cfe0ff}.hdr-sub{min-width:0;color:var(--faint);white-space:nowrap;text-overflow:ellipsis;font-size:12.5px;overflow:hidden}.ptabs{flex:1;align-items:center;gap:2px;min-width:0;margin-right:auto;display:flex;overflow:hidden}.ptab{color:#555;cursor:pointer;white-space:nowrap;border-radius:9px;flex:0 auto;align-items:center;gap:6px;min-width:0;max-width:200px;height:32px;padding:0 6px 0 10px;font-size:13px;display:inline-flex}.ptab>span{text-overflow:ellipsis;min-width:0;overflow:hidden}.ptab:hover{background:var(--sunk)}.ptab.on{color:var(--ink);box-shadow:0 0 0 1px var(--line);background:#fff}.ptab .i16{color:var(--mute)}.ptab-x{width:18px;height:18px;color:var(--faint);border-radius:5px;flex:none;place-items:center;display:grid}.ptab-x:hover{color:var(--ink);background:#e7e7ea}.ptab-x .i16{width:12px;height:12px}.tabpane{flex-direction:column;flex:1;min-height:0}.urlbar a,.urlbar button{color:#555;border-radius:7px;place-items:center;width:28px;height:28px;display:grid}.urlbar a:hover,.urlbar button:hover{background:#ececef}