agentix-cli 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -27
- package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
- package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
- package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
- package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
- package/dist/chunk-23AUU4S2.js +102 -0
- package/dist/chunk-23AUU4S2.js.map +1 -0
- package/dist/chunk-4MREHZP5.js +111 -0
- package/dist/chunk-4MREHZP5.js.map +1 -0
- package/dist/chunk-4YCH6IZV.js +2 -0
- package/dist/chunk-6B74TO2J.js +743 -0
- package/dist/chunk-6B74TO2J.js.map +1 -0
- package/dist/chunk-7P5QRX5W.js +108 -0
- package/dist/chunk-7P5QRX5W.js.map +1 -0
- package/dist/chunk-A6BKSZQA.js +3 -0
- package/dist/chunk-A6BKSZQA.js.map +1 -0
- package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
- package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
- package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
- package/dist/chunk-E4ZZILAK.js +15 -0
- package/dist/chunk-E4ZZILAK.js.map +1 -0
- package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
- package/dist/chunk-JTJIPNXY.js +2 -0
- package/dist/chunk-JTJIPNXY.js.map +1 -0
- package/dist/chunk-MBOLXKA2.js +6 -0
- package/dist/chunk-MBOLXKA2.js.map +1 -0
- package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
- package/dist/chunk-MTRWXRFF.js.map +1 -0
- package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
- package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
- package/dist/chunk-QW2OYR3N.js +7 -0
- package/dist/chunk-QW2OYR3N.js.map +1 -0
- package/dist/chunk-SV3I4CI7.js +440 -0
- package/dist/chunk-SV3I4CI7.js.map +1 -0
- package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
- package/dist/chunk-VWTBHLMX.js +1 -0
- package/dist/chunk-WFHIIVLL.js +719 -0
- package/dist/chunk-WFHIIVLL.js.map +1 -0
- package/dist/chunk-WPPUOTY6.js +6 -0
- package/dist/chunk-WPPUOTY6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
- package/dist/config-QXWKEQON.js +2 -0
- package/dist/config-mutator-53XGR2NT.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
- package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
- package/dist/debug-N3B5NVJU.js +2 -0
- package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
- package/dist/doctor-SEOUFHSL.js +2 -0
- package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
- package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
- package/dist/inbox-BIN2S5XN.js +2 -0
- package/dist/index.d.ts +830 -100
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
- package/dist/loader-5CA3O5GM.js +2 -0
- package/dist/loader-6IHUCPVO.js +2 -0
- package/dist/memory-extract-KRCHD3OP.js +2 -0
- package/dist/migrate-v2-QO7SLKIM.js +4 -0
- package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
- package/dist/processes-RJ6RDP32.js +2 -0
- package/dist/program-SASDC2GV.js +2 -0
- package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
- package/dist/query-HZYYRCLU.js +2 -0
- package/dist/recipes-EPLE2SNT.js +2 -0
- package/dist/registry-HM4ELSXS.js +2 -0
- package/dist/rotation-memo-GMTZPMNE.js +13 -0
- package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
- package/dist/scheduler-XVFIK4XH.js +2 -0
- package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
- package/dist/store-BAPDZHXE.js +2 -0
- package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
- package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
- package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
- package/dist/timers-5LPZN55J.js +2 -0
- package/dist/types-VECTMYTO.js +2 -0
- package/dist/types-VECTMYTO.js.map +1 -0
- package/dist/types-ZRLHHCGB.js +2 -0
- package/dist/types-ZRLHHCGB.js.map +1 -0
- package/dist/web/activity-graph.global.js +654 -0
- package/dist/web/activity-graph.global.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1 -1
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
- package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
- package/dist/wiki-53W5NLZV.js +2 -0
- package/dist/wiki-53W5NLZV.js.map +1 -0
- package/dist/workspace-setup-FPGN5VNY.js +2 -0
- package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
- package/package.json +3 -2
- package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
- package/dist/chunk-34NG7UDX.js +0 -11
- package/dist/chunk-34NG7UDX.js.map +0 -1
- package/dist/chunk-ASSJ4Z6Z.js +0 -164
- package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
- package/dist/chunk-BLUXL6HR.js +0 -854
- package/dist/chunk-BLUXL6HR.js.map +0 -1
- package/dist/chunk-BTAEYL3P.js +0 -72
- package/dist/chunk-BTAEYL3P.js.map +0 -1
- package/dist/chunk-DSCMCVTV.js +0 -7
- package/dist/chunk-DSCMCVTV.js.map +0 -1
- package/dist/chunk-HPDHNIA3.js +0 -2
- package/dist/chunk-IM376SD6.js +0 -13
- package/dist/chunk-IM376SD6.js.map +0 -1
- package/dist/chunk-LEIYC3BF.js +0 -104
- package/dist/chunk-LEIYC3BF.js.map +0 -1
- package/dist/chunk-LY7JGDV7.js +0 -6
- package/dist/chunk-LY7JGDV7.js.map +0 -1
- package/dist/chunk-U364GNQX.js +0 -719
- package/dist/chunk-U364GNQX.js.map +0 -1
- package/dist/chunk-WZEYII2S.js +0 -6
- package/dist/chunk-WZEYII2S.js.map +0 -1
- package/dist/config-UOHEG4BW.js +0 -2
- package/dist/config-mutator-KSEBWKJH.js +0 -2
- package/dist/debug-TGAP7E7W.js +0 -2
- package/dist/inbox-O5UB3YQN.js.map +0 -1
- package/dist/loader-BGWA7IRY.js +0 -2
- package/dist/loader-LBHKSXBK.js +0 -2
- package/dist/memory-extract-KKPKAOPH.js +0 -2
- package/dist/program-2MDELQLX.js +0 -2
- package/dist/query-FZAL7AQD.js +0 -2
- package/dist/recipes-LZ56TA3X.js +0 -2
- package/dist/registry-ONJSILH4.js +0 -2
- package/dist/scheduler-XRM2RJIV.js +0 -2
- package/dist/store-4UCO56K2.js +0 -2
- package/dist/timers-QB4KYNAE.js +0 -2
- package/dist/types-ECMEC5BZ.js +0 -2
- package/dist/types-TTB6DJUE.js +0 -2
- package/dist/usage-dashboard-FQGPPABD.js +0 -803
- package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
- package/dist/wiki-OUYKM7MB.js +0 -2
- package/dist/workspace-setup-6GDBEORT.js +0 -2
- /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
- /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
- /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
- /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
- /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
- /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
- /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
- /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
- /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
- /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
- /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
- /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
- /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
- /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
- /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
- /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
- /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
- /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
- /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
- /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
- /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
- /package/dist/{registry-ONJSILH4.js.map → program-SASDC2GV.js.map} +0 -0
- /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
- /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
- /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
- /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
- /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
- /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
- /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
- /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
- /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
- /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
- /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
- /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
- /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
|
@@ -17,7 +17,7 @@ SKIP:
|
|
|
17
17
|
For each fact, output a JSON array:
|
|
18
18
|
[{"category":"fact|secret|preference|commitment|task-state","content":"concise fact, 1-2 sentences","keywords":["keyword1","keyword2"]}]
|
|
19
19
|
|
|
20
|
-
If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-
|
|
20
|
+
If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-WVCMIW67.js"),c=(await i("claude").generate([{role:"system",content:f},{role:"user",content:`${n.sender} (via ${n.channel}): ${s}
|
|
21
21
|
|
|
22
22
|
Agent: ${a}`}],{model:m,maxTokens:512})).content.trim().match(/\[[\s\S]*\]/);if(!c)return;let o=JSON.parse(c[0]);if(!Array.isArray(o)||o.length===0)return;let d=new Date().toISOString().slice(0,10);for(let e of o)r.hasSimilar(t,e.content)||r.addMemory(t,{agentId:t,category:e.category,content:e.content,keywords:e.keywords||[],source:{...n,date:d},expiresAt:e.category==="task-state"?new Date(Date.now()+7*24*60*60*1e3).toISOString():void 0});Math.random()<.02&&r.prune(t)}export{h as a};
|
|
23
|
-
//# sourceMappingURL=chunk-
|
|
23
|
+
//# sourceMappingURL=chunk-IKBHHTIT.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{z as e}from"zod";var i=e.enum(["enum","free","ref"]),a=e.object({name:e.string().regex(/^[a-z][a-z0-9_]*$/i,"axis name must be a simple identifier"),type:i,values:e.array(e.string()).optional(),refLevel:e.string().optional(),optional:e.boolean().optional(),description:e.string().optional()}),r=e.object({id:e.string().regex(/^[a-z][a-z0-9_-]*$/,"level id must be lowercase slug"),axes:e.array(a).min(1),description:e.string().optional()}),f=e.object({version:e.number().int().default(1),levels:e.array(r).min(1).max(5),leafInput:a.optional(),leafOutput:a.optional()}),t=e.string().regex(/^[a-z0-9][a-z0-9._-]*$/,"node id must be lowercase slug (alnum, '.', '_', '-')"),n=e.object({id:t,level:e.string(),parentId:t.nullable(),axes:e.record(e.string(),e.string()),createdAt:e.string(),createdBy:e.string().optional()}),o=e.enum(["llm","user","cache"]),s=e.enum(["pending","approved","rejected"]),p=e.object({ts:e.string(),msgHash:e.string(),agentId:e.string().optional(),channel:e.string().optional(),sender:e.string().optional(),path:e.array(t),proposedAxes:e.record(e.string(),e.record(e.string(),e.string())).default({}),leaf:e.object({input:e.string().optional(),output:e.string().optional()}).partial().default({}),source:o,status:s,confidence:e.number().min(0).max(1).optional(),preview:e.string().optional()}),c=e.object({fingerprint:e.string(),path:e.array(t),leaf:p.shape.leaf,updatedAt:e.string()}),m=e.object({version:e.number().int().default(1),nodes:e.array(n).default([])}),d=e.object({version:e.number().int().default(1),entries:e.record(e.string(),c).default({})});export{f as a,p as b,m as c,d};
|
|
2
|
+
//# sourceMappingURL=chunk-JTJIPNXY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph/types.ts"],"sourcesContent":["import { z } from \"zod\"\n\n// --- Intent Knowledge Graph types + Zod schemas ---\n//\n// A hierarchical, enumerable intent taxonomy with fixed axes per level.\n// See plan: /Users/macbookpro/.claude/plans/delightful-floating-diffie.md\n//\n// Shape on disk (.agentx/graph/):\n// schema.json — level definitions + required axes per level\n// nodes.json — tree of nodes: { id, level, parentId, axes }\n// classifications.jsonl — append-only log of message → path decisions\n// index.json — fingerprint → pathId snap-to-path cache\n\nexport const axisTypeSchema = z.enum([\"enum\", \"free\", \"ref\"])\nexport type AxisType = z.infer<typeof axisTypeSchema>\n\nexport const axisDefSchema = z.object({\n name: z.string().regex(/^[a-z][a-z0-9_]*$/i, \"axis name must be a simple identifier\"),\n type: axisTypeSchema,\n /** For type=\"enum\": closed set of allowed values. */\n values: z.array(z.string()).optional(),\n /** For type=\"ref\": which level's nodes may be referenced. */\n refLevel: z.string().optional(),\n /** If true, this axis may be absent from a node without failing validation.\n * Use for axes that apply conditionally (e.g. a department's \"lead\" ref\n * doesn't apply to a person-kind unit). Absent/false = required. */\n optional: z.boolean().optional(),\n /** Optional human-readable help, shown as tooltip in the admin UI. */\n description: z.string().optional(),\n})\nexport type AxisDef = z.infer<typeof axisDefSchema>\n\nexport const levelDefSchema = z.object({\n id: z.string().regex(/^[a-z][a-z0-9_-]*$/, \"level id must be lowercase slug\"),\n /** Axes every node at this level MUST carry. Order matters for UI rendering. */\n axes: z.array(axisDefSchema).min(1),\n description: z.string().optional(),\n})\nexport type LevelDef = z.infer<typeof levelDefSchema>\n\nexport const graphSchemaSchema = z.object({\n version: z.number().int().default(1),\n /** Ordered from root to leaf. Max 5 to keep the taxonomy scannable. */\n levels: z.array(levelDefSchema).min(1).max(5),\n /** Axis describing where a leaf classification came from (channel, cron, etc.). */\n leafInput: axisDefSchema.optional(),\n /** Free-form description of the expected result of a leaf classification. */\n leafOutput: axisDefSchema.optional(),\n})\nexport type GraphSchema = z.infer<typeof graphSchemaSchema>\n\n// Allow dots so verb-level node ids like \"review.merge-request\" or\n// \"deploy.staging\" parse cleanly (Phase 1 of the classifier-retire plan).\n// Dots are dot-namespaced verbs only — never the leading char.\nexport const nodeIdSchema = z.string().regex(/^[a-z0-9][a-z0-9._-]*$/, \"node id must be lowercase slug (alnum, '.', '_', '-')\")\n\nexport const graphNodeSchema = z.object({\n id: nodeIdSchema,\n /** Matches a `levels[*].id` from the schema. */\n level: z.string(),\n /** null for root-level nodes (level[0]); otherwise the id of a node at level-1. */\n parentId: nodeIdSchema.nullable(),\n /** Values for every axis declared at this level's schema entry. `ref` axes\n * hold the id of another node; `enum` axes hold one of the declared values;\n * `free` axes hold any string. */\n axes: z.record(z.string(), z.string()),\n createdAt: z.string(),\n createdBy: z.string().optional(),\n})\nexport type GraphNode = z.infer<typeof graphNodeSchema>\n\nexport const classificationSourceSchema = z.enum([\"llm\", \"user\", \"cache\"])\nexport type ClassificationSource = z.infer<typeof classificationSourceSchema>\n\nexport const classificationStatusSchema = z.enum([\"pending\", \"approved\", \"rejected\"])\nexport type ClassificationStatus = z.infer<typeof classificationStatusSchema>\n\nexport const classificationSchema = z.object({\n ts: z.string(),\n msgHash: z.string(),\n agentId: z.string().optional(),\n channel: z.string().optional(),\n sender: z.string().optional(),\n /** Node ids from root to leaf, one per level. Shorter-than-full paths are\n * allowed if the classifier was only confident down to a certain depth. */\n path: z.array(nodeIdSchema),\n /** Axis values asserted by the classifier for new nodes it proposed along\n * the path. Keyed by node id. */\n proposedAxes: z.record(z.string(), z.record(z.string(), z.string())).default({}),\n /** Leaf-level extras. */\n leaf: z.object({\n input: z.string().optional(),\n output: z.string().optional(),\n }).partial().default({}),\n source: classificationSourceSchema,\n status: classificationStatusSchema,\n confidence: z.number().min(0).max(1).optional(),\n /** Redacted first ~200 chars of the message, for the approval UI. */\n preview: z.string().optional(),\n})\nexport type Classification = z.infer<typeof classificationSchema>\n\n/** Row in the fingerprint → path snap cache. */\nexport const fingerprintEntrySchema = z.object({\n fingerprint: z.string(),\n path: z.array(nodeIdSchema),\n leaf: classificationSchema.shape.leaf,\n updatedAt: z.string(),\n})\nexport type FingerprintEntry = z.infer<typeof fingerprintEntrySchema>\n\n/** Nodes file is a plain list, not a nested tree — lookups are by id. */\nexport const nodesFileSchema = z.object({\n version: z.number().int().default(1),\n nodes: z.array(graphNodeSchema).default([]),\n})\nexport type NodesFile = z.infer<typeof nodesFileSchema>\n\n/** Index file — record of fingerprints. Hot path; kept as a map on disk. */\nexport const indexFileSchema = z.object({\n version: z.number().int().default(1),\n entries: z.record(z.string(), fingerprintEntrySchema).default({}),\n})\nexport type IndexFile = z.infer<typeof indexFileSchema>\n"],"mappings":"AAAA,OAAS,KAAAA,MAAS,MAaX,IAAMC,EAAiBD,EAAE,KAAK,CAAC,OAAQ,OAAQ,KAAK,CAAC,EAG/CE,EAAgBF,EAAE,OAAO,CACpC,KAAMA,EAAE,OAAO,EAAE,MAAM,qBAAsB,uCAAuC,EACpF,KAAMC,EAEN,OAAQD,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,SAAS,EAErC,SAAUA,EAAE,OAAO,EAAE,SAAS,EAI9B,SAAUA,EAAE,QAAQ,EAAE,SAAS,EAE/B,YAAaA,EAAE,OAAO,EAAE,SAAS,CACnC,CAAC,EAGYG,EAAiBH,EAAE,OAAO,CACrC,GAAIA,EAAE,OAAO,EAAE,MAAM,qBAAsB,iCAAiC,EAE5E,KAAMA,EAAE,MAAME,CAAa,EAAE,IAAI,CAAC,EAClC,YAAaF,EAAE,OAAO,EAAE,SAAS,CACnC,CAAC,EAGYI,EAAoBJ,EAAE,OAAO,CACxC,QAASA,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,EAEnC,OAAQA,EAAE,MAAMG,CAAc,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAE5C,UAAWD,EAAc,SAAS,EAElC,WAAYA,EAAc,SAAS,CACrC,CAAC,EAMYG,EAAeL,EAAE,OAAO,EAAE,MAAM,yBAA0B,uDAAuD,EAEjHM,EAAkBN,EAAE,OAAO,CACtC,GAAIK,EAEJ,MAAOL,EAAE,OAAO,EAEhB,SAAUK,EAAa,SAAS,EAIhC,KAAML,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,OAAO,CAAC,EACrC,UAAWA,EAAE,OAAO,EACpB,UAAWA,EAAE,OAAO,EAAE,SAAS,CACjC,CAAC,EAGYO,EAA6BP,EAAE,KAAK,CAAC,MAAO,OAAQ,OAAO,CAAC,EAG5DQ,EAA6BR,EAAE,KAAK,CAAC,UAAW,WAAY,UAAU,CAAC,EAGvES,EAAuBT,EAAE,OAAO,CAC3C,GAAIA,EAAE,OAAO,EACb,QAASA,EAAE,OAAO,EAClB,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,OAAQA,EAAE,OAAO,EAAE,SAAS,EAG5B,KAAMA,EAAE,MAAMK,CAAY,EAG1B,aAAcL,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAE/E,KAAMA,EAAE,OAAO,CACb,MAAOA,EAAE,OAAO,EAAE,SAAS,EAC3B,OAAQA,EAAE,OAAO,EAAE,SAAS,CAC9B,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EACvB,OAAQO,EACR,OAAQC,EACR,WAAYR,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,EAE9C,QAASA,EAAE,OAAO,EAAE,SAAS,CAC/B,CAAC,EAIYU,EAAyBV,EAAE,OAAO,CAC7C,YAAaA,EAAE,OAAO,EACtB,KAAMA,EAAE,MAAMK,CAAY,EAC1B,KAAMI,EAAqB,MAAM,KACjC,UAAWT,EAAE,OAAO,CACtB,CAAC,EAIYW,EAAkBX,EAAE,OAAO,CACtC,QAASA,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,EACnC,MAAOA,EAAE,MAAMM,CAAe,EAAE,QAAQ,CAAC,CAAC,CAC5C,CAAC,EAIYM,EAAkBZ,EAAE,OAAO,CACtC,QAASA,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,EACnC,QAASA,EAAE,OAAOA,EAAE,OAAO,EAAGU,CAAsB,EAAE,QAAQ,CAAC,CAAC,CAClE,CAAC","names":["z","axisTypeSchema","axisDefSchema","levelDefSchema","graphSchemaSchema","nodeIdSchema","graphNodeSchema","classificationSourceSchema","classificationStatusSchema","classificationSchema","fingerprintEntrySchema","nodesFileSchema","indexFileSchema"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{z as e}from"zod";import{readFileSync as y,existsSync as u}from"fs";import{resolve as p}from"path";import{z as t}from"zod";var x=t.enum(["mon","tue","wed","thu","fri","sat","sun"]),k=t.object({days:t.array(x).default(["mon","tue","wed","thu","fri"]),start:t.string().regex(/^\d{2}:\d{2}$/),end:t.string().regex(/^\d{2}:\d{2}$/),lunch:t.object({start:t.string().regex(/^\d{2}:\d{2}$/),end:t.string().regex(/^\d{2}:\d{2}$/)}).optional()}),S=t.object({title:t.string(),responsibilities:t.array(t.string()).default([]),sopPath:t.string().optional(),kpis:t.array(t.string()).default([])}),j=t.object({role:t.string(),reportsTo:t.string().optional(),schedule:k,utilizationTarget:t.number().min(0).max(1).default(.8)}),w=t.object({channel:t.string(),chatId:t.string(),accountId:t.string().optional()}),C=t.discriminatedUnion("type",[t.object({type:t.literal("backlog"),path:t.string().default(".agentx/backlog.md")}),t.object({type:t.literal("gitlab"),projects:t.array(t.string()).default([])}),t.object({type:t.literal("wiki"),path:t.string(),glob:t.string().default("**/*.md")})]),v=t.object({id:t.string(),pm:t.string().optional(),client:t.string().optional()}),T=t.object({channel:t.string().optional(),chatId:t.string().optional(),username:t.string().optional(),senderId:t.string().optional(),client:t.string(),project:t.string().optional(),displayName:t.string().optional()}),b=t.object({enabled:t.boolean().default(!1),timezone:t.string().default("UTC"),mainChannel:w,workSource:C,roles:t.record(t.string(),S).default({}),orgChart:t.record(t.string(),j).default({}),projects:t.array(v).default([]),contactMap:t.array(T).default([]),workTickMinutes:t.number().int().min(1).max(60).default(15),idleQueueThreshold:t.number().int().min(0).default(0)});import{z as n}from"zod";var L=n.object({name:n.string(),color:n.string().regex(/^#[0-9a-f]{6}$/i).default("#6366f1"),description:n.string().optional()}),z=n.object({id:n.string().regex(/^[a-z0-9_-]+$/,"column id must be lowercase slug"),title:n.string(),kind:n.enum(["open-backlog","scoped-label","closed","label"]).default("label"),mapsToLabel:n.string().optional(),scopedLabel:n.string().optional(),scopedPrefix:n.string().default("Status"),accent:n.string().optional()}),I=n.object({enabled:n.boolean().default(!0),staleDoingMinutes:n.number().int().min(1).default(45),respectLunchBreak:n.boolean().default(!0),respectSchedule:n.boolean().default(!0),action:n.enum(["badge","notify"]).default("badge")}),D=n.discriminatedUnion("type",[n.object({type:n.literal("gitlab"),projects:n.array(n.string()).min(1)})]),B=[{id:"open",title:"Open",kind:"open-backlog",scopedPrefix:"Status",accent:"#6b7280"},{id:"todo",title:"To Do",kind:"scoped-label",scopedLabel:"Status::To Do",scopedPrefix:"Status",accent:"#fb7a35"},{id:"doing",title:"Doing",kind:"scoped-label",scopedLabel:"Status::Doing",scopedPrefix:"Status",accent:"#22c55e"},{id:"onhold",title:"On Hold",kind:"scoped-label",scopedLabel:"Status::On Hold",scopedPrefix:"Status",accent:"#f59e0b"},{id:"review",title:"Review",kind:"scoped-label",scopedLabel:"Status::Review",scopedPrefix:"Status",accent:"#3b82f6"},{id:"closed",title:"Closed",kind:"closed",scopedPrefix:"Status",accent:"#6b7280"}],$=n.object({id:n.string().regex(/^[a-z0-9_-]+$/,"board id must be lowercase slug"),name:n.string(),source:D,primaryToolLabel:n.string().optional(),labels:n.array(L).default([]),columns:n.array(z).min(1).default(B),timeRangeDays:n.number().int().min(1).max(365).default(30),closedWindowDays:n.number().int().min(1).max(365).default(30),reconciliation:I.default({})}),m=n.array($).default([]),P=n.object({name:n.string(),url:n.string(),dashboardUrl:n.string().optional(),token:n.string().optional(),dashboardToken:n.string().optional()}),h=n.object({enabled:n.boolean().default(!1),port:n.number().int().default(4202),bind:n.string().default("127.0.0.1"),token:n.string().optional(),daemonUrl:n.string().default("http://localhost:18800"),daemons:n.array(P).default([]),draftAgent:n.string().optional()}).default({});function V(a,r){let s=new Set(a.labels||[]);if(a.state==="closed"){let o=r.find(l=>l.kind==="closed");if(o)return o.id}for(let o of r)if(o.kind==="scoped-label"&&o.scopedLabel&&s.has(o.scopedLabel))return o.id;for(let o of r)if(o.kind==="label"&&o.mapsToLabel&&s.has(o.mapsToLabel))return o.id;let i=r.find(o=>o.kind==="open-backlog");if(i){let o=i.scopedPrefix+"::";return[...s].some(d=>d.startsWith(o))?null:i.id}return r[0]?.id??null}function X(a,r){let s=[],i=[],o=!1,l=!1;return r.kind==="scoped-label"&&r.scopedLabel?s.push(r.scopedLabel):r.kind==="label"&&r.mapsToLabel?s.push(r.mapsToLabel):r.kind==="closed"&&(o=!0),a&&(a.kind==="scoped-label"&&a.scopedLabel?i.push(a.scopedLabel):a.kind==="label"&&a.mapsToLabel?i.push(a.mapsToLabel):a.kind==="closed"&&r.kind!=="closed"&&(l=!0)),{addLabels:s.length?s:void 0,removeLabels:i.length?i:void 0,closeIssue:o||void 0,reopen:l||void 0}}function A(a){let r=p(a,".env");if(!u(r))return;let s=y(r,"utf-8");for(let i of s.split(`
|
|
2
|
+
`)){let o=i.trim();if(!o||o.startsWith("#"))continue;let l=o.indexOf("=");if(l===-1)continue;let d=o.slice(0,l).trim(),c=o.slice(l+1).trim();process.env[d]||(process.env[d]=c)}}var M=e.object({apiKey:e.string().optional(),defaultModel:e.string().optional(),baseUrl:e.string().optional()}),R=e.object({command:e.string(),args:e.array(e.string()).default([]),env:e.record(e.string(),e.string()).optional()}),E=e.object({name:e.string(),workspace:e.string(),tier:e.enum(["claude-code","sdk","orchestrator"]).default("claude-code"),provider:e.string().optional(),model:e.string().optional(),systemPrompt:e.string().optional(),mentions:e.array(e.string()).default([]),intents:e.array(e.string()).default([]),maxDelegationDepth:e.number().int().min(0).max(50).default(5),mcp:e.record(e.string(),R).optional(),contextStrategy:e.enum(["layered","planner"]).optional(),contextReferences:e.boolean().default(!1),maxConcurrent:e.number().default(1),maxExecutionMinutes:e.number().int().min(1).max(240).default(20),permissionMode:e.string().default("default"),queueMode:e.enum(["collect","followup","drop"]).default("collect"),heartbeat:e.object({enabled:e.boolean().default(!1),intervalMinutes:e.number().default(30),prompt:e.string().default("Check inbox, pending tasks, and system health. Report anything that needs attention."),channel:e.string().default("heartbeat")}).default({}),access:e.enum(["private","public"]).default("private")}),O=e.object({token:e.string(),agentBinding:e.string(),allowFrom:e.array(e.string()).optional(),pollInbound:e.boolean().default(!0)}),U=e.object({telegram:e.object({enabled:e.boolean().default(!1),accounts:e.record(e.string(),O).default({}),policy:e.object({dm:e.enum(["pair","block"]).default("pair"),group:e.enum(["mention-required","all"]).default("mention-required"),allowFrom:e.array(e.string()).optional()}).default({})}).default({}),whatsapp:e.object({enabled:e.boolean().default(!1),sessionDir:e.string().default(".agentx/whatsapp-sessions"),defaultAgent:e.string().optional(),allowFrom:e.array(e.string()).optional(),routes:e.array(e.object({contact:e.string().optional(),group:e.string().optional(),agent:e.string()})).default([]),ingest:e.object({enabled:e.boolean().default(!1),mode:e.enum(["metadata-only","messages"]).default("metadata-only"),allowContacts:e.array(e.string()).default([]),allowGroups:e.array(e.string()).default([]),denyContacts:e.array(e.string()).default([]),denyGroups:e.array(e.string()).default([]),messageCap:e.number().int().min(1).max(500).default(50),historyDays:e.number().int().min(1).max(365).default(30),contactRefreshDays:e.number().int().min(1).max(90).default(7),throttle:e.object({minMsBetweenCalls:e.number().int().min(100).default(1500),maxCallsPerMinute:e.number().int().min(1).default(20),maxChatsPerSweep:e.number().int().min(1).default(25)}).default({}),retentionDays:e.number().int().min(0).default(0)}).default({})}).default({}),discord:e.object({enabled:e.boolean().default(!1),token:e.string().optional(),agentBinding:e.string().optional()}).default({}),slack:e.object({enabled:e.boolean().default(!1),botToken:e.string().optional(),appToken:e.string().optional(),agentBinding:e.string().optional()}).default({}),gitlab:e.object({enabled:e.boolean().default(!1),webhookPort:e.number().default(18810),webhookSecret:e.string().optional(),host:e.string().default("https://gitlab.com"),token:e.string().optional(),routes:e.array(e.object({project:e.string(),agent:e.string()})).default([]),agentMappings:e.array(e.object({agentId:e.string(),gitlabUsernames:e.array(e.string()).default([]),keywords:e.array(e.string()).default([]),token:e.string().optional(),node:e.string().optional()})).default([])}).default({}),github:e.object({enabled:e.boolean().default(!1),token:e.string().optional(),tokenFile:e.string().optional(),appId:e.number().optional(),clientId:e.string().optional(),privateKeyFile:e.string().optional(),webhookSecret:e.string().optional(),routes:e.array(e.object({repo:e.string(),agent:e.string()})).default([]),agentMappings:e.array(e.object({agentId:e.string(),githubUsernames:e.array(e.string()).default([]),token:e.string().optional(),tokenFile:e.string().optional(),node:e.string().optional()})).default([])}).default({}),webrtc:e.object({enabled:e.boolean().default(!1),stunServers:e.array(e.string()).default(["stun:stun.l.google.com:19302"]),turnServers:e.array(e.object({urls:e.string(),username:e.string().optional(),credential:e.string().optional()})).default([]),allowedCallers:e.array(e.string()).default([]),ringNotify:e.array(e.object({channel:e.string(),chatId:e.string(),accountId:e.string().optional()})).default([]),callUrlBase:e.string().optional(),bot:e.object({enabled:e.boolean().default(!1),defaultAgentId:e.string().optional(),whisperBackend:e.enum(["auto","mlx","openai"]).default("auto"),whisperModel:e.string().optional(),whisperLanguage:e.string().default("auto"),mlxBinary:e.string().optional(),transcriptChannel:e.object({channel:e.string(),chatId:e.string(),accountId:e.string().optional()}).optional(),maxCallMinutes:e.number().int().min(1).max(240).default(30)}).default({})}).default({})}),_=e.object({enabled:e.boolean().default(!0),schedule:e.string(),timezone:e.string().default("UTC"),agent:e.string(),prompt:e.string(),timeout:e.number().default(600),model:e.string().optional(),maxOutputTokens:e.number().int().min(50).max(8e3).optional(),onError:e.union([e.enum(["log","notify","disable"]),e.array(e.enum(["log","notify","disable"]))]).default("log").transform(a=>Array.isArray(a)?a:[a]),notify:e.object({channel:e.string(),chatId:e.string(),accountId:e.string().optional()}).optional()}),F=e.object({name:e.string(),triggers:e.array(e.object({pattern:e.string(),channel:e.string().optional()})),allowedContacts:e.array(e.string()).optional(),agent:e.string(),prompt:e.string(),schedule:e.string().optional(),timezone:e.string().default("UTC"),notify:e.object({channel:e.string(),chatId:e.string(),accountId:e.string().optional()}).optional()}),N=e.object({url:e.string(),name:e.string(),token:e.string().optional()}),W=e.object({enabled:e.boolean().default(!1),peers:e.array(N).default([]),discovery:e.enum(["static","mdns"]).default("static"),healthCheck:e.object({interval:e.number().default(60),timeout:e.number().default(10)}).default({})}),J=e.object({enabled:e.boolean().default(!1),baseDir:e.string().default(".agentx/graph"),draftAgent:e.string().optional(),reviewAgent:e.string().optional(),autoApproveStructure:e.enum(["strict","extend-leaves","any"]).default("extend-leaves"),autoApproveConfidence:e.number().min(0).max(1).default(1),classifierModel:e.string().default("claude-haiku-4-5-20251001"),retrievalWeights:e.object({graph:e.number().min(0).default(.6),bm25:e.number().min(0).default(.4)}).default({})}).default({}),q=e.object({longTaskThreshold:e.number().default(30),destination:e.object({channel:e.string(),chatId:e.string(),accountId:e.string().optional()}).optional(),on:e.object({taskComplete:e.boolean().default(!0),taskError:e.boolean().default(!0),taskQueued:e.boolean().default(!1)}).default({})}).default({}),K=e.object({node:e.object({id:e.string(),name:e.string(),bind:e.string().default("127.0.0.1:18800"),defaultAgent:e.string().optional()}),providers:e.record(e.string(),M).default({}),agents:e.record(e.string(),E).default({}),channels:U.default({}),crons:e.record(e.string(),_).default({}),services:e.record(e.string(),F).default({}),notifications:q,mesh:W.default({}),business:b.optional(),boards:m,dashboard:h,graph:J,workflows:e.object({enabled:e.boolean().default(!1),dir:e.string().default(".agentx/workflows"),editor:e.enum(["disabled","readonly","edit"]).default("edit")}).default({}),webhooks:e.array(e.object({id:e.string().regex(/^[a-z0-9][a-z0-9_-]*$/,"webhook id must be lowercase slug"),source:e.enum(["gitlab","github","sentry","stripe","discord","slack","custom"]),agentId:e.string(),secretEnv:e.string().optional(),description:e.string().optional(),enabled:e.boolean().default(!0),node:e.string().optional(),triggers:e.record(e.string(),e.string()).optional(),defaultWorkflow:e.string().optional()})).default([]),session:e.object({staleMinutes:e.number().int().min(1).max(1440).default(45),maxTurnsPerSession:e.number().int().min(2).max(200).default(15),tierTwoThresholdTokens:e.number().int().min(5e4).max(2e5).default(195e3),contextStrategy:e.enum(["layered","planner"]).default("layered"),maxClaudeCodeDispatchesPerHour:e.number().int().min(1).max(1e4).default(80),maxClaudeCodeDispatchesPer5h:e.number().int().min(1).max(5e4).default(180)}).default({}),plugins:e.array(e.string()).default([])});function f(a){if(typeof a=="string")return a.replace(/\$\{(\w+)\}/g,(r,s)=>process.env[s]||"");if(Array.isArray(a))return a.map(f);if(a!==null&&typeof a=="object"){let r={};for(let[s,i]of Object.entries(a))r[s]=f(i);return r}return a}function oe(a){let r=a?[a]:[p(process.cwd(),"agentx.json"),p(process.cwd(),".agentx/config.json")];A(process.cwd());let s,i;for(let c of r)if(u(c)){s=y(c,"utf-8"),i=c;break}if(!s||!i)throw new Error(`No config found. Create agentx.json or .agentx/config.json
|
|
3
|
+
Searched: ${r.join(", ")}`);let o;try{o=JSON.parse(s)}catch(c){throw new Error(`Invalid JSON in ${i}: ${c.message}`)}let l=f(o),d=K.safeParse(l);if(!d.success){let c=d.error.issues.map(g=>` ${g.path.join(".")}: ${g.message}`).join(`
|
|
4
|
+
`);throw new Error(`Config validation failed (${i}):
|
|
5
|
+
${c}`)}return d.data}function re(a){let r=[];for(let[s,i]of Object.entries(a.agents)){if(!u(i.workspace)){r.push(`Agent "${s}": workspace not found at ${i.workspace}`);continue}if(i.tier==="claude-code"){let d=p(i.workspace,".claude");u(d)||r.push(`Agent "${s}": no .claude/ directory in workspace ${i.workspace}. Claude Code native features (hooks, MCP, skills) won't be available.`)}let o=i.provider||"claude",l=a.providers[o];i.tier!=="claude-code"&&(!l||!l.apiKey)&&r.push(`Agent "${s}": provider "${o}" has no API key configured. Set providers.${o}.apiKey in config or use tier "claude-code" for subscription.`)}for(let[s,i]of Object.entries(a.crons))a.agents[i.agent]||r.push(`Cron "${s}": references unknown agent "${i.agent}"`);if(a.channels.telegram.enabled)for(let[s,i]of Object.entries(a.channels.telegram.accounts))a.agents[i.agentBinding]||r.push(`Telegram account "${s}": references unknown agent "${i.agentBinding}"`);return r}export{V as a,X as b,K as c,f as d,oe as e,re as f};
|
|
6
|
+
//# sourceMappingURL=chunk-MBOLXKA2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/config.ts","../src/business/config.ts","../src/boards/config.ts"],"sourcesContent":["import { z } from \"zod\"\nimport { readFileSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { businessConfigSchema } from \"@/business/config\"\nimport { boardsConfigSchema, dashboardConfigSchema } from \"@/boards/config\"\n\n/**\n * Load .env file into process.env (simple, no dependency).\n */\nfunction loadDotEnv(dir: string): void {\n const envPath = resolve(dir, \".env\")\n if (!existsSync(envPath)) return\n\n const content = readFileSync(envPath, \"utf-8\")\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) continue\n const eqIdx = trimmed.indexOf(\"=\")\n if (eqIdx === -1) continue\n const key = trimmed.slice(0, eqIdx).trim()\n const value = trimmed.slice(eqIdx + 1).trim()\n if (!process.env[key]) {\n process.env[key] = value\n }\n }\n}\n\n// --- Daemon configuration schema & loader ---\n\nconst providerConfigSchema = z.object({\n apiKey: z.string().optional(),\n defaultModel: z.string().optional(),\n baseUrl: z.string().optional(),\n})\n\nconst mcpServerSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n env: z.record(z.string(), z.string()).optional(),\n})\n\nconst agentConfigSchema = z.object({\n name: z.string(),\n workspace: z.string(),\n tier: z.enum([\"claude-code\", \"sdk\", \"orchestrator\"]).default(\"claude-code\"),\n provider: z.string().optional(),\n model: z.string().optional(),\n systemPrompt: z.string().optional(),\n mentions: z.array(z.string()).default([]),\n /** Phase 5 — typed capabilities (drop-condition fallback). Free-form\n * list of intent strings this agent is allowed to handle. When set,\n * the org-chart `canHandle(agentId, project, intent)` check rejects\n * dispatches with intents not in this list. When empty/unset\n * (the default), the agent is permissive — handles any intent.\n * Intent matching is exact-string for now; a glob/prefix layer can\n * come later if it produces real rejections.\n * Example: [\"issue.opened\", \"issue.commented\", \"merge_request.opened\"]\n * Example: [\"cron.fired\", \"message.received\"] */\n intents: z.array(z.string()).default([]),\n /** Phase 8 — capability-bounded security. Max distinct upstream\n * agents in the delegation chain on the same (project, subject)\n * before a dispatch to THIS agent is refused. The ledger itself\n * provides the chain — `decideAndCommit` walks recent decisions on\n * the subject and counts distinct agents. Default 5; set to 0 to\n * disable for an agent that's always called as the bottom of a\n * chain. The check prevents cascade loops where A → B → A → B → ...\n * blows past sane chain depth. */\n maxDelegationDepth: z.number().int().min(0).max(50).default(5),\n /** MCP servers this agent's Claude Code session should load. Synced\n * to <workspace>/.mcp.json at daemon boot via agent-mcp.ts. Operator\n * edits to .mcp.json are respected (see SyncResult.skipped-operator-owned). */\n mcp: z.record(z.string(), mcpServerSchema).optional(),\n /** Per-agent override for the global `session.contextStrategy`. Lets\n * one agent run `planner` (smaller upfront context, more tool-driven\n * exploration) while siblings stay on `layered`. Used for agents that\n * consistently bloat their cache via large workspace reads. */\n contextStrategy: z.enum([\"layered\", \"planner\"]).optional(),\n /** When true, the registry resolves references-recipes for this agent's\n * workspace and renders a deterministic [Verified References] block at\n * priority 4.7. Off by default — flip on per agent (pm-ksi, devops-agent,\n * etc.) once a `references/` registry exists in the agent's workspace\n * or repo root. See src/agents/references/. */\n contextReferences: z.boolean().default(false),\n maxConcurrent: z.number().default(1),\n /** Hard wall-clock cap on a single Claude Code invocation. Exceeding the\n * cap sends SIGTERM (exit 143). Default 20 min — bump for devops/coder\n * agents that do long investigations or multi-file refactors. */\n maxExecutionMinutes: z.number().int().min(1).max(240).default(20),\n permissionMode: z.string().default(\"default\"),\n queueMode: z.enum([\"collect\", \"followup\", \"drop\"]).default(\"collect\"),\n heartbeat: z.object({\n enabled: z.boolean().default(false),\n intervalMinutes: z.number().default(30),\n prompt: z.string().default(\"Check inbox, pending tasks, and system health. Report anything that needs attention.\"),\n channel: z.string().default(\"heartbeat\"),\n }).default({}),\n /** Reachability via the external public API.\n * - \"private\" (default): this agent is daemon-internal + channel-bound only.\n * - \"public\": external apps can POST /api/public/agents/<id>/messages\n * with a scoped token (agent:<id> or agent:*). */\n access: z.enum([\"private\", \"public\"]).default(\"private\"),\n})\n\nconst telegramAccountSchema = z.object({\n token: z.string(),\n agentBinding: z.string(),\n /** Per-account sender allowlist. When set, OVERRIDES the global\n * `policy.allowFrom`. Entries accept:\n * - numeric user id (\"1816212449\") — matches sender (from.id)\n * - numeric chat id (\"-1003861455814\") — matches chat (chat.id)\n * - \"@username\" — matches sender username\n * A message is dispatched iff at least one entry matches. */\n allowFrom: z.array(z.string()).optional(),\n /** When false, the daemon keeps the bot token registered so outbound\n * `send()` / ring notifications work, but does NOT long-poll for inbound\n * updates. Use this on nodes where the bound agent lives on a different\n * daemon — otherwise two daemons race on the same `getUpdates` cursor and\n * Telegram responds 409 Conflict on every poll.\n * Defaults to true (poll) so existing single-node setups are unchanged. */\n pollInbound: z.boolean().default(true),\n})\n\nconst channelsConfigSchema = z.object({\n telegram: z.object({\n enabled: z.boolean().default(false),\n accounts: z.record(z.string(), telegramAccountSchema).default({}),\n policy: z.object({\n dm: z.enum([\"pair\", \"block\"]).default(\"pair\"),\n group: z.enum([\"mention-required\", \"all\"]).default(\"mention-required\"),\n /** Global sender allowlist applied to every account that doesn't set\n * its own `allowFrom`. When neither is configured, every incoming\n * message is dropped — closed by default. Same entry forms as the\n * per-account list (user id, chat id, @username). */\n allowFrom: z.array(z.string()).optional(),\n }).default({}),\n }).default({}),\n whatsapp: z.object({\n enabled: z.boolean().default(false),\n sessionDir: z.string().default(\".agentx/whatsapp-sessions\"),\n defaultAgent: z.string().optional(),\n allowFrom: z.array(z.string()).optional(),\n routes: z.array(z.object({\n contact: z.string().optional(),\n group: z.string().optional(),\n agent: z.string(),\n })).default([]),\n /** Data-source ingestion — turns WhatsApp from a messaging-only channel\n * into a source that seeds the wiki with contact/group metadata (and\n * optionally bounded message windows) per an explicit allowlist.\n * Default-deny: `enabled: false` and empty allowlists mean nothing is\n * ingested. See docs/reference/whatsapp-ingest.md for the full story. */\n ingest: z.object({\n enabled: z.boolean().default(false),\n /** `metadata-only` pulls contact/group info; `messages` additionally\n * pulls the last `messageCap` messages per allowlisted chat. */\n mode: z.enum([\"metadata-only\", \"messages\"]).default(\"metadata-only\"),\n /** Phone numbers or JIDs. Substring match, same semantics as allowFrom. */\n allowContacts: z.array(z.string()).default([]),\n allowGroups: z.array(z.string()).default([]),\n denyContacts: z.array(z.string()).default([]),\n denyGroups: z.array(z.string()).default([]),\n /** Per-chat cap when mode = \"messages\". Keeps the raw-entry size bounded. */\n messageCap: z.number().int().min(1).max(500).default(50),\n /** Max age (days) of messages to consider for the bounded window. */\n historyDays: z.number().int().min(1).max(365).default(30),\n /** Skip re-writing a contact entry unless this many days have passed\n * OR the profile hash differs. Prevents churn on unchanged profiles. */\n contactRefreshDays: z.number().int().min(1).max(90).default(7),\n /** Safety rails on live Baileys reads — personal-account accounts\n * can get throttled/banned under burst reads. */\n throttle: z.object({\n minMsBetweenCalls: z.number().int().min(100).default(1500),\n maxCallsPerMinute: z.number().int().min(1).default(20),\n maxChatsPerSweep: z.number().int().min(1).default(25),\n }).default({}),\n /** Purge absorbed raw entries older than this many days. `0` = never. */\n retentionDays: z.number().int().min(0).default(0),\n }).default({}),\n }).default({}),\n discord: z.object({\n enabled: z.boolean().default(false),\n token: z.string().optional(),\n agentBinding: z.string().optional(),\n }).default({}),\n slack: z.object({\n enabled: z.boolean().default(false),\n /** xoxb-... — bot user OAuth token from the Slack app's \"OAuth & Permissions\" tab. */\n botToken: z.string().optional(),\n /** xapp-... — app-level token with connections:write scope (for Socket Mode). */\n appToken: z.string().optional(),\n agentBinding: z.string().optional(),\n }).default({}),\n gitlab: z.object({\n enabled: z.boolean().default(false),\n webhookPort: z.number().default(18810),\n webhookSecret: z.string().optional(),\n host: z.string().default(\"https://gitlab.com\"),\n token: z.string().optional(),\n routes: z.array(z.object({\n project: z.string(),\n agent: z.string(),\n })).default([]),\n agentMappings: z.array(z.object({\n agentId: z.string(),\n gitlabUsernames: z.array(z.string()).default([]),\n keywords: z.array(z.string()).default([]),\n token: z.string().optional(),\n /** If set, the agent lives on a remote mesh peer (node id). Forces the\n * username→agent map to resolve to this mapping even when a local\n * agent's token resolves to the same GitLab user — prevents collisions\n * like two agents both claiming @devops-noqta. */\n node: z.string().optional(),\n })).default([]),\n }).default({}),\n github: z.object({\n enabled: z.boolean().default(false),\n /** GitHub PAT or env var (${GITHUB_TOKEN}) for posting comments back. */\n token: z.string().optional(),\n /** Path to file containing the token (first line read at startup). */\n tokenFile: z.string().optional(),\n /** GitHub App ID (legacy issuer). */\n appId: z.number().optional(),\n /** GitHub App Client ID (preferred JWT issuer per GitHub's updated docs). */\n clientId: z.string().optional(),\n /** Path to the GitHub App private key PEM file. */\n privateKeyFile: z.string().optional(),\n /** Webhook secret for validating X-Hub-Signature-256. */\n webhookSecret: z.string().optional(),\n /** Repo → agent routing. Use \"owner/repo\" or \"*\" for default. */\n routes: z.array(z.object({\n repo: z.string(),\n agent: z.string(),\n })).default([]),\n /** Per-agent identity mappings (GitHub usernames, tokens, mesh nodes). */\n agentMappings: z.array(z.object({\n agentId: z.string(),\n githubUsernames: z.array(z.string()).default([]),\n token: z.string().optional(),\n tokenFile: z.string().optional(),\n node: z.string().optional(),\n })).default([]),\n }).default({}),\n webrtc: z.object({\n enabled: z.boolean().default(false),\n /** ICE STUN servers for NAT discovery. Default is Google's public STUN. */\n stunServers: z.array(z.string()).default([\"stun:stun.l.google.com:19302\"]),\n /** Optional TURN relays (required when both peers are behind symmetric NAT). */\n turnServers: z.array(z.object({\n urls: z.string(),\n username: z.string().optional(),\n credential: z.string().optional(),\n })).default([]),\n /** Peer names permitted to *initiate* a call into this daemon. Empty = allow all mesh peers. */\n allowedCallers: z.array(z.string()).default([]),\n /** Where to send \"someone is calling\" notifications when an inbound ring\n * arrives. Each entry is delivered via the matching channel adapter —\n * same plumbing as any other outbound message. Empty disables notifications. */\n ringNotify: z.array(z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n })).default([]),\n /** Base URL used when building the tap-to-join link in ring notifications.\n * Defaults to `http://<node.bind>` — override when the daemon's public\n * hostname differs (e.g. HTTPS-terminated tunnel, Tailscale MagicDNS). */\n callUrlBase: z.string().optional(),\n /** AI participant (\"bot\") joins calls when a browser opens with `?bot=<id>`.\n * v1 is transcribe-only — bot consumes remote audio, transcribes via\n * Whisper, posts chunks to the configured channel. No TTS-back. */\n bot: z.object({\n enabled: z.boolean().default(false),\n /** Default agent id used to attribute the transcript when the URL\n * doesn't override via `?bot=<other-agent>`. */\n defaultAgentId: z.string().optional(),\n /** \"auto\" tries mlx-whisper, falls back to OpenAI; explicit forces. */\n whisperBackend: z.enum([\"auto\", \"mlx\", \"openai\"]).default(\"auto\"),\n whisperModel: z.string().optional(),\n whisperLanguage: z.string().default(\"auto\"),\n /** Absolute path to mlx_whisper if not on the daemon's PATH (common\n * on macOS launchd, where ~/.pyenv/shims is missing from the env). */\n mlxBinary: z.string().optional(),\n /** Where to send each transcribed chunk. Same shape as ringNotify[]. */\n transcriptChannel: z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n }).optional(),\n /** Hard cap so a forgotten bot doesn't run forever. */\n maxCallMinutes: z.number().int().min(1).max(240).default(30),\n }).default({}),\n }).default({}),\n})\n\nconst cronJobSchema = z.object({\n enabled: z.boolean().default(true),\n schedule: z.string(),\n timezone: z.string().default(\"UTC\"),\n agent: z.string(),\n prompt: z.string(),\n timeout: z.number().default(600),\n model: z.string().optional(),\n /** Soft cap on output length. Claude Code CLI has no hard flag for this,\n * but appending an instruction to the prompt is reliably honored. Use\n * 1500 for briefs, 500 for status pings, 300 for pure classifiers. */\n maxOutputTokens: z.number().int().min(50).max(8000).optional(),\n onError: z.union([\n z.enum([\"log\", \"notify\", \"disable\"]),\n z.array(z.enum([\"log\", \"notify\", \"disable\"])),\n ]).default(\"log\").transform(v => Array.isArray(v) ? v : [v]),\n notify: z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n }).optional(),\n})\n\nconst serviceSchema = z.object({\n name: z.string(),\n triggers: z.array(z.object({\n pattern: z.string(),\n channel: z.string().optional(),\n })),\n allowedContacts: z.array(z.string()).optional(),\n agent: z.string(),\n prompt: z.string(),\n schedule: z.string().optional(),\n timezone: z.string().default(\"UTC\"),\n notify: z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n }).optional(),\n})\n\nconst meshPeerSchema = z.object({\n url: z.string(),\n name: z.string(),\n token: z.string().optional(),\n})\n\nconst meshConfigSchema = z.object({\n enabled: z.boolean().default(false),\n peers: z.array(meshPeerSchema).default([]),\n discovery: z.enum([\"static\", \"mdns\"]).default(\"static\"),\n healthCheck: z.object({\n interval: z.number().default(60),\n timeout: z.number().default(10),\n }).default({}),\n})\n\n/** Intent Knowledge Graph — fixed-axis, LLM-proposed taxonomy used by the\n * Intent layer in context.ts and (eventually) wiki retrieval. Off by default\n * so existing installs see no change. */\nconst graphConfigSchema = z.object({\n enabled: z.boolean().default(false),\n /** Where the schema/nodes/classifications live. Relative to cwd. */\n baseDir: z.string().default(\".agentx/graph\"),\n /** Which agent makes LLM classification proposals. Falls back to\n * `dashboard.draftAgent` at call sites if unset. */\n draftAgent: z.string().optional(),\n /** Agent used by `agentx graph review` to triage pending classifications.\n * Should be an agent with the wiki skill so it can call `wiki query` for\n * context before deciding approve/reject. Falls back to draftAgent. */\n reviewAgent: z.string().optional(),\n /** Structural auto-approval policy. Evaluated against each classification\n * independently of `autoApproveConfidence`; either triggers approval.\n * - \"strict\" : never approve via structure — every classification\n * waits for human review\n * - \"extend-leaves\" : auto-approve when the proposed path (a) reuses only\n * existing nodes, or (b) adds exactly one NEW node at\n * the deepest level. Structural changes (new mid-path\n * node, new root) still queue for review. Default.\n * - \"any\" : auto-approve every classification regardless of\n * structural change\n */\n autoApproveStructure: z.enum([\"strict\", \"extend-leaves\", \"any\"]).default(\"extend-leaves\"),\n /** Minimum classifier confidence (0..1) to auto-approve. OR'd with the\n * structural policy — either hitting the threshold lets the classification\n * bypass the pending queue. 1.0 (default) disables this knob, so approval\n * is driven by `autoApproveStructure` alone. Lower it to e.g. 0.7 if you\n * also want high-confidence structural changes auto-approved. */\n autoApproveConfidence: z.number().min(0).max(1).default(1.0),\n /** Anthropic model id used for the direct classifier call (Phase 2 of\n * classifier-retire). Default haiku — classification is metadata, not\n * work, so we use the cheapest fast model. Override only if Haiku is\n * rate-limited or you want to A/B test. */\n classifierModel: z.string().default(\"claude-haiku-4-5-20251001\"),\n /** Weights for the wiki hybrid retrieval score. Path-ancestry match vs\n * BM25 over article text. Sum need not be 1. */\n retrievalWeights: z.object({\n graph: z.number().min(0).default(0.6),\n bm25: z.number().min(0).default(0.4),\n }).default({}),\n}).default({})\n\nconst notificationsSchema = z.object({\n /** Send notification when task takes longer than this (seconds). 0 = disabled. */\n longTaskThreshold: z.number().default(30),\n /** Where to send notifications */\n destination: z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n }).optional(),\n /** Notify on these events */\n on: z.object({\n taskComplete: z.boolean().default(true),\n taskError: z.boolean().default(true),\n taskQueued: z.boolean().default(false),\n }).default({}),\n}).default({})\n\nexport const daemonConfigSchema = z.object({\n node: z.object({\n id: z.string(),\n name: z.string(),\n bind: z.string().default(\"127.0.0.1:18800\"),\n /** Default agent for voice/API calls that don't specify one (e.g. Siri) */\n defaultAgent: z.string().optional(),\n }),\n providers: z.record(z.string(), providerConfigSchema).default({}),\n agents: z.record(z.string(), agentConfigSchema).default({}),\n channels: channelsConfigSchema.default({}),\n crons: z.record(z.string(), cronJobSchema).default({}),\n services: z.record(z.string(), serviceSchema).default({}),\n notifications: notificationsSchema,\n mesh: meshConfigSchema.default({}),\n business: businessConfigSchema.optional(),\n boards: boardsConfigSchema,\n dashboard: dashboardConfigSchema,\n graph: graphConfigSchema,\n /** Workflow engine — declarative state machines that bind channel events\n * to agents. Off by default; existing installs see no change until\n * flipped. Definitions live under `dir` (default .agentx/workflows/). */\n workflows: z.object({\n enabled: z.boolean().default(false),\n dir: z.string().default(\".agentx/workflows\"),\n /** Controls whether the dashboard exposes the visual editor. \"readonly\"\n * serves the list + run timelines but strips write controls from the\n * page. \"disabled\" hides the tab entirely. */\n editor: z.enum([\"disabled\", \"readonly\", \"edit\"]).default(\"edit\"),\n }).default({}),\n /** Registered inbound webhooks — an inventory the dashboard manages. Each\n * entry binds an (agent, source) pair to an optional signing secret. The\n * actual inbound URL is always POST /webhook/<agentId>/<source>. */\n webhooks: z.array(z.object({\n id: z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, \"webhook id must be lowercase slug\"),\n source: z.enum([\"gitlab\", \"github\", \"sentry\", \"stripe\", \"discord\", \"slack\", \"custom\"]),\n agentId: z.string(),\n secretEnv: z.string().optional(),\n description: z.string().optional(),\n enabled: z.boolean().default(true),\n /** Route to a mesh peer instead of local execution. */\n node: z.string().optional(),\n /** Per-event-type workflow routing. Keys are platform event-type\n * strings — the format the platform's HTTP header uses, optionally\n * joined with the action. Examples:\n * GitHub: \"issues.opened\", \"pull_request.synchronize\",\n * \"push\" (action-less events use the bare type)\n * GitLab: \"Note Hook\", \"Merge Request Hook\"\n * Values are workflow ids registered via `agentx workflow create`.\n * When the inbound event-type matches a key, the named workflow is\n * dispatched; if no key matches, the webhook falls through to\n * `defaultWorkflow` (or the agent itself). Closes the recurring\n * GitHub problem of multiple event types collapsing to a single\n * workflow. */\n triggers: z.record(z.string(), z.string()).optional(),\n /** Workflow id used when no `triggers` entry matches the event-type.\n * Backward-compatible: pre-existing webhooks without `triggers`\n * always hit this path. */\n defaultWorkflow: z.string().optional(),\n })).default([]),\n /** Session cache-reuse policy. Controls when we drop a Claude `--resume`\n * session and rebuild the prompt from scratch.\n * - `staleMinutes`: idle timeout before rotation. Longer is cheaper at\n * Opus rates but lets a session accumulate tool-result bloat across an\n * all-day chat.\n * - `maxTurnsPerSession`: hard cap on turns per Claude session. Prevents\n * unbounded `--resume` growth. When hit, next turn starts fresh with\n * the compacted summary + recent-messages context.\n * - `tierTwoThresholdTokens`: if the prior turn's (input + cacheRead +\n * cacheCreate) exceeded this, rotate proactively. Claude bills tier-2\n * at 1.5× above 200K total input, so rotating before that threshold\n * undercuts the multiplier. */\n session: z.object({\n staleMinutes: z.number().int().min(1).max(1440).default(45),\n maxTurnsPerSession: z.number().int().min(2).max(200).default(15),\n tierTwoThresholdTokens: z.number().int().min(50_000).max(200_000).default(195_000),\n /** Context assembly strategy:\n * - \"layered\" (default): the classic stacked layers — session history,\n * memory, cross-chat, wiki hint all appended every turn.\n * - \"planner\": a Haiku pre-call decides what to retrieve before the\n * main agent runs. Always-on core is kept (channel/scope/landscape/\n * identity/intent + last 3 turns verbatim); history blob, wiki hint,\n * and cross-chat are replaced by planner-selected bundles. Per-task\n * overrides via AgentTask.contextStrategy for A/B benchmarking. */\n contextStrategy: z.enum([\"layered\", \"planner\"]).default(\"layered\"),\n /** Soft dispatch budget for claude-code-tier agents (shared OAuth pools\n * one counter across the fleet). Warms at 80% of the cap; short-circuits\n * cold dispatches (no warm Claude session) when exceeded. Warm sessions\n * are always allowed through. Defaults sized for Max 5×:\n * - maxClaudeCodeDispatchesPerHour: 80 (headroom under typical hourly cap)\n * - maxClaudeCodeDispatchesPer5h: 180 (headroom under ~225/5h tier)\n * Raise for Max 20×, lower if your workload stays under the cap naturally. */\n maxClaudeCodeDispatchesPerHour: z.number().int().min(1).max(10_000).default(80),\n maxClaudeCodeDispatchesPer5h: z.number().int().min(1).max(50_000).default(180),\n }).default({}),\n /** Move B — JS/TS plugins. Each entry is an installed npm package name\n * (e.g. `agentx-plugin-mattermost` or `@noqta/plugin-mattermost`); the\n * loader does a dynamic `import(name)` at boot, validates the manifest,\n * and calls plugin.setup(ctx). Plugins can register channel adapters\n * via ctx.addChannel() and subscribe to bus events via ctx.on(). Empty\n * default — installs that don't list plugins are unaffected. Plugin\n * authoring guide: docs/architecture/plugins.md. */\n plugins: z.array(z.string()).default([]),\n})\n\nexport type DaemonConfig = z.infer<typeof daemonConfigSchema>\nexport type AgentDef = z.infer<typeof agentConfigSchema>\nexport type CronJobDef = z.infer<typeof cronJobSchema>\nexport type MeshPeer = z.infer<typeof meshPeerSchema>\n\n/**\n * Expand environment variables in strings: ${VAR_NAME} -> process.env.VAR_NAME\n */\nexport function expandEnvVars(obj: unknown): unknown {\n if (typeof obj === \"string\") {\n return obj.replace(/\\$\\{(\\w+)\\}/g, (_match, name) => process.env[name] || \"\")\n }\n if (Array.isArray(obj)) {\n return obj.map(expandEnvVars)\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = expandEnvVars(value)\n }\n return result\n }\n return obj\n}\n\n/**\n * Load daemon config from agentx.json, with env var expansion and validation.\n */\nexport function loadDaemonConfig(configPath?: string): DaemonConfig {\n const paths = configPath\n ? [configPath]\n : [\n resolve(process.cwd(), \"agentx.json\"),\n resolve(process.cwd(), \".agentx/config.json\"),\n ]\n\n // Load .env from same directory as config search\n loadDotEnv(process.cwd())\n\n let raw: string | undefined\n let foundPath: string | undefined\n\n for (const p of paths) {\n if (existsSync(p)) {\n raw = readFileSync(p, \"utf-8\")\n foundPath = p\n break\n }\n }\n\n if (!raw || !foundPath) {\n throw new Error(\n `No config found. Create agentx.json or .agentx/config.json\\n` +\n `Searched: ${paths.join(\", \")}`\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (e: any) {\n throw new Error(`Invalid JSON in ${foundPath}: ${e.message}`)\n }\n\n // Expand environment variables\n const expanded = expandEnvVars(parsed)\n\n // Validate\n const result = daemonConfigSchema.safeParse(expanded)\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\")\n throw new Error(`Config validation failed (${foundPath}):\\n${issues}`)\n }\n\n return result.data\n}\n\n/**\n * Validate agent workspace directories exist and have .claude/ setup.\n */\nexport function validateWorkspaces(config: DaemonConfig): string[] {\n const warnings: string[] = []\n\n for (const [id, agent] of Object.entries(config.agents)) {\n if (!existsSync(agent.workspace)) {\n warnings.push(`Agent \"${id}\": workspace not found at ${agent.workspace}`)\n continue\n }\n\n if (agent.tier === \"claude-code\") {\n const claudeDir = resolve(agent.workspace, \".claude\")\n if (!existsSync(claudeDir)) {\n warnings.push(\n `Agent \"${id}\": no .claude/ directory in workspace ${agent.workspace}. ` +\n `Claude Code native features (hooks, MCP, skills) won't be available.`\n )\n }\n }\n\n // Check provider availability\n const providerName = agent.provider || \"claude\"\n const providerConfig = config.providers[providerName]\n if (agent.tier !== \"claude-code\" && (!providerConfig || !providerConfig.apiKey)) {\n warnings.push(\n `Agent \"${id}\": provider \"${providerName}\" has no API key configured. ` +\n `Set providers.${providerName}.apiKey in config or use tier \"claude-code\" for subscription.`\n )\n }\n }\n\n // Validate cron agent bindings\n for (const [id, cron] of Object.entries(config.crons)) {\n if (!config.agents[cron.agent]) {\n warnings.push(`Cron \"${id}\": references unknown agent \"${cron.agent}\"`)\n }\n }\n\n // Validate channel agent bindings\n if (config.channels.telegram.enabled) {\n for (const [name, account] of Object.entries(config.channels.telegram.accounts)) {\n if (!config.agents[account.agentBinding]) {\n warnings.push(\n `Telegram account \"${name}\": references unknown agent \"${account.agentBinding}\"`\n )\n }\n }\n }\n\n return warnings\n}\n","import { z } from \"zod\"\n\n// --- Business layer configuration schema ---\n// Optional top-level `business` key in agentx.json.\n// When enabled, AgentX simulates a team with roles, schedules, and a work pool.\n\nconst dayEnum = z.enum([\"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\", \"sun\"])\n\nconst scheduleSchema = z.object({\n days: z.array(dayEnum).default([\"mon\", \"tue\", \"wed\", \"thu\", \"fri\"]),\n start: z.string().regex(/^\\d{2}:\\d{2}$/), // \"09:00\"\n end: z.string().regex(/^\\d{2}:\\d{2}$/), // \"17:00\"\n lunch: z.object({\n start: z.string().regex(/^\\d{2}:\\d{2}$/),\n end: z.string().regex(/^\\d{2}:\\d{2}$/),\n }).optional(),\n})\n\nconst roleSchema = z.object({\n title: z.string(),\n responsibilities: z.array(z.string()).default([]),\n sopPath: z.string().optional(),\n kpis: z.array(z.string()).default([]),\n})\n\nconst orgEntrySchema = z.object({\n role: z.string(),\n reportsTo: z.string().optional(),\n schedule: scheduleSchema,\n utilizationTarget: z.number().min(0).max(1).default(0.8),\n})\n\nconst channelRefSchema = z.object({\n channel: z.string(),\n chatId: z.string(),\n accountId: z.string().optional(),\n})\n\nconst workSourceSchema = z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"backlog\"),\n path: z.string().default(\".agentx/backlog.md\"),\n }),\n z.object({\n type: z.literal(\"gitlab\"),\n projects: z.array(z.string()).default([]), // empty = all configured gitlab projects\n }),\n z.object({\n type: z.literal(\"wiki\"),\n path: z.string(),\n glob: z.string().default(\"**/*.md\"),\n }),\n])\n\n/** Per-project metadata. Phase 3 of the architectural rescue —\n * `pm` is the agentId responsible for gating dispatches on this\n * project. Optional; when null, dispatches don't go through a PM\n * gate. The wiring lives in `decideAndCommit` (gated by a flag,\n * separate commit). */\nconst projectSchema = z.object({\n /** Project identifier. For GitLab/GitHub: \"owner/repo\". For\n * noqta-internal projects: a stable string. */\n id: z.string(),\n /** AgentId of the PM for this project. Looked up by\n * `Organization.pmFor(project)` at dispatch time. */\n pm: z.string().optional(),\n /** Optional client this project belongs to. Used by the activity\n * graph to attribute non-projected events (Telegram chats, free-\n * text WhatsApp, cron jobs) to the right client. Falls back to\n * the leading segment of `id` (split on \"/\") when unset. */\n client: z.string().optional(),\n})\n\n/** Maps a chat or sender to a client/project so free-text channels\n * (telegram/whatsapp/slack/discord/email) get attributed correctly\n * in the activity graph. Without this, every chat lands in the\n * fallback \"internal\" bucket — the operator can't tell which client\n * the agent was helping.\n *\n * Match priority: chatId > username > channel-default. First match\n * wins. All fields except `client` are optional; `project` falls\n * back to `${client}/_chat` when unset. */\nconst contactMapSchema = z.object({\n /** Channel kind, e.g. \"telegram\", \"whatsapp\", \"slack\", \"discord\". */\n channel: z.string().optional(),\n /** Native chat id (\"-100...\" for tg groups, JID for WhatsApp). */\n chatId: z.string().optional(),\n /** Sender username/handle as exposed by the channel adapter. */\n username: z.string().optional(),\n /** Numeric sender id when username is unstable. */\n senderId: z.string().optional(),\n /** Client this contact belongs to. Required. */\n client: z.string(),\n /** Project this contact's traffic should attribute to. Defaults to\n * `${client}/_chat`. */\n project: z.string().optional(),\n /** Display name override for the initiator pill. */\n displayName: z.string().optional(),\n})\n\nexport const businessConfigSchema = z.object({\n enabled: z.boolean().default(false),\n timezone: z.string().default(\"UTC\"),\n mainChannel: channelRefSchema,\n workSource: workSourceSchema,\n roles: z.record(z.string(), roleSchema).default({}),\n orgChart: z.record(z.string(), orgEntrySchema).default({}),\n /** Per-project metadata. Phase 3 — defaults to empty array, so\n * existing agentx.json files validate without modification. */\n projects: z.array(projectSchema).default([]),\n /** Maps free-text chat senders to a client/project so the activity\n * graph can attribute Telegram/WhatsApp/etc. to the right client\n * instead of the catch-all \"internal\" bucket. */\n contactMap: z.array(contactMapSchema).default([]),\n /** Work-tick cadence in minutes during business hours. Default 15. */\n workTickMinutes: z.number().int().min(1).max(60).default(15),\n /** Max queue depth for an idle agent before skipping the work tick (avoids piling up). */\n idleQueueThreshold: z.number().int().min(0).default(0),\n})\n\nexport type BusinessConfig = z.infer<typeof businessConfigSchema>\nexport type BusinessRole = z.infer<typeof roleSchema>\nexport type BusinessOrgEntry = z.infer<typeof orgEntrySchema>\nexport type BusinessSchedule = z.infer<typeof scheduleSchema>\nexport type BusinessWorkSource = z.infer<typeof workSourceSchema>\nexport type BusinessChannelRef = z.infer<typeof channelRefSchema>\nexport type BusinessProject = z.infer<typeof projectSchema>\n","import { z } from \"zod\"\n\n// --- Kanban board configuration ---\n//\n// Top-level optional keys in agentx.json, independent of the business layer:\n//\n// boards: array of kanban boards, each backed by a WorkSource\n// dashboard: HTTP server controlling whether the board UI serves\n//\n// Both default to empty / disabled so existing installs see no change.\n\nexport const boardLabelSchema = z.object({\n /** Label name — used verbatim as the GitLab label string. */\n name: z.string(),\n color: z.string().regex(/^#[0-9a-f]{6}$/i).default(\"#6366f1\"),\n description: z.string().optional(),\n})\n\n/**\n * A board column. `kind` controls what issues land here and how drag-to-this-column\n * mutates the issue:\n *\n * - \"open-backlog\": opened issues that have NO scoped label matching `scopedPrefix`\n * (default \"Status\"). Dragging INTO this column removes any existing scoped label.\n * - \"scoped-label\": opened issues carrying `scopedLabel` (e.g. \"Status::Doing\").\n * Scoped labels are mutually exclusive by prefix, so adding the new one lets\n * GitLab auto-remove the old one (Premium), but we still explicitly strip the\n * previous column's label for compatibility.\n * - \"closed\": all closed issues. Dragging here closes the issue; dragging away\n * reopens it.\n * - \"label\" (default, legacy): opened issues with `mapsToLabel`; flat add/remove.\n */\nexport const boardColumnSchema = z.object({\n id: z.string().regex(/^[a-z0-9_-]+$/, \"column id must be lowercase slug\"),\n title: z.string(),\n kind: z.enum([\"open-backlog\", \"scoped-label\", \"closed\", \"label\"]).default(\"label\"),\n /** For kind=\"label\": label written on entry, removed on exit. */\n mapsToLabel: z.string().optional(),\n /** For kind=\"scoped-label\": the full scoped label (e.g. \"Status::Doing\"). */\n scopedLabel: z.string().optional(),\n /** For kind=\"open-backlog\": scoped prefix that marks \"claimed\" cards to exclude. */\n scopedPrefix: z.string().default(\"Status\"),\n /** CSS color or hex — shown as the column's top accent bar. */\n accent: z.string().optional(),\n})\nexport type BoardColumnId = string\n\nexport const boardReconcileSchema = z.object({\n enabled: z.boolean().default(true),\n staleDoingMinutes: z.number().int().min(1).default(45),\n respectLunchBreak: z.boolean().default(true),\n respectSchedule: z.boolean().default(true),\n /** Badge only for P1/P3; auto-notify + auto-demote deferred. */\n action: z.enum([\"badge\", \"notify\"]).default(\"badge\"),\n})\n\nexport const boardSourceSchema = z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"gitlab\"),\n /** GitLab project paths (e.g. \"mtgl/mtgl-system-v2\"). At least one required. */\n projects: z.array(z.string()).min(1),\n }),\n // future: backlog, wiki\n])\n\n/**\n * GitLab-style default board — six columns:\n * Open (backlog) → To Do → Doing → On Hold → Review → Closed\n *\n * Drives the `Status::*` scoped label taxonomy. Moving a card adds/removes the\n * column's scoped label; moving INTO Closed closes the issue; moving OUT of\n * Closed reopens it; moving INTO Open strips any Status::* label.\n */\nexport const DEFAULT_COLUMNS: Array<z.infer<typeof boardColumnSchema>> = [\n { id: \"open\", title: \"Open\", kind: \"open-backlog\", scopedPrefix: \"Status\", accent: \"#6b7280\" },\n { id: \"todo\", title: \"To Do\", kind: \"scoped-label\", scopedLabel: \"Status::To Do\", scopedPrefix: \"Status\", accent: \"#fb7a35\" },\n { id: \"doing\", title: \"Doing\", kind: \"scoped-label\", scopedLabel: \"Status::Doing\", scopedPrefix: \"Status\", accent: \"#22c55e\" },\n { id: \"onhold\", title: \"On Hold\", kind: \"scoped-label\", scopedLabel: \"Status::On Hold\", scopedPrefix: \"Status\", accent: \"#f59e0b\" },\n { id: \"review\", title: \"Review\", kind: \"scoped-label\", scopedLabel: \"Status::Review\", scopedPrefix: \"Status\", accent: \"#3b82f6\" },\n { id: \"closed\", title: \"Closed\", kind: \"closed\", scopedPrefix: \"Status\", accent: \"#6b7280\" },\n]\n\nexport const boardSchema = z.object({\n id: z.string().regex(/^[a-z0-9_-]+$/, \"board id must be lowercase slug\"),\n name: z.string(),\n source: boardSourceSchema,\n /** If set, ANDed into every listAll query and shown as a baseline chip in the UI. */\n primaryToolLabel: z.string().optional(),\n labels: z.array(boardLabelSchema).default([]),\n columns: z.array(boardColumnSchema).min(1).default(DEFAULT_COLUMNS),\n timeRangeDays: z.number().int().min(1).max(365).default(30),\n /** Maximum closed-issue window (days) shown in the Closed column. */\n closedWindowDays: z.number().int().min(1).max(365).default(30),\n reconciliation: boardReconcileSchema.default({}),\n})\n\nexport const boardsConfigSchema = z.array(boardSchema).default([])\n\nexport const dashboardDaemonSchema = z.object({\n name: z.string(),\n /** Daemon HTTP URL (the one serving /agents, /health, /task). */\n url: z.string(),\n /** Optional board-dashboard URL if we want to proxy admin writes to this\n * peer. Defaults to `url` with port :18800/:19900 rewritten to :4202,\n * matching our convention that the dashboard runs alongside the daemon. */\n dashboardUrl: z.string().optional(),\n /** Bearer for the daemon's HTTP API (live snapshot, task stream). */\n token: z.string().optional(),\n /** Bearer for the peer's dashboard when proxying admin calls. Defaults to\n * `token` when unset. */\n dashboardToken: z.string().optional(),\n})\n\nexport const dashboardConfigSchema = z.object({\n enabled: z.boolean().default(false),\n port: z.number().int().default(4202),\n bind: z.string().default(\"127.0.0.1\"),\n /** Opt-in bearer token for writes. If unset, writes are unauthenticated. */\n token: z.string().optional(),\n /** Primary daemon the dashboard talks to for live view + A2A. */\n daemonUrl: z.string().default(\"http://localhost:18800\"),\n /** Additional daemons to poll (beyond peers discovered via primary's /mesh). */\n daemons: z.array(dashboardDaemonSchema).default([]),\n /** Default agent used for AI-assisted drafting in the create-issue flow. */\n draftAgent: z.string().optional(),\n}).default({})\n\nexport type BoardConfig = z.infer<typeof boardSchema>\nexport type BoardLabel = z.infer<typeof boardLabelSchema>\nexport type BoardColumn = z.infer<typeof boardColumnSchema>\nexport type BoardSource = z.infer<typeof boardSourceSchema>\nexport type BoardReconcile = z.infer<typeof boardReconcileSchema>\nexport type DashboardConfig = z.infer<typeof dashboardConfigSchema>\n\n/**\n * Decide which column an item lives in. Walks columns in order; the first\n * matching column wins. Items with `state: \"closed\"` always land in the\n * first `kind: \"closed\"` column; otherwise the first `scoped-label` whose\n * `scopedLabel` is present wins; otherwise the first `label` column whose\n * `mapsToLabel` is present; otherwise the first `open-backlog` column.\n *\n * Returns `null` when the item carries a scoped label matching an\n * `open-backlog` column's `scopedPrefix` but no `scoped-label` column claims\n * it — those are off-board workflow states (e.g. `Status::Done` when no\n * Done column exists) and must not leak into the backlog.\n */\nexport function deriveStage(\n item: { state?: \"opened\" | \"closed\"; labels?: string[] },\n columns: BoardColumn[],\n): BoardColumnId | null {\n const set = new Set(item.labels || [])\n if (item.state === \"closed\") {\n const closed = columns.find((c) => c.kind === \"closed\")\n if (closed) return closed.id\n }\n for (const c of columns) {\n if (c.kind === \"scoped-label\" && c.scopedLabel && set.has(c.scopedLabel)) return c.id\n }\n for (const c of columns) {\n if (c.kind === \"label\" && c.mapsToLabel && set.has(c.mapsToLabel)) return c.id\n }\n const backlog = columns.find((c) => c.kind === \"open-backlog\")\n if (backlog) {\n // If the item has any scoped-prefix label, it's not an open-backlog candidate.\n const prefix = backlog.scopedPrefix + \"::\"\n const hasScoped = [...set].some((l) => l.startsWith(prefix))\n return hasScoped ? null : backlog.id\n }\n return columns[0]?.id ?? null\n}\n\n/**\n * Describes how to mutate an issue to move it between two columns.\n * `closeIssue` / `reopen` take precedence over label mutations on the same action.\n */\nexport interface ColumnTransition {\n addLabels?: string[]\n removeLabels?: string[]\n closeIssue?: boolean\n reopen?: boolean\n}\n\nexport function transitionDiff(\n fromColumn: BoardColumn | undefined,\n toColumn: BoardColumn,\n): ColumnTransition {\n const add: string[] = []\n const remove: string[] = []\n let closeIssue = false\n let reopen = false\n\n // Handle target column.\n if (toColumn.kind === \"scoped-label\" && toColumn.scopedLabel) {\n add.push(toColumn.scopedLabel)\n } else if (toColumn.kind === \"label\" && toColumn.mapsToLabel) {\n add.push(toColumn.mapsToLabel)\n } else if (toColumn.kind === \"closed\") {\n closeIssue = true\n }\n\n // Handle source column — strip its label; if it was \"closed\", reopen.\n if (fromColumn) {\n if (fromColumn.kind === \"scoped-label\" && fromColumn.scopedLabel) {\n remove.push(fromColumn.scopedLabel)\n } else if (fromColumn.kind === \"label\" && fromColumn.mapsToLabel) {\n remove.push(fromColumn.mapsToLabel)\n } else if (fromColumn.kind === \"closed\" && toColumn.kind !== \"closed\") {\n reopen = true\n }\n }\n\n return {\n addLabels: add.length ? add : undefined,\n removeLabels: remove.length ? remove : undefined,\n closeIssue: closeIssue || undefined,\n reopen: reopen || undefined,\n }\n}\n"],"mappings":"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","names":["z","readFileSync","existsSync","resolve","z","dayEnum","scheduleSchema","roleSchema","orgEntrySchema","channelRefSchema","workSourceSchema","projectSchema","contactMapSchema","businessConfigSchema","z","boardLabelSchema","boardColumnSchema","boardReconcileSchema","boardSourceSchema","DEFAULT_COLUMNS","boardSchema","boardsConfigSchema","dashboardDaemonSchema","dashboardConfigSchema","deriveStage","item","columns","set","closed","c","backlog","prefix","l","transitionDiff","fromColumn","toColumn","add","remove","closeIssue","reopen","loadDotEnv","dir","envPath","resolve","existsSync","content","readFileSync","line","trimmed","eqIdx","key","value","providerConfigSchema","z","mcpServerSchema","agentConfigSchema","telegramAccountSchema","channelsConfigSchema","cronJobSchema","v","serviceSchema","meshPeerSchema","meshConfigSchema","graphConfigSchema","notificationsSchema","daemonConfigSchema","businessConfigSchema","boardsConfigSchema","dashboardConfigSchema","expandEnvVars","obj","_match","name","result","loadDaemonConfig","configPath","paths","raw","foundPath","p","parsed","e","expanded","issues","i","validateWorkspaces","config","warnings","id","agent","claudeDir","providerName","providerConfig","cron","account"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a,b as t}from"./chunk-
|
|
1
|
+
import{a as o,b as t}from"./chunk-CNNLREHD.js";function s(e={}){let a=`<div class="ax-inbox__root" data-actor="${t(e.actor??"")}">
|
|
2
2
|
<aside class="ax-inbox__list">
|
|
3
3
|
<header>
|
|
4
4
|
<h2>Inbox</h2>
|
|
@@ -17,12 +17,24 @@ import{a,b as t}from"./chunk-6PPIFDOQ.js";import"./chunk-HPDHNIA3.js";function s
|
|
|
17
17
|
<section class="ax-inbox__detail">
|
|
18
18
|
<header id="inbox-detail-head">
|
|
19
19
|
<span class="hint">Select a task to open its form.</span>
|
|
20
|
+
<button id="inbox-history-toggle" type="button" style="margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer">Show completed history</button>
|
|
20
21
|
</header>
|
|
21
22
|
<div id="inbox-detail-body" class="ax-inbox__detail-body"></div>
|
|
23
|
+
<div id="inbox-history" class="ax-inbox__detail-body" style="display:none;padding:14px 16px">
|
|
24
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
|
25
|
+
<h3 style="margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600">Completed user-tasks (most recent first)</h3>
|
|
26
|
+
<span style="font-size:11px;color:var(--ax-muted)">SLA breaches highlighted in red</span>
|
|
27
|
+
</div>
|
|
28
|
+
<table style="width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace">
|
|
29
|
+
<thead><tr style="text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px"><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">submitted</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">workflow</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">title</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">by</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">duration</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">sla</th></tr></thead>
|
|
30
|
+
<tbody id="inbox-history-rows"></tbody>
|
|
31
|
+
</table>
|
|
32
|
+
<p id="inbox-history-empty" style="font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none">no completed user-tasks yet</p>
|
|
33
|
+
</div>
|
|
22
34
|
</section>
|
|
23
35
|
</div>
|
|
24
36
|
|
|
25
|
-
<div id="inbox-toast" class="ax-inbox__toast" hidden></div>`;return
|
|
37
|
+
<div id="inbox-toast" class="ax-inbox__toast" hidden></div>`;return o({title:"AgentX \xB7 Inbox",activeTab:"workflows",subtitle:"Inbox",peers:e.peers,currentPeerId:e.currentPeerId,body:a,css:r,scripts:`<script>${i}</script>`})}var r=`
|
|
26
38
|
.ax-inbox__root {
|
|
27
39
|
display: grid;
|
|
28
40
|
grid-template-columns: 340px 1fr;
|
|
@@ -308,8 +320,61 @@ import{a,b as t}from"./chunk-6PPIFDOQ.js";import"./chunk-HPDHNIA3.js";function s
|
|
|
308
320
|
});
|
|
309
321
|
$('#inbox-refresh').addEventListener('click', fetchTasks);
|
|
310
322
|
|
|
323
|
+
// History toggle \u2014 surfaces the SLA-aware completion log.
|
|
324
|
+
let historyOpen = false;
|
|
325
|
+
function fmtDuration(ms) {
|
|
326
|
+
if (ms == null) return '\u2014';
|
|
327
|
+
const s = Math.floor(ms / 1000);
|
|
328
|
+
if (s < 60) return s + 's';
|
|
329
|
+
const m = Math.floor(s / 60);
|
|
330
|
+
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
|
331
|
+
const h = Math.floor(m / 60);
|
|
332
|
+
return h + 'h ' + (m % 60) + 'm';
|
|
333
|
+
}
|
|
334
|
+
function fmtTs(s) {
|
|
335
|
+
if (!s) return '\u2014';
|
|
336
|
+
return s.replace('T', ' ').slice(0, 16);
|
|
337
|
+
}
|
|
338
|
+
async function loadHistory() {
|
|
339
|
+
try {
|
|
340
|
+
const r = await fetch('/api/workflows/tasks/history?limit=100');
|
|
341
|
+
const data = await r.json();
|
|
342
|
+
const rows = data.rows || [];
|
|
343
|
+
const tbody = $('#inbox-history-rows');
|
|
344
|
+
const empty = $('#inbox-history-empty');
|
|
345
|
+
if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }
|
|
346
|
+
empty.style.display = 'none';
|
|
347
|
+
tbody.innerHTML = rows.map(function(r){
|
|
348
|
+
const slaCell = r.dueAt
|
|
349
|
+
? (r.breachedSla
|
|
350
|
+
? '<span style="color:var(--ax-err,#e74c3c)">breached</span>'
|
|
351
|
+
: '<span style="color:#2ecc71">on time</span>')
|
|
352
|
+
: '<span style="color:var(--ax-muted)">\u2014</span>';
|
|
353
|
+
const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';
|
|
354
|
+
return '<tr style="' + rowStyle + '">' +
|
|
355
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +
|
|
356
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.workflowId || '') + '</td>' +
|
|
357
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +
|
|
358
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.submittedBy || '') + '</td>' +
|
|
359
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +
|
|
360
|
+
'<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + slaCell + '</td>' +
|
|
361
|
+
'</tr>';
|
|
362
|
+
}).join('');
|
|
363
|
+
} catch (e) {
|
|
364
|
+
$('#inbox-history-empty').textContent = 'failed to load: ' + e.message;
|
|
365
|
+
$('#inbox-history-empty').style.display = '';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
$('#inbox-history-toggle').addEventListener('click', () => {
|
|
369
|
+
historyOpen = !historyOpen;
|
|
370
|
+
$('#inbox-history').style.display = historyOpen ? 'block' : 'none';
|
|
371
|
+
$('#inbox-detail-body').style.display = historyOpen ? 'none' : '';
|
|
372
|
+
$('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';
|
|
373
|
+
if (historyOpen) loadHistory();
|
|
374
|
+
});
|
|
375
|
+
|
|
311
376
|
fetchTasks();
|
|
312
377
|
setInterval(fetchTasks, 10000);
|
|
313
378
|
})();
|
|
314
|
-
`;export{s as
|
|
315
|
-
//# sourceMappingURL=
|
|
379
|
+
`;export{s as a};
|
|
380
|
+
//# sourceMappingURL=chunk-MTRWXRFF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/ui/pages/inbox.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Inbox page ---\n//\n// Per-actor (or global) list of open user-tasks with an inline form\n// renderer. Reads /api/workflows/tasks; submits via\n// POST /api/workflows/tasks/:id/submit.\n//\n// Scope is intentionally tight for Phase 1:\n// - text / long-text / number / boolean / select / multi-select / date\n// - primary + optional secondary action buttons\n// - required-field validation is server-side; the UI just relays errors\n//\n// No channel-adapter coupling here — the form UI is the same whether the\n// renderer ran on Telegram or not.\n\nexport interface InboxPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n /** Optional actor id to pre-filter. When unset, the page shows a prompt\n * asking the user to pick their actor id. */\n actor?: string\n}\n\nexport function renderInboxPage(opts: InboxPageOpts = {}): string {\n const body = `<div class=\"ax-inbox__root\" data-actor=\"${esc(opts.actor ?? \"\")}\">\n <aside class=\"ax-inbox__list\">\n <header>\n <h2>Inbox</h2>\n <button id=\"inbox-refresh\" class=\"ax-inbox__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-inbox__actor\">\n <label>Actor <input id=\"inbox-actor\" type=\"text\" placeholder=\"actor:<id>\" value=\"${esc(opts.actor ?? \"\")}\" autocomplete=\"off\" /></label>\n </div>\n <ul id=\"inbox-tasks\" class=\"ax-inbox__cards\" aria-live=\"polite\"></ul>\n <div id=\"inbox-empty\" class=\"ax-inbox__empty\" hidden>\n <p>No open tasks.</p>\n <p class=\"hint\">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>\n </div>\n </aside>\n\n <section class=\"ax-inbox__detail\">\n <header id=\"inbox-detail-head\">\n <span class=\"hint\">Select a task to open its form.</span>\n <button id=\"inbox-history-toggle\" type=\"button\" style=\"margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer\">Show completed history</button>\n </header>\n <div id=\"inbox-detail-body\" class=\"ax-inbox__detail-body\"></div>\n <div id=\"inbox-history\" class=\"ax-inbox__detail-body\" style=\"display:none;padding:14px 16px\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:10px\">\n <h3 style=\"margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600\">Completed user-tasks (most recent first)</h3>\n <span style=\"font-size:11px;color:var(--ax-muted)\">SLA breaches highlighted in red</span>\n </div>\n <table style=\"width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace\">\n <thead><tr style=\"text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px\"><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">submitted</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">workflow</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">title</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">by</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">duration</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">sla</th></tr></thead>\n <tbody id=\"inbox-history-rows\"></tbody>\n </table>\n <p id=\"inbox-history-empty\" style=\"font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none\">no completed user-tasks yet</p>\n </div>\n </section>\n</div>\n\n<div id=\"inbox-toast\" class=\"ax-inbox__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Inbox\",\n activeTab: \"workflows\",\n subtitle: \"Inbox\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: INBOX_PAGE_CSS,\n scripts: `<script>${INBOX_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst INBOX_PAGE_CSS = `\n.ax-inbox__root {\n display: grid;\n grid-template-columns: 340px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-inbox__list {\n border-right: 1px solid var(--ax-border);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.ax-inbox__list > header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-inbox__actor {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-inbox__actor input {\n font: inherit;\n padding: 6px 8px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-inbox__card {\n padding: 10px 12px;\n border-radius: 6px;\n margin-bottom: 4px;\n cursor: pointer;\n border: 1px solid transparent;\n}\n.ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }\n.ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }\n.ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }\n.ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }\n.ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }\n.ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {\n font: inherit;\n padding: 8px 10px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__field textarea { min-height: 100px; resize: vertical; }\n.ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__field-error { font-size: 11px; color: #c0392b; }\n.ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }\n.ax-inbox__actions button {\n font: inherit;\n padding: 8px 14px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n cursor: pointer;\n background: var(--ax-surface-2);\n color: var(--ax-fg);\n}\n.ax-inbox__actions button.is-primary {\n background: #2ecc71; color: white; border-color: #27ae60;\n}\n.ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }\n.ax-inbox__icon {\n background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;\n font-size: 14px; padding: 4px 8px; border-radius: 4px;\n}\n.ax-inbox__icon:hover { background: var(--ax-surface-2); }\n.ax-inbox__toast {\n position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;\n background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;\n font-size: 13px; z-index: 100;\n}\n`\n\n// Deliberately vanilla JS — no framework dep. Fetches task list, renders\n// per-field inputs, POSTs submission.\nconst INBOX_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };\n const toast = (msg) => {\n const el = $('#inbox-toast');\n el.textContent = msg;\n el.hidden = false;\n setTimeout(() => { el.hidden = true; }, 2500);\n };\n\n async function fetchTasks() {\n const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';\n try {\n const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });\n if (!r.ok) { toast('fetch failed: ' + r.status); return; }\n const data = await r.json();\n state.tasks = Array.isArray(data.tasks) ? data.tasks : [];\n renderList();\n if (state.selected && !state.tasks.find(t => t.id === state.selected)) {\n state.selected = null;\n renderDetail();\n }\n } catch (e) { toast('fetch error: ' + e.message); }\n }\n\n function renderList() {\n const ul = $('#inbox-tasks');\n const empty = $('#inbox-empty');\n ul.innerHTML = '';\n if (!state.tasks.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const t of state.tasks) {\n const li = document.createElement('li');\n li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-inbox__card-title\">' + escHtml(t.title) + '</div>' +\n '<div class=\"ax-inbox__card-meta\">' + escHtml(t.assignee) + ' · ' + escHtml(t.workflowId) + '</div>' +\n (t.dueAt ? ('<div class=\"ax-inbox__card-due\">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');\n li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#inbox-detail-head');\n const body = $('#inbox-detail-body');\n const t = state.tasks.find(x => x.id === state.selected);\n if (!t) {\n head.innerHTML = '<span class=\"hint\">Select a task to open its form.</span>';\n body.innerHTML = '';\n return;\n }\n head.innerHTML = '<h2 style=\"margin:0;font-size:15px\">' + escHtml(t.title) + '</h2>' +\n (t.description ? '<p class=\"hint\" style=\"margin:6px 0 0\">' + escHtml(t.description) + '</p>' : '');\n const form = document.createElement('form');\n form.className = 'ax-inbox__form';\n form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });\n for (const field of (t.form.fields || [])) {\n form.appendChild(renderField(field));\n }\n const actions = document.createElement('div');\n actions.className = 'ax-inbox__actions';\n const primary = document.createElement('button');\n primary.type = 'submit';\n primary.className = 'is-primary';\n primary.textContent = t.form.submitLabel || 'Submit';\n actions.appendChild(primary);\n if (t.form.secondaryAction) {\n const secondary = document.createElement('button');\n secondary.type = 'button';\n secondary.textContent = t.form.secondaryAction.label;\n secondary.addEventListener('click', () => submit(t, 'secondary'));\n actions.appendChild(secondary);\n }\n form.appendChild(actions);\n body.innerHTML = '';\n body.appendChild(form);\n }\n\n function renderField(field) {\n const wrap = document.createElement('div');\n wrap.className = 'ax-inbox__field';\n const label = document.createElement('label');\n label.textContent = field.label + (field.required ? ' *' : '');\n wrap.appendChild(label);\n let input;\n switch (field.type) {\n case 'long-text':\n input = document.createElement('textarea');\n break;\n case 'number':\n input = document.createElement('input'); input.type = 'number';\n break;\n case 'boolean':\n input = document.createElement('select');\n for (const v of ['', 'yes', 'no']) {\n const o = document.createElement('option'); o.value = v; o.textContent = v || '—';\n input.appendChild(o);\n }\n break;\n case 'date':\n input = document.createElement('input'); input.type = 'date';\n break;\n case 'select':\n input = document.createElement('select');\n if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '—'; input.appendChild(o); }\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'multi-select':\n input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'file':\n input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';\n break;\n default:\n input = document.createElement('input'); input.type = 'text';\n }\n input.name = field.key;\n if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);\n wrap.appendChild(input);\n if (field.hint) {\n const hint = document.createElement('div');\n hint.className = 'ax-inbox__field-hint';\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n const err = document.createElement('div');\n err.className = 'ax-inbox__field-error';\n err.dataset.for = field.key;\n wrap.appendChild(err);\n return wrap;\n }\n\n async function submit(task, action) {\n const form = $('.ax-inbox__form');\n if (!form) return;\n const values = {};\n for (const field of (task.form.fields || [])) {\n const el = form.querySelector('[name=\"' + field.key + '\"]');\n if (!el) continue;\n if (field.type === 'multi-select') {\n values[field.key] = Array.from(el.selectedOptions).map(o => o.value);\n } else if (field.type === 'boolean') {\n values[field.key] = el.value;\n } else {\n values[field.key] = el.value;\n }\n }\n form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });\n try {\n const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),\n });\n const data = await r.json().catch(() => ({}));\n if (!r.ok) {\n if (data.fieldErrors && Array.isArray(data.fieldErrors)) {\n for (const fe of data.fieldErrors) {\n const node = form.querySelector('[data-for=\"' + fe.field + '\"]');\n if (node) node.textContent = fe.message;\n }\n toast('form has errors');\n } else {\n toast(data.error || 'submit failed');\n }\n return;\n }\n toast('submitted');\n state.selected = null;\n fetchTasks();\n } catch (e) { toast('submit error: ' + e.message); }\n }\n\n function escHtml(s) {\n return String(s).replace(/[&<>\"']/g, c => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''})[c]);\n }\n\n $('#inbox-actor').addEventListener('change', (e) => {\n state.actor = e.target.value.trim();\n state.selected = null;\n const url = new URL(window.location.href);\n if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');\n history.replaceState({}, '', url.toString());\n fetchTasks();\n });\n $('#inbox-refresh').addEventListener('click', fetchTasks);\n\n // History toggle — surfaces the SLA-aware completion log.\n let historyOpen = false;\n function fmtDuration(ms) {\n if (ms == null) return '—';\n const s = Math.floor(ms / 1000);\n if (s < 60) return s + 's';\n const m = Math.floor(s / 60);\n if (m < 60) return m + 'm ' + (s % 60) + 's';\n const h = Math.floor(m / 60);\n return h + 'h ' + (m % 60) + 'm';\n }\n function fmtTs(s) {\n if (!s) return '—';\n return s.replace('T', ' ').slice(0, 16);\n }\n async function loadHistory() {\n try {\n const r = await fetch('/api/workflows/tasks/history?limit=100');\n const data = await r.json();\n const rows = data.rows || [];\n const tbody = $('#inbox-history-rows');\n const empty = $('#inbox-history-empty');\n if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }\n empty.style.display = 'none';\n tbody.innerHTML = rows.map(function(r){\n const slaCell = r.dueAt\n ? (r.breachedSla\n ? '<span style=\"color:var(--ax-err,#e74c3c)\">breached</span>'\n : '<span style=\"color:#2ecc71\">on time</span>')\n : '<span style=\"color:var(--ax-muted)\">—</span>';\n const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';\n return '<tr style=\"' + rowStyle + '\">' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.workflowId || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.submittedBy || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + slaCell + '</td>' +\n '</tr>';\n }).join('');\n } catch (e) {\n $('#inbox-history-empty').textContent = 'failed to load: ' + e.message;\n $('#inbox-history-empty').style.display = '';\n }\n }\n $('#inbox-history-toggle').addEventListener('click', () => {\n historyOpen = !historyOpen;\n $('#inbox-history').style.display = historyOpen ? 'block' : 'none';\n $('#inbox-detail-body').style.display = historyOpen ? 'none' : '';\n $('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';\n if (historyOpen) loadHistory();\n });\n\n fetchTasks();\n setInterval(fetchTasks, 10000);\n})();\n`\n"],"mappings":"+CAwBO,SAASA,EAAgBC,EAAsB,CAAC,EAAW,CAChE,IAAMC,EAAO,2CAA2CC,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yFAOWE,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6DA+B3G,OAAOG,EAAY,CACjB,MAAO,oBACP,UAAW,YACX,SAAU,QACV,MAAOH,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKG,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6FjBC,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderInboxPage","opts","body","esc","renderShell","INBOX_PAGE_CSS","INBOX_PAGE_SCRIPT"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e}from"./chunk-
|
|
1
|
+
import{a as e}from"./chunk-CNNLREHD.js";function i(t={}){let r=`<div class="ax-proc__kpis" id="proc-kpis" hidden></div>
|
|
2
2
|
<div class="ax-proc__root">
|
|
3
3
|
<aside class="ax-proc__list">
|
|
4
4
|
<header>
|
|
@@ -252,5 +252,5 @@ import{a as e}from"./chunk-6PPIFDOQ.js";import"./chunk-HPDHNIA3.js";function i(t
|
|
|
252
252
|
refresh();
|
|
253
253
|
setInterval(refresh, 5000);
|
|
254
254
|
})();
|
|
255
|
-
`;export{i as
|
|
256
|
-
//# sourceMappingURL=
|
|
255
|
+
`;export{i as a};
|
|
256
|
+
//# sourceMappingURL=chunk-NOF4HSGK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon/ui/pages/processes.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Processes dashboard ---\n//\n// Live view over workflow runs with composition-tree rendering and SLA\n// indicators computed from userTasks' dueAt. Reads:\n// GET /api/workflows/runs — list of recent runs\n// GET /api/workflows/tasks — open user tasks\n// GET /api/workflows — workflow definitions (for titles)\n\nexport interface ProcessesPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n}\n\nexport function renderProcessesPage(opts: ProcessesPageOpts = {}): string {\n const body = `<div class=\"ax-proc__kpis\" id=\"proc-kpis\" hidden></div>\n<div class=\"ax-proc__root\">\n <aside class=\"ax-proc__list\">\n <header>\n <h2>Processes</h2>\n <button id=\"proc-refresh\" class=\"ax-proc__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-proc__filter\">\n <label>Status\n <select id=\"proc-status\">\n <option value=\"\">All</option>\n <option value=\"running\">Running</option>\n <option value=\"paused\">Paused</option>\n <option value=\"completed\">Completed</option>\n <option value=\"failed\">Failed</option>\n </select>\n </label>\n </div>\n <ul id=\"proc-runs\" class=\"ax-proc__cards\" aria-live=\"polite\"></ul>\n <div id=\"proc-empty\" class=\"ax-proc__empty\" hidden>\n <p>No runs yet.</p>\n </div>\n </aside>\n\n <section class=\"ax-proc__detail\">\n <header id=\"proc-detail-head\">\n <span class=\"hint\">Select a process to see its composition tree and history.</span>\n </header>\n <div id=\"proc-detail-body\" class=\"ax-proc__detail-body\"></div>\n </section>\n</div>\n\n<div id=\"proc-toast\" class=\"ax-proc__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Processes\",\n activeTab: \"workflows\",\n subtitle: \"Processes\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: PROC_PAGE_CSS,\n scripts: `<script>${PROC_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst PROC_PAGE_CSS = `\n.ax-proc__root {\n display: grid; grid-template-columns: 360px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-proc__list { border-right: 1px solid var(--ax-border); display: flex; flex-direction: column; min-height: 0; }\n.ax-proc__list > header { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-proc__filter { padding: 10px 14px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__filter label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-proc__filter select { font: inherit; padding: 6px 8px; border: 1px solid var(--ax-border); border-radius: 6px; background: var(--ax-bg); color: var(--ax-fg); }\n.ax-proc__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-proc__card { padding: 10px 12px; border-radius: 6px; margin-bottom: 4px; cursor: pointer; border: 1px solid transparent; }\n.ax-proc__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-proc__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-proc__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; display: flex; justify-content: space-between; align-items: center; gap: 6px; }\n.ax-proc__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-proc__status-pill { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; text-transform: uppercase; }\n.ax-proc__status-running { background: #3498db; color: white; }\n.ax-proc__status-paused { background: #f1c40f; color: #333; }\n.ax-proc__status-completed { background: #27ae60; color: white; }\n.ax-proc__status-failed { background: #e74c3c; color: white; }\n.ax-proc__status-canceled { background: #95a5a6; color: white; }\n.ax-proc__sla { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n.ax-proc__sla-green { background: #27ae60; }\n.ax-proc__sla-yellow { background: #f1c40f; }\n.ax-proc__sla-red { background: #e74c3c; }\n.ax-proc__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-proc__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-proc__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-proc__tree { margin: 0 0 16px; font-family: var(--ax-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.6; }\n.ax-proc__tree-row { padding: 2px 0; }\n.ax-proc__tree-depth { display: inline-block; color: var(--ax-muted); }\n.ax-proc__history { border-top: 1px solid var(--ax-border); padding-top: 12px; }\n.ax-proc__history table { width: 100%; border-collapse: collapse; font-size: 12px; }\n.ax-proc__history th, .ax-proc__history td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__icon { background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer; font-size: 14px; padding: 4px 8px; border-radius: 4px; }\n.ax-proc__icon:hover { background: var(--ax-surface-2); }\n.ax-proc__toast { position: fixed; bottom: 16px; right: 16px; padding: 10px 14px; background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px; font-size: 13px; z-index: 100; }\n.ax-proc__kpis {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--ax-border);\n background: var(--ax-surface-2, rgba(127,127,127,0.04));\n}\n.ax-proc__kpi { padding: 6px 10px; border-radius: 6px; background: var(--ax-bg); border: 1px solid var(--ax-border); }\n.ax-proc__kpi-label { font-size: 11px; color: var(--ax-muted); text-transform: uppercase; letter-spacing: 0.04em; }\n.ax-proc__kpi-value { font-size: 20px; font-weight: 600; }\n.ax-proc__kpi-breakdown { font-size: 11px; color: var(--ax-muted); margin-top: 2px; }\n`\n\nconst PROC_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { runs: [], tasks: [], workflows: [], selected: null, filter: '' };\n const toast = (msg) => { const el = $('#proc-toast'); el.textContent = msg; el.hidden = false; setTimeout(() => el.hidden = true, 2500); };\n\n async function refresh() {\n try {\n const [r1, r2, r3, r4] = await Promise.all([\n fetch('/api/workflows/runs?limit=200', { credentials: 'same-origin' }).then(r => r.json()),\n fetch('/api/workflows/tasks', { credentials: 'same-origin' }).then(r => r.json()).catch(() => ({ tasks: [] })),\n fetch('/api/workflows', { credentials: 'same-origin' }).then(r => r.json()),\n fetch('/api/workflows/kpis', { credentials: 'same-origin' }).then(r => r.json()).catch(() => null),\n ]);\n state.runs = Array.isArray(r1.runs) ? r1.runs : [];\n state.tasks = Array.isArray(r2.tasks) ? r2.tasks : [];\n state.workflows = Array.isArray(r3.workflows) ? r3.workflows : [];\n renderKpis(r4);\n renderList();\n if (state.selected) renderDetail();\n } catch (e) { toast('refresh failed: ' + e.message); }\n }\n\n function formatDuration(ms) {\n if (ms == null) return '—';\n if (ms < 1000) return ms + 'ms';\n if (ms < 60000) return Math.round(ms/100)/10 + 's';\n if (ms < 3600000) return Math.round(ms/6000)/10 + 'm';\n return Math.round(ms/360000)/10 + 'h';\n }\n\n function renderKpis(kpis) {\n const el = $('#proc-kpis');\n if (!kpis || !kpis.totals) { el.hidden = true; return; }\n const t = kpis.totals;\n const breachPct = t.slaBreachRate == null ? '—' : Math.round(t.slaBreachRate * 100) + '%';\n const topActors = (kpis.byActor || []).slice(0, 3);\n el.hidden = false;\n el.innerHTML =\n kpiCard('Open tasks', t.openTasks, topActors.map(a => a.actorId + ': ' + a.openTasks).join(' · ')) +\n kpiCard('Completed', t.completedTasks, '') +\n kpiCard('Avg duration', formatDuration(t.avgDurationMs), '') +\n kpiCard('SLA breach', breachPct, t.breachedCount + ' tasks');\n }\n\n function kpiCard(label, value, breakdown) {\n return '<div class=\"ax-proc__kpi\">' +\n '<div class=\"ax-proc__kpi-label\">' + escHtml(label) + '</div>' +\n '<div class=\"ax-proc__kpi-value\">' + escHtml(value) + '</div>' +\n (breakdown ? '<div class=\"ax-proc__kpi-breakdown\">' + escHtml(breakdown) + '</div>' : '') +\n '</div>';\n }\n\n function workflowTitle(id) {\n const wf = state.workflows.find(w => w.id === id);\n return wf?.title || id;\n }\n\n function slaForRun(runId) {\n // Worst SLA across open tasks on this run: red > yellow > green > none.\n const tasks = state.tasks.filter(t => t.runId === runId && t.dueAt);\n if (!tasks.length) return null;\n const now = Date.now();\n let worst = 'green';\n for (const t of tasks) {\n const remaining = Date.parse(t.dueAt) - now;\n const windowMs = Math.max(1, Date.parse(t.dueAt) - Date.parse(t.createdAt));\n if (remaining <= 0) { worst = 'red'; break; }\n if (remaining / windowMs < 0.25 && worst !== 'red') worst = 'yellow';\n }\n return worst;\n }\n\n function renderList() {\n const ul = $('#proc-runs');\n const empty = $('#proc-empty');\n ul.innerHTML = '';\n const filter = state.filter;\n // Only show root runs in the list; children show inside the composition tree.\n const roots = state.runs.filter(r => !r.parentRunId && (!filter || r.status === filter));\n if (!roots.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const r of roots) {\n const sla = slaForRun(r.id);\n const li = document.createElement('li');\n li.className = 'ax-proc__card' + (r.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-proc__card-title\">' +\n '<span>' + escHtml(workflowTitle(r.workflowId)) + '</span>' +\n '<span>' +\n (sla ? '<span class=\"ax-proc__sla ax-proc__sla-' + sla + '\" title=\"SLA\"></span> ' : '') +\n '<span class=\"ax-proc__status-pill ax-proc__status-' + r.status + '\">' + r.status + '</span>' +\n '</span></div>' +\n '<div class=\"ax-proc__card-meta\">' + escHtml(r.id.slice(0, 8)) + ' · depth ' + (r.depth ?? 0) +\n (r.pausedAt ? ' · paused on ' + escHtml(r.pausedAt.kind) : '') + '</div>';\n li.addEventListener('click', () => { state.selected = r.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#proc-detail-head');\n const body = $('#proc-detail-body');\n const root = state.runs.find(r => r.id === state.selected);\n if (!root) { head.innerHTML = '<span class=\"hint\">Select a process.</span>'; body.innerHTML = ''; return; }\n\n head.innerHTML =\n '<h2 style=\"margin:0;font-size:15px\">' + escHtml(workflowTitle(root.workflowId)) + '</h2>' +\n '<p class=\"hint\" style=\"margin:6px 0 0\">Run ' + escHtml(root.id) + ' · status <span class=\"ax-proc__status-pill ax-proc__status-' + root.status + '\">' + root.status + '</span></p>';\n\n // Composition tree — walk all runs whose rootRunId equals root.id.\n const tree = state.runs.filter(r => (r.rootRunId || r.id) === root.id);\n // Build adjacency: parentRunId → children.\n const byParent = new Map();\n for (const r of tree) {\n const key = r.parentRunId || null;\n if (!byParent.has(key)) byParent.set(key, []);\n byParent.get(key).push(r);\n }\n const render = (parentId, depth) => {\n const kids = byParent.get(parentId) || [];\n return kids.map(k => {\n const pad = ' '.repeat(depth);\n const sla = slaForRun(k.id);\n return '<div class=\"ax-proc__tree-row\"><span class=\"ax-proc__tree-depth\">' + pad + '└─ </span>' +\n '<a href=\"#\" data-run=\"' + k.id + '\">' + escHtml(workflowTitle(k.workflowId)) + '</a>' +\n ' <span class=\"ax-proc__status-pill ax-proc__status-' + k.status + '\">' + k.status + '</span>' +\n (sla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + sla + '\" title=\"SLA\"></span>' : '') +\n (k.pausedAt ? ' <span class=\"hint\">paused: ' + escHtml(k.pausedAt.kind) + '</span>' : '') +\n '</div>' + render(k.id, depth + 1);\n }).join('');\n };\n\n const rootSla = slaForRun(root.id);\n const treeHtml =\n '<div class=\"ax-proc__tree\"><div class=\"ax-proc__tree-row\">' +\n escHtml(workflowTitle(root.workflowId)) +\n ' <span class=\"ax-proc__status-pill ax-proc__status-' + root.status + '\">' + root.status + '</span>' +\n (rootSla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + rootSla + '\"></span>' : '') +\n '</div>' + render(root.id, 1) + '</div>';\n\n const openTasks = state.tasks.filter(t => (tree.some(r => r.id === t.runId)));\n const tasksHtml = openTasks.length ? ('<h3 style=\"margin:0 0 6px; font-size: 13px\">Open tasks</h3><ul>' +\n openTasks.map(t => {\n const sla = t.dueAt ? (Date.parse(t.dueAt) < Date.now() ? 'red' : 'green') : null;\n return '<li>' + escHtml(t.title) + ' — assignee ' + escHtml(t.assignee) +\n (t.dueAt ? ' · due ' + new Date(t.dueAt).toLocaleString() : '') +\n (sla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + sla + '\"></span>' : '') +\n '</li>';\n }).join('') + '</ul>') : '';\n\n const historyHtml = '<div class=\"ax-proc__history\"><h3 style=\"margin:0 0 6px; font-size: 13px\">Root run history</h3>' +\n '<table><thead><tr><th>At</th><th>Node</th><th>Status</th><th>Note</th></tr></thead><tbody>' +\n (root.history || []).map(h =>\n '<tr><td>' + escHtml(new Date(h.at).toLocaleTimeString()) + '</td><td>' + escHtml(h.nodeId) + '</td><td>' + escHtml(h.status) + '</td><td>' + escHtml(h.note || '') + '</td></tr>'\n ).join('') +\n '</tbody></table></div>';\n\n body.innerHTML = treeHtml + tasksHtml + historyHtml;\n\n body.querySelectorAll('a[data-run]').forEach(a => {\n a.addEventListener('click', (e) => { e.preventDefault(); state.selected = a.dataset.run; renderList(); renderDetail(); });\n });\n }\n\n function escHtml(s) { return String(s ?? '').replace(/[&<>\"']/g, c => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''})[c]); }\n\n $('#proc-refresh').addEventListener('click', refresh);\n $('#proc-status').addEventListener('change', (e) => { state.filter = e.target.value; renderList(); });\n refresh();\n setInterval(refresh, 5000);\n})();\n`\n"],"mappings":"oEAeO,SAASA,EAAoBC,EAA0B,CAAC,EAAW,CACxE,IAAMC,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2DAkCb,OAAOC,EAAY,CACjB,MAAO,wBACP,UAAW,YACX,SAAU,YACV,MAAOF,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKE,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoDhBC,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderProcessesPage","opts","body","renderShell","PROC_PAGE_CSS","PROC_PAGE_SCRIPT"]}
|
|
1
|
+
{"version":3,"sources":["../src/daemon/ui/pages/processes.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Processes dashboard ---\n//\n// Live view over workflow runs with composition-tree rendering and SLA\n// indicators computed from userTasks' dueAt. Reads:\n// GET /api/workflows/runs — list of recent runs\n// GET /api/workflows/tasks — open user tasks\n// GET /api/workflows — workflow definitions (for titles)\n\nexport interface ProcessesPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n}\n\nexport function renderProcessesPage(opts: ProcessesPageOpts = {}): string {\n const body = `<div class=\"ax-proc__kpis\" id=\"proc-kpis\" hidden></div>\n<div class=\"ax-proc__root\">\n <aside class=\"ax-proc__list\">\n <header>\n <h2>Processes</h2>\n <button id=\"proc-refresh\" class=\"ax-proc__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-proc__filter\">\n <label>Status\n <select id=\"proc-status\">\n <option value=\"\">All</option>\n <option value=\"running\">Running</option>\n <option value=\"paused\">Paused</option>\n <option value=\"completed\">Completed</option>\n <option value=\"failed\">Failed</option>\n </select>\n </label>\n </div>\n <ul id=\"proc-runs\" class=\"ax-proc__cards\" aria-live=\"polite\"></ul>\n <div id=\"proc-empty\" class=\"ax-proc__empty\" hidden>\n <p>No runs yet.</p>\n </div>\n </aside>\n\n <section class=\"ax-proc__detail\">\n <header id=\"proc-detail-head\">\n <span class=\"hint\">Select a process to see its composition tree and history.</span>\n </header>\n <div id=\"proc-detail-body\" class=\"ax-proc__detail-body\"></div>\n </section>\n</div>\n\n<div id=\"proc-toast\" class=\"ax-proc__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Processes\",\n activeTab: \"workflows\",\n subtitle: \"Processes\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: PROC_PAGE_CSS,\n scripts: `<script>${PROC_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst PROC_PAGE_CSS = `\n.ax-proc__root {\n display: grid; grid-template-columns: 360px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-proc__list { border-right: 1px solid var(--ax-border); display: flex; flex-direction: column; min-height: 0; }\n.ax-proc__list > header { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-proc__filter { padding: 10px 14px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__filter label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-proc__filter select { font: inherit; padding: 6px 8px; border: 1px solid var(--ax-border); border-radius: 6px; background: var(--ax-bg); color: var(--ax-fg); }\n.ax-proc__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-proc__card { padding: 10px 12px; border-radius: 6px; margin-bottom: 4px; cursor: pointer; border: 1px solid transparent; }\n.ax-proc__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-proc__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-proc__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; display: flex; justify-content: space-between; align-items: center; gap: 6px; }\n.ax-proc__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-proc__status-pill { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; text-transform: uppercase; }\n.ax-proc__status-running { background: #3498db; color: white; }\n.ax-proc__status-paused { background: #f1c40f; color: #333; }\n.ax-proc__status-completed { background: #27ae60; color: white; }\n.ax-proc__status-failed { background: #e74c3c; color: white; }\n.ax-proc__status-canceled { background: #95a5a6; color: white; }\n.ax-proc__sla { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n.ax-proc__sla-green { background: #27ae60; }\n.ax-proc__sla-yellow { background: #f1c40f; }\n.ax-proc__sla-red { background: #e74c3c; }\n.ax-proc__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-proc__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-proc__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-proc__tree { margin: 0 0 16px; font-family: var(--ax-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.6; }\n.ax-proc__tree-row { padding: 2px 0; }\n.ax-proc__tree-depth { display: inline-block; color: var(--ax-muted); }\n.ax-proc__history { border-top: 1px solid var(--ax-border); padding-top: 12px; }\n.ax-proc__history table { width: 100%; border-collapse: collapse; font-size: 12px; }\n.ax-proc__history th, .ax-proc__history td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--ax-border); }\n.ax-proc__icon { background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer; font-size: 14px; padding: 4px 8px; border-radius: 4px; }\n.ax-proc__icon:hover { background: var(--ax-surface-2); }\n.ax-proc__toast { position: fixed; bottom: 16px; right: 16px; padding: 10px 14px; background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px; font-size: 13px; z-index: 100; }\n.ax-proc__kpis {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--ax-border);\n background: var(--ax-surface-2, rgba(127,127,127,0.04));\n}\n.ax-proc__kpi { padding: 6px 10px; border-radius: 6px; background: var(--ax-bg); border: 1px solid var(--ax-border); }\n.ax-proc__kpi-label { font-size: 11px; color: var(--ax-muted); text-transform: uppercase; letter-spacing: 0.04em; }\n.ax-proc__kpi-value { font-size: 20px; font-weight: 600; }\n.ax-proc__kpi-breakdown { font-size: 11px; color: var(--ax-muted); margin-top: 2px; }\n`\n\nconst PROC_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { runs: [], tasks: [], workflows: [], selected: null, filter: '' };\n const toast = (msg) => { const el = $('#proc-toast'); el.textContent = msg; el.hidden = false; setTimeout(() => el.hidden = true, 2500); };\n\n async function refresh() {\n try {\n const [r1, r2, r3, r4] = await Promise.all([\n fetch('/api/workflows/runs?limit=200', { credentials: 'same-origin' }).then(r => r.json()),\n fetch('/api/workflows/tasks', { credentials: 'same-origin' }).then(r => r.json()).catch(() => ({ tasks: [] })),\n fetch('/api/workflows', { credentials: 'same-origin' }).then(r => r.json()),\n fetch('/api/workflows/kpis', { credentials: 'same-origin' }).then(r => r.json()).catch(() => null),\n ]);\n state.runs = Array.isArray(r1.runs) ? r1.runs : [];\n state.tasks = Array.isArray(r2.tasks) ? r2.tasks : [];\n state.workflows = Array.isArray(r3.workflows) ? r3.workflows : [];\n renderKpis(r4);\n renderList();\n if (state.selected) renderDetail();\n } catch (e) { toast('refresh failed: ' + e.message); }\n }\n\n function formatDuration(ms) {\n if (ms == null) return '—';\n if (ms < 1000) return ms + 'ms';\n if (ms < 60000) return Math.round(ms/100)/10 + 's';\n if (ms < 3600000) return Math.round(ms/6000)/10 + 'm';\n return Math.round(ms/360000)/10 + 'h';\n }\n\n function renderKpis(kpis) {\n const el = $('#proc-kpis');\n if (!kpis || !kpis.totals) { el.hidden = true; return; }\n const t = kpis.totals;\n const breachPct = t.slaBreachRate == null ? '—' : Math.round(t.slaBreachRate * 100) + '%';\n const topActors = (kpis.byActor || []).slice(0, 3);\n el.hidden = false;\n el.innerHTML =\n kpiCard('Open tasks', t.openTasks, topActors.map(a => a.actorId + ': ' + a.openTasks).join(' · ')) +\n kpiCard('Completed', t.completedTasks, '') +\n kpiCard('Avg duration', formatDuration(t.avgDurationMs), '') +\n kpiCard('SLA breach', breachPct, t.breachedCount + ' tasks');\n }\n\n function kpiCard(label, value, breakdown) {\n return '<div class=\"ax-proc__kpi\">' +\n '<div class=\"ax-proc__kpi-label\">' + escHtml(label) + '</div>' +\n '<div class=\"ax-proc__kpi-value\">' + escHtml(value) + '</div>' +\n (breakdown ? '<div class=\"ax-proc__kpi-breakdown\">' + escHtml(breakdown) + '</div>' : '') +\n '</div>';\n }\n\n function workflowTitle(id) {\n const wf = state.workflows.find(w => w.id === id);\n return wf?.title || id;\n }\n\n function slaForRun(runId) {\n // Worst SLA across open tasks on this run: red > yellow > green > none.\n const tasks = state.tasks.filter(t => t.runId === runId && t.dueAt);\n if (!tasks.length) return null;\n const now = Date.now();\n let worst = 'green';\n for (const t of tasks) {\n const remaining = Date.parse(t.dueAt) - now;\n const windowMs = Math.max(1, Date.parse(t.dueAt) - Date.parse(t.createdAt));\n if (remaining <= 0) { worst = 'red'; break; }\n if (remaining / windowMs < 0.25 && worst !== 'red') worst = 'yellow';\n }\n return worst;\n }\n\n function renderList() {\n const ul = $('#proc-runs');\n const empty = $('#proc-empty');\n ul.innerHTML = '';\n const filter = state.filter;\n // Only show root runs in the list; children show inside the composition tree.\n const roots = state.runs.filter(r => !r.parentRunId && (!filter || r.status === filter));\n if (!roots.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const r of roots) {\n const sla = slaForRun(r.id);\n const li = document.createElement('li');\n li.className = 'ax-proc__card' + (r.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-proc__card-title\">' +\n '<span>' + escHtml(workflowTitle(r.workflowId)) + '</span>' +\n '<span>' +\n (sla ? '<span class=\"ax-proc__sla ax-proc__sla-' + sla + '\" title=\"SLA\"></span> ' : '') +\n '<span class=\"ax-proc__status-pill ax-proc__status-' + r.status + '\">' + r.status + '</span>' +\n '</span></div>' +\n '<div class=\"ax-proc__card-meta\">' + escHtml(r.id.slice(0, 8)) + ' · depth ' + (r.depth ?? 0) +\n (r.pausedAt ? ' · paused on ' + escHtml(r.pausedAt.kind) : '') + '</div>';\n li.addEventListener('click', () => { state.selected = r.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#proc-detail-head');\n const body = $('#proc-detail-body');\n const root = state.runs.find(r => r.id === state.selected);\n if (!root) { head.innerHTML = '<span class=\"hint\">Select a process.</span>'; body.innerHTML = ''; return; }\n\n head.innerHTML =\n '<h2 style=\"margin:0;font-size:15px\">' + escHtml(workflowTitle(root.workflowId)) + '</h2>' +\n '<p class=\"hint\" style=\"margin:6px 0 0\">Run ' + escHtml(root.id) + ' · status <span class=\"ax-proc__status-pill ax-proc__status-' + root.status + '\">' + root.status + '</span></p>';\n\n // Composition tree — walk all runs whose rootRunId equals root.id.\n const tree = state.runs.filter(r => (r.rootRunId || r.id) === root.id);\n // Build adjacency: parentRunId → children.\n const byParent = new Map();\n for (const r of tree) {\n const key = r.parentRunId || null;\n if (!byParent.has(key)) byParent.set(key, []);\n byParent.get(key).push(r);\n }\n const render = (parentId, depth) => {\n const kids = byParent.get(parentId) || [];\n return kids.map(k => {\n const pad = ' '.repeat(depth);\n const sla = slaForRun(k.id);\n return '<div class=\"ax-proc__tree-row\"><span class=\"ax-proc__tree-depth\">' + pad + '└─ </span>' +\n '<a href=\"#\" data-run=\"' + k.id + '\">' + escHtml(workflowTitle(k.workflowId)) + '</a>' +\n ' <span class=\"ax-proc__status-pill ax-proc__status-' + k.status + '\">' + k.status + '</span>' +\n (sla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + sla + '\" title=\"SLA\"></span>' : '') +\n (k.pausedAt ? ' <span class=\"hint\">paused: ' + escHtml(k.pausedAt.kind) + '</span>' : '') +\n '</div>' + render(k.id, depth + 1);\n }).join('');\n };\n\n const rootSla = slaForRun(root.id);\n const treeHtml =\n '<div class=\"ax-proc__tree\"><div class=\"ax-proc__tree-row\">' +\n escHtml(workflowTitle(root.workflowId)) +\n ' <span class=\"ax-proc__status-pill ax-proc__status-' + root.status + '\">' + root.status + '</span>' +\n (rootSla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + rootSla + '\"></span>' : '') +\n '</div>' + render(root.id, 1) + '</div>';\n\n const openTasks = state.tasks.filter(t => (tree.some(r => r.id === t.runId)));\n const tasksHtml = openTasks.length ? ('<h3 style=\"margin:0 0 6px; font-size: 13px\">Open tasks</h3><ul>' +\n openTasks.map(t => {\n const sla = t.dueAt ? (Date.parse(t.dueAt) < Date.now() ? 'red' : 'green') : null;\n return '<li>' + escHtml(t.title) + ' — assignee ' + escHtml(t.assignee) +\n (t.dueAt ? ' · due ' + new Date(t.dueAt).toLocaleString() : '') +\n (sla ? ' <span class=\"ax-proc__sla ax-proc__sla-' + sla + '\"></span>' : '') +\n '</li>';\n }).join('') + '</ul>') : '';\n\n const historyHtml = '<div class=\"ax-proc__history\"><h3 style=\"margin:0 0 6px; font-size: 13px\">Root run history</h3>' +\n '<table><thead><tr><th>At</th><th>Node</th><th>Status</th><th>Note</th></tr></thead><tbody>' +\n (root.history || []).map(h =>\n '<tr><td>' + escHtml(new Date(h.at).toLocaleTimeString()) + '</td><td>' + escHtml(h.nodeId) + '</td><td>' + escHtml(h.status) + '</td><td>' + escHtml(h.note || '') + '</td></tr>'\n ).join('') +\n '</tbody></table></div>';\n\n body.innerHTML = treeHtml + tasksHtml + historyHtml;\n\n body.querySelectorAll('a[data-run]').forEach(a => {\n a.addEventListener('click', (e) => { e.preventDefault(); state.selected = a.dataset.run; renderList(); renderDetail(); });\n });\n }\n\n function escHtml(s) { return String(s ?? '').replace(/[&<>\"']/g, c => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''})[c]); }\n\n $('#proc-refresh').addEventListener('click', refresh);\n $('#proc-status').addEventListener('change', (e) => { state.filter = e.target.value; renderList(); });\n refresh();\n setInterval(refresh, 5000);\n})();\n`\n"],"mappings":"wCAeO,SAASA,EAAoBC,EAA0B,CAAC,EAAW,CACxE,IAAMC,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2DAkCb,OAAOC,EAAY,CACjB,MAAO,wBACP,UAAW,YACX,SAAU,YACV,MAAOF,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKE,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoDhBC,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderProcessesPage","opts","body","renderShell","PROC_PAGE_CSS","PROC_PAGE_SCRIPT"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{a as C,b as T}from"./chunk-DLVOHCTA.js";import{a as S,c as x}from"./chunk-4MREHZP5.js";import{a as L}from"./chunk-VJUTQAPJ.js";import{writeFileSync as P,mkdirSync as R,existsSync as D,readFileSync as q}from"fs";import{resolve as v}from"path";var H=["off","shadow","authoritative"],z={telegram:"INTENT_LEDGER_MODE_TELEGRAM",slack:"INTENT_LEDGER_MODE_SLACK",whatsapp:"INTENT_LEDGER_MODE_WHATSAPP",discord:"INTENT_LEDGER_MODE_DISCORD",gitlab:"INTENT_LEDGER_MODE_GITLAB",github:"INTENT_LEDGER_MODE_GITHUB",workflow:"INTENT_LEDGER_MODE_WORKFLOW",cron:"INTENT_LEDGER_MODE_CRON",mesh:"INTENT_LEDGER_MODE_MESH"},K="INTENT_LEDGER_MODE";function O(s,e=process.env){let n=_(e[z[s]]);if(n)return n;let t=_(e[K]);return t||"off"}function _(s){if(typeof s!="string")return null;let e=s.trim().toLowerCase();return H.includes(e)?e:null}var A;function X(s){A=s}function N(){return A}function k(s,e,n,t=Date.now,r){return s.db.transaction(i=>{let c=s.recordEvent(e),a=r??N(),u=a?.pmFor?.(c.project),d=u?`pm:${u}`:n.decidedBy,p=s.getDecisionsForEvent(c.id).find(h=>h.decidedBy===d);if(p)return p;let y=s.getActiveDecisionForSubject(c.project,c.subject);if(y){let h={eventId:c.id,decidedAt:i,decidedBy:d,agentId:null,outcome:"deduped",reason:`active dispatch in flight: ${y.decidedBy} \u2192 ${y.agentId??"?"}`};return s.recordDecision(h),h}let g=n.decide(c),l=g?g.agentId:null,f=g?g.outcome:"halted",m=g?g.reason:"no policy match";if(a?.canHandle&&f==="dispatched"&&l&&!a.canHandle(l,c.project,c.intent)){let $=l;l=null,f="halted",m=`org-chart: agent "${$}" cannot handle (${c.project??"no-project"}/${c.intent??"no-intent"})`}if(a?.withinDelegationBudget&&f==="dispatched"&&l&&!a.withinDelegationBudget(l,c.project,c.subject)){let h=l;l=null,f="halted",m=`delegation-budget: agent "${h}" depth-limit exceeded on ${c.project??"?"}/${c.subject??"?"}`}u&&f==="dispatched"&&l&&(m=m?`${m} (pm-gate: rubber-stamped by ${u})`:`pm-gate: rubber-stamped by ${u}`);let E={eventId:c.id,decidedAt:i,decidedBy:d,agentId:l,outcome:f,reason:m};return s.recordDecision(E),E})(t())}function M(s,e,n,t,r=Date.now){if(U(n,t))return!1;let o=r(),i={id:S(o),ts:o,source:e,eventId:n.eventId,decidedBy:n.decidedBy,ledgerAgentId:n.agentId,ledgerOutcome:n.outcome,ledgerReason:n.reason,legacyAgentId:t.agentId,legacyOutcome:t.outcome,legacyReason:t.reason??null};return s.recordDivergence(i),L.warn(`[ledger-divergence] source=${e} event=${n.eventId} decided_by=${n.decidedBy} ledger=${n.outcome}/${n.agentId??"null"} legacy=${t.outcome}/${t.agentId??"null"}`),!0}function U(s,e){return s.outcome===e.outcome&&s.agentId===e.agentId}function V(s,e,n=Date.now){return{ts:n(),source:"cron",sourceEventId:`${s.jobId}:${s.firedAt.getTime()}`,project:null,subject:`cron:${s.jobId}`,intent:"cron.fired",rawJson:e}}function W(s){return{decidedBy:"cron-scheduler",decide:()=>({agentId:s.agentId,outcome:s.outcome==="deduped"?"halted":s.outcome,reason:s.outcome==="deduped"?`legacy-dedup: ${s.reason??""}`.trim():s.reason??null})}}function J(s,e,n,t,r=Date.now){let o=V(e,n,r),i=W(t),c=k(s,o,i,r);return M(s,"cron",c,t,r),c}function F(s,e){if(!e)return s;let n=e*4;return`${s}
|
|
2
|
+
|
|
3
|
+
[Response budget]
|
|
4
|
+
Keep your response under ~${e} tokens (~${n} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function b(s,e,n){let t=[];for(let r of s.split(","))if(r==="*")for(let o=e;o<=n;o++)t.push(o);else if(r.includes("/")){let[o,i]=r.split("/"),c=parseInt(i,10),a=o==="*"?e:parseInt(o,10);for(let u=a;u<=n;u+=c)t.push(u)}else if(r.includes("-")){let[o,i]=r.split("-").map(Number);for(let c=o;c<=i;c++)t.push(c)}else t.push(parseInt(r,10));return[...new Set(t)].sort((r,o)=>r-o)}function B(s,e,n){let t=s.trim().split(/\s+/);if(t.length!==5)throw new Error(`Invalid cron: ${s}`);let r=b(t[0],0,59),o=b(t[1],0,23),i=b(t[2],1,31),c=b(t[3],1,12),a=b(t[4],0,6),u=new Intl.DateTimeFormat("en-US",{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}),d=new Date(e.getTime()+6e4);d.setSeconds(0,0);let p=new Date(d.getTime()+366*24*60*60*1e3);for(;d<p;){let y=u.formatToParts(d),g=$=>parseInt(y.find(j=>j.type===$)?.value||"0",10),l=g("minute"),f=g("hour"),m=g("day"),E=g("month"),h=d.getDay();if(r.includes(l)&&o.includes(f)&&i.includes(m)&&c.includes(E)&&a.includes(h))return d;d.setTime(d.getTime()+6e4)}throw new Error(`No next run found for cron "${s}" within 1 year`)}var I=5,w=[3e4,6e4,3e5,9e5,36e5],G=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,n,t,r=console.error.bind(console,"[cron]")){this.registry=n,this.hooks=t,this.log=r,this.runsDir=v(process.cwd(),".agentx/cron/runs"),this.lastRunFile=v(process.cwd(),".agentx/cron/last-runs.json");for(let[o,i]of Object.entries(e.crons))this.jobs.set(o,{id:o,enabled:i.enabled,schedule:i.schedule,timezone:i.timezone,agent:i.agent,prompt:i.prompt,timeout:i.timeout,model:i.model,maxOutputTokens:i.maxOutputTokens,onError:i.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,D(this.runsDir)||R(this.runsDir,{recursive:!0});let e=this.detectMissedRuns();if(e.length>0){this.log(`Detected ${e.length} missed cron run(s) while daemon was down:`);for(let{jobId:n,missedAt:t}of e)this.log(` "${n}" should have run at ${t.toISOString()}`);this.executeMissedRuns(e).catch(n=>{this.log(`Error executing missed runs: ${n.message}`)})}for(let[n,t]of this.jobs){if(!t.enabled){this.log(`Job "${n}" is disabled, skipping`);continue}this.scheduleNext(n)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(n=>n.enabled).length} enabled`)}async stop(){this.running=!1;for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.saveLastRuns()}scheduleNext(e){let n=this.jobs.get(e);if(!n||!n.enabled||!this.running)return;let t=this.timers.get(e);t&&clearTimeout(t);try{let r=B(n.schedule,new Date,n.timezone);n.nextRun=r,n.retryPending=!1;let o=r.getTime()-Date.now();this.log(`Job "${e}" next run: ${r.toISOString()} (in ${Math.round(o/1e3)}s)`);let i=setTimeout(()=>this.executeJob(e),o);this.timers.set(e,i)}catch(r){this.log(`Failed to schedule "${e}": ${r.message}`)}}scheduleRetry(e,n){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;if(n>=I){this.log(`Job "${e}" exhausted all ${I} retries, scheduling next regular run`),this.scheduleNext(e);return}let r=w[n]||w[w.length-1];t.retryPending=!0,this.log(`Job "${e}" retry ${n+1}/${I} in ${Math.round(r/1e3)}s`);let o=setTimeout(()=>this.executeJob(e,n+1),r);this.timers.set(e,o)}recordCronDecisionInLedger(e,n,t,r){if(O("cron")!=="off")try{let o=J(x(),{jobId:e,agentId:n,firedAt:t},JSON.stringify({jobId:e,agentId:n,firedAt:t.toISOString()}),r);return o.outcome==="dispatched"?{eventId:o.eventId,decidedBy:o.decidedBy}:void 0}catch(o){this.log(`[ledger] cron "${e}" record failed: ${o?.message??o}`);return}}async executeJob(e,n=0){let t=this.jobs.get(e);if(!t||!this.running)return;let r=n>0,o=new Date;if(this.hooks?.has("pre:cron-run")){let a=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:t.agent,prompt:t.prompt});if(a.blocked){this.log(`Job "${e}" blocked by hook: ${a.message}`),this.recordCronDecisionInLedger(e,t.agent,o,{agentId:null,outcome:"halted",reason:`pre-hook blocked: ${a.message??""}`.trim()}),this.scheduleNext(e);return}}this.log(`${r?`[retry ${n}] `:""}Executing job "${e}" -> agent "${t.agent}"`);let i=o;t.lastRun=i,t.totalRuns++;let c=this.recordCronDecisionInLedger(e,t.agent,o,{agentId:t.agent,outcome:"dispatched",reason:r?`retry ${n}`:null});try{let a=await this.registry.execute({message:F(t.prompt,t.maxOutputTokens),agentId:t.agent,model:t.model,intentRef:c,context:{channel:"cron",chatId:`cron:${e}`}}),u={jobId:e,startedAt:i,completedAt:new Date,success:!a.error,response:a.content,error:a.error,duration:a.duration||Date.now()-i.getTime(),isRetry:r,retryAttempt:n};if(a.error){if(t.consecutiveErrors++,t.totalFailures++,t.lastError=a.error,this.log(`Job "${e}" failed (${t.consecutiveErrors} consecutive): ${a.error}`),await this.notifyFailure(t,a.error),t.onError.includes("disable")&&t.consecutiveErrors>=3){t.enabled=!1,this.log(`Job "${e}" disabled after ${t.consecutiveErrors} consecutive errors`);try{let d=await C(p=>T(p,`crons.${e}.enabled`,!1),{reload:!1});d.success||this.log(`Failed to persist disable for "${e}": ${d.error}`)}catch(d){this.log(`Failed to persist disable for "${e}": ${d.message}`)}await this.notifyDisabled(t)}this.logRun(u),this.scheduleRetry(e,n);return}else t.consecutiveErrors>0&&this.log(`Job "${e}" recovered after ${t.consecutiveErrors} failure(s)`),t.consecutiveErrors=0,t.lastSuccess=new Date,t.lastError=void 0,t.retryPending=!1,this.log(`Job "${e}" completed in ${u.duration}ms`);this.logRun(u),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:u.success,duration:u.duration,error:u.error?new Error(u.error):void 0})}catch(a){t.consecutiveErrors++,t.totalFailures++,t.lastError=a.message,this.log(`Job "${e}" threw: ${a.message}`),await this.notifyFailure(t,a.message),this.scheduleRetry(e,n);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],n=this.loadLastRuns();if(!n)return e;for(let[t,r]of this.jobs){if(!r.enabled)continue;let o=n[t];if(!o)continue;let i=new Date(o);try{let c=B(r.schedule,i,r.timezone);c.getTime()<Date.now()&&e.push({jobId:t,missedAt:c})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:n,missedAt:t}of e){let r=this.jobs.get(n);if(!(!r||!r.enabled)){this.log(`Running missed job "${n}" (was due at ${t.toISOString()})`);try{let o=await this.registry.execute({message:F(`[MISSED RUN \u2014 was scheduled for ${t.toISOString()}]
|
|
5
|
+
|
|
6
|
+
${r.prompt}`,r.maxOutputTokens),agentId:r.agent,context:{channel:"cron",chatId:`cron:${n}`}}),i={jobId:n,startedAt:new Date,completedAt:new Date,success:!o.error,response:o.content,error:o.error,duration:o.duration||0,isRetry:!1};this.logRun(i),o.error?this.log(`Missed job "${n}" failed: ${o.error}`):(this.log(`Missed job "${n}" completed`),r.lastSuccess=new Date),r.lastRun=new Date,r.totalRuns++}catch(o){this.log(`Missed job "${n}" threw: ${o.message}`)}}}this.saveLastRuns()}async notifyFailure(e,n){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${n.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,n,e.consecutiveErrors)}catch{}}}async notifyDisabled(e){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" has been DISABLED after ${e.consecutiveErrors} consecutive failures`);return}try{await this.notifyCallback(e.id,e.agent,`AUTO-DISABLED after ${e.consecutiveErrors} consecutive failures. Last error: ${e.lastError||"unknown"}`,e.consecutiveErrors)}catch{}}saveLastRuns(){try{let e={};for(let[t,r]of this.jobs)r.lastRun&&(e[t]=r.lastRun.toISOString());let n=v(process.cwd(),".agentx/cron");D(n)||R(n,{recursive:!0}),P(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{return D(this.lastRunFile)?JSON.parse(q(this.lastRunFile,"utf-8")):null}catch{return null}}logRun(e){try{let n=v(this.runsDir,e.jobId);D(n)||R(n,{recursive:!0});let t=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;P(v(n,t),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),n=this.detectMissedRuns();return{healthy:e.filter(t=>t.enabled&&t.consecutiveErrors===0).length,failing:e.filter(t=>t.enabled&&t.consecutiveErrors>0).length,disabled:e.filter(t=>!t.enabled).length,missed:n.length,jobs:e.map(t=>({id:t.id,status:t.enabled?t.retryPending?"retrying":t.consecutiveErrors>0?"failing":"healthy":"disabled",consecutiveErrors:t.consecutiveErrors,lastError:t.lastError}))}}};export{O as a,X as b,k as c,M as d,B as e,G as f};
|
|
7
|
+
//# sourceMappingURL=chunk-QW2OYR3N.js.map
|