input-kanban 0.0.8 → 0.0.9
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/LICENSE +21 -0
- package/PROJECT_GUIDE.md +114 -4
- package/README.en.md +11 -5
- package/README.md +11 -5
- package/RELEASE_NOTES.md +3 -1
- package/bin/input-kanban.js +256 -40
- package/package.json +4 -2
- package/public/index.html +74 -3
- package/src/orchestrator.js +358 -175
- package/src/server.js +8 -4
- package/src/utils.js +5 -0
package/public/index.html
CHANGED
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
.modal-backdrop.hidden { display: none; }
|
|
120
120
|
.modal-card { width: min(760px, 100%); border: 1px solid var(--line); border-radius: 14px; background: var(--panel); box-shadow: 0 18px 60px rgba(0,0,0,.38); padding: 16px; }
|
|
121
121
|
.modal-card textarea { min-height: 220px; }
|
|
122
|
+
.page-footer { padding: 0 18px 18px; color: var(--muted); text-align: center; font-size: 12px; }
|
|
122
123
|
</style>
|
|
123
124
|
</head>
|
|
124
125
|
<body>
|
|
@@ -186,6 +187,7 @@
|
|
|
186
187
|
</section>
|
|
187
188
|
</div>
|
|
188
189
|
</main>
|
|
190
|
+
<footer id="pageFooter" class="page-footer">版本:-</footer>
|
|
189
191
|
<div id="manualCompleteModal" class="modal-backdrop hidden">
|
|
190
192
|
<div class="modal-card">
|
|
191
193
|
<h2>手动标记成功</h2>
|
|
@@ -205,12 +207,17 @@ let selectedTask = null;
|
|
|
205
207
|
let selectedFileName = null;
|
|
206
208
|
let manualCompleteTaskId = null;
|
|
207
209
|
let pendingArchiveRunId = null;
|
|
210
|
+
const autoDispatchingRuns = new Set();
|
|
211
|
+
const autoJudgingRuns = new Set();
|
|
212
|
+
const autoRetryingRuns = new Set();
|
|
213
|
+
const autoRetrySkippedRuns = new Set();
|
|
208
214
|
let currentState = null;
|
|
209
215
|
let lastAutoRefreshAt = null;
|
|
210
216
|
let runListVisibleCount = 10;
|
|
211
217
|
let latestRuns = [];
|
|
212
218
|
const statusByRunId = new Map();
|
|
213
219
|
const AUTO_REFRESH_MS = 3000;
|
|
220
|
+
const AUTO_MAX_RETRIES = 1;
|
|
214
221
|
const RUN_LIST_PAGE_SIZE = 10;
|
|
215
222
|
|
|
216
223
|
async function api(path, opts={}) {
|
|
@@ -330,6 +337,7 @@ async function loadHealth() {
|
|
|
330
337
|
const h = await api('/api/health');
|
|
331
338
|
document.getElementById('repo').value = h.defaultRepo;
|
|
332
339
|
document.getElementById('runsDir').value = h.runsDir;
|
|
340
|
+
document.getElementById('pageFooter').textContent = h.version ? `版本:v${h.version}` : '版本:未知(请重启服务)';
|
|
333
341
|
}
|
|
334
342
|
function showCreateForm() {
|
|
335
343
|
selectedRun = null; selectedTask = null; selectedFileName = null; currentState = null;
|
|
@@ -357,6 +365,7 @@ async function refreshRuns() {
|
|
|
357
365
|
const data = await api('/api/runs');
|
|
358
366
|
latestRuns = data.runs || [];
|
|
359
367
|
renderRunList();
|
|
368
|
+
await maybeAutoAdvanceRunSummaries(latestRuns);
|
|
360
369
|
}
|
|
361
370
|
function renderRunList() {
|
|
362
371
|
const visibleRuns = latestRuns.slice(0, runListVisibleCount);
|
|
@@ -382,7 +391,7 @@ function showMoreRuns() {
|
|
|
382
391
|
renderRunList();
|
|
383
392
|
}
|
|
384
393
|
async function selectRun(id) { selectedRun = id; selectedTask = null; selectedFileName = null; clearFileView(); hideCreateForm(); await refreshSelected(); }
|
|
385
|
-
async function refreshSelected({auto=false} = {}) {
|
|
394
|
+
async function refreshSelected({auto=false, skipAutoAdvance=false} = {}) {
|
|
386
395
|
if (!selectedRun) return;
|
|
387
396
|
currentState = await api(`/api/runs/${selectedRun}/status`);
|
|
388
397
|
statusByRunId.set(selectedRun, currentState);
|
|
@@ -394,6 +403,7 @@ async function refreshSelected({auto=false} = {}) {
|
|
|
394
403
|
await loadTaskDescription();
|
|
395
404
|
renderTasks(); await refreshRuns();
|
|
396
405
|
if (selectedTask && selectedFileName) await loadFile(selectedFileName, { preserveScroll: true });
|
|
406
|
+
if (!skipAutoAdvance) await maybeAutoAdvanceSelectedRun();
|
|
397
407
|
}
|
|
398
408
|
function renderSelectedHeader() {
|
|
399
409
|
if (!currentState) return '<div class="muted">未选择任务批次</div>';
|
|
@@ -463,9 +473,9 @@ function updateAutoRefreshHint() {
|
|
|
463
473
|
const el = document.getElementById('autoRefreshHint');
|
|
464
474
|
if (!el) return;
|
|
465
475
|
if (!selectedRun || !currentState) { el.textContent = '自动刷新:未启动'; return; }
|
|
466
|
-
const active = ['planning','running','judging'].includes(currentState.status) || (currentState.tasks || []).some(t => t.status === 'running') || currentState.planner?.status === 'running' || currentState.judge?.status === 'running';
|
|
476
|
+
const active = ['planning','running','judging','planned','batches_completed','batch_blocked'].includes(currentState.status) || (currentState.tasks || []).some(t => t.status === 'running') || currentState.planner?.status === 'running' || currentState.judge?.status === 'running';
|
|
467
477
|
const last = lastAutoRefreshAt ? lastAutoRefreshAt.toLocaleTimeString() : '尚未触发';
|
|
468
|
-
el.textContent = active ?
|
|
478
|
+
el.textContent = active ? `自动模式中:每 ${AUTO_REFRESH_MS / 1000} 秒刷新并推进一次|上次刷新 ${last}` : `自动模式待命:每 ${AUTO_REFRESH_MS / 1000} 秒检查一次|上次刷新 ${last}`;
|
|
469
479
|
}
|
|
470
480
|
function taskStatusCell(t) {
|
|
471
481
|
if (t?.manualCompletion) {
|
|
@@ -802,6 +812,67 @@ async function renameRunLabel(event, runId = selectedRun) {
|
|
|
802
812
|
else await refreshRuns();
|
|
803
813
|
});
|
|
804
814
|
}
|
|
815
|
+
async function maybeAutoAdvanceRunSummaries(runs) {
|
|
816
|
+
for (const run of runs || []) {
|
|
817
|
+
if (!run || run.runId === selectedRun) continue;
|
|
818
|
+
if (run.status !== 'batch_blocked') autoRetrySkippedRuns.delete(run.runId);
|
|
819
|
+
if (run.status === 'planned') await autoDispatchRun(run.runId, false);
|
|
820
|
+
else if (run.status === 'batches_completed') await autoJudgeRun(run.runId, false);
|
|
821
|
+
else if (run.status === 'batch_blocked') await autoRetryRun(run.runId, false);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
async function autoDispatchRun(runId, refreshSelectedAfter = true) {
|
|
825
|
+
if (!runId || autoDispatchingRuns.has(runId)) return false;
|
|
826
|
+
autoDispatchingRuns.add(runId);
|
|
827
|
+
try {
|
|
828
|
+
await api(`/api/runs/${runId}/dispatch`, {method:'POST'});
|
|
829
|
+
if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
|
|
830
|
+
return true;
|
|
831
|
+
} catch (error) {
|
|
832
|
+
console.error('自动派发失败', error);
|
|
833
|
+
return false;
|
|
834
|
+
} finally {
|
|
835
|
+
autoDispatchingRuns.delete(runId);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async function autoJudgeRun(runId, refreshSelectedAfter = true) {
|
|
839
|
+
if (!runId || autoJudgingRuns.has(runId)) return false;
|
|
840
|
+
autoJudgingRuns.add(runId);
|
|
841
|
+
try {
|
|
842
|
+
await api(`/api/runs/${runId}/judge`, {method:'POST'});
|
|
843
|
+
if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
|
|
844
|
+
return true;
|
|
845
|
+
} catch (error) {
|
|
846
|
+
console.error('自动验收失败', error);
|
|
847
|
+
return false;
|
|
848
|
+
} finally {
|
|
849
|
+
autoJudgingRuns.delete(runId);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async function autoRetryRun(runId, refreshSelectedAfter = true) {
|
|
853
|
+
if (!runId || autoRetryingRuns.has(runId) || autoRetrySkippedRuns.has(runId)) return false;
|
|
854
|
+
autoRetryingRuns.add(runId);
|
|
855
|
+
try {
|
|
856
|
+
await api(`/api/runs/${runId}/retry`, {method:'POST', body: JSON.stringify({ reason: 'auto retry from dashboard', maxRetries: AUTO_MAX_RETRIES, auto: true })});
|
|
857
|
+
if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
|
|
858
|
+
return true;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
autoRetrySkippedRuns.add(runId);
|
|
861
|
+
console.error('自动重试失败', error);
|
|
862
|
+
return false;
|
|
863
|
+
} finally {
|
|
864
|
+
autoRetryingRuns.delete(runId);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
async function maybeAutoAdvanceSelectedRun() {
|
|
868
|
+
const runId = selectedRun;
|
|
869
|
+
const state = currentState;
|
|
870
|
+
if (!runId || !state) return;
|
|
871
|
+
if (state.status !== 'batch_blocked') autoRetrySkippedRuns.delete(runId);
|
|
872
|
+
if (state.status === 'planned') await autoDispatchRun(runId);
|
|
873
|
+
else if (state.status === 'batches_completed' && state.judge?.status !== 'running' && state.judge?.status !== 'completed') await autoJudgeRun(runId);
|
|
874
|
+
else if (state.status === 'batch_blocked') await autoRetryRun(runId);
|
|
875
|
+
}
|
|
805
876
|
async function planRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/plan`, {method:'POST'}); await refreshSelected(); }); }
|
|
806
877
|
async function dispatchRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/dispatch`, {method:'POST'}); await refreshSelected(); }); }
|
|
807
878
|
async function judgeRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/judge`, {method:'POST'}); await refreshSelected(); }); }
|