@galda/cli 0.10.75 → 0.10.76
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 +16 -7
- package/engine/lib.mjs +4 -59
- package/engine/server.mjs +7 -31
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -3354,12 +3354,14 @@ const withKey = (u) => {
|
|
|
3354
3354
|
};
|
|
3355
3355
|
|
|
3356
3356
|
// Theme names v3 (§6.5): English registered names only. Migrate persisted values
|
|
3357
|
-
// (old internal codenames → v3 names; dark→midnight; archived glass→
|
|
3357
|
+
// (old internal codenames → v3 names; dark→midnight; archived glass→midnight default).
|
|
3358
3358
|
const THEMES = ['studio', 'gradient', 'banff', 'midnight', 'cinema'];
|
|
3359
3359
|
const THEME_MIGRATE = { w4: 'studio', arc: 'gradient', nature: 'banff', glow: 'midnight', comp: 'cinema', dark: 'midnight', glass: 'gradient' };
|
|
3360
3360
|
const savedTheme = (() => {
|
|
3361
3361
|
const s = localStorage.getItem('theme');
|
|
3362
|
-
|
|
3362
|
+
// New installations start in Midnight. Existing valid values are preserved;
|
|
3363
|
+
// only a missing/invalid persisted value takes this default.
|
|
3364
|
+
const v = THEME_MIGRATE[s] ?? (THEMES.includes(s) ? s : 'midnight');
|
|
3363
3365
|
if (v !== s) localStorage.setItem('theme', v);
|
|
3364
3366
|
return v;
|
|
3365
3367
|
})();
|
|
@@ -8702,6 +8704,11 @@ async function postOutboxItem(item){
|
|
|
8702
8704
|
render();
|
|
8703
8705
|
return; // finally still runs posting.delete(); skip land()/upsert()
|
|
8704
8706
|
}
|
|
8707
|
+
// This optimistic turn was only for the short interval before the server
|
|
8708
|
+
// classified the input. A real goal is represented by the To Do lane and
|
|
8709
|
+
// its live Implementing log, never as a second message below the composer.
|
|
8710
|
+
const turns = state.chatTurns[item.projectId];
|
|
8711
|
+
if (turns) state.chatTurns[item.projectId] = turns.filter((turn) => turn.oid !== item.oid);
|
|
8705
8712
|
goal.imageUrls = item.imageUrls;
|
|
8706
8713
|
outbox.land(item.oid, goal.id);
|
|
8707
8714
|
upsert(state.goals, goal);
|
|
@@ -8875,8 +8882,9 @@ async function send(){
|
|
|
8875
8882
|
const fullText = batt.length
|
|
8876
8883
|
? `${text}${text ? '\n\n' : ''}添付画像(workerはこのパスをReadで確認できます): ${batt.map((a) => a.path).join(', ')}`
|
|
8877
8884
|
: text;
|
|
8878
|
-
//
|
|
8879
|
-
|
|
8885
|
+
// A bound send belongs to this goal's persisted thread. Do not also add it
|
|
8886
|
+
// to the center conversation feed: that made a task reply appear below the
|
|
8887
|
+
// composer as an unrelated "You" turn.
|
|
8880
8888
|
state.attach = [];
|
|
8881
8889
|
state.msg = { mode: null, model: null, effort: null, skill: null, later: false, review: false };
|
|
8882
8890
|
renderMsgHint();
|
|
@@ -8925,9 +8933,10 @@ async function send(){
|
|
|
8925
8933
|
status: 'sending', ts: Date.now(),
|
|
8926
8934
|
};
|
|
8927
8935
|
outbox.add(item); // saved locally BEFORE any network call
|
|
8928
|
-
//
|
|
8929
|
-
//
|
|
8930
|
-
|
|
8936
|
+
// The server decides whether this is a conversation or a real task. Show an
|
|
8937
|
+
// optimistic conversation turn while that decision is pending, then remove it
|
|
8938
|
+
// if the request becomes a task (tasks belong in To Do / Implementing only).
|
|
8939
|
+
(state.chatTurns[state.active] ||= []).push({ role: 'you', text, oid: item.oid, ts: Date.now() });
|
|
8931
8940
|
state.attach = [];
|
|
8932
8941
|
// mode/skill/later/review are per-message → clear after send (model stays sticky).
|
|
8933
8942
|
state.msg = { mode: null, model: null, effort: null, skill: null, later: false, review: false };
|
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,
|
|
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',
|
|
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
|
-
//
|
|
5299
|
-
//
|
|
5300
|
-
//
|
|
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