@enderfga/claw-orchestrator 4.0.0 → 4.0.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/README.md +39 -259
- package/configs/autoloop-coder-prompt.md +65 -28
- package/configs/autoloop-planner-prompt.md +103 -42
- package/configs/autoloop-reviewer-prompt.md +58 -30
- package/dist/src/autoloop/dispatcher.d.ts +10 -1
- package/dist/src/autoloop/dispatcher.js +42 -5
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/planner-tools.d.ts +8 -3
- package/dist/src/autoloop/planner-tools.js +21 -4
- package/dist/src/autoloop/planner-tools.js.map +1 -1
- package/dist/src/dashboard/index.html +410 -6
- package/dist/src/embedded-server.d.ts +7 -0
- package/dist/src/embedded-server.js +282 -26
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/session-manager.d.ts +37 -1
- package/dist/src/session-manager.js +179 -6
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +9 -0
- package/dist/src/ultraapp/manager.js +31 -8
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/ultraapp/store.js +11 -1
- package/dist/src/ultraapp/store.js.map +1 -1
- package/package.json +1 -1
- package/skills/references/autoloop.md +10 -5
- package/skills/references/dashboard.md +172 -0
- package/skills/references/ultraapp.md +2 -2
|
@@ -119,12 +119,29 @@ export async function applyPlannerToolCalls(calls, fx, iter) {
|
|
|
119
119
|
fx.updatePushPolicy(call.args);
|
|
120
120
|
break;
|
|
121
121
|
}
|
|
122
|
-
case '
|
|
123
|
-
|
|
122
|
+
case 'write_plan': {
|
|
123
|
+
const { content, commit_message } = call.args;
|
|
124
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
125
|
+
throw new Error('write_plan requires non-empty `content` (full plan.md body)');
|
|
126
|
+
}
|
|
127
|
+
await fx.writePlanFile('plan.md', content, commit_message);
|
|
124
128
|
break;
|
|
125
129
|
}
|
|
126
|
-
case '
|
|
127
|
-
|
|
130
|
+
case 'write_goal': {
|
|
131
|
+
const { content, commit_message } = call.args;
|
|
132
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
133
|
+
throw new Error('write_goal requires non-empty `content` (full goal.json body)');
|
|
134
|
+
}
|
|
135
|
+
// Validate it parses as JSON — goal.json must be machine-readable.
|
|
136
|
+
// The error message includes the parse position so the Planner can
|
|
137
|
+
// self-correct on the next turn.
|
|
138
|
+
try {
|
|
139
|
+
JSON.parse(content);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
throw new Error(`write_goal content is not valid JSON: ${e.message}`);
|
|
143
|
+
}
|
|
144
|
+
await fx.writePlanFile('goal.json', content, commit_message);
|
|
128
145
|
break;
|
|
129
146
|
}
|
|
130
147
|
default:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-tools.js","sourceRoot":"","sources":["../../../src/autoloop/planner-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAA2B,GAAG,EAAoC,MAAM,eAAe,CAAC;AA0B/F,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,MAAM,YAAY,GAAkD,EAAE,CAAC;IACvE,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QAC/D,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAoB,CAAC;YAC1D,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACjG,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;gBACjF,OAAO,EAAE,CAAC,CAAC,2DAA2D;YACxE,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,CAAC;AAChE,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-tools.js","sourceRoot":"","sources":["../../../src/autoloop/planner-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -533,7 +533,9 @@
|
|
|
533
533
|
cursor: pointer;
|
|
534
534
|
}
|
|
535
535
|
.forge-spec-msg { font-size: 11px; color: var(--muted); }
|
|
536
|
-
#forgeNew
|
|
536
|
+
#forgeNew,
|
|
537
|
+
#councilNew,
|
|
538
|
+
#autoloopNew {
|
|
537
539
|
margin: 8px 12px;
|
|
538
540
|
padding: 6px 12px;
|
|
539
541
|
border-radius: 4px;
|
|
@@ -543,6 +545,109 @@
|
|
|
543
545
|
cursor: pointer;
|
|
544
546
|
width: calc(100% - 24px);
|
|
545
547
|
}
|
|
548
|
+
#forgeNew:hover,
|
|
549
|
+
#councilNew:hover,
|
|
550
|
+
#autoloopNew:hover {
|
|
551
|
+
filter: brightness(0.95);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* ─── Launcher modal (Council / Autoloop forms) ─────────────── */
|
|
555
|
+
.modal-root {
|
|
556
|
+
position: fixed;
|
|
557
|
+
inset: 0;
|
|
558
|
+
background: rgba(0, 0, 0, 0.45);
|
|
559
|
+
display: flex;
|
|
560
|
+
align-items: center;
|
|
561
|
+
justify-content: center;
|
|
562
|
+
z-index: 100;
|
|
563
|
+
}
|
|
564
|
+
.modal-root[hidden] { display: none; }
|
|
565
|
+
.modal {
|
|
566
|
+
background: var(--surface);
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
min-width: 440px;
|
|
569
|
+
max-width: 600px;
|
|
570
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
|
|
571
|
+
}
|
|
572
|
+
.modal-header {
|
|
573
|
+
display: flex;
|
|
574
|
+
align-items: center;
|
|
575
|
+
justify-content: space-between;
|
|
576
|
+
padding: 14px 20px;
|
|
577
|
+
border-bottom: 1px solid var(--border);
|
|
578
|
+
}
|
|
579
|
+
.modal-header h2 { margin: 0; font-size: 15px; }
|
|
580
|
+
.modal-header button {
|
|
581
|
+
background: transparent;
|
|
582
|
+
border: none;
|
|
583
|
+
font-size: 22px;
|
|
584
|
+
cursor: pointer;
|
|
585
|
+
color: var(--muted);
|
|
586
|
+
padding: 0 6px;
|
|
587
|
+
}
|
|
588
|
+
.modal-body {
|
|
589
|
+
padding: 16px 20px;
|
|
590
|
+
display: flex;
|
|
591
|
+
flex-direction: column;
|
|
592
|
+
gap: 12px;
|
|
593
|
+
}
|
|
594
|
+
.modal-body label {
|
|
595
|
+
display: flex;
|
|
596
|
+
flex-direction: column;
|
|
597
|
+
gap: 4px;
|
|
598
|
+
font-size: 12px;
|
|
599
|
+
color: var(--muted);
|
|
600
|
+
}
|
|
601
|
+
.modal-body input,
|
|
602
|
+
.modal-body textarea {
|
|
603
|
+
padding: 6px 8px;
|
|
604
|
+
border: 1px solid var(--border);
|
|
605
|
+
border-radius: 4px;
|
|
606
|
+
font: inherit;
|
|
607
|
+
color: var(--text);
|
|
608
|
+
background: var(--bg);
|
|
609
|
+
}
|
|
610
|
+
.modal-footer {
|
|
611
|
+
display: flex;
|
|
612
|
+
justify-content: flex-end;
|
|
613
|
+
gap: 8px;
|
|
614
|
+
padding: 12px 20px 18px;
|
|
615
|
+
}
|
|
616
|
+
.modal-footer button {
|
|
617
|
+
padding: 6px 14px;
|
|
618
|
+
border-radius: 4px;
|
|
619
|
+
border: 1px solid var(--border);
|
|
620
|
+
background: var(--surface);
|
|
621
|
+
cursor: pointer;
|
|
622
|
+
font: inherit;
|
|
623
|
+
}
|
|
624
|
+
.modal-footer button.primary {
|
|
625
|
+
background: var(--accent);
|
|
626
|
+
color: white;
|
|
627
|
+
border-color: var(--accent);
|
|
628
|
+
}
|
|
629
|
+
.modal-footer button:disabled {
|
|
630
|
+
opacity: 0.6;
|
|
631
|
+
cursor: not-allowed;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* ─── Empty-state CTA ──────────────────────────────────────── */
|
|
635
|
+
.empty-cta {
|
|
636
|
+
text-align: center;
|
|
637
|
+
margin: 80px auto 0;
|
|
638
|
+
max-width: 420px;
|
|
639
|
+
}
|
|
640
|
+
.empty-cta h2 { margin: 0 0 8px; font-size: 16px; }
|
|
641
|
+
.empty-cta p { margin: 0 0 16px; color: var(--muted); }
|
|
642
|
+
.empty-cta button {
|
|
643
|
+
padding: 8px 16px;
|
|
644
|
+
border-radius: 4px;
|
|
645
|
+
border: 1px solid var(--accent);
|
|
646
|
+
background: var(--accent);
|
|
647
|
+
color: white;
|
|
648
|
+
cursor: pointer;
|
|
649
|
+
font: inherit;
|
|
650
|
+
}
|
|
546
651
|
#forgeStartBuild {
|
|
547
652
|
margin-top: 8px;
|
|
548
653
|
padding: 8px 14px;
|
|
@@ -602,7 +707,9 @@
|
|
|
602
707
|
<div class="body">
|
|
603
708
|
<aside class="sidebar">
|
|
604
709
|
<h2 id="sidebarTitle">Runs</h2>
|
|
605
|
-
<button id="
|
|
710
|
+
<button id="autoloopNew" hidden>+ New autoloop</button>
|
|
711
|
+
<button id="councilNew" hidden>+ New council</button>
|
|
712
|
+
<button id="forgeNew" hidden>+ New forge</button>
|
|
606
713
|
<ul class="runlist" id="runlist"></ul>
|
|
607
714
|
</aside>
|
|
608
715
|
|
|
@@ -614,6 +721,60 @@
|
|
|
614
721
|
<div class="footer">SSE-driven dashboard • served by claw-orchestrator embedded server</div>
|
|
615
722
|
</div>
|
|
616
723
|
|
|
724
|
+
<!-- ─── Launcher modal (tab-aware) ───────────────────────────────── -->
|
|
725
|
+
<div id="modalRoot" class="modal-root" hidden>
|
|
726
|
+
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
|
727
|
+
<div class="modal-header">
|
|
728
|
+
<h2 id="modalTitle">New run</h2>
|
|
729
|
+
<button id="modalClose" type="button" aria-label="Close">×</button>
|
|
730
|
+
</div>
|
|
731
|
+
<form id="modalForm" class="modal-body" autocomplete="off">
|
|
732
|
+
<!-- Council fields -->
|
|
733
|
+
<div data-for-tab="council" style="display: contents">
|
|
734
|
+
<label>
|
|
735
|
+
Task
|
|
736
|
+
<textarea
|
|
737
|
+
name="task"
|
|
738
|
+
rows="3"
|
|
739
|
+
placeholder="What should the council achieve?"
|
|
740
|
+
></textarea>
|
|
741
|
+
</label>
|
|
742
|
+
<label>
|
|
743
|
+
Project directory
|
|
744
|
+
<input
|
|
745
|
+
name="projectDir"
|
|
746
|
+
type="text"
|
|
747
|
+
placeholder="/abs/path/to/workspace"
|
|
748
|
+
/>
|
|
749
|
+
</label>
|
|
750
|
+
<label>
|
|
751
|
+
Max rounds
|
|
752
|
+
<input name="maxRounds" type="number" min="1" max="50" value="15" />
|
|
753
|
+
</label>
|
|
754
|
+
</div>
|
|
755
|
+
<!-- Autoloop fields -->
|
|
756
|
+
<div data-for-tab="autoloop" style="display: contents">
|
|
757
|
+
<label>
|
|
758
|
+
Workspace
|
|
759
|
+
<input
|
|
760
|
+
name="workspace"
|
|
761
|
+
type="text"
|
|
762
|
+
placeholder="/abs/path/to/git/workspace"
|
|
763
|
+
/>
|
|
764
|
+
</label>
|
|
765
|
+
<label>
|
|
766
|
+
Planner model (optional)
|
|
767
|
+
<input name="planner_model" type="text" placeholder="claude-opus-4-7" />
|
|
768
|
+
</label>
|
|
769
|
+
</div>
|
|
770
|
+
<div class="modal-footer">
|
|
771
|
+
<button type="button" id="modalCancel">Cancel</button>
|
|
772
|
+
<button type="submit" id="modalSubmit" class="primary">Start</button>
|
|
773
|
+
</div>
|
|
774
|
+
</form>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
|
|
617
778
|
<script>
|
|
618
779
|
// ─── State ────────────────────────────────────────────────────
|
|
619
780
|
const state = {
|
|
@@ -671,6 +832,8 @@
|
|
|
671
832
|
? 'Council sessions'
|
|
672
833
|
: 'Forge apps';
|
|
673
834
|
$('forgeNew').hidden = tab !== 'forge';
|
|
835
|
+
$('councilNew').hidden = tab !== 'council';
|
|
836
|
+
$('autoloopNew').hidden = tab !== 'autoloop';
|
|
674
837
|
state.selectedId = null;
|
|
675
838
|
closeStreams();
|
|
676
839
|
renderEmptyMain();
|
|
@@ -705,7 +868,33 @@
|
|
|
705
868
|
}
|
|
706
869
|
|
|
707
870
|
function renderEmptyMain() {
|
|
708
|
-
|
|
871
|
+
// When the sidebar list is empty, invite the user to start their
|
|
872
|
+
// first run. Otherwise just say "pick one". The button delegates
|
|
873
|
+
// to the same launcher trigger as the sidebar "+ New" button.
|
|
874
|
+
const labels = {
|
|
875
|
+
autoloop: 'autoloop run',
|
|
876
|
+
council: 'council session',
|
|
877
|
+
forge: 'forge app',
|
|
878
|
+
};
|
|
879
|
+
const noun = labels[state.tab] || 'run';
|
|
880
|
+
if (state.runs.length === 0) {
|
|
881
|
+
const triggerId =
|
|
882
|
+
state.tab === 'autoloop'
|
|
883
|
+
? 'autoloopNew'
|
|
884
|
+
: state.tab === 'council'
|
|
885
|
+
? 'councilNew'
|
|
886
|
+
: 'forgeNew';
|
|
887
|
+
$('main').innerHTML = `
|
|
888
|
+
<div class="empty empty-cta">
|
|
889
|
+
<h2>No ${escapeHtml(noun)}s yet</h2>
|
|
890
|
+
<p>Click <strong>+ New</strong> in the sidebar to start your first one.</p>
|
|
891
|
+
<button type="button" onclick="document.getElementById('${triggerId}').click()">+ New ${escapeHtml(noun)}</button>
|
|
892
|
+
</div>
|
|
893
|
+
`;
|
|
894
|
+
} else {
|
|
895
|
+
$('main').innerHTML =
|
|
896
|
+
'<div class="empty" style="margin-top:80px">Select a run to view.</div>';
|
|
897
|
+
}
|
|
709
898
|
}
|
|
710
899
|
|
|
711
900
|
// ─── List polling ─────────────────────────────────────────────
|
|
@@ -727,6 +916,9 @@
|
|
|
727
916
|
? j.councils || []
|
|
728
917
|
: j.runs || [];
|
|
729
918
|
renderList();
|
|
919
|
+
// Refresh the empty-state CTA — runs may have just appeared or
|
|
920
|
+
// disappeared, and renderEmptyMain's text depends on state.runs.length.
|
|
921
|
+
if (!state.selectedId) renderEmptyMain();
|
|
730
922
|
$('status').textContent = 'connected';
|
|
731
923
|
} catch (err) {
|
|
732
924
|
$('status').textContent = 'disconnected';
|
|
@@ -756,6 +948,9 @@
|
|
|
756
948
|
<span>iter ${r.iter}</span>
|
|
757
949
|
<span>↑${r.push_log_count}</span>
|
|
758
950
|
</div>
|
|
951
|
+
<div class="lifecycle-actions">
|
|
952
|
+
<button data-act="delete">Delete</button>
|
|
953
|
+
</div>
|
|
759
954
|
</li>`;
|
|
760
955
|
} else if (state.tab === 'council') {
|
|
761
956
|
return `
|
|
@@ -801,6 +996,30 @@
|
|
|
801
996
|
ev.stopPropagation();
|
|
802
997
|
const action = btn.dataset.act;
|
|
803
998
|
const id = el.dataset.id;
|
|
999
|
+
if (state.tab === 'autoloop') {
|
|
1000
|
+
if (action !== 'delete') return;
|
|
1001
|
+
if (!confirm(`Delete autoloop run "${id}"? The registry entry is removed; the ledger directory under tasks/${id}/ is kept on disk.`)) return;
|
|
1002
|
+
try {
|
|
1003
|
+
const r = await fetch(`/autoloop/${encodeURIComponent(id)}/delete`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
|
|
1004
|
+
if (!r.ok) {
|
|
1005
|
+
let detail = `HTTP ${r.status}`;
|
|
1006
|
+
try { const j = await r.json(); if (j && j.error) detail = j.error; } catch { /* ignore */ }
|
|
1007
|
+
alert(`Delete failed: ${detail}`);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
// If we were viewing this run, close the detail pane and pick another.
|
|
1011
|
+
if (state.selectedId === id) {
|
|
1012
|
+
closeStreams();
|
|
1013
|
+
state.selectedId = null;
|
|
1014
|
+
$('main').innerHTML = '<div class="empty">Select a run from the left.</div>';
|
|
1015
|
+
}
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
alert(`Delete failed: ${(err && err.message) || err}`);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
refreshList();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
804
1023
|
if (action === 'delete' && !confirm('Delete this run? Container, image, and on-disk artifacts are removed.')) return;
|
|
805
1024
|
await forgeLifecycleAction(id, action);
|
|
806
1025
|
refreshList();
|
|
@@ -1272,6 +1491,57 @@
|
|
|
1272
1491
|
state.autoloop.pushLog = logJ.entries || [];
|
|
1273
1492
|
paintPushLog();
|
|
1274
1493
|
}
|
|
1494
|
+
// Replay chat history from <ledger>/chat.jsonl so a refresh /
|
|
1495
|
+
// reopening a terminated run shows the prior conversation. New
|
|
1496
|
+
// entries that arrive via SSE (planner_reply) get appended on top.
|
|
1497
|
+
const histR = await fetch(`/autoloop/${encodeURIComponent(id)}/chat_history`);
|
|
1498
|
+
if (histR.ok) {
|
|
1499
|
+
const histJ = await histR.json();
|
|
1500
|
+
if (histJ.ok && Array.isArray(histJ.entries)) {
|
|
1501
|
+
state.autoloop.plannerLog = histJ.entries
|
|
1502
|
+
.filter((e) => e && typeof e.text === 'string')
|
|
1503
|
+
.map((e) => ({ who: e.who || 'planner', text: e.text, ts: e.ts || '' }));
|
|
1504
|
+
paintPlanner();
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
// For terminated runs, surface a Resume affordance under the
|
|
1508
|
+
// top bar. The button kicks off /autoloop/<id>/resume and then
|
|
1509
|
+
// re-runs openAutoloop so SSE reconnects with the live state.
|
|
1510
|
+
if (state.autoloop.state && state.autoloop.state.status === 'terminated') {
|
|
1511
|
+
const bar = document.querySelector('.topbar');
|
|
1512
|
+
if (bar && !document.getElementById('auto-resume')) {
|
|
1513
|
+
const btn = document.createElement('button');
|
|
1514
|
+
btn.id = 'auto-resume';
|
|
1515
|
+
btn.textContent = 'Resume run';
|
|
1516
|
+
btn.style.cssText =
|
|
1517
|
+
'padding:6px 12px;border:1px solid var(--border);background:var(--accent);color:#fff;border-radius:6px;cursor:pointer;font:inherit;font-weight:600;margin-left:auto';
|
|
1518
|
+
btn.addEventListener('click', async () => {
|
|
1519
|
+
btn.disabled = true;
|
|
1520
|
+
btn.textContent = 'Resuming…';
|
|
1521
|
+
try {
|
|
1522
|
+
const r2 = await fetch(`/autoloop/${encodeURIComponent(id)}/resume`, {
|
|
1523
|
+
method: 'POST',
|
|
1524
|
+
headers: { 'content-type': 'application/json' },
|
|
1525
|
+
body: '{}',
|
|
1526
|
+
});
|
|
1527
|
+
if (!r2.ok) {
|
|
1528
|
+
const j2 = await r2.json().catch(() => ({}));
|
|
1529
|
+
alert(`Resume failed: ${(j2 && j2.error) || `HTTP ${r2.status}`}`);
|
|
1530
|
+
btn.disabled = false;
|
|
1531
|
+
btn.textContent = 'Resume run';
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
closeStreams();
|
|
1535
|
+
openAutoloop(id);
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
alert(`Resume failed: ${(err && err.message) || err}`);
|
|
1538
|
+
btn.disabled = false;
|
|
1539
|
+
btn.textContent = 'Resume run';
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
bar.appendChild(btn);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1275
1545
|
} catch (err) {
|
|
1276
1546
|
/* ignore */
|
|
1277
1547
|
}
|
|
@@ -1291,9 +1561,21 @@
|
|
|
1291
1561
|
});
|
|
1292
1562
|
es.addEventListener('planner_reply', (e) => {
|
|
1293
1563
|
const { text } = JSON.parse(e.data);
|
|
1564
|
+
// Clear the "waiting…" placeholder, if any, before appending the reply.
|
|
1565
|
+
state.autoloop.plannerLog = state.autoloop.plannerLog.filter((m) => m.who !== 'pending');
|
|
1294
1566
|
state.autoloop.plannerLog.push({ who: 'planner', text, ts: new Date().toISOString() });
|
|
1295
1567
|
paintPlanner();
|
|
1296
1568
|
});
|
|
1569
|
+
es.addEventListener('planner_error', (e) => {
|
|
1570
|
+
const { message } = JSON.parse(e.data);
|
|
1571
|
+
state.autoloop.plannerLog = state.autoloop.plannerLog.filter((m) => m.who !== 'pending');
|
|
1572
|
+
state.autoloop.plannerLog.push({
|
|
1573
|
+
who: 'system',
|
|
1574
|
+
text: `[planner error] ${message}`,
|
|
1575
|
+
ts: new Date().toISOString(),
|
|
1576
|
+
});
|
|
1577
|
+
paintPlanner();
|
|
1578
|
+
});
|
|
1297
1579
|
es.addEventListener('coder_reply', (e) => {
|
|
1298
1580
|
const { text } = JSON.parse(e.data);
|
|
1299
1581
|
state.autoloop.coderLog.push({ who: 'coder', text, ts: new Date().toISOString() });
|
|
@@ -1442,16 +1724,47 @@
|
|
|
1442
1724
|
const btn = $('auto-send');
|
|
1443
1725
|
btn.disabled = true;
|
|
1444
1726
|
ta.disabled = true;
|
|
1727
|
+
// Clear input optimistically — the message is already echoed in the
|
|
1728
|
+
// planner log below, and the new server contract is fire-and-forget
|
|
1729
|
+
// (returns 202 immediately; reply streams via SSE). If the POST fails
|
|
1730
|
+
// synchronously we surface the error in the log.
|
|
1731
|
+
ta.value = '';
|
|
1445
1732
|
state.autoloop.plannerLog.push({ who: 'user', text, ts: new Date().toISOString() });
|
|
1733
|
+
state.autoloop.plannerLog.push({
|
|
1734
|
+
who: 'pending',
|
|
1735
|
+
text: 'Planner is thinking…',
|
|
1736
|
+
ts: new Date().toISOString(),
|
|
1737
|
+
});
|
|
1446
1738
|
paintPlanner();
|
|
1447
1739
|
try {
|
|
1448
|
-
await fetch(
|
|
1740
|
+
const r = await fetch(`/autoloop/${encodeURIComponent(id)}/chat`, {
|
|
1449
1741
|
method: 'POST',
|
|
1450
1742
|
headers: { 'content-type': 'application/json' },
|
|
1451
|
-
body: JSON.stringify({
|
|
1743
|
+
body: JSON.stringify({ text }),
|
|
1452
1744
|
});
|
|
1453
|
-
|
|
1745
|
+
// Server returns 202 on accepted (queued); 200 is also fine for
|
|
1746
|
+
// legacy clients. Anything else is a real failure.
|
|
1747
|
+
if (!r.ok) {
|
|
1748
|
+
let detail = `HTTP ${r.status}`;
|
|
1749
|
+
try {
|
|
1750
|
+
const j = await r.json();
|
|
1751
|
+
if (j && typeof j.error === 'string') detail = j.error;
|
|
1752
|
+
} catch {
|
|
1753
|
+
/* non-JSON body — keep the status code */
|
|
1754
|
+
}
|
|
1755
|
+
state.autoloop.plannerLog = state.autoloop.plannerLog.filter((m) => m.who !== 'pending');
|
|
1756
|
+
state.autoloop.plannerLog.push({
|
|
1757
|
+
who: 'system',
|
|
1758
|
+
text: `[error] ${detail}`,
|
|
1759
|
+
ts: new Date().toISOString(),
|
|
1760
|
+
});
|
|
1761
|
+
paintPlanner();
|
|
1762
|
+
}
|
|
1763
|
+
// On success, do nothing — the planner_reply / planner_error SSE
|
|
1764
|
+
// handler upstairs clears the "thinking…" placeholder when the
|
|
1765
|
+
// Planner actually replies (or errors).
|
|
1454
1766
|
} catch (err) {
|
|
1767
|
+
state.autoloop.plannerLog = state.autoloop.plannerLog.filter((m) => m.who !== 'pending');
|
|
1455
1768
|
state.autoloop.plannerLog.push({
|
|
1456
1769
|
who: 'system',
|
|
1457
1770
|
text: `[error] ${(err && err.message) || 'send failed'}`,
|
|
@@ -1604,7 +1917,98 @@
|
|
|
1604
1917
|
el.scrollTop = el.scrollHeight;
|
|
1605
1918
|
}
|
|
1606
1919
|
|
|
1920
|
+
// ─── Modal launcher (Council / Autoloop) ─────────────────────
|
|
1921
|
+
//
|
|
1922
|
+
// forgeNew uses a different shape (no form: ultraapp is interview-
|
|
1923
|
+
// driven, so POST /ultraapp/new with an empty body is enough). For
|
|
1924
|
+
// Council / Autoloop we need real form input; one modal handles
|
|
1925
|
+
// both, switching visible fields by state.tab.
|
|
1926
|
+
function openLauncherModal() {
|
|
1927
|
+
const root = $('modalRoot');
|
|
1928
|
+
$('modalTitle').textContent =
|
|
1929
|
+
state.tab === 'council' ? 'New Council session' : 'New Autoloop run';
|
|
1930
|
+
root.querySelectorAll('[data-for-tab]').forEach((el) => {
|
|
1931
|
+
const visible = el.dataset.forTab === state.tab;
|
|
1932
|
+
el.style.display = visible ? 'contents' : 'none';
|
|
1933
|
+
// Only the visible group's required fields actually count.
|
|
1934
|
+
el.querySelectorAll('input,textarea').forEach((f) => {
|
|
1935
|
+
if (visible && (f.name === 'task' || f.name === 'projectDir' || f.name === 'workspace')) {
|
|
1936
|
+
f.required = true;
|
|
1937
|
+
} else {
|
|
1938
|
+
f.required = false;
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
});
|
|
1942
|
+
root.hidden = false;
|
|
1943
|
+
setTimeout(() => {
|
|
1944
|
+
const firstVisible = root.querySelector(
|
|
1945
|
+
`[data-for-tab="${state.tab}"] input, [data-for-tab="${state.tab}"] textarea`,
|
|
1946
|
+
);
|
|
1947
|
+
firstVisible?.focus();
|
|
1948
|
+
}, 0);
|
|
1949
|
+
}
|
|
1950
|
+
function closeLauncherModal() {
|
|
1951
|
+
$('modalRoot').hidden = true;
|
|
1952
|
+
$('modalForm').reset();
|
|
1953
|
+
}
|
|
1954
|
+
$('councilNew').addEventListener('click', openLauncherModal);
|
|
1955
|
+
$('autoloopNew').addEventListener('click', openLauncherModal);
|
|
1956
|
+
$('modalClose').addEventListener('click', closeLauncherModal);
|
|
1957
|
+
$('modalCancel').addEventListener('click', closeLauncherModal);
|
|
1958
|
+
$('modalRoot').addEventListener('click', (e) => {
|
|
1959
|
+
if (e.target.id === 'modalRoot') closeLauncherModal();
|
|
1960
|
+
});
|
|
1961
|
+
document.addEventListener('keydown', (e) => {
|
|
1962
|
+
if (e.key === 'Escape' && !$('modalRoot').hidden) closeLauncherModal();
|
|
1963
|
+
});
|
|
1964
|
+
$('modalForm').addEventListener('submit', async (e) => {
|
|
1965
|
+
e.preventDefault();
|
|
1966
|
+
const fd = new FormData(e.target);
|
|
1967
|
+
const body = {};
|
|
1968
|
+
if (state.tab === 'council') {
|
|
1969
|
+
body.task = (fd.get('task') || '').toString().trim();
|
|
1970
|
+
body.projectDir = (fd.get('projectDir') || '').toString().trim();
|
|
1971
|
+
const mr = (fd.get('maxRounds') || '').toString().trim();
|
|
1972
|
+
if (mr) body.maxRounds = Number(mr);
|
|
1973
|
+
} else if (state.tab === 'autoloop') {
|
|
1974
|
+
body.workspace = (fd.get('workspace') || '').toString().trim();
|
|
1975
|
+
const pm = (fd.get('planner_model') || '').toString().trim();
|
|
1976
|
+
if (pm) body.planner_model = pm;
|
|
1977
|
+
}
|
|
1978
|
+
const url = state.tab === 'council' ? '/council/new' : '/autoloop/new';
|
|
1979
|
+
const submitBtn = $('modalSubmit');
|
|
1980
|
+
submitBtn.disabled = true;
|
|
1981
|
+
submitBtn.textContent = 'Starting...';
|
|
1982
|
+
try {
|
|
1983
|
+
const r = await fetch(url, {
|
|
1984
|
+
method: 'POST',
|
|
1985
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1986
|
+
body: JSON.stringify(body),
|
|
1987
|
+
});
|
|
1988
|
+
const j = await r.json();
|
|
1989
|
+
if (!j.ok) {
|
|
1990
|
+
alert('Failed: ' + (j.error || `HTTP ${r.status}`));
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
const id = j.id ?? j.run_id;
|
|
1994
|
+
closeLauncherModal();
|
|
1995
|
+
await refreshList();
|
|
1996
|
+
state.selectedId = id;
|
|
1997
|
+
renderList();
|
|
1998
|
+
openDetail(id);
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
alert('Failed: ' + err.message);
|
|
2001
|
+
} finally {
|
|
2002
|
+
submitBtn.disabled = false;
|
|
2003
|
+
submitBtn.textContent = 'Start';
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
|
|
1607
2007
|
// ─── Boot ────────────────────────────────────────────────────
|
|
2008
|
+
// Make the right "+ New" button visible for the default tab.
|
|
2009
|
+
$('autoloopNew').hidden = state.tab !== 'autoloop';
|
|
2010
|
+
$('councilNew').hidden = state.tab !== 'council';
|
|
2011
|
+
$('forgeNew').hidden = state.tab !== 'forge';
|
|
1608
2012
|
refreshList();
|
|
1609
2013
|
</script>
|
|
1610
2014
|
</body>
|
|
@@ -18,6 +18,13 @@ export declare class EmbeddedServer {
|
|
|
18
18
|
private host;
|
|
19
19
|
constructor(manager: SessionManager, port?: number, host?: string);
|
|
20
20
|
private _checkRateLimit;
|
|
21
|
+
/**
|
|
22
|
+
* Try to reuse the token persisted by a previous server. Returns null if
|
|
23
|
+
* the file is absent, unreadable, the wrong shape, or the wrong perms.
|
|
24
|
+
* We accept any 32+ hex-char string (the format crypto.randomBytes(32)
|
|
25
|
+
* produces) — anything else is treated as garbage and rotated out.
|
|
26
|
+
*/
|
|
27
|
+
private _readPersistedToken;
|
|
21
28
|
private _writeTokenFile;
|
|
22
29
|
start(): Promise<number>;
|
|
23
30
|
stop(): Promise<void>;
|