@oversk7/pi-web-dsh-style 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oversk7/pi-web-dsh-style",
3
- "version": "1.0.0",
4
- "description": "An unofficial DSH-styled local Windows browser UI extension for pi coding agent",
3
+ "version": "1.0.1",
4
+ "description": "An unofficial DSH-styled Windows browser UI for pi coding agent with configurable LAN and native frp relay access",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "oversk7",
@@ -25,6 +25,9 @@
25
25
  "file-preview.ts",
26
26
  "index.ts",
27
27
  "local-session.ts",
28
+ "network-access.ts",
29
+ "network-settings.ts",
30
+ "frp-process.ts",
28
31
  "pi-rpc.ts",
29
32
  "sse-channel.ts",
30
33
  "server.ts",
package/server.ts CHANGED
@@ -3,7 +3,7 @@ import { createServer } from "node:http";
3
3
  import { spawn } from "node:child_process";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { existsSync } from "node:fs";
6
- import { mkdir, readFile, readdir, stat, unlink } from "node:fs/promises";
6
+ import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink } from "node:fs/promises";
7
7
  import { homedir, platform, tmpdir } from "node:os";
8
8
  import { basename, dirname, extname, join, normalize, resolve, sep } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
@@ -17,10 +17,13 @@ import {
17
17
  type RpcQueueSnapshot,
18
18
  } from "./abort-session.ts";
19
19
  import { ImageAttachmentStore } from "./attachment-store.ts";
20
+ import { isLocalRequest, WebAccess } from "./network-access.ts";
21
+ import { FrpProcess } from "./frp-process.ts";
22
+ import { frpcConfig, loadNetworkSettings, proxyForNetwork, publicNetworkSettings, relayServerFiles, resolveFrpc, saveNetworkSettings, updateNetworkSettings, type NetworkMode, type NetworkSettings } from "./network-settings.ts";
20
23
  import { closeSseClient, createSseClient, sendSseEvent, type SseClient } from "./sse-channel.ts";
21
24
  import { closeAllPiRpcs, spawnPiRpc, type Json, type PiRpc } from "./pi-rpc.ts";
22
25
  import { loadLocalSession } from "./local-session.ts";
23
- import { loadJsonWithLegacyMigration, writeJsonAtomically } from "./state-store.ts";
26
+ import { loadJsonWithLegacyMigration, writeJsonAtomically, writeTextAtomically } from "./state-store.ts";
24
27
  import { buildFilePreview } from "./file-preview.ts";
25
28
  import { readClipboardImage, readClipboardText } from "./windows-clipboard.ts";
26
29
  import {
@@ -44,6 +47,8 @@ const DEFAULT_PORT = 18789;
44
47
  const STATE_DIR = process.env.PI_WEB_STATE_DIR || join(getAgentDir(), "pi-web");
45
48
  const STATE_FILE = resolve(process.env.PI_WEB_STATE_FILE || join(STATE_DIR, "state.json"));
46
49
  const LEGACY_STATE_FILE = resolve(process.env.PI_WEB_LEGACY_STATE_FILE || join(here, "state.json"));
50
+ const AUTH_FILE = join(dirname(STATE_FILE), "auth.json");
51
+ const NETWORK_FILE = join(dirname(STATE_FILE), "network.json");
47
52
 
48
53
  interface Workspace {
49
54
  id: string;
@@ -205,6 +210,13 @@ interface MaintenanceCommandResult {
205
210
  let server: ReturnType<typeof createServer> | undefined;
206
211
  let serverUrl: string | undefined;
207
212
  let serverInstanceId: string | undefined;
213
+ let webAccess = new WebAccess(false);
214
+ let networkSettings: NetworkSettings = { mode: "local" };
215
+ let networkApplying = false;
216
+ let networkChange: Promise<void> = Promise.resolve();
217
+ let networkError: string | undefined;
218
+ let frpRuntimeDir: string | undefined;
219
+ const frpProcess = new FrpProcess(() => broadcastEvent({ type: "network_changed" }));
208
220
  let updateExtensions: (() => Promise<MaintenanceCommandResult>) | undefined;
209
221
  let reloadRuntime: (() => Promise<void>) | undefined;
210
222
  let maintenanceRunning = false;
@@ -1707,28 +1719,6 @@ function readJsonBody(req: import("node:http").IncomingMessage, limit = 12 * 102
1707
1719
  });
1708
1720
  }
1709
1721
 
1710
- function isTrustedLocalMutation(req: import("node:http").IncomingMessage): boolean {
1711
- const host = req.headers.host ?? "";
1712
- let hostname = "";
1713
- try {
1714
- hostname = new URL(`http://${host}`).hostname.toLowerCase();
1715
- } catch {
1716
- return false;
1717
- }
1718
- if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "[::1]") return false;
1719
- const fetchSite = req.headers["sec-fetch-site"];
1720
- if (fetchSite === "cross-site") return false;
1721
- const origin = req.headers.origin;
1722
- if (origin) {
1723
- try {
1724
- if (new URL(origin).host.toLowerCase() !== host.toLowerCase()) return false;
1725
- } catch {
1726
- return false;
1727
- }
1728
- }
1729
- return true;
1730
- }
1731
-
1732
1722
  function findRecord(id: string): RuntimeSession | undefined {
1733
1723
  return sessions.get(id);
1734
1724
  }
