@galda/cli 0.10.52 → 0.10.53

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
@@ -7695,6 +7695,7 @@ const AUTH_REASON_TEXT = {
7695
7695
  'logged-out': 'Not signed in to claude. Run `claude` in a terminal and /login, then relaunch the manager from that terminal — tasks are paused until then.',
7696
7696
  'bad-credentials': 'Worker sign-in failed. Check ANTHROPIC_API_KEY, or run `claude` and /login, then relaunch the manager from that terminal.',
7697
7697
  'probe-failed': 'Couldn’t verify claude sign-in. Ensure the `claude` CLI is installed and signed in, then relaunch the manager.',
7698
+ 'rate-limited': 'Claude has reached its usage limit. Claude tasks are queued until access resets; Codex tasks can continue now.',
7698
7699
  };
7699
7700
  function renderAuthBanner(auth){
7700
7701
  const bar = $('errbar');
package/engine/server.mjs CHANGED
@@ -26,6 +26,7 @@ import { createSerialQueue } from './lib.mjs';
26
26
  import { collectProjectRules } from './lib.mjs';
27
27
  import { FALLBACK_CODEX_MODELS, loadCodexModels } from './codex-models.mjs';
28
28
  import { CLAUDE_MODELS } from './claude-models.mjs';
29
+ import { classifyWorkerPreflight, workerCanStart } from './worker-availability.mjs';
29
30
  import { openPR } from './pr.mjs';
30
31
  import { runVerification, exerciseUi } from './verify.mjs';
31
32
 
@@ -1470,9 +1471,9 @@ function startNextStacked(projectId) {
1470
1471
  // standalone `claude` (runClaude), inheriting the launching shell's login. If
1471
1472
  // that shell has no usable credentials, every task fails late with "Not logged
1472
1473
  // in". We probe once at boot and hold the queue + raise a banner instead of
1473
- // failing task after task. Optimistic default (ok:true) so we never falsely
1474
- // block a correctly-authed setup during the ~1s probe.
1475
- let workerAuth = { ok: true, reason: null, checkedAt: null };
1474
+ // failing task after task. Claude waits for the short probe; Codex is gated
1475
+ // independently and may start immediately.
1476
+ let workerAuth = { ok: null, reason: null, checkedAt: null };
1476
1477
  const publicAuth = () => ({ ok: workerAuth.ok, reason: workerAuth.reason });
1477
1478
 
1478
1479
  // Boot preflight: one cheap Read-only `claude` call; classify the result (pure,
@@ -1481,14 +1482,18 @@ const publicAuth = () => ({ ok: workerAuth.ok, reason: workerAuth.reason });
1481
1482
  async function probeWorkerAuth() {
1482
1483
  try {
1483
1484
  const out = await runClaude({ prompt: 'Reply with the single word: ok', cwd: ROOT, tools: 'Read', model: 'haiku' });
1484
- workerAuth = { ...classifyAuthProbe({ result: out?.result, code: out?.code }), checkedAt: Date.now() };
1485
+ workerAuth = {
1486
+ ...classifyWorkerPreflight(out, { classifyAuthProbe, isRateLimited }),
1487
+ checkedAt: Date.now(),
1488
+ };
1485
1489
  } catch {
1486
1490
  workerAuth = { ok: false, reason: 'probe-failed', checkedAt: Date.now() };
1487
1491
  }
1488
1492
  console.log(workerAuth.ok
1489
1493
  ? '[manager] worker auth preflight OK'
1490
- : `[manager] worker auth preflight FAILED (${workerAuth.reason}) — holding the queue. ${authBannerText(workerAuth.reason, 'en')}`);
1494
+ : `[manager] Claude preflight unavailable (${workerAuth.reason}) — holding Claude tasks; Codex may continue.`);
1491
1495
  send({ ev: 'auth', auth: publicAuth() });
1496
+ if (workerAuth.ok) for (const project of projects) pump(project.id);
1492
1497
  return workerAuth;
1493
1498
  }
1494
1499
 
@@ -1654,10 +1659,8 @@ async function authorVerify(task, goal, project) {
1654
1659
  // `lastStartAt` check in pump() below.
1655
1660
  const slowModeLastStart = new Map(); // projectId -> ms epoch of the last task start
1656
1661
  function pump(projectId) {
1657
- // Worker auth preflight failed hold the queue (the UI banner tells the user
1658
- // to sign `claude` in and relaunch). Never spawn a worker that can only fail
1659
- // with "Not logged in". Cleared automatically once a probe succeeds.
1660
- if (!workerAuth.ok) return;
1662
+ // A Claude sign-in or usage hold must not stop an installed Codex worker.
1663
+ // Claude work stays queued until a later probe succeeds.
1661
1664
  // Project paused by the user (Doing列の一時停止 / chat) → hold this project's
1662
1665
  // queue exactly like the auth hold above. Resume flips the flag and pumps.
1663
1666
  if (isProjectPaused(projectId)) return;
@@ -1684,7 +1687,8 @@ function pump(projectId) {
1684
1687
  if (wait > 0) { setTimeout(() => pump(projectId), wait); return; }
1685
1688
  }
1686
1689
  const runningGoalIds = [...q.running].map((t) => t.goalId);
1687
- const toStart = nextRunnableTasks(q.waiting, runningGoalIds, slow.enabled ? 1 : slots);
1690
+ const eligible = q.waiting.filter((task) => workerCanStart({ agent: task.agent, auth: workerAuth }));
1691
+ const toStart = nextRunnableTasks(eligible, runningGoalIds, slow.enabled ? 1 : slots);
1688
1692
  for (const task of toStart) {
1689
1693
  const i = q.waiting.indexOf(task);
1690
1694
  if (i !== -1) q.waiting.splice(i, 1);
@@ -1981,7 +1985,9 @@ function queueReplyTask(goal, text, { from = 'manager', via = 'composer' } = {})
1981
1985
  const projectCap = slow.enabled ? Math.min(PROJECT_MAX_PARALLEL, slow.maxParallel) : PROJECT_MAX_PARALLEL;
1982
1986
  const slots = Math.min(projectCap - q.running.size, GLOBAL_MAX_PARALLEL - globalRunningCount());
1983
1987
  const runningGoalIds = [...q.running].map((t) => t.goalId);
1984
- const startsNow = nextRunnableTasks(q.waiting, runningGoalIds, slow.enabled ? 1 : slots).some((t) => t.id === task.id);
1988
+ const eligible = q.waiting.filter((candidate) => workerCanStart({ agent: candidate.agent, auth: workerAuth }));
1989
+ const startsNow = workerCanStart({ agent: task.agent, auth: workerAuth })
1990
+ && nextRunnableTasks(eligible, runningGoalIds, slow.enabled ? 1 : slots).some((candidate) => candidate.id === task.id);
1985
1991
  goal.startedAt = startsNow ? new Date().toISOString() : null;
1986
1992
  saveGoal(goal);
1987
1993
  }
@@ -4913,7 +4919,10 @@ server.listen(PORT, '127.0.0.1', () => {
4913
4919
  spawn('open', ['-g', openUrl], { stdio: 'ignore' }).unref();
4914
4920
  }
4915
4921
  console.log(`[manager] projects: ${projects.map((p) => `${p.id}=${p.dir}`).join(' ')}`);
4916
- probeWorkerAuth().catch(() => {}); // hold the queue + banner if `claude` isn't signed in
4922
+ probeWorkerAuth().catch(() => {}); // hold Claude tasks + banner if Claude is unavailable
4923
+ setInterval(() => {
4924
+ if (!workerAuth.ok) probeWorkerAuth().catch(() => {});
4925
+ }, 5 * 60_000).unref();
4917
4926
  syncAllExternal().catch(() => {});
4918
4927
  syncGoalMerges().catch(() => {});
4919
4928
  setInterval(() => {
@@ -0,0 +1,8 @@
1
+ export function classifyWorkerPreflight({ result = '', code = 0 } = {}, { classifyAuthProbe, isRateLimited }) {
2
+ if (isRateLimited(result)) return { ok: false, reason: 'rate-limited' };
3
+ return classifyAuthProbe({ result, code });
4
+ }
5
+
6
+ export function workerCanStart({ agent, auth } = {}) {
7
+ return auth?.ok === true || agent === 'codex';
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.52",
3
+ "version": "0.10.53",
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": {