@galda/cli 0.10.58 → 0.10.60
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 +19 -3
- package/engine/lib.mjs +19 -0
- package/engine/server.mjs +19 -6
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -7055,9 +7055,10 @@ function renderFsTodo(){
|
|
|
7055
7055
|
for (const b of el.querySelectorAll('[data-fslog]')) b.onclick = () => fsToggleLog(Number(b.dataset.fslog));
|
|
7056
7056
|
for (const b of el.querySelectorAll('[data-fsglog]')) b.onclick = () => toggleGoalDetail(Number(b.dataset.fsglog));
|
|
7057
7057
|
for (const b of el.querySelectorAll('[data-fsreverify]')) b.onclick = async () => {
|
|
7058
|
-
b.disabled = true; b.textContent = '
|
|
7058
|
+
b.disabled = true; b.textContent = 'Starting…';
|
|
7059
7059
|
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreverify}/reverify`), { method: 'POST' });
|
|
7060
|
-
if (!r.ok) showErr('
|
|
7060
|
+
if (!r.ok) { showErr(document.documentElement.lang === 'ja' ? 'Retryに失敗しました' : 'Retry failed.'); b.disabled = false; b.textContent = 'Retry'; }
|
|
7061
|
+
else await refresh();
|
|
7061
7062
|
};
|
|
7062
7063
|
for (const b of el.querySelectorAll('[data-fsarchive]')) b.onclick = async () => {
|
|
7063
7064
|
const r = await fetch(withKey(`/api/goals/${b.dataset.fsarchive}/archive`), { method: 'POST' });
|
|
@@ -9452,6 +9453,11 @@ async function saEnhanceShotBoxes(){
|
|
|
9452
9453
|
}
|
|
9453
9454
|
function saRender(){
|
|
9454
9455
|
const S = $('seeall'); if (!S) return;
|
|
9456
|
+
// SSE, diff fetches and action responses rebuild the dialog. Remember the
|
|
9457
|
+
// review body's position before replacing it so reading never jumps to top.
|
|
9458
|
+
const previousBody = S.querySelector('.sa-body');
|
|
9459
|
+
const previousScrollTop = previousBody?.scrollTop ?? 0;
|
|
9460
|
+
const restoreScroll = Boolean(previousBody) && SA.focusGid == null;
|
|
9455
9461
|
const pending = SA.items.filter((x) => x.st === 0);
|
|
9456
9462
|
const green = pending.filter((x) => !x.testResult || x.testResult.ok !== false).length;
|
|
9457
9463
|
// Solo mode = a single "Needs you" goal opened as this card. It is not a review batch, so the
|
|
@@ -9464,6 +9470,16 @@ function saRender(){
|
|
|
9464
9470
|
const hdKeys = solo ? '↑↓ MOVE · ESC CLOSE' : '↑↓ MOVE · A APPROVE · D DISMISS · ESC CLOSE';
|
|
9465
9471
|
S.innerHTML = `<div class="sa-panel"><div class="sa-hd"><span class="t">${hdTitle}</span><span class="c">${c}</span><span class="sa-count">${posLabel}</span><span class="keys">${hdKeys}</span><button class="sa-x" data-saact="close:0">✕</button></div>
|
|
9466
9472
|
<div class="sa-body">${SA.items.length ? SA.items.map((it, i) => saItemHtml(it, i)).join('') : '<div class="sa-empty">Nothing waiting for review.</div>'}</div></div>`;
|
|
9473
|
+
const body = S.querySelector('.sa-body');
|
|
9474
|
+
if (body && restoreScroll) body.scrollTop = previousScrollTop;
|
|
9475
|
+
// Once the person scrolls, their position wins over the short opening-focus
|
|
9476
|
+
// animation. Programmatic opening scroll fires this too, which is fine: the
|
|
9477
|
+
// first placement happened and later SSE ticks should preserve it.
|
|
9478
|
+
body?.addEventListener('scroll', () => {
|
|
9479
|
+
if (SA.focusGid == null) return;
|
|
9480
|
+
SA.focusGid = null;
|
|
9481
|
+
if (SA.focusTimer) { clearTimeout(SA.focusTimer); SA.focusTimer = null; }
|
|
9482
|
+
}, { once: true });
|
|
9467
9483
|
// Native <details> would collapse on every rebuild (SSE tick, approve, etc.) —
|
|
9468
9484
|
// track what's open per goal+section in SA.detOpen (persists across re-renders)
|
|
9469
9485
|
// and wire each one's toggle to keep that set current.
|
|
@@ -9779,7 +9795,7 @@ $('seeall').addEventListener('click', async (e) => {
|
|
|
9779
9795
|
if (link) link.disabled = true;
|
|
9780
9796
|
if (v) v.textContent = 'Starting it…';
|
|
9781
9797
|
let r = null, body = {};
|
|
9782
|
-
try { r = await fetch(`/api/tasks/${it.run.taskId}/preview
|
|
9798
|
+
try { r = await fetch(withKey(`/api/tasks/${it.run.taskId}/preview`), { method: 'POST' }); body = await r.json().catch(() => ({})); }
|
|
9783
9799
|
catch (e) { body = { error: String(e.message ?? e) }; }
|
|
9784
9800
|
if (link) link.disabled = false;
|
|
9785
9801
|
const noteText = it.run.note || it.run.cmd || 'Starts when you press it.';
|
package/engine/lib.mjs
CHANGED
|
@@ -1646,6 +1646,25 @@ export function detectRequestLanguage(text) {
|
|
|
1646
1646
|
return /[-ヿ㐀-鿿]/.test(text || '') ? 'ja' : 'en';
|
|
1647
1647
|
}
|
|
1648
1648
|
|
|
1649
|
+
export function testFailureReason({ text = '', count = 0, attempts = null } = {}) {
|
|
1650
|
+
const n = Number(count) || 0;
|
|
1651
|
+
const tries = attempts == null ? null : Number(attempts) || 0;
|
|
1652
|
+
if (detectRequestLanguage(text) === 'ja') {
|
|
1653
|
+
return tries == null ? `テスト失敗 ${n} 件` : `テスト失敗 ${n} 件(自動修正 ${tries} 回後も失敗)`;
|
|
1654
|
+
}
|
|
1655
|
+
const tests = `${n} test${n === 1 ? '' : 's'} failed`;
|
|
1656
|
+
return tries == null ? tests : `${tests} after ${tries} automatic fix attempt${tries === 1 ? '' : 's'}`;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
export function manualTestRetryPrompt({ text = '', count = 0, detail = '' } = {}) {
|
|
1660
|
+
const n = Number(count) || 0;
|
|
1661
|
+
const output = String(detail || '(no detailed output captured)').slice(0, 3200);
|
|
1662
|
+
if (detectRequestLanguage(text) === 'ja') {
|
|
1663
|
+
return `Retryが押されました。前回のテスト失敗 ${n} 件を確認し、元のタスクの作業環境で原因を修正してからテストを再実行してください。関係のないリファクタは行わないでください。\n\n前回のテスト出力:\n${output}`;
|
|
1664
|
+
}
|
|
1665
|
+
return `Retry was requested. Inspect the previous ${n} failing test${n === 1 ? '' : 's'}, fix the cause in the original task worktree, then rerun the tests. Do not make unrelated changes.\n\nPrevious test output:\n${output}`;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1649
1668
|
// SLICE 2 intent routing (docs/HANDOFF-ONBOARDING-conversational.md): the single
|
|
1650
1669
|
// composer is conversation-first — a chat/help/settings message ("what can you
|
|
1651
1670
|
// do?", "reply in English", a greeting) must get a conversational reply, NOT a
|
package/engine/server.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { homedir } from 'node:os';
|
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { pathToFileURL } from 'node:url';
|
|
23
23
|
import { needsProjectFolder, folderLabel, chooseFolderScript, parseChosenFolder, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer, routeQuestionAnswer, notUnderstoodNote, parseRelocalizedQuestion, classifyAuthProbe, authBannerText, makeSigninChallenge, parseClaimResponse, isCommandOnPath } from './lib.mjs';
|
|
24
|
-
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewSummaryPrompt, parseReviewSummary, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, rejectGoal, reopenGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, resolveTestGate, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, workerPrompt, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, pickFoldTarget, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, QUEUED_GOAL_STATUSES, verifyLicenseToken, licenseTokenPayload, shouldRefreshLicense, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, REPLY_INTENTS, buildReplyIntentPrompt, parseReplyIntent, buildGoalMessage, serializeGoalMessage, parseGoalMessages } from './lib.mjs';
|
|
24
|
+
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewSummaryPrompt, parseReviewSummary, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, rejectGoal, reopenGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, resolveTestGate, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, workerPrompt, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, pickFoldTarget, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, QUEUED_GOAL_STATUSES, verifyLicenseToken, licenseTokenPayload, shouldRefreshLicense, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, testFailureReason, manualTestRetryPrompt, isNothingVerifiable, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, REPLY_INTENTS, buildReplyIntentPrompt, parseReplyIntent, buildGoalMessage, serializeGoalMessage, parseGoalMessages } from './lib.mjs';
|
|
25
25
|
import { createSerialQueue } from './lib.mjs';
|
|
26
26
|
import { collectProjectRules } from './lib.mjs';
|
|
27
27
|
import { FALLBACK_CODEX_MODELS, loadCodexModels } from './codex-models.mjs';
|
|
@@ -3187,7 +3187,7 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
3187
3187
|
if (testsFailing) {
|
|
3188
3188
|
const failure = recordGoalFailure(goal, {
|
|
3189
3189
|
kind: 'test',
|
|
3190
|
-
reason:
|
|
3190
|
+
reason: testFailureReason({ text: goal.text, count: blockingFailCount }),
|
|
3191
3191
|
changedFiles,
|
|
3192
3192
|
testResult: goal.testResult,
|
|
3193
3193
|
});
|
|
@@ -3199,7 +3199,7 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
3199
3199
|
goal.autoRework = {
|
|
3200
3200
|
...(goal.autoRework ?? {}),
|
|
3201
3201
|
testFailures: used + 1,
|
|
3202
|
-
lastReason:
|
|
3202
|
+
lastReason: testFailureReason({ text: goal.text, count: blockingFailCount }),
|
|
3203
3203
|
lastAt: new Date().toISOString(),
|
|
3204
3204
|
};
|
|
3205
3205
|
goal.blocked = null;
|
|
@@ -3245,7 +3245,7 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
3245
3245
|
}
|
|
3246
3246
|
goal.blocked = {
|
|
3247
3247
|
kind: gate.kind,
|
|
3248
|
-
reason: testsFailing ?
|
|
3248
|
+
reason: testsFailing ? testFailureReason({ text: goal.text, count: blockingFailCount, attempts: goal.autoRework?.testFailures ?? 0 })
|
|
3249
3249
|
: nothingVerifiable ? '検証条件つきのタスクなのに、ファイル変更も proof も無い(約束した検証が行われていない)'
|
|
3250
3250
|
: '検証(proof)が全タスク分そろっていない(requireVerifyPass)',
|
|
3251
3251
|
};
|
|
@@ -4538,8 +4538,8 @@ const server = createServer(async (req, res) => {
|
|
|
4538
4538
|
return json(res, 200, goal);
|
|
4539
4539
|
}
|
|
4540
4540
|
|
|
4541
|
-
//
|
|
4542
|
-
//
|
|
4541
|
+
// Retry a test-blocked goal by sending the saved failure back to its worker.
|
|
4542
|
+
// Other blocked states still re-run the verification gate in place.
|
|
4543
4543
|
const reverifyMatch = url.pathname.match(/^\/api\/goals\/(\d+)\/reverify$/);
|
|
4544
4544
|
if (reverifyMatch && req.method === 'POST') {
|
|
4545
4545
|
const goal = goals.find((g) => g.id === Number(reverifyMatch[1]));
|
|
@@ -4548,6 +4548,19 @@ const server = createServer(async (req, res) => {
|
|
|
4548
4548
|
const project = projects.find((p) => p.id === goal.projectId);
|
|
4549
4549
|
if (!project) return json(res, 404, { error: 'project not found' });
|
|
4550
4550
|
const siblings = tasks.filter((t) => t.goalId === goal.id);
|
|
4551
|
+
if (goal.status === 'blocked' && goal.blocked?.kind === 'test') {
|
|
4552
|
+
const count = goal.testResult?.newFailures?.length ?? goal.testResult?.failed ?? 0;
|
|
4553
|
+
const prompt = manualTestRetryPrompt({ text: goal.text, count, detail: goal.testResult?.detail });
|
|
4554
|
+
goal.status = 'running';
|
|
4555
|
+
goal.blocked = null;
|
|
4556
|
+
goal.startedAt = null;
|
|
4557
|
+
// A manual retry is a fresh user decision, independent of the automatic
|
|
4558
|
+
// two-attempt budget. Keep the selected agent/model and same conversation.
|
|
4559
|
+
goal.autoRework = { ...(goal.autoRework ?? {}), testFailures: 0, manualRetryAt: new Date().toISOString() };
|
|
4560
|
+
saveGoal(goal);
|
|
4561
|
+
const task = queueReplyTask(goal, prompt, { from: 'manager', via: 'retry' });
|
|
4562
|
+
return json(res, 200, { goal, task, repairing: true });
|
|
4563
|
+
}
|
|
4551
4564
|
const wdir = (goalWorkDirs.get(goal.id) && existsSync(goalWorkDirs.get(goal.id))) ? goalWorkDirs.get(goal.id) : project.dir;
|
|
4552
4565
|
// 再検証 is the explicit human "run the tests now" — force the gate on for this
|
|
4553
4566
|
// one goal even when the default (automatic) gate is off.
|
package/package.json
CHANGED