march-cli 0.1.34 → 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/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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { mockWebUiModel } from "../mockData";
|
|
3
|
+
import type { MarchTimelineEvent, WebUiModel } from "../model";
|
|
4
|
+
import { applyRuntimeEvent } from "./runtimeTimeline";
|
|
5
|
+
import { connectRuntimeEvents, createRuntimeSession, fetchFsList, fetchFsRoots, fetchRuntimeSnapshot, submitRuntimeTurn } from "./client";
|
|
6
|
+
import type { FsEntry } from "./client";
|
|
7
|
+
|
|
8
|
+
export type WebRuntimeState = {
|
|
9
|
+
model: WebUiModel;
|
|
10
|
+
connected: boolean;
|
|
11
|
+
running: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
fsEntries: FsEntry[];
|
|
14
|
+
fsPath: string | null;
|
|
15
|
+
openSession: (sessionId: string) => Promise<void>;
|
|
16
|
+
createSession: (workspacePath: string) => Promise<void>;
|
|
17
|
+
browseRoots: () => Promise<void>;
|
|
18
|
+
browsePath: (path: string) => Promise<void>;
|
|
19
|
+
submitPrompt: (prompt: string) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function useWebRuntime(): WebRuntimeState {
|
|
23
|
+
const [model, setModel] = useState<WebUiModel>(mockWebUiModel);
|
|
24
|
+
const [connected, setConnected] = useState(false);
|
|
25
|
+
const [running, setRunning] = useState(false);
|
|
26
|
+
const [error, setError] = useState<string | null>(null);
|
|
27
|
+
const [fsEntries, setFsEntries] = useState<FsEntry[]>([]);
|
|
28
|
+
const [fsPath, setFsPath] = useState<string | null>(null);
|
|
29
|
+
const activeSessionId = model.activeSessionId ?? null;
|
|
30
|
+
const timelineCache = useRef(new Map<string, MarchTimelineEvent[]>());
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
let mounted = true;
|
|
34
|
+
fetchRuntimeSnapshot()
|
|
35
|
+
.then((snapshot) => {
|
|
36
|
+
if (!mounted) return;
|
|
37
|
+
setModel(snapshot);
|
|
38
|
+
setConnected(true);
|
|
39
|
+
})
|
|
40
|
+
.catch(() => setConnected(false));
|
|
41
|
+
fetchFsRoots().then((roots) => mounted && setFsEntries(roots)).catch(() => {});
|
|
42
|
+
return () => { mounted = false; };
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!activeSessionId) return undefined;
|
|
47
|
+
const disconnect = connectRuntimeEvents(
|
|
48
|
+
activeSessionId,
|
|
49
|
+
(event) => {
|
|
50
|
+
setConnected(true);
|
|
51
|
+
setModel((current) => {
|
|
52
|
+
const next = updateTimelineEvents(current, (events) => applyRuntimeEvent(events, event));
|
|
53
|
+
rememberTimeline(next, timelineCache.current);
|
|
54
|
+
return next;
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
() => setConnected(false),
|
|
58
|
+
);
|
|
59
|
+
return disconnect;
|
|
60
|
+
}, [activeSessionId]);
|
|
61
|
+
|
|
62
|
+
async function openSession(sessionId: string) {
|
|
63
|
+
setError(null);
|
|
64
|
+
const snapshot = await fetchRuntimeSnapshot(sessionId);
|
|
65
|
+
setModel(restoreCachedTimeline(snapshot, timelineCache.current));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function createSession(workspacePath: string) {
|
|
69
|
+
setRunning(true);
|
|
70
|
+
setError(null);
|
|
71
|
+
try {
|
|
72
|
+
const result = await createRuntimeSession(workspacePath);
|
|
73
|
+
setModel(result.snapshot);
|
|
74
|
+
rememberTimeline(result.snapshot, timelineCache.current);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
77
|
+
} finally {
|
|
78
|
+
setRunning(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function browseRoots() {
|
|
83
|
+
setFsPath(null);
|
|
84
|
+
setFsEntries(await fetchFsRoots());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function browsePath(path: string) {
|
|
88
|
+
setFsPath(path);
|
|
89
|
+
setFsEntries(await fetchFsList(path));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function submitPrompt(prompt: string) {
|
|
93
|
+
if (!activeSessionId) {
|
|
94
|
+
setError("Choose a workspace before sending a message");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
setRunning(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
try {
|
|
100
|
+
await submitRuntimeTurn(activeSessionId, prompt);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
103
|
+
setError(message);
|
|
104
|
+
setModel((current) => {
|
|
105
|
+
const next = updateTimelineEvents(current, (events) => [
|
|
106
|
+
...events,
|
|
107
|
+
{ id: `client-error:${Date.now()}`, type: "error", message },
|
|
108
|
+
]);
|
|
109
|
+
rememberTimeline(next, timelineCache.current);
|
|
110
|
+
return next;
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
setRunning(false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { model, connected, running, error, fsEntries, fsPath, openSession, createSession, browseRoots, browsePath, submitPrompt };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function restoreCachedTimeline(model: WebUiModel, cache: Map<string, MarchTimelineEvent[]>): WebUiModel {
|
|
121
|
+
const activeSessionId = model.activeSessionId;
|
|
122
|
+
const cached = activeSessionId ? cache.get(activeSessionId) : null;
|
|
123
|
+
return cached ? { ...model, timeline: { ...model.timeline, events: cached } } : model;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function rememberTimeline(model: WebUiModel, cache: Map<string, MarchTimelineEvent[]>) {
|
|
127
|
+
if (model.activeSessionId) cache.set(model.activeSessionId, model.timeline.events);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function updateTimelineEvents(model: WebUiModel, update: (events: MarchTimelineEvent[]) => MarchTimelineEvent[]): WebUiModel {
|
|
131
|
+
return { ...model, timeline: { ...model.timeline, events: update(model.timeline.events) } };
|
|
132
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.app-shell {
|
|
3
|
+
height: 100dvh;
|
|
4
|
+
display: grid;
|
|
5
|
+
grid-template-columns: var(--shell-left-width) minmax(0, 1fr) var(--shell-right-width);
|
|
6
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
7
|
+
grid-template-areas: "sidebar main right" "sidebar footer right";
|
|
8
|
+
background: var(--color-bg-page);
|
|
9
|
+
color: var(--color-text-primary);
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
isolation: isolate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.panel { min-height: 0; overflow: hidden; background: var(--color-bg-sidebar); }
|
|
15
|
+
.left-panel { grid-area: sidebar; border-right: 1px solid var(--color-border-subtle); }
|
|
16
|
+
.right-panel { grid-area: right; border-left: 1px solid var(--color-border-subtle); display: flex; flex-direction: column; }
|
|
17
|
+
|
|
18
|
+
.projects-header, .right-header, .main-header {
|
|
19
|
+
height: var(--header-height);
|
|
20
|
+
padding: 0 16px;
|
|
21
|
+
border-bottom: 1px solid var(--color-border-subtle);
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
background: var(--color-bg-sidebar);
|
|
26
|
+
color: var(--color-text-muted);
|
|
27
|
+
font-size: 11px;
|
|
28
|
+
font-weight: var(--font-weight-ui-medium);
|
|
29
|
+
text-transform: uppercase;
|
|
30
|
+
}
|
|
31
|
+
.projects-header h3 { margin: 0; font: inherit; letter-spacing: 0.5px; }
|
|
32
|
+
.menu-button {
|
|
33
|
+
width: 28px;
|
|
34
|
+
height: 28px;
|
|
35
|
+
border: 0;
|
|
36
|
+
border-radius: var(--radius-control);
|
|
37
|
+
background: transparent;
|
|
38
|
+
color: var(--color-text-muted);
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
gap: 3px;
|
|
43
|
+
}
|
|
44
|
+
.menu-button span { width: 3.6px; height: 3.6px; border-radius: 999px; background: currentColor; }
|
|
45
|
+
.menu-button:hover, .header-button:hover, .session-row:hover, .activity-row:hover { background: var(--color-hover); }
|
|
46
|
+
.projects-body { height: calc(100% - var(--header-height)); overflow: hidden; padding: var(--space-panel) 0; }
|
|
47
|
+
.project-tree-host {
|
|
48
|
+
height: 100%;
|
|
49
|
+
--trees-accent-override: var(--color-accent);
|
|
50
|
+
--trees-bg-override: var(--color-bg-sidebar);
|
|
51
|
+
--trees-bg-muted-override: var(--color-hover);
|
|
52
|
+
--trees-border-color-override: var(--color-border-subtle);
|
|
53
|
+
--trees-fg-override: var(--color-text-primary);
|
|
54
|
+
--trees-fg-muted-override: var(--color-text-muted);
|
|
55
|
+
--trees-selected-bg-override: var(--color-accent-soft);
|
|
56
|
+
--trees-selected-fg-override: var(--color-accent);
|
|
57
|
+
--trees-font-family-override: var(--font-sans);
|
|
58
|
+
--trees-font-size-override: 13px;
|
|
59
|
+
--trees-border-radius-override: var(--radius-row);
|
|
60
|
+
--trees-item-height: 30px;
|
|
61
|
+
--trees-item-padding-x-override: 8px;
|
|
62
|
+
--trees-item-margin-x-override: 8px;
|
|
63
|
+
--trees-item-row-gap-override: 4px;
|
|
64
|
+
--trees-level-gap-override: var(--tree-indent);
|
|
65
|
+
--trees-padding-inline-override: 0px;
|
|
66
|
+
--trees-scrollbar-gutter-override: 6px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.timeline { grid-area: main; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--color-bg-surface); overflow: hidden; }
|
|
70
|
+
.main-header { background: var(--color-bg-surface); }
|
|
71
|
+
.runtime-pill { border-radius: 999px; padding: 2px 7px; color: var(--color-text-muted); background: var(--color-bg-subtle); font-size: 12px; }
|
|
72
|
+
.runtime-pill.connected { color: var(--color-accent); background: var(--color-accent-soft); }
|
|
73
|
+
.timeline-scroll { flex: 1; min-height: 0; overflow: auto; padding: 16px 18px 24px; }
|
|
74
|
+
.session-title { display: flex; align-items: baseline; gap: 8px; margin: 0 0 14px 40px; }
|
|
75
|
+
.session-title h1 { margin: 0; font-size: 17px; font-weight: var(--font-weight-ui-strong); letter-spacing: -0.01em; }
|
|
76
|
+
.session-title span { color: var(--color-text-muted); font-size: 12px; }
|
|
77
|
+
.timeline-list { max-width: 920px; margin: 0 auto; }
|
|
78
|
+
.message-row { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 10px; padding: 10px 0; }
|
|
79
|
+
.message-row + .message-row { border-top: 1px solid var(--color-border-subtle); }
|
|
80
|
+
.agent-dot { display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius-control); background: var(--palette-slate-700); color: var(--color-text-inverse); font-size: 12px; font-weight: 800; }
|
|
81
|
+
.agent-dot.march { background: var(--color-accent); }
|
|
82
|
+
.message-body { min-width: 0; padding-top: 3px; color: var(--color-text-primary); font-size: 14px; font-weight: var(--font-weight-content-normal); line-height: 1.55; }
|
|
83
|
+
.message-body p { margin: 0; }
|
|
84
|
+
.message-body time { display: block; margin-top: 4px; color: var(--color-text-muted); font-size: 11px; }
|
|
85
|
+
.timeline-aux { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-control); padding: 7px 9px; background: var(--color-bg-subtle); }
|
|
86
|
+
.timeline-aux summary, .aux-title { display: flex; align-items: center; gap: 8px; min-height: 20px; }
|
|
87
|
+
.timeline-aux summary { cursor: pointer; }
|
|
88
|
+
.timeline-aux span, .timeline-aux em { color: var(--color-text-muted); font-size: 12px; font-style: normal; }
|
|
89
|
+
.timeline-aux strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: var(--font-weight-ui-medium); }
|
|
90
|
+
.timeline-aux p { margin: 7px 0 0; color: var(--color-text-muted); font-size: 13px; }
|
|
91
|
+
.tool-block span, .diff-inline, .terminal-block pre { font-family: var(--font-mono); }
|
|
92
|
+
.diff-inline { margin-top: 7px; border-left: 2px solid var(--color-border-strong); padding-left: 10px; font-size: 13px; }
|
|
93
|
+
.diff-line { padding: 2px 0; }
|
|
94
|
+
.diff-line.add { color: var(--color-success); }
|
|
95
|
+
.diff-line.remove, .error-block strong { color: var(--color-danger); }
|
|
96
|
+
.diff-line.keep { color: var(--color-text-muted); }
|
|
97
|
+
.terminal-block pre { margin: 7px 0 0; color: var(--color-text-muted); font-size: 12px; white-space: pre-wrap; }
|
|
98
|
+
.permission-block.pending strong { color: var(--color-warning); }
|
|
99
|
+
.permission-block.approved strong { color: var(--color-success); }
|
|
100
|
+
|
|
101
|
+
.right-body { flex: 1; min-height: 0; overflow: auto; padding: 12px; }
|
|
102
|
+
.workspace-picker { display: grid; gap: 7px; padding-bottom: 10px; }
|
|
103
|
+
.workspace-picker label { color: var(--color-text-muted); font-size: 11px; font-weight: var(--font-weight-ui-medium); text-transform: uppercase; }
|
|
104
|
+
.workspace-input-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px; }
|
|
105
|
+
.workspace-input-row input { min-width: 0; border: 1px solid var(--color-border-subtle); border-radius: var(--radius-control); padding: 7px 8px; background: var(--color-bg-surface); color: var(--color-text-primary); }
|
|
106
|
+
.workspace-input-row button, .fs-entry-row button, .fs-row { border: 0; border-radius: var(--radius-control); background: transparent; color: var(--color-text-muted); }
|
|
107
|
+
.workspace-input-row button { padding: 0 8px; background: var(--color-accent-soft); color: var(--color-accent); }
|
|
108
|
+
.workspace-input-row button:disabled { opacity: 0.45; }
|
|
109
|
+
.workspace-path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-muted); font-size: 12px; }
|
|
110
|
+
.fs-entry-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px; }
|
|
111
|
+
.fs-entry-row button, .fs-row { min-height: 28px; padding: 0 7px; text-align: left; }
|
|
112
|
+
.fs-entry-row button:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-primary); }
|
|
113
|
+
.session-row, .activity-row { width: 100%; min-height: 34px; border: 0; border-radius: var(--radius-control); padding: 7px 8px; background: transparent; color: var(--color-text-primary); display: flex; align-items: center; justify-content: space-between; gap: 8px; text-align: left; font-size: 13px; }
|
|
114
|
+
.session-row.active { background: var(--color-accent-soft); color: var(--color-accent); font-weight: var(--font-weight-ui-medium); }
|
|
115
|
+
.session-row time, .activity-row time { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-muted); font-size: 12px; }
|
|
116
|
+
.right-divider { margin: 12px 0 4px; padding-top: 12px; 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; }
|
|
117
|
+
.activity-row { color: var(--color-text-muted); }
|
|
118
|
+
|
|
119
|
+
.composer { grid-area: footer; min-width: 0; padding: 0 16px 12px; display: grid; grid-template-columns: 1fr; align-items: end; background: var(--color-bg-surface); }
|
|
120
|
+
.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); display: flex; align-items: center; position: relative; overflow: visible; }
|
|
121
|
+
.composer-box:focus-within { border-color: var(--color-accent); box-shadow: 0 0 0 1px var(--color-accent-soft); }
|
|
122
|
+
.composer textarea { width: 100%; max-height: 160px; resize: none; border: 0; outline: 0; background: transparent; color: var(--color-text-primary); font-weight: var(--font-weight-content-normal); padding: 12px 124px 12px 12px; }
|
|
123
|
+
.composer-actions { position: absolute; right: 8px; bottom: 8px; display: flex; align-items: center; gap: 2px; }
|
|
124
|
+
.session-ring, .chip-button, .icon-action, .send-icon, .mobile-toggle { width: 28px; height: 28px; border: 0; border-radius: var(--radius-control); background: transparent; color: var(--color-text-muted); display: inline-flex; align-items: center; justify-content: center; }
|
|
125
|
+
.session-ring::before { content: ""; width: 14px; height: 14px; border: 2px solid var(--color-accent); border-radius: 999px; }
|
|
126
|
+
.chip-button { width: auto; padding: 0 8px; font-size: 12px; }
|
|
127
|
+
.icon-action:hover, .chip-button:hover, .mobile-toggle:hover { background: var(--color-hover); }
|
|
128
|
+
.send-icon { background: var(--color-accent); color: var(--color-text-inverse); font-size: 16px; font-weight: 700; }
|
|
129
|
+
.send-icon:disabled { opacity: 0.45; }
|
|
130
|
+
.mobile-toggle, .overlay { display: none; }
|
|
131
|
+
|
|
132
|
+
@media (max-width: 920px) {
|
|
133
|
+
html, body { position: fixed; inset: 0; width: 100%; min-height: 0; }
|
|
134
|
+
.app-shell { display: flex; flex-direction: column; height: 100dvh; padding-top: env(safe-area-inset-top, 0); }
|
|
135
|
+
.timeline { flex: 1; min-height: 0; }
|
|
136
|
+
.composer { flex: 0 0 auto; grid-template-columns: 30px minmax(0, 1fr) 30px; gap: 1px; padding: 0 1px max(2px, env(safe-area-inset-bottom)); }
|
|
137
|
+
.mobile-toggle { display: inline-flex; height: 44px; align-self: center; }
|
|
138
|
+
.composer-box { border-radius: 10px; min-height: 44px; }
|
|
139
|
+
.composer textarea { padding-right: 114px; }
|
|
140
|
+
.left-panel, .right-panel {
|
|
141
|
+
position: fixed;
|
|
142
|
+
top: env(safe-area-inset-top, 0);
|
|
143
|
+
bottom: 0;
|
|
144
|
+
z-index: 30;
|
|
145
|
+
width: var(--shell-mobile-panel-width);
|
|
146
|
+
transform: translateX(-100%);
|
|
147
|
+
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
148
|
+
background: var(--color-bg-sidebar);
|
|
149
|
+
box-shadow: 4px 0 24px var(--color-shadow-drawer);
|
|
150
|
+
}
|
|
151
|
+
.left-panel { left: 0; border-top-right-radius: 14px; border-bottom-right-radius: 14px; }
|
|
152
|
+
.right-panel { right: 0; transform: translateX(100%); border-top-left-radius: 14px; border-bottom-left-radius: 14px; box-shadow: -4px 0 24px var(--color-shadow-drawer); }
|
|
153
|
+
.app-shell[data-left-open="true"] .left-panel, .app-shell[data-right-open="true"] .right-panel { transform: translateX(0); }
|
|
154
|
+
.app-shell[data-left-open="true"] .overlay, .app-shell[data-right-open="true"] .overlay { display: block; position: fixed; inset: 0; z-index: 20; background: var(--color-scrim); }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
@layer theme.palette, theme.semantic, theme.component, base;
|
|
2
|
+
|
|
3
|
+
@layer theme.palette {
|
|
4
|
+
:root {
|
|
5
|
+
color-scheme: light dark;
|
|
6
|
+
--font-sans:
|
|
7
|
+
"Segoe UI Variable Text",
|
|
8
|
+
"Segoe UI Variable",
|
|
9
|
+
"Segoe UI",
|
|
10
|
+
-apple-system,
|
|
11
|
+
BlinkMacSystemFont,
|
|
12
|
+
Roboto,
|
|
13
|
+
"Helvetica Neue",
|
|
14
|
+
Arial,
|
|
15
|
+
DengXian,
|
|
16
|
+
"等线",
|
|
17
|
+
"Microsoft YaHei UI",
|
|
18
|
+
sans-serif;
|
|
19
|
+
--font-mono:
|
|
20
|
+
ui-monospace,
|
|
21
|
+
"SFMono-Regular",
|
|
22
|
+
"Cascadia Code",
|
|
23
|
+
"JetBrains Mono",
|
|
24
|
+
"Fira Code",
|
|
25
|
+
Consolas,
|
|
26
|
+
"Liberation Mono",
|
|
27
|
+
Menlo,
|
|
28
|
+
monospace;
|
|
29
|
+
--font-weight-ui-normal: 500;
|
|
30
|
+
--font-weight-content-normal: 600;
|
|
31
|
+
--font-weight-ui-medium: 600;
|
|
32
|
+
--font-weight-ui-strong: 700;
|
|
33
|
+
--palette-white: #ffffff;
|
|
34
|
+
--palette-slate-50: #f8fafc;
|
|
35
|
+
--palette-slate-100: #f1f5f9;
|
|
36
|
+
--palette-slate-200: #e2e8f0;
|
|
37
|
+
--palette-slate-400: #94a3b8;
|
|
38
|
+
--palette-slate-500: #64748b;
|
|
39
|
+
--palette-slate-700: #334155;
|
|
40
|
+
--palette-slate-900: #0f172a;
|
|
41
|
+
--palette-slate-950: #020617;
|
|
42
|
+
--palette-blue-400: #60a5fa;
|
|
43
|
+
--palette-blue-600: #2563eb;
|
|
44
|
+
--palette-green-700: #15803d;
|
|
45
|
+
--palette-amber-600: #d97706;
|
|
46
|
+
--palette-red-600: #dc2626;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@layer theme.semantic {
|
|
51
|
+
:root {
|
|
52
|
+
--color-bg-page: #f3f4f6;
|
|
53
|
+
--color-bg-surface: var(--palette-white);
|
|
54
|
+
--color-bg-sidebar: rgba(255, 255, 255, 0.74);
|
|
55
|
+
--color-bg-subtle: var(--palette-slate-50);
|
|
56
|
+
--color-text-primary: var(--palette-slate-900);
|
|
57
|
+
--color-text-muted: var(--palette-slate-500);
|
|
58
|
+
--color-text-inverse: var(--palette-white);
|
|
59
|
+
--color-border-subtle: rgba(15, 23, 42, 0.08);
|
|
60
|
+
--color-border-strong: rgba(15, 23, 42, 0.14);
|
|
61
|
+
--color-accent: var(--palette-blue-600);
|
|
62
|
+
--color-accent-soft: rgba(37, 99, 235, 0.08);
|
|
63
|
+
--color-success: var(--palette-green-700);
|
|
64
|
+
--color-warning: var(--palette-amber-600);
|
|
65
|
+
--color-danger: var(--palette-red-600);
|
|
66
|
+
--color-hover: rgba(0, 0, 0, 0.05);
|
|
67
|
+
--color-shadow-drawer: rgba(0, 0, 0, 0.15);
|
|
68
|
+
--color-scrim: rgba(0, 0, 0, 0.3);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@media (prefers-color-scheme: dark) {
|
|
72
|
+
:root {
|
|
73
|
+
--color-bg-page: var(--palette-slate-900);
|
|
74
|
+
--color-bg-surface: var(--palette-slate-950);
|
|
75
|
+
--color-bg-sidebar: var(--palette-slate-900);
|
|
76
|
+
--color-bg-subtle: var(--palette-slate-900);
|
|
77
|
+
--color-text-primary: var(--palette-slate-50);
|
|
78
|
+
--color-text-muted: var(--palette-slate-400);
|
|
79
|
+
--color-border-subtle: rgba(148, 163, 184, 0.12);
|
|
80
|
+
--color-border-strong: rgba(148, 163, 184, 0.2);
|
|
81
|
+
--color-accent: var(--palette-blue-400);
|
|
82
|
+
--color-accent-soft: rgba(96, 165, 250, 0.14);
|
|
83
|
+
--color-hover: rgba(148, 163, 184, 0.08);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@layer theme.component {
|
|
89
|
+
:root {
|
|
90
|
+
--shell-left-width: 260px;
|
|
91
|
+
--shell-right-width: 280px;
|
|
92
|
+
--shell-mobile-panel-width: 75vw;
|
|
93
|
+
--header-height: 36px;
|
|
94
|
+
--composer-min-height: 46px;
|
|
95
|
+
--radius-row: 6px;
|
|
96
|
+
--radius-control: 8px;
|
|
97
|
+
--radius-composer: 12px;
|
|
98
|
+
--space-panel: 8px;
|
|
99
|
+
--tree-indent: 16px;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@layer base {
|
|
104
|
+
* { box-sizing: border-box; }
|
|
105
|
+
html, body, #root { height: 100%; margin: 0; overflow: hidden; }
|
|
106
|
+
body {
|
|
107
|
+
background: var(--color-bg-page);
|
|
108
|
+
color: var(--color-text-primary);
|
|
109
|
+
font-family: var(--font-sans);
|
|
110
|
+
font-weight: var(--font-weight-ui-normal);
|
|
111
|
+
-webkit-font-smoothing: antialiased;
|
|
112
|
+
text-rendering: optimizeLegibility;
|
|
113
|
+
}
|
|
114
|
+
button, textarea { font: inherit; }
|
|
115
|
+
button { cursor: pointer; }
|
|
116
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MarchTimelineEvent, TimelineItem } from "./model";
|
|
2
|
+
|
|
3
|
+
export function normalizeTimelineEvents(events: MarchTimelineEvent[]): TimelineItem[] {
|
|
4
|
+
const items: TimelineItem[] = [];
|
|
5
|
+
|
|
6
|
+
for (const event of events) {
|
|
7
|
+
if (event.type === "tool_result") {
|
|
8
|
+
const previous = items.at(-1);
|
|
9
|
+
if (previous?.kind === "tool" && previous.tool === event.tool) {
|
|
10
|
+
previous.summary = event.summary;
|
|
11
|
+
previous.status = event.status;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
items.push(toTimelineItem(event));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return items;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toTimelineItem(event: MarchTimelineEvent): TimelineItem {
|
|
23
|
+
switch (event.type) {
|
|
24
|
+
case "user_message":
|
|
25
|
+
return { id: event.id, kind: "message", actor: "user", text: event.text, time: event.time };
|
|
26
|
+
case "assistant_message":
|
|
27
|
+
return { id: event.id, kind: "message", actor: "march", text: event.text, time: event.time };
|
|
28
|
+
case "assistant_thought":
|
|
29
|
+
return { id: event.id, kind: "thought", title: event.title, text: event.text, status: event.status };
|
|
30
|
+
case "tool_call":
|
|
31
|
+
return { id: event.id, kind: "tool", tool: event.tool, target: event.target, status: event.status };
|
|
32
|
+
case "tool_result":
|
|
33
|
+
return { id: event.id, kind: "tool", tool: event.tool, target: "result", status: event.status, summary: event.summary };
|
|
34
|
+
case "file_diff":
|
|
35
|
+
return { id: event.id, kind: "diff", path: event.path, lines: event.lines };
|
|
36
|
+
case "terminal_output":
|
|
37
|
+
return { id: event.id, kind: "terminal", command: event.command, output: event.output, status: event.status };
|
|
38
|
+
case "permission_request":
|
|
39
|
+
return { id: event.id, kind: "permission", title: event.title, detail: event.detail, status: event.status };
|
|
40
|
+
case "error":
|
|
41
|
+
return { id: event.id, kind: "error", message: event.message, detail: event.detail };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"module": "ESNext",
|
|
13
|
+
"moduleResolution": "Bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx"
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"]
|
|
20
|
+
}
|