haki-skills 0.2.2 → 0.2.4
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/.agent/bin/haki-tools.cjs +91 -41
- package/.agent/bin/haki-ui-static/index.html +306 -0
- package/.agent/bin/haki-ui.cjs +30 -0
- package/.agent/bin/lib/core.cjs +29 -0
- package/.agent/bin/lib/haki-ui/event-emitter.cjs +58 -0
- package/.agent/bin/lib/haki-ui/event-schema.cjs +191 -0
- package/.agent/bin/lib/haki-ui/event-store-jsonl.cjs +213 -0
- package/.agent/bin/lib/haki-ui/projections.cjs +113 -0
- package/.agent/bin/lib/haki-ui/replay-engine.cjs +21 -0
- package/.agent/bin/lib/haki-ui/sse-server.cjs +118 -0
- package/.agent/bin/lib/roadmap.cjs +228 -83
- package/.agent/bin/lib/state.cjs +86 -23
- package/.agent/skills/brainstorming/SKILL.md +3 -2
- package/.agent/skills/brainstorming/scripts/frame-template.html +2 -2
- package/.agent/skills/brainstorming/scripts/start-server.sh +2 -2
- package/.agent/skills/brainstorming/scripts/stop-server.sh +1 -1
- package/.agent/skills/brainstorming/spec-document-reviewer-prompt.md +1 -1
- package/.agent/skills/brainstorming/visual-companion.md +26 -9
- package/.agent/skills/executing-plans/SKILL.md +12 -5
- package/.agent/skills/subagent-driven-development/SKILL.md +25 -10
- package/.agent/skills/subagent-driven-development/code-quality-reviewer-prompt.md +2 -1
- package/.agent/skills/swarm-dev-team/SKILL.md +810 -0
- package/.agent/skills/systematic-debugging/SKILL.md +28 -19
- package/.agent/skills/user-docs-generator/SKILL.md +1 -1
- package/.agent/skills/writing-plans/SKILL.md +12 -5
- package/.agent/workflows/haki-docs.md +1 -1
- package/README.md +15 -3
- package/bin/install.js +9 -1
- package/package.json +4 -3
|
@@ -30,6 +30,50 @@
|
|
|
30
30
|
const fs = require("fs");
|
|
31
31
|
const path = require("path");
|
|
32
32
|
const { findProjectRoot, error, output } = require("./lib/core.cjs");
|
|
33
|
+
const {
|
|
34
|
+
emitEvent,
|
|
35
|
+
getOrCreateRunContext,
|
|
36
|
+
} = require("./lib/haki-ui/event-emitter.cjs");
|
|
37
|
+
|
|
38
|
+
function withRunEvents(cwd, commandName, payload, handler) {
|
|
39
|
+
const context = getOrCreateRunContext(cwd);
|
|
40
|
+
emitEvent(cwd, {
|
|
41
|
+
type: "run.started",
|
|
42
|
+
entityType: "run",
|
|
43
|
+
entityId: context.runId,
|
|
44
|
+
payload: {
|
|
45
|
+
command: commandName,
|
|
46
|
+
args: payload,
|
|
47
|
+
entryWorkflow: commandName,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = handler();
|
|
53
|
+
emitEvent(cwd, {
|
|
54
|
+
type: "run.completed",
|
|
55
|
+
entityType: "run",
|
|
56
|
+
entityId: context.runId,
|
|
57
|
+
payload: {
|
|
58
|
+
command: commandName,
|
|
59
|
+
summary: "completed",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return result;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
emitEvent(cwd, {
|
|
65
|
+
type: "run.failed",
|
|
66
|
+
entityType: "run",
|
|
67
|
+
entityId: context.runId,
|
|
68
|
+
phase: "failed",
|
|
69
|
+
payload: {
|
|
70
|
+
command: commandName,
|
|
71
|
+
errorMessage: err?.message || String(err),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
33
77
|
const {
|
|
34
78
|
cmdConfigInit,
|
|
35
79
|
cmdConfigGet,
|
|
@@ -66,57 +110,63 @@ function main() {
|
|
|
66
110
|
case "config-set": {
|
|
67
111
|
const sub = command === "config" ? subCommand : command.split("-")[1];
|
|
68
112
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
withRunEvents(cwd, `config.${sub || "unknown"}`, restArgs, () => {
|
|
114
|
+
switch (sub) {
|
|
115
|
+
case "init":
|
|
116
|
+
cmdConfigInit(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
117
|
+
break;
|
|
118
|
+
case "get":
|
|
119
|
+
cmdConfigGet(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
120
|
+
break;
|
|
121
|
+
case "set":
|
|
122
|
+
cmdConfigSet(
|
|
123
|
+
cwd,
|
|
124
|
+
restArgs[0] || filteredArgs[2],
|
|
125
|
+
restArgs[1] || filteredArgs[3],
|
|
126
|
+
raw,
|
|
127
|
+
);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
error("Usage: config <init|get|set> [args]");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
87
133
|
break;
|
|
88
134
|
}
|
|
89
135
|
|
|
90
136
|
// ─── Roadmap ──────────────────────────────────────────────────────
|
|
91
137
|
case "roadmap": {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
withRunEvents(cwd, `roadmap.${subCommand || "unknown"}`, restArgs, () => {
|
|
139
|
+
switch (subCommand) {
|
|
140
|
+
case "analyze":
|
|
141
|
+
cmdRoadmapAnalyze(cwd, raw);
|
|
142
|
+
break;
|
|
143
|
+
case "next-task":
|
|
144
|
+
cmdRoadmapNextTask(cwd, raw);
|
|
145
|
+
break;
|
|
146
|
+
case "update-status":
|
|
147
|
+
cmdRoadmapUpdateStatus(cwd, restArgs[0], restArgs[1], raw);
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
error("Usage: roadmap <analyze|next-task|update-status> [args]");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
105
153
|
break;
|
|
106
154
|
}
|
|
107
155
|
|
|
108
156
|
// ─── State ────────────────────────────────────────────────────────
|
|
109
157
|
case "state": {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
withRunEvents(cwd, `state.${subCommand || "unknown"}`, restArgs, () => {
|
|
159
|
+
switch (subCommand) {
|
|
160
|
+
case "detect":
|
|
161
|
+
cmdStateDetect(cwd, raw);
|
|
162
|
+
break;
|
|
163
|
+
case "json":
|
|
164
|
+
cmdStateJson(cwd, raw);
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
error("Usage: state <detect|json>");
|
|
168
|
+
}
|
|
169
|
+
});
|
|
120
170
|
break;
|
|
121
171
|
}
|
|
122
172
|
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Haki UI</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
--bg: #07111f;
|
|
11
|
+
--panel: rgba(16, 24, 42, 0.92);
|
|
12
|
+
--panel-2: rgba(25, 37, 66, 0.94);
|
|
13
|
+
--border: rgba(120, 156, 234, 0.16);
|
|
14
|
+
--text: #eef4ff;
|
|
15
|
+
--muted: #9cadcf;
|
|
16
|
+
--accent: #67b7ff;
|
|
17
|
+
--accent-2: #8b5cf6;
|
|
18
|
+
--success: #34d399;
|
|
19
|
+
--warning: #f59e0b;
|
|
20
|
+
--error: #fb7185;
|
|
21
|
+
--glow: rgba(103, 183, 255, 0.38);
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
body {
|
|
25
|
+
margin: 0;
|
|
26
|
+
font-family: Inter, system-ui, sans-serif;
|
|
27
|
+
color: var(--text);
|
|
28
|
+
background:
|
|
29
|
+
radial-gradient(circle at 10% 10%, rgba(103,183,255,0.18), transparent 25%),
|
|
30
|
+
radial-gradient(circle at 90% 0%, rgba(139,92,246,0.18), transparent 30%),
|
|
31
|
+
linear-gradient(180deg, #08101d, #0a1323 55%, #060b14);
|
|
32
|
+
min-height: 100vh;
|
|
33
|
+
}
|
|
34
|
+
.shell { display: grid; grid-template-rows: auto auto 1fr; min-height: 100vh; }
|
|
35
|
+
.header {
|
|
36
|
+
padding: 20px 24px;
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: center;
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
background: rgba(8, 16, 29, 0.7);
|
|
42
|
+
backdrop-filter: blur(16px);
|
|
43
|
+
position: sticky;
|
|
44
|
+
top: 0;
|
|
45
|
+
z-index: 10;
|
|
46
|
+
}
|
|
47
|
+
.header h1 { margin: 0; font-size: 22px; }
|
|
48
|
+
.header p { margin: 4px 0 0; color: var(--muted); font-size: 13px; }
|
|
49
|
+
.badge {
|
|
50
|
+
padding: 8px 12px;
|
|
51
|
+
border-radius: 999px;
|
|
52
|
+
border: 1px solid rgba(103,183,255,0.24);
|
|
53
|
+
background: rgba(103,183,255,0.12);
|
|
54
|
+
color: var(--accent);
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
box-shadow: 0 0 18px rgba(103,183,255,0.1);
|
|
57
|
+
}
|
|
58
|
+
.timeline {
|
|
59
|
+
padding: 16px 24px;
|
|
60
|
+
border-bottom: 1px solid var(--border);
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: 12px;
|
|
63
|
+
background: rgba(13, 20, 36, 0.82);
|
|
64
|
+
backdrop-filter: blur(10px);
|
|
65
|
+
}
|
|
66
|
+
.timeline-meta {
|
|
67
|
+
display: flex;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
align-items: center;
|
|
70
|
+
color: var(--muted);
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
}
|
|
73
|
+
.timeline input[type="range"] { width: 100%; }
|
|
74
|
+
.content {
|
|
75
|
+
display: grid;
|
|
76
|
+
grid-template-columns: 1.6fr 1fr 320px;
|
|
77
|
+
gap: 16px;
|
|
78
|
+
padding: 16px;
|
|
79
|
+
}
|
|
80
|
+
.panel {
|
|
81
|
+
background: linear-gradient(180deg, var(--panel-2), var(--panel));
|
|
82
|
+
border: 1px solid var(--border);
|
|
83
|
+
border-radius: 20px;
|
|
84
|
+
padding: 16px;
|
|
85
|
+
min-height: 320px;
|
|
86
|
+
box-shadow: 0 18px 50px rgba(0,0,0,.28);
|
|
87
|
+
}
|
|
88
|
+
.panel h2 { margin: 0 0 12px; font-size: 16px; }
|
|
89
|
+
.graph { display: grid; gap: 12px; }
|
|
90
|
+
.node {
|
|
91
|
+
position: relative;
|
|
92
|
+
padding: 14px;
|
|
93
|
+
border-radius: 16px;
|
|
94
|
+
border: 1px solid rgba(103,183,255,.18);
|
|
95
|
+
background: linear-gradient(180deg, rgba(103,183,255,.08), rgba(103,183,255,.03));
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
.node.active {
|
|
99
|
+
border-color: rgba(103,183,255,.45);
|
|
100
|
+
box-shadow: 0 0 0 1px rgba(103,183,255,.18), 0 0 26px rgba(103,183,255,.18);
|
|
101
|
+
}
|
|
102
|
+
.node.active::after {
|
|
103
|
+
content: "";
|
|
104
|
+
position: absolute;
|
|
105
|
+
inset: -30%;
|
|
106
|
+
background: radial-gradient(circle, var(--glow), transparent 55%);
|
|
107
|
+
animation: pulse 2s ease-in-out infinite;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
}
|
|
110
|
+
.node .title { display: flex; justify-content: space-between; gap: 12px; position: relative; z-index: 1; }
|
|
111
|
+
.node strong { font-size: 14px; }
|
|
112
|
+
.node .meta { position: relative; z-index: 1; margin-top: 6px; color: var(--muted); font-size: 12px; }
|
|
113
|
+
.lane {
|
|
114
|
+
padding: 14px;
|
|
115
|
+
border-radius: 16px;
|
|
116
|
+
border: 1px solid rgba(139,92,246,.22);
|
|
117
|
+
background: linear-gradient(180deg, rgba(139,92,246,.10), rgba(139,92,246,.04));
|
|
118
|
+
margin-bottom: 12px;
|
|
119
|
+
}
|
|
120
|
+
.lane strong { display: block; margin-bottom: 8px; }
|
|
121
|
+
.lane .stats { display: flex; flex-wrap: wrap; gap: 8px; color: var(--muted); font-size: 12px; }
|
|
122
|
+
.chip {
|
|
123
|
+
padding: 4px 8px;
|
|
124
|
+
border-radius: 999px;
|
|
125
|
+
background: rgba(255,255,255,.06);
|
|
126
|
+
border: 1px solid rgba(255,255,255,.08);
|
|
127
|
+
}
|
|
128
|
+
.inspector pre {
|
|
129
|
+
margin: 0;
|
|
130
|
+
white-space: pre-wrap;
|
|
131
|
+
word-break: break-word;
|
|
132
|
+
color: var(--muted);
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
line-height: 1.5;
|
|
135
|
+
}
|
|
136
|
+
.events { display: grid; gap: 8px; margin-top: 14px; }
|
|
137
|
+
.event {
|
|
138
|
+
padding: 12px;
|
|
139
|
+
border-radius: 14px;
|
|
140
|
+
background: rgba(255,255,255,.04);
|
|
141
|
+
border: 1px solid rgba(255,255,255,.06);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
transition: transform .18s ease, border-color .18s ease, background .18s ease;
|
|
144
|
+
}
|
|
145
|
+
.event:hover { transform: translateY(-1px); border-color: rgba(103,183,255,.24); }
|
|
146
|
+
.event.active {
|
|
147
|
+
border-color: rgba(103,183,255,.45);
|
|
148
|
+
background: rgba(103,183,255,.12);
|
|
149
|
+
box-shadow: 0 0 20px rgba(103,183,255,.1);
|
|
150
|
+
}
|
|
151
|
+
.event strong { display: block; margin-bottom: 4px; }
|
|
152
|
+
.event .meta { color: var(--muted); font-size: 12px; }
|
|
153
|
+
@keyframes pulse {
|
|
154
|
+
0%, 100% { opacity: .18; transform: scale(.96); }
|
|
155
|
+
50% { opacity: .65; transform: scale(1.04); }
|
|
156
|
+
}
|
|
157
|
+
@media (max-width: 1100px) {
|
|
158
|
+
.content { grid-template-columns: 1fr; }
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
161
|
+
</head>
|
|
162
|
+
<body>
|
|
163
|
+
<div class="shell">
|
|
164
|
+
<div class="header">
|
|
165
|
+
<div>
|
|
166
|
+
<h1>Haki UI</h1>
|
|
167
|
+
<p>Realtime + replay monitor for Haki lifecycle runs.</p>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="badge" id="run-badge">Waiting for run…</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="timeline">
|
|
173
|
+
<div class="timeline-meta">
|
|
174
|
+
<span id="timeline-label">Playhead: 0 / 0</span>
|
|
175
|
+
<span id="timeline-mode">Live stream connected</span>
|
|
176
|
+
</div>
|
|
177
|
+
<input id="playhead" type="range" min="0" max="0" value="0" />
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="content">
|
|
181
|
+
<section class="panel">
|
|
182
|
+
<h2>Graph View</h2>
|
|
183
|
+
<div class="graph" id="graph"></div>
|
|
184
|
+
</section>
|
|
185
|
+
|
|
186
|
+
<section class="panel">
|
|
187
|
+
<h2>Stage View</h2>
|
|
188
|
+
<div id="stages"></div>
|
|
189
|
+
<div class="events" id="timeline-events"></div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<aside class="panel inspector">
|
|
193
|
+
<h2>Inspector</h2>
|
|
194
|
+
<pre id="inspector">Select an event to inspect details.</pre>
|
|
195
|
+
</aside>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<script>
|
|
200
|
+
const state = {
|
|
201
|
+
events: [],
|
|
202
|
+
replay: null,
|
|
203
|
+
selectedEventId: null,
|
|
204
|
+
runId: null,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const graphEl = document.getElementById('graph');
|
|
208
|
+
const stagesEl = document.getElementById('stages');
|
|
209
|
+
const timelineEventsEl = document.getElementById('timeline-events');
|
|
210
|
+
const inspectorEl = document.getElementById('inspector');
|
|
211
|
+
const playheadEl = document.getElementById('playhead');
|
|
212
|
+
const timelineLabelEl = document.getElementById('timeline-label');
|
|
213
|
+
const runBadgeEl = document.getElementById('run-badge');
|
|
214
|
+
|
|
215
|
+
function renderGraph(graph) {
|
|
216
|
+
graphEl.innerHTML = graph.nodes.map((node) => `
|
|
217
|
+
<div class="node ${node.id === graph.activeNodeId ? 'active' : ''}">
|
|
218
|
+
<div class="title">
|
|
219
|
+
<strong>${node.label}</strong>
|
|
220
|
+
<span>${node.count} events</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="meta">${node.entityType} · ${node.phase || 'unassigned'} · ${node.status || 'n/a'} · last: ${node.lastEventType}</div>
|
|
223
|
+
</div>
|
|
224
|
+
`).join('');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderStages(stage) {
|
|
228
|
+
stagesEl.innerHTML = stage.lanes.map((lane) => `
|
|
229
|
+
<div class="lane">
|
|
230
|
+
<strong>${lane.phase}</strong>
|
|
231
|
+
<div class="stats">
|
|
232
|
+
<span class="chip">${lane.total} events</span>
|
|
233
|
+
${Object.entries(lane.statuses).map(([status, count]) => `<span class="chip">${status}: ${count}</span>`).join('')}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
`).join('');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function renderTimeline(timeline) {
|
|
240
|
+
playheadEl.max = Math.max(0, timeline.events.length - 1);
|
|
241
|
+
playheadEl.value = state.replay.playhead;
|
|
242
|
+
timelineLabelEl.textContent = `Playhead: ${state.replay.playhead + 1} / ${timeline.events.length}`;
|
|
243
|
+
|
|
244
|
+
timelineEventsEl.innerHTML = timeline.events.map((event) => `
|
|
245
|
+
<div class="event ${event.id === state.selectedEventId ? 'active' : ''}" data-event-id="${event.id}">
|
|
246
|
+
<strong>${event.type}</strong>
|
|
247
|
+
<div class="meta">${event.entityType}:${event.entityId} · ${event.phase || 'unassigned'} · ${event.status || 'n/a'}</div>
|
|
248
|
+
</div>
|
|
249
|
+
`).join('');
|
|
250
|
+
|
|
251
|
+
timelineEventsEl.querySelectorAll('.event').forEach((element) => {
|
|
252
|
+
element.addEventListener('click', () => {
|
|
253
|
+
state.selectedEventId = element.dataset.eventId;
|
|
254
|
+
const event = state.events.find((item) => item.id === state.selectedEventId);
|
|
255
|
+
inspectorEl.textContent = JSON.stringify(event, null, 2);
|
|
256
|
+
render();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function render() {
|
|
262
|
+
if (!state.replay) return;
|
|
263
|
+
runBadgeEl.textContent = state.runId ? `Run ${state.runId}` : 'Waiting for run…';
|
|
264
|
+
renderGraph(state.replay.projections.graph);
|
|
265
|
+
renderStages(state.replay.projections.stage);
|
|
266
|
+
renderTimeline(state.replay.projections.timeline);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function loadReplay(playhead) {
|
|
270
|
+
const response = await fetch(`/api/replay?playhead=${playhead}`);
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
state.replay = data.replay;
|
|
273
|
+
state.runId = data.run?.runId || null;
|
|
274
|
+
render();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function loadEvents() {
|
|
278
|
+
const response = await fetch('/api/events');
|
|
279
|
+
const data = await response.json();
|
|
280
|
+
state.events = data.events || [];
|
|
281
|
+
state.runId = data.run?.runId || null;
|
|
282
|
+
const playhead = Math.max(0, state.events.length - 1);
|
|
283
|
+
await loadReplay(playhead);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
playheadEl.addEventListener('input', () => {
|
|
287
|
+
loadReplay(Number(playheadEl.value));
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const source = new EventSource('/api/stream');
|
|
291
|
+
source.addEventListener('message', async () => {
|
|
292
|
+
await loadEvents();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
source.addEventListener('error', () => {
|
|
296
|
+
document.getElementById('timeline-mode').textContent = 'Reconnecting live stream…';
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
source.addEventListener('open', () => {
|
|
300
|
+
document.getElementById('timeline-mode').textContent = 'Live stream connected';
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
loadEvents();
|
|
304
|
+
</script>
|
|
305
|
+
</body>
|
|
306
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { startServer } = require("./lib/haki-ui/sse-server.cjs");
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const portArg = process.argv.find((arg) => arg.startsWith("--port="));
|
|
7
|
+
const port = portArg ? Number(portArg.split("=")[1]) : 4312;
|
|
8
|
+
const { server, port: listeningPort } = await startServer({ port });
|
|
9
|
+
|
|
10
|
+
process.stdout.write(
|
|
11
|
+
JSON.stringify(
|
|
12
|
+
{
|
|
13
|
+
status: "listening",
|
|
14
|
+
port: listeningPort,
|
|
15
|
+
url: `http://localhost:${listeningPort}`,
|
|
16
|
+
},
|
|
17
|
+
null,
|
|
18
|
+
2,
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
process.on("SIGINT", () => {
|
|
23
|
+
server.close(() => process.exit(0));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main().catch((error) => {
|
|
28
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
package/.agent/bin/lib/core.cjs
CHANGED
|
@@ -43,6 +43,20 @@ function findProjectRoot(startDir) {
|
|
|
43
43
|
*/
|
|
44
44
|
function hakiPaths(cwd) {
|
|
45
45
|
const base = path.join(cwd, HAKI_DIR);
|
|
46
|
+
const runtime = path.join(base, "runtime");
|
|
47
|
+
const runtimeUi = path.join(runtime, "ui");
|
|
48
|
+
const runtimeUiRuns = path.join(runtimeUi, "runs");
|
|
49
|
+
const runtimeUiSnapshots = path.join(runtimeUi, "snapshots");
|
|
50
|
+
const runtimeBrainstorm = path.join(runtime, "brainstorm");
|
|
51
|
+
const runtimeBrainstormSessions = path.join(runtimeBrainstorm, "sessions");
|
|
52
|
+
const generated = path.join(base, "generated");
|
|
53
|
+
const generatedDocs = path.join(generated, "docs");
|
|
54
|
+
const generatedDocsUserGuides = path.join(generatedDocs, "user-guides");
|
|
55
|
+
|
|
56
|
+
const legacyHakiUi = path.join(base, "ui");
|
|
57
|
+
const legacyHakiUiRuns = path.join(legacyHakiUi, "runs");
|
|
58
|
+
const legacyHakiUiSnapshots = path.join(legacyHakiUi, "snapshots");
|
|
59
|
+
|
|
46
60
|
return {
|
|
47
61
|
haki: base,
|
|
48
62
|
project: path.join(base, "PROJECT.md"),
|
|
@@ -52,6 +66,21 @@ function hakiPaths(cwd) {
|
|
|
52
66
|
research: path.join(base, "research"),
|
|
53
67
|
codebase: path.join(base, "codebase"),
|
|
54
68
|
tasks: path.join(base, "tasks"),
|
|
69
|
+
runtime,
|
|
70
|
+
runtime_ui: runtimeUi,
|
|
71
|
+
runtime_ui_runs: runtimeUiRuns,
|
|
72
|
+
runtime_ui_current_run: path.join(runtimeUi, "current-run.json"),
|
|
73
|
+
runtime_ui_snapshots: runtimeUiSnapshots,
|
|
74
|
+
runtime_brainstorm: runtimeBrainstorm,
|
|
75
|
+
runtime_brainstorm_sessions: runtimeBrainstormSessions,
|
|
76
|
+
generated,
|
|
77
|
+
generated_docs: generatedDocs,
|
|
78
|
+
generated_docs_user_guides: generatedDocsUserGuides,
|
|
79
|
+
haki_ui: legacyHakiUi,
|
|
80
|
+
haki_ui_runs: legacyHakiUiRuns,
|
|
81
|
+
haki_ui_current_run: path.join(legacyHakiUi, "current-run.json"),
|
|
82
|
+
haki_ui_current_log: path.join(legacyHakiUi, "current-run.jsonl"),
|
|
83
|
+
haki_ui_snapshots: legacyHakiUiSnapshots,
|
|
55
84
|
};
|
|
56
85
|
}
|
|
57
86
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { createEvent, createRunId, createSessionId } = require("./event-schema.cjs");
|
|
2
|
+
const {
|
|
3
|
+
appendEventToRun,
|
|
4
|
+
buildRunContext,
|
|
5
|
+
readRunMeta,
|
|
6
|
+
writeRunMeta,
|
|
7
|
+
} = require("./event-store-jsonl.cjs");
|
|
8
|
+
|
|
9
|
+
function getOrCreateRunContext(cwd) {
|
|
10
|
+
const existing = readRunMeta(cwd);
|
|
11
|
+
if (existing?.runId && existing?.sessionId && existing?.logPath) {
|
|
12
|
+
return existing;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const runId = createRunId();
|
|
16
|
+
const sessionId = createSessionId();
|
|
17
|
+
return buildRunContext(cwd, runId, sessionId);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setCurrentRunContext(cwd, context) {
|
|
21
|
+
return writeRunMeta(cwd, context);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function emitEvent(cwd, input) {
|
|
25
|
+
try {
|
|
26
|
+
const context = getOrCreateRunContext(cwd);
|
|
27
|
+
const event = createEvent({
|
|
28
|
+
runId: context.runId,
|
|
29
|
+
sessionId: context.sessionId,
|
|
30
|
+
...input,
|
|
31
|
+
});
|
|
32
|
+
appendEventToRun(context, event);
|
|
33
|
+
return event;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createRunContext(cwd, createdAt = new Date().toISOString()) {
|
|
40
|
+
return buildRunContext(cwd, createRunId(), createSessionId(), createdAt);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readCurrentRunContext(cwd) {
|
|
44
|
+
return readRunMeta(cwd);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeCurrentRunContext(cwd, context) {
|
|
48
|
+
return writeRunMeta(cwd, context);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
getOrCreateRunContext,
|
|
53
|
+
setCurrentRunContext,
|
|
54
|
+
emitEvent,
|
|
55
|
+
createRunContext,
|
|
56
|
+
readCurrentRunContext,
|
|
57
|
+
writeCurrentRunContext,
|
|
58
|
+
};
|