@galda/cli 0.10.78 → 0.10.80
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/app/index.html +62 -26
- package/engine/server.mjs +5 -1
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -3595,6 +3595,12 @@ function reviewCardConfigFor(projectId){ return { ...DEFAULT_REVIEW_DEFINITION.r
|
|
|
3595
3595
|
// goal's own request language, same as the planner/worker; ja/en pin the
|
|
3596
3596
|
// project to one language regardless of what a given goal was typed in.
|
|
3597
3597
|
function reviewLangFor(projectId){ const l = reviewDefinitionFor(projectId).language; return (l === 'en' || l === 'ja') ? l : 'auto'; }
|
|
3598
|
+
function retryCopy(projectId){
|
|
3599
|
+
const ja = reviewLangFor(projectId) === 'ja';
|
|
3600
|
+
return ja
|
|
3601
|
+
? { infra: '検証基盤エラー', retry: '再検証', retrying: '再検証中…', failed: '再検証に失敗' }
|
|
3602
|
+
: { infra: 'Verification infrastructure error', retry: 'Retry verification', retrying: 'Retrying…', failed: 'Verification retry failed' };
|
|
3603
|
+
}
|
|
3598
3604
|
|
|
3599
3605
|
function esc(s){ const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
|
3600
3606
|
|
|
@@ -4636,7 +4642,7 @@ function renderTasks(){
|
|
|
4636
4642
|
const rs = g.reviewSummary ?? {};
|
|
4637
4643
|
const checkLine = (rs.check || g.plan?.[0] || g.text || '').replace(/\s+/g, ' ').slice(0, 58);
|
|
4638
4644
|
return `<div class="task revrow" data-goal="${g.id}"><div class="ic fail">!</div><div class="tx"><div class="name">${esc(checkLine)} ${goalSourceBadge(g.source)}</div>
|
|
4639
|
-
<div class="cond"><span class="blockreason">${esc(g.blocked?.reason ?? 'blocked')}</span> <button type="button" class="retrybtn" data-goalreverify="${g.id}"
|
|
4645
|
+
<div class="cond"><span class="blockreason">${esc(g.blocked?.reason ?? 'blocked')}</span> <button type="button" class="retrybtn" data-goalreverify="${g.id}">${retryCopy(g.projectId).retry}</button> <button type="button" class="retrybtn skip" data-archivegoal="${g.id}" title="Archive without rework">Archive</button></div></div></div>`;
|
|
4640
4646
|
}).join('');
|
|
4641
4647
|
const attention = blockedGoals + list.filter((t) => ['failed', 'interrupted'].includes(t.status)).map((t) => row(t, { retry: 1 })).join('');
|
|
4642
4648
|
const doing = grouped(list.filter((t) => t.status === 'running'), (t) => row(t));
|
|
@@ -4833,10 +4839,10 @@ function renderTasks(){
|
|
|
4833
4839
|
}
|
|
4834
4840
|
// Manager-verified gate: re-run tests + re-capture proof for a blocked goal.
|
|
4835
4841
|
for (const b of $('tasklist').querySelectorAll('[data-goalreverify]')) {
|
|
4836
|
-
b.onclick = async (e) => { e.stopPropagation(); b.disabled = true; b.textContent =
|
|
4842
|
+
b.onclick = async (e) => { e.stopPropagation(); const copy = retryCopy(state.active); b.disabled = true; b.textContent = copy.retrying;
|
|
4837
4843
|
let r; try { r = await fetch(withKey(`/api/goals/${b.dataset.goalreverify}/reverify`), { method: 'POST' }); }
|
|
4838
|
-
catch { showErr(
|
|
4839
|
-
qrefresh(r,
|
|
4844
|
+
catch { showErr(copy.failed); return; }
|
|
4845
|
+
qrefresh(r, copy.failed); };
|
|
4840
4846
|
}
|
|
4841
4847
|
for (const b of $('tasklist').querySelectorAll('[data-qreply]')) {
|
|
4842
4848
|
b.onclick = (e) => { e.stopPropagation();
|
|
@@ -5404,7 +5410,7 @@ function renderReviewChecklist(){
|
|
|
5404
5410
|
: '';
|
|
5405
5411
|
const infra = goal.verificationInfraError;
|
|
5406
5412
|
const infraHtml = infra?.detail
|
|
5407
|
-
? `<div class="rvinfra"><span class="rvlbl">${esc(infra.title ||
|
|
5413
|
+
? `<div class="rvinfra"><span class="rvlbl">${esc(infra.title || retryCopy(goal.projectId).infra)}</span>${esc(infra.detail)} <button type="button" class="retrybtn" data-goalreverify="${goal.id}">${retryCopy(goal.projectId).retry}</button></div>`
|
|
5408
5414
|
: '';
|
|
5409
5415
|
$('reviewSummary').innerHTML =
|
|
5410
5416
|
vfyHtml
|
|
@@ -5477,10 +5483,10 @@ function renderReviewChecklist(){
|
|
|
5477
5483
|
b.onclick = async (e) => {
|
|
5478
5484
|
e.stopPropagation();
|
|
5479
5485
|
b.disabled = true;
|
|
5480
|
-
b.textContent =
|
|
5486
|
+
b.textContent = retryCopy(goal.projectId).retrying;
|
|
5481
5487
|
let r; try { r = await fetch(withKey(`/api/goals/${b.dataset.goalreverify}/reverify`), { method: 'POST' }); }
|
|
5482
|
-
catch { showErr(
|
|
5483
|
-
if (r.ok) await refresh(); else showErr(
|
|
5488
|
+
catch { showErr(retryCopy(goal.projectId).failed); b.disabled = false; return; }
|
|
5489
|
+
if (r.ok) await refresh(); else showErr(retryCopy(goal.projectId).failed);
|
|
5484
5490
|
};
|
|
5485
5491
|
}
|
|
5486
5492
|
for (const b of $('reviewList').querySelectorAll('.reqsend')) {
|
|
@@ -6822,7 +6828,10 @@ function renderFsBoard(){
|
|
|
6822
6828
|
// errored is status-based now (blocked/failed/partial/interrupted live in the Needs-you column,
|
|
6823
6829
|
// not Doing). Doing holds only what's actively moving, so bkt==='doing' ⇒ healthy in flight.
|
|
6824
6830
|
const errored = ['blocked', 'failed', 'partial', 'interrupted'].includes(g.status);
|
|
6825
|
-
|
|
6831
|
+
// `partial` can mean an earlier step completed and the latest one was
|
|
6832
|
+
// interrupted. Read the concrete task so this card doesn't turn amber,
|
|
6833
|
+
// resumable work into a red generic failure.
|
|
6834
|
+
const intr = g.status === 'interrupted' || list.some((t) => t.goalId === g.id && t.status === 'interrupted');
|
|
6826
6835
|
const isPaused = g.status === 'blocked' && g.blocked?.kind === 'paused'; // 一時停止=エラーでなくアンバー扱い
|
|
6827
6836
|
const amber = intr || isPaused;
|
|
6828
6837
|
const healthyRun = bkt === 'doing' && !errored; // paused(=errored) goals now sit in 'doing' too but carry no progress bar
|
|
@@ -7155,7 +7164,10 @@ function errorButtonsHtml(meta, { taskId = null, goalId = null, interrupted = fa
|
|
|
7155
7164
|
const out = [];
|
|
7156
7165
|
for (const id of meta.actionIds || []) {
|
|
7157
7166
|
if (id === 'retry' && taskId != null) out.push(`<button class="errbtn" data-fsretry="${taskId}">${interrupted ? C.resume : C.retry}</button>`);
|
|
7158
|
-
|
|
7167
|
+
// This keeps the existing agent-switch endpoint, but says what it really does
|
|
7168
|
+
// for an interrupted run: continue the saved goal with another agent rather
|
|
7169
|
+
// than imply a brand-new, contextless task will be created.
|
|
7170
|
+
else if (id === 'run-with-codex' && canCodex) out.push(`<button class="errbtn" data-fscodex="${goalId}">${interrupted ? (failureLang() === 'ja' ? 'Codexで再開' : 'Resume with Codex') : C.other}</button>`);
|
|
7159
7171
|
else if (id === 'connect') out.push(`<button class="errbtn" data-fsconnectworker="1">${C.connect}</button>`);
|
|
7160
7172
|
else if (id === 'check-connection') out.push(`<button class="errbtn" data-fscheckworker="1">${C.check}</button>`);
|
|
7161
7173
|
else if (id === 'retry-tests' && goalId != null) out.push(`<button class="errbtn" data-fsreverify="${goalId}">${C.tests}</button>`);
|
|
@@ -7291,10 +7303,6 @@ function renderFsTodo(){
|
|
|
7291
7303
|
const needsCount = failed.filter((t) => !resumedGoalIds.has(t.goalId)).length + blocked.length + needsInput.length;
|
|
7292
7304
|
const doingCount = running.length + finalizingGoals.length + pausedGoalIds.size;
|
|
7293
7305
|
const nextCount = parkedReplyGoals.length + queued.length;
|
|
7294
|
-
const hasPipelineWork = doingCount > 0 || nextCount > 0 || pgoals.some((g) => ['planning', 'stacked'].includes(g.status));
|
|
7295
|
-
const laterIdle = later.length && !hasPipelineWork
|
|
7296
|
-
? `<div class="lateridle"><span class="msg">${document.documentElement.lang === 'ja' ? `実行待ちはありません。Laterの${later.length}件は自動開始されません。` : `Nothing is queued. ${later.length} Later item${later.length === 1 ? '' : 's'} will not start automatically.`}</span><button data-fsstartnextlater="${later[0].id}">${document.documentElement.lang === 'ja' ? '次を開始' : 'Start next'}</button></div>`
|
|
7297
|
-
: '';
|
|
7298
7306
|
// Free-tier pending counter (H22): on the free plan the header shows the wall
|
|
7299
7307
|
// — pending goals out of the 5-slot cap — reddening at the cap and gone once
|
|
7300
7308
|
// paid. Reads the SAME numbers the gate enforces (state.billing, never a local
|
|
@@ -7316,7 +7324,6 @@ function renderFsTodo(){
|
|
|
7316
7324
|
<div class="qadd" data-fsnew="1" role="button" tabindex="0" title="New goal"><span class="paplus"></span><span>New</span></div>
|
|
7317
7325
|
<span class="tlcap">Later</span>
|
|
7318
7326
|
<div class="qadd lateradd" data-fslaternew="1" role="button" tabindex="0" title="New Later goal"><span class="paplus"></span><span>New</span></div>
|
|
7319
|
-
${laterIdle}
|
|
7320
7327
|
${laterRows}
|
|
7321
7328
|
</div>
|
|
7322
7329
|
${doneAll.length ? `<div class="doneline${fsUI.doneOpen ? ' open' : ''}" id="fsDoneLine">✓ Done<span class="num">${doneAll.length}</span><span class="fold">▾</span></div>
|
|
@@ -7381,13 +7388,6 @@ function renderFsTodo(){
|
|
|
7381
7388
|
let r; try { r = await fetch(withKey(`/api/goals/${b.dataset.fspstart}/activate`), { method: 'POST' }); }
|
|
7382
7389
|
catch { showErr('Start failed.'); return; }
|
|
7383
7390
|
if (!r.ok) showErr('Start failed.'); else await refresh(); };
|
|
7384
|
-
const startNextLater = el.querySelector('[data-fsstartnextlater]');
|
|
7385
|
-
if (startNextLater) startNextLater.onclick = async (e) => { e.stopPropagation();
|
|
7386
|
-
startNextLater.disabled = true;
|
|
7387
|
-
let r; try { r = await fetch(withKey(`/api/goals/${startNextLater.dataset.fsstartnextlater}/activate`), { method: 'POST' }); }
|
|
7388
|
-
catch { showErr(document.documentElement.lang === 'ja' ? '開始できませんでした' : 'Could not start the next item.'); startNextLater.disabled = false; return; }
|
|
7389
|
-
if (!r.ok) { showErr(document.documentElement.lang === 'ja' ? '開始できませんでした' : 'Could not start the next item.'); startNextLater.disabled = false; }
|
|
7390
|
-
else await refresh(); };
|
|
7391
7391
|
for (const b of el.querySelectorAll('[data-fspdel]')) b.onclick = async (e) => { e.stopPropagation();
|
|
7392
7392
|
const preview = b.closest('.trow')?.querySelector('.tt')?.textContent || '';
|
|
7393
7393
|
if (!(await confirmDelete({ title: 'Delete this shelved goal?', preview }))) return;
|
|
@@ -8927,7 +8927,14 @@ async function send(){
|
|
|
8927
8927
|
// review goal → the AI decides continue/rescope/split/answer (same path as the
|
|
8928
8928
|
// review card's own reply box), with the note anchored under the composer.
|
|
8929
8929
|
const body = await replyToGoal(bind.goal.id, fullText, '#input');
|
|
8930
|
-
if (body) {
|
|
8930
|
+
if (body) {
|
|
8931
|
+
upsert(state.goals, body.goal); if (body.task) upsert(state.tasks, body.task);
|
|
8932
|
+
// A Review reply is a one-shot handoff. Keeping the composer bound
|
|
8933
|
+
// afterwards adds a redundant “Replying to #N” banner and makes the
|
|
8934
|
+
// next, unrelated instruction easy to send to the old goal by mistake.
|
|
8935
|
+
state.selectedGoal = null;
|
|
8936
|
+
render();
|
|
8937
|
+
}
|
|
8931
8938
|
} else {
|
|
8932
8939
|
// running/partial/failed/interrupted/blocked → a plain follow-up on the same goal.
|
|
8933
8940
|
const r = await fetch(withKey(`/api/goals/${bind.goal.id}/reply`), { method: 'POST',
|
|
@@ -9172,6 +9179,11 @@ function latestGoalRunForOutcome(goalId, tasks, outcome){
|
|
|
9172
9179
|
function saRowFromDigest(r, gs, ts){
|
|
9173
9180
|
const g = gs.find((x) => x.id === r.goalIds[0]);
|
|
9174
9181
|
const outcome = latestGoalOutcomeTask(g?.id, ts);
|
|
9182
|
+
// A timeout commonly settles its parent goal as `partial`: some earlier work
|
|
9183
|
+
// may be real, while the final task is retryable. Preserve the task-level truth
|
|
9184
|
+
// here so the card never calls that state a generic failure.
|
|
9185
|
+
const interruptedTask = [...ts].filter((t) => r.goalIds.includes(t.goalId) && t.status === 'interrupted')
|
|
9186
|
+
.sort((a, b) => taskSortTime(b) - taskSortTime(a))[0] || null;
|
|
9175
9187
|
const pt = ts.find((t) => r.goalIds.includes(t.goalId) && t.proof && (t.proof.png || t.proof.gif));
|
|
9176
9188
|
// "What we did" (H23 §2, replaces the old Plan/Review-thread folds): one bullet
|
|
9177
9189
|
// per finished requirement task's own report; falls back to the goal's plan
|
|
@@ -9201,6 +9213,7 @@ function saRowFromDigest(r, gs, ts){
|
|
|
9201
9213
|
.map((x) => ({ id: x.id, title: saReviewTitle(x) }));
|
|
9202
9214
|
return {
|
|
9203
9215
|
ids: r.goalIds, goalId: r.goalIds[0], goal: g, folded,
|
|
9216
|
+
interruptedTask,
|
|
9204
9217
|
no: r.reviewNumber,
|
|
9205
9218
|
title: saReviewTitle(g),
|
|
9206
9219
|
check: String(g?.reviewSummary?.check || r.checkLine || '').replace(/\s+/g, ' ').trim(),
|
|
@@ -9727,10 +9740,27 @@ function saItemHtml(it, i){
|
|
|
9727
9740
|
? `<div class="ropen rdeclerr"><p class="rbody v">${failureLang() === 'ja' ? 'AIが開けるものを用意しようとしましたが、記録が読み取れませんでした。返信して直させてください。' : 'The AI tried to declare something to open here, but the record was unreadable. Reply to have it rewritten.'}</p></div>`
|
|
9728
9741
|
: '';
|
|
9729
9742
|
const openBody = (it.run?.cmd || it.run?.url)
|
|
9730
|
-
? `<div class="ropen" id="runlocal${i}"><button type="button" class="olink" data-saact="preview:${i}" id="prevlink${i}">${esc(it.run.url || 'Start it')}<span class="ar">↗</span></button><p class="rbody v">${esc(it.run.note || it.run.cmd || 'Starts when you press it.')}</p></div>`
|
|
9743
|
+
? `<div class="ropen" id="runlocal${i}"><button type="button" class="olink" data-saact="preview:${i}" id="prevlink${i}">${esc(it.run.url || 'Start it')}<span class="ar">↗</span></button><p class="rbody v">${esc(it.run.note || it.run.cmd || 'Starts when you press it.')}${it.interruptedTask ? ' Available now; it does not wait for this run to finish.' : ''}</p></div>`
|
|
9731
9744
|
: it.pr
|
|
9732
9745
|
? `<div class="ropen"><a class="olink" href="${esc(it.pr)}" target="_blank" rel="noopener">${esc(it.pr)}<span class="ar">↗</span></a><p class="rbody v">Review and merge this pull request to finish the item.</p></div>`
|
|
9733
9746
|
: runDeclErrorBody;
|
|
9747
|
+
// An interrupted worker is not a failed implementation. Keep the recovery
|
|
9748
|
+
// record factual and compact: the last recorded step, files already changed,
|
|
9749
|
+
// whether verification remains, and the exact resume action. These are all
|
|
9750
|
+
// state fields — no Manager-generated diagnosis or task re-planning.
|
|
9751
|
+
const interruptedBody = it.interruptedTask ? (() => {
|
|
9752
|
+
const t = it.interruptedTask;
|
|
9753
|
+
const last = (state.act[t.id] ?? t.activity ?? []).filter(Boolean).at(-1);
|
|
9754
|
+
const files = [...new Set((t.changedFiles ?? []).concat(filePaths))];
|
|
9755
|
+
const tests = it.testResult?.ran
|
|
9756
|
+
? (it.testResult.ok ? 'Verification completed before the interruption.' : 'Verification remains incomplete or failed.')
|
|
9757
|
+
: 'Verification has not completed yet.';
|
|
9758
|
+
const reason = String(t.result || 'The worker reached its run limit before finishing.').replace(/\s+/g, ' ').slice(0, 240);
|
|
9759
|
+
return `<div class="rinfra"><p class="rbody v"><b>Interrupted — resume available</b><br>${esc(reason)}</p>`
|
|
9760
|
+
+ (last ? `<p class="rbody v">Last recorded step: ${esc(String(last).slice(0, 220))}</p>` : '')
|
|
9761
|
+
+ (files.length ? `<p class="rbody v">Changed so far: ${esc(files.join(' · '))}</p>` : '')
|
|
9762
|
+
+ `<p class="rbody v">${esc(tests)}</p><div class="acts2"><button class="errbtn" data-saact="resume:${i}">Resume</button>${errorButtonsHtml(taskErrorMeta(t, it.goal), { taskId: null, goalId: it.goalId, interrupted: true, agent: t.agent || it.goal?.agent })}</div></div>`;
|
|
9763
|
+
})() : '';
|
|
9734
9764
|
const prRepairBody = it.goal?.prError
|
|
9735
9765
|
? `<div class="prfail"><b>${esc(it.goal.prRepair?.title || 'PR blocked')}</b> <span class="dim">${esc(it.goal.prError)}</span>${it.goal.prRepair?.steps?.length ? `<ul>${it.goal.prRepair.steps.map((s) => `<li>${esc(s)}</li>`).join('')}</ul>` : ''}${it.goal.prRepair?.dirtyFiles?.length ? `<div class="dim">dirty: ${it.goal.prRepair.dirtyFiles.map(esc).join(' · ')}</div>` : ''}<div class="acts2"><button class="errbtn" data-saact="retrypr:${i}">${failureLang() === 'ja' ? 'PR再試行' : 'Retry PR'}</button><button class="errbtn" data-saact="glog:${i}">${failureLang() === 'ja' ? 'ログ確認' : 'View log'}</button></div></div>`
|
|
9736
9766
|
: '';
|
|
@@ -9893,8 +9923,8 @@ function saItemHtml(it, i){
|
|
|
9893
9923
|
const attn = !working && ['blocked', 'partial', 'failed', 'interrupted'].includes(gstatus);
|
|
9894
9924
|
// Match the flagship list's state colours (Masa's Interrupt=amber / Failed=red rule): an
|
|
9895
9925
|
// interrupted goal is amber+resumable, everything else stopped is a red error.
|
|
9896
|
-
const attnInt = gstatus === 'interrupted';
|
|
9897
|
-
const attnLabel =
|
|
9926
|
+
const attnInt = gstatus === 'interrupted' || Boolean(it.interruptedTask);
|
|
9927
|
+
const attnLabel = attnInt ? 'Interrupted — resume available' : gstatus === 'blocked' ? 'Blocked' : 'Failed';
|
|
9898
9928
|
const rst = working
|
|
9899
9929
|
? '<span class="rst working"><span class="rdot"></span>Working on it</span>'
|
|
9900
9930
|
: attn
|
|
@@ -9906,6 +9936,7 @@ function saItemHtml(it, i){
|
|
|
9906
9936
|
${meta}
|
|
9907
9937
|
${reqSec}
|
|
9908
9938
|
${S('Open it', openBody)}
|
|
9939
|
+
${S('Run state', interruptedBody)}
|
|
9909
9940
|
${S('PR', prRepairBody)}
|
|
9910
9941
|
${S('Also carries', carriesBody)}
|
|
9911
9942
|
${S('What to check', checkBody)}
|
|
@@ -10390,6 +10421,11 @@ $('seeall').addEventListener('click', async (e) => {
|
|
|
10390
10421
|
}
|
|
10391
10422
|
const it = SA.items[i];
|
|
10392
10423
|
if (!it) return;
|
|
10424
|
+
if (k === 'resume') {
|
|
10425
|
+
const taskId = it.interruptedTask?.id;
|
|
10426
|
+
if (taskId != null) return fsRetryTask(taskId);
|
|
10427
|
+
return;
|
|
10428
|
+
}
|
|
10393
10429
|
if (k === 'retrypr') return fsRetryPrGoal({ disabled: false, textContent: '', dataset: { fsprretry: String(it.goalId) } });
|
|
10394
10430
|
if (k === 'glog') return toggleGoalDetail(it.goalId);
|
|
10395
10431
|
// meta-row toggle (H23): tap the token chip to reveal the technique breakdown.
|
package/engine/server.mjs
CHANGED
|
@@ -2593,7 +2593,11 @@ async function startPreview(task) {
|
|
|
2593
2593
|
// They never need a child process; treating them like a Manager app made
|
|
2594
2594
|
// "Open it" fail even though the HTML artifact was ready to view.
|
|
2595
2595
|
if (!cmd && typeof url === 'string' && url.startsWith('/upload/')) {
|
|
2596
|
-
|
|
2596
|
+
// The card opens this relative URL in a new tab. Carry the manager key in
|
|
2597
|
+
// that URL; otherwise the new navigation loses the authenticated fetch
|
|
2598
|
+
// context and the upload route correctly returns its 403 gate.
|
|
2599
|
+
const keyedUrl = `${url}${url.includes('?') ? '&' : '?'}key=${encodeURIComponent(ACCESS_KEY)}`;
|
|
2600
|
+
return { url: keyedUrl, reused: false, started: false };
|
|
2597
2601
|
}
|
|
2598
2602
|
const live = previews.get(task.id);
|
|
2599
2603
|
if (live && live.proc.exitCode === null) {
|
package/package.json
CHANGED