@galda/cli 0.10.55 → 0.10.57
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 +17 -12
- package/engine/server.mjs +34 -0
- 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
|
|
@@ -6998,7 +6997,7 @@ function renderFsTodo(){
|
|
|
6998
6997
|
// queued.length specifically, since that suffix describes the Next-up sublist's own sort,
|
|
6999
6998
|
// not the lane total.
|
|
7000
6999
|
const needsCount = failed.filter((t) => !resumedGoalIds.has(t.goalId)).length + blocked.length + needsInput.length;
|
|
7001
|
-
const todoTotal = running.length +
|
|
7000
|
+
const todoTotal = running.length + parkedReplyGoals.length + needsCount + queued.length + later.length;
|
|
7002
7001
|
// Free-tier pending counter (H22): on the free plan the header shows the wall
|
|
7003
7002
|
// — pending goals out of the 5-slot cap — reddening at the cap and gone once
|
|
7004
7003
|
// paid. Reads the SAME numbers the gate enforces (state.billing, never a local
|
|
@@ -10055,6 +10054,12 @@ $('appmenu').addEventListener('click', (e) => {
|
|
|
10055
10054
|
renderEffortSelect();
|
|
10056
10055
|
renderMsgHint();
|
|
10057
10056
|
$('appmenu').classList.remove('show');
|
|
10057
|
+
const projectId = state.active;
|
|
10058
|
+
if (projectId) fetch(withKey(`/api/projects/${encodeURIComponent(projectId)}/queue-agent`), {
|
|
10059
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
10060
|
+
body: JSON.stringify({ agent: state.connectedApp, model: state.stickyModel, effort: state.stickyEffort }),
|
|
10061
|
+
}).then(async (r) => { if (!r.ok) showErr((await r.json().catch(() => ({}))).error || 'Could not update Next up.'); else await refresh(); })
|
|
10062
|
+
.catch(() => showErr('Could not update Next up.'));
|
|
10058
10063
|
});
|
|
10059
10064
|
document.addEventListener('click', (e) => {
|
|
10060
10065
|
if (!e.target.closest('.appsel')) $('appmenu').classList.remove('show');
|
package/engine/server.mjs
CHANGED
|
@@ -4008,6 +4008,40 @@ const server = createServer(async (req, res) => {
|
|
|
4008
4008
|
return json(res, 200, task);
|
|
4009
4009
|
}
|
|
4010
4010
|
|
|
4011
|
+
// The composer agent picker also owns Next up for this project. Only work
|
|
4012
|
+
// that has not started moves; running/review/done work keeps its original
|
|
4013
|
+
// agent and conversation.
|
|
4014
|
+
const queueAgentMatch = url.pathname.match(/^\/api\/projects\/([^/]+)\/queue-agent$/);
|
|
4015
|
+
if (queueAgentMatch && req.method === 'POST') {
|
|
4016
|
+
const projectId = decodeURIComponent(queueAgentMatch[1]);
|
|
4017
|
+
if (!projects.some((project) => project.id === projectId)) return json(res, 404, { error: 'project not found' });
|
|
4018
|
+
let body = '';
|
|
4019
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
4020
|
+
req.on('end', () => {
|
|
4021
|
+
let requested;
|
|
4022
|
+
try { requested = JSON.parse(body || '{}'); } catch { return json(res, 400, { error: 'bad json' }); }
|
|
4023
|
+
if (!['claude-code', 'codex'].includes(requested.agent)) return json(res, 400, { error: 'unsupported agent' });
|
|
4024
|
+
const agent = workerAgent(requested.agent);
|
|
4025
|
+
if (!AVAILABLE_AGENTS.includes(agent)) return json(res, 409, { error: `${agent} is not connected on this device` });
|
|
4026
|
+
const model = workerModel(agent, requested.model);
|
|
4027
|
+
const effort = workerEffort(agent, requested.effort);
|
|
4028
|
+
const runningGoalIds = new Set(tasks.filter((task) => task.projectId === projectId && task.status === 'running').map((task) => task.goalId));
|
|
4029
|
+
const queued = tasks.filter((task) => task.projectId === projectId && task.status === 'queued' && !runningGoalIds.has(task.goalId));
|
|
4030
|
+
const goalIds = new Set(queued.map((task) => task.goalId));
|
|
4031
|
+
for (const task of queued) {
|
|
4032
|
+
task.agent = agent; task.model = model; task.effort = effort; task.sessionId = null;
|
|
4033
|
+
saveTask(task);
|
|
4034
|
+
}
|
|
4035
|
+
for (const goal of goals.filter((item) => goalIds.has(item.id))) {
|
|
4036
|
+
goal.agent = agent; goal.model = model; goal.effort = effort; goal.sessionId = null; goal.startedAt = null;
|
|
4037
|
+
saveGoal(goal);
|
|
4038
|
+
}
|
|
4039
|
+
pump(projectId);
|
|
4040
|
+
return json(res, 200, { changed: queued.length, agent, model, effort });
|
|
4041
|
+
});
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4011
4045
|
// A Claude usage hold should be actionable without asking people to edit
|
|
4012
4046
|
// models or sessions. Move every still-queued step for this goal together so
|
|
4013
4047
|
// one tap cannot leave a mixed Claude/Codex chain behind.
|
package/package.json
CHANGED