@galda/cli 0.10.75 → 0.10.77

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 CHANGED
@@ -733,6 +733,12 @@
733
733
  @keyframes saFinishDraw{to{stroke-dashoffset:0}}
734
734
  @keyframes saFinishFade{0%,72%{opacity:1}100%{opacity:0}}
735
735
  @media (prefers-reduced-motion:reduce){.sa-finish,.sa-finish-mark,.sa-finish-mark path{animation:none}.sa-finish{background:transparent}}
736
+ /* A PR-less approval is just as complete as a merged PR. Keep this transient
737
+ celebration fixed above the board so it never changes document height or
738
+ scroll position. */
739
+ .approve-finish{position:fixed;inset:0;z-index:10000;display:grid;place-items:center;pointer-events:none;
740
+ background:rgba(0,0,0,.28);backdrop-filter:blur(2px);animation:saFinishFade .9s ease both}
741
+ .approve-finish .sa-finish-mark{width:112px;height:112px}
736
742
 
737
743
  /* connect modal */
738
744
  /* A confirm dialog (deleteConfirmOverlay etc.) must always sit above other
@@ -3354,12 +3360,14 @@ const withKey = (u) => {
3354
3360
  };
3355
3361
 
3356
3362
  // Theme names v3 (§6.5): English registered names only. Migrate persisted values
3357
- // (old internal codenames → v3 names; dark→midnight; archived glass→gradient default).
3363
+ // (old internal codenames → v3 names; dark→midnight; archived glass→midnight default).
3358
3364
  const THEMES = ['studio', 'gradient', 'banff', 'midnight', 'cinema'];
3359
3365
  const THEME_MIGRATE = { w4: 'studio', arc: 'gradient', nature: 'banff', glow: 'midnight', comp: 'cinema', dark: 'midnight', glass: 'gradient' };
3360
3366
  const savedTheme = (() => {
3361
3367
  const s = localStorage.getItem('theme');
3362
- const v = THEME_MIGRATE[s] ?? (THEMES.includes(s) ? s : 'gradient');
3368
+ // New installations start in Midnight. Existing valid values are preserved;
3369
+ // only a missing/invalid persisted value takes this default.
3370
+ const v = THEME_MIGRATE[s] ?? (THEMES.includes(s) ? s : 'midnight');
3363
3371
  if (v !== s) localStorage.setItem('theme', v);
3364
3372
  return v;
3365
3373
  })();
@@ -5592,6 +5600,20 @@ function finishOpenReview(goal){
5592
5600
  const delay = reduce ? 350 : 950;
5593
5601
  setTimeout(() => { layer.remove(); saClose(); render(); }, delay);
5594
5602
  }
5603
+ // PR-less review approval: use the same high-signal green completion mark as
5604
+ // the merged-PR path, but do not close or reflow the review surface. The canvas
5605
+ // burst starts at the actual Approve control, making the reward feel causal.
5606
+ function celebrateDirectApprove(cx, cy){
5607
+ const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
5608
+ if (!reduce) confettiBurst(cx, cy);
5609
+ const layer = document.createElement('div');
5610
+ layer.className = 'approve-finish';
5611
+ layer.setAttribute('role', 'status');
5612
+ layer.setAttribute('aria-label', 'Approved');
5613
+ layer.innerHTML = '<div class="sa-finish-mark"><svg viewBox="0 0 64 64" aria-hidden="true"><path d="M17 33l10 10 21-23"/></svg></div>';
5614
+ document.body.appendChild(layer);
5615
+ setTimeout(() => layer.remove(), reduce ? 350 : 950);
5616
+ }
5595
5617
  async function setRequirementStatus(goalId, taskId, status){
5596
5618
  const approvals = (state.reviewApprovals[goalId] ??= {});
5597
5619
  approvals[taskId] = approvals[taskId] === status ? undefined : status; // click again to undo
@@ -8702,6 +8724,11 @@ async function postOutboxItem(item){
8702
8724
  render();
8703
8725
  return; // finally still runs posting.delete(); skip land()/upsert()
8704
8726
  }
8727
+ // This optimistic turn was only for the short interval before the server
8728
+ // classified the input. A real goal is represented by the To Do lane and
8729
+ // its live Implementing log, never as a second message below the composer.
8730
+ const turns = state.chatTurns[item.projectId];
8731
+ if (turns) state.chatTurns[item.projectId] = turns.filter((turn) => turn.oid !== item.oid);
8705
8732
  goal.imageUrls = item.imageUrls;
8706
8733
  outbox.land(item.oid, goal.id);
8707
8734
  upsert(state.goals, goal);
@@ -8875,8 +8902,9 @@ async function send(){
8875
8902
  const fullText = batt.length
8876
8903
  ? `${text}${text ? '\n\n' : ''}添付画像(workerはこのパスをReadで確認できます): ${batt.map((a) => a.path).join(', ')}`
8877
8904
  : text;
8878
- // same housekeeping as a normal send: the words appear in the feed at once.
8879
- (state.chatTurns[state.active] ||= []).push({ role: 'you', text, ts: Date.now() });
8905
+ // A bound send belongs to this goal's persisted thread. Do not also add it
8906
+ // to the center conversation feed: that made a task reply appear below the
8907
+ // composer as an unrelated "You" turn.
8880
8908
  state.attach = [];
8881
8909
  state.msg = { mode: null, model: null, effort: null, skill: null, later: false, review: false };
8882
8910
  renderMsgHint();
@@ -8925,9 +8953,10 @@ async function send(){
8925
8953
  status: 'sending', ts: Date.now(),
8926
8954
  };
8927
8955
  outbox.add(item); // saved locally BEFORE any network call
8928
- // Pattern A (H23): the user's own turn enters the center feed immediately, so
8929
- // the composer reads like a conversation; if it's chat, a reply turn follows.
8930
- (state.chatTurns[state.active] ||= []).push({ role: 'you', text, ts: Date.now() });
8956
+ // The server decides whether this is a conversation or a real task. Show an
8957
+ // optimistic conversation turn while that decision is pending, then remove it
8958
+ // if the request becomes a task (tasks belong in To Do / Implementing only).
8959
+ (state.chatTurns[state.active] ||= []).push({ role: 'you', text, oid: item.oid, ts: Date.now() });
8931
8960
  state.attach = [];
8932
8961
  // mode/skill/later/review are per-message → clear after send (model stays sticky).
8933
8962
  state.msg = { mode: null, model: null, effort: null, skill: null, later: false, review: false };
@@ -10406,7 +10435,7 @@ $('seeall').addEventListener('click', async (e) => {
10406
10435
  // instead of celebrating/advancing; saCall already upserted the still-'review' goal.
10407
10436
  const g = state.goals.find((x) => x.id === it.goalId);
10408
10437
  if (g && g.status === 'review' && g.pr) { confirmMerge(g); return; }
10409
- confettiBurst(bx.left + bx.width / 2, bx.top + bx.height / 2); saAdvanceFocus(it.goalId); }); }
10438
+ celebrateDirectApprove(bx.left + bx.width / 2, bx.top + bx.height / 2); saAdvanceFocus(it.goalId); }); }
10410
10439
  if (k === 'dis') return saAct(it, 2, 'reject').then((ok) => { if (ok) saAdvanceFocus(it.goalId); }); // 07-20c: Reject → Rejected column
10411
10440
  if (k === 'arch') return saAct(it, 4, 'archive').then((ok) => { if (ok) saAdvanceFocus(it.goalId); });
10412
10441
  if (k === 'rev') return saAct(it, 5, 'revert').then((ok) => { if (ok) saAdvanceFocus(it.goalId); });
@@ -10469,7 +10498,7 @@ document.addEventListener('keydown', (e) => {
10469
10498
  if (g && g.status === 'review' && g.pr) { confirmMerge(g); return; } // 07-20c: PR goal → merge dialog
10470
10499
  const el = $('seeall').querySelector(`.sa-item[data-gid="${gid}"] .sa-app`);
10471
10500
  const box = el?.getBoundingClientRect();
10472
- confettiBurst(box ? box.left + box.width / 2 : innerWidth / 2, box ? box.top + box.height / 2 : innerHeight / 3);
10501
+ celebrateDirectApprove(box ? box.left + box.width / 2 : innerWidth / 2, box ? box.top + box.height / 2 : innerHeight / 3);
10473
10502
  saAdvanceFocus(gid);
10474
10503
  });
10475
10504
  } else saAct(it, 2, 'reject').then((ok) => { if (ok) saAdvanceFocus(gid); }); // 07-20c: D = Reject
package/engine/lib.mjs CHANGED
@@ -1306,6 +1306,10 @@ export function looksIncomplete(text) {
1306
1306
  export function isSuspiciouslyIncompleteDone({ status, changedFiles = [], result = '', hasVerdict = false } = {}) {
1307
1307
  if (status !== 'done' || hasVerdict) return false;
1308
1308
  if ((changedFiles ?? []).length > 0) return false;
1309
+ // A worker that only asks for permission to start a local server did not
1310
+ // produce a locally checkable result. Treating its clean exit as success
1311
+ // re-opened the old PR/review summary and hid this real blocker (#788).
1312
+ if (/\b(permission|approve running|approval required)\b|許可.*(必要|ください)|実行してよろしい/i.test(String(result))) return true;
1309
1313
  return looksIncomplete(result);
1310
1314
  }
1311
1315
 
@@ -2382,65 +2386,6 @@ export function dismissGoal({ goalStatus, outcome = 'continue' } = {}) {
2382
2386
  return { ok: true, outcome: 'continue', status: 'running', spawnsWorker: true };
2383
2387
  }
2384
2388
 
2385
- // 誰が決めるか=**AI本人** (docs/design/ONE-CONVERSATION.md §1.4)
2386
- //
2387
- // 返事が来た時、それが「このまま直して」なのか「考え直したい」なのか「別件」なのか
2388
- // 「ただ聞いただけ」なのかを、**我々の層で当てない**。キーワードでも類似度でもなく、
2389
- // 人の言葉を1回だけ AI に渡して、返ってきた答えのとおりに配線する。0.72 の自動合流を
2390
- // 消したのと同じ理由——この層が人の意図を推測し始めると、外れた時に黙って別のことを
2391
- // する(今日そこを1つずつ潰してきた)。
2392
- //
2393
- // 迷った時だけ人に聞く: AI が unsure と答えたら**ゴールに触らず**選択肢を返す。人は
2394
- // モードを普段選ばない。迷った時だけ選ぶ。
2395
- export const REPLY_INTENTS = [...REPLY_OUTCOMES, 'unsure'];
2396
- export function buildReplyIntentPrompt({ goalText, reply } = {}) {
2397
- return [
2398
- 'You route replies in Agent Manager: a person delegated a coding task, an AI did it, and it came back for review. The person has now replied.',
2399
- 'Decide what the reply ASKS FOR — exactly one of:',
2400
- '- "continue": fix or change the work that was just done (the usual case).',
2401
- '- "rescope": stop and rethink; the request itself should now say something different.',
2402
- '- "split": a separate piece of work that should not touch this one.',
2403
- '- "answer": a question about the work. Nothing should be built or changed — they want to know something.',
2404
- '- "unsure": you genuinely cannot tell. Prefer this over guessing: the person will be shown the choices.',
2405
- 'Judge the reply on its own words. Do not treat a question mark as proof of "answer", or an imperative as proof of "continue" — "why is this red? make it blue" asks for a change.',
2406
- 'Output ONLY one line of raw JSON: {"outcome":"continue|rescope|split|answer|unsure"}. No markdown, no code fence, no other text.',
2407
- '',
2408
- 'The request that was worked on:',
2409
- String(goalText ?? '').slice(0, 2000),
2410
- '',
2411
- 'Their reply:',
2412
- String(reply ?? '').slice(0, 4000),
2413
- ].join('\n');
2414
- }
2415
-
2416
- // Tolerant parser — never throws. It returns TWO things, and the difference
2417
- // between them is the whole point:
2418
- //
2419
- // read: true … the AI answered, and we understood it. 'unsure' here means the
2420
- // AI genuinely could not tell → ask the person (§1.4).
2421
- // read: false … we could not get an answer at all (no agent installed, offline,
2422
- // rate-limited, garbled output). The AI never said anything, so
2423
- // there is nobody to have been unsure.
2424
- //
2425
- // Treating those two the same is a real bug, found by an existing test on
2426
- // 2026-07-22: on a machine with no agent, a reply stopped doing ANYTHING — it put
2427
- // the four choices on screen and waited. But if the agent cannot be reached, no
2428
- // answer can be written either; the only useful thing a reply can still do is
2429
- // what it always did — go back as rework, and run when the agent returns.
2430
- // So the caller falls back to 'continue' when read is false. That is not a guess
2431
- // about what the person meant: it is the documented default this endpoint has
2432
- // always had when no outcome is given.
2433
- export function parseReplyIntent(raw) {
2434
- const unread = { outcome: 'unsure', read: false };
2435
- if (!raw || typeof raw !== 'string') return unread;
2436
- const m = raw.match(/\{[\s\S]*\}/);
2437
- if (!m) return unread;
2438
- let obj;
2439
- try { obj = JSON.parse(m[0]); } catch { return unread; }
2440
- if (!REPLY_INTENTS.includes(obj?.outcome)) return unread; // answered, but not in our words
2441
- return { outcome: obj.outcome, read: true };
2442
- }
2443
-
2444
2389
  // 宛先(docs/design/ONE-CONVERSATION.md §1.2)
2445
2390
  //
2446
2391
  // チャットで「#12 の件だけど」と書けたら、そのチケット宛て。**決定論で、我々の推測は
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, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, 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, REPLY_INTENTS, buildReplyIntentPrompt, parseReplyIntent, buildGoalMessage, serializeGoalMessage, parseGoalMessages, computeInternalQualityMetrics, classifyInternalQualityTaskError, resolveWorkspaceMode, goalSessionFor, setGoalAgentSession, switchGoalAgentSession } from './lib.mjs';
24
+ import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, 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 } from './lib.mjs';
25
25
  import { createSerialQueue } from './lib.mjs';
26
26
  import { isPrClosed } from './lib.mjs';
27
27
  import { collectProjectRules } from './lib.mjs';
@@ -5214,24 +5214,6 @@ const server = createServer(async (req, res) => {
5214
5214
  return json(res, 200, payload);
5215
5215
  }
5216
5216
 
5217
- // 返事の行き先を AI 本人に決めてもらう (ONE-CONVERSATION.md §1.4)。
5218
- //
5219
- // 判定は1往復だけの軽い仕事なので、作業した本人ではなくユーティリティのモデルに
5220
- // 聞く(答えを書く answer 本体は本人に聞く——あちらは中身を知っている必要がある)。
5221
- // 呼べなかった時は 'unsure'。「たぶん continue」で走り出すより、人に1タップ選ばせる
5222
- // ほうが安い。
5223
- async function judgeReplyIntent(goal, reply) {
5224
- let parsed;
5225
- try {
5226
- const r = await runUtility({ prompt: buildReplyIntentPrompt({ goalText: goal.text, reply }), cwd: ROOT, tools: 'Read' });
5227
- parsed = parseReplyIntent(r.result);
5228
- } catch { parsed = { outcome: 'unsure', read: false }; }
5229
- // Could not ask at all → do what a reply has always done (continue). Blocking the
5230
- // reply on four choices would leave a machine with no reachable agent unable to
5231
- // send anything back, and an answer could not have been written there anyway.
5232
- return parsed.read ? parsed.outcome : 'continue';
5233
- }
5234
-
5235
5217
  // 「聞いてるだけ」への返事 (ONE-CONVERSATION.md §1.3 answer)。
5236
5218
  //
5237
5219
  // 答えるのは**作業した AI 本人**=その agent の session を resume する。状況一覧しか
@@ -5285,26 +5267,20 @@ async function answerGoalQuestion(goal, question) {
5285
5267
  let body = '';
5286
5268
  req.on('data', (d) => { body += d; });
5287
5269
  req.on('end', async () => {
5288
- let text = '', outcome = 'continue', auto = false, requested = {};
5270
+ let text = '', outcome = 'continue', requested = {};
5289
5271
  try {
5290
5272
  requested = JSON.parse(body || '{}');
5291
5273
  text = String(requested.text ?? '').trim();
5292
5274
  if (REPLY_OUTCOMES.includes(requested.outcome)) outcome = requested.outcome;
5293
- auto = requested.outcome === 'auto';
5294
5275
  } catch {}
5295
5276
  const worker = applyReplyWorker(goal, requested);
5296
5277
  if (!worker.ok) return json(res, 409, { error: worker.error });
5297
5278
 
5298
- // outcome:'auto' = 決めるのは AI 本人 (§1.4)。人の言葉を1回だけ渡して、返って
5299
- // きた行き先のとおりに配線する。我々はキーワードも類似度も見ない。
5300
- // 迷ったと言われたら**ゴールに触らずに**選択肢を返す——ここで勝手に continue
5301
- // 倒すと、質問しただけの人のコードが動き出す(それが直したかった壊れ方)。
5302
- if (auto) {
5303
- if (!text) return json(res, 400, { error: 'text required' });
5304
- const judged = await judgeReplyIntent(goal, text);
5305
- if (judged === 'unsure') return json(res, 200, { goal, outcome: 'unsure', choices: REPLY_OUTCOMES });
5306
- outcome = judged;
5307
- }
5279
+ // A review reply is transport, not a classification request. Legacy clients
5280
+ // send outcome:'auto'; it deliberately means the deterministic default:
5281
+ // preserve the exact words and resume this goal's agent session. Specialized
5282
+ // outcomes work only when an explicit UI action sends one of REPLY_OUTCOMES.
5283
+ if (requested.outcome === 'auto' && !text) return json(res, 400, { error: 'text required' });
5308
5284
  // 返事の行き先4種 (ONE-CONVERSATION.md §1.3). 既定は今までどおり continue なので、
5309
5285
  // outcome を送ってこない今の UI の挙動は1ミリも変わらない。
5310
5286
  const result = dismissGoal({ goalStatus: goal.status, outcome });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.75",
3
+ "version": "0.10.77",
4
4
  "type": "module",
5
5
  "description": "Galda - hand off work to Claude Code or Codex, get proof back. Runs on your existing subscription, no extra API cost.",
6
6
  "scripts": {