@galda/cli 0.10.16 → 0.10.17
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 +58 -7
- package/engine/lib.mjs +66 -0
- package/engine/server.mjs +42 -4
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -1313,6 +1313,11 @@
|
|
|
1313
1313
|
discard path. */
|
|
1314
1314
|
#fsRoot .cc-ask .acts{position:absolute;top:8px;right:8px;display:flex;opacity:0;transition:opacity .15s}
|
|
1315
1315
|
#fsRoot .cc-ask:hover .acts{opacity:1}
|
|
1316
|
+
/* Touch devices have no hover → the hover-only trash was permanently invisible
|
|
1317
|
+
and the「Needs your answer」card had no reachable discard path (Masa
|
|
1318
|
+
2026-07-16: 右上のdeleteが押せない). Reveal it (dimmed, so it still reads as
|
|
1319
|
+
secondary per §1.5-10) whenever the pointer can't hover. */
|
|
1320
|
+
@media (hover: none){ #fsRoot .cc-ask .acts{opacity:.55} }
|
|
1316
1321
|
#fsRoot .qchips{display:flex;gap:6px;flex-wrap:wrap}
|
|
1317
1322
|
#fsRoot .achip{height:28px;padding:0 12px;border-radius:8px;border:1px solid var(--hair2);background:var(--panel);
|
|
1318
1323
|
color:var(--ink2);font:inherit;font-size:12.5px;cursor:pointer;transition:border-color .15s,color .15s,background .15s}
|
|
@@ -1937,7 +1942,7 @@
|
|
|
1937
1942
|
#fsClawdHero span{display:block;margin-top:5px;font:500 10px/1.4 var(--mono);letter-spacing:.08em;color:var(--ink3)}
|
|
1938
1943
|
/* Welcome P1 (H17 item 4, flagship f9c75ad): grounded hop + clean-cut eyes on settle + our CSS confetti.
|
|
1939
1944
|
Two pixel frames (neutral black-eyes / laughing), no arm-raise, no official confetti. reduce-motion=static. */
|
|
1940
|
-
#fsClawdHero .cstage{position:relative;width:170px;height:132px;display:flex;align-items:flex-end;justify-content:center;margin:0 auto 2px}#fsClawdHero .cw{position:relative;width:150px;transform-origin:50% 100%;will-change:transform;animation:chHop 2.4s linear infinite}#fsClawdHero .cw img{position:absolute;left:0;bottom:0;width:100%;display:block;image-rendering:pixelated}#fsClawdHero .cw .chNeu{animation:chNeu 2.4s linear infinite}#fsClawdHero .cw .chLau{opacity:0;animation:chLau 2.4s linear infinite}#fsClawdHero .chConf{position:absolute;left:50%;top:34px;width:0;height:0;pointer-events:none;z-index:3}#fsClawdHero .chConf i{position:absolute;width:4px;height:7px;border-radius:1.5px;opacity:0;transform:translate(-50%,-50%);animation:chPop 2.4s cubic-bezier(.2,.7,.3,1) infinite}@keyframes chHop{0%,52%{transform:translateY(0) scale(1,1)}55%{transform:translateY(0) scale(1.08,.85)}58%{transform:translateY(-16px) scale(.95,1.14)}61%{transform:translateY(-27px) scale(.98,1.05)}64%{transform:translateY(-31px) scale(1,1)}67%{transform:translateY(-26px) scale(.98,1.05)}70%{transform:translateY(-14px) scale(.95,1.12)}73%{transform:translateY(0) scale(1.1,.82)}77%{transform:translateY(-4px) scale(.99,1.02)}80%,100%{transform:translateY(0) scale(1,1)}}@keyframes chLau{0%,53.8%{opacity:0}54%,85.8%{opacity:1}86%,100%{opacity:0}}@keyframes chNeu{0%,53.8%{opacity:1}54%,85.8%{opacity:0}86%,100%{opacity:1}}@keyframes chPop{0%,54%{opacity:0;transform:translate(0,6px) rotate(0) scale(.5)}60%{opacity:1;transform:translate(calc(var(--dx)*.5),calc(var(--dy)*.5)) rotate(calc(var(--rot)*.5)) scale(1)}72%{opacity:1;transform:translate(var(--dx),var(--dy)) rotate(var(--rot))}100%{opacity:0;transform:translate(calc(var(--dx)*1.1),calc(var(--dy) + 44px)) rotate(calc(var(--rot)*1.4)) scale(.9)}}@media (prefers-reduced-motion:reduce){#fsClawdHero .cw,#fsClawdHero .cw .chNeu,#fsClawdHero .cw .chLau{animation:none}#fsClawdHero .cw .chLau{opacity:0}#fsClawdHero .chConf{display:none}}
|
|
1945
|
+
#fsClawdHero .cstage{position:relative;left:-13px;width:170px;height:132px;display:flex;align-items:flex-end;justify-content:center;margin:0 auto 2px}#fsClawdHero .cw{position:relative;width:150px;transform-origin:50% 100%;will-change:transform;animation:chHop 2.4s linear infinite}#fsClawdHero .cw img{position:absolute;left:0;bottom:0;width:100%;display:block;image-rendering:pixelated}#fsClawdHero .cw .chNeu{animation:chNeu 2.4s linear infinite}#fsClawdHero .cw .chLau{opacity:0;animation:chLau 2.4s linear infinite}#fsClawdHero .chConf{position:absolute;left:50%;top:34px;width:0;height:0;pointer-events:none;z-index:3}#fsClawdHero .chConf i{position:absolute;width:4px;height:7px;border-radius:1.5px;opacity:0;transform:translate(-50%,-50%);animation:chPop 2.4s cubic-bezier(.2,.7,.3,1) infinite}@keyframes chHop{0%,52%{transform:translateY(0) scale(1,1)}55%{transform:translateY(0) scale(1.08,.85)}58%{transform:translateY(-16px) scale(.95,1.14)}61%{transform:translateY(-27px) scale(.98,1.05)}64%{transform:translateY(-31px) scale(1,1)}67%{transform:translateY(-26px) scale(.98,1.05)}70%{transform:translateY(-14px) scale(.95,1.12)}73%{transform:translateY(0) scale(1.1,.82)}77%{transform:translateY(-4px) scale(.99,1.02)}80%,100%{transform:translateY(0) scale(1,1)}}@keyframes chLau{0%,53.8%{opacity:0}54%,85.8%{opacity:1}86%,100%{opacity:0}}@keyframes chNeu{0%,53.8%{opacity:1}54%,85.8%{opacity:0}86%,100%{opacity:1}}@keyframes chPop{0%,54%{opacity:0;transform:translate(0,6px) rotate(0) scale(.5)}60%{opacity:1;transform:translate(calc(var(--dx)*.5),calc(var(--dy)*.5)) rotate(calc(var(--rot)*.5)) scale(1)}72%{opacity:1;transform:translate(var(--dx),var(--dy)) rotate(var(--rot))}100%{opacity:0;transform:translate(calc(var(--dx)*1.1),calc(var(--dy) + 44px)) rotate(calc(var(--rot)*1.4)) scale(.9)}}@media (prefers-reduced-motion:reduce){#fsClawdHero .cw,#fsClawdHero .cw .chNeu,#fsClawdHero .cw .chLau{animation:none}#fsClawdHero .cw .chLau{opacity:0}#fsClawdHero .chConf{display:none}}
|
|
1941
1946
|
#fsRoot.clawdhero #fsClawdHero{display:block}
|
|
1942
1947
|
/* Non-Claude agent (e.g. Codex) hero: no Clawd mascot (that's a Claude Code brand mark,
|
|
1943
1948
|
DESIGN-RULES v3 §6.5) — a plain glyph badge stands in so the hero still names the
|
|
@@ -2107,6 +2112,12 @@
|
|
|
2107
2112
|
<div class="errbar" id="errbar"></div>
|
|
2108
2113
|
<div class="composer-wrap" id="composerWrap">
|
|
2109
2114
|
<div class="queue" id="queue"></div>
|
|
2115
|
+
<!-- Conversation-first composer (SLICE 2, docs/HANDOFF-ONBOARDING-conversational.md):
|
|
2116
|
+
a chat/help/settings message gets a quiet reply HERE instead of
|
|
2117
|
+
becoming a To-Do. Reuses the .peraanswer surface; hidden until a
|
|
2118
|
+
reply exists (progressive disclosure — DESIGN-RULES §1.5-3, "存在
|
|
2119
|
+
しないものの器を先に見せない"). -->
|
|
2120
|
+
<div class="peraanswer" id="chatReply" hidden></div>
|
|
2110
2121
|
<div class="composer">
|
|
2111
2122
|
<div class="attachbar" id="attachbar" style="display:none"></div>
|
|
2112
2123
|
<!-- Slash-command palette (Masa決定 2026-07-07: chips→slash). Floats
|
|
@@ -5367,10 +5378,15 @@ function applyFsBand(){
|
|
|
5367
5378
|
const CLAWD_HERO_IMGS = '<img class="chNeu" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAETCAYAAADtSRlPAAAMdElEQVR4nO3dPYwk6VkH8Keqq2f2E4uzzydzCToCH8JCIiAxgY1EDMIBSByS5cQkthCCHCFBgGTJSBA5cGQnRDYJws6xxElA6C9ZBEjGt+e1rZvdnZmurkJvdfds9+581HTvTL9d/fsF+0xrd7tm3u5+3v+89RUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBDFtr8BLvaPn3+nfWvypPu6Lo1UzqpmVn80vh9f/MrXfa7mqsUX5Oe1hw/izTpiFNOYxiiqqoy6btQMx2E6OYnxqIr32/vbfttkRYPJWD1to4g2Ttv0aBr1ZNpFTjW/cRhHxGRaR0iaKwxHxqpREdHWcZBKRHQP1SzHISkLH6cXGZHME0x60zZt082U03a2aKbmNw5Jep1YpcFknmDSmzY1mW3P0KoEsw4NJmMSzO4lmLKcP6CjwWRMgtmd5FRHOft1trGHepkGkzlrMLtRq2gkmHNoMJmzBpNHQpFg1qPBZMwazO4lGFZpMBmzBrP9ZOI4mM04kndnEkzZayYdF2WU0UQ1X2vsU7uZt+j3/LtYmzbi8IWft5qP63J98e/r6L+d9L9n43jLb5LMaTC5J5jJ/DiYdjajTi+pqbk8Oj6Jnx09jdNmGlVZRd3Ul9eTp1Ed3otfPjyI1x/eufT5d7U+OT6N/338uPs5rxyPpXF5642PxsGo7LWdFHUcyfsyDWYgCWYcTYzLMr79kw/iq9/4VjwqUr6/+piMURQxjTb+5pNvx+996nfjwZOfdh+WpmdiyrWm8Ug/xy8evhHf/M634svvfi+ix5gUZRlt08TrEfH5P/nDeOeNu905RleNxyLBlCPHwSyzBpOxdNBW371I3XEYo6WXM32Q0gcquaSm5nL2ODW1+VtiCHt1LnTJeKTmsnhcFqNoy9nX9iKtR4PJWDpoq+9xMN1ejOlsL8az9MfybN2zpmvOpOfp/vsA9uq8pM84LI1b006jSAs4PcbDcTDn02AGmGDupj/atov7nR5JZi8STJ9xWBq3lGAWJJj1aDADSzDVvfurcT/pm2S6RTkJ5mzc0tDMf0Xqm2BYpcEMLMHUT2eX2JRgNk8wy64a/+71cj2Yl2gwA1yD6UgwryTBnA3nFePfvV4SzEs0mIEcyXveXiRrMLeXYBZnU7PKiGROgtmNBGMv0vk0mMxJMNvbi7TMXqT1aDADOZvaGkweCYZVGswOrMEkN3Uk73JNNw9zHMxqglkcaNc3wbQ+Uis0mIwtz4jXPpI3ueaRvMneHweThqNpujFMR/IumrYEsx4nO2bqL3774+3Dg4Oo67L70C9myovO5q3bMqqyiDfvjOOPfu0j8d6DD/fazsHxpKtvvvZaV9NM3Gd7udduPIoy7p4+id9448PxmY+/1XvsT++M46NHP41PlHWcTNK5SVW0bXPl9tL9q8ajtnvtvvzu954fHr3HDMI1ffev/rSdtm2M0omCN1CXI/bJZPbhb+cvVJ+a3uzjahxF1yTKK+vCk3Ico5OnvbezKzWpilHcGRdRt0VURdvV9PMnadyXHy+ksUm3ha3b6dkHpc/20rbS3TirxQV3lvR5Pa7zuqWf5e0vfS3rz7AEc03pzTitm3jWTm9oBt78eaOezB9Pe9aIUTvJInncRG1jGkens8fH13wdkuslp9n/ezY57++nr6x2S0PjdMPavFmDuabZDOhOi7d9drQaK+MwP0UqexrMNc1itjstbvts6X2vzfN1+axpMNckwUgUOSSqUoIZJgkmjxl832sjwQyXNZjtz+D7XksJZni+8Ae/37b1xBpMBjP4vtdGghmmohpLMBnM4PteSwlmeP7pm//Svaz2Im1/Bt/32kgww2QvUh4z+L7XUoIZJnuR8pjB9702EswwSTB5zOD7XksJZpgkmDxm8H2vjQQzTBJMHjP4vtdSghkmCSaPGXzfayPBDJMEk8cMvu+1lGCGSYLJYwbf99pIMMMkweQxg+97LSWYYZJg8pjB9702EsxwOZt6+zP4vtdSghkeZ1Nvf+ZWQ4IZ8smOzqbOYwbf91pKMMPlbGpJYttJqrEGM0z2IuUxg+97LSWYYUo3R0t38EsOb7hac9jdpHTT748m3S64yD/GuKvANU3qiKN6dmvVm6on3c3C8pgp1euNQ8zri6/r6bR55e+T40n+DWZHgtZ++vpf/ln79uQX3ZrP5Ip7I6vbHYeUKFKy/c/il+Kz//AVn6s5CSZjk2nR3Yg+2lqSyDxRpdepaZsoy/xTxW3SYDI2Hj1/s257TUG9es2lLMpoGuFlmQaTsXrxS33kM1OrF6+9pATDKg0mY9ViapRgsk9QdZRdgmGVEcnY8owoOeSdoKzBnE+D2RHbnqHVfgnGGswqDWZHbHuGVvslmOVfa9Fgsrb8O70EsSMJxkLvimr1ITmxBrM7yWncJZhtv2Py41ekjEkwO5hgHAezQoPJmONgtp9M7EXajAaTseXDzrc9Q6sSzDo0mIwtx+1tz9Cq42DWocFkzJG8u5OcrMGcT4PJmL1Iu5OcHMl7Pg0mY/YibT+ZSDCb0WAyJsFsP5lIMJvRYDImwWw/mUgwm9FgMuY4mO0nEwlmMxpMxhwHs/1kIsFsRoPJmONgtp9MJJjNaDAZcxzM9pOJBLMZDSZj9iJtP5lIMJvRYDJmL9L2k4kEsxkNJmP2Im0/mUgwm3HBqdzXYObX/V7MpOlNf1lNFz26Mx5HW0+iqGa1rEbR1NNza123MapGcTKZRFWMom6nvbaz+PCNilEclG1MmyZGZdnVy7a3Tl08b6pPp0233b7j0R3GX4yiiNnPedV4LGobZdR1041Hn+3Vbdnd2dH1YFZpMANZg0lv8uTHx5P4nx/8KJrxg5hEE+NLQuri71P99dcexK986EO9Z+wklf/68ftx9OxZtMXNvpWKto4Hd+/Gb33sI2cf+j7f5/TwXrz36L34wfvz5ymfRtvcu7Au3I/H8atvfixev3MYddv0vqJduXSzPDSYnVuDuSy53B2V8Z2jJv7uX/9j3m76+/tP/2Z773c+FQ8/eNR9WCZR9koGn/vnf7v2tjbx33/+Tlv0HI/0cxwd3I93v//D+Ot//+61v8+//ewft5+5F1E2Eafp+S65R7gEcz5rMANJMN09rDdQlaNZnT9PnwSTksQ2tLcwHsu6e053TeTiJONs6vNpMAPZi5SuR7KJukm/dtRnz3PV9pKUYG7b9IU1kV7jscH3OZnW8+2m18P1YK5LgxlYginX/DDtSoJJi8q3mWAWTb7P9rqks3SZUzSYwSWYZs0PfUowXR1ignkFTd4V7dYjwQzkOBgJRoLJkQYzkLOpN00wCxLMc9VIgtmUBrMjbjrBHB6Ou2oN5rl6ag1mUxpMxm5zDebkZNJVCeY5CWZzGkzGbnMv0mKRV4LZLMEsX2IDDSZrt5lgFiSYDRPM0qSABpO120wwCxLMhmswS5MCGkzWJJjtHgcjwWxOu82YBLP9BFONZ3vXJJj1aDAZk2AySDCpy1iDWZsGkzEJZvsJJl18KpFg1qPBZEyCeZkEs1s0mIxJMC+TYHaLBpMxCWb3EszyCapoMFmTYHYwwbgezAoJJmO3mWAWF5xyJO/SmIwi6snsHC3Xg1mPBpOx20wwzqY+nyvabUaDydhtJpgnz467KsGsckW7zWgwGbMGk8EajGvybkSDyVi3VyK9REXVK8G0ZXHjZ1Mn6f5Axbxuay/S6fwOj4vv58W6GLeNtxejs+dZXIP4ojoeVe7s+AINJnPpdqTR1mcfnsWH/LCIKOc3ATv7dxveVaBqZs+TGtuLz794vPh+un/3Cu891NfheNylmHtV1d1GpJqPy+LnX9R0E7r0/Y2b2W1H1pVui5t+3rSddPvZi2pqLonrwaxy69iMPf7gKB7Haff1SX0ah1UZJ3XT1XQTsLYcRdGcdm/y9PjewUH87Gj276/r6OQ0nj75II6fHXfPt9hOquV4FM1kurLd43Yad7ZwbaX/O3ra1bRzJ52HeFFN32/6vn9SNfHz0/WazLPJaTx6UpyN/2IczqtFM+3G7fFZrgIAAAAAAAAAAAAAAAAgevp/2dsONs8JxOUAAAAASUVORK5CYII=" alt=""><img class="chLau" src="data:image/png;base64,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" alt=""></div></div>';
|
|
5368
5379
|
function clawdHeroHTML(agent){
|
|
5369
5380
|
const app = connectedAppMeta(agent);
|
|
5381
|
+
// Codex: wordmark + status only — no Clawd mascot (that's a Claude Code brand
|
|
5382
|
+
// mark, §6.5) and no glyph badge either (Masa 2026-07-16: 「codexの時は文字だけでよい」).
|
|
5370
5383
|
const stage = agent === 'codex'
|
|
5371
|
-
?
|
|
5384
|
+
? ''
|
|
5372
5385
|
: `<div class="cstage"><div class="chConf"></div><div class="cw">${CLAWD_HERO_IMGS}`;
|
|
5373
|
-
|
|
5386
|
+
// .hstat: the status line is the ONLY element renderClawdHero()/bootDisconnected()
|
|
5387
|
+
// may rewrite — never target it by bare `span` (the codex badge used to carry an
|
|
5388
|
+
// .appglyph <span>, so querySelector('span') clobbered the glyph with the status).
|
|
5389
|
+
return stage + `<b>${esc(app.label)}</b><span class="hstat">CONNECTED · WAITING FOR GOALS</span>`;
|
|
5374
5390
|
}
|
|
5375
5391
|
function spawnHeroConfetti(){
|
|
5376
5392
|
const conf = document.querySelector('#fsClawdHero .chConf'); if (!conf) return;
|
|
@@ -5391,9 +5407,9 @@ function spawnHeroConfetti(){
|
|
|
5391
5407
|
}
|
|
5392
5408
|
function renderClawdHero(){
|
|
5393
5409
|
const el = $('fsClawdHero'); if (!el) return;
|
|
5394
|
-
const prevStatus = el.querySelector('
|
|
5410
|
+
const prevStatus = el.querySelector('.hstat')?.textContent;
|
|
5395
5411
|
el.innerHTML = clawdHeroHTML(state.connectedApp);
|
|
5396
|
-
if (prevStatus) { const s = el.querySelector('
|
|
5412
|
+
if (prevStatus) { const s = el.querySelector('.hstat'); if (s) s.textContent = prevStatus; }
|
|
5397
5413
|
if (state.connectedApp !== 'codex') spawnHeroConfetti(); // Welcome P1 confetti (H17 item 4)
|
|
5398
5414
|
}
|
|
5399
5415
|
// one-time static wiring for the flagship shell (folds, lane resize, rail menu, band picker)
|
|
@@ -5782,7 +5798,7 @@ function bootDisconnected(info){
|
|
|
5782
5798
|
state.active = '__onboard';
|
|
5783
5799
|
state.billing = { licensed: false, email };
|
|
5784
5800
|
try { render(); } catch (e) { console.warn('disconnected render', e); }
|
|
5785
|
-
const heroSpan = document.querySelector('#fsClawdHero
|
|
5801
|
+
const heroSpan = document.querySelector('#fsClawdHero .hstat');
|
|
5786
5802
|
if (heroSpan) heroSpan.textContent = 'CONNECT YOUR MACHINE TO BEGIN';
|
|
5787
5803
|
|
|
5788
5804
|
const style = document.createElement('style');
|
|
@@ -5853,7 +5869,17 @@ function bootDisconnected(info){
|
|
|
5853
5869
|
const es = new EventSource('/presence');
|
|
5854
5870
|
es.addEventListener('presence', (e) => {
|
|
5855
5871
|
let d; try { d = JSON.parse(e.data); } catch { return; }
|
|
5856
|
-
if (d.state === 'connected') {
|
|
5872
|
+
if (d.state === 'connected') {
|
|
5873
|
+
// Confirm the engine is REALLY reachable before reloading. A stale/spurious
|
|
5874
|
+
// 'connected' presence otherwise reloads us into a white flash that lands
|
|
5875
|
+
// right back on this same connect screen — the welcome animation plays, then
|
|
5876
|
+
// 一瞬真っ白 (Masa 2026-07-16). Reuse the proven 6d71c32 contract: the relay
|
|
5877
|
+
// 503s every engine-backed path while offline and 200s once proxied, so
|
|
5878
|
+
// status !== 503 flips exactly at a genuine connect.
|
|
5879
|
+
fetch('/api/state', { cache: 'no-store', credentials: 'same-origin' })
|
|
5880
|
+
.then((r) => { if (r.status !== 503) { es.close(); location.reload(); } })
|
|
5881
|
+
.catch(() => {}); // engine unreachable → not connected yet; keep waiting
|
|
5882
|
+
}
|
|
5857
5883
|
else if (cgWait) cgWait.innerHTML = '<i></i>WAITING FOR THIS DEVICE';
|
|
5858
5884
|
});
|
|
5859
5885
|
window.addEventListener('beforeunload', () => { try { es.close(); } catch {} });
|
|
@@ -5917,6 +5943,19 @@ async function postOutboxItem(item){
|
|
|
5917
5943
|
}
|
|
5918
5944
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
5919
5945
|
const goal = await r.json();
|
|
5946
|
+
if (goal.kind === 'chat') {
|
|
5947
|
+
// Conversation-first composer (SLICE 2): this was a chat/help/settings
|
|
5948
|
+
// turn, not a To-Do. Retire the optimistic outbox item — a chat turn is
|
|
5949
|
+
// ephemeral by design, so dropping it does NOT weaken the never-lose
|
|
5950
|
+
// guarantee (which protects real tasks/goals). Show the reply quietly
|
|
5951
|
+
// above the input. We must NOT reach land()/upsert(): a kind:'chat'
|
|
5952
|
+
// response carries no goal id, so upserting it would wedge a ghost
|
|
5953
|
+
// "Sending…" row in the queue forever and pollute state.goals.
|
|
5954
|
+
outbox.remove(item.oid);
|
|
5955
|
+
showChatReply(goal.reply);
|
|
5956
|
+
render();
|
|
5957
|
+
return; // finally still runs posting.delete(); skip land()/upsert()
|
|
5958
|
+
}
|
|
5920
5959
|
goal.imageUrls = item.imageUrls;
|
|
5921
5960
|
outbox.land(item.oid, goal.id);
|
|
5922
5961
|
upsert(state.goals, goal);
|
|
@@ -5928,6 +5967,17 @@ async function postOutboxItem(item){
|
|
|
5928
5967
|
render();
|
|
5929
5968
|
}
|
|
5930
5969
|
|
|
5970
|
+
// SLICE 2: render / clear the conversation-first composer reply (reuses the
|
|
5971
|
+
// .peraanswer surface). Quiet, transient — cleared the moment the user sends
|
|
5972
|
+
// the next message so a stale reply never lingers above a new turn.
|
|
5973
|
+
function showChatReply(text){
|
|
5974
|
+
const el = $('chatReply'); if (!el) return;
|
|
5975
|
+
const t = (text || '').trim();
|
|
5976
|
+
if (!t) { el.hidden = true; el.textContent = ''; return; }
|
|
5977
|
+
el.className = 'peraanswer'; el.textContent = t; el.hidden = false;
|
|
5978
|
+
}
|
|
5979
|
+
function clearChatReply(){ const el = $('chatReply'); if (el) { el.hidden = true; el.textContent = ''; } }
|
|
5980
|
+
|
|
5931
5981
|
async function flushOutbox(){
|
|
5932
5982
|
// Only (re)send items that still need it — never re-POST 'landed' memos that
|
|
5933
5983
|
// are merely waiting for reconcile() (that was the double-send path).
|
|
@@ -6004,6 +6054,7 @@ async function send(){
|
|
|
6004
6054
|
renderMsgHint();
|
|
6005
6055
|
|
|
6006
6056
|
$('input').value = ''; $('input').style.height = 'auto';
|
|
6057
|
+
clearChatReply(); // a new turn starts — drop any prior conversational reply
|
|
6007
6058
|
closePalette();
|
|
6008
6059
|
localStorage.removeItem('draft');
|
|
6009
6060
|
renderAttach(); render();
|
package/engine/lib.mjs
CHANGED
|
@@ -1170,6 +1170,72 @@ export function detectRequestLanguage(text) {
|
|
|
1170
1170
|
return /[-ヿ㐀-鿿]/.test(text || '') ? 'ja' : 'en';
|
|
1171
1171
|
}
|
|
1172
1172
|
|
|
1173
|
+
// SLICE 2 intent routing (docs/HANDOFF-ONBOARDING-conversational.md): the single
|
|
1174
|
+
// composer is conversation-first — a chat/help/settings message ("what can you
|
|
1175
|
+
// do?", "reply in English", a greeting) must get a conversational reply, NOT a
|
|
1176
|
+
// To-Do; only implementation requests become goals. These are the pure,
|
|
1177
|
+
// testable pieces; the server wires them into POST /api/tasks.
|
|
1178
|
+
//
|
|
1179
|
+
// A dependency-free heuristic that short-circuits only the OBVIOUS cases so most
|
|
1180
|
+
// sends skip the LLM classifier:
|
|
1181
|
+
// 'task' — a clear implementation request (imperative verb / code token) and
|
|
1182
|
+
// no chat signal → skip the LLM, go straight to goal creation.
|
|
1183
|
+
// 'chat' — a clear help/settings/greeting message and no task signal.
|
|
1184
|
+
// 'unsure' — ambiguous (both signals, or neither) → defer to the LLM.
|
|
1185
|
+
// It is deliberately conservative about declaring 'task' (the only branch that
|
|
1186
|
+
// skips the LLM): a message carrying BOTH a task verb and a help/settings signal
|
|
1187
|
+
// is 'unsure', not 'task', so a help question phrased with "add"/"fix" isn't
|
|
1188
|
+
// silently turned into a goal.
|
|
1189
|
+
const INTENT_TASK_RE = /\b(fix|add|implement|create|build|refactor|remove|delete|rename|migrate|debug|integrate|revert|deploy)\b|実装|直し|直す|なおして|修正|作って|作成|追加|変更|削除|対応して|リファクタ|置き換え|バグ|不具合/i;
|
|
1190
|
+
const INTENT_CODE_RE = /\.(?:js|mjs|cjs|ts|tsx|jsx|css|scss|html|py|go|rs|java|rb|php|json|md|yml|yaml|sh|sql)\b|`[^`]+`|\b(?:function|const|class|import)\b|#\d+/;
|
|
1191
|
+
const INTENT_CHAT_META_RE = /何ができ|なにができ|何が出来|使い方|どう使|どうやって使|使える\?|説明して|教えて(?:ください|くれ|ほしい)?\s*[??]?$|about this (?:tool|app)|what (?:can|do|are|is|does) (?:you|it|this|i)|what'?s this|what can this|how (?:do|does|can) (?:i|this|you|it)|how to use|who are you|^\s*help\s*[??]?$|そもそも|初めて/i;
|
|
1192
|
+
const INTENT_CHAT_SETTINGS_RE = /英語で|日本語で|(?:reply|respond|answer|talk|speak|write)\s+(?:back\s+)?in\s+\w|語で(?:返|答|話|書)|言語/i;
|
|
1193
|
+
const INTENT_GREETING_RE = /^\s*(?:hi|hey|hello|yo|sup|thanks|thank you|thx|good (?:morning|evening|afternoon))\b[\s.!?]*$|^\s*(?:こんにち(?:は|わ)|こんばん(?:は|わ)|おはよう|はじめまして|やあ|ありがと(?:う)?|よろしく)[\s。!?!?]*$/i;
|
|
1194
|
+
|
|
1195
|
+
export function classifyComposerIntentHeuristic(text) {
|
|
1196
|
+
const t = (text || '').trim();
|
|
1197
|
+
if (!t) return 'chat';
|
|
1198
|
+
const hasTask = INTENT_TASK_RE.test(t) || INTENT_CODE_RE.test(t);
|
|
1199
|
+
const hasChat = INTENT_CHAT_META_RE.test(t) || INTENT_CHAT_SETTINGS_RE.test(t) || INTENT_GREETING_RE.test(t);
|
|
1200
|
+
if (hasTask && !hasChat) return 'task'; // pure implementation request → skip LLM
|
|
1201
|
+
if (hasChat && !hasTask) return 'chat'; // pure help/settings/greeting
|
|
1202
|
+
return 'unsure'; // both or neither → let the LLM decide
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// The one prompt that BOTH classifies and (for chat) writes the reply in the
|
|
1206
|
+
// requester's language — one Haiku round-trip, not two. The model returns a
|
|
1207
|
+
// single line of JSON: {"intent":"chat|task","reply":"…"}.
|
|
1208
|
+
export function buildIntentPrompt(text, lang) {
|
|
1209
|
+
const language = lang === 'ja' ? '日本語' : 'English';
|
|
1210
|
+
return [
|
|
1211
|
+
'You are the intent router for Agent Manager: a tool where a user delegates a coding task in plain language, an AI implements it, and it comes back as a reviewed pull request with proof.',
|
|
1212
|
+
'Classify the user message below into exactly one intent:',
|
|
1213
|
+
'- "task": a request to implement, change, fix, build, or remove something in code (even if vague, e.g. "add dark mode").',
|
|
1214
|
+
'- "chat": anything that is NOT an implementation request — questions about what this tool can do or how to use it, greetings, thanks, small talk, or a settings/preference request (e.g. "reply in English").',
|
|
1215
|
+
'When in doubt between the two, prefer "task".',
|
|
1216
|
+
`If intent is "chat", also write "reply": a short, warm, helpful answer in ${language} (at most 4 sentences). If the user asks what this can do or how to start, briefly explain: describe the change you want in plain language, and it will ask which folder/repo to work in, implement it, and return a PR with proof. If intent is "task", set "reply" to "".`,
|
|
1217
|
+
'Output ONLY one line of raw JSON with keys "intent" and "reply". No markdown, no code fence, no extra text.',
|
|
1218
|
+
'',
|
|
1219
|
+
'User message:',
|
|
1220
|
+
String(text || '').slice(0, 4000),
|
|
1221
|
+
].join('\n');
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// Tolerant parser for the router's reply — never throws. Extracts the first JSON
|
|
1225
|
+
// object; on any failure it fails safe to 'task' (so a garbled classification
|
|
1226
|
+
// can never silently swallow a real implementation request). Reply is clamped.
|
|
1227
|
+
export function parseIntentResponse(raw) {
|
|
1228
|
+
const fallback = { intent: 'task', reply: '' };
|
|
1229
|
+
if (!raw || typeof raw !== 'string') return fallback;
|
|
1230
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
1231
|
+
if (!m) return fallback;
|
|
1232
|
+
let obj;
|
|
1233
|
+
try { obj = JSON.parse(m[0]); } catch { return fallback; }
|
|
1234
|
+
const intent = obj && obj.intent === 'chat' ? 'chat' : 'task';
|
|
1235
|
+
const reply = intent === 'chat' && typeof obj.reply === 'string' ? obj.reply.trim().slice(0, 2000) : '';
|
|
1236
|
+
return { intent, reply };
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1173
1239
|
// Normalize a nested boolean-flag object (prSections/reviewCard) key by key,
|
|
1174
1240
|
// so a partial payload — or one missing the whole sub-object — can't
|
|
1175
1241
|
// silently disable flags it never mentioned; each key falls back to its own
|
package/engine/server.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { homedir } from 'node:os';
|
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { pathToFileURL } from 'node:url';
|
|
23
23
|
import { needsProjectFolder, folderLabel, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed } from './lib.mjs';
|
|
24
|
-
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable } from './lib.mjs';
|
|
24
|
+
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable, classifyComposerIntentHeuristic, buildIntentPrompt, parseIntentResponse } from './lib.mjs';
|
|
25
25
|
import { openPR } from './pr.mjs';
|
|
26
26
|
import { runVerification, exerciseUi } from './verify.mjs';
|
|
27
27
|
|
|
@@ -582,7 +582,19 @@ function sendGoalEvent(obj, goal) {
|
|
|
582
582
|
if (goalVisibleTo(goal, identity, MANAGER_OWNER_EMAIL)) res.write(data);
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
|
-
|
|
585
|
+
// Durable append to the restart-safe log. A failing disk (ENOSPC when the
|
|
586
|
+
// volume is full, EACCES/EROFS) must NEVER freeze the board: without this guard
|
|
587
|
+
// an appendFileSync throw inside a handler leaves the HTTP request hanging (no
|
|
588
|
+
// response) AND skips the SSE broadcast that follows, so a delete/answer looks
|
|
589
|
+
// like it did nothing — the question card stays stuck forever (Masa 2026-07-16).
|
|
590
|
+
// We swallow the write error (logged), and let the in-memory mutation + SSE
|
|
591
|
+
// proceed regardless. Durability is best-effort here; a live UI wins over a
|
|
592
|
+
// perfectly-replayable log on a disk that can't be written to anyway.
|
|
593
|
+
function logAppend(line) {
|
|
594
|
+
try { appendFileSync(logFile, line + '\n'); return true; }
|
|
595
|
+
catch (e) { console.error(`[manager] log write failed (${e?.code || e}) — state not persisted this write, UI still updated`); return false; }
|
|
596
|
+
}
|
|
597
|
+
function saveGoal(g) { logAppend(JSON.stringify({ ...g, kind: 'goal' })); sendGoalEvent({ ev: 'goal', goal: g }, g); }
|
|
586
598
|
// Persist the orphan-goal reclassification computed right after replayQueueLog
|
|
587
599
|
// above (deferred to here because saveGoal/send/sseClients don't exist yet at
|
|
588
600
|
// that point in the module) — writes it to the restart-safe log so it settles
|
|
@@ -594,7 +606,7 @@ for (const fix of orphanFixes) {
|
|
|
594
606
|
}
|
|
595
607
|
function saveTask(t) {
|
|
596
608
|
const { activity, todos, ...persist } = t; // activity + todos are ephemeral (live only)
|
|
597
|
-
|
|
609
|
+
logAppend(JSON.stringify({ ...persist, kind: 'task' }));
|
|
598
610
|
const goal = goals.find((g) => g.id === t.goalId);
|
|
599
611
|
if (goal) sendGoalEvent({ ev: 'task', task: persist }, goal); else send({ ev: 'task', task: persist });
|
|
600
612
|
emitTaskAnalytics(t);
|
|
@@ -2701,6 +2713,32 @@ const server = createServer(async (req, res) => {
|
|
|
2701
2713
|
const { projectId, text, pr, images, model, effort, source, pending, review, note, mode, skill, agent } = JSON.parse(body);
|
|
2702
2714
|
const project = projects.find((p) => p.id === projectId);
|
|
2703
2715
|
if (!project || !text?.trim()) return json(res, 400, { error: 'projectId and text required' });
|
|
2716
|
+
// Onboarding (docs/HANDOFF-ONBOARDING-conversational.md, SLICE 2): the
|
|
2717
|
+
// composer is conversation-first. A chat/help/settings message ("what can
|
|
2718
|
+
// you do?", "reply in English", a greeting) gets a conversational reply
|
|
2719
|
+
// and must NOT become a To-Do. Classified BEFORE dedupe/billing/goal
|
|
2720
|
+
// creation, so a chat turn never folds into a goal, never counts against
|
|
2721
|
+
// the free-tier cap, and never persists a task. /later (pending) and
|
|
2722
|
+
// /review are explicit user intent → skip classification. A cheap
|
|
2723
|
+
// heuristic short-circuits obvious implementation requests (no LLM);
|
|
2724
|
+
// anything else gets one Haiku round-trip that both classifies and writes
|
|
2725
|
+
// the reply. LLM failure falls back to 'task' — never drop a real
|
|
2726
|
+
// implementation ask (the folder guard below still prevents no-ops).
|
|
2727
|
+
if (!pending && !review && classifyComposerIntentHeuristic(text.trim()) === 'chat') {
|
|
2728
|
+
// Only a HIGH-confidence chat/help/settings message reaches the LLM —
|
|
2729
|
+
// to write the reply, and to let the model veto back to 'task' if the
|
|
2730
|
+
// heuristic over-fired. Everything else (clear tasks AND ambiguous
|
|
2731
|
+
// 'unsure') goes straight to goal creation: bias to task keeps the
|
|
2732
|
+
// common path LLM-free (no latency/tokens on real work) and the folder
|
|
2733
|
+
// guard below still prevents no-ops. LLM failure → treat as task and
|
|
2734
|
+
// fall through, so a real implementation ask is never dropped.
|
|
2735
|
+
const intentLang = detectRequestLanguage(text);
|
|
2736
|
+
try {
|
|
2737
|
+
const r = await runClaude({ prompt: buildIntentPrompt(text.trim(), intentLang), cwd: ROOT, tools: 'Read', model: 'haiku' });
|
|
2738
|
+
const parsed = parseIntentResponse(r.result);
|
|
2739
|
+
if (parsed.intent === 'chat') return json(res, 200, { kind: 'chat', reply: parsed.reply.slice(0, 2000), lang: intentLang });
|
|
2740
|
+
} catch { /* LLM unavailable → fall through to goal creation */ }
|
|
2741
|
+
}
|
|
2704
2742
|
// Dedupe (Masa: 被ったやつもまとめて): if this near-duplicates a goal
|
|
2705
2743
|
// already in flight (running/review with a live worker session), fold it
|
|
2706
2744
|
// in as a thread follow-up (context kept via session resume) instead of
|
|
@@ -2850,7 +2888,7 @@ const server = createServer(async (req, res) => {
|
|
|
2850
2888
|
const q = queues.get(goal.projectId);
|
|
2851
2889
|
if (q) q.waiting = q.waiting.filter((t) => t.goalId !== goal.id);
|
|
2852
2890
|
goals = goals.filter((g) => g.id !== goal.id);
|
|
2853
|
-
|
|
2891
|
+
logAppend(JSON.stringify({ kind: 'goal', id: goal.id, deleted: true }));
|
|
2854
2892
|
sendGoalEvent({ ev: 'goal-deleted', id: goal.id }, goal);
|
|
2855
2893
|
startNextStacked(goal.projectId);
|
|
2856
2894
|
pump(goal.projectId);
|
package/package.json
CHANGED