@galda/cli 0.10.53 → 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 +16 -6
- package/engine/lib.mjs +4 -3
- package/engine/server.mjs +40 -2
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -6894,16 +6894,17 @@ function renderFsTodo(){
|
|
|
6894
6894
|
// (only a reply task, which Doing excludes) — it used to vanish from the lane (Masa 2026-07-24).
|
|
6895
6895
|
// Show it in Doing as a goal row, and stop its old stopped task from lingering under "Needs you".
|
|
6896
6896
|
const runningTaskGoalIds = new Set(running.filter((t) => !t.reply).map((t) => t.goalId));
|
|
6897
|
-
const
|
|
6897
|
+
const runningReplyGoalIds = new Set(list.filter((t) => t.reply && t.status === 'running').map((t) => t.goalId));
|
|
6898
|
+
const resumedGoalIds = new Set(runningReplyGoalIds);
|
|
6898
6899
|
const doingGoals = pgoals.filter((g) => g.status === 'running' && g.startedAt
|
|
6899
|
-
&& !runningTaskGoalIds.has(g.id) && !pausedGoalIds.has(g.id));
|
|
6900
|
+
&& runningReplyGoalIds.has(g.id) && !runningTaskGoalIds.has(g.id) && !pausedGoalIds.has(g.id));
|
|
6900
6901
|
// A reply that was OVER the parallel cap (engine PR #342): the goal is 'running'
|
|
6901
6902
|
// but startedAt is cleared, and its only pending work is a QUEUED reply task —
|
|
6902
6903
|
// which Doing needs startedAt for, and Next-up excludes (!t.reply). With no
|
|
6903
6904
|
// bucket it vanished. Surface these "parked" goals at the TOP of To Do ("最上部",
|
|
6904
6905
|
// Masa 2026-07-24) as Queued rows — not "Working on it" (nothing runs yet). They
|
|
6905
6906
|
// auto-promote to Doing the moment a slot frees and the engine sets startedAt.
|
|
6906
|
-
const parkedReplyGoals = pgoals.filter((g) => g.status === 'running'
|
|
6907
|
+
const parkedReplyGoals = pgoals.filter((g) => g.status === 'running'
|
|
6907
6908
|
&& !pausedGoalIds.has(g.id) && !runningTaskGoalIds.has(g.id)
|
|
6908
6909
|
&& list.some((t) => t.goalId === g.id && t.reply && t.status === 'queued')
|
|
6909
6910
|
&& !list.some((t) => t.goalId === g.id && !t.reply && ['running', 'queued'].includes(t.status)));
|
|
@@ -6968,7 +6969,10 @@ function renderFsTodo(){
|
|
|
6968
6969
|
// Parked over-cap replies ride at the TOP of To Do (above the ordinary Next-up
|
|
6969
6970
|
// queue), as neutral "Queued" rows — the same goal-row shape as a resumed Doing
|
|
6970
6971
|
// goal but without the green "Working on it", because no worker is running yet.
|
|
6971
|
-
const
|
|
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('');
|
|
6972
6976
|
const NCAP = 6;
|
|
6973
6977
|
const upShown = fsUI.todoMore ? queued : queued.slice(0, NCAP);
|
|
6974
6978
|
const seenGoals = new Set();
|
|
@@ -6983,7 +6987,7 @@ function renderFsTodo(){
|
|
|
6983
6987
|
<div class="reps">${reps.map((r) => `<div class="rep">${tt(r.detail ?? r.title, 120)}</div>`).join('')}</div></div>`;
|
|
6984
6988
|
}
|
|
6985
6989
|
}
|
|
6986
|
-
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}`;
|
|
6987
6991
|
}).join('');
|
|
6988
6992
|
// CDO review 2026-07-08 §7: this header count used to be queued.length alone (the
|
|
6989
6993
|
// "Next up" pool only) — so a project sitting in Doing/Later with an empty Next-up
|
|
@@ -7043,6 +7047,12 @@ function renderFsTodo(){
|
|
|
7043
7047
|
};
|
|
7044
7048
|
for (const b of el.querySelectorAll('[data-fspause]')) b.onclick = togglePauseActive; // list版 Doing 一時停止/再生(+Paused行のResume)
|
|
7045
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
|
+
};
|
|
7046
7056
|
for (const b of el.querySelectorAll('[data-fslog]')) b.onclick = () => fsToggleLog(Number(b.dataset.fslog));
|
|
7047
7057
|
for (const b of el.querySelectorAll('[data-fsglog]')) b.onclick = () => toggleGoalDetail(Number(b.dataset.fsglog));
|
|
7048
7058
|
for (const b of el.querySelectorAll('[data-fsreverify]')) b.onclick = async () => {
|
|
@@ -7695,7 +7705,7 @@ const AUTH_REASON_TEXT = {
|
|
|
7695
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.',
|
|
7696
7706
|
'bad-credentials': 'Worker sign-in failed. Check ANTHROPIC_API_KEY, or run `claude` and /login, then relaunch the manager from that terminal.',
|
|
7697
7707
|
'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
|
|
7708
|
+
'rate-limited': 'Claude is temporarily unavailable. Your Claude tasks are safely waiting. Tap “Run with Codex” beside a task to start it now.',
|
|
7699
7709
|
};
|
|
7700
7710
|
function renderAuthBanner(auth){
|
|
7701
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
|
@@ -1102,6 +1102,7 @@ function rebuildQueuesAfterAdopt() {
|
|
|
1102
1102
|
for (const p of projects) queues.set(p.id, { running: new Set(), waiting: [] });
|
|
1103
1103
|
for (const p of projects) {
|
|
1104
1104
|
const pending = tasks.filter((t) => t.projectId === p.id && t.status === 'queued').sort((a, b) => a.num - b.num);
|
|
1105
|
+
reconcileQueuedGoalStarts(p.id);
|
|
1105
1106
|
queues.get(p.id).waiting.push(...pending);
|
|
1106
1107
|
pump(p.id);
|
|
1107
1108
|
for (const g of goals.filter((g) => g.projectId === p.id && g.status === 'stacked')) {
|
|
@@ -1111,6 +1112,17 @@ function rebuildQueuesAfterAdopt() {
|
|
|
1111
1112
|
}
|
|
1112
1113
|
}
|
|
1113
1114
|
|
|
1115
|
+
function reconcileQueuedGoalStarts(projectId) {
|
|
1116
|
+
const projectTasks = tasks.filter((task) => task.projectId === projectId);
|
|
1117
|
+
for (const goal of goals.filter((item) => item.projectId === projectId && item.status === 'running' && item.startedAt)) {
|
|
1118
|
+
const goalTasks = projectTasks.filter((task) => task.goalId === goal.id);
|
|
1119
|
+
if (goalTasks.some((task) => task.status === 'queued') && !goalTasks.some((task) => task.status === 'running')) {
|
|
1120
|
+
goal.startedAt = null;
|
|
1121
|
+
saveGoal(goal);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1114
1126
|
const ACTIVITY_LIMIT = 1000;
|
|
1115
1127
|
function activityLine(line) {
|
|
1116
1128
|
return String(line ?? '')
|
|
@@ -1180,10 +1192,10 @@ function runClaude({ prompt, cwd, tools, onEvent, onTodos, resume, model, effort
|
|
|
1180
1192
|
// The Codex half of the worker bridge. Same contract as runClaude, because
|
|
1181
1193
|
// nothing downstream may care which agent ran: live activity, the agent's own
|
|
1182
1194
|
// to-dos (the board), the thread id to talk to it again, usage, final message.
|
|
1183
|
-
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 }) {
|
|
1184
1196
|
return new Promise((done) => {
|
|
1185
1197
|
const sandbox = permissionMode === 'plan' ? 'read-only' : 'workspace-write';
|
|
1186
|
-
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 });
|
|
1187
1199
|
const child = spawn('codex', args, { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1188
1200
|
onChild?.(child);
|
|
1189
1201
|
child.stdin.end(prompt);
|
|
@@ -2318,6 +2330,7 @@ async function runTask(task) {
|
|
|
2318
2330
|
// reply/rework on a normal goal stays acceptEdits as before.
|
|
2319
2331
|
permissionMode: permissionModeFor(goal?.mode === 'plan' && goal?.planPhase !== 'executing' ? 'plan' : undefined),
|
|
2320
2332
|
model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
|
|
2333
|
+
images: goal?.images,
|
|
2321
2334
|
onTodos: (todos) => sendTodos(task, todos),
|
|
2322
2335
|
onChild: (c) => trackGoalWorker(goal?.id, c),
|
|
2323
2336
|
});
|
|
@@ -2373,6 +2386,7 @@ async function runTask(task) {
|
|
|
2373
2386
|
prompt, cwd: workDir, tools: WORKER_TOOLS,
|
|
2374
2387
|
resume: resumeSession, permissionMode,
|
|
2375
2388
|
model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
|
|
2389
|
+
images: goal?.images,
|
|
2376
2390
|
onTodos: (todos) => sendTodos(task, todos),
|
|
2377
2391
|
onChild: (c) => trackGoalWorker(goal?.id, c),
|
|
2378
2392
|
});
|
|
@@ -2398,6 +2412,7 @@ async function runTask(task) {
|
|
|
2398
2412
|
prompt: workerPrompt(task, goal, null, workerAgent(task.agent ?? goal?.agent)), cwd: workDir, tools: WORKER_TOOLS,
|
|
2399
2413
|
permissionMode,
|
|
2400
2414
|
model: task.model ?? goal?.model, effort: task.effort ?? goal?.effort, onEvent: (line) => sendAct(task, line),
|
|
2415
|
+
images: goal?.images,
|
|
2401
2416
|
onTodos: (todos) => sendTodos(task, todos),
|
|
2402
2417
|
onChild: (c) => trackGoalWorker(goal?.id, c),
|
|
2403
2418
|
});
|
|
@@ -3993,6 +4008,28 @@ const server = createServer(async (req, res) => {
|
|
|
3993
4008
|
return json(res, 200, task);
|
|
3994
4009
|
}
|
|
3995
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
|
+
|
|
3996
4033
|
// Start what the worker said to start, and hand back the URL to look at.
|
|
3997
4034
|
//
|
|
3998
4035
|
// This is the "動かして見られる状態" the review card offers. It replaces the
|
|
@@ -4939,6 +4976,7 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
4939
4976
|
for (const p of projects) {
|
|
4940
4977
|
const pending = tasks.filter((t) => t.projectId === p.id && t.status === 'queued').sort((a, b) => a.num - b.num);
|
|
4941
4978
|
const q = queues.get(p.id);
|
|
4979
|
+
reconcileQueuedGoalStarts(p.id);
|
|
4942
4980
|
q.waiting.push(...pending);
|
|
4943
4981
|
if (pending.length) console.log(`[manager] ${p.id}: re-queued ${pending.length} pending task(s) after restart`);
|
|
4944
4982
|
pump(p.id);
|
package/package.json
CHANGED