create-harness-vibe-coding 0.8.18 → 0.8.19
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/CHANGELOG.md +13 -0
- package/README.md +1 -0
- package/package.json +5 -2
- package/src/index.js +383 -267
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +17 -0
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +1 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +7 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +1 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +12 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +1 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js +50 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +1 -0
- package/src/ui/dist/assets/TerminalDrawer-6GBZ9nXN.css +32 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +74 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +1 -0
- package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +1 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +39 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +1 -0
- package/src/ui/dist/assets/index-BOVYWntB.js +117 -0
- package/src/ui/dist/assets/index-BOVYWntB.js.map +1 -0
- package/src/ui/dist/assets/index-DaUObq0H.css +1 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +7 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +1 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js +7 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js.map +1 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js +2 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js.map +1 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js +7 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +1 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js +7 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +1 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +7 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +1 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js +7 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js.map +1 -0
- package/src/ui/dist/index.html +18 -0
- package/src/ui/index.html +17 -0
- package/src/ui/package.json +33 -0
- package/src/ui/pnpm-lock.yaml +1582 -0
- package/src/ui/pnpm-workspace.yaml +2 -0
- package/src/ui/src/App.tsx +86 -0
- package/src/ui/src/api.ts +93 -0
- package/src/ui/src/components/AgentsRoute.tsx +503 -0
- package/src/ui/src/components/Footer.tsx +69 -0
- package/src/ui/src/components/Header.tsx +175 -0
- package/src/ui/src/components/LoadingView.tsx +22 -0
- package/src/ui/src/components/RolesRoute.tsx +169 -0
- package/src/ui/src/components/SettingsRoute.tsx +166 -0
- package/src/ui/src/components/TaskList.tsx +388 -0
- package/src/ui/src/components/TerminalDrawer.tsx +611 -0
- package/src/ui/src/components/WorkflowRoute.tsx +670 -0
- package/src/ui/src/hooks/useReducedMotion.ts +17 -0
- package/src/ui/src/hooks/useServerConnection.ts +114 -0
- package/src/ui/src/index.css +281 -0
- package/src/ui/src/main.tsx +11 -0
- package/src/ui/src/types.ts +108 -0
- package/src/ui/tsconfig.json +21 -0
- package/src/ui/vite.config.ts +19 -0
- package/src/wf-ui-server/__tests__/a2a-store.test.mjs +79 -0
- package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +268 -0
- package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +70 -0
- package/src/wf-ui-server/__tests__/runtime-config.test.mjs +43 -0
- package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +90 -0
- package/src/wf-ui-server/__tests__/security.test.mjs +59 -0
- package/src/wf-ui-server/__tests__/server.integration.test.mjs +150 -0
- package/src/wf-ui-server/__tests__/session-registry.test.mjs +238 -0
- package/src/wf-ui-server/__tests__/settings.test.mjs +135 -0
- package/src/wf-ui-server/__tests__/task-parser.test.mjs +200 -0
- package/src/wf-ui-server/__tests__/terminal-store.test.mjs +138 -0
- package/src/wf-ui-server/__tests__/token.test.mjs +48 -0
- package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +419 -0
- package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +383 -0
- package/src/wf-ui-server/a2a-store.mjs +296 -0
- package/src/wf-ui-server/peer-capsule.mjs +213 -0
- package/src/wf-ui-server/pty-adapter.mjs +155 -0
- package/src/wf-ui-server/runtime-config.mjs +153 -0
- package/src/wf-ui-server/runtime-detector.mjs +374 -0
- package/src/wf-ui-server/security.mjs +67 -0
- package/src/wf-ui-server/server.mjs +849 -0
- package/src/wf-ui-server/session-registry.mjs +204 -0
- package/src/wf-ui-server/settings.mjs +100 -0
- package/src/wf-ui-server/task-parser.mjs +133 -0
- package/src/wf-ui-server/terminal-store.mjs +225 -0
- package/src/wf-ui-server/token.mjs +41 -0
- package/src/wf-ui-server/ws-events.mjs +354 -0
- package/src/wf-ui-server/ws-terminal.mjs +473 -0
- package/templates/common/.claude/commands/wf-help.md +1 -0
- package/templates/common/.claude/commands/wf-task-list.md +2 -0
- package/templates/common/.claude/commands/wf-ui.md +26 -0
- package/templates/common/.claude/commands/wf-update.md +54 -9
- package/templates/common/.claude/rules/ecc/common.md +1 -1
- package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -3
- package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +2 -2
- package/templates/common/.claude/skills/wf-ui/SKILL.md +78 -0
- package/templates/common/.claude/skills/wf-update/SKILL.md +55 -58
- package/templates/common/.harness-version +63 -39
- package/templates/common/.opencode/commands/wf-help.md +1 -0
- package/templates/common/.opencode/commands/wf-task-list.md +2 -0
- package/templates/common/.opencode/commands/wf-ui.md +26 -0
- package/templates/common/.opencode/commands/wf-update.md +54 -9
- package/templates/common/CLAUDE.md +1 -1
- package/templates/common/Harness/MEMORY.md +2 -0
- package/templates/common/Harness/README.md +6 -4
- package/templates/common/Harness/a2a/role-graph.json +79 -0
- package/templates/common/Harness/a2a/runtime-registry.json +5 -0
- package/templates/common/Harness/a2a/skills/terminal-control.json +15 -0
- package/templates/common/Harness/ownership.manifest.json +57 -2
- package/templates/common/Harness/scripts/README.md +134 -0
- package/templates/common/Harness/scripts/a2a-terminal.mjs +191 -0
- package/templates/common/Harness/scripts/context-budget.mjs +1 -1
- package/templates/common/Harness/scripts/sync-host-global.mjs +278 -0
- package/templates/common/Harness/scripts/task-state.mjs +556 -23
- package/templates/common/Harness/scripts/validate-harness.mjs +229 -19
- package/templates/common/Harness/scripts/wf-remove.mjs +8 -3
- package/templates/common/Harness/scripts/wf-update-check.mjs +37 -9
- package/templates/common/Harness/scripts/wf-update-runner.mjs +325 -0
- package/templates/common/Harness/settings.json +35 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +7 -2
- package/templates/common/Harness/specs/runtime/command-surface.json +14 -0
- package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +8 -8
- package/templates/common/Harness/specs/workflows/WF-AUTO.md +12 -12
- package/templates/common/Harness/tasks/_template/NAMING.md +16 -16
- package/templates/common/Harness/tasks/_template/PLAN.md +17 -60
- package/templates/common/Harness/tasks/_template/PROBLEM.md +18 -0
- package/templates/common/Harness/tasks/_template/PROGRESS.md +9 -14
- package/templates/common/Harness/tasks/_template/REFERENCES.md +26 -0
- package/templates/common/Harness/tasks/_template/ARTIFACTS.md +0 -3
- package/templates/common/Harness/tasks/_template/NOTES.md +0 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { apiFetch, apiJson, wsUrl } from '../api';
|
|
3
|
+
|
|
4
|
+
type ConnState = 'connecting' | 'connected' | 'reconnecting' | 'degraded' | 'disconnected';
|
|
5
|
+
|
|
6
|
+
async function reportDebug(state: Record<string, unknown>) {
|
|
7
|
+
try {
|
|
8
|
+
await apiFetch('/api/debug/report', {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
body: JSON.stringify(state),
|
|
11
|
+
});
|
|
12
|
+
} catch { /* debug only, ignore failures */ }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useServerConnection() {
|
|
16
|
+
const [connState, setConnState] = useState<ConnState>('disconnected');
|
|
17
|
+
const [eventSeq, setEventSeq] = useState(0);
|
|
18
|
+
const [lastSync, setLastSync] = useState<string | null>(null);
|
|
19
|
+
const [lastError, setLastError] = useState<string | null>(null);
|
|
20
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
21
|
+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
22
|
+
const mountedRef = useRef(true);
|
|
23
|
+
|
|
24
|
+
const connect = useCallback(() => {
|
|
25
|
+
if (!mountedRef.current) return;
|
|
26
|
+
|
|
27
|
+
// Clean up previous connection
|
|
28
|
+
if (wsRef.current) {
|
|
29
|
+
wsRef.current.onclose = null;
|
|
30
|
+
wsRef.current.onerror = null;
|
|
31
|
+
wsRef.current.close();
|
|
32
|
+
wsRef.current = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setConnState('connecting');
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const ws = new WebSocket(wsUrl('/ws/events'));
|
|
39
|
+
wsRef.current = ws;
|
|
40
|
+
|
|
41
|
+
ws.onopen = () => {
|
|
42
|
+
if (!mountedRef.current) return;
|
|
43
|
+
setConnState('connected');
|
|
44
|
+
setLastSync(new Date().toLocaleTimeString());
|
|
45
|
+
setLastError(null);
|
|
46
|
+
reportDebug({ connected: true, events: ['ws:open'] });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
ws.onmessage = (event) => {
|
|
50
|
+
if (!mountedRef.current) return;
|
|
51
|
+
try {
|
|
52
|
+
const data = JSON.parse(event.data);
|
|
53
|
+
if (data.type === 'task.updated' && data.seq !== undefined) {
|
|
54
|
+
setEventSeq(data.seq);
|
|
55
|
+
setLastSync(new Date().toLocaleTimeString());
|
|
56
|
+
} else if (data.type === 'server.connected') {
|
|
57
|
+
setEventSeq(data.seq || 0);
|
|
58
|
+
}
|
|
59
|
+
} catch { /* ignore malformed messages */ }
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
ws.onclose = (e) => {
|
|
63
|
+
wsRef.current = null;
|
|
64
|
+
if (!mountedRef.current) return;
|
|
65
|
+
setConnState('reconnecting');
|
|
66
|
+
reportDebug({ connected: false, events: [`ws:close(${e.code})`] });
|
|
67
|
+
// Auto-reconnect after 2s
|
|
68
|
+
reconnectTimerRef.current = setTimeout(connect, 2000);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
ws.onerror = () => {
|
|
72
|
+
setLastError('WS error');
|
|
73
|
+
setConnState('degraded');
|
|
74
|
+
};
|
|
75
|
+
} catch (err: any) {
|
|
76
|
+
if (!mountedRef.current) return;
|
|
77
|
+
setConnState('disconnected');
|
|
78
|
+
setLastError(err?.message || 'connection failed');
|
|
79
|
+
reportDebug({ errors: [err?.message || 'ws connect failed'] });
|
|
80
|
+
// Retry after 3s
|
|
81
|
+
reconnectTimerRef.current = setTimeout(connect, 3000);
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
mountedRef.current = true;
|
|
87
|
+
connect();
|
|
88
|
+
return () => {
|
|
89
|
+
mountedRef.current = false;
|
|
90
|
+
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
|
91
|
+
if (wsRef.current) {
|
|
92
|
+
wsRef.current.onclose = null; // prevent reconnect on intentional close
|
|
93
|
+
wsRef.current.close();
|
|
94
|
+
wsRef.current = null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}, [connect]);
|
|
98
|
+
|
|
99
|
+
return { connState, eventSeq, lastSync, lastError };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function fetchTasks() {
|
|
103
|
+
return apiJson('/api/tasks');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function fetchTaskDetail(taskId: string) {
|
|
107
|
+
const res = await apiFetch(`/api/tasks/${encodeURIComponent(taskId)}`);
|
|
108
|
+
if (!res.ok) return null;
|
|
109
|
+
return res.json();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function fetchSettings() {
|
|
113
|
+
return apiJson('/api/settings');
|
|
114
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #ffffff;
|
|
3
|
+
--fg: #111111;
|
|
4
|
+
--muted: #6b7280;
|
|
5
|
+
--border: #e5e7eb;
|
|
6
|
+
--accent: #111111;
|
|
7
|
+
--surface: #f9fafb;
|
|
8
|
+
--danger: #dc2626;
|
|
9
|
+
--success: #16a34a;
|
|
10
|
+
--warn: #d97706;
|
|
11
|
+
--radius: 6px;
|
|
12
|
+
--header-h: 48px;
|
|
13
|
+
--footer-h: 28px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
*, *::before, *::after {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
html, body, #root {
|
|
23
|
+
height: 100%;
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
25
|
+
font-size: 13px;
|
|
26
|
+
line-height: 1.5;
|
|
27
|
+
color: var(--fg);
|
|
28
|
+
background: var(--bg);
|
|
29
|
+
-webkit-font-smoothing: antialiased;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
a { color: inherit; text-decoration: none; }
|
|
33
|
+
button { font: inherit; cursor: pointer; border: none; background: none; }
|
|
34
|
+
|
|
35
|
+
.route-progress {
|
|
36
|
+
position: fixed;
|
|
37
|
+
top: var(--header-h);
|
|
38
|
+
left: 0;
|
|
39
|
+
right: 0;
|
|
40
|
+
height: 2px;
|
|
41
|
+
z-index: 80;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
background: transparent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.route-progress::before {
|
|
47
|
+
content: '';
|
|
48
|
+
display: block;
|
|
49
|
+
width: 42%;
|
|
50
|
+
height: 100%;
|
|
51
|
+
border-radius: 99px;
|
|
52
|
+
background: #111;
|
|
53
|
+
animation: route-progress-slide 0.7s ease-in-out infinite;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.loading-view {
|
|
57
|
+
min-height: 240px;
|
|
58
|
+
display: grid;
|
|
59
|
+
place-items: start;
|
|
60
|
+
padding: 20px;
|
|
61
|
+
color: var(--muted);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.loading-view-full {
|
|
65
|
+
min-height: 100%;
|
|
66
|
+
place-items: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.loading-card {
|
|
70
|
+
min-width: 220px;
|
|
71
|
+
display: grid;
|
|
72
|
+
grid-template-columns: auto 1fr;
|
|
73
|
+
gap: 10px;
|
|
74
|
+
align-items: start;
|
|
75
|
+
padding: 12px;
|
|
76
|
+
border: 1px solid var(--border);
|
|
77
|
+
border-radius: var(--radius);
|
|
78
|
+
background: rgba(255,255,255,0.88);
|
|
79
|
+
box-shadow: 0 12px 32px rgba(0,0,0,0.06);
|
|
80
|
+
backdrop-filter: blur(16px);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.loading-spinner {
|
|
84
|
+
width: 14px;
|
|
85
|
+
height: 14px;
|
|
86
|
+
margin-top: 2px;
|
|
87
|
+
border: 2px solid #d1d5db;
|
|
88
|
+
border-top-color: #111;
|
|
89
|
+
border-radius: 50%;
|
|
90
|
+
animation: loading-spin 0.8s linear infinite;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.loading-label {
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
color: var(--fg);
|
|
97
|
+
margin-bottom: 8px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.loading-lines {
|
|
101
|
+
display: grid;
|
|
102
|
+
gap: 5px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.loading-lines span {
|
|
106
|
+
display: block;
|
|
107
|
+
height: 7px;
|
|
108
|
+
border-radius: 99px;
|
|
109
|
+
background: linear-gradient(90deg, #eef0f3, #f8f9fb, #eef0f3);
|
|
110
|
+
background-size: 220% 100%;
|
|
111
|
+
animation: loading-shimmer 1.1s ease-in-out infinite;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.loading-lines span:nth-child(1) { width: 150px; }
|
|
115
|
+
.loading-lines span:nth-child(2) { width: 116px; }
|
|
116
|
+
.loading-lines span:nth-child(3) { width: 176px; }
|
|
117
|
+
|
|
118
|
+
.wf-canvas-shell,
|
|
119
|
+
.wf-canvas-shell *:not(input):not(textarea):not(select) {
|
|
120
|
+
user-select: none;
|
|
121
|
+
-webkit-user-select: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.wf-canvas-shell input,
|
|
125
|
+
.wf-canvas-shell textarea,
|
|
126
|
+
.wf-canvas-shell select {
|
|
127
|
+
user-select: text;
|
|
128
|
+
-webkit-user-select: text;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.wf-flow {
|
|
132
|
+
background: linear-gradient(180deg, #fbfbfd 0%, #f6f7f9 100%);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.wf-flow .react-flow__panel {
|
|
136
|
+
margin: 16px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.wf-flow .react-flow__controls {
|
|
140
|
+
border: 1px solid var(--border);
|
|
141
|
+
border-radius: var(--radius);
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
box-shadow: 0 12px 28px rgba(0,0,0,0.08);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.wf-flow .react-flow__controls-button {
|
|
147
|
+
width: 28px;
|
|
148
|
+
height: 28px;
|
|
149
|
+
border-bottom-color: var(--border);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.wf-flow .react-flow__edge-path {
|
|
153
|
+
stroke-linecap: round;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.wf-flow .react-flow__node {
|
|
157
|
+
font-family: inherit;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.wf-flow-handle {
|
|
161
|
+
width: 8px;
|
|
162
|
+
height: 8px;
|
|
163
|
+
border: 1px solid #111;
|
|
164
|
+
background: #fff;
|
|
165
|
+
opacity: 0.45;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.wf-flow-handle:hover {
|
|
169
|
+
opacity: 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.markdown-body {
|
|
173
|
+
color: #24292f;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
line-height: 1.55;
|
|
176
|
+
word-break: break-word;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.markdown-body h1,
|
|
180
|
+
.markdown-body h2,
|
|
181
|
+
.markdown-body h3,
|
|
182
|
+
.markdown-body h4 {
|
|
183
|
+
margin: 12px 0 6px;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
line-height: 1.25;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.markdown-body h1 {
|
|
189
|
+
padding-bottom: 6px;
|
|
190
|
+
border-bottom: 1px solid #d8dee4;
|
|
191
|
+
font-size: 20px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.markdown-body h2 {
|
|
195
|
+
padding-bottom: 5px;
|
|
196
|
+
border-bottom: 1px solid #d8dee4;
|
|
197
|
+
font-size: 16px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.markdown-body h3 { font-size: 14px; }
|
|
201
|
+
.markdown-body h4 { font-size: 13px; }
|
|
202
|
+
.markdown-body p,
|
|
203
|
+
.markdown-body ul,
|
|
204
|
+
.markdown-body ol,
|
|
205
|
+
.markdown-body table,
|
|
206
|
+
.markdown-body pre {
|
|
207
|
+
margin: 0 0 10px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.markdown-body ul,
|
|
211
|
+
.markdown-body ol {
|
|
212
|
+
padding-left: 20px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.markdown-body code {
|
|
216
|
+
padding: 1px 4px;
|
|
217
|
+
border-radius: 4px;
|
|
218
|
+
background: #eff1f3;
|
|
219
|
+
font-family: "Cascadia Mono", Consolas, monospace;
|
|
220
|
+
font-size: 85%;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.markdown-body pre {
|
|
224
|
+
padding: 10px;
|
|
225
|
+
overflow: auto;
|
|
226
|
+
border-radius: var(--radius);
|
|
227
|
+
background: #f6f8fa;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.markdown-body pre code {
|
|
231
|
+
padding: 0;
|
|
232
|
+
background: transparent;
|
|
233
|
+
font-size: 11px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.markdown-body table {
|
|
237
|
+
width: 100%;
|
|
238
|
+
border-collapse: collapse;
|
|
239
|
+
font-size: 11px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.markdown-body th,
|
|
243
|
+
.markdown-body td {
|
|
244
|
+
padding: 5px 7px;
|
|
245
|
+
border: 1px solid #d0d7de;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.markdown-body th {
|
|
249
|
+
background: #f6f8fa;
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.markdown-body blockquote {
|
|
254
|
+
margin: 0 0 10px;
|
|
255
|
+
padding: 0 10px;
|
|
256
|
+
color: #57606a;
|
|
257
|
+
border-left: 3px solid #d0d7de;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@keyframes loading-spin {
|
|
261
|
+
to { transform: rotate(360deg); }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@keyframes loading-shimmer {
|
|
265
|
+
0% { background-position: 120% 0; }
|
|
266
|
+
100% { background-position: -120% 0; }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@keyframes route-progress-slide {
|
|
270
|
+
0% { transform: translateX(-110%); }
|
|
271
|
+
55% { transform: translateX(95%); }
|
|
272
|
+
100% { transform: translateX(240%); }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@media (prefers-reduced-motion: reduce) {
|
|
276
|
+
.route-progress::before,
|
|
277
|
+
.loading-spinner,
|
|
278
|
+
.loading-lines span {
|
|
279
|
+
animation: none;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
4
|
+
import App from './App';
|
|
5
|
+
import './index.css';
|
|
6
|
+
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<BrowserRouter>
|
|
9
|
+
<App />
|
|
10
|
+
</BrowserRouter>,
|
|
11
|
+
);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type Session = {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
taskId?: string | null;
|
|
4
|
+
peerId?: string;
|
|
5
|
+
runtime: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
objective?: string;
|
|
8
|
+
status: string;
|
|
9
|
+
attachMode?: boolean;
|
|
10
|
+
wsClientCount?: number;
|
|
11
|
+
startedAt?: string;
|
|
12
|
+
updatedAt?: string;
|
|
13
|
+
blockedReason?: string | null;
|
|
14
|
+
blockedHint?: string | null;
|
|
15
|
+
ptyProvider?: string | null;
|
|
16
|
+
agentSessionId?: string | null;
|
|
17
|
+
resumeSupported?: boolean;
|
|
18
|
+
resumeArgs?: string[];
|
|
19
|
+
resumeCommand?: string | null;
|
|
20
|
+
subagentMode?: string;
|
|
21
|
+
workflowMode?: string | null;
|
|
22
|
+
model?: string;
|
|
23
|
+
provider?: string;
|
|
24
|
+
ceoPrompt?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type RuntimeConfigFile = {
|
|
28
|
+
scope: string;
|
|
29
|
+
path: string;
|
|
30
|
+
absolutePath: string;
|
|
31
|
+
format: 'json' | 'toml' | string;
|
|
32
|
+
fields: Record<string, string>;
|
|
33
|
+
values: Record<string, string>;
|
|
34
|
+
exists: boolean;
|
|
35
|
+
writable: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type RuntimeInfo = {
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
command: string;
|
|
42
|
+
path: string | null;
|
|
43
|
+
version: string | null;
|
|
44
|
+
status: string;
|
|
45
|
+
launchable: boolean;
|
|
46
|
+
adapterStatus: string;
|
|
47
|
+
blockedReason?: string | null;
|
|
48
|
+
capabilities: string[];
|
|
49
|
+
configFiles?: RuntimeConfigFile[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type TaskOption = {
|
|
53
|
+
taskId: string;
|
|
54
|
+
status?: string;
|
|
55
|
+
phase?: string | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type WorkflowNode = {
|
|
59
|
+
id: string;
|
|
60
|
+
label: string;
|
|
61
|
+
kind: string;
|
|
62
|
+
level: number;
|
|
63
|
+
status?: string;
|
|
64
|
+
skills?: string[];
|
|
65
|
+
permissions?: string[];
|
|
66
|
+
sessionId?: string;
|
|
67
|
+
taskId?: string | null;
|
|
68
|
+
runtime?: string;
|
|
69
|
+
peerId?: string;
|
|
70
|
+
model?: string;
|
|
71
|
+
provider?: string;
|
|
72
|
+
subagentMode?: string;
|
|
73
|
+
workflowMode?: string | null;
|
|
74
|
+
objective?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type WorkflowEdge = { from: string; to: string; relation: string };
|
|
78
|
+
|
|
79
|
+
export type BuiltInWorkflow = {
|
|
80
|
+
id: string;
|
|
81
|
+
command: string;
|
|
82
|
+
label: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
defaultCeoPrompt?: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type WorkflowSnapshot = {
|
|
88
|
+
workflowId: string;
|
|
89
|
+
taskId: string | null;
|
|
90
|
+
mode: string | null;
|
|
91
|
+
phase: string | null;
|
|
92
|
+
gate: string | null;
|
|
93
|
+
rootAgentId: string;
|
|
94
|
+
availableWorkflows?: BuiltInWorkflow[];
|
|
95
|
+
subagentModes?: { id: string; label: string }[];
|
|
96
|
+
roles?: {
|
|
97
|
+
nodes: WorkflowNode[];
|
|
98
|
+
edges: WorkflowEdge[];
|
|
99
|
+
};
|
|
100
|
+
queues: {
|
|
101
|
+
acceptance?: { id?: string; text?: string; status?: string }[];
|
|
102
|
+
dependsOn?: string[];
|
|
103
|
+
blocks?: string[];
|
|
104
|
+
} | null;
|
|
105
|
+
nodes: WorkflowNode[];
|
|
106
|
+
edges: WorkflowEdge[];
|
|
107
|
+
sessions?: Session[];
|
|
108
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["./src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'@': path.resolve(__dirname, './src'),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
server: {
|
|
13
|
+
port: 5173,
|
|
14
|
+
},
|
|
15
|
+
build: {
|
|
16
|
+
outDir: 'dist',
|
|
17
|
+
sourcemap: true,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { buildWorkflowSnapshot, ensureA2aDefaults, loadA2aSkills, loadBuiltInWorkflows } from '../a2a-store.mjs';
|
|
7
|
+
import { SessionRegistry } from '../session-registry.mjs';
|
|
8
|
+
import { persistSession } from '../terminal-store.mjs';
|
|
9
|
+
|
|
10
|
+
function makeProject() {
|
|
11
|
+
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'a2a-store-'));
|
|
12
|
+
const taskRoot = path.join(projectRoot, 'Harness', 'tasks', 'task-alpha');
|
|
13
|
+
fs.mkdirSync(taskRoot, { recursive: true });
|
|
14
|
+
fs.writeFileSync(path.join(projectRoot, 'Harness', 'PROGRESS.md'), '## Active Task\n\n- task-alpha\n');
|
|
15
|
+
fs.writeFileSync(path.join(taskRoot, 'STATE.json'), JSON.stringify({
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
taskId: 'task-alpha',
|
|
18
|
+
status: 'active',
|
|
19
|
+
mode: 'wf',
|
|
20
|
+
phase: 'implementation',
|
|
21
|
+
gate: 'AC-GATE',
|
|
22
|
+
links: { dependsOn: [], blocks: [] },
|
|
23
|
+
acceptance: [{ id: 'AC-015', text: 'workflow', status: 'pending' }]
|
|
24
|
+
}));
|
|
25
|
+
return projectRoot;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('ensureA2aDefaults writes role graph and terminal skill manifest', () => {
|
|
29
|
+
const projectRoot = makeProject();
|
|
30
|
+
try {
|
|
31
|
+
ensureA2aDefaults(projectRoot);
|
|
32
|
+
assert.ok(fs.existsSync(path.join(projectRoot, 'Harness', 'a2a', 'role-graph.json')));
|
|
33
|
+
assert.ok(fs.existsSync(path.join(projectRoot, 'Harness', 'a2a', 'workflows.json')));
|
|
34
|
+
assert.ok(fs.existsSync(path.join(projectRoot, 'Harness', 'a2a', 'skills', 'terminal-control.json')));
|
|
35
|
+
assert.equal(loadA2aSkills(projectRoot)[0].skillId, 'terminal-control');
|
|
36
|
+
assert.ok(loadBuiltInWorkflows(projectRoot).some(workflow => workflow.command === '/wf'));
|
|
37
|
+
} finally {
|
|
38
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('buildWorkflowSnapshot returns CEO hierarchy and session-linked node', () => {
|
|
43
|
+
const projectRoot = makeProject();
|
|
44
|
+
try {
|
|
45
|
+
const registry = new SessionRegistry();
|
|
46
|
+
const session = registry.create({ taskId: 'task-alpha', runtime: 'codex' });
|
|
47
|
+
registry.update(session.sessionId, { status: 'blocked', blockedReason: 'pty-adapter-missing' });
|
|
48
|
+
const workflow = buildWorkflowSnapshot(projectRoot, registry);
|
|
49
|
+
assert.equal(workflow.rootAgentId, 'ceo');
|
|
50
|
+
assert.ok(workflow.nodes.some(node => node.id === 'ceo' && node.label === 'CEO Leader'));
|
|
51
|
+
assert.ok(workflow.roles.nodes.some(node => node.id === 'ceo'));
|
|
52
|
+
assert.ok(workflow.availableWorkflows.some(item => item.command === '/wf'));
|
|
53
|
+
assert.ok(workflow.subagentModes.some(item => item.id === 'wf-subagents'));
|
|
54
|
+
assert.ok(workflow.nodes.some(node => node.id === `session-${session.sessionId}`));
|
|
55
|
+
assert.ok(workflow.edges.some(edge => edge.from === 'terminal-controller' && edge.to === `session-${session.sessionId}`));
|
|
56
|
+
} finally {
|
|
57
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('buildWorkflowSnapshot includes unbound Harness/a2a terminal sessions', () => {
|
|
62
|
+
const projectRoot = makeProject();
|
|
63
|
+
try {
|
|
64
|
+
persistSession(projectRoot, {
|
|
65
|
+
taskId: null,
|
|
66
|
+
sessionId: 'session-unbound',
|
|
67
|
+
runtime: 'opencode',
|
|
68
|
+
role: 'wf-subagent',
|
|
69
|
+
status: 'running',
|
|
70
|
+
updatedAt: '2026-07-30T00:00:00.000Z',
|
|
71
|
+
});
|
|
72
|
+
const workflow = buildWorkflowSnapshot(projectRoot, new SessionRegistry());
|
|
73
|
+
const node = workflow.nodes.find(item => item.id === 'session-session-unbound');
|
|
74
|
+
assert.equal(node.runtime, 'opencode');
|
|
75
|
+
assert.equal(node.taskId, null);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|