agentix-cli 0.143.0 → 0.145.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{App-UTDCBFZI.js → App-MYHUCIKZ.js} +2 -2
- package/dist/{ChatApp-7P22XAL2.js → ChatApp-D6QN6HJP.js} +2 -2
- package/dist/{absorb-eval-EGO6JJOV.js → absorb-eval-UAGD42BG.js} +2 -2
- package/dist/{agent-AI6LNZV2.js → agent-HEGEDDUE.js} +2 -2
- package/dist/{board-dashboard-ZKN5Y3GJ.js → board-dashboard-4KIFLAFO.js} +4 -4
- package/dist/{chunk-5BXAKMYK.js → chunk-42BEORFZ.js} +2 -2
- package/dist/chunk-4NKPVTVP.js +62 -0
- package/dist/chunk-4NKPVTVP.js.map +1 -0
- package/dist/{chunk-YYPTXXCJ.js → chunk-5E37FADJ.js} +2 -2
- package/dist/{chunk-QSKG754X.js → chunk-5TTHN7DP.js} +12 -8
- package/dist/chunk-5TTHN7DP.js.map +1 -0
- package/dist/{chunk-XWHD2Z3Z.js → chunk-BPOMIEEE.js} +2 -2
- package/dist/chunk-DG2DC43E.js +11 -0
- package/dist/chunk-DG2DC43E.js.map +1 -0
- package/dist/{chunk-AJUWIBLV.js → chunk-EV76NT3E.js} +2 -2
- package/dist/{chunk-O2CYERSX.js → chunk-GMVWDQFF.js} +2 -2
- package/dist/{chunk-GB4YT5IG.js → chunk-HCSIM2QW.js} +294 -287
- package/dist/chunk-HCSIM2QW.js.map +1 -0
- package/dist/{chunk-6CTQPGBR.js → chunk-HP2NWDCS.js} +2 -2
- package/dist/{chunk-IABJLTZN.js → chunk-HSRNH4NR.js} +2 -2
- package/dist/{chunk-PYI7ZAU3.js → chunk-JTDQO4LI.js} +2 -2
- package/dist/{chunk-RB5W3FVO.js → chunk-KAGLCJA3.js} +2 -2
- package/dist/{chunk-NYWIN4BW.js → chunk-LJ62ABOZ.js} +2 -2
- package/dist/{chunk-YXFTUOZR.js → chunk-LLJZMS23.js} +2 -2
- package/dist/{chunk-UNIXYV3U.js → chunk-MMBFAVBR.js} +2 -2
- package/dist/{chunk-7H6HDN3K.js → chunk-OFJKTKRM.js} +2 -2
- package/dist/{chunk-THWDSPX4.js → chunk-P42M6CYF.js} +2 -2
- package/dist/{chunk-N2HLL25P.js → chunk-QXPMWP4R.js} +2 -2
- package/dist/{chunk-WY5ZOEOE.js → chunk-SANYTBHX.js} +2 -2
- package/dist/{chunk-HPQ3AAG3.js → chunk-SH5TNMS5.js} +2 -2
- package/dist/chunk-TJZGAKIB.js +4 -0
- package/dist/chunk-TJZGAKIB.js.map +1 -0
- package/dist/{chunk-OMO7UTWW.js → chunk-TM3I6C4V.js} +2 -2
- package/dist/{chunk-L4RCA3OC.js → chunk-VDJZ2Q3M.js} +2 -2
- package/dist/{chunk-QVPBSV5Z.js → chunk-VMMCT27Z.js} +2 -2
- package/dist/{chunk-ERG5OUKH.js → chunk-VTVEJ3RA.js} +2 -2
- package/dist/{chunk-YVNGPYXN.js → chunk-VXBL2DET.js} +58 -55
- package/dist/chunk-VXBL2DET.js.map +1 -0
- package/dist/{chunk-Y4CYQV7G.js → chunk-VY7TBMJU.js} +2 -2
- package/dist/chunk-VY7TBMJU.js.map +1 -0
- package/dist/{chunk-SHEUCITK.js → chunk-WBG6NA7R.js} +2 -2
- package/dist/{chunk-SHEUCITK.js.map → chunk-WBG6NA7R.js.map} +1 -1
- package/dist/{chunk-MFUN3ZS3.js → chunk-WUBSX6S5.js} +2 -2
- package/dist/{chunk-3WBOWXAN.js → chunk-YEFEUSB2.js} +2 -2
- package/dist/{chunk-SDSSRNSU.js → chunk-YUY35PY7.js} +3 -3
- package/dist/{chunk-DKLVB5L6.js → chunk-YWDXRGVC.js} +6 -6
- package/dist/chunk-YWDXRGVC.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{client-E66ELJK7.js → client-ZU5AWBXQ.js} +2 -2
- package/dist/config-Q4MNOHRI.js +2 -0
- package/dist/{config-mutate-QBDKJNHA.js → config-mutate-DVARJ4KM.js} +2 -2
- package/dist/config-mutator-5NYGSUJD.js +2 -0
- package/dist/{contributions-L5XZJ3DP.js → contributions-HKWDGIQV.js} +2 -2
- package/dist/{doctor-3ZUFSE6R.js → doctor-RTVGZP3X.js} +2 -2
- package/dist/{failure-candidates-XZSZP4HT.js → failure-candidates-NQX2JMMS.js} +2 -2
- package/dist/{follow-up-tool-6OCQUSC3.js → follow-up-tool-2T4SIFY2.js} +2 -2
- package/dist/{grants-CEUGZBQ4.js → grants-O7SJ2J4B.js} +2 -2
- package/dist/{hub-MOQRSVCQ.js → hub-3LDKRLTH.js} +2 -2
- package/dist/index.d.ts +295 -76
- package/dist/index.js +1 -1
- package/dist/{program-AR6F763U.js → program-MAM4NX5P.js} +289 -254
- package/dist/program-MAM4NX5P.js.map +1 -0
- package/dist/{promote-ZGJEPCBE.js → promote-YOV5ELNF.js} +2 -2
- package/dist/{query-JLTHESIZ.js → query-LTKK3M2Y.js} +2 -2
- package/dist/{query-settings-GMEL3EKR.js → query-settings-ACXSGMDU.js} +2 -2
- package/dist/{registry-MFD3XJBR.js → registry-NIAPKDQF.js} +2 -2
- package/dist/{request-planner-4ORFYSAT.js → request-planner-433C7M3L.js} +2 -2
- package/dist/{review-candidates-77NDFTGM.js → review-candidates-SI3QBYNH.js} +2 -2
- package/dist/{routing-3FL3KYPJ.js → routing-CXT77HBF.js} +2 -2
- package/dist/schedule-tool-3R4ISM5R.js +2 -0
- package/dist/{scheduler-QLL7ZHYW.js → scheduler-SFHWPWCA.js} +2 -2
- package/dist/{seat-RBKONP63.js → seat-CJVTUIM2.js} +2 -2
- package/dist/{select-MOUDR6PQ.js → select-JHGDG4H2.js} +2 -2
- package/dist/{set-enabled-JKYF3QIA.js → set-enabled-NOJGCJAL.js} +2 -2
- package/dist/{tool-Y3ZTSZGE.js → tool-ERK543LL.js} +3 -3
- package/dist/{tool-Y3ZTSZGE.js.map → tool-ERK543LL.js.map} +1 -1
- package/dist/tool-YOSMTSJM.js +6 -0
- package/dist/tool-YOSMTSJM.js.map +1 -0
- package/dist/traces-GVEQZX24.js +2 -0
- package/dist/{wiki-3FSSLQAE.js → wiki-2LLXQUEP.js} +2 -2
- package/dist/wiki-enrich-6C2O2MNV.js +2 -0
- package/dist/wiki-enrich-6C2O2MNV.js.map +1 -0
- package/dist/workflow-pilot-HCPSMGIC.js +2 -0
- package/dist/workflow-pilot-HCPSMGIC.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-3W2BHUGM.js +0 -11
- package/dist/chunk-3W2BHUGM.js.map +0 -1
- package/dist/chunk-AKWBDCMU.js +0 -4
- package/dist/chunk-AKWBDCMU.js.map +0 -1
- package/dist/chunk-DKLVB5L6.js.map +0 -1
- package/dist/chunk-GB4YT5IG.js.map +0 -1
- package/dist/chunk-K22XMF6M.js +0 -62
- package/dist/chunk-K22XMF6M.js.map +0 -1
- package/dist/chunk-QSKG754X.js.map +0 -1
- package/dist/chunk-Y4CYQV7G.js.map +0 -1
- package/dist/chunk-YVNGPYXN.js.map +0 -1
- package/dist/config-3C3Z7VKG.js +0 -2
- package/dist/config-mutator-ZI3SQIUN.js +0 -2
- package/dist/program-AR6F763U.js.map +0 -1
- package/dist/schedule-tool-5KJMXBRZ.js +0 -2
- package/dist/tool-C4JNMTEF.js +0 -6
- package/dist/tool-C4JNMTEF.js.map +0 -1
- package/dist/wiki-enrich-HQWXZRID.js +0 -2
- /package/dist/{App-UTDCBFZI.js.map → App-MYHUCIKZ.js.map} +0 -0
- /package/dist/{ChatApp-7P22XAL2.js.map → ChatApp-D6QN6HJP.js.map} +0 -0
- /package/dist/{absorb-eval-EGO6JJOV.js.map → absorb-eval-UAGD42BG.js.map} +0 -0
- /package/dist/{agent-AI6LNZV2.js.map → agent-HEGEDDUE.js.map} +0 -0
- /package/dist/{board-dashboard-ZKN5Y3GJ.js.map → board-dashboard-4KIFLAFO.js.map} +0 -0
- /package/dist/{chunk-5BXAKMYK.js.map → chunk-42BEORFZ.js.map} +0 -0
- /package/dist/{chunk-YYPTXXCJ.js.map → chunk-5E37FADJ.js.map} +0 -0
- /package/dist/{chunk-XWHD2Z3Z.js.map → chunk-BPOMIEEE.js.map} +0 -0
- /package/dist/{chunk-AJUWIBLV.js.map → chunk-EV76NT3E.js.map} +0 -0
- /package/dist/{chunk-O2CYERSX.js.map → chunk-GMVWDQFF.js.map} +0 -0
- /package/dist/{chunk-6CTQPGBR.js.map → chunk-HP2NWDCS.js.map} +0 -0
- /package/dist/{chunk-IABJLTZN.js.map → chunk-HSRNH4NR.js.map} +0 -0
- /package/dist/{chunk-PYI7ZAU3.js.map → chunk-JTDQO4LI.js.map} +0 -0
- /package/dist/{chunk-RB5W3FVO.js.map → chunk-KAGLCJA3.js.map} +0 -0
- /package/dist/{chunk-NYWIN4BW.js.map → chunk-LJ62ABOZ.js.map} +0 -0
- /package/dist/{chunk-YXFTUOZR.js.map → chunk-LLJZMS23.js.map} +0 -0
- /package/dist/{chunk-UNIXYV3U.js.map → chunk-MMBFAVBR.js.map} +0 -0
- /package/dist/{chunk-7H6HDN3K.js.map → chunk-OFJKTKRM.js.map} +0 -0
- /package/dist/{chunk-THWDSPX4.js.map → chunk-P42M6CYF.js.map} +0 -0
- /package/dist/{chunk-N2HLL25P.js.map → chunk-QXPMWP4R.js.map} +0 -0
- /package/dist/{chunk-WY5ZOEOE.js.map → chunk-SANYTBHX.js.map} +0 -0
- /package/dist/{chunk-HPQ3AAG3.js.map → chunk-SH5TNMS5.js.map} +0 -0
- /package/dist/{chunk-OMO7UTWW.js.map → chunk-TM3I6C4V.js.map} +0 -0
- /package/dist/{chunk-L4RCA3OC.js.map → chunk-VDJZ2Q3M.js.map} +0 -0
- /package/dist/{chunk-QVPBSV5Z.js.map → chunk-VMMCT27Z.js.map} +0 -0
- /package/dist/{chunk-ERG5OUKH.js.map → chunk-VTVEJ3RA.js.map} +0 -0
- /package/dist/{chunk-MFUN3ZS3.js.map → chunk-WUBSX6S5.js.map} +0 -0
- /package/dist/{chunk-3WBOWXAN.js.map → chunk-YEFEUSB2.js.map} +0 -0
- /package/dist/{chunk-SDSSRNSU.js.map → chunk-YUY35PY7.js.map} +0 -0
- /package/dist/{client-E66ELJK7.js.map → client-ZU5AWBXQ.js.map} +0 -0
- /package/dist/{config-3C3Z7VKG.js.map → config-Q4MNOHRI.js.map} +0 -0
- /package/dist/{config-mutate-QBDKJNHA.js.map → config-mutate-DVARJ4KM.js.map} +0 -0
- /package/dist/{config-mutator-ZI3SQIUN.js.map → config-mutator-5NYGSUJD.js.map} +0 -0
- /package/dist/{contributions-L5XZJ3DP.js.map → contributions-HKWDGIQV.js.map} +0 -0
- /package/dist/{doctor-3ZUFSE6R.js.map → doctor-RTVGZP3X.js.map} +0 -0
- /package/dist/{failure-candidates-XZSZP4HT.js.map → failure-candidates-NQX2JMMS.js.map} +0 -0
- /package/dist/{follow-up-tool-6OCQUSC3.js.map → follow-up-tool-2T4SIFY2.js.map} +0 -0
- /package/dist/{grants-CEUGZBQ4.js.map → grants-O7SJ2J4B.js.map} +0 -0
- /package/dist/{hub-MOQRSVCQ.js.map → hub-3LDKRLTH.js.map} +0 -0
- /package/dist/{promote-ZGJEPCBE.js.map → promote-YOV5ELNF.js.map} +0 -0
- /package/dist/{query-JLTHESIZ.js.map → query-LTKK3M2Y.js.map} +0 -0
- /package/dist/{query-settings-GMEL3EKR.js.map → query-settings-ACXSGMDU.js.map} +0 -0
- /package/dist/{registry-MFD3XJBR.js.map → registry-NIAPKDQF.js.map} +0 -0
- /package/dist/{request-planner-4ORFYSAT.js.map → request-planner-433C7M3L.js.map} +0 -0
- /package/dist/{review-candidates-77NDFTGM.js.map → review-candidates-SI3QBYNH.js.map} +0 -0
- /package/dist/{routing-3FL3KYPJ.js.map → routing-CXT77HBF.js.map} +0 -0
- /package/dist/{schedule-tool-5KJMXBRZ.js.map → schedule-tool-3R4ISM5R.js.map} +0 -0
- /package/dist/{scheduler-QLL7ZHYW.js.map → scheduler-SFHWPWCA.js.map} +0 -0
- /package/dist/{seat-RBKONP63.js.map → seat-CJVTUIM2.js.map} +0 -0
- /package/dist/{select-MOUDR6PQ.js.map → select-JHGDG4H2.js.map} +0 -0
- /package/dist/{set-enabled-JKYF3QIA.js.map → set-enabled-NOJGCJAL.js.map} +0 -0
- /package/dist/{wiki-3FSSLQAE.js.map → traces-GVEQZX24.js.map} +0 -0
- /package/dist/{wiki-enrich-HQWXZRID.js.map → wiki-2LLXQUEP.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{La as l,Ta as m}from"./chunk-4NKPVTVP.js";m();import{existsSync as g,readFileSync as u,writeFileSync as a,copyFileSync as y}from"fs";import{resolve as p}from"path";function P(e,n={}){let t=n.configPath||p(process.cwd(),"agentx.json");if(!g(t))throw new Error(`agentx.json not found at ${t}`);let i=JSON.parse(u(t,"utf-8")),o=e(i),s=typeof o=="string"?{summary:o}:o,c=JSON.stringify(i,null,2)+`
|
|
2
2
|
`,f=t+".tmp";a(f,c,"utf-8");try{JSON.parse(u(f,"utf-8"))}catch(d){throw new Error(`config did not round-trip cleanly: ${d.message}`)}let r;return n.skipBackup||(r=`${t}.bak.${Date.now()}`,y(t,r)),a(t,c,"utf-8"),n.skipReload||h().catch(()=>{}),{summary:s.summary,payload:s.payload,backupPath:r}}function S(e,n={}){let t=n.configPath||p(process.cwd(),"agentx.json");if(g(t)&&!n.overwrite)throw new Error(`agentx.json already exists at ${t}. Pass overwrite=true to replace it.`);return a(t,JSON.stringify(e,null,2)+`
|
|
3
3
|
`,"utf-8"),{file:t}}async function h(){try{let n=l().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${n}/reload`,{method:"POST"}).catch(()=>null)}catch{}}export{P as a,S as b};
|
|
4
|
-
//# sourceMappingURL=chunk-
|
|
4
|
+
//# sourceMappingURL=chunk-5E37FADJ.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as o,b as d}from"./chunk-FCPTN6NF.js";d();var m="[Institutional Wiki";function u(e){return[...new Set(e)].slice(0,20).map(n=>n.slice(0,80))}function p(e,n){let t=e??"";return{memory:u((n.memory??[]).filter(r=>r.content&&t.includes(r.content)).map(r=>r.id)),procedures:u((n.procedures??[]).filter(r=>t.includes(`[Known procedure: ${r.title}]`)).map(r=>r.id)),wiki:!!n.wikiContext&&t.includes(m)}}function c(e){return{memory:u(e.memory),procedures:u(e.procedures),wiki:e.wiki===!0}}function h(e,n,t=o()){return e.prepare(`
|
|
2
2
|
INSERT INTO task_traces (
|
|
3
3
|
task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,
|
|
4
4
|
workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,
|
|
5
5
|
model, status, started_at, message_preview, original_message,
|
|
6
6
|
resume_origin, resume_attempt, resumed_from, person
|
|
7
7
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?, ?, ?, ?, ?)
|
|
8
|
-
`).run(t,n.agentId,n.channel??null,n.chatId??null,n.workflowRunId??null,n.workflowId??null,n.workflowNodeId??null,n.intentEventId??null,n.intentDecidedBy??null,n.resumeSessionId??null,n.model??null,Date.now(),n.messagePreview??null,n.originalMessage??null,n.resumeOrigin??null,n.resumeAttempt??0,n.resumedFrom??null,n.person??null),t}function
|
|
8
|
+
`).run(t,n.agentId,n.channel??null,n.chatId??null,n.workflowRunId??null,n.workflowId??null,n.workflowNodeId??null,n.intentEventId??null,n.intentDecidedBy??null,n.resumeSessionId??null,n.model??null,Date.now(),n.messagePreview??null,n.originalMessage??null,n.resumeOrigin??null,n.resumeAttempt??0,n.resumedFrom??null,n.person??null),t}function E(e,n,t){let r=Date.now();e.prepare(`
|
|
9
9
|
UPDATE task_traces SET
|
|
10
10
|
status = ?,
|
|
11
11
|
final_session_id = ?,
|
|
@@ -24,14 +24,15 @@ import{a as i,b as c}from"./chunk-FCPTN6NF.js";var m="[Institutional Wiki";funct
|
|
|
24
24
|
injected_context = COALESCE(?, injected_context),
|
|
25
25
|
error = ?,
|
|
26
26
|
final_response = COALESCE(?, final_response),
|
|
27
|
+
model = COALESCE(?, model),
|
|
27
28
|
finished_at = ?,
|
|
28
29
|
duration_ms = ? - started_at
|
|
29
30
|
WHERE task_id = ?
|
|
30
|
-
`).run(t.status,t.finalSessionId??null,t.inputTokens??null,t.outputTokens??null,t.cacheReadTokens??null,t.cacheCreateTokens??null,t.tier2InputTokens??null,t.tier2OutputTokens??null,t.tier2CacheReadTokens??null,t.tier2CacheCreateTokens??null,t.resumed==null?null:t.resumed?1:0,t.resumeSessionId??null,t.jevArm??null,t.numTurns??null,t.injectedContext?JSON.stringify(
|
|
31
|
+
`).run(t.status,t.finalSessionId??null,t.inputTokens??null,t.outputTokens??null,t.cacheReadTokens??null,t.cacheCreateTokens??null,t.tier2InputTokens??null,t.tier2OutputTokens??null,t.tier2CacheReadTokens??null,t.tier2CacheCreateTokens??null,t.resumed==null?null:t.resumed?1:0,t.resumeSessionId??null,t.jevArm??null,t.numTurns??null,t.injectedContext?JSON.stringify(c(t.injectedContext)):null,t.error??null,t.finalResponse??null,t.model??null,r,r,n)}function T(e,n,t){let r=Date.now();return e.transaction(()=>{let i=(e.prepare("SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?").get(n)?.max_seq??-1)+1;return e.prepare(`
|
|
31
32
|
INSERT INTO task_trace_steps (
|
|
32
33
|
task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at
|
|
33
34
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
34
|
-
`).run(n,
|
|
35
|
+
`).run(n,i,t.name,t.action??null,t.status??null,t.inputSummary??null,t.outputSummary??null,t.error??null,t.ms??null,r),i})()}function g(e){if(typeof e!="string")return null;try{let n=JSON.parse(e);if(!n||typeof n!="object")return null;let t=r=>Array.isArray(r)?r.filter(s=>typeof s=="string"):[];return{memory:t(n.memory),procedures:t(n.procedures),wiki:n.wiki===!0}}catch{return null}}function l(e){return{taskId:e.task_id,agentId:e.agent_id,channel:e.channel??null,chatId:e.chat_id??null,workflowRunId:e.workflow_run_id??null,workflowId:e.workflow_id??null,workflowNodeId:e.workflow_node_id??null,intentEventId:e.intent_event_id??null,intentDecidedBy:e.intent_decided_by??null,resumeSessionId:e.resume_session_id??null,finalSessionId:e.final_session_id??null,model:e.model??null,status:e.status,startedAt:e.started_at,finishedAt:e.finished_at??null,durationMs:e.duration_ms??null,inputTokens:e.input_tokens??null,outputTokens:e.output_tokens??null,cacheReadTokens:e.cache_read_tokens??null,cacheCreateTokens:e.cache_create_tokens??null,tier2InputTokens:e.tier2_input_tokens??null,tier2OutputTokens:e.tier2_output_tokens??null,tier2CacheReadTokens:e.tier2_cache_read_tokens??null,tier2CacheCreateTokens:e.tier2_cache_create_tokens??null,resumed:e.resumed==null?null:e.resumed===1,jevArm:e.jev_arm??null,numTurns:e.num_turns??null,injectedContext:g(e.injected_context),error:e.error??null,messagePreview:e.message_preview??null,originalMessage:e.original_message??null,finalResponse:e.final_response??null}}function _(e){return{taskId:e.task_id,seq:e.seq,name:e.name,action:e.action??null,status:e.status??null,inputSummary:e.input_summary??null,outputSummary:e.output_summary??null,error:e.error??null,ms:e.ms??null,startedAt:e.started_at}}function R(e,n){let t=e.prepare("SELECT * FROM task_traces WHERE task_id = ?").get(n);if(!t)return null;let r=e.prepare("SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq").all(n);return{task:l(t),steps:r.map(_)}}function b(e){let n=Date.now();return e.prepare(`
|
|
35
36
|
UPDATE task_traces
|
|
36
37
|
SET status = 'canceled',
|
|
37
38
|
error = 'daemon-restart (orphaned in-flight)',
|
|
@@ -52,13 +53,16 @@ import{a as i,b as c}from"./chunk-FCPTN6NF.js";var m="[Institutional Wiki";funct
|
|
|
52
53
|
`);return t().map(s=>({taskId:s.task_id,agentId:s.agent_id,channel:s.channel??null,chatId:s.chat_id??null,workflowRunId:s.workflow_run_id??null,startedAt:s.started_at,originalMessage:s.original_message??null,resumeOrigin:s.resume_origin??null,resumeAttempt:s.resume_attempt??0,toolCalls:r.all(s.task_id).map(a=>({action:a.action??null,inputSummary:a.input_summary??null}))}))}function S(e,n){return(e.prepare(`
|
|
53
54
|
UPDATE task_traces SET resume_decision = 'deciding'
|
|
54
55
|
WHERE task_id = ? AND resume_decision = 'interrupted'
|
|
55
|
-
`).run(n).changes??0)===1}function
|
|
56
|
+
`).run(n).changes??0)===1}function D(e,n,t,r){e.prepare("UPDATE task_traces SET resume_decision = ?, resume_reason = ? WHERE task_id = ?").run(t,r.slice(0,500),n)}function w(e,n={}){let t=[],r=[];n.agentId&&(t.push("agent_id = ?"),r.push(n.agentId)),n.channel&&(t.push("channel = ?"),r.push(n.channel)),n.chatId&&(t.push("chat_id = ?"),r.push(n.chatId)),n.workflowRunId&&(t.push("workflow_run_id = ?"),r.push(n.workflowRunId)),n.status&&(t.push("status = ?"),r.push(n.status)),n.since!==void 0&&(t.push("started_at >= ?"),r.push(n.since)),n.until!==void 0&&(t.push("started_at <= ?"),r.push(n.until));let s=Math.max(1,Math.min(n.limit??100,1e3)),a=`
|
|
56
57
|
SELECT * FROM task_traces
|
|
57
58
|
${t.length>0?"WHERE "+t.join(" AND "):""}
|
|
58
59
|
ORDER BY started_at DESC
|
|
59
60
|
LIMIT ?
|
|
60
|
-
`;return r.push(s),e.prepare(a).all(...r).map(
|
|
61
|
+
`;return r.push(s),e.prepare(a).all(...r).map(l)}function y(e,n){let t=[];for(let r=0;r<n.length;r+=500){let s=n.slice(r,r+500);if(!s.length)continue;let a=e.prepare(`
|
|
62
|
+
SELECT * FROM task_traces WHERE workflow_run_id IN (${s.map(()=>"?").join(",")})
|
|
63
|
+
ORDER BY started_at
|
|
64
|
+
`).all(...s);t.push(...a.map(l))}return t}function A(e,n,t=200){return e.prepare(`
|
|
61
65
|
SELECT action, input_summary FROM task_trace_steps
|
|
62
66
|
WHERE task_id = ? AND name = 'tool_use' ORDER BY seq LIMIT ?
|
|
63
|
-
`).all(n,t).map(s=>({action:s.action??null,inputSummary:s.input_summary??null}))}export{p as a,h as b,
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
67
|
+
`).all(n,t).map(s=>({action:s.action??null,inputSummary:s.input_summary??null}))}export{p as a,h as b,E as c,T as d,g as e,R as f,b as g,C as h,S as i,D as j,w as k,y as l,A as m};
|
|
68
|
+
//# sourceMappingURL=chunk-5TTHN7DP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/traces.ts","../src/agents/injected-context.ts"],"sourcesContent":["import type Database from \"better-sqlite3\"\nimport { newEventId } from \"@/intent/ulid\"\nimport { boundInjectedContext } from \"@/agents/injected-context\"\n\n// --- Task trace store ---\n//\n// Improvement plan #2 — per-task observability backed by SQLite. One\n// task_traces row per executeTask invocation, an append-only stream of\n// task_trace_steps inside it. The store is intentionally dumb: it stores\n// what callers give it. Callers (capture sites in src/agents/runtime.ts)\n// are responsible for byte-capping large step payloads so the daemon\n// memory stays bounded under tool-heavy turns.\n//\n// Why a separate store layer (vs writing inline in runtime.ts)?\n// - Tests can exercise the trace lifecycle against a temp SQLite without\n// spinning up the agent runtime.\n// - The HTTP endpoints (GET /task/:id/trace) read through the same\n// functions producers write through, so there's a single source of\n// truth on field names + semantics.\n// - Future capture sites (sdk tier, orchestrator tier) reuse this API\n// without copying SQL.\n//\n// Why ULID for task_id?\n// - URL-safe in HTTP routes (GET /task/:id/trace).\n// - Time-sortable, so listTraces ORDER BY started_at and ORDER BY\n// task_id agree without an explicit timestamp column index.\n// - Already in-tree (src/intent/ulid.ts); no new dep.\n//\n// Cross-DB FK note:\n// intent_event_id and intent_decided_by are logical foreign keys into\n// the intent ledger at .agentx/intent/ledger.sqlite. better-sqlite3\n// doesn't enforce cross-database FKs and we don't ATTACH the ledger\n// here on purpose — the rescue plan's append-only contract on the\n// ledger is honoured by routing all writes through src/intent/ledger.ts.\n// Joins happen at the HTTP / CLI layer when needed.\n\nexport interface TraceStartInput {\n agentId: string\n channel?: string | null\n chatId?: string | null\n messagePreview?: string | null\n /** Full untruncated user message — used by `agentx trace replay <taskId>`\n * to re-fire a recorded task against the current agent config. NULL on\n * rows from before migration v8, in which case replay falls back to\n * messagePreview (which is capped at 200 chars). */\n originalMessage?: string | null\n workflowRunId?: string | null\n workflowId?: string | null\n workflowNodeId?: string | null\n intentEventId?: string | null\n intentDecidedBy?: string | null\n resumeSessionId?: string | null\n model?: string | null\n /** How to re-enter this run after a restart (JSON, see agents/resume). */\n resumeOrigin?: string | null\n /** 0 for a fresh run; n for the n-th resume of a cut-off run. */\n resumeAttempt?: number | null\n /** The cut-off run this one continues. */\n resumedFrom?: string | null\n /** The known person who started the chain (people, #384). */\n person?: string | null\n}\n\nexport interface TraceEndInput {\n status: \"ok\" | \"error\" | \"timeout\" | \"stopped\"\n finalSessionId?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n cacheReadTokens?: number | null\n cacheCreateTokens?: number | null\n tier2InputTokens?: number | null\n tier2OutputTokens?: number | null\n tier2CacheReadTokens?: number | null\n tier2CacheCreateTokens?: number | null\n /** Whether the turn continued a provider session. The resume decision\n * is made after task:started, so it is recorded at the end. Omitted\n * leaves both resumed and resume_session_id untouched. */\n resumed?: boolean | null\n resumeSessionId?: string | null\n /** Request-gate experiment arm: \"treatment\" | \"holdout\". */\n jevArm?: string | null\n /** Runtime-reported turn count (Claude `num_turns`). */\n numTurns?: number | null\n /** Lessons injected into the prompt. See InjectedContext. */\n injectedContext?: InjectedContext | null\n error?: string | null\n /** The agent's final reply text. Stored verbatim so `replay --diff`\n * can compare original output vs the new run's output without\n * reconstructing it from the step ledger. NULL when the response\n * wasn't captured (older rows or non-text outputs). */\n finalResponse?: string | null\n /** Model the turn was billed on. Known only once the turn ends, so the\n * start row usually has none. Omitted leaves the column untouched. */\n model?: string | null\n}\n\n/** Ids of the lessons injected into one task's prompt. Memory facts and\n * procedures are matched per message, so their ids are recorded; the wiki\n * is injected as a whole catalog, so only its presence is. */\nexport interface InjectedContext {\n memory: string[]\n procedures: string[]\n wiki: boolean\n}\n\nexport interface TraceStepInput {\n /** Coarse step kind. Conventional values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-form\n * strings allowed so future capture sites don't need a schema bump. */\n name: string\n /** Fine-grained name within the kind. For \"tool_use\", this is the tool\n * name (e.g. \"Bash\", \"Edit\"). Optional. */\n action?: string | null\n status?: \"ok\" | \"error\" | \"in-flight\" | null\n /** JSON-stringified input or human-readable summary. Caller-byte-capped. */\n inputSummary?: string | null\n outputSummary?: string | null\n error?: string | null\n /** Step duration when known. Optional — many step kinds are point-in-time. */\n ms?: number | null\n}\n\nexport interface TraceRecord {\n taskId: string\n agentId: string\n channel: string | null\n chatId: string | null\n workflowRunId: string | null\n workflowId: string | null\n workflowNodeId: string | null\n intentEventId: string | null\n intentDecidedBy: string | null\n resumeSessionId: string | null\n finalSessionId: string | null\n model: string | null\n status: string\n startedAt: number\n finishedAt: number | null\n durationMs: number | null\n inputTokens: number | null\n outputTokens: number | null\n cacheReadTokens: number | null\n cacheCreateTokens: number | null\n tier2InputTokens: number | null\n tier2OutputTokens: number | null\n tier2CacheReadTokens: number | null\n tier2CacheCreateTokens: number | null\n /** NULL on rows recorded before migration v12. */\n resumed: boolean | null\n /** NULL when the request gate was not active (or before migration v13). */\n jevArm: string | null\n /** NULL on rows recorded before lesson-impact capture (#98) or when the\n * runtime does not report a turn count. */\n numTurns: number | null\n /** NULL on rows recorded before lesson-impact capture (#98). */\n injectedContext: InjectedContext | null\n error: string | null\n messagePreview: string | null\n /** Full untruncated user message — populated for traces from migration v8\n * onward. NULL on older rows; consumers should fall back to messagePreview. */\n originalMessage: string | null\n /** Agent's final reply text — populated for traces from migration v8\n * onward when the executor records it on end. */\n finalResponse: string | null\n}\n\nexport interface TraceStepRecord {\n taskId: string\n seq: number\n name: string\n action: string | null\n status: string | null\n inputSummary: string | null\n outputSummary: string | null\n error: string | null\n ms: number | null\n startedAt: number\n}\n\nexport interface ListTracesFilters {\n agentId?: string\n channel?: string\n chatId?: string\n workflowRunId?: string\n status?: string\n /** ms epoch */\n since?: number\n /** ms epoch */\n until?: number\n /** Default 100, capped at 1000. */\n limit?: number\n}\n\n/**\n * Insert an in-flight trace row and return its ULID. Callers thread the\n * returned id through the executor and call recordTraceEnd at finish time.\n *\n * `taskId` is optional — when supplied, the caller has already allocated\n * a ULID upstream (e.g. registry.execute generating it before the bus\n * event so the runtime can capture per-step rows under the same id).\n * When omitted, a fresh ULID is allocated here.\n */\nexport function recordTraceStart(\n db: Database.Database,\n input: TraceStartInput,\n taskId: string = newEventId(),\n): string {\n db.prepare(`\n INSERT INTO task_traces (\n task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,\n workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,\n model, status, started_at, message_preview, original_message,\n resume_origin, resume_attempt, resumed_from, person\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?, ?, ?, ?, ?)\n `).run(\n taskId,\n input.agentId,\n input.channel ?? null,\n input.chatId ?? null,\n input.workflowRunId ?? null,\n input.workflowId ?? null,\n input.workflowNodeId ?? null,\n input.intentEventId ?? null,\n input.intentDecidedBy ?? null,\n input.resumeSessionId ?? null,\n input.model ?? null,\n Date.now(),\n input.messagePreview ?? null,\n input.originalMessage ?? null,\n input.resumeOrigin ?? null,\n input.resumeAttempt ?? 0,\n input.resumedFrom ?? null,\n input.person ?? null,\n )\n return taskId\n}\n\n/**\n * Finalize a trace. duration_ms is computed from finished_at - started_at\n * inside the UPDATE so the two columns can never disagree. Idempotent —\n * calling twice on the same task_id overwrites the prior end values; the\n * second call's status wins. (Soft-overwrite is intentional: timeout\n * handlers may write before a late stream-completion arrives.)\n */\nexport function recordTraceEnd(db: Database.Database, taskId: string, input: TraceEndInput): void {\n const finishedAt = Date.now()\n db.prepare(`\n UPDATE task_traces SET\n status = ?,\n final_session_id = ?,\n input_tokens = ?,\n output_tokens = ?,\n cache_read_tokens = ?,\n cache_create_tokens = ?,\n tier2_input_tokens = ?,\n tier2_output_tokens = ?,\n tier2_cache_read_tokens = ?,\n tier2_cache_create_tokens = ?,\n resumed = COALESCE(?, resumed),\n resume_session_id = COALESCE(?, resume_session_id),\n jev_arm = COALESCE(?, jev_arm),\n num_turns = COALESCE(?, num_turns),\n injected_context = COALESCE(?, injected_context),\n error = ?,\n final_response = COALESCE(?, final_response),\n model = COALESCE(?, model),\n finished_at = ?,\n duration_ms = ? - started_at\n WHERE task_id = ?\n `).run(\n input.status,\n input.finalSessionId ?? null,\n input.inputTokens ?? null,\n input.outputTokens ?? null,\n input.cacheReadTokens ?? null,\n input.cacheCreateTokens ?? null,\n input.tier2InputTokens ?? null,\n input.tier2OutputTokens ?? null,\n input.tier2CacheReadTokens ?? null,\n input.tier2CacheCreateTokens ?? null,\n input.resumed == null ? null : input.resumed ? 1 : 0,\n input.resumeSessionId ?? null,\n input.jevArm ?? null,\n input.numTurns ?? null,\n input.injectedContext ? JSON.stringify(boundInjectedContext(input.injectedContext)) : null,\n input.error ?? null,\n input.finalResponse ?? null,\n input.model ?? null,\n finishedAt,\n finishedAt,\n taskId,\n )\n}\n\n/**\n * Append a step. seq is auto-allocated as MAX(seq)+1 within a transaction\n * so concurrent step writes for the same task_id can't collide. Returns\n * the assigned seq, useful for downstream correlation.\n */\nexport function recordTraceStep(db: Database.Database, taskId: string, input: TraceStepInput): number {\n const startedAt = Date.now()\n const allocateAndInsert = db.transaction((): number => {\n const row = db\n .prepare(\"SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?\")\n .get(taskId) as { max_seq: number | null } | undefined\n const seq = ((row?.max_seq ?? -1) as number) + 1\n db.prepare(`\n INSERT INTO task_trace_steps (\n task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n taskId,\n seq,\n input.name,\n input.action ?? null,\n input.status ?? null,\n input.inputSummary ?? null,\n input.outputSummary ?? null,\n input.error ?? null,\n input.ms ?? null,\n startedAt,\n )\n return seq\n })\n return allocateAndInsert()\n}\n\nexport function parseInjectedContext(raw: unknown): InjectedContext | null {\n if (typeof raw !== \"string\") return null\n try {\n const v = JSON.parse(raw)\n if (!v || typeof v !== \"object\") return null\n const ids = (x: unknown) => (Array.isArray(x) ? x.filter((i): i is string => typeof i === \"string\") : [])\n return { memory: ids(v.memory), procedures: ids(v.procedures), wiki: v.wiki === true }\n } catch {\n return null\n }\n}\n\nfunction rowToTrace(row: Record<string, unknown>): TraceRecord {\n return {\n taskId: row.task_id as string,\n agentId: row.agent_id as string,\n channel: (row.channel as string) ?? null,\n chatId: (row.chat_id as string) ?? null,\n workflowRunId: (row.workflow_run_id as string) ?? null,\n workflowId: (row.workflow_id as string) ?? null,\n workflowNodeId: (row.workflow_node_id as string) ?? null,\n intentEventId: (row.intent_event_id as string) ?? null,\n intentDecidedBy: (row.intent_decided_by as string) ?? null,\n resumeSessionId: (row.resume_session_id as string) ?? null,\n finalSessionId: (row.final_session_id as string) ?? null,\n model: (row.model as string) ?? null,\n status: row.status as string,\n startedAt: row.started_at as number,\n finishedAt: (row.finished_at as number) ?? null,\n durationMs: (row.duration_ms as number) ?? null,\n inputTokens: (row.input_tokens as number) ?? null,\n outputTokens: (row.output_tokens as number) ?? null,\n cacheReadTokens: (row.cache_read_tokens as number) ?? null,\n cacheCreateTokens: (row.cache_create_tokens as number) ?? null,\n tier2InputTokens: (row.tier2_input_tokens as number) ?? null,\n tier2OutputTokens: (row.tier2_output_tokens as number) ?? null,\n tier2CacheReadTokens: (row.tier2_cache_read_tokens as number) ?? null,\n tier2CacheCreateTokens: (row.tier2_cache_create_tokens as number) ?? null,\n resumed: row.resumed == null ? null : row.resumed === 1,\n jevArm: (row.jev_arm as string) ?? null,\n numTurns: (row.num_turns as number) ?? null,\n injectedContext: parseInjectedContext(row.injected_context),\n error: (row.error as string) ?? null,\n messagePreview: (row.message_preview as string) ?? null,\n originalMessage: (row.original_message as string) ?? null,\n finalResponse: (row.final_response as string) ?? null,\n }\n}\n\nfunction rowToStep(row: Record<string, unknown>): TraceStepRecord {\n return {\n taskId: row.task_id as string,\n seq: row.seq as number,\n name: row.name as string,\n action: (row.action as string) ?? null,\n status: (row.status as string) ?? null,\n inputSummary: (row.input_summary as string) ?? null,\n outputSummary: (row.output_summary as string) ?? null,\n error: (row.error as string) ?? null,\n ms: (row.ms as number) ?? null,\n startedAt: row.started_at as number,\n }\n}\n\n/** Fetch a trace + its ordered steps. Returns null if no such task_id. */\nexport function getTrace(\n db: Database.Database,\n taskId: string,\n): { task: TraceRecord; steps: TraceStepRecord[] } | null {\n const row = db.prepare(\"SELECT * FROM task_traces WHERE task_id = ?\").get(taskId) as\n | Record<string, unknown>\n | undefined\n if (!row) return null\n const stepRows = db\n .prepare(\"SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq\")\n .all(taskId) as Record<string, unknown>[]\n return { task: rowToTrace(row), steps: stepRows.map(rowToStep) }\n}\n\n/**\n * Cancel any in-flight trace rows left over from a previous daemon\n * lifetime. Mirrors the intent ledger's startup cleanup (rescue plan\n * Phase 1 commit a8514d9): a hard-killed daemon never gets to fire\n * task:completed, so the row sits \"in-flight\" forever and contaminates\n * triage queries (`agentx trace list --status in-flight`) with stale\n * orphans. Run once at daemon boot before subscribers attach.\n *\n * Returns the number of rows updated. duration_ms is intentionally NOT\n * computed — we don't know when the task actually ended; preserving\n * NULL there is more honest than backdating to \"now\".\n */\nexport function cleanupOrphanedTraces(db: Database.Database): number {\n const now = Date.now()\n const r = db.prepare(`\n UPDATE task_traces\n SET status = 'canceled',\n error = 'daemon-restart (orphaned in-flight)',\n finished_at = ?\n WHERE status = 'in-flight'\n `).run(now)\n return (r.changes ?? 0) as number\n}\n\n// --- Resume after restart (#103) -------------------------------------------\n\n/** A run that was still going when the previous daemon stopped. */\nexport interface InterruptedRun {\n taskId: string\n agentId: string\n channel: string | null\n chatId: string | null\n workflowRunId: string | null\n startedAt: number\n originalMessage: string | null\n resumeOrigin: string | null\n resumeAttempt: number\n /** Tool calls it had made, in order, for the resume note. */\n toolCalls: Array<{ action: string | null; inputSummary: string | null }>\n}\n\n/** Close every run left in flight by the previous daemon, marking it\n * `interrupted` for the resume planner, and return them. One transaction,\n * so a second process booting on the same database gets nothing. */\nexport function takeInterruptedRuns(db: Database.Database, now = Date.now()): InterruptedRun[] {\n const take = db.transaction(() => {\n const rows = db.prepare(`\n SELECT task_id, agent_id, channel, chat_id, workflow_run_id, started_at,\n original_message, resume_origin, resume_attempt\n FROM task_traces WHERE status = 'in-flight' ORDER BY started_at\n `).all() as Array<Record<string, any>>\n db.prepare(`\n UPDATE task_traces\n SET status = 'canceled', error = 'daemon-restart (interrupted)',\n finished_at = ?, resume_decision = 'interrupted'\n WHERE status = 'in-flight'\n `).run(now)\n return rows\n })\n const steps = db.prepare(`\n SELECT action, input_summary FROM task_trace_steps\n WHERE task_id = ? AND name = 'tool_use' ORDER BY seq\n `)\n return take().map((r) => ({\n taskId: r.task_id,\n agentId: r.agent_id,\n channel: r.channel ?? null,\n chatId: r.chat_id ?? null,\n workflowRunId: r.workflow_run_id ?? null,\n startedAt: r.started_at,\n originalMessage: r.original_message ?? null,\n resumeOrigin: r.resume_origin ?? null,\n resumeAttempt: r.resume_attempt ?? 0,\n toolCalls: (steps.all(r.task_id) as Array<Record<string, any>>).map((s) => ({\n action: s.action ?? null, inputSummary: s.input_summary ?? null,\n })),\n }))\n}\n\n/** Claim an interrupted run before deciding what to do with it. Returns\n * false when another process already claimed or decided it, so a run is\n * never resumed — or its chat told — twice. */\nexport function claimResume(db: Database.Database, taskId: string): boolean {\n const r = db.prepare(`\n UPDATE task_traces SET resume_decision = 'deciding'\n WHERE task_id = ? AND resume_decision = 'interrupted'\n `).run(taskId)\n return (r.changes ?? 0) === 1\n}\n\nexport type ResumeDecision = \"resumed\" | \"reported\" | \"skipped\" | \"resume-failed\"\n\nexport function recordResumeDecision(\n db: Database.Database, taskId: string, decision: ResumeDecision, reason: string,\n): void {\n db.prepare(\"UPDATE task_traces SET resume_decision = ?, resume_reason = ? WHERE task_id = ?\")\n .run(decision, reason.slice(0, 500), taskId)\n}\n\n/** Triage / dashboard surface — list traces newest-first, filtered. */\nexport function listTraces(db: Database.Database, filters: ListTracesFilters = {}): TraceRecord[] {\n const where: string[] = []\n const params: unknown[] = []\n if (filters.agentId) { where.push(\"agent_id = ?\"); params.push(filters.agentId) }\n if (filters.channel) { where.push(\"channel = ?\"); params.push(filters.channel) }\n if (filters.chatId) { where.push(\"chat_id = ?\"); params.push(filters.chatId) }\n if (filters.workflowRunId) { where.push(\"workflow_run_id = ?\"); params.push(filters.workflowRunId) }\n if (filters.status) { where.push(\"status = ?\"); params.push(filters.status) }\n if (filters.since !== undefined) { where.push(\"started_at >= ?\"); params.push(filters.since) }\n if (filters.until !== undefined) { where.push(\"started_at <= ?\"); params.push(filters.until) }\n\n const limit = Math.max(1, Math.min(filters.limit ?? 100, 1000))\n const sql = `\n SELECT * FROM task_traces\n ${where.length > 0 ? \"WHERE \" + where.join(\" AND \") : \"\"}\n ORDER BY started_at DESC\n LIMIT ?\n `\n params.push(limit)\n return (db.prepare(sql).all(...params) as Record<string, unknown>[]).map(rowToTrace)\n}\n\n/** Every trace row of these workflow runs, oldest first. Unbounded by\n * design: a report over a few hundred runs needs all of their rows. */\nexport function listTracesForRuns(db: Database.Database, runIds: string[]): TraceRecord[] {\n const out: TraceRecord[] = []\n // SQLite caps bound parameters; stay well under it.\n for (let i = 0; i < runIds.length; i += 500) {\n const chunk = runIds.slice(i, i + 500)\n if (!chunk.length) continue\n const rows = db.prepare(`\n SELECT * FROM task_traces WHERE workflow_run_id IN (${chunk.map(() => \"?\").join(\",\")})\n ORDER BY started_at\n `).all(...chunk) as Record<string, unknown>[]\n out.push(...rows.map(rowToTrace))\n }\n return out\n}\n\n/** The tool calls one run made, in order: what a stop signal's resume plan\n * lists when the agent wrote none (#857). Bounded. */\nexport function traceToolCalls(\n db: Database.Database, taskId: string, limit = 200,\n): Array<{ action: string | null; inputSummary: string | null }> {\n const rows = db.prepare(`\n SELECT action, input_summary FROM task_trace_steps\n WHERE task_id = ? AND name = 'tool_use' ORDER BY seq LIMIT ?\n `).all(taskId, limit) as Array<Record<string, any>>\n return rows.map((s) => ({ action: s.action ?? null, inputSummary: s.input_summary ?? null }))\n}\n","import type { InjectedContext } from \"@/storage/traces\"\n\n// --- Injected lessons per task (#98) ---\n//\n// Which memory facts, procedures and wiki catalog actually reached the\n// prompt. Candidates are not enough: the request-context selector can drop\n// a whole layer, and the layer budgets in buildAgentContext can cut a layer\n// short. So a lesson counts as injected only when its text is in the\n// assembled context. Only ids are recorded, never content, and the lists\n// are capped, so the trace row stays small.\n\nexport const MAX_INJECTED_IDS = 20\nconst MAX_ID_LENGTH = 80\n\nexport interface InjectionCandidates {\n memory?: Array<{ id: string; content: string }>\n procedures?: Array<{ id: string; title: string }>\n /** The wiki catalog block offered to the prompt, if any. */\n wikiContext?: string\n}\n\n/** The first line of the wiki catalog block; see buildWikiContext. */\nconst WIKI_MARKER = \"[Institutional Wiki\"\n\nfunction capIds(ids: string[]): string[] {\n return [...new Set(ids)].slice(0, MAX_INJECTED_IDS).map((id) => id.slice(0, MAX_ID_LENGTH))\n}\n\nexport function injectedContextOf(assembled: string, candidates: InjectionCandidates): InjectedContext {\n const text = assembled ?? \"\"\n return {\n memory: capIds((candidates.memory ?? []).filter((m) => m.content && text.includes(m.content)).map((m) => m.id)),\n procedures: capIds(\n (candidates.procedures ?? []).filter((p) => text.includes(`[Known procedure: ${p.title}]`)).map((p) => p.id),\n ),\n wiki: Boolean(candidates.wikiContext) && text.includes(WIKI_MARKER),\n }\n}\n\n/** Clamp an InjectedContext from any source to the recorded bounds. */\nexport function boundInjectedContext(ctx: InjectedContext): InjectedContext {\n return { memory: capIds(ctx.memory), procedures: capIds(ctx.procedures), wiki: ctx.wiki === true }\n}\n"],"mappings":"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","names":["init_ulid","WIKI_MARKER","capIds","ids","id","injectedContextOf","assembled","candidates","text","m","p","boundInjectedContext","ctx","recordTraceStart","db","input","taskId","newEventId","recordTraceEnd","finishedAt","boundInjectedContext","recordTraceStep","startedAt","seq","parseInjectedContext","raw","v","ids","x","i","rowToTrace","row","rowToStep","getTrace","stepRows","cleanupOrphanedTraces","now","takeInterruptedRuns","take","rows","steps","r","s","claimResume","recordResumeDecision","decision","reason","listTraces","filters","where","params","limit","sql","listTracesForRuns","runIds","out","chunk","traceToolCalls"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{a as T,b as E,c as Y,d as j,e as G}from"./chunk-WPNRDEHW.js";import{v as R,y as O,z as U}from"./chunk-HPQ3AAG3.js";import{a as V,b as C,f as B}from"./chunk-72SZI2UW.js";import{a as W,b as se}from"./chunk-MCI3DIWW.js";import{o as z,p as re}from"./chunk-2PJKGZWP.js";B();var xe="wiki-rerank",Se=30,ne=900;function Ne(a){let e=a.excerptChars??ne;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ie(t.excerpt,e)}))}}function ke(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:V("Do any of the candidates answer the query?")}}function Ie(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ie(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},$={code:"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.",ops:"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.",support:"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.",admin:"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.",knowledge:"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.",social:"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.",system:"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this."},M=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as _,appendFileSync as oe,existsSync as S,mkdirSync as D,renameSync as ae,statSync as Q}from"fs";import{resolve as A,dirname as Z}from"path";import{createHash as ce}from"crypto";var F=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),D(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{_(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{_(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);X(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),X(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let u=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){u=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=u;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,u=p+1;break}}if(!d&&u<r.levels.length&&(d=r.levels[u].id,u++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${u}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let g={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(g),o.push(g)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=E.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
|
|
1
|
+
import{a as T,b as E,c as Y,d as j,e as G}from"./chunk-WPNRDEHW.js";import{v as R,y as O,z as U}from"./chunk-SH5TNMS5.js";import{a as V,b as C,f as B}from"./chunk-72SZI2UW.js";import{a as W,b as se}from"./chunk-MCI3DIWW.js";import{o as z,p as re}from"./chunk-2PJKGZWP.js";B();var xe="wiki-rerank",Se=30,ne=900;function Ne(a){let e=a.excerptChars??ne;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ie(t.excerpt,e)}))}}function ke(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:V("Do any of the candidates answer the query?")}}function Ie(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ie(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},$={code:"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.",ops:"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.",support:"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.",admin:"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.",knowledge:"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.",social:"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.",system:"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this."},M=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as _,appendFileSync as oe,existsSync as S,mkdirSync as D,renameSync as ae,statSync as Q}from"fs";import{resolve as A,dirname as Z}from"path";import{createHash as ce}from"crypto";var F=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),D(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{_(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{_(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);X(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),X(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let u=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){u=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=u;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,u=p+1;break}}if(!d&&u<r.levels.length&&(d=r.levels[u].id,u++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${u}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let g={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(g),o.push(g)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=E.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
|
|
2
2
|
`,n=this.classificationsPath();return S(n)||D(Z(n),{recursive:!0}),oe(n,r),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listAllClassifications(){return this.readAllCollapsed()}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!S(e))return[];let r=I(e,"utf-8").split(`
|
|
3
3
|
`).filter(Boolean),n=new Map;for(let o of r)try{let i=E.safeParse(JSON.parse(o));if(!i.success)continue;n.set(i.data.msgHash,i.data)}catch{}return Array.from(n.values()).sort((o,i)=>o.ts<i.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(i=>i.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let i=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+i).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!S(t))return[];let n=I(t,"utf-8").split(`
|
|
4
4
|
`).filter(Boolean),o=[];for(let i=n.length-1;i>=0&&o.length<e;i--)try{let s=E.safeParse(JSON.parse(n[i]));s.success&&o.push(s.data)}catch{}return o}fingerprint(e){let r=[de(e.text),e.channel??""].join("");return ce("sha256").update(r).digest("hex").slice(0,32)}loadIndex(){return structuredClone(this.index())}saveIndex(e){let t=G.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.indexPath(),t.data)}index(){let e=this.indexPath();return S(e)?this.readParsed(e,t=>{let r=G.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}):{version:1,entries:{}}}getFingerprint(e){let t=this.index().entries[e];return t&&structuredClone(t)}setFingerprint(e,t){let r=Y.safeParse({...t,fingerprint:e,updatedAt:new Date().toISOString()});if(!r.success)throw new Error(`Refusing to save invalid index entry: ${r.error.issues.map(o=>o.message).join("; ")}`);let n=this.index();this.writeParsed(this.indexPath(),{...n,entries:{...n.entries,[e]:r.data}})}updateNodeAxes(e,t){let r=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(s=>s.id===e);if(!o)throw new Error(`Node not found: ${e}`);let i={...o,axes:t};return this.validateNode(i,r,n.nodes.map(s=>s.id===e?i:s)),n.nodes=n.nodes.map(s=>s.id===e?i:s),this.saveNodes(n),i}deleteNode(e){let t=this.loadNodes(),r=t.nodes.find(o=>o.parentId===e);if(r)throw new Error(`Cannot delete "${e}" \u2014 child node "${r.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(i=>i===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),r=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(r.has(o))continue;let i=t.levels[n]?.id;if(!i)break;let s=e.proposedAxes?.[o]??{},c=n===0?null:e.path[n-1];this.addNode({id:o,level:i,parentId:c,axes:s,createdAt:new Date().toISOString(),createdBy:e.agentId}),r.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function X(a,e){D(Z(a),{recursive:!0});let t=`${a}.tmp-${process.pid}`;_(t,JSON.stringify(e,null,2)),ae(t,a)}function K(){let a=new Date().toISOString(),e=[],t=new Set;for(let r of M)t.has(r.category)||(e.push({id:r.category,level:"category",parentId:null,axes:{kind:r.category,description:$[r.category]},createdAt:a,createdBy:"starter"}),t.add(r.category));for(let r of M)e.push({id:r.verb,level:"verb",parentId:r.category,axes:{name:r.verb,...r.description?{description:r.description}:{}},createdAt:a,createdBy:"starter"});return{version:1,nodes:e}}function de(a){return a.toLowerCase().replace(/https?:\/\/\S+/g," url ").replace(/[@#][\w.\/-]+/g," ref ").replace(/\b[0-9a-f]{7,}\b/g," id ").replace(/\d+/g," num ").replace(/[^\p{L}\p{N}\s]+/gu," ").replace(/\s+/g," ").trim().slice(0,400)}se();re();U();import{createHash as ye}from"crypto";B();U();var P="intent-path",le=.5,pe=.3,ue=.45;function ge(a){return{message:ve(a.message,2e3),channel:a.channel??null,sender:a.sender??null,agent:a.agent??null}}function ee(a,e){let t={};for(let r of a)t[r.id]=me(r);return C(t,e)}function he(a){let e=a.map(t=>{let r=$[t.id];return t.axes?.description?.trim()||!r?t:{...t,axes:{...t.axes,description:r}}});return{category:ee(e,"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.")}}function fe(a){return{verb:ee(a,"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.")}}async function q(a,e,t={}){if(R(P)==="off")return null;let r=e.filter(m=>m.parentId===null);if(r.length<2)return null;let n=ge(a),o={agent:a.agent??"unknown",channel:a.channel??"unknown"},i=a.agent?[{kind:"agent",id:a.agent}]:void 0,s=await O(P,n,he(r),{incumbent:t.incumbent?.[0]?{category:t.incumbent[0]}:void 0,features:{...o,stage:"category"},links:i});if(!s)return null;let c=s.answers.category,l=c.pMax,h=c.pMax>=le,b=[c.choice],u=e.filter(m=>m.parentId===c.choice);if(u.length>=2){let m=t.incumbent?.[0]===c.choice,d=await O(P,{...n,category:c.choice},fe(u),{incumbent:m&&t.incumbent?.[1]?{verb:t.incumbent[1]}:void 0,features:{...o,stage:"verb",category:c.choice},links:i});if(!d)return null;let f=d.answers.verb;h=h&&f.pMax>=pe,(f.pMax>=ue||!h)&&(l=Math.min(l,f.pMax),b.push(f.choice))}else u.length===1&&b.push(u[0].id);return{path:b,confidence:l,confident:h,mode:s.mode}}function me(a){let e=a.axes?.description?.trim()||void 0,t=a.axes?.name?.trim(),r=t&&t!==a.id?t:void 0;return e&&r?`${r}: ${e}`:e??r??null}function ve(a,e){return a.length>e?`${a.slice(0,e)}\u2026`:a}var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=z(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),r=this.store.getFingerprint(t);if(r)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:r.path,proposedAxes:{},leaf:r.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:r.path,pathId:H(r.path),pathLabel:J(r.path,this.store.loadNodes().nodes),axes:{},leaf:r.leaf,source:"cache",status:"approved"};let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,i=await this.propose(e,n,o);if(!i)return null;let{path:s,proposedAxes:c,confidence:l,leaf:h}=i;if(s.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:s,proposedAxes:c,leaf:h,source:i.source,status:"pending",confidence:l,preview:e.text.slice(0,200)},u=new Set(o.map(g=>g.id)),m=s.map((g,p)=>u.has(g)?-1:p).filter(g=>g>=0),d=!1;if(this.autoApproveStructure==="any")d=!0;else if(this.autoApproveStructure==="extend-leaves"){let g=m.length===0,p=m.length===1&&m[0]===s.length-1;d=g||p}let f=l!==void 0&&l>=this.autoApprove,v="pending";if(d||f)try{this.commitNewNodes(s,c,n,o,e.sender),this.store.setFingerprint(t,{path:s,leaf:h}),v="approved"}catch(g){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(s)}`,`reason=${(g?.message||g).toString().slice(0,200)}`)}return b.status=v,this.store.appendClassification(b),{path:s,pathId:H(s),pathLabel:J(s,this.store.loadNodes().nodes),axes:c,leaf:h,source:i.source,status:v,confidence:l}}async propose(e,t,r){let n=R(P),o={message:e.text,channel:e.channel,sender:e.sender,agent:e.agentId};if(n==="active"){let s=await q(o,r).catch(c=>(this.log("intent-path seat failed:",c?.message||c),null));if(s?.confident)return{path:s.path,proposedAxes:{},leaf:{},confidence:s.confidence,source:"seat"}}let i=await this.proposePath(e,t,r).catch(s=>(this.log("LLM proposal failed:",s?.message||s),null));return n==="shadow"&&i&&q(o,r,{incumbent:i.path}).catch(()=>{}),i?{...i,source:"llm"}:null}async proposePath(e,t,r){let n=r.map(d=>({id:d.id,level:d.level,parentId:d.parentId,axes:d.axes})),o=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on globex/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "acme", "globex-v2", "review-mr-957-system"]','- "Deploy initech-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "acme", "initech-v2", "deploy-initech-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
|
|
5
5
|
`),i=await this.callProvider(o),s=W(i);if(!s||!Array.isArray(s.path))return null;let c=/^[a-z0-9][a-z0-9._-]*$/,l=new Map,h=s.path.filter(d=>typeof d=="string"&&d.length>0).map(d=>{let f=d.trim(),v=te(f);return f!==v&&l.set(f,v),v}).filter(d=>c.test(d));if(h.length===0)return null;let b={};if(s.proposedAxes&&typeof s.proposedAxes=="object")for(let[d,f]of Object.entries(s.proposedAxes)){if(!f||typeof f!="object")continue;let v=l.get(d)??te(d);if(!v)continue;let g={};for(let[p,y]of Object.entries(f))typeof y=="string"&&(g[p]=y);!g.name&&d!==v&&(g.name=d.trim()),b[v]=g}let u={};s.leaf&&typeof s.leaf=="object"&&(typeof s.leaf.input=="string"&&(u.input=s.leaf.input),typeof s.leaf.output=="string"&&(u.output=s.leaf.output));let m=typeof s.confidence=="number"?Math.max(0,Math.min(1,s.confidence)):void 0;return{path:h,proposedAxes:b,confidence:m,leaf:u}}commitNewNodes(e,t,r,n,o){this.store.commitNodesAlongPath(e,t,r,o)}async callProvider(e){let t=this.getProvider(),r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800,abortSignal:r.signal,bare:!0})).content||""}finally{clearTimeout(n)}}};function te(a){let e=a.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function H(a){return ye("sha1").update(a.join("")).digest("hex").slice(0,16)}function J(a,e){return a.map(t=>{let r=e.find(o=>o.id===t);return r&&(r.axes?.name||r.axes?.what)||t}).join(" \u203A ")}function we(a,e){if(a.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(a.length,e.length)&&a[n]===e[n];n++)t++;if(t===0)return 0;let r=Math.max(a.length,e.length);return t/r}export{xe as a,Se as b,Ne as c,ke as d,Ie as e,x as f,F as g,L as h,J as i,we as j};
|
|
6
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
//# sourceMappingURL=chunk-BPOMIEEE.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{a as w,b as f}from"./chunk-WBG6NA7R.js";import{randomUUID as _}from"crypto";function p(r){return r.status&&r.status!=="ringing"?", waiting_since=NULL, told_at=NULL":""}var E=["ringing","answered","later"],A=`id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,
|
|
2
|
+
ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,
|
|
3
|
+
ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`,y=class{constructor(t){this.db=t;t.exec(`CREATE TABLE IF NOT EXISTS calls (
|
|
4
|
+
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,
|
|
5
|
+
urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
6
|
+
ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,
|
|
7
|
+
ring_again_at INTEGER, note TEXT, summary TEXT);
|
|
8
|
+
CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`);let e=new Set(t.prepare("PRAGMA table_info(calls)").all().map(n=>n.name));e.has("kind")||t.exec("ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'"),e.has("channel")||t.exec("ALTER TABLE calls ADD COLUMN channel TEXT"),e.has("chat_id")||t.exec("ALTER TABLE calls ADD COLUMN chat_id TEXT"),e.has("waiting_since")||t.exec("ALTER TABLE calls ADD COLUMN waiting_since INTEGER"),e.has("told_at")||t.exec("ALTER TABLE calls ADD COLUMN told_at INTEGER")}waiting(){let t=this.db.prepare("SELECT id, waiting_since AS since, told_at AS toldAt FROM calls WHERE waiting_since IS NOT NULL AND status='ringing'").all();return new Map(t.map(({id:e,since:n,toldAt:i})=>[e,{since:n,toldAt:i??n}]))}setWaiting(t,e){this.db.prepare("UPDATE calls SET waiting_since=?, told_at=? WHERE id=?").run(e?.since??null,e?.toldAt??null,t)}insert(t){this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,
|
|
9
|
+
answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(t.id,t.agentId,t.kind,t.reason,t.urgency,t.status,t.createdAt,t.ringingSince,t.answeredAt,t.endedAt,t.ringAgainAt,t.note,t.summary,t.channel,t.chatId)}get(t){return this.db.prepare(`SELECT ${A} FROM calls WHERE id=?`).get(t)}update(t,e){let n={reason:"reason",urgency:"urgency",status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},i=Object.keys(e).filter(a=>a in n);if(i.length){let a=i.map(s=>`${n[s]}=?`).join(", ")+p(e);this.db.prepare(`UPDATE calls SET ${a} WHERE id=?`).run(...i.map(s=>e[s]??null),t)}return this.get(t)}transition(t,e,n){let i=Array.isArray(e)?e:[e],a={status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},s=Object.keys(n).filter(l=>l in a),o=s.map(l=>`${a[l]}=?`).join(", ")+p(n),u=i.map(()=>"?").join(",");return this.db.prepare(`UPDATE calls SET ${o} WHERE id=? AND status IN (${u})`).run(...s.map(l=>n[l]??null),t,...i).changes?this.get(t):void 0}list(t={}){let e=Math.min(Math.max(t.limit??50,1),200),n=[],i=[];t.status?.length&&(n.push(`status IN (${t.status.map(()=>"?").join(",")})`),i.push(...t.status)),t.kind&&(n.push("kind=?"),i.push(t.kind));let a=n.length?`WHERE ${n.join(" AND ")}`:"";return this.db.prepare(`SELECT ${A} FROM calls ${a} ORDER BY created_at DESC, rowid DESC LIMIT ?`).all(...i,e)}countSince(t,e){return this.db.prepare("SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?").get(t,e).n}};var C=200,k=1e4,I=2*36e5,$=10,T=240;function D(r=process.env){return r.AGENTX_TASK_ID?{"X-AgentX-Task":r.AGENTX_TASK_ID}:r.AGENTX_CHANNEL&&r.AGENTX_CHAT_ID?{"X-AgentX-Channel":r.AGENTX_CHANNEL,"X-AgentX-Chat":r.AGENTX_CHAT_ID}:{}}function N(r){return`[Call answered] The owner just picked up the voice call you asked for. You called because: "${r.reason}". Speak first: greet them in a few words and say why you called, then let them answer.`}var M="[Call ended] The owner hung up. In two or three plain sentences, summarise what was said and decided on this call, for your records. Reply with the summary only.",S=class{constructor(t){this.deps=t;this.waiting=t.store.waiting()}widgetSeenAt=0;widget={};waiting;setWaiting(t,e){if(e)this.waiting.set(t,e);else if(!this.waiting.delete(t))return;this.deps.store.setWaiting(t,e)}now(){return this.deps.now?.()??Date.now()}name(t){return this.deps.agentName(t)??t}async request(t,e={}){let n=String(t.agentId??"").trim(),i=String(t.reason??"").replace(/\s+/g," ").trim(),a=t.urgency==="urgent"?"urgent":"normal",s=t.kind==="camera"?"camera":"voice";if(t.kind!==void 0&&t.kind!=="voice"&&t.kind!=="camera")return{ok:!1,status:400,error:"kind must be voice or camera"};if(!n||!i)return{ok:!1,status:400,error:"Required: agentId and reason"};if(i.length>C)return{ok:!1,status:413,error:`reason is over ${C} characters; keep it to one line`};if(this.deps.agentName(n)===null)return{ok:!1,status:404,error:`Unknown agent: ${n}`};if(!this.deps.isRunningTurn(n,e))return{ok:!1,status:403,error:`No running turn of ${n} placed this call. An agent calls from inside its own run.`};let o=this.deps.config();if(!o.allow.includes("*")&&!o.allow.includes(n))return{ok:!1,status:403,error:`${n} ${s==="camera"?"may not ask to see through the owner's camera":"may not call the owner"}. Add it to calls.allow in agentx.json (or: agentx call allow ${n}).`};if(await this.sweep(),this.deps.store.list({status:E,limit:200}).some(m=>m.agentId===n))return{ok:!1,status:409,error:`${n} already has a call in progress`};let u=this.now();if(this.deps.store.countSince(n,u-36e5)>=o.maxPerHour)return{ok:!1,status:429,error:`${n} has placed ${o.maxPerHour} calls in the last hour (calls.maxPerHour)`};let g=s==="camera"?this.deps.turnSession?.(n,e)??e:{},l={id:`call-${_().slice(0,8)}`,agentId:n,kind:s,reason:i,urgency:a,status:"ringing",createdAt:u,ringingSince:u,answeredAt:null,endedAt:null,ringAgainAt:null,note:null,summary:null,channel:g.channel&&g.chatId?g.channel:null,chatId:g.channel&&g.chatId?g.chatId:null};this.deps.store.insert(l),this.deps.log?.(`[calls] ${n} asks ${s==="camera"?"to see":"for a call"} (${a}): ${i}`);let d=await this.ring(l),h=this.deps.store.get(l.id);return d!=="waiting"?{ok:!0,call:h,rang:d}:this.waiting.has(l.id)?{ok:!0,call:h,rang:d,busy:this.busyInfo(l)}:{ok:!0,call:h,rang:"widget"}}async cancel(t,e={}){let n=String(t.agentId??"").trim();if(!n)return{ok:!1,status:400,error:"Required: agentId"};if(!this.deps.isRunningTurn(n,e))return{ok:!1,status:403,error:`No running turn of ${n} asked for this. An agent withdraws its own call from inside its run.`};let i=this.deps.store.list({status:["ringing","later"],kind:"voice",limit:200}).find(s=>s.agentId===n);if(!i)return{ok:!1,status:404,error:`${n} has no call waiting or ringing`};let a=this.deps.store.transition(i.id,["ringing","later"],{status:"ended",endedAt:this.now(),ringingSince:null,ringAgainAt:null,note:b});return a?(this.setWaiting(i.id,null),this.deps.log?.(`[calls] ${i.id} withdrawn by ${n}`),{ok:!0,call:a}):{ok:!1,status:409,error:`call is ${this.deps.store.get(i.id)?.status}`}}async ring(t){let e=(this.deps.focus??w)();return e.active&&t.urgency!=="urgent"?(this.deps.store.transition(t.id,"ringing",{status:"missed",ringingSince:null,note:f(e)})&&await this.notice(t,this.missedTitle(t)),!1):t.kind==="camera"?(await this.notice(t,`${this.name(t.agentId)} wants to see through your camera`,`Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`),"notify"):this.widgetAlive()?!this.widget.busy&&!this.otherCalls(t).length?"widget":(this.startWaiting(t),await this.callWaiting(t),"waiting"):(await this.notice(t,`${this.name(t.agentId)} is calling`,`Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`),"notify")}async notice(t,e,n){try{await this.deps.alert({title:e,message:n?`${t.reason}
|
|
10
|
+
${n}`:t.reason,urgent:t.urgency==="urgent",from:t.agentId})}catch(i){this.deps.log?.(`[calls] notice for ${t.id} failed: ${i?.message??i}`)}}startWaiting(t){let e=this.now();this.setWaiting(t.id,{since:e,toldAt:e}),this.deps.log?.(`[calls] ${t.id} from ${t.agentId} waits: the widget is busy`)}otherCalls(t){return this.deps.store.list({status:["ringing","answered"],kind:"voice",limit:200}).filter(e=>e.id!==t.id).reverse()}async callWaiting(t){let e=this.deps.config().busy;await this.notice(t,e.title.replaceAll("{agent}",this.name(t.agentId)),e.notice)}busyInfo(t){let e=this.deps.config().busy,{since:n,toldAt:i}=this.waiting.get(t.id)??{since:this.now(),toldAt:this.now()},a=this.otherCalls(t),s=a.find(d=>d.status==="answered"),o=a.find(d=>d.id===this.widget.showing)??a.find(d=>!this.waiting.has(d.id)),u=n+e.maxWaitMinutes*6e4+this.deps.config().ringSeconds*1e3,g=i+e.retryMinutes*6e4,l=n+e.maxWaitMinutes*6e4;return{why:s?"call":o?"ringing":"voice",with:s?this.name(s.agentId):o?this.name(o.agentId):null,remindAt:g<l?g:null,waitsUntil:u}}missedTitle(t){return t.kind==="camera"?`${this.name(t.agentId)} asked to see through your camera`:`Missed call from ${this.name(t.agentId)}`}inCall(){if(this.widgetAlive()&&this.widget.busy)return"the widget is busy";let t=this.now()-I,e=this.deps.store.list({status:["answered"],limit:5}).find(n=>(n.answeredAt??0)>=t);return e?`on a call with ${this.name(e.agentId)}`:null}widgetAlive(){return this.now()-this.widgetSeenAt<k}async ringing(t={}){return this.widgetSeenAt=this.now(),this.widget=t,await this.hold(t),await this.sweep(),this.deps.store.list({status:["ringing"],kind:"voice",limit:20}).reverse()}async hold(t){let e=this.now(),n=this.deps.config().busy,i=[],a=this.deps.store.list({status:["ringing"],kind:"voice",limit:20});for(let s of[...this.waiting.keys()])a.some(o=>o.id===s)||this.setWaiting(s,null);for(let s of a){if(s.id===t.showing){this.waiting.has(s.id)&&(this.setWaiting(s.id,null),this.deps.store.transition(s.id,"ringing",{ringingSince:e}));continue}if(!t.busy)continue;let o=this.waiting.get(s.id);if(!(o&&e-o.since>=n.maxWaitMinutes*6e4)&&this.deps.store.transition(s.id,"ringing",{ringingSince:e})){if(!o)this.startWaiting(s);else if(e-o.toldAt>=n.retryMinutes*6e4)this.setWaiting(s.id,{since:o.since,toldAt:e});else continue;i.push(s)}}for(let s of i)await this.callWaiting(s)}async asking(){return await this.sweep(),this.deps.store.list({status:["ringing"],kind:"camera",limit:20}).reverse()}async sweep(){let t=this.now(),e=this.deps.config().ringSeconds*1e3,n=this.deps.config().maxCallMinutes*6e4;for(let i of this.deps.store.list({status:["ringing","later","answered"],limit:200}))if(i.status==="ringing"&&i.ringingSince!==null&&t-i.ringingSince>=e){if(!this.deps.store.transition(i.id,"ringing",{status:"missed",ringingSince:null,note:this.waiting.has(i.id)?"widget busy":"not answered"}))continue;this.deps.log?.(`[calls] ${i.id} from ${i.agentId} missed`),await this.notice(i,this.missedTitle(i))}else if(i.status==="answered"&&i.answeredAt!==null&&t-i.answeredAt>=n){if(!this.deps.store.transition(i.id,"answered",{status:"ended",endedAt:t,note:"no hang-up"}))continue;this.deps.log?.(`[calls] ${i.id} from ${i.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)}else if(i.status==="later"&&i.ringAgainAt!==null&&t>=i.ringAgainAt){let a=this.deps.store.transition(i.id,"later",{status:"ringing",ringingSince:t,ringAgainAt:null});if(!a)continue;this.setWaiting(i.id,null),await this.ring(a)}}answer(t){let e=this.deps.store.get(t);if(!e)return c(t);let n=this.deps.store.transition(t,"ringing",{status:"answered",answeredAt:this.now(),ringingSince:null});return n?(this.deps.log?.(`[calls] ${t} answered (${e.agentId})`),n.kind==="camera"?{ok:!0,call:n}:{ok:!0,call:n,opener:N(n)}):{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}decline(t){if(!this.deps.store.get(t))return c(t);let n=this.deps.store.transition(t,["ringing","later"],{status:"declined",endedAt:this.now(),ringingSince:null,ringAgainAt:null});return n?{ok:!0,call:n}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}`}}later(t,e){if(!this.deps.store.get(t))return c(t);let i=e===void 0?$:Number(e);if(!Number.isInteger(i)||i<1||i>T)return{ok:!1,status:400,error:`minutes must be a whole number from 1 to ${T}`};let a=this.deps.store.transition(t,"ringing",{status:"later",ringingSince:null,ringAgainAt:this.now()+i*6e4});return a?{ok:!0,call:a}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}hangup(t){let e=this.deps.store.get(t);if(!e)return c(t);let n=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now()});if(!n)return{ok:!1,status:409,error:`call is ${e.status}, not answered`};this.deps.log?.(`[calls] ${t} ended (${e.agentId})`);let i=this.deps.summarize;if(n.kind==="camera"||!this.deps.config().summary||!i)return{ok:!0,call:n};let a=(async()=>{try{let s=(await i(n))?.trim();if(!s)return;let o=this.deps.store.update(t,{summary:s});this.deps.file?.(o,s)}catch(s){this.deps.log?.(`[calls] summary for ${t} failed: ${s?.message??s}`)}})();return{ok:!0,call:n,summarized:a}}endCamera(t,e){let n=this.deps.store.get(t);if(!n||n.kind!=="camera")return!1;let i=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now(),note:e??null});return i&&this.deps.log?.(`[calls] ${t} ended (${n.agentId}): ${e??"share over"}`),!!i}get(t){return this.deps.store.get(t)}list(t={}){return this.deps.store.list(t)}},b="withdrawn by the caller";function W(r,t=Date.now(),e){let n=a=>`${new Date(a).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1,timeZoneName:"short",timeZone:e})} (in ${Math.max(1,Math.round((a-t)/6e4))} min)`;return`${r.why==="call"?`The owner is busy in another call${r.with?` (with ${r.with})`:""}.`:r.why==="ringing"?`The owner is busy: another call${r.with?` (from ${r.with})`:""} is ringing.`:"The owner is busy on the voice widget."} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. `+(r.remindAt!==null?`Still busy at ${n(r.remindAt)}, they are told again that you are waiting. `:"")+`It waits until ${n(r.waitsUntil)} at most, then counts as a missed call.`}var c=r=>({ok:!1,status:404,error:`No such call: ${r}`});export{y as a,D as b,M as c,S as d,W as e};
|
|
11
|
+
//# sourceMappingURL=chunk-DG2DC43E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/calls/service.ts","../src/calls/store.ts"],"sourcesContent":["// --- An agent rings the owner (#321) ---\n//\n// An agent that needs the owner directly asks for a call. It rings on the\n// desktop widget (AgentX Voice), which polls `ringing()`. Answering starts\n// the widget's hands-free conversation with that agent, which opens with\n// its reason. When the widget is not running, `notify` stands in. While\n// the widget is busy (a turn, the microphone, another call) the call waits\n// for it: its ring time does not run, and the owner gets a call-waiting\n// notice (#408), again every calls.busy.retryMinutes. The caller is told\n// the owner is busy, with whom, and until when its call waits (#969). It\n// rings by itself once the widget is free, oldest first; the caller may\n// withdraw it meanwhile (`cancel`).\n//\n// Checked before anything rings, in this order:\n// 0. the caller is that agent: a turn of it is running now, named by its\n// task id or by its channel and chat (see CallerProof),\n// 1. the owner allows this agent (calls.allow; empty allows nobody),\n// 2. the agent has no other call in progress,\n// 3. it is under calls.maxPerHour,\n// 4. Focus: a non-urgent call does not ring. It becomes a missed call,\n// and its notice goes through notify, which holds it until Focus\n// ends. Urgent calls ring through, as `notify --urgent` does.\n//\n// A camera ask (#325 phase 3, kind \"camera\") goes through the same checks\n// and the same table. It never rings the widget: the notice goes to the\n// phone through notify, and the phone app polls `asking()` to show a\n// Show / Decline bar. Answering it is the owner tapping Show; the camera\n// opens only then, and the share ends the ask (see camera-api.ts).\n\nimport { randomUUID } from \"crypto\"\nimport { focusLabel, readFocus, type FocusState } from \"@/notify/focus\"\nimport type { CallsConfig } from \"@/daemon/config\"\nimport { LIVE, type Call, type CallKind, type CallStore, type CallUrgency, type Waiting } from \"./store\"\n\nexport const REASON_MAX = 200\n/** The widget polls every 2 s; missing this long means it is not running. */\nexport const WIDGET_FRESH_MS = 10_000\n/** An answered call older than this holds nothing: one never marked ended\n * must not hold the owner's notices forever. */\nexport const CALL_HOLD_MAX_MS = 2 * 3_600_000\nexport const LATER_DEFAULT_MINUTES = 10\nexport const LATER_MAX_MINUTES = 240\n\n/** What a caller is told when the owner cannot take its call now (#969). */\nexport interface BusyInfo {\n /** \"call\": the owner is in a call; \"ringing\": another call is ringing;\n * \"voice\": AgentX Voice is in a turn of the owner's, or recording. */\n why: \"call\" | \"ringing\" | \"voice\"\n /** The agent the owner is in a call with, or whose call is ringing. */\n with: string | null\n /** When the owner is told again that this call waits, if still busy\n * then; null when the wait ends first. Ms since the epoch. */\n remindAt: number | null\n /** The call stops waiting then: still busy, it counts as missed. */\n waitsUntil: number\n}\n\nexport type CallResult =\n | { ok: true; call: Call }\n | { ok: false; status: number; error: string }\n\n/** What the request says about the turn placing it: the run's\n * AGENTX_TASK_ID, or, for a warm process that has none, its\n * AGENTX_CHANNEL and AGENTX_CHAT_ID. Loopback is not an identity. */\nexport interface CallerProof {\n taskId?: string\n channel?: string\n chatId?: string\n}\n\n/** The proof headers for a call placed from inside an agent's run. */\nexport function callerHeaders(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\n if (env.AGENTX_TASK_ID) return { \"X-AgentX-Task\": env.AGENTX_TASK_ID }\n if (env.AGENTX_CHANNEL && env.AGENTX_CHAT_ID) {\n return { \"X-AgentX-Channel\": env.AGENTX_CHANNEL, \"X-AgentX-Chat\": env.AGENTX_CHAT_ID }\n }\n return {}\n}\n\n/** What the widget says with its poll. A widget from before #408 says nothing. */\nexport interface WidgetState {\n /** It cannot ring a new call now. */\n busy?: boolean\n /** The call ringing on it. */\n showing?: string\n}\n\nexport interface CallDeps {\n store: CallStore\n config: () => CallsConfig\n /** The agent's display name, or null when there is no such agent here. */\n agentName: (agentId: string) => string | null\n /** True when `proof` names a running turn of `agentId`. */\n isRunningTurn: (agentId: string, proof: CallerProof) => boolean\n /** The channel and chat of that running turn, when it has one, so a\n * camera ask is answered where it was asked. Unset: only a proof that\n * names them itself is kept. */\n turnSession?: (agentId: string, proof: CallerProof) => { channel?: string; chatId?: string } | null\n /** Tell the owner through notify. `urgent` passes Focus, like notify --urgent. */\n alert: (notice: { title: string; message: string; urgent: boolean; from: string }) => Promise<void>\n /** Ask the agent, in its voice session, to summarise the call. */\n summarize?: (call: Call) => Promise<string | null>\n /** File a finished summary where the dashboard shows it. */\n file?: (call: Call, summary: string) => void\n focus?: () => FocusState\n now?: () => number\n log?: (m: string) => void\n}\n\n/** What the widget sends through /ask when the owner answers, so the\n * agent speaks first and says why it called. */\nexport function openerPrompt(call: Pick<Call, \"reason\">): string {\n return \"[Call answered] The owner just picked up the voice call you asked for. \" +\n `You called because: \"${call.reason}\". Speak first: greet them in a few words ` +\n \"and say why you called, then let them answer.\"\n}\n\nexport const SUMMARY_PROMPT =\n \"[Call ended] The owner hung up. In two or three plain sentences, summarise what \" +\n \"was said and decided on this call, for your records. Reply with the summary only.\"\n\nexport class CallService {\n private widgetSeenAt = 0\n /** What the widget said with its last poll. */\n private widget: WidgetState = {}\n /** Calls waiting for a busy widget: since when, and when the owner was\n * last told. Written through to the store, so a restart keeps them. */\n private waiting: Map<string, Waiting>\n\n constructor(private deps: CallDeps) {\n this.waiting = deps.store.waiting()\n }\n\n private setWaiting(id: string, w: Waiting | null): void {\n if (w) this.waiting.set(id, w)\n else if (!this.waiting.delete(id)) return\n this.deps.store.setWaiting(id, w)\n }\n\n private now(): number { return this.deps.now?.() ?? Date.now() }\n private name(agentId: string): string { return this.deps.agentName(agentId) ?? agentId }\n\n /** An agent asks for a call, or to see (kind \"camera\"). 201 ringing, or\n * waiting for a busy owner (`rang: \"waiting\"`, with `busy`); 202 held\n * as missed (Focus). */\n async request(input: { agentId?: unknown; reason?: unknown; urgency?: unknown; kind?: unknown }, proof: CallerProof = {}): Promise<CallResult & { rang?: Rang; busy?: BusyInfo }> {\n const agentId = String(input.agentId ?? \"\").trim()\n const reason = String(input.reason ?? \"\").replace(/\\s+/g, \" \").trim()\n const urgency: CallUrgency = input.urgency === \"urgent\" ? \"urgent\" : \"normal\"\n const kind: CallKind = input.kind === \"camera\" ? \"camera\" : \"voice\"\n if (input.kind !== undefined && input.kind !== \"voice\" && input.kind !== \"camera\") return { ok: false, status: 400, error: \"kind must be voice or camera\" }\n if (!agentId || !reason) return { ok: false, status: 400, error: \"Required: agentId and reason\" }\n if (reason.length > REASON_MAX) return { ok: false, status: 413, error: `reason is over ${REASON_MAX} characters; keep it to one line` }\n if (this.deps.agentName(agentId) === null) return { ok: false, status: 404, error: `Unknown agent: ${agentId}` }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} placed this call. An agent calls from inside its own run.` }\n }\n\n const cfg = this.deps.config()\n if (!cfg.allow.includes(\"*\") && !cfg.allow.includes(agentId)) {\n const verb = kind === \"camera\" ? \"may not ask to see through the owner's camera\" : \"may not call the owner\"\n return { ok: false, status: 403, error: `${agentId} ${verb}. Add it to calls.allow in agentx.json (or: agentx call allow ${agentId}).` }\n }\n await this.sweep()\n if (this.deps.store.list({ status: LIVE, limit: 200 }).some((c) => c.agentId === agentId)) {\n return { ok: false, status: 409, error: `${agentId} already has a call in progress` }\n }\n const now = this.now()\n if (this.deps.store.countSince(agentId, now - 3_600_000) >= cfg.maxPerHour) {\n return { ok: false, status: 429, error: `${agentId} has placed ${cfg.maxPerHour} calls in the last hour (calls.maxPerHour)` }\n }\n\n // Where the ask came from, for kind \"camera\": the turn's own chat, or\n // the one the proof names. A task run has neither.\n const session: { channel?: string; chatId?: string } = kind === \"camera\" ? (this.deps.turnSession?.(agentId, proof) ?? proof) : {}\n const call: Call = {\n id: `call-${randomUUID().slice(0, 8)}`, agentId, kind, reason, urgency, status: \"ringing\", createdAt: now,\n ringingSince: now, answeredAt: null, endedAt: null, ringAgainAt: null, note: null, summary: null,\n channel: session.channel && session.chatId ? session.channel : null,\n chatId: session.channel && session.chatId ? session.chatId : null,\n }\n this.deps.store.insert(call)\n this.deps.log?.(`[calls] ${agentId} asks ${kind === \"camera\" ? \"to see\" : \"for a call\"} (${urgency}): ${reason}`)\n const rang = await this.ring(call)\n const saved = this.deps.store.get(call.id)!\n if (rang !== \"waiting\") return { ok: true, call: saved, rang }\n // The widget may have come free while the owner was being told.\n return this.waiting.has(call.id) ? { ok: true, call: saved, rang, busy: this.busyInfo(call) } : { ok: true, call: saved, rang: \"widget\" }\n }\n\n /** The caller withdraws its own voice call while it waits or rings, for\n * example to send a message instead (#969). */\n async cancel(input: { agentId?: unknown }, proof: CallerProof = {}): Promise<CallResult> {\n const agentId = String(input.agentId ?? \"\").trim()\n if (!agentId) return { ok: false, status: 400, error: \"Required: agentId\" }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} asked for this. An agent withdraws its own call from inside its run.` }\n }\n // No sweep first: a call back that is due must not ring just to be withdrawn.\n const mine = this.deps.store.list({ status: [\"ringing\", \"later\"], kind: \"voice\", limit: 200 }).find((c) => c.agentId === agentId)\n if (!mine) return { ok: false, status: 404, error: `${agentId} has no call waiting or ringing` }\n const ended = this.deps.store.transition(mine.id, [\"ringing\", \"later\"], { status: \"ended\", endedAt: this.now(), ringingSince: null, ringAgainAt: null, note: CANCELLED_NOTE })\n if (!ended) return { ok: false, status: 409, error: `call is ${this.deps.store.get(mine.id)?.status}` }\n this.setWaiting(mine.id, null)\n this.deps.log?.(`[calls] ${mine.id} withdrawn by ${agentId}`)\n return { ok: true, call: ended }\n }\n\n /** Ring now, wait for a busy widget, or hold as missed in Focus.\n * Returns how it rang. */\n private async ring(call: Call): Promise<Rang> {\n const focus = (this.deps.focus ?? readFocus)()\n if (focus.active && call.urgency !== \"urgent\") {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: focusLabel(focus) })) return false\n await this.notice(call, this.missedTitle(call))\n return false\n }\n // A camera ask is answered on the phone, never on the widget.\n if (call.kind === \"camera\") {\n await this.notice(call, `${this.name(call.agentId)} wants to see through your camera`,\n `Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n if (this.widgetAlive()) {\n // Busy by its last poll, or by a call placed since that it will show first.\n if (!this.widget.busy && !this.otherCalls(call).length) return \"widget\"\n this.startWaiting(call)\n await this.callWaiting(call)\n return \"waiting\"\n }\n await this.notice(call, `${this.name(call.agentId)} is calling`,\n `Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n\n private async notice(call: Call, title: string, extra?: string): Promise<void> {\n try {\n await this.deps.alert({\n title, message: extra ? `${call.reason}\\n${extra}` : call.reason,\n urgent: call.urgency === \"urgent\", from: call.agentId,\n })\n } catch (e: any) {\n this.deps.log?.(`[calls] notice for ${call.id} failed: ${e?.message ?? e}`)\n }\n }\n\n /** The call starts waiting for the busy widget. */\n private startWaiting(call: Call): void {\n const now = this.now()\n this.setWaiting(call.id, { since: now, toldAt: now })\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} waits: the widget is busy`)\n }\n\n /** Voice calls ringing or answered other than `call`, oldest first. */\n private otherCalls(call: Call): Call[] {\n return this.deps.store.list({ status: [\"ringing\", \"answered\"], kind: \"voice\", limit: 200 }).filter((c) => c.id !== call.id).reverse()\n }\n\n private async callWaiting(call: Call): Promise<void> {\n const busy = this.deps.config().busy\n await this.notice(call, busy.title.replaceAll(\"{agent}\", this.name(call.agentId)), busy.notice)\n }\n\n /** Why the owner cannot take `call` now, and how long it waits. */\n private busyInfo(call: Call): BusyInfo {\n const cfg = this.deps.config().busy\n const { since, toldAt } = this.waiting.get(call.id) ?? { since: this.now(), toldAt: this.now() }\n const others = this.otherCalls(call)\n const talking = others.find((c) => c.status === \"answered\")\n // The call on the pill, or the oldest one it has yet to show.\n const showing = others.find((c) => c.id === this.widget.showing) ?? others.find((c) => !this.waiting.has(c.id))\n // After the wait the ring time runs once more before it is missed.\n const waitsUntil = since + cfg.maxWaitMinutes * 60_000 + this.deps.config().ringSeconds * 1000\n const remindAt = toldAt + cfg.retryMinutes * 60_000\n const stopsWaiting = since + cfg.maxWaitMinutes * 60_000\n return {\n why: talking ? \"call\" : showing ? \"ringing\" : \"voice\",\n with: talking ? this.name(talking.agentId) : showing ? this.name(showing.agentId) : null,\n remindAt: remindAt < stopsWaiting ? remindAt : null,\n waitsUntil,\n }\n }\n\n private missedTitle(call: Call): string {\n return call.kind === \"camera\" ? `${this.name(call.agentId)} asked to see through your camera` : `Missed call from ${this.name(call.agentId)}`\n }\n\n /** Why the owner is in a call now, or null (#394, the call check): a\n * voice call answered in the last two hours and not ended, or a live\n * widget that says it is busy (a call, the microphone, a turn). */\n inCall(): string | null {\n if (this.widgetAlive() && this.widget.busy) return \"the widget is busy\"\n const since = this.now() - CALL_HOLD_MAX_MS\n const live = this.deps.store.list({ status: [\"answered\"], limit: 5 }).find((c) => (c.answeredAt ?? 0) >= since)\n return live ? `on a call with ${this.name(live.agentId)}` : null\n }\n\n /** True when the widget polled recently. */\n widgetAlive(): boolean {\n return this.now() - this.widgetSeenAt < WIDGET_FRESH_MS\n }\n\n /** The widget's poll: voice calls ringing now, oldest first. Marks it alive. */\n async ringing(widget: WidgetState = {}): Promise<Call[]> {\n this.widgetSeenAt = this.now()\n this.widget = widget\n await this.hold(widget)\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 }).reverse()\n }\n\n /** A busy widget cannot ring: the ring time of each call it is not\n * showing starts over at every poll, for calls.busy.maxWaitMinutes at\n * most. The owner is told that the call waits, and again every\n * calls.busy.retryMinutes. The ring time starts over once more when the\n * widget first shows a call that waited. */\n private async hold(widget: WidgetState): Promise<void> {\n const now = this.now()\n const busy = this.deps.config().busy\n // Told after the loop: a poll that overlaps a slow notice must not\n // carry on from a list and a widget state that are out of date.\n const tell: Call[] = []\n const live = this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 })\n for (const id of [...this.waiting.keys()]) if (!live.some((c) => c.id === id)) this.setWaiting(id, null)\n for (const call of live) {\n if (call.id === widget.showing) {\n if (this.waiting.has(call.id)) {\n this.setWaiting(call.id, null)\n this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })\n }\n continue\n }\n if (!widget.busy) continue\n const held = this.waiting.get(call.id)\n if (held && now - held.since >= busy.maxWaitMinutes * 60_000) continue\n if (!this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })) continue\n if (!held) this.startWaiting(call)\n else if (now - held.toldAt >= busy.retryMinutes * 60_000) this.setWaiting(call.id, { since: held.since, toldAt: now })\n else continue\n tell.push(call)\n }\n for (const call of tell) await this.callWaiting(call)\n }\n\n /** The phone app's poll: camera asks waiting for the owner, oldest first.\n * Does not count as the widget being up. */\n async asking(): Promise<Call[]> {\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"camera\", limit: 20 }).reverse()\n }\n\n /** Unanswered calls become missed; \"later\" calls that came due ring\n * again; answered calls nobody hung up end after calls.maxCallMinutes. */\n async sweep(): Promise<void> {\n const now = this.now()\n const ringFor = this.deps.config().ringSeconds * 1000\n const talkFor = this.deps.config().maxCallMinutes * 60_000\n for (const call of this.deps.store.list({ status: [\"ringing\", \"later\", \"answered\"], limit: 200 })) {\n // The list was read before any await below: each move re-checks the\n // status in the store, so a call answered meanwhile is left alone.\n if (call.status === \"ringing\" && call.ringingSince !== null && now - call.ringingSince >= ringFor) {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: this.waiting.has(call.id) ? \"widget busy\" : \"not answered\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} missed`)\n await this.notice(call, this.missedTitle(call))\n } else if (call.status === \"answered\" && call.answeredAt !== null && now - call.answeredAt >= talkFor) {\n if (!this.deps.store.transition(call.id, \"answered\", { status: \"ended\", endedAt: now, note: \"no hang-up\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)\n } else if (call.status === \"later\" && call.ringAgainAt !== null && now >= call.ringAgainAt) {\n const again = this.deps.store.transition(call.id, \"later\", { status: \"ringing\", ringingSince: now, ringAgainAt: null })\n if (!again) continue\n // A wait from before the snooze does not carry over.\n this.setWaiting(call.id, null)\n await this.ring(again)\n }\n }\n }\n\n /** The owner picked up, or tapped Show. Returns the call and, for a\n * voice call, the opener for /ask. */\n answer(id: string): CallResult & { opener?: string } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const answered = this.deps.store.transition(id, \"ringing\", { status: \"answered\", answeredAt: this.now(), ringingSince: null })\n if (!answered) return { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n this.deps.log?.(`[calls] ${id} answered (${call.agentId})`)\n return answered.kind === \"camera\" ? { ok: true, call: answered } : { ok: true, call: answered, opener: openerPrompt(answered) }\n }\n\n decline(id: string): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const declined = this.deps.store.transition(id, [\"ringing\", \"later\"], { status: \"declined\", endedAt: this.now(), ringingSince: null, ringAgainAt: null })\n return declined ? { ok: true, call: declined } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}` }\n }\n\n /** \"Call back in N min\": it rings again then. */\n later(id: string, minutes?: unknown): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const n = minutes === undefined ? LATER_DEFAULT_MINUTES : Number(minutes)\n if (!Number.isInteger(n) || n < 1 || n > LATER_MAX_MINUTES) {\n return { ok: false, status: 400, error: `minutes must be a whole number from 1 to ${LATER_MAX_MINUTES}` }\n }\n const later = this.deps.store.transition(id, \"ringing\", { status: \"later\", ringingSince: null, ringAgainAt: this.now() + n * 60_000 })\n return later ? { ok: true, call: later } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n }\n\n /** End an answered call. The summary is written in the background;\n * `summarized` settles when it is filed (tests wait on it). */\n hangup(id: string): CallResult & { summarized?: Promise<void> } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now() })\n if (!ended) return { ok: false, status: 409, error: `call is ${call.status}, not answered` }\n this.deps.log?.(`[calls] ${id} ended (${call.agentId})`)\n const summarize = this.deps.summarize\n // What the agent saw is already its answer; only a voice call is summarised.\n if (ended.kind === \"camera\" || !this.deps.config().summary || !summarize) return { ok: true, call: ended }\n const summarized = (async () => {\n try {\n const text = (await summarize(ended))?.trim()\n if (!text) return\n const done = this.deps.store.update(id, { summary: text })!\n this.deps.file?.(done, text)\n } catch (e: any) {\n this.deps.log?.(`[calls] summary for ${id} failed: ${e?.message ?? e}`)\n }\n })()\n return { ok: true, call: ended, summarized }\n }\n\n /** A camera ask ends when the share ends; no summary. Only an answered\n * ask ends this way, so a stop before Show leaves it ringing. */\n endCamera(id: string, note?: string): boolean {\n const call = this.deps.store.get(id)\n if (!call || call.kind !== \"camera\") return false\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now(), note: note ?? null })\n if (ended) this.deps.log?.(`[calls] ${id} ended (${call.agentId}): ${note ?? \"share over\"}`)\n return !!ended\n }\n\n get(id: string): Call | undefined { return this.deps.store.get(id) }\n\n list(opts: Parameters<CallStore[\"list\"]>[0] = {}): Call[] { return this.deps.store.list(opts) }\n}\n\n/** How a call reached the owner: on the widget, through notify, waiting\n * for a busy widget, or not at all (held as missed). */\nexport type Rang = \"widget\" | \"notify\" | \"waiting\" | false\n\n/** The note on a call its caller withdrew. */\nexport const CANCELLED_NOTE = \"withdrawn by the caller\"\n\n/** What the caller reads when its call waits for a busy owner. Times are\n * in the host's time zone (or `timeZone`), and say which one. */\nexport function busyText(busy: BusyInfo, now: number = Date.now(), timeZone?: string): string {\n const at = (ms: number) => `${new Date(ms).toLocaleTimeString(\"en-GB\", { hour: \"2-digit\", minute: \"2-digit\", hour12: false, timeZoneName: \"short\", timeZone })} (in ${Math.max(1, Math.round((ms - now) / 60_000))} min)`\n const why = busy.why === \"call\" ? `The owner is busy in another call${busy.with ? ` (with ${busy.with})` : \"\"}.`\n : busy.why === \"ringing\" ? `The owner is busy: another call${busy.with ? ` (from ${busy.with})` : \"\"} is ringing.`\n : \"The owner is busy on the voice widget.\"\n return `${why} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. ` +\n (busy.remindAt !== null ? `Still busy at ${at(busy.remindAt)}, they are told again that you are waiting. ` : \"\") +\n `It waits until ${at(busy.waitsUntil)} at most, then counts as a missed call.`\n}\n\nconst unknown = (id: string): CallResult => ({ ok: false, status: 404, error: `No such call: ${id}` })\n","// --- Calls an agent placed to the owner, kept in SQLite ---\n//\n// One row per call, so a missed call is still there after a restart and\n// the dashboard can list them. Nothing else reads this table. A camera\n// ask (#325 phase 3: \"show me\") is a call of kind \"camera\": same\n// allowlist, same hourly limit, same lifecycle, answered on the phone\n// instead of the voice widget.\n\nimport type Database from \"better-sqlite3\"\n\nexport type CallStatus = \"ringing\" | \"answered\" | \"ended\" | \"declined\" | \"missed\" | \"later\"\nexport type CallUrgency = \"normal\" | \"urgent\"\n/** \"voice\": a live voice call on the widget. \"camera\": the agent asks to see through the phone camera. */\nexport type CallKind = \"voice\" | \"camera\"\n\nexport interface Call {\n id: string\n agentId: string\n kind: CallKind\n reason: string\n urgency: CallUrgency\n status: CallStatus\n /** The channel and chat the asking turn ran in, so what the agent sees\n * is answered there; null when the turn had none (a task run). */\n channel: string | null\n chatId: string | null\n /** When the agent asked, ms since the epoch. */\n createdAt: number\n /** When it last started ringing: at the request, or when \"later\" came due. */\n ringingSince: number | null\n answeredAt: number | null\n endedAt: number | null\n /** Status \"later\": when it rings again. */\n ringAgainAt: number | null\n /** Why it ended the way it did, in a few words (\"in Focus\", \"not answered\"). */\n note: string | null\n /** The agent's own summary, written after hang-up. */\n summary: string | null\n}\n\n/** A call waiting for a busy owner: since when, and when the owner was\n * last told. Ms since the epoch. */\nexport interface Waiting {\n since: number\n toldAt: number\n}\n\n/** A call that stops ringing (answered, snoozed, missed...) is no longer\n * waiting: the mark goes in the same write, so a call that rings again\n * later starts a fresh wait, even across a restart. */\nfunction leaving(patch: { status?: CallStatus }): string {\n return patch.status && patch.status !== \"ringing\" ? \", waiting_since=NULL, told_at=NULL\" : \"\"\n}\n\n/** Calls still in progress: an agent has at most one of these. */\nexport const LIVE: CallStatus[] = [\"ringing\", \"answered\", \"later\"]\n\nconst COLUMNS = `id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,\n ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,\n ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`\n\nexport class CallStore {\n constructor(private db: Database.Database) {\n db.exec(`CREATE TABLE IF NOT EXISTS calls (\n id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,\n urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,\n ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,\n ring_again_at INTEGER, note TEXT, summary TEXT);\n CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`)\n // Columns added since the table first shipped (#325 phase 3). A row\n // from before is a voice call from a turn whose chat was not kept.\n const have = new Set((db.prepare(\"PRAGMA table_info(calls)\").all() as Array<{ name: string }>).map((c) => c.name))\n if (!have.has(\"kind\")) db.exec(\"ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'\")\n if (!have.has(\"channel\")) db.exec(\"ALTER TABLE calls ADD COLUMN channel TEXT\")\n if (!have.has(\"chat_id\")) db.exec(\"ALTER TABLE calls ADD COLUMN chat_id TEXT\")\n // A call waiting for a busy owner (#969), kept so a restart does not\n // forget it: since when it waits, and when the owner was last told.\n if (!have.has(\"waiting_since\")) db.exec(\"ALTER TABLE calls ADD COLUMN waiting_since INTEGER\")\n if (!have.has(\"told_at\")) db.exec(\"ALTER TABLE calls ADD COLUMN told_at INTEGER\")\n }\n\n /** Ringing calls marked as waiting for a busy owner, by id. */\n waiting(): Map<string, Waiting> {\n const rows = this.db.prepare(\"SELECT id, waiting_since AS since, told_at AS toldAt FROM calls WHERE waiting_since IS NOT NULL AND status='ringing'\")\n .all() as Array<{ id: string } & Waiting>\n return new Map(rows.map(({ id, since, toldAt }) => [id, { since, toldAt: toldAt ?? since }]))\n }\n\n /** Mark a call as waiting, or clear the mark with null. */\n setWaiting(id: string, w: Waiting | null): void {\n this.db.prepare(\"UPDATE calls SET waiting_since=?, told_at=? WHERE id=?\").run(w?.since ?? null, w?.toldAt ?? null, id)\n }\n\n insert(call: Call): void {\n this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,\n answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n call.id, call.agentId, call.kind, call.reason, call.urgency, call.status, call.createdAt,\n call.ringingSince, call.answeredAt, call.endedAt, call.ringAgainAt, call.note, call.summary, call.channel, call.chatId)\n }\n\n get(id: string): Call | undefined {\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls WHERE id=?`).get(id) as Call | undefined\n }\n\n /** Write the changed fields of one call. */\n update(id: string, patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const cols: Record<string, string> = {\n reason: \"reason\", urgency: \"urgency\", status: \"status\", ringingSince: \"ringing_since\",\n answeredAt: \"answered_at\", endedAt: \"ended_at\", ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n if (keys.length) {\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \") + leaving(patch)\n this.db.prepare(`UPDATE calls SET ${set} WHERE id=?`).run(...keys.map((k) => (patch as any)[k] ?? null), id)\n }\n return this.get(id)\n }\n\n /** Change a call only if it is still `from`: the check and the write are\n * one statement, so two sweeps, or a sweep and the owner answering,\n * cannot both move the same call. Undefined when it had moved on. */\n transition(id: string, from: CallStatus | CallStatus[], patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const statuses = Array.isArray(from) ? from : [from]\n const cols: Record<string, string> = {\n status: \"status\", ringingSince: \"ringing_since\", answeredAt: \"answered_at\", endedAt: \"ended_at\",\n ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \") + leaving(patch)\n const marks = statuses.map(() => \"?\").join(\",\")\n const changed = this.db.prepare(`UPDATE calls SET ${set} WHERE id=? AND status IN (${marks})`)\n .run(...keys.map((k) => (patch as any)[k] ?? null), id, ...statuses).changes\n return changed ? this.get(id) : undefined\n }\n\n /** Newest first; only these statuses, and only this kind, when given. */\n list(opts: { status?: CallStatus[]; kind?: CallKind; limit?: number } = {}): Call[] {\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200)\n const where: string[] = []\n const args: unknown[] = []\n if (opts.status?.length) {\n where.push(`status IN (${opts.status.map(() => \"?\").join(\",\")})`)\n args.push(...opts.status)\n }\n if (opts.kind) { where.push(\"kind=?\"); args.push(opts.kind) }\n const filter = where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls ${filter} ORDER BY created_at DESC, rowid DESC LIMIT ?`)\n .all(...args, limit) as Call[]\n }\n\n /** Calls this agent placed after `since` (ms), for the hourly limit. */\n countSince(agentId: string, since: number): number {\n return (this.db.prepare(\"SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?\")\n .get(agentId, since) as { n: number }).n\n }\n}\n"],"mappings":"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","names":["randomUUID","leaving","patch","LIVE","COLUMNS","CallStore","db","have","c","rows","id","since","toldAt","w","call","cols","keys","k","set","from","statuses","marks","opts","limit","where","args","filter","agentId","REASON_MAX","WIDGET_FRESH_MS","CALL_HOLD_MAX_MS","LATER_DEFAULT_MINUTES","LATER_MAX_MINUTES","callerHeaders","env","openerPrompt","call","SUMMARY_PROMPT","CallService","deps","id","w","agentId","input","proof","reason","urgency","kind","cfg","LIVE","c","now","session","randomUUID","rang","saved","mine","ended","CANCELLED_NOTE","focus","readFocus","focusLabel","title","extra","e","busy","since","toldAt","others","talking","showing","waitsUntil","remindAt","stopsWaiting","live","widget","tell","held","ringFor","talkFor","again","unknown","answered","declined","minutes","n","later","summarize","summarized","text","done","note","opts","busyText","timeZone","at","ms"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{a as D,b as _}from"./chunk-RKPAVWXF.js";import{a as R,b as q,c as O,d as B,f as k,g as v,h as E,i as w,j as y}from"./chunk-
|
|
1
|
+
import{a as D,b as _}from"./chunk-RKPAVWXF.js";import{a as R,b as q,c as O,d as B,f as k,g as v,h as E,i as w,j as y}from"./chunk-SANYTBHX.js";import{a as T,b as C,c as I,d as x,e as P}from"./chunk-VMMCT27Z.js";import{existsSync as X,readFileSync as F}from"fs";var J=new Set(["cron","heartbeat","api","a2a","mesh"]),M=160;function s(n){return typeof n=="string"&&n.trim()?n.trim():void 0}function S(n,e=M){let t=n.replace(/\s+/g," ").trim();return t.length>e?`${t.slice(0,e-1)}\u2026`:t}function V(n){let e=T(n);if(!X(e))throw new Error(`config not found at ${e}`);return JSON.parse(F(e,"utf-8"))}function W(n,e){return n?.agents?.[e]?.admin===!0}function Y(n,e,t){return W(n,t.agentId)||e?.createdBy===t.agentId}function Z(n,e){let t=e?.createdBy?`agent "${e.createdBy}"`:"the operator";return`Not allowed: "${n}" was created by ${t}. Agents manage only schedules they created (unless configured admin).`}function K(n,e){let t=s(n.notify);if(t!=="none"){if(t&&t!=="here"){let r=t.split(":");return r.length<2||!r[0]||!r[1]?"invalid":{channel:r[0],chatId:r.slice(1).join(":")}}if(e.channel&&e.chatId&&!J.has(e.channel))return{channel:e.channel,chatId:e.chatId,...e.accountId?{accountId:e.accountId}:{}}}}function Q(n,e,t){let r=e.timezone||"UTC",d=e.enabled&&!y(e)?w(E(e.schedule,r,t),r):"\u2014",h=e.approval?`pending ${e.approval.action} approval`:e.enabled?"active":"paused",f=[`- ${n} [${h}] ${e.schedule} \u2014 ${v(e.schedule)} (${r})`,` agent: ${e.agent}; created by: ${e.createdBy||"operator"}; next: ${d}`];return e.notify&&f.push(` notify: ${e.notify.channel} ${e.notify.chatId}${e.deliverResult===!1?" (failures only)":" (results and failures)"}`),e.command?f.push(` command: ${S(String(e.command))}`):e.prompt&&f.push(` prompt: ${S(String(e.prompt))}`),f.join(`
|
|
2
2
|
`)}async function b(n,e,t){let r=n?.notifications?.destination;if(!r?.channel||!r?.chatId)return"No operator notification destination is configured (notifications.destination); the operator must check `agentx schedule list`.";if(!e.notifyOperator)return"Operator notification is unavailable in this context.";try{return await e.notifyOperator({channel:r.channel,chatId:r.chatId,accountId:r.accountId},t),"The operator has been notified."}catch(d){return`Could not notify the operator (${d?.message??d}); the request is still recorded.`}}async function re(n,e,t={}){let r=(s(n.action)||"list").toLowerCase(),d=t.now?t.now():new Date,h;try{h=V(t.configPath)}catch(a){return`Error: ${a.message}`}let f=h.crons||{};if(r==="list"){let a=n.mine===!0,l=Object.entries(f).filter(([,i])=>!a||e&&i?.createdBy===e.agentId);return l.length?l.map(([i,p])=>Q(i,p,d)).join(`
|
|
3
3
|
`):a?"You have no schedules.":"No schedules."}if(!e?.agentId)return"Error: caller agent is unknown (AGENTX_AGENT_ID not set). Pass callerAgentId.";if(!h.agents?.[e.agentId])return`Error: caller "${e.agentId}" is not an agent on this node.`;let o=s(n.id),m=a=>C(a,{configPath:t.configPath,reload:t.reload});if(r==="create"){let a=s(n.when),l=s(n.prompt);if(!a||!l)return"Error: create needs `when` (plain English) and `prompt`.";let i=D(a);if(!i)return`Error: couldn't parse "${a}". ${q}.`;let p=s(n.agent)||e.agentId;if(!h.agents?.[p])return`Error: agent "${p}" not found on this node.`;let g=s(n.timezone)||R;if(!B(g))return`Error: unknown timezone "${g}".`;let u=o||_(i.matched,p);if(!O(u))return`Error: invalid id "${u}" (letters, digits, "-" and "_" only).`;if(f[u])return`Error: a schedule "${u}" already exists. Pick another \`id\`.`;let $=K(n,e);if($==="invalid")return'Error: `notify` must be "here", "none", or "channel:chatId".';let z={action:"create",requestedBy:e.agentId,requestedAt:d.toISOString()},L=k({parsed:i,agent:p,prompt:l,timezone:g,notify:$,enabled:!1,createdBy:e.agentId,approval:z}),A=await m(U=>I(U,`crons.${u}`,L));if(!A.success)return`Error: ${A.error}`;let N=w(E(i.cron,g,d),g),G=[`Schedule approval requested by ${e.agentId}`,`Create: ${u}`,`When: ${i.human} \u2014 cron \`${i.cron}\` (${g})`,`Next fire: ${N}`,`Agent: ${p}`,$?`Results and failures go to: ${$.channel} ${$.chatId}${$.channel==="voice"?" (as a push notification)":""}`:"Results go to: run log only (no chat)",`Prompt: ${S(l,400)}`,"",`Approve: agentx schedule approve ${u}`,`Reject: agentx schedule reject ${u}`].join(`
|
|
4
4
|
`),H=await b(h,t,G);return[`Requested schedule "${u}": ${i.human} (cron ${i.cron}, ${g}); next fire would be ${N}.`,"It is saved DISABLED and will not run until the operator approves it.",H].join(`
|
|
5
5
|
`)}if(!o)return`Error: ${r} needs \`id\`.`;let c=f[o];if(!c)return`Error: schedule "${o}" not found.`;if(!Y(h,c,e))return Z(o,c);if(r==="pause"||r==="resume"){let a=r==="resume";if(a&&y(c))return`"${o}" is still awaiting operator approval; it can't be resumed until approved.`;if(c.enabled===a)return`"${o}" is already ${a?"active":"paused"}.`;let l=await m(i=>I(i,`crons.${o}.enabled`,a));return l.success?`"${o}" ${a?"resumed":"paused"}.`:`Error: ${l.error}`}if(r==="delete"){if(y(c)&&c.approval.requestedBy===e.agentId){let u=await m($=>P($,`crons.${o}`));return u.success?`Withdrew the pending request "${o}".`:`Error: ${u.error}`}if(y(c))return`"${o}" is a creation request still awaiting approval; only its requester or the operator (\`agentx schedule reject ${o}\`) can drop it.`;if(c.approval?.action==="delete")return`Deletion of "${o}" is already awaiting approval.`;let a={action:"delete",requestedBy:e.agentId,requestedAt:d.toISOString()},l=await m(u=>{if(!x(u,`crons.${o}`))throw new Error(`schedule "${o}" not found`);I(u,`crons.${o}.approval`,a)});if(!l.success)return`Error: ${l.error}`;let i=c.timezone||"UTC",p=[`Schedule approval requested by ${e.agentId}`,`Delete: ${o}`,`When: ${v(c.schedule)} \u2014 cron \`${c.schedule}\` (${i})`,`Next fire: ${c.enabled?w(E(c.schedule,i,d),i):"\u2014 (paused)"}`,`Agent: ${c.agent}`,"",`Approve: agentx schedule approve ${o}`,`Reject: agentx schedule reject ${o}`].join(`
|
|
6
6
|
`),g=await b(h,t,p);return[`Deletion of "${o}" requested. It keeps its current state until the operator approves.`,g].join(`
|
|
7
7
|
`)}return`Error: unknown action "${r}". Use list, create, pause, resume or delete.`}function oe(n,e=process.env){let t=s(e.AGENTX_AGENT_ID)||s(n.callerAgentId);if(!t)return null;let r=!!(s(e.AGENTX_CHANNEL)&&s(e.AGENTX_CHAT_ID)),d=s(r?e.AGENTX_CHANNEL:n.channel),h=s(r?e.AGENTX_CHAT_ID:n.chatId),f=s(n.accountId);return{agentId:t,channel:d,chatId:h,...f?{accountId:f}:{}}}export{re as a,oe as b};
|
|
8
|
-
//# sourceMappingURL=chunk-
|
|
8
|
+
//# sourceMappingURL=chunk-EV76NT3E.js.map
|
|
@@ -48,7 +48,7 @@ The "modified" field is optional and can contain updated values to pass forward.
|
|
|
48
48
|
... (output truncated)`:i;return s.exitCode!==0?{tool_use_id:e.id,content:`Command exited with code ${s.exitCode}:
|
|
49
49
|
${c}`,is_error:!0}:{tool_use_id:e.id,content:c||"(no output)"}}async editFile(e){let t=String(e.input.path||""),o=e.input.edits,n=ge.resolve(this.cwd,t);if(!o||o.length===0)return{tool_use_id:e.id,content:"No edits provided.",is_error:!0};let s=await W.checkFileWrite(t);if(s==="deny")return{tool_use_id:e.id,content:`File write blocked by permissions: ${t}`,is_error:!0};if(s==="skip")return{tool_use_id:e.id,content:`File write skipped (plan mode): ${t}`};if(f.has("pre:file-write")){let a=await f.execute("pre:file-write",{event:"pre:file-write",file:n,cwd:this.cwd});if(a.blocked)return{tool_use_id:e.id,content:a.message||`File edit blocked by pre:file-write hook: ${t}`,is_error:!0}}let i=await re.readFile(n,"utf8"),c=[];for(let a of o)i.includes(a.old_text)?(i=i.replace(a.old_text,a.new_text),c.push(`Replaced: "${a.old_text.slice(0,40)}..."`)):c.push(`Not found: "${a.old_text.slice(0,40)}..."`);return this.options.dryRun||await re.writeFile(n,i,"utf8"),f.has("post:file-write")&&await f.execute("post:file-write",{event:"post:file-write",file:n,fileContent:i,cwd:this.cwd}),{tool_use_id:e.id,content:`Edited ${t}:
|
|
50
50
|
${c.join(`
|
|
51
|
-
`)}`}}async createFiles(e){let t=e.input,o=t.files||[];return{tool_use_id:e.id,content:t.summary||`Queued ${o.length} file(s) for creation.`,files:o}}async spawnAgent(e){let t=String(e.input.agent_id||""),o=String(e.input.prompt||""),n=Number(e.input.timeout_seconds)||300;if(!t||!o)return{tool_use_id:e.id,content:"agent_id and prompt are required.",is_error:!0};try{let{SubAgentManager:s}=await import("./subagent-XY5N7TUU.js"),{getGlobalRegistry:i}=await import("./registry-
|
|
51
|
+
`)}`}}async createFiles(e){let t=e.input,o=t.files||[];return{tool_use_id:e.id,content:t.summary||`Queued ${o.length} file(s) for creation.`,files:o}}async spawnAgent(e){let t=String(e.input.agent_id||""),o=String(e.input.prompt||""),n=Number(e.input.timeout_seconds)||300;if(!t||!o)return{tool_use_id:e.id,content:"agent_id and prompt are required.",is_error:!0};try{let{SubAgentManager:s}=await import("./subagent-XY5N7TUU.js"),{getGlobalRegistry:i}=await import("./registry-NIAPKDQF.js"),c=i();if(!c)return{tool_use_id:e.id,content:"Sub-agent spawning requires the daemon to be running.",is_error:!0};let p=await new s(c).spawn({targetAgentId:t,prompt:o,parentAgentId:"orchestrator",timeout:n*1e3});return p.success?{tool_use_id:e.id,content:`Sub-agent "${t}" completed (${Math.round(p.duration/1e3)}s):
|
|
52
52
|
|
|
53
53
|
${p.content}`}:{tool_use_id:e.id,content:`Sub-agent "${t}" failed: ${p.error}`,is_error:!0}}catch(s){return{tool_use_id:e.id,content:`Failed to spawn sub-agent: ${s.message}`,is_error:!0}}}async askUser(e){let t=String(e.input.question||""),o=e.input.options,n=t;return o?.length&&(n+=`
|
|
54
54
|
Options: ${o.join(", ")}`),{tool_use_id:e.id,content:"Question sent to user.",followUp:n}}};async function Ae(r){let{provider:e,systemPrompt:t,messages:o,providerOptions:n,cwd:s,maxIterations:i=20,enabledTools:c,interactive:a=!0,overwrite:p=!1,dryRun:l=!1,onProgress:d,abortSignal:u}=r,m=u?{...n,abortSignal:u}:n;if(!e.generateRaw)return dt(r);let I=new se(s,{interactive:a,overwrite:p,dryRun:l,acmeContext:r.acmeContext,abortSignal:u}),D=be(c);if(r.acmeContext)for(let g of at)D.push({name:g.name,description:g.description,input_schema:g.input_schema});let E=new Set;if(r.mcpTools?.length)for(let g of r.mcpTools)D.push({name:g.name,description:g.description,input_schema:g.input_schema}),E.add(g.name);let C=o.filter(g=>g.role!=="system").map(g=>({role:g.role,content:g.content})),B=[],G=0,F=0,A=0,O="",j,_=0;for(;_<i;){if(_++,u?.aborted)throw new Error("cancelled");d?.({type:"iteration_start",iteration:_}),b.step(_,`Agentic loop iteration (${D.length} tools available)`);let g,M=!!e.generateRawStream&&!!d;try{if(M){let T=null,U=null,v="";for await(let x of e.generateRawStream(C,t,D,m))x.type==="text_delta"?(v+=x.text,d?.({type:"text_delta",text:x.text})):x.type==="thinking_delta"?d?.({type:"thinking_delta",text:x.text}):x.type==="raw_result"?T=x.result:x.type==="error"&&(U=x.error);if(U&&!T)throw new Error(U);if(!T)throw new Error("generateRawStream returned no raw_result event");g=T,g._streamedText=v}else g=await e.generateRaw(C,t,D,m)}catch(T){if(T.message?.includes("not available"))return dt(r);throw T}G+=g.usage.input_tokens+g.usage.output_tokens,F+=g.usage.input_tokens,A+=g.usage.output_tokens;let P=g._streamedText;for(let T of g.content)T.type==="text"&&(O+=T.text,P===void 0&&d?.({type:"text_delta",text:T.text}));if(P!==void 0&&delete g._streamedText,g.stop_reason==="end_turn"||g.stop_reason==="max_tokens")break;if(g.stop_reason==="tool_use"){let T=g.content.filter(v=>v.type==="tool_use");if(T.length===0)break;C.push({role:"assistant",content:g.content});let U=[];for(let v of T){d?.({type:"tool_call",name:v.name,id:v.id,input:v.input}),b.step(_,`Tool call: ${v.name}`);let x;if(E.has(v.name)&&r.mcpDispatch){let h=await r.mcpDispatch(v.name,v.input);x={tool_use_id:v.id,content:h.content,is_error:h.isError}}else x=await I.execute({name:v.name,id:v.id,input:v.input});d?.({type:"tool_result",name:v.name,id:v.id,content:x.content.slice(0,200),is_error:x.is_error}),x.files?.length&&(B.push(...x.files),d?.({type:"files_created",files:x.files})),x.followUp&&(j=x.followUp),U.push({type:"tool_result",tool_use_id:x.tool_use_id,content:x.content,is_error:x.is_error})}if(C.push({role:"user",content:U}),j)break;continue}break}return d?.({type:"complete",iterations:_,totalTokens:G}),{files:B,content:O,followUp:j,tokensUsed:G,inputTokens:F||void 0,outputTokens:A||void 0,iterations:_}}async function dt(r){let{provider:e,systemPrompt:t,messages:o,providerOptions:n,maxIterations:s=5}=r,c=[{role:"system",content:t+`
|
|
@@ -123,4 +123,4 @@ ${r.projectInstructions}`),r.memoryContext&&o.push(r.memoryContext),o.push(`# Ou
|
|
|
123
123
|
Generate output appropriate for: ${e}. Use the \`create_files\` tool to output all files.`),o.join(`
|
|
124
124
|
|
|
125
125
|
`)}export{qe as a,Vt as b,Yt as c,We as d,ze as e,Ve as f,Ye as g,Pe as h,ee as i,Re as j,le as k,te as l,Tt as m,oe as n,Mt as o,f as p,et as q,tt as r,Me as s,de as t,W as u,ne as v,it as w,Ee as x,$e as y,se as z,mt as A,Zn as B,er as C};
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-GMVWDQFF.js.map
|