@jmoyers/harness 0.1.0
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/LICENSE +21 -0
- package/README.md +145 -0
- package/native/ptyd/Cargo.lock +16 -0
- package/native/ptyd/Cargo.toml +7 -0
- package/native/ptyd/src/main.rs +257 -0
- package/package.json +90 -0
- package/scripts/build-ptyd.sh +73 -0
- package/scripts/control-plane-daemon.ts +277 -0
- package/scripts/cursor-hook-relay.ts +82 -0
- package/scripts/harness-animate.ts +469 -0
- package/scripts/harness-bin.js +77 -0
- package/scripts/harness-core.ts +1 -0
- package/scripts/harness-inspector.ts +439 -0
- package/scripts/harness.ts +2493 -0
- package/src/adapters/agent-session-state.ts +390 -0
- package/src/cli/gateway-record.ts +173 -0
- package/src/codex/live-session.ts +872 -0
- package/src/config/config-core.ts +1359 -0
- package/src/config/secrets-core.ts +170 -0
- package/src/control-plane/agent-realtime-api.ts +2441 -0
- package/src/control-plane/codex-session-stream.ts +392 -0
- package/src/control-plane/codex-telemetry.ts +1325 -0
- package/src/control-plane/lifecycle-hooks.ts +706 -0
- package/src/control-plane/session-summary.ts +380 -0
- package/src/control-plane/status/agent-status-reducer.ts +21 -0
- package/src/control-plane/status/reducer-base.ts +170 -0
- package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
- package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
- package/src/control-plane/status/session-status-engine.ts +76 -0
- package/src/control-plane/stream-client.ts +396 -0
- package/src/control-plane/stream-command-parser.ts +1673 -0
- package/src/control-plane/stream-protocol.ts +1808 -0
- package/src/control-plane/stream-server-background.ts +486 -0
- package/src/control-plane/stream-server-command.ts +2557 -0
- package/src/control-plane/stream-server-connection.ts +234 -0
- package/src/control-plane/stream-server-observed-filter.ts +112 -0
- package/src/control-plane/stream-server-session-runtime.ts +566 -0
- package/src/control-plane/stream-server-state-store.ts +15 -0
- package/src/control-plane/stream-server.ts +3192 -0
- package/src/cursor/managed-hooks.ts +282 -0
- package/src/domain/conversations.ts +414 -0
- package/src/domain/directories.ts +78 -0
- package/src/domain/repositories.ts +123 -0
- package/src/domain/tasks.ts +148 -0
- package/src/domain/workspace.ts +156 -0
- package/src/events/normalized-events.ts +124 -0
- package/src/mux/ansi-integrity.ts +103 -0
- package/src/mux/control-plane-op-queue.ts +212 -0
- package/src/mux/conversation-rail.ts +339 -0
- package/src/mux/double-click.ts +78 -0
- package/src/mux/dual-pane-core.ts +435 -0
- package/src/mux/harness-core-ui.ts +817 -0
- package/src/mux/input-shortcuts.ts +667 -0
- package/src/mux/live-mux/actions-conversation.ts +344 -0
- package/src/mux/live-mux/actions-repository.ts +246 -0
- package/src/mux/live-mux/actions-task.ts +115 -0
- package/src/mux/live-mux/args.ts +142 -0
- package/src/mux/live-mux/command-menu.ts +298 -0
- package/src/mux/live-mux/control-plane-records.ts +546 -0
- package/src/mux/live-mux/conversation-state.ts +188 -0
- package/src/mux/live-mux/directory-resolution.ts +34 -0
- package/src/mux/live-mux/event-mapping.ts +96 -0
- package/src/mux/live-mux/gateway-profiler.ts +152 -0
- package/src/mux/live-mux/gateway-render-trace.ts +177 -0
- package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
- package/src/mux/live-mux/git-parsing.ts +131 -0
- package/src/mux/live-mux/git-snapshot.ts +263 -0
- package/src/mux/live-mux/git-state.ts +136 -0
- package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
- package/src/mux/live-mux/home-pane-actions.ts +58 -0
- package/src/mux/live-mux/home-pane-drop.ts +44 -0
- package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
- package/src/mux/live-mux/home-pane-pointer.ts +96 -0
- package/src/mux/live-mux/input-forwarding.ts +112 -0
- package/src/mux/live-mux/layout.ts +30 -0
- package/src/mux/live-mux/left-nav-activation.ts +103 -0
- package/src/mux/live-mux/left-nav.ts +85 -0
- package/src/mux/live-mux/left-rail-actions.ts +118 -0
- package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
- package/src/mux/live-mux/left-rail-pointer.ts +74 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
- package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
- package/src/mux/live-mux/modal-input-reducers.ts +94 -0
- package/src/mux/live-mux/modal-overlays.ts +287 -0
- package/src/mux/live-mux/modal-pointer.ts +70 -0
- package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
- package/src/mux/live-mux/observed-stream.ts +87 -0
- package/src/mux/live-mux/palette-parsing.ts +128 -0
- package/src/mux/live-mux/pointer-routing.ts +108 -0
- package/src/mux/live-mux/process-usage.ts +53 -0
- package/src/mux/live-mux/project-pane-pointer.ts +44 -0
- package/src/mux/live-mux/rail-layout.ts +244 -0
- package/src/mux/live-mux/render-trace-analysis.ts +213 -0
- package/src/mux/live-mux/render-trace-state.ts +84 -0
- package/src/mux/live-mux/repository-folding.ts +207 -0
- package/src/mux/live-mux/runtime-shutdown.ts +51 -0
- package/src/mux/live-mux/selection.ts +411 -0
- package/src/mux/live-mux/startup-utils.ts +187 -0
- package/src/mux/live-mux/status-timeline-state.ts +82 -0
- package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
- package/src/mux/live-mux/terminal-palette.ts +79 -0
- package/src/mux/new-thread-prompt.ts +165 -0
- package/src/mux/project-tree.ts +295 -0
- package/src/mux/render-frame.ts +113 -0
- package/src/mux/runtime-wiring.ts +185 -0
- package/src/mux/selector-index.ts +160 -0
- package/src/mux/startup-sequencer.ts +238 -0
- package/src/mux/task-composer.ts +289 -0
- package/src/mux/task-focused-pane.ts +417 -0
- package/src/mux/task-screen-keybindings.ts +539 -0
- package/src/mux/terminal-input-modes.ts +35 -0
- package/src/mux/workspace-path.ts +55 -0
- package/src/mux/workspace-rail-model.ts +701 -0
- package/src/mux/workspace-rail.ts +247 -0
- package/src/perf/perf-core.ts +307 -0
- package/src/pty/pty_host.ts +217 -0
- package/src/pty/session-broker.ts +158 -0
- package/src/recording/terminal-recording.ts +383 -0
- package/src/services/control-plane.ts +567 -0
- package/src/services/conversation-lifecycle.ts +176 -0
- package/src/services/conversation-startup-hydration.ts +47 -0
- package/src/services/directory-hydration.ts +49 -0
- package/src/services/event-persistence.ts +104 -0
- package/src/services/mux-ui-state-persistence.ts +82 -0
- package/src/services/output-load-sampler.ts +231 -0
- package/src/services/process-usage-refresh.ts +88 -0
- package/src/services/recording.ts +75 -0
- package/src/services/render-trace-recorder.ts +177 -0
- package/src/services/runtime-control-actions.ts +123 -0
- package/src/services/runtime-control-plane-ops.ts +131 -0
- package/src/services/runtime-conversation-actions.ts +113 -0
- package/src/services/runtime-conversation-activation.ts +78 -0
- package/src/services/runtime-conversation-starter.ts +171 -0
- package/src/services/runtime-conversation-title-edit.ts +149 -0
- package/src/services/runtime-directory-actions.ts +164 -0
- package/src/services/runtime-envelope-handler.ts +198 -0
- package/src/services/runtime-git-state.ts +92 -0
- package/src/services/runtime-input-pipeline.ts +50 -0
- package/src/services/runtime-input-router.ts +202 -0
- package/src/services/runtime-layout-resize.ts +236 -0
- package/src/services/runtime-left-rail-render.ts +159 -0
- package/src/services/runtime-main-pane-input.ts +230 -0
- package/src/services/runtime-modal-input.ts +119 -0
- package/src/services/runtime-navigation-input.ts +207 -0
- package/src/services/runtime-process-wiring.ts +68 -0
- package/src/services/runtime-rail-input.ts +287 -0
- package/src/services/runtime-render-flush.ts +146 -0
- package/src/services/runtime-render-lifecycle.ts +104 -0
- package/src/services/runtime-render-orchestrator.ts +108 -0
- package/src/services/runtime-render-pipeline.ts +167 -0
- package/src/services/runtime-render-state.ts +72 -0
- package/src/services/runtime-repository-actions.ts +197 -0
- package/src/services/runtime-right-pane-render.ts +132 -0
- package/src/services/runtime-shutdown.ts +79 -0
- package/src/services/runtime-stream-subscriptions.ts +56 -0
- package/src/services/runtime-task-composer-persistence.ts +139 -0
- package/src/services/runtime-task-editor-actions.ts +83 -0
- package/src/services/runtime-task-pane-actions.ts +198 -0
- package/src/services/runtime-task-pane-shortcuts.ts +189 -0
- package/src/services/runtime-task-pane.ts +62 -0
- package/src/services/runtime-workspace-actions.ts +153 -0
- package/src/services/runtime-workspace-observed-events.ts +190 -0
- package/src/services/session-projection-instrumentation.ts +190 -0
- package/src/services/startup-background-probe.ts +91 -0
- package/src/services/startup-background-resume.ts +65 -0
- package/src/services/startup-orchestrator.ts +166 -0
- package/src/services/startup-output-tracker.ts +54 -0
- package/src/services/startup-paint-tracker.ts +115 -0
- package/src/services/startup-persisted-conversation-queue.ts +45 -0
- package/src/services/startup-settled-gate.ts +67 -0
- package/src/services/startup-shutdown.ts +53 -0
- package/src/services/startup-span-tracker.ts +77 -0
- package/src/services/startup-state-hydration.ts +94 -0
- package/src/services/startup-visibility.ts +35 -0
- package/src/services/status-timeline-recorder.ts +144 -0
- package/src/services/task-pane-selection-actions.ts +153 -0
- package/src/services/task-planning-hydration.ts +58 -0
- package/src/services/task-planning-observed-events.ts +89 -0
- package/src/services/workspace-observed-events.ts +113 -0
- package/src/store/control-plane-store-normalize.ts +760 -0
- package/src/store/control-plane-store-types.ts +224 -0
- package/src/store/control-plane-store.ts +2951 -0
- package/src/store/event-store.ts +253 -0
- package/src/store/sqlite.ts +81 -0
- package/src/terminal/compat-matrix.ts +345 -0
- package/src/terminal/differential-checkpoints.ts +132 -0
- package/src/terminal/parity-suite.ts +441 -0
- package/src/terminal/snapshot-oracle.ts +1840 -0
- package/src/ui/conversation-input-forwarder.ts +114 -0
- package/src/ui/conversation-selection-input.ts +103 -0
- package/src/ui/debug-footer-notice.ts +39 -0
- package/src/ui/global-shortcut-input.ts +126 -0
- package/src/ui/input-preflight.ts +68 -0
- package/src/ui/input-token-router.ts +312 -0
- package/src/ui/input.ts +238 -0
- package/src/ui/kit.ts +509 -0
- package/src/ui/left-nav-input.ts +80 -0
- package/src/ui/left-rail-pointer-input.ts +148 -0
- package/src/ui/main-pane-pointer-input.ts +150 -0
- package/src/ui/modals/manager.ts +192 -0
- package/src/ui/mux-theme.ts +529 -0
- package/src/ui/panes/conversation.ts +19 -0
- package/src/ui/panes/home-gridfire.ts +302 -0
- package/src/ui/panes/home.ts +109 -0
- package/src/ui/panes/left-rail.ts +12 -0
- package/src/ui/panes/project.ts +44 -0
- package/src/ui/pointer-routing-input.ts +158 -0
- package/src/ui/repository-fold-input.ts +91 -0
- package/src/ui/screen.ts +210 -0
- package/src/ui/surface.ts +224 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import type { ConversationRailSessionSummary } from '../conversation-rail.ts';
|
|
3
|
+
import { firstShortcutText } from '../input-shortcuts.ts';
|
|
4
|
+
import type { resolveMuxShortcutBindings } from '../input-shortcuts.ts';
|
|
5
|
+
import { buildWorkspaceRailViewRows } from '../workspace-rail-model.ts';
|
|
6
|
+
import { renderWorkspaceRailAnsiRows } from '../workspace-rail.ts';
|
|
7
|
+
import type {
|
|
8
|
+
StreamSessionController,
|
|
9
|
+
StreamSessionStatusModel,
|
|
10
|
+
} from '../../control-plane/stream-protocol.ts';
|
|
11
|
+
|
|
12
|
+
type ResolvedMuxShortcutBindings = ReturnType<typeof resolveMuxShortcutBindings>;
|
|
13
|
+
type WorkspaceRailModel = Parameters<typeof renderWorkspaceRailAnsiRows>[0];
|
|
14
|
+
|
|
15
|
+
interface GitSummary {
|
|
16
|
+
readonly branch: string;
|
|
17
|
+
readonly changedFiles: number;
|
|
18
|
+
readonly additions: number;
|
|
19
|
+
readonly deletions: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface GitRepositorySnapshot {
|
|
23
|
+
readonly normalizedRemoteUrl: string | null;
|
|
24
|
+
readonly commitCount: number | null;
|
|
25
|
+
readonly lastCommitAt: string | null;
|
|
26
|
+
readonly shortCommitHash: string | null;
|
|
27
|
+
readonly inferredName: string | null;
|
|
28
|
+
readonly defaultBranch: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MuxRailRepositoryRecord {
|
|
32
|
+
readonly repositoryId: string;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly remoteUrl: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface MuxRailDirectoryRecord {
|
|
38
|
+
readonly directoryId: string;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MuxRailConversationRecord {
|
|
43
|
+
readonly sessionId: string;
|
|
44
|
+
readonly directoryId: string | null;
|
|
45
|
+
readonly title: string;
|
|
46
|
+
readonly agentType: string;
|
|
47
|
+
readonly status: ConversationRailSessionSummary['status'];
|
|
48
|
+
readonly statusModel: StreamSessionStatusModel | null;
|
|
49
|
+
readonly attentionReason: string | null;
|
|
50
|
+
readonly live: boolean;
|
|
51
|
+
readonly startedAt: string;
|
|
52
|
+
readonly lastEventAt: string | null;
|
|
53
|
+
readonly controller: StreamSessionController | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface MuxRailProcessUsageSample {
|
|
57
|
+
readonly cpuPercent: number | null;
|
|
58
|
+
readonly memoryMb: number | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BuildRailModelArgs {
|
|
62
|
+
readonly repositories: ReadonlyMap<string, MuxRailRepositoryRecord>;
|
|
63
|
+
readonly repositoryAssociationByDirectoryId: ReadonlyMap<string, string>;
|
|
64
|
+
readonly directoryRepositorySnapshotByDirectoryId: ReadonlyMap<string, GitRepositorySnapshot>;
|
|
65
|
+
readonly directories: ReadonlyMap<string, MuxRailDirectoryRecord>;
|
|
66
|
+
readonly conversations: ReadonlyMap<string, MuxRailConversationRecord>;
|
|
67
|
+
readonly orderedIds: readonly string[];
|
|
68
|
+
readonly activeProjectId: string | null;
|
|
69
|
+
readonly activeRepositoryId: string | null;
|
|
70
|
+
readonly activeConversationId: string | null;
|
|
71
|
+
readonly projectSelectionEnabled: boolean;
|
|
72
|
+
readonly repositorySelectionEnabled: boolean;
|
|
73
|
+
readonly homeSelectionEnabled: boolean;
|
|
74
|
+
readonly repositoriesCollapsed: boolean;
|
|
75
|
+
readonly collapsedRepositoryGroupIds: ReadonlySet<string>;
|
|
76
|
+
readonly shortcutsCollapsed: boolean;
|
|
77
|
+
readonly gitSummaryByDirectoryId: ReadonlyMap<string, GitSummary>;
|
|
78
|
+
readonly processUsageBySessionId: ReadonlyMap<string, MuxRailProcessUsageSample>;
|
|
79
|
+
readonly shortcutBindings: ResolvedMuxShortcutBindings;
|
|
80
|
+
readonly loadingGitSummary: GitSummary;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface BuildRailRowsArgs extends BuildRailModelArgs {
|
|
84
|
+
readonly layout: {
|
|
85
|
+
leftCols: number;
|
|
86
|
+
paneRows: number;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function shortcutHintText(bindings: ResolvedMuxShortcutBindings): string {
|
|
91
|
+
const newConversation = firstShortcutText(bindings, 'mux.conversation.new') || 'ctrl+t';
|
|
92
|
+
const critiqueConversation =
|
|
93
|
+
firstShortcutText(bindings, 'mux.conversation.critique.open-or-create') || 'ctrl+g';
|
|
94
|
+
const deleteConversation = firstShortcutText(bindings, 'mux.conversation.delete') || 'ctrl+x';
|
|
95
|
+
const takeoverConversation = firstShortcutText(bindings, 'mux.conversation.takeover') || 'ctrl+l';
|
|
96
|
+
const addProject = firstShortcutText(bindings, 'mux.directory.add') || 'ctrl+o';
|
|
97
|
+
const closeProject = firstShortcutText(bindings, 'mux.directory.close') || 'ctrl+w';
|
|
98
|
+
const next = firstShortcutText(bindings, 'mux.conversation.next') || 'ctrl+j';
|
|
99
|
+
const previous = firstShortcutText(bindings, 'mux.conversation.previous') || 'ctrl+k';
|
|
100
|
+
const interruptConversation = firstShortcutText(bindings, 'mux.conversation.interrupt');
|
|
101
|
+
const quit = firstShortcutText(bindings, 'mux.app.interrupt-all') || 'ctrl+c';
|
|
102
|
+
const commandMenu = firstShortcutText(bindings, 'mux.command-menu.toggle') || 'ctrl+p';
|
|
103
|
+
const profile = firstShortcutText(bindings, 'mux.gateway.profile.toggle') || 'ctrl+shift+p';
|
|
104
|
+
const statusTimeline =
|
|
105
|
+
firstShortcutText(bindings, 'mux.gateway.status-timeline.toggle') || 'alt+r';
|
|
106
|
+
const renderTrace = firstShortcutText(bindings, 'mux.gateway.render-trace.toggle') || 'ctrl+]';
|
|
107
|
+
const switchHint = next === previous ? next : `${next}/${previous}`;
|
|
108
|
+
const interruptHint =
|
|
109
|
+
interruptConversation.length === 0 ? '' : ` ${interruptConversation} interrupt`;
|
|
110
|
+
return `${commandMenu} menu ${newConversation} new ${critiqueConversation} critique ${deleteConversation} archive ${takeoverConversation} takeover ${addProject}/${closeProject} projects ${switchHint} switch nav ${profile} profile ${statusTimeline} status ${renderTrace} render${interruptHint} ←/→ collapse/expand ${quit} quit`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function conversationSummary(
|
|
114
|
+
conversation: MuxRailConversationRecord,
|
|
115
|
+
): ConversationRailSessionSummary {
|
|
116
|
+
return {
|
|
117
|
+
sessionId: conversation.sessionId,
|
|
118
|
+
status: conversation.status,
|
|
119
|
+
statusModel: conversation.statusModel,
|
|
120
|
+
attentionReason: conversation.attentionReason,
|
|
121
|
+
live: conversation.live,
|
|
122
|
+
startedAt: conversation.startedAt,
|
|
123
|
+
lastEventAt: conversation.lastEventAt,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function buildRailModel(args: BuildRailModelArgs): WorkspaceRailModel {
|
|
128
|
+
const repositoryRows = [...args.repositories.values()].map((repository) => {
|
|
129
|
+
let associatedProjectCount = 0;
|
|
130
|
+
let commitCount: number | null = null;
|
|
131
|
+
let lastCommitAt: string | null = null;
|
|
132
|
+
let shortCommitHash: string | null = null;
|
|
133
|
+
for (const [directoryId, repositoryId] of args.repositoryAssociationByDirectoryId.entries()) {
|
|
134
|
+
if (repositoryId !== repository.repositoryId) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
associatedProjectCount += 1;
|
|
138
|
+
const snapshot = args.directoryRepositorySnapshotByDirectoryId.get(directoryId);
|
|
139
|
+
if (snapshot === undefined) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (
|
|
143
|
+
snapshot.commitCount !== null &&
|
|
144
|
+
(commitCount === null || snapshot.commitCount > commitCount)
|
|
145
|
+
) {
|
|
146
|
+
commitCount = snapshot.commitCount;
|
|
147
|
+
}
|
|
148
|
+
const snapshotCommitAtMs =
|
|
149
|
+
snapshot.lastCommitAt === null ? Number.NaN : Date.parse(snapshot.lastCommitAt);
|
|
150
|
+
const currentCommitAtMs = lastCommitAt === null ? Number.NaN : Date.parse(lastCommitAt);
|
|
151
|
+
if (
|
|
152
|
+
snapshot.lastCommitAt !== null &&
|
|
153
|
+
(!Number.isFinite(currentCommitAtMs) || snapshotCommitAtMs >= currentCommitAtMs)
|
|
154
|
+
) {
|
|
155
|
+
lastCommitAt = snapshot.lastCommitAt;
|
|
156
|
+
shortCommitHash = snapshot.shortCommitHash;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
repositoryId: repository.repositoryId,
|
|
161
|
+
name: repository.name,
|
|
162
|
+
remoteUrl: repository.remoteUrl,
|
|
163
|
+
associatedProjectCount,
|
|
164
|
+
commitCount,
|
|
165
|
+
lastCommitAt,
|
|
166
|
+
shortCommitHash,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const directoryRows = [...args.directories.values()].map((directory) => ({
|
|
170
|
+
key: directory.directoryId,
|
|
171
|
+
workspaceId: basename(directory.path) || directory.path,
|
|
172
|
+
worktreeId: directory.path,
|
|
173
|
+
repositoryId: args.repositoryAssociationByDirectoryId.get(directory.directoryId) ?? null,
|
|
174
|
+
git: args.gitSummaryByDirectoryId.get(directory.directoryId) ?? args.loadingGitSummary,
|
|
175
|
+
}));
|
|
176
|
+
const knownDirectoryKeys = new Set(directoryRows.map((directory) => directory.key));
|
|
177
|
+
for (const sessionId of args.orderedIds) {
|
|
178
|
+
const conversation = args.conversations.get(sessionId);
|
|
179
|
+
const directoryKey = conversation?.directoryId;
|
|
180
|
+
if (
|
|
181
|
+
directoryKey === null ||
|
|
182
|
+
directoryKey === undefined ||
|
|
183
|
+
knownDirectoryKeys.has(directoryKey)
|
|
184
|
+
) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
knownDirectoryKeys.add(directoryKey);
|
|
188
|
+
directoryRows.push({
|
|
189
|
+
key: directoryKey,
|
|
190
|
+
workspaceId: '(untracked)',
|
|
191
|
+
worktreeId: '(untracked)',
|
|
192
|
+
repositoryId: args.repositoryAssociationByDirectoryId.get(directoryKey) ?? null,
|
|
193
|
+
git: args.gitSummaryByDirectoryId.get(directoryKey) ?? args.loadingGitSummary,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
repositories: repositoryRows,
|
|
199
|
+
directories: directoryRows,
|
|
200
|
+
conversations: args.orderedIds
|
|
201
|
+
.map((sessionId) => {
|
|
202
|
+
const conversation = args.conversations.get(sessionId);
|
|
203
|
+
if (conversation === undefined) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const directoryKey = conversation.directoryId ?? 'directory-missing';
|
|
207
|
+
return {
|
|
208
|
+
...conversationSummary(conversation),
|
|
209
|
+
directoryKey,
|
|
210
|
+
title: conversation.title,
|
|
211
|
+
agentLabel: conversation.agentType,
|
|
212
|
+
cpuPercent: args.processUsageBySessionId.get(conversation.sessionId)?.cpuPercent ?? null,
|
|
213
|
+
memoryMb: args.processUsageBySessionId.get(conversation.sessionId)?.memoryMb ?? null,
|
|
214
|
+
controller: conversation.controller,
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
.flatMap((conversation) => (conversation === null ? [] : [conversation])),
|
|
218
|
+
activeProjectId: args.activeProjectId,
|
|
219
|
+
activeRepositoryId: args.activeRepositoryId,
|
|
220
|
+
activeConversationId: args.activeConversationId,
|
|
221
|
+
showTaskPlanningUi: true,
|
|
222
|
+
projectSelectionEnabled: args.projectSelectionEnabled,
|
|
223
|
+
repositorySelectionEnabled: args.repositorySelectionEnabled,
|
|
224
|
+
homeSelectionEnabled: args.homeSelectionEnabled,
|
|
225
|
+
repositoriesCollapsed: args.repositoriesCollapsed,
|
|
226
|
+
collapsedRepositoryGroupIds: [...args.collapsedRepositoryGroupIds],
|
|
227
|
+
processes: [],
|
|
228
|
+
shortcutHint: shortcutHintText(args.shortcutBindings),
|
|
229
|
+
shortcutsCollapsed: args.shortcutsCollapsed,
|
|
230
|
+
nowMs: Date.now(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function buildRailRows(args: BuildRailRowsArgs): {
|
|
235
|
+
ansiRows: readonly string[];
|
|
236
|
+
viewRows: ReturnType<typeof buildWorkspaceRailViewRows>;
|
|
237
|
+
} {
|
|
238
|
+
const railModel = buildRailModel(args);
|
|
239
|
+
const viewRows = buildWorkspaceRailViewRows(railModel, args.layout.paneRows);
|
|
240
|
+
return {
|
|
241
|
+
ansiRows: renderWorkspaceRailAnsiRows(railModel, args.layout.leftCols, args.layout.paneRows),
|
|
242
|
+
viewRows,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const ESC = '\u001b';
|
|
2
|
+
|
|
3
|
+
const SUPPORTED_ESC_SINGLE = new Set(['7', '8', 'D', 'E', 'M', 'H', 'c']);
|
|
4
|
+
const SUPPORTED_CSI_FINALS = new Set([
|
|
5
|
+
'm',
|
|
6
|
+
'A',
|
|
7
|
+
'B',
|
|
8
|
+
'C',
|
|
9
|
+
'D',
|
|
10
|
+
'G',
|
|
11
|
+
'H',
|
|
12
|
+
'f',
|
|
13
|
+
'J',
|
|
14
|
+
'K',
|
|
15
|
+
'S',
|
|
16
|
+
'T',
|
|
17
|
+
'L',
|
|
18
|
+
'M',
|
|
19
|
+
'@',
|
|
20
|
+
'P',
|
|
21
|
+
'g',
|
|
22
|
+
'r',
|
|
23
|
+
's',
|
|
24
|
+
'u',
|
|
25
|
+
]);
|
|
26
|
+
const SUPPORTED_PRIVATE_MODE_PARAMS = new Set([
|
|
27
|
+
6, 25, 1000, 1002, 1003, 1004, 1005, 1006, 1015, 2004, 1047, 1048, 1049,
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
type RenderTraceControlIssueKind = 'unsupported-esc' | 'unsupported-csi' | 'unsupported-dcs';
|
|
31
|
+
|
|
32
|
+
interface RenderTraceControlIssue {
|
|
33
|
+
readonly kind: RenderTraceControlIssueKind;
|
|
34
|
+
readonly offset: number;
|
|
35
|
+
readonly sequence: string;
|
|
36
|
+
readonly finalByte?: string;
|
|
37
|
+
readonly rawParams?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isLikelyCsiQueryPayload(payload: string): boolean {
|
|
41
|
+
if (/^(?:c|0c|>c|>0c)$/u.test(payload)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (/^[0-9]*n$/u.test(payload)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (/^(?:14|16|18)t$/u.test(payload)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (/^>0q$/u.test(payload)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (/^>4;\?m$/u.test(payload)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (/^\?[0-9;]*\$p$/u.test(payload)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (payload === '?u') {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function csiSupported(rawParams: string, finalByte: string): boolean {
|
|
66
|
+
const payload = `${rawParams}${finalByte}`;
|
|
67
|
+
if (isLikelyCsiQueryPayload(payload)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const privateMode = rawParams.startsWith('?');
|
|
72
|
+
if (privateMode && (finalByte === 'h' || finalByte === 'l')) {
|
|
73
|
+
const params = rawParams
|
|
74
|
+
.slice(1)
|
|
75
|
+
.split(';')
|
|
76
|
+
.map((value) => Number.parseInt(value, 10))
|
|
77
|
+
.filter((value) => Number.isFinite(value));
|
|
78
|
+
if (params.length === 0) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return params.every((value) => SUPPORTED_PRIVATE_MODE_PARAMS.has(value));
|
|
82
|
+
}
|
|
83
|
+
if (finalByte === 'q' && rawParams.endsWith(' ')) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return SUPPORTED_CSI_FINALS.has(finalByte);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function renderTraceChunkPreview(chunk: Buffer | string, maxChars = 200): string {
|
|
90
|
+
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
91
|
+
const replaced = text
|
|
92
|
+
.replaceAll('\r', '\\r')
|
|
93
|
+
.replaceAll('\n', '\\n')
|
|
94
|
+
.replaceAll('\t', '\\t')
|
|
95
|
+
.replaceAll(ESC, '\\u001b');
|
|
96
|
+
if (replaced.length <= maxChars) {
|
|
97
|
+
return replaced;
|
|
98
|
+
}
|
|
99
|
+
return `${replaced.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function findRenderTraceControlIssues(
|
|
103
|
+
chunk: Buffer | string,
|
|
104
|
+
maxIssues = 12,
|
|
105
|
+
): readonly RenderTraceControlIssue[] {
|
|
106
|
+
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
107
|
+
const issues: RenderTraceControlIssue[] = [];
|
|
108
|
+
|
|
109
|
+
let index = 0;
|
|
110
|
+
while (index < text.length && issues.length < maxIssues) {
|
|
111
|
+
if (text[index] !== ESC) {
|
|
112
|
+
index += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const next = text[index + 1];
|
|
116
|
+
if (next === undefined) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (next === '[') {
|
|
121
|
+
let cursor = index + 2;
|
|
122
|
+
let resolved = false;
|
|
123
|
+
while (cursor < text.length) {
|
|
124
|
+
const code = text.charCodeAt(cursor);
|
|
125
|
+
if (code >= 0x40 && code <= 0x7e) {
|
|
126
|
+
const finalByte = text[cursor]!;
|
|
127
|
+
const rawParams = text.slice(index + 2, cursor);
|
|
128
|
+
const sequence = text.slice(index, cursor + 1);
|
|
129
|
+
if (!csiSupported(rawParams, finalByte)) {
|
|
130
|
+
issues.push({
|
|
131
|
+
kind: 'unsupported-csi',
|
|
132
|
+
offset: index,
|
|
133
|
+
sequence,
|
|
134
|
+
finalByte,
|
|
135
|
+
rawParams,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
index = cursor + 1;
|
|
139
|
+
resolved = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (text[cursor] === ESC) {
|
|
143
|
+
index = cursor;
|
|
144
|
+
resolved = true;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
cursor += 1;
|
|
148
|
+
}
|
|
149
|
+
if (!resolved) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (next === ']') {
|
|
156
|
+
let cursor = index + 2;
|
|
157
|
+
let resolved = false;
|
|
158
|
+
while (cursor < text.length) {
|
|
159
|
+
if (text[cursor] === '\u0007') {
|
|
160
|
+
index = cursor + 1;
|
|
161
|
+
resolved = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (text[cursor] === ESC && text[cursor + 1] === '\\') {
|
|
165
|
+
index = cursor + 2;
|
|
166
|
+
resolved = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
cursor += 1;
|
|
170
|
+
}
|
|
171
|
+
if (resolved) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (next === 'P') {
|
|
178
|
+
let cursor = index + 2;
|
|
179
|
+
let resolved = false;
|
|
180
|
+
while (cursor < text.length) {
|
|
181
|
+
if (text[cursor] === ESC && text[cursor + 1] === '\\') {
|
|
182
|
+
issues.push({
|
|
183
|
+
kind: 'unsupported-dcs',
|
|
184
|
+
offset: index,
|
|
185
|
+
sequence: text.slice(index, cursor + 2),
|
|
186
|
+
});
|
|
187
|
+
index = cursor + 2;
|
|
188
|
+
resolved = true;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
cursor += 1;
|
|
192
|
+
}
|
|
193
|
+
if (resolved) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (SUPPORTED_ESC_SINGLE.has(next)) {
|
|
200
|
+
index += 2;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
issues.push({
|
|
205
|
+
kind: 'unsupported-esc',
|
|
206
|
+
offset: index,
|
|
207
|
+
sequence: `${ESC}${next}`,
|
|
208
|
+
});
|
|
209
|
+
index += 2;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return issues;
|
|
213
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const RENDER_TRACE_STATE_FILE_NAME = 'active-render-trace.json';
|
|
4
|
+
export const RENDER_TRACE_STATE_VERSION = 1;
|
|
5
|
+
export const RENDER_TRACE_MODE = 'live-mux-render-trace';
|
|
6
|
+
export const DEFAULT_RENDER_TRACE_ROOT_PATH = '.harness/render-traces';
|
|
7
|
+
export const RENDER_TRACE_FILE_NAME = 'render-trace.log';
|
|
8
|
+
|
|
9
|
+
export interface ActiveRenderTraceState {
|
|
10
|
+
version: typeof RENDER_TRACE_STATE_VERSION;
|
|
11
|
+
mode: typeof RENDER_TRACE_MODE;
|
|
12
|
+
outputPath: string;
|
|
13
|
+
sessionName: string | null;
|
|
14
|
+
conversationId: string | null;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveRenderTraceStatePath(
|
|
19
|
+
invocationDirectory: string,
|
|
20
|
+
sessionName: string | null,
|
|
21
|
+
): string {
|
|
22
|
+
if (sessionName === null) {
|
|
23
|
+
return resolve(invocationDirectory, '.harness', RENDER_TRACE_STATE_FILE_NAME);
|
|
24
|
+
}
|
|
25
|
+
return resolve(
|
|
26
|
+
invocationDirectory,
|
|
27
|
+
'.harness',
|
|
28
|
+
'sessions',
|
|
29
|
+
sessionName,
|
|
30
|
+
RENDER_TRACE_STATE_FILE_NAME,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveDefaultRenderTraceOutputPath(
|
|
35
|
+
invocationDirectory: string,
|
|
36
|
+
sessionName: string | null,
|
|
37
|
+
): string {
|
|
38
|
+
if (sessionName === null) {
|
|
39
|
+
return resolve(invocationDirectory, DEFAULT_RENDER_TRACE_ROOT_PATH, RENDER_TRACE_FILE_NAME);
|
|
40
|
+
}
|
|
41
|
+
return resolve(
|
|
42
|
+
invocationDirectory,
|
|
43
|
+
DEFAULT_RENDER_TRACE_ROOT_PATH,
|
|
44
|
+
sessionName,
|
|
45
|
+
RENDER_TRACE_FILE_NAME,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseActiveRenderTraceState(raw: unknown): ActiveRenderTraceState | null {
|
|
50
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const candidate = raw as Record<string, unknown>;
|
|
54
|
+
if (candidate['version'] !== RENDER_TRACE_STATE_VERSION) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (candidate['mode'] !== RENDER_TRACE_MODE) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const outputPath = candidate['outputPath'];
|
|
61
|
+
const sessionName = candidate['sessionName'];
|
|
62
|
+
const conversationId = candidate['conversationId'];
|
|
63
|
+
const startedAt = candidate['startedAt'];
|
|
64
|
+
if (typeof outputPath !== 'string' || outputPath.length === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (sessionName !== null && typeof sessionName !== 'string') {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (conversationId !== null && typeof conversationId !== 'string') {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (typeof startedAt !== 'string' || startedAt.length === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
version: RENDER_TRACE_STATE_VERSION,
|
|
78
|
+
mode: RENDER_TRACE_MODE,
|
|
79
|
+
outputPath,
|
|
80
|
+
sessionName,
|
|
81
|
+
conversationId,
|
|
82
|
+
startedAt,
|
|
83
|
+
};
|
|
84
|
+
}
|