symbiot 0.34.0 → 0.36.0
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/README.md +25 -61
- package/agents.mjs +328 -0
- package/core.mjs +53 -0
- package/drift.mjs +124 -0
- package/index.mjs +48 -419
- package/package.json +4 -1
- package/ui.mjs +24 -10
package/ui.mjs
CHANGED
|
@@ -214,6 +214,12 @@ label.check input{width:auto}
|
|
|
214
214
|
<input id="agentcmd" type="text" placeholder="e.g. aider --message "{prompt}" --yes · code {dir} · leave blank to just write the file">
|
|
215
215
|
<div class="note muted">Runs in each repo after tasks are written. Use <b>{dir}</b> = repo path, <b>{prompt}</b> = the task instruction. Works with any agent or editor — it's your command.</div>
|
|
216
216
|
<div id="agentpresets" style="margin-top:8px"></div>
|
|
217
|
+
<div id="grantbox" style="margin-top:10px">
|
|
218
|
+
<label style="font-size:12px;color:var(--faint)">When an agent asks to run something or read a folder, grant it here instead of editing the command:</label>
|
|
219
|
+
<div class="row" style="margin-top:6px"><input id="granttool" placeholder="allow a command, e.g. python3 or pytest" style="flex:1"><button class="ghost" id="granttoolbtn">Allow command</button></div>
|
|
220
|
+
<div class="row" style="margin-top:6px"><input id="grantdir" placeholder="allow a folder, e.g. /home/you/GoSolr" style="flex:1"><button class="ghost" id="grantdirbtn">Allow folder</button></div>
|
|
221
|
+
<div class="note" id="grantnote"></div>
|
|
222
|
+
</div>
|
|
217
223
|
</div>
|
|
218
224
|
<div style="margin-top:20px;border-top:1px solid var(--line);padding-top:16px">
|
|
219
225
|
<label>Email — add what you sent to Week and Standup <span class="muted">(experimental)</span></label>
|
|
@@ -372,12 +378,13 @@ var h="";keys.forEach(function(ty){h+="<div class='tgroup'>"+esc(ty)+" <span cla
|
|
|
372
378
|
el.innerHTML=h;wireTaskRows(el);}
|
|
373
379
|
function loadTasks(){TARCH=false;api('/api/tasks/sync',{}).then(function(){api('/api/tasks').then(function(list){ALLTASKS=list;renderTasks();});loadPending();});}
|
|
374
380
|
function loadPending(){api('/api/pending').then(function(list){var el=document.getElementById('reviewlist');PENDTASKS=[];if(!list||!list.length){el.innerHTML='';return;}
|
|
375
|
-
var n=0;list.forEach(function(r){n+=r.tasks.length;});
|
|
381
|
+
var n=0;list.forEach(function(r){n+=r.tasks.length||1;});
|
|
376
382
|
var h="<div class='tgroup' style='color:var(--amber)'>Awaiting your review <span class='tcount'>"+n+"</span></div>";
|
|
377
383
|
list.forEach(function(r){var ch=r.files.length?(r.files.length+" file"+(r.files.length>1?"s":"")+" changed"+(r.stat?" · "+esc(r.stat):"")):"no uncommitted changes";
|
|
378
384
|
h+="<div class='rcard' data-repo='"+esc(r.repo)+"'><div class='rhead'><b>"+esc(r.repo)+"</b><span class='muted'>"+(r.path?"on "+esc(r.branch||'?')+" · "+ch:"repo not found on disk")+"</span></div>";
|
|
379
385
|
r.tasks.forEach(function(t){PENDTASKS.push(t);h+="<div class='task' data-id='"+esc(t.id)+"'><span class='t'>"+esc(t.text)+"</span>"+askBtn(t)+"<button class='rm sendback' title='not right - send back to the agent (unticks it)'>↶</button></div>";});
|
|
380
|
-
h+="<div class='
|
|
386
|
+
if(r.untasked)h+="<div class='muted' style='margin:6px 0'>Uncommitted changes with no ticked task behind them. Check the diff before you approve.</div>";
|
|
387
|
+
h+="<div class='row'><button class='act approve'"+(r.path?"":" disabled")+(r.untasked?" data-untasked='1' title='commit on a branch, push and open a PR, without a task'>Approve changes without a task → PR":" title='commit on a branch, push, open a PR, then archive'>Approve → "+(r.files.length?"PR":"archive"))+"</button>"+(r.files.length?"<button class='ghost showdiff'>Show diff</button>":"")+"<label title='Queue GitHub auto-merge so this PR lands once its CI checks pass. Needs Allow auto-merge on the repo.' style='margin-left:auto;font-size:12px;color:var(--faint);display:flex;align-items:center;gap:6px'><input type='checkbox' class='amtoggle' style='width:auto'"+(r.autoMerge?" checked":"")+"> auto-merge on green CI</label></div><div class='rdiff hidden'></div></div>";});
|
|
381
388
|
el.innerHTML=h;
|
|
382
389
|
el.querySelectorAll('.rcard').forEach(function(card){var repo=card.getAttribute('data-repo');
|
|
383
390
|
card.querySelectorAll('.task').forEach(function(row){wireAsk(row,row.getAttribute('data-id'));});
|
|
@@ -387,9 +394,9 @@ var sd=card.querySelector('.showdiff'),pre=card.querySelector('.rdiff');
|
|
|
387
394
|
if(sd)sd.addEventListener('click',function(){if(!pre.classList.contains('hidden')){pre.classList.add('hidden');sd.textContent='Show diff';return;}
|
|
388
395
|
pre.textContent='Loading...';pre.classList.remove('hidden');sd.textContent='Hide diff';api('/api/pending/diff?repo='+encodeURIComponent(repo)).then(function(d){pre.textContent=(d&&d.diff)||'(no changes)';});});
|
|
389
396
|
var ap=card.querySelector('.approve');if(ap)ap.addEventListener('click',function(){ap.disabled=true;ap.textContent='Committing & pushing...';
|
|
390
|
-
api('/api/pending/approve',{repo:repo}).then(function(r){var o=document.getElementById('reviewout');
|
|
397
|
+
api(ap.getAttribute('data-untasked')?'/api/pending/approve-changes':'/api/pending/approve',{repo:repo}).then(function(r){var o=document.getElementById('reviewout');
|
|
391
398
|
if(!r||r.error){ap.disabled=false;ap.textContent='Approve - retry';o.innerHTML="<div class='note err'>"+esc(repo)+": "+esc((r&&r.error)||'failed')+"</div>";return;}
|
|
392
|
-
var m="✓ <b>"+esc(repo)+"</b>: approved "+r.approved+" task"+(r.approved>1?"s":"");
|
|
399
|
+
var m="✓ <b>"+esc(repo)+"</b>: approved "+(r.approved?r.approved+" task"+(r.approved>1?"s":""):"changes without a task");
|
|
393
400
|
if(r.commit)m+=" · committed <code>"+esc(r.commit)+"</code> on <code>"+esc(r.branch)+"</code>";
|
|
394
401
|
if(r.pr)m+=" · <a href='"+esc(r.pr)+"' target='_blank' rel='noopener'>open PR</a>";
|
|
395
402
|
if(r.autoMerge==='queued')m+=" · will auto-merge when CI passes";
|
|
@@ -477,15 +484,17 @@ function fillTaskRepos(){var sel=document.getElementById('newtaskrepo');if(!sel|
|
|
|
477
484
|
function addTaskUI(){var i=document.getElementById('newtask');var v=(i.value||'').trim();if(!v)return;var repo=(document.getElementById('newtaskrepo')||{}).value||'';api('/api/tasks/add',{text:v,repo:repo}).then(function(){i.value='';TFILTER={type:'',repo:''};loadTasks();});}
|
|
478
485
|
function pushTasksUI(){var o=document.getElementById('pushout');var btn=document.getElementById('pushtasks');btn.disabled=true;
|
|
479
486
|
o.innerHTML="<div class='muted' style='margin-top:10px'><span class='dot-c' style='background:var(--amber)'></span> Writing .symbiot/TASKS.md into your repos…</div>";
|
|
480
|
-
api('/api/tasks/push',{type:TFILTER.type||'',repo:TFILTER.repo||''}).then(function(r){
|
|
481
|
-
if(r.empty){o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send. Tick ideas in a repo review, or add tasks above.</div>";return;}
|
|
482
|
-
var n=(r.written||[]).length;var h="<div class='drift' style='margin-top:10px'><div class='dh'><span class='dot-c'></span><span class='dn'>Done — wrote "+n+" file"+(n===1?"":"s")+"</span></div>";
|
|
483
|
-
if(n){h+="<ul>";r.written.forEach(function(w){h+="<li class='info'>✓ <b>"+esc(w.name)+"</b> <span class='ev'>"+esc(w.file)+" ("+w.count+" task"+(w.count===1?"":"s")+")</span
|
|
487
|
+
api('/api/tasks/push',{type:TFILTER.type||'',repo:TFILTER.repo||''}).then(function(r){
|
|
488
|
+
if(r.empty){btn.disabled=false;o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send. Tick ideas in a repo review, or add tasks above.</div>";return;}
|
|
489
|
+
var n=(r.written||[]).length;var opens=[];var h="<div class='drift' style='margin-top:10px'><div class='dh'><span class='dot-c'></span><span class='dn'>Done — wrote "+n+" file"+(n===1?"":"s")+"</span></div>";
|
|
490
|
+
if(n){h+="<ul>";r.written.forEach(function(w){h+="<li class='info'>✓ <b>"+esc(w.name)+"</b> <span class='ev'>"+esc(w.file)+" ("+w.count+" task"+(w.count===1?"":"s")+")</span>"+(w.held?" <span class='ev'>⏸ held: an agent is still running there, so these land when it finishes</span>":"")+"</li>";});h+="</ul>";}
|
|
484
491
|
if(r.unresolved&&r.unresolved.length){var names=r.unresolved.map(function(u){return u.name;}).join(", ");var hasNoRepo=r.unresolved.some(function(u){return u.name==='(no repo)';});h+="<div class='dd' style='margin-top:6px'>⚠ not sent: "+esc(names)+". "+(hasNoRepo?"Pick a repo in the dropdown next to <b>Add</b> so the task has somewhere to go.":"That repo isn't in the map — add its folder in Settings.")+"</div>";}
|
|
485
|
-
if(r.handoff&&r.written&&r.written.length){r.written.
|
|
492
|
+
if(r.handoff&&r.written&&r.written.length){opens=r.written.map(function(w){return api('/api/open',{path:w.path}).then(function(x){if(x&&x.busy){var d=document.createElement('div');d.className='dd';d.innerHTML="⚠ <b>"+esc(w.name)+"</b> already has an agent running, so another wasn't started. Its new tasks are held until it finishes; send again then to start one on them.";o.appendChild(d);}}).catch(function(){});});h+="<div class='dd' style='margin-top:8px'>🤖 Handed "+n+" repo(s) to your agent — opening the <b>Agents</b> tab to watch it work…</div>";setTimeout(function(){setTab('agents');},500);}
|
|
486
493
|
else{h+="<div class='dd' style='margin-top:8px'>Set an <b>agent command</b> in Settings to auto-run it on send (and watch it in the Agents tab). For now, tell your agent: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div>";}
|
|
487
494
|
h+="</div>";
|
|
488
|
-
o.innerHTML=h;
|
|
495
|
+
o.innerHTML=h;
|
|
496
|
+
// re-enable only once every handoff has started (or been refused), so a double click can't send twice
|
|
497
|
+
Promise.all(opens).then(function(){btn.disabled=false;});}).catch(function(e){btn.disabled=false;o.innerHTML="<div class='err' style='margin-top:10px'>Couldn't write tasks: "+esc(String((e&&e.message)||e))+"</div>";});}
|
|
489
498
|
function loadAgentCfg(){api('/api/agentcfg').then(function(d){document.getElementById('agentcmd').value=d.cmd||'';
|
|
490
499
|
var chips=[];(d.agents||[]).forEach(function(a){chips.push(a);});(d.editors||[]).forEach(function(e){chips.push(e);});
|
|
491
500
|
var box=document.getElementById('agentpresets');
|
|
@@ -497,6 +506,7 @@ if(chips.length>na)h+="<div class='muted' style='font-size:12px;margin-top:8px'>
|
|
|
497
506
|
box.innerHTML=h;
|
|
498
507
|
box.querySelectorAll('.preset').forEach(function(btn){btn.addEventListener('click',function(){document.getElementById('agentcmd').value=chips[+btn.getAttribute('data-i')].tmpl;saveAgent();});});});}
|
|
499
508
|
function saveAgent(){api('/api/agentcmd',{cmd:document.getElementById('agentcmd').value});}
|
|
509
|
+
function grant(kind){var i=document.getElementById(kind==='tool'?'granttool':'grantdir');var v=(i.value||'').trim();if(!v)return;var body={};body[kind]=v;api('/api/agent/grant',body).then(function(r){var n=document.getElementById('grantnote');if(!r||r.error){n.className='note err';n.textContent=(r&&r.error)||'could not grant';return;}i.value='';n.className='note ok';n.textContent=(kind==='tool'?'Command allowed':'Folder allowed')+' — the agent can use it on its next run.';if(r.cmd)document.getElementById('agentcmd').value=r.cmd;});}
|
|
500
510
|
function loadScanRoots(){api('/api/scanroots').then(function(d){var box=document.getElementById('scanroots');var roots=d.effective||[];
|
|
501
511
|
box.innerHTML=roots.map(function(r){var custom=(d.roots||[]).indexOf(r)>=0;return "<div class='task' data-p='"+esc(r)+"'><span class='t' style='font-family:ui-monospace,monospace;font-size:12px'>"+esc(r)+"</span>"+(r===d.home?"<span class='rp'>home</span>":"")+(custom?"<button class='rm rmroot' title='remove'>×</button>":"")+"</div>";}).join("");
|
|
502
512
|
box.querySelectorAll('.rmroot').forEach(function(btn){btn.addEventListener('click',function(){api('/api/scanroots/remove',{path:btn.closest('.task').getAttribute('data-p')}).then(function(){loadScanRoots();mapLoaded=false;driftLoaded=false;});});});});}
|
|
@@ -562,6 +572,10 @@ document.getElementById('agentsrefresh').addEventListener('click',loadAgents);
|
|
|
562
572
|
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
563
573
|
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
564
574
|
document.getElementById('agentcmd').addEventListener('change',saveAgent);
|
|
575
|
+
document.getElementById('granttoolbtn').addEventListener('click',function(){grant('tool');});
|
|
576
|
+
document.getElementById('grantdirbtn').addEventListener('click',function(){grant('dir');});
|
|
577
|
+
document.getElementById('granttool').addEventListener('keydown',function(e){if(e.key==='Enter')grant('tool');});
|
|
578
|
+
document.getElementById('grantdir').addEventListener('keydown',function(e){if(e.key==='Enter')grant('dir');});
|
|
565
579
|
document.getElementById('mailon').addEventListener('change',setMailOn);
|
|
566
580
|
document.getElementById('mailaddrs').addEventListener('change',saveMailAddrs);
|
|
567
581
|
document.getElementById('addmail').addEventListener('click',addMailUI);
|