@galda/cli 0.10.93 → 0.10.95
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/engine/lib.mjs +8 -53
- package/engine/server.mjs +115 -39
- package/package.json +1 -1
package/engine/lib.mjs
CHANGED
|
@@ -2486,59 +2486,14 @@ export function dismissGoal({ goalStatus, outcome = 'continue' } = {}) {
|
|
|
2486
2486
|
// remains unsure. Returning unsure is safe because the UI already presents
|
|
2487
2487
|
// these exact choices without moving the goal.
|
|
2488
2488
|
export const REPLY_INTENTS = [...REPLY_OUTCOMES, 'unsure'];
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
'- "split": a separate piece of work that should not touch this one.',
|
|
2498
|
-
'- "answer": a question about the work. Nothing should be built or changed.',
|
|
2499
|
-
'- "unsure": you genuinely cannot tell. Prefer this over guessing.',
|
|
2500
|
-
'Judge the full meaning. A question followed by a requested change is "continue".',
|
|
2501
|
-
'Also decide which project must receive the requested work.',
|
|
2502
|
-
'- Keep targetProjectId equal to the current project for ordinary fixes to the reviewed work.',
|
|
2503
|
-
'- If the current project is design/docs/prototype-only and the reply asks to implement or launch the chosen design in the real product, select the matching production/code project.',
|
|
2504
|
-
'- Never infer a project that is not listed. If the target is ambiguous, use outcome "unsure" and targetProjectId null.',
|
|
2505
|
-
'Output ONLY one line of raw JSON: {"outcome":"continue|rescope|split|answer|unsure","targetProjectId":"listed-id-or-null"}.',
|
|
2506
|
-
'',
|
|
2507
|
-
`Current project: ${currentProject?.id || '(unknown)'}`,
|
|
2508
|
-
'Available projects:',
|
|
2509
|
-
...(projectLines.length ? projectLines : ['- (none)']),
|
|
2510
|
-
'',
|
|
2511
|
-
'The request that was worked on:',
|
|
2512
|
-
String(goalText ?? '').slice(0, 2000),
|
|
2513
|
-
'',
|
|
2514
|
-
'Their reply:',
|
|
2515
|
-
String(reply ?? '').slice(0, 4000),
|
|
2516
|
-
].join('\n');
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
export function parseReplyIntent(raw) {
|
|
2520
|
-
const unread = { outcome: 'unsure', read: false };
|
|
2521
|
-
if (!raw || typeof raw !== 'string') return unread;
|
|
2522
|
-
const m = raw.match(/\{[\s\S]*\}/);
|
|
2523
|
-
if (!m) return unread;
|
|
2524
|
-
let obj;
|
|
2525
|
-
try { obj = JSON.parse(m[0]); } catch { return unread; }
|
|
2526
|
-
if (!REPLY_INTENTS.includes(obj?.outcome)) return unread;
|
|
2527
|
-
const targetProjectId = typeof obj.targetProjectId === 'string' && obj.targetProjectId.trim()
|
|
2528
|
-
? obj.targetProjectId.trim() : null;
|
|
2529
|
-
return { outcome: obj.outcome, targetProjectId, read: true };
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
// 宛先(docs/design/ONE-CONVERSATION.md §1.2)
|
|
2533
|
-
//
|
|
2534
|
-
// チャットで「#12 の件だけど」と書けたら、そのチケット宛て。**決定論で、我々の推測は
|
|
2535
|
-
// ゼロ**。これが要るのは、今まで「同じ話かどうか」を我々の層が文字列の似かた(0.72)で
|
|
2536
|
-
// 当てずっぽうに決めていたから——当たれば黙って合流し、外れれば別のカードが生えた。
|
|
2537
|
-
// 人が番号を書けるなら、推測する理由が無い。
|
|
2538
|
-
//
|
|
2539
|
-
// 最初の `#N` だけを見る。番号が実在しなければ宛先なしとして扱う(=ふつうに新しい
|
|
2540
|
-
// チケットになる)ので、`#` を含むだけの文章がどこかへ黙って吸い込まれることはない。
|
|
2541
|
-
// 本文は削らない: 言われた言葉をそのまま作業AIへ渡す(P0 の「原文100%」と同じ約束)。
|
|
2489
|
+
// buildReplyIntentPrompt / parseReplyIntent lived here: the prompt and parser for a
|
|
2490
|
+
// utility LLM that decided what a Review reply MEANT before the goal's own agent saw
|
|
2491
|
+
// it. They are gone with their caller. Its "answer" bucket — "a question about the
|
|
2492
|
+
// work. Nothing should be built or changed" — swallowed every politely-phrased
|
|
2493
|
+
// Japanese instruction, because there is no other way to ask politely in Japanese.
|
|
2494
|
+
// Claude Code and Codex already tell a question from an instruction; a second opinion
|
|
2495
|
+
// in front of them could only ever be worse-informed. See the `auto` branch of
|
|
2496
|
+
// /dismiss in engine/server.mjs and engine/test/reply-goes-to-the-agent.test.mjs.
|
|
2542
2497
|
export function parseGoalAddress(text) {
|
|
2543
2498
|
// `(?![\w-])` =「番号の直後に英数字やハイフンが続くなら、それは番号ではない」:
|
|
2544
2499
|
// `#1f6f4a`(色) や `#12-3` を宛先と読み違えない。日本語は \w に入らないので
|
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, parseRequestedWorkspaceDir } from './lib.mjs';
|
|
24
|
-
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildReviewAttemptLedger, latestGoalOutcomeTask, buildDirtyWorkspacePrBlock, 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, rebasePreviewUrl, shouldCreateGoalPR, normalizePullRequestUrl, 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, autoFoldReviewsEnabled, parseGitUnifiedDiffLocations, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, classifyPrSafety, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildVerificationInfraError, verificationInfraErrorFrom, 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, isNothingVerifiableForPR, isSuspiciouslyIncompleteDone, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, shouldUsePlannerForGoal, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES,
|
|
24
|
+
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildReviewAttemptLedger, latestGoalOutcomeTask, buildDirtyWorkspacePrBlock, 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, rebasePreviewUrl, shouldCreateGoalPR, normalizePullRequestUrl, 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, autoFoldReviewsEnabled, parseGitUnifiedDiffLocations, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, classifyPrSafety, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildVerificationInfraError, verificationInfraErrorFrom, 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, isNothingVerifiableForPR, isSuspiciouslyIncompleteDone, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, shouldUsePlannerForGoal, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, buildGoalMessage, serializeGoalMessage, parseGoalMessages, computeInternalQualityMetrics, classifyInternalQualityTaskError, resolveWorkspaceMode, goalSessionFor, setGoalAgentSession, switchGoalAgentSession, parseApprovalRequest } from './lib.mjs';
|
|
25
25
|
import { createSerialQueue } from './lib.mjs';
|
|
26
26
|
import { parseRequirementEvidence, unmappedChangedFiles } from './lib.mjs';
|
|
27
27
|
import { isPrClosed } from './lib.mjs';
|
|
@@ -1425,8 +1425,19 @@ function gitStatusPaths(lines = []) {
|
|
|
1425
1425
|
function newAttemptId(goal, task) {
|
|
1426
1426
|
return `goal-${goal?.id ?? 'none'}-task-${task.id}-${Date.now()}-${randomBytes(3).toString('hex')}`;
|
|
1427
1427
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1428
|
+
// `refresh` re-measures a baseline that already exists. Without it the snapshot
|
|
1429
|
+
// is write-once: whatever the shared checkout happened to look like when the
|
|
1430
|
+
// goal's FIRST task started is what classifyPrSafety judges forever. So a goal
|
|
1431
|
+
// that began while some unrelated file was dirty is permanently unable to
|
|
1432
|
+
// produce a PR — "PR creation skipped: this goal started in a dirty shared
|
|
1433
|
+
// workspace" re-emits on every close-out even after the workspace was cleaned
|
|
1434
|
+
// minutes later, and no amount of asking can fix it. Measured on the operator's
|
|
1435
|
+
// instance 2026-07-25..29: goals #729 #733 #731 are all stuck this way.
|
|
1436
|
+
// Re-measuring is only correct while there is no PR yet — once a branch exists
|
|
1437
|
+
// it is the reference, and moving the baseline under it would compare the work
|
|
1438
|
+
// against the wrong starting point.
|
|
1439
|
+
function noteGoalTaskBaseline(goal, project, workDir, before, { refresh = false } = {}) {
|
|
1440
|
+
if (!goal || (goal.prBaseline && !refresh)) return;
|
|
1430
1441
|
const repoRoot = gitSyncOrNull(workDir, ['rev-parse', '--show-toplevel']);
|
|
1431
1442
|
const branch = repoRoot ? gitSyncOrNull(workDir, ['branch', '--show-current']) : null;
|
|
1432
1443
|
const head = repoRoot ? gitSyncOrNull(workDir, ['rev-parse', 'HEAD']) : null;
|
|
@@ -2731,7 +2742,9 @@ async function runTask(task) {
|
|
|
2731
2742
|
task.attemptId = task.attemptId || newAttemptId(goal, task);
|
|
2732
2743
|
if (goal) {
|
|
2733
2744
|
goal.latestAttemptId = task.attemptId;
|
|
2734
|
-
|
|
2745
|
+
// A reply is a fresh attempt: re-measure the workspace so a baseline taken
|
|
2746
|
+
// when the checkout happened to be dirty cannot outlive the mess itself.
|
|
2747
|
+
noteGoalTaskBaseline(goal, project, workDir, before, { refresh: !!task.reply && !goal.pr });
|
|
2735
2748
|
}
|
|
2736
2749
|
sendProcessAct(task, `Checked starting git state (${before.length} changed file${before.length === 1 ? '' : 's'} before this task).`);
|
|
2737
2750
|
// Plan mode makes no edits, so there is nothing to verify — never author a
|
|
@@ -3886,7 +3899,15 @@ async function createGoalPR(goal, project, goalTasks, reviewDefinition = DEFAULT
|
|
|
3886
3899
|
if (!shouldCreateGoalPR(goal, files)) {
|
|
3887
3900
|
goal.pr = null;
|
|
3888
3901
|
goal.prBranch = null;
|
|
3889
|
-
|
|
3902
|
+
// Two different situations used to leave the same silent hole. "No PR was
|
|
3903
|
+
// asked for" is nothing to explain — clear the error. But "a PR WAS asked
|
|
3904
|
+
// for and none exists" is a fact the person needs, and nulling prError here
|
|
3905
|
+
// erased the only field the Review card renders a reason from, so the card
|
|
3906
|
+
// showed a PR-less goal with no explanation at all (goals #725 #751 on the
|
|
3907
|
+
// operator's instance: status done, wantsPR true, pr null, prError empty).
|
|
3908
|
+
goal.prError = goal.wantsPR === true
|
|
3909
|
+
? 'PRを作成できませんでした: 変更されたファイルが1つもありません(実装が行われていない可能性があります)/ Cannot open a PR: this run produced no file changes.'
|
|
3910
|
+
: null;
|
|
3890
3911
|
saveGoal(goal);
|
|
3891
3912
|
goalAct(goal.wantsPR === false
|
|
3892
3913
|
? 'Deliverable is No PR, so PR creation is skipped even though files changed.'
|
|
@@ -5313,6 +5334,20 @@ const server = createServer(async (req, res) => {
|
|
|
5313
5334
|
goal.blocked = null;
|
|
5314
5335
|
saveGoal(goal);
|
|
5315
5336
|
}
|
|
5337
|
+
// "PR出して" has to count here too. This is the route EVERY follow-up reply
|
|
5338
|
+
// takes (the review card switches to it once a card is in conversation) and
|
|
5339
|
+
// the one MCP manager_reply uses, so until now a PR asked for on the second
|
|
5340
|
+
// reply — or from an agent session — could never take effect: the deliverable
|
|
5341
|
+
// was only ever upgraded in /dismiss. Measured on the operator's instance
|
|
5342
|
+
// 2026-07-25..29: 「あれ、prいるよね?」then「PR作成して」(#790) and
|
|
5343
|
+
// 「問題ないのでpr出して!」then「あれ、問題ないのでpr作ってください」(#800) —
|
|
5344
|
+
// both asked twice and got no PR either time.
|
|
5345
|
+
// Upgrade only: this can turn the deliverable on, never off, so a later
|
|
5346
|
+
// unrelated reply cannot silently withdraw a PR the person already asked for.
|
|
5347
|
+
if (resolveWantsPR('auto', text, false) && goal.wantsPR !== true) {
|
|
5348
|
+
goal.wantsPR = true;
|
|
5349
|
+
saveGoal(goal);
|
|
5350
|
+
}
|
|
5316
5351
|
json(res, 200, queueReplyTask(goal, text.trim(), { from: 'you', via: 'thread' }));
|
|
5317
5352
|
} catch { json(res, 400, { error: 'bad json' }); }
|
|
5318
5353
|
});
|
|
@@ -5391,6 +5426,41 @@ const server = createServer(async (req, res) => {
|
|
|
5391
5426
|
return json(res, 200, goal);
|
|
5392
5427
|
}
|
|
5393
5428
|
|
|
5429
|
+
// PR再試行 / Retry PR. The Review card has offered this button all along and it
|
|
5430
|
+
// has never had a route to call — fsRetryPrGoal POSTs here, the server had no
|
|
5431
|
+
// match for it, and the press 404'd and showed an error. So the one repair the
|
|
5432
|
+
// failure message advertises ("commit/stash/revert those unrelated changes,
|
|
5433
|
+
// then rerun") was unreachable from the product: the person cleaned the
|
|
5434
|
+
// workspace and had no way to say "now try again".
|
|
5435
|
+
//
|
|
5436
|
+
// Deliberately narrow — this only re-runs PR creation. It starts no worker and
|
|
5437
|
+
// changes no code, so it is safe to press repeatedly: either the PR appears or
|
|
5438
|
+
// the same honest reason comes back.
|
|
5439
|
+
const retryPrMatch = url.pathname.match(/^\/api\/goals\/(\d+)\/retry-pr$/);
|
|
5440
|
+
if (retryPrMatch && req.method === 'POST') {
|
|
5441
|
+
const goal = goals.find((g) => g.id === Number(retryPrMatch[1]));
|
|
5442
|
+
if (!goal) return json(res, 404, { error: 'goal not found' });
|
|
5443
|
+
if (!requireGoalOwnership(req, res, goal)) return;
|
|
5444
|
+
if (goal.pr) return json(res, 409, { error: 'this goal already has a pull request' });
|
|
5445
|
+
const project = projects.find((p) => p.id === goal.projectId);
|
|
5446
|
+
if (!project) return json(res, 404, { error: 'project not found' });
|
|
5447
|
+
// Asking to retry the PR IS asking for a PR — a goal that was created as
|
|
5448
|
+
// No PR but whose owner now presses this wants one.
|
|
5449
|
+
goal.wantsPR = true;
|
|
5450
|
+
// The frozen dirty-workspace verdict is exactly what this button exists to
|
|
5451
|
+
// clear. Re-measure against the workspace as it is NOW, which is the state
|
|
5452
|
+
// the person just repaired.
|
|
5453
|
+
const wdir = (goalWorkDirs.get(goal.id) && existsSync(goalWorkDirs.get(goal.id))) ? goalWorkDirs.get(goal.id) : project.dir;
|
|
5454
|
+
goal.prBaseline = null;
|
|
5455
|
+
goal.prSafety = null;
|
|
5456
|
+
goal.prError = null;
|
|
5457
|
+
noteGoalTaskBaseline(goal, project, wdir, await gitChanges(wdir));
|
|
5458
|
+
const siblings = tasks.filter((t) => t.goalId === goal.id);
|
|
5459
|
+
await createGoalPR(goal, { ...project, dir: wdir }, siblings, getReviewDefinition(goal.projectId), null);
|
|
5460
|
+
saveGoal(goal);
|
|
5461
|
+
return json(res, 200, { goal, pr: goal.pr ?? null, prError: goal.prError ?? null });
|
|
5462
|
+
}
|
|
5463
|
+
|
|
5394
5464
|
// Archive: retire an UNFINISHED goal (partial/failed/interrupted/blocked)
|
|
5395
5465
|
// cleanly — spawning no rework. DELETE only accepts stacked/pending, and
|
|
5396
5466
|
// dismiss on a partial goal queues a "差し戻し" rework task, so a goal that
|
|
@@ -5522,29 +5592,13 @@ const server = createServer(async (req, res) => {
|
|
|
5522
5592
|
// 「聞いてるだけ」への返事 (ONE-CONVERSATION.md §1.3 answer)。
|
|
5523
5593
|
//
|
|
5524
5594
|
// 答えるのは**作業した AI 本人**=その agent の session を resume する。状況一覧しか
|
|
5525
|
-
//
|
|
5526
|
-
//
|
|
5527
|
-
//
|
|
5528
|
-
//
|
|
5529
|
-
//
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
const currentProject = projects.find((project) => project.id === goal.projectId) ?? null;
|
|
5533
|
-
const r = await runUtility({
|
|
5534
|
-
prompt: buildReplyIntentPrompt({ goalText: goal.text, reply, currentProject, projects }),
|
|
5535
|
-
cwd: ROOT,
|
|
5536
|
-
tools: 'Read',
|
|
5537
|
-
});
|
|
5538
|
-
const parsed = parseReplyIntent(r.result);
|
|
5539
|
-
if (!parsed.read) return { outcome: 'unsure', targetProjectId: null };
|
|
5540
|
-
if (parsed.targetProjectId && !projects.some((project) => project.id === parsed.targetProjectId)) {
|
|
5541
|
-
return { outcome: 'unsure', targetProjectId: null };
|
|
5542
|
-
}
|
|
5543
|
-
return { outcome: parsed.outcome, targetProjectId: parsed.targetProjectId };
|
|
5544
|
-
} catch {
|
|
5545
|
-
return { outcome: 'unsure', targetProjectId: null };
|
|
5546
|
-
}
|
|
5547
|
-
}
|
|
5595
|
+
// judgeReplyIntent lived here: a utility-LLM pass that sorted a Review reply into
|
|
5596
|
+
// continue / rescope / split / answer BEFORE the goal's own agent saw it. It is
|
|
5597
|
+
// gone — see the `auto` branch of /dismiss for the measured reason. Its stated
|
|
5598
|
+
// worry was "accidentally starting a ten-minute implementation worker for a
|
|
5599
|
+
// question", but the trade it actually made was the opposite one: it answered
|
|
5600
|
+
// real instructions, and the person paid several round trips to get the work
|
|
5601
|
+
// started at all. Deciding that is the agent's job.
|
|
5548
5602
|
|
|
5549
5603
|
// 知らない別AIでは「なぜそうしたか」に答えられない(Masa確定 2026-07-22)。
|
|
5550
5604
|
// コードは1文字も変わらない: ツールは Read だけ、permissionMode は 'plan'
|
|
@@ -5608,16 +5662,33 @@ async function answerGoalQuestion(goal, question) {
|
|
|
5608
5662
|
|
|
5609
5663
|
if (auto) {
|
|
5610
5664
|
if (!text) return json(res, 400, { error: 'text required' });
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5665
|
+
// The reply goes to the agent exactly as written. Galda does not decide
|
|
5666
|
+
// first what the person meant.
|
|
5667
|
+
//
|
|
5668
|
+
// This used to call a utility LLM (judgeReplyIntent) to sort the reply
|
|
5669
|
+
// into continue / rescope / split / answer before the agent ever saw it.
|
|
5670
|
+
// Its definition of "answer" was "a question about the work. Nothing
|
|
5671
|
+
// should be built or changed" — and Japanese has no other way to make a
|
|
5672
|
+
// polite request. Measured on the operator's own instance 2026-07-25..29:
|
|
5673
|
+
// 「案3で実装出来ますか?」「Aの方針で実装できますか!?」「あれ、prいるよね?」
|
|
5674
|
+
// 「okなんでprなげれますか?」「案がみえないのでローカルで確認できるように貰える?」
|
|
5675
|
+
// were all real instructions, and every one of them could be read as a
|
|
5676
|
+
// question. When it read one that way it created NO task at all, so the
|
|
5677
|
+
// request was answered instead of done and the person had to ask two or
|
|
5678
|
+
// three more times.
|
|
5679
|
+
//
|
|
5680
|
+
// The fix is not a better classifier. Claude Code and Codex already
|
|
5681
|
+
// decide correctly whether a message wants an answer or a change — that
|
|
5682
|
+
// is what they are. Galda's job is the interface around them, not a
|
|
5683
|
+
// second opinion in front of them. Note the `answer` route spawned an
|
|
5684
|
+
// agent too (Read-only, plan mode), so passing the reply through costs
|
|
5685
|
+
// no extra run — it removes one utility-LLM call per reply.
|
|
5686
|
+
//
|
|
5687
|
+
// Explicit routes are untouched: when the PERSON picks rescope / split /
|
|
5688
|
+
// answer from the chips, `requested.outcome` carries it and this branch
|
|
5689
|
+
// never runs. A human choosing where their words go is the interface
|
|
5690
|
+
// working; Galda guessing is not.
|
|
5691
|
+
outcome = 'continue';
|
|
5621
5692
|
}
|
|
5622
5693
|
// 返事の行き先4種 (ONE-CONVERSATION.md §1.3). 既定は今までどおり continue なので、
|
|
5623
5694
|
// outcome を送ってこない今の UI の挙動は1ミリも変わらない。
|
|
@@ -5677,7 +5748,12 @@ async function answerGoalQuestion(goal, question) {
|
|
|
5677
5748
|
// Manager close-out below owns commit/push/PR creation. Keep this
|
|
5678
5749
|
// monotonic: an explicit later PR request upgrades a No-PR/auto goal,
|
|
5679
5750
|
// while ordinary feedback never silently downgrades an existing PR goal.
|
|
5680
|
-
|
|
5751
|
+
// Not gated on spawnsWorker: what the person wants DELIVERED is independent
|
|
5752
|
+
// of where their words were routed. A PR asked for while parking the goal
|
|
5753
|
+
// (rescope) or while asking a question (answer) is still a PR they asked
|
|
5754
|
+
// for, and it must survive until close-out rather than being dropped
|
|
5755
|
+
// because this particular reply happened not to wake a worker.
|
|
5756
|
+
if (resolveWantsPR('auto', text, false)) goal.wantsPR = true;
|
|
5681
5757
|
goal.status = result.status;
|
|
5682
5758
|
saveGoal(goal);
|
|
5683
5759
|
// rescope はワーカーを起こさない——「いったん止めて考え直す」なので、走り出したら
|
package/package.json
CHANGED