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.
Files changed (55) hide show
  1. package/package.json +12 -1
  2. package/src/agent/lifecycle/runner-lifecycle.mjs +16 -0
  3. package/src/agent/lifecycle/runtime-restart-tool.mjs +22 -0
  4. package/src/agent/runner.mjs +9 -14
  5. package/src/agent/runtime/remote-runner-client.mjs +7 -15
  6. package/src/agent/runtime/runner-ipc-target.mjs +3 -22
  7. package/src/agent/runtime/runner-process-client.mjs +101 -24
  8. package/src/agent/runtime/runner-runtime-host.mjs +2 -0
  9. package/src/agent/runtime/state/runner-state.mjs +80 -0
  10. package/src/agent/session/session-options.mjs +2 -1
  11. package/src/agent/tools.mjs +3 -1
  12. package/src/cli/args.mjs +14 -3
  13. package/src/cli/commands/catalog/visible-commands.mjs +5 -0
  14. package/src/cli/commands/help-command.mjs +1 -7
  15. package/src/cli/commands/registry/slash-command-registry.mjs +293 -0
  16. package/src/cli/input/autocomplete.mjs +2 -25
  17. package/src/cli/repl-loop.mjs +24 -41
  18. package/src/cli/slash-commands.mjs +19 -185
  19. package/src/cli/startup/app-runtime.mjs +201 -0
  20. package/src/cli/startup/configured-command.mjs +9 -0
  21. package/src/cli/startup/early-command.mjs +29 -0
  22. package/src/cli/turn/turn-input-preparer.mjs +41 -0
  23. package/src/main.mjs +47 -242
  24. package/src/memory/markdown/memory-id.mjs +36 -0
  25. package/src/memory/markdown-store.mjs +17 -6
  26. package/src/memory/markdown-tools.mjs +3 -2
  27. package/src/web-ui/command.mjs +112 -0
  28. package/src/web-ui/dist/assets/index-BUmhnID4.css +1 -0
  29. package/src/web-ui/dist/assets/index-CtuqTjcB.js +1845 -0
  30. package/src/web-ui/dist/index.html +13 -0
  31. package/src/web-ui/index.html +12 -0
  32. package/src/web-ui/runtime-host.mjs +185 -0
  33. package/src/web-ui/server.mjs +139 -0
  34. package/src/web-ui/session-manager.mjs +109 -0
  35. package/src/web-ui/src/App.tsx +7 -0
  36. package/src/web-ui/src/components/AppShell.tsx +47 -0
  37. package/src/web-ui/src/components/Composer.tsx +47 -0
  38. package/src/web-ui/src/components/FileExplorer.tsx +46 -0
  39. package/src/web-ui/src/components/RightSidebar.tsx +70 -0
  40. package/src/web-ui/src/components/SessionTimeline.tsx +31 -0
  41. package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +109 -0
  42. package/src/web-ui/src/components/timeline/TimelineList.tsx +14 -0
  43. package/src/web-ui/src/fileTreeAdapter.ts +51 -0
  44. package/src/web-ui/src/main.tsx +11 -0
  45. package/src/web-ui/src/mockData.ts +87 -0
  46. package/src/web-ui/src/model.ts +62 -0
  47. package/src/web-ui/src/runtime/client.ts +74 -0
  48. package/src/web-ui/src/runtime/runtimeTimeline.ts +88 -0
  49. package/src/web-ui/src/runtime/useWebRuntime.ts +132 -0
  50. package/src/web-ui/src/styles/shell.css +156 -0
  51. package/src/web-ui/src/styles/tokens.css +116 -0
  52. package/src/web-ui/src/timelineAdapter.ts +43 -0
  53. package/src/web-ui/src/vite-env.d.ts +1 -0
  54. package/src/web-ui/tsconfig.json +20 -0
  55. 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
+ }
@@ -0,0 +1,11 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ root: "src/web-ui",
6
+ plugins: [react()],
7
+ server: {
8
+ host: "127.0.0.1",
9
+ port: 4174,
10
+ },
11
+ });