@galda/cli 0.10.16 → 0.10.17

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
@@ -1313,6 +1313,11 @@
1313
1313
  discard path. */
1314
1314
  #fsRoot .cc-ask .acts{position:absolute;top:8px;right:8px;display:flex;opacity:0;transition:opacity .15s}
1315
1315
  #fsRoot .cc-ask:hover .acts{opacity:1}
1316
+ /* Touch devices have no hover → the hover-only trash was permanently invisible
1317
+ and the「Needs your answer」card had no reachable discard path (Masa
1318
+ 2026-07-16: 右上のdeleteが押せない). Reveal it (dimmed, so it still reads as
1319
+ secondary per §1.5-10) whenever the pointer can't hover. */
1320
+ @media (hover: none){ #fsRoot .cc-ask .acts{opacity:.55} }
1316
1321
  #fsRoot .qchips{display:flex;gap:6px;flex-wrap:wrap}
1317
1322
  #fsRoot .achip{height:28px;padding:0 12px;border-radius:8px;border:1px solid var(--hair2);background:var(--panel);
1318
1323
  color:var(--ink2);font:inherit;font-size:12.5px;cursor:pointer;transition:border-color .15s,color .15s,background .15s}
@@ -1937,7 +1942,7 @@
1937
1942
  #fsClawdHero span{display:block;margin-top:5px;font:500 10px/1.4 var(--mono);letter-spacing:.08em;color:var(--ink3)}
1938
1943
  /* Welcome P1 (H17 item 4, flagship f9c75ad): grounded hop + clean-cut eyes on settle + our CSS confetti.
1939
1944
  Two pixel frames (neutral black-eyes / laughing), no arm-raise, no official confetti. reduce-motion=static. */
