@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 +61 -0
- package/engine/server.mjs +23 -13
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
6317
|
-
|
|
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