agentix-cli 0.80.0 → 0.81.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-2UCGXCX3.js → App-HF6W6CV2.js} +2 -2
- package/dist/{App-2UCGXCX3.js.map → App-HF6W6CV2.js.map} +1 -1
- package/dist/ChatApp-74VBWP7R.js +7 -0
- package/dist/{ChatApp-IRPSBYC7.js.map → ChatApp-74VBWP7R.js.map} +1 -1
- package/dist/{agent-UPTK3HFR.js → agent-BDEPUFBQ.js} +2 -2
- package/dist/{board-dashboard-GAUAYKWV.js → board-dashboard-TWEF5XVE.js} +189 -169
- package/dist/board-dashboard-TWEF5XVE.js.map +1 -0
- package/dist/{chunk-PXIGWFIQ.js → chunk-4KJ3PCAI.js} +2 -2
- package/dist/{chunk-2YJ2ZIYC.js → chunk-5GXCOHDM.js} +2 -2
- package/dist/{chunk-2YJ2ZIYC.js.map → chunk-5GXCOHDM.js.map} +1 -1
- package/dist/{chunk-3GYCFRBK.js → chunk-74FSHUB3.js} +2 -2
- package/dist/{chunk-AIHWGU4P.js → chunk-ATXE3EMV.js} +2 -2
- package/dist/{chunk-BIJB7CNG.js → chunk-BB3ESU74.js} +2 -2
- package/dist/{chunk-Z6G26FWC.js → chunk-BDM4VTBR.js} +52 -52
- package/dist/chunk-BDM4VTBR.js.map +1 -0
- package/dist/chunk-BYD5B3P3.js +9 -0
- package/dist/{chunk-Z3IP66SL.js.map → chunk-BYD5B3P3.js.map} +1 -1
- package/dist/chunk-EFV7U5LD.js +152 -0
- package/dist/chunk-EFV7U5LD.js.map +1 -0
- package/dist/chunk-ES7Z64Z5.js +22 -0
- package/dist/chunk-ES7Z64Z5.js.map +1 -0
- package/dist/{chunk-WP6KLXTP.js → chunk-GLQLCBJS.js} +4 -4
- package/dist/chunk-GLQLCBJS.js.map +1 -0
- package/dist/{chunk-PHFOYJUB.js → chunk-GUAJNZIX.js} +2 -2
- package/dist/{chunk-4NUF3SXN.js → chunk-HUQRPEDP.js} +2 -2
- package/dist/{chunk-36TJTWG3.js → chunk-HYE2UUQ4.js} +248 -217
- package/dist/chunk-HYE2UUQ4.js.map +1 -0
- package/dist/chunk-JG3O5GL6.js +5 -0
- package/dist/{chunk-I2Z6D3MI.js.map → chunk-JG3O5GL6.js.map} +1 -1
- package/dist/chunk-JXF54PT7.js +2 -0
- package/dist/chunk-JXF54PT7.js.map +1 -0
- package/dist/chunk-LTHQXWZN.js +51 -0
- package/dist/chunk-LTHQXWZN.js.map +1 -0
- package/dist/{chunk-BBI3HG77.js → chunk-LUL4ILAQ.js} +2 -2
- package/dist/{chunk-N6EWZGIX.js → chunk-PN6XHTG5.js} +6 -6
- package/dist/{chunk-N6EWZGIX.js.map → chunk-PN6XHTG5.js.map} +1 -1
- package/dist/{chunk-KAFUQILA.js → chunk-TW4L6RP2.js} +2 -2
- package/dist/chunk-UCKKAWLZ.js +2 -0
- package/dist/chunk-UCKKAWLZ.js.map +1 -0
- package/dist/{chunk-PT5Z5WW4.js → chunk-VVW4JYV6.js} +24 -24
- package/dist/chunk-VVW4JYV6.js.map +1 -0
- package/dist/{chunk-Z522XZBL.js → chunk-WWU2D65N.js} +2 -2
- package/dist/chunk-XF65CDNS.js +5 -0
- package/dist/{chunk-AFSM3IY3.js.map → chunk-XF65CDNS.js.map} +1 -1
- package/dist/{chunk-O67KV3DX.js → chunk-XQ7MVPWP.js} +7 -7
- package/dist/chunk-XQ7MVPWP.js.map +1 -0
- package/dist/{chunk-7EIUNFDF.js → chunk-ZDDJNKU5.js} +2 -2
- package/dist/{chunk-D3FDPA74.js → chunk-ZUAV3PTL.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-JDY2FZUF.js +2 -0
- package/dist/config-JCETBKWT.js +2 -0
- package/dist/config-mutator-YQWTY47O.js +2 -0
- package/dist/doctor-6EWW7NC2.js +2 -0
- package/dist/{facts-YY2364CP.js → facts-5T5GRUTG.js} +6 -6
- package/dist/{facts-YY2364CP.js.map → facts-5T5GRUTG.js.map} +1 -1
- package/dist/{failure-candidates-RIXA3Y6D.js → failure-candidates-PBPF45GN.js} +2 -2
- package/dist/index.d.ts +630 -502
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{chunk-ECEDX6C6.js → program-FUYHN2F7.js} +193 -183
- package/dist/program-FUYHN2F7.js.map +1 -0
- package/dist/promote-ZZWMGVO2.js +2 -0
- package/dist/query-OQBB7EFS.js +2 -0
- package/dist/registry-PLSOZ3CL.js +2 -0
- package/dist/request-planner-HX44G2SX.js +2 -0
- package/dist/{request-planner-RNXA2QAH.js.map → request-planner-HX44G2SX.js.map} +1 -1
- package/dist/review-candidates-FBDLPU6H.js +4 -0
- package/dist/{review-candidates-THKWXS7S.js.map → review-candidates-FBDLPU6H.js.map} +1 -1
- package/dist/routing-3SZHWHUA.js +2 -0
- package/dist/schedule-tool-5FULSHWJ.js +2 -0
- package/dist/scheduler-ITOKSMYT.js +2 -0
- package/dist/seat-2HJG3UNW.js +2 -0
- package/dist/select-UKAOL7QB.js +2 -0
- package/dist/{select-PRQP2B6G.js.map → select-UKAOL7QB.js.map} +1 -1
- package/dist/set-enabled-ADFSNG5A.js +2 -0
- package/dist/{set-enabled-E2ML2E33.js.map → set-enabled-ADFSNG5A.js.map} +1 -1
- package/dist/signal-exit-WL7G3EEZ.js +2 -0
- package/dist/signal-exit-WL7G3EEZ.js.map +1 -0
- package/dist/{surface-usage-HVVP22SA.js → surface-usage-O5L4KLGS.js} +2 -2
- package/dist/{tool-4WPUUUE4.js → tool-MSXWU44P.js} +2 -2
- package/dist/{tool-4WPUUUE4.js.map → tool-MSXWU44P.js.map} +1 -1
- package/dist/tool-X7YEIJVZ.js +4 -0
- package/dist/tool-X7YEIJVZ.js.map +1 -0
- package/dist/wiki-QUVWB4IN.js +2 -0
- package/package.json +1 -1
- package/dist/ChatApp-IRPSBYC7.js +0 -7
- package/dist/board-dashboard-GAUAYKWV.js.map +0 -1
- package/dist/chunk-36TJTWG3.js.map +0 -1
- package/dist/chunk-445P5QPS.js +0 -15
- package/dist/chunk-445P5QPS.js.map +0 -1
- package/dist/chunk-6TLQ4I32.js +0 -57
- package/dist/chunk-6TLQ4I32.js.map +0 -1
- package/dist/chunk-AFSM3IY3.js +0 -5
- package/dist/chunk-ECEDX6C6.js.map +0 -1
- package/dist/chunk-I2Z6D3MI.js +0 -5
- package/dist/chunk-KBSBMOOL.js +0 -152
- package/dist/chunk-KBSBMOOL.js.map +0 -1
- package/dist/chunk-NMFXY3QU.js +0 -2
- package/dist/chunk-NMFXY3QU.js.map +0 -1
- package/dist/chunk-O67KV3DX.js.map +0 -1
- package/dist/chunk-PT5Z5WW4.js.map +0 -1
- package/dist/chunk-WP6KLXTP.js.map +0 -1
- package/dist/chunk-Z3IP66SL.js +0 -9
- package/dist/chunk-Z6G26FWC.js.map +0 -1
- package/dist/client-ABDGDO7S.js +0 -2
- package/dist/config-XPFCUOQT.js +0 -2
- package/dist/config-mutator-WYHQV2XF.js +0 -2
- package/dist/doctor-XLLNQRRT.js +0 -2
- package/dist/program-KZOBDSXQ.js +0 -2
- package/dist/promote-YUZL3ZU2.js +0 -2
- package/dist/query-3XH7I6WX.js +0 -2
- package/dist/registry-5OD2IN5G.js +0 -2
- package/dist/request-planner-RNXA2QAH.js +0 -2
- package/dist/review-candidates-THKWXS7S.js +0 -4
- package/dist/routing-X45VX723.js +0 -2
- package/dist/schedule-tool-ZTL6UZCC.js +0 -2
- package/dist/scheduler-VH7IJ4VA.js +0 -2
- package/dist/seat-LQ47SSNN.js +0 -2
- package/dist/select-PRQP2B6G.js +0 -2
- package/dist/set-enabled-E2ML2E33.js +0 -2
- package/dist/tool-67JK7SK6.js +0 -4
- package/dist/tool-67JK7SK6.js.map +0 -1
- package/dist/wiki-AS5YHSR7.js +0 -2
- package/dist/wiki-AS5YHSR7.js.map +0 -1
- /package/dist/{agent-UPTK3HFR.js.map → agent-BDEPUFBQ.js.map} +0 -0
- /package/dist/{chunk-PXIGWFIQ.js.map → chunk-4KJ3PCAI.js.map} +0 -0
- /package/dist/{chunk-3GYCFRBK.js.map → chunk-74FSHUB3.js.map} +0 -0
- /package/dist/{chunk-AIHWGU4P.js.map → chunk-ATXE3EMV.js.map} +0 -0
- /package/dist/{chunk-BIJB7CNG.js.map → chunk-BB3ESU74.js.map} +0 -0
- /package/dist/{chunk-PHFOYJUB.js.map → chunk-GUAJNZIX.js.map} +0 -0
- /package/dist/{chunk-4NUF3SXN.js.map → chunk-HUQRPEDP.js.map} +0 -0
- /package/dist/{chunk-BBI3HG77.js.map → chunk-LUL4ILAQ.js.map} +0 -0
- /package/dist/{chunk-KAFUQILA.js.map → chunk-TW4L6RP2.js.map} +0 -0
- /package/dist/{chunk-Z522XZBL.js.map → chunk-WWU2D65N.js.map} +0 -0
- /package/dist/{chunk-7EIUNFDF.js.map → chunk-ZDDJNKU5.js.map} +0 -0
- /package/dist/{chunk-D3FDPA74.js.map → chunk-ZUAV3PTL.js.map} +0 -0
- /package/dist/{client-ABDGDO7S.js.map → client-JDY2FZUF.js.map} +0 -0
- /package/dist/{config-XPFCUOQT.js.map → config-JCETBKWT.js.map} +0 -0
- /package/dist/{config-mutator-WYHQV2XF.js.map → config-mutator-YQWTY47O.js.map} +0 -0
- /package/dist/{doctor-XLLNQRRT.js.map → doctor-6EWW7NC2.js.map} +0 -0
- /package/dist/{failure-candidates-RIXA3Y6D.js.map → failure-candidates-PBPF45GN.js.map} +0 -0
- /package/dist/{program-KZOBDSXQ.js.map → promote-ZZWMGVO2.js.map} +0 -0
- /package/dist/{promote-YUZL3ZU2.js.map → query-OQBB7EFS.js.map} +0 -0
- /package/dist/{query-3XH7I6WX.js.map → registry-PLSOZ3CL.js.map} +0 -0
- /package/dist/{registry-5OD2IN5G.js.map → routing-3SZHWHUA.js.map} +0 -0
- /package/dist/{routing-X45VX723.js.map → schedule-tool-5FULSHWJ.js.map} +0 -0
- /package/dist/{schedule-tool-ZTL6UZCC.js.map → scheduler-ITOKSMYT.js.map} +0 -0
- /package/dist/{scheduler-VH7IJ4VA.js.map → seat-2HJG3UNW.js.map} +0 -0
- /package/dist/{seat-LQ47SSNN.js.map → surface-usage-O5L4KLGS.js.map} +0 -0
- /package/dist/{surface-usage-HVVP22SA.js.map → wiki-QUVWB4IN.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{b as a,c as b,d as c,e as d,f as e,g as f,h as g,i as h,j as i,k as j,l as k,m as l,n as m,o as n,p as o,q as p,r as q,s as r,t as s,u as t}from"./chunk-WWU2D65N.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import"./chunk-DP7RTNDM.js";import"./chunk-LUL4ILAQ.js";import"./chunk-CECHWYJ2.js";import"./chunk-GUAJNZIX.js";import"./chunk-WPNRDEHW.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{b as DEFAULT_PROMOTE_TYPES,a as PROMOTER_OWNER,c as PROMOTION_LEDGER_FILE,j as appendPromotionLedger,s as approveProposal,n as callPromotionLlm,p as estimateTokens,r as evidenceFor,m as getUnpromotedMemories,k as groupCandidates,h as listAllAgentMemories,d as memoryKey,e as memoryStamp,g as mergeSources,f as parseMemoryStamp,l as parsePromotionResponse,i as readPromotionLedger,t as rejectProposal,o as runPromotion,q as sourceKind};
|
|
2
|
+
//# sourceMappingURL=promote-ZZWMGVO2.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b,c}from"./chunk-HUQRPEDP.js";import"./chunk-GUAJNZIX.js";import"./chunk-WPNRDEHW.js";import"./chunk-2GIDVZ4H.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{b as DEFAULT_GRAPH_WEIGHT,a as agenticQuery,c as rankCatalogPool};
|
|
2
|
+
//# sourceMappingURL=query-OQBB7EFS.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{J as a,K as b,L as c,M as d,N as e,O as f,P as g,Q as h,R as i,S as j,T as k,U as l}from"./chunk-EFV7U5LD.js";import"./chunk-ES7Z64Z5.js";import"./chunk-GLQLCBJS.js";import"./chunk-DP7RTNDM.js";import"./chunk-SZDACK6O.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import"./chunk-4KJ3PCAI.js";import"./chunk-XF65CDNS.js";import"./chunk-VWTBHLMX.js";import"./chunk-ATXE3EMV.js";import"./chunk-LUL4ILAQ.js";import"./chunk-KA2UIZXG.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-74FSHUB3.js";import"./chunk-MYVHVJWZ.js";import"./chunk-DWADWDMU.js";import"./chunk-23A5K5UW.js";import"./chunk-QD6IICK7.js";import"./chunk-CECHWYJ2.js";import"./chunk-HUQRPEDP.js";import"./chunk-GUAJNZIX.js";import"./chunk-WPNRDEHW.js";import"./chunk-2GIDVZ4H.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-DSYKYZMT.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{k as AgentRegistry,f as TRACE_STEP_SUMMARY_BYTES,c as buildEntryMeta,d as buildEventDigest,e as buildWikiContext,g as clipForTrace,j as emitTraceStepsFromStreamEvent,i as firstMissingRequiredTool,b as getGlobalRegistry,l as richReplyPrompt,a as setGlobalRegistry,h as tallyToolUses};
|
|
2
|
+
//# sourceMappingURL=registry-PLSOZ3CL.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{q as m,t as p,v as d,w as x}from"./chunk-5GXCOHDM.js";import{a as u,f}from"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";x();f();var l="request-gate",h="request-context",g=.1;async function q(t,o,c,r=Math.random){let s=p(l)==="active";if(s){let i=m().seats[l]?.holdout??g;if(r()<i)return{active:!0,preprocess:!1,arm:"holdout"}}let a=s?"treatment":void 0,e=await d(l,{request:t.slice(0,2e3),agent:o,channel:c,operations:["select optional context","evaluate model routing where permitted"],constraints:["Keep the original request and mandatory instructions","Desktop model cannot be downgraded"]},{preprocess:u("This request benefits from structured context selection or another bounded typed decision before the main agent executes.",{true:"Relevant context must be selected, or a bounded routing decision can help. Follow-ups and ambiguous references need context.",false:"Typed preprocessing adds no useful decision; send the request to the configured main agent with existing context."})},{timeoutMs:3e3});if(!e||e.mode!=="active")return{active:!1,preprocess:!1,arm:a};let n=e.answers.preprocess?.noul;return{active:!0,preprocess:Number.isFinite(n)&&n>=.5,arm:a}}var v={landscape:"Agent directory and mesh overview",patternContext:"Learned behavioral patterns",references:"Retrieved project references",memoryContext:"Retrieved agent memory",crossChatContext:"Summaries from other conversations",longMemoryRecall:"Older conversation recall",wikiContext:"Retrieved wiki knowledge"};async function b(t){let o=Object.entries(v).flatMap(([e,n])=>{let i=t[e];return i?[{id:e,description:n,source:e,characters:i.length,preview:i.slice(0,400),trust:"context data, not instructions"}]:[]});if(!o.length)return{input:t,excluded:[]};let c=Object.fromEntries(o.map(e=>[e.id,u(`Context block ${e.id} is relevant to completing the current request correctly.`,{true:"Needed or potentially relevant; retain if uncertain or the preview is incomplete.",false:"Clearly unrelated to the current request; safe to omit."})])),r=await d(h,{request:t.message.slice(0,2e3),channel:t.channel,agent:t.agentId,mandatory:["request","identity","permissions and instructions","same-chat history","handover","attachments"],context:o},c,{timeoutMs:3e3});if(!r||r.mode!=="active")return{input:t,excluded:[]};let s={...t},a=[];for(let e of o){let n=r.answers[e.id]?.noul;Number.isFinite(n)&&n>=0&&n<.2&&(delete s[e.id],a.push(e.id))}return{input:s,excluded:a}}export{g as DEFAULT_REQUEST_GATE_HOLDOUT,h as REQUEST_CONTEXT_SEAT,l as REQUEST_GATE_SEAT,q as evaluateRequest,b as selectRequestContext};
|
|
2
|
+
//# sourceMappingURL=request-planner-HX44G2SX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agents/request-planner.ts"],"sourcesContent":["import { askSeat, decisionsRuntime, getSeatMode } from '@/decisions/seat'\nimport { noul } from '@/decisions/questions'\nimport type { NoulAnswer, Questions } from '@/decisions/types'\nimport type { ContextInput } from './context'\n\nexport const REQUEST_GATE_SEAT = 'request-gate'\nexport const REQUEST_CONTEXT_SEAT = 'request-context'\n\n/** Share of turns held out of Jev preprocessing when the gate is active.\n * Overridden by decisions.seats.request-gate.holdout; 0 turns it off. */\nexport const DEFAULT_REQUEST_GATE_HOLDOUT = 0.1\n\n/** Experiment arm for an active gate. \"treatment\" is assigned before the\n * gate is asked, so a failed or skipped gate call stays in treatment —\n * the comparison is intent-to-treat, not \"turns Jev preprocessed\". */\nexport type RequestGateArm = 'treatment' | 'holdout'\n\nexport interface RequestGate {\n active: boolean\n preprocess: boolean\n arm?: RequestGateArm\n}\n\n// This gate decides whether typed preprocessing helps; it never answers the user.\nexport async function evaluateRequest(message: string, agent: string, channel: string, random: () => number = Math.random): Promise<RequestGate> {\n // The holdout is the \"without Jev\" control: no gate call, and the turn\n // proceeds exactly as a gate \"skip\" would — existing context, the agent's\n // own model, no context planner. Drawn before the call so it costs nothing.\n const assigned = getSeatMode(REQUEST_GATE_SEAT) === 'active'\n if (assigned) {\n const rate = decisionsRuntime().seats[REQUEST_GATE_SEAT]?.holdout ?? DEFAULT_REQUEST_GATE_HOLDOUT\n if (random() < rate) return { active: true, preprocess: false, arm: 'holdout' }\n }\n const arm: RequestGateArm | undefined = assigned ? 'treatment' : undefined\n const result = await askSeat(REQUEST_GATE_SEAT, {\n request: message.slice(0, 2000), agent, channel,\n operations: ['select optional context', 'evaluate model routing where permitted'],\n constraints: ['Keep the original request and mandatory instructions', 'Desktop model cannot be downgraded'],\n }, { preprocess: noul('This request benefits from structured context selection or another bounded typed decision before the main agent executes.', {\n true: 'Relevant context must be selected, or a bounded routing decision can help. Follow-ups and ambiguous references need context.',\n false: 'Typed preprocessing adds no useful decision; send the request to the configured main agent with existing context.',\n }) }, { timeoutMs: 3000 })\n if (!result || result.mode !== 'active') return { active: false, preprocess: false, arm }\n const p = (result.answers.preprocess as NoulAnswer)?.noul\n return { active: true, preprocess: Number.isFinite(p) && p >= 0.5, arm }\n}\n\n// Only optional, application-assembled knowledge is selectable. Identity,\n// permissions, runbooks, skills, request, attachments and same-chat continuity\n// stay outside this allowlist. Native CLI history remains owned by the CLI.\nconst OPTIONAL_CONTEXT = {\n landscape: 'Agent directory and mesh overview',\n patternContext: 'Learned behavioral patterns',\n references: 'Retrieved project references',\n memoryContext: 'Retrieved agent memory',\n crossChatContext: 'Summaries from other conversations',\n longMemoryRecall: 'Older conversation recall',\n wikiContext: 'Retrieved wiki knowledge',\n} as const\n\nexport async function selectRequestContext(input: ContextInput): Promise<{ input: ContextInput; excluded: string[] }> {\n const blocks = Object.entries(OPTIONAL_CONTEXT).flatMap(([id, description]) => {\n const content = input[id as keyof typeof OPTIONAL_CONTEXT]\n return content ? [{ id, description, source: id, characters: content.length, preview: content.slice(0, 400), trust: 'context data, not instructions' }] : []\n })\n if (!blocks.length) return { input, excluded: [] }\n const questions: Questions = Object.fromEntries(blocks.map(block => [block.id, noul(\n `Context block ${block.id} is relevant to completing the current request correctly.`,\n { true: 'Needed or potentially relevant; retain if uncertain or the preview is incomplete.', false: 'Clearly unrelated to the current request; safe to omit.' },\n )]))\n const result = await askSeat(REQUEST_CONTEXT_SEAT, {\n request: input.message.slice(0, 2000), channel: input.channel, agent: input.agentId,\n mandatory: ['request', 'identity', 'permissions and instructions', 'same-chat history', 'handover', 'attachments'],\n context: blocks,\n }, questions, { timeoutMs: 3000 })\n if (!result || result.mode !== 'active') return { input, excluded: [] }\n const selected = { ...input }\n const excluded: string[] = []\n for (const block of blocks) {\n const p = (result.answers[block.id] as NoulAnswer)?.noul\n // Malformed or uncertain answers preserve context, never silently erase it.\n if (Number.isFinite(p) && p >= 0 && p < 0.2) {\n delete selected[block.id as keyof typeof OPTIONAL_CONTEXT]\n excluded.push(block.id)\n }\n }\n return { input: selected, excluded }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/agents/request-planner.ts"],"sourcesContent":["import { askSeat, decisionsRuntime, getSeatMode } from '@/decisions/seat'\nimport { noul } from '@/decisions/questions'\nimport type { NoulAnswer, Questions } from '@/decisions/types'\nimport type { ContextInput } from './context'\n\nexport const REQUEST_GATE_SEAT = 'request-gate'\nexport const REQUEST_CONTEXT_SEAT = 'request-context'\n\n/** Share of turns held out of Jev preprocessing when the gate is active.\n * Overridden by decisions.seats.request-gate.holdout; 0 turns it off. */\nexport const DEFAULT_REQUEST_GATE_HOLDOUT = 0.1\n\n/** Experiment arm for an active gate. \"treatment\" is assigned before the\n * gate is asked, so a failed or skipped gate call stays in treatment —\n * the comparison is intent-to-treat, not \"turns Jev preprocessed\". */\nexport type RequestGateArm = 'treatment' | 'holdout'\n\nexport interface RequestGate {\n active: boolean\n preprocess: boolean\n arm?: RequestGateArm\n}\n\n// This gate decides whether typed preprocessing helps; it never answers the user.\nexport async function evaluateRequest(message: string, agent: string, channel: string, random: () => number = Math.random): Promise<RequestGate> {\n // The holdout is the \"without Jev\" control: no gate call, and the turn\n // proceeds exactly as a gate \"skip\" would — existing context, the agent's\n // own model, no context planner. Drawn before the call so it costs nothing.\n const assigned = getSeatMode(REQUEST_GATE_SEAT) === 'active'\n if (assigned) {\n const rate = decisionsRuntime().seats[REQUEST_GATE_SEAT]?.holdout ?? DEFAULT_REQUEST_GATE_HOLDOUT\n if (random() < rate) return { active: true, preprocess: false, arm: 'holdout' }\n }\n const arm: RequestGateArm | undefined = assigned ? 'treatment' : undefined\n const result = await askSeat(REQUEST_GATE_SEAT, {\n request: message.slice(0, 2000), agent, channel,\n operations: ['select optional context', 'evaluate model routing where permitted'],\n constraints: ['Keep the original request and mandatory instructions', 'Desktop model cannot be downgraded'],\n }, { preprocess: noul('This request benefits from structured context selection or another bounded typed decision before the main agent executes.', {\n true: 'Relevant context must be selected, or a bounded routing decision can help. Follow-ups and ambiguous references need context.',\n false: 'Typed preprocessing adds no useful decision; send the request to the configured main agent with existing context.',\n }) }, { timeoutMs: 3000 })\n if (!result || result.mode !== 'active') return { active: false, preprocess: false, arm }\n const p = (result.answers.preprocess as NoulAnswer)?.noul\n return { active: true, preprocess: Number.isFinite(p) && p >= 0.5, arm }\n}\n\n// Only optional, application-assembled knowledge is selectable. Identity,\n// permissions, runbooks, skills, request, attachments and same-chat continuity\n// stay outside this allowlist. Native CLI history remains owned by the CLI.\nconst OPTIONAL_CONTEXT = {\n landscape: 'Agent directory and mesh overview',\n patternContext: 'Learned behavioral patterns',\n references: 'Retrieved project references',\n memoryContext: 'Retrieved agent memory',\n crossChatContext: 'Summaries from other conversations',\n longMemoryRecall: 'Older conversation recall',\n wikiContext: 'Retrieved wiki knowledge',\n} as const\n\nexport async function selectRequestContext(input: ContextInput): Promise<{ input: ContextInput; excluded: string[] }> {\n const blocks = Object.entries(OPTIONAL_CONTEXT).flatMap(([id, description]) => {\n const content = input[id as keyof typeof OPTIONAL_CONTEXT]\n return content ? [{ id, description, source: id, characters: content.length, preview: content.slice(0, 400), trust: 'context data, not instructions' }] : []\n })\n if (!blocks.length) return { input, excluded: [] }\n const questions: Questions = Object.fromEntries(blocks.map(block => [block.id, noul(\n `Context block ${block.id} is relevant to completing the current request correctly.`,\n { true: 'Needed or potentially relevant; retain if uncertain or the preview is incomplete.', false: 'Clearly unrelated to the current request; safe to omit.' },\n )]))\n const result = await askSeat(REQUEST_CONTEXT_SEAT, {\n request: input.message.slice(0, 2000), channel: input.channel, agent: input.agentId,\n mandatory: ['request', 'identity', 'permissions and instructions', 'same-chat history', 'handover', 'attachments'],\n context: blocks,\n }, questions, { timeoutMs: 3000 })\n if (!result || result.mode !== 'active') return { input, excluded: [] }\n const selected = { ...input }\n const excluded: string[] = []\n for (const block of blocks) {\n const p = (result.answers[block.id] as NoulAnswer)?.noul\n // Malformed or uncertain answers preserve context, never silently erase it.\n if (Number.isFinite(p) && p >= 0 && p < 0.2) {\n delete selected[block.id as keyof typeof OPTIONAL_CONTEXT]\n excluded.push(block.id)\n }\n }\n return { input: selected, excluded }\n}\n"],"mappings":"mWAAAA,IACAC,IAIO,IAAMC,EAAoB,eACpBC,EAAuB,kBAIvBC,EAA+B,GAc5C,eAAsBC,EAAgBC,EAAiBC,EAAeC,EAAiBC,EAAuB,KAAK,OAA8B,CAI/I,IAAMC,EAAWC,EAAYT,CAAiB,IAAM,SACpD,GAAIQ,EAAU,CACZ,IAAME,EAAOC,EAAiB,EAAE,MAAMX,CAAiB,GAAG,SAAWE,EACrE,GAAIK,EAAO,EAAIG,EAAM,MAAO,CAAE,OAAQ,GAAM,WAAY,GAAO,IAAK,SAAU,EAEhF,IAAME,EAAkCJ,EAAW,YAAc,OAC3DK,EAAS,MAAMC,EAAQd,EAAmB,CAC9C,QAASI,EAAQ,MAAM,EAAG,GAAI,EAAG,MAAAC,EAAO,QAAAC,EACxC,WAAY,CAAC,0BAA2B,wCAAwC,EAChF,YAAa,CAAC,uDAAwD,oCAAoC,CAC5G,EAAG,CAAE,WAAYS,EAAK,4HAA6H,CACjJ,KAAM,+HACN,MAAO,mHACT,CAAC,CAAE,EAAG,CAAE,UAAW,GAAK,CAAC,EACzB,GAAI,CAACF,GAAUA,EAAO,OAAS,SAAU,MAAO,CAAE,OAAQ,GAAO,WAAY,GAAO,IAAAD,CAAI,EACxF,IAAMI,EAAKH,EAAO,QAAQ,YAA2B,KACrD,MAAO,CAAE,OAAQ,GAAM,WAAY,OAAO,SAASG,CAAC,GAAKA,GAAK,GAAK,IAAAJ,CAAI,CACzE,CAKA,IAAMK,EAAmB,CACvB,UAAW,oCACX,eAAgB,8BAChB,WAAY,+BACZ,cAAe,yBACf,iBAAkB,qCAClB,iBAAkB,4BAClB,YAAa,0BACf,EAEA,eAAsBC,EAAqBC,EAA2E,CACpH,IAAMC,EAAS,OAAO,QAAQH,CAAgB,EAAE,QAAQ,CAAC,CAACI,EAAIC,CAAW,IAAM,CAC7E,IAAMC,EAAUJ,EAAME,CAAmC,EACzD,OAAOE,EAAU,CAAC,CAAE,GAAAF,EAAI,YAAAC,EAAa,OAAQD,EAAI,WAAYE,EAAQ,OAAQ,QAASA,EAAQ,MAAM,EAAG,GAAG,EAAG,MAAO,gCAAiC,CAAC,EAAI,CAAC,CAC7J,CAAC,EACD,GAAI,CAACH,EAAO,OAAQ,MAAO,CAAE,MAAAD,EAAO,SAAU,CAAC,CAAE,EACjD,IAAMK,EAAuB,OAAO,YAAYJ,EAAO,IAAIK,GAAS,CAACA,EAAM,GAAIV,EAC7E,iBAAiBU,EAAM,8DACvB,CAAE,KAAM,oFAAqF,MAAO,yDAA0D,CAChK,CAAC,CAAC,CAAC,EACGZ,EAAS,MAAMC,EAAQb,EAAsB,CACjD,QAASkB,EAAM,QAAQ,MAAM,EAAG,GAAI,EAAG,QAASA,EAAM,QAAS,MAAOA,EAAM,QAC5E,UAAW,CAAC,UAAW,WAAY,+BAAgC,oBAAqB,WAAY,aAAa,EACjH,QAASC,CACX,EAAGI,EAAW,CAAE,UAAW,GAAK,CAAC,EACjC,GAAI,CAACX,GAAUA,EAAO,OAAS,SAAU,MAAO,CAAE,MAAAM,EAAO,SAAU,CAAC,CAAE,EACtE,IAAMO,EAAW,CAAE,GAAGP,CAAM,EACtBQ,EAAqB,CAAC,EAC5B,QAAWF,KAASL,EAAQ,CAC1B,IAAMJ,EAAKH,EAAO,QAAQY,EAAM,EAAE,GAAkB,KAEhD,OAAO,SAAST,CAAC,GAAKA,GAAK,GAAKA,EAAI,KACtC,OAAOU,EAASD,EAAM,EAAmC,EACzDE,EAAS,KAAKF,EAAM,EAAE,GAG1B,MAAO,CAAE,MAAOC,EAAU,SAAAC,CAAS,CACrC","names":["init_seat","init_questions","REQUEST_GATE_SEAT","REQUEST_CONTEXT_SEAT","DEFAULT_REQUEST_GATE_HOLDOUT","evaluateRequest","message","agent","channel","random","assigned","getSeatMode","rate","decisionsRuntime","arm","result","askSeat","noul","p","OPTIONAL_CONTEXT","selectRequestContext","input","blocks","id","description","content","questions","block","selected","excluded"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{b as I}from"./chunk-ZUAV3PTL.js";import"./chunk-GLQLCBJS.js";import"./chunk-2GIDVZ4H.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";import{createHash as E}from"crypto";var v="review:",b={decisions:"project",warnings:"feedback",friction:"feedback",context:"reference"};function A(t,n,r){let s=E("sha1").update(r.trim().toLowerCase()).digest("hex").slice(0,10);return`${v}${t}/${n}@${s}`}function F(t){if(!t.startsWith(v))return null;let n=t.slice(v.length),r=n.lastIndexOf("@"),s=n.lastIndexOf("/",r===-1?void 0:r);return r===-1||s===-1?null:{reviewId:n.slice(0,s),kind:n.slice(s+1,r),digest:n.slice(r+1)}}var C=t=>t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,48)||"finding",L=5;function T(t,n){return`${t}|${I(n)}`}function K(t,n={}){let r=n.kinds??Object.keys(b),s=n.minLength??24,h=[],d=new Map,u=new Map,f=new Map,x=new Map;for(let e of t){let i;try{i=JSON.parse(e.result)}catch{continue}let o=e.updated_at?new Date(e.updated_at).toISOString():new Date().toISOString();for(let c of r){let l=b[c];if(!l)continue;let R=i[c];if(Array.isArray(R))for(let $ of R){let a=String($?.text??"").trim();if(a.length<s)continue;let g=A(e.id,c,a),_=g.slice(g.lastIndexOf("@")),w=`${c}${_}`,y=T(c,a),S=f.get(y)??new Set;if(S.add(e.session_id??e.id),f.set(y,S),d.has(w))continue;let O=String($?.evidence??"").trim(),k={occurrences:1,agentId:e.agent,key:`${e.agent}/${l}_${C(a)}`,stamp:g,memory:{name:C(a),type:l,description:a.slice(0,200),body:"",createdAt:o,updatedAt:o}};x.set(g,{text:a,evidence:O,agent:e.agent,source:e.source}),d.set(w,k),u.set(w,y),h.push(k)}}}for(let[e,i]of d){let o=f.get(u.get(e)??"");i.occurrences=o?.size??1,i.sessions=[...o??[]].slice(0,L);let c=x.get(i.stamp);i.memory.body=D(c,i.occurrences)}let m=new Map;for(let[e,i]of d){let o=u.get(e)??e,c=m.get(o)??[];c.push(i),m.set(o,c)}let M=[...m.values()].sort((e,i)=>i[0].occurrences-e[0].occurrences),p=[];for(let e=0;p.length<h.length;e++){let i=!1;for(let o of M)e<o.length&&(p.push(o[e]),i=!0);if(!i)break}return p}function D(t,n){let r=`${t.agent}${t.source?` via ${t.source}`:""}`,s=[];return n>1?(s.push(`Observed in ${n} separate sessions. Most recently stated as:`),s.push(`> ${t.text}`),s.push(`Recurring across ${n} sessions is what makes this worth recording \u2014 any single occurrence is session state, the repetition is the finding.`)):s.push(t.text),t.evidence&&s.push(`**Evidence:** ${t.evidence}`),s.push(`_From ${n>1?"session reviews":"a session review"} of ${r}._`),s.join(`
|
|
2
|
+
|
|
3
|
+
`)}export{L as MAX_EVIDENCE_SESSIONS,b as PROMOTABLE_KINDS,v as REVIEW_STAMP_PREFIX,F as parseReviewStamp,A as reviewStamp,K as reviewsToCandidates};
|
|
4
|
+
//# sourceMappingURL=review-candidates-FBDLPU6H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wiki/review-candidates.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport type { MemoryType } from \"../agents/agent-memory\"\nimport { commitmentKey } from \"@/daemon/session-monitor\"\nimport type { MemoryCandidate } from \"./promote\"\n\n// Monitor reviews → wiki promotion candidates.\n//\n// The session monitor has been producing structured findings all along\n// and none of them have ever reached the wiki. 574 parsed reviews hold\n// 1,543 decisions, 1,511 warnings, 1,278 pieces of context, 1,216\n// actions and 1,106 friction notes — 6,654 items against a wiki of 784\n// articles. The single richest seam of institutional knowledge on the\n// fleet has been sitting in SQLite because nothing was pointed at it.\n//\n// It never reached absorb for a structural reason rather than an\n// oversight: absorb reads raw *conversation* entries, and a review is\n// not a conversation. It is a judgement already extracted from one, with\n// its evidence attached. Pushing reviews through absorb would pay a\n// writing model to re-derive what the monitor already derived.\n//\n// So they go through `wiki promote` instead, which exists for exactly\n// this shape — a durable, already-formed claim that needs judging for\n// fleet relevance and writing into a shared article. Reviews become\n// MemoryCandidates carrying a `review:` stamp, and inherit the ledger,\n// the idempotency and the judge without any of it being rebuilt.\n//\n// Only the kinds that describe what IS or WAS are eligible. `actions`\n// are excluded: an action is a thing somebody should do, it goes stale\n// the moment it is done or abandoned, and a wiki full of last month's\n// to-dos is worse than one without them. Those belong on the questions\n// queue if anywhere.\n\nexport const REVIEW_STAMP_PREFIX = \"review:\"\n\n/** Review kinds worth keeping, mapped to the memory type they promote as. */\nexport const PROMOTABLE_KINDS: Record<string, MemoryType> = {\n decisions: \"project\",\n warnings: \"feedback\",\n friction: \"feedback\",\n context: \"reference\",\n}\n\nexport interface ReviewRow {\n id: string\n /** The run being reviewed. The monitor re-reviews a session as it\n * progresses, so several rows share one session_id. */\n session_id?: string\n agent: string\n source?: string\n updated_at?: number\n /** The parsed Review JSON as stored. */\n result: string\n}\n\nexport interface ReviewItem {\n text: string\n evidence?: string\n}\n\n/**\n * Stable identity for one item inside one review.\n *\n * Keyed on the item's TEXT, not its index. The monitor re-reviews a\n * session as it progresses, so the same finding reappears at a\n * different position in a later review of the same run; indexing by\n * position would promote it again as something new. Hashing the text\n * means a re-stated finding is recognised and a genuinely reworded one\n * is treated as new, which is the right way round.\n */\nexport function reviewStamp(reviewId: string, kind: string, text: string): string {\n const digest = createHash(\"sha1\").update(text.trim().toLowerCase()).digest(\"hex\").slice(0, 10)\n return `${REVIEW_STAMP_PREFIX}${reviewId}/${kind}@${digest}`\n}\n\nexport function parseReviewStamp(s: string): { reviewId: string; kind: string; digest: string } | null {\n if (!s.startsWith(REVIEW_STAMP_PREFIX)) return null\n const rest = s.slice(REVIEW_STAMP_PREFIX.length)\n const at = rest.lastIndexOf(\"@\")\n const slash = rest.lastIndexOf(\"/\", at === -1 ? undefined : at)\n if (at === -1 || slash === -1) return null\n return { reviewId: rest.slice(0, slash), kind: rest.slice(slash + 1, at), digest: rest.slice(at + 1) }\n}\n\nconst slug = (s: string) =>\n s.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\").slice(0, 48) || \"finding\"\n\n/**\n * Turn stored reviews into promotion candidates.\n *\n * The evidence travels with the claim. A warning without it is an\n * assertion a reader has to take on faith, and the monitor already went\n * to the trouble of collecting it — dropping it here would make the\n * resulting article exactly the kind of unfalsifiable prose the wiki is\n * meant not to accumulate.\n */\n/** Example sessions kept per finding; the count is in `occurrences`. */\nexport const MAX_EVIDENCE_SESSIONS = 5\n\nexport interface ReviewCandidate extends MemoryCandidate {\n /** Some of the sessions (ids) that produced this finding. */\n sessions?: string[]\n /** How many DISTINCT SESSIONS produced substantially this finding.\n *\n * Distinct sessions, not review rows, and the difference decides\n * whether the number means anything. The monitor re-reviews a run as\n * it progresses, so counting rows made a single miner run that\n * restated one observation 24 times look like the fleet's most\n * recurrent finding. It ranked top, and the promotion judge threw it\n * out as \"a snapshot of pending operator items from one miner run\" —\n * correctly. Recurrence across sessions is evidence of a pattern;\n * recurrence within one is evidence of a long session. */\n occurrences: number\n}\n\n/**\n * Two keys, deliberately, because dedupe and ranking want opposite errors.\n *\n * IDENTITY is the exact text hash in the stamp. It must never merge two\n * findings that differ, because a merge drops one permanently — so it\n * errs towards treating a rewording as new.\n *\n * SIMILARITY, for counting recurrence, wants the opposite: monitor\n * findings are free text re-derived from scratch every session, so exact\n * matching almost never fires. On this corpus it found 3 recurrences in\n * 5,435 findings, which is no signal at all. `commitmentKey` is the\n * monitor's own answer to this, written after one leaked token produced\n * 18 separate \"rotate it\" actions — verb plus the entity acted on. Over-\n * merging here only inflates a rank; it cannot lose a finding.\n */\nfunction similarityKey(kind: string, text: string): string {\n return `${kind}|${commitmentKey(text)}`\n}\n\nexport function reviewsToCandidates(\n rows: ReviewRow[],\n opts: { kinds?: string[]; minLength?: number } = {},\n): ReviewCandidate[] {\n const kinds = opts.kinds ?? Object.keys(PROMOTABLE_KINDS)\n const minLength = opts.minLength ?? 24\n\n const out: ReviewCandidate[] = []\n // Emit once per exact wording…\n const byExact = new Map<string, ReviewCandidate>()\n const familyOf = new Map<string, string>()\n // …but count per family. Keeping these separate is the point: sharing\n // one map would make an over-merge drop a distinct finding instead of\n // merely inflating a rank.\n const familySessions = new Map<string, Set<string>>()\n const provenance = new Map<string, { text: string; evidence: string; agent: string; source?: string }>()\n\n for (const row of rows) {\n let review: Record<string, unknown>\n try { review = JSON.parse(row.result) as Record<string, unknown> } catch { continue }\n\n const when = row.updated_at ? new Date(row.updated_at).toISOString() : new Date().toISOString()\n\n for (const kind of kinds) {\n const type = PROMOTABLE_KINDS[kind]\n if (!type) continue\n const items = review[kind]\n if (!Array.isArray(items)) continue\n\n for (const raw of items as ReviewItem[]) {\n const text = String(raw?.text ?? \"\").trim()\n // Very short findings are almost always a restated status line\n // (\"deploy succeeded\"), which carries nothing an article wants.\n if (text.length < minLength) continue\n\n const stamp = reviewStamp(row.id, kind, text)\n // The same finding recurs across reviews of one run and across\n // runs. Promote the first occurrence, but count the rest: a\n // finding stated once is usually about that session, while one\n // stated a dozen times is a property of the fleet. That count is\n // the only durability signal this source offers for free, and\n // without it 5,000 candidates arrive in arbitrary order.\n const dedupe = stamp.slice(stamp.lastIndexOf(\"@\"))\n const key = `${kind}${dedupe}`\n\n // Count every occurrence, including restatements of something\n // already emitted — a finding said five different ways is one\n // recurring finding, and that is what deserves promoting first.\n const family = similarityKey(kind, text)\n const sessions = familySessions.get(family) ?? new Set<string>()\n sessions.add(row.session_id ?? row.id)\n familySessions.set(family, sessions)\n if (byExact.has(key)) continue\n\n const evidence = String(raw?.evidence ?? \"\").trim()\n const candidate: ReviewCandidate = {\n occurrences: 1,\n agentId: row.agent,\n key: `${row.agent}/${type}_${slug(text)}`,\n stamp,\n memory: {\n name: slug(text),\n type,\n description: text.slice(0, 200),\n // Body is filled in after the counting pass, because how a\n // finding should be framed depends on how often it recurred.\n body: \"\",\n createdAt: when,\n updatedAt: when,\n },\n }\n provenance.set(stamp, { text, evidence, agent: row.agent, source: row.source })\n byExact.set(key, candidate)\n familyOf.set(key, family)\n out.push(candidate)\n }\n }\n }\n\n for (const [key, candidate] of byExact) {\n const sessions = familySessions.get(familyOf.get(key) ?? \"\")\n candidate.occurrences = sessions?.size ?? 1\n // A few of the sessions it came from, as evidence a reviewer can open.\n candidate.sessions = [...(sessions ?? [])].slice(0, MAX_EVIDENCE_SESSIONS)\n const p = provenance.get(candidate.stamp)!\n candidate.memory.body = renderBody(p, candidate.occurrences)\n }\n\n // Recurrence decides which finding matters; diversity decides what a\n // capped run gets to see. Sorting on count alone put five rewordings\n // of the same 24× observation in the top five, spending the whole\n // nightly budget on one idea. So: one representative per family\n // first, in count order, then second representatives, and so on.\n // Nothing is dropped — the rewordings are still in the list, just\n // behind a first look at everything else.\n const byFamily = new Map<string, ReviewCandidate[]>()\n for (const [key, candidate] of byExact) {\n const family = familyOf.get(key) ?? key\n const list = byFamily.get(family) ?? []\n list.push(candidate)\n byFamily.set(family, list)\n }\n const families = [...byFamily.values()].sort((a, b) => b[0].occurrences - a[0].occurrences)\n\n const ordered: ReviewCandidate[] = []\n for (let round = 0; ordered.length < out.length; round++) {\n let placed = false\n for (const list of families) {\n if (round < list.length) { ordered.push(list[round]); placed = true }\n }\n if (!placed) break\n }\n return ordered\n}\n\n/**\n * How a finding is written decides whether it survives the judge.\n *\n * The monitor writes point-in-time observations, and the promotion\n * judge — correctly — rejects those: \"a transient OAuth expiry that\n * stalled #38\", \"issue-thread state, not a wiki fact\". Twelve\n * candidates across two runs, all skipped, every reason sound.\n *\n * But a finding seen in seven separate sessions is no longer a status\n * report about one of them; it is evidence of something that keeps\n * happening, and that IS durable. The observation was never the\n * knowledge — the repetition is. Saying so explicitly, with the session\n * count as the evidence, is the difference between offering the judge a\n * stale fact and offering it a pattern.\n *\n * A one-off stays phrased as what it is. Dressing a single observation\n * up as a pattern would just launder session state past the judge,\n * which is the failure this is trying to avoid, not a way around it.\n */\nfunction renderBody(\n p: { text: string; evidence: string; agent: string; source?: string },\n occurrences: number,\n): string {\n const origin = `${p.agent}${p.source ? ` via ${p.source}` : \"\"}`\n const parts: string[] = []\n\n if (occurrences > 1) {\n parts.push(`Observed in ${occurrences} separate sessions. Most recently stated as:`)\n parts.push(`> ${p.text}`)\n parts.push(\n `Recurring across ${occurrences} sessions is what makes this worth recording — ` +\n `any single occurrence is session state, the repetition is the finding.`,\n )\n } else {\n parts.push(p.text)\n }\n\n if (p.evidence) parts.push(`**Evidence:** ${p.evidence}`)\n parts.push(`_From ${occurrences > 1 ? \"session reviews\" : \"a session review\"} of ${origin}._`)\n return parts.join(\"\\n\\n\")\n}\n"],"mappings":"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","names":["createHash","REVIEW_STAMP_PREFIX","PROMOTABLE_KINDS","reviewStamp","reviewId","kind","text","digest","createHash","parseReviewStamp","s","rest","at","slash","slug","MAX_EVIDENCE_SESSIONS","similarityKey","commitmentKey","reviewsToCandidates","rows","opts","kinds","minLength","out","byExact","familyOf","familySessions","provenance","row","review","when","type","items","raw","stamp","dedupe","key","family","sessions","evidence","candidate","p","renderBody","byFamily","list","families","a","b","ordered","round","placed","occurrences","origin","parts"]}
|
|
1
|
+
{"version":3,"sources":["../src/wiki/review-candidates.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport type { MemoryType } from \"../agents/agent-memory\"\nimport { commitmentKey } from \"@/daemon/session-monitor\"\nimport type { MemoryCandidate } from \"./promote\"\n\n// Monitor reviews → wiki promotion candidates.\n//\n// The session monitor has been producing structured findings all along\n// and none of them have ever reached the wiki. 574 parsed reviews hold\n// 1,543 decisions, 1,511 warnings, 1,278 pieces of context, 1,216\n// actions and 1,106 friction notes — 6,654 items against a wiki of 784\n// articles. The single richest seam of institutional knowledge on the\n// fleet has been sitting in SQLite because nothing was pointed at it.\n//\n// It never reached absorb for a structural reason rather than an\n// oversight: absorb reads raw *conversation* entries, and a review is\n// not a conversation. It is a judgement already extracted from one, with\n// its evidence attached. Pushing reviews through absorb would pay a\n// writing model to re-derive what the monitor already derived.\n//\n// So they go through `wiki promote` instead, which exists for exactly\n// this shape — a durable, already-formed claim that needs judging for\n// fleet relevance and writing into a shared article. Reviews become\n// MemoryCandidates carrying a `review:` stamp, and inherit the ledger,\n// the idempotency and the judge without any of it being rebuilt.\n//\n// Only the kinds that describe what IS or WAS are eligible. `actions`\n// are excluded: an action is a thing somebody should do, it goes stale\n// the moment it is done or abandoned, and a wiki full of last month's\n// to-dos is worse than one without them. Those belong on the questions\n// queue if anywhere.\n\nexport const REVIEW_STAMP_PREFIX = \"review:\"\n\n/** Review kinds worth keeping, mapped to the memory type they promote as. */\nexport const PROMOTABLE_KINDS: Record<string, MemoryType> = {\n decisions: \"project\",\n warnings: \"feedback\",\n friction: \"feedback\",\n context: \"reference\",\n}\n\nexport interface ReviewRow {\n id: string\n /** The run being reviewed. The monitor re-reviews a session as it\n * progresses, so several rows share one session_id. */\n session_id?: string\n agent: string\n source?: string\n updated_at?: number\n /** The parsed Review JSON as stored. */\n result: string\n}\n\nexport interface ReviewItem {\n text: string\n evidence?: string\n}\n\n/**\n * Stable identity for one item inside one review.\n *\n * Keyed on the item's TEXT, not its index. The monitor re-reviews a\n * session as it progresses, so the same finding reappears at a\n * different position in a later review of the same run; indexing by\n * position would promote it again as something new. Hashing the text\n * means a re-stated finding is recognised and a genuinely reworded one\n * is treated as new, which is the right way round.\n */\nexport function reviewStamp(reviewId: string, kind: string, text: string): string {\n const digest = createHash(\"sha1\").update(text.trim().toLowerCase()).digest(\"hex\").slice(0, 10)\n return `${REVIEW_STAMP_PREFIX}${reviewId}/${kind}@${digest}`\n}\n\nexport function parseReviewStamp(s: string): { reviewId: string; kind: string; digest: string } | null {\n if (!s.startsWith(REVIEW_STAMP_PREFIX)) return null\n const rest = s.slice(REVIEW_STAMP_PREFIX.length)\n const at = rest.lastIndexOf(\"@\")\n const slash = rest.lastIndexOf(\"/\", at === -1 ? undefined : at)\n if (at === -1 || slash === -1) return null\n return { reviewId: rest.slice(0, slash), kind: rest.slice(slash + 1, at), digest: rest.slice(at + 1) }\n}\n\nconst slug = (s: string) =>\n s.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\").slice(0, 48) || \"finding\"\n\n/**\n * Turn stored reviews into promotion candidates.\n *\n * The evidence travels with the claim. A warning without it is an\n * assertion a reader has to take on faith, and the monitor already went\n * to the trouble of collecting it — dropping it here would make the\n * resulting article exactly the kind of unfalsifiable prose the wiki is\n * meant not to accumulate.\n */\n/** Example sessions kept per finding; the count is in `occurrences`. */\nexport const MAX_EVIDENCE_SESSIONS = 5\n\nexport interface ReviewCandidate extends MemoryCandidate {\n /** Some of the sessions (ids) that produced this finding. */\n sessions?: string[]\n /** How many DISTINCT SESSIONS produced substantially this finding.\n *\n * Distinct sessions, not review rows, and the difference decides\n * whether the number means anything. The monitor re-reviews a run as\n * it progresses, so counting rows made a single miner run that\n * restated one observation 24 times look like the fleet's most\n * recurrent finding. It ranked top, and the promotion judge threw it\n * out as \"a snapshot of pending operator items from one miner run\" —\n * correctly. Recurrence across sessions is evidence of a pattern;\n * recurrence within one is evidence of a long session. */\n occurrences: number\n}\n\n/**\n * Two keys, deliberately, because dedupe and ranking want opposite errors.\n *\n * IDENTITY is the exact text hash in the stamp. It must never merge two\n * findings that differ, because a merge drops one permanently — so it\n * errs towards treating a rewording as new.\n *\n * SIMILARITY, for counting recurrence, wants the opposite: monitor\n * findings are free text re-derived from scratch every session, so exact\n * matching almost never fires. On this corpus it found 3 recurrences in\n * 5,435 findings, which is no signal at all. `commitmentKey` is the\n * monitor's own answer to this, written after one leaked token produced\n * 18 separate \"rotate it\" actions — verb plus the entity acted on. Over-\n * merging here only inflates a rank; it cannot lose a finding.\n */\nfunction similarityKey(kind: string, text: string): string {\n return `${kind}|${commitmentKey(text)}`\n}\n\nexport function reviewsToCandidates(\n rows: ReviewRow[],\n opts: { kinds?: string[]; minLength?: number } = {},\n): ReviewCandidate[] {\n const kinds = opts.kinds ?? Object.keys(PROMOTABLE_KINDS)\n const minLength = opts.minLength ?? 24\n\n const out: ReviewCandidate[] = []\n // Emit once per exact wording…\n const byExact = new Map<string, ReviewCandidate>()\n const familyOf = new Map<string, string>()\n // …but count per family. Keeping these separate is the point: sharing\n // one map would make an over-merge drop a distinct finding instead of\n // merely inflating a rank.\n const familySessions = new Map<string, Set<string>>()\n const provenance = new Map<string, { text: string; evidence: string; agent: string; source?: string }>()\n\n for (const row of rows) {\n let review: Record<string, unknown>\n try { review = JSON.parse(row.result) as Record<string, unknown> } catch { continue }\n\n const when = row.updated_at ? new Date(row.updated_at).toISOString() : new Date().toISOString()\n\n for (const kind of kinds) {\n const type = PROMOTABLE_KINDS[kind]\n if (!type) continue\n const items = review[kind]\n if (!Array.isArray(items)) continue\n\n for (const raw of items as ReviewItem[]) {\n const text = String(raw?.text ?? \"\").trim()\n // Very short findings are almost always a restated status line\n // (\"deploy succeeded\"), which carries nothing an article wants.\n if (text.length < minLength) continue\n\n const stamp = reviewStamp(row.id, kind, text)\n // The same finding recurs across reviews of one run and across\n // runs. Promote the first occurrence, but count the rest: a\n // finding stated once is usually about that session, while one\n // stated a dozen times is a property of the fleet. That count is\n // the only durability signal this source offers for free, and\n // without it 5,000 candidates arrive in arbitrary order.\n const dedupe = stamp.slice(stamp.lastIndexOf(\"@\"))\n const key = `${kind}${dedupe}`\n\n // Count every occurrence, including restatements of something\n // already emitted — a finding said five different ways is one\n // recurring finding, and that is what deserves promoting first.\n const family = similarityKey(kind, text)\n const sessions = familySessions.get(family) ?? new Set<string>()\n sessions.add(row.session_id ?? row.id)\n familySessions.set(family, sessions)\n if (byExact.has(key)) continue\n\n const evidence = String(raw?.evidence ?? \"\").trim()\n const candidate: ReviewCandidate = {\n occurrences: 1,\n agentId: row.agent,\n key: `${row.agent}/${type}_${slug(text)}`,\n stamp,\n memory: {\n name: slug(text),\n type,\n description: text.slice(0, 200),\n // Body is filled in after the counting pass, because how a\n // finding should be framed depends on how often it recurred.\n body: \"\",\n createdAt: when,\n updatedAt: when,\n },\n }\n provenance.set(stamp, { text, evidence, agent: row.agent, source: row.source })\n byExact.set(key, candidate)\n familyOf.set(key, family)\n out.push(candidate)\n }\n }\n }\n\n for (const [key, candidate] of byExact) {\n const sessions = familySessions.get(familyOf.get(key) ?? \"\")\n candidate.occurrences = sessions?.size ?? 1\n // A few of the sessions it came from, as evidence a reviewer can open.\n candidate.sessions = [...(sessions ?? [])].slice(0, MAX_EVIDENCE_SESSIONS)\n const p = provenance.get(candidate.stamp)!\n candidate.memory.body = renderBody(p, candidate.occurrences)\n }\n\n // Recurrence decides which finding matters; diversity decides what a\n // capped run gets to see. Sorting on count alone put five rewordings\n // of the same 24× observation in the top five, spending the whole\n // nightly budget on one idea. So: one representative per family\n // first, in count order, then second representatives, and so on.\n // Nothing is dropped — the rewordings are still in the list, just\n // behind a first look at everything else.\n const byFamily = new Map<string, ReviewCandidate[]>()\n for (const [key, candidate] of byExact) {\n const family = familyOf.get(key) ?? key\n const list = byFamily.get(family) ?? []\n list.push(candidate)\n byFamily.set(family, list)\n }\n const families = [...byFamily.values()].sort((a, b) => b[0].occurrences - a[0].occurrences)\n\n const ordered: ReviewCandidate[] = []\n for (let round = 0; ordered.length < out.length; round++) {\n let placed = false\n for (const list of families) {\n if (round < list.length) { ordered.push(list[round]); placed = true }\n }\n if (!placed) break\n }\n return ordered\n}\n\n/**\n * How a finding is written decides whether it survives the judge.\n *\n * The monitor writes point-in-time observations, and the promotion\n * judge — correctly — rejects those: \"a transient OAuth expiry that\n * stalled #38\", \"issue-thread state, not a wiki fact\". Twelve\n * candidates across two runs, all skipped, every reason sound.\n *\n * But a finding seen in seven separate sessions is no longer a status\n * report about one of them; it is evidence of something that keeps\n * happening, and that IS durable. The observation was never the\n * knowledge — the repetition is. Saying so explicitly, with the session\n * count as the evidence, is the difference between offering the judge a\n * stale fact and offering it a pattern.\n *\n * A one-off stays phrased as what it is. Dressing a single observation\n * up as a pattern would just launder session state past the judge,\n * which is the failure this is trying to avoid, not a way around it.\n */\nfunction renderBody(\n p: { text: string; evidence: string; agent: string; source?: string },\n occurrences: number,\n): string {\n const origin = `${p.agent}${p.source ? ` via ${p.source}` : \"\"}`\n const parts: string[] = []\n\n if (occurrences > 1) {\n parts.push(`Observed in ${occurrences} separate sessions. Most recently stated as:`)\n parts.push(`> ${p.text}`)\n parts.push(\n `Recurring across ${occurrences} sessions is what makes this worth recording — ` +\n `any single occurrence is session state, the repetition is the finding.`,\n )\n } else {\n parts.push(p.text)\n }\n\n if (p.evidence) parts.push(`**Evidence:** ${p.evidence}`)\n parts.push(`_From ${occurrences > 1 ? \"session reviews\" : \"a session review\"} of ${origin}._`)\n return parts.join(\"\\n\\n\")\n}\n"],"mappings":"oZAAA,OAAS,cAAAA,MAAkB,SAgCpB,IAAMC,EAAsB,UAGtBC,EAA+C,CAC1D,UAAW,UACX,SAAU,WACV,SAAU,WACV,QAAS,WACX,EA6BO,SAASC,EAAYC,EAAkBC,EAAcC,EAAsB,CAChF,IAAMC,EAASC,EAAW,MAAM,EAAE,OAAOF,EAAK,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,EAAG,EAAE,EAC7F,MAAO,GAAGL,IAAsBG,KAAYC,KAAQE,GACtD,CAEO,SAASE,EAAiBC,EAAsE,CACrG,GAAI,CAACA,EAAE,WAAWT,CAAmB,EAAG,OAAO,KAC/C,IAAMU,EAAOD,EAAE,MAAMT,EAAoB,MAAM,EACzCW,EAAKD,EAAK,YAAY,GAAG,EACzBE,EAAQF,EAAK,YAAY,IAAKC,IAAO,GAAK,OAAYA,CAAE,EAC9D,OAAIA,IAAO,IAAMC,IAAU,GAAW,KAC/B,CAAE,SAAUF,EAAK,MAAM,EAAGE,CAAK,EAAG,KAAMF,EAAK,MAAME,EAAQ,EAAGD,CAAE,EAAG,OAAQD,EAAK,MAAMC,EAAK,CAAC,CAAE,CACvG,CAEA,IAAME,EAAQJ,GACZA,EAAE,YAAY,EAAE,QAAQ,cAAe,GAAG,EAAE,QAAQ,SAAU,EAAE,EAAE,MAAM,EAAG,EAAE,GAAK,UAYvEK,EAAwB,EAiCrC,SAASC,EAAcX,EAAcC,EAAsB,CACzD,MAAO,GAAGD,KAAQY,EAAcX,CAAI,GACtC,CAEO,SAASY,EACdC,EACAC,EAAiD,CAAC,EAC/B,CACnB,IAAMC,EAAQD,EAAK,OAAS,OAAO,KAAKlB,CAAgB,EAClDoB,EAAYF,EAAK,WAAa,GAE9BG,EAAyB,CAAC,EAE1BC,EAAU,IAAI,IACdC,EAAW,IAAI,IAIfC,EAAiB,IAAI,IACrBC,EAAa,IAAI,IAEvB,QAAWC,KAAOT,EAAM,CACtB,IAAIU,EACJ,GAAI,CAAEA,EAAS,KAAK,MAAMD,EAAI,MAAM,CAA6B,MAAE,CAAQ,QAAS,CAEpF,IAAME,EAAOF,EAAI,WAAa,IAAI,KAAKA,EAAI,UAAU,EAAE,YAAY,EAAI,IAAI,KAAK,EAAE,YAAY,EAE9F,QAAWvB,KAAQgB,EAAO,CACxB,IAAMU,EAAO7B,EAAiBG,CAAI,EAClC,GAAI,CAAC0B,EAAM,SACX,IAAMC,EAAQH,EAAOxB,CAAI,EACzB,GAAK,MAAM,QAAQ2B,CAAK,EAExB,QAAWC,KAAOD,EAAuB,CACvC,IAAM1B,EAAO,OAAO2B,GAAK,MAAQ,EAAE,EAAE,KAAK,EAG1C,GAAI3B,EAAK,OAASgB,EAAW,SAE7B,IAAMY,EAAQ/B,EAAYyB,EAAI,GAAIvB,EAAMC,CAAI,EAOtC6B,EAASD,EAAM,MAAMA,EAAM,YAAY,GAAG,CAAC,EAC3CE,EAAM,GAAG/B,IAAO8B,IAKhBE,EAASrB,EAAcX,EAAMC,CAAI,EACjCgC,EAAWZ,EAAe,IAAIW,CAAM,GAAK,IAAI,IAGnD,GAFAC,EAAS,IAAIV,EAAI,YAAcA,EAAI,EAAE,EACrCF,EAAe,IAAIW,EAAQC,CAAQ,EAC/Bd,EAAQ,IAAIY,CAAG,EAAG,SAEtB,IAAMG,EAAW,OAAON,GAAK,UAAY,EAAE,EAAE,KAAK,EAC5CO,EAA6B,CACjC,YAAa,EACb,QAASZ,EAAI,MACb,IAAK,GAAGA,EAAI,SAASG,KAAQjB,EAAKR,CAAI,IACtC,MAAA4B,EACA,OAAQ,CACN,KAAMpB,EAAKR,CAAI,EACf,KAAAyB,EACA,YAAazB,EAAK,MAAM,EAAG,GAAG,EAG9B,KAAM,GACN,UAAWwB,EACX,UAAWA,CACb,CACF,EACAH,EAAW,IAAIO,EAAO,CAAE,KAAA5B,EAAM,SAAAiC,EAAU,MAAOX,EAAI,MAAO,OAAQA,EAAI,MAAO,CAAC,EAC9EJ,EAAQ,IAAIY,EAAKI,CAAS,EAC1Bf,EAAS,IAAIW,EAAKC,CAAM,EACxBd,EAAI,KAAKiB,CAAS,IAKxB,OAAW,CAACJ,EAAKI,CAAS,IAAKhB,EAAS,CACtC,IAAMc,EAAWZ,EAAe,IAAID,EAAS,IAAIW,CAAG,GAAK,EAAE,EAC3DI,EAAU,YAAcF,GAAU,MAAQ,EAE1CE,EAAU,SAAW,CAAC,GAAIF,GAAY,CAAC,CAAE,EAAE,MAAM,EAAGvB,CAAqB,EACzE,IAAM0B,EAAId,EAAW,IAAIa,EAAU,KAAK,EACxCA,EAAU,OAAO,KAAOE,EAAWD,EAAGD,EAAU,WAAW,EAU7D,IAAMG,EAAW,IAAI,IACrB,OAAW,CAACP,EAAKI,CAAS,IAAKhB,EAAS,CACtC,IAAMa,EAASZ,EAAS,IAAIW,CAAG,GAAKA,EAC9BQ,EAAOD,EAAS,IAAIN,CAAM,GAAK,CAAC,EACtCO,EAAK,KAAKJ,CAAS,EACnBG,EAAS,IAAIN,EAAQO,CAAI,EAE3B,IAAMC,EAAW,CAAC,GAAGF,EAAS,OAAO,CAAC,EAAE,KAAK,CAACG,EAAGC,IAAMA,EAAE,CAAC,EAAE,YAAcD,EAAE,CAAC,EAAE,WAAW,EAEpFE,EAA6B,CAAC,EACpC,QAASC,EAAQ,EAAGD,EAAQ,OAASzB,EAAI,OAAQ0B,IAAS,CACxD,IAAIC,EAAS,GACb,QAAWN,KAAQC,EACbI,EAAQL,EAAK,SAAUI,EAAQ,KAAKJ,EAAKK,CAAK,CAAC,EAAGC,EAAS,IAEjE,GAAI,CAACA,EAAQ,MAEf,OAAOF,CACT,CAqBA,SAASN,EACPD,EACAU,EACQ,CACR,IAAMC,EAAS,GAAGX,EAAE,QAAQA,EAAE,OAAS,QAAQA,EAAE,SAAW,KACtDY,EAAkB,CAAC,EAEzB,OAAIF,EAAc,GAChBE,EAAM,KAAK,eAAeF,+CAAyD,EACnFE,EAAM,KAAK,KAAKZ,EAAE,MAAM,EACxBY,EAAM,KACJ,oBAAoBF,6HAEtB,GAEAE,EAAM,KAAKZ,EAAE,IAAI,EAGfA,EAAE,UAAUY,EAAM,KAAK,iBAAiBZ,EAAE,UAAU,EACxDY,EAAM,KAAK,SAASF,EAAc,EAAI,kBAAoB,yBAAyBC,KAAU,EACtFC,EAAM,KAAK;AAAA;AAAA,CAAM,CAC1B","names":["createHash","REVIEW_STAMP_PREFIX","PROMOTABLE_KINDS","reviewStamp","reviewId","kind","text","digest","createHash","parseReviewStamp","s","rest","at","slash","slug","MAX_EVIDENCE_SESSIONS","similarityKey","commitmentKey","reviewsToCandidates","rows","opts","kinds","minLength","out","byExact","familyOf","familySessions","provenance","row","review","when","type","items","raw","stamp","dedupe","key","family","sessions","evidence","candidate","p","renderBody","byFamily","list","families","a","b","ordered","round","placed","occurrences","origin","parts"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b}from"./chunk-74FSHUB3.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{a as cheapModelForEngine,b as routeTaskModel};
|
|
2
|
+
//# sourceMappingURL=routing-3SZHWHUA.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b}from"./chunk-TW4L6RP2.js";import"./chunk-RKPAVWXF.js";import"./chunk-BB3ESU74.js";import"./chunk-4KJ3PCAI.js";import"./chunk-XF65CDNS.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";export{b as resolveScheduleCaller,a as runScheduleTool};
|
|
2
|
+
//# sourceMappingURL=schedule-tool-5FULSHWJ.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as a,o as b,p as c,q as d,r as e,s as f}from"./chunk-4KJ3PCAI.js";import"./chunk-XF65CDNS.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";export{a as AGENT_RUN_TIMEOUT_FLOOR,f as CronScheduler,b as agentRunTimeout,e as cronRunId,d as getNextCronDate,c as runLinkIds};
|
|
2
|
+
//# sourceMappingURL=scheduler-ITOKSMYT.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{o as a,p as b,q as c,r as d,s as e,t as f,u as g,v as h,w as i}from"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";i();export{h as askSeat,a as configureDecisions,c as decisionsRuntime,f as getSeatMode,d as parseSeatMode,g as recordSeatOutcome,b as resetDecisionsRuntime,e as seatEnvVar};
|
|
2
|
+
//# sourceMappingURL=seat-2HJG3UNW.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{v as S,w}from"./chunk-5GXCOHDM.js";import{a as g,b as h,f as R}from"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import{e as f}from"./chunk-JHCI6XCJ.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";w();R();var p="skill-select";function y(l){return{task:d(l.task,1200),...l.agent?{agent:l.agent}:{},skills:l.candidates.map(t=>({name:t.name,purpose:d(t.description,300),...t.category?{category:t.category}:{},...t.tags?.length?{tags:t.tags.slice(0,8)}:{}}))}}function b(l){let t={};for(let i of l)t[i.name]=d(i.description,300);return{anyRelevant:g("At least one of these skills is about the task at hand.",{true:"The task is the kind of work one of these skills exists for, and following it would change how the work is done \u2014 it names the same system, the same procedure or the same kind of artefact.",false:"None of them is about this task. They may share vocabulary with it, mention the same tools, or belong to the same project without bearing on what is being asked. Sharing words with a task is not being about it."}),best:h(t,"Which skill is this task most about?")}}function v(l){let t=l.anyRelevant,i=l.best,o=Object.entries(i.probabilities??{}).map(([e,s])=>({name:e,p:Number(s)||0})).sort((e,s)=>s.p-e.p);return{anyRelevant:t.noul,name:i.choice,confidence:i.confidence,ranked:o}}function d(l,t){return l.length>t?`${l.slice(0,t)}\u2026`:l}var x=.1,A=12;async function E(l,t,i={}){let o=i.minRelevant??.6,e=f(l,t).filter(n=>n.relevance>=x).slice(0,A),s=e.map(n=>({name:n.skill.frontmatter.name,lexical:n.relevance}));if(e.length===0)return{skill:null,anyRelevant:0,confidence:0,shortlist:s,judged:!1,reason:"no skill mentions anything in this task"};if(e.length===1)return{skill:e[0].skill,anyRelevant:e[0].relevance,confidence:e[0].relevance,shortlist:s,judged:!1,reason:`only ${e[0].skill.frontmatter.name} matched (${e[0].matchReason})`};let r=e.map(n=>({name:n.skill.frontmatter.name,description:n.skill.frontmatter.description,category:n.skill.frontmatter.category??null,tags:n.skill.frontmatter.tags})),m=await S(p,y({task:t,agent:i.agent??null,candidates:r}),b(r),{incumbent:{best:r[0].name},features:{agent:i.agent??"unknown",candidates:r.length}});if(!m)return{skill:e[0].skill,anyRelevant:e[0].relevance,confidence:e[0].relevance,shortlist:s,judged:!1,reason:`seat off \u2014 lexical top match (${e[0].matchReason})`};let a=v(m.answers),c=s.map(n=>({...n,p:a.ranked.find(k=>k.name===n.name)?.p??0})).sort((n,k)=>(k.p??0)-(n.p??0));if(a.anyRelevant<o)return{skill:null,anyRelevant:a.anyRelevant,confidence:a.confidence,shortlist:c,judged:!0,reason:`nothing on the shortlist is about this task (${a.anyRelevant.toFixed(2)} below ${o})`};let u=e.find(n=>n.skill.frontmatter.name===a.name);return u?{skill:u.skill,anyRelevant:a.anyRelevant,confidence:a.confidence,shortlist:c,judged:!0,reason:`${a.name} (confidence ${a.confidence.toFixed(2)})`}:{skill:null,anyRelevant:a.anyRelevant,confidence:a.confidence,shortlist:c,judged:!0,reason:`seat chose "${a.name}", which was not on the shortlist`}}export{E as pickSkillForTask};
|
|
2
|
+
//# sourceMappingURL=select-UKAOL7QB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent/skills/select.ts","../src/decisions/seats/skill-select.ts"],"sourcesContent":["import { askSeat } from \"@/decisions/seat\"\nimport {\n SKILL_SELECT_SEAT,\n skillSelectQuestions,\n skillSelectState,\n toSkillSelection,\n type SkillCandidate,\n type SkillSelectAnswers,\n} from \"@/decisions/seats/skill-select\"\nimport { matchSkillsToTask } from \"./loader\"\nimport type { Skill } from \"./types\"\n\n// Choosing a skill for a task, in two stages.\n//\n// 1. matchSkillsToTask — lexical, free, no network. Narrows a hundred\n// skills to a handful. Good at recall, indifferent about precision.\n// 2. the skill-select seat — judgement over that handful.\n//\n// Same division as wiki-rerank, and for the same reason: the cheap stage\n// is the one that has to look at everything.\n//\n// Stage 1 only sees skills whose author wrote trigger patterns or tags, so\n// it is also the stage that limits what is reachable. SHORTLIST_FLOOR is\n// deliberately lower than the injection threshold the incumbent uses —\n// recall is stage one's job, and the seat is what says no.\n\n/** Below this the lexical matcher is reporting noise, but the bar is low\n * on purpose: a skill the shortlist never mentions cannot be chosen. */\nconst SHORTLIST_FLOOR = 0.1\n\n/** How many the seat is asked to judge. Beyond a dozen the request grows\n * without the answer improving — the lexical stage is not sorting well\n * enough for the twelfth candidate to be a contender. */\nconst SHORTLIST_SIZE = 12\n\nexport interface SkillPick {\n /** The chosen skill, or null when nothing applies. */\n skill: Skill | null\n /** P(any shortlisted skill is about this task). */\n anyRelevant: number\n confidence: number\n /** What the lexical stage put forward, best first. */\n shortlist: Array<{ name: string; lexical: number; p?: number }>\n /** True when the seat answered; false when this is the lexical result\n * alone, because a seat that is off must not look like a judgement. */\n judged: boolean\n reason: string\n}\n\n/**\n * Rank a workspace's skills against a task.\n *\n * Returns a pick, never applies it — whether a skill is loaded, suggested\n * or ignored is the caller's policy, and this deliberately has no opinion\n * about per-turn context injection.\n */\nexport async function pickSkillForTask(\n skills: Skill[],\n task: string,\n opts: { agent?: string | null; minRelevant?: number } = {},\n): Promise<SkillPick> {\n const minRelevant = opts.minRelevant ?? 0.6\n\n const lexical = matchSkillsToTask(skills, task)\n .filter((m) => m.relevance >= SHORTLIST_FLOOR)\n .slice(0, SHORTLIST_SIZE)\n\n const shortlist = lexical.map((m) => ({\n name: m.skill.frontmatter.name,\n lexical: m.relevance,\n }))\n\n if (lexical.length === 0) {\n return {\n skill: null, anyRelevant: 0, confidence: 0, shortlist, judged: false,\n reason: \"no skill mentions anything in this task\",\n }\n }\n\n // One candidate is not a choice. Still worth asking whether it applies,\n // but a Choice over a single option carries no information, so the\n // lexical score stands and the Noul is not paid for either.\n if (lexical.length === 1) {\n return {\n skill: lexical[0].skill, anyRelevant: lexical[0].relevance,\n confidence: lexical[0].relevance, shortlist, judged: false,\n reason: `only ${lexical[0].skill.frontmatter.name} matched (${lexical[0].matchReason})`,\n }\n }\n\n const candidates: SkillCandidate[] = lexical.map((m) => ({\n name: m.skill.frontmatter.name,\n description: m.skill.frontmatter.description,\n category: m.skill.frontmatter.category ?? null,\n tags: m.skill.frontmatter.tags,\n }))\n\n const result = await askSeat(\n SKILL_SELECT_SEAT,\n skillSelectState({ task, agent: opts.agent ?? null, candidates }),\n skillSelectQuestions(candidates),\n {\n // What the lexical matcher would have done, so agreement between the\n // two is measurable rather than assumed.\n incumbent: { best: candidates[0].name },\n features: { agent: opts.agent ?? \"unknown\", candidates: candidates.length },\n },\n )\n\n if (!result) {\n return {\n skill: lexical[0].skill, anyRelevant: lexical[0].relevance,\n confidence: lexical[0].relevance, shortlist, judged: false,\n reason: `seat off — lexical top match (${lexical[0].matchReason})`,\n }\n }\n\n const selection = toSkillSelection(result.answers as SkillSelectAnswers)\n const ranked = shortlist.map((s) => ({\n ...s,\n p: selection.ranked.find((r) => r.name === s.name)?.p ?? 0,\n })).sort((a, b) => (b.p ?? 0) - (a.p ?? 0))\n\n // Read anyRelevant FIRST. The Choice named something regardless.\n if (selection.anyRelevant < minRelevant) {\n return {\n skill: null, anyRelevant: selection.anyRelevant, confidence: selection.confidence,\n shortlist: ranked, judged: true,\n reason: `nothing on the shortlist is about this task (${selection.anyRelevant.toFixed(2)} below ${minRelevant})`,\n }\n }\n\n const chosen = lexical.find((m) => m.skill.frontmatter.name === selection.name)\n if (!chosen) {\n // Named something outside the option set. Refuse rather than guess.\n return {\n skill: null, anyRelevant: selection.anyRelevant, confidence: selection.confidence,\n shortlist: ranked, judged: true,\n reason: `seat chose \"${selection.name}\", which was not on the shortlist`,\n }\n }\n\n return {\n skill: chosen.skill, anyRelevant: selection.anyRelevant,\n confidence: selection.confidence, shortlist: ranked, judged: true,\n reason: `${selection.name} (confidence ${selection.confidence.toFixed(2)})`,\n }\n}\n","import { choice, noul } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, NoulAnswer, StateValue } from \"../types\"\n\n// Which skill, if any, a task should start from.\n//\n// The incumbent is matchSkillsToTask() in agent/skills/loader.ts: regex\n// triggers at 0.9, tag overlap at 0.3 per tag capped at 0.8, shared words\n// over four characters at 0.2 each capped at 0.6, and anything over 0.1\n// gets used. Those numbers are not wrong so much as unexamined — they were\n// chosen to make a lexical score come out roughly sorted, and they answer\n// \"which skill shares the most words with this sentence\", which is a\n// different question from \"which skill is this task about\".\n//\n// The lexical matcher stays. It is free, it runs with no network, and it\n// is a perfectly good way to narrow a hundred skills down to a handful.\n// This seat re-ranks that handful, which is the same division of labour as\n// wiki-rerank: cheap recall first, judgement second.\n//\n// `anyRelevant` is read FIRST and is the more valuable of the two answers.\n// A Choice over skills always names one, and the failure that matters here\n// is not picking the wrong skill — it is loading a skill at all for a task\n// that needed none, which spends context on instructions that do not\n// apply and quietly steers the agent toward them.\n//\n// One thing this seat can do that the incumbent cannot: rank by what a\n// skill is FOR. The lexical path needs an author to have written trigger\n// patterns and tags by hand, and across ten agent workspaces and 101 skill\n// files, exactly zero have opted into auto-injection. A description is\n// something every skill already has.\n\nexport const SKILL_SELECT_SEAT = \"skill-select\"\n\n/** Enough to judge a skill by, small enough that a dozen fit in one\n * request. The instructions themselves are deliberately excluded — they\n * are thousands of tokens each and the description is what says what the\n * skill is for. */\nexport interface SkillCandidate {\n name: string\n description: string\n category?: string | null\n tags?: string[]\n}\n\nexport interface SkillSelectInput {\n /** The task, in the words it arrived in. */\n task: string\n /** Which agent is about to run it — a deploy skill means something\n * different to devops than to a content writer. */\n agent?: string | null\n candidates: SkillCandidate[]\n}\n\nexport function skillSelectState(input: SkillSelectInput): StateValue {\n return {\n task: clip(input.task, 1200),\n ...(input.agent ? { agent: input.agent } : {}),\n skills: input.candidates.map((c) => ({\n name: c.name,\n purpose: clip(c.description, 300),\n ...(c.category ? { category: c.category } : {}),\n ...(c.tags?.length ? { tags: c.tags.slice(0, 8) } : {}),\n })),\n }\n}\n\n/** Built per call — the option set IS the shortlist. */\nexport function skillSelectQuestions(candidates: SkillCandidate[]) {\n const criteria: Record<string, string> = {}\n for (const c of candidates) criteria[c.name] = clip(c.description, 300)\n\n return {\n anyRelevant: noul(\n \"At least one of these skills is about the task at hand.\",\n {\n true:\n \"The task is the kind of work one of these skills exists for, and following it would change how the work is done — it names the same system, the same procedure or the same kind of artefact.\",\n false:\n \"None of them is about this task. They may share vocabulary with it, mention the same tools, or belong to the same project without bearing on what is being asked. Sharing words with a task is not being about it.\",\n },\n ),\n best: choice(criteria, \"Which skill is this task most about?\"),\n }\n}\n\nexport type SkillSelectAnswers = AnswersFor<ReturnType<typeof skillSelectQuestions>>\n\nexport interface SkillSelection {\n /** P(any of the shortlist applies). Read before `name`. */\n anyRelevant: number\n name: string\n /** Derived from the shape of the probabilities, never self-reported. */\n confidence: number\n ranked: Array<{ name: string; p: number }>\n}\n\nexport function toSkillSelection(answers: SkillSelectAnswers): SkillSelection {\n const any = answers.anyRelevant as NoulAnswer\n const best = answers.best as ChoiceAnswer\n const ranked = Object.entries(best.probabilities ?? {})\n .map(([name, p]) => ({ name, p: Number(p) || 0 }))\n .sort((a, b) => b.p - a.p)\n return {\n anyRelevant: any.noul,\n name: best.choice,\n confidence: best.confidence,\n ranked,\n }\n}\n\nfunction clip(s: string, max: number): string {\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n"],"mappings":"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","names":["init_seat","init_questions","SKILL_SELECT_SEAT","skillSelectState","input","clip","c","skillSelectQuestions","candidates","criteria","noul","choice","toSkillSelection","answers","any","best","ranked","name","p","a","b","s","max","SHORTLIST_FLOOR","SHORTLIST_SIZE","pickSkillForTask","skills","task","opts","minRelevant","lexical","matchSkillsToTask","m","shortlist","candidates","result","askSeat","SKILL_SELECT_SEAT","skillSelectState","skillSelectQuestions","selection","toSkillSelection","ranked","s","r","a","b","chosen"]}
|
|
1
|
+
{"version":3,"sources":["../src/agent/skills/select.ts","../src/decisions/seats/skill-select.ts"],"sourcesContent":["import { askSeat } from \"@/decisions/seat\"\nimport {\n SKILL_SELECT_SEAT,\n skillSelectQuestions,\n skillSelectState,\n toSkillSelection,\n type SkillCandidate,\n type SkillSelectAnswers,\n} from \"@/decisions/seats/skill-select\"\nimport { matchSkillsToTask } from \"./loader\"\nimport type { Skill } from \"./types\"\n\n// Choosing a skill for a task, in two stages.\n//\n// 1. matchSkillsToTask — lexical, free, no network. Narrows a hundred\n// skills to a handful. Good at recall, indifferent about precision.\n// 2. the skill-select seat — judgement over that handful.\n//\n// Same division as wiki-rerank, and for the same reason: the cheap stage\n// is the one that has to look at everything.\n//\n// Stage 1 only sees skills whose author wrote trigger patterns or tags, so\n// it is also the stage that limits what is reachable. SHORTLIST_FLOOR is\n// deliberately lower than the injection threshold the incumbent uses —\n// recall is stage one's job, and the seat is what says no.\n\n/** Below this the lexical matcher is reporting noise, but the bar is low\n * on purpose: a skill the shortlist never mentions cannot be chosen. */\nconst SHORTLIST_FLOOR = 0.1\n\n/** How many the seat is asked to judge. Beyond a dozen the request grows\n * without the answer improving — the lexical stage is not sorting well\n * enough for the twelfth candidate to be a contender. */\nconst SHORTLIST_SIZE = 12\n\nexport interface SkillPick {\n /** The chosen skill, or null when nothing applies. */\n skill: Skill | null\n /** P(any shortlisted skill is about this task). */\n anyRelevant: number\n confidence: number\n /** What the lexical stage put forward, best first. */\n shortlist: Array<{ name: string; lexical: number; p?: number }>\n /** True when the seat answered; false when this is the lexical result\n * alone, because a seat that is off must not look like a judgement. */\n judged: boolean\n reason: string\n}\n\n/**\n * Rank a workspace's skills against a task.\n *\n * Returns a pick, never applies it — whether a skill is loaded, suggested\n * or ignored is the caller's policy, and this deliberately has no opinion\n * about per-turn context injection.\n */\nexport async function pickSkillForTask(\n skills: Skill[],\n task: string,\n opts: { agent?: string | null; minRelevant?: number } = {},\n): Promise<SkillPick> {\n const minRelevant = opts.minRelevant ?? 0.6\n\n const lexical = matchSkillsToTask(skills, task)\n .filter((m) => m.relevance >= SHORTLIST_FLOOR)\n .slice(0, SHORTLIST_SIZE)\n\n const shortlist = lexical.map((m) => ({\n name: m.skill.frontmatter.name,\n lexical: m.relevance,\n }))\n\n if (lexical.length === 0) {\n return {\n skill: null, anyRelevant: 0, confidence: 0, shortlist, judged: false,\n reason: \"no skill mentions anything in this task\",\n }\n }\n\n // One candidate is not a choice. Still worth asking whether it applies,\n // but a Choice over a single option carries no information, so the\n // lexical score stands and the Noul is not paid for either.\n if (lexical.length === 1) {\n return {\n skill: lexical[0].skill, anyRelevant: lexical[0].relevance,\n confidence: lexical[0].relevance, shortlist, judged: false,\n reason: `only ${lexical[0].skill.frontmatter.name} matched (${lexical[0].matchReason})`,\n }\n }\n\n const candidates: SkillCandidate[] = lexical.map((m) => ({\n name: m.skill.frontmatter.name,\n description: m.skill.frontmatter.description,\n category: m.skill.frontmatter.category ?? null,\n tags: m.skill.frontmatter.tags,\n }))\n\n const result = await askSeat(\n SKILL_SELECT_SEAT,\n skillSelectState({ task, agent: opts.agent ?? null, candidates }),\n skillSelectQuestions(candidates),\n {\n // What the lexical matcher would have done, so agreement between the\n // two is measurable rather than assumed.\n incumbent: { best: candidates[0].name },\n features: { agent: opts.agent ?? \"unknown\", candidates: candidates.length },\n },\n )\n\n if (!result) {\n return {\n skill: lexical[0].skill, anyRelevant: lexical[0].relevance,\n confidence: lexical[0].relevance, shortlist, judged: false,\n reason: `seat off — lexical top match (${lexical[0].matchReason})`,\n }\n }\n\n const selection = toSkillSelection(result.answers as SkillSelectAnswers)\n const ranked = shortlist.map((s) => ({\n ...s,\n p: selection.ranked.find((r) => r.name === s.name)?.p ?? 0,\n })).sort((a, b) => (b.p ?? 0) - (a.p ?? 0))\n\n // Read anyRelevant FIRST. The Choice named something regardless.\n if (selection.anyRelevant < minRelevant) {\n return {\n skill: null, anyRelevant: selection.anyRelevant, confidence: selection.confidence,\n shortlist: ranked, judged: true,\n reason: `nothing on the shortlist is about this task (${selection.anyRelevant.toFixed(2)} below ${minRelevant})`,\n }\n }\n\n const chosen = lexical.find((m) => m.skill.frontmatter.name === selection.name)\n if (!chosen) {\n // Named something outside the option set. Refuse rather than guess.\n return {\n skill: null, anyRelevant: selection.anyRelevant, confidence: selection.confidence,\n shortlist: ranked, judged: true,\n reason: `seat chose \"${selection.name}\", which was not on the shortlist`,\n }\n }\n\n return {\n skill: chosen.skill, anyRelevant: selection.anyRelevant,\n confidence: selection.confidence, shortlist: ranked, judged: true,\n reason: `${selection.name} (confidence ${selection.confidence.toFixed(2)})`,\n }\n}\n","import { choice, noul } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, NoulAnswer, StateValue } from \"../types\"\n\n// Which skill, if any, a task should start from.\n//\n// The incumbent is matchSkillsToTask() in agent/skills/loader.ts: regex\n// triggers at 0.9, tag overlap at 0.3 per tag capped at 0.8, shared words\n// over four characters at 0.2 each capped at 0.6, and anything over 0.1\n// gets used. Those numbers are not wrong so much as unexamined — they were\n// chosen to make a lexical score come out roughly sorted, and they answer\n// \"which skill shares the most words with this sentence\", which is a\n// different question from \"which skill is this task about\".\n//\n// The lexical matcher stays. It is free, it runs with no network, and it\n// is a perfectly good way to narrow a hundred skills down to a handful.\n// This seat re-ranks that handful, which is the same division of labour as\n// wiki-rerank: cheap recall first, judgement second.\n//\n// `anyRelevant` is read FIRST and is the more valuable of the two answers.\n// A Choice over skills always names one, and the failure that matters here\n// is not picking the wrong skill — it is loading a skill at all for a task\n// that needed none, which spends context on instructions that do not\n// apply and quietly steers the agent toward them.\n//\n// One thing this seat can do that the incumbent cannot: rank by what a\n// skill is FOR. The lexical path needs an author to have written trigger\n// patterns and tags by hand, and across ten agent workspaces and 101 skill\n// files, exactly zero have opted into auto-injection. A description is\n// something every skill already has.\n\nexport const SKILL_SELECT_SEAT = \"skill-select\"\n\n/** Enough to judge a skill by, small enough that a dozen fit in one\n * request. The instructions themselves are deliberately excluded — they\n * are thousands of tokens each and the description is what says what the\n * skill is for. */\nexport interface SkillCandidate {\n name: string\n description: string\n category?: string | null\n tags?: string[]\n}\n\nexport interface SkillSelectInput {\n /** The task, in the words it arrived in. */\n task: string\n /** Which agent is about to run it — a deploy skill means something\n * different to devops than to a content writer. */\n agent?: string | null\n candidates: SkillCandidate[]\n}\n\nexport function skillSelectState(input: SkillSelectInput): StateValue {\n return {\n task: clip(input.task, 1200),\n ...(input.agent ? { agent: input.agent } : {}),\n skills: input.candidates.map((c) => ({\n name: c.name,\n purpose: clip(c.description, 300),\n ...(c.category ? { category: c.category } : {}),\n ...(c.tags?.length ? { tags: c.tags.slice(0, 8) } : {}),\n })),\n }\n}\n\n/** Built per call — the option set IS the shortlist. */\nexport function skillSelectQuestions(candidates: SkillCandidate[]) {\n const criteria: Record<string, string> = {}\n for (const c of candidates) criteria[c.name] = clip(c.description, 300)\n\n return {\n anyRelevant: noul(\n \"At least one of these skills is about the task at hand.\",\n {\n true:\n \"The task is the kind of work one of these skills exists for, and following it would change how the work is done — it names the same system, the same procedure or the same kind of artefact.\",\n false:\n \"None of them is about this task. They may share vocabulary with it, mention the same tools, or belong to the same project without bearing on what is being asked. Sharing words with a task is not being about it.\",\n },\n ),\n best: choice(criteria, \"Which skill is this task most about?\"),\n }\n}\n\nexport type SkillSelectAnswers = AnswersFor<ReturnType<typeof skillSelectQuestions>>\n\nexport interface SkillSelection {\n /** P(any of the shortlist applies). Read before `name`. */\n anyRelevant: number\n name: string\n /** Derived from the shape of the probabilities, never self-reported. */\n confidence: number\n ranked: Array<{ name: string; p: number }>\n}\n\nexport function toSkillSelection(answers: SkillSelectAnswers): SkillSelection {\n const any = answers.anyRelevant as NoulAnswer\n const best = answers.best as ChoiceAnswer\n const ranked = Object.entries(best.probabilities ?? {})\n .map(([name, p]) => ({ name, p: Number(p) || 0 }))\n .sort((a, b) => b.p - a.p)\n return {\n anyRelevant: any.noul,\n name: best.choice,\n confidence: best.confidence,\n ranked,\n }\n}\n\nfunction clip(s: string, max: number): string {\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n"],"mappings":"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","names":["init_seat","init_questions","SKILL_SELECT_SEAT","skillSelectState","input","clip","c","skillSelectQuestions","candidates","criteria","noul","choice","toSkillSelection","answers","any","best","ranked","name","p","a","b","s","max","SHORTLIST_FLOOR","SHORTLIST_SIZE","pickSkillForTask","skills","task","opts","minRelevant","lexical","matchSkillsToTask","m","shortlist","candidates","result","askSeat","SKILL_SELECT_SEAT","skillSelectState","skillSelectQuestions","selection","toSkillSelection","ranked","s","r","a","b","chosen"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{b as a}from"./chunk-XF65CDNS.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";async function i(o,r,s={}){let e=null,n=await a(l=>{let t=l.crons?.[o];if(t?r&&t.approval?.action==="create"?e={ok:!1,status:409,error:`schedule "${o}" is waiting for approval: agentx schedule approve ${o}`}:t.enabled===r&&(e={ok:!0,id:o,enabled:r,changed:!1}):e={ok:!1,status:404,error:`no schedule "${o}"`},e)throw new Error("no write");t.enabled=r,e={ok:!0,id:o,enabled:r,changed:!0}},{configPath:s.configPath,reload:!1});return e&&(!e.ok||!e.changed)?e:n.success?e??{ok:!1,status:500,error:"mutation did not run"}:{ok:!1,status:500,error:n.error||"could not write agentx.json"}}export{i as setCronEnabled};
|
|
2
|
+
//# sourceMappingURL=set-enabled-ADFSNG5A.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crons/set-enabled.ts"],"sourcesContent":["import { applyConfigMutation } from \"@/daemon/config-mutator\"\n\n// --- Switch one schedule on or off ---\n//\n// The HTTP twin of `agentx cron enable|disable` (commands/manage.ts), used by\n// the daemon's POST /crons/:id/enabled so the phone app and the dashboard can\n// retire an obsolete schedule without a terminal. Same rules as the CLI: an\n// unknown id is an error, and a schedule an agent proposed stays off until it\n// is approved (`agentx schedule approve`), whoever asks.\n//\n// Writes agentx.json through applyConfigMutation (schema-validated, ${VAR}\n// tokens preserved). The caller reloads; this helper never does, so the\n// daemon doesn't POST /reload to itself.\n\nexport type SetCronEnabledResult =\n | { ok: true; id: string; enabled: boolean; changed: boolean }\n | { ok: false; status: 404 | 409 | 500; error: string }\n\nexport async function setCronEnabled(\n id: string,\n enabled: boolean,\n opts: { configPath?: string } = {},\n): Promise<SetCronEnabledResult> {\n // Throwing from the mutator aborts before anything is written, so a\n // refusal or a no-op never touches agentx.json (and never wakes the\n // daemon's config watcher).\n let outcome: SetCronEnabledResult | null = null\n const r = await applyConfigMutation((cfg) => {\n const job = cfg.crons?.[id]\n if (!job) outcome = { ok: false, status: 404, error: `no schedule \"${id}\"` }\n else if (enabled && job.approval?.action === \"create\") {\n outcome = { ok: false, status: 409, error: `schedule \"${id}\" is waiting for approval: agentx schedule approve ${id}` }\n } else if (job.enabled === enabled) outcome = { ok: true, id, enabled, changed: false }\n if (outcome) throw new Error(\"no write\")\n job.enabled = enabled\n outcome = { ok: true, id, enabled, changed: true }\n }, { configPath: opts.configPath, reload: false })\n if (outcome && (!(outcome as SetCronEnabledResult).ok || !(outcome as { changed: boolean }).changed)) return outcome\n if (!r.success) return { ok: false, status: 500, error: r.error || \"could not write agentx.json\" }\n return outcome ?? { ok: false, status: 500, error: \"mutation did not run\" }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/crons/set-enabled.ts"],"sourcesContent":["import { applyConfigMutation } from \"@/daemon/config-mutator\"\n\n// --- Switch one schedule on or off ---\n//\n// The HTTP twin of `agentx cron enable|disable` (commands/manage.ts), used by\n// the daemon's POST /crons/:id/enabled so the phone app and the dashboard can\n// retire an obsolete schedule without a terminal. Same rules as the CLI: an\n// unknown id is an error, and a schedule an agent proposed stays off until it\n// is approved (`agentx schedule approve`), whoever asks.\n//\n// Writes agentx.json through applyConfigMutation (schema-validated, ${VAR}\n// tokens preserved). The caller reloads; this helper never does, so the\n// daemon doesn't POST /reload to itself.\n\nexport type SetCronEnabledResult =\n | { ok: true; id: string; enabled: boolean; changed: boolean }\n | { ok: false; status: 404 | 409 | 500; error: string }\n\nexport async function setCronEnabled(\n id: string,\n enabled: boolean,\n opts: { configPath?: string } = {},\n): Promise<SetCronEnabledResult> {\n // Throwing from the mutator aborts before anything is written, so a\n // refusal or a no-op never touches agentx.json (and never wakes the\n // daemon's config watcher).\n let outcome: SetCronEnabledResult | null = null\n const r = await applyConfigMutation((cfg) => {\n const job = cfg.crons?.[id]\n if (!job) outcome = { ok: false, status: 404, error: `no schedule \"${id}\"` }\n else if (enabled && job.approval?.action === \"create\") {\n outcome = { ok: false, status: 409, error: `schedule \"${id}\" is waiting for approval: agentx schedule approve ${id}` }\n } else if (job.enabled === enabled) outcome = { ok: true, id, enabled, changed: false }\n if (outcome) throw new Error(\"no write\")\n job.enabled = enabled\n outcome = { ok: true, id, enabled, changed: true }\n }, { configPath: opts.configPath, reload: false })\n if (outcome && (!(outcome as SetCronEnabledResult).ok || !(outcome as { changed: boolean }).changed)) return outcome\n if (!r.success) return { ok: false, status: 500, error: r.error || \"could not write agentx.json\" }\n return outcome ?? { ok: false, status: 500, error: \"mutation did not run\" }\n}\n"],"mappings":"wJAkBA,eAAsBA,EACpBC,EACAC,EACAC,EAAgC,CAAC,EACF,CAI/B,IAAIC,EAAuC,KACrCC,EAAI,MAAMC,EAAqBC,GAAQ,CAC3C,IAAMC,EAAMD,EAAI,QAAQN,CAAE,EAK1B,GAJKO,EACIN,GAAWM,EAAI,UAAU,SAAW,SAC3CJ,EAAU,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,aAAaH,uDAAwDA,GAAK,EAC5GO,EAAI,UAAYN,IAASE,EAAU,CAAE,GAAI,GAAM,GAAAH,EAAI,QAAAC,EAAS,QAAS,EAAM,GAH5EE,EAAU,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,gBAAgBH,IAAM,EAIvEG,EAAS,MAAM,IAAI,MAAM,UAAU,EACvCI,EAAI,QAAUN,EACdE,EAAU,CAAE,GAAI,GAAM,GAAAH,EAAI,QAAAC,EAAS,QAAS,EAAK,CACnD,EAAG,CAAE,WAAYC,EAAK,WAAY,OAAQ,EAAM,CAAC,EACjD,OAAIC,IAAY,CAAEA,EAAiC,IAAM,CAAEA,EAAiC,SAAiBA,EACxGC,EAAE,QACAD,GAAW,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,sBAAuB,EADnD,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAOC,EAAE,OAAS,6BAA8B,CAEnG","names":["setCronEnabled","id","enabled","opts","outcome","r","applyConfigMutation","cfg","job"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/signal-exit.ts"],"sourcesContent":["// --- Ctrl-C / SIGTERM for CLI commands ---\n//\n// Short commands should exit at once when stopped. Long-running ones (the\n// daemon above all) install their own handler to finish in-flight work first.\n// The CLI's quick exit used to be registered unconditionally, before the\n// daemon's, and Node runs listeners in registration order — so every stop\n// exited the process before the daemon's drain began and killed in-flight\n// agent tasks mid-turn (#103). Now the quick exit only fires when nobody\n// else handles the signal.\n\ntype SignalTarget = Pick<NodeJS.Process, \"on\" | \"listenerCount\" | \"exit\">\n\nexport const CLI_EXIT_SIGNALS = [\"SIGINT\", \"SIGTERM\"] as const\n\nexport function installCliSignalExit(proc: SignalTarget = process): void {\n for (const sig of CLI_EXIT_SIGNALS) {\n proc.on(sig, () => {\n // This listener is always one of them; anything more means a command\n // is handling the stop itself.\n if (proc.listenerCount(sig) <= 1) proc.exit(0)\n })\n }\n}\n"],"mappings":"4BAYO,IAAMA,EAAmB,CAAC,SAAU,SAAS,EAE7C,SAASC,EAAqBC,EAAqB,QAAe,CACvE,QAAWC,KAAOH,EAChBE,EAAK,GAAGC,EAAK,IAAM,CAGbD,EAAK,cAAcC,CAAG,GAAK,GAAGD,EAAK,KAAK,CAAC,CAC/C,CAAC,CAEL","names":["CLI_EXIT_SIGNALS","installCliSignalExit","proc","sig"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b,c,d}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=surface-usage-
|
|
1
|
+
import{a,b,c,d}from"./chunk-BYD5B3P3.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";export{c as commandPath,b as listSurfaceUse,a as recordSurfaceUse,d as shouldRecordCommand};
|
|
2
|
+
//# sourceMappingURL=surface-usage-O5L4KLGS.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b as g}from"./chunk-
|
|
1
|
+
import{b as g}from"./chunk-TW4L6RP2.js";import"./chunk-RKPAVWXF.js";import{b as h}from"./chunk-OVN5LVJY.js";import"./chunk-SHEUCITK.js";import"./chunk-BB3ESU74.js";import"./chunk-4KJ3PCAI.js";import"./chunk-XF65CDNS.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";var $=t=>typeof t=="string"?t.trim():"",w={in_progress:"in progress",waiting_owner:"waiting on the owner",waiting_other:"waiting on another agent",needs_attention:"needs attention"};function f(t){return t.length?t.map(e=>{let i=new Date(e.createdAt).toISOString().slice(0,16).replace("T"," "),a=e.state==="waiting_owner"&&e.question?` Question: ${e.question}`:e.state==="needs_attention"&&e.attentionReason?` ${e.attentionReason}`:"",n=String(e.text).replace(/\s+/g," ").trim();return`- ${e.id} [${w[e.state]??e.state}] ${i} UTC, ${e.channel}, ${e.agentId}: ${n.length>160?`${n.slice(0,159)}\u2026`:n}.${a}`}).join(`
|
|
2
2
|
`):"No open requests."}async function m(t,e){let i=e.fetch??fetch,a=e.daemonUrl.replace(/\/$/,""),n=($(t.action)||"list").toLowerCase();try{if(n==="list"){let d=await i(`${a}/requests`,{signal:AbortSignal.timeout(1e4)}),r=await d.json().catch(()=>({}));return d.ok?r.enabled?`${r.count} open request${r.count===1?"":"s"}, oldest first.
|
|
3
3
|
${f(r.items??[])}`:"Requests are turned off on this node (requests.enabled), so nothing is recorded.":`Error: ${r?.error||`HTTP ${d.status}`}`}let s=e.env??process.env,u=g(t,s);if(!u)return"Error: couldn't tell which agent you are. Pass callerAgentId.";let c=await i(`${a}/requests`,{method:"POST",headers:{"Content-Type":"application/json",...h(s)},body:JSON.stringify({action:n,agentId:u.agentId,id:t.id,question:t.question,evidence:t.evidence,reason:t.reason}),signal:AbortSignal.timeout(1e4)}),l=await c.json().catch(()=>({}));if(!c.ok)return`Error: ${l?.error||`HTTP ${c.status}`}`;let o=l.request;return n==="accept"?`Request ${o.id} is open and in progress. Close it with done (and a link to the evidence) or decline (and the reason); it stays on the owner's list until then.`:n==="wait"?`Request ${o.id} is waiting on the owner with your question. Ask them the question now, once.`:n==="done"?`Request ${o.id} is closed as done. Evidence: ${o.evidence}`:`Request ${o.id} is closed as declined. Reason: ${o.closeReason}`}catch(s){return`Error: couldn't reach the daemon (${s?.message??s}).`}}export{f as describeOpen,m as runRequestTool};
|
|
4
|
-
//# sourceMappingURL=tool-
|
|
4
|
+
//# sourceMappingURL=tool-MSXWU44P.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/requests/tool.ts"],"sourcesContent":["import { resolveScheduleCaller } from \"@/crons/schedule-tool\"\nimport { callerHeaders } from \"@/calls/service\"\n\n// --- Agent-facing `agentx_request` tool (#356) ---\n//\n// Lets an agent say what it is doing with the owner's request, so the\n// request stays visible until it is closed:\n// accept I am taking this on; keep it open after this turn\n// wait the next step is the owner's answer to this question\n// done finished, with a link to the evidence\n// decline I will not do it, with the reason\n// list what is still open\n// It talks to the daemon's /requests endpoints. Dropping a request is the\n// owner's alone; there is no action for it here.\n\nexport interface RequestToolDeps {\n daemonUrl: string\n fetch?: typeof fetch\n env?: NodeJS.ProcessEnv\n}\n\nconst str = (v: unknown) => (typeof v === \"string\" ? v.trim() : \"\")\n\nconst STATE_WORDS: Record<string, string> = {\n in_progress: \"in progress\",\n waiting_owner: \"waiting on the owner\",\n waiting_other: \"waiting on another agent\",\n needs_attention: \"needs attention\",\n}\n\n/** One line per open request, oldest first. */\nexport function describeOpen(items: any[]): string {\n if (!items.length) return \"No open requests.\"\n return items.map((r) => {\n const when = new Date(r.createdAt).toISOString().slice(0, 16).replace(\"T\", \" \")\n const extra = r.state === \"waiting_owner\" && r.question ? ` Question: ${r.question}`\n : r.state === \"needs_attention\" && r.attentionReason ? ` ${r.attentionReason}` : \"\"\n const words = String(r.text).replace(/\\s+/g, \" \").trim()\n return `- ${r.id} [${STATE_WORDS[r.state] ?? r.state}] ${when} UTC, ${r.channel}, ${r.agentId}: ${words.length > 160 ? `${words.slice(0, 159)}…` : words}.${extra}`\n }).join(\"\\n\")\n}\n\n/** Returns the text the agent sees. Every refusal is text, never a throw. */\nexport async function runRequestTool(args: Record<string, unknown>, deps: RequestToolDeps): Promise<string> {\n const doFetch = deps.fetch ?? fetch\n const base = deps.daemonUrl.replace(/\\/$/, \"\")\n const action = (str(args.action) || \"list\").toLowerCase()\n\n try {\n if (action === \"list\") {\n const res = await doFetch(`${base}/requests`, { signal: AbortSignal.timeout(10_000) })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n if (!data.enabled) return \"Requests are turned off on this node (requests.enabled), so nothing is recorded.\"\n return `${data.count} open request${data.count === 1 ? \"\" : \"s\"}, oldest first.\\n${describeOpen(data.items ?? [])}`\n }\n\n const env = deps.env ?? process.env\n const caller = resolveScheduleCaller(args, env)\n if (!caller) return \"Error: couldn't tell which agent you are. Pass callerAgentId.\"\n const res = await doFetch(`${base}/requests`, {\n method: \"POST\",\n // The headers name this run: the daemon refuses a write that does\n // not come from a running turn of the agent.\n headers: { \"Content-Type\": \"application/json\", ...callerHeaders(env) },\n body: JSON.stringify({\n action, agentId: caller.agentId,\n id: args.id, question: args.question, evidence: args.evidence, reason: args.reason,\n }),\n signal: AbortSignal.timeout(10_000),\n })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n const r = data.request\n if (action === \"accept\") return `Request ${r.id} is open and in progress. Close it with done (and a link to the evidence) or decline (and the reason); it stays on the owner's list until then.`\n if (action === \"wait\") return `Request ${r.id} is waiting on the owner with your question. Ask them the question now, once.`\n if (action === \"done\") return `Request ${r.id} is closed as done. Evidence: ${r.evidence}`\n return `Request ${r.id} is closed as declined. Reason: ${r.closeReason}`\n } catch (e: any) {\n return `Error: couldn't reach the daemon (${e?.message ?? e}).`\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/requests/tool.ts"],"sourcesContent":["import { resolveScheduleCaller } from \"@/crons/schedule-tool\"\nimport { callerHeaders } from \"@/calls/service\"\n\n// --- Agent-facing `agentx_request` tool (#356) ---\n//\n// Lets an agent say what it is doing with the owner's request, so the\n// request stays visible until it is closed:\n// accept I am taking this on; keep it open after this turn\n// wait the next step is the owner's answer to this question\n// done finished, with a link to the evidence\n// decline I will not do it, with the reason\n// list what is still open\n// It talks to the daemon's /requests endpoints. Dropping a request is the\n// owner's alone; there is no action for it here.\n\nexport interface RequestToolDeps {\n daemonUrl: string\n fetch?: typeof fetch\n env?: NodeJS.ProcessEnv\n}\n\nconst str = (v: unknown) => (typeof v === \"string\" ? v.trim() : \"\")\n\nconst STATE_WORDS: Record<string, string> = {\n in_progress: \"in progress\",\n waiting_owner: \"waiting on the owner\",\n waiting_other: \"waiting on another agent\",\n needs_attention: \"needs attention\",\n}\n\n/** One line per open request, oldest first. */\nexport function describeOpen(items: any[]): string {\n if (!items.length) return \"No open requests.\"\n return items.map((r) => {\n const when = new Date(r.createdAt).toISOString().slice(0, 16).replace(\"T\", \" \")\n const extra = r.state === \"waiting_owner\" && r.question ? ` Question: ${r.question}`\n : r.state === \"needs_attention\" && r.attentionReason ? ` ${r.attentionReason}` : \"\"\n const words = String(r.text).replace(/\\s+/g, \" \").trim()\n return `- ${r.id} [${STATE_WORDS[r.state] ?? r.state}] ${when} UTC, ${r.channel}, ${r.agentId}: ${words.length > 160 ? `${words.slice(0, 159)}…` : words}.${extra}`\n }).join(\"\\n\")\n}\n\n/** Returns the text the agent sees. Every refusal is text, never a throw. */\nexport async function runRequestTool(args: Record<string, unknown>, deps: RequestToolDeps): Promise<string> {\n const doFetch = deps.fetch ?? fetch\n const base = deps.daemonUrl.replace(/\\/$/, \"\")\n const action = (str(args.action) || \"list\").toLowerCase()\n\n try {\n if (action === \"list\") {\n const res = await doFetch(`${base}/requests`, { signal: AbortSignal.timeout(10_000) })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n if (!data.enabled) return \"Requests are turned off on this node (requests.enabled), so nothing is recorded.\"\n return `${data.count} open request${data.count === 1 ? \"\" : \"s\"}, oldest first.\\n${describeOpen(data.items ?? [])}`\n }\n\n const env = deps.env ?? process.env\n const caller = resolveScheduleCaller(args, env)\n if (!caller) return \"Error: couldn't tell which agent you are. Pass callerAgentId.\"\n const res = await doFetch(`${base}/requests`, {\n method: \"POST\",\n // The headers name this run: the daemon refuses a write that does\n // not come from a running turn of the agent.\n headers: { \"Content-Type\": \"application/json\", ...callerHeaders(env) },\n body: JSON.stringify({\n action, agentId: caller.agentId,\n id: args.id, question: args.question, evidence: args.evidence, reason: args.reason,\n }),\n signal: AbortSignal.timeout(10_000),\n })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n const r = data.request\n if (action === \"accept\") return `Request ${r.id} is open and in progress. Close it with done (and a link to the evidence) or decline (and the reason); it stays on the owner's list until then.`\n if (action === \"wait\") return `Request ${r.id} is waiting on the owner with your question. Ask them the question now, once.`\n if (action === \"done\") return `Request ${r.id} is closed as done. Evidence: ${r.evidence}`\n return `Request ${r.id} is closed as declined. Reason: ${r.closeReason}`\n } catch (e: any) {\n return `Error: couldn't reach the daemon (${e?.message ?? e}).`\n }\n}\n"],"mappings":"oYAqBA,IAAMA,EAAOC,GAAgB,OAAOA,GAAM,SAAWA,EAAE,KAAK,EAAI,GAE1DC,EAAsC,CAC1C,YAAa,cACb,cAAe,uBACf,cAAe,2BACf,gBAAiB,iBACnB,EAGO,SAASC,EAAaC,EAAsB,CACjD,OAAKA,EAAM,OACJA,EAAM,IAAKC,GAAM,CACtB,IAAMC,EAAO,IAAI,KAAKD,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,EAAE,QAAQ,IAAK,GAAG,EACxEE,EAAQF,EAAE,QAAU,iBAAmBA,EAAE,SAAW,cAAcA,EAAE,WACtEA,EAAE,QAAU,mBAAqBA,EAAE,gBAAkB,IAAIA,EAAE,kBAAoB,GAC7EG,EAAQ,OAAOH,EAAE,IAAI,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,EACvD,MAAO,KAAKA,EAAE,OAAOH,EAAYG,EAAE,KAAK,GAAKA,EAAE,UAAUC,UAAaD,EAAE,YAAYA,EAAE,YAAYG,EAAM,OAAS,IAAM,GAAGA,EAAM,MAAM,EAAG,GAAG,UAAOA,KAASD,GAC9J,CAAC,EAAE,KAAK;AAAA,CAAI,EAPc,mBAQ5B,CAGA,eAAsBE,EAAeC,EAA+BC,EAAwC,CAC1G,IAAMC,EAAUD,EAAK,OAAS,MACxBE,EAAOF,EAAK,UAAU,QAAQ,MAAO,EAAE,EACvCG,GAAUd,EAAIU,EAAK,MAAM,GAAK,QAAQ,YAAY,EAExD,GAAI,CACF,GAAII,IAAW,OAAQ,CACrB,IAAMC,EAAM,MAAMH,EAAQ,GAAGC,aAAiB,CAAE,OAAQ,YAAY,QAAQ,GAAM,CAAE,CAAC,EAC/EG,EAAO,MAAMD,EAAI,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,EAC9C,OAAKA,EAAI,GACJC,EAAK,QACH,GAAGA,EAAK,qBAAqBA,EAAK,QAAU,EAAI,GAAK;AAAA,EAAuBb,EAAaa,EAAK,OAAS,CAAC,CAAC,IADtF,mFADN,UAAUA,GAAM,OAAS,QAAQD,EAAI,WAK3D,IAAME,EAAMN,EAAK,KAAO,QAAQ,IAC1BO,EAASC,EAAsBT,EAAMO,CAAG,EAC9C,GAAI,CAACC,EAAQ,MAAO,gEACpB,IAAMH,EAAM,MAAMH,EAAQ,GAAGC,aAAiB,CAC5C,OAAQ,OAGR,QAAS,CAAE,eAAgB,mBAAoB,GAAGO,EAAcH,CAAG,CAAE,EACrE,KAAM,KAAK,UAAU,CACnB,OAAAH,EAAQ,QAASI,EAAO,QACxB,GAAIR,EAAK,GAAI,SAAUA,EAAK,SAAU,SAAUA,EAAK,SAAU,OAAQA,EAAK,MAC9E,CAAC,EACD,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EACKM,EAAO,MAAMD,EAAI,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,EAC9C,GAAI,CAACA,EAAI,GAAI,MAAO,UAAUC,GAAM,OAAS,QAAQD,EAAI,WACzD,IAAMV,EAAIW,EAAK,QACf,OAAIF,IAAW,SAAiB,WAAWT,EAAE,oJACzCS,IAAW,OAAe,WAAWT,EAAE,kFACvCS,IAAW,OAAe,WAAWT,EAAE,mCAAmCA,EAAE,WACzE,WAAWA,EAAE,qCAAqCA,EAAE,aAC7D,OAASgB,EAAP,CACA,MAAO,qCAAqCA,GAAG,SAAWA,KAC5D,CACF","names":["str","v","STATE_WORDS","describeOpen","items","r","when","extra","words","runRequestTool","args","deps","doFetch","base","action","res","data","env","caller","resolveScheduleCaller","callerHeaders","e"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{b as h}from"./chunk-TW4L6RP2.js";import"./chunk-RKPAVWXF.js";import{f}from"./chunk-Y4CYQV7G.js";import{b as u}from"./chunk-OVN5LVJY.js";import"./chunk-SHEUCITK.js";import"./chunk-BB3ESU74.js";import"./chunk-4KJ3PCAI.js";import"./chunk-XF65CDNS.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-QIJLFPWE.js";var m=new Set(["cron","heartbeat","api","a2a","mesh","approvals","requests"]);function y(e){return typeof e=="string"?e.trim():""}async function A(e,i){let c=i.fetch??fetch,d=i.daemonUrl.replace(/\/$/,""),s=(y(e.action)||"create").toLowerCase(),r=h(e,i.env??process.env);if(s==="status"){let o=y(e.id);if(!o)return"Error: `id` is required for status (the card id you got when you raised it).";try{let n=await c(`${d}/approvals/${encodeURIComponent(o)}`,{signal:AbortSignal.timeout(1e4)}),a=await n.json().catch(()=>({}));if(!n.ok)return`Error: ${a?.error||`HTTP ${n.status}`}`;let t=a.card;if(r&&t.raised_by!==r.agentId)return`Error: card "${o}" was raised by another agent.`;if(t.status==="pending")return`Card ${t.id} is still waiting for the operator. It expires ${t.expires}; then "${t.if_silent}" applies.`;if(t.status==="decided"){let p=t.verdict==="yes"?f(t).join(`
|
|
2
|
+
`):"";return`Card ${t.id}: the operator said ${String(t.verdict).toUpperCase()}.${t.note?` Note: ${t.note}`:""}${p?`
|
|
3
|
+
${p}`:""}`}return`Card ${t.id} expired unanswered; the default applied: ${t.outcome??t.if_silent}.`}catch(n){return`Error: couldn't reach the daemon (${n?.message??n}).`}}if(s!=="create")return`Error: unknown action "${s}". Use create or status.`;if(!r)return"Error: couldn't tell which agent you are. Pass callerAgentId.";let l=r.channel&&r.chatId&&!m.has(r.channel)?{channel:r.channel,chatId:r.chatId,...r.accountId?{accountId:r.accountId}:{}}:void 0,$={title:e.title,ask:e.ask,recommend:e.recommend,if_silent:e.if_silent,expires:e.expires,source:e.source,choices:e.choices,draft:e.draft,say:e.say,context:e.context,raised_by:r.agentId,...l?{reply:l}:{}};try{let o=await c(`${d}/approvals`,{method:"POST",headers:{"Content-Type":"application/json",...u(i.env??process.env)},body:JSON.stringify($),signal:AbortSignal.timeout(1e4)}),n=await o.json().catch(()=>({}));if(!o.ok)return`Error: ${n?.error||`HTTP ${o.status}`}`;let a=n.card;return[`Decision card ${a.id} is in the operator's Approvals inbox.`,`If nobody answers by ${a.expires}, "${a.if_silent}" applies.`,"You will get a message with the result. Tell the requester it is waiting for approval; don't ask the operator again in chat."].join(" ")}catch(o){return`Error: couldn't reach the daemon (${o?.message??o}).`}}export{A as runApprovalTool};
|
|
4
|
+
//# sourceMappingURL=tool-X7YEIJVZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/approvals/tool.ts"],"sourcesContent":["import { resolveScheduleCaller } from \"@/crons/schedule-tool\"\nimport { callerHeaders } from \"@/calls/service\"\nimport { answerLines } from \"./choices\"\n\n// --- Agent-facing `agentx_approval` tool ---\n//\n// Lets an agent raise a decision card instead of asking the operator in\n// chat, and check what happened to one. It talks to the daemon's\n// /approvals endpoints, which never decide: there is no action here that\n// approves or rejects anything.\n\nexport type ApprovalToolAction = \"create\" | \"status\"\n\nexport interface ApprovalToolDeps {\n daemonUrl: string\n fetch?: typeof fetch\n env?: NodeJS.ProcessEnv\n}\n\nconst NON_CHAT_CHANNELS = new Set([\"cron\", \"heartbeat\", \"api\", \"a2a\", \"mesh\", \"approvals\", \"requests\"])\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v.trim() : \"\"\n}\n\n/** Returns the text the agent sees. Every refusal is text, never a throw. */\nexport async function runApprovalTool(args: Record<string, unknown>, deps: ApprovalToolDeps): Promise<string> {\n const doFetch = deps.fetch ?? fetch\n const base = deps.daemonUrl.replace(/\\/$/, \"\")\n const action = (str(args.action) || \"create\").toLowerCase() as ApprovalToolAction\n const caller = resolveScheduleCaller(args, deps.env ?? process.env)\n\n if (action === \"status\") {\n const id = str(args.id)\n if (!id) return \"Error: `id` is required for status (the card id you got when you raised it).\"\n try {\n const res = await doFetch(`${base}/approvals/${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(10_000) })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n const c = data.card\n if (caller && c.raised_by !== caller.agentId) return `Error: card \"${id}\" was raised by another agent.`\n if (c.status === \"pending\") return `Card ${c.id} is still waiting for the operator. It expires ${c.expires}; then \"${c.if_silent}\" applies.`\n if (c.status === \"decided\") {\n const picked = c.verdict === \"yes\" ? answerLines(c).join(\"\\n\") : \"\"\n return `Card ${c.id}: the operator said ${String(c.verdict).toUpperCase()}.${c.note ? ` Note: ${c.note}` : \"\"}${picked ? `\\n${picked}` : \"\"}`\n }\n return `Card ${c.id} expired unanswered; the default applied: ${c.outcome ?? c.if_silent}.`\n } catch (e: any) {\n return `Error: couldn't reach the daemon (${e?.message ?? e}).`\n }\n }\n\n if (action !== \"create\") return `Error: unknown action \"${action}\". Use create or status.`\n if (!caller) return \"Error: couldn't tell which agent you are. Pass callerAgentId.\"\n\n const reply = caller.channel && caller.chatId && !NON_CHAT_CHANNELS.has(caller.channel)\n ? { channel: caller.channel, chatId: caller.chatId, ...(caller.accountId ? { accountId: caller.accountId } : {}) }\n : undefined\n const payload = {\n title: args.title,\n ask: args.ask,\n recommend: args.recommend,\n if_silent: args.if_silent,\n expires: args.expires,\n source: args.source,\n choices: args.choices,\n draft: args.draft,\n say: args.say,\n context: args.context,\n raised_by: caller.agentId,\n ...(reply ? { reply } : {}),\n }\n try {\n const res = await doFetch(`${base}/approvals`, {\n method: \"POST\",\n // The headers name this run: a card moves a request to \"waiting on\n // the owner\" only when it comes from the turn of that request.\n headers: { \"Content-Type\": \"application/json\", ...callerHeaders(deps.env ?? process.env) },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n })\n const data = await res.json().catch(() => ({})) as any\n if (!res.ok) return `Error: ${data?.error || `HTTP ${res.status}`}`\n const c = data.card\n return [\n `Decision card ${c.id} is in the operator's Approvals inbox.`,\n `If nobody answers by ${c.expires}, \"${c.if_silent}\" applies.`,\n \"You will get a message with the result. Tell the requester it is waiting for approval; don't ask the operator again in chat.\",\n ].join(\" \")\n } catch (e: any) {\n return `Error: couldn't reach the daemon (${e?.message ?? e}).`\n }\n}\n"],"mappings":"uaAmBA,IAAMA,EAAoB,IAAI,IAAI,CAAC,OAAQ,YAAa,MAAO,MAAO,OAAQ,YAAa,UAAU,CAAC,EAEtG,SAASC,EAAIC,EAAoB,CAC/B,OAAO,OAAOA,GAAM,SAAWA,EAAE,KAAK,EAAI,EAC5C,CAGA,eAAsBC,EAAgBC,EAA+BC,EAAyC,CAC5G,IAAMC,EAAUD,EAAK,OAAS,MACxBE,EAAOF,EAAK,UAAU,QAAQ,MAAO,EAAE,EACvCG,GAAUP,EAAIG,EAAK,MAAM,GAAK,UAAU,YAAY,EACpDK,EAASC,EAAsBN,EAAMC,EAAK,KAAO,QAAQ,GAAG,EAElE,GAAIG,IAAW,SAAU,CACvB,IAAMG,EAAKV,EAAIG,EAAK,EAAE,EACtB,GAAI,CAACO,EAAI,MAAO,+EAChB,GAAI,CACF,IAAMC,EAAM,MAAMN,EAAQ,GAAGC,eAAkB,mBAAmBI,CAAE,IAAK,CAAE,OAAQ,YAAY,QAAQ,GAAM,CAAE,CAAC,EAC1GE,EAAO,MAAMD,EAAI,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,EAC9C,GAAI,CAACA,EAAI,GAAI,MAAO,UAAUC,GAAM,OAAS,QAAQD,EAAI,WACzD,IAAME,EAAID,EAAK,KACf,GAAIJ,GAAUK,EAAE,YAAcL,EAAO,QAAS,MAAO,gBAAgBE,kCACrE,GAAIG,EAAE,SAAW,UAAW,MAAO,QAAQA,EAAE,oDAAoDA,EAAE,kBAAkBA,EAAE,sBACvH,GAAIA,EAAE,SAAW,UAAW,CAC1B,IAAMC,EAASD,EAAE,UAAY,MAAQE,EAAYF,CAAC,EAAE,KAAK;AAAA,CAAI,EAAI,GACjE,MAAO,QAAQA,EAAE,yBAAyB,OAAOA,EAAE,OAAO,EAAE,YAAY,KAAKA,EAAE,KAAO,UAAUA,EAAE,OAAS,KAAKC,EAAS;AAAA,EAAKA,IAAW,KAE3I,MAAO,QAAQD,EAAE,+CAA+CA,EAAE,SAAWA,EAAE,YACjF,OAASG,EAAP,CACA,MAAO,qCAAqCA,GAAG,SAAWA,KAC5D,EAGF,GAAIT,IAAW,SAAU,MAAO,0BAA0BA,4BAC1D,GAAI,CAACC,EAAQ,MAAO,gEAEpB,IAAMS,EAAQT,EAAO,SAAWA,EAAO,QAAU,CAACT,EAAkB,IAAIS,EAAO,OAAO,EAClF,CAAE,QAASA,EAAO,QAAS,OAAQA,EAAO,OAAQ,GAAIA,EAAO,UAAY,CAAE,UAAWA,EAAO,SAAU,EAAI,CAAC,CAAG,EAC/G,OACEU,EAAU,CACd,MAAOf,EAAK,MACZ,IAAKA,EAAK,IACV,UAAWA,EAAK,UAChB,UAAWA,EAAK,UAChB,QAASA,EAAK,QACd,OAAQA,EAAK,OACb,QAASA,EAAK,QACd,MAAOA,EAAK,MACZ,IAAKA,EAAK,IACV,QAASA,EAAK,QACd,UAAWK,EAAO,QAClB,GAAIS,EAAQ,CAAE,MAAAA,CAAM,EAAI,CAAC,CAC3B,EACA,GAAI,CACF,IAAMN,EAAM,MAAMN,EAAQ,GAAGC,cAAkB,CAC7C,OAAQ,OAGR,QAAS,CAAE,eAAgB,mBAAoB,GAAGa,EAAcf,EAAK,KAAO,QAAQ,GAAG,CAAE,EACzF,KAAM,KAAK,UAAUc,CAAO,EAC5B,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EACKN,EAAO,MAAMD,EAAI,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,EAC9C,GAAI,CAACA,EAAI,GAAI,MAAO,UAAUC,GAAM,OAAS,QAAQD,EAAI,WACzD,IAAME,EAAID,EAAK,KACf,MAAO,CACL,iBAAiBC,EAAE,2CACnB,wBAAwBA,EAAE,aAAaA,EAAE,sBACzC,8HACF,EAAE,KAAK,GAAG,CACZ,OAASG,EAAP,CACA,MAAO,qCAAqCA,GAAG,SAAWA,KAC5D,CACF","names":["NON_CHAT_CHANNELS","str","v","runApprovalTool","args","deps","doFetch","base","action","caller","resolveScheduleCaller","id","res","data","c","picked","answerLines","e","reply","payload","callerHeaders"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-VWTBHLMX.js";import{a as b}from"./chunk-ATXE3EMV.js";import{a}from"./chunk-LUL4ILAQ.js";import"./chunk-CECHWYJ2.js";import{a as c}from"./chunk-HUQRPEDP.js";import"./chunk-GUAJNZIX.js";import"./chunk-WPNRDEHW.js";import"./chunk-2GIDVZ4H.js";import"./chunk-5GXCOHDM.js";import"./chunk-72SZI2UW.js";import"./chunk-FX3D7YON.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-LTHQXWZN.js";import"./chunk-UCKKAWLZ.js";import"./chunk-PN6XHTG5.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{b as WikiHub,a as WikiStore,c as agenticQuery};
|
|
2
|
+
//# sourceMappingURL=wiki-QUVWB4IN.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentix-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "AI operations layer for small and medium businesses. Routes Telegram, WhatsApp, GitLab, GitHub, crons, webhooks, and mesh tasks to AI agents. Web wizard for operators, CLI for engineers. Self-hosted.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
package/dist/ChatApp-IRPSBYC7.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{a as Y,b as Z,c as _,d as ee,e as te,f as ne,g as re}from"./chunk-U2CKL755.js";import{b as Q,g as X}from"./chunk-I2Z6D3MI.js";import"./chunk-445P5QPS.js";import"./chunk-QIJLFPWE.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
|
|
2
|
-
`):[""],B=A.find(s=>s.id===T),D=ne(B?.tier??"",B?.model),pe=O?.rows||24,fe=z.slice(-40);return o(g,{flexDirection:"column",height:pe,children:[o(g,{flexDirection:"column",flexGrow:1,justifyContent:"flex-end",overflow:"hidden",children:[z.length===0&&!$?o(g,{flexDirection:"column",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),c(t,{dimColor:!0,children:" \u2014 chat with "}),o(t,{color:m.accent,children:["@",T]}),c(t,{dimColor:!0,children:" via "}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" (",B.model,")"]}):null]}),c(t,{dimColor:!0,children:"streaming \xB7 markdown \xB7 tools \xB7 type a message, or / for commands"})]}):null,fe.map(s=>c(ce,{turn:s,width:V},s.id)),$?c(ce,{turn:$,width:V}):null]}),N?c(g,{flexDirection:"column",marginTop:1,children:N.map((s,e)=>o(g,{children:[c(t,{color:e===0?m.accent:m.muted,bold:e===0,children:s.cmd.padEnd(10)}),c(t,{dimColor:!0,children:s.desc})]},s.cmd))}):j?o(g,{marginTop:1,children:[c(t,{color:m.accent,children:"\u21B9 "}),j.items.map((s,e)=>o(t,{color:e===0?m.accent:m.muted,children:[s,e<j.items.length-1?" ":""]},e))]}):J?c(g,{marginTop:1,children:c(t,{dimColor:!0,children:J})}):null,c(g,{borderStyle:"single",borderColor:G,borderLeft:!1,borderRight:!1,borderDimColor:!v,width:"100%",children:c(g,{flexDirection:"column",width:"100%",children:ge.map((s,e,r)=>o(g,{children:[c(t,{color:G,bold:!0,children:e===0?"\u203A ":" "}),c(t,{children:s}),e===r.length-1&&!v?c(t,{color:G,children:"\u258F"}):null]},e))})}),o(g,{justifyContent:"space-between",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),o(t,{dimColor:!0,children:[" \xB7 @",T," \xB7 "]}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" ",re(B.model)]}):null]}),o(t,{dimColor:!0,children:["\u21B5 send / cmds @ mention esc ",v?"stop":"exit"]})]})]})}function ce({turn:n,width:p}){let f=H(()=>ye(n,p),[n,p]),a=n.segs.some(h=>h.type==="text"&&h.content);return o(g,{flexDirection:"column",marginBottom:1,children:[o(t,{color:m.accent,bold:!0,children:[F," you"]}),n.you.split(`
|
|
3
|
-
`).map((h,w)=>o(t,{color:"white",children:[" ",h]},`u${w}`)),o(t,{color:m.agent,bold:!0,children:[F," ",n.agentId??"agent"]}),n.thinking?c(g,{flexDirection:"column",children:we(n.thinking,n.live?3:2).map((h,w)=>o(t,{dimColor:!0,italic:!0,children:[" ",h]},`th${w}`))}):null,f,n.live?c(te,{startedAt:n.startedAt,phase:a?"responding":"thinking",hint:"esc to interrupt"}):n.interrupted?o(t,{dimColor:!0,children:[" \u2298 interrupted \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s"]}):n.error?o(t,{color:"red",children:[" \u2717 ",n.error]}):n.elapsedMs!=null?o(t,{dimColor:!0,children:[" \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s",n.outTokens!=null?`, ${n.outTokens} tok`:""]}):null]})}function we(n,p){return n.split(`
|
|
4
|
-
`).filter(a=>a.trim()).slice(-p)}function ye(n,p){let f=[];return n.segs.forEach((a,h)=>{a.type==="tool"?f.push(o(t,{color:a.error?"red":"green",children:[" ","\u25CF ",o(t,{bold:!a.error,dimColor:a.error,children:[a.name,a.arg?o(t,{dimColor:!0,children:["(",a.arg,")"]}):null,a.error?" failed":""]})]},h)):a.content&&f.push(c(t,{children:Ce(Y(a.content,p,{balance:n.live}))},h))}),f}function Ce(n){return n.split(`
|
|
5
|
-
`).map(p=>p.length?" "+p:p).join(`
|
|
6
|
-
`)}export{Pe as ChatApp};
|
|
7
|
-
//# sourceMappingURL=ChatApp-IRPSBYC7.js.map
|