1940
- #fsClawdHero .cstage{position:relative;width:170px;height:132px;display:flex;align-items:flex-end;justify-content:center;margin:0 auto 2px}#fsClawdHero .cw{position:relative;width:150px;transform-origin:50% 100%;will-change:transform;animation:chHop 2.4s linear infinite}#fsClawdHero .cw img{position:absolute;left:0;bottom:0;width:100%;display:block;image-rendering:pixelated}#fsClawdHero .cw .chNeu{animation:chNeu 2.4s linear infinite}#fsClawdHero .cw .chLau{opacity:0;animation:chLau 2.4s linear infinite}#fsClawdHero .chConf{position:absolute;left:50%;top:34px;width:0;height:0;pointer-events:none;z-index:3}#fsClawdHero .chConf i{position:absolute;width:4px;height:7px;border-radius:1.5px;opacity:0;transform:translate(-50%,-50%);animation:chPop 2.4s cubic-bezier(.2,.7,.3,1) infinite}@keyframes chHop{0%,52%{transform:translateY(0) scale(1,1)}55%{transform:translateY(0) scale(1.08,.85)}58%{transform:translateY(-16px) scale(.95,1.14)}61%{transform:translateY(-27px) scale(.98,1.05)}64%{transform:translateY(-31px) scale(1,1)}67%{transform:translateY(-26px) scale(.98,1.05)}70%{transform:translateY(-14px) scale(.95,1.12)}73%{transform:translateY(0) scale(1.1,.82)}77%{transform:translateY(-4px) scale(.99,1.02)}80%,100%{transform:translateY(0) scale(1,1)}}@keyframes chLau{0%,53.8%{opacity:0}54%,85.8%{opacity:1}86%,100%{opacity:0}}@keyframes chNeu{0%,53.8%{opacity:1}54%,85.8%{opacity:0}86%,100%{opacity:1}}@keyframes chPop{0%,54%{opacity:0;transform:translate(0,6px) rotate(0) scale(.5)}60%{opacity:1;transform:translate(calc(var(--dx)*.5),calc(var(--dy)*.5)) rotate(calc(var(--rot)*.5)) scale(1)}72%{opacity:1;transform:translate(var(--dx),var(--dy)) rotate(var(--rot))}100%{opacity:0;transform:translate(calc(var(--dx)*1.1),calc(var(--dy) + 44px)) rotate(calc(var(--rot)*1.4)) scale(.9)}}@media (prefers-reduced-motion:reduce){#fsClawdHero .cw,#fsClawdHero .cw .chNeu,#fsClawdHero .cw .chLau{animation:none}#fsClawdHero .cw .chLau{opacity:0}#fsClawdHero .chConf{display:none}}
1945
+ #fsClawdHero .cstage{position:relative;left:-13px;width:170px;height:132px;display:flex;align-items:flex-end;justify-content:center;margin:0 auto 2px}#fsClawdHero .cw{position:relative;width:150px;transform-origin:50% 100%;will-change:transform;animation:chHop 2.4s linear infinite}#fsClawdHero .cw img{position:absolute;left:0;bottom:0;width:100%;display:block;image-rendering:pixelated}#fsClawdHero .cw .chNeu{animation:chNeu 2.4s linear infinite}#fsClawdHero .cw .chLau{opacity:0;animation:chLau 2.4s linear infinite}#fsClawdHero .chConf{position:absolute;left:50%;top:34px;width:0;height:0;pointer-events:none;z-index:3}#fsClawdHero .chConf i{position:absolute;width:4px;height:7px;border-radius:1.5px;opacity:0;transform:translate(-50%,-50%);animation:chPop 2.4s cubic-bezier(.2,.7,.3,1) infinite}@keyframes chHop{0%,52%{transform:translateY(0) scale(1,1)}55%{transform:translateY(0) scale(1.08,.85)}58%{transform:translateY(-16px) scale(.95,1.14)}61%{transform:translateY(-27px) scale(.98,1.05)}64%{transform:translateY(-31px) scale(1,1)}67%{transform:translateY(-26px) scale(.98,1.05)}70%{transform:translateY(-14px) scale(.95,1.12)}73%{transform:translateY(0) scale(1.1,.82)}77%{transform:translateY(-4px) scale(.99,1.02)}80%,100%{transform:translateY(0) scale(1,1)}}@keyframes chLau{0%,53.8%{opacity:0}54%,85.8%{opacity:1}86%,100%{opacity:0}}@keyframes chNeu{0%,53.8%{opacity:1}54%,85.8%{opacity:0}86%,100%{opacity:1}}@keyframes chPop{0%,54%{opacity:0;transform:translate(0,6px) rotate(0) scale(.5)}60%{opacity:1;transform:translate(calc(var(--dx)*.5),calc(var(--dy)*.5)) rotate(calc(var(--rot)*.5)) scale(1)}72%{opacity:1;transform:translate(var(--dx),var(--dy)) rotate(var(--rot))}100%{opacity:0;transform:translate(calc(var(--dx)*1.1),calc(var(--dy) + 44px)) rotate(calc(var(--rot)*1.4)) scale(.9)}}@media (prefers-reduced-motion:reduce){#fsClawdHero .cw,#fsClawdHero .cw .chNeu,#fsClawdHero .cw .chLau{animation:none}#fsClawdHero .cw .chLau{opacity:0}#fsClawdHero .chConf{display:none}}
1941
1946
  #fsRoot.clawdhero #fsClawdHero{display:block}
