agentix-cli 0.13.0 → 0.15.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.
Files changed (82) hide show
  1. package/README.md +65 -478
  2. package/dist/agent-LRSSVFDR.js +2 -0
  3. package/dist/board-dashboard-DAPHL6W2.js +4508 -0
  4. package/dist/board-dashboard-DAPHL6W2.js.map +1 -0
  5. package/dist/chunk-3XULOWYB.js +199 -0
  6. package/dist/chunk-3XULOWYB.js.map +1 -0
  7. package/dist/chunk-6U2XOQHU.js +7 -0
  8. package/dist/chunk-6U2XOQHU.js.map +1 -0
  9. package/dist/{chunk-ZRYBSI5G.js → chunk-7MBTMCKY.js} +2 -2
  10. package/dist/{chunk-D6EDMQRV.js → chunk-AU37OREL.js} +5 -5
  11. package/dist/chunk-B4Q4X7D5.js +6 -0
  12. package/dist/chunk-B4Q4X7D5.js.map +1 -0
  13. package/dist/chunk-CXY2RYNI.js +11 -0
  14. package/dist/chunk-CXY2RYNI.js.map +1 -0
  15. package/dist/chunk-FIOB3MEX.js +2 -0
  16. package/dist/chunk-HLRVDCNH.js +5 -0
  17. package/dist/chunk-HLRVDCNH.js.map +1 -0
  18. package/dist/chunk-IEVEMKAI.js +168 -0
  19. package/dist/chunk-IEVEMKAI.js.map +1 -0
  20. package/dist/chunk-RB7265LO.js +12 -0
  21. package/dist/chunk-RB7265LO.js.map +1 -0
  22. package/dist/chunk-RGUYBH77.js +7 -0
  23. package/dist/chunk-RGUYBH77.js.map +1 -0
  24. package/dist/chunk-WVNSWBCU.js +138 -0
  25. package/dist/chunk-WVNSWBCU.js.map +1 -0
  26. package/dist/cli.js +64 -48
  27. package/dist/cli.js.map +1 -1
  28. package/dist/{compaction-7I3E5QUO.js → compaction-JBZZ7I7Y.js} +4 -4
  29. package/dist/config-DCJYECL4.js +2 -0
  30. package/dist/config-mutator-6EJHSP3Y.js +2 -0
  31. package/dist/debug-3MEUO2P2.js +2 -0
  32. package/dist/{heal-OTGT5HHJ.js → heal-WKY5I4KB.js} +2 -2
  33. package/dist/index.d.ts +894 -70
  34. package/dist/index.js +11 -11
  35. package/dist/index.js.map +1 -1
  36. package/dist/loader-PTCXLJEX.js +2 -0
  37. package/dist/memory-extract-AD7KMRPC.js +2 -0
  38. package/dist/{providers-G3WZ4RVJ.js → providers-VUIWO3ZK.js} +2 -2
  39. package/dist/registry-5ND7GLFJ.js +2 -0
  40. package/dist/scheduler-FCXOXCN5.js +2 -0
  41. package/dist/scheduler-FCXOXCN5.js.map +1 -0
  42. package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
  43. package/dist/{usage-dashboard-2TJQBFSH.js → usage-dashboard-QX3CF6KK.js} +8 -8
  44. package/dist/usage-dashboard-QX3CF6KK.js.map +1 -0
  45. package/dist/whatsapp-state-6D6FHHD2.js +2 -0
  46. package/dist/whatsapp-state-6D6FHHD2.js.map +1 -0
  47. package/dist/workspace-setup-ZWTT42XT.js +2 -0
  48. package/dist/workspace-setup-ZWTT42XT.js.map +1 -0
  49. package/package.json +10 -1
  50. package/dist/agent-5KIMJI3F.js +0 -2
  51. package/dist/chunk-34UAFPK4.js +0 -10
  52. package/dist/chunk-34UAFPK4.js.map +0 -1
  53. package/dist/chunk-4YCH6IZV.js +0 -2
  54. package/dist/chunk-CJ45Y2IR.js +0 -12
  55. package/dist/chunk-CJ45Y2IR.js.map +0 -1
  56. package/dist/chunk-T25UEXVK.js +0 -126
  57. package/dist/chunk-T25UEXVK.js.map +0 -1
  58. package/dist/chunk-VH23CDHL.js +0 -157
  59. package/dist/chunk-VH23CDHL.js.map +0 -1
  60. package/dist/chunk-WJ6ZF6EZ.js +0 -6
  61. package/dist/chunk-WJ6ZF6EZ.js.map +0 -1
  62. package/dist/config-HAD5E4YX.js +0 -2
  63. package/dist/debug-N3B5NVJU.js +0 -2
  64. package/dist/loader-YQE6XJBT.js +0 -2
  65. package/dist/memory-extract-RGPUPMKU.js +0 -2
  66. package/dist/registry-CDWY7CLL.js +0 -2
  67. package/dist/usage-dashboard-2TJQBFSH.js.map +0 -1
  68. package/dist/workspace-setup-QQTCJY4B.js +0 -2
  69. /package/dist/{agent-5KIMJI3F.js.map → agent-LRSSVFDR.js.map} +0 -0
  70. /package/dist/{chunk-ZRYBSI5G.js.map → chunk-7MBTMCKY.js.map} +0 -0
  71. /package/dist/{chunk-D6EDMQRV.js.map → chunk-AU37OREL.js.map} +0 -0
  72. /package/dist/{chunk-4YCH6IZV.js.map → chunk-FIOB3MEX.js.map} +0 -0
  73. /package/dist/{compaction-7I3E5QUO.js.map → compaction-JBZZ7I7Y.js.map} +0 -0
  74. /package/dist/{config-HAD5E4YX.js.map → config-DCJYECL4.js.map} +0 -0
  75. /package/dist/{debug-N3B5NVJU.js.map → config-mutator-6EJHSP3Y.js.map} +0 -0
  76. /package/dist/{heal-OTGT5HHJ.js.map → debug-3MEUO2P2.js.map} +0 -0
  77. /package/dist/{loader-YQE6XJBT.js.map → heal-WKY5I4KB.js.map} +0 -0
  78. /package/dist/{memory-extract-RGPUPMKU.js.map → loader-PTCXLJEX.js.map} +0 -0
  79. /package/dist/{providers-G3WZ4RVJ.js.map → memory-extract-AD7KMRPC.js.map} +0 -0
  80. /package/dist/{registry-CDWY7CLL.js.map → providers-VUIWO3ZK.js.map} +0 -0
  81. /package/dist/{workspace-setup-QQTCJY4B.js.map → registry-5ND7GLFJ.js.map} +0 -0
  82. /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
