@galda/cli 0.10.54 → 0.10.56
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 +23 -15
- package/engine/lib.mjs +4 -3
- package/engine/server.mjs +27 -2
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -6890,14 +6890,12 @@ function renderFsTodo(){
|
|
|
6890
6890
|
// 一時停止したゴール(blocked kind:'paused')は Doing に「Paused」1行で留める(Masa 2026-07-19)。
|
|
6891
6891
|
// Needs you 側(failed/blocked)へは出さず、その走行/中断タスク行も抑止して二重行を作らない。
|
|
6892
6892
|
const pausedGoalIds = new Set(pgoals.filter((g) => g.status === 'blocked' && g.blocked?.kind === 'paused').map((g) => g.id));
|
|
6893
|
-
// A
|
|
6894
|
-
//
|
|
6895
|
-
//
|
|
6893
|
+
// A reply is a real running task and already renders in Doing. Keep its goal id
|
|
6894
|
+
// only to hide the old failed row; rendering a second goal row duplicated the
|
|
6895
|
+
// same card whenever feedback was being applied.
|
|
6896
6896
|
const runningTaskGoalIds = new Set(running.filter((t) => !t.reply).map((t) => t.goalId));
|
|
6897
6897
|
const runningReplyGoalIds = new Set(list.filter((t) => t.reply && t.status === 'running').map((t) => t.goalId));
|
|
6898
6898
|
const resumedGoalIds = new Set(runningReplyGoalIds);
|
|
6899
|
-
const doingGoals = pgoals.filter((g) => g.status === 'running' && g.startedAt
|
|
6900
|
-
&& runningReplyGoalIds.has(g.id) && !runningTaskGoalIds.has(g.id) && !pausedGoalIds.has(g.id));
|
|
6901
6899
|
// A reply that was OVER the parallel cap (engine PR #342): the goal is 'running'
|
|
6902
6900
|
// but startedAt is cleared, and its only pending work is a QUEUED reply task —
|
|
6903
6901
|
// which Doing needs startedAt for, and Next-up excludes (!t.reply). With no
|
|
@@ -6926,13 +6924,14 @@ function renderFsTodo(){
|
|
|
6926
6924
|
// out to its own "Needs you" caption below (Masa 2026-07-19) — folding it into Doing read
|
|
6927
6925
|
// as "still in flight".
|
|
6928
6926
|
const doingRows =
|
|
6929
|
-
running.filter((t) => !pausedGoalIds.has(t.goalId)).map((t) =>
|
|
6930
|
-
|
|
6927
|
+
running.filter((t) => !pausedGoalIds.has(t.goalId)).map((t) => {
|
|
6928
|
+
const goal = pgoals.find((g) => g.id === t.goalId);
|
|
6929
|
+
const title = t.reply ? (goal?.reviewSummary?.check || goal?.plan?.[0] || goal?.text || t.title) : t.title;
|
|
6930
|
+
return `<div class="drow"><div class="trow now"><span class="st run"></span>${noSpan(t.goalId)}<span class="tt">${tt(title)}</span><span class="pct num">${progressFor(t)}%</span>${fsActs(t.goalId)}</div>
|
|
6931
|
+
<div class="tbar"><i style="width:${progressFor(t)}%"></i></div></div>`;
|
|
6932
|
+
}).join('')
|
|
6931
6933
|
// 一時停止したゴールは Doing に「Paused」1行(アンバー)で留める+Resume。走行/中断行は上と needsRows で抑止済み。
|
|
6932
|
-
+ pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume — 再開">Resume</button></div></div>`).join('')
|
|
6933
|
-
// A goal picked back up by a reply (running, no running work task yet): show it here so it
|
|
6934
|
-
// lands in Doing instead of vanishing. No % bar — nothing measurable is running yet.
|
|
6935
|
-
+ doingGoals.map((g) => `<div class="drow"><div class="trow now"><span class="st run"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword" style="color:var(--green)">Working on it</span>${fsActs(g.id)}</div></div>`).join('');
|
|
6934
|
+
+ pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume — 再開">Resume</button></div></div>`).join('');
|
|
6936
6935
|
// NEEDS YOU: stopped/errored work that used to fold into Doing, now under its own caption so
|
|
6937
6936
|
// it no longer reads as "still in flight" (Masa 2026-07-19). Three kinds share it, each
|
|
6938
6937
|
// keeping the affordances it already had — nothing about the failed-task and blocked-goal
|
|
@@ -6969,7 +6968,10 @@ function renderFsTodo(){
|
|
|
6969
6968
|
// Parked over-cap replies ride at the TOP of To Do (above the ordinary Next-up
|
|
6970
6969
|
// queue), as neutral "Queued" rows — the same goal-row shape as a resumed Doing
|
|
6971
6970
|
// goal but without the green "Working on it", because no worker is running yet.
|
|
6972
|
-
const
|
|
6971
|
+
const canUseCodex = state.auth?.reason === 'rate-limited' && (state.agents || []).includes('codex');
|
|
6972
|
+
const codexAction = (goalId, agent) => canUseCodex && agent === 'claude-code'
|
|
6973
|
+
? `<button class="ab txt" data-fscodex="${goalId}" title="Start now with Codex">Run with Codex</button>` : '';
|
|
6974
|
+
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
6975
|
const NCAP = 6;
|
|
6974
6976
|
const upShown = fsUI.todoMore ? queued : queued.slice(0, NCAP);
|
|
6975
6977
|
const seenGoals = new Set();
|
|
@@ -6984,7 +6986,7 @@ function renderFsTodo(){
|
|
|
6984
6986
|
<div class="reps">${reps.map((r) => `<div class="rep">${tt(r.detail ?? r.title, 120)}</div>`).join('')}</div></div>`;
|
|
6985
6987
|
}
|
|
6986
6988
|
}
|
|
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}`;
|
|
6989
|
+
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
6990
|
}).join('');
|
|
6989
6991
|
// CDO review 2026-07-08 §7: this header count used to be queued.length alone (the
|
|
6990
6992
|
// "Next up" pool only) — so a project sitting in Doing/Later with an empty Next-up
|
|
@@ -6995,7 +6997,7 @@ function renderFsTodo(){
|
|
|
6995
6997
|
// queued.length specifically, since that suffix describes the Next-up sublist's own sort,
|
|
6996
6998
|
// not the lane total.
|
|
6997
6999
|
const needsCount = failed.filter((t) => !resumedGoalIds.has(t.goalId)).length + blocked.length + needsInput.length;
|
|
6998
|
-
const todoTotal = running.length +
|
|
7000
|
+
const todoTotal = running.length + parkedReplyGoals.length + needsCount + queued.length + later.length;
|
|
6999
7001
|
// Free-tier pending counter (H22): on the free plan the header shows the wall
|
|
7000
7002
|
// — pending goals out of the 5-slot cap — reddening at the cap and gone once
|
|
7001
7003
|
// paid. Reads the SAME numbers the gate enforces (state.billing, never a local
|
|
@@ -7044,6 +7046,12 @@ function renderFsTodo(){
|
|
|
7044
7046
|
};
|
|
7045
7047
|
for (const b of el.querySelectorAll('[data-fspause]')) b.onclick = togglePauseActive; // list版 Doing 一時停止/再生(+Paused行のResume)
|
|
7046
7048
|
for (const b of el.querySelectorAll('[data-fsretry]')) b.onclick = () => fsRetryTask(b.dataset.fsretry);
|
|
7049
|
+
for (const b of el.querySelectorAll('[data-fscodex]')) b.onclick = async (e) => { e.stopPropagation();
|
|
7050
|
+
b.disabled = true; b.textContent = 'Starting…';
|
|
7051
|
+
const r = await fetch(withKey(`/api/goals/${b.dataset.fscodex}/run-with-codex`), { method: 'POST' });
|
|
7052
|
+
if (!r.ok) showErr((await r.json().catch(() => ({}))).error || 'Could not start with Codex.');
|
|
7053
|
+
await refresh();
|
|
7054
|
+
};
|
|
7047
7055
|
for (const b of el.querySelectorAll('[data-fslog]')) b.onclick = () => fsToggleLog(Number(b.dataset.fslog));
|
|
7048
7056
|
for (const b of el.querySelectorAll('[data-fsglog]')) b.onclick = () => toggleGoalDetail(Number(b.dataset.fsglog));
|
|
7049
7057
|
for (const b of el.querySelectorAll('[data-fsreverify]')) b.onclick = async () => {
|
|
@@ -7696,7 +7704,7 @@ const AUTH_REASON_TEXT = {
|
|
|
7696
7704
|
'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
7705
|
'bad-credentials': 'Worker sign-in failed. Check ANTHROPIC_API_KEY, or run `claude` and /login, then relaunch the manager from that terminal.',
|
|
7698
7706
|
'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
|
|
7707
|
+
'rate-limited': 'Claude is temporarily unavailable. Your Claude tasks are safely waiting. Tap “Run with Codex” beside a task to start it now.',
|
|
7700
7708
|
};
|
|
7701
7709
|
function renderAuthBanner(auth){
|
|
7702
7710
|
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