1942
1947
  /* Non-Claude agent (e.g. Codex) hero: no Clawd mascot (that's a Claude Code brand mark,
1943
1948
  DESIGN-RULES v3 §6.5) — a plain glyph badge stands in so the hero still names the
@@ -2107,6 +2112,12 @@
2107
2112
  <div class="errbar" id="errbar"></div>
2108
2113
  <div class="composer-wrap" id="composerWrap">
2109
2114
  <div class="queue" id="queue"></div>
2115
+ <!-- Conversation-first composer (SLICE 2, docs/HANDOFF-ONBOARDING-conversational.md):
2116
+ a chat/help/settings message gets a quiet reply HERE instead of
2117
+ becoming a To-Do. Reuses the .peraanswer surface; hidden until a
2118
+ reply exists (progressive disclosure — DESIGN-RULES §1.5-3, "存在
2119
+ しないものの器を先に見せない"). -->
2120
+ <div class="peraanswer" id="chatReply" hidden></div>
2110
2121
  <div class="composer">
2111
2122
  <div class="attachbar" id="attachbar" style="display:none"></div>
2112
2123
  <!-- Slash-command palette (Masa決定 2026-07-07: chips→slash). Floats
@@ -5367,10 +5378,15 @@ function applyFsBand(){
5367
5378
  const CLAWD_HERO_IMGS = '<img class="chNeu" src="data:image/png;base64,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" alt=""><img class="chLau" src="data:image/png;base64,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" alt=""></div></div>';
5368
5379
  function clawdHeroHTML(agent){
5369
5380
  const app = connectedAppMeta(agent);
5381
+ // Codex: wordmark + status only — no Clawd mascot (that's a Claude Code brand
5382
+ // mark, §6.5) and no glyph badge either (Masa 2026-07-16: 「codexの時は文字だけでよい」).
5370
5383
  const stage = agent === 'codex'
5371
- ? `<div class="cstage"><div class="chBadge">${app.icon}</div></div>`
5384
+ ? ''
5372
5385
  : `<div class="cstage"><div class="chConf"></div><div class="cw">${CLAWD_HERO_IMGS}`;
5373
- return stage + `<b>${esc(app.label)}</b><span>CONNECTED · WAITING FOR GOALS</span>`;
5386
+ // .hstat: the status line is the ONLY element renderClawdHero()/bootDisconnected()
5387
+ // may rewrite — never target it by bare `span` (the codex badge used to carry an
5388
+ // .appglyph <span>, so querySelector('span') clobbered the glyph with the status).
5389
+ return stage + `<b>${esc(app.label)}</b><span class="hstat">CONNECTED · WAITING FOR GOALS</span>`;
5374
5390
  }
5375
5391
  function spawnHeroConfetti(){
5376
5392
  const conf = document.querySelector('#fsClawdHero .chConf'); if (!conf) return;
@@ -5391,9 +5407,9 @@ function spawnHeroConfetti(){
5391
5407
  }
5392
5408
  function renderClawdHero(){
5393
5409
  const el = $('fsClawdHero'); if (!el) return;
5394
- const prevStatus = el.querySelector('span')?.textContent;
5410
+ const prevStatus = el.querySelector('.hstat')?.textContent;
5395
5411
  el.innerHTML = clawdHeroHTML(state.connectedApp);
5396
- if (prevStatus) { const s = el.querySelector('span'); if (s) s.textContent = prevStatus; }
5412
+ if (prevStatus) { const s = el.querySelector('.hstat'); if (s) s.textContent = prevStatus; }
5397
5413
  if (state.connectedApp !== 'codex') spawnHeroConfetti(); // Welcome P1 confetti (H17 item 4)
5398
5414
  }
5399
5415
  // one-time static wiring for the flagship shell (folds, lane resize, rail menu, band picker)
@@ -5782,7 +5798,7 @@ function bootDisconnected(info){
5782
5798
  state.active = '__onboard';
5783
5799
  state.billing = { licensed: false, email };
5784
5800
  try { render(); } catch (e) { console.warn('disconnected render', e); }
5785
- const heroSpan = document.querySelector('#fsClawdHero span');
5801
+ const heroSpan = document.querySelector('#fsClawdHero .hstat');
5786
5802
  if (heroSpan) heroSpan.textContent = 'CONNECT YOUR MACHINE TO BEGIN';
5787
5803
 
5788
5804
  const style = document.createElement('style');
@@ -5853,7 +5869,17 @@ function bootDisconnected(info){
5853
5869
  const es = new EventSource('/presence');
5854
5870
  es.addEventListener('presence', (e) => {
5855
5871
  let d; try { d = JSON.parse(e.data); } catch { return; }
5856
- if (d.state === 'connected') { es.close(); location.reload(); } // one reload, only on a stable connect
5872
+ if (d.state === 'connected') {
5873
+ // Confirm the engine is REALLY reachable before reloading. A stale/spurious
5874
+ // 'connected' presence otherwise reloads us into a white flash that lands
5875
+ // right back on this same connect screen — the welcome animation plays, then
5876
+ // 一瞬真っ白 (Masa 2026-07-16). Reuse the proven 6d71c32 contract: the relay
5877
+ // 503s every engine-backed path while offline and 200s once proxied, so
5878
+ // status !== 503 flips exactly at a genuine connect.
5879
+ fetch('/api/state', { cache: 'no-store', credentials: 'same-origin' })
5880
+ .then((r) => { if (r.status !== 503) { es.close(); location.reload(); } })
5881
+ .catch(() => {}); // engine unreachable → not connected yet; keep waiting
5882
+ }
5857
5883
  else if (cgWait) cgWait.innerHTML = '<i></i>WAITING FOR THIS DEVICE';
5858
5884
  });
5859
5885
  window.addEventListener('beforeunload', () => { try { es.close(); } catch {} });
@@ -5917,6 +5943,19 @@ async function postOutboxItem(item){
5917
5943
  }
5918
5944
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
5919
5945
  const goal = await r.json();
5946
+ if (goal.kind === 'chat') {
5947
+ // Conversation-first composer (SLICE 2): this was a chat/help/settings
5948
+ // turn, not a To-Do. Retire the optimistic outbox item — a chat turn is
5949
+ // ephemeral by design, so dropping it does NOT weaken the never-lose
5950
+ // guarantee (which protects real tasks/goals). Show the reply quietly
5951
+ // above the input. We must NOT reach land()/upsert(): a kind:'chat'
5952
+ // response carries no goal id, so upserting it would wedge a ghost
5953
+ // "Sending…" row in the queue forever and pollute state.goals.
5954
+ outbox.remove(item.oid);
5955
+ showChatReply(goal.reply);
5956
+ render();
5957
+ return; // finally still runs posting.delete(); skip land()/upsert()
5958
+ }
5920
5959
  goal.imageUrls = item.imageUrls;
5921
5960
  outbox.land(item.oid, goal.id);
5922
5961
  upsert(state.goals, goal);
@@ -5928,6 +5967,17 @@ async function postOutboxItem(item){
5928
5967
  render();
5929
5968
  }
5930
5969
 
5970
+ // SLICE 2: render / clear the conversation-first composer reply (reuses the
5971
+ // .peraanswer surface). Quiet, transient — cleared the moment the user sends
5972
+ // the next message so a stale reply never lingers above a new turn.
5973
+ function showChatReply(text){
5974
+ const el = $('chatReply'); if (!el) return;
5975
+ const t = (text || '').trim();
5976
+ if (!t) { el.hidden = true; el.textContent = ''; return; }
5977
+ el.className = 'peraanswer'; el.textContent = t; el.hidden = false;
5978
+ }
5979
+ function clearChatReply(){ const el = $('chatReply'); if (el) { el.hidden = true; el.textContent = ''; } }
5980
+
5931
5981
  async function flushOutbox(){
5932
5982
  // Only (re)send items that still need it — never re-POST 'landed' memos that
5933
5983
  // are merely waiting for reconcile() (that was the double-send path).
@@ -6004,6 +6054,7 @@ async function send(){
6004
6054
  renderMsgHint();
6005
6055
 
6006
6056
  $('input').value = ''; $('input').style.height = 'auto';
6057
+ clearChatReply(); // a new turn starts — drop any prior conversational reply
6007
6058
  closePalette();
6008
6059
  localStorage.removeItem('draft');
6009
6060
  renderAttach(); render();
package/engine/lib.mjs CHANGED
@@ -1170,6 +1170,72 @@ export function detectRequestLanguage(text) {
1170
1170
  return /[぀-ヿ㐀-鿿]/.test(text || '') ? 'ja' : 'en';
1171
1171
  }
1172
1172
 
1173
+ // SLICE 2 intent routing (docs/HANDOFF-ONBOARDING-conversational.md): the single
1174
+ // composer is conversation-first — a chat/help/settings message ("what can you
1175
+ // do?", "reply in English", a greeting) must get a conversational reply, NOT a
1176
+ // To-Do; only implementation requests become goals. These are the pure,
1177
+ // testable pieces; the server wires them into POST /api/tasks.
1178
+ //
1179
+ // A dependency-free heuristic that short-circuits only the OBVIOUS cases so most
1180
+ // sends skip the LLM classifier:
1181
+ // 'task' — a clear implementation request (imperative verb / code token) and
1182
+ // no chat signal → skip the LLM, go straight to goal creation.
1183
+ // 'chat' — a clear help/settings/greeting message and no task signal.
1184
+ // 'unsure' — ambiguous (both signals, or neither) → defer to the LLM.
1185
+ // It is deliberately conservative about declaring 'task' (the only branch that
1186
+ // skips the LLM): a message carrying BOTH a task verb and a help/settings signal
1187
+ // is 'unsure', not 'task', so a help question phrased with "add"/"fix" isn't
1188
+ // silently turned into a goal.
1189
+ const INTENT_TASK_RE = /\b(fix|add|implement|create|build|refactor|remove|delete|rename|migrate|debug|integrate|revert|deploy)\b|実装|直し|直す|なおして|修正|作って|作成|追加|変更|削除|対応して|リファクタ|置き換え|バグ|不具合/i;
1190
+ const INTENT_CODE_RE = /\.(?:js|mjs|cjs|ts|tsx|jsx|css|scss|html|py|go|rs|java|rb|php|json|md|yml|yaml|sh|sql)\b|`[^`]+`|\b(?:function|const|class|import)\b|#\d+/;
1191
+ const INTENT_CHAT_META_RE = /何ができ|なにができ|何が出来|使い方|どう使|どうやって使|使える\?|説明して|教えて(?:ください|くれ|ほしい)?\s*[??]?$|about this (?:tool|app)|what (?:can|do|are|is|does) (?:you|it|this|i)|what'?s this|what can this|how (?:do|does|can) (?:i|this|you|it)|how to use|who are you|^\s*help\s*[??]?$|そもそも|初めて/i;
1192
+ const INTENT_CHAT_SETTINGS_RE = /英語で|日本語で|(?:reply|respond|answer|talk|speak|write)\s+(?:back\s+)?in\s+\w|語で(?:返|答|話|書)|言語/i;
1193
+ const INTENT_GREETING_RE = /^\s*(?:hi|hey|hello|yo|sup|thanks|thank you|thx|good (?:morning|evening|afternoon))\b[\s.!?]*$|^\s*(?:こんにち(?:は|わ)|こんばん(?:は|わ)|おはよう|はじめまして|やあ|ありがと(?:う)?|よろしく)[\s。!?!?]*$/i;
1194
+
1195
+ export function classifyComposerIntentHeuristic(text) {
1196
+ const t = (text || '').trim();
1197
+ if (!t) return 'chat';
1198
+ const hasTask = INTENT_TASK_RE.test(t) || INTENT_CODE_RE.test(t);
1199
+ const hasChat = INTENT_CHAT_META_RE.test(t) || INTENT_CHAT_SETTINGS_RE.test(t) || INTENT_GREETING_RE.test(t);
1200
+ if (hasTask && !hasChat) return 'task'; // pure implementation request → skip LLM
1201
+ if (hasChat && !hasTask) return 'chat'; // pure help/settings/greeting
1202
+ return 'unsure'; // both or neither → let the LLM decide
1203
+ }
1204
+
1205
+ // The one prompt that BOTH classifies and (for chat) writes the reply in the
1206
+ // requester's language — one Haiku round-trip, not two. The model returns a
1207
+ // single line of JSON: {"intent":"chat|task","reply":"…"}.
1208
+ export function buildIntentPrompt(text, lang) {
1209
+ const language = lang === 'ja' ? '日本語' : 'English';
1210
+ return [
1211
+ 'You are the intent router for Agent Manager: a tool where a user delegates a coding task in plain language, an AI implements it, and it comes back as a reviewed pull request with proof.',
1212
+ 'Classify the user message below into exactly one intent:',
1213
+ '- "task": a request to implement, change, fix, build, or remove something in code (even if vague, e.g. "add dark mode").',
1214
+ '- "chat": anything that is NOT an implementation request — questions about what this tool can do or how to use it, greetings, thanks, small talk, or a settings/preference request (e.g. "reply in English").',
1215
+ 'When in doubt between the two, prefer "task".',
1216
+ `If intent is "chat", also write "reply": a short, warm, helpful answer in ${language} (at most 4 sentences). If the user asks what this can do or how to start, briefly explain: describe the change you want in plain language, and it will ask which folder/repo to work in, implement it, and return a PR with proof. If intent is "task", set "reply" to "".`,
1217
+ 'Output ONLY one line of raw JSON with keys "intent" and "reply". No markdown, no code fence, no extra text.',
1218
+ '',
1219
+ 'User message:',
1220
+ String(text || '').slice(0, 4000),
1221
+ ].join('\n');
1222
+ }
1223
+
1224
+ // Tolerant parser for the router's reply — never throws. Extracts the first JSON
1225
+ // object; on any failure it fails safe to 'task' (so a garbled classification
1226
+ // can never silently swallow a real implementation request). Reply is clamped.
1227
+ export function parseIntentResponse(raw) {
1228
+ const fallback = { intent: 'task', reply: '' };
1229
+ if (!raw || typeof raw !== 'string') return fallback;
1230
+ const m = raw.match(/\{[\s\S]*\}/);
1231
+ if (!m) return fallback;
1232
+ let obj;
1233
+ try { obj = JSON.parse(m[0]); } catch { return fallback; }
1234
+ const intent = obj && obj.intent === 'chat' ? 'chat' : 'task';
1235
+ const reply = intent === 'chat' && typeof obj.reply === 'string' ? obj.reply.trim().slice(0, 2000) : '';
1236
+ return { intent, reply };
1237
+ }
1238
+
1173
1239
  // Normalize a nested boolean-flag object (prSections/reviewCard) key by key,
1174
1240
  // so a partial payload — or one missing the whole sub-object — can't
1175
1241
  // silently disable flags it never mentioned; each key falls back to its own
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, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed } from './lib.mjs';
24
- import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, 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, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable } from './lib.mjs';
24
+ import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, 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, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable, classifyComposerIntentHeuristic, buildIntentPrompt, parseIntentResponse } from './lib.mjs';
25
25
  import { openPR } from './pr.mjs';
26
26
  import { runVerification, exerciseUi } from './verify.mjs';
27
27
 
@@ -582,7 +582,19 @@ function sendGoalEvent(obj, goal) {
582
582
  if (goalVisibleTo(goal, identity, MANAGER_OWNER_EMAIL)) res.write(data);
583
583
  }
584
584
  }
585
- function saveGoal(g) { appendFileSync(logFile, JSON.stringify({ ...g, kind: 'goal' }) + '\n'); sendGoalEvent({ ev: 'goal', goal: g }, g); }
585
+ // Durable append to the restart-safe log. A failing disk (ENOSPC when the
586
+ // volume is full, EACCES/EROFS) must NEVER freeze the board: without this guard
587
+ // an appendFileSync throw inside a handler leaves the HTTP request hanging (no
588
+ // response) AND skips the SSE broadcast that follows, so a delete/answer looks
589
+ // like it did nothing — the question card stays stuck forever (Masa 2026-07-16).
590
+ // We swallow the write error (logged), and let the in-memory mutation + SSE
591
+ // proceed regardless. Durability is best-effort here; a live UI wins over a
592
+ // perfectly-replayable log on a disk that can't be written to anyway.
593
+ function logAppend(line) {
594
+ try { appendFileSync(logFile, line + '\n'); return true; }
595
+ catch (e) { console.error(`[manager] log write failed (${e?.code || e}) — state not persisted this write, UI still updated`); return false; }
596
+ }
597
+ function saveGoal(g) { logAppend(JSON.stringify({ ...g, kind: 'goal' })); sendGoalEvent({ ev: 'goal', goal: g }, g); }
586
598
  // Persist the orphan-goal reclassification computed right after replayQueueLog
587
599
  // above (deferred to here because saveGoal/send/sseClients don't exist yet at
588
600
  // that point in the module) — writes it to the restart-safe log so it settles
@@ -594,7 +606,7 @@ for (const fix of orphanFixes) {
594
606
  }
595
607
  function saveTask(t) {
596
608
  const { activity, todos, ...persist } = t; // activity + todos are ephemeral (live only)
597
- appendFileSync(logFile, JSON.stringify({ ...persist, kind: 'task' }) + '\n');
609
+ logAppend(JSON.stringify({ ...persist, kind: 'task' }));
598
610
  const goal = goals.find((g) => g.id === t.goalId);
599
611
  if (goal) sendGoalEvent({ ev: 'task', task: persist }, goal); else send({ ev: 'task', task: persist });
600
612
  emitTaskAnalytics(t);
@@ -2701,6 +2713,32 @@ const server = createServer(async (req, res) => {
2701
2713
  const { projectId, text, pr, images, model, effort, source, pending, review, note, mode, skill, agent } = JSON.parse(body);
2702
2714
  const project = projects.find((p) => p.id === projectId);
2703
2715
  if (!project || !text?.trim()) return json(res, 400, { error: 'projectId and text required' });
2716
+ // Onboarding (docs/HANDOFF-ONBOARDING-conversational.md, SLICE 2): the
2717
+ // composer is conversation-first. A chat/help/settings message ("what can
2718
+ // you do?", "reply in English", a greeting) gets a conversational reply
2719
+ // and must NOT become a To-Do. Classified BEFORE dedupe/billing/goal
2720
+ // creation, so a chat turn never folds into a goal, never counts against
2721
+ // the free-tier cap, and never persists a task. /later (pending) and
2722
+ // /review are explicit user intent → skip classification. A cheap
2723
+ // heuristic short-circuits obvious implementation requests (no LLM);
2724
+ // anything else gets one Haiku round-trip that both classifies and writes
2725
+ // the reply. LLM failure falls back to 'task' — never drop a real
2726
+ // implementation ask (the folder guard below still prevents no-ops).
2727
+ if (!pending && !review && classifyComposerIntentHeuristic(text.trim()) === 'chat') {
2728
+ // Only a HIGH-confidence chat/help/settings message reaches the LLM —
2729
+ // to write the reply, and to let the model veto back to 'task' if the
2730
+ // heuristic over-fired. Everything else (clear tasks AND ambiguous
2731
+ // 'unsure') goes straight to goal creation: bias to task keeps the
2732
+ // common path LLM-free (no latency/tokens on real work) and the folder
2733
+ // guard below still prevents no-ops. LLM failure → treat as task and
2734
+ // fall through, so a real implementation ask is never dropped.
2735
+ const intentLang = detectRequestLanguage(text);
2736
+ try {
2737
+ const r = await runClaude({ prompt: buildIntentPrompt(text.trim(), intentLang), cwd: ROOT, tools: 'Read', model: 'haiku' });
2738
+ const parsed = parseIntentResponse(r.result);
2739
+ if (parsed.intent === 'chat') return json(res, 200, { kind: 'chat', reply: parsed.reply.slice(0, 2000), lang: intentLang });
2740
+ } catch { /* LLM unavailable → fall through to goal creation */ }
2741
+ }
2704
2742
  // Dedupe (Masa: 被ったやつもまとめて): if this near-duplicates a goal
2705
2743
  // already in flight (running/review with a live worker session), fold it
2706
2744
  // in as a thread follow-up (context kept via session resume) instead of
@@ -2850,7 +2888,7 @@ const server = createServer(async (req, res) => {
2850
2888
  const q = queues.get(goal.projectId);
2851
2889
  if (q) q.waiting = q.waiting.filter((t) => t.goalId !== goal.id);
2852
2890
  goals = goals.filter((g) => g.id !== goal.id);
2853
- appendFileSync(logFile, JSON.stringify({ kind: 'goal', id: goal.id, deleted: true }) + '\n');
2891
+ logAppend(JSON.stringify({ kind: 'goal', id: goal.id, deleted: true }));
2854
2892
  sendGoalEvent({ ev: 'goal-deleted', id: goal.id }, goal);
2855
2893
  startNextStacked(goal.projectId);
2856
2894
  pump(goal.projectId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.16",
3
+ "version": "0.10.17",
4
4
  "type": "module",
5
5
  "description": "Galda - hand off work to your Claude Code, get proof back. Runs on your existing subscription, no extra API cost.",
6
6
  "scripts": {