@galda/cli 0.10.115 → 0.10.116

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 CHANGED
@@ -5355,6 +5355,67 @@ export function shouldEscalateConnectGateHint(waitedMs, email, thresholdMs = 150
5355
5355
  // for this" signal is a concrete MANAGER_PORT: the launcher (bin/manager-for-ai
5356
5356
  // .mjs) resolves a real port BEFORE spawn, while every ephemeral spawn uses
5357
5357
  // MANAGER_PORT=0 (OS-assigned). So port 0 is never a browser-opening boot.
5358
+ // What the terminal says when Galda starts.
5359
+ //
5360
+ // It used to say one log line among several — `[manager] Manager for AI →
5361
+ // http://localhost:4400/?key=…` — and then open a tab in the background, in
5362
+ // whatever browser the machine calls default. Masa's friend never saw that tab
5363
+ // (2026-07-31): it opened behind their windows in a browser they do not use, and
5364
+ // nothing on screen told them where their own copy of Galda was. The address is
5365
+ // the one thing a person needs, so it gets its own line and an instruction, and
5366
+ // the auto-opened tab is described as what it is — a convenience that may have
5367
+ // missed.
5368
+ //
5369
+ // Three destinations, and the wording differs because the links differ:
5370
+ // local — http://localhost:PORT/?key=… safe to paste anywhere on this Mac,
5371
+ // in any browser, as often as you like.
5372
+ // app — the hosted app; already signed in.
5373
+ // signin — a ONE-TIME sign-in link. Which browser it is opened in decides
5374
+ // which browser ends up signed in, so that choice is stated before
5375
+ // it is made rather than explained afterwards in a 400 page.
5376
+ // WHERE this install's Galda actually is. Decided from how it was installed, not
5377
+ // from whether a browser could be opened: `npx @galda/cli` configures the billing
5378
+ // + app URLs and those people live in the hosted app, while the localhost ?key
5379
+ // board is the entry only for local/dev. Reading this off the auto-open branch
5380
+ // (as the first version of the banner did) told a hosted user on Linux — or
5381
+ // anyone with MANAGER_OPEN_BROWSER=0 — to open a localhost board with an access
5382
+ // key, which the hosted flow has always refused to do.
5383
+ export function bootDestination({ hosted, signedIn, localUrl, appUrl, signinUrl } = {}) {
5384
+ if (!hosted) return { url: String(localUrl ?? ''), mode: 'local' };
5385
+ return signedIn
5386
+ ? { url: String(appUrl ?? ''), mode: 'app' }
5387
+ : { url: String(signinUrl ?? ''), mode: 'signin' };
5388
+ }
5389
+
5390
+ export function bootBanner({ url, mode = 'local', opened = false } = {}) {
5391
+ const address = String(url ?? '');
5392
+ if (!address) return [];
5393
+ const head = {
5394
+ local: 'Galda is running. Open this in your browser:',
5395
+ app: 'Galda is running. Open your board:',
5396
+ signin: 'Galda is running. Sign in to continue — open this in the browser you want to use Galda in:',
5397
+ }[mode] ?? 'Galda is running. Open this in your browser:';
5398
+ const foot = {
5399
+ local: 'Paste it into any browser on this Mac — the key is part of the address.',
5400
+ app: 'Paste it into any browser you are signed in to.',
5401
+ signin: 'This link works once. Whichever browser opens it is the one that ends up signed in.',
5402
+ }[mode];
5403
+ return [
5404
+ '',
5405
+ ` ${head}`,
5406
+ '',
5407
+ ` ${address}`,
5408
+ '',
5409
+ ` ${foot}`,
5410
+ // Said even when we opened a tab — especially then, for a sign-in link:
5411
+ // the tab we opened is what chose the browser, and the person may not have
5412
+ // seen it happen.
5413
+ ...(opened ? [' (A tab may already have opened in your default browser. If you did not see it,'
5414
+ + ' or you use a different browser, use the address above.)'] : []),
5415
+ '',
5416
+ ];
5417
+ }
5418
+
5358
5419
  export function shouldOpenBrowserOnBoot({ openBrowser, platform, managerPort } = {}) {
5359
5420
  if (String(openBrowser) !== '1') return false;
5360
5421
  if (platform !== 'darwin') return false;
package/engine/server.mjs CHANGED
@@ -20,7 +20,7 @@ import { resolve, dirname, join, basename } from 'node:path';
20
20
  import { homedir } from 'node:os';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { pathToFileURL } from 'node:url';
23
- import { folderLabel, chooseFolderScript, parseChosenFolder, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer, routeQuestionAnswer, notUnderstoodNote, parseRelocalizedQuestion, classifyAuthProbe, authBannerText, makeSigninChallenge, parseClaimResponse, isCommandOnPath, hostReadiness, parseRequestedWorkspaceDir } from './lib.mjs';
23
+ import { bootBanner, bootDestination, folderLabel, chooseFolderScript, parseChosenFolder, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer, routeQuestionAnswer, notUnderstoodNote, parseRelocalizedQuestion, classifyAuthProbe, authBannerText, makeSigninChallenge, parseClaimResponse, isCommandOnPath, hostReadiness, parseRequestedWorkspaceDir } from './lib.mjs';
24
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, buildQueueWaits, 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, REQUIREMENT_EVIDENCE_FILE, parseRequirementEvidenceDocument, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, buildGoalMessage, serializeGoalMessage, parseGoalMessages, computeInternalQualityMetrics, classifyInternalQualityTaskError, resolveWorkspaceMode, goalSessionFor, setGoalAgentSession, switchGoalAgentSession, parseApprovalRequest } from './lib.mjs';
25
25
  import { migrateRequirementModel, validateRequirementModel } from './requirement-model.mjs';
26
26
  import { buildManagerVerificationChecks, buildRequirementVerificationEvidence, mergeRequirementVerificationEvidence } from './requirement-verification.mjs';
@@ -6304,7 +6304,25 @@ server.listen(PORT, '127.0.0.1', () => {
6304
6304
  }
6305
6305
  PORT = server.address().port; // the OS-assigned one when MANAGER_PORT=0
6306
6306
  try { server6.listen(PORT, '::1'); } catch { /* IPv6 loopback unavailable */ }
6307
- console.log(`[manager] Manager for AI → http://localhost:${PORT}/?key=${ACCESS_KEY}`);
6307
+ const localUrl = `http://localhost:${PORT}/?key=${ACCESS_KEY}`;
6308
+ // WHERE this person's Galda is depends on how it was installed, not on whether
6309
+ // we managed to open a tab. `npx @galda/cli` sets the billing + app URLs, and
6310
+ // those people live in the hosted app; the localhost ?key board is the entry
6311
+ // only for local/dev (MANAGER_BILLING_API_URL= opt-out, or `npm start` in the
6312
+ // repo). Deciding this from the auto-open branch — as this first did — told a
6313
+ // hosted user on Linux, or anyone with MANAGER_OPEN_BROWSER=0, to open a
6314
+ // localhost board with an access key: the one thing the flow below has always
6315
+ // refused to do.
6316
+ const hostedFlow = Boolean(BILLING_API_URL && APP_URL);
6317
+ const alreadySignedIn = existsSync(licenseFile) && !FORCE_SIGNIN;
6318
+ // buildSigninUrl() also starts the background claim poll — which is exactly
6319
+ // what a person about to open that link by hand needs running, so it is called
6320
+ // only on the branch that will actually show it.
6321
+ const { url: bannerUrl, mode: bannerMode } = bootDestination({
6322
+ hosted: hostedFlow, signedIn: alreadySignedIn, localUrl, appUrl: APP_URL,
6323
+ signinUrl: hostedFlow && !alreadySignedIn ? buildSigninUrl() : '',
6324
+ });
6325
+ let bannerOpened = false;
6308
6326
  if (shouldOpenBrowserOnBoot({ openBrowser: process.env.MANAGER_OPEN_BROWSER, platform: process.platform, managerPort: process.env.MANAGER_PORT })) {
6309
6327
  // Hosted flow (billing worker + named app configured): the user lives in
6310
6328
  // app.galda.app, driven over the relay — they must NEVER be dropped on a
@@ -6313,18 +6331,10 @@ server.listen(PORT, '127.0.0.1', () => {
6313
6331
  // → one-click Google sign-in; already signed in → straight to the app (the
6314
6332
  // board comes alive as relay-client rebinds via its license.token watcher).
6315
6333
  // The localhost ?key board stays the entry ONLY for local/dev (no billing).
6316
- let openUrl = `http://localhost:${PORT}/?key=${ACCESS_KEY}`;
6317
- if (BILLING_API_URL && APP_URL) {
6318
- const signedIn = existsSync(licenseFile) && !FORCE_SIGNIN;
6319
- openUrl = signedIn
6320
- ? APP_URL
6321
- : buildSigninUrl(); // device-flow: also starts the background claim poll
6322
- console.log(signedIn
6323
- ? `[manager] opening your app → ${APP_URL}`
6324
- : `[manager] ${FORCE_SIGNIN ? 'switching account' : 'first run'}: opening one-click sign-in → ${openUrl}`);
6325
- }
6326
- spawn('open', ['-g', openUrl], { stdio: 'ignore' }).unref();
6334
+ bannerOpened = true;
6335
+ spawn('open', ['-g', bannerUrl], { stdio: 'ignore' }).unref();
6327
6336
  }
6337
+ for (const line of bootBanner({ url: bannerUrl, mode: bannerMode, opened: bannerOpened })) console.log(line);
6328
6338
  console.log(`[manager] projects: ${projects.map((p) => `${p.id}=${p.dir}`).join(' ')}`);
6329
6339
  if (shouldProbeClaudeAuth(AVAILABLE_AGENTS)) probeWorkerAuth().catch(() => {}); // hold Claude tasks + banner if Claude is unavailable
6330
6340
  setInterval(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.115",
3
+ "version": "0.10.116",
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": {