@galda/cli 0.10.64 → 0.10.66
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 +18 -13
- package/engine/server.mjs +24 -6
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -3373,7 +3373,7 @@ const savedTheme = (() => {
|
|
|
3373
3373
|
if (v !== s) localStorage.setItem('theme', v);
|
|
3374
3374
|
return v;
|
|
3375
3375
|
})();
|
|
3376
|
-
const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: localStorage.getItem('sel:project') || 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], expanded: new Set(), openThreads: new Set(), queueOrder: {}, externalActivity: [], selectedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
|
|
3376
|
+
const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: localStorage.getItem('sel:project') || 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], auth: null, expanded: new Set(), openThreads: new Set(), queueOrder: {}, externalActivity: [], selectedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
|
|
3377
3377
|
// per-message send overrides set via slash-commands (reset after each send,
|
|
3378
3378
|
// except model which is sticky in localStorage). palette = open command list.
|
|
3379
3379
|
msg: { mode: null, model: null, effort: null, skill: null, later: false, review: false },
|
|
@@ -4365,7 +4365,7 @@ function renderStream(){
|
|
|
4365
4365
|
if (e.key !== 'Enter' || e.isComposing || !inp.value.trim()) return;
|
|
4366
4366
|
const text = inp.value.trim(); inp.value = ''; inp.disabled = true;
|
|
4367
4367
|
const r = await fetch(withKey(`/api/goals/${inp.dataset.goal}/reply`), { method: 'POST',
|
|
4368
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
4368
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
4369
4369
|
if (!r.ok) showErr('Reply failed — the goal may not have started yet.');
|
|
4370
4370
|
inp.disabled = false;
|
|
4371
4371
|
});
|
|
@@ -4948,7 +4948,7 @@ function openGoalheadReply(id, btn){
|
|
|
4948
4948
|
const snd = document.createElement('button'); snd.className = 'qbtn ok'; snd.textContent = '↑';
|
|
4949
4949
|
wrap.appendChild(inp); wrap.appendChild(snd); head.insertAdjacentElement('afterend', wrap); inp.focus();
|
|
4950
4950
|
const send = async () => { const t = inp.value.trim(); if (!t) return;
|
|
4951
|
-
const r = await fetch(withKey(`/api/goals/${id}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: t }) });
|
|
4951
|
+
const r = await fetch(withKey(`/api/goals/${id}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: t })) });
|
|
4952
4952
|
if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh(); };
|
|
4953
4953
|
inp.onkeydown = (e) => { if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); send(); } if (e.key === 'Escape') wrap.remove(); };
|
|
4954
4954
|
snd.onclick = send;
|
|
@@ -5405,7 +5405,7 @@ function renderReviewChecklist(){
|
|
|
5405
5405
|
b.disabled = true; input.disabled = true;
|
|
5406
5406
|
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST',
|
|
5407
5407
|
headers: { 'content-type': 'application/json' },
|
|
5408
|
-
body: JSON.stringify({ text: `[Fix request for requirement #${t?.num ?? ''} "${t?.title ?? ''}"] ${text}` }) });
|
|
5408
|
+
body: JSON.stringify(replyWorkerPayload({ text: `[Fix request for requirement #${t?.num ?? ''} "${t?.title ?? ''}"] ${text}` })) });
|
|
5409
5409
|
if (r.ok) { input.value = ''; input.placeholder = 'Sent — the worker will pick it up'; }
|
|
5410
5410
|
else showErr('Failed to send the fix request.');
|
|
5411
5411
|
b.disabled = false; input.disabled = false;
|
|
@@ -5649,9 +5649,12 @@ const REPLY_CHOICE_LABEL = { continue: 'Fix this', rescope: 'Rethink it', split:
|
|
|
5649
5649
|
// is the task/workflow vocabulary and this is neither (CDO §5).
|
|
5650
5650
|
const REPLY_WAIT_ROUTING = 'Thinking…';
|
|
5651
5651
|
const REPLY_WAIT_ANSWERING = 'Answering…';
|
|
5652
|
+
function replyWorkerPayload(extra = {}){
|
|
5653
|
+
return { ...extra, agent: state.connectedApp, model: state.stickyModel, effort: state.stickyEffort };
|
|
5654
|
+
}
|
|
5652
5655
|
async function postReply(goalId, text, outcome = 'auto'){
|
|
5653
5656
|
const r = await fetch(withKey(`/api/goals/${goalId}/dismiss`), { method: 'POST',
|
|
5654
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text, outcome }) });
|
|
5657
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text, outcome })) });
|
|
5655
5658
|
if (!r.ok) throw new Error();
|
|
5656
5659
|
return r.json();
|
|
5657
5660
|
}
|
|
@@ -6139,7 +6142,7 @@ async function sendGoalInstruction(goalId, text){
|
|
|
6139
6142
|
$('gdSend').disabled = true; $('gdDiscard').disabled = true;
|
|
6140
6143
|
try {
|
|
6141
6144
|
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST',
|
|
6142
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
6145
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
6143
6146
|
if (!r.ok) throw new Error();
|
|
6144
6147
|
upsert(state.tasks, await r.json());
|
|
6145
6148
|
const g = state.goals.find((x) => x.id === goalId);
|
|
@@ -7095,7 +7098,7 @@ function renderFsTodo(){
|
|
|
7095
7098
|
// hover-toolbar actions → same endpoints as the SL1 goalhead toolbar
|
|
7096
7099
|
for (const b of el.querySelectorAll('[data-fsreply]')) b.onclick = (e) => { e.stopPropagation();
|
|
7097
7100
|
fsInlineInput(b.closest('.trow'), '', 'Reply to this goal…', async (text) => {
|
|
7098
|
-
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreply}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text }) });
|
|
7101
|
+
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreply}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) });
|
|
7099
7102
|
if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh();
|
|
7100
7103
|
}); };
|
|
7101
7104
|
for (const b of el.querySelectorAll('[data-fsedit]')) b.onclick = (e) => { e.stopPropagation();
|
|
@@ -7725,10 +7728,11 @@ const AUTH_COPY = {
|
|
|
7725
7728
|
};
|
|
7726
7729
|
function renderAuthBanner(auth){
|
|
7727
7730
|
const bar = $('errbar');
|
|
7728
|
-
|
|
7731
|
+
state.auth = auth ?? state.auth;
|
|
7732
|
+
if (state.connectedApp === 'claude-code' && state.auth?.ok === false){
|
|
7729
7733
|
authDown = true;
|
|
7730
7734
|
const copy = AUTH_COPY[billingLang()];
|
|
7731
|
-
const limited = auth.reason === 'rate-limited';
|
|
7735
|
+
const limited = state.auth.reason === 'rate-limited';
|
|
7732
7736
|
const canUseCodex = (state.agents || []).includes('codex');
|
|
7733
7737
|
bar.classList.add('billing'); // strips #errbar's error fill so the card stands alone
|
|
7734
7738
|
bar.innerHTML = `<div class="bx-ask${limited ? ' info' : ''}"><div class="bx-qt"><div class="bx-lb">${limited ? copy.limitTitle : copy.connectTitle}</div>`
|
|
@@ -8024,7 +8028,7 @@ async function refresh(){
|
|
|
8024
8028
|
state.connectCommand = s.connectCommand ?? ''; state.models = s.models ?? ['sonnet']; state.agentModels = s.agentModels ?? null; state.agentEfforts = s.agentEfforts ?? null; state.agents = s.agents ?? ['claude-code', 'codex']; state.uiVersion = s.uiVersion;
|
|
8025
8029
|
state.billing = s.billing ?? { licensed: false, email: null };
|
|
8026
8030
|
renderAppSelect(); // picker follows the server's installed-agent set; may correct a stale connectedApp
|
|
8027
|
-
renderAuthBanner(s.auth); //
|
|
8031
|
+
renderAuthBanner(s.auth); // only the selected worker's auth can raise a banner
|
|
8028
8032
|
setAgentModel(state.connectedApp);
|
|
8029
8033
|
setAgentEffort(state.connectedApp);
|
|
8030
8034
|
renderModelSelect();
|
|
@@ -8121,7 +8125,7 @@ function connectSSE(){
|
|
|
8121
8125
|
if (!state.projects.some((p) => p.id === state.active)) state.active = state.projects[0]?.id;
|
|
8122
8126
|
}
|
|
8123
8127
|
else if (m.ev === 'ping') { checkUiVersion(m.uiVersion); return; }
|
|
8124
|
-
else if (m.ev === 'auth') { renderAuthBanner(m.auth); return; } // worker sign-in preflight result
|
|
8128
|
+
else if (m.ev === 'auth') { renderAuthBanner(m.auth); return; } // selected worker's sign-in preflight result
|
|
8125
8129
|
scheduleRender();
|
|
8126
8130
|
};
|
|
8127
8131
|
es.onerror = () => {
|
|
@@ -8576,7 +8580,7 @@ async function send(){
|
|
|
8576
8580
|
} else {
|
|
8577
8581
|
// running/partial/failed/interrupted/blocked → a plain follow-up on the same goal.
|
|
8578
8582
|
const r = await fetch(withKey(`/api/goals/${bind.goal.id}/reply`), { method: 'POST',
|
|
8579
|
-
headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: fullText }) });
|
|
8583
|
+
headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: fullText })) });
|
|
8580
8584
|
if (!r.ok) throw new Error();
|
|
8581
8585
|
const g = await r.json(); const goal = g.goal ?? g;
|
|
8582
8586
|
if (goal?.id) upsert(state.goals, goal);
|
|
@@ -9780,7 +9784,7 @@ async function saSendReply(it, i, text){
|
|
|
9780
9784
|
it.st = 3; it.rstatus = 'working'; SA.cur = goalId;
|
|
9781
9785
|
saPaintCard(goalId);
|
|
9782
9786
|
try {
|
|
9783
|
-
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: workerText }) });
|
|
9787
|
+
const r = await fetch(withKey(`/api/goals/${goalId}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: workerText })) });
|
|
9784
9788
|
if (!r.ok) throw new Error('reply failed');
|
|
9785
9789
|
if (isAttn) {
|
|
9786
9790
|
// A Needs-you reply picks the goal back up → it belongs in Doing, not lingering in
|
|
@@ -10213,6 +10217,7 @@ $('appmenu').addEventListener('click', (e) => {
|
|
|
10213
10217
|
renderModelSelect();
|
|
10214
10218
|
renderEffortSelect();
|
|
10215
10219
|
renderMsgHint();
|
|
10220
|
+
renderAuthBanner(state.auth);
|
|
10216
10221
|
$('appmenu').classList.remove('show');
|
|
10217
10222
|
const projectId = state.active;
|
|
10218
10223
|
if (projectId) fetch(withKey(`/api/projects/${encodeURIComponent(projectId)}/queue-agent`), {
|
package/engine/server.mjs
CHANGED
|
@@ -1973,6 +1973,19 @@ function resumeProject(projectId) {
|
|
|
1973
1973
|
|
|
1974
1974
|
// Slack風スレッド返信タスクの生成(/api/goals/:id/reply と /api/goals/:id/dismiss で共有)。
|
|
1975
1975
|
// runTask()のtask.reply分岐がgoal.sessionIdをresumeに渡して同じ会話を続ける。
|
|
1976
|
+
function applyReplyWorker(goal, requested = {}) {
|
|
1977
|
+
if (requested.agent == null) return { ok: true };
|
|
1978
|
+
if (!WORKER_AGENTS.includes(requested.agent)) return { ok: false, error: 'unsupported agent' };
|
|
1979
|
+
const agent = workerAgent(requested.agent);
|
|
1980
|
+
if (!AVAILABLE_AGENTS.includes(agent)) return { ok: false, error: `${agent} is not connected on this device` };
|
|
1981
|
+
const switchedAgent = agent !== goal.agent;
|
|
1982
|
+
goal.agent = agent;
|
|
1983
|
+
goal.model = workerModel(agent, requested.model);
|
|
1984
|
+
goal.effort = workerEffort(agent, requested.effort);
|
|
1985
|
+
if (switchedAgent) goal.sessionId = null;
|
|
1986
|
+
return { ok: true };
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1976
1989
|
function queueReplyTask(goal, text, { from = 'manager', via = 'composer' } = {}) {
|
|
1977
1990
|
// Every follow-up on a goal is part of its conversation (§1.1). Recorded here
|
|
1978
1991
|
// rather than at each call site so an internally-generated nudge (auto-retest
|
|
@@ -4522,8 +4535,11 @@ const server = createServer(async (req, res) => {
|
|
|
4522
4535
|
req.on('data', (d) => { body += d; });
|
|
4523
4536
|
req.on('end', () => {
|
|
4524
4537
|
try {
|
|
4525
|
-
const
|
|
4538
|
+
const requested = JSON.parse(body);
|
|
4539
|
+
const { text } = requested;
|
|
4526
4540
|
if (!text?.trim()) return json(res, 400, { error: 'text required' });
|
|
4541
|
+
const worker = applyReplyWorker(goal, requested);
|
|
4542
|
+
if (!worker.ok) return json(res, 409, { error: worker.error });
|
|
4527
4543
|
// Reply picks a parked goal back up into the work queue. 'review' MUST be
|
|
4528
4544
|
// here: a completed goal awaiting your approve/reject is 'review', and a
|
|
4529
4545
|
// reply to it means "not done — keep working" (Masa 2026-07-24: replied to
|
|
@@ -4814,13 +4830,15 @@ async function answerGoalQuestion(goal, question) {
|
|
|
4814
4830
|
let body = '';
|
|
4815
4831
|
req.on('data', (d) => { body += d; });
|
|
4816
4832
|
req.on('end', async () => {
|
|
4817
|
-
let text = '', outcome = 'continue', auto = false;
|
|
4833
|
+
let text = '', outcome = 'continue', auto = false, requested = {};
|
|
4818
4834
|
try {
|
|
4819
|
-
|
|
4820
|
-
text = String(
|
|
4821
|
-
if (REPLY_OUTCOMES.includes(
|
|
4822
|
-
auto =
|
|
4835
|
+
requested = JSON.parse(body || '{}');
|
|
4836
|
+
text = String(requested.text ?? '').trim();
|
|
4837
|
+
if (REPLY_OUTCOMES.includes(requested.outcome)) outcome = requested.outcome;
|
|
4838
|
+
auto = requested.outcome === 'auto';
|
|
4823
4839
|
} catch {}
|
|
4840
|
+
const worker = applyReplyWorker(goal, requested);
|
|
4841
|
+
if (!worker.ok) return json(res, 409, { error: worker.error });
|
|
4824
4842
|
|
|
4825
4843
|
// outcome:'auto' = 決めるのは AI 本人 (§1.4)。人の言葉を1回だけ渡して、返って
|
|
4826
4844
|
// きた行き先のとおりに配線する。我々はキーワードも類似度も見ない。
|
package/package.json
CHANGED