@galda/cli 0.10.65 → 0.10.67
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 +11 -8
- package/engine/lib.mjs +10 -7
- package/engine/server.mjs +30 -9
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -4365,7 +4365,7 @@ function renderStream(){
|
|
|
4365
4365
|
if (e.key !== 'Enter' || e.isComposing || !inp.value.trim()) return;
|
|
4366
4366
|
const text = inp.value.trim(); inp.value = ''; inp.disabled = true;
|
|
4367
4367
|
const r = await fetch(withKey(`/api/goals/${inp.dataset.goal}/reply`), { method: 'POST',
|
|
4368
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
4368
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
4369
4369
|
if (!r.ok) showErr('Reply failed — the goal may not have started yet.');
|
|
4370
4370
|
inp.disabled = false;
|
|
4371
4371
|
});
|
|
@@ -4948,7 +4948,7 @@ function openGoalheadReply(id, btn){
|
|
|
4948
4948
|
const snd = document.createElement('button'); snd.className = 'qbtn ok'; snd.textContent = '↑';
|
|
4949
4949
|
wrap.appendChild(inp); wrap.appendChild(snd); head.insertAdjacentElement('afterend', wrap); inp.focus();
|
|
4950
4950
|
const send = async () => { const t = inp.value.trim(); if (!t) return;
|
|
4951
|
-
const r = await fetch(withKey(`/api/goals/${id}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: t }) });
|
|
4951
|
+
const r = await fetch(withKey(`/api/goals/${id}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: t })) });
|
|
4952
4952
|
if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh(); };
|
|
4953
4953
|
inp.onkeydown = (e) => { if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); send(); } if (e.key === 'Escape') wrap.remove(); };
|
|
4954
4954
|
snd.onclick = send;
|
|
@@ -5405,7 +5405,7 @@ function renderReviewChecklist(){
|
|
|
5405
5405
|
b.disabled = true; input.disabled = true;
|
|
5406
5406
|
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST',
|
|
5407
5407
|
headers: { 'content-type': 'application/json' },
|
|
5408
|
-
body: JSON.stringify({ text: `[Fix request for requirement #${t?.num ?? ''} "${t?.title ?? ''}"] ${text}` }) });
|
|
5408
|
+
body: JSON.stringify(replyWorkerPayload({ text: `[Fix request for requirement #${t?.num ?? ''} "${t?.title ?? ''}"] ${text}` })) });
|
|
5409
5409
|
if (r.ok) { input.value = ''; input.placeholder = 'Sent — the worker will pick it up'; }
|
|
5410
5410
|
else showErr('Failed to send the fix request.');
|
|
5411
5411
|
b.disabled = false; input.disabled = false;
|
|
@@ -5649,9 +5649,12 @@ const REPLY_CHOICE_LABEL = { continue: 'Fix this', rescope: 'Rethink it', split:
|
|
|
5649
5649
|
// is the task/workflow vocabulary and this is neither (CDO §5).
|
|
5650
5650
|
const REPLY_WAIT_ROUTING = 'Thinking…';
|
|
5651
5651
|
const REPLY_WAIT_ANSWERING = 'Answering…';
|
|
5652
|
+
function replyWorkerPayload(extra = {}){
|
|
5653
|
+
return { ...extra, agent: state.connectedApp, model: state.stickyModel, effort: state.stickyEffort };
|
|
5654
|
+
}
|
|
5652
5655
|
async function postReply(goalId, text, outcome = 'auto'){
|
|
5653
5656
|
const r = await fetch(withKey(`/api/goals/${goalId}/dismiss`), { method: 'POST',
|
|
5654
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text, outcome }) });
|
|
5657
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text, outcome })) });
|
|
5655
5658
|
if (!r.ok) throw new Error();
|
|
5656
5659
|
return r.json();
|
|
5657
5660
|
}
|
|
@@ -6139,7 +6142,7 @@ async function sendGoalInstruction(goalId, text){
|
|
|
6139
6142
|
$('gdSend').disabled = true; $('gdDiscard').disabled = true;
|
|
6140
6143
|
try {
|
|
6141
6144
|
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST',
|
|
6142
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
6145
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
6143
6146
|
if (!r.ok) throw new Error();
|
|
6144
6147
|
upsert(state.tasks, await r.json());
|
|
6145
6148
|
const g = state.goals.find((x) => x.id === goalId);
|
|
@@ -7095,7 +7098,7 @@ function renderFsTodo(){
|
|
|
7095
7098
|
// hover-toolbar actions → same endpoints as the SL1 goalhead toolbar
|
|
7096
7099
|
for (const b of el.querySelectorAll('[data-fsreply]')) b.onclick = (e) => { e.stopPropagation();
|
|
7097
7100
|
fsInlineInput(b.closest('.trow'), '', 'Reply to this goal…', async (text) => {
|
|
7098
|
-
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreply}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
7101
|
+
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreply}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
7099
7102
|
if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh();
|
|
7100
7103
|
}); };
|
|
7101
7104
|
for (const b of el.querySelectorAll('[data-fsedit]')) b.onclick = (e) => { e.stopPropagation();
|
|
@@ -8577,7 +8580,7 @@ async function send(){
|
|
|
8577
8580
|
} else {
|
|
8578
8581
|
// running/partial/failed/interrupted/blocked → a plain follow-up on the same goal.
|
|
8579
8582
|
const r = await fetch(withKey(`/api/goals/${bind.goal.id}/reply`), { method: 'POST',
|
|
8580
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: fullText }) });
|
|
8583
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: fullText })) });
|
|
8581
8584
|
if (!r.ok) throw new Error();
|
|
8582
8585
|
const g = await r.json(); const goal = g.goal ?? g;
|
|
8583
8586
|
if (goal?.id) upsert(state.goals, goal);
|
|
@@ -9781,7 +9784,7 @@ async function saSendReply(it, i, text){
|
|
|
9781
9784
|
it.st = 3; it.rstatus = 'working'; SA.cur = goalId;
|
|
9782
9785
|
saPaintCard(goalId);
|
|
9783
9786
|
try {
|
|
9784
|
-
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: workerText }) });
|
|
9787
|
+
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: workerText })) });
|
|
9785
9788
|
if (!r.ok) throw new Error('reply failed');
|
|
9786
9789
|
if (isAttn) {
|
|
9787
9790
|
// A Needs-you reply picks the goal back up → it belongs in Doing, not lingering in
|
package/engine/lib.mjs
CHANGED
|
@@ -2008,20 +2008,23 @@ export function isPrMerged({ state, mergedAt } = {}) {
|
|
|
2008
2008
|
return state === 'MERGED' || !!mergedAt;
|
|
2009
2009
|
}
|
|
2010
2010
|
|
|
2011
|
+
export function isPrClosed({ state, mergedAt } = {}) {
|
|
2012
|
+
return state === 'CLOSED' && !mergedAt;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2011
2015
|
// Interpret the same `gh pr view` result's reviewDecision field
|
|
2012
2016
|
// (APPROVED / CHANGES_REQUESTED / REVIEW_REQUIRED / null).
|
|
2013
2017
|
export function isPrApproved({ reviewDecision } = {}) {
|
|
2014
2018
|
return reviewDecision === 'APPROVED';
|
|
2015
2019
|
}
|
|
2016
2020
|
|
|
2017
|
-
// A 'review' goal whose PR merged OR
|
|
2018
|
-
//
|
|
2021
|
+
// A 'review' goal whose PR merged OR closed is done; every other goal status
|
|
2022
|
+
// (including an approved but still-open PR) passes through unchanged — this
|
|
2019
2023
|
// never regresses a goal out of 'review' on its own.
|
|
2020
|
-
export function reviewToDoneStatus({ goalStatus, merged }) {
|
|
2021
|
-
//
|
|
2022
|
-
//
|
|
2023
|
-
|
|
2024
|
-
return goalStatus === 'review' && merged ? 'done' : goalStatus;
|
|
2024
|
+
export function reviewToDoneStatus({ goalStatus, merged, closed }) {
|
|
2025
|
+
// Merge and close both end the GitHub review lifecycle. Approval alone does
|
|
2026
|
+
// not: an approved but still-open PR remains actionable in Review.
|
|
2027
|
+
return goalStatus === 'review' && (merged || closed) ? 'done' : goalStatus;
|
|
2025
2028
|
}
|
|
2026
2029
|
|
|
2027
2030
|
// Build the proof PR body (EN + JA, no emoji) and the PROOF.md committed to
|
package/engine/server.mjs
CHANGED
|
@@ -23,6 +23,7 @@ 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
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
|
+
import { isPrClosed } from './lib.mjs';
|
|
26
27
|
import { collectProjectRules } from './lib.mjs';
|
|
27
28
|
import { FALLBACK_CODEX_MODELS, loadCodexModels } from './codex-models.mjs';
|
|
28
29
|
import { CLAUDE_MODELS } from './claude-models.mjs';
|
|
@@ -1400,7 +1401,7 @@ async function syncAllExternal() {
|
|
|
1400
1401
|
}
|
|
1401
1402
|
|
|
1402
1403
|
// Goals awaiting review carry their PR url in goal.pr; poll `gh pr view` for
|
|
1403
|
-
// each and flip 'review' -> 'done' once GitHub reports it merged or
|
|
1404
|
+
// each and flip 'review' -> 'done' once GitHub reports it merged or closed.
|
|
1404
1405
|
function ghPrView(url) {
|
|
1405
1406
|
return new Promise((res) => {
|
|
1406
1407
|
execFile('gh', ['pr', 'view', url, '--json', 'state,mergedAt,reviewDecision'], (e, out, stderr) => {
|
|
@@ -1418,13 +1419,15 @@ async function syncGoalMerges() {
|
|
|
1418
1419
|
if (!raw) return;
|
|
1419
1420
|
let data;
|
|
1420
1421
|
try { data = JSON.parse(raw); } catch { return; }
|
|
1421
|
-
const
|
|
1422
|
+
const merged = isPrMerged(data);
|
|
1423
|
+
const closed = isPrClosed(data);
|
|
1424
|
+
const newStatus = reviewToDoneStatus({ goalStatus: g.status, merged, closed, approved: isPrApproved(data) });
|
|
1422
1425
|
if (newStatus !== g.status) {
|
|
1423
1426
|
g.status = newStatus; saveGoal(g);
|
|
1424
1427
|
// §3/§7: a merged PR is truly terminal (no rework possible) — reclaim the
|
|
1425
1428
|
// per-goal worktree so .manager-wt/goal-<id> doesn't leak forever. (Manual
|
|
1426
1429
|
// Approve stays reworkable via revert, so we don't clean those up here.)
|
|
1427
|
-
if (newStatus === 'done' &&
|
|
1430
|
+
if (newStatus === 'done' && (merged || closed)) {
|
|
1428
1431
|
const project = projects.find((p) => p.id === g.projectId);
|
|
1429
1432
|
if (project) removeGoalWorkDir(g, project);
|
|
1430
1433
|
}
|
|
@@ -1973,6 +1976,19 @@ function resumeProject(projectId) {
|
|
|
1973
1976
|
|
|
1974
1977
|
// Slack風スレッド返信タスクの生成(/api/goals/:id/reply と /api/goals/:id/dismiss で共有)。
|
|
1975
1978
|
// runTask()のtask.reply分岐がgoal.sessionIdをresumeに渡して同じ会話を続ける。
|
|
1979
|
+
function applyReplyWorker(goal, requested = {}) {
|
|
1980
|
+
if (requested.agent == null) return { ok: true };
|
|
1981
|
+
if (!WORKER_AGENTS.includes(requested.agent)) return { ok: false, error: 'unsupported agent' };
|
|
1982
|
+
const agent = workerAgent(requested.agent);
|
|
1983
|
+
if (!AVAILABLE_AGENTS.includes(agent)) return { ok: false, error: `${agent} is not connected on this device` };
|
|
1984
|
+
const switchedAgent = agent !== goal.agent;
|
|
1985
|
+
goal.agent = agent;
|
|
1986
|
+
goal.model = workerModel(agent, requested.model);
|
|
1987
|
+
goal.effort = workerEffort(agent, requested.effort);
|
|
1988
|
+
if (switchedAgent) goal.sessionId = null;
|
|
1989
|
+
return { ok: true };
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1976
1992
|
function queueReplyTask(goal, text, { from = 'manager', via = 'composer' } = {}) {
|
|
1977
1993
|
// Every follow-up on a goal is part of its conversation (§1.1). Recorded here
|
|
1978
1994
|
// rather than at each call site so an internally-generated nudge (auto-retest
|
|
@@ -4522,8 +4538,11 @@ const server = createServer(async (req, res) => {
|
|
|
4522
4538
|
req.on('data', (d) => { body += d; });
|
|
4523
4539
|
req.on('end', () => {
|
|
4524
4540
|
try {
|
|
4525
|
-
const
|
|
4541
|
+
const requested = JSON.parse(body);
|
|
4542
|
+
const { text } = requested;
|
|
4526
4543
|
if (!text?.trim()) return json(res, 400, { error: 'text required' });
|
|
4544
|
+
const worker = applyReplyWorker(goal, requested);
|
|
4545
|
+
if (!worker.ok) return json(res, 409, { error: worker.error });
|
|
4527
4546
|
// Reply picks a parked goal back up into the work queue. 'review' MUST be
|
|
4528
4547
|
// here: a completed goal awaiting your approve/reject is 'review', and a
|
|
4529
4548
|
// reply to it means "not done — keep working" (Masa 2026-07-24: replied to
|
|
@@ -4814,13 +4833,15 @@ async function answerGoalQuestion(goal, question) {
|
|
|
4814
4833
|
let body = '';
|
|
4815
4834
|
req.on('data', (d) => { body += d; });
|
|
4816
4835
|
req.on('end', async () => {
|
|
4817
|
-
let text = '', outcome = 'continue', auto = false;
|
|
4836
|
+
let text = '', outcome = 'continue', auto = false, requested = {};
|
|
4818
4837
|
try {
|
|
4819
|
-
|
|
4820
|
-
text = String(
|
|
4821
|
-
if (REPLY_OUTCOMES.includes(
|
|
4822
|
-
auto =
|
|
4838
|
+
requested = JSON.parse(body || '{}');
|
|
4839
|
+
text = String(requested.text ?? '').trim();
|
|
4840
|
+
if (REPLY_OUTCOMES.includes(requested.outcome)) outcome = requested.outcome;
|
|
4841
|
+
auto = requested.outcome === 'auto';
|
|
4823
4842
|
} catch {}
|
|
4843
|
+
const worker = applyReplyWorker(goal, requested);
|
|
4844
|
+
if (!worker.ok) return json(res, 409, { error: worker.error });
|
|
4824
4845
|
|
|
4825
4846
|
// outcome:'auto' = 決めるのは AI 本人 (§1.4)。人の言葉を1回だけ渡して、返って
|
|
4826
4847
|
// きた行き先のとおりに配線する。我々はキーワードも類似度も見ない。
|
package/package.json
CHANGED