@appsforgood/next-supabase-kit 0.1.7 → 0.2.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/BEST_PRACTICE_EVIDENCE.md +1 -1
- package/CHANGELOG.md +17 -0
- package/DOGFOOD.md +12 -1
- package/README.md +33 -4
- package/REPOSITORY_SETTINGS.md +12 -6
- package/SECURITY.md +14 -0
- package/SUPPLY_CHAIN.md +15 -14
- package/UPGRADE.md +14 -4
- package/antigravity/plugin.json +1 -1
- package/assistant-adapters/README.md +3 -0
- package/assistant-adapters/antigravity.md +2 -0
- package/assistant-adapters/claude-code-subagents.md +1 -0
- package/assistant-adapters/codex-agents.md +2 -0
- package/assistant-adapters/cursor-agent-kit.mdc +2 -0
- package/assistant-adapters/cursor-planner.mdc +1 -1
- package/assistant-adapters/orchestrator-runtime.md +39 -0
- package/checklists/frontend-quality.md +1 -0
- package/checklists/ui-detectors.md +6 -0
- package/dist/index.js +1813 -713
- package/dist/index.js.map +1 -1
- package/dist/studio/office/assets/office.css +167 -6
- package/dist/studio/office/assets/office.js +298 -4
- package/dist/studio/wizard/assets/wizard.js +7 -1
- package/examples/next-supabase-installed/.agent-kit/manifest.json +187 -14
- package/examples/next-supabase-installed/audit-output.json +405 -380
- package/package.json +27 -4
- package/prompts/frontend-design-review.md +6 -0
- package/prompts/screenshot-review.md +2 -1
- package/schemas/audit-report-v2.schema.json +71 -0
- package/schemas/audit-report.schema.json +16 -16
- package/schemas/orchestrator.schema.json +167 -0
- package/schemas/runtime-event.schema.json +66 -0
- package/schemas/runtime-run.schema.json +58 -0
- package/schemas/session-event.schema.json +2 -0
- package/templates/next-supabase/.agent-kit/orchestrator.json +48 -0
- package/templates/next-supabase/.agent-kit/runtime/gitignore.template +2 -0
- package/templates/next-supabase/AGENTS.md +2 -0
- package/templates/next-supabase/ASSISTANT_ADAPTERS.md +20 -0
- package/templates/next-supabase/DECISIONS.md +14 -0
- package/templates/next-supabase/DEPLOYMENT.md +4 -0
- package/templates/next-supabase/DESIGN.md +6 -0
- package/templates/next-supabase/DOCS.md +8 -0
- package/templates/next-supabase/MODEL_ROUTING.md +9 -0
- package/templates/next-supabase/QUALITY_GATES.md +5 -1
- package/templates/next-supabase/SECURITY.md +10 -0
- package/templates/next-supabase/SPEC.md +3 -1
- package/templates/next-supabase/STYLE_GUIDE.md +15 -0
- package/templates/next-supabase/TESTING.md +6 -0
- package/templates/next-supabase/UPGRADE.md +1 -0
|
@@ -566,6 +566,10 @@ body {
|
|
|
566
566
|
display: none !important;
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
+
[hidden] {
|
|
570
|
+
display: none !important;
|
|
571
|
+
}
|
|
572
|
+
|
|
569
573
|
code {
|
|
570
574
|
font-family: ui-monospace, Menlo, Consolas, monospace;
|
|
571
575
|
font-size: 12px;
|
|
@@ -600,13 +604,160 @@ code {
|
|
|
600
604
|
max-height: calc(100vh - 64px);
|
|
601
605
|
}
|
|
602
606
|
|
|
607
|
+
.studio-tabs {
|
|
608
|
+
display: grid;
|
|
609
|
+
grid-template-columns: 1fr 1fr;
|
|
610
|
+
margin-bottom: 12px;
|
|
611
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
612
|
+
border-radius: 6px;
|
|
613
|
+
overflow: hidden;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.studio-tab {
|
|
617
|
+
min-height: 34px;
|
|
618
|
+
border: 0;
|
|
619
|
+
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
|
620
|
+
background: #0f172a;
|
|
621
|
+
color: #94a3b8;
|
|
622
|
+
font: inherit;
|
|
623
|
+
cursor: pointer;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.studio-tab:last-child {
|
|
627
|
+
border-right: 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.studio-tab.active {
|
|
631
|
+
background: #334155;
|
|
632
|
+
color: #f8fafc;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.studio-tab:focus-visible {
|
|
636
|
+
outline: 2px solid #fbbf24;
|
|
637
|
+
outline-offset: -2px;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.studio-controls {
|
|
641
|
+
display: grid;
|
|
642
|
+
gap: 10px;
|
|
643
|
+
margin-bottom: 12px;
|
|
644
|
+
padding-bottom: 12px;
|
|
645
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.studio-label {
|
|
649
|
+
font-size: 12px;
|
|
650
|
+
color: #94a3b8;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.studio-note-form {
|
|
654
|
+
display: grid;
|
|
655
|
+
gap: 8px;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.studio-note-form input,
|
|
659
|
+
.studio-note-form select,
|
|
660
|
+
#session-picker,
|
|
661
|
+
#runtime-picker,
|
|
662
|
+
#runtime-goal {
|
|
663
|
+
width: 100%;
|
|
664
|
+
padding: 8px 10px;
|
|
665
|
+
border-radius: 8px;
|
|
666
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
667
|
+
background: #0f172a;
|
|
668
|
+
color: #e2e8f0;
|
|
669
|
+
font-size: 13px;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
#runtime-goal {
|
|
673
|
+
min-height: 82px;
|
|
674
|
+
resize: vertical;
|
|
675
|
+
line-height: 1.4;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.runtime-start-form,
|
|
679
|
+
.runtime-toolbar {
|
|
680
|
+
display: grid;
|
|
681
|
+
gap: 8px;
|
|
682
|
+
padding-bottom: 12px;
|
|
683
|
+
margin-bottom: 12px;
|
|
684
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.runtime-check {
|
|
688
|
+
display: flex;
|
|
689
|
+
align-items: center;
|
|
690
|
+
gap: 8px;
|
|
691
|
+
font-size: 12px;
|
|
692
|
+
color: #cbd5e1;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.runtime-check input {
|
|
696
|
+
width: 16px;
|
|
697
|
+
height: 16px;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.runtime-summary {
|
|
701
|
+
margin: 0 0 12px;
|
|
702
|
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.runtime-summary div {
|
|
706
|
+
display: grid;
|
|
707
|
+
grid-template-columns: 72px minmax(0, 1fr);
|
|
708
|
+
gap: 8px;
|
|
709
|
+
padding: 7px 0;
|
|
710
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
711
|
+
font-size: 12px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.runtime-summary dt {
|
|
715
|
+
color: #94a3b8;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.runtime-summary dd {
|
|
719
|
+
margin: 0;
|
|
720
|
+
color: #f8fafc;
|
|
721
|
+
overflow-wrap: anywhere;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.runtime-approval {
|
|
725
|
+
padding: 10px;
|
|
726
|
+
margin-bottom: 10px;
|
|
727
|
+
border: 1px solid rgba(251, 191, 36, 0.32);
|
|
728
|
+
border-radius: 6px;
|
|
729
|
+
background: rgba(251, 191, 36, 0.08);
|
|
730
|
+
color: #f8fafc;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.runtime-approval p {
|
|
734
|
+
margin: 6px 0 10px;
|
|
735
|
+
color: #cbd5e1;
|
|
736
|
+
font-size: 12px;
|
|
737
|
+
line-height: 1.4;
|
|
738
|
+
white-space: pre-wrap;
|
|
739
|
+
overflow-wrap: anywhere;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.runtime-actions {
|
|
743
|
+
display: grid;
|
|
744
|
+
grid-template-columns: 1fr 1fr;
|
|
745
|
+
gap: 8px;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.runtime-cancel {
|
|
749
|
+
width: 100%;
|
|
750
|
+
margin-bottom: 12px;
|
|
751
|
+
}
|
|
752
|
+
|
|
603
753
|
.transcript-panel h2 {
|
|
604
754
|
margin: 0 0 8px;
|
|
605
755
|
font-size: 14px;
|
|
606
756
|
color: #f8fafc;
|
|
607
757
|
}
|
|
608
758
|
|
|
609
|
-
#transcript-list
|
|
759
|
+
#transcript-list,
|
|
760
|
+
#runtime-event-list {
|
|
610
761
|
list-style: none;
|
|
611
762
|
margin: 0;
|
|
612
763
|
padding: 0;
|
|
@@ -615,7 +766,8 @@ code {
|
|
|
615
766
|
font-size: 12px;
|
|
616
767
|
}
|
|
617
768
|
|
|
618
|
-
#transcript-list li
|
|
769
|
+
#transcript-list li,
|
|
770
|
+
#runtime-event-list li {
|
|
619
771
|
padding: 8px;
|
|
620
772
|
border-radius: 8px;
|
|
621
773
|
background: rgba(255, 255, 255, 0.04);
|
|
@@ -662,11 +814,20 @@ code {
|
|
|
662
814
|
@media (max-width: 900px) {
|
|
663
815
|
.office-main.studio-layout {
|
|
664
816
|
grid-template-columns: 1fr;
|
|
817
|
+
grid-template-areas:
|
|
818
|
+
"activity"
|
|
819
|
+
"office";
|
|
820
|
+
}
|
|
821
|
+
.office-main.studio-layout > .canvas-wrap {
|
|
822
|
+
grid-area: office;
|
|
665
823
|
}
|
|
666
824
|
.transcript-panel {
|
|
667
|
-
|
|
825
|
+
grid-area: activity;
|
|
826
|
+
height: min(640px, 72vh);
|
|
827
|
+
max-height: none;
|
|
668
828
|
border-left: 0;
|
|
669
|
-
border-top:
|
|
829
|
+
border-top: 0;
|
|
830
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
670
831
|
}
|
|
671
832
|
}
|
|
672
833
|
|
|
@@ -702,8 +863,8 @@ code {
|
|
|
702
863
|
padding: 6px 10px;
|
|
703
864
|
background: #fff;
|
|
704
865
|
color: var(--ink);
|
|
705
|
-
border:
|
|
706
|
-
border-radius:
|
|
866
|
+
border: 1px solid #cbd5e1;
|
|
867
|
+
border-radius: 8px;
|
|
707
868
|
font-size: 11px;
|
|
708
869
|
line-height: 1.3;
|
|
709
870
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
handoffPulse: null,
|
|
36
36
|
studioSessionId: boot.activeSessionId || "",
|
|
37
37
|
studioEvents: [],
|
|
38
|
+
studioEventSource: null,
|
|
39
|
+
runtimeRuns: [],
|
|
40
|
+
runtimeRunId: "",
|
|
41
|
+
runtimeEvents: [],
|
|
38
42
|
speechBubbles: [],
|
|
39
43
|
agenticLevel: null
|
|
40
44
|
};
|
|
@@ -71,7 +75,30 @@
|
|
|
71
75
|
bubbleLayer: document.getElementById("bubble-layer"),
|
|
72
76
|
officeHint: document.getElementById("office-hint"),
|
|
73
77
|
canvasWrap: document.querySelector(".canvas-wrap"),
|
|
74
|
-
transcriptList: document.getElementById("transcript-list")
|
|
78
|
+
transcriptList: document.getElementById("transcript-list"),
|
|
79
|
+
sessionPicker: document.getElementById("session-picker"),
|
|
80
|
+
studioNoteForm: document.getElementById("studio-note-form"),
|
|
81
|
+
studioNoteAgent: document.getElementById("studio-note-agent"),
|
|
82
|
+
studioNoteText: document.getElementById("studio-note-text"),
|
|
83
|
+
studioRenderBtn: document.getElementById("studio-render-btn"),
|
|
84
|
+
studioCouncilTab: document.getElementById("studio-council-tab"),
|
|
85
|
+
studioRuntimeTab: document.getElementById("studio-runtime-tab"),
|
|
86
|
+
studioCouncilView: document.getElementById("studio-council-view"),
|
|
87
|
+
studioRuntimeView: document.getElementById("studio-runtime-view"),
|
|
88
|
+
runtimeStartForm: document.getElementById("runtime-start-form"),
|
|
89
|
+
runtimeStartBtn: document.getElementById("runtime-start-btn"),
|
|
90
|
+
runtimeGoal: document.getElementById("runtime-goal"),
|
|
91
|
+
runtimeDirtyBase: document.getElementById("runtime-dirty-base"),
|
|
92
|
+
runtimePicker: document.getElementById("runtime-picker"),
|
|
93
|
+
runtimeRefreshBtn: document.getElementById("runtime-refresh-btn"),
|
|
94
|
+
runtimeSummary: document.getElementById("runtime-summary"),
|
|
95
|
+
runtimeApproval: document.getElementById("runtime-approval"),
|
|
96
|
+
runtimeApprovalTitle: document.getElementById("runtime-approval-title"),
|
|
97
|
+
runtimeApprovalDetail: document.getElementById("runtime-approval-detail"),
|
|
98
|
+
runtimeApproveBtn: document.getElementById("runtime-approve-btn"),
|
|
99
|
+
runtimeRejectBtn: document.getElementById("runtime-reject-btn"),
|
|
100
|
+
runtimeCancelBtn: document.getElementById("runtime-cancel-btn"),
|
|
101
|
+
runtimeEventList: document.getElementById("runtime-event-list")
|
|
75
102
|
};
|
|
76
103
|
|
|
77
104
|
const ctx = els.canvas?.getContext("2d");
|
|
@@ -169,7 +196,13 @@
|
|
|
169
196
|
}
|
|
170
197
|
|
|
171
198
|
async function api(path, options) {
|
|
172
|
-
const
|
|
199
|
+
const requestOptions = options ? { ...options } : {};
|
|
200
|
+
const method = String(requestOptions.method || "GET").toUpperCase();
|
|
201
|
+
if (!["GET", "HEAD", "OPTIONS"].includes(method)) {
|
|
202
|
+
const token = document.querySelector('meta[name="agent-kit-csrf-token"]')?.getAttribute("content") || "";
|
|
203
|
+
requestOptions.headers = { ...(requestOptions.headers || {}), "Content-Type": "application/json", "X-Agent-Kit-CSRF": token };
|
|
204
|
+
}
|
|
205
|
+
const response = await fetch(path, requestOptions);
|
|
173
206
|
const data = await response.json();
|
|
174
207
|
if (!response.ok) throw new Error(data.error || "Request failed");
|
|
175
208
|
return data;
|
|
@@ -210,22 +243,281 @@
|
|
|
210
243
|
|
|
211
244
|
async function loadStudioState() {
|
|
212
245
|
const sessionsRes = await api("/api/sessions");
|
|
213
|
-
|
|
246
|
+
const sessions = sessionsRes.sessions || [];
|
|
247
|
+
state.studioSessionId = sessionsRes.activeSessionId || boot.activeSessionId || sessions[0]?.sessionId || "";
|
|
248
|
+
populateSessionPicker(sessions, state.studioSessionId);
|
|
249
|
+
populateStudioAgents();
|
|
214
250
|
if (els.sessionPill) {
|
|
215
251
|
els.sessionPill.textContent = state.studioSessionId ? state.studioSessionId.slice(0, 24) : "No session";
|
|
216
252
|
}
|
|
217
|
-
|
|
253
|
+
const activeSession = sessions.find((s) => s.sessionId === state.studioSessionId);
|
|
254
|
+
if (els.projectName) els.projectName.textContent = activeSession?.title || "Council session";
|
|
218
255
|
if (els.officeHint) {
|
|
219
256
|
els.officeHint.classList.remove("hidden");
|
|
220
257
|
window.setTimeout(() => els.officeHint?.classList.add("hidden"), 6000);
|
|
221
258
|
}
|
|
222
259
|
connectStudioStream();
|
|
260
|
+
await loadRuntimeRuns().catch((error) => renderRuntimeUnavailable(error.message));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function populateSessionPicker(sessions, selectedId) {
|
|
264
|
+
if (!els.sessionPicker) return;
|
|
265
|
+
els.sessionPicker.innerHTML = sessions.length
|
|
266
|
+
? sessions
|
|
267
|
+
.map(
|
|
268
|
+
(s) =>
|
|
269
|
+
'<option value="' +
|
|
270
|
+
escapeHtml(s.sessionId) +
|
|
271
|
+
'"' +
|
|
272
|
+
(s.sessionId === selectedId ? " selected" : "") +
|
|
273
|
+
">" +
|
|
274
|
+
escapeHtml((s.title || s.sessionId).slice(0, 48)) +
|
|
275
|
+
"</option>"
|
|
276
|
+
)
|
|
277
|
+
.join("")
|
|
278
|
+
: '<option value="">No sessions</option>';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function populateStudioAgents() {
|
|
282
|
+
if (!els.studioNoteAgent) return;
|
|
283
|
+
els.studioNoteAgent.innerHTML = state.agents.length
|
|
284
|
+
? state.agents.map((a) => '<option value="' + escapeHtml(a.id) + '">' + escapeHtml(a.name || a.id) + "</option>").join("")
|
|
285
|
+
: '<option value="planner">Planner</option>';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function switchStudioView(view) {
|
|
289
|
+
const runtime = view === "runtime";
|
|
290
|
+
if (els.studioCouncilView) els.studioCouncilView.hidden = runtime;
|
|
291
|
+
if (els.studioRuntimeView) els.studioRuntimeView.hidden = !runtime;
|
|
292
|
+
els.studioCouncilTab?.classList.toggle("active", !runtime);
|
|
293
|
+
els.studioRuntimeTab?.classList.toggle("active", runtime);
|
|
294
|
+
els.studioCouncilTab?.setAttribute("aria-selected", String(!runtime));
|
|
295
|
+
els.studioRuntimeTab?.setAttribute("aria-selected", String(runtime));
|
|
296
|
+
if (runtime) void loadRuntimeRuns().catch((error) => renderRuntimeUnavailable(error.message));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function populateRuntimePicker() {
|
|
300
|
+
if (!els.runtimePicker) return;
|
|
301
|
+
els.runtimePicker.innerHTML = state.runtimeRuns.length
|
|
302
|
+
? state.runtimeRuns
|
|
303
|
+
.map(
|
|
304
|
+
(run) =>
|
|
305
|
+
'<option value="' +
|
|
306
|
+
escapeHtml(run.runId) +
|
|
307
|
+
'"' +
|
|
308
|
+
(run.runId === state.runtimeRunId ? " selected" : "") +
|
|
309
|
+
">" +
|
|
310
|
+
escapeHtml(run.runId.slice(0, 30) + " · " + run.status) +
|
|
311
|
+
"</option>"
|
|
312
|
+
)
|
|
313
|
+
.join("")
|
|
314
|
+
: '<option value="">No runs</option>';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function loadRuntimeRuns() {
|
|
318
|
+
const response = await api("/api/runtime/runs");
|
|
319
|
+
const runtimeEnabled = response.enabled === true;
|
|
320
|
+
if (els.runtimeStartBtn) els.runtimeStartBtn.disabled = !runtimeEnabled;
|
|
321
|
+
state.runtimeRuns = Array.isArray(response.runs) ? response.runs : [];
|
|
322
|
+
if (!state.runtimeRunId || !state.runtimeRuns.some((run) => run.runId === state.runtimeRunId)) {
|
|
323
|
+
state.runtimeRunId = state.runtimeRuns[0]?.runId || "";
|
|
324
|
+
}
|
|
325
|
+
populateRuntimePicker();
|
|
326
|
+
if (state.runtimeRunId) await loadRuntimeRun(state.runtimeRunId);
|
|
327
|
+
else if (runtimeEnabled) renderRuntimeRun(null, []);
|
|
328
|
+
else renderRuntimeUnavailable("Runtime is disabled in .agent-kit/orchestrator.json.");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function loadRuntimeRun(runId) {
|
|
332
|
+
if (!runId) {
|
|
333
|
+
renderRuntimeRun(null, []);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const response = await api("/api/runtime/runs/" + encodeURIComponent(runId));
|
|
337
|
+
state.runtimeRunId = runId;
|
|
338
|
+
state.runtimeEvents = response.events || [];
|
|
339
|
+
const index = state.runtimeRuns.findIndex((run) => run.runId === runId);
|
|
340
|
+
if (index >= 0) state.runtimeRuns[index] = response.run;
|
|
341
|
+
else state.runtimeRuns.unshift(response.run);
|
|
342
|
+
populateRuntimePicker();
|
|
343
|
+
renderRuntimeRun(response.run, state.runtimeEvents);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function renderRuntimeUnavailable(message) {
|
|
347
|
+
if (els.runtimeStartBtn) els.runtimeStartBtn.disabled = true;
|
|
348
|
+
if (els.runtimeSummary) els.runtimeSummary.innerHTML = "<div><dt>Status</dt><dd>" + escapeHtml(message) + "</dd></div>";
|
|
349
|
+
if (els.runtimeEventList) els.runtimeEventList.innerHTML = "";
|
|
350
|
+
if (els.runtimeApproval) els.runtimeApproval.hidden = true;
|
|
351
|
+
if (els.runtimeCancelBtn) els.runtimeCancelBtn.hidden = true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function renderRuntimeRun(run, events) {
|
|
355
|
+
if (els.runtimeSummary) {
|
|
356
|
+
const items = run
|
|
357
|
+
? [
|
|
358
|
+
["Status", run.status],
|
|
359
|
+
["Workflow", run.workflowId],
|
|
360
|
+
["Agent", run.activeAgentId || "—"],
|
|
361
|
+
["Commit", run.commit ? run.commit.slice(0, 12) : "—"]
|
|
362
|
+
]
|
|
363
|
+
: [["Status", "No runs"]];
|
|
364
|
+
els.runtimeSummary.innerHTML = items.map(([label, value]) => "<div><dt>" + escapeHtml(label) + "</dt><dd>" + escapeHtml(value) + "</dd></div>").join("");
|
|
365
|
+
}
|
|
366
|
+
const pending = run?.pendingApproval;
|
|
367
|
+
if (els.runtimeApproval) els.runtimeApproval.hidden = !pending;
|
|
368
|
+
if (els.runtimeApprovalTitle) els.runtimeApprovalTitle.textContent = pending?.title || "";
|
|
369
|
+
if (els.runtimeApprovalDetail) els.runtimeApprovalDetail.textContent = pending?.detail || "";
|
|
370
|
+
const terminal = !run || ["complete", "failed", "cancelled"].includes(run.status);
|
|
371
|
+
if (els.runtimeCancelBtn) els.runtimeCancelBtn.hidden = terminal;
|
|
372
|
+
if (els.runtimeEventList) {
|
|
373
|
+
els.runtimeEventList.innerHTML = (events || [])
|
|
374
|
+
.slice(-100)
|
|
375
|
+
.map(
|
|
376
|
+
(event) =>
|
|
377
|
+
'<li><span class="tx-time">' +
|
|
378
|
+
escapeHtml((event.createdAt || "").slice(11, 19)) +
|
|
379
|
+
"</span> <strong>" +
|
|
380
|
+
escapeHtml(event.agentId || "run") +
|
|
381
|
+
"</strong> " +
|
|
382
|
+
escapeHtml(eventLabel(event)) +
|
|
383
|
+
"</li>"
|
|
384
|
+
)
|
|
385
|
+
.join("");
|
|
386
|
+
els.runtimeEventList.scrollTop = els.runtimeEventList.scrollHeight;
|
|
387
|
+
}
|
|
388
|
+
if (run && els.sessionPill && !els.studioRuntimeView?.hidden) els.sessionPill.textContent = run.runId.slice(0, 24);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function decideRuntime(decision) {
|
|
392
|
+
if (!state.runtimeRunId) return;
|
|
393
|
+
const response = await api("/api/runtime/runs/" + encodeURIComponent(state.runtimeRunId) + "/decision", {
|
|
394
|
+
method: "POST",
|
|
395
|
+
body: JSON.stringify({ decision, actor: "studio-operator" })
|
|
396
|
+
});
|
|
397
|
+
state.runtimeEvents = response.events || [];
|
|
398
|
+
renderRuntimeRun(response.run, state.runtimeEvents);
|
|
399
|
+
await loadRuntimeRuns();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function bindStudioControls() {
|
|
403
|
+
els.studioCouncilTab?.addEventListener("click", () => switchStudioView("council"));
|
|
404
|
+
els.studioRuntimeTab?.addEventListener("click", () => switchStudioView("runtime"));
|
|
405
|
+
if (els.sessionPicker) {
|
|
406
|
+
els.sessionPicker.addEventListener("change", () => {
|
|
407
|
+
state.studioSessionId = els.sessionPicker.value;
|
|
408
|
+
if (els.sessionPill) {
|
|
409
|
+
els.sessionPill.textContent = state.studioSessionId ? state.studioSessionId.slice(0, 24) : "No session";
|
|
410
|
+
}
|
|
411
|
+
connectStudioStream();
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (els.studioNoteForm) {
|
|
415
|
+
els.studioNoteForm.addEventListener("submit", async (event) => {
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
if (!state.studioSessionId) {
|
|
418
|
+
setStatus("error", "Select a session first.");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const agent = els.studioNoteAgent?.value || "planner";
|
|
422
|
+
const text = (els.studioNoteText?.value || "").trim();
|
|
423
|
+
if (!text) {
|
|
424
|
+
setStatus("error", "Enter note text.");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
await api("/api/sessions/" + encodeURIComponent(state.studioSessionId) + "/note", {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: { "Content-Type": "application/json" },
|
|
431
|
+
body: JSON.stringify({ agent, text })
|
|
432
|
+
});
|
|
433
|
+
if (els.studioNoteText) els.studioNoteText.value = "";
|
|
434
|
+
setStatus("ok", "Note recorded.");
|
|
435
|
+
} catch (error) {
|
|
436
|
+
setStatus("error", error.message);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (els.studioRenderBtn) {
|
|
441
|
+
els.studioRenderBtn.addEventListener("click", async () => {
|
|
442
|
+
if (!state.studioSessionId) {
|
|
443
|
+
setStatus("error", "Select a session first.");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const result = await api("/api/sessions/" + encodeURIComponent(state.studioSessionId) + "/render", {
|
|
448
|
+
method: "POST"
|
|
449
|
+
});
|
|
450
|
+
setStatus("ok", "Rendered " + (result.files?.length || 0) + " markdown files.");
|
|
451
|
+
} catch (error) {
|
|
452
|
+
setStatus("error", error.message);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (els.runtimePicker) {
|
|
457
|
+
els.runtimePicker.addEventListener("change", () => {
|
|
458
|
+
state.runtimeRunId = els.runtimePicker.value;
|
|
459
|
+
void loadRuntimeRun(state.runtimeRunId).catch((error) => setStatus("error", error.message));
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
els.runtimeRefreshBtn?.addEventListener("click", () => {
|
|
463
|
+
void loadRuntimeRuns().catch((error) => setStatus("error", error.message));
|
|
464
|
+
});
|
|
465
|
+
if (els.runtimeStartForm) {
|
|
466
|
+
els.runtimeStartForm.addEventListener("submit", async (event) => {
|
|
467
|
+
event.preventDefault();
|
|
468
|
+
const goal = (els.runtimeGoal?.value || "").trim();
|
|
469
|
+
if (!goal) {
|
|
470
|
+
setStatus("error", "Enter a run goal.");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const response = await api("/api/runtime/runs", {
|
|
475
|
+
method: "POST",
|
|
476
|
+
body: JSON.stringify({ goal, acknowledgeDirtyBase: Boolean(els.runtimeDirtyBase?.checked) })
|
|
477
|
+
});
|
|
478
|
+
state.runtimeRunId = response.run.runId;
|
|
479
|
+
state.runtimeEvents = response.events || [];
|
|
480
|
+
if (els.runtimeGoal) els.runtimeGoal.value = "";
|
|
481
|
+
await loadRuntimeRuns();
|
|
482
|
+
setStatus("ok", "Run started.");
|
|
483
|
+
} catch (error) {
|
|
484
|
+
setStatus("error", error.message);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
els.runtimeApproveBtn?.addEventListener("click", () => {
|
|
489
|
+
void decideRuntime("approve").catch((error) => setStatus("error", error.message));
|
|
490
|
+
});
|
|
491
|
+
els.runtimeRejectBtn?.addEventListener("click", () => {
|
|
492
|
+
void decideRuntime("reject").catch((error) => setStatus("error", error.message));
|
|
493
|
+
});
|
|
494
|
+
els.runtimeCancelBtn?.addEventListener("click", async () => {
|
|
495
|
+
if (!state.runtimeRunId) return;
|
|
496
|
+
try {
|
|
497
|
+
const response = await api("/api/runtime/runs/" + encodeURIComponent(state.runtimeRunId) + "/cancel", { method: "POST" });
|
|
498
|
+
state.runtimeEvents = response.events || [];
|
|
499
|
+
renderRuntimeRun(response.run, state.runtimeEvents);
|
|
500
|
+
await loadRuntimeRuns();
|
|
501
|
+
} catch (error) {
|
|
502
|
+
setStatus("error", error.message);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
window.setInterval(() => {
|
|
506
|
+
if (els.studioRuntimeView && !els.studioRuntimeView.hidden) {
|
|
507
|
+
void loadRuntimeRuns().catch(() => undefined);
|
|
508
|
+
}
|
|
509
|
+
}, 2500);
|
|
223
510
|
}
|
|
224
511
|
|
|
225
512
|
function connectStudioStream() {
|
|
226
513
|
if (!state.studioSessionId) return;
|
|
514
|
+
if (state.studioEventSource) {
|
|
515
|
+
state.studioEventSource.close();
|
|
516
|
+
state.studioEventSource = null;
|
|
517
|
+
}
|
|
227
518
|
const url = "/api/events/stream?sessionId=" + encodeURIComponent(state.studioSessionId);
|
|
228
519
|
const source = new EventSource(url);
|
|
520
|
+
state.studioEventSource = source;
|
|
229
521
|
source.addEventListener("snapshot", (ev) => {
|
|
230
522
|
try {
|
|
231
523
|
const payload = JSON.parse(ev.data);
|
|
@@ -249,6 +541,7 @@
|
|
|
249
541
|
});
|
|
250
542
|
source.onerror = () => {
|
|
251
543
|
source.close();
|
|
544
|
+
if (state.studioEventSource === source) state.studioEventSource = null;
|
|
252
545
|
};
|
|
253
546
|
}
|
|
254
547
|
|
|
@@ -1119,6 +1412,7 @@
|
|
|
1119
1412
|
}
|
|
1120
1413
|
|
|
1121
1414
|
loop();
|
|
1415
|
+
if (isStudio) bindStudioControls();
|
|
1122
1416
|
loadState().catch((error) => setStatus("error", error.message));
|
|
1123
1417
|
window.addEventListener("resize", () => {
|
|
1124
1418
|
renderNameplates();
|
|
@@ -59,7 +59,13 @@
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function api(path, options) {
|
|
62
|
-
const
|
|
62
|
+
const requestOptions = options ? { ...options } : {};
|
|
63
|
+
const method = String(requestOptions.method || "GET").toUpperCase();
|
|
64
|
+
if (!["GET", "HEAD", "OPTIONS"].includes(method)) {
|
|
65
|
+
const token = document.querySelector('meta[name="agent-kit-csrf-token"]')?.getAttribute("content") || "";
|
|
66
|
+
requestOptions.headers = { ...(requestOptions.headers || {}), "Content-Type": "application/json", "X-Agent-Kit-CSRF": token };
|
|
67
|
+
}
|
|
68
|
+
const response = await fetch(path, requestOptions);
|
|
63
69
|
const data = await response.json();
|
|
64
70
|
if (!response.ok) throw new Error(data.error || "Request failed");
|
|
65
71
|
return data;
|