@galda/cli 0.10.32 → 0.10.34
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/bin/manager-for-ai.mjs +6 -2
- package/engine/lib.mjs +32 -0
- package/engine/server.mjs +2 -2
- package/package.json +1 -1
package/bin/manager-for-ai.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync } from 'node:fs';
|
|
|
11
11
|
import { join, resolve, dirname } from 'node:path';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
13
|
import { createServer } from 'node:net';
|
|
14
|
-
import { isCommandOnPath, resolveConnectedAgents } from '../engine/lib.mjs';
|
|
14
|
+
import { isCommandOnPath, resolveConnectedAgents, resolveOpenBrowserDefault } from '../engine/lib.mjs';
|
|
15
15
|
|
|
16
16
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
17
|
const say = (m) => console.log(`[galda] ${m}`);
|
|
@@ -78,7 +78,11 @@ if (ffmpeg.error || ffmpeg.status !== 0) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// 4) start the server (state in ~/.manager-for-ai unless MANAGER_HOME is set)
|
|
81
|
-
|
|
81
|
+
// Default to opening a browser only for an interactive (human) run. A worker or
|
|
82
|
+
// CI that shells out to this CLI is non-interactive (piped stdout) and must never
|
|
83
|
+
// pop a Google sign-in window on the user's desktop — 2026-07-18 incident. An
|
|
84
|
+
// explicit MANAGER_OPEN_BROWSER (including empty) is always respected.
|
|
85
|
+
process.env.MANAGER_OPEN_BROWSER = resolveOpenBrowserDefault({ existing: process.env.MANAGER_OPEN_BROWSER, isTTY: Boolean(process.stdout.isTTY) });
|
|
82
86
|
// Public-build defaults (this CLI is the shipped entry point): point at the
|
|
83
87
|
// deployed billing worker + the fixed-URL relay unless the operator overrides.
|
|
84
88
|
// Set on env BEFORE importing the server so it reads them like any other config.
|
package/engine/lib.mjs
CHANGED
|
@@ -3335,6 +3335,38 @@ export function shouldEscalateConnectGateHint(waitedMs, email, thresholdMs = 150
|
|
|
3335
3335
|
return Number(waitedMs) >= thresholdMs && Boolean(String(email ?? '').trim());
|
|
3336
3336
|
}
|
|
3337
3337
|
|
|
3338
|
+
// Whether a booting manager should auto-open a browser window. A browser only
|
|
3339
|
+
// ever opens for a HUMAN-launched interactive manager. Ephemeral spawns — the
|
|
3340
|
+
// test helper (startServer), proof/verify capture instances — must never touch
|
|
3341
|
+
// the user's desktop: they boot with a throwaway license-less MANAGER_HOME,
|
|
3342
|
+
// which reads as "not signed in" and would pop a Google sign-in window per boot
|
|
3343
|
+
// (2026-07-18 incident: UI goals sprayed multiple Chrome sign-in windows while
|
|
3344
|
+
// workers ran the test suite, because MANAGER_OPEN_BROWSER=1 leaked in from the
|
|
3345
|
+
// launcher env into worker→test child processes). The reliable "a human asked
|
|
3346
|
+
// for this" signal is a concrete MANAGER_PORT: the launcher (bin/manager-for-ai
|
|
3347
|
+
// .mjs) resolves a real port BEFORE spawn, while every ephemeral spawn uses
|
|
3348
|
+
// MANAGER_PORT=0 (OS-assigned). So port 0 is never a browser-opening boot.
|
|
3349
|
+
export function shouldOpenBrowserOnBoot({ openBrowser, platform, managerPort } = {}) {
|
|
3350
|
+
if (String(openBrowser) !== '1') return false;
|
|
3351
|
+
if (platform !== 'darwin') return false;
|
|
3352
|
+
if (String(managerPort ?? '') === '0') return false; // ephemeral / test / proof spawn
|
|
3353
|
+
return true;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
// The launcher (bin/manager-for-ai.mjs) side of the same rule: what to DEFAULT
|
|
3357
|
+
// MANAGER_OPEN_BROWSER to when the operator did not set it. shouldOpenBrowserOnBoot
|
|
3358
|
+
// is the belt on the spawn side (ephemeral port-0 boots never open); this is the
|
|
3359
|
+
// belt on the entry side. A human who ran `npx @galda/cli` in a terminal has an
|
|
3360
|
+
// interactive stdout (isTTY); a worker / CI / automation that shells out to the
|
|
3361
|
+
// CLI to "verify against a real server" is non-interactive (piped) — and must
|
|
3362
|
+
// never pop a Google sign-in window on the user's desktop (that path resolves a
|
|
3363
|
+
// real port, so the port-0 belt alone would not catch it). An explicit value
|
|
3364
|
+
// (including empty, meaning "self-hosted, no hosted flow") is always respected.
|
|
3365
|
+
export function resolveOpenBrowserDefault({ existing, isTTY } = {}) {
|
|
3366
|
+
if (existing !== undefined && existing !== null) return String(existing);
|
|
3367
|
+
return isTTY ? '1' : '0';
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3338
3370
|
// --- device-flow (PKCE) sign-in helpers --------------------------------------
|
|
3339
3371
|
// The browser no longer delivers the minted token to a live loopback port —
|
|
3340
3372
|
// the engine POLLS the Worker to claim it (device-flow). PKCE ties the claim to
|
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, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer, routeQuestionAnswer, notUnderstoodNote, parseRelocalizedQuestion, classifyAuthProbe, authBannerText, makeSigninChallenge, parseClaimResponse } 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, pickFoldTarget, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, QUEUED_GOAL_STATUSES, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable, classifyComposerIntentHeuristic, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision } 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, pickFoldTarget, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, QUEUED_GOAL_STATUSES, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable, classifyComposerIntentHeuristic, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot } from './lib.mjs';
|
|
25
25
|
import { openPR } from './pr.mjs';
|
|
26
26
|
import { runVerification, exerciseUi } from './verify.mjs';
|
|
27
27
|
|
|
@@ -4110,7 +4110,7 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
4110
4110
|
PORT = server.address().port; // the OS-assigned one when MANAGER_PORT=0
|
|
4111
4111
|
try { server6.listen(PORT, '::1'); } catch { /* IPv6 loopback unavailable */ }
|
|
4112
4112
|
console.log(`[manager] Manager for AI → http://localhost:${PORT}/?key=${ACCESS_KEY}`);
|
|
4113
|
-
if (process.env.MANAGER_OPEN_BROWSER
|
|
4113
|
+
if (shouldOpenBrowserOnBoot({ openBrowser: process.env.MANAGER_OPEN_BROWSER, platform: process.platform, managerPort: process.env.MANAGER_PORT })) {
|
|
4114
4114
|
// Hosted flow (billing worker + named app configured): the user lives in
|
|
4115
4115
|
// app.galda.app, driven over the relay — they must NEVER be dropped on a
|
|
4116
4116
|
// localhost board or an access key (Masa 2026-07-15: 「npm→ターミナル→
|
package/package.json
CHANGED