@galda/cli 0.10.54 → 0.10.55

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
@@ -6969,7 +6969,10 @@ function renderFsTodo(){
6969
6969
  // Parked over-cap replies ride at the TOP of To Do (above the ordinary Next-up
6970
6970
  // queue), as neutral "Queued" rows — the same goal-row shape as a resumed Doing
6971
6971
  // goal but without the green "Working on it", because no worker is running yet.
6972
- const parkedRows = parkedReplyGoals.map((g) => `<div class="trow up" data-goalid="${g.id}"><span class="st"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword" style="color:var(--ink3)">Queued</span>${fsActs(g.id)}</div>`).join('');
6972
+ const canUseCodex = state.auth?.reason === 'rate-limited' && (state.agents || []).includes('codex');
6973
+ const codexAction = (goalId, agent) => canUseCodex && agent === 'claude-code'
6974
+ ? `<button class="ab txt" data-fscodex="${goalId}" title="Start now with Codex">Run with Codex</button>` : '';
6975
+ const parkedRows = parkedReplyGoals.map((g) => `<div class="trow up" data-goalid="${g.id}"><span class="st"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword" style="color:var(--ink3)">Queued</span>${codexAction(g.id, g.agent)}${fsActs(g.id)}</div>`).join('');
6973
6976
  const NCAP = 6;
6974
6977
  const upShown = fsUI.todoMore ? queued : queued.slice(0, NCAP);
6975
6978
  const seenGoals = new Set();
@@ -6984,7 +6987,7 @@ function renderFsTodo(){
6984
6987
  <div class="reps">${reps.map((r) => `<div class="rep">${tt(r.detail ?? r.title, 120)}</div>`).join('')}</div></div>`;
6985
6988
  }
6986
6989
  }
6987
- return `<div class="trow up${i >= 2 ? ' dim' : ''}" data-goalid="${t.goalId ?? ''}" data-taskid="${t.id}"><span class="st"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span>${fsActs(t.goalId)}</div>${grp}`;
6990
+ return `<div class="trow up${i >= 2 ? ' dim' : ''}" data-goalid="${t.goalId ?? ''}" data-taskid="${t.id}"><span class="st"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span>${codexAction(t.goalId, t.agent)}${fsActs(t.goalId)}</div>${grp}`;
6988
6991
  }).join('');
6989
6992
  // CDO review 2026-07-08 §7: this header count used to be queued.length alone (the
6990
6993
  // "Next up" pool only) — so a project sitting in Doing/Later with an empty Next-up
@@ -7044,6 +7047,12 @@ function renderFsTodo(){
7044
7047
  };
7045
7048
  for (const b of el.querySelectorAll('[data-fspause]')) b.onclick = togglePauseActive; // list版 Doing 一時停止/再生(+Paused行のResume)
7046
7049
  for (const b of el.querySelectorAll('[data-fsretry]')) b.onclick = () => fsRetryTask(b.dataset.fsretry);
7050
+ for (const b of el.querySelectorAll('[data-fscodex]')) b.onclick = async (e) => { e.stopPropagation();
7051
+ b.disabled = true; b.textContent = 'Starting…';
7052
+ const r = await fetch(withKey(`/api/goals/${b.dataset.fscodex}/run-with-codex`), { method: 'POST' });
7053
+ if (!r.ok) showErr((await r.json().catch(() => ({}))).error || 'Could not start with Codex.');
7054
+ await refresh();
7055
+ };
7047
7056
  for (const b of el.querySelectorAll('[data-fslog]')) b.onclick = () => fsToggleLog(Number(b.dataset.fslog));
7048
7057
  for (const b of el.querySelectorAll('[data-fsglog]')) b.onclick = () => toggleGoalDetail(Number(b.dataset.fsglog));
7049
7058
  for (const b of el.querySelectorAll('[data-fsreverify]')) b.onclick = async () => {
@@ -7696,7 +7705,7 @@ const AUTH_REASON_TEXT = {
7696
7705
  '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.',
7697
7706
  'bad-credentials': 'Worker sign-in failed. Check ANTHROPIC_API_KEY, or run `claude` and /login, then relaunch the manager from that terminal.',
7698
7707
  'probe-failed': 'Couldn’t verify claude sign-in. Ensure the `claude` CLI is installed and signed in, then relaunch the manager.',
7699
- 'rate-limited': 'Claude has reached its usage limit. Claude tasks are queued until access resets; Codex tasks can continue now.',
7708
+ 'rate-limited': 'Claude is temporarily unavailable. Your Claude tasks are safely waiting. Tap “Run with Codex beside a task to start it now.',
7700
7709
  };
7701
7710
  function renderAuthBanner(auth){
7702
7711
  const bar = $('errbar');
package/engine/lib.mjs CHANGED
@@ -631,11 +631,12 @@ export function parseCodexEvents(line) {
631
631
  // (the id from a previous run's thread.started) — that subcommand takes neither
632
632
  // --cd nor --sandbox, so the working directory comes from the spawn's cwd and
633
633
  // the sandbox from -c sandbox_mode. Verified against codex-cli 0.142.4.
634
- export function buildCodexArgs({ model, effort, sandbox, cwd, resume } = {}) {
634
+ export function buildCodexArgs({ model, effort, sandbox, cwd, resume, images = [] } = {}) {
635
635
  const common = ['--json', '-m', model, '-c', `model_reasoning_effort="${effort}"`, '--skip-git-repo-check'];
636
+ const imageArgs = images.filter(Boolean).flatMap((path) => ['--image', String(path)]);
636
637
  return resume
637
- ? ['exec', 'resume', String(resume), ...common, '-c', `sandbox_mode="${sandbox}"`, '-']
638
- : ['exec', ...common, '--sandbox', sandbox, '--cd', cwd, '-'];
638
+ ? ['exec', 'resume', String(resume), ...common, ...imageArgs, '-c', `sandbox_mode="${sandbox}"`, '-']
639
+ : ['exec', ...common, ...imageArgs, '--sandbox', sandbox, '--cd', cwd, '-'];
639
640
  }
640
641
 
641
642
  // Parse the planner's reply into { entry, tasks }. Accepts either a JSON
package/engine/server.mjs CHANGED
@@ -1192,10 +1192,10 @@ function runClaude({ prompt, cwd, tools, onEvent, onTodos, resume, model, effort
1192
1192
  // The Codex half of the worker bridge. Same contract as runClaude, because
1193
1193
  // nothing downstream may care which agent ran: live activity, the agent's own
1194
1194
  // to-dos (the board), the thread id to talk to it again, usage, final message.
1195
- function runCodex({ prompt, cwd, onEvent, onTodos, resume, permissionMode = 'acceptEdits', model, effort, onChild }) {
1195
+ function runCodex({ prompt, cwd, onEvent, onTodos, resume, permissionMode = 'acceptEdits', model, effort, images, onChild }) {
1196
1196
  return new Promise((done) => {
1197
1197
  const sandbox = permissionMode === 'plan' ? 'read-only' : 'workspace-write';
1198
- const args = buildCodexArgs({ model: workerModel('codex', model), effort: workerEffort('codex', effort), sandbox, cwd, resume });
1198
+ const args = buildCodexArgs({ model: workerModel('codex', model), effort: workerEffort('codex', effort), sandbox, cwd, resume, images });
1199
1199
  const child = spawn('codex', args, { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
1200
1200
  onChild?.(child);
1201
1201
  child.stdin.end(prompt);
@@ -2330,6 +2330,7 @@ async function runTask(task) {
2330
2330
  // reply/rework on a normal goal stays acceptEdits as before.
2331
2331
  permissionMode: permissionModeFor(goal?.mode === 'plan' && goal?.planPhase !== 'executing' ? 'plan' : undefined),
2332
2332
  model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
2333
+ images: goal?.images,
2333
2334
  onTodos: (todos) => sendTodos(task, todos),
2334
2335
  onChild: (c) => trackGoalWorker(goal?.id, c),
2335
2336
  });
@@ -2385,6 +2386,7 @@ async function runTask(task) {
2385
2386
  prompt, cwd: workDir, tools: WORKER_TOOLS,
2386
2387
  resume: resumeSession, permissionMode,
2387
2388
  model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
2389
+ images: goal?.images,
2388
2390
  onTodos: (todos) => sendTodos(task, todos),
2389
2391
  onChild: (c) => trackGoalWorker(goal?.id, c),
2390
2392
  });
@@ -2410,6 +2412,7 @@ async function runTask(task) {
2410
2412
  prompt: workerPrompt(task, goal, null, workerAgent(task.agent ?? goal?.agent)), cwd: workDir, tools: WORKER_TOOLS,
2411
2413
  permissionMode,
2412
2414
  model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
2415
+ images: goal?.images,
2413
2416
  onTodos: (todos) => sendTodos(task, todos),
2414
2417
  onChild: (c) => trackGoalWorker(goal?.id, c),
2415
2418
  });
@@ -4005,6 +4008,28 @@ const server = createServer(async (req, res) => {
4005
4008
  return json(res, 200, task);
4006
4009
  }
4007
4010
 
4011
+ // A Claude usage hold should be actionable without asking people to edit
4012
+ // models or sessions. Move every still-queued step for this goal together so
4013
+ // one tap cannot leave a mixed Claude/Codex chain behind.
4014
+ const runWithCodexMatch = url.pathname.match(/^\/api\/goals\/(\d+)\/run-with-codex$/);
4015
+ if (runWithCodexMatch && req.method === 'POST') {
4016
+ const goal = goals.find((g) => g.id === Number(runWithCodexMatch[1]));
4017
+ if (!goal) return json(res, 404, { error: 'goal not found' });
4018
+ if (!requireGoalOwnership(req, res, goal)) return;
4019
+ if (!AVAILABLE_AGENTS.includes('codex')) return json(res, 409, { error: 'Codex is not connected on this device' });
4020
+ const queuedTasks = tasks.filter((task) => task.goalId === goal.id && task.status === 'queued');
4021
+ if (!queuedTasks.length) return json(res, 409, { error: 'this goal has no queued work' });
4022
+ const model = CODEX_MODELS[0];
4023
+ goal.agent = 'codex'; goal.model = model; goal.sessionId = null; goal.startedAt = null;
4024
+ saveGoal(goal);
4025
+ for (const task of queuedTasks) {
4026
+ task.agent = 'codex'; task.model = model; task.sessionId = null;
4027
+ saveTask(task);
4028
+ }
4029
+ pump(goal.projectId);
4030
+ return json(res, 200, { goal, tasks: queuedTasks });
4031
+ }
4032
+
4008
4033
  // Start what the worker said to start, and hand back the URL to look at.
4009
4034
  //
4010
4035
  // This is the "動かして見られる状態" the review card offers. It replaces the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.54",
3
+ "version": "0.10.55",
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": {