@@ -2083,8 +2073,76 @@ function scheduleRuntimeReload(): void {
2083
2073
  async function handleApi(req: import("node:http").IncomingMessage, res: ServerResponse, url: URL): Promise<boolean> {
2084
2074
  const path = url.pathname;
2085
2075
  const method = req.method ?? "GET";
2086
- if (method !== "GET" && method !== "HEAD" && !isTrustedLocalMutation(req)) {
2087
- return sendJson(res, 403, { error: "forbidden origin" });
2076
+ if (!webAccess.trusted(req)) return sendJson(res, 403, { error: "forbidden origin" });
2077
+ res.setHeader("cache-control", "no-store");
2078
+ if (method === "GET" && path === "/api/auth") {
2079
+ return sendJson(res, 200, { passwordConfigured: webAccess.passwordConfigured });
2080
+ }
2081
+ if (method === "POST" && path === "/api/auth") {
2082
+ const body = await readJsonBody(req, 2048);
2083
+ if (!webAccess.passwordConfigured) {
2084
+ return sendJson(res, 503, { error: "请先在电脑网页的设置中设置访问密码" });
2085
+ }
2086
+ if (!webAccess.remoteEnabled || !await webAccess.acceptsPassword(body.password)) {
2087
+ return sendJson(res, 401, { error: "密码不正确" });
2088
+ }
2089
+ res.setHeader("set-cookie", webAccess.cookie(req));
2090
+ return sendJson(res, 200, { ok: true });
2091
+ }
2092
+ if (!webAccess.authorized(req)) {
2093
+ return sendJson(res, 401, { error: "请输入访问密码" });
2094
+ }
2095
+ if (method === "POST" && path === "/api/auth/password") {
2096
+ if (!isLocalRequest(req)) return sendJson(res, 403, { error: "请在电脑本机网页设置密码" });
2097
+ if (networkApplying) return sendJson(res, 409, { error: "网络设置正在应用,请稍后保存密码" });
2098
+ networkApplying = true;
2099
+ try {
2100
+ const body = await readJsonBody(req, 2048);
2101
+ await webAccess.setPassword(body.password, AUTH_FILE);
2102
+ } catch (error) {
2103
+ return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
2104
+ } finally {
2105
+ networkApplying = false;
2106
+ }
2107
+ for (const client of [...sseClients]) {
2108
+ closeSseClient(client);
2109
+ (client.res as ServerResponse).end();
2110
+ }
2111
+ sseClients.clear();
2112
+ return sendJson(res, 200, { ok: true });
2113
+ }
2114
+
2115
+ if (path === "/api/network" || path === "/api/network/server-files") {
2116
+ if (!isLocalRequest(req)) return sendJson(res, 403, { error: "请在电脑本机网页配置网络访问" });
2117
+ if (method === "GET" && path === "/api/network/server-files") {
2118
+ if (!networkSettings.relay) return sendJson(res, 400, { error: "请先保存服务器中转配置" });
2119
+ return sendJson(res, 200, { files: relayServerFiles(networkSettings.relay) });
2120
+ }
2121
+ if (method === "GET" && path === "/api/network") return sendJson(res, 200, networkView());
2122
+ if (method === "POST" && path === "/api/network") {
2123
+ if (networkApplying) return sendJson(res, 409, { error: "网络设置正在应用" });
2124
+ networkApplying = true;
2125
+ try {
2126
+ const next = updateNetworkSettings(await readJsonBody(req, 16 * 1024), networkSettings);
2127
+ if (next.mode === "relay") await resolveFrpc(next.relay!.frpcPath, dirname(NETWORK_FILE));
2128
+ await saveNetworkSettings(NETWORK_FILE, next);
2129
+ networkSettings = next;
2130
+ networkError = undefined;
2131
+ sendJson(res, 200, networkView());
2132
+ networkChange = new Promise<void>((resolveChange) => setImmediate(resolveChange)).then(async () => {
2133
+ if (!shuttingDown) await applyNetworkSettings();
2134
+ }).catch((error) => {
2135
+ networkError = error instanceof Error ? error.message : String(error);
2136
+ }).finally(() => {
2137
+ networkApplying = false;
2138
+ broadcastEvent({ type: "network_changed" });
2139
+ });
2140
+ return true;
2141
+ } catch (error) {
2142
+ networkApplying = false;
2143
+ return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
2144
+ }
2145
+ }
2088
2146
  }
2089
2147
 
2090
2148
  if (method === "GET" && path === "/api/bootstrap") {
@@ -2096,6 +2154,10 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
2096
2154
  commands: bootstrapCommands(),
2097
2155
  url: serverUrl,
2098
2156
  instanceId: serverInstanceId,
2157
+ localClient: isLocalRequest(req),
2158
+ lanEnabled: webAccess.remoteEnabled,
2159
+ passwordConfigured: webAccess.passwordConfigured,
2160
+ lanUrls: isLocalRequest(req) ? getWebLanUrls() : [],
2099
2161
  });
2100
2162
  }
2101
2163
 
@@ -2137,6 +2199,19 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
2137
2199
  }
2138
2200
  }
2139
2201
 
2202
+ if (method === "POST" && path === "/api/attachments") {
2203
+ const body = await readJsonBody(req, 21 * 1024 * 1024);
2204
+ if (typeof body.data !== "string" || !/^[A-Za-z0-9+/]+={0,2}$/.test(body.data)) {
2205
+ return sendJson(res, 400, { error: "图片数据无效" });
2206
+ }
2207
+ try {
2208
+ const attachment = await imageAttachments.create(Buffer.from(body.data, "base64"));
2209
+ return sendJson(res, 200, { attachment: { ...attachment, url: `/api/attachments/${attachment.id}` } });
2210
+ } catch (error) {
2211
+ return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
2212
+ }
2213
+ }
2214
+
2140
2215
  const attachmentMatch = /^\/api\/attachments\/([a-f0-9-]+)$/.exec(path);
