mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
fix(plan-canvas): deliver browser chat to the agent every time (#2739)
Feedback sent from the canvas only reached an agent through a live /api/await long poll. When a turn ended with no await parked, queueFeedback wrote the message to sessions.json and nothing ever consumed it, so sending appeared to do nothing at all. The presence pill made it worse: workingKeys had no expiry and the feedback handler never broadcast presence, so it froze on "agent working" while nobody was listening. Delivery: - Add the stop:plan-canvas-pending hook. It drains undelivered feedback and blocks the Stop, handing the messages to the agent, so a canvas message lands even when no await is running. Scoped to sessions under cwd so parallel agents cannot swallow each other's feedback; set ECC_PLAN_CANVAS_STOP_SCOPE=all to widen. Honors stop_hook_active and fails open on every error path. - run-with-flags.js did not await a hook's run(), so any async hook silently degraded to pass-through. Fixed; plan-canvas-pending is the only async hook today. Presence and indicators: - Presence is now ended/typing/thinking/listening/queued/waiting. thinking and typing self-expire (90s/30s) and a 5s sweep pushes the decay to an idle browser, so the pill can no longer stick. - Broadcast presence when feedback is queued, and clear the activity state when an agent reply lands. - Add POST /api/session/:key/typing so agents can drive the indicator. - Chat shows an animated dots bubble for thinking and typing, plus an explicit note when a message is queued with nobody listening. Respects prefers-reduced-motion. - Send status reports what actually happened instead of always claiming the agent will pick it up. CLI and skill: - Add `ecc-plan-canvas pending` and `typing <file> --state ...`. - SKILL.md documents background await as the primary pattern and makes replying in the canvas mandatory. Tests: 6 new server cases covering queued presence, the typing endpoint, state expiry and the sweep, plus a new hook suite covering delivery, drain-once, stop_hook_active, cwd scoping and fail-open. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,17 @@
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:plan-canvas-pending','scripts/hooks/plan-canvas-pending.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
|
||||
}
|
||||
],
|
||||
"description": "Deliver undelivered Plan Canvas browser feedback before the agent stops",
|
||||
"id": "stop:plan-canvas-pending"
|
||||
},
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
|
||||
Reference in New Issue
Block a user