@@ -0,0 +1,199 @@
1
+ import{f as b,i as $}from"./chunk-B4Q4X7D5.js";import{existsSync as g,readFileSync as S,writeFileSync as T,appendFileSync as R,mkdirSync as W}from"fs";import{resolve as c}from"path";import{hostname as j}from"os";$();import{existsSync as v,readFileSync as x,writeFileSync as h,copyFileSync as B}from"fs";import{resolve as y}from"path";function w(e,t={}){let n=t.configPath||y(process.cwd(),"agentx.json");if(!v(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(x(n,"utf-8")),r=e(a),i=typeof r=="string"?{summary:r}:r,s=JSON.stringify(a,null,2)+`
2
+ `,d=n+".tmp";h(d,s,"utf-8");try{JSON.parse(x(d,"utf-8"))}catch(u){throw new Error(`config did not round-trip cleanly: ${u.message}`)}let l;return t.skipBackup||(l=`${n}.bak.${Date.now()}`,B(n,l)),h(n,s,"utf-8"),t.skipReload||_().catch(()=>{}),{summary:i.summary,payload:i.payload,backupPath:l}}function k(e,t={}){let n=t.configPath||y(process.cwd(),"agentx.json");if(v(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return h(n,JSON.stringify(e,null,2)+`
3
+ `,"utf-8"),{file:n}}async function _(){try{let t=b().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function C(e=process.cwd()){let t=c(e,"agentx.json");if(!g(t))return{configExists:!1,agentCount:0,channelCount:0};try{let n=JSON.parse(S(t,"utf-8")),a=n.agents?Object.keys(n.agents).length:0,r=n.channels?Object.values(n.channels).filter(i=>i&&i.enabled).length:0;return{configExists:!0,agentCount:a,channelCount:r,nodeName:n.node?.name}}catch{return{configExists:!1,agentCount:0,channelCount:0}}}function F(e,t=process.cwd()){if(!e.nodeName?.trim())throw new Error("Team name is required.");if(!e.agent?.id?.trim())throw new Error("Agent id is required.");if(!/^[a-z0-9][a-z0-9_-]*$/.test(e.agent.id))throw new Error("Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.");if(!e.agent.name?.trim())throw new Error("Agent name is required.");let n=D(e.agent.triggerWords);if(n.length===0)throw new Error("At least one trigger word is required.");if(e.agent.tier!=="claude-code"&&e.agent.tier!=="sdk")throw new Error("AI engine must be claude-code or sdk.");let a=c(t,"agentx.json"),r=c(t,".env"),i=c(t,"agents",e.agent.id),s=C(t);W(i,{recursive:!0});let d=c(i,"CLAUDE.md");if(!g(d)){let m=e.agent.personality?.trim()||`You are ${e.agent.name}, an assistant on the ${e.nodeName} team.`;T(d,`# ${e.agent.name}
4
+
5
+ ${m}
6
+ `)}let l=e.telegram?`TG_${e.agent.id.toUpperCase()}_BOT_TOKEN`:void 0,u;if(s.configExists)u=w(o=>{if(o.node=o.node||{},o.node.name||(o.node.name=e.nodeName),o.node.id||(o.node.id=E(e.nodeName)||"team"),o.node.bind||(o.node.bind="127.0.0.1:18800"),o.node.defaultAgent=o.node.defaultAgent||e.agent.id,o.providers=o.providers||{},o.providers.claude||(o.providers.claude={apiKey:"${ANTHROPIC_API_KEY}"}),o.agents=o.agents||{},o.agents[e.agent.id])throw new Error(`Agent "${e.agent.id}" already exists.`);return o.agents[e.agent.id]=I(e,i,n),e.telegram&&(o.channels=o.channels||{},o.channels.telegram=o.channels.telegram||{enabled:!0,accounts:{},policy:{dm:"pair",group:"mention-required"}},o.channels.telegram.enabled=!0,o.channels.telegram.accounts=o.channels.telegram.accounts||{},o.channels.telegram.accounts[e.telegram.accountId]=z(e,l)),{summary:`added agent "${e.agent.id}"`+(e.telegram?" + telegram account":"")}},{configPath:a}).backupPath;else{let m=U(e,i,n,l);k(m,{configPath:a})}H(r,e,l);let p=[];return(e.anthropicApiKey||!g(c(t,".env")))&&p.push("Double-check the API keys in .env"),e.agent.tier==="claude-code"&&p.push("Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)"),p.push("Start the daemon: agentx daemon start"),p.push("Open the dashboard: http://127.0.0.1:4202"),{summary:s.configExists?`Added agent "${e.agent.id}" to your team.`:`Initialized your team with first agent "${e.agent.id}".`,nextSteps:p,backupPath:u}}function D(e){return(e||"").split(/[,\s]+/).map(t=>t.trim()).filter(Boolean).filter((t,n,a)=>a.indexOf(t)===n)}function E(e){return(e||"").toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")}function U(e,t,n,a){let i={node:{id:E(e.nodeName)||"team",name:e.nodeName,bind:"127.0.0.1:18800",defaultAgent:e.agent.id},providers:{claude:{apiKey:"${ANTHROPIC_API_KEY}"}},agents:{[e.agent.id]:I(e,t,n)},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},dashboard:{enabled:!0,port:4202,bind:"127.0.0.1",daemonUrl:"http://localhost:18800"},boards:[]};return e.telegram&&a?i.channels.telegram={enabled:!0,accounts:{[e.telegram.accountId]:z(e,a)},policy:{dm:"pair",group:"mention-required"}}:i.channels.telegram={enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},i}function I(e,t,n){let a={name:e.agent.name,workspace:t,tier:e.agent.tier,mentions:n,maxConcurrent:2,permissionMode:"default"};return e.agent.model&&(a.model=e.agent.model),e.agent.personality?.trim()&&(a.systemPrompt=e.agent.personality.trim().slice(0,400)),a}function z(e,t){return{botToken:"${"+t+"}",botUsername:e.telegram?.botUsername||"",agentBinding:e.agent.id}}function H(e,t,n){let a=g(e)?S(e,"utf-8"):"",r=[];if(t.anthropicApiKey&&!a.includes("ANTHROPIC_API_KEY=")&&r.push(`ANTHROPIC_API_KEY=${t.anthropicApiKey}`),t.telegram&&n&&!a.includes(`${n}=`)&&r.push(`${n}=${t.telegram.botToken}`),!g(e)){T(e,[...["# AgentX environment variables","# (Never commit this file \u2014 it contains secrets.)",""],...r,""].join(`
7
+ `));return}if(r.length>0){let i=a.endsWith(`
8
+ `)?"":`
9
+ `;R(e,i+r.join(`
10
+ `)+`
11
+ `)}}function se(e,t){let n=C(),a=K(n);t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(a)}async function de(e,t){let n="";await new Promise(r=>{e.on("data",i=>{n+=i}),e.on("end",()=>r())});let a;try{a=JSON.parse(n)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON"}));return}try{let r=F(a);t.writeHead(200,{"Content-Type":"application/json"}),t.end(JSON.stringify(r))}catch(r){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:r.message||"wizard failed"}))}}function K(e){let t=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",n=e.nodeName||j().replace(/\.local$/,"");return`<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1">
16
+ <title>AgentX \u2014 Setup</title>
17
+ <style>
18
+ :root{--bg:#0b0d14;--card:#151823;--border:#2a2d3a;--text:#e6e8ef;--muted:#8b8fa3;--accent:#6366f1;--green:#22c55e;--red:#ef4444}
19
+ *{box-sizing:border-box}
20
+ html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
21
+ header{padding:18px 24px;border-bottom:1px solid var(--border);background:#10131c;display:flex;align-items:center;gap:12px}
22
+ header .brand{font-weight:600;color:var(--accent);font-size:15px}
23
+ header .sub{color:var(--muted);font-weight:500}
24
+ .spacer{flex:1}
25
+ a.link{color:var(--muted);text-decoration:none;font-size:13px;padding:4px 10px;border:1px solid var(--border);border-radius:6px}
26
+ a.link:hover{color:var(--accent);border-color:var(--accent)}
27
+ main{max-width:620px;margin:0 auto;padding:32px 22px 60px}
28
+ h1{font-size:24px;margin:0 0 6px}
29
+ .lead{color:var(--muted);margin:0 0 26px}
30
+ section.step{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:20px 22px;margin:14px 0}
31
+ section.step h2{font-size:14px;font-weight:600;margin:0 0 14px;color:var(--text);display:flex;align-items:center;gap:10px}
32
+ section.step h2 .num{background:var(--accent);color:#fff;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}
33
+ label{display:block;margin:10px 0 4px;font-size:12px;color:var(--muted)}
34
+ label .hint{color:var(--muted);font-weight:400;font-size:11px;margin-left:6px}
35
+ input,textarea,select{width:100%;background:#0e1119;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px 10px;font:13px/1.4 inherit}
36
+ input:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}
37
+ textarea{resize:vertical;min-height:70px;font-family:inherit}
38
+ .row{display:flex;gap:10px}
39
+ .row > *{flex:1}
40
+ .toggle{display:flex;align-items:center;gap:8px;margin:14px 0 4px;font-size:13px;color:var(--text);cursor:pointer}
41
+ .toggle input{width:auto}
42
+ .optional{border-left:2px solid var(--border);padding-left:14px;margin-top:10px;display:none}
43
+ .optional.show{display:block}
44
+ footer.actions{display:flex;gap:10px;padding:20px 0;align-items:center}
45
+ button.primary{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer}
46
+ button.primary:hover{filter:brightness(1.1)}
47
+ button.primary:disabled{opacity:0.5;cursor:not-allowed}
48
+ button.ghost{background:transparent;color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:9px 16px;cursor:pointer}
49
+ .hint-block{font-size:11px;color:var(--muted);margin-top:6px;line-height:1.55}
50
+ .hint-block code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:11px}
51
+ #msg{margin:14px 0;padding:12px 16px;border-radius:6px;font-size:13px;display:none}
52
+ #msg.ok{display:block;background:rgba(34,197,94,0.12);color:var(--green);border:1px solid rgba(34,197,94,0.3)}
53
+ #msg.err{display:block;background:rgba(239,68,68,0.12);color:var(--red);border:1px solid rgba(239,68,68,0.3)}
54
+ #next-steps{margin-top:12px;padding-left:20px}
55
+ #next-steps li{margin:4px 0}
56
+ #next-steps code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:12px}
57
+ .config-banner{background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.3);border-radius:6px;padding:10px 14px;margin-bottom:16px;color:var(--muted);font-size:12px}
58
+ .config-banner b{color:var(--text)}
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <header>
63
+ <div class="brand">AgentX <span class="sub">\xB7 Setup</span></div>
64
+ <div class="spacer"></div>
65
+ <a href="/glossary" class="link">? Glossary</a>
66
+ <a href="/live" class="link">Skip \u2014 open dashboard</a>
67
+ </header>
68
+ <main>
69
+ <h1>${A(t)}</h1>
70
+ <p class="lead">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>
71
+
72
+ ${e.configExists?`<div class="config-banner">Existing install: <b>${e.agentCount}</b> agent(s), <b>${e.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>`:""}
73
+
74
+ <div id="msg"></div>
75
+
76
+ <form id="wizard" autocomplete="off">
77
+ <section class="step">
78
+ <h2><span class="num">1</span>Team basics</h2>
79
+ <label>Team name<span class="hint">(what you'd call this AgentX install)</span></label>
80
+ <input name="nodeName" value="${A(n)}" placeholder="My Team" required />
81
+ </section>
82
+
83
+ <section class="step">
84
+ <h2><span class="num">2</span>First agent</h2>
85
+ <div class="row">
86
+ <div>
87
+ <label>Agent name</label>
88
+ <input name="agentName" value="Assistant" placeholder="Support Bot" required />
89
+ </div>
90
+ <div>
91
+ <label>Agent id<span class="hint">(lowercase, no spaces)</span></label>
92
+ <input name="agentId" value="assistant" pattern="[a-z0-9][a-z0-9_-]*" required />
93
+ </div>
94
+ </div>
95
+ <label>Trigger words<span class="hint">(words that activate this agent \u2014 comma or space separated, e.g. <code>@support, support</code>)</span></label>
96
+ <input name="triggerWords" value="@assistant, assistant" required />
97
+ <div class="row">
98
+ <div>
99
+ <label>AI engine</label>
100
+ <select name="tier">
101
+ <option value="claude-code">Claude Code (recommended)</option>
102
+ <option value="sdk">Anthropic API (BYO key)</option>
103
+ </select>
104
+ </div>
105
+ <div>
106
+ <label>Model<span class="hint">(optional)</span></label>
107
+ <input name="model" value="claude-sonnet-4-6" placeholder="claude-sonnet-4-6" />
108
+ </div>
109
+ </div>
110
+ <label>Personality / instructions<span class="hint">(optional \u2014 what this agent does, in plain English)</span></label>
111
+ <textarea name="personality" placeholder="You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short."></textarea>
112
+ </section>
113
+
114
+ <section class="step">
115
+ <h2><span class="num">3</span>First channel</h2>
116
+ <label class="toggle"><input type="checkbox" id="enableTelegram" /> Connect Telegram now</label>
117
+ <div class="optional" id="telegramFields">
118
+ <label>Account id<span class="hint">(a label you choose \u2014 e.g. <code>support</code>)</span></label>
119
+ <input name="telegramAccountId" value="default" />
120
+ <label>Bot token<span class="hint">(from <a href="https://t.me/BotFather" target="_blank" style="color:var(--accent)">@BotFather</a>)</span></label>
121
+ <input name="telegramBotToken" placeholder="123456:ABCdef..." />
122
+ <label>Bot username<span class="hint">(optional \u2014 e.g. <code>my_support_bot</code>)</span></label>
123
+ <input name="telegramBotUsername" placeholder="my_support_bot" />
124
+ </div>
125
+ </section>
126
+
127
+ <section class="step">
128
+ <h2><span class="num">4</span>Anthropic API key</h2>
129
+ <label>API key<span class="hint">(optional if you'll paste it into <code>.env</code> manually, or if you're only using Claude Code)</span></label>
130
+ <input name="anthropicApiKey" type="password" placeholder="sk-ant-\u2026" autocomplete="off" />
131
+ <div class="hint-block">
132
+ The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it \u2014 everything stays on this machine.
133
+ </div>
134
+ </section>
135
+
136
+ <footer class="actions">
137
+ <button type="submit" class="primary" id="submitBtn">Save and continue</button>
138
+ <button type="button" class="ghost" onclick="window.location.href='/live'">Skip, I'll do it later</button>
139
+ </footer>
140
+ </form>
141
+ </main>
142
+ <script>
143
+ document.getElementById('enableTelegram').addEventListener('change', (e) => {
144
+ document.getElementById('telegramFields').classList.toggle('show', e.target.checked);
145
+ });
146
+ document.getElementById('wizard').addEventListener('submit', async (e) => {
147
+ e.preventDefault();
148
+ const f = e.target;
149
+ const msg = document.getElementById('msg');
150
+ const btn = document.getElementById('submitBtn');
151
+ msg.className = ''; msg.style.display = 'none';
152
+ btn.disabled = true; btn.textContent = 'Saving\u2026';
153
+ const telegramEnabled = document.getElementById('enableTelegram').checked;
154
+ const payload = {
155
+ nodeName: f.nodeName.value.trim(),
156
+ agent: {
157
+ id: f.agentId.value.trim(),
158
+ name: f.agentName.value.trim(),
159
+ triggerWords: f.triggerWords.value.trim(),
160
+ tier: f.tier.value,
161
+ model: f.model.value.trim() || undefined,
162
+ personality: f.personality.value.trim() || undefined,
163
+ },
164
+ anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,
165
+ };
166
+ if (telegramEnabled) {
167
+ payload.telegram = {
168
+ accountId: f.telegramAccountId.value.trim() || 'default',
169
+ botToken: f.telegramBotToken.value.trim(),
170
+ botUsername: f.telegramBotUsername.value.trim() || undefined,
171
+ };
172
+ if (!payload.telegram.botToken) {
173
+ msg.className = 'err'; msg.textContent = 'Bot token is required when Telegram is enabled.';
174
+ btn.disabled = false; btn.textContent = 'Save and continue'; return;
175
+ }
176
+ }
177
+ try {
178
+ const r = await fetch('/api/setup', {
179
+ method: 'POST',
180
+ headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },
181
+ body: JSON.stringify(payload),
182
+ });
183
+ const data = await r.json();
184
+ if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));
185
+ const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\`([^\`]+)\`/g, '<code>$1</code>') + '</li>').join('');
186
+ msg.className = 'ok';
187
+ msg.innerHTML = '<b>' + data.summary + '</b><ol id="next-steps">' + steps + '</ol>';
188
+ btn.textContent = 'Done';
189
+ btn.disabled = true;
190
+ } catch (err) {
191
+ msg.className = 'err'; msg.textContent = err.message;
192
+ btn.disabled = false; btn.textContent = 'Save and continue';
193
+ }
194
+ });
195
+ </script>
196
+ </body>
197
+ </html>`}function A(e){return String(e||"").replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}import{createHash as M,randomBytes as N}from"crypto";import{existsSync as q,mkdirSync as J,readFileSync as L,writeFileSync as Y}from"fs";import{resolve as X,dirname as G}from"path";var f="agx_live_",V=".agentx/tokens.json",P=class{file;constructor(t=process.cwd(),n=V){this.file=X(t,n)}load(){if(!q(this.file))return[];try{return JSON.parse(L(this.file,"utf-8"))}catch{return[]}}save(t){J(G(this.file),{recursive:!0}),Y(this.file,JSON.stringify(t,null,2)+`
198
+ `,"utf-8")}create(t){if(!t.name?.trim())throw new Error("Token name is required.");if(!Array.isArray(t.scopes)||t.scopes.length===0)throw new Error("At least one scope is required.");for(let s of t.scopes)if(!Q(s))throw new Error(`Invalid scope: ${s}`);let n=N(32).toString("hex"),a=f+n,r={id:"tok_"+N(4).toString("hex"),name:t.name.trim(),prefix:f+n.slice(0,4),hash:O(a),scopes:[...t.scopes],createdAt:new Date().toISOString(),expiresAt:t.expiresInDays?new Date(Date.now()+t.expiresInDays*86400*1e3).toISOString():void 0},i=this.load();return i.push(r),this.save(i),{token:a,record:r}}list(){return this.load()}revoke(t){let n=this.load(),a=n.find(r=>r.id===t);return a?(a.revokedAt=new Date().toISOString(),this.save(n),a):null}verify(t){if(!t||!t.startsWith(f))return null;let n=O(t),a=this.load(),r=a.find(d=>d.hash===n);if(!r||r.revokedAt||r.expiresAt&&Date.parse(r.expiresAt)<Date.now())return null;let i=Date.now(),s=r.lastUsedAt?Date.parse(r.lastUsedAt):0;if(i-s>6e4){r.lastUsedAt=new Date(i).toISOString();try{this.save(a)}catch{}}return r}};function ue(e,t){return!!(e.scopes.includes(t)||t.startsWith("agent:")&&e.scopes.includes("agent:*")||t==="dashboard:read"&&e.scopes.includes("dashboard:write"))}function Q(e){return e==="dashboard:read"||e==="dashboard:write"||e==="agent:*"||/^agent:[a-z0-9][a-z0-9_-]*$/.test(e)||e==="mesh:peer"}function O(e){return"sha256:"+M("sha256").update(e).digest("hex")}function me(e){let t=e.headers.authorization,n=Array.isArray(t)?t[0]:t;if(n&&n.toLowerCase().startsWith("bearer "))return n.slice(7).trim();if(e.url)try{let r=new URL(e.url,"http://localhost").searchParams.get("token");if(r)return r}catch{}return null}export{w as a,C as b,se as c,de as d,P as e,ue as f,me as g};
199
+ //# sourceMappingURL=chunk-3XULOWYB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/daemon/setup-wizard.ts","../src/daemon/config-mutate.ts","../src/daemon/token-store.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { hostname } from \"os\"\nimport type { IncomingMessage, ServerResponse } from \"http\"\nimport { mutateAgentxConfig, writeAgentxConfig } from \"./config-mutate\"\n\n// --- /setup wizard: form-driven first-run experience for non-technical operators ---\n//\n// This is the web equivalent of `agentx init` + `agentx agent add` + `agentx\n// channel add`, collapsed into one page. It writes agentx.json and scaffolds\n// the first agent's workspace so a business operator can go from a blank\n// machine to a working AgentX install without editing JSON.\n//\n// The route lives on the board-dashboard server (port 4202) because that's the\n// surface operators actually open. It talks to the loopback daemon for the\n// post-install restart; the wizard itself never requires a running daemon.\n\nexport interface WizardPayload {\n nodeName: string\n agent: {\n id: string\n name: string\n triggerWords: string // comma/space separated; we normalise on the server\n tier: \"claude-code\" | \"sdk\"\n model?: string\n personality?: string // short free text → CLAUDE.md\n }\n telegram?: {\n accountId: string\n botToken: string\n botUsername?: string\n }\n anthropicApiKey?: string\n}\n\n/**\n * Return the current state of the working directory so the wizard can choose\n * between \"fresh install\" and \"add another\" flows.\n */\nexport function wizardState(baseDir: string = process.cwd()): {\n configExists: boolean\n agentCount: number\n channelCount: number\n nodeName?: string\n} {\n const file = resolve(baseDir, \"agentx.json\")\n if (!existsSync(file)) return { configExists: false, agentCount: 0, channelCount: 0 }\n try {\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const agents = cfg.agents ? Object.keys(cfg.agents).length : 0\n const channels = cfg.channels\n ? Object.values(cfg.channels).filter((c: any) => c && c.enabled).length\n : 0\n return { configExists: true, agentCount: agents, channelCount: channels, nodeName: cfg.node?.name }\n } catch {\n return { configExists: false, agentCount: 0, channelCount: 0 }\n }\n}\n\n/**\n * Execute the wizard submission. Returns a short human-readable summary of\n * what was written. Throws on validation error — the caller surfaces the\n * message to the user.\n */\nexport function runWizard(payload: WizardPayload, baseDir: string = process.cwd()): {\n summary: string\n nextSteps: string[]\n backupPath?: string\n} {\n // --- validate ---\n if (!payload.nodeName?.trim()) throw new Error(\"Team name is required.\")\n if (!payload.agent?.id?.trim()) throw new Error(\"Agent id is required.\")\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(payload.agent.id)) {\n throw new Error(\"Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n if (!payload.agent.name?.trim()) throw new Error(\"Agent name is required.\")\n const mentions = splitMentions(payload.agent.triggerWords)\n if (mentions.length === 0) throw new Error(\"At least one trigger word is required.\")\n if (payload.agent.tier !== \"claude-code\" && payload.agent.tier !== \"sdk\") {\n throw new Error(\"AI engine must be claude-code or sdk.\")\n }\n\n const configPath = resolve(baseDir, \"agentx.json\")\n const envPath = resolve(baseDir, \".env\")\n const workspaceDir = resolve(baseDir, \"agents\", payload.agent.id)\n const state = wizardState(baseDir)\n\n // --- scaffold workspace ---\n mkdirSync(workspaceDir, { recursive: true })\n const claudeMd = resolve(workspaceDir, \"CLAUDE.md\")\n if (!existsSync(claudeMd)) {\n const personality = payload.agent.personality?.trim() || `You are ${payload.agent.name}, an assistant on the ${payload.nodeName} team.`\n writeFileSync(claudeMd, `# ${payload.agent.name}\\n\\n${personality}\\n`)\n }\n\n // --- build / mutate agentx.json ---\n const telegramTokenRef = payload.telegram\n ? `TG_${payload.agent.id.toUpperCase()}_BOT_TOKEN`\n : undefined\n\n let backupPath: string | undefined\n if (!state.configExists) {\n const cfg = buildFreshConfig(payload, workspaceDir, mentions, telegramTokenRef)\n writeAgentxConfig(cfg, { configPath })\n } else {\n const result = mutateAgentxConfig((cfg) => {\n cfg.node = cfg.node || {}\n if (!cfg.node.name) cfg.node.name = payload.nodeName\n if (!cfg.node.id) cfg.node.id = slugify(payload.nodeName) || \"team\"\n if (!cfg.node.bind) cfg.node.bind = \"127.0.0.1:18800\"\n cfg.node.defaultAgent = cfg.node.defaultAgent || payload.agent.id\n\n cfg.providers = cfg.providers || {}\n if (!cfg.providers.claude) cfg.providers.claude = { apiKey: \"${ANTHROPIC_API_KEY}\" }\n\n cfg.agents = cfg.agents || {}\n if (cfg.agents[payload.agent.id]) throw new Error(`Agent \"${payload.agent.id}\" already exists.`)\n cfg.agents[payload.agent.id] = agentBlock(payload, workspaceDir, mentions)\n\n if (payload.telegram) {\n cfg.channels = cfg.channels || {}\n cfg.channels.telegram = cfg.channels.telegram || { enabled: true, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n cfg.channels.telegram.enabled = true\n cfg.channels.telegram.accounts = cfg.channels.telegram.accounts || {}\n cfg.channels.telegram.accounts[payload.telegram.accountId] = telegramAccountBlock(payload, telegramTokenRef!)\n }\n\n return { summary: `added agent \"${payload.agent.id}\"` + (payload.telegram ? \" + telegram account\" : \"\") }\n }, { configPath })\n backupPath = result.backupPath\n }\n\n // --- write / append .env ---\n ensureEnvEntries(envPath, payload, telegramTokenRef)\n\n const nextSteps: string[] = []\n if (payload.anthropicApiKey || !existsSync(resolve(baseDir, \".env\"))) {\n nextSteps.push(\"Double-check the API keys in .env\")\n }\n if (payload.agent.tier === \"claude-code\") {\n nextSteps.push(\"Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)\")\n }\n nextSteps.push(\"Start the daemon: agentx daemon start\")\n nextSteps.push(\"Open the dashboard: http://127.0.0.1:4202\")\n\n return {\n summary: state.configExists\n ? `Added agent \"${payload.agent.id}\" to your team.`\n : `Initialized your team with first agent \"${payload.agent.id}\".`,\n nextSteps,\n backupPath,\n }\n}\n\nfunction splitMentions(s: string): string[] {\n return (s || \"\")\n .split(/[,\\s]+/)\n .map((m) => m.trim())\n .filter(Boolean)\n // Normalise: ensure @-prefix entries stay, plain ones are kept as-is.\n .filter((m, i, a) => a.indexOf(m) === i)\n}\n\nfunction slugify(s: string): string {\n return (s || \"\").toLowerCase().replace(/[^a-z0-9-]+/g, \"-\").replace(/^-+|-+$/g, \"\")\n}\n\nfunction buildFreshConfig(payload: WizardPayload, workspaceDir: string, mentions: string[], telegramTokenRef?: string) {\n const id = slugify(payload.nodeName) || \"team\"\n const cfg: any = {\n node: { id, name: payload.nodeName, bind: \"127.0.0.1:18800\", defaultAgent: payload.agent.id },\n providers: { claude: { apiKey: \"${ANTHROPIC_API_KEY}\" } },\n agents: { [payload.agent.id]: agentBlock(payload, workspaceDir, mentions) },\n channels: {},\n crons: {},\n mesh: { enabled: false, peers: [], discovery: \"static\", healthCheck: { interval: 60, timeout: 10 } },\n dashboard: { enabled: true, port: 4202, bind: \"127.0.0.1\", daemonUrl: \"http://localhost:18800\" },\n boards: [],\n }\n if (payload.telegram && telegramTokenRef) {\n cfg.channels.telegram = {\n enabled: true,\n accounts: { [payload.telegram.accountId]: telegramAccountBlock(payload, telegramTokenRef) },\n policy: { dm: \"pair\", group: \"mention-required\" },\n }\n } else {\n cfg.channels.telegram = { enabled: false, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n }\n return cfg\n}\n\nfunction agentBlock(payload: WizardPayload, workspaceDir: string, mentions: string[]) {\n const block: any = {\n name: payload.agent.name,\n workspace: workspaceDir,\n tier: payload.agent.tier,\n mentions,\n maxConcurrent: 2,\n permissionMode: \"default\",\n }\n if (payload.agent.model) block.model = payload.agent.model\n if (payload.agent.personality?.trim()) block.systemPrompt = payload.agent.personality.trim().slice(0, 400)\n return block\n}\n\nfunction telegramAccountBlock(payload: WizardPayload, tokenRef: string) {\n return {\n botToken: \"${\" + tokenRef + \"}\",\n botUsername: payload.telegram?.botUsername || \"\",\n agentBinding: payload.agent.id,\n }\n}\n\nfunction ensureEnvEntries(envPath: string, payload: WizardPayload, telegramTokenRef?: string): void {\n const existing = existsSync(envPath) ? readFileSync(envPath, \"utf-8\") : \"\"\n const lines: string[] = []\n\n if (payload.anthropicApiKey && !existing.includes(\"ANTHROPIC_API_KEY=\")) {\n lines.push(`ANTHROPIC_API_KEY=${payload.anthropicApiKey}`)\n }\n if (payload.telegram && telegramTokenRef && !existing.includes(`${telegramTokenRef}=`)) {\n lines.push(`${telegramTokenRef}=${payload.telegram.botToken}`)\n }\n\n if (!existsSync(envPath)) {\n const header = [\n \"# AgentX environment variables\",\n \"# (Never commit this file — it contains secrets.)\",\n \"\",\n ]\n writeFileSync(envPath, [...header, ...lines, \"\"].join(\"\\n\"))\n return\n }\n if (lines.length > 0) {\n const sep = existing.endsWith(\"\\n\") ? \"\" : \"\\n\"\n appendFileSync(envPath, sep + lines.join(\"\\n\") + \"\\n\")\n }\n}\n\n// --- HTTP handlers used by board-dashboard.ts ---\n\nexport function handleWizardGet(_req: IncomingMessage, res: ServerResponse): void {\n const state = wizardState()\n const html = renderWizardHtml(state)\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(html)\n}\n\nexport async function handleWizardPost(req: IncomingMessage, res: ServerResponse): Promise<void> {\n let raw = \"\"\n await new Promise<void>((ok) => { req.on(\"data\", (c) => { raw += c }); req.on(\"end\", () => ok()) })\n let payload: WizardPayload\n try { payload = JSON.parse(raw) }\n catch { res.writeHead(400, { \"Content-Type\": \"application/json\" }); res.end(JSON.stringify({ error: \"Invalid JSON\" })); return }\n try {\n const result = runWizard(payload)\n res.writeHead(200, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(result))\n } catch (e: any) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify({ error: e.message || \"wizard failed\" }))\n }\n}\n\nfunction renderWizardHtml(state: ReturnType<typeof wizardState>): string {\n const heading = state.configExists\n ? (state.agentCount === 0 ? \"Add your first agent\" : \"Add another agent\")\n : \"Set up your team\"\n const defaultNode = state.nodeName || hostname().replace(/\\.local$/, \"\")\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>AgentX — Setup</title>\n<style>\n:root{--bg:#0b0d14;--card:#151823;--border:#2a2d3a;--text:#e6e8ef;--muted:#8b8fa3;--accent:#6366f1;--green:#22c55e;--red:#ef4444}\n*{box-sizing:border-box}\nhtml,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif}\nheader{padding:18px 24px;border-bottom:1px solid var(--border);background:#10131c;display:flex;align-items:center;gap:12px}\nheader .brand{font-weight:600;color:var(--accent);font-size:15px}\nheader .sub{color:var(--muted);font-weight:500}\n.spacer{flex:1}\na.link{color:var(--muted);text-decoration:none;font-size:13px;padding:4px 10px;border:1px solid var(--border);border-radius:6px}\na.link:hover{color:var(--accent);border-color:var(--accent)}\nmain{max-width:620px;margin:0 auto;padding:32px 22px 60px}\nh1{font-size:24px;margin:0 0 6px}\n.lead{color:var(--muted);margin:0 0 26px}\nsection.step{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:20px 22px;margin:14px 0}\nsection.step h2{font-size:14px;font-weight:600;margin:0 0 14px;color:var(--text);display:flex;align-items:center;gap:10px}\nsection.step h2 .num{background:var(--accent);color:#fff;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}\nlabel{display:block;margin:10px 0 4px;font-size:12px;color:var(--muted)}\nlabel .hint{color:var(--muted);font-weight:400;font-size:11px;margin-left:6px}\ninput,textarea,select{width:100%;background:#0e1119;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px 10px;font:13px/1.4 inherit}\ninput:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}\ntextarea{resize:vertical;min-height:70px;font-family:inherit}\n.row{display:flex;gap:10px}\n.row > *{flex:1}\n.toggle{display:flex;align-items:center;gap:8px;margin:14px 0 4px;font-size:13px;color:var(--text);cursor:pointer}\n.toggle input{width:auto}\n.optional{border-left:2px solid var(--border);padding-left:14px;margin-top:10px;display:none}\n.optional.show{display:block}\nfooter.actions{display:flex;gap:10px;padding:20px 0;align-items:center}\nbutton.primary{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer}\nbutton.primary:hover{filter:brightness(1.1)}\nbutton.primary:disabled{opacity:0.5;cursor:not-allowed}\nbutton.ghost{background:transparent;color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:9px 16px;cursor:pointer}\n.hint-block{font-size:11px;color:var(--muted);margin-top:6px;line-height:1.55}\n.hint-block code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:11px}\n#msg{margin:14px 0;padding:12px 16px;border-radius:6px;font-size:13px;display:none}\n#msg.ok{display:block;background:rgba(34,197,94,0.12);color:var(--green);border:1px solid rgba(34,197,94,0.3)}\n#msg.err{display:block;background:rgba(239,68,68,0.12);color:var(--red);border:1px solid rgba(239,68,68,0.3)}\n#next-steps{margin-top:12px;padding-left:20px}\n#next-steps li{margin:4px 0}\n#next-steps code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:12px}\n.config-banner{background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.3);border-radius:6px;padding:10px 14px;margin-bottom:16px;color:var(--muted);font-size:12px}\n.config-banner b{color:var(--text)}\n</style>\n</head>\n<body>\n<header>\n <div class=\"brand\">AgentX <span class=\"sub\">· Setup</span></div>\n <div class=\"spacer\"></div>\n <a href=\"/glossary\" class=\"link\">? Glossary</a>\n <a href=\"/live\" class=\"link\">Skip — open dashboard</a>\n</header>\n<main>\n <h1>${escapeHtml(heading)}</h1>\n <p class=\"lead\">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>\n\n ${state.configExists ? `<div class=\"config-banner\">Existing install: <b>${state.agentCount}</b> agent(s), <b>${state.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>` : \"\"}\n\n <div id=\"msg\"></div>\n\n <form id=\"wizard\" autocomplete=\"off\">\n <section class=\"step\">\n <h2><span class=\"num\">1</span>Team basics</h2>\n <label>Team name<span class=\"hint\">(what you'd call this AgentX install)</span></label>\n <input name=\"nodeName\" value=\"${escapeHtml(defaultNode)}\" placeholder=\"My Team\" required />\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">2</span>First agent</h2>\n <div class=\"row\">\n <div>\n <label>Agent name</label>\n <input name=\"agentName\" value=\"Assistant\" placeholder=\"Support Bot\" required />\n </div>\n <div>\n <label>Agent id<span class=\"hint\">(lowercase, no spaces)</span></label>\n <input name=\"agentId\" value=\"assistant\" pattern=\"[a-z0-9][a-z0-9_-]*\" required />\n </div>\n </div>\n <label>Trigger words<span class=\"hint\">(words that activate this agent — comma or space separated, e.g. <code>@support, support</code>)</span></label>\n <input name=\"triggerWords\" value=\"@assistant, assistant\" required />\n <div class=\"row\">\n <div>\n <label>AI engine</label>\n <select name=\"tier\">\n <option value=\"claude-code\">Claude Code (recommended)</option>\n <option value=\"sdk\">Anthropic API (BYO key)</option>\n </select>\n </div>\n <div>\n <label>Model<span class=\"hint\">(optional)</span></label>\n <input name=\"model\" value=\"claude-sonnet-4-6\" placeholder=\"claude-sonnet-4-6\" />\n </div>\n </div>\n <label>Personality / instructions<span class=\"hint\">(optional — what this agent does, in plain English)</span></label>\n <textarea name=\"personality\" placeholder=\"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short.\"></textarea>\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">3</span>First channel</h2>\n <label class=\"toggle\"><input type=\"checkbox\" id=\"enableTelegram\" /> Connect Telegram now</label>\n <div class=\"optional\" id=\"telegramFields\">\n <label>Account id<span class=\"hint\">(a label you choose — e.g. <code>support</code>)</span></label>\n <input name=\"telegramAccountId\" value=\"default\" />\n <label>Bot token<span class=\"hint\">(from <a href=\"https://t.me/BotFather\" target=\"_blank\" style=\"color:var(--accent)\">@BotFather</a>)</span></label>\n <input name=\"telegramBotToken\" placeholder=\"123456:ABCdef...\" />\n <label>Bot username<span class=\"hint\">(optional — e.g. <code>my_support_bot</code>)</span></label>\n <input name=\"telegramBotUsername\" placeholder=\"my_support_bot\" />\n </div>\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">4</span>Anthropic API key</h2>\n <label>API key<span class=\"hint\">(optional if you'll paste it into <code>.env</code> manually, or if you're only using Claude Code)</span></label>\n <input name=\"anthropicApiKey\" type=\"password\" placeholder=\"sk-ant-…\" autocomplete=\"off\" />\n <div class=\"hint-block\">\n The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it — everything stays on this machine.\n </div>\n </section>\n\n <footer class=\"actions\">\n <button type=\"submit\" class=\"primary\" id=\"submitBtn\">Save and continue</button>\n <button type=\"button\" class=\"ghost\" onclick=\"window.location.href='/live'\">Skip, I'll do it later</button>\n </footer>\n </form>\n</main>\n<script>\ndocument.getElementById('enableTelegram').addEventListener('change', (e) => {\n document.getElementById('telegramFields').classList.toggle('show', e.target.checked);\n});\ndocument.getElementById('wizard').addEventListener('submit', async (e) => {\n e.preventDefault();\n const f = e.target;\n const msg = document.getElementById('msg');\n const btn = document.getElementById('submitBtn');\n msg.className = ''; msg.style.display = 'none';\n btn.disabled = true; btn.textContent = 'Saving…';\n const telegramEnabled = document.getElementById('enableTelegram').checked;\n const payload = {\n nodeName: f.nodeName.value.trim(),\n agent: {\n id: f.agentId.value.trim(),\n name: f.agentName.value.trim(),\n triggerWords: f.triggerWords.value.trim(),\n tier: f.tier.value,\n model: f.model.value.trim() || undefined,\n personality: f.personality.value.trim() || undefined,\n },\n anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,\n };\n if (telegramEnabled) {\n payload.telegram = {\n accountId: f.telegramAccountId.value.trim() || 'default',\n botToken: f.telegramBotToken.value.trim(),\n botUsername: f.telegramBotUsername.value.trim() || undefined,\n };\n if (!payload.telegram.botToken) {\n msg.className = 'err'; msg.textContent = 'Bot token is required when Telegram is enabled.';\n btn.disabled = false; btn.textContent = 'Save and continue'; return;\n }\n }\n try {\n const r = await fetch('/api/setup', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },\n body: JSON.stringify(payload),\n });\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\\`([^\\`]+)\\`/g, '<code>$1</code>') + '</li>').join('');\n msg.className = 'ok';\n msg.innerHTML = '<b>' + data.summary + '</b><ol id=\"next-steps\">' + steps + '</ol>';\n btn.textContent = 'Done';\n btn.disabled = true;\n } catch (err) {\n msg.className = 'err'; msg.textContent = err.message;\n btn.disabled = false; btn.textContent = 'Save and continue';\n }\n});\n</script>\n</body>\n</html>`\n}\n\nfunction escapeHtml(s: string): string {\n return String(s || \"\").replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c] as string))\n}\n","import { existsSync, readFileSync, writeFileSync, copyFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { loadDaemonConfig } from \"./config\"\n\n// --- Shared helpers for safe agentx.json mutation ---\n//\n// Multiple CLI commands and the dashboard admin UI all need to read → tweak →\n// write agentx.json without corrupting it on failure. This module owns the\n// atomic-write + backup + best-effort-reload pattern so every caller gets the\n// same guarantees.\n\nexport interface MutateOptions {\n /** Absolute path to the target agentx.json. Defaults to ./agentx.json. */\n configPath?: string\n /** When true, don't write a timestamped backup. Use for operations that are\n * intrinsically reversible (or when the caller already took a snapshot). */\n skipBackup?: boolean\n /** When true, don't POST /reload after writing. Handy in tests. */\n skipReload?: boolean\n}\n\nexport interface MutateResult<T = void> {\n summary: string\n payload?: T\n}\n\n/**\n * Read agentx.json, let the mutator tweak it in-place, then atomically write\n * the result back to disk. The mutator returns a short human-readable summary\n * of what changed and may optionally return a payload for the caller to relay.\n *\n * If the mutator throws, nothing is written. If writing succeeds but the file\n * doesn't round-trip as JSON, we restore from backup and rethrow.\n */\nexport function mutateAgentxConfig<T = void>(\n mutator: (cfg: any) => MutateResult<T> | string,\n opts: MutateOptions = {},\n): { summary: string; payload?: T; backupPath?: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (!existsSync(file)) throw new Error(`agentx.json not found at ${file}`)\n\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const raw = mutator(cfg)\n const result: MutateResult<T> = typeof raw === \"string\" ? { summary: raw } : raw\n\n const json = JSON.stringify(cfg, null, 2) + \"\\n\"\n const tmp = file + \".tmp\"\n writeFileSync(tmp, json, \"utf-8\")\n try {\n JSON.parse(readFileSync(tmp, \"utf-8\"))\n } catch (e: any) {\n throw new Error(`config did not round-trip cleanly: ${e.message}`)\n }\n\n let backupPath: string | undefined\n if (!opts.skipBackup) {\n backupPath = `${file}.bak.${Date.now()}`\n copyFileSync(file, backupPath)\n }\n writeFileSync(file, json, \"utf-8\")\n\n if (!opts.skipReload) triggerLocalReload().catch(() => { /* daemon may be off */ })\n\n return { summary: result.summary, payload: result.payload, backupPath }\n}\n\n/**\n * Write a fresh agentx.json from scratch. Used by the setup wizard on a truly\n * empty working directory. Fails if a file already exists (callers must pass\n * the overwrite flag explicitly — we don't want to silently clobber an\n * operator's hand-written config).\n */\nexport function writeAgentxConfig(\n cfg: unknown,\n opts: { configPath?: string; overwrite?: boolean } = {},\n): { file: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (existsSync(file) && !opts.overwrite) {\n throw new Error(`agentx.json already exists at ${file}. Pass overwrite=true to replace it.`)\n }\n writeFileSync(file, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\")\n return { file }\n}\n\n/**\n * POST to the primary daemon's /reload endpoint so it picks up config changes\n * without a full restart. Silently no-ops if the daemon isn't running.\n */\nasync function triggerLocalReload(): Promise<void> {\n try {\n const cfg = loadDaemonConfig()\n const url = cfg.dashboard.daemonUrl?.replace(/\\/+$/, \"\") || \"http://127.0.0.1:18800\"\n await fetch(`${url}/reload`, { method: \"POST\" }).catch(() => null)\n } catch { /* config didn't load — leave it */ }\n}\n","import { createHash, randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- Scoped API tokens ---\n//\n// Tokens let external services (other agentx instances, webhook senders, custom\n// apps) talk to this daemon with explicit scopes instead of sharing a single\n// all-powerful dashboard token.\n//\n// We only ever store the sha256 of each token + its printable prefix. The full\n// token is shown exactly once, at creation time, and the operator copies it.\n// Format: agx_live_<hex(32)> — prefix lets secret-scanners catch leaks.\n//\n// Scopes (string equality; \"agent:*\" grants any agent):\n// dashboard:read — /api/live, /api/agents, /api/task/history\n// dashboard:write — /api/admin/*, config mutation endpoints\n// agent:<id> — /api/public/agents/<id>/messages\n// agent:* — any agent\n// mesh:peer — cross-node mesh (forward-compatible — not enforced yet)\n\nexport const TOKEN_PREFIX = \"agx_live_\"\nconst DEFAULT_FILE = \".agentx/tokens.json\"\n\nexport interface TokenRecord {\n id: string // short public id (shown in lists)\n name: string\n prefix: string // full printable prefix (TOKEN_PREFIX + first-4 chars of body)\n hash: string // sha256 of the full token\n scopes: string[]\n createdAt: string\n expiresAt?: string\n revokedAt?: string\n lastUsedAt?: string\n}\n\nexport class TokenStore {\n private file: string\n\n constructor(baseDir: string = process.cwd(), fileRel: string = DEFAULT_FILE) {\n this.file = resolve(baseDir, fileRel)\n }\n\n private load(): TokenRecord[] {\n if (!existsSync(this.file)) return []\n try { return JSON.parse(readFileSync(this.file, \"utf-8\")) as TokenRecord[] } catch { return [] }\n }\n\n private save(records: TokenRecord[]): void {\n mkdirSync(dirname(this.file), { recursive: true })\n writeFileSync(this.file, JSON.stringify(records, null, 2) + \"\\n\", \"utf-8\")\n }\n\n /**\n * Mint a new token. Returns the full token string AND the record.\n * The full token is the only place the secret is ever exposed — the caller\n * must surface it to the operator immediately; after this call the store only\n * knows the hash.\n */\n create(input: {\n name: string\n scopes: string[]\n expiresInDays?: number\n }): { token: string; record: TokenRecord } {\n if (!input.name?.trim()) throw new Error(\"Token name is required.\")\n if (!Array.isArray(input.scopes) || input.scopes.length === 0) {\n throw new Error(\"At least one scope is required.\")\n }\n for (const s of input.scopes) {\n if (!isValidScope(s)) throw new Error(`Invalid scope: ${s}`)\n }\n const body = randomBytes(32).toString(\"hex\")\n const token = TOKEN_PREFIX + body\n const record: TokenRecord = {\n id: \"tok_\" + randomBytes(4).toString(\"hex\"),\n name: input.name.trim(),\n prefix: TOKEN_PREFIX + body.slice(0, 4),\n hash: hashToken(token),\n scopes: [...input.scopes],\n createdAt: new Date().toISOString(),\n expiresAt: input.expiresInDays\n ? new Date(Date.now() + input.expiresInDays * 86400 * 1000).toISOString()\n : undefined,\n }\n const records = this.load()\n records.push(record)\n this.save(records)\n return { token, record }\n }\n\n list(): TokenRecord[] {\n return this.load()\n }\n\n revoke(id: string): TokenRecord | null {\n const records = this.load()\n const rec = records.find((r) => r.id === id)\n if (!rec) return null\n rec.revokedAt = new Date().toISOString()\n this.save(records)\n return rec\n }\n\n /**\n * Look up a token string. Returns the record on success, null on unknown /\n * revoked / expired. Updates lastUsedAt opportunistically (write-on-success).\n */\n verify(token: string): TokenRecord | null {\n if (!token || !token.startsWith(TOKEN_PREFIX)) return null\n const hash = hashToken(token)\n const records = this.load()\n const rec = records.find((r) => r.hash === hash)\n if (!rec) return null\n if (rec.revokedAt) return null\n if (rec.expiresAt && Date.parse(rec.expiresAt) < Date.now()) return null\n // Throttle lastUsedAt updates to at most once per minute to avoid disk churn.\n const now = Date.now()\n const last = rec.lastUsedAt ? Date.parse(rec.lastUsedAt) : 0\n if (now - last > 60_000) {\n rec.lastUsedAt = new Date(now).toISOString()\n try { this.save(records) } catch { /* best-effort */ }\n }\n return rec\n }\n}\n\n/**\n * Check whether a record has a given required scope. \"agent:<id>\" required\n * matches either itself or \"agent:*\". \"dashboard:write\" implicitly grants\n * \"dashboard:read\" for convenience.\n */\nexport function recordHasScope(record: TokenRecord, required: string): boolean {\n if (record.scopes.includes(required)) return true\n if (required.startsWith(\"agent:\") && record.scopes.includes(\"agent:*\")) return true\n if (required === \"dashboard:read\" && record.scopes.includes(\"dashboard:write\")) return true\n return false\n}\n\nfunction isValidScope(s: string): boolean {\n return (\n s === \"dashboard:read\" ||\n s === \"dashboard:write\" ||\n s === \"agent:*\" ||\n /^agent:[a-z0-9][a-z0-9_-]*$/.test(s) ||\n s === \"mesh:peer\"\n )\n}\n\nexport function hashToken(token: string): string {\n return \"sha256:\" + createHash(\"sha256\").update(token).digest(\"hex\")\n}\n\n/**\n * Extract a bearer token from the request headers. Falls back to the\n * `?token=` query param so EventSource (which can't set headers in most\n * browsers) can still authenticate.\n */\nexport function extractToken(req: { headers: Record<string, string | string[] | undefined>; url?: string }): string | null {\n const auth = req.headers[\"authorization\"]\n const authHeader = Array.isArray(auth) ? auth[0] : auth\n if (authHeader && authHeader.toLowerCase().startsWith(\"bearer \")) {\n return authHeader.slice(7).trim()\n }\n if (req.url) {\n try {\n const u = new URL(req.url, \"http://localhost\")\n const tok = u.searchParams.get(\"token\")\n if (tok) return tok\n } catch { /* */ }\n }\n return null\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","appendFileSync","mkdirSync","resolve","hostname","init_config","existsSync","readFileSync","writeFileSync","copyFileSync","resolve","mutateAgentxConfig","mutator","opts","file","cfg","raw","result","json","tmp","e","backupPath","triggerLocalReload","writeAgentxConfig","url","loadDaemonConfig","wizardState","baseDir","file","resolve","existsSync","cfg","readFileSync","agents","channels","c","runWizard","payload","mentions","splitMentions","configPath","envPath","workspaceDir","state","mkdirSync","claudeMd","personality","writeFileSync","telegramTokenRef","backupPath","mutateAgentxConfig","slugify","agentBlock","telegramAccountBlock","buildFreshConfig","writeAgentxConfig","ensureEnvEntries","nextSteps","s","m","i","block","tokenRef","existing","lines","sep","appendFileSync","handleWizardGet","_req","res","html","renderWizardHtml","handleWizardPost","req","raw","ok","result","e","heading","defaultNode","hostname","escapeHtml","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","isValidScope","body","token","record","hashToken","id","rec","hash","r","now","last","recordHasScope","required","s","extractToken","req","auth","authHeader","tok"]}
@@ -0,0 +1,7 @@
1
+ import{readFileSync as p,writeFileSync as k,existsSync as u,readdirSync as _,statSync as j}from"fs";import{resolve as f,join as v}from"path";var h=/^(\s*)-\s*\[( |x|X)\]\s*(.*)$/,g=/@([a-z0-9_-]+)/i,w=/\[time:\s*([0-9hm\s]+)\]/i;function U(d){let e=d.match(/(\d+)\s*h/i),s=d.match(/(\d+)\s*m/i);return(e?parseInt(e[1],10)*3600:0)+(s?parseInt(s[1],10)*60:0)}var b=class{constructor(e){this.path=e}type="backlog";capabilities={listAll:!1,create:!1,transition:!1,labels:!1,search:!1};read(){let e=f(process.cwd(),this.path);return u(e)?p(e,"utf-8").split(`
2
+ `):[]}write(e){let s=f(process.cwd(),this.path);k(s,e.join(`
3
+ `))}async listOpen(e){let s=this.read(),n=[];for(let t=0;t<s.length;t++){let i=s[t].match(h);if(!i||i[2].toLowerCase()==="x")continue;let r=i[3],l=r.match(g);if(l&&l[1].toLowerCase()!==e.toLowerCase())continue;let a=r.match(w),c=r.replace(g,"").replace(w,"").trim();n.push({id:`backlog:${t}`,title:c||`Task at line ${t+1}`,assignee:l?l[1]:void 0,estimatedSeconds:a?U(a[1]):void 0,priority:t})}return n}async claim(e,s){}async report(e,s){if(!e.startsWith("backlog:"))throw new Error(`not a backlog id: ${e}`);let n=parseInt(e.slice(8),10),t=this.read(),i=t[n];if(!i)throw new Error(`backlog line ${n} missing`);if(!i.match(h))throw new Error(`backlog line ${n} not a checkbox`);if(s.status==="done"){t[n]=i.replace(/- \[ \]/,"- [x]");let r=s.timeSeconds?` <!-- done by agent, ${Math.round(s.timeSeconds/60)}m -->`:" <!-- done by agent -->";t.splice(n+1,0,r)}else s.status==="blocked"?t.splice(n+1,0,` <!-- BLOCKED: ${s.blocker||s.note||"?"} -->`):s.note&&t.splice(n+1,0,` <!-- ${s.note.replace(/-->/g,"\u2014 >")} -->`);this.write(t)}},y=class{constructor(e,s){this.root=e;this.glob=s}type="wiki";capabilities={listAll:!1,create:!1,transition:!1,labels:!1,search:!1};walk(e,s){if(u(e))for(let n of _(e)){let t=v(e,n);j(t).isDirectory()?this.walk(t,s):n.endsWith(".md")&&s.push(t)}}async listOpen(e){let s=[];this.walk(f(process.cwd(),this.root),s);let n=[];for(let t of s){let i=p(t,"utf-8").split(`
4
+ `);for(let o=0;o<i.length;o++){let r=i[o].match(h);if(!r||r[2].toLowerCase()==="x")continue;let l=r[3].match(g);!l||l[1].toLowerCase()!==e.toLowerCase()||n.push({id:`wiki:${t}:${o}`,title:r[3].replace(g,"").trim(),assignee:l[1],priority:o})}}return n}async claim(){}async report(e,s){let n=e.split(":"),t=parseInt(n[n.length-1],10),i=n.slice(1,-1).join(":");if(!u(i))return;let o=p(i,"utf-8").split(`
5
+ `);s.status==="done"&&o[t]&&(o[t]=o[t].replace(/- \[ \]/,"- [x]"),k(i,o.join(`
6
+ `)))}},$=class{constructor(e,s,n,t,i){this.host=e;this.token=s;this.projects=n;this.agentUsernames=t;this.log=i}type="gitlab";capabilities={listAll:!0,create:!0,transition:!0,labels:!0,search:!0};usernameToAgent(e){for(let[s,n]of Object.entries(this.agentUsernames))if(n.some(t=>t.toLowerCase()===e.toLowerCase()))return s}mapIssue(e,s){let n=e.references?.full?.split("#")[0]||s,t=[],i;Array.isArray(e.labels)&&(e.labels.length&&typeof e.labels[0]=="object"?(i=e.labels.map(a=>({name:a.name,color:a.color,text_color:a.text_color})),t=i.map(a=>a.name)):t=e.labels);let r=(Array.isArray(e.assignees)&&e.assignees.length?e.assignees:e.assignee?[e.assignee]:[]).map(a=>({username:a.username,name:a.name,avatarUrl:a.avatar_url})),l=r[0]?.username;return{id:`gitlab:${n}:issue:${e.iid}`,title:e.title,description:e.description||void 0,assignee:l?this.usernameToAgent(l)||l:void 0,assignees:r,url:e.web_url,priority:e.weight??99,estimatedSeconds:e.time_stats?.time_estimate||void 0,labels:t,labelDetails:i,updatedAt:e.updated_at,state:e.state==="closed"?"closed":"opened",milestone:e.milestone?.title||void 0}}async api(e,s={}){let n=await fetch(`${this.host}/api/v4${e}`,{...s,headers:{"PRIVATE-TOKEN":this.token,"Content-Type":"application/json",...s.headers||{}}});if(!n.ok)throw new Error(`GitLab ${n.status}: ${await n.text().catch(()=>"")}`);return n.json()}async listOpen(e){let s=this.agentUsernames[e]||[];if(!s.length)return[];let n=[],t=this.projects.length?this.projects:["__ALL__"];for(let i of s)for(let o of t)try{let r=`assignee_username=${encodeURIComponent(i)}&state=opened&scope=all&per_page=20`,l=o==="__ALL__"?`/issues?${r}`:`/projects/${encodeURIComponent(o)}/issues?${r}`,a=await this.api(l);for(let c of a){let m=this.mapIssue(c,o);m.assignee=e,n.push(m)}}catch(r){this.log(`[business] GitLab listOpen failed for ${i} on ${o}: ${r.message}`)}return n}async claim(e,s){let{project:n,iid:t,type:i}=this.parseId(s),o=`/projects/${encodeURIComponent(n)}/${i==="issue"?"issues":"merge_requests"}/${t}?add_labels=Doing&remove_labels=To Do`;try{await this.api(o,{method:"PUT"})}catch(r){this.log(`[business] claim label update failed: ${r.message}`)}}async report(e,s){let{project:n,iid:t,type:i}=this.parseId(e),o=i==="issue"?"issues":"merge_requests";if(s.timeSeconds&&s.timeSeconds>0){let r=Math.max(1,Math.round(s.timeSeconds/60)),l=r>=60?`${Math.floor(r/60)}h${r%60?` ${r%60}m`:""}`:`${r}m`;try{await this.api(`/projects/${encodeURIComponent(n)}/${o}/${t}/add_spent_time?duration=${encodeURIComponent(l)}`,{method:"POST"})}catch(a){this.log(`[business] add_spent_time failed: ${a.message}`)}}if(s.status==="done")try{await this.api(`/projects/${encodeURIComponent(n)}/${o}/${t}?add_labels=Done&remove_labels=Doing`,{method:"PUT"})}catch(r){this.log(`[business] done label update failed: ${r.message}`)}else if(s.status==="blocked")try{await this.api(`/projects/${encodeURIComponent(n)}/${o}/${t}?add_labels=Blocked`,{method:"PUT"})}catch(r){this.log(`[business] blocked label update failed: ${r.message}`)}if(s.note)try{await this.api(`/projects/${encodeURIComponent(n)}/${o}/${t}/notes`,{method:"POST",body:JSON.stringify({body:s.note})})}catch(r){this.log(`[business] report note failed: ${r.message}`)}}async listAll(e={}){let s=e.sinceDays??30,n=new Date(Date.now()-s*24*36e5).toISOString(),t=new URLSearchParams,i=e.state??"opened";t.set("state",i),t.set("updated_after",n),t.set("scope","all"),t.set("per_page","100"),t.set("with_labels_details","true"),t.set("order_by","updated_at"),t.set("sort","desc"),e.labels?.length&&t.set("labels",e.labels.join(",")),e.milestone&&t.set("milestone",e.milestone),e.search&&t.set("search",e.search);let o=this.projects.length?this.projects:["__ALL__"],r=[];for(let a of o)try{let c=a==="__ALL__"?`/issues?${t.toString()}`:`/projects/${encodeURIComponent(a)}/issues?${t.toString()}`,m=await this.api(c);for(let I of m)r.push(this.mapIssue(I,a))}catch(c){this.log(`[business] GitLab listAll failed on ${a}: ${c.message}`)}let l=r;if(e.notLabels?.length){let a=new Set(e.notLabels);l=l.filter(c=>!(c.labels||[]).some(m=>a.has(m)))}if(e.withoutScopedPrefix){let a=e.withoutScopedPrefix+"::";l=l.filter(c=>!(c.labels||[]).some(m=>m.startsWith(a)))}return l}async setState(e,s){let{project:n,iid:t,type:i}=this.parseId(e),o=i==="issue"?"issues":"merge_requests";await this.api(`/projects/${encodeURIComponent(n)}/${o}/${t}?state_event=${s}`,{method:"PUT"})}async getItem(e){let{project:s,iid:n}=this.parseId(e);try{let t=await this.api(`/projects/${encodeURIComponent(s)}/issues/${n}?with_labels_details=true`);return{...this.mapIssue(t,s),descriptionHtml:t.description_html||void 0,createdAt:t.created_at,author:t.author?{username:t.author.username,name:t.author.name,avatarUrl:t.author.avatar_url}:void 0,allLabels:Array.isArray(t.labels)?typeof t.labels[0]=="object"?t.labels.map(o=>o.name):t.labels:[]}}catch(t){return this.log(`[board] GitLab getItem failed for ${e}: ${t.message}`),null}}async updateItem(e,s){let{project:n,iid:t}=this.parseId(e),i;s.assigneeUsernames&&(i=(await Promise.all(s.assigneeUsernames.map(async c=>{try{let m=await this.api(`/users?username=${encodeURIComponent(c)}`);return Array.isArray(m)&&m[0]?.id?m[0].id:null}catch{return null}}))).filter(c=>typeof c=="number"));let o;if(s.milestoneTitle!==void 0)if(s.milestoneTitle===null||s.milestoneTitle==="")o=0;else try{let a=await this.api(`/projects/${encodeURIComponent(n)}/milestones?title=${encodeURIComponent(s.milestoneTitle)}`);Array.isArray(a)&&a[0]?.id&&(o=a[0].id)}catch(a){this.log(`[board] milestone lookup failed for "${s.milestoneTitle}": ${a.message}`)}let r={};s.title!==void 0&&(r.title=s.title),s.description!==void 0&&(r.description=s.description),s.labels!==void 0&&(r.labels=s.labels.join(",")),s.addLabels?.length&&(r.add_labels=s.addLabels.join(",")),s.removeLabels?.length&&(r.remove_labels=s.removeLabels.join(",")),i!==void 0&&(r.assignee_ids=i),o!==void 0&&(r.milestone_id=o);let l=await this.api(`/projects/${encodeURIComponent(n)}/issues/${t}`,{method:"PUT",body:JSON.stringify(r)});return this.mapIssue(l,n)}async listMembers(){let e=new Map;for(let s of this.projects)try{let n=await this.api(`/projects/${encodeURIComponent(s)}/members/all?per_page=100`);for(let t of n)t.username&&!e.has(t.username)&&e.set(t.username,{username:t.username,name:t.name,avatarUrl:t.avatar_url})}catch(n){this.log(`[board] listMembers failed for ${s}: ${n.message}`)}return[...e.values()].sort((s,n)=>s.username.localeCompare(n.username))}async listMilestones(){let e=new Map;for(let n of this.projects)try{let t=await this.api(`/projects/${encodeURIComponent(n)}/milestones?per_page=100`);for(let i of t)i.title&&!e.has(i.title)&&e.set(i.title,{id:i.id,title:i.title,state:i.state,dueDate:i.due_date})}catch(t){this.log(`[board] listMilestones failed for ${n}: ${t.message}`)}let s=[...e.values()];return s.sort((n,t)=>n.state!==t.state?n.state==="active"?-1:1:n.title.localeCompare(t.title)),s}async create(e){let s=e.projectHint||this.projects[0];if(!s)throw new Error("GitLabWorkSource.create requires projectHint or configured projects");let n,t=e.assigneeUsernames?.length?e.assigneeUsernames:e.assignee?(this.agentUsernames[e.assignee]||[]).slice(0,1):[];t.length&&(n=(await Promise.all(t.map(async a=>{try{let c=await this.api(`/users?username=${encodeURIComponent(a)}`);return Array.isArray(c)&&c[0]?.id?c[0].id:null}catch(c){return this.log(`[board] user lookup failed for ${a}: ${c.message}`),null}}))).filter(a=>typeof a=="number"));let i;if(e.milestoneTitle)try{let l=await this.api(`/projects/${encodeURIComponent(s)}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);Array.isArray(l)&&l[0]?.id&&(i=l[0].id)}catch(l){this.log(`[board] milestone lookup failed for "${e.milestoneTitle}": ${l.message}`)}let o={title:e.title,description:e.description,labels:(e.labels||[]).join(",")};n?.length&&(o.assignee_ids=n),i!==void 0&&(o.milestone_id=i);let r=await this.api(`/projects/${encodeURIComponent(s)}/issues`,{method:"POST",body:JSON.stringify(o)});return this.mapIssue(r,s)}async transition(e,s,n){await this.transitionMany(e,s?[s]:[],n?[n]:[])}async transitionMany(e,s,n){let{project:t,iid:i,type:o}=this.parseId(e),r=o==="issue"?"issues":"merge_requests",l=new URLSearchParams;s.length&&l.set("add_labels",s.join(",")),n.length&&l.set("remove_labels",n.join(",")),l.toString()&&await this.api(`/projects/${encodeURIComponent(t)}/${r}/${i}?${l.toString()}`,{method:"PUT"})}parseId(e){let n=e.replace(/^gitlab:/,"").split(":"),t=n.pop(),i=n.pop()==="mr"?"mr":"issue";return{project:n.join(":"),type:i,iid:t}}};function W(d,e,s){let n=d.workSource;switch(n.type){case"backlog":return new b(n.path);case"wiki":return new y(n.path,n.glob);case"gitlab":{let t=e.channels.gitlab;if(!t?.token)throw new Error("business.workSource.type=gitlab requires channels.gitlab.token");let i={};for(let r of t.agentMappings)r.gitlabUsernames.length&&(i[r.agentId]=r.gitlabUsernames);let o=n.projects.length?n.projects:t.routes.map(r=>r.project);return new $(t.host,t.token,o,i,s)}}}export{$ as a,W as b};
7
+ //# sourceMappingURL=chunk-6U2XOQHU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/business/work-pool.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport type { BusinessConfig, BusinessWorkSource } from \"./config\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\n// --- Work pool: pluggable source of tasks for the business layer ---\n\nexport interface WorkItem {\n id: string // stable id for claim/report (e.g. \"backlog:line-12\", \"gitlab:proj:issue:42\")\n title: string\n description?: string\n assignee?: string // agentId\n estimatedSeconds?: number\n url?: string\n priority?: number // lower = higher priority\n /** Board stage derived from labels (kanban column id). */\n stage?: \"triage\" | \"todo\" | \"doing\" | \"onhold\" | \"review\" | \"done\"\n /** Raw labels from the source (GitLab). Empty array if source doesn't support labels. */\n labels?: string[]\n /** Label name+color when the source returns colored labels (GitLab with_labels_details). */\n labelDetails?: Array<{ name: string; color: string; text_color?: string }>\n /** ISO timestamp of last update on the source. */\n updatedAt?: string\n /** Open/closed state from the source. */\n state?: \"opened\" | \"closed\"\n /** Optional milestone title (GitLab: issue.milestone.title). */\n milestone?: string\n /** GitLab assignees (0..n). `assignee` above is the agentId alias for assignees[0]. */\n assignees?: Array<{ username: string; name?: string; avatarUrl?: string }>\n}\n\n/** Member of a source (GitLab project members) — used for assignee pickers. */\nexport interface WorkMember {\n username: string\n name?: string\n avatarUrl?: string\n}\n\n/** Milestone on a source — used for milestone pickers. */\nexport interface WorkMilestone {\n id: number | string\n title: string\n state?: \"active\" | \"closed\"\n dueDate?: string\n}\n\n/** Full issue detail — a superset of WorkItem for the detail view. */\nexport interface WorkDetail extends WorkItem {\n /** Raw body, may be markdown. */\n descriptionHtml?: string\n /** ISO timestamp of creation. */\n createdAt?: string\n /** Author (issue reporter). */\n author?: { username: string; name?: string; avatarUrl?: string }\n /** Full labels list (all of them). */\n allLabels?: string[]\n}\n\nexport interface WorkReport {\n status: \"in-progress\" | \"done\" | \"blocked\"\n note?: string\n timeSeconds?: number\n blocker?: string\n}\n\n/** What a WorkSource can do beyond the minimal listOpen/claim/report triple. */\nexport interface WorkSourceCapabilities {\n /** Enumerate items beyond assignee filter — required for a board view. */\n listAll: boolean\n /** Create new items (e.g. POST GitLab issue). */\n create: boolean\n /** Arbitrary column transitions (label add/remove pair). */\n transition: boolean\n /** Surfaces labels[] on returned items. */\n labels: boolean\n /** Server-side text search. */\n search: boolean\n}\n\nexport interface WorkCreateInput {\n title: string\n description?: string\n /** agentId */\n assignee?: string\n /** Raw GitLab usernames — bypass agentId resolution for UI-driven creates. */\n assigneeUsernames?: string[]\n labels?: string[]\n /** Milestone title (resolved to id). */\n milestoneTitle?: string\n /** For sources that span multiple projects (gitlab): \"group/project\". */\n projectHint?: string\n}\n\nexport interface ListAllOpts {\n sinceDays?: number\n labels?: string[]\n /** Exclude items carrying any of these labels (client-side filter). */\n notLabels?: string[]\n /** Exclude items whose labels carry this scoped prefix (e.g. \"Status\"). */\n withoutScopedPrefix?: string\n search?: string\n assignee?: string\n /** GitLab issue state. Defaults to \"opened\" (boards hide closed issues). */\n state?: \"opened\" | \"closed\" | \"all\"\n /** Milestone filter (GitLab \"milestone\" query param). */\n milestone?: string\n}\n\nexport interface WorkSource {\n type: string\n capabilities: WorkSourceCapabilities\n listOpen(agentId: string): Promise<WorkItem[]>\n claim(agentId: string, itemId: string): Promise<void>\n report(itemId: string, update: WorkReport): Promise<void>\n /** Optional — gated by capabilities.listAll. */\n listAll?(opts: ListAllOpts): Promise<WorkItem[]>\n /** Optional — gated by capabilities.create. */\n create?(input: WorkCreateInput): Promise<WorkItem>\n /**\n * Optional — gated by capabilities.transition. Add `toLabel` and remove\n * `fromLabel` (if provided) from the item. Used by the kanban board on drag.\n */\n transition?(itemId: string, toLabel: string, fromLabel?: string): Promise<void>\n}\n\n// ---------- BacklogWorkSource: GFM checklist file ----------\n// Format in .agentx/backlog.md (or configured path):\n//\n// - [ ] @alice Implement login form [time: 2h]\n// - [x] @alice Write unit tests (done)\n// - [ ] @bob Design pricing page [time: 1h30m]\n//\n// Line number is the stable id.\n\nconst CHECK_RE = /^(\\s*)-\\s*\\[( |x|X)\\]\\s*(.*)$/\nconst MENTION_RE = /@([a-z0-9_-]+)/i\nconst TIME_RE = /\\[time:\\s*([0-9hm\\s]+)\\]/i\n\nfunction parseDuration(s: string): number {\n // \"2h30m\" -> 9000 s. \"1h\" -> 3600. \"45m\" -> 2700.\n const hMatch = s.match(/(\\d+)\\s*h/i)\n const mMatch = s.match(/(\\d+)\\s*m/i)\n return (hMatch ? parseInt(hMatch[1], 10) * 3600 : 0) + (mMatch ? parseInt(mMatch[1], 10) * 60 : 0)\n}\n\nexport class BacklogWorkSource implements WorkSource {\n type = \"backlog\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private path: string) {}\n\n private read(): string[] {\n const full = resolve(process.cwd(), this.path)\n if (!existsSync(full)) return []\n return readFileSync(full, \"utf-8\").split(\"\\n\")\n }\n\n private write(lines: string[]): void {\n const full = resolve(process.cwd(), this.path)\n writeFileSync(full, lines.join(\"\\n\"))\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const lines = this.read()\n const items: WorkItem[] = []\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m) continue\n const checked = m[2].toLowerCase() === \"x\"\n if (checked) continue\n const body = m[3]\n const mention = body.match(MENTION_RE)\n if (mention && mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n const timeMatch = body.match(TIME_RE)\n const title = body.replace(MENTION_RE, \"\").replace(TIME_RE, \"\").trim()\n items.push({\n id: `backlog:${i}`,\n title: title || `Task at line ${i + 1}`,\n assignee: mention ? mention[1] : undefined,\n estimatedSeconds: timeMatch ? parseDuration(timeMatch[1]) : undefined,\n priority: i,\n })\n }\n return items\n }\n\n async claim(_agentId: string, _itemId: string): Promise<void> {\n // No-op: backlog file has no \"claimed\" state distinct from \"assigned\".\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n if (!itemId.startsWith(\"backlog:\")) throw new Error(`not a backlog id: ${itemId}`)\n const lineNo = parseInt(itemId.slice(\"backlog:\".length), 10)\n const lines = this.read()\n const line = lines[lineNo]\n if (!line) throw new Error(`backlog line ${lineNo} missing`)\n const m = line.match(CHECK_RE)\n if (!m) throw new Error(`backlog line ${lineNo} not a checkbox`)\n\n if (update.status === \"done\") {\n lines[lineNo] = line.replace(/- \\[ \\]/, \"- [x]\")\n const note = update.timeSeconds\n ? ` <!-- done by agent, ${Math.round(update.timeSeconds / 60)}m -->`\n : \" <!-- done by agent -->\"\n lines.splice(lineNo + 1, 0, note)\n } else if (update.status === \"blocked\") {\n lines.splice(lineNo + 1, 0, ` <!-- BLOCKED: ${update.blocker || update.note || \"?\"} -->`)\n } else if (update.note) {\n lines.splice(lineNo + 1, 0, ` <!-- ${update.note.replace(/-->/g, \"— >\")} -->`)\n }\n this.write(lines)\n }\n}\n\n// ---------- WikiWorkSource: scan markdown files for @mention checkboxes ----------\n\nexport class WikiWorkSource implements WorkSource {\n type = \"wiki\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private root: string, private glob: string) {}\n\n private walk(dir: string, out: string[]): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const st = statSync(full)\n if (st.isDirectory()) {\n this.walk(full, out)\n } else if (entry.endsWith(\".md\")) {\n out.push(full)\n }\n }\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const files: string[] = []\n this.walk(resolve(process.cwd(), this.root), files)\n const items: WorkItem[] = []\n for (const file of files) {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m || m[2].toLowerCase() === \"x\") continue\n const mention = m[3].match(MENTION_RE)\n if (!mention || mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n items.push({\n id: `wiki:${file}:${i}`,\n title: m[3].replace(MENTION_RE, \"\").trim(),\n assignee: mention[1],\n priority: i,\n })\n }\n }\n return items\n }\n\n async claim(): Promise<void> { /* no-op */ }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // wiki:<file>:<line>\n const parts = itemId.split(\":\")\n const lineNo = parseInt(parts[parts.length - 1], 10)\n const file = parts.slice(1, -1).join(\":\")\n if (!existsSync(file)) return\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n if (update.status === \"done\" && lines[lineNo]) {\n lines[lineNo] = lines[lineNo].replace(/- \\[ \\]/, \"- [x]\")\n writeFileSync(file, lines.join(\"\\n\"))\n }\n }\n}\n\n// ---------- GitLabWorkSource: assignee-filtered issues via GitLab API ----------\n\nexport class GitLabWorkSource implements WorkSource {\n type = \"gitlab\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: true, transition: true, labels: true, search: true,\n }\n\n constructor(\n private host: string,\n private token: string,\n private projects: string[],\n /** Map from agentId -> GitLab username(s) */\n private agentUsernames: Record<string, string[]>,\n private log: (...args: unknown[]) => void,\n ) {}\n\n /** Reverse lookup: GitLab username -> agentId. Used when resolving board items\n * back to an agent (for the reconciler and card display). */\n private usernameToAgent(username: string): string | undefined {\n for (const [agentId, names] of Object.entries(this.agentUsernames)) {\n if (names.some((n) => n.toLowerCase() === username.toLowerCase())) return agentId\n }\n return undefined\n }\n\n private mapIssue(issue: any, project: string): WorkItem {\n const ns = issue.references?.full?.split(\"#\")[0] || project\n // `labels` may be a string[] or, with with_labels_details=true, an object[].\n let labels: string[] = []\n let labelDetails: Array<{ name: string; color: string; text_color?: string }> | undefined\n if (Array.isArray(issue.labels)) {\n if (issue.labels.length && typeof issue.labels[0] === \"object\") {\n labelDetails = issue.labels.map((l: any) => ({\n name: l.name, color: l.color, text_color: l.text_color,\n }))\n labels = labelDetails!.map((l) => l.name)\n } else {\n labels = issue.labels\n }\n }\n const rawAssignees: any[] = Array.isArray(issue.assignees) && issue.assignees.length\n ? issue.assignees\n : (issue.assignee ? [issue.assignee] : [])\n const assignees = rawAssignees.map((a) => ({\n username: a.username, name: a.name, avatarUrl: a.avatar_url,\n }))\n const firstUsername = assignees[0]?.username\n return {\n id: `gitlab:${ns}:issue:${issue.iid}`,\n title: issue.title,\n description: issue.description || undefined,\n // Prefer configured agentId mapping; fall back to the raw GitLab username\n // so cards still render an assignee even without agentMappings configured.\n assignee: firstUsername ? (this.usernameToAgent(firstUsername) || firstUsername) : undefined,\n assignees,\n url: issue.web_url,\n priority: issue.weight ?? 99,\n estimatedSeconds: issue.time_stats?.time_estimate || undefined,\n labels,\n labelDetails,\n updatedAt: issue.updated_at,\n state: issue.state === \"closed\" ? \"closed\" : \"opened\",\n milestone: issue.milestone?.title || undefined,\n }\n }\n\n private async api(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetch(`${this.host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": this.token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n if (!res.ok) throw new Error(`GitLab ${res.status}: ${await res.text().catch(() => \"\")}`)\n return res.json()\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const usernames = this.agentUsernames[agentId] || []\n if (!usernames.length) return []\n\n const items: WorkItem[] = []\n const projectList = this.projects.length\n ? this.projects\n : [\"__ALL__\"] // falls back to a global issues query\n\n for (const username of usernames) {\n for (const project of projectList) {\n try {\n const q = `assignee_username=${encodeURIComponent(username)}&state=opened&scope=all&per_page=20`\n const path = project === \"__ALL__\"\n ? `/issues?${q}`\n : `/projects/${encodeURIComponent(project)}/issues?${q}`\n const issues = await this.api(path)\n for (const issue of issues) {\n const item = this.mapIssue(issue, project)\n // Force assignee to the agent we queried for (listOpen is per-agent).\n item.assignee = agentId\n items.push(item)\n }\n } catch (e: any) {\n this.log(`[business] GitLab listOpen failed for ${username} on ${project}: ${e.message}`)\n }\n }\n }\n return items\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n // itemId: \"gitlab:<project>:issue:<iid>\"\n const { project, iid, type } = this.parseId(itemId)\n // Transition labels: add \"Doing\", remove \"To Do\"\n const path = `/projects/${encodeURIComponent(project)}/${type === \"issue\" ? \"issues\" : \"merge_requests\"}/${iid}?add_labels=Doing&remove_labels=To Do`\n try {\n await this.api(path, { method: \"PUT\" })\n } catch (e: any) {\n this.log(`[business] claim label update failed: ${e.message}`)\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n\n // Time tracking\n if (update.timeSeconds && update.timeSeconds > 0) {\n const mins = Math.max(1, Math.round(update.timeSeconds / 60))\n const dur = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? ` ${mins % 60}m` : \"\"}` : `${mins}m`\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/add_spent_time?duration=${encodeURIComponent(dur)}`,\n { method: \"POST\" },\n )\n } catch (e: any) {\n this.log(`[business] add_spent_time failed: ${e.message}`)\n }\n }\n\n // Status → labels\n if (update.status === \"done\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Done&remove_labels=Doing`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] done label update failed: ${e.message}`)\n }\n } else if (update.status === \"blocked\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Blocked`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] blocked label update failed: ${e.message}`)\n }\n }\n\n // Note/comment\n if (update.note) {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/notes`,\n { method: \"POST\", body: JSON.stringify({ body: update.note }) },\n )\n } catch (e: any) {\n this.log(`[business] report note failed: ${e.message}`)\n }\n }\n }\n\n async listAll(opts: ListAllOpts = {}): Promise<WorkItem[]> {\n const sinceDays = opts.sinceDays ?? 30\n const updatedAfter = new Date(Date.now() - sinceDays * 24 * 3600_000).toISOString()\n\n const params = new URLSearchParams()\n const state = opts.state ?? \"opened\"\n params.set(\"state\", state)\n // `updated_after` makes sense for the Closed column (bounded window) and for\n // keeping the open list recent; for \"opened\" we pass it too but GitLab treats\n // the window loosely (any issue updated since counts, regardless of open-age).\n params.set(\"updated_after\", updatedAfter)\n params.set(\"scope\", \"all\")\n params.set(\"per_page\", \"100\")\n params.set(\"with_labels_details\", \"true\")\n params.set(\"order_by\", \"updated_at\")\n params.set(\"sort\", \"desc\")\n if (opts.labels?.length) params.set(\"labels\", opts.labels.join(\",\"))\n if (opts.milestone) params.set(\"milestone\", opts.milestone)\n if (opts.search) params.set(\"search\", opts.search)\n\n const projectList = this.projects.length ? this.projects : [\"__ALL__\"]\n const items: WorkItem[] = []\n for (const project of projectList) {\n try {\n const path = project === \"__ALL__\"\n ? `/issues?${params.toString()}`\n : `/projects/${encodeURIComponent(project)}/issues?${params.toString()}`\n const issues = await this.api(path)\n for (const issue of issues) items.push(this.mapIssue(issue, project))\n } catch (e: any) {\n this.log(`[business] GitLab listAll failed on ${project}: ${e.message}`)\n }\n }\n\n // Client-side negative-label filter (GitLab API supports `not[labels]` only\n // via the `not[]` nested query form, which isn't convenient via URLSearchParams).\n let result = items\n if (opts.notLabels?.length) {\n const deny = new Set(opts.notLabels)\n result = result.filter((i) => !(i.labels || []).some((l) => deny.has(l)))\n }\n if (opts.withoutScopedPrefix) {\n const prefix = opts.withoutScopedPrefix + \"::\"\n result = result.filter((i) => !(i.labels || []).some((l) => l.startsWith(prefix)))\n }\n return result\n }\n\n /** Close or reopen an issue via state_event. */\n async setState(itemId: string, to: \"close\" | \"reopen\"): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?state_event=${to}`,\n { method: \"PUT\" },\n )\n }\n\n /** Fetch a single issue's full detail. */\n async getItem(itemId: string): Promise<WorkDetail | null> {\n const { project, iid } = this.parseId(itemId)\n try {\n const issue = await this.api(\n `/projects/${encodeURIComponent(project)}/issues/${iid}?with_labels_details=true`,\n )\n const base = this.mapIssue(issue, project)\n return {\n ...base,\n descriptionHtml: issue.description_html || undefined,\n createdAt: issue.created_at,\n author: issue.author ? {\n username: issue.author.username, name: issue.author.name, avatarUrl: issue.author.avatar_url,\n } : undefined,\n allLabels: Array.isArray(issue.labels)\n ? (typeof issue.labels[0] === \"object\" ? issue.labels.map((l: any) => l.name) : issue.labels)\n : [],\n }\n } catch (e: any) {\n this.log(`[board] GitLab getItem failed for ${itemId}: ${e.message}`)\n return null\n }\n }\n\n /** Update fields on an issue. Accepts string usernames for assignees. */\n async updateItem(\n itemId: string,\n patch: {\n title?: string\n description?: string\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n assigneeUsernames?: string[]\n milestoneTitle?: string | null\n },\n ): Promise<WorkItem> {\n const { project, iid } = this.parseId(itemId)\n\n // Resolve assignee usernames → user ids (parallel).\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames) {\n const results = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch { return null }\n }))\n assigneeIds = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id (null clears the milestone).\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestoneId = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${patch.milestoneTitle}\": ${e.message}`)\n }\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues/${iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n /** List project members (direct + inherited). Deduped by username across projects. */\n async listMembers(): Promise<WorkMember[]> {\n const byUsername = new Map<string, WorkMember>()\n for (const project of this.projects) {\n try {\n const members = await this.api(\n `/projects/${encodeURIComponent(project)}/members/all?per_page=100`,\n )\n for (const m of members) {\n if (m.username && !byUsername.has(m.username)) {\n byUsername.set(m.username, {\n username: m.username, name: m.name, avatarUrl: m.avatar_url,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMembers failed for ${project}: ${e.message}`)\n }\n }\n return [...byUsername.values()].sort((a, b) => a.username.localeCompare(b.username))\n }\n\n /** List milestones (active first, then closed). Deduped by title across projects. */\n async listMilestones(): Promise<WorkMilestone[]> {\n const byTitle = new Map<string, WorkMilestone>()\n for (const project of this.projects) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?per_page=100`,\n )\n for (const m of ms) {\n if (m.title && !byTitle.has(m.title)) {\n byTitle.set(m.title, {\n id: m.id, title: m.title, state: m.state, dueDate: m.due_date,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMilestones failed for ${project}: ${e.message}`)\n }\n }\n const all = [...byTitle.values()]\n all.sort((a, b) => {\n if (a.state !== b.state) return a.state === \"active\" ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n return all\n }\n\n async create(input: WorkCreateInput): Promise<WorkItem> {\n const project = input.projectHint || this.projects[0]\n if (!project) throw new Error(\"GitLabWorkSource.create requires projectHint or configured projects\")\n\n // Resolve assignee usernames → user ids. Prefer explicit `assigneeUsernames`,\n // fall back to agentId → configured mapping for legacy callers.\n let assignee_ids: number[] | undefined\n const usernames = input.assigneeUsernames?.length\n ? input.assigneeUsernames\n : (input.assignee ? (this.agentUsernames[input.assignee] || []).slice(0, 1) : [])\n if (usernames.length) {\n const results = await Promise.all(usernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch (e: any) {\n this.log(`[board] user lookup failed for ${u}: ${e.message}`)\n return null\n }\n }))\n assignee_ids = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestone_id: number | undefined\n if (input.milestoneTitle) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(input.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestone_id = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${input.milestoneTitle}\": ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n description: input.description,\n labels: (input.labels || []).join(\",\"),\n }\n if (assignee_ids?.length) body.assignee_ids = assignee_ids\n if (milestone_id !== undefined) body.milestone_id = milestone_id\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n async transition(itemId: string, toLabel: string, fromLabel?: string): Promise<void> {\n // Legacy single add/remove — boards use transitionMany.\n await this.transitionMany(itemId, toLabel ? [toLabel] : [], fromLabel ? [fromLabel] : [])\n }\n\n /** Multi-label transition; used by board drag-drop (scoped labels produce pairs). */\n async transitionMany(itemId: string, add: string[], remove: string[]): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n const params = new URLSearchParams()\n if (add.length) params.set(\"add_labels\", add.join(\",\"))\n if (remove.length) params.set(\"remove_labels\", remove.join(\",\"))\n if (!params.toString()) return\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?${params.toString()}`,\n { method: \"PUT\" },\n )\n }\n\n private parseId(itemId: string): { project: string; type: \"issue\" | \"mr\"; iid: string } {\n // \"gitlab:<project>:issue:<iid>\" (project may contain a slash → it's joined)\n const body = itemId.replace(/^gitlab:/, \"\")\n const parts = body.split(\":\")\n const iid = parts.pop() as string\n const type = parts.pop() === \"mr\" ? \"mr\" : \"issue\"\n const project = parts.join(\":\")\n return { project, type, iid }\n }\n}\n\n// ---------- Factory ----------\n\nexport function createWorkSource(\n business: BusinessConfig,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): WorkSource {\n const ws: BusinessWorkSource = business.workSource\n switch (ws.type) {\n case \"backlog\":\n return new BacklogWorkSource(ws.path)\n\n case \"wiki\":\n return new WikiWorkSource(ws.path, ws.glob)\n\n case \"gitlab\": {\n const gl = daemon.channels.gitlab\n if (!gl?.token) {\n throw new Error(\"business.workSource.type=gitlab requires channels.gitlab.token\")\n }\n const agentUsernames: Record<string, string[]> = {}\n for (const mapping of gl.agentMappings) {\n if (mapping.gitlabUsernames.length) {\n agentUsernames[mapping.agentId] = mapping.gitlabUsernames\n }\n }\n const projects = ws.projects.length\n ? ws.projects\n : gl.routes.map((r) => r.project)\n return new GitLabWorkSource(gl.host, gl.token, projects, agentUsernames, log)\n }\n }\n}\n"],"mappings":"AAAA,OAAS,gBAAAA,EAAc,iBAAAC,EAAe,cAAAC,EAAY,eAAAC,EAAa,YAAAC,MAAgB,KAC/E,OAAS,WAAAC,EAAS,QAAAC,MAAY,OAqI9B,IAAMC,EAAW,gCACXC,EAAa,kBACbC,EAAU,4BAEhB,SAASC,EAAcC,EAAmB,CAExC,IAAMC,EAASD,EAAE,MAAM,YAAY,EAC7BE,EAASF,EAAE,MAAM,YAAY,EACnC,OAAQC,EAAS,SAASA,EAAO,CAAC,EAAG,EAAE,EAAI,KAAO,IAAMC,EAAS,SAASA,EAAO,CAAC,EAAG,EAAE,EAAI,GAAK,EAClG,CAEO,IAAMC,EAAN,KAA8C,CAKnD,YAAoBC,EAAc,CAAd,UAAAA,CAAe,CAJnC,KAAO,UACP,aAAuC,CACrC,QAAS,GAAO,OAAQ,GAAO,WAAY,GAAO,OAAQ,GAAO,OAAQ,EAC3E,EAGQ,MAAiB,CACvB,IAAMC,EAAOX,EAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAC7C,OAAKH,EAAWc,CAAI,EACbhB,EAAagB,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EADf,CAAC,CAEjC,CAEQ,MAAMC,EAAuB,CACnC,IAAMD,EAAOX,EAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAC7CJ,EAAce,EAAMC,EAAM,KAAK;AAAA,CAAI,CAAC,CACtC,CAEA,MAAM,SAASC,EAAsC,CACnD,IAAMD,EAAQ,KAAK,KAAK,EAClBE,EAAoB,CAAC,EAC3B,QAASC,EAAI,EAAGA,EAAIH,EAAM,OAAQG,IAAK,CACrC,IAAMC,EAAIJ,EAAMG,CAAC,EAAE,MAAMb,CAAQ,EAGjC,GAFI,CAACc,GACWA,EAAE,CAAC,EAAE,YAAY,IAAM,IAC1B,SACb,IAAMC,EAAOD,EAAE,CAAC,EACVE,EAAUD,EAAK,MAAMd,CAAU,EACrC,GAAIe,GAAWA,EAAQ,CAAC,EAAE,YAAY,IAAML,EAAQ,YAAY,EAAG,SACnE,IAAMM,EAAYF,EAAK,MAAMb,CAAO,EAC9BgB,EAAQH,EAAK,QAAQd,EAAY,EAAE,EAAE,QAAQC,EAAS,EAAE,EAAE,KAAK,EACrEU,EAAM,KAAK,CACT,GAAI,WAAWC,IACf,MAAOK,GAAS,gBAAgBL,EAAI,IACpC,SAAUG,EAAUA,EAAQ,CAAC,EAAI,OACjC,iBAAkBC,EAAYd,EAAcc,EAAU,CAAC,CAAC,EAAI,OAC5D,SAAUJ,CACZ,CAAC,EAEH,OAAOD,CACT,CAEA,MAAM,MAAMO,EAAkBC,EAAgC,CAE9D,CAEA,MAAM,OAAOC,EAAgBC,EAAmC,CAC9D,GAAI,CAACD,EAAO,WAAW,UAAU,EAAG,MAAM,IAAI,MAAM,qBAAqBA,GAAQ,EACjF,IAAME,EAAS,SAASF,EAAO,MAAM,CAAiB,EAAG,EAAE,EACrDX,EAAQ,KAAK,KAAK,EAClBc,EAAOd,EAAMa,CAAM,EACzB,GAAI,CAACC,EAAM,MAAM,IAAI,MAAM,gBAAgBD,WAAgB,EAE3D,GAAI,CADMC,EAAK,MAAMxB,CAAQ,EACrB,MAAM,IAAI,MAAM,gBAAgBuB,kBAAuB,EAE/D,GAAID,EAAO,SAAW,OAAQ,CAC5BZ,EAAMa,CAAM,EAAIC,EAAK,QAAQ,UAAW,OAAO,EAC/C,IAAMC,EAAOH,EAAO,YAChB,2BAA2B,KAAK,MAAMA,EAAO,YAAc,EAAE,SAC7D,6BACJZ,EAAM,OAAOa,EAAS,EAAG,EAAGE,CAAI,OACvBH,EAAO,SAAW,UAC3BZ,EAAM,OAAOa,EAAS,EAAG,EAAG,qBAAqBD,EAAO,SAAWA,EAAO,MAAQ,SAAS,EAClFA,EAAO,MAChBZ,EAAM,OAAOa,EAAS,EAAG,EAAG,YAAYD,EAAO,KAAK,QAAQ,OAAQ,UAAK,OAAO,EAElF,KAAK,MAAMZ,CAAK,CAClB,CACF,EAIagB,EAAN,KAA2C,CAKhD,YAAoBC,EAAsBC,EAAc,CAApC,UAAAD,EAAsB,UAAAC,CAAe,CAJzD,KAAO,OACP,aAAuC,CACrC,QAAS,GAAO,OAAQ,GAAO,WAAY,GAAO,OAAQ,GAAO,OAAQ,EAC3E,EAGQ,KAAKC,EAAaC,EAAqB,CAC7C,GAAKnC,EAAWkC,CAAG,EACnB,QAAWE,KAASnC,EAAYiC,CAAG,EAAG,CACpC,IAAMpB,EAAOV,EAAK8B,EAAKE,CAAK,EACjBlC,EAASY,CAAI,EACjB,YAAY,EACjB,KAAK,KAAKA,EAAMqB,CAAG,EACVC,EAAM,SAAS,KAAK,GAC7BD,EAAI,KAAKrB,CAAI,EAGnB,CAEA,MAAM,SAASE,EAAsC,CACnD,IAAMqB,EAAkB,CAAC,EACzB,KAAK,KAAKlC,EAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAAGkC,CAAK,EAClD,IAAMpB,EAAoB,CAAC,EAC3B,QAAWqB,KAAQD,EAAO,CACxB,IAAMtB,EAAQjB,EAAawC,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EACpD,QAASpB,EAAI,EAAGA,EAAIH,EAAM,OAAQG,IAAK,CACrC,IAAMC,EAAIJ,EAAMG,CAAC,EAAE,MAAMb,CAAQ,EACjC,GAAI,CAACc,GAAKA,EAAE,CAAC,EAAE,YAAY,IAAM,IAAK,SACtC,IAAME,EAAUF,EAAE,CAAC,EAAE,MAAMb,CAAU,EACjC,CAACe,GAAWA,EAAQ,CAAC,EAAE,YAAY,IAAML,EAAQ,YAAY,GACjEC,EAAM,KAAK,CACT,GAAI,QAAQqB,KAAQpB,IACpB,MAAOC,EAAE,CAAC,EAAE,QAAQb,EAAY,EAAE,EAAE,KAAK,EACzC,SAAUe,EAAQ,CAAC,EACnB,SAAUH,CACZ,CAAC,GAGL,OAAOD,CACT,CAEA,MAAM,OAAuB,CAAc,CAE3C,MAAM,OAAOS,EAAgBC,EAAmC,CAE9D,IAAMY,EAAQb,EAAO,MAAM,GAAG,EACxBE,EAAS,SAASW,EAAMA,EAAM,OAAS,CAAC,EAAG,EAAE,EAC7CD,EAAOC,EAAM,MAAM,EAAG,EAAE,EAAE,KAAK,GAAG,EACxC,GAAI,CAACvC,EAAWsC,CAAI,EAAG,OACvB,IAAMvB,EAAQjB,EAAawC,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EAChDX,EAAO,SAAW,QAAUZ,EAAMa,CAAM,IAC1Cb,EAAMa,CAAM,EAAIb,EAAMa,CAAM,EAAE,QAAQ,UAAW,OAAO,EACxD7B,EAAcuC,EAAMvB,EAAM,KAAK;AAAA,CAAI,CAAC,EAExC,CACF,EAIayB,EAAN,KAA6C,CAMlD,YACUC,EACAC,EACAC,EAEAC,EACAC,EACR,CANQ,UAAAJ,EACA,WAAAC,EACA,cAAAC,EAEA,oBAAAC,EACA,SAAAC,CACP,CAZH,KAAO,SACP,aAAuC,CACrC,QAAS,GAAM,OAAQ,GAAM,WAAY,GAAM,OAAQ,GAAM,OAAQ,EACvE,EAaQ,gBAAgBC,EAAsC,CAC5D,OAAW,CAAC9B,EAAS+B,CAAK,IAAK,OAAO,QAAQ,KAAK,cAAc,EAC/D,GAAIA,EAAM,KAAMC,GAAMA,EAAE,YAAY,IAAMF,EAAS,YAAY,CAAC,EAAG,OAAO9B,CAG9E,CAEQ,SAASiC,EAAYC,EAA2B,CACtD,IAAMC,EAAKF,EAAM,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC,GAAKC,EAEhDE,EAAmB,CAAC,EACpBC,EACA,MAAM,QAAQJ,EAAM,MAAM,IACxBA,EAAM,OAAO,QAAU,OAAOA,EAAM,OAAO,CAAC,GAAM,UACpDI,EAAeJ,EAAM,OAAO,IAAKK,IAAY,CAC3C,KAAMA,EAAE,KAAM,MAAOA,EAAE,MAAO,WAAYA,EAAE,UAC9C,EAAE,EACFF,EAASC,EAAc,IAAKC,GAAMA,EAAE,IAAI,GAExCF,EAASH,EAAM,QAMnB,IAAMM,GAHsB,MAAM,QAAQN,EAAM,SAAS,GAAKA,EAAM,UAAU,OAC1EA,EAAM,UACLA,EAAM,SAAW,CAACA,EAAM,QAAQ,EAAI,CAAC,GACX,IAAK,IAAO,CACzC,SAAU,EAAE,SAAU,KAAM,EAAE,KAAM,UAAW,EAAE,UACnD,EAAE,EACIO,EAAgBD,EAAU,CAAC,GAAG,SACpC,MAAO,CACL,GAAI,UAAUJ,WAAYF,EAAM,MAChC,MAAOA,EAAM,MACb,YAAaA,EAAM,aAAe,OAGlC,SAAUO,EAAiB,KAAK,gBAAgBA,CAAa,GAAKA,EAAiB,OACnF,UAAAD,EACA,IAAKN,EAAM,QACX,SAAUA,EAAM,QAAU,GAC1B,iBAAkBA,EAAM,YAAY,eAAiB,OACrD,OAAAG,EACA,aAAAC,EACA,UAAWJ,EAAM,WACjB,MAAOA,EAAM,QAAU,SAAW,SAAW,SAC7C,UAAWA,EAAM,WAAW,OAAS,MACvC,CACF,CAEA,MAAc,IAAIpC,EAAc4C,EAAoB,CAAC,EAAiB,CACpE,IAAMC,EAAM,MAAM,MAAM,GAAG,KAAK,cAAc7C,IAAQ,CACpD,GAAG4C,EACH,QAAS,CACP,gBAAiB,KAAK,MACtB,eAAgB,mBAChB,GAAIA,EAAK,SAAW,CAAC,CACvB,CACF,CAAC,EACD,GAAI,CAACC,EAAI,GAAI,MAAM,IAAI,MAAM,UAAUA,EAAI,WAAW,MAAMA,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,EACxF,OAAOA,EAAI,KAAK,CAClB,CAEA,MAAM,SAAS1C,EAAsC,CACnD,IAAM2C,EAAY,KAAK,eAAe3C,CAAO,GAAK,CAAC,EACnD,GAAI,CAAC2C,EAAU,OAAQ,MAAO,CAAC,EAE/B,IAAM1C,EAAoB,CAAC,EACrB2C,EAAc,KAAK,SAAS,OAC9B,KAAK,SACL,CAAC,SAAS,EAEd,QAAWd,KAAYa,EACrB,QAAWT,KAAWU,EACpB,GAAI,CACF,IAAMC,EAAI,qBAAqB,mBAAmBf,CAAQ,uCACpDjC,EAAOqC,IAAY,UACrB,WAAWW,IACX,aAAa,mBAAmBX,CAAO,YAAYW,IACjDC,EAAS,MAAM,KAAK,IAAIjD,CAAI,EAClC,QAAWoC,KAASa,EAAQ,CAC1B,IAAMC,EAAO,KAAK,SAASd,EAAOC,CAAO,EAEzCa,EAAK,SAAW/C,EAChBC,EAAM,KAAK8C,CAAI,EAEnB,OAASC,EAAP,CACA,KAAK,IAAI,yCAAyClB,QAAeI,MAAYc,EAAE,SAAS,CAC1F,CAGJ,OAAO/C,CACT,CAEA,MAAM,MAAMD,EAAiBU,EAA+B,CAE1D,GAAM,CAAE,QAAAwB,EAAS,IAAAe,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQxC,CAAM,EAE5Cb,EAAO,aAAa,mBAAmBqC,CAAO,KAAKgB,IAAS,QAAU,SAAW,oBAAoBD,yCAC3G,GAAI,CACF,MAAM,KAAK,IAAIpD,EAAM,CAAE,OAAQ,KAAM,CAAC,CACxC,OAASmD,EAAP,CACA,KAAK,IAAI,yCAAyCA,EAAE,SAAS,CAC/D,CACF,CAEA,MAAM,OAAOtC,EAAgBC,EAAmC,CAC9D,GAAM,CAAE,QAAAuB,EAAS,IAAAe,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQxC,CAAM,EAC5CyC,EAAMD,IAAS,QAAU,SAAW,iBAG1C,GAAIvC,EAAO,aAAeA,EAAO,YAAc,EAAG,CAChD,IAAMyC,EAAO,KAAK,IAAI,EAAG,KAAK,MAAMzC,EAAO,YAAc,EAAE,CAAC,EACtD0C,EAAMD,GAAQ,GAAK,GAAG,KAAK,MAAMA,EAAO,EAAE,KAAKA,EAAO,GAAK,IAAIA,EAAO,MAAQ,KAAO,GAAGA,KAC9F,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmBlB,CAAO,KAAKiB,KAAOF,6BAA+B,mBAAmBI,CAAG,IACxG,CAAE,OAAQ,MAAO,CACnB,CACF,OAASL,EAAP,CACA,KAAK,IAAI,qCAAqCA,EAAE,SAAS,CAC3D,EAIF,GAAIrC,EAAO,SAAW,OACpB,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmBuB,CAAO,KAAKiB,KAAOF,wCACnD,CAAE,OAAQ,KAAM,CAClB,CACF,OAASD,EAAP,CACA,KAAK,IAAI,wCAAwCA,EAAE,SAAS,CAC9D,SACSrC,EAAO,SAAW,UAC3B,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmBuB,CAAO,KAAKiB,KAAOF,uBACnD,CAAE,OAAQ,KAAM,CAClB,CACF,OAASD,EAAP,CACA,KAAK,IAAI,2CAA2CA,EAAE,SAAS,CACjE,CAIF,GAAIrC,EAAO,KACT,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmBuB,CAAO,KAAKiB,KAAOF,UACnD,CAAE,OAAQ,OAAQ,KAAM,KAAK,UAAU,CAAE,KAAMtC,EAAO,IAAK,CAAC,CAAE,CAChE,CACF,OAASqC,EAAP,CACA,KAAK,IAAI,kCAAkCA,EAAE,SAAS,CACxD,CAEJ,CAEA,MAAM,QAAQM,EAAoB,CAAC,EAAwB,CACzD,IAAMC,EAAYD,EAAK,WAAa,GAC9BE,EAAe,IAAI,KAAK,KAAK,IAAI,EAAID,EAAY,GAAK,IAAQ,EAAE,YAAY,EAE5EE,EAAS,IAAI,gBACbC,EAAQJ,EAAK,OAAS,SAC5BG,EAAO,IAAI,QAASC,CAAK,EAIzBD,EAAO,IAAI,gBAAiBD,CAAY,EACxCC,EAAO,IAAI,QAAS,KAAK,EACzBA,EAAO,IAAI,WAAY,KAAK,EAC5BA,EAAO,IAAI,sBAAuB,MAAM,EACxCA,EAAO,IAAI,WAAY,YAAY,EACnCA,EAAO,IAAI,OAAQ,MAAM,EACrBH,EAAK,QAAQ,QAAQG,EAAO,IAAI,SAAUH,EAAK,OAAO,KAAK,GAAG,CAAC,EAC/DA,EAAK,WAAWG,EAAO,IAAI,YAAaH,EAAK,SAAS,EACtDA,EAAK,QAAQG,EAAO,IAAI,SAAUH,EAAK,MAAM,EAEjD,IAAMV,EAAc,KAAK,SAAS,OAAS,KAAK,SAAW,CAAC,SAAS,EAC/D3C,EAAoB,CAAC,EAC3B,QAAWiC,KAAWU,EACpB,GAAI,CACF,IAAM/C,EAAOqC,IAAY,UACrB,WAAWuB,EAAO,SAAS,IAC3B,aAAa,mBAAmBvB,CAAO,YAAYuB,EAAO,SAAS,IACjEX,EAAS,MAAM,KAAK,IAAIjD,CAAI,EAClC,QAAWoC,KAASa,EAAQ7C,EAAM,KAAK,KAAK,SAASgC,EAAOC,CAAO,CAAC,CACtE,OAASc,EAAP,CACA,KAAK,IAAI,uCAAuCd,MAAYc,EAAE,SAAS,CACzE,CAKF,IAAIW,EAAS1D,EACb,GAAIqD,EAAK,WAAW,OAAQ,CAC1B,IAAMM,EAAO,IAAI,IAAIN,EAAK,SAAS,EACnCK,EAASA,EAAO,OAAQzD,GAAM,EAAEA,EAAE,QAAU,CAAC,GAAG,KAAMoC,GAAMsB,EAAK,IAAItB,CAAC,CAAC,CAAC,EAE1E,GAAIgB,EAAK,oBAAqB,CAC5B,IAAMO,EAASP,EAAK,oBAAsB,KAC1CK,EAASA,EAAO,OAAQzD,GAAM,EAAEA,EAAE,QAAU,CAAC,GAAG,KAAMoC,GAAMA,EAAE,WAAWuB,CAAM,CAAC,CAAC,EAEnF,OAAOF,CACT,CAGA,MAAM,SAASjD,EAAgBoD,EAAuC,CACpE,GAAM,CAAE,QAAA5B,EAAS,IAAAe,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQxC,CAAM,EAC5CyC,EAAMD,IAAS,QAAU,SAAW,iBAC1C,MAAM,KAAK,IACT,aAAa,mBAAmBhB,CAAO,KAAKiB,KAAOF,iBAAmBa,IACtE,CAAE,OAAQ,KAAM,CAClB,CACF,CAGA,MAAM,QAAQpD,EAA4C,CACxD,GAAM,CAAE,QAAAwB,EAAS,IAAAe,CAAI,EAAI,KAAK,QAAQvC,CAAM,EAC5C,GAAI,CACF,IAAMuB,EAAQ,MAAM,KAAK,IACvB,aAAa,mBAAmBC,CAAO,YAAYe,4BACrD,EAEA,MAAO,CACL,GAFW,KAAK,SAAShB,EAAOC,CAAO,EAGvC,gBAAiBD,EAAM,kBAAoB,OAC3C,UAAWA,EAAM,WACjB,OAAQA,EAAM,OAAS,CACrB,SAAUA,EAAM,OAAO,SAAU,KAAMA,EAAM,OAAO,KAAM,UAAWA,EAAM,OAAO,UACpF,EAAI,OACJ,UAAW,MAAM,QAAQA,EAAM,MAAM,EAChC,OAAOA,EAAM,OAAO,CAAC,GAAM,SAAWA,EAAM,OAAO,IAAKK,GAAWA,EAAE,IAAI,EAAIL,EAAM,OACpF,CAAC,CACP,CACF,OAASe,EAAP,CACA,YAAK,IAAI,qCAAqCtC,MAAWsC,EAAE,SAAS,EAC7D,IACT,CACF,CAGA,MAAM,WACJtC,EACAqD,EASmB,CACnB,GAAM,CAAE,QAAA7B,EAAS,IAAAe,CAAI,EAAI,KAAK,QAAQvC,CAAM,EAGxCsD,EACAD,EAAM,oBAORC,GANgB,MAAM,QAAQ,IAAID,EAAM,kBAAkB,IAAI,MAAOE,GAAM,CACzE,GAAI,CACF,IAAMC,EAAQ,MAAM,KAAK,IAAI,mBAAmB,mBAAmBD,CAAC,GAAG,EACvE,OAAO,MAAM,QAAQC,CAAK,GAAKA,EAAM,CAAC,GAAG,GAAKA,EAAM,CAAC,EAAE,GAAK,IAC9D,MAAE,CAAQ,OAAO,IAAK,CACxB,CAAC,CAAC,GACoB,OAAQC,GAAmB,OAAOA,GAAM,QAAQ,GAIxE,IAAIC,EACJ,GAAIL,EAAM,iBAAmB,OAC3B,GAAIA,EAAM,iBAAmB,MAAQA,EAAM,iBAAmB,GAC5DK,EAAc,MAEd,IAAI,CACF,IAAMC,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmBnC,CAAO,sBAAsB,mBAAmB6B,EAAM,cAAc,GACtG,EACI,MAAM,QAAQM,CAAE,GAAKA,EAAG,CAAC,GAAG,KAAID,EAAcC,EAAG,CAAC,EAAE,GAC1D,OAASrB,EAAP,CACA,KAAK,IAAI,wCAAwCe,EAAM,oBAAoBf,EAAE,SAAS,CACxF,CAIJ,IAAM5C,EAAgC,CAAC,EACnC2D,EAAM,QAAU,SAAW3D,EAAK,MAAQ2D,EAAM,OAC9CA,EAAM,cAAgB,SAAW3D,EAAK,YAAc2D,EAAM,aAC1DA,EAAM,SAAW,SAAW3D,EAAK,OAAS2D,EAAM,OAAO,KAAK,GAAG,GAC/DA,EAAM,WAAW,SAAQ3D,EAAK,WAAa2D,EAAM,UAAU,KAAK,GAAG,GACnEA,EAAM,cAAc,SAAQ3D,EAAK,cAAgB2D,EAAM,aAAa,KAAK,GAAG,GAC5EC,IAAgB,SAAW5D,EAAK,aAAe4D,GAC/CI,IAAgB,SAAWhE,EAAK,aAAegE,GAEnD,IAAMnC,EAAQ,MAAM,KAAK,IAAI,aAAa,mBAAmBC,CAAO,YAAYe,IAAO,CACrF,OAAQ,MACR,KAAM,KAAK,UAAU7C,CAAI,CAC3B,CAAC,EACD,OAAO,KAAK,SAAS6B,EAAOC,CAAO,CACrC,CAGA,MAAM,aAAqC,CACzC,IAAMoC,EAAa,IAAI,IACvB,QAAWpC,KAAW,KAAK,SACzB,GAAI,CACF,IAAMqC,EAAU,MAAM,KAAK,IACzB,aAAa,mBAAmBrC,CAAO,4BACzC,EACA,QAAW/B,KAAKoE,EACVpE,EAAE,UAAY,CAACmE,EAAW,IAAInE,EAAE,QAAQ,GAC1CmE,EAAW,IAAInE,EAAE,SAAU,CACzB,SAAUA,EAAE,SAAU,KAAMA,EAAE,KAAM,UAAWA,EAAE,UACnD,CAAC,CAGP,OAAS6C,EAAP,CACA,KAAK,IAAI,kCAAkCd,MAAYc,EAAE,SAAS,CACpE,CAEF,MAAO,CAAC,GAAGsB,EAAW,OAAO,CAAC,EAAE,KAAK,CAACE,EAAGC,IAAMD,EAAE,SAAS,cAAcC,EAAE,QAAQ,CAAC,CACrF,CAGA,MAAM,gBAA2C,CAC/C,IAAMC,EAAU,IAAI,IACpB,QAAWxC,KAAW,KAAK,SACzB,GAAI,CACF,IAAMmC,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmBnC,CAAO,2BACzC,EACA,QAAW/B,KAAKkE,EACVlE,EAAE,OAAS,CAACuE,EAAQ,IAAIvE,EAAE,KAAK,GACjCuE,EAAQ,IAAIvE,EAAE,MAAO,CACnB,GAAIA,EAAE,GAAI,MAAOA,EAAE,MAAO,MAAOA,EAAE,MAAO,QAASA,EAAE,QACvD,CAAC,CAGP,OAAS6C,EAAP,CACA,KAAK,IAAI,qCAAqCd,MAAYc,EAAE,SAAS,CACvE,CAEF,IAAM2B,EAAM,CAAC,GAAGD,EAAQ,OAAO,CAAC,EAChC,OAAAC,EAAI,KAAK,CAACH,EAAGC,IACPD,EAAE,QAAUC,EAAE,MAAcD,EAAE,QAAU,SAAW,GAAK,EACrDA,EAAE,MAAM,cAAcC,EAAE,KAAK,CACrC,EACME,CACT,CAEA,MAAM,OAAOC,EAA2C,CACtD,IAAM1C,EAAU0C,EAAM,aAAe,KAAK,SAAS,CAAC,EACpD,GAAI,CAAC1C,EAAS,MAAM,IAAI,MAAM,qEAAqE,EAInG,IAAI2C,EACElC,EAAYiC,EAAM,mBAAmB,OACvCA,EAAM,kBACLA,EAAM,UAAY,KAAK,eAAeA,EAAM,QAAQ,GAAK,CAAC,GAAG,MAAM,EAAG,CAAC,EAAI,CAAC,EAC7EjC,EAAU,SAUZkC,GATgB,MAAM,QAAQ,IAAIlC,EAAU,IAAI,MAAOsB,GAAM,CAC3D,GAAI,CACF,IAAMC,EAAQ,MAAM,KAAK,IAAI,mBAAmB,mBAAmBD,CAAC,GAAG,EACvE,OAAO,MAAM,QAAQC,CAAK,GAAKA,EAAM,CAAC,GAAG,GAAKA,EAAM,CAAC,EAAE,GAAK,IAC9D,OAASlB,EAAP,CACA,YAAK,IAAI,kCAAkCiB,MAAMjB,EAAE,SAAS,EACrD,IACT,CACF,CAAC,CAAC,GACqB,OAAQmB,GAAmB,OAAOA,GAAM,QAAQ,GAIzE,IAAIW,EACJ,GAAIF,EAAM,eACR,GAAI,CACF,IAAMP,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmBnC,CAAO,sBAAsB,mBAAmB0C,EAAM,cAAc,GACtG,EACI,MAAM,QAAQP,CAAE,GAAKA,EAAG,CAAC,GAAG,KAAIS,EAAeT,EAAG,CAAC,EAAE,GAC3D,OAASrB,EAAP,CACA,KAAK,IAAI,wCAAwC4B,EAAM,oBAAoB5B,EAAE,SAAS,CACxF,CAGF,IAAM5C,EAAgC,CACpC,MAAOwE,EAAM,MACb,YAAaA,EAAM,YACnB,QAASA,EAAM,QAAU,CAAC,GAAG,KAAK,GAAG,CACvC,EACIC,GAAc,SAAQzE,EAAK,aAAeyE,GAC1CC,IAAiB,SAAW1E,EAAK,aAAe0E,GAEpD,IAAM7C,EAAQ,MAAM,KAAK,IAAI,aAAa,mBAAmBC,CAAO,WAAY,CAC9E,OAAQ,OACR,KAAM,KAAK,UAAU9B,CAAI,CAC3B,CAAC,EACD,OAAO,KAAK,SAAS6B,EAAOC,CAAO,CACrC,CAEA,MAAM,WAAWxB,EAAgBqE,EAAiBC,EAAmC,CAEnF,MAAM,KAAK,eAAetE,EAAQqE,EAAU,CAACA,CAAO,EAAI,CAAC,EAAGC,EAAY,CAACA,CAAS,EAAI,CAAC,CAAC,CAC1F,CAGA,MAAM,eAAetE,EAAgBuE,EAAeC,EAAiC,CACnF,GAAM,CAAE,QAAAhD,EAAS,IAAAe,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQxC,CAAM,EAC5CyC,EAAMD,IAAS,QAAU,SAAW,iBACpCO,EAAS,IAAI,gBACfwB,EAAI,QAAQxB,EAAO,IAAI,aAAcwB,EAAI,KAAK,GAAG,CAAC,EAClDC,EAAO,QAAQzB,EAAO,IAAI,gBAAiByB,EAAO,KAAK,GAAG,CAAC,EAC1DzB,EAAO,SAAS,GACrB,MAAM,KAAK,IACT,aAAa,mBAAmBvB,CAAO,KAAKiB,KAAOF,KAAOQ,EAAO,SAAS,IAC1E,CAAE,OAAQ,KAAM,CAClB,CACF,CAEQ,QAAQ/C,EAAwE,CAGtF,IAAMa,EADOb,EAAO,QAAQ,WAAY,EAAE,EACvB,MAAM,GAAG,EACtBuC,EAAM1B,EAAM,IAAI,EAChB2B,EAAO3B,EAAM,IAAI,IAAM,KAAO,KAAO,QAE3C,MAAO,CAAE,QADOA,EAAM,KAAK,GAAG,EACZ,KAAA2B,EAAM,IAAAD,CAAI,CAC9B,CACF,EAIO,SAASkC,EACdC,EACAC,EACAxD,EACY,CACZ,IAAMyD,EAAyBF,EAAS,WACxC,OAAQE,EAAG,KAAM,CACf,IAAK,UACH,OAAO,IAAI1F,EAAkB0F,EAAG,IAAI,EAEtC,IAAK,OACH,OAAO,IAAIvE,EAAeuE,EAAG,KAAMA,EAAG,IAAI,EAE5C,IAAK,SAAU,CACb,IAAMC,EAAKF,EAAO,SAAS,OAC3B,GAAI,CAACE,GAAI,MACP,MAAM,IAAI,MAAM,gEAAgE,EAElF,IAAM3D,EAA2C,CAAC,EAClD,QAAW4D,KAAWD,EAAG,cACnBC,EAAQ,gBAAgB,SAC1B5D,EAAe4D,EAAQ,OAAO,EAAIA,EAAQ,iBAG9C,IAAM7D,EAAW2D,EAAG,SAAS,OACzBA,EAAG,SACHC,EAAG,OAAO,IAAK,GAAM,EAAE,OAAO,EAClC,OAAO,IAAI/D,EAAiB+D,EAAG,KAAMA,EAAG,MAAO5D,EAAUC,EAAgBC,CAAG,CAC9E,CACF,CACF","names":["readFileSync","writeFileSync","existsSync","readdirSync","statSync","resolve","join","CHECK_RE","MENTION_RE","TIME_RE","parseDuration","s","hMatch","mMatch","BacklogWorkSource","path","full","lines","agentId","items","i","m","body","mention","timeMatch","title","_agentId","_itemId","itemId","update","lineNo","line","note","WikiWorkSource","root","glob","dir","out","entry","files","file","parts","GitLabWorkSource","host","token","projects","agentUsernames","log","username","names","n","issue","project","ns","labels","labelDetails","l","assignees","firstUsername","init","res","usernames","projectList","q","issues","item","e","iid","type","seg","mins","dur","opts","sinceDays","updatedAfter","params","state","result","deny","prefix","to","patch","assigneeIds","u","users","x","milestoneId","ms","byUsername","members","a","b","byTitle","all","input","assignee_ids","milestone_id","toLabel","fromLabel","add","remove","createWorkSource","business","daemon","ws","gl","mapping"]}
@@ -17,7 +17,7 @@ SKIP:
17
17
  For each fact, output a JSON array:
18
18
  [{"category":"fact|secret|preference|commitment|task-state","content":"concise fact, 1-2 sentences","keywords":["keyword1","keyword2"]}]
19
19
 
20
- If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-G3WZ4RVJ.js"),c=(await i("claude").generate([{role:"system",content:f},{role:"user",content:`${n.sender} (via ${n.channel}): ${s}
20
+ If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-VUIWO3ZK.js"),c=(await i("claude").generate([{role:"system",content:f},{role:"user",content:`${n.sender} (via ${n.channel}): ${s}
21
21
 
22
22
  Agent: ${a}`}],{model:m,maxTokens:512})).content.trim().match(/\[[\s\S]*\]/);if(!c)return;let o=JSON.parse(c[0]);if(!Array.isArray(o)||o.length===0)return;let d=new Date().toISOString().slice(0,10);for(let e of o)r.hasSimilar(t,e.content)||r.addMemory(t,{agentId:t,category:e.category,content:e.content,keywords:e.keywords||[],source:{...n,date:d},expiresAt:e.category==="task-state"?new Date(Date.now()+7*24*60*60*1e3).toISOString():void 0});Math.random()<.02&&r.prune(t)}export{h as a};
23
- //# sourceMappingURL=chunk-ZRYBSI5G.js.map
23
+ //# sourceMappingURL=chunk-7MBTMCKY.js.map