2141
2216
  if (attachmentMatch && (method === "GET" || method === "HEAD")) {
2142
2217
  try {
@@ -2922,19 +2997,77 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
2922
2997
  // server lifecycle
2923
2998
  // ---------------------------------------------------------------------------
2924
2999
 
2925
- function listenOn(app: ReturnType<typeof createServer>, preferredPort: number): Promise<number> {
3000
+ function networkView() {
3001
+ const status = networkError ? { state: "error", message: networkError }
3002
+ : networkSettings.mode === "relay" ? frpProcess.status
3003
+ : { state: networkSettings.mode === "lan" ? "connected" : "stopped", message: networkSettings.mode === "lan" ? "局域网访问已开启" : "仅本机访问" };
3004
+ return { settings: publicNetworkSettings(networkSettings), status, urls: getWebLanUrls(), applying: networkApplying };
3005
+ }
3006
+
3007
+ function disconnectWebClients(): void {
3008
+ for (const client of [...sseClients]) {
3009
+ closeSseClient(client);
3010
+ (client.res as ServerResponse).end();
3011
+ }
3012
+ sseClients.clear();
3013
+ }
3014
+
3015
+ async function stopRelay(): Promise<void> {
3016
+ await frpProcess.stop();
3017
+ if (frpRuntimeDir) await rm(frpRuntimeDir, { recursive: true, force: true });
3018
+ frpRuntimeDir = undefined;
3019
+ }
3020
+
3021
+ async function startRelay(): Promise<void> {
3022
+ if (networkSettings.mode !== "relay" || !networkSettings.relay || !serverUrl) return;
3023
+ const relay = networkSettings.relay;
3024
+ const executable = await resolveFrpc(relay.frpcPath, dirname(NETWORK_FILE));
3025
+ const runtimeRoot = join(dirname(NETWORK_FILE), "runtime");
3026
+ await mkdir(runtimeRoot, { recursive: true });
3027
+ frpRuntimeDir = await mkdtemp(join(runtimeRoot, "frpc-"));
3028
+ const configPath = join(frpRuntimeDir, "frpc.toml");
3029
+ await writeTextAtomically(configPath, frpcConfig(relay, Number(new URL(serverUrl).port)));
3030
+ await chmod(configPath, 0o600);
3031
+ await frpProcess.start({ executable, args: ["-c", configPath], cwd: dirname(NETWORK_FILE) });
3032
+ }
3033
+
3034
+ async function applyNetworkSettings(): Promise<void> {
3035
+ if (!server || !serverUrl) return;
3036
+ await stopRelay();
3037
+ const next = await WebAccess.load(networkSettings.mode === "lan", AUTH_FILE, proxyForNetwork(networkSettings));
3038
+ disconnectWebClients();
3039
+ if (next.lan !== webAccess.lan) {
3040
+ const port = Number(new URL(serverUrl).port);
3041
+ const previousHost = webAccess.lan ? "0.0.0.0" : HOST;
3042
+ const app = server;
3043
+ await new Promise<void>((resolveClose) => {
3044
+ app.close(() => resolveClose());
3045
+ app.closeAllConnections();
3046
+ });
3047
+ try {
3048
+ await listenOn(app, port, next.lan ? "0.0.0.0" : HOST, false);
3049
+ } catch (error) {
3050
+ await listenOn(app, port, previousHost, false);
3051
+ throw error;
3052
+ }
3053
+ }
3054
+ webAccess = next;
3055
+ await startRelay();
3056
+ }
3057
+
3058
+ function listenOn(app: ReturnType<typeof createServer>, preferredPort: number, host = HOST, allowFallback = true): Promise<number> {
2926
3059
  return new Promise((resolvePort, reject) => {
2927
3060
  const onError = (error: NodeJS.ErrnoException) => {
2928
- if (error.code === "EADDRINUSE" && preferredPort !== 0) {
3061
+ if (allowFallback && error.code === "EADDRINUSE" && preferredPort !== 0) {
2929
3062
  app.removeListener("error", onError);
2930
- listenOn(app, 0).then(resolvePort, reject);
3063
+ listenOn(app, 0, host).then(resolvePort, reject);
2931
3064
  return;
2932
3065
  }
2933
3066
  app.removeListener("error", onError);
2934
3067
  reject(error);
2935
3068
  };
2936
3069
  app.once("error", onError);
2937
- app.listen(preferredPort, HOST, () => {
3070
+ app.listen(preferredPort, host, () => {
2938
3071
  app.removeListener("error", onError);
2939
3072
  const address = app.address();
2940
3073
  resolvePort(address && typeof address === "object" ? address.port : preferredPort);
@@ -2950,6 +3083,7 @@ function openBrowser(url: string): void {
2950
3083
  }
2951
3084
 
2952
3085
  function exitHook(): void {
3086
+ frpProcess.kill();
2953
3087
  for (const record of sessions.values()) {
2954
3088
  try {
2955
3089
  record.proc?.close();
@@ -2972,6 +3106,8 @@ function shutdown(): Promise<void> {
2972
3106
  }
2973
3107
 
2974
3108
  async function shutdownServer(): Promise<void> {
3109
+ await networkChange;
3110
+ await stopRelay();
2975
3111
  for (const client of [...sseClients]) {
2976
3112
  closeSseClient(client);
2977
3113
  try {
@@ -3010,6 +3146,8 @@ export async function startWebServer(options: {
3010
3146
  defaultCwd?: string;
3011
3147
  port?: number;
3012
3148
  open?: boolean;
3149
+ lan?: boolean;
3150
+ mode?: NetworkMode;
3013
3151
  initialModelInfo?: ModelInfo;
3014
3152
  initialCommands?: unknown[];
3015
3153
  updateExtensions?: () => Promise<MaintenanceCommandResult>;
@@ -3019,7 +3157,16 @@ export async function startWebServer(options: {
3019
3157
  const initialCwd = normalizePath(options.defaultCwd ?? process.cwd());
3020
3158
  updateExtensions = options.updateExtensions;
3021
3159
  reloadRuntime = options.reloadRuntime;
3160
+ const mode = options.mode ?? (options.lan === undefined ? undefined : options.lan ? "lan" : "local");
3022
3161
  if (serverUrl && server) {
3162
+ await networkChange;
3163
+ if (mode !== undefined) {
3164
+ networkSettings = updateNetworkSettings({ mode }, networkSettings);
3165
+ await saveNetworkSettings(NETWORK_FILE, networkSettings);
3166
+ networkError = undefined;
3167
+ await applyNetworkSettings();
3168
+ broadcastEvent({ type: "network_changed" });
3169
+ }
3023
3170
  if (options.initialCommands?.length) commandCache.set(initialCwd, normalizeCommands(options.initialCommands));
3024
3171
  if (options.open !== false) openBrowser(serverUrl);
3025
3172
  return serverUrl;
@@ -3037,9 +3184,23 @@ export async function startWebServer(options: {
3037
3184
  }
3038
3185
  if (options.initialCommands?.length) commandCache.set(initialCwd, normalizeCommands(options.initialCommands));
3039
3186
  if (state.workspaces.length === 0 && options.defaultCwd) await addWorkspace(options.defaultCwd);
3040
- const requestedPort = options.port && Number.isFinite(options.port) ? options.port : DEFAULT_PORT;
3187
+ const requestedPort = options.port ?? DEFAULT_PORT;
3188
+ if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
3189
+ throw new Error("端口必须是 0–65535 之间的整数");
3190
+ }
3191
+ networkSettings = await loadNetworkSettings(NETWORK_FILE);
3192
+ if (mode !== undefined) {
3193
+ networkSettings = updateNetworkSettings({ mode }, networkSettings);
3194
+ await saveNetworkSettings(NETWORK_FILE, networkSettings);
3195
+ }
3196
+ networkApplying = false;
3197
+ networkError = undefined;
3198
+ webAccess = await WebAccess.load(networkSettings.mode === "lan", AUTH_FILE, proxyForNetwork(networkSettings));
3041
3199
  const app = createServer(async (req, res) => {
3042
3200
  try {
3201
+ res.setHeader("referrer-policy", "no-referrer");
3202
+ res.setHeader("x-content-type-options", "nosniff");
3203
+ res.setHeader("x-frame-options", "DENY");
3043
3204
  if (shuttingDown) return sendJson(res, 503, { error: "pi-web 正在关闭" });
3044
3205
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? `${HOST}:${requestedPort}`}`);
3045
3206
  if (url.pathname.startsWith("/api/")) {
@@ -3053,7 +3214,7 @@ export async function startWebServer(options: {
3053
3214
  else res.end();
3054
3215
  }
3055
3216
  });
3056
- const port = await listenOn(app, requestedPort);
3217
+ const port = await listenOn(app, requestedPort, webAccess.lan ? "0.0.0.0" : HOST);
3057
3218
  server = app;
3058
3219
  serverUrl = `http://${HOST}:${port}`;
3059
3220
  serverInstanceId = randomUUID();
@@ -3061,6 +3222,9 @@ export async function startWebServer(options: {
3061
3222
  process.on("exit", exitHook);
3062
3223
  process.on("SIGINT", sigintHook);
3063
3224
  process.on("SIGTERM", sigintHook);
3225
+ try { await startRelay(); } catch (error) {
3226
+ networkError = error instanceof Error ? error.message : String(error);
3227
+ }
3064
3228
  if (options.open !== false) openBrowser(serverUrl);
3065
3229
  return serverUrl;
3066
3230
  }
@@ -3072,3 +3236,11 @@ export async function stopWebServer(): Promise<void> {
3072
3236
  export function getWebUrl(): string | undefined {
3073
3237
  return serverUrl;
3074
3238
  }
3239
+
3240
+ export function getWebLanUrls(): string[] {
3241
+ return serverUrl ? webAccess.urls(Number(new URL(serverUrl).port)) : [];
3242
+ }
3243
+
3244
+ export function getWebConnectionOptions(): { port: number; lan: boolean; mode: NetworkMode } | undefined {
3245
+ return serverUrl ? { port: Number(new URL(serverUrl).port), lan: webAccess.lan, mode: networkSettings.mode } : undefined;
3246
+ }
package/web/app.css CHANGED
@@ -58,7 +58,7 @@
58
58
  .lXshSW_root{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex:none;margin:0 auto;overflow:hidden}.lXshSW_body{flex-direction:column;gap:8px;padding:6px 12px;display:flex}.lXshSW_header{text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:10px;width:100%;padding:0;display:flex}.lXshSW_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}.lXshSW_title{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px}.lXshSW_progress{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:13px;font-weight:400;line-height:20px;overflow:hidden}.lXshSW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}.lXshSW_list{flex-direction:column;gap:8px;max-height:180px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.lXshSW_item{min-width:0;color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:13px;line-height:20px;display:flex}.lXshSW_glyph{flex:none;place-items:center;width:16px;height:16px;display:grid}.lXshSW_glyphCompleted{color:var(--dsw-alias-state-success-primary)}.lXshSW_glyphPending{color:var(--dsw-alias-label-caption)}.lXshSW_glyphProgress{color:var(--dsw-alias-state-business-primary);animation:1s linear infinite lXshSW_todo-progress-spin}@keyframes lXshSW_todo-progress-spin{to{transform:rotate(360deg)}}.lXshSW_content{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}
59
59
  ._7yHdaG_dock{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));margin:0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);padding:0 var(--dsh-composer-dock-inset);flex:none}._7yHdaG_panel{background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px 12px 0 0;width:100%;padding:2px 0;position:relative;overflow:hidden}._7yHdaG_panel:after{border:1px solid var(--dsw-alias-border-l1);border-radius:inherit;content:"";pointer-events:none;border-bottom:none;position:absolute;inset:0}._7yHdaG_header{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:4px 12px;display:flex}._7yHdaG_header:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_header:disabled{cursor:default}._7yHdaG_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_count{min-width:0;font-family:Inter, var(--dsw-font-family);flex:auto;font-size:13px;font-weight:500;line-height:24px}._7yHdaG_chevron{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_list{max-height:180px;margin:0;padding:0;list-style:none;overflow-y:auto}._7yHdaG_row{box-sizing:border-box;border-radius:8px;align-items:center;gap:10px;width:100%;height:36px;padding:4px 5px 4px 12px;display:flex}._7yHdaG_row+._7yHdaG_row{box-shadow:inset 0 1px 0 var(--dsw-alias-border-l1)}._7yHdaG_preview,._7yHdaG_editor{min-width:0;font:var(--dsw-font-xs-13);font-family:Inter, var(--dsw-font-family);flex:auto}._7yHdaG_preview{color:var(--dsw-alias-label-primary-dimmed);text-overflow:ellipsis;white-space:nowrap;word-break:break-word;overflow:hidden}._7yHdaG_editor{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);height:28px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:0 8px}._7yHdaG_editor:focus{border-color:var(--dsw-alias-state-business-primary)}._7yHdaG_actions{flex:none;align-items:center;gap:10px;display:flex}._7yHdaG_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;display:grid}._7yHdaG_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7yHdaG_action:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_action:disabled{cursor:default;opacity:.45}
60
60
  .pXSMma_root{justify-content:center;align-items:center;min-width:0;height:100%;padding:0 24px;display:flex}.pXSMma_stack{width:100%;max-width:var(--dsh-composer-card-max-width);flex-direction:column;align-items:stretch;gap:12px;display:flex;overflow:visible}.pXSMma_headline{color:var(--dsw-alias-label-primary);grid-template-columns:34px auto auto;justify-content:center;align-items:center;column-gap:10px;font-size:26px;font-weight:500;line-height:32px;display:grid}.pXSMma_headlineText{grid-area:1/2}.pXSMma_previewBadge{border:1px solid var(--dsw-alias-interactive-bg-hover);background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish);font-family:var(--ds-font-family-code);white-space:nowrap;border-radius:24px;grid-area:1/3;align-self:start;margin-top:2px;margin-left:-3px;padding:1px 7px 0;font-size:12px;font-weight:500;line-height:18px}.pXSMma_fishHitbox{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.pXSMma_fish{color:var(--dsw-alias-label-primary);transform-origin:50% 60%}@keyframes pXSMma_hero-fish-swim{0%,to{transform:translate(0)rotate(0)}35%{transform:translate(-1px,-1px)rotate(-5deg)}70%{transform:translate(1px)rotate(3deg)}}@media (hover:hover) and (prefers-reduced-motion:no-preference){.pXSMma_fishHitbox:hover .pXSMma_fish{animation:pXSMma_hero-fish-swim var(--ds-transition-duration-slow) var(--ds-ease-in-out)}}.pXSMma_body{flex-direction:column;gap:12px;min-width:0;display:flex;position:relative;overflow:visible}.pXSMma_body>*{z-index:1;position:relative}.pXSMma_body>.pXSMma_workspaceRow{z-index:10;align-items:center;min-width:0;padding-left:8px;display:flex}.pXSMma_workspace{max-width:min(100%,360px);min-height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:16px;align-items:center;gap:4px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.pXSMma_workspace:not(:disabled):hover,.pXSMma_workspace[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.pXSMma_workspace:disabled{cursor:default}.pXSMma_folder{color:var(--dsw-alias-label-primary);flex:none}.pXSMma_workspaceLabel{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.pXSMma_chevron{color:var(--dsw-alias-label-caption);flex:none}.pXSMma_modalInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.pXSMma_modalInput::placeholder{color:var(--dsw-alias-label-caption)}.pXSMma_modalInput:disabled{color:var(--dsw-alias-label-dimmed)}.pXSMma_modalAction{min-width:72px}.pXSMma_modalError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}
61
- .wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;--dsh-composer-text-max-height:336px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:"";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.wSkVaW_headerUtilities{flex:none;align-items:center;gap:8px;margin-left:20px;display:flex}.wSkVaW_headerUtilities:empty{display:none}.wSkVaW_tabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.wSkVaW_tab{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.wSkVaW_tab:after{content:"";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_tabActive{color:var(--dsw-alias-state-business-primary)}.wSkVaW_tabActive:after{background:var(--dsw-alias-state-business-primary)}.wSkVaW_viewArea{flex-direction:column;flex:1;min-height:0;display:flex}.wSkVaW_composerStack{--dsh-composer-stack-gap:6px;gap:var(--dsh-composer-stack-gap);flex-direction:column;display:flex}.wSkVaW_composerSeat{--dsh-composer-text-max-height:336px;flex-direction:column;flex:none;display:flex}.wSkVaW_root[data-phase=active]{overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_header{flex:none}.wSkVaW_scrollBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_viewArea{scrollbar-gutter:stable;overscroll-behavior:contain;overflow-anchor:none;flex:1 1 auto;min-height:0;overflow-x:hidden;overflow-y:auto}.wSkVaW_root[data-phase=active] .wSkVaW_composerSeat{z-index:7;background:linear-gradient(180deg, color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px, var(--dsw-alias-bg-base) 36px)}.wSkVaW_scrollBody:has([data-conversation-composer-overlay]){scrollbar-gutter:auto;position:relative;overflow:hidden auto}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>[data-slot=conversation\.session]>.wSkVaW_viewArea{flex:1 1 0;min-height:0;overflow:hidden}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>.wSkVaW_composerSeat{right:var(--dsh-scrollbar-width);position:absolute;bottom:0;left:0}.wSkVaW_composerHero{width:min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);z-index:1;align-self:center;gap:8px;padding-bottom:32px;position:relative}.wSkVaW_heroGlow{z-index:-1;aspect-ratio:1051/468;pointer-events:none;width:135.438%;position:absolute;bottom:92px;left:50%;transform:translate(-50%,50%)}.wSkVaW_heroWorkspaceRow{align-items:center;gap:2px;min-width:0;margin-top:4px;padding-left:20px;display:flex}.wSkVaW_root[data-phase=hero] .wSkVaW_scrollBody{justify-content:center;overflow-y:auto}.wSkVaW_root[data-phase=settling] .wSkVaW_composerSeat{visibility:hidden}
61
+ .wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;--dsh-composer-text-max-height:336px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:"";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.wSkVaW_tabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.wSkVaW_tab{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.wSkVaW_tab:after{content:"";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_tabActive{color:var(--dsw-alias-state-business-primary)}.wSkVaW_tabActive:after{background:var(--dsw-alias-state-business-primary)}.wSkVaW_viewArea{flex-direction:column;flex:1;min-height:0;display:flex}.wSkVaW_composerStack{--dsh-composer-stack-gap:6px;gap:var(--dsh-composer-stack-gap);flex-direction:column;display:flex}.wSkVaW_composerSeat{--dsh-composer-text-max-height:336px;flex-direction:column;flex:none;display:flex}.wSkVaW_root[data-phase=active]{overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_header{flex:none}.wSkVaW_scrollBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_viewArea{scrollbar-gutter:stable;overscroll-behavior:contain;overflow-anchor:none;flex:1 1 auto;min-height:0;overflow-x:hidden;overflow-y:auto}.wSkVaW_root[data-phase=active] .wSkVaW_composerSeat{z-index:7;background:linear-gradient(180deg, color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px, var(--dsw-alias-bg-base) 36px)}.wSkVaW_scrollBody:has([data-conversation-composer-overlay]){scrollbar-gutter:auto;position:relative;overflow:hidden auto}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>[data-slot=conversation\.session]>.wSkVaW_viewArea{flex:1 1 0;min-height:0;overflow:hidden}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>.wSkVaW_composerSeat{right:var(--dsh-scrollbar-width);position:absolute;bottom:0;left:0}.wSkVaW_composerHero{width:min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);z-index:1;align-self:center;gap:8px;padding-bottom:32px;position:relative}.wSkVaW_heroGlow{z-index:-1;aspect-ratio:1051/468;pointer-events:none;width:135.438%;position:absolute;bottom:92px;left:50%;transform:translate(-50%,50%)}.wSkVaW_heroWorkspaceRow{align-items:center;gap:2px;min-width:0;margin-top:4px;padding-left:20px;display:flex}.wSkVaW_root[data-phase=hero] .wSkVaW_scrollBody{justify-content:center;overflow-y:auto}.wSkVaW_root[data-phase=settling] .wSkVaW_composerSeat{visibility:hidden}
62
62
  .ydkMvW_root{border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex-direction:column;min-width:0;height:100%;display:flex}.ydkMvW_header{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;padding:14px 12px 12px;display:flex}.ydkMvW_title{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.ydkMvW_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.ydkMvW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.ydkMvW_body{flex:1;min-height:0;padding:12px 16px;overflow-y:auto}.ydkMvW_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}.ydkMvW_section{margin-bottom:16px}.ydkMvW_sectionLabel{color:var(--dsw-alias-label-secondary);margin-bottom:6px;font-size:12px;font-weight:500;line-height:18px}.ydkMvW_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}.ydkMvW_code[data-error]{color:var(--dsw-alias-state-error-primary)}
63
63
  .hXDBwq_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}
64
64
  .QWLzlG_root{flex-direction:column;display:flex}.QWLzlG_row{position:relative;overflow:hidden}.QWLzlG_root[data-state=running] .QWLzlG_row:after{content:"";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite QWLzlG_dsh-reasoning-row-sweep;position:absolute;left:0}@keyframes QWLzlG_dsh-reasoning-row-sweep{0%{left:-300px}90%,to{left:100%}}.QWLzlG_leading{flex-shrink:0}.QWLzlG_chevron{color:var(--dsw-alias-label-secondary)}.QWLzlG_title{font-weight:400}.QWLzlG_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.QWLzlG_summary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.QWLzlG_summary[data-follow-end]{text-overflow:clip}.QWLzlG_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}@media (prefers-reduced-motion:reduce){.QWLzlG_root[data-state=running] .QWLzlG_row:after{animation:none}}
@@ -198,6 +198,9 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
198
198
  .ztWv_q_callRow .Sxvs8a_root{margin:4px 0 4px 22px;max-height:min(56vh,520px);overflow:auto}
199
199
  .pi-messageGroup{display:contents}
200
200
  .pi-assistantMessageGroup{display:flex;flex-direction:column}
201
+ /* Adjacent execution groups offset 12px of the conversation column's 16px gap. */
202
+ .pi-messageGroup:has(> .pi-assistantMessageGroup > :is([data-chat-flow-kind=tool-call],[data-chat-flow-kind=assistant-step]):last-child,> [data-chat-flow-kind=tool-result],> [data-chat-flow-kind=bash])
203
+ + .pi-messageGroup:has(> .pi-assistantMessageGroup > :is([data-chat-flow-kind=tool-call],[data-chat-flow-kind=assistant-step]):first-child,> [data-chat-flow-kind=tool-result],> [data-chat-flow-kind=bash]) > :first-child{margin-top:-12px}
201
204
  .pi-assistantMessageActions{display:flex;align-items:center;justify-content:flex-start;min-height:28px;margin-top:0;padding:0 14px 12px}
202
205
  .pi-assistantCopyButton{width:28px;height:28px;border-radius:6px;flex:none}
203
206
  .pi-composerActions{display:inline-flex;align-items:center;gap:6px;flex:none}
@@ -264,6 +267,19 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
264
267
  [data-chat-flow-kind=assistant-text] .md-table-wrap :is(th,td):last-child{border-right:0}
265
268
  [data-chat-flow-kind=assistant-text] .md-table-wrap [data-align=center]{text-align:center}
266
269
  [data-chat-flow-kind=assistant-text] .md-table-wrap [data-align=right]{text-align:right}
270
+ @media(max-width:640px),(pointer:coarse) and (max-width:900px){
271
+ [data-chat-flow-kind=user] .gdEzaW_bubble,[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5{font-size:13px;line-height:21px}
272
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h1{font-size:22px;line-height:30px;margin:24px 0 10px}
273
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h2{font-size:19px;line-height:27px;margin:22px 0 9px}
274
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h3{font-size:16px;line-height:24px;margin:18px 0 8px}
275
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h4{font-size:14px;line-height:22px;margin:16px 0 6px}
276
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 :is(h5,h6){font-size:13px;line-height:21px;margin:14px 0 6px}
277
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 :is(p,ul,ol){margin:10px 0}
278
+ [data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 li::marker{line-height:21px}
279
+ [data-chat-flow-kind=assistant-text] .md-code-block{font-size:12px;line-height:19px;margin:12px 0;padding:12px}
280
+ [data-chat-flow-kind=assistant-text] .md-table-wrap table{font-size:12px;line-height:19px}
281
+ [data-chat-flow] .Sxvs8a_root,[data-chat-flow] .CY-8Ka_title,[data-chat-flow] .CY-8Ka_summary,[data-chat-flow] .QWLzlG_thinkBody,[data-chat-flow] .QWLzlG_summary{font-size:12px;line-height:20px}
282
+ }
267
283
  [data-chat-flow-kind=assistant-step] .QWLzlG_leading,[data-chat-flow-kind=assistant-step] .QWLzlG_title{color:var(--dsw-alias-state-warn-label)}
268
284
  .CY-8Ka_root[data-state=ok] .CY-8Ka_leading{color:var(--dsw-alias-state-success-primary)}
269
285
  .CY-8Ka_root[data-state=running] .CY-8Ka_leading,.CY-8Ka_root[data-state=running] .CY-8Ka_title{color:var(--dsw-alias-state-business-primary)}
@@ -298,6 +314,7 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
298
314
  .pi-queuedMessageWithdraw:disabled{cursor:wait;opacity:.45}
299
315
  .pi-historyView{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;background:var(--dsw-alias-bg-base)}
300
316
  .pi-historyHeader{box-sizing:border-box;width:min(100%,860px);flex:none;display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;margin:0 auto;padding:24px 22px 14px}
317
+ .pi-historyActions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
301
318
  .pi-historyReturn{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:6px 0;color:var(--dsw-alias-state-business-primary);background:transparent;border:0;cursor:pointer;font:500 12px/20px var(--dsw-font-family)}
302
319
  .pi-historyReturn svg{flex:none}
303
320
  .pi-historyReturn:hover:not(:disabled){text-decoration:underline}
@@ -352,7 +369,7 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
352
369
  .pi-historyDecisionCancel:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
353
370
  .pi-historyBusy{flex:none;padding:9px 16px;color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary);border-top:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:18px;text-align:center}
354
371
  @media(max-width:980px){.pi-historyDecision{align-items:stretch;flex-direction:column}.pi-historyDecisionActions{width:100%}.pi-historyDecisionButton{min-width:0;flex:1}}
355
- @media(max-width:760px){.pi-dialogBackdrop{padding:12px}.pi-historyHeader{padding:16px}.pi-historyTabs{padding:0 16px}.pi-historyKind{display:none}.pi-historyDecisionActions{flex-direction:column}.pi-historyDecisionCancel{align-self:flex-end}.wSkVaW_headerUtilities{gap:5px;margin-left:8px}.nL4_yW_sessionLogButton{min-width:auto}}
372
+ @media(max-width:760px){.pi-dialogBackdrop{padding:12px}.pi-historyHeader{padding:16px}.pi-historyTabs{padding:0 16px}.pi-historyKind{display:none}.pi-historyDecisionActions{flex-direction:column}.pi-historyDecisionCancel{align-self:flex-end}.nL4_yW_sessionLogButton{min-width:auto}}
356
373
  /* Composer input bar — restored from DeepSeek Harness InputBar.module.css (missing from the copied DSH stylesheet) */
357
374
  .uV2eYG_root{display:flex;flex-direction:column;align-items:center;padding:0 var(--dsh-composer-side-clearance) 8px}
358
375
  .uV2eYG_hero{padding:0 var(--dsh-composer-side-clearance)}
@@ -537,3 +554,63 @@ body[data-ds-dark-theme] .pi-filePreview .hljs-meta,body[data-ds-dark-theme] .pi
537
554
  @keyframes pi-turn-target-flash{0%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent)}35%{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 54%,transparent),0 0 0 4px color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent)}100%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent)}}
538
555
  @media(max-width:640px){.pi-turnNav{right:2px;width:34px}.pi-scrollBottom{right:5px}.pi-turnNavMarker{width:34px}.pi-turnNavMarker:hover .pi-turnNavLine,.pi-turnNavMarker:focus-visible .pi-turnNavLine{width:24px}.pi-turnTooltip{right:32px;width:min(292px,calc(100vw - 48px))}}
539
556
  @media(prefers-reduced-motion:reduce){.pi-turnNavLine,.pi-turnTooltip,.pi-scrollBottom{transition:none}.pi-turnTargetFlash [data-chat-flow-kind=user] .gdEzaW_bubble{animation:none}}
557
+ .pi-networkForm,.pi-networkRelay,.pi-networkAdvanced{display:grid;gap:12px;min-width:0}
558
+ .pi-networkForm [hidden]{display:none}
559
+ .pi-networkForm label{display:grid;gap:5px;min-width:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
560
+ .pi-networkForm p{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow-wrap:anywhere}
561
+ .pi-networkForm a{color:var(--dsw-alias-state-business-primary);text-decoration:underline}
562
+ .pi-networkForm select{cursor:pointer}
563
+ .pi-networkForm summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}
564
+ .pi-networkAdvanced{padding-top:10px}
565
+ .pi-networkForm .pi-settingsStatus{margin-top:0}
566
+ .pi-networkDownloads{display:grid;gap:8px}
567
+ .pi-networkDownloadButtons{display:flex;flex-wrap:wrap;gap:8px}
568
+ .pi-networkDownloadButtons .pi-settingsAction{width:auto;max-width:100%;height:auto;min-height:34px;overflow-wrap:anywhere}
569
+ @media(max-width:560px){.pi-networkDownloadButtons{display:grid;grid-template-columns:minmax(0,1fr)}.pi-networkDownloadButtons .pi-settingsAction{width:100%;min-height:44px}}
570
+ .pi-passwordForm{margin-top:12px}
571
+ .pi-passwordForm label,.pi-loginCard label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
572
+ .pi-passwordInput{box-sizing:border-box;min-width:0;width:100%;height:44px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:16px/24px var(--dsw-font-family)}
573
+ .pi-passwordInput:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
574
+ .pi-passwordStatus{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
575
+ .pi-loginBackdrop{position:fixed;z-index:1200;top:var(--pi-viewport-top,0px);left:0;right:0;box-sizing:border-box;height:var(--pi-viewport-height,100dvh);display:grid;place-items:center;padding:20px;background:var(--dsw-alias-bg-base)}
576
+ .pi-loginCard{box-sizing:border-box;display:flex;flex-direction:column;gap:14px;width:min(360px,100%);max-height:100%;overflow:auto;padding:24px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}
577
+ .pi-loginCard h1{margin:0;font-size:22px;line-height:30px}
578
+ .pi-loginMessage{margin:0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}
579
+ .pi-loginCard .pi-settingsAction{width:100%;min-height:44px}
580
+ .pi-lanUrl{box-sizing:border-box;width:100%;min-width:0;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:12px/20px var(--ds-font-family-code)}
581
+ .pi-copyField{box-sizing:border-box;width:100%;min-height:160px;max-height:40vh;margin:12px 0;padding:10px;resize:vertical;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:16px/24px var(--dsw-font-family)}
582
+ @media(pointer:coarse){
583
+ #root{box-sizing:border-box;position:fixed;top:var(--pi-viewport-top,0px);left:0;right:0;height:var(--pi-viewport-height,100dvh);padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)}
584
+ .wSkVaW_root,.wSkVaW_composerSeat{--dsh-composer-text-max-height:min(200px,calc(var(--pi-viewport-height,100dvh) * .3))}
585
+ .wSkVaW_composerSeat{padding-bottom:env(safe-area-inset-bottom)}
586
+ .YDXeBa_projectRow,.YDXeBa_sessionRow{height:auto;min-height:48px}
587
+ .YDXeBa_rowActions,.YDXeBa_projectRow .YDXeBa_rowActions{display:inline-flex;height:auto;gap:0}
588
+ .YDXeBa_iconButton,.hHd-Xa_iconButton,.pi-mobileSidebarButton,.pi-settingsClose,.pi-queuedMessageWithdraw{width:44px;height:44px}
589
+ .YDXeBa_sessionRow .YDXeBa_time{display:none}
590
+ .YDXeBa_dragHandle{display:none}
591
+ .qDHVXG_sessionOverflowButton,.hHd-Xa_newSession,._7KE1Ra_trigger,.pi-settingsAction,.pi-settingsTheme button{min-height:44px}
592
+ .uV2eYG_add,.uV2eYG_primary,.pi-abortButton{width:44px;height:44px;transform:none}
593
+ .pi-dialogBackdrop,.pi-settingsBackdrop{top:var(--pi-viewport-top,0px);bottom:auto;height:var(--pi-viewport-height,100dvh);box-sizing:border-box}
594
+ .pi-dialogCard,.pi-settingsPanel{max-height:calc(var(--pi-viewport-height,100dvh) - 24px)}
595
+ input,select,textarea{font-size:16px}
596
+ .pi-mobileSidebarButton{top:7px;left:6px}
597
+ }
598
+ @media(max-width:640px){
599
+ .pI_x6G_sidebarCol{box-shadow:none}
600
+ .pI_x6G_frame[data-mobile-sidebar-open] .pI_x6G_sidebarCol{box-shadow:12px 0 32px rgb(31 35 41 / .14)}
601
+ .wSkVaW_root{--dsh-composer-side-clearance:8px}
602
+ .wSkVaW_header{padding-right:8px}
603
+ .wSkVaW_crumb{max-width:140px}
604
+
605
+ .uV2eYG_row{gap:4px;padding:2px 6px 6px}
606
+ .uV2eYG_tools,.uV2eYG_trailing{gap:4px}
607
+ .uV2eYG_trailing{flex:1;justify-content:flex-end}
608
+ .pi-modelControl{min-width:0;flex:1;max-width:180px}
609
+ .pi-modelControl ._7KE1Ra_trigger{max-width:100%;padding:0 4px}
610
+ .pi-thinkingControl ._7KE1Ra_trigger{padding:0 4px}
611
+ .pi-thinkingIcon,._7KE1Ra_chevron,.JObwrW_root{display:none}
612
+ .FJxK0a_root{flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start;max-height:28px;white-space:nowrap}
613
+ .FJxK0a_root>span{flex:none}
614
+ .wSkVaW_composerHero{padding-bottom:12px}
615
+ .pi-settingsPanel{padding-bottom:env(safe-area-inset-bottom)}
616
+ }