symbiot 0.35.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/agents.mjs +21 -1
- package/index.mjs +2 -1
- package/package.json +1 -1
- package/ui.mjs +11 -0
package/agents.mjs
CHANGED
|
@@ -33,6 +33,26 @@ function setHandoffCmd(cmd) {
|
|
|
33
33
|
delete cfg.ide; saveConfig(cfg);
|
|
34
34
|
return { ok: true, cmd: cfg.agentCmd || "" };
|
|
35
35
|
}
|
|
36
|
+
// Grant a blocked agent what it asked for, instead of hand-editing the command:
|
|
37
|
+
// `tool` (a command like "python3" → Bash(python3:*), or a full "Tool(spec)")
|
|
38
|
+
// joins --allowedTools; `dir` joins --add-dir. Claude only (that's where these
|
|
39
|
+
// flags live); for another agent we say to edit the command directly.
|
|
40
|
+
function grantAgent({ tool, dir } = {}) {
|
|
41
|
+
const cfg = loadConfig(); let cmd = (cfg.agentCmd || CLAUDE_CMD).trim();
|
|
42
|
+
if (!/^\s*claude\b/.test(cmd)) return { error: "Grants apply to the Claude agent command. Pick a Claude preset first, or edit the command directly." };
|
|
43
|
+
if (tool) {
|
|
44
|
+
let t = String(tool).trim();
|
|
45
|
+
if (t && !/^[A-Za-z]+\(/.test(t)) t = `Bash(${t.replace(/[()"]/g, "")}:*)`; // bare word -> Bash(word:*)
|
|
46
|
+
if (t && !cmd.includes(`"${t}"`)) {
|
|
47
|
+
if (/--allowedTools\b/.test(cmd)) cmd = cmd.replace(/(--allowedTools\s+(?:"[^"]*"\s*)+)/, (m) => m.trimEnd() + ` "${t}" `);
|
|
48
|
+
else cmd += ` --allowedTools "${t}"`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (dir) { const d = String(dir).trim(); if (d && !cmd.includes(`--add-dir "${d}"`)) cmd += ` --add-dir "${d}"`; }
|
|
52
|
+
cmd = cmd.replace(/\s+/g, " ").trim();
|
|
53
|
+
cfg.agentCmd = cmd; saveConfig(cfg);
|
|
54
|
+
return { ok: true, cmd };
|
|
55
|
+
}
|
|
36
56
|
const fillHandoff = (tmpl, repoPath) => tmpl.replace(/\{dir\}/g, shSingle(repoPath)).replace(/\{prompt\}/g, escDq(HANDOFF_PROMPT));
|
|
37
57
|
// One agent per folder: two identical runs once started on the same repo 6s
|
|
38
58
|
// apart and raced each other. The registry catches a second click in this
|
|
@@ -305,4 +325,4 @@ function agentsList() {
|
|
|
305
325
|
});
|
|
306
326
|
}
|
|
307
327
|
|
|
308
|
-
export { HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, fillHandoff, runHandoff, runningHandoff, writeTasks, releaseHeldTasks, detectHandoffs, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, agentQuestions, answerQuestions, agentsList };
|
|
328
|
+
export { HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, fillHandoff, runHandoff, runningHandoff, writeTasks, releaseHeldTasks, detectHandoffs, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, agentQuestions, answerQuestions, agentsList };
|
package/index.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
30
30
|
import { EMBEDDED_UI } from "./ui.mjs";
|
|
31
31
|
import { detectMailSources, mailActivity } from "./mail.mjs";
|
|
32
32
|
import { CONFIG_PATH, loadConfig, saveConfig, loadTasks, saveTasks, sh, hasCmd, repoState } from "./core.mjs";
|
|
33
|
-
import { HANDOFF_PROMPT, QUESTIONS_MAX, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, fillHandoff, runHandoff, writeTasks, releaseHeldTasks, detectHandoffs, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, parseQuestions, agentQuestions, answerQuestions, agentsList } from "./agents.mjs";
|
|
33
|
+
import { HANDOFF_PROMPT, QUESTIONS_MAX, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, fillHandoff, runHandoff, writeTasks, releaseHeldTasks, detectHandoffs, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, parseQuestions, agentQuestions, answerQuestions, agentsList } from "./agents.mjs";
|
|
34
34
|
import { gitDefaultBranch, loadDeploys, driftRepo } from "./drift.mjs";
|
|
35
35
|
|
|
36
36
|
const HERE = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -1484,6 +1484,7 @@ async function cmdApp() {
|
|
|
1484
1484
|
if (u.pathname === "/api/scanroots/remove" && req.method === "POST") { const b = await readBody(req); return json(res, removeScanRoot(String(b.path || ""))); }
|
|
1485
1485
|
if (u.pathname === "/api/agentcfg") { const d = detectHandoffs(); return json(res, { cmd: handoffCmd(), agents: d.agents, editors: d.editors }); }
|
|
1486
1486
|
if (u.pathname === "/api/agentcmd" && req.method === "POST") { const b = await readBody(req); return json(res, setHandoffCmd(b.cmd)); }
|
|
1487
|
+
if (u.pathname === "/api/agent/grant" && req.method === "POST") { const b = await readBody(req); return json(res, grantAgent({ tool: b.tool, dir: b.dir })); }
|
|
1487
1488
|
if (u.pathname === "/api/open" && req.method === "POST") { const b = await readBody(req); const e = runHandoff(String(b.path || "")); return json(res, { opened: !!e && !e.busy, busy: !!(e && e.busy), id: e ? e.id : "" }); }
|
|
1488
1489
|
if (u.pathname === "/api/setup-local" && req.method === "POST") {
|
|
1489
1490
|
const b = await readBody(req);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
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>
|
|
@@ -500,6 +506,7 @@ if(chips.length>na)h+="<div class='muted' style='font-size:12px;margin-top:8px'>
|
|
|
500
506
|
box.innerHTML=h;
|
|
501
507
|
box.querySelectorAll('.preset').forEach(function(btn){btn.addEventListener('click',function(){document.getElementById('agentcmd').value=chips[+btn.getAttribute('data-i')].tmpl;saveAgent();});});});}
|
|
502
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;});}
|
|
503
510
|
function loadScanRoots(){api('/api/scanroots').then(function(d){var box=document.getElementById('scanroots');var roots=d.effective||[];
|
|
504
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("");
|
|
505
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;});});});});}
|
|
@@ -565,6 +572,10 @@ document.getElementById('agentsrefresh').addEventListener('click',loadAgents);
|
|
|
565
572
|
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
566
573
|
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
567
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');});
|
|
568
579
|
document.getElementById('mailon').addEventListener('change',setMailOn);
|
|
569
580
|
document.getElementById('mailaddrs').addEventListener('change',saveMailAddrs);
|
|
570
581
|
document.getElementById('addmail').addEventListener('click',addMailUI);
|