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.
Files changed (128) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -0
  3. package/package.json +5 -2
  4. package/src/index.js +383 -267
  5. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +17 -0
  6. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +1 -0
  7. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +7 -0
  8. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +1 -0
  9. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +12 -0
  10. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +1 -0
  11. package/src/ui/dist/assets/TaskList-BN4r8s1i.js +50 -0
  12. package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +1 -0
  13. package/src/ui/dist/assets/TerminalDrawer-6GBZ9nXN.css +32 -0
  14. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +74 -0
  15. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +1 -0
  16. package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +1 -0
  17. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +39 -0
  18. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +1 -0
  19. package/src/ui/dist/assets/index-BOVYWntB.js +117 -0
  20. package/src/ui/dist/assets/index-BOVYWntB.js.map +1 -0
  21. package/src/ui/dist/assets/index-DaUObq0H.css +1 -0
  22. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +7 -0
  23. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +1 -0
  24. package/src/ui/dist/assets/plus-QuTJyoT3.js +7 -0
  25. package/src/ui/dist/assets/plus-QuTJyoT3.js.map +1 -0
  26. package/src/ui/dist/assets/proxy-D0vUbds5.js +2 -0
  27. package/src/ui/dist/assets/proxy-D0vUbds5.js.map +1 -0
  28. package/src/ui/dist/assets/refresh-cw-JNmExijd.js +7 -0
  29. package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +1 -0
  30. package/src/ui/dist/assets/terminal-BBQvnOAK.js +7 -0
  31. package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +1 -0
  32. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +7 -0
  33. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +1 -0
  34. package/src/ui/dist/assets/x-DoIOGAJz.js +7 -0
  35. package/src/ui/dist/assets/x-DoIOGAJz.js.map +1 -0
  36. package/src/ui/dist/index.html +18 -0
  37. package/src/ui/index.html +17 -0
  38. package/src/ui/package.json +33 -0
  39. package/src/ui/pnpm-lock.yaml +1582 -0
  40. package/src/ui/pnpm-workspace.yaml +2 -0
  41. package/src/ui/src/App.tsx +86 -0
  42. package/src/ui/src/api.ts +93 -0
  43. package/src/ui/src/components/AgentsRoute.tsx +503 -0
  44. package/src/ui/src/components/Footer.tsx +69 -0
  45. package/src/ui/src/components/Header.tsx +175 -0
  46. package/src/ui/src/components/LoadingView.tsx +22 -0
  47. package/src/ui/src/components/RolesRoute.tsx +169 -0
  48. package/src/ui/src/components/SettingsRoute.tsx +166 -0
  49. package/src/ui/src/components/TaskList.tsx +388 -0
  50. package/src/ui/src/components/TerminalDrawer.tsx +611 -0
  51. package/src/ui/src/components/WorkflowRoute.tsx +670 -0
  52. package/src/ui/src/hooks/useReducedMotion.ts +17 -0
  53. package/src/ui/src/hooks/useServerConnection.ts +114 -0
  54. package/src/ui/src/index.css +281 -0
  55. package/src/ui/src/main.tsx +11 -0
  56. package/src/ui/src/types.ts +108 -0
  57. package/src/ui/tsconfig.json +21 -0
  58. package/src/ui/vite.config.ts +19 -0
  59. package/src/wf-ui-server/__tests__/a2a-store.test.mjs +79 -0
  60. package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +268 -0
  61. package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +70 -0
  62. package/src/wf-ui-server/__tests__/runtime-config.test.mjs +43 -0
  63. package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +90 -0
  64. package/src/wf-ui-server/__tests__/security.test.mjs +59 -0
  65. package/src/wf-ui-server/__tests__/server.integration.test.mjs +150 -0
  66. package/src/wf-ui-server/__tests__/session-registry.test.mjs +238 -0
  67. package/src/wf-ui-server/__tests__/settings.test.mjs +135 -0
  68. package/src/wf-ui-server/__tests__/task-parser.test.mjs +200 -0
  69. package/src/wf-ui-server/__tests__/terminal-store.test.mjs +138 -0
  70. package/src/wf-ui-server/__tests__/token.test.mjs +48 -0
  71. package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +419 -0
  72. package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +383 -0
  73. package/src/wf-ui-server/a2a-store.mjs +296 -0
  74. package/src/wf-ui-server/peer-capsule.mjs +213 -0
  75. package/src/wf-ui-server/pty-adapter.mjs +155 -0
  76. package/src/wf-ui-server/runtime-config.mjs +153 -0
  77. package/src/wf-ui-server/runtime-detector.mjs +374 -0
  78. package/src/wf-ui-server/security.mjs +67 -0
  79. package/src/wf-ui-server/server.mjs +849 -0
  80. package/src/wf-ui-server/session-registry.mjs +204 -0
  81. package/src/wf-ui-server/settings.mjs +100 -0
  82. package/src/wf-ui-server/task-parser.mjs +133 -0
  83. package/src/wf-ui-server/terminal-store.mjs +225 -0
  84. package/src/wf-ui-server/token.mjs +41 -0
  85. package/src/wf-ui-server/ws-events.mjs +354 -0
  86. package/src/wf-ui-server/ws-terminal.mjs +473 -0
  87. package/templates/common/.claude/commands/wf-help.md +1 -0
  88. package/templates/common/.claude/commands/wf-task-list.md +2 -0
  89. package/templates/common/.claude/commands/wf-ui.md +26 -0
  90. package/templates/common/.claude/commands/wf-update.md +54 -9
  91. package/templates/common/.claude/rules/ecc/common.md +1 -1
  92. package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -3
  93. package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +2 -2
  94. package/templates/common/.claude/skills/wf-ui/SKILL.md +78 -0
  95. package/templates/common/.claude/skills/wf-update/SKILL.md +55 -58
  96. package/templates/common/.harness-version +63 -39
  97. package/templates/common/.opencode/commands/wf-help.md +1 -0
  98. package/templates/common/.opencode/commands/wf-task-list.md +2 -0
  99. package/templates/common/.opencode/commands/wf-ui.md +26 -0
  100. package/templates/common/.opencode/commands/wf-update.md +54 -9
  101. package/templates/common/CLAUDE.md +1 -1
  102. package/templates/common/Harness/MEMORY.md +2 -0
  103. package/templates/common/Harness/README.md +6 -4
  104. package/templates/common/Harness/a2a/role-graph.json +79 -0
  105. package/templates/common/Harness/a2a/runtime-registry.json +5 -0
  106. package/templates/common/Harness/a2a/skills/terminal-control.json +15 -0
  107. package/templates/common/Harness/ownership.manifest.json +57 -2
  108. package/templates/common/Harness/scripts/README.md +134 -0
  109. package/templates/common/Harness/scripts/a2a-terminal.mjs +191 -0
  110. package/templates/common/Harness/scripts/context-budget.mjs +1 -1
  111. package/templates/common/Harness/scripts/sync-host-global.mjs +278 -0
  112. package/templates/common/Harness/scripts/task-state.mjs +556 -23
  113. package/templates/common/Harness/scripts/validate-harness.mjs +229 -19
  114. package/templates/common/Harness/scripts/wf-remove.mjs +8 -3
  115. package/templates/common/Harness/scripts/wf-update-check.mjs +37 -9
  116. package/templates/common/Harness/scripts/wf-update-runner.mjs +325 -0
  117. package/templates/common/Harness/settings.json +35 -0
  118. package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +7 -2
  119. package/templates/common/Harness/specs/runtime/command-surface.json +14 -0
  120. package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +8 -8
  121. package/templates/common/Harness/specs/workflows/WF-AUTO.md +12 -12
  122. package/templates/common/Harness/tasks/_template/NAMING.md +16 -16
  123. package/templates/common/Harness/tasks/_template/PLAN.md +17 -60
  124. package/templates/common/Harness/tasks/_template/PROBLEM.md +18 -0
  125. package/templates/common/Harness/tasks/_template/PROGRESS.md +9 -14
  126. package/templates/common/Harness/tasks/_template/REFERENCES.md +26 -0
  127. package/templates/common/Harness/tasks/_template/ARTIFACTS.md +0 -3
  128. 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
+ });