@galda/cli 0.10.56 → 0.10.58

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
@@ -10054,6 +10054,12 @@ $('appmenu').addEventListener('click', (e) => {
10054
10054
  renderEffortSelect();
10055
10055
  renderMsgHint();
10056
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.'));
10057
10063
  });
10058
10064
  document.addEventListener('click', (e) => {
10059
10065
  if (!e.target.closest('.appsel')) $('appmenu').classList.remove('show');
package/engine/lib.mjs CHANGED
@@ -2583,6 +2583,7 @@ export function workerPrompt(task, goal, lastFailure, agentName = 'claude-code')
2583
2583
  // (research, summaries, answers) simply skip this — the worker decides,
2584
2584
  // never us.
2585
2585
  `- 人が実際に動かして結果を見られる依頼なら、最後にプロジェクト直下へ ${RUN_DECL_FILE} を書く: {"cmd": "<起動コマンド>", "url": "<開くURL>", "note": "<何が見えるか1行>", "check": "<人に何を見て判断してほしいか1行>", "title": "<この作業のチケット名。対象が分かる程度に説明的で動作を含む短い名前。例「看板モードの追加」「ヘッダのズレの修正」。依頼文をそのまま写さない>"}。Manager はこれを使って人に「動かして見る」と「何を見ればいいか」を出す。動かして見せるものが無い依頼(調査・要約・質問への回答など)では書かなくてよい。`,
2586
+ `- 「ローカルで見れない」というフィードバックには、ターミナル操作や npx の再実行を案内して終わらないこと。${RUN_DECL_FILE} を実際に開ける内容へ更新し、可能なら自分でURLへ接続確認してから完了すること。`,
2586
2587
  // Links are rendered as links. The product cannot tell a real one from a
2587
2588
  // decorative one, so the only place this can be held is here.
2588
2589
  `- 自分が作っていない場所へのリンクを報告に書かない(存在しないURLや「詳しくはここ」のような飾りのリンクは、そのままリンクとして人に表示される)。`,
package/engine/server.mjs CHANGED
@@ -2129,7 +2129,7 @@ async function startEphemeralApp(projectDir) {
2129
2129
  try {
2130
2130
  const key = readFileSync(join(home, 'secret.key'), 'utf8').trim();
2131
2131
  const r = await fetch(`http://localhost:${port}/api/state?key=${key}`);
2132
- if (r.ok) return { url: `http://localhost:${port}/?key=${key}`, kill: () => { try { child.kill(); } catch {} } };
2132
+ if (r.ok) return { url: `http://localhost:${port}/?key=${key}`, child, kill: () => { try { child.kill(); } catch {} } };
2133
2133
  } catch { /* not up yet */ }
2134
2134
  }
2135
2135
  try { child.kill(); } catch {}
@@ -2259,6 +2259,19 @@ async function startPreview(task) {
2259
2259
  live.expires = Date.now() + PREVIEW_IDLE_MS;
2260
2260
  return { url: live.url, reused: true };
2261
2261
  }
2262
+ // A Manager app cannot preview itself on its declared production port: that
2263
+ // port is already occupied, and its bare URL requires an access key. Boot the
2264
+ // edited checkout with isolated state and return its real key-bearing URL.
2265
+ let managerPackage = false;
2266
+ try { managerPackage = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).name === '@galda/cli'; } catch {}
2267
+ if (managerPackage && existsSync(join(dir, 'engine', 'server.mjs')) && existsSync(join(dir, 'app', 'index.html'))) {
2268
+ const eph = await startEphemeralApp(dir);
2269
+ if (!eph) throw new Error('Could not start the local preview. Please try again.');
2270
+ const entry = { proc: eph.child, url: eph.url, expires: Date.now() + PREVIEW_IDLE_MS };
2271
+ previews.set(task.id, entry);
2272
+ eph.child.on('close', () => { if (previews.get(task.id) === entry) previews.delete(task.id); });
2273
+ return { url: eph.url, reused: false, started: true };
2274
+ }
2262
2275
  // Declared a URL but no command: the thing is already reachable (an app the
2263
2276
  // worker left running, a hosted page). Nothing to start.
2264
2277
  if (!cmd) return { url, reused: false, started: false };
@@ -4008,6 +4021,40 @@ const server = createServer(async (req, res) => {
4008
4021
  return json(res, 200, task);
4009
4022
  }
4010
4023
 
4024
+ // The composer agent picker also owns Next up for this project. Only work
4025
+ // that has not started moves; running/review/done work keeps its original
4026
+ // agent and conversation.
4027
+ const queueAgentMatch = url.pathname.match(/^\/api\/projects\/([^/]+)\/queue-agent$/);
4028
+ if (queueAgentMatch && req.method === 'POST') {
4029
+ const projectId = decodeURIComponent(queueAgentMatch[1]);
4030
+ if (!projects.some((project) => project.id === projectId)) return json(res, 404, { error: 'project not found' });
4031
+ let body = '';
4032
+ req.on('data', (chunk) => { body += chunk; });
4033
+ req.on('end', () => {
4034
+ let requested;
4035
+ try { requested = JSON.parse(body || '{}'); } catch { return json(res, 400, { error: 'bad json' }); }
4036
+ if (!['claude-code', 'codex'].includes(requested.agent)) return json(res, 400, { error: 'unsupported agent' });
4037
+ const agent = workerAgent(requested.agent);
4038
+ if (!AVAILABLE_AGENTS.includes(agent)) return json(res, 409, { error: `${agent} is not connected on this device` });
4039
+ const model = workerModel(agent, requested.model);
4040
+ const effort = workerEffort(agent, requested.effort);
4041
+ const runningGoalIds = new Set(tasks.filter((task) => task.projectId === projectId && task.status === 'running').map((task) => task.goalId));
4042
+ const queued = tasks.filter((task) => task.projectId === projectId && task.status === 'queued' && !runningGoalIds.has(task.goalId));
4043
+ const goalIds = new Set(queued.map((task) => task.goalId));
4044
+ for (const task of queued) {
4045
+ task.agent = agent; task.model = model; task.effort = effort; task.sessionId = null;
4046
+ saveTask(task);
4047
+ }
4048
+ for (const goal of goals.filter((item) => goalIds.has(item.id))) {
4049
+ goal.agent = agent; goal.model = model; goal.effort = effort; goal.sessionId = null; goal.startedAt = null;
4050
+ saveGoal(goal);
4051
+ }
4052
+ pump(projectId);
4053
+ return json(res, 200, { changed: queued.length, agent, model, effort });
4054
+ });
4055
+ return;
4056
+ }
4057
+
4011
4058
  // A Claude usage hold should be actionable without asking people to edit
4012
4059
  // models or sessions. Move every still-queued step for this goal together so
4013
4060
  // one tap cannot leave a mixed Claude/Codex chain behind.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.56",
3
+ "version": "0.10.58",
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": {