march-cli 0.1.36 → 0.1.38
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 +1 -1
- package/src/agent/code-search/tool.mjs +1 -1
- package/src/agent/runner/runner-utils.mjs +20 -0
- package/src/agent/runner.mjs +16 -18
- package/src/agent/runtime/remote-ui-client.mjs +0 -1
- package/src/agent/runtime/runner-process-client.mjs +7 -0
- package/src/agent/runtime/runner-process-factory.mjs +7 -3
- package/src/agent/runtime/runner-runtime-host.mjs +2 -2
- package/src/agent/runtime/ui-event-bridge.mjs +0 -2
- package/src/agent/session/session-options.mjs +2 -2
- package/src/agent/tools.mjs +5 -23
- package/src/agent/turn/turn-events.mjs +41 -0
- package/src/agent/turn/turn-runner.mjs +5 -2
- package/src/cli/args.mjs +0 -3
- package/src/cli/commands/registry/slash-command-registry.mjs +2 -0
- package/src/cli/fallback-ui.mjs +0 -2
- package/src/cli/input/history-store.mjs +65 -3
- package/src/cli/input/mode-state.mjs +1 -1
- package/src/cli/repl-loop.mjs +75 -25
- package/src/cli/startup/app-runtime.mjs +72 -31
- package/src/cli/startup/create-runtime-runner.mjs +5 -46
- package/src/cli/startup/startup-session.mjs +3 -13
- package/src/cli/tui/input/history-navigation-controller.mjs +56 -0
- package/src/cli/turn/turn-input-preparer.mjs +0 -1
- package/src/cli/ui.mjs +9 -6
- package/src/cli/workspace/command.mjs +147 -0
- package/src/cli/workspace/output-router.mjs +108 -0
- package/src/cli/workspace/project-runtime.mjs +92 -0
- package/src/config/features.mjs +0 -1
- package/src/context/engine.mjs +4 -2
- package/src/context/system-core/base.md +4 -1
- package/src/extensions/lifecycle-adapter.mjs +1 -1
- package/src/history/runner.mjs +11 -0
- package/src/history/store.mjs +129 -0
- package/src/history/tool.mjs +39 -0
- package/src/lsp/client.mjs +12 -5
- package/src/lsp/service.mjs +15 -3
- package/src/main.mjs +5 -2
- package/src/notification/desktop-notifier.mjs +16 -8
- package/src/web-ui/command.mjs +2 -2
- package/src/web-ui/dist/assets/index-BQtl1uQs.css +1 -0
- package/src/web-ui/dist/assets/index-DrlJis_D.js +1845 -0
- package/src/web-ui/dist/index.html +13 -0
- package/src/web-ui/runtime-host.mjs +5 -25
- package/src/web-ui/session-manager.mjs +2 -2
- package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +2 -10
- package/src/web-ui/src/mockData.ts +1 -8
- package/src/web-ui/src/model.ts +0 -2
- package/src/web-ui/src/runtime/client.ts +0 -1
- package/src/web-ui/src/runtime/runtimeTimeline.ts +1 -3
- package/src/web-ui/src/styles/shell.css +1 -2
- package/src/web-ui/src/timelineAdapter.ts +1 -2
- package/src/workspace/project-id.mjs +14 -0
- package/src/workspace/project-registry.mjs +74 -0
- package/src/workspace/session-index.mjs +75 -0
- package/src/workspace/supervisor.mjs +172 -0
- package/src/cli/permissions.mjs +0 -103
- package/src/cli/tui/permission-request-ui.mjs +0 -18
package/src/lsp/service.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import { LspDiagnosticStore } from "./diagnostic-store.mjs";
|
|
|
3
3
|
import { resolveLspServerStatus } from "./servers.mjs";
|
|
4
4
|
|
|
5
5
|
export class LspService {
|
|
6
|
-
constructor({ cwd, onEvent = null }) {
|
|
6
|
+
constructor({ cwd, onEvent = null, onStatusChange = null }) {
|
|
7
7
|
this.cwd = cwd;
|
|
8
8
|
this.onEvent = onEvent;
|
|
9
|
+
this.onStatusChange = onStatusChange;
|
|
9
10
|
this.store = new LspDiagnosticStore();
|
|
10
11
|
this.clients = new Map();
|
|
11
12
|
this.spawning = new Map();
|
|
@@ -19,6 +20,7 @@ export class LspService {
|
|
|
19
20
|
if (result.status === "unavailable") {
|
|
20
21
|
this.unavailable.set(result.id, result);
|
|
21
22
|
this.#emitOnce(`unavailable:${result.id}:${result.reason}`, result);
|
|
23
|
+
this.#emitStatusChange(result);
|
|
22
24
|
return result;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -34,7 +36,9 @@ export class LspService {
|
|
|
34
36
|
return { status: "starting", id: server.id, root: server.root };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
const startingEvent = { status: "starting", id: server.id, root: server.root, managed: server.managed };
|
|
40
|
+
this.#emitOnce(`starting:${key}`, startingEvent);
|
|
41
|
+
this.#emitStatusChange(startingEvent);
|
|
38
42
|
const task = this.#startClient(server, key).then((client) => {
|
|
39
43
|
client?.touchFile(path);
|
|
40
44
|
return client;
|
|
@@ -76,18 +80,22 @@ export class LspService {
|
|
|
76
80
|
cwd: server.root,
|
|
77
81
|
initialization: server.initialization,
|
|
78
82
|
store: this.store,
|
|
83
|
+
onStatusChange: (event) => this.#emitStatusChange(event),
|
|
79
84
|
});
|
|
80
85
|
try {
|
|
81
86
|
await client.start();
|
|
82
87
|
this.clients.set(key, client);
|
|
83
88
|
this.unavailable.delete(server.id);
|
|
84
|
-
|
|
89
|
+
const attachedEvent = { status: "attached", id: server.id, root: server.root, managed: server.managed };
|
|
90
|
+
this.#emitOnce(`attached:${key}`, attachedEvent);
|
|
91
|
+
this.#emitStatusChange(attachedEvent);
|
|
85
92
|
return client;
|
|
86
93
|
} catch (err) {
|
|
87
94
|
client.status = "failed";
|
|
88
95
|
const event = { status: "failed", id: server.id, root: server.root, reason: err.message };
|
|
89
96
|
this.unavailable.set(server.id, event);
|
|
90
97
|
this.#emitOnce(`failed:${key}:${err.message}`, event);
|
|
98
|
+
this.#emitStatusChange(event);
|
|
91
99
|
return null;
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -97,6 +105,10 @@ export class LspService {
|
|
|
97
105
|
this.announced.add(key);
|
|
98
106
|
this.onEvent?.(event);
|
|
99
107
|
}
|
|
108
|
+
|
|
109
|
+
#emitStatusChange(event) {
|
|
110
|
+
this.onStatusChange?.(event);
|
|
111
|
+
}
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
function summarizeStatus(servers) {
|
package/src/main.mjs
CHANGED
|
@@ -26,13 +26,12 @@ export async function run(argv) {
|
|
|
26
26
|
|
|
27
27
|
const config = loadConfig(cwd);
|
|
28
28
|
const stateRoot = join(homedir(), ".march");
|
|
29
|
-
const useRuntimeProcess = process.env.MARCH_RUNTIME_PROCESS !== "0";
|
|
30
29
|
installNetworkEnvironment(config.network);
|
|
31
30
|
|
|
32
31
|
const earlyCommand = await runEarlyCliCommand(args, { config, cwd, stateRoot });
|
|
33
32
|
if (earlyCommand.handled) return earlyCommand.code;
|
|
34
33
|
|
|
35
|
-
const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot
|
|
34
|
+
const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot });
|
|
36
35
|
if (!app.ok) return app.code;
|
|
37
36
|
|
|
38
37
|
const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, {
|
|
@@ -78,6 +77,10 @@ export async function run(argv) {
|
|
|
78
77
|
runner: app.runner,
|
|
79
78
|
memoryStore: app.memoryStore,
|
|
80
79
|
currentProject: app.currentProject,
|
|
80
|
+
currentProjectInfo: app.currentProjectInfo,
|
|
81
|
+
workspaceSupervisor: app.workspaceSupervisor,
|
|
82
|
+
workspaceOutputRouter: app.workspaceOutputRouter,
|
|
83
|
+
stateRoot,
|
|
81
84
|
sessionState: app.sessionState,
|
|
82
85
|
sessionsRoot: app.sessionsRoot,
|
|
83
86
|
projectMarchDir: app.projectMarchDir,
|
|
@@ -12,6 +12,7 @@ export function createDesktopTurnNotifier({
|
|
|
12
12
|
writeBell = () => process.stdout.write("\x07"),
|
|
13
13
|
toastNotifier = nodeNotifier,
|
|
14
14
|
config = {},
|
|
15
|
+
onActivation = null,
|
|
15
16
|
} = {}) {
|
|
16
17
|
const channels = resolveNotificationChannels(config);
|
|
17
18
|
const minDurationMs = normalizeNonNegativeInteger(config.minDurationMs, 0);
|
|
@@ -31,7 +32,7 @@ export function createDesktopTurnNotifier({
|
|
|
31
32
|
if (channels.desktop) {
|
|
32
33
|
results.push({
|
|
33
34
|
channel: "desktop",
|
|
34
|
-
...(await sendDesktopNotification({ platform, spawnProcess, toastNotifier, ...payload })),
|
|
35
|
+
...(await sendDesktopNotification({ platform, spawnProcess, toastNotifier, onActivation, activation: normalizedEvent.activation, ...payload })),
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
if (channels.bell) results.push({ channel: "bell", ...sendBellNotification({ writeBell }) });
|
|
@@ -52,14 +53,14 @@ export function createDesktopTurnNotifier({
|
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
export async function sendDesktopNotification({ platform = process.platform, spawnProcess = spawn, toastNotifier = nodeNotifier, title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true }) {
|
|
56
|
+
export async function sendDesktopNotification({ platform = process.platform, spawnProcess = spawn, toastNotifier = nodeNotifier, title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true, activation = null, onActivation = null }) {
|
|
56
57
|
if (platform !== "win32") return { ok: false, reason: "unsupported-platform" };
|
|
57
58
|
|
|
58
59
|
const safeTitle = normalizeNotificationText(title) || "March";
|
|
59
60
|
const safeMessage = normalizeNotificationText(message) || "Turn finished";
|
|
60
61
|
|
|
61
62
|
try {
|
|
62
|
-
const toastResult = await sendWindowsToastNotification({ toastNotifier, title: safeTitle, message: safeMessage, iconPath, sound });
|
|
63
|
+
const toastResult = await sendWindowsToastNotification({ toastNotifier, title: safeTitle, message: safeMessage, iconPath, sound, activation, onActivation });
|
|
63
64
|
if (toastResult.ok) return { ok: true };
|
|
64
65
|
|
|
65
66
|
const script = buildWindowsNotificationScript({ title: safeTitle, message: safeMessage, iconPath });
|
|
@@ -140,18 +141,19 @@ export function buildWindowsNotificationScript({ title, message, timeoutMs = DEF
|
|
|
140
141
|
return buildWindowsBalloonScript({ title, message, timeoutMs, iconPath });
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
export function buildWindowsToastOptions({ title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true }) {
|
|
144
|
+
export function buildWindowsToastOptions({ title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true, activation = null }) {
|
|
144
145
|
return {
|
|
145
146
|
title,
|
|
146
147
|
message,
|
|
147
148
|
icon: iconPath,
|
|
148
149
|
appID: "March",
|
|
149
150
|
sound,
|
|
150
|
-
wait:
|
|
151
|
+
wait: Boolean(activation),
|
|
152
|
+
...(activation ? { activation } : {}),
|
|
151
153
|
};
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, message, iconPath, sound }) {
|
|
156
|
+
function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, message, iconPath, sound, activation = null, onActivation = null }) {
|
|
155
157
|
return new Promise((resolve) => {
|
|
156
158
|
const notify = toastNotifier?.notify;
|
|
157
159
|
if (typeof notify !== "function") {
|
|
@@ -161,12 +163,13 @@ function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, mes
|
|
|
161
163
|
|
|
162
164
|
let settled = false;
|
|
163
165
|
const timeout = setTimeout(() => finish({ ok: false, reason: "toast-timeout" }), DEFAULT_BALLOON_TIMEOUT_MS + 5000);
|
|
164
|
-
notify.call(toastNotifier, buildWindowsToastOptions({ title, message, iconPath, sound }), (err) => {
|
|
166
|
+
notify.call(toastNotifier, buildWindowsToastOptions({ title, message, iconPath, sound, activation }), (err, response, metadata) => {
|
|
165
167
|
if (err) {
|
|
166
168
|
finish({ ok: false, reason: err?.message ?? String(err) });
|
|
167
169
|
return;
|
|
168
170
|
}
|
|
169
|
-
|
|
171
|
+
if (isNotificationActivation(response, metadata)) onActivation?.(activation);
|
|
172
|
+
finish({ ok: true, activated: isNotificationActivation(response, metadata) });
|
|
170
173
|
});
|
|
171
174
|
|
|
172
175
|
function finish(result) {
|
|
@@ -178,6 +181,11 @@ function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, mes
|
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
function isNotificationActivation(response, metadata) {
|
|
185
|
+
const text = `${response ?? ""} ${metadata?.activationType ?? ""} ${metadata?.activationKind ?? ""}`.toLowerCase();
|
|
186
|
+
return /activate|click|contentsclicked|buttonclicked/.test(text);
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function resolveNotificationChannels(config) {
|
|
182
190
|
return {
|
|
183
191
|
desktop: config.desktop !== false,
|
package/src/web-ui/command.mjs
CHANGED
|
@@ -6,11 +6,11 @@ import { createWebSessionManager, resolveWorkspace } from "./session-manager.mjs
|
|
|
6
6
|
const DEFAULT_HOST = "127.0.0.1";
|
|
7
7
|
const DEFAULT_PORT = 4174;
|
|
8
8
|
|
|
9
|
-
export async function runWebUiCommand(args, { config, cwd, stateRoot
|
|
9
|
+
export async function runWebUiCommand(args, { config, cwd, stateRoot } = {}) {
|
|
10
10
|
const host = args.host ?? DEFAULT_HOST;
|
|
11
11
|
assertLoopbackHost(host);
|
|
12
12
|
const port = Number.parseInt(args.port ?? "", 10) || DEFAULT_PORT;
|
|
13
|
-
const runtime = createWebSessionManager({ args, config, launchCwd: cwd, stateRoot
|
|
13
|
+
const runtime = createWebSessionManager({ args, config, launchCwd: cwd, stateRoot });
|
|
14
14
|
const initialWorkspace = resolveInitialWorkspace(args, cwd);
|
|
15
15
|
if (initialWorkspace) await runtime.createSession(initialWorkspace);
|
|
16
16
|
|
|
@@ -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}.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}.provider-quota{border-top:1px solid var(--color-border-subtle);gap:9px;margin-top:12px;padding-top:12px;display:grid}.provider-quota-header{justify-content:space-between;align-items:center;gap:8px;font-size:12px;display:flex}.provider-quota-header span{color:var(--color-text-primary);font-weight:var(--font-weight-ui-medium)}.provider-quota-header time,.quota-row em{color:var(--color-text-muted);font-style:normal}.quota-row{gap:5px;font-size:12px;display:grid}.quota-row-main{justify-content:space-between;align-items:center;gap:8px;display:flex}.quota-row-main span{color:var(--color-text-primary)}.quota-row-main strong{color:var(--color-accent);font-size:12px}.quota-bar{background:var(--color-bg-surface);height:6px;box-shadow:inset 0 0 0 1px var(--color-border-subtle);border-radius:999px;overflow:hidden}.quota-bar span{border-radius:inherit;background:linear-gradient(90deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 72%, white));height:100%;display:block}.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}}}
|