march-cli 0.1.33 → 0.1.35
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 +12 -1
- package/src/agent/lifecycle/runner-lifecycle.mjs +16 -0
- package/src/agent/lifecycle/runtime-restart-tool.mjs +22 -0
- package/src/agent/runner.mjs +9 -14
- package/src/agent/runtime/remote-runner-client.mjs +7 -15
- package/src/agent/runtime/runner-ipc-target.mjs +3 -22
- package/src/agent/runtime/runner-process-client.mjs +101 -24
- package/src/agent/runtime/runner-runtime-host.mjs +2 -0
- package/src/agent/runtime/state/runner-state.mjs +80 -0
- package/src/agent/session/session-options.mjs +2 -1
- package/src/agent/tools.mjs +3 -1
- package/src/cli/args.mjs +14 -3
- package/src/cli/commands/catalog/visible-commands.mjs +5 -0
- package/src/cli/commands/help-command.mjs +1 -7
- package/src/cli/commands/registry/slash-command-registry.mjs +293 -0
- package/src/cli/input/autocomplete.mjs +2 -25
- package/src/cli/repl-loop.mjs +24 -41
- package/src/cli/slash-commands.mjs +19 -185
- package/src/cli/startup/app-runtime.mjs +201 -0
- package/src/cli/startup/configured-command.mjs +9 -0
- package/src/cli/startup/early-command.mjs +29 -0
- package/src/cli/turn/turn-input-preparer.mjs +41 -0
- package/src/main.mjs +47 -242
- package/src/memory/markdown/memory-id.mjs +36 -0
- package/src/memory/markdown-store.mjs +17 -6
- package/src/memory/markdown-tools.mjs +3 -2
- package/src/web-ui/command.mjs +112 -0
- package/src/web-ui/dist/assets/index-BUmhnID4.css +1 -0
- package/src/web-ui/dist/assets/index-CtuqTjcB.js +1845 -0
- package/src/web-ui/dist/index.html +13 -0
- package/src/web-ui/index.html +12 -0
- package/src/web-ui/runtime-host.mjs +185 -0
- package/src/web-ui/server.mjs +139 -0
- package/src/web-ui/session-manager.mjs +109 -0
- package/src/web-ui/src/App.tsx +7 -0
- package/src/web-ui/src/components/AppShell.tsx +47 -0
- package/src/web-ui/src/components/Composer.tsx +47 -0
- package/src/web-ui/src/components/FileExplorer.tsx +46 -0
- package/src/web-ui/src/components/RightSidebar.tsx +70 -0
- package/src/web-ui/src/components/SessionTimeline.tsx +31 -0
- package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +109 -0
- package/src/web-ui/src/components/timeline/TimelineList.tsx +14 -0
- package/src/web-ui/src/fileTreeAdapter.ts +51 -0
- package/src/web-ui/src/main.tsx +11 -0
- package/src/web-ui/src/mockData.ts +87 -0
- package/src/web-ui/src/model.ts +62 -0
- package/src/web-ui/src/runtime/client.ts +74 -0
- package/src/web-ui/src/runtime/runtimeTimeline.ts +88 -0
- package/src/web-ui/src/runtime/useWebRuntime.ts +132 -0
- package/src/web-ui/src/styles/shell.css +156 -0
- package/src/web-ui/src/styles/tokens.css +116 -0
- package/src/web-ui/src/timelineAdapter.ts +43 -0
- package/src/web-ui/src/vite-env.d.ts +1 -0
- package/src/web-ui/tsconfig.json +20 -0
- package/src/web-ui/vite.config.mjs +11 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { scoreEntry, toHint } from "./markdown/markdown-recall.mjs";
|
|
14
14
|
import { clearMarkdownMemoryIndex, loadMarkdownMemoryIndex, openMarkdownMemoryIndex, queryMarkdownMemoryIndex, replaceMarkdownMemoryIndex } from "./markdown/sqlite-index.mjs";
|
|
15
15
|
import { softDeleteMemoryFile } from "./markdown/markdown-delete.mjs";
|
|
16
|
+
import { isMemoryIdLike, isSingleEditAway } from "./markdown/memory-id.mjs";
|
|
16
17
|
import { openMarkdownRoot, searchMarkdownRoot } from "./search.mjs";
|
|
17
18
|
|
|
18
19
|
export { formatRecallHints } from "./markdown/markdown-recall.mjs";
|
|
@@ -154,10 +155,9 @@ export class MarkdownMemoryStore {
|
|
|
154
155
|
this.ensureFresh();
|
|
155
156
|
const raw = String(identifier ?? "").trim();
|
|
156
157
|
if (!raw) throw new Error("memory id or path is required");
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
return { ...opened, entry: entry ?? null };
|
|
158
|
+
const resolved = this.#resolveOpenTarget(raw);
|
|
159
|
+
const opened = openMarkdownRoot({ root: this.root, path: resolved.path, ...options });
|
|
160
|
+
return { ...opened, entry: resolved.entry, requestedId: resolved.requestedId };
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
save({ id = null, name = null, description = null, body = null, tags = null } = {}) {
|
|
@@ -261,11 +261,22 @@ export class MarkdownMemoryStore {
|
|
|
261
261
|
return path;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
#resolveOpenTarget(raw) {
|
|
265
|
+
const exact = this.entries.get(raw);
|
|
266
|
+
if (exact) return { path: exact.path, entry: exact, requestedId: null };
|
|
267
|
+
if (!isMemoryIdLike(raw)) return { path: this.#resolveMemoryPath(raw), entry: null, requestedId: null };
|
|
268
|
+
|
|
269
|
+
const candidates = [...this.entries.values()].filter((entry) => isSingleEditAway(raw, entry.id));
|
|
270
|
+
if (candidates.length === 1) return { path: candidates[0].path, entry: candidates[0], requestedId: raw };
|
|
271
|
+
if (candidates.length > 1) {
|
|
272
|
+
throw new Error(`memory id is ambiguous: ${raw}; candidates: ${candidates.map((entry) => entry.id).join(", ")}`);
|
|
273
|
+
}
|
|
274
|
+
throw new Error(`memory not found: ${raw}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
264
277
|
#activeMemoryPaths() {
|
|
265
278
|
return [...this.entries.values()]
|
|
266
279
|
.filter((entry) => entry.status === "active")
|
|
267
280
|
.map((entry) => entry.path);
|
|
268
281
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
282
|
}
|
|
@@ -146,12 +146,13 @@ function formatMemorySearchResults(results, requestedSource) {
|
|
|
146
146
|
|
|
147
147
|
function formatLocalOpen(opened) {
|
|
148
148
|
const range = opened.startLine && opened.endLine ? `lines: ${opened.startLine}-${opened.endLine}\n` : "";
|
|
149
|
-
|
|
149
|
+
const correction = opened.requestedId && opened.entry?.id ? `matched id: ${opened.entry.id} (requested: ${opened.requestedId})\n` : "";
|
|
150
|
+
return `path: ${opened.path}\n${correction}${range}Use edit_file with this path for targeted edits.\n\ncontent:\n${opened.content}`;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
function formatRemoteOpen(opened) {
|
|
153
154
|
const range = opened.startLine && opened.endLine ? `lines: ${opened.startLine}-${opened.endLine}\n` : "";
|
|
154
|
-
return `source: ${opened.source}\npath: ${opened.path}\n${range}Remote memory is read-only.\n\n
|
|
155
|
+
return `source: ${opened.source}\npath: ${opened.path}\n${range}Remote memory is read-only.\n\ncontent:\n${opened.content}`;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
function formatMemorySearchMiss(query, source) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { createWebUiServer } from "./server.mjs";
|
|
4
|
+
import { createWebSessionManager, resolveWorkspace } from "./session-manager.mjs";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
7
|
+
const DEFAULT_PORT = 4174;
|
|
8
|
+
|
|
9
|
+
export async function runWebUiCommand(args, { config, cwd, stateRoot, useRuntimeProcess = true } = {}) {
|
|
10
|
+
const host = args.host ?? DEFAULT_HOST;
|
|
11
|
+
assertLoopbackHost(host);
|
|
12
|
+
const port = Number.parseInt(args.port ?? "", 10) || DEFAULT_PORT;
|
|
13
|
+
const runtime = createWebSessionManager({ args, config, launchCwd: cwd, stateRoot, useRuntimeProcess });
|
|
14
|
+
const initialWorkspace = resolveInitialWorkspace(args, cwd);
|
|
15
|
+
if (initialWorkspace) await runtime.createSession(initialWorkspace);
|
|
16
|
+
|
|
17
|
+
if (args.dev) return runWebUiDevCommand({ args, host, port, runtime, initialWorkspace });
|
|
18
|
+
assertWebBuildReady();
|
|
19
|
+
const server = createWebUiServer({ runtime });
|
|
20
|
+
await listen(server, port, host);
|
|
21
|
+
process.stdout.write(`March Web running at http://${host}:${port}\n`);
|
|
22
|
+
if (initialWorkspace) process.stdout.write(`Workspace: ${initialWorkspace}\n`);
|
|
23
|
+
await waitForShutdown({ servers: [server], runtime });
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveInitialWorkspace(args, launchCwd) {
|
|
28
|
+
const positional = args.command?.args ?? [];
|
|
29
|
+
if (positional.length > 1) throw new Error("Usage: march web [workspace] [--host <host>] [--port <port>]");
|
|
30
|
+
if (args.workspace && positional.length > 0) throw new Error("Use either march web <workspace> or --workspace <path>, not both");
|
|
31
|
+
const requested = args.workspace ?? positional[0];
|
|
32
|
+
return requested ? resolveWorkspace(requested, launchCwd) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function assertLoopbackHost(host) {
|
|
36
|
+
if (["127.0.0.1", "localhost", "::1"].includes(host)) return;
|
|
37
|
+
throw new Error("march web only exposes local filesystem APIs on 127.0.0.1/localhost");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertWebBuildReady() {
|
|
41
|
+
if (existsSync(new URL("./dist/index.html", import.meta.url))) return;
|
|
42
|
+
throw new Error("Web UI build not found. Run: npm run web:build or use march web --dev");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runWebUiDevCommand({ args, host, port, runtime, initialWorkspace }) {
|
|
46
|
+
const apiPort = Number.parseInt(args.apiPort ?? "", 10) || port + 1;
|
|
47
|
+
const apiServer = createWebUiServer({ runtime });
|
|
48
|
+
let apiStarted = false;
|
|
49
|
+
try {
|
|
50
|
+
await listen(apiServer, apiPort, host);
|
|
51
|
+
apiStarted = true;
|
|
52
|
+
const vite = await createViteDevServer({ host, port, apiPort });
|
|
53
|
+
process.stdout.write(`March Web dev running at http://${host}:${port}\n`);
|
|
54
|
+
process.stdout.write(`March Web API running at http://${host}:${apiPort}\n`);
|
|
55
|
+
if (initialWorkspace) process.stdout.write(`Workspace: ${initialWorkspace}\n`);
|
|
56
|
+
await waitForShutdown({ servers: [apiServer], runtime, vite });
|
|
57
|
+
return 0;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (apiStarted) await closeServer(apiServer);
|
|
60
|
+
await runtime.dispose?.();
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function createViteDevServer({ host, port, apiPort }) {
|
|
66
|
+
let createServer;
|
|
67
|
+
try {
|
|
68
|
+
({ createServer } = await import("vite"));
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error("Vite is required for march web --dev. Run npm install in the March repo.");
|
|
71
|
+
}
|
|
72
|
+
const vite = await createServer({
|
|
73
|
+
configFile: fileURLToPath(new URL("./vite.config.mjs", import.meta.url)),
|
|
74
|
+
server: {
|
|
75
|
+
host,
|
|
76
|
+
port,
|
|
77
|
+
strictPort: true,
|
|
78
|
+
proxy: { "/api": `http://${host}:${apiPort}` },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await vite.listen();
|
|
82
|
+
return vite;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function listen(server, port, host) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
server.once("error", reject);
|
|
88
|
+
server.listen(port, host, () => {
|
|
89
|
+
server.off("error", reject);
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function closeServer(server) {
|
|
96
|
+
return new Promise((resolve) => server.close(resolve));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function waitForShutdown({ servers, runtime, vite = null }) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
const shutdown = async () => {
|
|
102
|
+
process.off("SIGINT", shutdown);
|
|
103
|
+
process.off("SIGTERM", shutdown);
|
|
104
|
+
if (vite) await vite.close();
|
|
105
|
+
await Promise.all(servers.map(closeServer));
|
|
106
|
+
await runtime.dispose?.();
|
|
107
|
+
resolve();
|
|
108
|
+
};
|
|
109
|
+
process.once("SIGINT", shutdown);
|
|
110
|
+
process.once("SIGTERM", shutdown);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer theme.palette{:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--font-sans:"Segoe UI Variable Text", "Segoe UI Variable", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, DengXian, "等线", "Microsoft YaHei UI", sans-serif;--font-mono:ui-monospace, "SFMono-Regular", "Cascadia Code", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, monospace;--font-weight-ui-normal:500;--font-weight-content-normal:600;--font-weight-ui-medium:600;--font-weight-ui-strong:700;--palette-white:#fff;--palette-slate-50:#f8fafc;--palette-slate-100:#f1f5f9;--palette-slate-200:#e2e8f0;--palette-slate-400:#94a3b8;--palette-slate-500:#64748b;--palette-slate-700:#334155;--palette-slate-900:#0f172a;--palette-slate-950:#020617;--palette-blue-400:#60a5fa;--palette-blue-600:#2563eb;--palette-green-700:#15803d;--palette-amber-600:#d97706;--palette-red-600:#dc2626}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}}@layer theme.semantic{:root{--color-bg-page:#f3f4f6;--color-bg-surface:var(--palette-white);--color-bg-sidebar:#ffffffbd;--color-bg-subtle:var(--palette-slate-50);--color-text-primary:var(--palette-slate-900);--color-text-muted:var(--palette-slate-500);--color-text-inverse:var(--palette-white);--color-border-subtle:#0f172a14;--color-border-strong:#0f172a24;--color-accent:var(--palette-blue-600);--color-accent-soft:#2563eb14;--color-success:var(--palette-green-700);--color-warning:var(--palette-amber-600);--color-danger:var(--palette-red-600);--color-hover:#0000000d;--color-shadow-drawer:#00000026;--color-scrim:#0000004d}@media (prefers-color-scheme:dark){:root{--color-bg-page:var(--palette-slate-900);--color-bg-surface:var(--palette-slate-950);--color-bg-sidebar:var(--palette-slate-900);--color-bg-subtle:var(--palette-slate-900);--color-text-primary:var(--palette-slate-50);--color-text-muted:var(--palette-slate-400);--color-border-subtle:#94a3b81f;--color-border-strong:#94a3b833;--color-accent:var(--palette-blue-400);--color-accent-soft:#60a5fa24;--color-hover:#94a3b814}}}@layer theme.component{:root{--shell-left-width:260px;--shell-right-width:280px;--shell-mobile-panel-width:75vw;--header-height:36px;--composer-min-height:46px;--radius-row:6px;--radius-control:8px;--radius-composer:12px;--space-panel:8px;--tree-indent:16px}}@layer base{*{box-sizing:border-box}html,body,#root{height:100%;margin:0;overflow:hidden}body{background:var(--color-bg-page);color:var(--color-text-primary);font-family:var(--font-sans);font-weight:var(--font-weight-ui-normal);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}button,textarea{font:inherit}button{cursor:pointer}}@layer components{.app-shell{grid-template-columns:var(--shell-left-width) minmax(0, 1fr) var(--shell-right-width);background:var(--color-bg-page);height:100dvh;color:var(--color-text-primary);isolation:isolate;grid-template-rows:minmax(0,1fr) auto;grid-template-areas:"sidebar main right""sidebar footer right";display:grid;overflow:hidden}.panel{background:var(--color-bg-sidebar);min-height:0;overflow:hidden}.left-panel{border-right:1px solid var(--color-border-subtle);grid-area:sidebar}.right-panel{border-left:1px solid var(--color-border-subtle);flex-direction:column;grid-area:right;display:flex}.projects-header,.right-header,.main-header{height:var(--header-height);border-bottom:1px solid var(--color-border-subtle);background:var(--color-bg-sidebar);color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase;justify-content:space-between;align-items:center;padding:0 16px;display:flex}.projects-header h3{font:inherit;letter-spacing:.5px;margin:0}.menu-button{border-radius:var(--radius-control);width:28px;height:28px;color:var(--color-text-muted);background:0 0;border:0;justify-content:center;align-items:center;gap:3px;display:inline-flex}.menu-button span{background:currentColor;border-radius:999px;width:3.6px;height:3.6px}.menu-button:hover,.header-button:hover,.session-row:hover,.activity-row:hover{background:var(--color-hover)}.projects-body{height:calc(100% - var(--header-height));padding:var(--space-panel) 0;overflow:hidden}.project-tree-host{--trees-accent-override:var(--color-accent);--trees-bg-override:var(--color-bg-sidebar);--trees-bg-muted-override:var(--color-hover);--trees-border-color-override:var(--color-border-subtle);--trees-fg-override:var(--color-text-primary);--trees-fg-muted-override:var(--color-text-muted);--trees-selected-bg-override:var(--color-accent-soft);--trees-selected-fg-override:var(--color-accent);--trees-font-family-override:var(--font-sans);--trees-font-size-override:13px;--trees-border-radius-override:var(--radius-row);--trees-item-height:30px;--trees-item-padding-x-override:8px;--trees-item-margin-x-override:8px;--trees-item-row-gap-override:4px;--trees-level-gap-override:var(--tree-indent);--trees-padding-inline-override:0px;--trees-scrollbar-gutter-override:6px;height:100%}.timeline{background:var(--color-bg-surface);flex-direction:column;grid-area:main;min-width:0;min-height:0;display:flex;overflow:hidden}.main-header{background:var(--color-bg-surface)}.runtime-pill{color:var(--color-text-muted);background:var(--color-bg-subtle);border-radius:999px;padding:2px 7px;font-size:12px}.runtime-pill.connected{color:var(--color-accent);background:var(--color-accent-soft)}.timeline-scroll{flex:1;min-height:0;padding:16px 18px 24px;overflow:auto}.session-title{align-items:baseline;gap:8px;margin:0 0 14px 40px;display:flex}.session-title h1{font-size:17px;font-weight:var(--font-weight-ui-strong);letter-spacing:-.01em;margin:0}.session-title span{color:var(--color-text-muted);font-size:12px}.timeline-list{max-width:920px;margin:0 auto}.message-row{grid-template-columns:30px minmax(0,1fr);gap:10px;padding:10px 0;display:grid}.message-row+.message-row{border-top:1px solid var(--color-border-subtle)}.agent-dot{border-radius:var(--radius-control);background:var(--palette-slate-700);width:28px;height:28px;color:var(--color-text-inverse);place-items:center;font-size:12px;font-weight:800;display:grid}.agent-dot.march{background:var(--color-accent)}.message-body{min-width:0;color:var(--color-text-primary);font-size:14px;font-weight:var(--font-weight-content-normal);padding-top:3px;line-height:1.55}.message-body p{margin:0}.message-body time{color:var(--color-text-muted);margin-top:4px;font-size:11px;display:block}.timeline-aux{border:1px solid var(--color-border-subtle);border-radius:var(--radius-control);background:var(--color-bg-subtle);padding:7px 9px}.timeline-aux summary,.aux-title{align-items:center;gap:8px;min-height:20px;display:flex}.timeline-aux summary{cursor:pointer}.timeline-aux span,.timeline-aux em{color:var(--color-text-muted);font-size:12px;font-style:normal}.timeline-aux strong{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13px;font-weight:var(--font-weight-ui-medium);overflow:hidden}.timeline-aux p{color:var(--color-text-muted);margin:7px 0 0;font-size:13px}.tool-block span,.diff-inline,.terminal-block pre{font-family:var(--font-mono)}.diff-inline{border-left:2px solid var(--color-border-strong);margin-top:7px;padding-left:10px;font-size:13px}.diff-line{padding:2px 0}.diff-line.add{color:var(--color-success)}.diff-line.remove,.error-block strong{color:var(--color-danger)}.diff-line.keep{color:var(--color-text-muted)}.terminal-block pre{color:var(--color-text-muted);white-space:pre-wrap;margin:7px 0 0;font-size:12px}.permission-block.pending strong{color:var(--color-warning)}.permission-block.approved strong{color:var(--color-success)}.right-body{flex:1;min-height:0;padding:12px;overflow:auto}.workspace-picker{gap:7px;padding-bottom:10px;display:grid}.workspace-picker label{color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase}.workspace-input-row{grid-template-columns:minmax(0,1fr) auto;gap:4px;display:grid}.workspace-input-row input{border:1px solid var(--color-border-subtle);border-radius:var(--radius-control);background:var(--color-bg-surface);min-width:0;color:var(--color-text-primary);padding:7px 8px}.workspace-input-row button,.fs-entry-row button,.fs-row{border-radius:var(--radius-control);color:var(--color-text-muted);background:0 0;border:0}.workspace-input-row button{background:var(--color-accent-soft);color:var(--color-accent);padding:0 8px}.workspace-input-row button:disabled{opacity:.45}.workspace-path{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--color-text-muted);font-size:12px;overflow:hidden}.fs-entry-row{grid-template-columns:minmax(0,1fr) auto;gap:4px;display:grid}.fs-entry-row button,.fs-row{text-align:left;min-height:28px;padding:0 7px}.fs-entry-row button:first-child{text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary);overflow:hidden}.session-row,.activity-row{border-radius:var(--radius-control);width:100%;min-height:34px;color:var(--color-text-primary);text-align:left;background:0 0;border:0;justify-content:space-between;align-items:center;gap:8px;padding:7px 8px;font-size:13px;display:flex}.session-row.active{background:var(--color-accent-soft);color:var(--color-accent);font-weight:var(--font-weight-ui-medium)}.session-row time,.activity-row time{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--color-text-muted);font-size:12px;overflow:hidden}.right-divider{border-top:1px solid var(--color-border-subtle);color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase;margin:12px 0 4px;padding-top:12px}.activity-row{color:var(--color-text-muted)}.composer{background:var(--color-bg-surface);grid-area:footer;grid-template-columns:1fr;align-items:end;min-width:0;padding:0 16px 12px;display:grid}.composer-box{width:100%;min-height:var(--composer-min-height);border:1px solid var(--color-border-strong);border-radius:var(--radius-composer);background:var(--color-bg-sidebar);align-items:center;display:flex;position:relative;overflow:visible}.composer-box:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 1px var(--color-accent-soft)}.composer textarea{resize:none;width:100%;max-height:160px;color:var(--color-text-primary);font-weight:var(--font-weight-content-normal);background:0 0;border:0;outline:0;padding:12px 124px 12px 12px}.composer-actions{align-items:center;gap:2px;display:flex;position:absolute;bottom:8px;right:8px}.session-ring,.chip-button,.icon-action,.send-icon,.mobile-toggle{border-radius:var(--radius-control);width:28px;height:28px;color:var(--color-text-muted);background:0 0;border:0;justify-content:center;align-items:center;display:inline-flex}.session-ring:before{content:"";border:2px solid var(--color-accent);border-radius:999px;width:14px;height:14px}.chip-button{width:auto;padding:0 8px;font-size:12px}.icon-action:hover,.chip-button:hover,.mobile-toggle:hover{background:var(--color-hover)}.send-icon{background:var(--color-accent);color:var(--color-text-inverse);font-size:16px;font-weight:700}.send-icon:disabled{opacity:.45}.mobile-toggle,.overlay{display:none}@media (width<=920px){html,body{width:100%;min-height:0;position:fixed;inset:0}.app-shell{height:100dvh;padding-top:env(safe-area-inset-top,0);flex-direction:column;display:flex}.timeline{flex:1;min-height:0}.composer{padding:0 1px max(2px, env(safe-area-inset-bottom));flex:none;grid-template-columns:30px minmax(0,1fr) 30px;gap:1px}.mobile-toggle{align-self:center;height:44px;display:inline-flex}.composer-box{border-radius:10px;min-height:44px}.composer textarea{padding-right:114px}.left-panel,.right-panel{top:env(safe-area-inset-top,0);z-index:30;width:var(--shell-mobile-panel-width);background:var(--color-bg-sidebar);box-shadow:4px 0 24px var(--color-shadow-drawer);transition:transform .22s cubic-bezier(.2,.8,.2,1);position:fixed;bottom:0;transform:translate(-100%)}.left-panel{border-top-right-radius:14px;border-bottom-right-radius:14px;left:0}.right-panel{box-shadow:-4px 0 24px var(--color-shadow-drawer);border-top-left-radius:14px;border-bottom-left-radius:14px;right:0;transform:translate(100%)}.app-shell[data-left-open=true] .left-panel,.app-shell[data-right-open=true] .right-panel{transform:translate(0)}.app-shell[data-left-open=true] .overlay,.app-shell[data-right-open=true] .overlay{z-index:20;background:var(--color-scrim);display:block;position:fixed;inset:0}}}
|