@task-handoff/control-plane 0.0.3-alpha.6 → 0.0.3-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/package.json +1 -1
- package/ui/assets/{abnfDiagram-VRR7QNED-JsIw5eUB.js → abnfDiagram-VRR7QNED-BSVJkVyi.js} +1 -1
- package/ui/assets/{arc-B2N0wz0H.js → arc-D9eId-Qo.js} +1 -1
- package/ui/assets/{architectureDiagram-ZJ3FMSHR-Asf5JqJq.js → architectureDiagram-ZJ3FMSHR-NwxKFD_i.js} +1 -1
- package/ui/assets/{blockDiagram-677ZJIJ3-5GnT1Ofe.js → blockDiagram-677ZJIJ3-BxvLh-7H.js} +1 -1
- package/ui/assets/{c4Diagram-LMCZKHZV-CUp2y31Y.js → c4Diagram-LMCZKHZV-CU4VHpZ_.js} +1 -1
- package/ui/assets/channel-CjEppeG1.js +1 -0
- package/ui/assets/{chunk-2Q5K7J3B-BipPvMDc.js → chunk-2Q5K7J3B-DDPeSCKz.js} +1 -1
- package/ui/assets/{chunk-32BRIVSS-DZeBN2eM.js → chunk-32BRIVSS-K4MrXabe.js} +1 -1
- package/ui/assets/{chunk-5VM5RSS4-D4zxxDg4.js → chunk-5VM5RSS4-CzTC32Op.js} +1 -1
- package/ui/assets/{chunk-EX3LRPZG-DaG89drY.js → chunk-EX3LRPZG-VXAn5ISx.js} +1 -1
- package/ui/assets/{chunk-JWPE2WC7-CTy_6ucj.js → chunk-JWPE2WC7-BmDI9Fxa.js} +1 -1
- package/ui/assets/{chunk-MOJQB5TN-C3TukL0h.js → chunk-MOJQB5TN-2ZffjNig.js} +1 -1
- package/ui/assets/{chunk-RYQCIY6F-BmQEtGAu.js → chunk-RYQCIY6F-Dxjo9s2i.js} +1 -1
- package/ui/assets/{chunk-V7JOEXUC-CtqWQSZz.js → chunk-V7JOEXUC-DkphDFF9.js} +1 -1
- package/ui/assets/{chunk-VR4S4FIN-BS4GfMTw.js → chunk-VR4S4FIN-aRvLnnVF.js} +1 -1
- package/ui/assets/{chunk-XXDRQBXY-BB-3Yl5F.js → chunk-XXDRQBXY-l8Efr6jh.js} +1 -1
- package/ui/assets/classDiagram-OUVF2IWQ-OzvGf6kz.js +1 -0
- package/ui/assets/classDiagram-v2-EOCWNBFH-OzvGf6kz.js +1 -0
- package/ui/assets/{cose-bilkent-JH36ORCC-CILv3yHj.js → cose-bilkent-JH36ORCC-DJdmWL8n.js} +1 -1
- package/ui/assets/{cynefin-VYW2F7L2-E02_wrb2.js → cynefin-VYW2F7L2-oExeZ8XG.js} +1 -1
- package/ui/assets/{cynefinDiagram-TSTJHNR4-CJ4bpRrW.js → cynefinDiagram-TSTJHNR4-CPvo4LvB.js} +1 -1
- package/ui/assets/{dagre-VKFMJZFB-nmlNshUX.js → dagre-VKFMJZFB-CsYuNVv2.js} +1 -1
- package/ui/assets/{diagram-FQU43EPY-DJ2-j93c.js → diagram-FQU43EPY-Bk-avdiV.js} +1 -1
- package/ui/assets/{diagram-G47NLZAW-DleKlsQj.js → diagram-G47NLZAW-CP4ubafo.js} +1 -1
- package/ui/assets/{diagram-NH7WQ7WH-D2z2Fudn.js → diagram-NH7WQ7WH-D373SJ8o.js} +1 -1
- package/ui/assets/{diagram-OA4YK3LP-Bm_xWWD4.js → diagram-OA4YK3LP-C9SlZPNZ.js} +1 -1
- package/ui/assets/{diagram-WEI45ONY-B7DFIt44.js → diagram-WEI45ONY-DFdTkLUB.js} +1 -1
- package/ui/assets/{ebnfDiagram-CCIWWBDH-C8-UIgK-.js → ebnfDiagram-CCIWWBDH-aM3dPMWg.js} +1 -1
- package/ui/assets/{erDiagram-Q63AITRT-y0hCt7YP.js → erDiagram-Q63AITRT-DKtXjm4k.js} +1 -1
- package/ui/assets/{flowDiagram-23GEKE2U-wzbWoNeR.js → flowDiagram-23GEKE2U-D7mKHdBz.js} +1 -1
- package/ui/assets/{ganttDiagram-NO4QXBWP-B8CIL5iS.js → ganttDiagram-NO4QXBWP-BRT1O3S0.js} +1 -1
- package/ui/assets/{gitGraphDiagram-IHSO6WYX-D2XkSPip.js → gitGraphDiagram-IHSO6WYX-wxF1dbvt.js} +1 -1
- package/ui/assets/{index-wihlgcYm.css → index-D7USaoPf.css} +1 -1
- package/ui/assets/index-LTf8vwed.js +400 -0
- package/ui/assets/{infoDiagram-FWYZ7A6U-Bt8gfUHU.js → infoDiagram-FWYZ7A6U-pd8tL4Vk.js} +1 -1
- package/ui/assets/{ishikawaDiagram-FXEZZL3T-Dt3IveoP.js → ishikawaDiagram-FXEZZL3T-VTYUzxP3.js} +1 -1
- package/ui/assets/{journeyDiagram-5HDEW3XC-D-u5iOR4.js → journeyDiagram-5HDEW3XC-CZ0EEPfV.js} +1 -1
- package/ui/assets/{kanban-definition-HUTT4EX6-DjjSujo-.js → kanban-definition-HUTT4EX6-DEzIu9ly.js} +1 -1
- package/ui/assets/{linear-BY7krGQF.js → linear-CcRQjM0v.js} +1 -1
- package/ui/assets/{mermaid.core-ARJs3dA3.js → mermaid.core-pPXLKTF5.js} +4 -4
- package/ui/assets/{mindmap-definition-LN4V7U3C-CYMbS4wV.js → mindmap-definition-LN4V7U3C-BAKqfr6E.js} +1 -1
- package/ui/assets/{pegDiagram-2B236MQR-jseGuGUp.js → pegDiagram-2B236MQR-KDwzfUqx.js} +1 -1
- package/ui/assets/{pieDiagram-ENE6RG2P-CXsJY0GJ.js → pieDiagram-ENE6RG2P-CRT5UMVH.js} +1 -1
- package/ui/assets/{quadrantDiagram-ABIIQ3AL-DGOXqHSK.js → quadrantDiagram-ABIIQ3AL-Dr5uMmZ7.js} +1 -1
- package/ui/assets/{railroadDiagram-RFXS5EU6-CzI5ek1V.js → railroadDiagram-RFXS5EU6-B7PcJawb.js} +1 -1
- package/ui/assets/{requirementDiagram-TGXJPOKE-QhYZq1e3.js → requirementDiagram-TGXJPOKE-BlhonxXF.js} +1 -1
- package/ui/assets/{sankeyDiagram-HTMAVEWB-B_kw6M-Z.js → sankeyDiagram-HTMAVEWB-sE8CiLtJ.js} +1 -1
- package/ui/assets/{sequenceDiagram-DBY2YBRQ-DzXEgHaC.js → sequenceDiagram-DBY2YBRQ-5jhEJoxS.js} +1 -1
- package/ui/assets/{sizeCapture-X5ZJPWSS-CdC3xEbQ.js → sizeCapture-X5ZJPWSS-DzBZB_qw.js} +1 -1
- package/ui/assets/{stateDiagram-2N3HPSRC-D-ztA6S4.js → stateDiagram-2N3HPSRC-BdXO594c.js} +1 -1
- package/ui/assets/stateDiagram-v2-6OUMAXLB-5FQrwrf9.js +1 -0
- package/ui/assets/{swimlanes-5IMT3BWC-PgOMZesx.js → swimlanes-5IMT3BWC-DECVG5ez.js} +2 -2
- package/ui/assets/swimlanesDiagram-G3AALYLV-Bo4jwoyf.js +8 -0
- package/ui/assets/{timeline-definition-FHXFAJF6-BREX-N-j.js → timeline-definition-FHXFAJF6-BF93Qpan.js} +1 -1
- package/ui/assets/ui-vendor-CxIbXZyS.js +3 -0
- package/ui/assets/{vennDiagram-L72KCM5P-Bk3wYbhM.js → vennDiagram-L72KCM5P-B3GOpYIJ.js} +1 -1
- package/ui/assets/{wardleyDiagram-EHGQE667-CZvk2_y-.js → wardleyDiagram-EHGQE667-DB3WNS_s.js} +1 -1
- package/ui/assets/{xychartDiagram-FW5EYKEG-DbARTGJr.js → xychartDiagram-FW5EYKEG-CFJU8a9w.js} +1 -1
- package/ui/index.html +3 -3
- package/ui/assets/channel-BEOsbtkA.js +0 -1
- package/ui/assets/classDiagram-OUVF2IWQ-DV_YAz9S.js +0 -1
- package/ui/assets/classDiagram-v2-EOCWNBFH-DV_YAz9S.js +0 -1
- package/ui/assets/index-D-JnnRnu.js +0 -396
- package/ui/assets/stateDiagram-v2-6OUMAXLB-C1kZ-ul4.js +0 -1
- package/ui/assets/swimlanesDiagram-G3AALYLV-Dmj0Sivq.js +0 -8
- package/ui/assets/ui-vendor-D1aTc0NA.js +0 -3
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var e=require("commander"),t=require("node:fs"),n=require("node:path"),s=require("fastify"),i=require("@fastify/cookie"),a=require("@fastify/websocket"),r=require("zod"),o=require("node:crypto"),d=require("node:util"),c=require("node:os"),l=require("write-file-atomic"),p=require("ws"),u=require("node:http"),g=require("node:net"),h=require("dingtalk-stream"),m=require("telegramify-markdown"),f=require("node:fs/promises");const I=20971520,y=41943040,b="ai-session.snapshot",w="ai-session.patch",A="ai-session.removed",S=r.z.string().trim().min(1).max(80),v=r.z.enum(["running","waiting","idle","failed"]),k=r.z.enum(["thinking","tool","editing","approval","responding","unknown"]),T=r.z.object({name:r.z.string().trim().min(1).max(120),inputPreview:r.z.string().trim().max(500).optional(),startedAt:r.z.string().datetime().optional()}).strict(),E=r.z.object({toolCalls:r.z.number().int().min(0).default(0),edits:r.z.number().int().min(0).default(0),approvals:r.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),_=r.z.object({send:r.z.boolean().optional(),interrupt:r.z.boolean().optional(),approval:r.z.boolean().optional()}).strict(),C=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),size:r.z.number().int().min(0).max(I),data:r.z.string().min(1).max(31457280)}).strict(),N=C.omit({data:!0}),z=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]).optional()}).strict(),O=r.z.enum(["auto","queue","steer","immediate"]),x=r.z.object({message:r.z.string().trim().min(1).max(2e4),mode:O.optional()});x.extend({attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const D=x.extend({attachments:r.z.array(z).max(6).default([])}).strict(),R=r.z.object({decision:r.z.enum(["allow","deny","skip"])}).strict();r.z.object({queueIds:r.z.array(r.z.string().trim().min(1).max(120)).max(100)}).strict(),r.z.object({code:r.z.string().trim().min(1).max(160),message:r.z.string(),instanceId:r.z.string().trim().min(1).max(160).optional(),nodeId:r.z.string().trim().min(1).max(160).optional(),route:r.z.string().trim().min(1).max(4096).optional()}).strict();const j=r.z.object({id:r.z.string().trim().min(1).max(120),message:r.z.string().trim().min(1).max(2e4),attachments:r.z.array(N).max(6).default([]),status:r.z.enum(["queued","sending","failed"]).default("queued"),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),error:r.z.string().trim().max(4e3).optional()}).strict(),P=r.z.object({pendingCount:r.z.number().int().min(0).default(0),items:r.z.array(j).max(100).default([])}).strict().default({pendingCount:0,items:[]}),M=r.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),$=r.z.object({id:r.z.string().trim().min(1).max(240),providerTurnId:r.z.string().trim().max(240).optional(),source:M.optional(),userPrompt:r.z.string().trim().optional(),status:r.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),revision:r.z.number().int().min(0).default(0),sourcePriority:r.z.number().int().min(0).max(100).optional(),snapshotVersion:r.z.number().int().min(0).optional(),observedAt:r.z.string().datetime().optional(),startedAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional(),completedAt:r.z.string().datetime().optional()}).strict(),q=r.z.object({id:r.z.string().trim().min(1).max(120),agent:S,appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),activeTurnId:r.z.string().trim().max(240).optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:v.default("running"),phase:k.default("unknown"),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),currentTool:T.optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),startedAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional(),error:r.z.string().trim().max(4e3).optional(),counters:E,queue:P}).strict(),L=r.z.object({session:q,provider:r.z.string().trim().min(1).max(80),action:r.z.enum(["send","queue","steer","interrupt","approval"]),decision:r.z.enum(["allow","deny","skip"]).optional(),turnId:r.z.string().trim().min(1).max(240).optional(),providerTurnId:r.z.string().trim().min(1).max(240).optional(),queueId:r.z.string().trim().min(1).max(120).optional()}).strict(),U=q.pick({id:!0,agent:!0,appSessionId:!0,appId:!0,providerSessionId:!0,providerMeta:!0,appBindingKeys:!0,actions:!0,activeTurnId:!0,conversationId:!0,title:!0,cwd:!0,userPrompt:!0,turns:!0,status:!0,phase:!0,summary:!0,lastMessage:!0,currentTool:!0,queue:!0,startedAt:!0,updatedAt:!0,error:!0}),B=r.z.object({runningCount:r.z.number().int().min(0).default(0),waitingCount:r.z.number().int().min(0).default(0),staleCount:r.z.number().int().min(0).default(0),sessions:r.z.array(U).default([]),updatedAt:r.z.string().datetime()}).strict(),F=r.z.object({revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:B}).strict(),H=B.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),revision:r.z.number().int().min(0).optional()});function G(e,t={}){const n=e.sessions.filter(e=>"idle"===e.status).length,s=e.sessions.filter(e=>"failed"===e.status).length;return H.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:n,problemCount:s,updatedAt:e.updatedAt,revision:t.revision})}const W=r.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),V=r.z.object({instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),snapshotRevision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),eventSeq:r.z.number().int().min(1),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:W}).strict(),K=r.z.object({meta:V,snapshot:B}).strict(),J=r.z.object({meta:V,upserted:r.z.array(U).default([]),removed:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),Q=r.z.object({meta:V,sessionIds:r.z.array(r.z.string().trim().min(1).max(120)).default([]),expiresAt:r.z.string().datetime()}).strict(),Y=r.z.object({instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(b),payload:K}).strict(),r.z.object({type:r.z.literal(w),payload:J}).strict(),r.z.object({type:r.z.literal(A),payload:Q}).strict()])).default([])}).strict(),X=r.z.object({source:M,sourcePriority:r.z.number().int().min(0).max(100).optional(),observedAt:r.z.string().datetime().optional(),snapshotVersion:r.z.number().int().min(0).optional()}).strict(),Z=X.extend({type:r.z.literal("snapshot"),agent:r.z.string().trim().min(1).max(80),appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),activeTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:v.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),replaceActivity:r.z.boolean().optional()}).strict(),ee=X.extend({type:r.z.literal("event"),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:r.z.string().trim().max(240).optional(),providerTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),text:r.z.string().optional(),status:v.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),counters:r.z.object({toolCalls:r.z.number().int().min(0).optional(),edits:r.z.number().int().min(0).optional(),approvals:r.z.number().int().min(0).optional()}).strict().optional()}).strict();r.z.discriminatedUnion("type",[Z,ee]);const te=r.z.union([r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("interval").default("interval"),intervalMs:r.z.number().int().min(1e3)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("daily"),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("weekly"),weekdays:r.z.array(r.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict()]),ne=r.z.union([te,r.z.object({type:r.z.literal("file-change"),roots:r.z.array(r.z.string().trim().min(1).max(4096)).min(1).max(50),globs:r.z.array(r.z.string().trim().min(1).max(500)).min(1).max(100),ignore:r.z.array(r.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:r.z.number().int().min(100).max(6e4).default(1500)}).strict(),r.z.object({type:r.z.literal("ai-session"),conversationId:r.z.number().int().positive().optional(),agent:r.z.string().trim().min(1).max(80).optional(),statuses:r.z.array(r.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:r.z.array(r.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),se=r.z.object({promptTemplate:r.z.string().trim().min(1).max(2e4)}).strict(),ie=r.z.object({cooldownMs:r.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:r.z.number().int().min(1).max(20).default(1),whenBusy:r.z.enum(["skip","queue"]).default("skip")}).strict(),ae=r.z.object({configHash:r.z.string().trim().min(8).max(80),name:r.z.string().trim().min(1).max(160),description:r.z.string().trim().max(1e3).optional(),source:ne,action:se,policy:ie.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),re=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("conversation"),conversationId:r.z.number().int().positive()}).strict(),r.z.object({type:r.z.literal("ai-session"),aiSessionId:r.z.string().trim().min(1).max(160)}).strict()]),oe=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),origin:r.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:r.z.boolean().default(!0),target:re,localName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),de=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),status:r.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:r.z.string().datetime().optional(),lastCompletedAt:r.z.string().datetime().optional(),lastSkippedAt:r.z.string().datetime().optional(),lastError:r.z.string().trim().max(4e3).optional(),runCount:r.z.number().int().min(0).default(0),skippedCount:r.z.number().int().min(0).default(0)}).strict(),ce=r.z.object({id:r.z.string().trim().min(1).max(160),configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),eventType:r.z.enum(["manual","schedule","file-change","ai-session"]),status:r.z.enum(["started","completed","failed","skipped"]),target:re,promptPreview:r.z.string().trim().max(500),eventSummary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),startedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional()}).strict(),le=r.z.object({schemaVersion:r.z.literal(1).default(1),configs:r.z.array(ae).default([]),deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([]),recentRuns:r.z.array(ce).default([])}).strict();function pe(e){return Array.isArray(e)?e.map(pe):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,pe(t)])):e}const ue="2026-06-23",ge=r.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),he=r.z.string().datetime(),me=r.z.record(r.z.string(),r.z.string()).default({}),fe=r.z.record(r.z.string(),r.z.string()).default({}),Ie=r.z.object({codexModelId:ge.optional(),claudeModelId:ge.optional()}).strict().default({}),ye=r.z.object({component:r.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:r.z.string().trim().max(160).optional(),packageVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80).optional(),buildId:r.z.string().trim().max(160).optional(),builtAt:r.z.string().trim().max(120).optional(),gitCommit:r.z.string().trim().max(120).optional(),imageRef:r.z.string().trim().max(512).optional(),imageDigest:r.z.string().trim().max(240).optional()}).strict(),be=r.z.enum(["stable","beta","alpha"]),we=r.z.discriminatedUnion("component",[r.z.object({component:r.z.literal("node-agent")}).strict(),r.z.object({component:r.z.literal("controlled-instance"),instanceId:ge}).strict()]),Ae=r.z.object({target:we,channel:be.default("stable")}).strict(),Se=r.z.object({target:we,source:r.z.enum(["npm","docker-registry"]),channel:be,currentVersion:r.z.string().trim().max(240).optional(),availableVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),updateAvailable:r.z.boolean(),supported:r.z.boolean().default(!0),reason:r.z.string().trim().max(2048).optional(),checkedAt:he}).strict(),ve=r.z.object({id:ge,nodeId:ge,target:we,source:r.z.enum(["npm","docker-registry"]),channel:be,fromVersion:r.z.string().trim().max(240).optional(),toVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),status:r.z.enum(["queued","updating","restarting","succeeded","failed"]),error:r.z.string().trim().max(4096).optional(),startedAt:he.optional(),completedAt:he.optional(),createdAt:he,updatedAt:he}).strict(),ke=r.z.object({type:r.z.enum(["branch","tag","commit"]),name:r.z.string().trim().min(1).max(240).optional(),commit:r.z.string().trim().max(80).optional()}).strict(),Te=r.z.object({type:r.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:r.z.string().trim().min(1).max(120).optional()}).strict(),Ee=r.z.object({depth:r.z.number().int().positive().max(1e5).optional(),submodules:r.z.boolean().default(!1),lfs:r.z.boolean().default(!1),subdirectory:r.z.string().trim().max(240).default("")}).strict();r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:ge.optional(),modelSelection:Ie,labels:me,createdAt:he,updatedAt:he}).strict();const _e=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),path:r.z.string().trim().min(1).max(4096),defaultImageId:ge.optional(),modelSelection:Ie,labels:me,createdAt:he,updatedAt:he}).strict(),Ce=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("git-repository"),repositoryId:ge.optional(),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("git-template"),url:r.z.string().trim().min(1).max(2048),templateId:r.z.string().trim().min(1).max(120).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("local-folder"),localFolderId:ge.optional(),path:r.z.string().trim().min(1).max(4096),ownerNodeId:ge.optional()}).strict()]),Ne=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:r.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:r.z.string().trim().max(240).optional(),readOnly:r.z.boolean().default(!1)}).strict(),ze=r.z.enum(["codex","claude"]),Oe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),source:Ce,defaultImageId:ge.optional(),defaultNodeId:ge.optional(),defaultRuntimeId:ge.optional(),modelSelection:Ie,workspacePolicy:Ne,labels:me,createdAt:he,updatedAt:he}).strict(),xe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),endpoint:r.z.string().trim().min(1).max(2048),key:r.z.string().trim().min(1).max(4096),model:r.z.string().trim().min(1).max(240),app:ze,enabled:r.z.boolean().default(!0),order:r.z.number().int().min(0).max(1e6).default(0),labels:me,createdAt:he,updatedAt:he}).strict(),De=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),image:r.z.string().trim().min(1).max(512),registry:r.z.string().trim().min(1).max(160).default("local"),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).default([]),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).default([]),defaultEnv:fe,labels:me,createdAt:he,updatedAt:he}).strict(),Re=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),connectionMode:r.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:ge.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:he.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:he.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:r.z.string().trim().max(2048).optional(),controlEndpoint:r.z.string().trim().max(2048).optional(),containerEndpoint:r.z.string().trim().max(2048).optional(),publicWebBase:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,lastSeenAt:he.optional(),createdAt:he,updatedAt:he}).strict(),je=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),type:r.z.enum(["docker","kubernetes","local"]),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,createdAt:he,updatedAt:he}).strict(),Pe=r.z.object({ok:r.z.boolean().optional(),role:r.z.string().optional(),nodeId:ge.optional()}).passthrough(),Me=r.z.object({nodeId:ge,joinToken:r.z.string().trim().min(1),expiresAt:he}).passthrough(),$e=r.z.object({id:ge,keyId:ge,url:r.z.string().trim().min(1).max(2048).optional(),name:r.z.string().trim().min(1).max(160).optional(),pairedAt:he,updatedAt:he.optional(),active:r.z.boolean().optional(),current:r.z.boolean().optional()}).passthrough(),qe=r.z.object({remote:r.z.object({id:ge,url:r.z.string().trim().min(1).max(2048).optional(),keyId:ge,pairedAt:he,active:r.z.boolean()}).passthrough(),tunnel:r.z.object({status:r.z.enum(["disabled","saved","connecting","connected","failed"]),error:r.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),Le=r.z.object({deleted:r.z.boolean().optional()}).passthrough(),Ue=r.z.object({repository:r.z.string().trim().optional(),tag:r.z.string().trim().optional(),id:r.z.string().trim(),createdSince:r.z.string().trim().optional(),size:r.z.string().trim().optional(),reference:r.z.string().trim().min(1)}).passthrough(),Be=r.z.lazy(()=>r.z.object({name:r.z.string().trim().min(1),path:r.z.string().trim().min(1).max(4096),children:r.z.array(Be)}).passthrough()),Fe=r.z.object({status:r.z.number().int().min(100).max(599).optional(),headers:r.z.record(r.z.string(),r.z.string()).optional(),bodyBase64:r.z.string().optional()}).passthrough(),He=r.z.object({strategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),vnc:r.z.string().trim().max(2048).optional(),tty:r.z.string().trim().max(2048).optional(),logs:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),Ge=r.z.object({strategy:r.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),ws:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),We=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:r.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:r.z.string().trim().max(4096).optional(),resolvedCommit:r.z.string().trim().max(120).optional(),error:r.z.string().trim().max(2048).optional()}).passthrough(),Ve=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce,sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).default({}),nodeId:ge,runtimeId:ge,imageId:ge.optional(),imageSnapshot:De.optional(),status:r.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:r.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:r.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:r.z.string().trim().max(80).optional(),instanceVersion:r.z.string().trim().max(80).optional(),build:ye.optional(),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),config:r.z.object({autoImportAgentConfigs:r.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:We.default({status:"unknown"}),target:He.default({strategy:"direct-port",status:"unknown"}),access:Ge,receiver:r.z.object({status:r.z.string().trim().max(80).default("unknown"),pendingCount:r.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),updatedAt:he.optional(),revision:r.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:B.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:r.z.object({enabledCount:r.z.number().int().min(0).default(0),runningCount:r.z.number().int().min(0).default(0),errorCount:r.z.number().int().min(0).default(0),configs:r.z.array(r.z.object({configHash:r.z.string().trim().min(1),config:ae,deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([])}).strict()).default([]),recentRuns:r.z.array(ce).default([])}).strict().optional(),runtime:r.z.object({kind:r.z.string().trim().max(80).optional(),containerName:r.z.string().trim().max(240).optional(),containerId:r.z.string().trim().max(240).optional(),workspacePath:r.z.string().trim().max(4096).optional(),pid:r.z.number().int().positive().optional(),port:r.z.number().int().positive().max(65535).optional(),labels:r.z.record(r.z.string(),r.z.string()).default({})}).strict().default({labels:{}}),registrationToken:r.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:he.optional(),createdAt:he,updatedAt:he}).strict();function Ke(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const n=e;return Object.fromEntries(t.filter(e=>Object.prototype.hasOwnProperty.call(n,e)).map(e=>[e,n[e]]))}r.z.object({instanceId:ge.optional(),name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce.optional(),nodeId:ge.optional(),runtimeId:ge.optional(),imageId:ge.optional(),instanceVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),target:He.default({strategy:"direct-port",status:"unknown"}),workspace:We.default({status:"unknown"}),registrationToken:r.z.string().trim().max(240).optional()}).strict(),r.z.object({status:Ve.shape.status.optional(),health:Ve.shape.health.optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),capabilities:Ve.shape.capabilities.optional(),receiver:Ve.shape.receiver.optional(),apps:Ve.shape.apps.optional(),aiSessions:Ve.shape.aiSessions.optional(),triggers:Ve.shape.triggers.optional(),workspace:We.optional(),target:He.partial().optional()}).strict();const Je=r.z.enum(["web","telegram","wechat","dingding"]),Qe=r.z.object({id:ge,channel:Je,name:r.z.string().trim().min(1).max(160),enabled:r.z.boolean().default(!1),token:r.z.string().trim().max(4096).optional(),defaultChatId:r.z.string().trim().max(240).optional(),allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:r.z.number().int().positive().max(6e4).default(3e3),settings:r.z.record(r.z.string(),r.z.unknown()).default({}),createdAt:he,updatedAt:he}).strict(),Ye=r.z.object({id:ge,channel:Je,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional(),activeProjectId:ge.optional(),activeInstanceId:ge.optional(),activeAiSessionId:r.z.string().trim().min(1).max(120).optional(),lastUsedAt:he,createdAt:he,updatedAt:he}).strict(),Xe=r.z.object({source:r.z.object({type:r.z.literal("chat-gateway").default("chat-gateway"),channel:Je,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),message:r.z.object({text:r.z.string().trim().max(2e4).default(""),attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:r.z.object({instanceId:ge,aiSessionId:r.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),Ze=r.z.object({id:ge,instanceId:ge,projectId:ge.optional(),aiSessionId:r.z.string().trim().min(1).max(120),providerSessionId:r.z.string().trim().max(240).optional(),result:r.z.string().default(""),timeoutMs:r.z.number().positive().optional(),source:r.z.string().trim().max(120).optional(),kind:r.z.enum(["task","approval"]).default("task"),status:r.z.enum(["pending","unreachable"]).default("pending"),error:r.z.string().trim().max(2048).optional(),lastSeenAt:he}).strict();function et(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||n.join(c.homedir(),".config","task-handoff","control-plane")}()){const t=n.resolve(e);return{dataDir:t,settingsPath:n.join(t,"control-plane-settings.json"),projectsDir:n.join(t,"projects"),modelsDir:n.join(t,"models"),imagesDir:n.join(t,"images"),nodesDir:n.join(t,"nodes"),nodeRuntimesDir:n.join(t,"node-runtimes"),controlledInstancesDir:n.join(t,"controlled-instances"),chatSessionsDir:n.join(t,"chat-sessions"),chatBridgesDir:n.join(t,"chat-bridges"),triggersDir:n.join(t,"triggers"),nodeJoinInvitesDir:n.join(t,"node-join-invites"),authUsersDir:n.join(t,"auth-users"),authSessionsDir:n.join(t,"auth-sessions"),logsDir:n.join(t,"logs")}}function tt(e){t.mkdirSync(e,{recursive:!0})}function nt(e,t){tt(n.dirname(e)),l.sync(e,`${JSON.stringify(t,null,2)}\n`,{encoding:"utf8"})}function st(e,t){console.warn(JSON.stringify({message:e,...t}))}function it(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function at(e,n){const s=n.logger||st;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void s("stored JSON could not be read",{filePath:e,...it(t)})}if(!n.schema)return i;const a=n.sanitize?n.sanitize(i):i,r=n.schema.safeParse(a);if(r.success)return r.data;const o="strip"in n.schema&&"function"==typeof n.schema.strip?n.schema.strip().safeParse(a):void 0;if(o?.success)return s("unknown stored fields were ignored",{filePath:e,issues:r.error.issues}),o.data;s("stored record failed validation and was isolated",{filePath:e,issues:r.error.issues})}function rt(e){return`${e}_${o.randomUUID().replace(/-/g,"").slice(0,20)}`}function ot(){return o.randomBytes(24).toString("base64url")}class dt{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){tt(this.directory)}filePath(e){return n.join(this.directory,`${e}.json`)}list(){return this.init(),t.readdirSync(this.directory).filter(e=>e.endsWith(".json")).sort().flatMap(e=>{const t=at(n.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const n=this.filePath(e);return t.existsSync(n)?at(n,this.options):void 0}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,n=this.options.schema?this.options.schema.parse(t):t;return nt(this.filePath(n.id),n),n}patch(e,t){const n=this.get(e);if(!n)return;const s={...n,...t,id:n.id,createdAt:n.createdAt,updatedAt:(new Date).toISOString()};return this.put(s)}delete(e){const n=this.filePath(e);return!!t.existsSync(n)&&(t.unlinkSync(n),!0)}}class ct{filePathValue;defaults;options;constructor(e,t,n={}){this.filePathValue=e,this.defaults=t,this.options=n}init(){tt(n.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?at(this.filePathValue,this.options)??this.defaults():this.defaults()}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,n=this.options.schema?this.options.schema.parse(t):t;return nt(this.filePathValue,n),n}}const lt=d.promisify(o.scrypt),pt="task_handoff_cp_session",ut=r.z.enum(["disabled","password"]),gt=r.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),ht=r.z.string().min(8).max(4096),mt=r.z.object({username:gt,password:ht}).strict(),ft=r.z.object({username:r.z.string().trim().min(1).max(80),password:r.z.string().min(1).max(4096)}).strict(),It=r.z.object({id:r.z.string().trim().min(1),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}),yt=It.extend({username:gt,passwordHash:r.z.string().trim().min(1),role:r.z.enum(["viewer","operator","admin"]),lastLoginAt:r.z.string().datetime().optional()}).strict(),bt=It.extend({userId:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),lastSeenAt:r.z.string().datetime().optional()}).strict();function wt(){return(new Date).toISOString()}function At(e){return o.createHash("sha256").update(e).digest("base64url")}async function St(e){const t=o.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await lt(e,t,64)).toString("base64url")}`}function vt(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}class kt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return ut.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new dt(e.authUsersDir,{schema:yt}),this.sessions=new dt(e.authSessionsDir,{schema:bt})}init(){this.users.init(),this.sessions.init()}enabled(){return"password"===this.mode}state(){return{mode:this.mode,enabled:this.enabled(),requiresBootstrap:this.enabled()&&0===this.users.list().length}}async bootstrapAdmin(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}if(this.users.list().length>0){const e=new Error("Control Plane admin user already exists.");throw Object.assign(e,{statusCode:409,code:"AUTH_BOOTSTRAP_ALREADY_DONE"}),e}const t=mt.parse(e),n=wt();return vt(this.users.put({id:rt("user"),username:t.username,passwordHash:await St(t.password),role:"admin",createdAt:n,updatedAt:n}))}async login(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}const t=ft.parse(e),n=this.users.list().find(e=>e.username===t.username);if(!n||!await async function(e,t){const[n,s,i]=t.split("$");if("scrypt"!==n||!s||!i)return!1;const a=await lt(e,s,64),r=Buffer.from(i,"base64url");return r.length===a.length&&o.timingSafeEqual(r,a)}(t.password,n.passwordHash)){const e=new Error("Invalid username or password.");throw Object.assign(e,{statusCode:401,code:"AUTH_LOGIN_FAILED"}),e}const s=wt(),i=ot(),a=new Date(Date.now()+12096e5).toISOString(),r=this.sessions.put({id:rt("sess"),userId:n.id,tokenHash:At(i),expiresAt:a,createdAt:s,updatedAt:s,lastSeenAt:s});return this.users.put({...n,lastLoginAt:s,updatedAt:s}),{user:vt({...n,lastLoginAt:s,updatedAt:s}),sessionToken:`${r.id}.${i}`,expiresAt:a}}async userForSessionToken(e){if(!this.enabled())return;if(!e)return;const[t,n]=e.split(".");if(!t||!n)return;const s=this.sessions.get(t);if(!s||s.expiresAt<=wt()||s.tokenHash!==At(n))return;const i=this.users.get(s.userId);return i?(this.sessions.put({...s,lastSeenAt:wt(),updatedAt:wt()}),vt(i)):void 0}async currentSession(e){const t=this.state(),n=await this.userForSessionToken(e);return{...t,authenticated:!this.enabled()||Boolean(n),user:n}}logout(e){const t=e?.split(".")[0];return t&&this.sessions.delete(t),{ok:!0}}}const Tt="app-session.snapshot",Et="app-session.patch",_t="app-session.removed",Ct=r.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Nt=r.z.object({type:r.z.enum(["app-session","provider-session","adapter-key"]),id:r.z.string().trim().min(1).max(240),agent:r.z.string().trim().min(1).max(80).optional(),adapter:r.z.string().trim().min(1).max(80).optional(),key:r.z.string().trim().min(1).max(240).optional()}).strict(),zt=r.z.record(r.z.string(),r.z.unknown()).and(r.z.object({id:r.z.string().trim().min(1).max(160),appId:r.z.string().trim().min(1).max(160).optional(),title:r.z.string().trim().max(240).optional(),kind:r.z.string().trim().max(80).optional(),status:Ct.default("unknown"),bindings:r.z.array(Nt).max(40).default([]),workspace:r.z.object({cwd:r.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional()})),Ot=r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),sessions:r.z.array(zt).default([]),updatedAt:r.z.string().datetime()}).strict(),xt=r.z.object({revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:Ot}).strict();Ot.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:r.z.number().int().min(0).optional()});const Dt=r.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Rt=r.z.object({instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),snapshotRevision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),eventSeq:r.z.number().int().min(1),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:Dt}).strict(),jt=r.z.object({meta:Rt,snapshot:Ot}).strict(),Pt=r.z.object({meta:Rt,session:zt}).strict(),Mt=r.z.object({meta:Rt,sessionId:r.z.string().trim().min(1).max(160),tombstone:zt.optional(),expiresAt:r.z.string().datetime().optional()}).strict(),$t=r.z.object({instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(Tt),payload:jt}).strict(),r.z.object({type:r.z.literal(Et),payload:Pt}).strict(),r.z.object({type:r.z.literal(_t),payload:Mt}).strict()])).default([])}).strict();function qt(e,t=(new Date).toISOString()){const n=e.map(e=>Lt(e));return Ot.parse({runningCount:n.filter(e=>"running"===e.status).length,problemCount:n.filter(e=>"failed"===e.status).length,sessions:n,updatedAt:t})}function Lt(e){const t=Ut("string"==typeof e.status?e.status:void 0),n="string"==typeof e.id?e.id:"",s=function(e){const t=Wt(e.workspace),n=Wt(e.tty),s=Wt(e.launch),i=Wt(e.ai),a=Wt(i.claude),r=Vt(t.cwd)||Vt(n.cwd)||Vt(a.cwd)||Vt(s.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return zt.parse({...a,status:t,bindings:Gt([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=Nt.safeParse(e);return t.success?[t.data]:[]}):[]),...n?[{type:"app-session",id:n}]:[],...Ft(e)]),...s?{workspace:s}:{}});var r}function Ut(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":Ct.options.includes(t)?t:"unknown"}function Bt(e){return"app-session"===e.type?`app:${e.id}`:"provider-session"===e.type?`provider:${e.agent||""}:${e.id}`:`adapter:${e.adapter||""}:${e.key||e.id}`}function Ft(e){const t=Wt(e.ai),n=Wt(t.claude),s=Wt(t.appServer),i=[],a=Vt(n.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Vt(n.providerSessionId);r&&i.push({type:"provider-session",agent:"claude",id:r});const o=Array.isArray(t.threadIds)?t.threadIds.filter(e=>"string"==typeof e&&Boolean(e.trim())):[],d=Vt(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Vt(s.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Ht(e){const t="string"==typeof e.id?e.id:"",n=Wt(e.ai),s=Vt(Wt(n.claude).short);return[t?`app:${t}`:"",s?`claude-short:${s}`:""]}function Gt(e){const t=new Map;for(const n of e)t.set(Bt(n),n);return[...t.values()]}function Wt(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Vt(e){return"string"==typeof e&&e.trim()?e.trim():""}function Kt(e=(new Date).toISOString()){return Ot.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function Jt(e,t,n,s){const i=s&&"object"==typeof s?s:{};return{nodeId:e.id,route:"string"==typeof i.route?i.route:t,method:n,code:"string"==typeof i.code?i.code:"NODE_AGENT_REQUEST_FAILED",message:s instanceof Error?s.message:String(s),statusCode:"number"==typeof i.statusCode?i.statusCode:void 0,issues:Array.isArray(i.issues)?i.issues:void 0}}class Qt{requestImpl;logger;constructor(e){this.requestImpl=e.request,this.logger=e.logger}async request(e,t,n={}){const s=n.method||"GET";let i,a;try{i=await this.requestImpl(e,t,n)}catch(n){throw this.logger?.warn?.({nodeId:e.id,route:t,method:s,error:n instanceof Error?n.message:String(n),errorCode:"NODE_AGENT_TRANSPORT_FAILED"},"node agent transport failed"),n}try{a=await i.json()}catch(n){this.logger?.warn?.({nodeId:e.id,route:t,method:s,statusCode:i.status,error:n instanceof Error?n.message:String(n),errorCode:"NODE_AGENT_RESPONSE_INVALID_JSON"},"node agent response was not valid json");const a=new Error(`Node agent ${e.id} returned invalid JSON for ${t}.`);throw Object.assign(a,{statusCode:502,code:"NODE_AGENT_RESPONSE_INVALID_JSON",nodeId:e.id,route:t}),a}if(!i.ok){const n=new Error(a.error?.message||`Node agent request failed with HTTP ${i.status}`);throw Object.assign(n,{statusCode:i.status,code:a.error?.code||"NODE_AGENT_REQUEST_FAILED",nodeId:e.id,route:t}),this.logger?.warn?.({nodeId:e.id,route:t,method:s,statusCode:i.status,errorCode:a.error?.code||"NODE_AGENT_REQUEST_FAILED",error:n.message},"node agent request failed"),n}return this.logger?.info?.({nodeId:e.id,route:t,method:s,statusCode:i.status},"node agent request ok"),a.data??a}async requestSchema(e,t,n,s={}){return this.parse(e,t,n,await this.request(e,t,s),s.method||"GET")}parse(e,t,n,s,i="GET"){const a=n.safeParse(s);if(a.success)return a.data;this.logger?.warn?.({nodeId:e.id,route:t,method:i,errorCode:"NODE_AGENT_PROTOCOL_INVALID",issues:a.error.issues},"node agent protocol response did not match schema");const r=new Error(`Node agent ${e.id} returned invalid protocol data for ${t}.`);throw Object.assign(r,{statusCode:502,code:"NODE_AGENT_PROTOCOL_INVALID",nodeId:e.id,route:t,issues:a.error.issues}),r}}class Yt{client;constructor(e){this.client=e}request(e,t,n={}){return this.client.request(e,t,n)}health(e){return this.client.requestSchema(e,"/health",Pe)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",Se,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",ve,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",r.z.array(ve))}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",Me,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t&&"object"==typeof t?t:{})})}connectRemote(e,t){return this.client.requestSchema(e,"/remotes/connect",qe,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",r.z.array($e))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,Le,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",r.z.array(Ue))}listFolderTree(e,t={}){const n=new URLSearchParams;t.path&&n.set("path",t.path),void 0!==t.depth&&n.set("depth",String(t.depth));const s=n.toString();return this.client.requestSchema(e,"/folders/tree"+(s?`?${s}`:""),r.z.array(Be))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",r.z.array(_e))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}async requireLocalFolder(e,t){const n=(await this.listLocalFolders(e)).find(e=>e.id===t);if(!n){const n=new Error(`Local folder ${t} was not found on node ${e.id}.`);throw Object.assign(n,{statusCode:404,code:"NODE_LOCAL_FOLDER_NOT_FOUND",nodeId:e.id,route:"/local-folders"}),n}return n}listRuntimes(e){return this.client.requestSchema(e,"/runtimes",r.z.array(je))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",je,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateRuntime(e,t,n){const s=`/runtimes/${encodeURIComponent(t)}`;return this.client.requestSchema(e,s,je,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}checkRuntime(e,t){const n=`/runtimes/${encodeURIComponent(t)}/check`;return this.client.requestSchema(e,n,je,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,Le,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,Le,{method:"DELETE"})}async listFleetRuntimes(e){return(await Promise.allSettled(e.map(async e=>({node:e,runtimes:await this.listRuntimes(e)})))).reduce((t,n,s)=>{if("fulfilled"===n.status)t.items.push(...n.value.runtimes);else{const i=e[s];i&&t.nodeErrors.push(Jt(i,"/runtimes","GET",n.reason))}return t},{items:[],nodeErrors:[]})}listInstances(e){return this.listInstancesWithDiagnostics(e).then(e=>e.items)}async listInstancesWithDiagnostics(e){return this.client.requestSchema(e,"/instances",r.z.array(r.z.unknown())).then(t=>t.reduce((t,n)=>{const s=this.normalizeInstance(e,n);return s.instance&&t.items.push(s.instance),s.error&&t.nodeErrors.push(s.error),t},{items:[],nodeErrors:[]}))}createInstance(e,t){return this.client.requestSchema(e,"/instances",Ve,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,n){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Ve,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,Le,{method:"POST"})}startInstance(e,t,n={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Ve,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Ve,{method:"POST"})}restartInstance(e,t,n={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Ve,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}proxyRawInstance(e,t,n){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/proxy/raw`,Fe,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}async listFleetInstances(e){return(await Promise.allSettled(e.map(async e=>({node:e,result:await this.listInstancesWithDiagnostics(e)})))).reduce((t,n,s)=>("fulfilled"===n.status?(t.items.push(...n.value.result.items),t.nodeErrors.push(...n.value.result.nodeErrors)):t.nodeErrors.push(Jt(e[s],"/instances","GET",n.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const n="/instances",s=(i=t,Ve.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,n=new Set(Object.keys(Ve.shape)),s={};for(const[e,i]of Object.entries(t))n.has(e)&&(s[e]=i);return s.apps=Ke(t.apps,["runningCount","problemCount","updatedAt","revision"]),s.config=Ke(t.config,["autoImportAgentConfigs"]),s.receiver=Ke(t.receiver,["status","pendingCount"]),s.runtime=Ke(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),s.target=Ke(t.target??t.endpoints,["strategy","web","api","status"]),s.access=Ke(t.access,["strategy","web","api","ws","status"]),s}(i)));var i;if(s.success)return s.data.protocolVersion&&s.data.protocolVersion!==ue?(this.client.logger?.warn?.({nodeId:e.id,instanceId:s.data.id,expectedProtocolVersion:ue,actualProtocolVersion:s.data.protocolVersion,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node instance protocol version mismatch"),{error:{nodeId:e.id,route:n,method:"GET",code:"PROTOCOL_VERSION_MISMATCH",message:`Node instance ${s.data.id} protocol version ${s.data.protocolVersion} does not match ${ue}.`,statusCode:502}}):{instance:s.data};const a=t&&"object"==typeof t?t:{},r="string"==typeof a.id?a.id:void 0;return this.client.logger?.warn?.({nodeId:e.id,instanceId:r,errorCode:"NODE_INSTANCE_PAYLOAD_INVALID",issues:s.error.issues.map(e=>({path:e.path.join("."),message:e.message}))},"node instance payload invalid"),{error:{nodeId:e.id,route:n,method:"GET",code:"NODE_INSTANCE_PAYLOAD_INVALID",message:r?`Node instance ${r} payload is invalid.`:"Node instance payload is invalid.",statusCode:502,issues:s.error.issues}}}}function Xt(){return(new Date).toISOString()}function Zt(e){return e?e instanceof Headers?Object.fromEntries(e.entries()):Array.isArray(e)?Object.fromEntries(e.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(e).map(([e,t])=>[e,String(t)])):{}}function en(e){const t=String(e||"").split(":");if(3===t.length&&"ai"===t[1]&&t[0]&&t[2])return{instanceId:t[0],aiSessionId:t[2]}}function tn(e){return e instanceof Error?e.message:String(e)}function nn(e,t){const n=new Error(t);throw Object.assign(n,{statusCode:404,code:e}),n}class sn{requireNode;nodeAgentGateway;nodeAgentRequest;fetchImpl;constructor(e){this.requireNode=e.requireNode,this.nodeAgentGateway=e.nodeAgentGateway,this.nodeAgentRequest=e.nodeAgentRequest,this.fetchImpl=e.fetchImpl}async request(e,t,n={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_UNREACHABLE"}),t}const s=this.requireNode(e.nodeId),i=await this.nodeAgentRequest(s,`/instances/${encodeURIComponent(e.id)}/proxy`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({path:`/api/${t.replace(/^\/+/,"")}`,method:n.method||"GET",headers:Zt(n.headers),body:(a=n.body,"string"==typeof a?a:null==a?void 0:String(a))})});var a;const r=await i.json().catch(()=>({}));if(!i.ok){const n=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,n="string"==typeof t.code&&t.code.trim()?t.code:void 0,s="string"==typeof t.message&&t.message?t.message:void 0;return n||s?{code:n,message:s}:void 0}(r.error),s=n?.message||function(e){if(!e||"object"!=typeof e||Array.isArray(e))return"";const t=e.message;return"string"==typeof t?t:""}(r.error)||`Instance request failed with HTTP ${i.status}`,a=new Error(s);throw Object.assign(a,{statusCode:i.status,code:n?.code||"INSTANCE_REQUEST_FAILED",instanceId:e.id,nodeId:e.nodeId,route:t}),a}return r.data??r}async proxyHttp(e,t,n={}){if(!e.target.web){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const s=this.requireNode(e.nodeId),i=await this.nodeAgentGateway.proxyRawInstance(s,e.id,{path:t,method:n.method||"GET",headers:Zt(n.headers),...(a=n.body,null==a?{}:"string"==typeof a?{body:a}:Buffer.isBuffer(a)?{bodyBase64:a.toString("base64")}:a instanceof Uint8Array?{bodyBase64:Buffer.from(a).toString("base64")}:{body:String(a)})});var a;return{status:i.status||502,headers:i.headers||{},body:Buffer.from(i.bodyBase64||"","base64")}}proxyWebSocket(e,t,n,s,i,a={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const r=this.requireNode(e.nodeId),o=s.startsWith("/")?s.slice(1):s;t.proxyWebSocket(r,n,`/instances/${encodeURIComponent(e.id)}/proxy/ws/${o}`,i,a)}async reportHeartbeat(e,t,n){if(!e.registrationToken)return;const s=this.requireNode(e.nodeId),i=`/instances/${encodeURIComponent(e.id)}/heartbeat`,a={method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.registrationToken}`},body:JSON.stringify(t)},r="reverse-wss"===s.connectionMode?await(n?.request(s,i,a)):await this.fetchImpl(`${(s.controlEndpoint||s.endpoint||"").replace(/\/$/,"")}/api/node-agent${i}`,a);if(!r)return;const o=await r.json().catch(()=>({}));if(!r.ok){const e=new Error(o.error?.message||`Instance heartbeat sync failed with HTTP ${r.status}`);throw Object.assign(e,{statusCode:r.status,code:"INSTANCE_HEARTBEAT_SYNC_FAILED"}),e}}}function an(e){if(!e||"object"!=typeof e)return;const t=e,n=ye.safeParse(t.build);return{..."boolean"==typeof t.ok?{ok:t.ok}:{},..."string"==typeof t.role?{role:t.role}:{},..."string"==typeof t.nodeId?{nodeId:t.nodeId}:{},..."string"==typeof t.protocolVersion?{protocolVersion:t.protocolVersion}:{},...n.success?{build:n.data}:{},..."string"==typeof t.serverTime?{serverTime:t.serverTime}:{}}}function rn(e){const{agent:t,...n}=e.capabilities,{secret:s,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=an(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...n,agent:r}:n}}function on(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function dn(e){return function(e){const{registrationToken:t,target:n,agentStatus:s,targetStatus:i,uiAccessStatus:a,...r}=e;return r}(function(e){const t=`/instances/${encodeURIComponent(e.id)}`,n={strategy:"control-plane-proxy",web:`${t}/`,api:`${t}/api`,ws:`${t}/api`,status:"online"===e.connectionStatus||"online"===e.agentStatus?"reachable":"endpoint-unreachable"};return{...e,access:n}}(e))}function cn(e){const{key:t,...n}=e;return{...n,keyPreview:(s=e.key,s.length<=8?"set":`${s.slice(0,4)}...${s.slice(-4)}`),keySet:!0};var s}class ln{read(e){const t=new Map(e.projects.map(e=>[e.id,e])),n=new Map(e.images.map(e=>[e.id,e])),s=new Map(e.nodes.map(e=>[e.id,e])),i=new Map(e.runtimes.map(e=>[`${e.nodeId}:${e.id}`,e])),a=Date.now();return{items:e.instances.map(e=>function(e){const t=e.instance.lastHeartbeatAt?e.currentTime-Date.parse(e.instance.lastHeartbeatAt):void 0,n=void 0!==t&&t>3e4?"offline":e.instance.connectionStatus,s=dn(e.instance);return{...s,aiSessions:G(s.aiSessions),connectionStatus:n,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?rn(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===ue}}({instance:e,currentTime:a,project:e.projectId?t.get(e.projectId):void 0,image:e.imageId&&n.get(e.imageId)||e.imageSnapshot,node:s.get(e.nodeId),runtime:i.get(`${e.nodeId}:${e.runtimeId}`)})),nodeErrors:e.nodeErrors||[]}}}const pn=r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Re.shape.id.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:Re.shape.createdAt.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:Re.shape.createdAt.optional()}).strict().optional()}).strict(),un=r.z.object({id:Oe.shape.id.optional(),name:Oe.shape.name,source:Ce,defaultImageId:Oe.shape.defaultImageId,defaultNodeId:Oe.shape.defaultNodeId,defaultRuntimeId:Oe.shape.defaultRuntimeId,modelSelection:Oe.shape.modelSelection.optional(),workspacePolicy:Ne.optional(),labels:Oe.shape.labels.optional()}).strict(),gn=un.omit({id:!0}).partial().strict(),hn=r.z.object({id:Ve.shape.id.optional(),name:Ve.shape.name.optional(),projectId:Ve.shape.projectId,source:Ce.optional(),sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).optional(),nodeId:Ve.shape.nodeId.optional(),runtimeId:Ve.shape.runtimeId.optional(),imageId:Ve.shape.imageId,config:Ve.shape.config.optional()}).strict(),mn=r.z.object({name:Ve.shape.name.optional()}).strict(),fn=r.z.object({publicBaseUrl:r.z.string().trim().max(2048).optional(),updateChannel:be.default("stable")}).strict(),In=fn.partial().strict(),yn=r.z.object({id:xe.shape.id.optional(),name:xe.shape.name,endpoint:xe.shape.endpoint,key:xe.shape.key,model:xe.shape.model,app:xe.shape.app,enabled:xe.shape.enabled.optional(),order:xe.shape.order.optional(),labels:xe.shape.labels.optional()}).strict(),bn=yn.omit({id:!0}).partial().strict(),wn=r.z.object({id:De.shape.id.optional(),name:De.shape.name,image:De.shape.image,registry:De.shape.registry.optional(),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).optional(),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).optional(),defaultEnv:De.shape.defaultEnv.optional(),labels:De.shape.labels.optional()}).strict(),An=wn.omit({id:!0}).partial().strict(),Sn=r.z.object({id:Re.shape.id.optional(),name:Re.shape.name,connectionMode:Re.shape.connectionMode.optional(),auth:pn.optional(),joinToken:r.z.string().trim().min(1).max(4096).optional(),endpoint:Re.shape.endpoint,controlEndpoint:Re.shape.controlEndpoint,containerEndpoint:Re.shape.containerEndpoint,publicWebBase:Re.shape.publicWebBase,status:r.z.enum(["unknown","online","offline","degraded"]).optional(),health:r.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:Re.shape.capabilities.optional(),labels:Re.shape.labels.optional(),lastSeenAt:Re.shape.lastSeenAt}).strict(),vn=Sn.omit({id:!0,joinToken:!0}).partial().strict(),kn=r.z.object({controlPlaneUrl:r.z.string().trim().url().max(2048),joinToken:r.z.string().trim().min(1).max(4096),controlPlaneName:r.z.string().trim().min(1).max(160).optional(),activate:r.z.boolean().optional()}).strict(),Tn=r.z.object({nodeName:r.z.string().trim().min(1).max(160).optional(),expiresInMs:r.z.number().int().positive().max(36e5).optional()}).strict(),En=r.z.object({name:Qe.shape.name.optional(),channel:Qe.shape.channel.optional(),enabled:r.z.boolean().optional(),token:Qe.shape.token,defaultChatId:Qe.shape.defaultChatId,allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Qe.shape.pollIntervalMs.optional(),settings:Qe.shape.settings.optional()}).strict(),_n=En.extend({channel:Qe.shape.channel,name:Qe.shape.name.optional()}),Cn=ae.extend({id:ae.shape.configHash}),Nn=r.z.object({name:ae.shape.name,description:ae.shape.description,source:ne,action:se,policy:ie.partial().optional()}).strict(),zn=r.z.object({instanceIds:r.z.array(Ve.shape.id).min(1),target:re.optional(),enabled:r.z.boolean().optional()}).strict(),On=r.z.object({configHash:ae.shape.configHash,enabled:r.z.boolean().optional()}).strict();class xn{triggers;listInstances;requireInstance;instanceRequest;constructor(e){this.triggers=e.triggers,this.listInstances=e.listInstances,this.requireInstance=e.requireInstance,this.instanceRequest=e.instanceRequest}async listTriggers(){const e=await this.listInstances(),t=new Map;for(const e of this.triggers.list())t.set(e.configHash,{configHash:e.configHash,config:e,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!0,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]});for(const n of e){const e=n.triggers;for(const s of e?.configs||[]){const i=t.get(s.configHash)||{configHash:s.configHash,config:s.config,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!1,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]},a=(s.deployments||[]).map(e=>({instanceId:n.id,instanceName:n.name,deployment:e,runtime:(s.runtime||[]).find(t=>t.deploymentId?t.deploymentId===e.deploymentId:t.configHash===e.configHash)}));i.deploymentCount+=a.length,i.enabledCount+=a.filter(e=>Boolean(e.deployment.enabled)).length,i.controlPlaneDeploymentCount+=a.filter(e=>"control-plane"===e.deployment.origin).length,i.runningCount+=(s.runtime||[]).filter(e=>"running"===e.status).length,i.errorCount+=(s.runtime||[]).filter(e=>"error"===e.status).length,i.deployments.push(...a),i.recentRuns.push(...(e.recentRuns||[]).filter(e=>e.configHash===s.configHash).map(e=>({...e,instanceId:n.id,instanceName:n.name}))),t.set(s.configHash,i)}}return{updatedAt:(new Date).toISOString(),triggers:[...t.values()].sort((e,t)=>String(e.config.name||e.configHash).localeCompare(String(t.config.name||t.configHash)))}}createTrigger(e){const t=Nn.parse(e||{}),n=Xt(),s={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=pe({source:e.source,action:e.action,policy:ie.parse(e.policy||{})});return`trg_${o.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:s}),a=Cn.parse({id:i,configHash:i,name:t.name,description:t.description,source:t.source,action:t.action,policy:s,createdAt:n,updatedAt:n});return this.triggers.put(a)}async deleteTrigger(e){const t=this.triggers.delete(e),n=await this.listInstances(),s=[];for(const t of n){const n=t.triggers?.configs?.find(t=>t.configHash===e);if(!n)continue;const i=(n.deployments||[]).filter(e=>"control-plane"===e.origin);if(!i.length)continue;const a=n.deployments.every(e=>"control-plane"===e.origin);for(const n of i)try{const i=n.deploymentId?`/triggers/${encodeURIComponent(e)}/deployments/${encodeURIComponent(n.deploymentId)}`:a?`/triggers/${encodeURIComponent(e)}`:void 0;if(!i){s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!1,skipped:"missing-deployment-id"});continue}const r=await this.instanceRequest(t,i,{method:"DELETE"});if(s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!0,data:r}),!n.deploymentId)break}catch(i){s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!1,error:i instanceof Error?i.message:String(i)})}}return t||s.length||nn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:s}}async applyTrigger(e,t){const n=this.requireTrigger(e),s=zn.parse(t||{}),i=[];for(const e of s.instanceIds){const t=await this.requireInstance(e);i.push({instanceId:e,data:await this.instanceRequest(t,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:n.name,description:n.description,source:n.source,action:n.action,policy:n.policy,deployment:{origin:"control-plane",enabled:s.enabled??!0,target:s.target}})})})}return{configHash:e,results:i}}async listInstanceTriggers(e){const t=await this.requireInstance(e);return this.instanceRequest(t,"/triggers")}async bindAiSessionTrigger(e,t,n){const s=On.parse(n||{}),i=this.requireTrigger(s.configHash),a=await this.requireInstance(e);return this.instanceRequest(a,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:i.name,description:i.description,source:i.source,action:i.action,policy:i.policy,deployment:{deploymentId:Dn(t,s.configHash),origin:"control-plane",enabled:s.enabled??!0,target:{type:"ai-session",aiSessionId:t}}})})}async unbindAiSessionTrigger(e,t,n){const s=await this.requireInstance(e),i=Dn(t,n);return this.instanceRequest(s,`/triggers/${encodeURIComponent(n)}/deployments/${encodeURIComponent(i)}`,{method:"DELETE"})}async runInstanceTrigger(e,t,n={}){const s=await this.requireInstance(e);return this.instanceRequest(s,`/triggers/${encodeURIComponent(t)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n||{})})}requireTrigger(e){const t=this.triggers.get(e);return t||nn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function Dn(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}class Rn{records=new Map;options;constructor(e){this.options=e}create(e){const t=o.randomBytes(this.options.tokenBytes).toString("base64url"),n={...e,token:t,expiresAt:new Date(Date.now()+(e.ttlMs||this.options.ttlMs)).toISOString()};return this.records.set(t,n),n}resolve(e,t){const n=this.records.get(e);if(!n)throw this.invalidError();if(n.expiresAt<=(new Date).toISOString())throw this.records.delete(e),this.invalidError();if(t&&!t(n))throw this.invalidError();return n}invalidError(){const e=new Error(this.options.invalidMessage);return Object.assign(e,{statusCode:this.options.invalidStatusCode||400,code:this.options.invalidCode}),e}}class jn{requireInstance;listAppSessions;tokens=new Rn({invalidMessage:"App access link is invalid or expired.",invalidCode:"APP_ACCESS_TOKEN_INVALID",invalidStatusCode:401,tokenBytes:24,ttlMs:9e5});constructor(e){this.requireInstance=e.requireInstance,this.listAppSessions=e.listAppSessions}createToken(e){return this.tokens.create({instanceId:e.instanceId,sessionId:e.sessionId,mode:e.mode,ttlMs:e.ttlMs})}resolveToken(e,t){return this.tokens.resolve(e,e=>!t||e.mode===t)}async proxyTarget(e,t,n=""){const s=this.resolveToken(e,t),i=await this.requireInstance(s.instanceId),a=await this.listAppSessions(),r=a.instances.find(e=>e.instanceId===s.instanceId)?.appSessions.sessions.find(e=>$n(e.id)===s.sessionId);if(!r){const e=new Error("App session was not found.");throw Object.assign(e,{statusCode:404,code:"APP_SESSION_NOT_FOUND"}),e}return{access:s,instance:i,session:r,path:Pn(t,s.sessionId,r,n)}}}function Pn(e,t,n,s=""){return"tty"===e?`/api/apps/sessions/${encodeURIComponent(t)}/tty`:"vnc"===e?s?function(e,t){const n=`/${t.replace(/^\/+/,"")}`,s=n.split("?",1)[0]||"/",i=encodeURIComponent(e);if(!["/api/novnc/",`/api/apps/sessions/${i}/web/`,`/api/apps/sessions/${i}/vnc`,`/api/apps/sessions/${i}/novnc/`].some(e=>s===e.replace(/\/$/,"")||s.startsWith(e))){const e=new Error("App access token cannot be used for this path.");throw Object.assign(e,{statusCode:403,code:"APP_ACCESS_PATH_FORBIDDEN"}),e}return n}(t,s):$n(Mn(n.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:$n(Mn(n.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Mn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function $n(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class qn{tokens=new Rn({invalidMessage:"Chat action expired.",invalidCode:"CHAT_ACTION_TOKEN_INVALID",tokenBytes:9,ttlMs:6e5});create(e){return this.tokens.create({type:e.type,instanceId:"instanceId"in e?e.instanceId:void 0,appId:"appId"in e?e.appId:void 0,routeId:"routeId"in e?e.routeId:void 0,decision:"decision"in e?e.decision:void 0,ttlMs:e.ttlMs})}resolve(e,t){return this.tokens.resolve(e,e=>!t||e.type===t)}pendingDecisionCallbackData(e,t){return`task_handoff:cp_p:${this.create({type:"pending-decision",routeId:e,decision:t}).token}`}}function Ln(e){const{token:t,...n}=e;return{...n,settings:Bn(e.settings),tokenSet:Boolean(e.token)}}function Un(e,t){if(!t)return e;const n={...e};for(const[e,s]of Object.entries(t))void 0!==s&&""!==s&&(n[e]=s);return n}function Bn(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}class Fn{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(Ln)}require(e){const t=this.chatBridges.get(e);return t||nn("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=_n.parse(e),n=Xt(),s=Qe.parse({...t,id:rt(`chat_${t.channel}`),name:t.name||(i=t.channel,a=this.chatBridges.list().filter(e=>e.channel===t.channel).length+1,`${"telegram"===i?"Telegram":"wechat"===i?"WeChat":"dingding"===i?"DingDing":"Web"} ${a}`),enabled:t.enabled??!1,allowedUserIds:t.allowedUserIds||[],pollIntervalMs:t.pollIntervalMs||3e3,settings:Un({},t.settings),createdAt:n,updatedAt:n});var i,a;return this.chatBridges.put(s),Ln(s)}update(e,t){const n=En.parse(t),s=this.require(e),i=Un(s.settings,n.settings),a=Qe.parse({...s,...n,id:e,channel:s.channel,name:n.name||s.name,settings:i,updatedAt:Xt()});return this.chatBridges.put(a),Ln(a)}delete(e){const t=this.chatBridges.delete(e);if(t)for(const t of this.chatSessions.list())t.bridgeId===e&&this.chatSessions.delete(t.id);return t}}class Hn{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||nn("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=Xt(),n=(s=e.channel,i=e.chatSessionId,`${e.bridgeId||s}:${i.replace(/[^a-zA-Z0-9_.:-]/g,"_")}`);var s,i;const a=this.chatSessions.get(n),r=Ye.parse({...a||{},...e,id:n,channel:e.channel,bridgeId:e.bridgeId,chatSessionId:e.chatSessionId,lastUsedAt:t,createdAt:a?.createdAt||t,updatedAt:t});return this.chatSessions.put(r),r}}function Gn(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function Wn(e,t=40){return Gn(e.slice(0,t).map(e=>[e]))}function Vn(e){return Buffer.from(e,"utf8").toString("base64url")}function Kn(e){const t=Qn(e).inline_keyboard,n=Array.isArray(t)?t:[],s=[];for(const e of n)if(Array.isArray(e))for(const t of e){const e=Qn(t),n=String(e.callback_data||""),i=String(e.text||"").trim();i&&n&&s.push({text:i,id:`th_cb_${Vn(n)}`})}return s.slice(0,40)}function Jn(e,t){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function Qn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const Yn=new Set(["stopped","failed","exited","closed","terminated"]);function Xn(e){return!function(e){return Yn.has(Ut(e))}(e)}function Zn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const n=(e.apps?.sessions||[]).filter(as);if(t.appSessionId&&n.some(e=>e.id===t.appSessionId))return!0;const s=new Set(t.appBindingKeys||[]);return Boolean(s.size&&n.some(e=>function(e){return[...Lt(e).bindings.map(Bt),...Ht(e)].filter(Boolean)}(e).some(e=>s.has(e))))}(e,t))}function es(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function ts(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function ns(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",n=t?` - ${Jn(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${n}`}function ss(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return Wn(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Jn(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Jn(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Jn(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function is(e){const t=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,n="approval"===e.kind?`/approve ${e.id} | /deny ${e.id} | /skip ${e.id}`:`/reply ${e.id} <message>`;return`[${t}] ${e.kind} ${e.id}: ${e.result}\n${n}`}function as(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return Xn("string"==typeof t.status?t.status:void 0)}class rs{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleAiSessionsCommand(e){const t=await this.targets.aiSessionChoices(e),n=t.find(e=>e.active),s=t.map(e=>function(e){const t=e.session.userPrompt||e.session.summary||e.session.lastMessage||"",n=e.active?"* ":"",s=e.session.phase&&"unknown"!==e.session.phase?`/${e.session.phase}`:"";return`${n}${e.index+1}. ${e.projectName} / ${e.instance.name} - ${e.session.agent} - ${e.session.status}${s} - ${e.session.id}${t?` - ${t}`:""}`}(e));return{accepted:!0,routed:!1,binding:e,instances:t.map(e=>e.instance),reply:t.length?["Select AI session",`Current ${n?`${n.projectName} / ${n.instance.name} / ${n.session.id}`:await this.targets.renderChatTarget(e)}, total ${t.length}`,"",...s].join("\n"):"No AI sessions found.",replyMarkup:t.length?Wn(t.map(e=>({text:`${e.active?"✓ ":""}${ns(e)}`,callbackData:`task_handoff:cp_session:${e.index}`}))):void 0}}async handleAiSessionCommand(e,t){const n=String(t[0]||"").trim();if(!n)return this.handleAiSessionsCommand(e);const s=await this.targets.findAiSession(n);if(!s)return{accepted:!1,routed:!1,binding:e,reply:`AI session ${n} was not found. Try /sessions.`};const i=s.instance.projectId?this.deps.getProject(s.instance.projectId):void 0;return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i?.id||e.activeProjectId,activeInstanceId:s.instance.id,activeAiSessionId:s.session.id}),instance:s.instance,aiSession:s.session,reply:`Active AI session: ${s.instance.name} / ${s.session.agent} ${s.session.id}`}}async handleAiSessionIndexAction(e,t){const n=(await this.targets.aiSessionChoices(e)).find(e=>e.index===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"AI session was not found. Use /session to refresh the list.",replyMarkup:void 0};const s=this.deps.upsertChatSession({...e,activeProjectId:n.projectId||e.activeProjectId,activeInstanceId:n.instance.id,activeAiSessionId:n.session.id}),i=await this.handleAiSessionsCommand(s);return{...i,accepted:!0,binding:s,instance:n.instance,aiSession:n.session,message:`selected ${n.session.id}`,reply:[`Current chat is bound to AI session ${n.session.id}`,"",i.reply].join("\n")}}async handleAiSessionInterruptCommand(e){if(!e.activeInstanceId||!e.activeAiSessionId)return{accepted:!1,routed:!1,binding:e,reply:"No active AI session is selected. Use /sessions and /session <id> first."};const t=await this.deps.requireControlledInstance(e.activeInstanceId,!0),n=await this.deps.interruptAiSession(t.id,e.activeAiSessionId);return{accepted:!0,routed:!0,binding:e,instance:dn(t),aiSession:n,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function os(e){return e.providerTurnId||e.turnId||""}function ds(e){return Xn(gs(e.status))}function cs(e,t){const n=gs(t.appId)||"App",s=gs(t.title).trim();if(s&&s!==n)return s;const i=function(e,t){for(const n of function(e){const t=e.apps;return Array.isArray(t)?t.filter(e=>"object"==typeof e&&null!==e&&!Array.isArray(e)):[]}(e.sourceSnapshot))if(gs(n.id)===t)return gs(n.name).trim();return""}(e,n);return i||n}function ls(e){const t=function(e){const t=fs(e.capabilities).apps;if(Array.isArray(t)&&t.length>0)return t;const n=fs(e.image).optionalApps;return Array.isArray(n)&&n.length>0?n:["terminal-tty"]}(e),n=new Map;for(const e of t){const t=hs(e);t&&!n.has(t.id)&&n.set(t.id,t)}return[...n.values()]}function ps(e,t,n,s){const i=gs(t.id)?function(e,t){return`/apps/access/${us(e)}?token=${encodeURIComponent(t())}`}(t,s):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const n=gs(t);if(n&&new RegExp("^https?://","i").test(n))return new URL(e,n.endsWith("/")?n:n+"/").toString()}(i,n);return{text:a||i,url:a}}function us(e){const t=gs(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function gs(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function hs(e){if("string"==typeof e&&e.trim())return{id:e,label:ms(e)};const t=fs(e),n=gs(t.id).trim();return n?{id:n,label:gs(t.name).trim()||ms(n)}:void 0}function ms(e){return{"terminal-tty":"Terminal","gui-terminal":"GUI Terminal",chromium:"Chromium",browser:"Browser","vscode-web":"VS Code"}[e]||e.replace(/[-_]+/g," ").replace(/\b\w/g,e=>e.toUpperCase())}function fs(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}class Is{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleInstancesCommand(e){const t=await this.targets.instancesForBinding(e);return{accepted:!0,routed:!1,binding:e,instances:t,reply:(n=t.length,0===n?"No controlled instances are registered yet.":`Instances: ${n}. Tap an instance to create an app.`),replyMarkup:t.length?Wn(t.map(e=>{return{text:Jn(e.name||e.id,56),callbackData:(t=this.deps.createChatActionToken({type:"instance-app-menu",instanceId:e.id}).token,`task_handoff:cp_i:${t}`)};var t})):void 0};var n}async handleInstanceAppMenuAction(e,t){const n=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const s=ls(n);return{accepted:!0,routed:!1,binding:e,instance:n,message:`Select an app for ${n.name}.`,reply:s.length?`New app for ${n.name}`:`No launchable apps for ${n.name}.`,replyMarkup:s.length?Wn(s.map(e=>{return{text:Jn(e.label,56),callbackData:(t=this.deps.createChatActionToken({type:"launch-app",instanceId:n.id,appId:e.id}).token,`task_handoff:cp_a:${t}`)};var t})):void 0}}async handleLaunchAppAction(e,t,n){const s=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const i=ls(s).find(e=>e.id===n);return i?{accepted:!0,routed:!1,binding:e,instance:s,session:await this.deps.launchAppSession(s.id,i.id),message:`${i.label} launched`,reply:`Launched ${i.label} on ${s.name}.`}:{accepted:!1,routed:!1,binding:e,reply:`App ${n} is not launchable for ${s.name}.`}}async handleAppsCommand(e){const t=await this.deps.boardAsync(),n=new Map(t.map(e=>[e.id,e])),s=(await this.deps.listAppSessions()).instances.flatMap(e=>{const t=n.get(e.instanceId);return t?e.appSessions.sessions.filter(ds).filter(e=>Boolean(gs(e.id))).map(e=>({instance:t,session:e,link:ps(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:gs(e.id)||"",mode:us(e)}).token)})):[]}),i=s.filter(e=>e.link.url).map(e=>({text:Jn(`${e.instance.name} · ${cs(e.instance,e.session)}`,56),url:e.link.url}));return{accepted:!0,routed:!1,binding:e,instances:t,reply:(a=s.length,r=i.length,0===a?"No app sessions found.":0===r?`App sessions: ${a}. Set the control plane public URL in Settings > Appearance to open them from chat.`:`App sessions: ${a}. Tap a button to open.`),replyMarkup:i.length?Wn(i):void 0};var a,r}}class ys{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async forwardChatMessage(e,t){const n=t.target?.instanceId||e.activeInstanceId||"",s=t.target?.aiSessionId||e.activeAiSessionId,i=await this.deps.requireControlledInstance(n,!0),a=i.projectId?this.deps.requireProject(i.projectId):void 0;if("online"!==i.connectionStatus){const e=new Error(`Instance ${i.name} is not reachable.`);throw Object.assign(e,{statusCode:409,code:"CHAT_TARGET_UNREACHABLE"}),e}if(s){const n=await this.trySendAiSessionMessage(e,i.id,s,t.message.text,t.message.attachments,!t.target);return n.ok?this.sentResult(e,a,i,s,n.aiSession):this.failedSendResult(n.binding,a,i,n.reply)}const r=await this.targets.defaultAiSessionIdForInstance(i.id);if(r){const n=this.deps.upsertChatSession({...e,activeAiSessionId:r}),s=await this.trySendAiSessionMessage(n,i.id,r,t.message.text,t.message.attachments);return s.ok?this.sentResult(n,a,i,r,s.aiSession):this.failedSendResult(s.binding,a,i,s.reply)}return{accepted:!1,routed:!1,binding:e,project:a||void 0,instance:dn(i),reply:`No active AI session is selected for ${i.name}. Use /sessions and /session <id> first.`}}async trySendAiSessionMessage(e,t,n,s,i=[],a=!0){try{return{ok:!0,binding:e,aiSession:await this.deps.sendAiSessionMessage(t,n,s||"请查看附件图片。",void 0,i)}}catch(t){return function(e){return"AI_SESSION_NOT_FOUND"===(e&&"object"==typeof e?e:{}).code}(t)?{ok:!1,binding:a?this.deps.upsertChatSession({...e,activeAiSessionId:void 0}):e,reply:`The selected AI session ${n} no longer exists. Use /session to choose an active AI session.`}:{ok:!1,binding:e,reply:`Failed to send to AI session ${n}: ${tn(t)}`}}}failedSendResult(e,t,n,s){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:dn(n),reply:s}}sentResult(e,t,n,s,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:dn(n),aiSession:i,instanceId:n.id,aiSessionId:s,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:os(i),reply:`Sent to ${t?.name||n.sourceSnapshot.name||"Source"} / ${n.name} / ${s}.`};var a}}class bs{deps;constructor(e){this.deps=e}async handlePendingCommand(e){const t=await this.deps.listPendingRoutes(),n=e.activeInstanceId?t.filter(t=>t.instanceId===e.activeInstanceId):t;return{accepted:!0,routed:!1,binding:e,pending:n,reply:n.length?n.map(is).join("\n"):"No pending routes.",replyMarkup:n.length?ss(n,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,n){const s=en(t[0]);return s?s.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:s,aiSession:await this.deps.resolveAiSessionApproval(s.instanceId,s.aiSessionId,n),reply:`${n} sent for ${s.instanceId}:${s.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:s,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:`Usage: /${"allow"===n?"approve":n} <instance-id:pending-id>`}}async handlePendingReplyCommand(e,t){const n=en(t[0]),s=t.slice(1).join(" ").trim();return n&&s?n.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:n,aiSession:await this.deps.sendAiSessionMessage(n.instanceId,n.aiSessionId,s),reply:`Reply sent for ${n.instanceId}:${n.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:n,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:"Usage: /reply <instance-id:pending-id> <message>"}}}class ws{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleUseCommand(e,t){if(0===t.length)return{accepted:!0,routed:!1,binding:e,instances:await this.targets.instancesForBinding(e),reply:await this.targets.renderUseHelp(e)};if(t.length>=2){const n=this.targets.resolveProject(t[0]),s=await this.targets.resolveInstance(t[1],n.id),i=this.deps.upsertChatSession({...e,activeProjectId:n.id,activeInstanceId:s.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(s.id)});return{accepted:!0,routed:!1,binding:i,reply:await this.targets.renderTargetSelected(i)}}const n=t[0],s=await this.targets.findInstance(n);if(s){const t=s.projectId?this.deps.requireProject(s.projectId):void 0,n=this.deps.upsertChatSession({...e,activeProjectId:t?.id,activeInstanceId:s.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(s.id)});return{accepted:!0,routed:!1,binding:n,reply:await this.targets.renderTargetSelected(n)}}const i=this.targets.resolveProject(n),a=(await this.deps.boardAsync()).filter(e=>e.projectId===i.id),r=a.filter(e=>"running"===e.status&&"online"===e.connectionStatus);if(1===r.length){const t=this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:r[0].id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(r[0].id)});return{accepted:!0,routed:!1,binding:t,reply:await this.targets.renderTargetSelected(t)}}return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:void 0,activeAiSessionId:void 0}),instances:a,reply:`${i.name} selected. Choose an instance with /use ${i.id} <instance-id-or-name>.\n${this.targets.renderInstanceLines(a)}`}}async handleProjectCommand(e,t){if(0===t.length)return{accepted:!1,routed:!1,binding:e,reply:"Usage: /project <project-id-or-name>"};const n=this.targets.resolveProject(t[0]),s=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,i=this.deps.upsertChatSession({...e,activeProjectId:n.id,activeInstanceId:s?.projectId===n.id?s.id:void 0,activeAiSessionId:s?.projectId===n.id?e.activeAiSessionId:void 0});return{accepted:!0,routed:!1,binding:i,reply:`Active project: ${n.name}${i.activeInstanceId?"":". Choose an instance with /use."}`}}}class As{deps;constructor(e){this.deps=e}async instancesForBinding(e){const t=await this.deps.boardAsync();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async fullInstancesForBinding(e){const t=await this.deps.listNodeInstances();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async renderUseHelp(e){return`Active target: ${await this.renderChatTarget(e)}\nUse /instances to list targets, then /use <instance-id-or-name>. Use /sessions to choose an AI session when there is more than one.`}async renderChatStatus(e){const t=e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0;return t?`Active target: ${await this.renderChatTarget(e)}\nStatus: ${t.status}, connection: ${t.connectionStatus}, pending: ${t.receiver.pendingCount}`:`Active target: ${await this.renderChatTarget(e)}`}async renderChatHelp(e){return["TaskHandoff chat commands:","/help - show this help","/status or /target - show the active project, instance, and AI session","/instances - list controlled instances","/apps - list app sessions","/use <instance-id-or-name> - choose an active instance","/use <project-id-or-name> <instance-id-or-name> - choose a project and instance","/project <project-id-or-name> - narrow instance selection to a project","/sessions - list all AI sessions","/session <ai-session-id> - choose an active AI session","/pending - list waiting approvals","/approve <instance-id:pending-id>, /deny <...>, /skip <...> - answer approvals","/reply <instance-id:pending-id> <message> - reply to a waiting session","/cancel - interrupt the active AI session","","Send normal text after selecting a target; if there is only one AI session, it is selected automatically.",`Current target: ${await this.renderChatTarget(e)}`].join("\n")}renderInstanceLines(e){return e.map(e=>`${e.name} (${e.id}) - ${e.status}/${e.connectionStatus}`).join("\n")}async renderChatTarget(e){const t=e.activeProjectId?this.deps.getProject(e.activeProjectId):void 0,n=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,s=e.activeAiSessionId||"none";return`${t?.name||e.activeProjectId||"none"} / ${n?.name||e.activeInstanceId||"none"} / ${s}`}async renderTargetSelected(e){const t=await this.renderChatTarget(e);return e.activeAiSessionId?`Active target: ${t}`:`Active target: ${t}\nNo AI session selected yet. Use /sessions and /session <id>, or send a message once exactly one session is available.`}async defaultAiSessionIdForInstance(e){const t=await this.aiSessionSnapshotForInstance(e),n=(await this.deps.boardAsync()).find(t=>t.id===e),s=(await this.deps.listAppSessions()).instances.find(t=>t.instanceId===e),i=n?{...n,apps:{...n.apps,sessions:s?.appSessions.sessions||[]}}:void 0,a=t&&n?Zn({...i,aiSessions:t}):t?.sessions||[];return 1===a.length?a[0].id:void 0}async aiSessionChoices(e){return(await this.boardInstancesForBinding({},{refresh:!0})).flatMap(t=>Zn(t).map(n=>({index:0,projectId:t.projectId,projectName:t.project?.name||("string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:"Source"),instance:t,session:n,active:e.activeInstanceId===t.id&&e.activeAiSessionId===n.id}))).sort((e,t)=>{const n=es(t.session)-es(e.session);return n||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||ts(e.session).localeCompare(ts(t.session)))}).map((e,t)=>({...e,index:t}))}async boardInstancesForBinding(e,t={}){const n=await this.deps.boardAsync(),s=await this.deps.listAiSessions(t),i=await this.deps.listAppSessions(t),a=new Map(s.instances.map(e=>[e.instanceId,e.aiSessions])),r=new Map(i.instances.map(e=>[e.instanceId,e.appSessions])),o=n.map(e=>{return{...e,apps:{...e.apps,sessions:r.get(e.id)?.sessions||[]},aiSessions:a.get(e.id)||(t=e.aiSessions.updatedAt,{runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:t})};var t});return e.activeProjectId?o.filter(t=>t.projectId===e.activeProjectId):o}async findAiSession(e){const t=await this.boardInstancesForBinding({},{refresh:!0});for(const n of t){const t=(n.aiSessions?.sessions||[]).find(t=>t.id===e||t.providerSessionId===e);if(t)return{instance:n,session:t}}}async aiSessionSnapshotForInstance(e){const t=await this.deps.listAiSessions();return t.instances.find(t=>t.instanceId===e)?.aiSessions}resolveProject(e){const t=this.deps.getProject(e)||this.deps.listProjects().find(t=>t.name===e);return t||nn("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const n=await this.findInstance(e,t);return n||nn("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`),n}async findInstance(e,t){return(await this.deps.listNodeInstances()).find(n=>!(n.id!==e&&n.name!==e||t&&n.projectId!==t))}}class Ss{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new As(e),this.sender=new ys(e,this.targets),this.appCommands=new Is(e,this.targets),this.pendingCommands=new bs(e),this.aiSessionCommands=new rs(e,this.targets),this.targetCommands=new ws(e,this.targets)}async handleChatGatewayMessage(e){const t=Xe.parse(e),n=this.deps.upsertChatSession({channel:t.source.channel,bridgeId:t.source.bridgeId,chatSessionId:t.source.chatSessionId,userId:t.source.userId}),s=t.message.text.trim();return s.startsWith("/")?this.handleChatCommand(n,s):s||t.message.attachments.length?n.activeInstanceId?this.sender.forwardChatMessage(n,t):{accepted:!1,routed:!1,binding:n,reply:await this.targets.renderUseHelp(n)}:{accepted:!1,routed:!1,binding:n,reply:""}}async handleChatGatewayAction(e){const t=this.deps.upsertChatSession({channel:e.source.channel,bridgeId:e.source.bridgeId,chatSessionId:e.source.chatSessionId,userId:e.source.userId});return"ai-session"===e.action.type?this.aiSessionCommands.handleAiSessionIndexAction(t,e.action.index):"instance-app-menu"===e.action.type?this.appCommands.handleInstanceAppMenuAction(t,e.action.instanceId):"launch-app"===e.action.type?this.appCommands.handleLaunchAppAction(t,e.action.instanceId,e.action.appId):"pending-decision"===e.action.type?this.pendingCommands.handlePendingDecisionCommand(t,[e.action.routeId],e.action.decision):{accepted:!1,routed:!1,binding:t,reply:"Unknown action."}}async handleChatCommand(e,t){const[n="",...s]=t.split(/\s+/),i=n.replace(/@[\w_]+$/,"");switch(i.toLowerCase()){case"/help":case"/start":return{accepted:!0,routed:!1,binding:e,reply:await this.targets.renderChatHelp(e)};case"/sessions":return this.aiSessionCommands.handleAiSessionsCommand(e);case"/session":return this.aiSessionCommands.handleAiSessionCommand(e,s);case"/cancel":case"/interrupt":return this.aiSessionCommands.handleAiSessionInterruptCommand(e);case"/pending":return this.pendingCommands.handlePendingCommand(e);case"/approve":return this.pendingCommands.handlePendingDecisionCommand(e,s,"allow");case"/deny":return this.pendingCommands.handlePendingDecisionCommand(e,s,"deny");case"/skip":return this.pendingCommands.handlePendingDecisionCommand(e,s,"skip");case"/reply":return this.pendingCommands.handlePendingReplyCommand(e,s);case"/instances":return this.appCommands.handleInstancesCommand(e);case"/apps":return this.appCommands.handleAppsCommand(e);case"/use":return this.targetCommands.handleUseCommand(e,s);case"/project":return this.targetCommands.handleProjectCommand(e,s);case"/status":case"/target":return{accepted:!0,routed:!1,binding:e,instance:e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0,reply:await this.targets.renderChatStatus(e)};default:return{accepted:!1,routed:!1,binding:e,reply:`Unknown command: ${i||n}. Try /help.`}}}}function vs(e,t){const n=t.filter(e=>e.enabled),s={},i=Ts(n,"codex",e.modelSelection.codexModelId);i&&(s.OPENAI_API_KEY=i.key,s.OPENAI_BASE_URL=i.endpoint,s.CODEX_MODEL=i.model,s.TASK_HANDOFF_CODEX_MODEL=i.model,s.TASK_HANDOFF_CODEX_MODEL_ID=i.id);const a=Ts(n,"claude",e.modelSelection.claudeModelId);return a&&(s.ANTHROPIC_API_KEY=a.key,s.ANTHROPIC_BASE_URL=a.endpoint,s.CLAUDE_MODEL=a.model,s.TASK_HANDOFF_CLAUDE_MODEL=a.model,s.TASK_HANDOFF_CLAUDE_MODEL_ID=a.id),s}function ks(e){const t=e.modelSelection;if(t&&"object"==typeof t&&!Array.isArray(t)){const e=t;return{..."string"==typeof e.codexModelId?{codexModelId:e.codexModelId}:{},..."string"==typeof e.claudeModelId?{claudeModelId:e.claudeModelId}:{}}}return{}}function Ts(e,t,n){const s=e.filter(e=>e.app===t);return s.find(e=>n&&e.id===n)||s[0]}function Es(e){return{enabledCount:e.deployments.filter(e=>e.enabled).length,runningCount:e.runtime.filter(e=>"running"===e.status).length,errorCount:e.runtime.filter(e=>"error"===e.status).length,configs:e.configs.map(t=>({configHash:t.configHash,config:t,deployments:e.deployments.filter(e=>e.configHash===t.configHash),runtime:e.runtime.filter(e=>e.configHash===t.configHash)})),recentRuns:e.recentRuns.slice(0,20)}}const _s="TASK_HANDOFF_NODE_AGENT_V1",Cs=6e4;function Ns(e=""){return o.createHash("sha256").update(e).digest("hex")}function zs(e,t){return o.createHmac("sha256",e).update(function(e){return[_s,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function Os(e){const t=e.timestamp||(new Date).toISOString(),n=e.nonce||o.randomBytes(18).toString("base64url"),s=Ns(e.body||""),i=zs(e.secret,{keyId:e.keyId,method:e.method,pathWithQuery:e.pathWithQuery,timestamp:t,nonce:n,bodySha256:s});return{"x-taskhandoff-node-id":e.nodeId,"x-taskhandoff-key-id":e.keyId,"x-taskhandoff-timestamp":t,"x-taskhandoff-nonce":n,"x-taskhandoff-body-sha256":s,"x-taskhandoff-signature":i}}function xs(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function Ds(e,n=process.platform){if(!function(e,n=process.platform){if("win32"===n)return e.startsWith("\\\\.\\pipe\\");const s=t.statSync(e),i=process.getuid?.();return void 0===i||s.uid===i}(e,n)){const e=new Error("Local node agent IPC socket is not owned by the current user.");throw Object.assign(e,{statusCode:403,code:"NODE_AGENT_IPC_OWNER_INVALID"}),e}}function Rs(e,t,n,s={}){const i=`/api/node-agent${t}`,a={createConnection:()=>g.connect(e),headers:{host:"task-handoff-node-agent.local",...s}};return n&&(Array.isArray(n)?n.length>0:n.trim().length>0)?new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,n,a):new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,a)}const js=r.z.object({id:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),nodeName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),Ps="task-handoff.control-plane.local",Ms="task-handoff.control-plane.builtin";function $s(e){return"true"===e.labels[Ps]}function qs(e){const t=function(e){return"paired-hmac"===e.auth.mode?e.auth.keyId:void 0}(e);if(!t){const t=new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`);throw Object.assign(t,{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"}),t}return t}function Ls(e,t){const n=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(n)return Os({nodeId:e.id,keyId:qs(e),secret:n,method:t.method,pathWithQuery:t.pathWithQuery,body:t.body});const s=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return s?{authorization:`Bearer ${s}`}:{}}class Us{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new qn;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new ln;controlPlaneTriggerService;chatBridgeService;chatSessionService;chatSessionRuntime;aiSessionSnapshotProvider;appSessionSnapshotProvider;constructor(e,t={}){var n;this.paths=e,this.fetchImpl=t.fetchImpl||fetch,this.dockerCommandRunner=t.dockerCommandRunner,this.nodeAgentTransport=t.nodeAgentTransport,this.logger=(n=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(n||"").trim().toLowerCase())?t.logger:void 0);const s=new Qt({request:(e,t,n)=>this.nodeAgentFetch(e,t,n),logger:this.logger});this.nodeAgentGateway=new Yt(s),this.controlledInstanceGateway=new sn({requireNode:e=>this.requireNode(e),nodeAgentGateway:this.nodeAgentGateway,nodeAgentRequest:(e,t,n)=>this.nodeAgentFetch(e,t,n),fetchImpl:this.fetchImpl});const i=e=>({schema:e,logger:(e,t)=>this.logWarn(t,e)});this.projects=new dt(e.projectsDir,i(Oe)),this.models=new dt(e.modelsDir,i(xe)),this.images=new dt(e.imagesDir,i(De)),this.nodes=new dt(e.nodesDir,i(Re)),this.chatSessions=new dt(e.chatSessionsDir,i(Ye)),this.chatBridges=new dt(e.chatBridgesDir,i(Qe)),this.triggers=new dt(e.triggersDir,i(Cn)),this.nodeJoinInvites=new dt(e.nodeJoinInvitesDir,i(js)),this.settings=new ct(e.settingsPath,()=>fn.parse({}),i(fn)),this.controlPlaneTriggerService=new xn({triggers:this.triggers,listInstances:()=>this.listNodeInstances(),requireInstance:async e=>this.requireControlledInstance(e,!0),instanceRequest:(e,t,n)=>this.instanceRequest(e,t,n)}),this.appAccessService=new jn({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Fn({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new Hn({chatSessions:this.chatSessions}),this.chatSessionRuntime=new Ss({upsertChatSession:e=>this.upsertChatSession(e),boardAsync:()=>this.boardAsync(),listAiSessions:e=>this.listAiSessions(e),listAppSessions:e=>this.listAppSessions(e),listNodeInstances:()=>this.listNodeInstances(),listPendingRoutes:()=>this.listPendingRoutes(),pendingDecisionCallbackData:(e,t)=>this.pendingDecisionCallbackData(e,t),createChatActionToken:e=>this.createChatActionToken(e),createAppAccessToken:e=>this.createAppAccessToken(e),controlPlanePublicBaseUrl:()=>this.getSettings().publicBaseUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_URL||process.env.TASK_HANDOFF_PUBLIC_BASE||process.env.TASK_HANDOFF_CONTROL_PLANE_PUBLIC_URL,requireControlledInstance:(e,t)=>this.requireControlledInstance(e,t),requireProject:e=>this.requireProject(e),getProject:e=>this.projects.get(e),listProjects:()=>this.projects.list(),resolveAiSessionApproval:(e,t,n)=>this.resolveAiSessionApproval(e,t,n),sendAiSessionMessage:(e,t,n,s,i)=>this.sendAiSessionMessage(e,t,n,s,i),interruptAiSession:(e,t)=>this.interruptAiSession(e,t),launchAppSession:(e,t,n)=>this.launchAppSession(e,t,n)})}logInfo(e,t){this.logger?.info?.(e,t)}logWarn(e,t){this.logger?.warn?.(e,t)}logError(e,t){this.logger?.error?.(e,t)}setNodeAgentTransport(e){this.nodeAgentTransport=e}setAiSessionSnapshotProvider(e){this.aiSessionSnapshotProvider=e}async listAiSessions(e={}){return this.aiSessionSnapshotProvider?this.aiSessionSnapshotProvider(e):this.bootstrapAiSessionsFromInstances()}setAppSessionSnapshotProvider(e){this.appSessionSnapshotProvider=e}async listAppSessions(e={}){return this.appSessionSnapshotProvider?this.appSessionSnapshotProvider(e):this.bootstrapAppSessionsFromInstances()}init(){this.projects.init(),this.models.init(),this.images.init(),this.nodes.init(),this.chatSessions.init(),this.chatBridges.init(),this.triggers.init(),this.nodeJoinInvites.init(),this.settings.init(),this.seedDefaults()}getSettings(){return fn.parse(this.settings.get())}updateSettings(e){const t=In.parse(e),n=fn.parse({...this.getSettings(),...t,..."publicBaseUrl"in t?{publicBaseUrl:t.publicBaseUrl?.trim()||void 0}:{}});return this.settings.put(n)}seedDefaults(){if(!this.images.get("img_default")){const e=Xt();this.images.put(De.parse({id:"img_default",name:"TaskHandoff Browser + Terminal",image:process.env.TASK_HANDOFF_CONTROLLED_IMAGE||"task-handoff-web:local",registry:"local",capabilities:["browser","terminal","gui-terminal","codex","claude"],optionalApps:["chromium","terminal-tty","gui-terminal","vscode-web"],defaultEnv:{},labels:{"task-handoff.image.kind":"controlled-instance"},createdAt:e,updatedAt:e}))}}listProjects(){return this.projects.list().map(e=>this.normalizeProjectRecord(e))}defaultNodeId(){return this.nodes.list().find($s)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const n="/health",s=t?Ls(t,{method:"GET",pathWithQuery:`/api/node-agent${n}`}):{},i=await this.fetchNodeAgentEndpoint(e,n,{headers:s}),a=await i.json().catch(()=>({}));if(!i.ok){const t=new Error(a.error?.message||`Node agent health check failed with HTTP ${i.status}`);throw Object.assign(t,{statusCode:i.status,code:"NODE_AGENT_HEALTH_FAILED"}),this.logWarn({nodeEndpoint:e,statusCode:i.status,errorCode:"NODE_AGENT_HEALTH_FAILED"},"node agent health check failed"),t}const r="string"==typeof a.data?.nodeId&&a.data.nodeId.trim()?a.data.nodeId.trim():void 0;if(!r){const t=new Error("Node agent health response did not include nodeId.");throw Object.assign(t,{statusCode:502,code:"NODE_AGENT_ID_MISSING"}),this.logWarn({nodeEndpoint:e,payload:a.data,errorCode:"NODE_AGENT_ID_MISSING"},"node agent health response missing node id"),t}if(a.data?.protocolVersion!==ue){const t=function(e,t){const n="string"==typeof t&&t.trim()?t:"missing",s=new Error(`${e} protocol version ${n} is not compatible with ${ue}.`);return Object.assign(s,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),s}(`Node agent ${r}`,a.data?.protocolVersion);throw this.logWarn({nodeId:r,nodeEndpoint:e,expectedProtocolVersion:ue,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=ye.safeParse(a.data?.build);return this.logInfo({nodeId:r,nodeEndpoint:e,protocolVersion:a.data.protocolVersion,build:o.success?o.data:a.data?.build},"node agent health check ok"),{nodeId:r,data:a.data||{}}}async fetchNodeAgentEndpoint(e,t,n={}){const s=xs(e);return s?(Ds(s),async function(e,t,n={}){const s=n.method||"GET",i="string"==typeof n.body||n.body instanceof Buffer?n.body:void 0===n.body||null===n.body?void 0:String(n.body),a=new Headers(n.headers||{});return void 0===i||a.has("content-length")||a.set("content-length",String(Buffer.byteLength(i))),new Promise((n,r)=>{const o=u.request({socketPath:e,method:s,path:`/api/node-agent${t}`,headers:Object.fromEntries(a.entries())},e=>{const t=[];e.on("data",e=>t.push(Buffer.isBuffer(e)?e:Buffer.from(e))),e.on("end",()=>{n(new Response(Buffer.concat(t),{status:e.statusCode||500,headers:e.headers}))})});o.on("error",r),void 0!==i&&o.write(i),o.end()})}(s,t,n)):this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent${t}`,n)}async completeNodeAgentPairing(e,t){const n=await this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent/pairing/complete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({joinToken:t.joinToken,controlPlaneName:t.controlPlaneName||"Control Plane",controlPlaneUrl:t.controlPlaneUrl})}),s=await n.json().catch(()=>({}));if(!n.ok){const e=new Error(s.error?.message||`Node agent pairing failed with status ${n.status}.`);throw Object.assign(e,{statusCode:n.status,code:"NODE_AGENT_PAIRING_FAILED"}),e}if("string"!=typeof s.data?.nodeId||!s.data.nodeId.trim()||"string"!=typeof s.data?.keyId||!s.data.keyId.trim()||"string"!=typeof s.data?.secret||!s.data.secret.trim()){const e=new Error("Node agent pairing response did not include nodeId, keyId, and secret.");throw Object.assign(e,{statusCode:502,code:"NODE_AGENT_PAIRING_RESPONSE_INVALID"}),e}return{nodeId:s.data.nodeId.trim(),keyId:s.data.keyId.trim(),secret:s.data.secret.trim(),pairedAt:"string"==typeof s.data.pairedAt?s.data.pairedAt:Xt()}}async syncLocalNodeConnection(){const e={[Ps]:"true",[Ms]:"true"},t=process.env.TASK_HANDOFF_NODE_AGENT_TOKEN?.trim()||void 0,n=process.env.TASK_HANDOFF_NODE_AGENT_CONTROL_ENDPOINT||process.env.TASK_HANDOFF_NODE_AGENT_ENDPOINT||"http://127.0.0.1:8091",s=function(e){return xs(e)?"local-ipc":"local-loopback"}(n),i=Re.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:s,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:Xt(),updatedAt:Xt()}),a=await this.inspectNodeAgent(n,i),r=Xt(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>$s(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=Re.parse({...o||{},id:a.nodeId,name:o?.name||"Local Node",connectionMode:s,auth:{mode:"local-static-key",secret:t},endpoint:n,controlEndpoint:n,containerEndpoint:process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL,publicWebBase:process.env.TASK_HANDOFF_INSTANCE_PUBLIC_WEB_BASE,status:"online",health:"ok",capabilities:{...o?.capabilities||{},agent:an(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(cn)}listAllModels(){return this.models.list().map(e=>this.normalizeModelRecord(e)).sort((e,t)=>e.order-t.order||e.createdAt.localeCompare(t.createdAt)||e.id.localeCompare(t.id))}createModel(e){const t=yn.parse(e),n=Xt(),s=this.nextModelOrder(),i=xe.parse({...t,id:t.id||rt("model"),enabled:t.enabled??!0,order:t.order??s,labels:t.labels||{},createdAt:n,updatedAt:n});return cn(this.models.put(i))}updateModel(e,t){const n=bn.parse(t),s=this.requireModelSecret(e),i=xe.parse({...s,...n,id:e,key:n.key?.trim()?n.key:s.key,createdAt:s.createdAt,updatedAt:Xt()});return cn(this.models.put(i))}deleteModel(e){const t=this.models.delete(e);if(t)for(const t of this.listProjects())t.modelSelection.codexModelId!==e&&t.modelSelection.claudeModelId!==e||this.projects.put(Oe.parse({...t,modelSelection:{...t.modelSelection.codexModelId===e?{}:{codexModelId:t.modelSelection.codexModelId},...t.modelSelection.claudeModelId===e?{}:{claudeModelId:t.modelSelection.claudeModelId}},updatedAt:Xt()}));return t}reorderModels(e){const t=[...new Set(e)],n=new Map(this.models.list().map(e=>[e.id,e]));for(const e of t)n.has(e)||nn("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const s=n.get(e);this.models.put(xe.parse({...s,order:100*(t+1),updatedAt:Xt()}))}),this.listModels()}createProject(e){const t=un.parse(e),n=Xt(),s=Ce.parse(t.source),i=this.validateProjectModelSelection(t.modelSelection||{}),a=Oe.parse({...t,id:t.id||rt("proj"),source:s,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",modelSelection:i,workspacePolicy:Ne.parse(t.workspacePolicy||on(s)),labels:t.labels||{},createdAt:n,updatedAt:n});return this.projects.put(a)}updateProject(e,t){const n=gn.parse(t),s=this.requireProject(e),i=n.source?Ce.parse(n.source):s.source,a=n.modelSelection?this.validateProjectModelSelection(n.modelSelection):s.modelSelection,r=Oe.parse({...s,...n,id:e,source:i,modelSelection:a,workspacePolicy:n.workspacePolicy?Ne.parse(n.workspacePolicy):s.workspacePolicy,createdAt:s.createdAt,updatedAt:Xt()});return this.projects.put(r)}deleteProject(e){return this.projects.delete(e)}listImages(){return this.images.list()}createImage(e){const t=wn.parse(e),n=Xt(),s=De.parse({...t,id:t.id||rt("img"),registry:t.registry||"local",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:n,updatedAt:n});return this.images.put(s)}async listNodeDockerImages(e){const t=this.requireNode(e);return this.nodeAgentGateway.listDockerImages(t)}async listNodeLocalFolders(e){const t=this.requireNode(e);return this.nodeAgentGateway.listLocalFolders(t)}async listNodeFolderTree(e,t={}){const n=this.requireNode(e);return this.nodeAgentGateway.listFolderTree(n,t)}async createNodeLocalFolder(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.createLocalFolder(n,t)}async deleteNodeLocalFolder(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteLocalFolder(n,t)}async checkNode(e){const t=this.requireNode(e);try{const e=await this.nodeAgentGateway.health(t),n=e&&"object"==typeof e?e:{},s="string"==typeof n.nodeId&&n.nodeId.trim()?n.nodeId.trim():t.id,i=Re.parse({...t,id:s,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:an(e)}:t.capabilities,lastSeenAt:Xt(),updatedAt:Xt()});return s!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:Xt(),agent:e}}catch(e){const n=Re.parse({...t,status:"offline",health:"failed",updatedAt:Xt()});return this.nodes.put(n),{id:t.id,status:"offline",checkedAt:Xt(),error:e instanceof Error?e.message:String(e)}}}async checkNodeUpdate(e,t){return this.nodeAgentGateway.checkUpdate(this.requireNode(e),t)}async applyNodeUpdate(e,t){const n=this.requireNode(e);if("controlled-instance"===t.target.component){const e=await this.requireNodeInstance(t.target.instanceId);if(e.nodeId!==n.id){const t=new Error(`Instance ${e.id} does not belong to node ${n.id}.`);throw Object.assign(t,{statusCode:400,code:"UPDATE_TARGET_NODE_MISMATCH"}),t}return this.nodeAgentGateway.applyUpdate(n,{...t,modelEnv:this.modelEnvForInstance(e)})}return this.nodeAgentGateway.applyUpdate(n,t)}async listNodeUpdateJobs(e){return this.nodeAgentGateway.listUpdateJobs(this.requireNode(e))}async createNodePairingInvite(e,t={}){const n=this.requireNode(e);return this.nodeAgentGateway.createPairingInvite(n,t)}pruneNodeJoinInvites(){const e=Date.now();for(const t of this.nodeJoinInvites.list())Date.parse(t.expiresAt)<=e&&this.nodeJoinInvites.delete(t.id)}createNodeJoinInvite(e={}){this.pruneNodeJoinInvites();const t=Tn.parse(e&&"object"==typeof e?e:{}),n=Xt(),s=ot(),i={id:rt("node_join"),tokenHash:o.createHash("sha256").update(s).digest("hex"),expiresAt:new Date(Date.now()+(t.expiresInMs||6e5)).toISOString(),...t.nodeName?{nodeName:t.nodeName}:{},createdAt:n,updatedAt:n};return this.nodeJoinInvites.put(i),{id:i.id,joinToken:s,expiresAt:i.expiresAt}}completeNodeJoin(e){this.pruneNodeJoinInvites();const t=r.z.object({joinToken:r.z.string().trim().min(1).max(4096),nodeId:Re.shape.id,nodeName:Re.shape.name.optional(),keyId:r.z.string().trim().min(1).max(160),secret:r.z.string().trim().min(1).max(4096),pairedAt:r.z.string().datetime().optional()}).parse(e),n=o.createHash("sha256").update(t.joinToken).digest("hex"),s=this.nodeJoinInvites.list().find(e=>e.tokenHash===n&&Date.parse(e.expiresAt)>Date.now());if(!s){const e=new Error("Node join token is invalid or expired.");throw Object.assign(e,{statusCode:401,code:"NODE_JOIN_TOKEN_INVALID"}),e}this.nodeJoinInvites.delete(s.id);const i=Xt();if(this.nodes.get(t.nodeId)){const e=new Error(`Node ${t.nodeId} already exists in this control-plane.`);throw Object.assign(e,{statusCode:409,code:"NODE_JOIN_NODE_ALREADY_EXISTS"}),e}const a=Re.parse({id:t.nodeId,name:t.nodeName||s.nodeName||t.nodeId,connectionMode:"reverse-wss",auth:{mode:"paired-hmac",keyId:t.keyId,secret:t.secret,pairedAt:t.pairedAt||i,pairing:{status:"paired"}},status:"unknown",health:"unknown",capabilities:{},labels:{},createdAt:i,updatedAt:i});return this.nodes.put(a)}async connectNodeToControlPlane(e,t){const n=this.requireNode(e),s=kn.parse(t);return this.nodeAgentGateway.connectRemote(n,s)}async listNodeRemoteControlPlanes(e){const t=this.requireNode(e);return this.nodeAgentGateway.listRemotes(t)}async deleteNodeRemoteControlPlane(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteRemote(n,t)}updateImage(e,t){const n=An.parse(t),s=this.requireImage(e),i=De.parse({...s,...n,id:e,createdAt:s.createdAt,updatedAt:Xt()});return this.images.put(i)}deleteImage(e){return this.images.delete(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(rn)}async createNode(e){const t=Sn.parse(e),n=Xt(),s=t.connectionMode||"direct-http";let i="auth"in(e&&"object"==typeof e&&!Array.isArray(e)?e:{})?t.auth:{mode:"local-ipc"===s||"local-loopback"===s?"local-static-key":"paired-hmac"};if(("direct-http"===s||"reverse-wss"===s)&&"paired-hmac"!==i.mode){const e=new Error("Remote node connections require paired-HMAC authentication.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_REQUIRES_PAIRED_HMAC"}),e}const a=t.controlEndpoint||t.endpoint;if(!a&&"reverse-wss"!==s){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const r=t.joinToken&&a?await this.completeNodeAgentPairing(a,{joinToken:t.joinToken}):void 0;if(r&&(i={mode:"paired-hmac",keyId:r.keyId,secret:r.secret,pairedAt:r.pairedAt,pairing:{status:"paired"}}),("direct-http"===s||"reverse-wss"===s)&&!i.secret){const e=new Error("Remote node connections require a paired node secret or join token.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_SECRET_REQUIRED"}),e}if(("direct-http"===s||"reverse-wss"===s)&&!i.keyId){const e=new Error("Remote node connections require a paired key id.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_KEY_ID_REQUIRED"}),e}const o=t.id||r?.nodeId||rt("node"),d=Re.parse({id:o,name:t.name,connectionMode:s,auth:i,labels:t.labels||{},createdAt:n,updatedAt:n}),c="reverse-wss"===s&&(t.id||r?.nodeId)?void 0:await this.inspectNodeAgent(a||"",d),l=t.id||r?.nodeId||c?.nodeId||o;if(!l){const e=new Error("Node id is required.");throw Object.assign(e,{statusCode:400,code:"NODE_ID_REQUIRED"}),e}if(t.id&&c?.nodeId&&c.nodeId!==t.id){const e=new Error(`Node agent id ${c.nodeId} does not match requested node id ${t.id}.`);throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ID_MISMATCH"}),e}const{joinToken:p,...u}=t,g=Re.parse({...u,id:l,connectionMode:s,auth:i,endpoint:a,controlEndpoint:a,status:t.status||(c?"online":"unknown"),health:t.health||(c?"ok":"unknown"),capabilities:{...t.capabilities||{},...c?{agent:an(c.data)}:{}},labels:t.labels||{},createdAt:n,updatedAt:n});return this.nodes.put(g)}updateNode(e,t){const n=vn.parse(t),s=this.requireNode(e),i=Re.parse({...s,...n,id:e,createdAt:s.createdAt,updatedAt:Xt()});return this.nodes.put(i)}deleteNode(e){if("true"===this.requireNode(e).labels[Ms]){const e=new Error("The built-in local node connection cannot be deleted.");throw Object.assign(e,{statusCode:400,code:"LOCAL_NODE_CANNOT_BE_DELETED"}),e}return this.nodes.delete(e)}async listNodeRuntimes(e){const t=e?[this.requireNode(e)]:this.listNodes();return e?this.nodeAgentGateway.listRuntimes(t[0]):(await this.nodeAgentGateway.listFleetRuntimes(t)).items}async listNodeRuntimesWithDiagnostics(){return this.nodeAgentGateway.listFleetRuntimes(this.listNodes())}async createNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.createRuntime(n,t)}async updateNodeRuntime(e,t,n){const s=this.requireNode(e);return this.nodeAgentGateway.updateRuntime(s,t,n)}async deleteNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteRuntime(n,t)}async checkNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.checkRuntime(n,t)}async listControlledInstances(){return(await this.listNodeInstances()).map(dn)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(dn),nodeErrors:e.nodeErrors}}async createControlledInstance(e){const t=hn.parse(e),n=t.projectId?this.requireProject(t.projectId):void 0,s=t.runtimeId||n?.defaultRuntimeId||"runtime_local_docker",i=t.nodeId||n?.defaultNodeId||this.defaultNodeId();if(!i){const e=new Error("At least one node connection is required before creating an instance.");throw Object.assign(e,{statusCode:400,code:"NODE_REQUIRED"}),e}const a=this.requireNode(i),r=await this.requireNodeRuntimeOnNode(a.id,s),o="boolean"==typeof r.capabilities.requiresImage?r.capabilities.requiresImage:"local"!==r.type,d=t.imageId||(o?n?.defaultImageId:void 0),c=d?this.requireImage(d):void 0;if(o&&!c){const e=new Error(`Runtime ${r.name} requires an image.`);throw Object.assign(e,{statusCode:400,code:"RUNTIME_IMAGE_REQUIRED"}),e}let l=t.source||n?.source,p=t.sourceSnapshot||n||{};if(!l){const e=new Error("Instance source is required.");throw Object.assign(e,{statusCode:400,code:"INSTANCE_SOURCE_REQUIRED"}),e}if("local"===r.type&&"local-folder"!==l.type){const e=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(e,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),e}if("local-folder"===l.type){if(l.ownerNodeId&&l.ownerNodeId!==a.id){const e=new Error(`Local folder ${l.path} belongs to node ${l.ownerNodeId}, not ${a.id}.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),e}if(l.localFolderId){const e=await this.requireNodeLocalFolder(a,l.localFolderId);l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}else{const e=await this.nodeAgentGateway.createLocalFolder(a,{name:"string"==typeof p.name?p.name:"Local folder",path:l.path,defaultImageId:c?.id,modelSelection:(u=p,u.modelSelection&&"object"==typeof u.modelSelection&&!Array.isArray(u.modelSelection)?u.modelSelection:{})});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}var u;const g=await this.nodeAgentGateway.createInstance(a,{id:t.id,name:t.name,runtimeId:s,...c?{imageId:c.id,image:c}:{},projectId:n?.id,source:l,sourceSnapshot:p,config:t.config,modelEnv:n?this.modelEnvForProject(n):this.modelEnvForSourceSnapshot(l,p)});return{...dn(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const n=mn.parse(t),s=await this.requireNodeInstance(e),i=this.requireNode(s.nodeId);return dn(await this.nodeAgentGateway.updateInstance(i,e,n))}async deleteControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId),s=await this.nodeAgentGateway.deleteInstance(n,e);return Boolean(s.deleted)}async startControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return dn(await this.nodeAgentGateway.startInstance(n,e,{modelEnv:this.modelEnvForInstance(t)}))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return dn(await this.nodeAgentGateway.stopInstance(n,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return dn(await this.nodeAgentGateway.restartInstance(n,e,{modelEnv:this.modelEnvForInstance(t)}))}async boardAsync(){return(await this.boardWithDiagnostics()).items}async boardWithDiagnostics(){const e=await this.listNodeRuntimesWithDiagnostics(),t=await this.listNodeInstancesWithDiagnostics();return this.instanceBoardReader.read({projects:this.listProjects(),images:this.images.list(),nodes:this.nodes.list(),runtimes:e.items,instances:t.items,nodeErrors:[...e.nodeErrors,...t.nodeErrors]})}async bootstrapAiSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus)return{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions};try{return F.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:tn(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,n)=>({instanceId:e.id,aiSessions:t[n].snapshot,revision:t[n].revision,lastEventAt:t[n].lastEventAt}))}}async bootstrapAppSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const e=Kt();return{revision:0,lastEventAt:e.updatedAt,snapshot:e}}try{return xt.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:tn(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const n=Kt();return{revision:0,lastEventAt:n.updatedAt,snapshot:n}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,n)=>({instanceId:e.id,appSessions:t[n].snapshot,revision:t[n].revision,lastEventAt:t[n].lastEventAt}))}}async listTriggers(){return this.controlPlaneTriggerService.listTriggers()}createTrigger(e){return this.controlPlaneTriggerService.createTrigger(e)}async deleteTrigger(e){return this.controlPlaneTriggerService.deleteTrigger(e)}async applyTrigger(e,t){return this.controlPlaneTriggerService.applyTrigger(e,t)}async listInstanceTriggers(e){return this.controlPlaneTriggerService.listInstanceTriggers(e)}async bindAiSessionTrigger(e,t,n){return this.controlPlaneTriggerService.bindAiSessionTrigger(e,t,n)}async unbindAiSessionTrigger(e,t,n){return this.controlPlaneTriggerService.unbindAiSessionTrigger(e,t,n)}async runInstanceTrigger(e,t,n={}){return this.controlPlaneTriggerService.runInstanceTrigger(e,t,n)}listChatSessions(){return this.chatSessionService.list()}createAppAccessToken(e){return this.appAccessService.createToken(e)}resolveAppAccessToken(e,t){return this.appAccessService.resolveToken(e,t)}createChatActionToken(e){return this.chatActionTokenService.create(e)}resolveChatActionToken(e,t){return this.chatActionTokenService.resolve(e,t)}pendingDecisionCallbackData(e,t){return this.chatActionTokenService.pendingDecisionCallbackData(e,t)}async appAccessProxyTarget(e,t,n=""){return this.appAccessService.proxyTarget(e,t,n)}listChatBridges(){return this.chatBridgeService.list()}requireChatBridge(e){return this.chatBridgeService.require(e)}createChatBridge(e){return this.chatBridgeService.create(e)}updateChatBridge(e,t){return this.chatBridgeService.update(e,t)}deleteChatBridge(e){return this.chatBridgeService.delete(e)}requireChatSession(e){return this.chatSessionService.require(e)}upsertChatSession(e){return this.chatSessionService.upsert(e)}async handleChatGatewayMessage(e){return this.chatSessionRuntime.handleChatGatewayMessage(e)}async handleChatGatewayAction(e){return this.chatSessionRuntime.handleChatGatewayAction(e)}async handleChatCommand(e,t){return this.chatSessionRuntime.handleChatCommand(e,t)}async listPendingRoutes(){const e=Xt(),t=[],n=await this.listAiSessions(),s=new Map(n.instances.map(e=>[e.instanceId,e.aiSessions]));for(const n of await this.listNodeInstances()){if("online"!==n.connectionStatus&&"online"!==n.agentStatus||!n.target.web)continue;const i=n.projectId?this.projects.get(n.projectId):void 0;for(const a of s.get(n.id)?.sessions||[]){if("waiting"!==a.status)continue;const s=Ze.parse({id:`${n.id}:ai:${a.id}`,instanceId:n.id,projectId:n.projectId,aiSessionId:a.id,providerSessionId:a.providerSessionId,result:a.summary||a.currentTool?.inputPreview||a.lastMessage||`${a.agent} is waiting for approval`,source:a.agent,kind:"approval",status:"pending",lastSeenAt:e});t.push({...s,project:i,instance:dn(n)})}}return t}async resolveAiSessionApproval(e,t,n){const s=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/approval`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({decision:n})}))}async launchAppSession(e,t="terminal-tty",n={}){const s=await this.requireControlledInstance(e,!0);if("online"!==s.connectionStatus&&"online"!==s.agentStatus){const e=new Error(`Instance ${s.name} is still starting. Wait for it to connect before launching apps.`);throw Object.assign(e,{statusCode:409,code:"INSTANCE_NOT_CONNECTED"}),e}const i=await this.resolveAppLaunchOptions(s,n),a=await this.instanceRequest(s,"/apps/sessions",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appId:t,...i})});return await this.listAppSessions({refresh:!0}),a}async stopAppSession(e,t){const n=await this.requireControlledInstance(e,!0),s=await this.instanceRequest(n,`/apps/sessions/${encodeURIComponent(t)}/stop`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});return await this.listAppSessions({refresh:!0}),s}async renameAppSession(e,t,n){const s=await this.requireControlledInstance(e,!0),i=await this.instanceRequest(s,`/apps/sessions/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({title:n})});return await this.listAppSessions({refresh:!0}),i}async sendAiSessionMessage(e,t,n,s,i=[]){!function(e){if(e.reduce((e,t)=>e+t.size,0)<=y)return;const t=new Error("Images must be 41943040 bytes or less in total.");throw Object.assign(t,{statusCode:400,code:"AI_SESSION_ATTACHMENTS_TOO_LARGE"}),t}(i);const a=await this.requireControlledInstance(e,!0),r={message:n,...s?{mode:s}:{},...i.length?{attachments:i}:{}};return L.parse(await this.instanceRequest(a,`/ai-sessions/${encodeURIComponent(t)}/messages`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(r)}))}async aiSessionQueue(e,t){const n=await this.requireControlledInstance(e,!0);return P.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}/steer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async retryAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}/retry`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async removeAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}`,{method:"DELETE"}))}async interruptAiSession(e,t){const n=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/interrupt`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async proxyInstanceHttp(e,t,n={}){const s=await this.requireControlledInstance(e,!0);return this.controlledInstanceGateway.proxyHttp(s,t,n)}async proxyInstanceWebSocket(e,t,n,s,i={}){const a=await this.requireControlledInstance(e,!0);this.controlledInstanceGateway.proxyWebSocket(a,this.nodeAgentTransportFor(this.requireNode(a.nodeId)),t,n,s,i)}listConfigSyncPresets(){return[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}]}async syncInstanceConfig(e,t,n){const s=await this.requireControlledInstance(e,!0);if("export"===t&&"local-folder"!==s.source.type){const e=new Error("Exporting instance config is only available for local folder projects.");throw Object.assign(e,{statusCode:400,code:"CONFIG_SYNC_EXPORT_REQUIRES_LOCAL_PROJECT"}),e}const i=[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}].find(e=>e.id===n);if(!i){const e=new Error(`Config sync preset ${n} was not found.`);throw Object.assign(e,{statusCode:404,code:"CONFIG_SYNC_PRESET_NOT_FOUND"}),e}return this.instanceRequest(s,`/config-sync/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({preset:i})})}async instanceRequest(e,t,n={}){return this.controlledInstanceGateway.request(e,t,n)}async reportInstanceHeartbeat(e,t){await this.controlledInstanceGateway.reportHeartbeat(e,t,this.nodeAgentTransport)}async nodeAgentFetch(e,t,n={}){return this.nodeAgentTransportFor(e).request(e,t,n)}nodeAgentTransportFor(e){if("reverse-wss"===e.connectionMode){if(!this.nodeAgentTransport){const e=new Error("Reverse node agent transport is not available.");throw Object.assign(e,{statusCode:503,code:"NODE_AGENT_REVERSE_TRANSPORT_UNAVAILABLE"}),e}return this.nodeAgentTransport}return this.directNodeAgentTransport()}directNodeAgentTransport(){return{request:async(e,t,n={})=>{const s=e.controlEndpoint||e.endpoint;if(!s){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const i=n.method||"GET",a="string"==typeof n.body||n.body instanceof Buffer?n.body:void 0===n.body||null===n.body?void 0:String(n.body),r=Ls(e,{method:i,pathWithQuery:`/api/node-agent${t}`,body:a||""});return this.fetchNodeAgentEndpoint(s,t,{...n,body:a,headers:{...n.headers||{},...r}})},proxyWebSocket:(e,t,n,s,i={})=>{const a=e.controlEndpoint||e.endpoint;if(!a)throw Object.assign(new Error("Node agent direct HTTP mode requires an endpoint."),{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"});const r=xs(a),o=`/api/node-agent${n}`,d=Ls(e,{method:"GET",pathWithQuery:o});if(r){Ds(r);const e=Rs(r,n,s,{...i,...d});return e.on("open",()=>{t.on("message",(t,n)=>e.readyState===p.WebSocket.OPEN&&e.send(n?t:String(t))),t.on("close",()=>e.close()),t.on("error",()=>e.close())}),e.on("message",(e,n)=>{t.readyState===p.WebSocket.OPEN&&t.send(n?e:e.toString())}),e.on("close",()=>t.close()),void e.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}const c=new URL(o,a.replace(/\/$/,""));c.protocol="https:"===c.protocol?"wss:":"ws:";const l=s?new p.WebSocket(c.toString(),s,{headers:{...i,...d}}):new p.WebSocket(c.toString(),{headers:{...i,...d}});l.on("open",()=>{t.on("message",(e,t)=>l.readyState===p.WebSocket.OPEN&&l.send(t?e:String(e))),t.on("close",()=>l.close()),t.on("error",()=>l.close())}),l.on("message",(e,n)=>{t.readyState===p.WebSocket.OPEN&&t.send(n?e:e.toString())}),l.on("close",()=>t.close()),l.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}}}async listNodeInstances(){return(await this.listNodeInstancesWithDiagnostics()).items}async listNodeInstancesWithDiagnostics(){const e=await this.nodeAgentGateway.listFleetInstances(this.listNodes());return{items:await Promise.all(e.items.map(e=>this.withFreshTriggerSnapshot(e))),nodeErrors:e.nodeErrors}}async withFreshTriggerSnapshot(e){if("online"!==e.connectionStatus&&"online"!==e.agentStatus)return e;try{const t=le.parse(await this.instanceRequest(e,"/triggers"));return Ve.parse({...e,triggers:Es(t)})}catch{return e}}async requireNodeInstance(e){for(const t of this.listNodes())try{const n=(await this.nodeAgentGateway.listInstances(t)).find(t=>t.id===e);if(n)return n}catch{}nn("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`)}async requireNodeRuntimeOnNode(e,t){const n=(await this.listNodeRuntimes(e)).find(e=>e.id===t);return n||nn("NODE_RUNTIME_NOT_FOUND",`Runtime ${t} was not found on node ${e}.`),n}async requireNodeLocalFolder(e,t){return this.nodeAgentGateway.requireLocalFolder(e,t)}async resolveAppLaunchOptions(e,t){const n="string"==typeof t.cwdFolderId?t.cwdFolderId.trim():"",s={...t};if(delete s.cwdFolderId,!n)return s;const i=this.requireNode(e.nodeId),a=await this.requireNodeLocalFolder(i,n);if(a.nodeId!==e.nodeId){const t=new Error(`Local folder ${a.id} belongs to node ${a.nodeId}, not ${e.nodeId}.`);throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_INSTANCE_NODE"}),t}const r=await this.requireNodeRuntimeOnNode(e.nodeId,e.runtimeId);return{...s,cwd:this.appLaunchCwdForFolder(e,a,r)}}appLaunchCwdForFolder(e,t,s){if("local"===s.type)return n.resolve(t.path);if("local-folder"!==e.source.type){const e=new Error("App cwd folder selection is only supported for local-folder instances.");throw Object.assign(e,{statusCode:400,code:"APP_CWD_REQUIRES_LOCAL_FOLDER_SOURCE"}),e}const i=n.resolve(e.source.path),a=n.resolve(t.path),r=n.relative(i,a);if(r.startsWith("..")||n.isAbsolute(r)){const n=new Error(`Local folder ${t.path} is outside the instance workspace ${e.source.path}.`);throw Object.assign(n,{statusCode:400,code:"APP_CWD_OUTSIDE_WORKSPACE"}),n}const o=e.runtime.workspacePath||e.workspace.path||on(e.source).path||"/workspace";return r?n.posix.join(o,...r.split(n.sep)):o}modelEnvForInstance(e){return function(e,t){const n=ks(e.sourceSnapshot);return vs(Oe.parse({id:e.projectId||`project_${e.id}`,name:"string"==typeof e.sourceSnapshot.name?e.sourceSnapshot.name:e.name,source:e.source,defaultImageId:e.imageId,defaultNodeId:e.nodeId,defaultRuntimeId:e.runtimeId,modelSelection:n,workspacePolicy:on(e.source),labels:{},createdAt:e.createdAt,updatedAt:e.updatedAt}),t)}(e,this.listAllModels())}modelEnvForSourceSnapshot(e,t){return function(e,t,n){const s=ks(t),i=(new Date).toISOString();return vs(Oe.parse({id:"string"==typeof t.id?t.id:`source_${rt("tmp")}`,name:"string"==typeof t.name?t.name:"Source",source:e,defaultImageId:"string"==typeof t.defaultImageId?t.defaultImageId:void 0,modelSelection:s,workspacePolicy:on(e),labels:{},createdAt:i,updatedAt:i}),n)}(e,t,this.listAllModels())}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}validateProjectModelSelection(e){const t=new Map(this.listAllModels().map(e=>[e.id,e])),n={},s=e.codexModelId?.trim(),i=e.claudeModelId?.trim();if(s){const e=t.get(s);if(e||nn("MODEL_NOT_FOUND",`Model ${s} was not found.`),"codex"!==e.app){const e=new Error(`Model ${s} is not a Codex model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}n.codexModelId=s}if(i){const e=t.get(i);if(e||nn("MODEL_NOT_FOUND",`Model ${i} was not found.`),"claude"!==e.app){const e=new Error(`Model ${i} is not a Claude model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}n.claudeModelId=i}return n}modelEnvForProject(e){return vs(e,this.listAllModels())}requireProject(e){const t=this.projects.get(e);return t||nn("PROJECT_NOT_FOUND",`Project ${e} was not found.`),this.normalizeProjectRecord(t)}normalizeProjectRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};return"defaultRuntimeId"in t||(t.defaultRuntimeId="runtime_local_docker"),Oe.parse(t)}return Oe.parse(e)}(e);return t!==e&&this.projects.put(t),t}requireModel(e,t=!1){const n=this.models.get(e);n||nn("MODEL_NOT_FOUND",`Model ${e} was not found.`);const s=this.normalizeModelRecord(n);return t?s:cn(s)}requireModelSecret(e){const t=this.models.get(e);return t||nn("MODEL_NOT_FOUND",`Model ${e} was not found.`),this.normalizeModelRecord(t)}normalizeModelRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};if(!("app"in t)){const e=Array.isArray(t.apps)?t.apps:[];t.app=e.includes("claude")&&!e.includes("codex")?"claude":"codex"}return delete t.apps,xe.parse(t)}return xe.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){const t=this.images.get(e);return t||nn("IMAGE_NOT_FOUND",`Image ${e} was not found.`),t}requireNode(e){const t=this.nodes.get(e);return t||nn("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return rn(this.requireNode(e))}async requireControlledInstance(e,t=!1){const n=await this.requireNodeInstance(e);return t?n:dn(n)}}function Bs(e){return m(String(e),"escape").replace(/\n+$/,"")}function Fs(e){const[t,...n]=String(e).split("\n");return[`*${Bs(t)}*`,...n.map(Bs)].join("\n")}function Hs(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Gs(e,t,n,s,i={}){if(t.token)return Vs(n=>e(`https://api.telegram.org/bot${t.token}/sendMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),"sendMessage",s,{chat_id:n,...i.replyToMessageId?{reply_to_message_id:i.replyToMessageId,allow_sending_without_reply:!0}:{},...i.replyMarkup?{reply_markup:i.replyMarkup}:{}},i)}async function Ws(e,t,n){if(!t.token||!n)throw new Error("Telegram file id is required.");const s=await e(`https://api.telegram.org/bot${t.token}/getFile`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({file_id:n})}),i=await s.json().catch(()=>({}));if(!s.ok||!1===i.ok||!i.result?.file_path)throw new Error(i.description||`Telegram getFile failed with HTTP ${s.status}`);return`https://api.telegram.org/file/bot${t.token}/${i.result.file_path}`}async function Vs(e,t,n,s,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(n):Bs(n),o=await e({...s,text:r,disable_web_page_preview:!0,parse_mode:a}),d=await o.json().catch(()=>({}));if(o.ok&&!1!==d.ok)return d.result;if("editMessageText"===t&&Ks(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...s,text:Hs(n),disable_web_page_preview:!0,parse_mode:"Markdown"}),a=await i.json().catch(()=>({}));if(i.ok&&!1!==a.ok)return a.result;if("editMessageText"===t&&Ks(a.description))return}const c=await e({...s,text:i.rawMarkdownV2?Hs(n):String(n),disable_web_page_preview:!0}),l=await c.json().catch(()=>({}));if(c.ok&&!1!==l.ok)return l.result;if("editMessageText"!==t||!Ks(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function Ks(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function Js(e){return e instanceof Error?e.message:String(e)}function Qs(e){const t=Number(function(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}(e).message_id);return Number.isInteger(t)&&t>0?t:void 0}class Ys{entries;updateIntervalMs;send;edit;deleteMessage;messageIdFromResult;retryAfterMs;isMessageTooLong;onLog;constructor(e){this.entries=e.entries||new Map,this.updateIntervalMs=e.updateIntervalMs||1e3,this.send=e.send,this.edit=e.edit,this.deleteMessage=e.delete,this.messageIdFromResult=e.messageIdFromResult||Qs,this.retryAfterMs=e.retryAfterMs,this.isMessageTooLong=e.isMessageTooLong,this.onLog=e.onLog}remember(e,t,n,s,i,a){const r=this.entries.get(e);return r&&(clearTimeout(r.timer),r.timer=void 0,r.pendingText=void 0),this.entries.set(e,{lastText:n,lastUpdateAt:Date.now(),messageId:t,route:s,options:i,renderOptions:a}),r}rekey(e,t){if(e===t)return this.entries.has(e);const n=this.entries.get(e);if(!n||this.entries.has(t))return!1;if(clearTimeout(n.timer),n.timer=void 0,this.entries.delete(e),this.entries.set(t,n),n.pendingText){const e=n.rateLimitedUntil?Math.max(0,n.rateLimitedUntil-Date.now()):Math.max(0,this.updateIntervalMs-(Date.now()-n.lastUpdateAt));this.scheduleFlush(t,e)}return!0}scheduleFlush(e,t){const n=this.entries.get(e);n&&!n.timer&&(n.timer=setTimeout(()=>{n.timer=void 0;const t=n.pendingText;n.pendingText=void 0,t&&this.applyUpdate(e,t,n.route,n.options)},t))}markRateLimited(e,t,n,s){const i=this.entries.get(e);i&&(i.rateLimitedUntil=Date.now()+n,i.pendingText=t,i.route=s||i.route,this.scheduleFlush(e,n))}async applyUpdate(e,t,n,s){let i=this.entries.get(e);const a=i,r=n||i?.route,o=s||i?.options;try{if(i&&(i.route=r,i.options=o),i?.pending&&await i.pending,i=this.entries.get(e),a&&i!==a)return!1;if(i?.messageId){if(i.lastText===t)return!0;const n=Date.now()-(i.lastUpdateAt||0);return n<this.updateIntervalMs?(i.pendingText=t,i.route=r||i.route,i.options=o||i.options,this.scheduleFlush(e,this.updateIntervalMs-n),!0):(await this.edit(i.messageId,t,r||i.route,o),i.lastText=t,i.lastUpdateAt=Date.now(),i.rateLimitedUntil=void 0,!0)}const n={lastText:t,lastUpdateAt:Date.now(),route:r,options:o};return n.pending=this.send(t,r,o).then(e=>{const t=this.messageIdFromResult(e);t&&(n.messageId=t)}).finally(()=>{n.pending=void 0}),this.entries.set(e,n),await n.pending,this.entries.get(e)===n&&(n.rateLimitedUntil=void 0,Boolean(n.messageId))}catch(n){const s=this.retryAfterMs?.(n);return s&&this.markRateLimited(e,t,s,r),this.onLog?.(`Telegram progress update skipped: ${Js(n)}`),!1}}update(e,t,n,s){const i=String(t||"").trim();if(!i)return;const a=this.entries.get(e);if(a&&(a.route=n||a.route,a.options=s||a.options),a?.lastText===i&&!a.pending&&!a.pendingText)return;const r=Date.now();if(a?.rateLimitedUntil){if(a.rateLimitedUntil>r)return a.pendingText=i,void this.scheduleFlush(e,a.rateLimitedUntil-r);a.rateLimitedUntil=void 0}if(a?.pending&&!a.messageId)return a.pendingText=i,void this.scheduleFlush(e,this.updateIntervalMs);if(!a||!a.messageId)return void this.applyUpdate(e,i,n,s);const o=Date.now()-(a.lastUpdateAt||0);if(o>=this.updateIntervalMs)return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,void this.applyUpdate(e,i,n||a.route,s||a.options);a.pendingText=i,this.scheduleFlush(e,this.updateIntervalMs-o)}async finish(e,t,n,s,i){const a=this.entries.get(e);if(!a)return!1;try{return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,a.pending&&await a.pending,this.entries.delete(e),!!a.messageId&&(a.lastText===t||await this.edit(a.messageId,t,n||a.route,s||a.options,i||a.renderOptions),!0)}catch(t){return this.entries.delete(e),this.isMessageTooLong?.(t)?this.onLog?.("Telegram progress finish too long; sending result as multiple messages"):this.onLog?.(`Telegram progress finish skipped: ${Js(t)}`),!1}}finishPayload(e,t,n,s,i){return this.finish(e,`${t.title}\n\n${t.body}`,n,s,i)}async delete(e,t){const n=this.entries.get(e);if(!n)return!1;try{return clearTimeout(n.timer),n.timer=void 0,n.pendingText=void 0,n.pending&&await n.pending,this.entries.delete(e),!(!this.deleteMessage||!n.messageId)&&(await this.deleteMessage(n.messageId,t||n.route),!0)}catch(t){return this.entries.delete(e),this.onLog?.(`Telegram progress delete skipped: ${Js(t)}`),!1}}async flush(e){const t=this.entries.get(e);if(t?.pending&&await t.pending,t?.timer){clearTimeout(t.timer),t.timer=void 0;const n=t.pendingText;t.pendingText=void 0,n&&await this.applyUpdate(e,n,t.route,t.options)}}async wait(e){await function(e){return new Promise(t=>setTimeout(t,e))}(e)}}const Xs=20971520;function Zs(e){const t=[],n=[...e.photo||[]].filter(e=>e.file_id).sort((e,t)=>Number(t.file_size||t.width||0)-Number(e.file_size||e.width||0))[0];return n?.file_id&&t.push({fileId:n.file_id,fileName:"telegram-photo.jpg",fileSize:n.file_size}),e.document?.file_id&&String(e.document.mime_type||"").startsWith("image/")&&t.push({fileId:e.document.file_id,fileName:e.document.file_name,fileSize:e.document.file_size}),t}async function ei(e,t,s="telegram-image",i){if(i&&i>Xs)throw new Error(`image is too large: ${i} bytes`);const a=await e.getFileLink(t),r=String(a),o=await(e.fetchFile||fetch)(r);if(!o.ok)throw new Error(`download failed with HTTP ${o.status}`);const d=Number(o.headers.get("content-length")||0);if(d>Xs)throw new Error(`image is too large: ${d} bytes`);const l=n.extname(s)||".jpg";let p=l;try{p=n.extname(new URL(r).pathname)||l}catch{p=l}const u=n.join(c.tmpdir(),"task-handoff-images");await f.mkdir(u,{recursive:!0});const g=p.replace(/[^.\w-]/g,"")||".jpg",h=n.join(u,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${g}`),m=Buffer.from(await o.arrayBuffer());if(m.length>Xs)throw new Error(`image is too large: ${m.length} bytes`);return await f.writeFile(h,m),h}class ti{store;constructor(e){this.store=new Ys({updateIntervalMs:e.updateIntervalMs,send:(t,n,s)=>e.send(e.requireBridge(n?.bridgeId||""),n?.chatId||"",Fs(t),{rawMarkdownV2:!0,replyMarkup:Di(s)}),edit:(t,n,s,i)=>e.edit(e.requireBridge(s?.bridgeId||""),s?.chatId||"",t,Fs(n),{rawMarkdownV2:!0,replyMarkup:Di(i)}),delete:(t,n)=>e.deleteMessage(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t),onLog:e.onLog})}get entries(){return this.store.entries}remember(e,t,n,s){return this.store.remember(e,t,n,s)}rekey(e,t){return this.store.rekey(e,t)}delete(e){return this.store.delete(e)}async applyUpdate(e){return this.store.applyUpdate(e.key,e.text,{bridgeId:e.bridge.id,chatId:e.chatId},{actionRows:e.actionRows})}}function ni(e,t){if("string"==typeof e&&e.trim())return e.trim();ii(`Chat action token is missing ${t}.`)}function si(e){if("allow"===e||"deny"===e||"skip"===e)return e;ii("Chat action token has an invalid decision.")}function ii(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class ai{entries=new Map;fetchImpl;constructor(e){this.fetchImpl=e}clear(){this.clearEntries(()=>!0)}clearBridge(e){this.clearEntries(t=>t.bridgeId===e)}clearEntries(e){for(const[t,n]of this.entries)e(n)&&(n.timer&&clearTimeout(n.timer),n.cancelPending?.(new Error("DingDing progress update was cancelled.")),this.entries.delete(t))}async applyUpdate(e){const t=e.dingdingRuntime;if(!t)return!1;const n=xi(e.replyMarkup);let s=this.entries.get(e.key);if(s?.pending&&await s.pending,s=this.entries.get(e.key),s?.outTrackId){if(s.lastText===e.text&&s.lastActionsFingerprint===n)return!0;const t=Date.now()-(s.lastUpdateAt||0);return t<1e3?(s.pendingText=e.text,s.pendingReplyMarkup=e.replyMarkup,s.timer||(s.pending=new Promise((n,i)=>{s.cancelPending=i,s.timer=setTimeout(()=>{s.timer=void 0,s.cancelPending=void 0;const t=s.pendingText,a=s.pendingReplyMarkup;s.pendingText=void 0,s.pendingReplyMarkup=void 0,t?this.updateExistingEntry(e,s,t,a,xi(a)).then(n,i):n(void 0)},1e3-t)}).finally(()=>{s.pending=void 0,s.timer=void 0,s.cancelPending=void 0})),await s.pending,this.entries.has(e.key)):(await this.updateExistingEntry(e,s,e.text,e.replyMarkup,n),!0)}const i={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:n,lastUpdateAt:Date.now()};return i.pending=Si({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||zi(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||zi(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(e=>{e?.outTrackId&&(i.outTrackId=e.outTrackId),e?.delivered&&(i.delivered=!0,i.lastUpdateAt=Date.now())}).catch(t=>{throw this.entries.delete(e.key),t}).finally(()=>{i.pending=void 0}),this.entries.set(e.key,i),await i.pending,Boolean(i.delivered)}async updateExistingEntry(e,t,n,s,i){const a=e.dingdingRuntime;a&&t.outTrackId&&(await async function(e){await vi(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:li({type:"actions",title:e.title,description:e.text,list:Kn(e.replyMarkup),biz_step:e.step,error_msg:""})},cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},userIdType:Number(e.bridge.settings.cardUserIdType||1)})}({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:a,outTrackId:t.outTrackId,text:n,replyMarkup:s,title:"TaskHandoff 执行中",step:"progress"}),t.lastText=n,t.lastActionsFingerprint=i,t.lastUpdateAt=Date.now())}}function ri(e){const{fetchImpl:t,bridge:n,dingdingRuntime:s}=e;return"telegram"===n.channel?{bridge:n,send:async(e,s,i={})=>{const a=await Gs(t,n,e,s,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Ci(a).message_id)||void 0}}}:"dingding"===n.channel?{bridge:n,send:async(e,i,a={})=>{const r=a.sessionWebhook||s?.chatWebhooks.get(e)||zi(n.settings.sessionWebhook);if(a.replyMarkup&&s){const o=await Si({fetchImpl:t,bridge:n,runtime:s,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||s.senderIds.get(e)||zi(n.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await bi(t,r,i,s?.onLog),{provider:"dingding"}}}:"wechat"===n.channel?{bridge:n,send:async(e,s,i={})=>{const a=i.contextToken||zi(n.settings.contextToken);if(a)return await async function(e,t,n,s,i){const a=zi(t.settings.baseUrl)||"https://ilinkai.weixin.qq.com",r=await e(`${a}/ilink/bot/sendmessage`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${t.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({msg:{from_user_id:"",to_user_id:n,client_id:o.randomUUID(),message_type:2,message_state:2,context_token:s,item_list:[{type:1,text_item:{text:i}}]},base_info:{channel_version:"1.0.2"}})}),d=await r.json().catch(()=>({}));if(!r.ok||d.errcode)throw new Error(zi(d.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(t,n,e,a,s),{provider:"wechat"}}}:{bridge:n,send:async()=>{}}}async function oi(e){return(await async function(e,t,n,s){const i=[];for(const a of Zs(n))try{const n=await ei({getFileLink:n=>Ws(e,t,n),fetchFile:e},a.fileId,a.fileName,a.fileSize);i.push(n)}catch(e){s?.({fileId:a.fileId,fileName:a.fileName,error:e})}return i}(e.fetchImpl,e.bridge,e.message,e.onImageDownloadError)).map((e,s)=>{const i=t.readFileSync(e);return{id:`tg_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:n.basename(e)||`telegram-image-${s+1}`,mime:di(e),size:i.length,data:i.toString("base64")}})}function di(e){const t=n.extname(e).toLowerCase();return".png"===t?"image/png":".webp"===t?"image/webp":".gif"===t?"image/gif":".bmp"===t?"image/bmp":"image/jpeg"}function ci(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function li(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function pi(e){return`dtv1.card//IM_GROUP.${e}`}function ui(e){return`dtv1.card//IM_ROBOT.${e}`}function gi(e,t){return zi(t.biz_conversation_id).trim()||zi(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function hi(e,t={}){const n=zi(e).trim().toUpperCase();return"2"===n||"IM_GROUP"===n?"IM_GROUP":"1"===n||"IM_SINGLE"===n?t.robotMessage?"IM_ROBOT":"IM_SINGLE":"IM_ROBOT"===n?"IM_ROBOT":void 0}function mi(e){if(e.startsWith("task_handoff:"))return e;if(!e.startsWith("th_cb_"))return"";try{return Buffer.from(e.slice(6),"base64url").toString("utf8")}catch{return""}}function fi(e,t,n,s,i){const a=gi(s,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:li({type:"actions",title:"TaskHandoff",description:e,list:Kn(t),biz_out_track_id:s.outTrackId||i.biz_out_track_id||"",biz_conversation_id:a,biz_sender_id:i.biz_sender_id||s.userId||"",biz_session_webhook:i.biz_session_webhook||"",biz_conversation_type:i.biz_conversation_type||"",biz_step:n,error_msg:""})}}}function Ii(e,t,n){const s=[];return yi(s,n),yi(s,t),yi(s,e),s.find(e=>mi(e))||""}function yi(e,t){for(const n of["actionIdList","actionIds"]){const s=t[n];Array.isArray(s)&&e.push(...s.map(e=>zi(e)).filter(Boolean))}for(const n of["actionId","action_id","id","actionValue"]){const s=zi(t[n]);s&&e.push(s)}}async function bi(e,t,n,s){s?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:_i(n)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:ci(n),text:n}})}),a=await i.json().catch(()=>{});if(s?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:Ei(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function wi(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Ci(e);if(1!==t.type)continue;const n=zi(Ci(t.text_item).text).trim();if(n)return n}return""}function Ai(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function Si(e){const t=Kn(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await bi(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const n=e.bridge.settings||{},s=zi(n.robotCode),i=zi(n.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=zi(n.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||zi(n.senderId),o=(d=e.runtime,c=e.chatId,d.conversationTypes?.get(c)||"IM_GROUP");var d,c;if(!s||!i||!r){if(e.sessionWebhook&&!e.forceCard)return await bi(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),{delivered:!0};throw new Error("DingDing card target is incomplete.")}const l=`task_handoff_cp_${Date.now()}_${Math.random().toString(16).slice(2)}`,p=li({type:"actions",title:e.title||ci(e.text),description:e.text,list:t,biz_out_track_id:l,biz_step:e.step||"task_handoff_actions",biz_conversation_id:e.chatId,biz_sender_id:r,biz_session_webhook:e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)||"",biz_conversation_type:o,error_msg:""}),u=function(e,t,n,s){return"IM_SINGLE"===e?{openSpaceId:(i=t,`dtv1.card//IM_SINGLE.${i}`),imSingleOpenSpaceModel:{},imSingleOpenDeliverModel:{extension:{}}}:"IM_ROBOT"===e?{openSpaceId:ui(n),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:s,spaceType:"IM_ROBOT"}}:{openSpaceId:pi(t),imGroupOpenSpaceModel:{supportForward:!1},imGroupOpenDeliverModel:{extension:{},robotCode:s}};var i}(o,e.chatId,r,s);return e.runtime.onLog?.("info",{target:"card",chatId:e.chatId,senderId:r,conversationType:o,openSpaceId:u.openSpaceId,hasSessionWebhook:Boolean(e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)),hasReplyMarkup:Boolean(e.replyMarkup),actionCount:t.length,textPreview:_i(e.text)},"dingding card send requested"),await vi(e.fetchImpl,e.bridge,e.runtime,"POST","/v1.0/card/instances/createAndDeliver",{userId:r,cardTemplateId:i,outTrackId:l,callbackType:"STREAM",callbackRouteKey:a,cardData:{cardParamMap:p},userIdType:Number(n.cardUserIdType||1),...u}),{delivered:!0,outTrackId:l}}async function vi(e,t,n,s,i,a){const r=await async function(e,t,n){if(n.accessToken&&n.accessToken.expiresAt>Date.now()+6e4)return n.accessToken.value;const s=zi(t.settings.clientSecret);if(!t.token||!s)throw new Error("DingDing client id/secret is not configured.");const i=await e("https://api.dingtalk.com/v1.0/oauth2/accessToken",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appKey:t.token,appSecret:s})}),a=await i.json().catch(()=>({}));if(!i.ok||!a.accessToken)throw new Error(`DingDing access token failed with HTTP ${i.status}`);const r=Number(a.expireIn||7200);return n.accessToken={value:a.accessToken,expiresAt:Date.now()+1e3*Math.max(60,r-120)},n.accessToken.value}(e,t,n);n.onLog?.("info",{method:s,path:i,body:ki(a)},"dingding api request");const o=await e(`https://api.dingtalk.com${i}`,{method:s,headers:{"content-type":"application/json","x-acs-dingtalk-access-token":r},body:JSON.stringify(a)}),d=await o.json().catch(()=>{});if(n.onLog?.(o.ok?"info":"warn",{method:s,path:i,status:o.status,response:Ei(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=Ci(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error(zi(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=Ci(Ci(e).result).deliverResults;if(!Array.isArray(t))return"";const n=t.find(e=>!1===Ci(e).success);if(!n)return"";const s=Ci(n),i=zi(s.spaceId),a=zi(s.errorMsg||s.message||s.errmsg)||"delivery failed";return i?`DingDing card delivery failed for ${i}: ${a}`:`DingDing card delivery failed: ${a}`}(d);if(u)throw new Error(u);return o}function ki(e){const t=Ci(e),n=Ci(t.cardData),s=Ci(n.cardParamMap);return{openSpaceId:zi(t.openSpaceId),outTrackId:zi(t.outTrackId),userId:zi(t.userId),userIdType:t.userIdType,callbackType:zi(t.callbackType),callbackRouteKey:zi(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:Ti(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:Ti(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:Ti(t.imRobotOpenDeliverModel),bizConversationId:zi(s.biz_conversation_id),bizConversationType:zi(s.biz_conversation_type),bizSenderId:zi(s.biz_sender_id),hasBizSessionWebhook:Boolean(zi(s.biz_session_webhook))}}function Ti(e){const t=Ci(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean(zi(t.robotCode)),spaceType:zi(t.spaceType),recipients:Array.isArray(t.recipients)?t.recipients.length:void 0,atUserIds:t.atUserIds&&"object"==typeof t.atUserIds?Object.keys(t.atUserIds).length:void 0}}function Ei(e){const t=Ci(e),n=Ci(t.result);return{code:t.code??t.errcode,message:_i(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(n).length?n:void 0}}function _i(e,t=160){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function Ci(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Ni(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?Oi(e):{}}function zi(e){return"string"==typeof e?e:""}function Oi(e){try{return Ci(JSON.parse(String(e||"{}")))}catch{return{}}}function xi(e){return JSON.stringify(Kn(e))}function Di(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Gn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Ri(e){return Buffer.from(e,"base64url").toString("utf8")}function ji(e){return new h.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class Pi{service;fetchImpl;createDingdingClient;logger;bridgeTimers=new Map;dingdingBridges=new Map;bridgeErrors=new Map;telegramOffsets=new Map;seenTelegramUpdates=new Set;wechatCursors=new Map;pendingTimer;announcedPending=new Set;deliveredAiSessionFingerprints=new Map;aiSessionCancelTokens=new Map;aiSessionQueueActionTokens=new Map;telegramMessageAggregates=new Map;telegramMessageAggregateEndTokens=new Map;telegramProgressMessageTargets=new Map;telegramProgress;dingdingProgress;stopAiSessionListener;constructor(e,t=fetch,n={}){var s;this.service=e,this.fetchImpl=t,this.logger=(s=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(s||"").trim().toLowerCase())?n.logger:void 0),this.telegramProgress=new ti({updateIntervalMs:n.telegramProgressUpdateIntervalMs,requireBridge:e=>this.service.requireChatBridge(e),send:(e,t,n,s={})=>this.sendTelegramMessage(e,t,n,s),edit:(e,t,n,s,i={})=>this.editTelegramMessage(e,t,n,s,i),deleteMessage:(e,t,n)=>this.deleteTelegramMessage(e,t,n),onLog:e=>this.logWarn({message:e},"telegram progress store warning")}),this.dingdingProgress=new ai(this.fetchImpl),this.stopAiSessionListener=n.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:Wi(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=n.createDingdingClient||ji}logInfo(e,t){this.logger?.info?.({component:"control-plane-chat-gateway",...e},t)}logWarn(e,t){this.logger?.warn?.({component:"control-plane-chat-gateway",...e},t)}updateChatBridge(e,t){this.service.updateChatBridge?.(e,t)}startEnabled(){this.logAiSessionDelivery({stage:"runtime-start-enabled",bridgeCount:this.service.listChatBridges().length,enabledBridgeCount:this.service.listChatBridges().filter(e=>e.enabled).length});for(const e of this.service.listChatBridges())e.enabled&&this.startBridge(e.id);this.startPendingPolling()}startBridge(e){if(this.bridgeTimers.has(e)||this.dingdingBridges.has(e))return this.logAiSessionDelivery({stage:"bridge-start-skipped-running",bridgeId:e}),this.startPendingPolling(),this.status();const t=this.service.requireChatBridge(e);return this.logAiSessionDelivery({stage:"bridge-start",bridgeId:t.id,channel:t.channel,enabled:t.enabled,hasToken:Boolean(t.token)}),"wechat"===t.channel?this.startPollingBridge(t,e=>this.pollWechat(e)):"dingding"===t.channel?this.startDingdingBridge(t):"telegram"!==t.channel?(this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status()):t.token?this.startPollingBridge(t,e=>this.pollTelegram(e)):(this.bridgeErrors.set(e,"Telegram token is not configured."),this.status())}stopBridge(e){const t=this.bridgeTimers.get(e);t&&(clearInterval(t),this.bridgeTimers.delete(e));const n=this.dingdingBridges.get(e);if(n){try{n.client.disconnect()}catch{}this.dingdingBridges.delete(e)}return this.dingdingProgress.clearBridge(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.keys()]))this.stopBridge(e);this.pendingTimer&&(clearInterval(this.pendingTimer),this.pendingTimer=void 0),this.dingdingProgress.clear();for(const e of this.telegramMessageAggregates.values())e.timer&&clearTimeout(e.timer);this.telegramMessageAggregates.clear(),this.telegramMessageAggregateEndTokens.clear(),this.stopAiSessionListener?.(),this.stopAiSessionListener=void 0}async pollBridgeNow(e){const t=this.service.requireChatBridge(e);return"telegram"===t.channel?(await this.pollTelegram(t),this.status()):"wechat"===t.channel?(await this.pollWechat(t),this.status()):("dingding"===t.channel||this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status())}async pollAiSessionsNow(){return await this.pollAiSessionUpdates(),this.status()}status(){const e=this.service.listChatBridges().map(e=>({id:e.id,channel:e.channel,name:e.name,running:this.bridgeTimers.has(e.id)||this.dingdingBridges.has(e.id),tokenSet:Boolean(e.tokenSet),defaultChatId:e.defaultChatId,lastUpdateId:this.telegramOffsets.get(e.id),error:this.bridgeErrors.get(e.id)}));return{running:e.some(e=>e.running),bridges:e}}startPendingPolling(){this.pendingTimer?this.logAiSessionDelivery({stage:"pending-poll-already-started"}):(this.logAiSessionDelivery({stage:"pending-poll-started",intervalMs:5e3}),this.pendingTimer=setInterval(()=>{this.pollPendingRoutes()},5e3))}startPollingBridge(e,t){if(!e.token)return this.bridgeErrors.set(e.id,`${e.channel} token is not configured.`),this.status();this.bridgeErrors.delete(e.id);const n=setInterval(()=>{const n=this.service.requireChatBridge(e.id);n.enabled?t(n).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))):this.stopBridge(e.id)},e.pollIntervalMs);if(this.bridgeTimers.set(e.id,n),this.logAiSessionDelivery({stage:"bridge-poll-started",bridgeId:e.id,channel:e.channel,intervalMs:e.pollIntervalMs}),e.enabled){const n=this.service.requireChatBridge(e.id);t(n).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}return this.startPendingPolling(),this.status()}startDingdingBridge(e){const t=Gi((e.settings||{}).clientSecret);if(!e.token||!t)return this.bridgeErrors.set(e.id,"DingDing client id/secret is not configured."),this.status();const n={client:this.createDingdingClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,n,s)=>{"warn"!==t?this.logInfo({bridgeId:e.id,...n},s):this.logWarn({bridgeId:e.id,...n},s)}};return n.client.registerCallbackListener(h.TOPIC_ROBOT,t=>{this.handleDingdingRobotMessage(e,n,t).catch(t=>{this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))})}),n.client.registerCallbackListener(h.TOPIC_CARD,t=>{this.handleDingdingCardCallback(e,n,t).then(e=>n.client.socketCallBackResponse(t.headers.messageId,e||{})).catch(s=>{this.bridgeErrors.set(e.id,s instanceof Error?s.message:String(s)),n.client.socketCallBackResponse(t.headers.messageId,{})})}),this.bridgeErrors.delete(e.id),this.dingdingBridges.set(e.id,n),n.client.connect().then(()=>{this.dingdingBridges.get(e.id)===n&&this.bridgeErrors.delete(e.id)}).catch(t=>{this.dingdingBridges.get(e.id)===n&&(this.dingdingBridges.delete(e.id),this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}),this.startPendingPolling(),this.status()}async handleDingdingRobotMessage(e,t,n){const s=function(e){const t=Oi(e),n=zi(t.conversationId),s=zi(Ci(t.text).content).trim();if(!n||!s)return;const i=zi(t.senderStaffId||t.senderId);return{chatId:n,userId:i||void 0,senderId:i||void 0,sessionWebhook:zi(t.sessionWebhook)||void 0,conversationType:hi(t.conversationType,{robotMessage:!0}),text:s,raw:t}}(n.data);if(t.client.socketCallBackResponse(n.headers.messageId,{}),!s||!this.dingdingAllowed(e,s.senderId))return void this.logWarn({bridgeId:e.id,hasEvent:Boolean(s),senderId:s?.senderId,allowed:s?this.dingdingAllowed(e,s.senderId):void 0},"dingding robot message ignored");const{chatId:i,text:a,senderId:r,sessionWebhook:o}=s;o&&t.chatWebhooks.set(i,o),s.conversationType&&t.conversationTypes.set(i,s.conversationType);const d=o||t.chatWebhooks.get(i)||Gi(e.settings.sessionWebhook);let c;r&&t.senderIds.set(i,r),e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:i}),this.logInfo({bridgeId:e.id,chatId:i,senderId:r,conversationType:s.conversationType,hasSessionWebhook:Boolean(o),hasCachedSessionWebhook:Boolean(t.chatWebhooks.get(i)),hasResolvedSessionWebhook:Boolean(d),textPreview:Vi(a)},"dingding chat gateway message received");try{c=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:r||void 0},message:{text:a,attachments:[]}})}catch(e){if(d)return void await bi(this.fetchImpl,d,`Failed to handle message: ${Wi(e)}`,t.onLog);throw e}const l=Mi(c);if(l){const n=qi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:n,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const s=ri({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:t}),o=await s.send(i,l,{replyMarkup:n,sessionWebhook:d,senderId:r});if(o)return void this.logInfo({bridgeId:e.id,chatId:i,senderId:r,provider:o.provider,interactionId:o.interactionId,routed:!0===Qi(c).routed,hasReplyMarkup:Boolean(n),replyPreview:Vi(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(n),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(Gi(e.settings.robotCode)),replyPreview:Vi(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,n,s,i,a={}){if(!0!==e.routed)return!1;const r=Zi(e),{instanceId:o,sessionId:d,turnId:c}=r;if(!o||!d||!c)return this.logWarn({bridgeId:t.id,chatId:s,instanceId:o,sessionId:d,turnId:c,turnIdSources:r.sources,routed:e.routed},"dingding ai session progress card not started"),!1;const l=ra(o,d,c,t.id,s);return!!await this.dingdingProgress.applyUpdate({bridge:t,dingdingRuntime:n,key:l,chatId:s,text:i,replyMarkup:a.replyMarkup})&&(this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:s,instanceId:o,sessionId:d,turnId:c,key:l,hasSessionWebhook:Boolean(a.sessionWebhook),hasSenderId:Boolean(a.senderId),sourcePrompt:Vi(a.sourcePrompt,80),textPreview:Vi(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,n){const s=function(e){const t=Oi(e),n=Ni(t.cardActionData||t.content),s=Ni(n.cardPrivateData||t.cardPrivateData||t.content),i=Ni(s.params||n.params||t.params),a=zi(t.userId),r=zi(i.biz_sender_id);return{userId:a||void 0,chatId:gi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:zi(i.biz_session_webhook)||void 0,conversationType:hi(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:mi(Ii(t,n,s)),body:t,params:i}}(n.data);if(!this.dingdingAllowed(e,s.userId))return fi("not authorized",void 0,"unauthorized",s.body,s.params);const i=s.chatId||Gi(e.defaultChatId),a=s.deliverySenderId||s.senderId,r=s.sessionWebhook;i&&r&&t.chatWebhooks.set(i,r),i&&s.conversationType&&t.conversationTypes.set(i,s.conversationType),i&&a&&t.senderIds.set(i,a);const o=this.parseChatGatewayCallbackAction(s.callbackData);if(!o)return this.logWarn({bridgeId:e.id,chatId:i,userId:s.userId,senderId:a,...Ki(s)},"dingding card callback action unsupported"),fi("Unsupported action",void 0,"unsupported",s.body,s.params);this.logInfo({bridgeId:e.id,chatId:i,userId:s.userId,senderId:a,actionType:o.type,...Ki(s)},"dingding card callback action parsed");const d=await this.service.handleChatGatewayAction({source:{channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:s.userId||void 0},action:o});return"pending-decision"===o.type&&$i(d)?fi(`${o.decision} sent`,void 0,"approval_done",s.body,s.params):fi(Mi(d)||Gi(d.message)||"updated",qi(d),"updated",s.body,s.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},n=this.wechatCursors.get(e.id)||Gi(t.updatesBuf),s=await async function(e){const t=zi((e.bridge.settings||{}).baseUrl)||"https://ilinkai.weixin.qq.com",n=await e.fetchImpl(`${t}/ilink/bot/getupdates`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${e.bridge.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({get_updates_buf:e.cursor,base_info:{channel_version:"1.0.2"}})}),s=await n.json().catch(()=>({}));if(!n.ok)throw new Error(`HTTP ${n.status}`);const i=Number(s.errcode??s.ret??0);if(0!==i)throw new Error(zi(s.errmsg||s.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(s.msgs)?s.msgs:[]){const t=Ci(e);if(1!==t.message_type)continue;const n=zi(t.from_user_id),s=wi(t);n&&s&&a.push({chatId:n,text:s,contextToken:zi(t.context_token)})}return{cursor:zi(s.get_updates_buf),messages:a}}({fetchImpl:this.fetchImpl,bridge:e,cursor:n});s.cursor&&(this.wechatCursors.set(e.id,s.cursor),this.updateChatBridge(e.id,{settings:{updatesBuf:s.cursor}}));for(const n of s.messages){const{chatId:s,contextToken:i,text:a}=n;if(e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:s,settings:{contextToken:i}}),e.defaultChatId&&e.defaultChatId!==s)continue;const r=Mi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:s},message:{text:a,attachments:[]}}));if(r){const n=ri({fetchImpl:this.fetchImpl,bridge:e});await n.send(s,r,{contextToken:i||Gi(t.contextToken)})}}}async pollPendingRoutes(){const e=await this.service.listPendingRoutes().catch(()=>[]);for(const t of e){if(this.announcedPending.has(t.id))continue;const e=ea(t,(e,t)=>this.service.pendingDecisionCallbackData(e,t)),n=t.aiSessionId?this.service.listChatSessions().filter(e=>e.activeInstanceId===t.instanceId&&e.activeAiSessionId===t.aiSessionId):[],s=new Set;for(const t of n)t.bridgeId&&await this.sendToChatBinding(t,e).then(e=>{e&&s.add(t.id)});(0!==s.size||await this.sendToDefaultBridgeChats(e))&&this.announcedPending.add(t.id)}}async sendToDefaultBridgeChats(e){let t=!1;for(const n of this.service.listChatBridges()){if(!n.enabled||!n.defaultChatId)continue;let s=!1;await this.sendViaBridge(n,n.defaultChatId,e).catch(e=>{this.bridgeErrors.set(n.id,e instanceof Error?e.message:String(e)),s=!0})?t=!0:s||this.bridgeErrors.set(n.id,"Chat bridge message was not delivered.")}return t}async sendToChatBinding(e,t){if(!e.bridgeId)return!1;const n=this.service.requireChatBridge(e.bridgeId);if(!n.enabled)return!1;const s=await this.sendViaBridge(n,e.chatSessionId,t).catch(e=>{this.bridgeErrors.set(n.id,e instanceof Error?e.message:String(e))});return Boolean(s)}async sendViaBridge(e,t,n){const s="string"==typeof n?n:n.text,i="string"==typeof n?void 0:n.replyMarkup;return ri({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:this.dingdingBridges.get(e.id)}).send(t,s,{replyMarkup:i})}async pollTelegram(e){if(!e.token)return;const t=Number(e.settings?.telegramLastUpdateId),n=this.telegramOffsets.get(e.id)||(Number.isInteger(t)?t:void 0),s=await async function(e){const t=new URLSearchParams({timeout:"0",...e.offset?{offset:String(e.offset+1)}:{}}),n=await e.fetchImpl(`https://api.telegram.org/bot${e.bridge.token}/getUpdates?${t.toString()}`),s=await n.json().catch(()=>({}));if(!n.ok||!1===s.ok){if(409===n.status||/terminated by other getUpdates request/i.test(String(s.description||"")))return{updates:[],conflict:s.description||"Telegram getUpdates conflict: another bot instance is polling this token."};throw new Error(s.description||`Telegram getUpdates failed with HTTP ${n.status}`)}const i=[];for(const t of s.result||[]){const n=Number(t.update_id),s=Number.isInteger(n)?n:void 0,a=Ci(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:s,callbackQuery:a});continue}const r=Ci(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Ci(r.chat),d=Ci(r.from),c=void 0!==o.id?String(o.id):e.bridge.defaultChatId,l=void 0!==d.id?String(d.id):void 0;if(!c)continue;const p=zi(r.text??r.caption),u=Zs(r);(p||0!==u.length)&&i.push({updateId:s,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:Ai(r),replyToMessageId:Ai(Ci(r.reply_to_message)),quoteText:zi(Ci(r.quote).text),autoBegin:1===u.length&&!p.trim(),hasSingleImageWithoutText:1===u.length&&!p.trim()})}return{updates:i}}({fetchImpl:this.fetchImpl,bridge:e,offset:n});if(s.conflict)this.bridgeErrors.set(e.id,s.conflict);else for(const t of s.updates){if(void 0!==t.updateId){const n=`${e.id}:${t.updateId}`;if(this.seenTelegramUpdates.has(n)||t.updateId<=(this.telegramOffsets.get(e.id)||0))continue;this.seenTelegramUpdates.add(n),this.telegramOffsets.set(e.id,t.updateId),this.updateChatBridge(e.id,{settings:{...e.settings,telegramLastUpdateId:t.updateId}})}if("callbackQuery"in t){const n=t.callbackQuery;await this.handleTelegramCallback(e,n).catch(async t=>{const s=Qi(n.message),i=Qi(s.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,Wi(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${Wi(t)}`).catch(t=>{this.bridgeErrors.set(e.id,Wi(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const n=await oi({fetchImpl:this.fetchImpl,bridge:e,message:t.message,onImageDownloadError:({fileId:t,fileName:n,error:s})=>{this.logWarn({bridgeId:e.id,fileId:t,fileName:n,error:Wi(s)},"telegram image download skipped")}}),s=t.rawText.trim();(s||n.length)&&(this.logInfo({bridgeId:e.id,chatId:t.chatId,messageId:t.messageId,replyToMessageId:t.replyToMessageId,hasQuote:Boolean(t.quoteText),textPreview:Vi(s)},"telegram chat gateway message received"),await this.handleTelegramIncomingText(e,t.chatId,t.userId,s,n,{sourceMessageId:t.messageId,replyToMessageId:t.replyToMessageId,quoteText:t.quoteText},{autoBegin:t.autoBegin}))}}async handleTelegramIncomingText(e,t,n,s,i=[],a={},r={}){const o=s.trim(),d=o.split(/\s+/,1)[0]?.toLowerCase()||"",c=Bi(e.id,t,n),l=this.telegramMessageAggregates.get(c);return r.autoBegin&&!l?(this.startTelegramAggregate(c,e,t,n,a),this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!0),void await this.sendTelegramMessage(e,t,"Image received. Continue sending messages, then send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Gn([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):"/begin"===d?(this.startTelegramAggregate(c,e,t,n,a),void await this.sendTelegramMessage(e,t,"Started collecting messages. Send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Gn([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):void("/end"!==d?l?.explicit?this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!0):this.isImmediateTelegramCommand(o)?await this.dispatchTelegramGatewayMessage(e,t,n,s,i,a):this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!1):await this.flushTelegramAggregate(c,"manual"))}isImmediateTelegramCommand(e){return e.trim().startsWith("/")}startTelegramAggregate(e,t,n,s,i={}){const a=this.telegramMessageAggregates.get(e);a?.timer&&clearTimeout(a.timer),this.telegramMessageAggregates.set(e,{bridgeId:t.id,chatId:n,userId:s,texts:[],attachments:[],context:i,explicit:!0})}enqueueTelegramAggregate(e,t,n,s,i,a,r,o){const d=this.telegramMessageAggregates.get(e);d?.timer&&clearTimeout(d.timer);const c=d||{bridgeId:t.id,chatId:n,userId:s,texts:[],attachments:[],context:r,explicit:o};var l,p;c.bridgeId=t.id,c.chatId=n,c.userId=s,c.context=(l=c.context,p=r,{sourceMessageId:l?.sourceMessageId??p.sourceMessageId,replyToMessageId:l?.replyToMessageId??p.replyToMessageId,quoteText:l?.quoteText||p.quoteText}),c.explicit=c.explicit||o,i.trim()&&c.texts.push(i),c.attachments.push(...a),c.explicit?c.timer=void 0:c.timer=setTimeout(()=>{this.flushTelegramAggregate(e,"timer")},1e3),this.telegramMessageAggregates.set(e,c)}async flushTelegramAggregate(e,t){const n=this.telegramMessageAggregates.get(e);if(!n)return!1;n.timer&&clearTimeout(n.timer),this.telegramMessageAggregates.delete(e);const s=this.service.requireChatBridge(n.bridgeId),i=n.texts.join("\n\n").trim();return i||n.attachments.length?(await this.dispatchTelegramGatewayMessage(s,n.chatId,n.userId,i,n.attachments,n.context),!0):("timer"!==t&&await this.sendTelegramMessage(s,n.chatId,"No messages collected.",{replyToMessageId:n.context?.sourceMessageId}),!1)}async handleTelegramAggregateEndCallback(e,t,n,s,i){const a=this.telegramMessageAggregateEndTokens.get(s)||"";if(a!==Bi(e.id,t,i))return void await this.answerTelegramCallback(e,n,"This collection is not active here");const r=await this.flushTelegramAggregate(a,"callback");await this.answerTelegramCallback(e,n,r?"Collected message sent":"No messages collected")}telegramAggregateEndCallbackData(e){const t=o.createHash("sha256").update(e).digest("base64url").slice(0,16);return this.telegramMessageAggregateEndTokens.set(t,e),`task_handoff:cp_msg_end:${t}`}async dispatchTelegramGatewayMessage(e,t,n,s,i=[],a={}){try{const r=void 0!==a.replyToMessageId?this.telegramReplyAiSessionTarget(e.id,t,a.replyToMessageId):void 0,o=function(e,t){const n=String(t||"").trim(),s=e.trim();return n?[`引用:${n}`,s].filter(Boolean).join("\n"):s}(s,a.quoteText),d=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"telegram",bridgeId:e.id,chatSessionId:t,userId:n},message:{text:o,attachments:i},...r?{target:{instanceId:r.instanceId,aiSessionId:r.sessionId}}:{}}),c=Mi(d),l=qi(d);if(c){const n=await this.sendTelegramMessage(e,t,c,{replyMarkup:l,replyToMessageId:a.sourceMessageId});this.logInfo({bridgeId:e.id,chatId:t,sourceMessageId:a.sourceMessageId,replyToMessageId:a.replyToMessageId,replyTargetInstanceId:r?.instanceId,replyTargetSessionId:r?.sessionId,sentMessageId:Number(Qi(n).message_id)||void 0,routed:!0===Qi(d).routed,replyPreview:Vi(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,n,c)}}catch(n){this.bridgeErrors.set(e.id,Wi(n)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${Wi(n)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,Wi(t))})}}async handleTelegramCallback(e,t){const n=Gi(t.data),s=Qi(t.from),i=Qi(t.message),a=Qi(i.chat),r=void 0!==a.id?String(a.id):e.defaultChatId,o=void 0!==s.id?String(s.id):void 0;if(!r||!this.telegramAllowed(e,o))return;const d=n.match(/^task_handoff:cp_ai_cancel:([^:]+)$/);if(d){const n=this.aiSessionCancelTokens.get(d[1]);return n?void await this.handleTelegramAiSessionCancelCallback(e,r,Gi(t.id),n.instanceId,n.sessionId,o,Ui(i)):void await this.answerTelegramCallback(e,Gi(t.id),"This AI session action expired")}const c=n.match(/^task_handoff:cp_ai_steer:([^:]+)$/);if(c){const n=this.aiSessionQueueActionTokens.get(c[1]);return n&&"steer"===n.type?void await this.handleTelegramAiSessionQueueSteerCallback(e,r,Gi(t.id),n.instanceId,n.sessionId,n.queueId,o,Ui(i)):void await this.answerTelegramCallback(e,Gi(t.id),"This AI session action expired")}const l=n.match(/^task_handoff:cp_ai_qdel_menu:([^:]+)$/);if(l){const n=this.aiSessionQueueActionTokens.get(l[1]);return n&&"delete-menu"===n.type?void await this.handleTelegramAiSessionQueueDeleteMenuCallback(e,r,Gi(t.id),n.instanceId,n.sessionId,o,Ui(i)):void await this.answerTelegramCallback(e,Gi(t.id),"This AI session action expired")}const p=n.match(/^task_handoff:cp_ai_qdel:([^:]+)$/);if(p){const n=this.aiSessionQueueActionTokens.get(p[1]);return n&&"delete-item"===n.type?void await this.handleTelegramAiSessionQueueDeleteItemCallback(e,r,Gi(t.id),Ui(i),n.instanceId,n.sessionId,n.queueId,o):void await this.answerTelegramCallback(e,Gi(t.id),"This AI session action expired")}const u=n.match(/^task_handoff:cp_msg_end:([^:]+)$/);if(u)return void await this.handleTelegramAggregateEndCallback(e,r,Gi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(n);if(!g)return void await this.answerTelegramCallback(e,Gi(t.id),"Unsupported action");const h=await this.service.handleChatGatewayAction({source:{channel:"telegram",bridgeId:e.id,chatSessionId:r,userId:o},action:g});if("pending-decision"===g.type&&$i(h)){await this.answerTelegramCallback(e,Gi(t.id),"");const n=Ui(i);return void(void 0!==n&&await this.deleteTelegramStandaloneApprovalMessage(e,r,n))}await this.answerTelegramCallback(e,Gi(t.id),Gi(h.message)||"updated");const m=Mi(h),f=qi(h),I=i.message_id;m&&"number"==typeof I?await this.editTelegramMessage(e,r,I,m,{replyMarkup:f}):m&&await this.sendTelegramMessage(e,r,m,{replyMarkup:f})}async deleteTelegramStandaloneApprovalMessage(e,t,n){[...this.telegramProgress.entries.values()].find(s=>s.messageId===n&&s.route?.bridgeId===e.id&&s.route?.chatId===t)||await this.deleteTelegramMessage(e,t,n).catch(s=>{this.logWarn({bridgeId:e.id,chatId:t,messageId:n,error:Wi(s)},"telegram callback message delete failed")})}async handleTelegramAiSessionCancelCallback(e,t,n,s,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,s,i,r))try{await this.service.interruptAiSession(s,i),await this.answerTelegramCallback(e,n,"Interrupt sent"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,userId:a},"telegram ai session cancel sent")}catch(t){this.bridgeErrors.set(e.id,Wi(t)),await this.answerTelegramCallback(e,n,`Interrupt failed: ${Vi(Wi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueSteerCallback(e,t,n,s,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,s,i,o))try{await this.service.steerAiSessionQueuedMessage(s,i,a),await this.answerTelegramCallback(e,n,"Steered queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,queueId:a,userId:r},"telegram ai session queued message steered")}catch(t){this.bridgeErrors.set(e.id,Wi(t)),await this.answerTelegramCallback(e,n,`Steer failed: ${Vi(Wi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteMenuCallback(e,t,n,s,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,s,i,r))try{const r=Qi(await this.service.aiSessionQueue(s,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>Qi(e)).filter(e=>"queued"===Gi(e.status)&&Gi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,n,"Queue is empty");const d=o.slice(0,ta).map((e,t)=>[{text:aa(Gi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(s,i,Gi(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:Gn(d)}),await this.answerTelegramCallback(e,n,"Select a queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,itemCount:o.length,userId:a},"telegram ai session queue delete menu sent")}catch(t){this.bridgeErrors.set(e.id,Wi(t)),await this.answerTelegramCallback(e,n,`Queue menu failed: ${Vi(Wi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteItemCallback(e,t,n,s,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,i,a,s))try{await this.service.removeAiSessionQueuedMessage(i,a,r),await this.answerTelegramCallback(e,n,"Queued message deleted"),Number.isInteger(s)&&await this.deleteTelegramMessage(e,t,s).catch(n=>{this.logWarn({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,messageId:s,error:Wi(n)},"telegram ai session queue delete menu message delete failed")}),this.logInfo({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,userId:o},"telegram ai session queued message deleted")}catch(t){this.bridgeErrors.set(e.id,Wi(t)),await this.answerTelegramCallback(e,n,`Delete failed: ${Vi(Wi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}telegramAiSessionBindingActive(e,t,n,s){return this.service.listChatSessions().some(i=>i.bridgeId===e.id&&i.chatSessionId===t&&i.activeInstanceId===n&&i.activeAiSessionId===s)}telegramAiSessionActionAllowed(e,t,n,s,i){if(this.telegramAiSessionBindingActive(e,t,n,s))return!0;if(!Number.isInteger(i))return!1;const a=this.telegramReplyAiSessionTarget(e.id,t,i);return Boolean(a&&a.instanceId===n&&a.sessionId===s)}parseChatGatewayCallbackAction(e){return function(e,t){const n=e.match(/^task_handoff:cp_session:(\d+)$/);if(n)return{type:"ai-session",index:Number(n[1])};const s=e.match(/^task_handoff:cp_i:([^:]+)$/)||e.match(/^task_handoff:cp_instance_apps:([^:]+)$/);if(s)return s[0].startsWith("task_handoff:cp_i:")?{type:"instance-app-menu",instanceId:ni(t(s[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Ri(s[1])};const i=e.match(/^task_handoff:cp_a:([^:]+)$/)||e.match(/^task_handoff:cp_launch_app:([^:]+):([^:]+)$/);if(i){if(i[0].startsWith("task_handoff:cp_a:")){const e=t(i[1],"launch-app");return{type:"launch-app",instanceId:ni(e.instanceId,"instanceId"),appId:ni(e.appId,"appId")}}return{type:"launch-app",instanceId:Ri(i[1]),appId:Ri(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:ni(e.routeId,"routeId"),decision:si(e.decision)}}const r=e.match(/^task_handoff:approval:([^:]+:ai:[^:]+):(allow|deny|skip)$/);return r?{type:"pending-decision",routeId:r[1],decision:r[2]}:void 0}(e,(e,t)=>this.service.resolveChatActionToken(e,t))}dingdingAllowed(e,t){const n=String(t||"").trim();return!!n&&(0===e.allowedUserIds.length?(e.allowedUserIds=[n],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:n},"dingding user bound"),!0):e.allowedUserIds.includes(n))}telegramAllowed(e,t){const n=String(t||"").trim();return!!n&&(0===e.allowedUserIds.length?(e.allowedUserIds=[n],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:n},"telegram user bound"),!0):e.allowedUserIds.includes(n))}async answerTelegramCallback(e,t,n){return async function(e,t,n,s){t.token&&n&&await e(`https://api.telegram.org/bot${t.token}/answerCallbackQuery`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({callback_query_id:n,text:s.slice(0,200)})})}(this.fetchImpl,e,t,n)}async editTelegramMessage(e,t,n,s,i={}){return async function(e,t,n,s,i,a={}){if(t.token)return Vs(n=>e(`https://api.telegram.org/bot${t.token}/editMessageText`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),"editMessageText",i,{chat_id:n,message_id:s,...a.replyMarkup?{reply_markup:a.replyMarkup}:{}},a)}(this.fetchImpl,e,t,n,s,i)}async sendTelegramMessage(e,t,n,s={}){return Gs(this.fetchImpl,e,t,n,s)}async deleteTelegramMessage(e,t,n){return async function(e,t,n,s){if(!t.token||!Number.isInteger(s))return;const i=await e(`https://api.telegram.org/bot${t.token}/deleteMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({chat_id:n,message_id:s})}),a=await i.json().catch(()=>({}));if(!i.ok||!1===a.ok)throw new Error(a.description||`Telegram deleteMessage failed with HTTP ${i.status}`)}(this.fetchImpl,e,t,n)}async rememberTelegramProgressMessage(e,t,n,s,i){if(!0!==e.routed)return;const a=Zi(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(Qi(s).message_id);if(!(r&&o&&d&&Number.isInteger(c)))return void this.logWarn({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,messageId:Number.isInteger(c)?c:void 0,routed:e.routed},"telegram ai session progress message not remembered");const l=ra(r,o,d,t.id,n),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:n});this.telegramProgressMessageTargets.set(Fi(t.id,n,c),{instanceId:r,sessionId:o,turnId:d,messageId:c}),this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,key:l,messageId:c,progressKeys:this.telegramProgressKeysFor(r,o,t.id,n),textPreview:Vi(i)},"telegram ai session progress message remembered"),"queue"===function(e){return e.aiSession&&"action"in e.aiSession?e.aiSession.action:void 0}(e)&&p?.messageId&&p.messageId!==c&&(this.telegramProgressMessageTargets.delete(Fi(t.id,n,p.messageId)),await this.deleteTelegramMessage(t,n,p.messageId).catch(e=>{this.logWarn({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,replacedMessageId:p.messageId,replacementMessageId:c,error:Wi(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,n){const s=this.telegramProgressMessageTargets.get(Fi(e,t,n));if(s)return s;for(const[s,i]of this.telegramProgress.entries)if(i.messageId===n&&i.route?.bridgeId===e&&i.route?.chatId===t){const i=Hi(s,e,t);if(i){const s={...i,messageId:n};return this.telegramProgressMessageTargets.set(Fi(e,t,n),s),s}}}async pollAiSessionUpdates(){this.logAiSessionDelivery({stage:"poll-tick"});const e=await this.service.listAiSessions({refresh:!0}).catch(()=>{});if(!e)return void this.logAiSessionDelivery({stage:"poll-no-snapshot"});const t=new Map(e.instances.map(e=>[e.instanceId,e.aiSessions]));this.logAiSessionDelivery({stage:"poll-snapshot",instanceCount:t.size,instances:[...t.entries()].map(([e,t])=>({instanceId:e,sessionCount:t.sessions?.length||0,runningCount:t.runningCount,waitingCount:t.waitingCount,updatedAt:t.updatedAt}))});const n=await this.aiSessionInstanceNames();for(const[e,s]of t)await this.deliverAiSessionSnapshot(e,s,n)}async deliverAiSessionSnapshot(e,t,n){const s=Array.isArray(t.sessions)?t.sessions:[];if(this.pruneAiSessionDeliveryState(e,new Set(s.map(e=>e.id))),0===s.length)return;const i=n||await this.aiSessionInstanceNames(),a=await this.aiSessionPendingApprovalRoutes(e);await this.deliverOwnedTelegramProgress(e,s,i,a),await this.deliverActiveBindingTelegramProgress(e,s,i,a)}pruneAiSessionDeliveryState(e,t){for(const[n,s]of this.telegramProgress.entries){const i=s.route?.bridgeId&&s.route.chatId?this.telegramProgressMessageTargets.get(Fi(s.route.bridgeId,s.route.chatId,s.messageId||0))||Hi(n,s.route.bridgeId,s.route.chatId):void 0;i&&i.instanceId===e&&!t.has(i.sessionId)&&(s.timer&&clearTimeout(s.timer),this.telegramProgress.entries.delete(n),this.deliveredAiSessionFingerprints.delete(n),s.route?.bridgeId&&s.route.chatId&&s.messageId&&this.telegramProgressMessageTargets.delete(Fi(s.route.bridgeId,s.route.chatId,s.messageId)))}}async deliverActiveBindingTelegramProgress(e,t,n,s){for(const i of this.service.listChatSessions()){if(!i.bridgeId||i.activeInstanceId!==e||!i.activeAiSessionId)continue;const a=t.find(e=>e.id===i.activeAiSessionId);if(!a)continue;const r=pa(n.get(e)||e,a),o=la(a,r);if(!o){this.logAiSessionDelivery({stage:"skip-empty-text",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:Ji(ua(a))});continue}const d=oa(a);if(!d){this.logAiSessionDelivery({stage:"skip-empty-turn-id",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:Ji(ua(a))});continue}if(this.listTelegramOwnedProgressEntries(e).some(e=>e.route.bridgeId===i.bridgeId&&e.route.chatId===i.chatSessionId&&e.owner.sessionId===a.id&&e.owner.turnId===d))continue;const c=ra(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=da(a,r);if(!l||this.deliveredAiSessionFingerprints.get(c)===l){this.logAiSessionDelivery({stage:"skip-duplicate-fingerprint",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,hasFingerprint:Boolean(l),fingerprintMatched:this.deliveredAiSessionFingerprints.get(c)===l,latestTurn:Ji(ua(a)),textPreview:Vi(o)});continue}const p=sa({cancelCallbackData:this.aiSessionCancelCallbackData(e,a.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,a.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,a.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:s.get(a.id)||ia(e,a),session:a});if(this.logAiSessionDelivery({stage:"attempt",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),hasProgressEntry:this.telegramProgress.entries.has(c),status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:Ji(ua(a)),textPreview:Vi(o)}),await this.updateChatAiSessionProgress(i,o,c,p).catch(t=>(this.bridgeErrors.set(i.bridgeId||"",t instanceof Error?t.message:String(t)),this.logWarn({bridgeId:i.bridgeId,chatId:i.chatSessionId,instanceId:e,sessionId:a.id,turnId:d,key:c,error:Wi(t)},"chat ai session progress update failed"),!1)))this.logAiSessionDelivery({stage:"delivered",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:Vi(o)}),this.deliveredAiSessionFingerprints.set(c,l);else{if("telegram"===this.service.requireChatBridge(i.bridgeId).channel){const t=this.telegramProgress.entries.get(c),n=this.service.requireChatBridge(i.bridgeId);this.logAiSessionDelivery({stage:t?"telegram-update-not-delivered":"telegram-no-progress-entry",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),bridgeEnabled:n.enabled,bridgeChannel:n.channel,progressMessageId:t?.messageId,progressLastText:Vi(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:Ji(ua(a)),textPreview:Vi(o)});continue}await this.sendToChatBinding(i,{text:o}).catch(e=>(this.bridgeErrors.set(i.bridgeId||"",e instanceof Error?e.message:String(e)),!1))&&(this.logAiSessionDelivery({stage:"fallback-sent",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:Vi(o)}),this.deliveredAiSessionFingerprints.set(c,l))}}}async deliverOwnedTelegramProgress(e,t,n,s){for(const i of this.listTelegramOwnedProgressEntries(e)){const a=this.service.requireChatBridge(i.route.bridgeId);if("telegram"!==a.channel||!a.enabled)continue;const r=this.matchTelegramOwnedProgressSession(i,t);if(!r)continue;const o={bridgeId:i.route.bridgeId,chatSessionId:i.route.chatId},d=pa(n.get(e)||e,r),c=la(r,d),l=oa(r);if(!c||!l){this.logAiSessionDelivery({stage:"remembered-skip-turn-or-text",key:i.key,parsedTurnId:i.owner.turnId,currentTurnId:l,hasText:Boolean(c),instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}if(i.owner.turnId&&l!==i.owner.turnId){this.logAiSessionDelivery({stage:"remembered-skip-different-active-turn",key:i.key,ownerTurnId:i.owner.turnId,currentTurnId:l,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const p=l===i.owner.turnId?i.key:ra(e,r.id,l,i.route.bridgeId,i.route.chatId);if(p!==i.key&&this.telegramProgress.entries.has(p))continue;if(p!==i.key&&!this.rekeyTelegramOwnedProgress(i,p,{instanceId:e,sessionId:r.id,turnId:l}))continue;const u=da(r,d);if(!u||this.deliveredAiSessionFingerprints.get(p)===u){this.logAiSessionDelivery({stage:"remembered-skip-fingerprint",key:i.key,hasFingerprint:Boolean(u),fingerprintMatched:this.deliveredAiSessionFingerprints.get(p)===u,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const g=sa({cancelCallbackData:this.aiSessionCancelCallbackData(e,r.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,r.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,r.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:s.get(r.id)||ia(e,r),session:r}),h=await this.updateChatAiSessionProgress(o,c,p,g).catch(e=>(this.bridgeErrors.set(o.bridgeId||"",e instanceof Error?e.message:String(e)),!1));this.logAiSessionDelivery({stage:h?"remembered-delivered":"remembered-not-delivered",key:p,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId,textPreview:Vi(c)}),h&&(this.telegramProgressMessageTargets.set(Fi(i.route.bridgeId,i.route.chatId,i.messageId),{instanceId:e,sessionId:r.id,turnId:l,messageId:i.messageId}),this.deliveredAiSessionFingerprints.set(p,u))}}rekeyTelegramOwnedProgress(e,t,n){return!!this.telegramProgress.rekey(e.key,t)&&(this.deliveredAiSessionFingerprints.delete(e.key),this.telegramProgressMessageTargets.set(Fi(e.route.bridgeId,e.route.chatId,e.messageId),{...n,messageId:e.messageId}),!0)}logAiSessionDelivery(e){this.logInfo(e,"telegram ai session delivery flow")}telegramProgressKeysFor(e,t,n,s){const i=`${e}:${t}:`,a=`:${n}:${s}`;return[...this.telegramProgress.entries.keys()].filter(e=>e.startsWith(i)&&e.endsWith(a))}listTelegramOwnedProgressEntries(e){const t=[];for(const[n,s]of this.telegramProgress.entries){if(!s.route?.bridgeId||!s.route.chatId||!Number.isInteger(s.messageId))continue;const i=this.telegramProgressMessageTargets.get(Fi(s.route.bridgeId,s.route.chatId,s.messageId))||Hi(n,s.route.bridgeId,s.route.chatId);i&&i.instanceId===e&&t.push({key:n,route:{bridgeId:s.route.bridgeId,chatId:s.route.chatId},messageId:s.messageId,owner:{...i,messageId:s.messageId}})}return t}matchTelegramOwnedProgressSession(e,t){const n=t.find(t=>t.id===e.owner.sessionId);if(n)return n;if(e.owner.turnId){const n=t.find(t=>t.activeTurnId===e.owner.turnId||ua(t)?.id===e.owner.turnId||ua(t)?.providerTurnId===e.owner.turnId);if(n)return n}}async updateChatAiSessionProgress(e,t,n,s){if(!e.bridgeId)return this.logAiSessionDelivery({stage:"chat-update-skip-missing-bridge",chatId:e.chatSessionId,key:n}),!1;const i=this.service.requireChatBridge(e.bridgeId);if(!i.enabled)return this.logAiSessionDelivery({stage:"chat-update-skip-bridge-state",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,bridgeEnabled:i.enabled,bridgeChannel:i.channel}),!1;if("dingding"===i.channel){const a=this.dingdingBridges.get(i.id);if(!a)return!1;const r=await this.dingdingProgress.applyUpdate({bridge:i,dingdingRuntime:a,key:n,chatId:e.chatSessionId,text:t,replyMarkup:Li({actionRows:s})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,textPreview:Vi(t)},"dingding ai session progress updated"),r}if("telegram"!==i.channel)return!1;const a=this.telegramProgress.entries.get(n);if(!await this.telegramProgress.applyUpdate({bridge:i,key:n,chatId:e.chatSessionId,text:t,actionRows:s})){const t=this.telegramProgress.entries.get(n);return this.logAiSessionDelivery({stage:"telegram-update-returned-false",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,progressMessageId:a?.messageId||t?.messageId,progressLastText:Vi(a?.lastText||t?.lastText),progressHasPending:Boolean(a?.pending||t?.pending)}),!1}return this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,messageId:a?.messageId||this.telegramProgress.entries.get(n)?.messageId,textPreview:Vi(t)},a?"telegram ai session progress edited":"telegram ai session progress sent"),!0}async aiSessionInstanceNames(){const e=await this.service.boardAsync().catch(()=>[]);return new Map(e.map(e=>[e.id,e.name||e.id]))}async aiSessionPendingApprovalRoutes(e){const t=await this.service.listPendingRoutes().catch(()=>[]);return new Map(t.filter(t=>t.instanceId===e&&t.aiSessionId&&"approval"===t.kind).map(e=>[e.aiSessionId||"",e]))}aiSessionCancelCallbackData(e,t){const n=o.createHash("sha256").update(`${e}\0${t}`).digest("base64url").slice(0,16);return this.aiSessionCancelTokens.set(n,{instanceId:e,sessionId:t}),`task_handoff:cp_ai_cancel:${n}`}aiSessionQueueSteerCallbackData(e,t,n){return`task_handoff:cp_ai_steer:${this.aiSessionQueueActionToken({type:"steer",instanceId:e,sessionId:t,queueId:n})}`}aiSessionQueueDeleteMenuCallbackData(e,t){return`task_handoff:cp_ai_qdel_menu:${this.aiSessionQueueActionToken({type:"delete-menu",instanceId:e,sessionId:t})}`}aiSessionQueueDeleteItemCallbackData(e,t,n){return`task_handoff:cp_ai_qdel:${this.aiSessionQueueActionToken({type:"delete-item",instanceId:e,sessionId:t,queueId:n})}`}aiSessionQueueActionToken(e){const t=o.createHash("sha256").update([e.type,e.instanceId,e.sessionId,"queueId"in e?e.queueId:""].join("\0")).digest("base64url").slice(0,16);return this.aiSessionQueueActionTokens.set(t,e),t}}function Mi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function $i(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function qi(e){const t="object"==typeof e&&e&&"replyMarkup"in e?e.replyMarkup:void 0;if(t&&"object"==typeof t&&!Array.isArray(t))return t}function Li(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Gn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Ui(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Bi(e,t,n){return[e,t,n||""].join(":")}function Fi(e,t,n){return[e,t,String(n)].join(":")}function Hi(e,t,n){const s=`:${t}:${n}`;if(!e.endsWith(s))return;const i=e.slice(0,-s.length).split(":"),a=i[0]||"",r=i[1]||"",o=i.slice(2).join(":")||void 0;return a&&r?{instanceId:a,sessionId:r,turnId:o}:void 0}function Gi(e){return"string"==typeof e?e:""}function Wi(e){return e instanceof Error?e.message:String(e)}function Vi(e,t=160){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function Ki(e){const t=Yi(e.body.cardActionData||e.body.content),n=Yi(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:Gi(e.body.outTrackId),spaceId:Gi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:Vi(e.callbackData,120),actionIds:[...Xi(n.actionIdList),...Xi(t.actionIdList),Gi(n.actionId),Gi(t.actionId),Gi(e.body.actionId)].filter(Boolean).slice(0,5),cardActionDataKeys:Object.keys(t).sort(),privateDataKeys:Object.keys(n).sort(),paramKeys:Object.keys(e.params).sort(),hasSessionWebhook:Boolean(e.sessionWebhook)}}function Ji(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:Vi(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:Vi(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function Qi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Yi(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return Qi(JSON.parse(e))}catch{return{}}return{}}function Xi(e){return Array.isArray(e)?e.map(e=>Gi(e)).filter(Boolean):[]}function Zi(e){const t=e.aiSession&&"session"in e.aiSession?e.aiSession:void 0,n=t?.session||(e.aiSession&&"id"in e.aiSession?e.aiSession:void 0);return{instanceId:e.instanceId||e.instance?.id||"",sessionId:e.aiSessionId||n?.id||e.binding?.activeAiSessionId||"",turnId:e.turnId||e.providerTurnId||t?.turnId||t?.providerTurnId||(n?oa(n):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:n?.activeTurnId,latestTurnId:n?ua(n)?.id:void 0}}}function ea(e,t){const n=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,s="approval"===e.kind?`/approve ${e.id}\n/deny ${e.id}\n/skip ${e.id}`:`/reply ${e.id} <message>`,i=`[${n}]\n${e.result}\n\n${s}`;return"approval"!==e.kind?{text:i}:{text:i,replyMarkup:Gn([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const ta=5,na=48;function sa(e){const{cancelCallbackData:t,queueSteerCallbackData:n,queueDeleteMenuCallbackData:s,permissionCallbackData:i,pendingApproval:a,session:r}=e,o="approval"===a?.kind?[[{text:"Allow",callbackData:i(a.id,"allow")},{text:"Skip",callbackData:i(a.id,"skip")},{text:"Deny",callbackData:i(a.id,"deny")}]]:[];if("running"!==r.status)return o;const d=(r.queue?.items||[]).filter(e=>"queued"===e.status).slice(0,ta);return[...d.map((e,t)=>[{text:aa(e.message,t),callbackData:n(e.id)}]),d.length?[{text:"Delete Queue",callbackData:s()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function ia(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function aa(e,t){const n=`${t+1}. `,s=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,na-n.length),a=Array.from(s);return`${n}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:s}`}function ra(e,t,...n){return[e,t,...n].join(":")}function oa(e){const t=ua(e);return t?.id?t.id:Gi(e.activeTurnId)}function da(e,t=""){const n=ua(e);return[t,n?.id||"",String(n?.revision??""),n?.status||e.status||"",n?.phase||e.phase||"",n?.lastMessage||"",n?.summary||"",n?.userPrompt||"",e.error||"",e.currentTool?.name||"",e.currentTool?.inputPreview||"",ca(e)].join("\0")}function ca(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function la(e,t){const n=function(e,t){return t?t.lastMessage?.trim()?t.lastMessage.trim():t.summary?.trim()?t.summary.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:"":e.lastMessage?.trim()?e.lastMessage.trim():e.summary?.trim()?e.summary.trim():e.error?.trim()?"failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:""}(e,ua(e));return n?`${t}\n${n}`:""}function pa(e,t){const n=[t.status,t.phase&&"unknown"!==t.phase?t.phase:void 0].filter(Boolean).join("/");return`${e} · ${t.agent||"ai"} ${n||"unknown"}`}function ua(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}function ga(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("receiver.")?"receiver":e.startsWith("conversation.")?"conversations":e.startsWith("trigger.")?"triggers":e.startsWith("channel.")?"channels":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}class ha{clients=new Set;listeners=new Set;seq=0;connect(e){e.topics=new Set(["*"]),this.clients.add(e),e.on("close",()=>{this.clients.delete(e)}),e.on("message",t=>{const n=function(e){try{const t=JSON.parse(String(e||"{}"));return t&&"object"==typeof t?t:void 0}catch{return}}(t);"subscribe"===n?.type&&(e.topics=new Set(n.topics?.length?n.topics:["*"]))})}publish(e,t,n={}){const s=this.createEvent(e,t,n),i=s.topic,a=JSON.stringify(s);for(const e of this.listeners)try{e(s)}catch{this.listeners.delete(e)}for(const t of this.clients)if(t.readyState===t.OPEN&&ma(t.topics,i,e))try{t.send(a)}catch{this.clients.delete(t)}return s}on(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}send(e,t,n,s={}){const i=this.createEvent(t,n,s);return e.send(JSON.stringify(i)),i}createEvent(e,t,n={}){return{v:1,id:`evt_${Date.now().toString(36)}_${Math.random().toString(16).slice(2)}`,seq:++this.seq,type:e,topic:n.topic||ga(e),createdAt:(new Date).toISOString(),payload:t,scope:n.scope}}}function ma(e,t,n){return!e||e.has("*")||e.has(t)||e.has(n)}const fa={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},Ia=r.z.object({instanceId:r.z.string().trim().min(1).max(120),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.literal("image").default("image"),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),data:r.z.string().min(1).max(31457280)}).strict();class ya{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const s=Ia.parse(e),i=function(e,t){const n=/^data:([^;,]+);base64,(.+)$/i.exec(e.trim()),s=n?.[1]||t,i=n?.[2]||e;return{mime:s,buffer:Buffer.from(i,"base64")}}(s.data,s.mime),a=i.mime.toLowerCase(),r=fa[a];if(!r)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>I)throw new Error("Image must be between 1 byte and 20971520 bytes.");const d=`att_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,l=new Date,p=new Date(l.getTime()+this.ttlMs),u=n.join(c.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(u,{recursive:!0});const g=function(e){return n.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(s.name),h=n.join(u,`${d}-${g.endsWith(r)?g:`${g}${r}`}`);t.writeFileSync(h,i.buffer);const m={id:d,instanceId:s.instanceId,sessionId:s.sessionId,kind:"image",name:g,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:h,createdAt:l.toISOString(),expiresAt:p.toISOString()};return this.items.set(d,m),this.publicAttachment(m)}resolveRefs(e,t,n){this.pruneExpired();const s=r.z.array(z).max(6).default([]).parse(e),i=s.map(e=>this.requireAttachment(e,t,n));if(i.reduce((e,t)=>e+t.size,0)>y)throw new Error("Images must be 41943040 bytes or less in total.");for(const e of s)this.consumeAttachment(e.id);return i}requireAttachment(e,t,n){const s=this.items.get(e.id);if(!s)throw new Error(`AI session attachment not found or expired: ${e.id}`);if(e.kind&&e.kind!==s.kind)throw new Error(`AI session attachment kind mismatch: ${e.id}`);if(s.instanceId!==t||s.sessionId!==n)throw new Error(`AI session attachment scope mismatch: ${e.id}`);return C.parse({id:s.id,kind:s.kind,name:s.name,mime:s.mime,size:s.size,data:s.data})}consumeAttachment(e){const n=this.items.get(e);if(n){this.items.delete(e);try{t.unlinkSync(n.path)}catch{}}}publicAttachment(e){return{id:e.id,kind:e.kind,name:e.name,mime:e.mime,size:e.size,expiresAt:e.expiresAt}}pruneExpired(){const e=Date.now();for(const[n,s]of this.items)if(!(Date.parse(s.expiresAt)>e)){this.items.delete(n);try{t.unlinkSync(s.path)}catch{}}}}function ba(e){return e.readyState===(e.OPEN??1)}function wa(e,t,n){e.close(function(e){if("number"==typeof e&&Number.isInteger(e))return e>=3e3&&e<=4999||e>=1e3&&e<=1014&&1004!==e&&1005!==e&&1006!==e?e:void 0}(t),function(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):"string"==typeof e?e:"";if(Buffer.byteLength(t,"utf8")<=123)return t;let n="";for(const e of t){const t=`${n}${e}`;if(Buffer.byteLength(t,"utf8")>123)break;n=t}return n}(n)||void 0)}function Aa(e,t){e.send(t.data,{binary:t.isBinary})}function Sa(e,t){if(ba(e))for(const n of t.splice(0))Aa(e,n)}const va=r.z.object({nodeId:r.z.string().trim().min(1).max(120)}).strict();function ka(e,t){if(!t)throw Object.assign(new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`),{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"});return t}function Ta(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Ea=new Map;function _a(e,t){if("paired-hmac"!==e.auth.mode||!e.auth.keyId||!e.auth.secret){const e=new Error("Reverse tunnel requires paired-HMAC node auth.");throw Object.assign(e,{statusCode:401,code:"NODE_AGENT_TUNNEL_REQUIRES_PAIRED_HMAC"}),e}const n=function(e){const t=new Map;for(const[n,s]of Object.entries(e)){const e=Array.isArray(s)?s.join(","):"string"==typeof s?s:void 0===s?"":String(s);t.set(n.toLowerCase(),e)}return{nodeId:t.get("x-taskhandoff-node-id")||"",keyId:t.get("x-taskhandoff-key-id")||"",timestamp:t.get("x-taskhandoff-timestamp")||"",nonce:t.get("x-taskhandoff-nonce")||"",bodySha256:t.get("x-taskhandoff-body-sha256")||"",signature:t.get("x-taskhandoff-signature")||""}}(t.headers);if(n.nodeId!==e.id)throw Object.assign(new Error("Invalid reverse tunnel node id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NODE_MISMATCH"});if(n.keyId!==e.auth.keyId)throw Object.assign(new Error("Invalid reverse tunnel key id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_KEY_INVALID"});const s=Date.parse(n.timestamp);if(!Number.isFinite(s)||Math.abs(Date.now()-s)>Cs)throw Object.assign(new Error("Reverse tunnel timestamp is outside the allowed window."),{statusCode:401,code:"NODE_AGENT_TUNNEL_TIMESTAMP_INVALID"});const i=Date.now()-Cs;for(const[e,t]of Ea)t<=i&&Ea.delete(e);const a=`${e.id}:${n.keyId}:${n.nonce}`;if(!n.nonce||Ea.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=Ns("");if(n.bodySha256!==r)throw Object.assign(new Error("Reverse tunnel body hash is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_BODY_HASH_INVALID"});const d=t.url||`/api/node-agent/tunnel?nodeId=${encodeURIComponent(e.id)}`,c=zs(e.auth.secret,{keyId:n.keyId,method:"GET",pathWithQuery:d,timestamp:n.timestamp,nonce:n.nonce,bodySha256:r});if(!function(e,t){const n=Buffer.from(e,"hex"),s=Buffer.from(t,"hex");return n.length===s.length&&o.timingSafeEqual(n,s)}(n.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});Ea.set(a,Date.now()+Cs)}class Ca{sockets=new Map;events;constructor(e){this.events=e}attach(e,t){const n=this.sockets.get(e);if(n){for(const t of n.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} was replaced.`));for(const[e,t]of n.streams)n.streams.delete(e),t.downstream.close(1011,"Reverse tunnel was replaced.")}const s=new Map;this.sockets.set(e,{socket:t,pending:s,streams:new Map}),t.on?.("close",()=>this.detach(e,t)),t.on?.("error",()=>this.detach(e,t))}detach(e,t){const n=this.sockets.get(e);if(n&&(!t||n.socket===t)){this.sockets.delete(e);for(const t of n.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} disconnected.`));for(const[e,t]of n.streams)n.streams.delete(e),t.downstream.close(1011,"Reverse tunnel disconnected.")}}handleMessage(e,t){if(this.handleNodeAgentEvent(e,t))return!0;if(this.handleWebSocketMessage(e,t))return!0;if("node-agent.response"!==t.type)return!1;const n="string"==typeof t.requestId?t.requestId:"",s=this.sockets.get(e),i=s?.pending.get(n);if(!i||!s)return!0;s.pending.delete(n),clearTimeout(i.timer);const a=Number(t.status)||502,r=t.headers&&"object"==typeof t.headers?t.headers:{},o="string"==typeof t.body?t.body:"";if(t.error&&"object"==typeof t.error){const e=t.error,n=new Error("string"==typeof e.message?e.message:`Reverse tunnel request failed with HTTP ${a}`);return Object.assign(n,{statusCode:a,code:"string"==typeof e.code?e.code:"NODE_AGENT_REVERSE_REQUEST_FAILED"}),i.reject(n),!0}return i.resolve(new Response(o,{status:a,headers:r})),!0}handleNodeAgentEvent(e,t){if(!("string"==typeof t.type?t.type:"").startsWith("node-agent.event."))return!1;const n=t.event&&"object"==typeof t.event&&!Array.isArray(t.event)?t.event:void 0,s="string"==typeof n?.type?n.type:"";if(!s)return!0;const i="payload"in n?n.payload:{},a=n.scope&&"object"==typeof n.scope&&!Array.isArray(n.scope)?n.scope:{},r="string"==typeof t.instanceId?t.instanceId:void 0;return this.events?.publish(s,i,{topic:"string"==typeof n.topic?n.topic:void 0,scope:{...a,nodeId:e,instanceId:"string"==typeof a.instanceId?a.instanceId:"string"==typeof n.instanceId?n.instanceId:r}}),!0}async request(e,t,n={}){const s=this.sockets.get(e.id);if(!s){const t=new Error(`Reverse tunnel for node ${e.id} is not connected.`);throw Object.assign(t,{statusCode:503,code:"NODE_AGENT_REVERSE_TUNNEL_OFFLINE"}),t}const i=Ta(),a="string"==typeof n.body?n.body:void 0===n.body||null===n.body?void 0:String(n.body),r={type:"control-plane.request",requestId:i,route:t,init:{method:n.method||"GET",headers:(o=n.headers,o?o instanceof Headers?Object.fromEntries(o.entries()):Array.isArray(o)?Object.fromEntries(o.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(o).map(([e,t])=>[e,String(t)])):{}),body:a}};var o;return new Promise((n,a)=>{const o=setTimeout(()=>{s.pending.delete(i);const n=new Error(`Reverse tunnel request to ${e.id}${t} timed out.`);Object.assign(n,{statusCode:504,code:"NODE_AGENT_REVERSE_REQUEST_TIMEOUT"}),a(n)},3e4);s.pending.set(i,{resolve:n,reject:a,timer:o}),s.socket.send(JSON.stringify(r))})}proxyWebSocket(e,t,n,s,i={}){const a=e.id,r=this.sockets.get(a);if(!r)return void t.close(1011,"Reverse tunnel is not connected.");const o=Ta(),d=e=>{try{r.socket.send(JSON.stringify(e))}catch{r.streams.delete(o),t.close(1011,"Reverse tunnel send failed.")}},c={downstream:t,pendingFrames:[]};r.streams.set(o,c),d({type:"control-plane.websocket.open",streamId:o,route:n,protocols:Array.isArray(s)?s:s?[s]:[],headers:i}),t.on("message",(e,n)=>{const s=r.streams.get(o);if(s&&!s.upstream)return s.pendingFrames.length>=256?(r.streams.delete(o),t.close(1011,"Reverse websocket stream pending frame limit exceeded."),void d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Reverse websocket stream pending frame limit exceeded."})):void s.pendingFrames.push({data:e,isBinary:Boolean(n)})}),t.on("close",(e,t)=>{const n=r.streams.get(o);n&&(r.streams.delete(o),n.upstream?.close("number"==typeof e?e:1e3,Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""),d({type:"control-plane.websocket.close",streamId:o,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""}))}),t.on("error",()=>{const e=r.streams.get(o);e&&(r.streams.delete(o),e.upstream?.close(1011,"Downstream websocket failed."),d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Downstream websocket failed."}))})}attachWebSocketStream(e,t,n){const s=this.sockets.get(e),i=s?.streams.get(t);if(!s||!i)return n.close(1008,"Unknown reverse websocket stream."),!1;i.upstream=n;for(const e of i.pendingFrames.splice(0))n.send(e.data,{binary:e.isBinary});return function(e,t,n={}){const s=n.pendingFrameLimit??256;let i=!1,a=ba(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(n,i)=>{if(n.length>=s)return wa(e,1011,"WebSocket bridge pending frame limit exceeded."),void wa(t,1011,"WebSocket bridge pending frame limit exceeded.");n.push(i)};n.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(wa(e,1011,"Upstream websocket did not open."),wa(t))},n.upstreamOpenTimeoutMs)),e.on("open",()=>Sa(e,o)),t.on("open",()=>{c(),a=!0,n.onUpstreamOpen?.(),Sa(t,r)}),e.on("message",(e,n=!1)=>{const s={data:e,isBinary:Boolean(n)};ba(t)?Aa(t,s):l(r,s)}),t.on("message",(t,n=!1)=>{const s={data:t,isBinary:Boolean(n)};ba(e)?Aa(e,s):l(o,s)}),t.on("close",(t,s)=>{c(),n.onUpstreamClose?.(t,s),(a||i||!n.onUpstreamCloseBeforeOpen?.())&&wa(e,t,s)}),t.on("error",t=>{n.onUpstreamError?.(t),(a||i||!n.onUpstreamErrorBeforeOpen?.(t))&&wa(e)}),e.on("close",(e,s)=>{c(),i=!0,n.onClientClose?.(e,s),wa(t,e,s)}),e.on("error",e=>{c(),i=!0,n.onClientError?.(e),wa(t,1011,"WebSocket bridge client failed.")})}(i.downstream,n,{pendingFrameLimit:256,onClientClose:()=>{s.streams.delete(t)},onClientError:()=>{s.streams.delete(t)},onUpstreamClose:()=>{s.streams.delete(t)},onUpstreamError:()=>{s.streams.delete(t)}}),!0}handleWebSocketMessage(e,t){const n="string"==typeof t.type?t.type:"";if(!n.startsWith("node-agent.websocket."))return!1;const s=this.sockets.get(e),i="string"==typeof t.streamId?t.streamId:"",a=s?.streams.get(i);return!a||("node-agent.websocket.close"===n?(s?.streams.delete(i),a.downstream.close("number"==typeof t.code?t.code:1e3,"string"==typeof t.reason?t.reason:""),!0):"node-agent.websocket.error"!==n||(s?.streams.delete(i),a.downstream.close(1011,"string"==typeof t.message?t.message:"Reverse websocket proxy failed."),!0))}}class Na{sockets=new Map;service;tunnel;timer;constructor(e,t){this.service=e,this.tunnel=t}start(){this.sync(),this.timer=setInterval(()=>this.sync(),5e3)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const e of this.sockets.values())e.close();this.sockets.clear()}syncNow(){this.sync()}sync(){const e=new Set;for(const t of this.service.listNodes()){const n=this.nodeEventsUrl(t);n&&(e.add(t.id),this.sockets.has(t.id)||this.connect(t,n))}for(const[t,n]of this.sockets)e.has(t)||(n.close(),this.sockets.delete(t))}connect(e,t){const n=xs(t),s=n?void 0:new URL(t),i=function(e,t){const n=function(e){return"paired-hmac"===e.auth.mode&&e.auth.secret?{keyId:e.auth.keyId,secret:e.auth.secret}:void 0}(e);if(n)return Os({nodeId:e.id,keyId:ka(e,n.keyId),secret:n.secret,method:t.method,pathWithQuery:t.pathWithQuery});const s=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return s?{authorization:`Bearer ${s}`}:void 0}(e,{method:"GET",pathWithQuery:s?`${s.pathname}${s.search}`:"/api/node-agent/events"}),a=n?Rs(n,"/events",void 0,i):new p.WebSocket(t,{headers:i});this.sockets.set(e.id,a),a.on("message",t=>{let n;try{n=JSON.parse(String(t))}catch{return}if(n&&"object"==typeof n&&!Array.isArray(n)){const t=n,s="string"==typeof t.type&&t.type.startsWith("node-agent.")?t:{type:"node-agent.event.forwarded",event:t};this.tunnel.handleMessage(e.id,s)}}),a.on("close",()=>{this.sockets.get(e.id)===a&&this.sockets.delete(e.id)}),a.on("error",()=>{a.close()})}nodeEventsUrl(e){const t=e.controlEndpoint||e.endpoint;if(t&&"reverse-wss"!==e.connectionMode){if(xs(t))return t;try{const e=new URL("/api/node-agent/events",t.replace(/\/$/,""));return e.protocol="https:"===e.protocol?"wss:":"ws:",e.toString()}catch{return}}}}class za extends Error{code;lockPath;owner;constructor(e,t,n="process"){super(`${"control-plane"===n?"Control plane":"node-agent"===n?"Node agent":"Process"} is already running${t?.pid?` pid ${t.pid}`:""}. Lock: ${e}`),this.name="ProcessSingletonError",this.code="control-plane"===n?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===n?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function Oa(e){return n.join(e,"owner.json")}function xa(e){try{return JSON.parse(t.readFileSync(Oa(e),"utf8"))}catch{return}}function Da(e){if(!Number.isInteger(e)||Number(e)<=0)return!1;try{return process.kill(Number(e),0),!0}catch(e){return"EPERM"===e.code}}function Ra(e,n){t.writeFileSync(Oa(e),`${JSON.stringify(n,null,2)}\n`,"utf8")}function ja(e,t,n){if(function(e,t,n){return"system"===e.type||("chat-bridge"===e.type?function(e,t){return"ai-session"===t.type?["read","send-message","approve","interrupt"].includes(e):"instance"===t.type?["read","start","stop","restart"].includes(e):"read"===e}(t,n):"admin"===e.role||("viewer"===e.role?"read"===t:"operator"===e.role&&function(e,t){return"read"===e||("instance"===t.type?["create","update","delete","start","stop","restart","proxy"].includes(e):"ai-session"===t.type?["send-message","approve","interrupt"].includes(e):("trigger"===t.type||"runtime"===t.type)&&["create","update","delete"].includes(e))}(t,n)))}(e,t,n))return;const s=new Error(`Actor is not allowed to ${t} ${n.type}${n.id?` ${n.id}`:""}.`);throw Object.assign(s,{statusCode:403,code:"CONTROL_PLANE_FORBIDDEN"}),s}const Pa=r.z.string().trim().min(1),Ma=r.z.object({id:Pa}).strict(),$a=r.z.object({id:Pa,keyId:Pa}).strict(),qa=r.z.object({id:Pa,runtimeId:Pa}).strict(),La=r.z.object({id:Pa,folderId:Pa}).strict(),Ua=r.z.object({id:Pa,sessionId:Pa}).strict(),Ba=r.z.object({id:Pa,sessionId:Pa,queueId:Pa}).strict(),Fa=r.z.object({configHash:Pa}).strict(),Ha=r.z.object({id:Pa,configHash:Pa}).strict(),Ga=r.z.object({id:Pa,sessionId:Pa,configHash:Pa}).strict(),Wa=r.z.object({path:r.z.string().optional(),depth:r.z.coerce.number().int().min(0).max(32).optional()}).strict(),Va=r.z.object({ids:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),Ka=r.z.object({source:r.z.object({channel:Je,bridgeId:r.z.string().trim().min(1).max(120).optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),action:r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("ai-session"),index:r.z.number().int().nonnegative()}).strict(),r.z.object({type:r.z.literal("instance-app-menu"),instanceId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("launch-app"),instanceId:r.z.string().trim().min(1).max(120),appId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("pending-decision"),routeId:r.z.string().trim().min(1).max(120),decision:r.z.enum(["allow","deny","skip"])}).strict()])}).strict();function Ja(e){const t=Ka.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const Qa=r.z.object({appId:r.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:r.z.string().trim().min(1).max(120).optional(),options:r.z.record(r.z.string(),r.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),Ya=r.z.object({title:r.z.string().trim().min(1).max(120)}).strict(),Xa=r.z.object({id:r.z.string().trim().min(1),direction:r.z.enum(["import","export"]),preset:r.z.string().trim().min(1).max(120)});function Za({app:e,service:t,events:n,appSessionAggregator:s,aiSessionAggregator:i,chatGateway:a,aiSessionAttachments:o,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}){!function({app:e,service:t,events:n}){e.get("/api/projects",async()=>({data:t.listProjects()})),e.post("/api/projects",async(e,s)=>{const i=t.createProject(e.body);return n.publish("project.created",{projectId:i.id}),s.code(201).send({data:i})}),e.get("/api/projects/:id",async e=>({data:t.requireProject(Ma.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const s=t.updateProject(Ma.parse(e.params).id,e.body);return n.publish("project.updated",{projectId:s.id}),{data:s}}),e.delete("/api/projects/:id",async e=>{const s=Ma.parse(e.params).id,i=t.deleteProject(s);return n.publish("project.deleted",{projectId:s,deleted:i}),{data:{deleted:i}}}),e.get("/api/models",async()=>({data:t.listModels()})),e.post("/api/models",async(e,s)=>{const i=t.createModel(e.body);return n.publish("model.created",{modelId:i.id}),s.code(201).send({data:i})}),e.post("/api/models/reorder",async e=>{const s=Va.parse(e.body),i=t.reorderModels(s.ids);return n.publish("model.reordered",{ids:s.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(Ma.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const s=t.updateModel(Ma.parse(e.params).id,e.body);return n.publish("model.updated",{modelId:s.id}),{data:s}}),e.delete("/api/models/:id",async e=>{const s=Ma.parse(e.params).id,i=t.deleteModel(s);return n.publish("model.deleted",{modelId:s,deleted:i}),{data:{deleted:i}}}),e.get("/api/images",async()=>({data:t.listImages()})),e.post("/api/images",async(e,s)=>{const i=t.createImage(e.body);return n.publish("image.created",{imageId:i.id}),s.code(201).send({data:i})}),e.get("/api/images/:id",async e=>({data:t.requireImage(Ma.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const s=t.updateImage(Ma.parse(e.params).id,e.body);return n.publish("image.updated",{imageId:s.id}),{data:s}}),e.delete("/api/images/:id",async e=>{const s=Ma.parse(e.params).id,i=t.deleteImage(s);return n.publish("image.deleted",{imageId:s,deleted:i}),{data:{deleted:i}}})}({app:e,service:t,events:n}),function({app:e,service:t,events:n,nodeAgentTunnel:s,nodeEventSubscriber:i,errorPayload:a}){e.get("/api/nodes",async()=>({data:t.listPublicNodes()})),e.post("/api/nodes/local/sync",async()=>{const e=await t.syncLocalNodeConnection();return i.syncNow(),n.publish("node.local-synced",{nodeId:e.id}),{data:t.requirePublicNode(e.id)}}),e.post("/api/nodes",async(e,s)=>{const a=await t.createNode(e.body);return i.syncNow(),n.publish("node.created",{nodeId:a.id}),s.code(201).send({data:t.requirePublicNode(a.id)})}),e.post("/api/nodes/:id/check",async e=>{const s=await t.checkNode(Ma.parse(e.params).id);return n.publish("node.checked",{nodeId:s.id}),{data:s}}),e.get("/api/nodes/:id/updates/jobs",async e=>({data:await t.listNodeUpdateJobs(Ma.parse(e.params).id)})),e.post("/api/nodes/:id/updates/check",async e=>{const s=Ma.parse(e.params).id,i=await t.checkNodeUpdate(s,Ae.parse(e.body));return n.publish("node.update.checked",{nodeId:s,target:i.target}),{data:i}}),e.post("/api/nodes/:id/updates/apply",async(e,s)=>{const i=Ma.parse(e.params).id,a=await t.applyNodeUpdate(i,Ae.parse(e.body));return n.publish("node.update.queued",{nodeId:i,updateJobId:a.id,target:a.target}),s.code(202).send({data:a})}),e.post("/api/nodes/:id/pairing/invites",async(e,s)=>{const i=Ma.parse(e.params),a=await t.createNodePairingInvite(i.id,e.body);return n.publish("node.pairing-invite.created",{nodeId:i.id}),s.code(201).send({data:a})}),e.post("/api/nodes/:id/remotes/connect",async e=>{const s=Ma.parse(e.params),i=await t.connectNodeToControlPlane(s.id,e.body);return n.publish("node.remote-connect.requested",{nodeId:s.id}),{data:i}}),e.get("/api/nodes/:id/remotes",async e=>{const n=Ma.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(n.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const s=$a.parse(e.params),i=await t.deleteNodeRemoteControlPlane(s.id,s.keyId);return n.publish("node.remote-key.deleted",{nodeId:s.id,keyId:s.keyId}),{data:i}}),e.post("/api/node-join/invites",async(e,s)=>{const i=t.createNodeJoinInvite(e.body);return n.publish("node-join.invite.created",{inviteId:i.id}),s.code(201).send({data:i})}),e.post("/api/node-join/complete",async(e,s)=>{const a=t.completeNodeJoin(e.body);return i.syncNow(),n.publish("node.joined",{nodeId:a.id}),s.code(201).send({data:t.requirePublicNode(a.id)})}),e.get("/api/nodes/:id/runtimes",async e=>({data:await t.listNodeRuntimes(Ma.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,s)=>{const i=Ma.parse(e.params),a=await t.createNodeRuntime(i.id,e.body);return n.publish("node.runtime.created",{nodeId:i.id,runtimeId:a.id}),s.code(201).send({data:a})}),e.patch("/api/nodes/:id/runtimes/:runtimeId",async e=>{const s=qa.parse(e.params),i=await t.updateNodeRuntime(s.id,s.runtimeId,e.body);return n.publish("node.runtime.updated",{nodeId:s.id,runtimeId:i.id}),{data:i}}),e.delete("/api/nodes/:id/runtimes/:runtimeId",async e=>{const s=qa.parse(e.params),i=await t.deleteNodeRuntime(s.id,s.runtimeId);return n.publish("node.runtime.deleted",{nodeId:s.id,runtimeId:s.runtimeId}),{data:i}}),e.post("/api/nodes/:id/runtimes/:runtimeId/check",async e=>{const s=qa.parse(e.params),i=await t.checkNodeRuntime(s.id,s.runtimeId);return n.publish("node.runtime.checked",{nodeId:s.id,runtimeId:i.id}),{data:i}}),e.get("/api/nodes/:id/docker/images",async e=>({data:await t.listNodeDockerImages(Ma.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Ma.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const n=Ma.parse(e.params),s=Wa.parse(e.query);return{data:await t.listNodeFolderTree(n.id,{path:s.path,depth:s.depth})}}),e.post("/api/nodes/:id/local-folders",async(e,s)=>{const i=Ma.parse(e.params),a=await t.createNodeLocalFolder(i.id,e.body);return n.publish("node.local-folder.created",{nodeId:i.id,folderId:a.id}),s.code(201).send({data:a})}),e.delete("/api/nodes/:id/local-folders/:folderId",async e=>{const s=La.parse(e.params),i=await t.deleteNodeLocalFolder(s.id,s.folderId);return n.publish("node.local-folder.deleted",{nodeId:s.id,folderId:s.folderId}),{data:i}}),e.get("/api/nodes/:id",async e=>({data:t.requirePublicNode(Ma.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const s=t.updateNode(Ma.parse(e.params).id,e.body);return i.syncNow(),n.publish("node.updated",{nodeId:s.id}),{data:t.requirePublicNode(s.id)}}),e.delete("/api/nodes/:id",async e=>{const s=Ma.parse(e.params).id,a=t.deleteNode(s);return i.syncNow(),n.publish("node.deleted",{nodeId:s,deleted:a}),{data:{deleted:a}}}),e.get("/api/node-runtimes",async()=>{const e=await t.listNodeRuntimesWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),function(e){const{app:t,service:n,nodeAgentTunnel:s,errorPayload:i}=e;t.get("/api/node-agent/tunnel",{websocket:!0},(e,t)=>{try{const i=va.parse(t.query),a=n.requireNode(i.nodeId);_a(a,t),s.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:ue,nodeId:a.id,serverTime:(new Date).toISOString(),capabilities:{reverseTunnel:"request-response",lifecycleCommands:!0,instanceApiProxy:!0}})),e.on("message",t=>{let n;try{n=JSON.parse(String(t))}catch{return void e.send(JSON.stringify({type:"control-plane.error",code:"INVALID_JSON"}))}const i=n&&"object"==typeof n?n:{};s.handleMessage(a.id,i)||("node-agent.ping"!==i.type?"node-agent.identify"!==i.type?e.send(JSON.stringify({type:"control-plane.error",code:"UNSUPPORTED_MESSAGE"})):e.send(JSON.stringify({type:"control-plane.identified",nodeId:a.id,serverTime:(new Date).toISOString()})):e.send(JSON.stringify({type:"control-plane.pong",nodeId:a.id,serverTime:(new Date).toISOString()})))})}catch(t){const n=i(t);e.send(JSON.stringify({type:"control-plane.error",code:n.code,message:n.message})),e.close(1008,n.message)}}),t.get("/api/node-agent/tunnel/streams/:streamId",{websocket:!0},(e,t)=>{try{const i=va.parse(t.query),a=n.requireNode(i.nodeId);_a(a,t);const r=t.params.streamId;s.attachWebSocketStream(a.id,r,e)}catch(t){const n=i(t);e.close(1008,n.message)}})}({app:e,service:t,nodeAgentTunnel:s,errorPayload:a})}({app:e,service:t,events:n,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}),e.get("/api/app-sessions",async e=>{const t=r.z.object({refresh:r.z.string().optional(),includeTombstones:r.z.string().optional(),instanceId:r.z.string().trim().min(1).optional(),sinceRevision:r.z.string().optional()}).parse(e.query||{});if(void 0!==t.sinceRevision){const e=Number(t.sinceRevision);if(!Number.isInteger(e)||e<0){const e=new Error("sinceRevision must be a non-negative integer.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}return{data:await s.delta({instanceId:t.instanceId,sinceRevision:e})}}return{data:await s.list({refresh:"true"===t.refresh||"1"===t.refresh,includeTombstones:"true"===t.includeTombstones||"1"===t.includeTombstones})}}),e.get("/api/controlled-instances",async()=>({data:await t.listControlledInstances()})),e.post("/api/controlled-instances",async(e,s)=>{const i=await t.createControlledInstance(e.body);return n.publish("instance.created",{instanceId:i.id}),s.code(201).send({data:i})}),e.get("/api/controlled-instances/:id",async e=>({data:await t.requireControlledInstance(Ma.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const s=await t.updateControlledInstance(Ma.parse(e.params).id,e.body);return n.publish("instance.updated",{instanceId:s.id}),{data:s}}),e.delete("/api/controlled-instances/:id",async e=>{const s=Ma.parse(e.params).id,i=await t.deleteControlledInstance(s);return n.publish("instance.deleted",{instanceId:s,deleted:i}),{data:{deleted:i}}}),e.post("/api/controlled-instances/:id/start",async e=>{const s=await t.startControlledInstance(Ma.parse(e.params).id);return n.publish("instance.started",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/stop",async e=>{const s=await t.stopControlledInstance(Ma.parse(e.params).id);return n.publish("instance.stopped",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/restart",async e=>{const s=await t.restartControlledInstance(Ma.parse(e.params).id);return n.publish("instance.restarted",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/apps/sessions",async e=>{const s=Ma.parse(e.params),i=Qa.parse(e.body),a=await t.launchAppSession(s.id,i.appId,{...i.options,...i.cwdFolderId?{cwdFolderId:i.cwdFolderId}:{}});return n.publish("instance.app-session.launched",{instanceId:s.id,sessionId:"string"==typeof a.id?a.id:void 0,appId:i.appId}),{data:a}}),e.post("/api/controlled-instances/:id/apps/sessions/:sessionId/stop",async e=>{const s=Ua.parse(e.params),i=await t.stopAppSession(s.id,s.sessionId);return n.publish("instance.app-session.stopped",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.patch("/api/controlled-instances/:id/apps/sessions/:sessionId",async e=>{const s=Ua.parse(e.params),i=Ya.parse(e.body),a=await t.renameAppSession(s.id,s.sessionId,i.title);return n.publish("instance.app-session.renamed",{instanceId:s.id,sessionId:s.sessionId,title:i.title}),{data:a}}),e.get("/api/controlled-instances/:id/app-sessions",async e=>{const t=Ma.parse(e.params),n=(await s.list()).instances.find(e=>e.instanceId===t.id);return{data:n?.appSessions||{runningCount:0,problemCount:0,sessions:[],updatedAt:(new Date).toISOString()}}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/messages",async e=>{const s=Ua.parse(e.params),i=D.parse(e.body||{}),a=o.resolveRefs(i.attachments,s.id,s.sessionId),r=await t.sendAiSessionMessage(s.id,s.sessionId,i.message,i.mode,a);return n.publish("instance.ai-session.message-sent",{instanceId:s.id,sessionId:s.sessionId}),{data:r}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const n=Ua.parse(e.params);return{data:await t.aiSessionQueue(n.id,n.sessionId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/steer",async e=>{const s=Ba.parse(e.params),i=await t.steerAiSessionQueuedMessage(s.id,s.sessionId,s.queueId);return n.publish("instance.ai-session.message-sent",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/retry",async e=>{const n=Ba.parse(e.params);return{data:await t.retryAiSessionQueuedMessage(n.id,n.sessionId,n.queueId)}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId",async e=>{const n=Ba.parse(e.params);return{data:await t.removeAiSessionQueuedMessage(n.id,n.sessionId,n.queueId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/interrupt",async e=>{const s=Ua.parse(e.params),i=await t.interruptAiSession(s.id,s.sessionId);return n.publish("instance.ai-session.interrupted",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/approval",async e=>{const s=Ua.parse(e.params),i=R.parse(e.body||{}),a=await t.resolveAiSessionApproval(s.id,s.sessionId,i.decision);return n.publish("instance.ai-session.approval-resolved",{instanceId:s.id,sessionId:s.sessionId,decision:i.decision}),{data:a}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const s=Xa.parse(e.params),i=await t.syncInstanceConfig(s.id,s.direction,s.preset);return n.publish("instance.config-synced",{instanceId:s.id,direction:s.direction,preset:s.preset}),{data:i}}),e.get("/api/instance-board",async()=>{const e=await t.boardWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),e.get("/api/ai-sessions",async(e,t)=>{const n=e.query;if(void 0!==n.sinceRevision){const e=Number(n.sinceRevision);if(!Number.isInteger(e)||e<0)return t.code(400).send({error:"invalid_since_revision"});try{return{data:await i.delta({instanceId:n.instanceId,sinceRevision:e})}}catch(e){if("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED"===e.message)return t.code(400).send({error:"instance_id_required"});throw e}}return{data:await i.list({refresh:"true"===n.refresh||"1"===n.refresh})}}),e.get("/api/triggers",async()=>({data:await t.listTriggers()})),e.post("/api/triggers",async(e,s)=>{const i=t.createTrigger(e.body);return n.publish("trigger.created",{configHash:i.configHash}),s.code(201).send({data:i})}),e.delete("/api/triggers/:configHash",async e=>{const s=Fa.parse(e.params),i=await t.deleteTrigger(s.configHash);return n.publish("trigger.deleted",{configHash:s.configHash}),{data:i}}),e.post("/api/triggers/:configHash/apply",async e=>{const s=Fa.parse(e.params),i=await t.applyTrigger(s.configHash,e.body||{});return n.publish("trigger.applied",{configHash:s.configHash}),{data:i}}),e.get("/api/controlled-instances/:id/triggers",async e=>{const n=Ma.parse(e.params);return{data:await t.listInstanceTriggers(n.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const s=Ua.parse(e.params),i=await t.bindAiSessionTrigger(s.id,s.sessionId,e.body||{});return n.publish("instance.ai-session.trigger-bound",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers/:configHash",async e=>{const s=Ga.parse(e.params),i=await t.unbindAiSessionTrigger(s.id,s.sessionId,s.configHash);return n.publish("instance.ai-session.trigger-unbound",{instanceId:s.id,sessionId:s.sessionId,configHash:s.configHash}),{data:i}}),e.post("/api/controlled-instances/:id/triggers/:configHash/run",async e=>{const s=Ha.parse(e.params),i=await t.runInstanceTrigger(s.id,s.configHash,e.body||{});return n.publish("instance.trigger.run",{instanceId:s.id,configHash:s.configHash}),{data:i}}),function({app:e,service:t,chatGateway:n}){e.get("/api/chat/sessions",async()=>({data:t.listChatSessions()})),e.get("/api/chat/sessions/:id",async e=>({data:t.requireChatSession(Ma.parse(e.params).id)})),e.get("/api/chat-gateway/status",async()=>({data:n.status()})),e.post("/api/chat-gateway/poll-ai-sessions",async()=>({data:await n.pollAiSessionsNow()})),e.get("/api/chat-gateway/bridges",async()=>({data:t.listChatBridges()})),e.post("/api/chat-gateway/bridges",async e=>({data:t.createChatBridge(e.body)})),e.patch("/api/chat-gateway/bridges/:id",async s=>{const i=Ma.parse(s.params).id,a=t.requireChatBridge(i),r=En.parse(s.body),o=t.updateChatBridge(i,s.body),d="boolean"==typeof r.enabled?r.enabled:void 0;return e.log.info({component:"control-plane-chat-gateway",action:"bridge-patch",bridgeId:i,hasEnabled:void 0!==d,requestedEnabled:d,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),void 0!==d&&d!==a.enabled&&(d?n.startBridge(i):n.stopBridge(i)),{data:o}}),e.post("/api/chat-gateway/bridges/:id/start",async s=>{const i=Ma.parse(s.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!0}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-start-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:n.startBridge(i)}}),e.post("/api/chat-gateway/bridges/:id/stop",async s=>{const i=Ma.parse(s.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!1}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-stop-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:n.stopBridge(i)}}),e.delete("/api/chat-gateway/bridges/:id",async e=>({data:{deleted:t.deleteChatBridge(Ma.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(Xe.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(Ja(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:a})}const er=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),tr=new Set(["content-encoding"]);function nr(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!er.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function sr(e){return Object.fromEntries(Object.entries(nr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function ir(e){const t=e["sec-websocket-protocol"],n=(Array.isArray(t)?t.join(","):"string"==typeof t?t:"").split(",").map(e=>e.trim()).filter(Boolean);return n.length?n:void 0}function ar(e,t){for(const[n,s]of Object.entries(t.headers)){const t=n.toLowerCase();er.has(t)||tr.has(t)||e.header(n,s)}return e.code(t.status).send(t.body)}function rr(e){return`/instances/${encodeURIComponent(e)}`}function or(e,t,n,s){const i=function(e,t){const n=e.split("?",1)[0]||"/";return t.toLowerCase().includes("text/html")&&!n.startsWith("/api/")}(s,Object.entries(t.headers).find(([e])=>"content-type"===e.toLowerCase())?.[1]||"")?Buffer.from(function(e,t){if(e.includes("__TASK_HANDOFF_PUBLIC_BASE__"))return e;const n=`<script>window.__TASK_HANDOFF_PUBLIC_BASE__=${JSON.stringify(t)};<\/script>`;return/<head(\s[^>]*)?>/i.test(e)?e.replace(/<head(\s[^>]*)?>/i,e=>`${e}\n${n}`):`${n}\n${e}`}(t.body.toString("utf8"),rr(n)),"utf8"):t.body;return ar(e,{...t,body:i})}function dr(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):e instanceof URLSearchParams?e.toString():"string"==typeof e?e:null==e?void 0:JSON.stringify(e)}function cr(e,t){if("string"!=typeof t||!e.hasContentTypeParser(t))try{e.addContentTypeParser(t,{parseAs:"buffer"},(e,t,n)=>n(null,t))}catch(e){if(e instanceof Error&&/Content type parser .* already present/i.test(e.message))return;throw e}}function lr(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function pr(e,t,n){const s=encodeURIComponent(e),i=encodeURIComponent(t),a=n.vnc&&"object"==typeof n.vnc?n.vnc:{};return"kasmvnc"===("string"==typeof a.backend?a.backend:"")?`/apps/access/vnc/proxy/api/apps/sessions/${i}/web/?token=${s}`:`/apps/access/vnc/proxy/api/novnc/vnc.html?token=${s}&path=${encodeURIComponent(`apps/access/vnc/proxy/api/apps/sessions/${i}/vnc?token=${s}`)}&autoconnect=1&resize=scale`}class ur{snapshots=new Map;history=new Map;listeners=new Set;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===b?K:e.type===w?J:e.type===A?Q:void 0;if(!t)return!1;const n=t.safeParse(e.payload);return n.success?(e.type===b&&this.applySnapshot(n.data),e.type===w&&this.applyPatch(n.data),e.type===A&&this.applyRemoved(n.data),!0):(this.logger?.warn?.({eventType:e.type,issues:n.error.issues,errorCode:"AI_SESSION_EVENT_INVALID"},"ai-session.aggregator.event.invalid"),!0)}onSnapshot(e){return this.listeners.add(e),()=>this.listeners.delete(e)}applySnapshot(e){return this.accept({type:b,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=new Map(t.snapshot.sessions.map(e=>[e.id,e]));for(const t of e.upserted)n.set(t.id,t);for(const t of e.removed)n.delete(t);return this.accept({type:w,payload:e},gr([...n.values()],e.meta.generatedAt,t.snapshot.staleCount))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=new Set(e.sessionIds);return this.accept({type:A,payload:e},gr(t.snapshot.sessions.filter(e=>!n.has(e.id)),e.meta.generatedAt,t.snapshot.staleCount))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view()}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const n=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>n)return Y.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!0,events:[]});if(e.sinceRevision===n)return Y.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!1,events:[]});const s=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!s.length||s[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return Y.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:i,events:i?[]:s.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:n}=e.payload,s=this.snapshots.get(n.instanceId);if(s&&s.revision>=n.snapshotRevision)return this.logger?.warn?.({traceId:n.traceId,instanceId:n.instanceId,currentRevision:s.revision,snapshotRevision:n.snapshotRevision},"ai-session.aggregator.event.stale"),!1;this.snapshots.set(n.instanceId,{snapshot:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt}),this.rememberEvent(n.instanceId,e);const i={instanceId:n.instanceId,aiSessions:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt};for(const e of this.listeners)try{e(i)}catch{this.listeners.delete(e)}return this.logger?.info?.({traceId:n.traceId,instanceId:n.instanceId,snapshotRevision:n.snapshotRevision,eventSeq:n.eventSeq,reason:n.reason,sessionCount:t.sessions.length},"ai-session.aggregator.event.accepted"),!0}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"ai-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"ai-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.aiSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const n=this.history.get(e)??[];n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const n=t.filter(e=>e.createdAtMs>=Date.now()-36e5);n.length?this.history.set(e,n):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED")}view(){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([e,t])=>({instanceId:e,aiSessions:t.snapshot,revision:t.revision,lastEventAt:t.lastEventAt}))}}}function gr(e,t,n){return B.parse({sessions:e,runningCount:e.filter(e=>"running"===e.status).length,waitingCount:e.filter(e=>"waiting"===e.status).length,staleCount:n,updatedAt:t})}class hr{snapshots=new Map;history=new Map;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===Tt?jt:e.type===Et?Pt:e.type===_t?Mt:void 0;if(!t)return!1;const n=t.safeParse(e.payload);return n.success?(e.type===Tt&&this.applySnapshot(n.data),e.type===Et&&this.applyPatch(n.data),e.type===_t&&this.applyRemoved(n.data),!0):(this.logger?.warn?.({eventType:e.type,issues:n.error.issues,errorCode:"APP_SESSION_EVENT_INVALID"},"app-session.aggregator.event.invalid"),!0)}applySnapshot(e){return this.accept({type:Tt,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=[...t.snapshot.sessions],s=n.findIndex(t=>t.id===e.session.id);return s<0?n.push(e.session):n[s]={...n[s],...e.session},this.accept({type:Et,payload:e},qt(n,e.meta.generatedAt))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=t.snapshot.sessions.filter(t=>t.id!==e.sessionId);return e.tombstone&&n.push(e.tombstone),this.accept({type:_t,payload:e},qt(n,e.meta.generatedAt))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view({includeTombstones:e.includeTombstones})}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const n=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>n)return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!0,events:[]});if(e.sinceRevision===n)return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!1,events:[]});const s=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!s.length||s[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:i,events:i?[]:s.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:n}=e.payload,s=this.snapshots.get(n.instanceId);return s&&s.revision>=n.snapshotRevision?(this.logger?.warn?.({traceId:n.traceId,instanceId:n.instanceId,currentRevision:s.revision,snapshotRevision:n.snapshotRevision},"app-session.aggregator.event.stale"),!1):(this.snapshots.set(n.instanceId,{snapshot:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt}),this.rememberEvent(n.instanceId,e),this.logger?.info?.({traceId:n.traceId,instanceId:n.instanceId,snapshotRevision:n.snapshotRevision,eventSeq:n.eventSeq,reason:n.reason,sessionCount:t.sessions.length,runningCount:t.runningCount,problemCount:t.problemCount},"app-session.aggregator.event.accepted"),!0)}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"app-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"app-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.appSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const n=this.history.get(e)??[];n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const n=t.filter(e=>e.createdAtMs>=Date.now()-36e5);n.length?this.history.set(e,n):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("APP_SESSION_DELTA_INSTANCE_ID_REQUIRED")}visibleSnapshot(e){return qt(e.sessions.filter(e=>Xn("string"==typeof e.status?e.status:void 0)),e.updatedAt)}view(e={}){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([t,n])=>({instanceId:t,appSessions:e.includeTombstones?n.snapshot:this.visibleSnapshot(n.snapshot),revision:n.revision,lastEventAt:n.lastEventAt}))}}}function mr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function fr(e){const t=process.env[e]?.trim();return t||void 0}function Ir(){try{const e=n.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function yr(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:Ir(),protocolVersion:ue,buildId:fr("TASK_HANDOFF_BUILD_ID"),builtAt:fr("TASK_HANDOFF_BUILT_AT"),gitCommit:fr("TASK_HANDOFF_GIT_COMMIT"),imageRef:fr("TASK_HANDOFF_IMAGE_REF"),imageDigest:fr("TASK_HANDOFF_IMAGE_DIGEST")}}const br={".css":"text/css; charset=utf-8",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".png":"image/png",".svg":"image/svg+xml",".webp":"image/webp"};function wr(e,s){const i=n.resolve(e),a=n.resolve(i,s);if(a===i||a.startsWith(`${i}${n.sep}`))return t.existsSync(a)&&t.statSync(a).isFile()?a:void 0}function Ar(e,s,i){const a=wr(s,i);return a?(e.header("cache-control","no-store"),e.type(br[n.extname(a).toLowerCase()]||"application/octet-stream"),e.send(t.createReadStream(a))):e.code(404).send({error:{code:"STATIC_FILE_NOT_FOUND",message:`${i} was not found in the Control Plane UI static directory.`}})}function Sr(e){if(e instanceof r.z.ZodError)return{statusCode:400,code:"VALIDATION_ERROR",message:e.issues.map(e=>`${e.path.join(".")||"body"}: ${e.message}`).join("; ")};const t=e&&"object"==typeof e?e:{};return{statusCode:"number"==typeof t.statusCode?t.statusCode:500,code:"string"==typeof t.code?t.code:"CONTROL_PLANE_ERROR",message:e instanceof Error?e.message:String(e)}}async function vr(e,t){if(!e.enabled())return{type:"system",reason:"auth-disabled"};const n=await e.userForSessionToken(t);return n?{type:"user",userId:n.id,role:n.role}:void 0}function kr(e,t){const n=t.split("?")[0]||"/";if(!n.startsWith("/api/"))return;if(n.startsWith("/api/auth/")||"/api/health"===n||"/api/events"===n)return;const s=function(e){return"GET"===e||"HEAD"===e?"read":"POST"===e?"create":"PATCH"===e||"PUT"===e?"update":"DELETE"===e?"delete":"read"}(e);return n.startsWith("/api/control-plane/settings")?{action:"GET"===e?"read":"manage-settings",resource:{type:"control-plane-settings"}}:n.startsWith("/api/models")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"model"}}:n.startsWith("/api/chat-gateway/bridges")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"chat-bridge"}}:"/api/node-join/invites"===n?{action:"manage-node-auth",resource:{type:"node"}}:n.startsWith("/api/nodes")?/\/(runtimes|local-folders)(\/|$)/.test(n)?{action:s,resource:{type:"runtime"}}:{action:"GET"===e?"read":"manage-node-auth",resource:{type:"node"}}:n.startsWith("/api/controlled-instances")?n.includes("/ai-sessions/")?n.endsWith("/approval")?{action:"approve",resource:{type:"ai-session"}}:n.endsWith("/interrupt")?{action:"interrupt",resource:{type:"ai-session"}}:n.includes("/messages")||n.includes("/queue/")?{action:"send-message",resource:{type:"ai-session"}}:{action:"read",resource:{type:"ai-session"}}:n.includes("/start")?{action:"start",resource:{type:"instance"}}:n.includes("/stop")?{action:"stop",resource:{type:"instance"}}:n.includes("/restart")?{action:"restart",resource:{type:"instance"}}:n.includes("/config-sync/")?{action:"update",resource:{type:"instance"}}:{action:s,resource:{type:"instance"}}:n.startsWith("/api/projects")?{action:s,resource:{type:"project"}}:n.startsWith("/api/images")?{action:s,resource:{type:"runtime"}}:n.startsWith("/api/triggers")?{action:s,resource:{type:"trigger"}}:{action:"read",resource:{type:"control-plane-settings"}}}async function Tr(e={}){const r=et(e.dataDir),o=s({logger:e.logger??!0,bodyLimit:67108864}),d=new Us(r,{...e.service,logger:o.log}),c=new kt(r,e.auth),l=new ha,p=new ur({bootstrap:()=>d.bootstrapAiSessionsFromInstances(),logger:o.log}),u=new hr({bootstrap:()=>d.bootstrapAppSessionsFromInstances(),logger:o.log});d.setAppSessionSnapshotProvider(e=>u.list(e)),d.setAiSessionSnapshotProvider(e=>p.list(e)),l.on(e=>{u.handleEvent(e),p.handleEvent(e)});const g=new ya,h=new Ca(l);d.setNodeAgentTransport(h),d.init(),await d.syncLocalNodeConnection().catch(()=>{}),c.init();const m=new Pi(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const f=new Na(d,h);f.start(),await o.register(i),await o.register(a),o.addHook("onClose",async()=>{f.stop(),m.stopAll()});const I=(y=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(y||"").trim().toLowerCase()));var y;o.setErrorHandler((e,t,n)=>{const s=Sr(e);(I||s.statusCode>=500)&&(s.statusCode>=500?o.log.error.bind(o.log):o.log.warn.bind(o.log))({method:t.method,url:t.url,statusCode:s.statusCode,errorCode:s.code,error:s.message},"control plane request failed"),n.code(s.statusCode).send({error:{code:s.code,message:s.message}})}),o.addHook("preHandler",async(e,t)=>{if(!c.enabled()||function(e,t){const n=t.split("?")[0];return!("/api/health"!==n&&!n.startsWith("/api/auth/"))||"GET"===e&&"/install-node-agent.sh"===n||"POST"===e&&"/api/node-join/complete"===n||"/api/app-access/session"===n||!!n.startsWith("/apps/access/")||!("/favicon.ico"!==n&&!n.startsWith("/assets/"))||"GET"===e&&!n.startsWith("/api/")&&!n.startsWith("/instances/")}(e.method,e.url)){const t=await vr(c,e.cookies[pt]),n=kr(e.method,e.url);return void(n&&ja(t||{type:"system",reason:"auth-disabled"},n.action,n.resource))}const n=await vr(c,e.cookies[pt]);if(!n)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const s=kr(e.method,e.url);s&&ja(n,s.action,s.resource)});const b=n.resolve(e.staticDir||process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||n.resolve(process.cwd(),"packages","control-plane-ui","dist"));return t.existsSync(b)&&(o.get("/assets/*",async(e,t)=>{const s=e.params["*"];return Ar(t,b,n.join("assets",s||""))}),o.get("/favicon.ico",async(e,t)=>wr(b,"favicon.ico")?Ar(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:ue,build:yr(),dataDir:r.dataDir,serverTime:(new Date).toISOString()}})),o.get("/install-node-agent.sh",async(e,t)=>(t.header("cache-control","no-store"),t.type("text/x-shellscript; charset=utf-8"),mr`#!/bin/sh
|
|
1
|
+
"use strict";var e=require("commander"),t=require("node:fs"),s=require("node:path"),n=require("fastify"),i=require("@fastify/cookie"),a=require("@fastify/websocket"),r=require("zod"),o=require("node:crypto"),d=require("node:util"),c=require("node:os"),l=require("write-file-atomic"),p=require("ws"),u=require("node:http"),g=require("node:net"),h=require("dingtalk-stream"),m=require("telegramify-markdown"),I=require("node:fs/promises");const f=20971520,y=41943040,b="ai-session.snapshot",v="ai-session.patch",w="ai-session.removed",S=r.z.string().trim().min(1).max(80),A=r.z.enum(["running","waiting","idle","failed"]),k=r.z.enum(["thinking","tool","editing","approval","responding","unknown"]),T=r.z.object({name:r.z.string().trim().min(1).max(120),inputPreview:r.z.string().trim().max(500).optional(),startedAt:r.z.string().datetime().optional()}).strict(),E=r.z.object({toolCalls:r.z.number().int().min(0).default(0),edits:r.z.number().int().min(0).default(0),approvals:r.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),_=r.z.object({send:r.z.boolean().optional(),interrupt:r.z.boolean().optional(),approval:r.z.boolean().optional()}).strict(),C=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),size:r.z.number().int().min(0).max(f),data:r.z.string().min(1).max(31457280)}).strict(),N=C.omit({data:!0}),z=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]).optional()}).strict(),O=r.z.enum(["auto","queue","steer","immediate"]),R=r.z.object({message:r.z.string().trim().min(1).max(2e4),mode:O.optional()});R.extend({attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const x=R.extend({attachments:r.z.array(z).max(6).default([])}).strict(),D=r.z.object({decision:r.z.enum(["allow","deny","skip"])}).strict();r.z.object({queueIds:r.z.array(r.z.string().trim().min(1).max(120)).max(100)}).strict(),r.z.object({code:r.z.string().trim().min(1).max(160),message:r.z.string(),instanceId:r.z.string().trim().min(1).max(160).optional(),nodeId:r.z.string().trim().min(1).max(160).optional(),route:r.z.string().trim().min(1).max(4096).optional()}).strict();const j=r.z.object({id:r.z.string().trim().min(1).max(120),message:r.z.string().trim().min(1).max(2e4),attachments:r.z.array(N).max(6).default([]),status:r.z.enum(["queued","sending","failed"]).default("queued"),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),error:r.z.string().trim().max(4e3).optional()}).strict(),P=r.z.object({pendingCount:r.z.number().int().min(0).default(0),items:r.z.array(j).max(100).default([])}).strict().default({pendingCount:0,items:[]}),M=r.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),$=r.z.object({id:r.z.string().trim().min(1).max(240),providerTurnId:r.z.string().trim().max(240).optional(),source:M.optional(),userPrompt:r.z.string().trim().optional(),status:r.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),revision:r.z.number().int().min(0).default(0),sourcePriority:r.z.number().int().min(0).max(100).optional(),snapshotVersion:r.z.number().int().min(0).optional(),observedAt:r.z.string().datetime().optional(),startedAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional(),completedAt:r.z.string().datetime().optional()}).strict(),q=r.z.object({id:r.z.string().trim().min(1).max(120),agent:S,appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),activeTurnId:r.z.string().trim().max(240).optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:A.default("running"),phase:k.default("unknown"),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),currentTool:T.optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),startedAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional(),error:r.z.string().trim().max(4e3).optional(),counters:E,queue:P}).strict(),L=r.z.object({session:q,provider:r.z.string().trim().min(1).max(80),action:r.z.enum(["send","queue","steer","interrupt","approval"]),decision:r.z.enum(["allow","deny","skip"]).optional(),turnId:r.z.string().trim().min(1).max(240).optional(),providerTurnId:r.z.string().trim().min(1).max(240).optional(),queueId:r.z.string().trim().min(1).max(120).optional()}).strict(),U=q.pick({id:!0,agent:!0,appSessionId:!0,appId:!0,providerSessionId:!0,providerMeta:!0,appBindingKeys:!0,actions:!0,activeTurnId:!0,conversationId:!0,title:!0,cwd:!0,userPrompt:!0,turns:!0,status:!0,phase:!0,summary:!0,lastMessage:!0,currentTool:!0,queue:!0,startedAt:!0,updatedAt:!0,error:!0}),F=r.z.object({runningCount:r.z.number().int().min(0).default(0),waitingCount:r.z.number().int().min(0).default(0),staleCount:r.z.number().int().min(0).default(0),sessions:r.z.array(U).default([]),updatedAt:r.z.string().datetime()}).strict(),B=r.z.object({streamId:r.z.string().trim().min(1).max(240),revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:F}).strict(),H=F.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),revision:r.z.number().int().min(0).optional()});function G(e,t={}){const s=e.sessions.filter(e=>"idle"===e.status).length,n=e.sessions.filter(e=>"failed"===e.status).length;return H.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:s,problemCount:n,updatedAt:e.updatedAt,revision:t.revision})}const W=r.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),V=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),revision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:W}).strict(),K=r.z.object({meta:V,snapshot:F}).strict(),J=r.z.object({meta:V,upserted:r.z.array(U).default([]),removed:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),Q=r.z.object({meta:V,sessionIds:r.z.array(r.z.string().trim().min(1).max(120)).default([]),expiresAt:r.z.string().datetime()}).strict(),Y=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(b),payload:K}).strict(),r.z.object({type:r.z.literal(v),payload:J}).strict(),r.z.object({type:r.z.literal(w),payload:Q}).strict()])).default([])}).strict(),X=r.z.object({source:M,sourcePriority:r.z.number().int().min(0).max(100).optional(),observedAt:r.z.string().datetime().optional(),snapshotVersion:r.z.number().int().min(0).optional()}).strict(),Z=X.extend({type:r.z.literal("snapshot"),agent:r.z.string().trim().min(1).max(80),appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),activeTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:A.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),replaceActivity:r.z.boolean().optional()}).strict(),ee=X.extend({type:r.z.literal("event"),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:r.z.string().trim().max(240).optional(),providerTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),text:r.z.string().optional(),status:A.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),counters:r.z.object({toolCalls:r.z.number().int().min(0).optional(),edits:r.z.number().int().min(0).optional(),approvals:r.z.number().int().min(0).optional()}).strict().optional()}).strict();r.z.discriminatedUnion("type",[Z,ee]);const te=r.z.union([r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("interval").default("interval"),intervalMs:r.z.number().int().min(1e3)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("daily"),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("weekly"),weekdays:r.z.array(r.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict()]),se=r.z.union([te,r.z.object({type:r.z.literal("file-change"),roots:r.z.array(r.z.string().trim().min(1).max(4096)).min(1).max(50),globs:r.z.array(r.z.string().trim().min(1).max(500)).min(1).max(100),ignore:r.z.array(r.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:r.z.number().int().min(100).max(6e4).default(1500)}).strict(),r.z.object({type:r.z.literal("ai-session"),conversationId:r.z.number().int().positive().optional(),agent:r.z.string().trim().min(1).max(80).optional(),statuses:r.z.array(r.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:r.z.array(r.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),ne=r.z.object({promptTemplate:r.z.string().trim().min(1).max(2e4)}).strict(),ie=r.z.object({cooldownMs:r.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:r.z.number().int().min(1).max(20).default(1),whenBusy:r.z.enum(["skip","queue"]).default("skip")}).strict(),ae=r.z.object({configHash:r.z.string().trim().min(8).max(80),name:r.z.string().trim().min(1).max(160),description:r.z.string().trim().max(1e3).optional(),source:se,action:ne,policy:ie.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),re=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("conversation"),conversationId:r.z.number().int().positive()}).strict(),r.z.object({type:r.z.literal("ai-session"),aiSessionId:r.z.string().trim().min(1).max(160)}).strict()]),oe=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),origin:r.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:r.z.boolean().default(!0),target:re,localName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),de=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),status:r.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:r.z.string().datetime().optional(),lastCompletedAt:r.z.string().datetime().optional(),lastSkippedAt:r.z.string().datetime().optional(),lastError:r.z.string().trim().max(4e3).optional(),runCount:r.z.number().int().min(0).default(0),skippedCount:r.z.number().int().min(0).default(0)}).strict(),ce=r.z.object({id:r.z.string().trim().min(1).max(160),configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),eventType:r.z.enum(["manual","schedule","file-change","ai-session"]),status:r.z.enum(["started","completed","failed","skipped"]),target:re,promptPreview:r.z.string().trim().max(500),eventSummary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),startedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional()}).strict(),le=r.z.object({schemaVersion:r.z.literal(1).default(1),configs:r.z.array(ae).default([]),deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([]),recentRuns:r.z.array(ce).default([])}).strict();function pe(e){return Array.isArray(e)?e.map(pe):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,pe(t)])):e}const ue="2026-07-13",ge=r.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),he=r.z.string().datetime(),me=r.z.record(r.z.string(),r.z.string()).default({}),Ie=r.z.record(r.z.string(),r.z.string()).default({}),fe=r.z.object({codexModelId:ge.optional(),claudeModelId:ge.optional()}).strict().default({}),ye=r.z.object({component:r.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:r.z.string().trim().max(160).optional(),packageVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80).optional(),buildId:r.z.string().trim().max(160).optional(),builtAt:r.z.string().trim().max(120).optional(),gitCommit:r.z.string().trim().max(120).optional(),imageRef:r.z.string().trim().max(512).optional(),imageDigest:r.z.string().trim().max(240).optional()}).strict(),be=r.z.enum(["stable","beta","alpha"]),ve=r.z.discriminatedUnion("component",[r.z.object({component:r.z.literal("node-agent")}).strict(),r.z.object({component:r.z.literal("controlled-instance"),instanceId:ge}).strict()]),we=r.z.object({target:ve,channel:be.default("stable")}).strict(),Se=r.z.object({target:ve,source:r.z.enum(["npm","docker-registry"]),channel:be,currentVersion:r.z.string().trim().max(240).optional(),availableVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),updateAvailable:r.z.boolean(),supported:r.z.boolean().default(!0),reason:r.z.string().trim().max(2048).optional(),checkedAt:he}).strict(),Ae=r.z.object({id:ge,nodeId:ge,target:ve,source:r.z.enum(["npm","docker-registry"]),channel:be,fromVersion:r.z.string().trim().max(240).optional(),toVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),status:r.z.enum(["queued","updating","restarting","succeeded","failed"]),error:r.z.string().trim().max(4096).optional(),startedAt:he.optional(),completedAt:he.optional(),createdAt:he,updatedAt:he}).strict(),ke=r.z.object({type:r.z.enum(["branch","tag","commit"]),name:r.z.string().trim().min(1).max(240).optional(),commit:r.z.string().trim().max(80).optional()}).strict(),Te=r.z.object({type:r.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:r.z.string().trim().min(1).max(120).optional()}).strict(),Ee=r.z.object({depth:r.z.number().int().positive().max(1e5).optional(),submodules:r.z.boolean().default(!1),lfs:r.z.boolean().default(!1),subdirectory:r.z.string().trim().max(240).default("")}).strict();r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:ge.optional(),modelSelection:fe,labels:me,createdAt:he,updatedAt:he}).strict();const _e=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),path:r.z.string().trim().min(1).max(4096),defaultImageId:ge.optional(),modelSelection:fe,labels:me,createdAt:he,updatedAt:he}).strict(),Ce=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("git-repository"),repositoryId:ge.optional(),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("git-template"),url:r.z.string().trim().min(1).max(2048),templateId:r.z.string().trim().min(1).max(120).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("local-folder"),localFolderId:ge.optional(),path:r.z.string().trim().min(1).max(4096),ownerNodeId:ge.optional()}).strict()]),Ne=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:r.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:r.z.string().trim().max(240).optional(),readOnly:r.z.boolean().default(!1)}).strict(),ze=r.z.enum(["codex","claude"]),Oe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),source:Ce,defaultImageId:ge.optional(),defaultNodeId:ge.optional(),defaultRuntimeId:ge.optional(),modelSelection:fe,workspacePolicy:Ne,labels:me,createdAt:he,updatedAt:he}).strict(),Re=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),endpoint:r.z.string().trim().min(1).max(2048),key:r.z.string().trim().min(1).max(4096),model:r.z.string().trim().min(1).max(240),app:ze,enabled:r.z.boolean().default(!0),order:r.z.number().int().min(0).max(1e6).default(0),labels:me,createdAt:he,updatedAt:he}).strict(),xe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),image:r.z.string().trim().min(1).max(512),registry:r.z.string().trim().min(1).max(160).default("local"),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).default([]),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).default([]),defaultEnv:Ie,labels:me,createdAt:he,updatedAt:he}).strict(),De=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),connectionMode:r.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:ge.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:he.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:he.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:r.z.string().trim().max(2048).optional(),controlEndpoint:r.z.string().trim().max(2048).optional(),containerEndpoint:r.z.string().trim().max(2048).optional(),publicWebBase:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,lastSeenAt:he.optional(),createdAt:he,updatedAt:he}).strict(),je=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),type:r.z.enum(["docker","kubernetes","local"]),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,createdAt:he,updatedAt:he}).strict(),Pe=r.z.object({ok:r.z.boolean().optional(),role:r.z.string().optional(),nodeId:ge.optional()}).passthrough(),Me=r.z.object({bindScope:r.z.enum(["loopback","all-ipv4"]),port:r.z.number().int().min(1).max(65535)}).strict().extend({host:r.z.enum(["127.0.0.1","0.0.0.0"]),status:r.z.enum(["listening","error"]),source:r.z.enum(["bootstrap","persisted"]),error:r.z.string().trim().max(2048).optional()}).strict(),$e=r.z.object({nodeId:ge,joinToken:r.z.string().trim().min(1),expiresAt:he}).passthrough(),qe=r.z.object({id:ge,keyId:ge,url:r.z.string().trim().min(1).max(2048).optional(),name:r.z.string().trim().min(1).max(160).optional(),pairedAt:he,updatedAt:he.optional(),active:r.z.boolean().optional(),current:r.z.boolean().optional()}).passthrough(),Le=r.z.object({remote:r.z.object({id:ge,url:r.z.string().trim().min(1).max(2048).optional(),keyId:ge,pairedAt:he,active:r.z.boolean()}).passthrough(),tunnel:r.z.object({status:r.z.enum(["disabled","saved","connecting","connected","failed"]),error:r.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),Ue=r.z.object({deleted:r.z.boolean().optional()}).passthrough(),Fe=r.z.object({repository:r.z.string().trim().optional(),tag:r.z.string().trim().optional(),id:r.z.string().trim(),createdSince:r.z.string().trim().optional(),size:r.z.string().trim().optional(),reference:r.z.string().trim().min(1)}).passthrough(),Be=r.z.lazy(()=>r.z.object({name:r.z.string().trim().min(1),path:r.z.string().trim().min(1).max(4096),children:r.z.array(Be)}).passthrough()),He=r.z.object({status:r.z.number().int().min(100).max(599).optional(),headers:r.z.record(r.z.string(),r.z.string()).optional(),bodyBase64:r.z.string().optional()}).passthrough(),Ge=r.z.object({strategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),vnc:r.z.string().trim().max(2048).optional(),tty:r.z.string().trim().max(2048).optional(),logs:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),We=r.z.object({strategy:r.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),ws:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),Ve=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:r.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:r.z.string().trim().max(4096).optional(),resolvedCommit:r.z.string().trim().max(120).optional(),error:r.z.string().trim().max(2048).optional()}).passthrough(),Ke=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce,sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).default({}),nodeId:ge,runtimeId:ge,imageId:ge.optional(),imageSnapshot:xe.optional(),status:r.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:r.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:r.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:r.z.string().trim().max(80).optional(),instanceVersion:r.z.string().trim().max(80).optional(),build:ye.optional(),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),config:r.z.object({autoImportAgentConfigs:r.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:Ve.default({status:"unknown"}),target:Ge.default({strategy:"direct-port",status:"unknown"}),access:We,receiver:r.z.object({status:r.z.string().trim().max(80).default("unknown"),pendingCount:r.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),updatedAt:he.optional(),revision:r.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:F.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:r.z.object({enabledCount:r.z.number().int().min(0).default(0),runningCount:r.z.number().int().min(0).default(0),errorCount:r.z.number().int().min(0).default(0),configs:r.z.array(r.z.object({configHash:r.z.string().trim().min(1),config:ae,deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([])}).strict()).default([]),recentRuns:r.z.array(ce).default([])}).strict().optional(),runtime:r.z.object({kind:r.z.string().trim().max(80).optional(),containerName:r.z.string().trim().max(240).optional(),containerId:r.z.string().trim().max(240).optional(),workspacePath:r.z.string().trim().max(4096).optional(),pid:r.z.number().int().positive().optional(),port:r.z.number().int().positive().max(65535).optional(),labels:r.z.record(r.z.string(),r.z.string()).default({})}).strict().default({labels:{}}),registrationToken:r.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:he.optional(),createdAt:he,updatedAt:he}).strict();function Je(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const s=e;return Object.fromEntries(t.filter(e=>Object.prototype.hasOwnProperty.call(s,e)).map(e=>[e,s[e]]))}r.z.object({instanceId:ge.optional(),name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce.optional(),nodeId:ge.optional(),runtimeId:ge.optional(),imageId:ge.optional(),instanceVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),target:Ge.default({strategy:"direct-port",status:"unknown"}),workspace:Ve.default({status:"unknown"}),registrationToken:r.z.string().trim().max(240).optional()}).strict(),r.z.object({status:Ke.shape.status.optional(),health:Ke.shape.health.optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),capabilities:Ke.shape.capabilities.optional(),receiver:Ke.shape.receiver.optional(),apps:Ke.shape.apps.optional(),aiSessions:Ke.shape.aiSessions.optional(),triggers:Ke.shape.triggers.optional(),workspace:Ve.optional(),target:Ge.partial().optional()}).strict();const Qe=r.z.enum(["web","telegram","wechat","dingding"]),Ye=r.z.object({id:ge,channel:Qe,name:r.z.string().trim().min(1).max(160),enabled:r.z.boolean().default(!1),token:r.z.string().trim().max(4096).optional(),defaultChatId:r.z.string().trim().max(240).optional(),allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:r.z.number().int().positive().max(6e4).default(3e3),settings:r.z.record(r.z.string(),r.z.unknown()).default({}),createdAt:he,updatedAt:he}).strict(),Xe=r.z.object({id:ge,channel:Qe,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional(),activeProjectId:ge.optional(),activeInstanceId:ge.optional(),activeAiSessionId:r.z.string().trim().min(1).max(120).optional(),lastUsedAt:he,createdAt:he,updatedAt:he}).strict(),Ze=r.z.object({source:r.z.object({type:r.z.literal("chat-gateway").default("chat-gateway"),channel:Qe,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),message:r.z.object({text:r.z.string().trim().max(2e4).default(""),attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:r.z.object({instanceId:ge,aiSessionId:r.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),et=r.z.object({id:ge,instanceId:ge,projectId:ge.optional(),aiSessionId:r.z.string().trim().min(1).max(120),providerSessionId:r.z.string().trim().max(240).optional(),result:r.z.string().default(""),timeoutMs:r.z.number().positive().optional(),source:r.z.string().trim().max(120).optional(),kind:r.z.enum(["task","approval"]).default("task"),status:r.z.enum(["pending","unreachable"]).default("pending"),error:r.z.string().trim().max(2048).optional(),lastSeenAt:he}).strict();function tt(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("receiver.")?"receiver":e.startsWith("conversation.")?"conversations":e.startsWith("trigger.")?"triggers":e.startsWith("channel.")?"channels":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}const st=r.z.enum(["ai.sessions","app.sessions"]),nt=r.z.object({topic:st,instanceId:r.z.string().trim().min(1).max(160),streamId:r.z.string().trim().min(1).max(240),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0)}).strict(),it=r.z.object({protocolVersion:r.z.literal(1),streams:r.z.array(nt)}).strict();function at(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||s.join(c.homedir(),".config","task-handoff","control-plane")}()){const t=s.resolve(e);return{dataDir:t,settingsPath:s.join(t,"control-plane-settings.json"),projectsDir:s.join(t,"projects"),modelsDir:s.join(t,"models"),imagesDir:s.join(t,"images"),nodesDir:s.join(t,"nodes"),nodeRuntimesDir:s.join(t,"node-runtimes"),controlledInstancesDir:s.join(t,"controlled-instances"),chatSessionsDir:s.join(t,"chat-sessions"),chatBridgesDir:s.join(t,"chat-bridges"),triggersDir:s.join(t,"triggers"),nodeJoinInvitesDir:s.join(t,"node-join-invites"),authUsersDir:s.join(t,"auth-users"),authSessionsDir:s.join(t,"auth-sessions"),logsDir:s.join(t,"logs")}}function rt(e){t.mkdirSync(e,{recursive:!0})}function ot(e,t){rt(s.dirname(e)),l.sync(e,`${JSON.stringify(t,null,2)}\n`,{encoding:"utf8"})}function dt(e,t){console.warn(JSON.stringify({message:e,...t}))}function ct(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function lt(e,s){const n=s.logger||dt;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void n("stored JSON could not be read",{filePath:e,...ct(t)})}if(!s.schema)return i;const a=s.sanitize?s.sanitize(i):i,r=s.schema.safeParse(a);if(r.success)return r.data;const o="strip"in s.schema&&"function"==typeof s.schema.strip?s.schema.strip().safeParse(a):void 0;if(o?.success)return n("unknown stored fields were ignored",{filePath:e,issues:r.error.issues}),o.data;n("stored record failed validation and was isolated",{filePath:e,issues:r.error.issues})}function pt(e){return`${e}_${o.randomUUID().replace(/-/g,"").slice(0,20)}`}function ut(){return o.randomBytes(24).toString("base64url")}class gt{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){rt(this.directory)}filePath(e){return s.join(this.directory,`${e}.json`)}list(){return this.init(),t.readdirSync(this.directory).filter(e=>e.endsWith(".json")).sort().flatMap(e=>{const t=lt(s.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const s=this.filePath(e);return t.existsSync(s)?lt(s,this.options):void 0}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,s=this.options.schema?this.options.schema.parse(t):t;return ot(this.filePath(s.id),s),s}patch(e,t){const s=this.get(e);if(!s)return;const n={...s,...t,id:s.id,createdAt:s.createdAt,updatedAt:(new Date).toISOString()};return this.put(n)}delete(e){const s=this.filePath(e);return!!t.existsSync(s)&&(t.unlinkSync(s),!0)}}class ht{filePathValue;defaults;options;constructor(e,t,s={}){this.filePathValue=e,this.defaults=t,this.options=s}init(){rt(s.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?lt(this.filePathValue,this.options)??this.defaults():this.defaults()}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,s=this.options.schema?this.options.schema.parse(t):t;return ot(this.filePathValue,s),s}}const mt=d.promisify(o.scrypt),It="task_handoff_cp_session",ft=r.z.enum(["disabled","password"]),yt=r.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),bt=r.z.string().min(8).max(4096),vt=r.z.object({username:yt,password:bt}).strict(),wt=r.z.object({username:r.z.string().trim().min(1).max(80),password:r.z.string().min(1).max(4096)}).strict(),St=r.z.object({id:r.z.string().trim().min(1),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}),At=St.extend({username:yt,passwordHash:r.z.string().trim().min(1),role:r.z.enum(["viewer","operator","admin"]),lastLoginAt:r.z.string().datetime().optional()}).strict(),kt=St.extend({userId:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),lastSeenAt:r.z.string().datetime().optional()}).strict();function Tt(){return(new Date).toISOString()}function Et(e){return o.createHash("sha256").update(e).digest("base64url")}async function _t(e){const t=o.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await mt(e,t,64)).toString("base64url")}`}function Ct(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}class Nt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return ft.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new gt(e.authUsersDir,{schema:At}),this.sessions=new gt(e.authSessionsDir,{schema:kt})}init(){this.users.init(),this.sessions.init()}enabled(){return"password"===this.mode}state(){return{mode:this.mode,enabled:this.enabled(),requiresBootstrap:this.enabled()&&0===this.users.list().length}}async bootstrapAdmin(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}if(this.users.list().length>0){const e=new Error("Control Plane admin user already exists.");throw Object.assign(e,{statusCode:409,code:"AUTH_BOOTSTRAP_ALREADY_DONE"}),e}const t=vt.parse(e),s=Tt();return Ct(this.users.put({id:pt("user"),username:t.username,passwordHash:await _t(t.password),role:"admin",createdAt:s,updatedAt:s}))}async login(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}const t=wt.parse(e),s=this.users.list().find(e=>e.username===t.username);if(!s||!await async function(e,t){const[s,n,i]=t.split("$");if("scrypt"!==s||!n||!i)return!1;const a=await mt(e,n,64),r=Buffer.from(i,"base64url");return r.length===a.length&&o.timingSafeEqual(r,a)}(t.password,s.passwordHash)){const e=new Error("Invalid username or password.");throw Object.assign(e,{statusCode:401,code:"AUTH_LOGIN_FAILED"}),e}const n=Tt(),i=ut(),a=new Date(Date.now()+12096e5).toISOString(),r=this.sessions.put({id:pt("sess"),userId:s.id,tokenHash:Et(i),expiresAt:a,createdAt:n,updatedAt:n,lastSeenAt:n});return this.users.put({...s,lastLoginAt:n,updatedAt:n}),{user:Ct({...s,lastLoginAt:n,updatedAt:n}),sessionToken:`${r.id}.${i}`,expiresAt:a}}async userForSessionToken(e){if(!this.enabled())return;if(!e)return;const[t,s]=e.split(".");if(!t||!s)return;const n=this.sessions.get(t);if(!n||n.expiresAt<=Tt()||n.tokenHash!==Et(s))return;const i=this.users.get(n.userId);return i?(this.sessions.put({...n,lastSeenAt:Tt(),updatedAt:Tt()}),Ct(i)):void 0}async currentSession(e){const t=this.state(),s=await this.userForSessionToken(e);return{...t,authenticated:!this.enabled()||Boolean(s),user:s}}logout(e){const t=e?.split(".")[0];return t&&this.sessions.delete(t),{ok:!0}}}const zt="app-session.snapshot",Ot="app-session.patch",Rt="app-session.removed",xt=r.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Dt=r.z.object({type:r.z.enum(["app-session","provider-session","adapter-key"]),id:r.z.string().trim().min(1).max(240),agent:r.z.string().trim().min(1).max(80).optional(),adapter:r.z.string().trim().min(1).max(80).optional(),key:r.z.string().trim().min(1).max(240).optional()}).strict(),jt=r.z.record(r.z.string(),r.z.unknown()).and(r.z.object({id:r.z.string().trim().min(1).max(160),appId:r.z.string().trim().min(1).max(160).optional(),title:r.z.string().trim().max(240).optional(),kind:r.z.string().trim().max(80).optional(),status:xt.default("unknown"),bindings:r.z.array(Dt).max(40).default([]),workspace:r.z.object({cwd:r.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional()})),Pt=r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),sessions:r.z.array(jt).default([]),updatedAt:r.z.string().datetime()}).strict(),Mt=r.z.object({streamId:r.z.string().trim().min(1).max(240),revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:Pt}).strict();Pt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:r.z.number().int().min(0).optional()});const $t=r.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),qt=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),revision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:$t}).strict(),Lt=r.z.object({meta:qt,snapshot:Pt}).strict(),Ut=r.z.object({meta:qt,session:jt}).strict(),Ft=r.z.object({meta:qt,sessionId:r.z.string().trim().min(1).max(160),tombstone:jt.optional(),expiresAt:r.z.string().datetime().optional()}).strict(),Bt=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(zt),payload:Lt}).strict(),r.z.object({type:r.z.literal(Ot),payload:Ut}).strict(),r.z.object({type:r.z.literal(Rt),payload:Ft}).strict()])).default([])}).strict();function Ht(e,t=(new Date).toISOString()){const s=e.map(e=>Gt(e));return Pt.parse({runningCount:s.filter(e=>"running"===e.status).length,problemCount:s.filter(e=>"failed"===e.status).length,sessions:s,updatedAt:t})}function Gt(e){const t=Wt("string"==typeof e.status?e.status:void 0),s="string"==typeof e.id?e.id:"",n=function(e){const t=Yt(e.workspace),s=Yt(e.tty),n=Yt(e.launch),i=Yt(e.ai),a=Yt(i.claude),r=Xt(t.cwd)||Xt(s.cwd)||Xt(a.cwd)||Xt(n.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return jt.parse({...a,status:t,bindings:Qt([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=Dt.safeParse(e);return t.success?[t.data]:[]}):[]),...s?[{type:"app-session",id:s}]:[],...Kt(e)]),...n?{workspace:n}:{}});var r}function Wt(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":xt.options.includes(t)?t:"unknown"}function Vt(e){return"app-session"===e.type?`app:${e.id}`:"provider-session"===e.type?`provider:${e.agent||""}:${e.id}`:`adapter:${e.adapter||""}:${e.key||e.id}`}function Kt(e){const t=Yt(e.ai),s=Yt(t.claude),n=Yt(t.appServer),i=[],a=Xt(s.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Xt(s.providerSessionId);r&&i.push({type:"provider-session",agent:"claude",id:r});const o=Array.isArray(t.threadIds)?t.threadIds.filter(e=>"string"==typeof e&&Boolean(e.trim())):[],d=Xt(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Xt(n.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Jt(e){const t="string"==typeof e.id?e.id:"",s=Yt(e.ai),n=Xt(Yt(s.claude).short);return[t?`app:${t}`:"",n?`claude-short:${n}`:""]}function Qt(e){const t=new Map;for(const s of e)t.set(Vt(s),s);return[...t.values()]}function Yt(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Xt(e){return"string"==typeof e&&e.trim()?e.trim():""}function Zt(e=(new Date).toISOString()){return Pt.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function es(e,t,s,n){const i=n&&"object"==typeof n?n:{};return{nodeId:e.id,route:"string"==typeof i.route?i.route:t,method:s,code:"string"==typeof i.code?i.code:"NODE_AGENT_REQUEST_FAILED",message:n instanceof Error?n.message:String(n),statusCode:"number"==typeof i.statusCode?i.statusCode:void 0,issues:Array.isArray(i.issues)?i.issues:void 0}}class ts{requestImpl;logger;constructor(e){this.requestImpl=e.request,this.logger=e.logger}async request(e,t,s={}){const n=s.method||"GET";let i,a;try{i=await this.requestImpl(e,t,s)}catch(s){throw this.logger?.warn?.({nodeId:e.id,route:t,method:n,error:s instanceof Error?s.message:String(s),errorCode:"NODE_AGENT_TRANSPORT_FAILED"},"node agent transport failed"),s}try{a=await i.json()}catch(s){this.logger?.warn?.({nodeId:e.id,route:t,method:n,statusCode:i.status,error:s instanceof Error?s.message:String(s),errorCode:"NODE_AGENT_RESPONSE_INVALID_JSON"},"node agent response was not valid json");const a=new Error(`Node agent ${e.id} returned invalid JSON for ${t}.`);throw Object.assign(a,{statusCode:502,code:"NODE_AGENT_RESPONSE_INVALID_JSON",nodeId:e.id,route:t}),a}if(!i.ok){const s=new Error(a.error?.message||`Node agent request failed with HTTP ${i.status}`);throw Object.assign(s,{statusCode:i.status,code:a.error?.code||"NODE_AGENT_REQUEST_FAILED",nodeId:e.id,route:t}),this.logger?.warn?.({nodeId:e.id,route:t,method:n,statusCode:i.status,errorCode:a.error?.code||"NODE_AGENT_REQUEST_FAILED",error:s.message},"node agent request failed"),s}return this.logger?.info?.({nodeId:e.id,route:t,method:n,statusCode:i.status},"node agent request ok"),a.data??a}async requestSchema(e,t,s,n={}){return this.parse(e,t,s,await this.request(e,t,n),n.method||"GET")}parse(e,t,s,n,i="GET"){const a=s.safeParse(n);if(a.success)return a.data;this.logger?.warn?.({nodeId:e.id,route:t,method:i,errorCode:"NODE_AGENT_PROTOCOL_INVALID",issues:a.error.issues},"node agent protocol response did not match schema");const r=new Error(`Node agent ${e.id} returned invalid protocol data for ${t}.`);throw Object.assign(r,{statusCode:502,code:"NODE_AGENT_PROTOCOL_INVALID",nodeId:e.id,route:t,issues:a.error.issues}),r}}class ss{client;constructor(e){this.client=e}request(e,t,s={}){return this.client.request(e,t,s)}health(e){return this.client.requestSchema(e,"/health",Pe)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",Se,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",Ae,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",r.z.array(Ae))}getExternalListener(e){return this.client.requestSchema(e,"/settings/external-listener",Me)}updateExternalListener(e,t){return this.client.requestSchema(e,"/settings/external-listener",Me,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",$e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t&&"object"==typeof t?t:{})})}connectRemote(e,t){return this.client.requestSchema(e,"/remotes/connect",Le,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",r.z.array(qe))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",r.z.array(Fe))}listFolderTree(e,t={}){const s=new URLSearchParams;t.path&&s.set("path",t.path),void 0!==t.depth&&s.set("depth",String(t.depth));const n=s.toString();return this.client.requestSchema(e,"/folders/tree"+(n?`?${n}`:""),r.z.array(Be))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",r.z.array(_e))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}async requireLocalFolder(e,t){const s=(await this.listLocalFolders(e)).find(e=>e.id===t);if(!s){const s=new Error(`Local folder ${t} was not found on node ${e.id}.`);throw Object.assign(s,{statusCode:404,code:"NODE_LOCAL_FOLDER_NOT_FOUND",nodeId:e.id,route:"/local-folders"}),s}return s}listRuntimes(e){return this.client.requestSchema(e,"/runtimes",r.z.array(je))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",je,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateRuntime(e,t,s){const n=`/runtimes/${encodeURIComponent(t)}`;return this.client.requestSchema(e,n,je,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}checkRuntime(e,t){const s=`/runtimes/${encodeURIComponent(t)}/check`;return this.client.requestSchema(e,s,je,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}async listFleetRuntimes(e){return(await Promise.allSettled(e.map(async e=>({node:e,runtimes:await this.listRuntimes(e)})))).reduce((t,s,n)=>{if("fulfilled"===s.status)t.items.push(...s.value.runtimes);else{const i=e[n];i&&t.nodeErrors.push(es(i,"/runtimes","GET",s.reason))}return t},{items:[],nodeErrors:[]})}listInstances(e){return this.listInstancesWithDiagnostics(e).then(e=>e.items)}async listInstancesWithDiagnostics(e){return this.client.requestSchema(e,"/instances",r.z.array(r.z.unknown())).then(t=>t.reduce((t,s)=>{const n=this.normalizeInstance(e,s);return n.instance&&t.items.push(n.instance),n.error&&t.nodeErrors.push(n.error),t},{items:[],nodeErrors:[]}))}createInstance(e,t){return this.client.requestSchema(e,"/instances",Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Ke,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,Ue,{method:"POST"})}startInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Ke,{method:"POST"})}restartInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}proxyRawInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/proxy/raw`,He,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}async listFleetInstances(e){return(await Promise.allSettled(e.map(async e=>({node:e,result:await this.listInstancesWithDiagnostics(e)})))).reduce((t,s,n)=>("fulfilled"===s.status?(t.items.push(...s.value.result.items),t.nodeErrors.push(...s.value.result.nodeErrors)):t.nodeErrors.push(es(e[n],"/instances","GET",s.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const s="/instances",n=(i=t,Ke.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,s=new Set(Object.keys(Ke.shape)),n={};for(const[e,i]of Object.entries(t))s.has(e)&&(n[e]=i);return n.apps=Je(t.apps,["runningCount","problemCount","updatedAt","revision"]),n.config=Je(t.config,["autoImportAgentConfigs"]),n.receiver=Je(t.receiver,["status","pendingCount"]),n.runtime=Je(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),n.target=Je(t.target??t.endpoints,["strategy","web","api","status"]),n.access=Je(t.access,["strategy","web","api","ws","status"]),n}(i)));var i;if(n.success)return n.data.protocolVersion&&n.data.protocolVersion!==ue?(this.client.logger?.warn?.({nodeId:e.id,instanceId:n.data.id,expectedProtocolVersion:ue,actualProtocolVersion:n.data.protocolVersion,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node instance protocol version mismatch"),{instance:n.data,error:{nodeId:e.id,route:s,method:"GET",code:"PROTOCOL_VERSION_MISMATCH",message:`Node instance ${n.data.id} protocol version ${n.data.protocolVersion} does not match ${ue}.`,statusCode:502}}):{instance:n.data};const a=t&&"object"==typeof t?t:{},r="string"==typeof a.id?a.id:void 0;return this.client.logger?.warn?.({nodeId:e.id,instanceId:r,errorCode:"NODE_INSTANCE_PAYLOAD_INVALID",issues:n.error.issues.map(e=>({path:e.path.join("."),message:e.message}))},"node instance payload invalid"),{error:{nodeId:e.id,route:s,method:"GET",code:"NODE_INSTANCE_PAYLOAD_INVALID",message:r?`Node instance ${r} payload is invalid.`:"Node instance payload is invalid.",statusCode:502,issues:n.error.issues}}}}function ns(){return(new Date).toISOString()}function is(e){return e?e instanceof Headers?Object.fromEntries(e.entries()):Array.isArray(e)?Object.fromEntries(e.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(e).map(([e,t])=>[e,String(t)])):{}}function as(e){const t=String(e||"").split(":");if(3===t.length&&"ai"===t[1]&&t[0]&&t[2])return{instanceId:t[0],aiSessionId:t[2]}}function rs(e){return e instanceof Error?e.message:String(e)}function os(e,t){const s=new Error(t);throw Object.assign(s,{statusCode:404,code:e}),s}class ds{requireNode;nodeAgentGateway;nodeAgentRequest;fetchImpl;constructor(e){this.requireNode=e.requireNode,this.nodeAgentGateway=e.nodeAgentGateway,this.nodeAgentRequest=e.nodeAgentRequest,this.fetchImpl=e.fetchImpl}async request(e,t,s={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_UNREACHABLE"}),t}const n=this.requireNode(e.nodeId),i=await this.nodeAgentRequest(n,`/instances/${encodeURIComponent(e.id)}/proxy`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({path:`/api/${t.replace(/^\/+/,"")}`,method:s.method||"GET",headers:is(s.headers),body:(a=s.body,"string"==typeof a?a:null==a?void 0:String(a))})});var a;const r=await i.json().catch(()=>({}));if(!i.ok){const s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,s="string"==typeof t.code&&t.code.trim()?t.code:void 0,n="string"==typeof t.message&&t.message?t.message:void 0;return s||n?{code:s,message:n}:void 0}(r.error),n=s?.message||function(e){if(!e||"object"!=typeof e||Array.isArray(e))return"";const t=e.message;return"string"==typeof t?t:""}(r.error)||`Instance request failed with HTTP ${i.status}`,a=new Error(n);throw Object.assign(a,{statusCode:i.status,code:s?.code||"INSTANCE_REQUEST_FAILED",instanceId:e.id,nodeId:e.nodeId,route:t}),a}return r.data??r}async proxyHttp(e,t,s={}){if(!e.target.web){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const n=this.requireNode(e.nodeId),i=await this.nodeAgentGateway.proxyRawInstance(n,e.id,{path:t,method:s.method||"GET",headers:is(s.headers),...(a=s.body,null==a?{}:"string"==typeof a?{body:a}:Buffer.isBuffer(a)?{bodyBase64:a.toString("base64")}:a instanceof Uint8Array?{bodyBase64:Buffer.from(a).toString("base64")}:{body:String(a)})});var a;return{status:i.status||502,headers:i.headers||{},body:Buffer.from(i.bodyBase64||"","base64")}}proxyWebSocket(e,t,s,n,i,a={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const r=this.requireNode(e.nodeId),o=n.startsWith("/")?n.slice(1):n;t.proxyWebSocket(r,s,`/instances/${encodeURIComponent(e.id)}/proxy/ws/${o}`,i,a)}async reportHeartbeat(e,t,s){if(!e.registrationToken)return;const n=this.requireNode(e.nodeId),i=`/instances/${encodeURIComponent(e.id)}/heartbeat`,a={method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.registrationToken}`},body:JSON.stringify(t)},r="reverse-wss"===n.connectionMode?await(s?.request(n,i,a)):await this.fetchImpl(`${(n.controlEndpoint||n.endpoint||"").replace(/\/$/,"")}/api/node-agent${i}`,a);if(!r)return;const o=await r.json().catch(()=>({}));if(!r.ok){const e=new Error(o.error?.message||`Instance heartbeat sync failed with HTTP ${r.status}`);throw Object.assign(e,{statusCode:r.status,code:"INSTANCE_HEARTBEAT_SYNC_FAILED"}),e}}}function cs(e){if(!e||"object"!=typeof e)return;const t=e,s=ye.safeParse(t.build);return{..."boolean"==typeof t.ok?{ok:t.ok}:{},..."string"==typeof t.role?{role:t.role}:{},..."string"==typeof t.nodeId?{nodeId:t.nodeId}:{},..."string"==typeof t.protocolVersion?{protocolVersion:t.protocolVersion}:{},...s.success?{build:s.data}:{},..."string"==typeof t.serverTime?{serverTime:t.serverTime}:{}}}function ls(e){const{agent:t,...s}=e.capabilities,{secret:n,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=cs(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...s,agent:r}:s}}function ps(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function us(e){return function(e){const{registrationToken:t,target:s,agentStatus:n,targetStatus:i,uiAccessStatus:a,...r}=e;return r}(function(e){const t=`/instances/${encodeURIComponent(e.id)}`,s={strategy:"control-plane-proxy",web:`${t}/`,api:`${t}/api`,ws:`${t}/api`,status:"online"===e.connectionStatus||"online"===e.agentStatus?"reachable":"endpoint-unreachable"};return{...e,access:s}}(e))}function gs(e){const{key:t,...s}=e;return{...s,keyPreview:(n=e.key,n.length<=8?"set":`${n.slice(0,4)}...${n.slice(-4)}`),keySet:!0};var n}class hs{read(e){const t=new Map(e.projects.map(e=>[e.id,e])),s=new Map(e.images.map(e=>[e.id,e])),n=new Map(e.nodes.map(e=>[e.id,e])),i=new Map(e.runtimes.map(e=>[`${e.nodeId}:${e.id}`,e])),a=Date.now();return{items:e.instances.map(e=>function(e){const t=e.instance.lastHeartbeatAt?e.currentTime-Date.parse(e.instance.lastHeartbeatAt):void 0,s=void 0!==t&&t>3e4?"offline":e.instance.connectionStatus,n=us(e.instance);return{...n,aiSessions:G(n.aiSessions),connectionStatus:s,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?ls(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===ue}}({instance:e,currentTime:a,project:e.projectId?t.get(e.projectId):void 0,image:e.imageId&&s.get(e.imageId)||e.imageSnapshot,node:n.get(e.nodeId),runtime:i.get(`${e.nodeId}:${e.runtimeId}`)})),nodeErrors:e.nodeErrors||[]}}}const ms=r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:De.shape.id.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:De.shape.createdAt.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:De.shape.createdAt.optional()}).strict().optional()}).strict(),Is=r.z.object({id:Oe.shape.id.optional(),name:Oe.shape.name,source:Ce,defaultImageId:Oe.shape.defaultImageId,defaultNodeId:Oe.shape.defaultNodeId,defaultRuntimeId:Oe.shape.defaultRuntimeId,modelSelection:Oe.shape.modelSelection.optional(),workspacePolicy:Ne.optional(),labels:Oe.shape.labels.optional()}).strict(),fs=Is.omit({id:!0}).partial().strict(),ys=r.z.object({id:Ke.shape.id.optional(),name:Ke.shape.name.optional(),projectId:Ke.shape.projectId,source:Ce.optional(),sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).optional(),nodeId:Ke.shape.nodeId.optional(),runtimeId:Ke.shape.runtimeId.optional(),imageId:Ke.shape.imageId,config:Ke.shape.config.optional()}).strict(),bs=r.z.object({name:Ke.shape.name.optional()}).strict(),vs=r.z.object({publicBaseUrl:r.z.string().trim().max(2048).optional(),updateChannel:be.default("stable")}).strict(),ws=vs.partial().strict(),Ss=r.z.object({id:Re.shape.id.optional(),name:Re.shape.name,endpoint:Re.shape.endpoint,key:Re.shape.key,model:Re.shape.model,app:Re.shape.app,enabled:Re.shape.enabled.optional(),order:Re.shape.order.optional(),labels:Re.shape.labels.optional()}).strict(),As=Ss.omit({id:!0}).partial().strict(),ks=r.z.object({id:xe.shape.id.optional(),name:xe.shape.name,image:xe.shape.image,registry:xe.shape.registry.optional(),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).optional(),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).optional(),defaultEnv:xe.shape.defaultEnv.optional(),labels:xe.shape.labels.optional()}).strict(),Ts=ks.omit({id:!0}).partial().strict(),Es=r.z.object({id:De.shape.id.optional(),name:De.shape.name,connectionMode:De.shape.connectionMode.optional(),auth:ms.optional(),joinToken:r.z.string().trim().min(1).max(4096).optional(),endpoint:De.shape.endpoint,controlEndpoint:De.shape.controlEndpoint,containerEndpoint:De.shape.containerEndpoint,publicWebBase:De.shape.publicWebBase,status:r.z.enum(["unknown","online","offline","degraded"]).optional(),health:r.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:De.shape.capabilities.optional(),labels:De.shape.labels.optional(),lastSeenAt:De.shape.lastSeenAt}).strict(),_s=Es.omit({id:!0,joinToken:!0}).partial().strict(),Cs=r.z.object({controlPlaneUrl:r.z.string().trim().url().max(2048),joinToken:r.z.string().trim().min(1).max(4096),controlPlaneName:r.z.string().trim().min(1).max(160).optional(),activate:r.z.boolean().optional()}).strict(),Ns=r.z.object({nodeName:r.z.string().trim().min(1).max(160).optional(),expiresInMs:r.z.number().int().positive().max(36e5).optional()}).strict(),zs=r.z.object({name:Ye.shape.name.optional(),channel:Ye.shape.channel.optional(),enabled:r.z.boolean().optional(),token:Ye.shape.token,defaultChatId:Ye.shape.defaultChatId,allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Ye.shape.pollIntervalMs.optional(),settings:Ye.shape.settings.optional()}).strict(),Os=zs.extend({channel:Ye.shape.channel,name:Ye.shape.name.optional()}),Rs=ae.extend({id:ae.shape.configHash}),xs=r.z.object({name:ae.shape.name,description:ae.shape.description,source:se,action:ne,policy:ie.partial().optional()}).strict(),Ds=r.z.object({instanceIds:r.z.array(Ke.shape.id).min(1),target:re.optional(),enabled:r.z.boolean().optional()}).strict(),js=r.z.object({configHash:ae.shape.configHash,enabled:r.z.boolean().optional()}).strict();class Ps{triggers;listInstances;requireInstance;instanceRequest;constructor(e){this.triggers=e.triggers,this.listInstances=e.listInstances,this.requireInstance=e.requireInstance,this.instanceRequest=e.instanceRequest}async listTriggers(){const e=await this.listInstances(),t=new Map;for(const e of this.triggers.list())t.set(e.configHash,{configHash:e.configHash,config:e,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!0,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]});for(const s of e){const e=s.triggers;for(const n of e?.configs||[]){const i=t.get(n.configHash)||{configHash:n.configHash,config:n.config,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!1,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]},a=(n.deployments||[]).map(e=>({instanceId:s.id,instanceName:s.name,deployment:e,runtime:(n.runtime||[]).find(t=>t.deploymentId?t.deploymentId===e.deploymentId:t.configHash===e.configHash)}));i.deploymentCount+=a.length,i.enabledCount+=a.filter(e=>Boolean(e.deployment.enabled)).length,i.controlPlaneDeploymentCount+=a.filter(e=>"control-plane"===e.deployment.origin).length,i.runningCount+=(n.runtime||[]).filter(e=>"running"===e.status).length,i.errorCount+=(n.runtime||[]).filter(e=>"error"===e.status).length,i.deployments.push(...a),i.recentRuns.push(...(e.recentRuns||[]).filter(e=>e.configHash===n.configHash).map(e=>({...e,instanceId:s.id,instanceName:s.name}))),t.set(n.configHash,i)}}return{updatedAt:(new Date).toISOString(),triggers:[...t.values()].sort((e,t)=>String(e.config.name||e.configHash).localeCompare(String(t.config.name||t.configHash)))}}createTrigger(e){const t=xs.parse(e||{}),s=ns(),n={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=pe({source:e.source,action:e.action,policy:ie.parse(e.policy||{})});return`trg_${o.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:n}),a=Rs.parse({id:i,configHash:i,name:t.name,description:t.description,source:t.source,action:t.action,policy:n,createdAt:s,updatedAt:s});return this.triggers.put(a)}async deleteTrigger(e){const t=this.triggers.delete(e),s=await this.listInstances(),n=[];for(const t of s){const s=t.triggers?.configs?.find(t=>t.configHash===e);if(!s)continue;const i=(s.deployments||[]).filter(e=>"control-plane"===e.origin);if(!i.length)continue;const a=s.deployments.every(e=>"control-plane"===e.origin);for(const s of i)try{const i=s.deploymentId?`/triggers/${encodeURIComponent(e)}/deployments/${encodeURIComponent(s.deploymentId)}`:a?`/triggers/${encodeURIComponent(e)}`:void 0;if(!i){n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!1,skipped:"missing-deployment-id"});continue}const r=await this.instanceRequest(t,i,{method:"DELETE"});if(n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!0,data:r}),!s.deploymentId)break}catch(i){n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!1,error:i instanceof Error?i.message:String(i)})}}return t||n.length||os("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:n}}async applyTrigger(e,t){const s=this.requireTrigger(e),n=Ds.parse(t||{}),i=[];for(const e of n.instanceIds){const t=await this.requireInstance(e);i.push({instanceId:e,data:await this.instanceRequest(t,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:s.name,description:s.description,source:s.source,action:s.action,policy:s.policy,deployment:{origin:"control-plane",enabled:n.enabled??!0,target:n.target}})})})}return{configHash:e,results:i}}async listInstanceTriggers(e){const t=await this.requireInstance(e);return this.instanceRequest(t,"/triggers")}async bindAiSessionTrigger(e,t,s){const n=js.parse(s||{}),i=this.requireTrigger(n.configHash),a=await this.requireInstance(e);return this.instanceRequest(a,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:i.name,description:i.description,source:i.source,action:i.action,policy:i.policy,deployment:{deploymentId:Ms(t,n.configHash),origin:"control-plane",enabled:n.enabled??!0,target:{type:"ai-session",aiSessionId:t}}})})}async unbindAiSessionTrigger(e,t,s){const n=await this.requireInstance(e),i=Ms(t,s);return this.instanceRequest(n,`/triggers/${encodeURIComponent(s)}/deployments/${encodeURIComponent(i)}`,{method:"DELETE"})}async runInstanceTrigger(e,t,s={}){const n=await this.requireInstance(e);return this.instanceRequest(n,`/triggers/${encodeURIComponent(t)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s||{})})}requireTrigger(e){const t=this.triggers.get(e);return t||os("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function Ms(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}class $s{records=new Map;options;constructor(e){this.options=e}create(e){const t=o.randomBytes(this.options.tokenBytes).toString("base64url"),s={...e,token:t,expiresAt:new Date(Date.now()+(e.ttlMs||this.options.ttlMs)).toISOString()};return this.records.set(t,s),s}resolve(e,t){const s=this.records.get(e);if(!s)throw this.invalidError();if(s.expiresAt<=(new Date).toISOString())throw this.records.delete(e),this.invalidError();if(t&&!t(s))throw this.invalidError();return s}invalidError(){const e=new Error(this.options.invalidMessage);return Object.assign(e,{statusCode:this.options.invalidStatusCode||400,code:this.options.invalidCode}),e}}class qs{requireInstance;listAppSessions;tokens=new $s({invalidMessage:"App access link is invalid or expired.",invalidCode:"APP_ACCESS_TOKEN_INVALID",invalidStatusCode:401,tokenBytes:24,ttlMs:9e5});constructor(e){this.requireInstance=e.requireInstance,this.listAppSessions=e.listAppSessions}createToken(e){return this.tokens.create({instanceId:e.instanceId,sessionId:e.sessionId,mode:e.mode,ttlMs:e.ttlMs})}resolveToken(e,t){return this.tokens.resolve(e,e=>!t||e.mode===t)}async proxyTarget(e,t,s=""){const n=this.resolveToken(e,t),i=await this.requireInstance(n.instanceId),a=await this.listAppSessions(),r=a.instances.find(e=>e.instanceId===n.instanceId)?.appSessions.sessions.find(e=>Fs(e.id)===n.sessionId);if(!r){const e=new Error("App session was not found.");throw Object.assign(e,{statusCode:404,code:"APP_SESSION_NOT_FOUND"}),e}return{access:n,instance:i,session:r,path:Ls(t,n.sessionId,r,s)}}}function Ls(e,t,s,n=""){return"tty"===e?`/api/apps/sessions/${encodeURIComponent(t)}/tty`:"vnc"===e?n?function(e,t){const s=`/${t.replace(/^\/+/,"")}`,n=s.split("?",1)[0]||"/",i=encodeURIComponent(e);if(!["/api/novnc/",`/api/apps/sessions/${i}/web/`,`/api/apps/sessions/${i}/vnc`,`/api/apps/sessions/${i}/novnc/`].some(e=>n===e.replace(/\/$/,"")||n.startsWith(e))){const e=new Error("App access token cannot be used for this path.");throw Object.assign(e,{statusCode:403,code:"APP_ACCESS_PATH_FORBIDDEN"}),e}return s}(t,n):Fs(Us(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Fs(Us(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Us(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Fs(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Bs{tokens=new $s({invalidMessage:"Chat action expired.",invalidCode:"CHAT_ACTION_TOKEN_INVALID",tokenBytes:9,ttlMs:6e5});create(e){return this.tokens.create({type:e.type,instanceId:"instanceId"in e?e.instanceId:void 0,appId:"appId"in e?e.appId:void 0,routeId:"routeId"in e?e.routeId:void 0,decision:"decision"in e?e.decision:void 0,ttlMs:e.ttlMs})}resolve(e,t){return this.tokens.resolve(e,e=>!t||e.type===t)}pendingDecisionCallbackData(e,t){return`task_handoff:cp_p:${this.create({type:"pending-decision",routeId:e,decision:t}).token}`}}function Hs(e){const{token:t,...s}=e;return{...s,settings:Ws(e.settings),tokenSet:Boolean(e.token)}}function Gs(e,t){if(!t)return e;const s={...e};for(const[e,n]of Object.entries(t))void 0!==n&&""!==n&&(s[e]=n);return s}function Ws(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}class Vs{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(Hs)}require(e){const t=this.chatBridges.get(e);return t||os("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=Os.parse(e),s=ns(),n=Ye.parse({...t,id:pt(`chat_${t.channel}`),name:t.name||(i=t.channel,a=this.chatBridges.list().filter(e=>e.channel===t.channel).length+1,`${"telegram"===i?"Telegram":"wechat"===i?"WeChat":"dingding"===i?"DingDing":"Web"} ${a}`),enabled:t.enabled??!1,allowedUserIds:t.allowedUserIds||[],pollIntervalMs:t.pollIntervalMs||3e3,settings:Gs({},t.settings),createdAt:s,updatedAt:s});var i,a;return this.chatBridges.put(n),Hs(n)}update(e,t){const s=zs.parse(t),n=this.require(e),i=Gs(n.settings,s.settings),a=Ye.parse({...n,...s,id:e,channel:n.channel,name:s.name||n.name,settings:i,updatedAt:ns()});return this.chatBridges.put(a),Hs(a)}delete(e){const t=this.chatBridges.delete(e);if(t)for(const t of this.chatSessions.list())t.bridgeId===e&&this.chatSessions.delete(t.id);return t}}class Ks{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||os("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=ns(),s=(n=e.channel,i=e.chatSessionId,`${e.bridgeId||n}:${i.replace(/[^a-zA-Z0-9_.:-]/g,"_")}`);var n,i;const a=this.chatSessions.get(s),r=Xe.parse({...a||{},...e,id:s,channel:e.channel,bridgeId:e.bridgeId,chatSessionId:e.chatSessionId,lastUsedAt:t,createdAt:a?.createdAt||t,updatedAt:t});return this.chatSessions.put(r),r}}function Js(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function Qs(e,t=40){return Js(e.slice(0,t).map(e=>[e]))}function Ys(e){return Buffer.from(e,"utf8").toString("base64url")}function Xs(e){const t=en(e).inline_keyboard,s=Array.isArray(t)?t:[],n=[];for(const e of s)if(Array.isArray(e))for(const t of e){const e=en(t),s=String(e.callback_data||""),i=String(e.text||"").trim();i&&s&&n.push({text:i,id:`th_cb_${Ys(s)}`})}return n.slice(0,40)}function Zs(e,t){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function en(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const tn=new Set(["stopped","failed","exited","closed","terminated"]);function sn(e){return!function(e){return tn.has(Wt(e))}(e)}function nn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const s=(e.apps?.sessions||[]).filter(ln);if(t.appSessionId&&s.some(e=>e.id===t.appSessionId))return!0;const n=new Set(t.appBindingKeys||[]);return Boolean(n.size&&s.some(e=>function(e){return[...Gt(e).bindings.map(Vt),...Jt(e)].filter(Boolean)}(e).some(e=>n.has(e))))}(e,t))}function an(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function rn(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function on(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=t?` - ${Zs(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${s}`}function dn(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return Qs(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Zs(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Zs(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Zs(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function cn(e){const t=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,s="approval"===e.kind?`/approve ${e.id} | /deny ${e.id} | /skip ${e.id}`:`/reply ${e.id} <message>`;return`[${t}] ${e.kind} ${e.id}: ${e.result}\n${s}`}function ln(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return sn("string"==typeof t.status?t.status:void 0)}class pn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleAiSessionsCommand(e){const t=await this.targets.aiSessionChoices(e),s=t.find(e=>e.active),n=t.map(e=>function(e){const t=e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=e.active?"* ":"",n=e.session.phase&&"unknown"!==e.session.phase?`/${e.session.phase}`:"";return`${s}${e.index+1}. ${e.projectName} / ${e.instance.name} - ${e.session.agent} - ${e.session.status}${n} - ${e.session.id}${t?` - ${t}`:""}`}(e));return{accepted:!0,routed:!1,binding:e,instances:t.map(e=>e.instance),reply:t.length?["Select AI session",`Current ${s?`${s.projectName} / ${s.instance.name} / ${s.session.id}`:await this.targets.renderChatTarget(e)}, total ${t.length}`,"",...n].join("\n"):"No AI sessions found.",replyMarkup:t.length?Qs(t.map(e=>({text:`${e.active?"✓ ":""}${on(e)}`,callbackData:`task_handoff:cp_session:${e.index}`}))):void 0}}async handleAiSessionCommand(e,t){const s=String(t[0]||"").trim();if(!s)return this.handleAiSessionsCommand(e);const n=await this.targets.findAiSession(s);if(!n)return{accepted:!1,routed:!1,binding:e,reply:`AI session ${s} was not found. Try /sessions.`};const i=n.instance.projectId?this.deps.getProject(n.instance.projectId):void 0;return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i?.id||e.activeProjectId,activeInstanceId:n.instance.id,activeAiSessionId:n.session.id}),instance:n.instance,aiSession:n.session,reply:`Active AI session: ${n.instance.name} / ${n.session.agent} ${n.session.id}`}}async handleAiSessionIndexAction(e,t){const s=(await this.targets.aiSessionChoices(e)).find(e=>e.index===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"AI session was not found. Use /session to refresh the list.",replyMarkup:void 0};const n=this.deps.upsertChatSession({...e,activeProjectId:s.projectId||e.activeProjectId,activeInstanceId:s.instance.id,activeAiSessionId:s.session.id}),i=await this.handleAiSessionsCommand(n);return{...i,accepted:!0,binding:n,instance:s.instance,aiSession:s.session,message:`selected ${s.session.id}`,reply:[`Current chat is bound to AI session ${s.session.id}`,"",i.reply].join("\n")}}async handleAiSessionInterruptCommand(e){if(!e.activeInstanceId||!e.activeAiSessionId)return{accepted:!1,routed:!1,binding:e,reply:"No active AI session is selected. Use /sessions and /session <id> first."};const t=await this.deps.requireControlledInstance(e.activeInstanceId,!0),s=await this.deps.interruptAiSession(t.id,e.activeAiSessionId);return{accepted:!0,routed:!0,binding:e,instance:us(t),aiSession:s,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function un(e){return e.providerTurnId||e.turnId||""}function gn(e){return sn(yn(e.status))}function hn(e,t){const s=yn(t.appId)||"App",n=yn(t.title).trim();if(n&&n!==s)return n;const i=function(e,t){for(const s of function(e){const t=e.apps;return Array.isArray(t)?t.filter(e=>"object"==typeof e&&null!==e&&!Array.isArray(e)):[]}(e.sourceSnapshot))if(yn(s.id)===t)return yn(s.name).trim();return""}(e,s);return i||s}function mn(e){const t=function(e){const t=wn(e.capabilities).apps;if(Array.isArray(t)&&t.length>0)return t;const s=wn(e.image).optionalApps;return Array.isArray(s)&&s.length>0?s:["terminal-tty"]}(e),s=new Map;for(const e of t){const t=bn(e);t&&!s.has(t.id)&&s.set(t.id,t)}return[...s.values()]}function In(e,t,s,n){const i=yn(t.id)?function(e,t){return`/apps/access/${fn(e)}?token=${encodeURIComponent(t())}`}(t,n):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const s=yn(t);if(s&&new RegExp("^https?://","i").test(s))return new URL(e,s.endsWith("/")?s:s+"/").toString()}(i,s);return{text:a||i,url:a}}function fn(e){const t=yn(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function yn(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function bn(e){if("string"==typeof e&&e.trim())return{id:e,label:vn(e)};const t=wn(e),s=yn(t.id).trim();return s?{id:s,label:yn(t.name).trim()||vn(s)}:void 0}function vn(e){return{"terminal-tty":"Terminal","gui-terminal":"GUI Terminal",chromium:"Chromium",browser:"Browser","vscode-web":"VS Code"}[e]||e.replace(/[-_]+/g," ").replace(/\b\w/g,e=>e.toUpperCase())}function wn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}class Sn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleInstancesCommand(e){const t=await this.targets.instancesForBinding(e);return{accepted:!0,routed:!1,binding:e,instances:t,reply:(s=t.length,0===s?"No controlled instances are registered yet.":`Instances: ${s}. Tap an instance to create an app.`),replyMarkup:t.length?Qs(t.map(e=>{return{text:Zs(e.name||e.id,56),callbackData:(t=this.deps.createChatActionToken({type:"instance-app-menu",instanceId:e.id}).token,`task_handoff:cp_i:${t}`)};var t})):void 0};var s}async handleInstanceAppMenuAction(e,t){const s=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const n=mn(s);return{accepted:!0,routed:!1,binding:e,instance:s,message:`Select an app for ${s.name}.`,reply:n.length?`New app for ${s.name}`:`No launchable apps for ${s.name}.`,replyMarkup:n.length?Qs(n.map(e=>{return{text:Zs(e.label,56),callbackData:(t=this.deps.createChatActionToken({type:"launch-app",instanceId:s.id,appId:e.id}).token,`task_handoff:cp_a:${t}`)};var t})):void 0}}async handleLaunchAppAction(e,t,s){const n=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const i=mn(n).find(e=>e.id===s);return i?{accepted:!0,routed:!1,binding:e,instance:n,session:await this.deps.launchAppSession(n.id,i.id),message:`${i.label} launched`,reply:`Launched ${i.label} on ${n.name}.`}:{accepted:!1,routed:!1,binding:e,reply:`App ${s} is not launchable for ${n.name}.`}}async handleAppsCommand(e){const t=await this.deps.boardAsync(),s=new Map(t.map(e=>[e.id,e])),n=(await this.deps.listAppSessions()).instances.flatMap(e=>{const t=s.get(e.instanceId);return t?e.appSessions.sessions.filter(gn).filter(e=>Boolean(yn(e.id))).map(e=>({instance:t,session:e,link:In(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:yn(e.id)||"",mode:fn(e)}).token)})):[]}),i=n.filter(e=>e.link.url).map(e=>({text:Zs(`${e.instance.name} · ${hn(e.instance,e.session)}`,56),url:e.link.url}));return{accepted:!0,routed:!1,binding:e,instances:t,reply:(a=n.length,r=i.length,0===a?"No app sessions found.":0===r?`App sessions: ${a}. Set the control plane public URL in Settings > Appearance to open them from chat.`:`App sessions: ${a}. Tap a button to open.`),replyMarkup:i.length?Qs(i):void 0};var a,r}}class An{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async forwardChatMessage(e,t){const s=t.target?.instanceId||e.activeInstanceId||"",n=t.target?.aiSessionId||e.activeAiSessionId,i=await this.deps.requireControlledInstance(s,!0),a=i.projectId?this.deps.requireProject(i.projectId):void 0;if("online"!==i.connectionStatus){const e=new Error(`Instance ${i.name} is not reachable.`);throw Object.assign(e,{statusCode:409,code:"CHAT_TARGET_UNREACHABLE"}),e}if(n){const s=await this.trySendAiSessionMessage(e,i.id,n,t.message.text,t.message.attachments,!t.target);return s.ok?this.sentResult(e,a,i,n,s.aiSession):this.failedSendResult(s.binding,a,i,s.reply)}const r=await this.targets.defaultAiSessionIdForInstance(i.id);if(r){const s=this.deps.upsertChatSession({...e,activeAiSessionId:r}),n=await this.trySendAiSessionMessage(s,i.id,r,t.message.text,t.message.attachments);return n.ok?this.sentResult(s,a,i,r,n.aiSession):this.failedSendResult(n.binding,a,i,n.reply)}return{accepted:!1,routed:!1,binding:e,project:a||void 0,instance:us(i),reply:`No active AI session is selected for ${i.name}. Use /sessions and /session <id> first.`}}async trySendAiSessionMessage(e,t,s,n,i=[],a=!0){try{return{ok:!0,binding:e,aiSession:await this.deps.sendAiSessionMessage(t,s,n||"请查看附件图片。",void 0,i)}}catch(t){return function(e){return"AI_SESSION_NOT_FOUND"===(e&&"object"==typeof e?e:{}).code}(t)?{ok:!1,binding:a?this.deps.upsertChatSession({...e,activeAiSessionId:void 0}):e,reply:`The selected AI session ${s} no longer exists. Use /session to choose an active AI session.`}:{ok:!1,binding:e,reply:`Failed to send to AI session ${s}: ${rs(t)}`}}}failedSendResult(e,t,s,n){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:us(s),reply:n}}sentResult(e,t,s,n,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:us(s),aiSession:i,instanceId:s.id,aiSessionId:n,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:un(i),reply:`Sent to ${t?.name||s.sourceSnapshot.name||"Source"} / ${s.name} / ${n}.`};var a}}class kn{deps;constructor(e){this.deps=e}async handlePendingCommand(e){const t=await this.deps.listPendingRoutes(),s=e.activeInstanceId?t.filter(t=>t.instanceId===e.activeInstanceId):t;return{accepted:!0,routed:!1,binding:e,pending:s,reply:s.length?s.map(cn).join("\n"):"No pending routes.",replyMarkup:s.length?dn(s,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,s){const n=as(t[0]);return n?n.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:n,aiSession:await this.deps.resolveAiSessionApproval(n.instanceId,n.aiSessionId,s),reply:`${s} sent for ${n.instanceId}:${n.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:n,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:`Usage: /${"allow"===s?"approve":s} <instance-id:pending-id>`}}async handlePendingReplyCommand(e,t){const s=as(t[0]),n=t.slice(1).join(" ").trim();return s&&n?s.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:s,aiSession:await this.deps.sendAiSessionMessage(s.instanceId,s.aiSessionId,n),reply:`Reply sent for ${s.instanceId}:${s.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:s,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:"Usage: /reply <instance-id:pending-id> <message>"}}}class Tn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleUseCommand(e,t){if(0===t.length)return{accepted:!0,routed:!1,binding:e,instances:await this.targets.instancesForBinding(e),reply:await this.targets.renderUseHelp(e)};if(t.length>=2){const s=this.targets.resolveProject(t[0]),n=await this.targets.resolveInstance(t[1],s.id),i=this.deps.upsertChatSession({...e,activeProjectId:s.id,activeInstanceId:n.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(n.id)});return{accepted:!0,routed:!1,binding:i,reply:await this.targets.renderTargetSelected(i)}}const s=t[0],n=await this.targets.findInstance(s);if(n){const t=n.projectId?this.deps.requireProject(n.projectId):void 0,s=this.deps.upsertChatSession({...e,activeProjectId:t?.id,activeInstanceId:n.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(n.id)});return{accepted:!0,routed:!1,binding:s,reply:await this.targets.renderTargetSelected(s)}}const i=this.targets.resolveProject(s),a=(await this.deps.boardAsync()).filter(e=>e.projectId===i.id),r=a.filter(e=>"running"===e.status&&"online"===e.connectionStatus);if(1===r.length){const t=this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:r[0].id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(r[0].id)});return{accepted:!0,routed:!1,binding:t,reply:await this.targets.renderTargetSelected(t)}}return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:void 0,activeAiSessionId:void 0}),instances:a,reply:`${i.name} selected. Choose an instance with /use ${i.id} <instance-id-or-name>.\n${this.targets.renderInstanceLines(a)}`}}async handleProjectCommand(e,t){if(0===t.length)return{accepted:!1,routed:!1,binding:e,reply:"Usage: /project <project-id-or-name>"};const s=this.targets.resolveProject(t[0]),n=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,i=this.deps.upsertChatSession({...e,activeProjectId:s.id,activeInstanceId:n?.projectId===s.id?n.id:void 0,activeAiSessionId:n?.projectId===s.id?e.activeAiSessionId:void 0});return{accepted:!0,routed:!1,binding:i,reply:`Active project: ${s.name}${i.activeInstanceId?"":". Choose an instance with /use."}`}}}class En{deps;constructor(e){this.deps=e}async instancesForBinding(e){const t=await this.deps.boardAsync();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async fullInstancesForBinding(e){const t=await this.deps.listNodeInstances();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async renderUseHelp(e){return`Active target: ${await this.renderChatTarget(e)}\nUse /instances to list targets, then /use <instance-id-or-name>. Use /sessions to choose an AI session when there is more than one.`}async renderChatStatus(e){const t=e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0;return t?`Active target: ${await this.renderChatTarget(e)}\nStatus: ${t.status}, connection: ${t.connectionStatus}, pending: ${t.receiver.pendingCount}`:`Active target: ${await this.renderChatTarget(e)}`}async renderChatHelp(e){return["TaskHandoff chat commands:","/help - show this help","/status or /target - show the active project, instance, and AI session","/instances - list controlled instances","/apps - list app sessions","/use <instance-id-or-name> - choose an active instance","/use <project-id-or-name> <instance-id-or-name> - choose a project and instance","/project <project-id-or-name> - narrow instance selection to a project","/sessions - list all AI sessions","/session <ai-session-id> - choose an active AI session","/pending - list waiting approvals","/approve <instance-id:pending-id>, /deny <...>, /skip <...> - answer approvals","/reply <instance-id:pending-id> <message> - reply to a waiting session","/cancel - interrupt the active AI session","","Send normal text after selecting a target; if there is only one AI session, it is selected automatically.",`Current target: ${await this.renderChatTarget(e)}`].join("\n")}renderInstanceLines(e){return e.map(e=>`${e.name} (${e.id}) - ${e.status}/${e.connectionStatus}`).join("\n")}async renderChatTarget(e){const t=e.activeProjectId?this.deps.getProject(e.activeProjectId):void 0,s=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,n=e.activeAiSessionId||"none";return`${t?.name||e.activeProjectId||"none"} / ${s?.name||e.activeInstanceId||"none"} / ${n}`}async renderTargetSelected(e){const t=await this.renderChatTarget(e);return e.activeAiSessionId?`Active target: ${t}`:`Active target: ${t}\nNo AI session selected yet. Use /sessions and /session <id>, or send a message once exactly one session is available.`}async defaultAiSessionIdForInstance(e){const t=await this.aiSessionSnapshotForInstance(e),s=(await this.deps.boardAsync()).find(t=>t.id===e),n=(await this.deps.listAppSessions()).instances.find(t=>t.instanceId===e),i=s?{...s,apps:{...s.apps,sessions:n?.appSessions.sessions||[]}}:void 0,a=t&&s?nn({...i,aiSessions:t}):t?.sessions||[];return 1===a.length?a[0].id:void 0}async aiSessionChoices(e){return(await this.boardInstancesForBinding({},{refresh:!0})).flatMap(t=>nn(t).map(s=>({index:0,projectId:t.projectId,projectName:t.project?.name||("string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:"Source"),instance:t,session:s,active:e.activeInstanceId===t.id&&e.activeAiSessionId===s.id}))).sort((e,t)=>{const s=an(t.session)-an(e.session);return s||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||rn(e.session).localeCompare(rn(t.session)))}).map((e,t)=>({...e,index:t}))}async boardInstancesForBinding(e,t={}){const s=await this.deps.boardAsync(),n=await this.deps.listAiSessions(t),i=await this.deps.listAppSessions(t),a=new Map(n.instances.map(e=>[e.instanceId,e.aiSessions])),r=new Map(i.instances.map(e=>[e.instanceId,e.appSessions])),o=s.map(e=>{return{...e,apps:{...e.apps,sessions:r.get(e.id)?.sessions||[]},aiSessions:a.get(e.id)||(t=e.aiSessions.updatedAt,{runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:t})};var t});return e.activeProjectId?o.filter(t=>t.projectId===e.activeProjectId):o}async findAiSession(e){const t=await this.boardInstancesForBinding({},{refresh:!0});for(const s of t){const t=(s.aiSessions?.sessions||[]).find(t=>t.id===e||t.providerSessionId===e);if(t)return{instance:s,session:t}}}async aiSessionSnapshotForInstance(e){const t=await this.deps.listAiSessions();return t.instances.find(t=>t.instanceId===e)?.aiSessions}resolveProject(e){const t=this.deps.getProject(e)||this.deps.listProjects().find(t=>t.name===e);return t||os("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const s=await this.findInstance(e,t);return s||os("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`),s}async findInstance(e,t){return(await this.deps.listNodeInstances()).find(s=>!(s.id!==e&&s.name!==e||t&&s.projectId!==t))}}class _n{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new En(e),this.sender=new An(e,this.targets),this.appCommands=new Sn(e,this.targets),this.pendingCommands=new kn(e),this.aiSessionCommands=new pn(e,this.targets),this.targetCommands=new Tn(e,this.targets)}async handleChatGatewayMessage(e){const t=Ze.parse(e),s=this.deps.upsertChatSession({channel:t.source.channel,bridgeId:t.source.bridgeId,chatSessionId:t.source.chatSessionId,userId:t.source.userId}),n=t.message.text.trim();return n.startsWith("/")?this.handleChatCommand(s,n):n||t.message.attachments.length?s.activeInstanceId?this.sender.forwardChatMessage(s,t):{accepted:!1,routed:!1,binding:s,reply:await this.targets.renderUseHelp(s)}:{accepted:!1,routed:!1,binding:s,reply:""}}async handleChatGatewayAction(e){const t=this.deps.upsertChatSession({channel:e.source.channel,bridgeId:e.source.bridgeId,chatSessionId:e.source.chatSessionId,userId:e.source.userId});return"ai-session"===e.action.type?this.aiSessionCommands.handleAiSessionIndexAction(t,e.action.index):"instance-app-menu"===e.action.type?this.appCommands.handleInstanceAppMenuAction(t,e.action.instanceId):"launch-app"===e.action.type?this.appCommands.handleLaunchAppAction(t,e.action.instanceId,e.action.appId):"pending-decision"===e.action.type?this.pendingCommands.handlePendingDecisionCommand(t,[e.action.routeId],e.action.decision):{accepted:!1,routed:!1,binding:t,reply:"Unknown action."}}async handleChatCommand(e,t){const[s="",...n]=t.split(/\s+/),i=s.replace(/@[\w_]+$/,"");switch(i.toLowerCase()){case"/help":case"/start":return{accepted:!0,routed:!1,binding:e,reply:await this.targets.renderChatHelp(e)};case"/sessions":return this.aiSessionCommands.handleAiSessionsCommand(e);case"/session":return this.aiSessionCommands.handleAiSessionCommand(e,n);case"/cancel":case"/interrupt":return this.aiSessionCommands.handleAiSessionInterruptCommand(e);case"/pending":return this.pendingCommands.handlePendingCommand(e);case"/approve":return this.pendingCommands.handlePendingDecisionCommand(e,n,"allow");case"/deny":return this.pendingCommands.handlePendingDecisionCommand(e,n,"deny");case"/skip":return this.pendingCommands.handlePendingDecisionCommand(e,n,"skip");case"/reply":return this.pendingCommands.handlePendingReplyCommand(e,n);case"/instances":return this.appCommands.handleInstancesCommand(e);case"/apps":return this.appCommands.handleAppsCommand(e);case"/use":return this.targetCommands.handleUseCommand(e,n);case"/project":return this.targetCommands.handleProjectCommand(e,n);case"/status":case"/target":return{accepted:!0,routed:!1,binding:e,instance:e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0,reply:await this.targets.renderChatStatus(e)};default:return{accepted:!1,routed:!1,binding:e,reply:`Unknown command: ${i||s}. Try /help.`}}}}function Cn(e,t){const s=t.filter(e=>e.enabled),n={},i=zn(s,"codex",e.modelSelection.codexModelId);i&&(n.OPENAI_API_KEY=i.key,n.OPENAI_BASE_URL=i.endpoint,n.CODEX_MODEL=i.model,n.TASK_HANDOFF_CODEX_MODEL=i.model,n.TASK_HANDOFF_CODEX_MODEL_ID=i.id);const a=zn(s,"claude",e.modelSelection.claudeModelId);return a&&(n.ANTHROPIC_API_KEY=a.key,n.ANTHROPIC_BASE_URL=a.endpoint,n.CLAUDE_MODEL=a.model,n.TASK_HANDOFF_CLAUDE_MODEL=a.model,n.TASK_HANDOFF_CLAUDE_MODEL_ID=a.id),n}function Nn(e){const t=e.modelSelection;if(t&&"object"==typeof t&&!Array.isArray(t)){const e=t;return{..."string"==typeof e.codexModelId?{codexModelId:e.codexModelId}:{},..."string"==typeof e.claudeModelId?{claudeModelId:e.claudeModelId}:{}}}return{}}function zn(e,t,s){const n=e.filter(e=>e.app===t);return n.find(e=>s&&e.id===s)||n[0]}function On(e){return{enabledCount:e.deployments.filter(e=>e.enabled).length,runningCount:e.runtime.filter(e=>"running"===e.status).length,errorCount:e.runtime.filter(e=>"error"===e.status).length,configs:e.configs.map(t=>({configHash:t.configHash,config:t,deployments:e.deployments.filter(e=>e.configHash===t.configHash),runtime:e.runtime.filter(e=>e.configHash===t.configHash)})),recentRuns:e.recentRuns.slice(0,20)}}const Rn="TASK_HANDOFF_NODE_AGENT_V1",xn=6e4;function Dn(e=""){return o.createHash("sha256").update(e).digest("hex")}function jn(e,t){return o.createHmac("sha256",e).update(function(e){return[Rn,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function Pn(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||o.randomBytes(18).toString("base64url"),n=Dn(e.body||""),i=jn(e.secret,{keyId:e.keyId,method:e.method,pathWithQuery:e.pathWithQuery,timestamp:t,nonce:s,bodySha256:n});return{"x-taskhandoff-node-id":e.nodeId,"x-taskhandoff-key-id":e.keyId,"x-taskhandoff-timestamp":t,"x-taskhandoff-nonce":s,"x-taskhandoff-body-sha256":n,"x-taskhandoff-signature":i}}function Mn(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function $n(e,s=process.platform){if(!function(e,s=process.platform){if("win32"===s)return e.startsWith("\\\\.\\pipe\\");const n=t.statSync(e),i=process.getuid?.();return void 0===i||n.uid===i}(e,s)){const e=new Error("Local node agent IPC socket is not owned by the current user.");throw Object.assign(e,{statusCode:403,code:"NODE_AGENT_IPC_OWNER_INVALID"}),e}}function qn(e,t,s,n={}){const i=`/api/node-agent${t}`,a={createConnection:()=>g.connect(e),headers:{host:"task-handoff-node-agent.local",...n}};return s&&(Array.isArray(s)?s.length>0:s.trim().length>0)?new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,s,a):new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,a)}const Ln=r.z.object({id:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),nodeName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),Un="task-handoff.control-plane.local",Fn="task-handoff.control-plane.builtin";function Bn(e){return"true"===e.labels[Un]}function Hn(e){return"true"===e.labels[Fn]}function Gn(e){const t=function(e){return"paired-hmac"===e.auth.mode?e.auth.keyId:void 0}(e);if(!t){const t=new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`);throw Object.assign(t,{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"}),t}return t}function Wn(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return Pn({nodeId:e.id,keyId:Gn(e),secret:s,method:t.method,pathWithQuery:t.pathWithQuery,body:t.body});const n=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return n?{authorization:`Bearer ${n}`}:{}}class Vn{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new Bs;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new hs;controlPlaneTriggerService;chatBridgeService;chatSessionService;chatSessionRuntime;aiSessionSnapshotProvider;appSessionSnapshotProvider;constructor(e,t={}){var s;this.paths=e,this.fetchImpl=t.fetchImpl||fetch,this.dockerCommandRunner=t.dockerCommandRunner,this.nodeAgentTransport=t.nodeAgentTransport,this.logger=(s=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(s||"").trim().toLowerCase())?t.logger:void 0);const n=new ts({request:(e,t,s)=>this.nodeAgentFetch(e,t,s),logger:this.logger});this.nodeAgentGateway=new ss(n),this.controlledInstanceGateway=new ds({requireNode:e=>this.requireNode(e),nodeAgentGateway:this.nodeAgentGateway,nodeAgentRequest:(e,t,s)=>this.nodeAgentFetch(e,t,s),fetchImpl:this.fetchImpl});const i=e=>({schema:e,logger:(e,t)=>this.logWarn(t,e)});this.projects=new gt(e.projectsDir,i(Oe)),this.models=new gt(e.modelsDir,i(Re)),this.images=new gt(e.imagesDir,i(xe)),this.nodes=new gt(e.nodesDir,i(De)),this.chatSessions=new gt(e.chatSessionsDir,i(Xe)),this.chatBridges=new gt(e.chatBridgesDir,i(Ye)),this.triggers=new gt(e.triggersDir,i(Rs)),this.nodeJoinInvites=new gt(e.nodeJoinInvitesDir,i(Ln)),this.settings=new ht(e.settingsPath,()=>vs.parse({}),i(vs)),this.controlPlaneTriggerService=new Ps({triggers:this.triggers,listInstances:()=>this.listNodeInstances(),requireInstance:async e=>this.requireControlledInstance(e,!0),instanceRequest:(e,t,s)=>this.instanceRequest(e,t,s)}),this.appAccessService=new qs({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Vs({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new Ks({chatSessions:this.chatSessions}),this.chatSessionRuntime=new _n({upsertChatSession:e=>this.upsertChatSession(e),boardAsync:()=>this.boardAsync(),listAiSessions:e=>this.listAiSessions(e),listAppSessions:e=>this.listAppSessions(e),listNodeInstances:()=>this.listNodeInstances(),listPendingRoutes:()=>this.listPendingRoutes(),pendingDecisionCallbackData:(e,t)=>this.pendingDecisionCallbackData(e,t),createChatActionToken:e=>this.createChatActionToken(e),createAppAccessToken:e=>this.createAppAccessToken(e),controlPlanePublicBaseUrl:()=>this.getSettings().publicBaseUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_URL||process.env.TASK_HANDOFF_PUBLIC_BASE||process.env.TASK_HANDOFF_CONTROL_PLANE_PUBLIC_URL,requireControlledInstance:(e,t)=>this.requireControlledInstance(e,t),requireProject:e=>this.requireProject(e),getProject:e=>this.projects.get(e),listProjects:()=>this.projects.list(),resolveAiSessionApproval:(e,t,s)=>this.resolveAiSessionApproval(e,t,s),sendAiSessionMessage:(e,t,s,n,i)=>this.sendAiSessionMessage(e,t,s,n,i),interruptAiSession:(e,t)=>this.interruptAiSession(e,t),launchAppSession:(e,t,s)=>this.launchAppSession(e,t,s)})}logInfo(e,t){this.logger?.info?.(e,t)}logWarn(e,t){this.logger?.warn?.(e,t)}logError(e,t){this.logger?.error?.(e,t)}setNodeAgentTransport(e){this.nodeAgentTransport=e}setAiSessionSnapshotProvider(e){this.aiSessionSnapshotProvider=e}async listAiSessions(e={}){return this.aiSessionSnapshotProvider?this.aiSessionSnapshotProvider(e):this.bootstrapAiSessionsFromInstances()}setAppSessionSnapshotProvider(e){this.appSessionSnapshotProvider=e}async listAppSessions(e={}){return this.appSessionSnapshotProvider?this.appSessionSnapshotProvider(e):this.bootstrapAppSessionsFromInstances()}init(){this.projects.init(),this.models.init(),this.images.init(),this.nodes.init(),this.chatSessions.init(),this.chatBridges.init(),this.triggers.init(),this.nodeJoinInvites.init(),this.settings.init(),this.seedDefaults()}getSettings(){return vs.parse(this.settings.get())}updateSettings(e){const t=ws.parse(e),s=vs.parse({...this.getSettings(),...t,..."publicBaseUrl"in t?{publicBaseUrl:t.publicBaseUrl?.trim()||void 0}:{}});return this.settings.put(s)}seedDefaults(){if(!this.images.get("img_default")){const e=ns();this.images.put(xe.parse({id:"img_default",name:"TaskHandoff Browser + Terminal",image:process.env.TASK_HANDOFF_CONTROLLED_IMAGE||"task-handoff-web:local",registry:"local",capabilities:["browser","terminal","gui-terminal","codex","claude"],optionalApps:["chromium","terminal-tty","gui-terminal","vscode-web"],defaultEnv:{},labels:{"task-handoff.image.kind":"controlled-instance"},createdAt:e,updatedAt:e}))}}listProjects(){return this.projects.list().map(e=>this.normalizeProjectRecord(e))}defaultNodeId(){return this.nodes.list().find(Bn)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?Wn(t,{method:"GET",pathWithQuery:`/api/node-agent${s}`}):{},i=await this.fetchNodeAgentEndpoint(e,s,{headers:n}),a=await i.json().catch(()=>({}));if(!i.ok){const t=new Error(a.error?.message||`Node agent health check failed with HTTP ${i.status}`);throw Object.assign(t,{statusCode:i.status,code:"NODE_AGENT_HEALTH_FAILED"}),this.logWarn({nodeEndpoint:e,statusCode:i.status,errorCode:"NODE_AGENT_HEALTH_FAILED"},"node agent health check failed"),t}const r="string"==typeof a.data?.nodeId&&a.data.nodeId.trim()?a.data.nodeId.trim():void 0;if(!r){const t=new Error("Node agent health response did not include nodeId.");throw Object.assign(t,{statusCode:502,code:"NODE_AGENT_ID_MISSING"}),this.logWarn({nodeEndpoint:e,payload:a.data,errorCode:"NODE_AGENT_ID_MISSING"},"node agent health response missing node id"),t}if(a.data?.protocolVersion!==ue){const t=function(e,t){const s="string"==typeof t&&t.trim()?t:"missing",n=new Error(`${e} protocol version ${s} is not compatible with ${ue}.`);return Object.assign(n,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),n}(`Node agent ${r}`,a.data?.protocolVersion);throw this.logWarn({nodeId:r,nodeEndpoint:e,expectedProtocolVersion:ue,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=ye.safeParse(a.data?.build);return this.logInfo({nodeId:r,nodeEndpoint:e,protocolVersion:a.data.protocolVersion,build:o.success?o.data:a.data?.build},"node agent health check ok"),{nodeId:r,data:a.data||{}}}async fetchNodeAgentEndpoint(e,t,s={}){const n=Mn(e);return n?($n(n),async function(e,t,s={}){const n=s.method||"GET",i="string"==typeof s.body||s.body instanceof Buffer?s.body:void 0===s.body||null===s.body?void 0:String(s.body),a=new Headers(s.headers||{});return void 0===i||a.has("content-length")||a.set("content-length",String(Buffer.byteLength(i))),new Promise((s,r)=>{const o=u.request({socketPath:e,method:n,path:`/api/node-agent${t}`,headers:Object.fromEntries(a.entries())},e=>{const t=[];e.on("data",e=>t.push(Buffer.isBuffer(e)?e:Buffer.from(e))),e.on("end",()=>{s(new Response(Buffer.concat(t),{status:e.statusCode||500,headers:e.headers}))})});o.on("error",r),void 0!==i&&o.write(i),o.end()})}(n,t,s)):this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent${t}`,s)}async completeNodeAgentPairing(e,t){const s=await this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent/pairing/complete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({joinToken:t.joinToken,controlPlaneName:t.controlPlaneName||"Control Plane",controlPlaneUrl:t.controlPlaneUrl})}),n=await s.json().catch(()=>({}));if(!s.ok){const e=new Error(n.error?.message||`Node agent pairing failed with status ${s.status}.`);throw Object.assign(e,{statusCode:s.status,code:"NODE_AGENT_PAIRING_FAILED"}),e}if("string"!=typeof n.data?.nodeId||!n.data.nodeId.trim()||"string"!=typeof n.data?.keyId||!n.data.keyId.trim()||"string"!=typeof n.data?.secret||!n.data.secret.trim()){const e=new Error("Node agent pairing response did not include nodeId, keyId, and secret.");throw Object.assign(e,{statusCode:502,code:"NODE_AGENT_PAIRING_RESPONSE_INVALID"}),e}return{nodeId:n.data.nodeId.trim(),keyId:n.data.keyId.trim(),secret:n.data.secret.trim(),pairedAt:"string"==typeof n.data.pairedAt?n.data.pairedAt:ns()}}async syncLocalNodeConnection(){const e={[Un]:"true",[Fn]:"true"},t=process.env.TASK_HANDOFF_NODE_AGENT_TOKEN?.trim()||void 0,s=process.env.TASK_HANDOFF_NODE_AGENT_CONTROL_ENDPOINT||process.env.TASK_HANDOFF_NODE_AGENT_ENDPOINT||"http://127.0.0.1:8091",n=function(e){return Mn(e)?"local-ipc":"local-loopback"}(s),i=De.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:n,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:ns(),updatedAt:ns()}),a=await this.inspectNodeAgent(s,i),r=ns(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>Bn(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=De.parse({...o||{},id:a.nodeId,name:o?.name||"Local Node",connectionMode:n,auth:{mode:"local-static-key",secret:t},endpoint:s,controlEndpoint:s,containerEndpoint:process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL,publicWebBase:process.env.TASK_HANDOFF_INSTANCE_PUBLIC_WEB_BASE,status:"online",health:"ok",capabilities:{...o?.capabilities||{},agent:cs(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(gs)}listAllModels(){return this.models.list().map(e=>this.normalizeModelRecord(e)).sort((e,t)=>e.order-t.order||e.createdAt.localeCompare(t.createdAt)||e.id.localeCompare(t.id))}createModel(e){const t=Ss.parse(e),s=ns(),n=this.nextModelOrder(),i=Re.parse({...t,id:t.id||pt("model"),enabled:t.enabled??!0,order:t.order??n,labels:t.labels||{},createdAt:s,updatedAt:s});return gs(this.models.put(i))}updateModel(e,t){const s=As.parse(t),n=this.requireModelSecret(e),i=Re.parse({...n,...s,id:e,key:s.key?.trim()?s.key:n.key,createdAt:n.createdAt,updatedAt:ns()});return gs(this.models.put(i))}deleteModel(e){const t=this.models.delete(e);if(t)for(const t of this.listProjects())t.modelSelection.codexModelId!==e&&t.modelSelection.claudeModelId!==e||this.projects.put(Oe.parse({...t,modelSelection:{...t.modelSelection.codexModelId===e?{}:{codexModelId:t.modelSelection.codexModelId},...t.modelSelection.claudeModelId===e?{}:{claudeModelId:t.modelSelection.claudeModelId}},updatedAt:ns()}));return t}reorderModels(e){const t=[...new Set(e)],s=new Map(this.models.list().map(e=>[e.id,e]));for(const e of t)s.has(e)||os("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const n=s.get(e);this.models.put(Re.parse({...n,order:100*(t+1),updatedAt:ns()}))}),this.listModels()}createProject(e){const t=Is.parse(e),s=ns(),n=Ce.parse(t.source),i=this.validateProjectModelSelection(t.modelSelection||{}),a=Oe.parse({...t,id:t.id||pt("proj"),source:n,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",modelSelection:i,workspacePolicy:Ne.parse(t.workspacePolicy||ps(n)),labels:t.labels||{},createdAt:s,updatedAt:s});return this.projects.put(a)}updateProject(e,t){const s=fs.parse(t),n=this.requireProject(e),i=s.source?Ce.parse(s.source):n.source,a=s.modelSelection?this.validateProjectModelSelection(s.modelSelection):n.modelSelection,r=Oe.parse({...n,...s,id:e,source:i,modelSelection:a,workspacePolicy:s.workspacePolicy?Ne.parse(s.workspacePolicy):n.workspacePolicy,createdAt:n.createdAt,updatedAt:ns()});return this.projects.put(r)}deleteProject(e){return this.projects.delete(e)}listImages(){return this.images.list()}createImage(e){const t=ks.parse(e),s=ns(),n=xe.parse({...t,id:t.id||pt("img"),registry:t.registry||"local",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:s,updatedAt:s});return this.images.put(n)}async listNodeDockerImages(e){const t=this.requireNode(e);return this.nodeAgentGateway.listDockerImages(t)}async listNodeLocalFolders(e){const t=this.requireNode(e);return this.nodeAgentGateway.listLocalFolders(t)}async listNodeFolderTree(e,t={}){const s=this.requireNode(e);return this.nodeAgentGateway.listFolderTree(s,t)}async createNodeLocalFolder(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.createLocalFolder(s,t)}async deleteNodeLocalFolder(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteLocalFolder(s,t)}async checkNode(e){const t=this.requireNode(e);try{const e=await this.nodeAgentGateway.health(t),s=e&&"object"==typeof e?e:{},n="string"==typeof s.nodeId&&s.nodeId.trim()?s.nodeId.trim():t.id,i=De.parse({...t,id:n,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:cs(e)}:t.capabilities,lastSeenAt:ns(),updatedAt:ns()});return n!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:ns(),agent:e}}catch(e){const s=De.parse({...t,status:"offline",health:"failed",updatedAt:ns()});return this.nodes.put(s),{id:t.id,status:"offline",checkedAt:ns(),error:e instanceof Error?e.message:String(e)}}}async checkNodeUpdate(e,t){return this.nodeAgentGateway.checkUpdate(this.requireNode(e),t)}async applyNodeUpdate(e,t){const s=this.requireNode(e);if("controlled-instance"===t.target.component){const e=await this.requireNodeInstance(t.target.instanceId);if(e.nodeId!==s.id){const t=new Error(`Instance ${e.id} does not belong to node ${s.id}.`);throw Object.assign(t,{statusCode:400,code:"UPDATE_TARGET_NODE_MISMATCH"}),t}return this.nodeAgentGateway.applyUpdate(s,{...t,modelEnv:this.modelEnvForInstance(e)})}return this.nodeAgentGateway.applyUpdate(s,t)}async listNodeUpdateJobs(e){return this.nodeAgentGateway.listUpdateJobs(this.requireNode(e))}async getLocalNodeExternalListener(e){return this.nodeAgentGateway.getExternalListener(this.requireLocalListenerNode(e))}async updateLocalNodeExternalListener(e,t){return this.nodeAgentGateway.updateExternalListener(this.requireLocalListenerNode(e),t)}async createNodePairingInvite(e,t={}){const s=this.requireNode(e);return this.nodeAgentGateway.createPairingInvite(s,t)}requireLocalListenerNode(e){const t=this.requireNode(e);if(!Hn(t)||!Bn(t)){const e=new Error("Node agent TCP listener settings are available only for the built-in local node.");throw Object.assign(e,{statusCode:403,code:"LOCAL_NODE_LISTENER_ONLY"}),e}return t}pruneNodeJoinInvites(){const e=Date.now();for(const t of this.nodeJoinInvites.list())Date.parse(t.expiresAt)<=e&&this.nodeJoinInvites.delete(t.id)}createNodeJoinInvite(e={}){this.pruneNodeJoinInvites();const t=Ns.parse(e&&"object"==typeof e?e:{}),s=ns(),n=ut(),i={id:pt("node_join"),tokenHash:o.createHash("sha256").update(n).digest("hex"),expiresAt:new Date(Date.now()+(t.expiresInMs||6e5)).toISOString(),...t.nodeName?{nodeName:t.nodeName}:{},createdAt:s,updatedAt:s};return this.nodeJoinInvites.put(i),{id:i.id,joinToken:n,expiresAt:i.expiresAt}}completeNodeJoin(e){this.pruneNodeJoinInvites();const t=r.z.object({joinToken:r.z.string().trim().min(1).max(4096),nodeId:De.shape.id,nodeName:De.shape.name.optional(),keyId:r.z.string().trim().min(1).max(160),secret:r.z.string().trim().min(1).max(4096),pairedAt:r.z.string().datetime().optional()}).parse(e),s=o.createHash("sha256").update(t.joinToken).digest("hex"),n=this.nodeJoinInvites.list().find(e=>e.tokenHash===s&&Date.parse(e.expiresAt)>Date.now());if(!n){const e=new Error("Node join token is invalid or expired.");throw Object.assign(e,{statusCode:401,code:"NODE_JOIN_TOKEN_INVALID"}),e}this.nodeJoinInvites.delete(n.id);const i=ns();if(this.nodes.get(t.nodeId)){const e=new Error(`Node ${t.nodeId} already exists in this control-plane.`);throw Object.assign(e,{statusCode:409,code:"NODE_JOIN_NODE_ALREADY_EXISTS"}),e}const a=De.parse({id:t.nodeId,name:t.nodeName||n.nodeName||t.nodeId,connectionMode:"reverse-wss",auth:{mode:"paired-hmac",keyId:t.keyId,secret:t.secret,pairedAt:t.pairedAt||i,pairing:{status:"paired"}},status:"unknown",health:"unknown",capabilities:{},labels:{},createdAt:i,updatedAt:i});return this.nodes.put(a)}async connectNodeToControlPlane(e,t){const s=this.requireNode(e),n=Cs.parse(t);return this.nodeAgentGateway.connectRemote(s,n)}async listNodeRemoteControlPlanes(e){const t=this.requireNode(e);return this.nodeAgentGateway.listRemotes(t)}async deleteNodeRemoteControlPlane(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteRemote(s,t)}updateImage(e,t){const s=Ts.parse(t),n=this.requireImage(e),i=xe.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:ns()});return this.images.put(i)}deleteImage(e){return this.images.delete(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(ls)}async createNode(e){const t=Es.parse(e),s=ns(),n=t.connectionMode||"direct-http";let i="auth"in(e&&"object"==typeof e&&!Array.isArray(e)?e:{})?t.auth:{mode:"local-ipc"===n||"local-loopback"===n?"local-static-key":"paired-hmac"};if(("direct-http"===n||"reverse-wss"===n)&&"paired-hmac"!==i.mode){const e=new Error("Remote node connections require paired-HMAC authentication.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_REQUIRES_PAIRED_HMAC"}),e}const a=t.controlEndpoint||t.endpoint;if(!a&&"reverse-wss"!==n){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const r=t.joinToken&&a?await this.completeNodeAgentPairing(a,{joinToken:t.joinToken}):void 0;if(r&&(i={mode:"paired-hmac",keyId:r.keyId,secret:r.secret,pairedAt:r.pairedAt,pairing:{status:"paired"}}),("direct-http"===n||"reverse-wss"===n)&&!i.secret){const e=new Error("Remote node connections require a paired node secret or join token.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_SECRET_REQUIRED"}),e}if(("direct-http"===n||"reverse-wss"===n)&&!i.keyId){const e=new Error("Remote node connections require a paired key id.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_KEY_ID_REQUIRED"}),e}const o=t.id||r?.nodeId||pt("node"),d=De.parse({id:o,name:t.name,connectionMode:n,auth:i,labels:t.labels||{},createdAt:s,updatedAt:s}),c="reverse-wss"===n&&(t.id||r?.nodeId)?void 0:await this.inspectNodeAgent(a||"",d),l=t.id||r?.nodeId||c?.nodeId||o;if(!l){const e=new Error("Node id is required.");throw Object.assign(e,{statusCode:400,code:"NODE_ID_REQUIRED"}),e}if(t.id&&c?.nodeId&&c.nodeId!==t.id){const e=new Error(`Node agent id ${c.nodeId} does not match requested node id ${t.id}.`);throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ID_MISMATCH"}),e}const{joinToken:p,...u}=t,g=De.parse({...u,id:l,connectionMode:n,auth:i,endpoint:a,controlEndpoint:a,status:t.status||(c?"online":"unknown"),health:t.health||(c?"ok":"unknown"),capabilities:{...t.capabilities||{},...c?{agent:cs(c.data)}:{}},labels:t.labels||{},createdAt:s,updatedAt:s});return this.nodes.put(g)}updateNode(e,t){const s=_s.parse(t),n=this.requireNode(e),i=De.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:ns()});return this.nodes.put(i)}deleteNode(e){if(Hn(this.requireNode(e))){const e=new Error("The built-in local node connection cannot be deleted.");throw Object.assign(e,{statusCode:400,code:"LOCAL_NODE_CANNOT_BE_DELETED"}),e}return this.nodes.delete(e)}async listNodeRuntimes(e){const t=e?[this.requireNode(e)]:this.listNodes();return e?this.nodeAgentGateway.listRuntimes(t[0]):(await this.nodeAgentGateway.listFleetRuntimes(t)).items}async listNodeRuntimesWithDiagnostics(){return this.nodeAgentGateway.listFleetRuntimes(this.listNodes())}async createNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.createRuntime(s,t)}async updateNodeRuntime(e,t,s){const n=this.requireNode(e);return this.nodeAgentGateway.updateRuntime(n,t,s)}async deleteNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteRuntime(s,t)}async checkNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.checkRuntime(s,t)}async listControlledInstances(){return(await this.listNodeInstances()).map(us)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(us),nodeErrors:e.nodeErrors}}async createControlledInstance(e){const t=ys.parse(e),s=t.projectId?this.requireProject(t.projectId):void 0,n=t.runtimeId||s?.defaultRuntimeId||"runtime_local_docker",i=t.nodeId||s?.defaultNodeId||this.defaultNodeId();if(!i){const e=new Error("At least one node connection is required before creating an instance.");throw Object.assign(e,{statusCode:400,code:"NODE_REQUIRED"}),e}const a=this.requireNode(i),r=await this.requireNodeRuntimeOnNode(a.id,n),o="boolean"==typeof r.capabilities.requiresImage?r.capabilities.requiresImage:"local"!==r.type,d=t.imageId||(o?s?.defaultImageId:void 0),c=d?this.requireImage(d):void 0;if(o&&!c){const e=new Error(`Runtime ${r.name} requires an image.`);throw Object.assign(e,{statusCode:400,code:"RUNTIME_IMAGE_REQUIRED"}),e}let l=t.source||s?.source,p=t.sourceSnapshot||s||{};if(!l){const e=new Error("Instance source is required.");throw Object.assign(e,{statusCode:400,code:"INSTANCE_SOURCE_REQUIRED"}),e}if("local"===r.type&&"local-folder"!==l.type){const e=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(e,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),e}if("local-folder"===l.type){if(l.ownerNodeId&&l.ownerNodeId!==a.id){const e=new Error(`Local folder ${l.path} belongs to node ${l.ownerNodeId}, not ${a.id}.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),e}if(l.localFolderId){const e=await this.requireNodeLocalFolder(a,l.localFolderId);l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}else{const e=await this.nodeAgentGateway.createLocalFolder(a,{name:"string"==typeof p.name?p.name:"Local folder",path:l.path,defaultImageId:c?.id,modelSelection:(u=p,u.modelSelection&&"object"==typeof u.modelSelection&&!Array.isArray(u.modelSelection)?u.modelSelection:{})});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}var u;const g=await this.nodeAgentGateway.createInstance(a,{id:t.id,name:t.name,runtimeId:n,...c?{imageId:c.id,image:c}:{},projectId:s?.id,source:l,sourceSnapshot:p,config:t.config,modelEnv:s?this.modelEnvForProject(s):this.modelEnvForSourceSnapshot(l,p)});return{...us(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const s=bs.parse(t),n=await this.requireNodeInstance(e),i=this.requireNode(n.nodeId);return us(await this.nodeAgentGateway.updateInstance(i,e,s))}async deleteControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId),n=await this.nodeAgentGateway.deleteInstance(s,e);return Boolean(n.deleted)}async startControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.startInstance(s,e,{modelEnv:this.modelEnvForInstance(t)}))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.stopInstance(s,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.restartInstance(s,e,{modelEnv:this.modelEnvForInstance(t)}))}async boardAsync(){return(await this.boardWithDiagnostics()).items}async boardWithDiagnostics(){const e=await this.listNodeRuntimesWithDiagnostics(),t=await this.listNodeInstancesWithDiagnostics();return this.instanceBoardReader.read({projects:this.listProjects(),images:this.images.list(),nodes:this.nodes.list(),runtimes:e.items,instances:t.items,nodeErrors:[...e.nodeErrors,...t.nodeErrors]})}async bootstrapAiSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus)return{streamId:`bootstrap:${e.id}:ai.sessions`,revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions};try{return B.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:rs(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{streamId:`bootstrap:${e.id}:ai.sessions`,revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,streamId:t[s].streamId,aiSessions:t[s].snapshot,revision:t[s].revision,lastEventAt:t[s].lastEventAt}))}}async bootstrapAppSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=Zt();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:t.updatedAt,snapshot:t}}try{return Mt.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:rs(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const s=Zt();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:s.updatedAt,snapshot:s}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,streamId:t[s].streamId,appSessions:t[s].snapshot,revision:t[s].revision,lastEventAt:t[s].lastEventAt}))}}async recoverAiSessionDelta(e,t,s){const n=await this.requireControlledInstance(e,!0),i=new URLSearchParams({streamId:t,sinceRevision:String(s)});return Y.parse(await this.instanceRequest(n,`/ai-sessions?${i}`))}async recoverAiSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return B.parse(await this.instanceRequest(t,"/ai-sessions/state"))}async recoverAppSessionDelta(e,t,s){const n=await this.requireControlledInstance(e,!0),i=new URLSearchParams({streamId:t,sinceRevision:String(s)});return Bt.parse(await this.instanceRequest(n,`/apps/sessions?${i}`))}async recoverAppSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return Mt.parse(await this.instanceRequest(t,"/apps/sessions/state"))}async listTriggers(){return this.controlPlaneTriggerService.listTriggers()}createTrigger(e){return this.controlPlaneTriggerService.createTrigger(e)}async deleteTrigger(e){return this.controlPlaneTriggerService.deleteTrigger(e)}async applyTrigger(e,t){return this.controlPlaneTriggerService.applyTrigger(e,t)}async listInstanceTriggers(e){return this.controlPlaneTriggerService.listInstanceTriggers(e)}async bindAiSessionTrigger(e,t,s){return this.controlPlaneTriggerService.bindAiSessionTrigger(e,t,s)}async unbindAiSessionTrigger(e,t,s){return this.controlPlaneTriggerService.unbindAiSessionTrigger(e,t,s)}async runInstanceTrigger(e,t,s={}){return this.controlPlaneTriggerService.runInstanceTrigger(e,t,s)}listChatSessions(){return this.chatSessionService.list()}createAppAccessToken(e){return this.appAccessService.createToken(e)}resolveAppAccessToken(e,t){return this.appAccessService.resolveToken(e,t)}createChatActionToken(e){return this.chatActionTokenService.create(e)}resolveChatActionToken(e,t){return this.chatActionTokenService.resolve(e,t)}pendingDecisionCallbackData(e,t){return this.chatActionTokenService.pendingDecisionCallbackData(e,t)}async appAccessProxyTarget(e,t,s=""){return this.appAccessService.proxyTarget(e,t,s)}listChatBridges(){return this.chatBridgeService.list()}requireChatBridge(e){return this.chatBridgeService.require(e)}createChatBridge(e){return this.chatBridgeService.create(e)}updateChatBridge(e,t){return this.chatBridgeService.update(e,t)}deleteChatBridge(e){return this.chatBridgeService.delete(e)}requireChatSession(e){return this.chatSessionService.require(e)}upsertChatSession(e){return this.chatSessionService.upsert(e)}async handleChatGatewayMessage(e){return this.chatSessionRuntime.handleChatGatewayMessage(e)}async handleChatGatewayAction(e){return this.chatSessionRuntime.handleChatGatewayAction(e)}async handleChatCommand(e,t){return this.chatSessionRuntime.handleChatCommand(e,t)}async listPendingRoutes(){const e=ns(),t=[],s=await this.listAiSessions(),n=new Map(s.instances.map(e=>[e.instanceId,e.aiSessions]));for(const s of await this.listNodeInstances()){if("online"!==s.connectionStatus&&"online"!==s.agentStatus||!s.target.web)continue;const i=s.projectId?this.projects.get(s.projectId):void 0;for(const a of n.get(s.id)?.sessions||[]){if("waiting"!==a.status)continue;const n=et.parse({id:`${s.id}:ai:${a.id}`,instanceId:s.id,projectId:s.projectId,aiSessionId:a.id,providerSessionId:a.providerSessionId,result:a.summary||a.currentTool?.inputPreview||a.lastMessage||`${a.agent} is waiting for approval`,source:a.agent,kind:"approval",status:"pending",lastSeenAt:e});t.push({...n,project:i,instance:us(s)})}}return t}async resolveAiSessionApproval(e,t,s){const n=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/approval`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({decision:s})}))}async launchAppSession(e,t="terminal-tty",s={}){const n=await this.requireControlledInstance(e,!0);if("online"!==n.connectionStatus&&"online"!==n.agentStatus){const e=new Error(`Instance ${n.name} is still starting. Wait for it to connect before launching apps.`);throw Object.assign(e,{statusCode:409,code:"INSTANCE_NOT_CONNECTED"}),e}const i=await this.resolveAppLaunchOptions(n,s),a=await this.instanceRequest(n,"/apps/sessions",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appId:t,...i})});return await this.listAppSessions({refresh:!0}),a}async stopAppSession(e,t){const s=await this.requireControlledInstance(e,!0),n=await this.instanceRequest(s,`/apps/sessions/${encodeURIComponent(t)}/stop`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});return await this.listAppSessions({refresh:!0}),n}async renameAppSession(e,t,s){const n=await this.requireControlledInstance(e,!0),i=await this.instanceRequest(n,`/apps/sessions/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({title:s})});return await this.listAppSessions({refresh:!0}),i}async sendAiSessionMessage(e,t,s,n,i=[]){!function(e){if(e.reduce((e,t)=>e+t.size,0)<=y)return;const t=new Error("Images must be 41943040 bytes or less in total.");throw Object.assign(t,{statusCode:400,code:"AI_SESSION_ATTACHMENTS_TOO_LARGE"}),t}(i);const a=await this.requireControlledInstance(e,!0),r={message:s,...n?{mode:n}:{},...i.length?{attachments:i}:{}};return L.parse(await this.instanceRequest(a,`/ai-sessions/${encodeURIComponent(t)}/messages`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(r)}))}async aiSessionQueue(e,t){const s=await this.requireControlledInstance(e,!0);return P.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}/steer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async retryAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}/retry`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async removeAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}`,{method:"DELETE"}))}async interruptAiSession(e,t){const s=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/interrupt`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async proxyInstanceHttp(e,t,s={}){const n=await this.requireControlledInstance(e,!0);return this.controlledInstanceGateway.proxyHttp(n,t,s)}async proxyInstanceWebSocket(e,t,s,n,i={}){const a=await this.requireControlledInstance(e,!0);this.controlledInstanceGateway.proxyWebSocket(a,this.nodeAgentTransportFor(this.requireNode(a.nodeId)),t,s,n,i)}listConfigSyncPresets(){return[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}]}async syncInstanceConfig(e,t,s){const n=await this.requireControlledInstance(e,!0);if("export"===t&&"local-folder"!==n.source.type){const e=new Error("Exporting instance config is only available for local folder projects.");throw Object.assign(e,{statusCode:400,code:"CONFIG_SYNC_EXPORT_REQUIRES_LOCAL_PROJECT"}),e}const i=[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}].find(e=>e.id===s);if(!i){const e=new Error(`Config sync preset ${s} was not found.`);throw Object.assign(e,{statusCode:404,code:"CONFIG_SYNC_PRESET_NOT_FOUND"}),e}return this.instanceRequest(n,`/config-sync/${encodeURIComponent(t)}/${encodeURIComponent(s)}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({preset:i})})}async instanceRequest(e,t,s={}){return this.controlledInstanceGateway.request(e,t,s)}async reportInstanceHeartbeat(e,t){await this.controlledInstanceGateway.reportHeartbeat(e,t,this.nodeAgentTransport)}async nodeAgentFetch(e,t,s={}){return this.nodeAgentTransportFor(e).request(e,t,s)}nodeAgentTransportFor(e){if("reverse-wss"===e.connectionMode){if(!this.nodeAgentTransport){const e=new Error("Reverse node agent transport is not available.");throw Object.assign(e,{statusCode:503,code:"NODE_AGENT_REVERSE_TRANSPORT_UNAVAILABLE"}),e}return this.nodeAgentTransport}return this.directNodeAgentTransport()}directNodeAgentTransport(){return{request:async(e,t,s={})=>{const n=e.controlEndpoint||e.endpoint;if(!n){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const i=s.method||"GET",a="string"==typeof s.body||s.body instanceof Buffer?s.body:void 0===s.body||null===s.body?void 0:String(s.body),r=Wn(e,{method:i,pathWithQuery:`/api/node-agent${t}`,body:a||""});return this.fetchNodeAgentEndpoint(n,t,{...s,body:a,headers:{...s.headers||{},...r}})},proxyWebSocket:(e,t,s,n,i={})=>{const a=e.controlEndpoint||e.endpoint;if(!a)throw Object.assign(new Error("Node agent direct HTTP mode requires an endpoint."),{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"});const r=Mn(a),o=`/api/node-agent${s}`,d=Wn(e,{method:"GET",pathWithQuery:o});if(r){$n(r);const e=qn(r,s,n,{...i,...d});return e.on("open",()=>{t.on("message",(t,s)=>e.readyState===p.WebSocket.OPEN&&e.send(s?t:String(t))),t.on("close",()=>e.close()),t.on("error",()=>e.close())}),e.on("message",(e,s)=>{t.readyState===p.WebSocket.OPEN&&t.send(s?e:e.toString())}),e.on("close",()=>t.close()),void e.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}const c=new URL(o,a.replace(/\/$/,""));c.protocol="https:"===c.protocol?"wss:":"ws:";const l=n?new p.WebSocket(c.toString(),n,{headers:{...i,...d}}):new p.WebSocket(c.toString(),{headers:{...i,...d}});l.on("open",()=>{t.on("message",(e,t)=>l.readyState===p.WebSocket.OPEN&&l.send(t?e:String(e))),t.on("close",()=>l.close()),t.on("error",()=>l.close())}),l.on("message",(e,s)=>{t.readyState===p.WebSocket.OPEN&&t.send(s?e:e.toString())}),l.on("close",()=>t.close()),l.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}}}async listNodeInstances(){return(await this.listNodeInstancesWithDiagnostics()).items}async listNodeInstancesWithDiagnostics(){const e=await this.nodeAgentGateway.listFleetInstances(this.listNodes());return{items:await Promise.all(e.items.map(e=>this.withFreshTriggerSnapshot(e))),nodeErrors:e.nodeErrors}}async withFreshTriggerSnapshot(e){if("online"!==e.connectionStatus&&"online"!==e.agentStatus)return e;try{const t=le.parse(await this.instanceRequest(e,"/triggers"));return Ke.parse({...e,triggers:On(t)})}catch{return e}}async requireNodeInstance(e){for(const t of this.listNodes())try{const s=(await this.nodeAgentGateway.listInstances(t)).find(t=>t.id===e);if(s)return s}catch{}os("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`)}async requireNodeRuntimeOnNode(e,t){const s=(await this.listNodeRuntimes(e)).find(e=>e.id===t);return s||os("NODE_RUNTIME_NOT_FOUND",`Runtime ${t} was not found on node ${e}.`),s}async requireNodeLocalFolder(e,t){return this.nodeAgentGateway.requireLocalFolder(e,t)}async resolveAppLaunchOptions(e,t){const s="string"==typeof t.cwdFolderId?t.cwdFolderId.trim():"",n={...t};if(delete n.cwdFolderId,!s)return n;const i=this.requireNode(e.nodeId),a=await this.requireNodeLocalFolder(i,s);if(a.nodeId!==e.nodeId){const t=new Error(`Local folder ${a.id} belongs to node ${a.nodeId}, not ${e.nodeId}.`);throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_INSTANCE_NODE"}),t}const r=await this.requireNodeRuntimeOnNode(e.nodeId,e.runtimeId);return{...n,cwd:this.appLaunchCwdForFolder(e,a,r)}}appLaunchCwdForFolder(e,t,n){if("local"===n.type)return s.resolve(t.path);if("local-folder"!==e.source.type){const e=new Error("App cwd folder selection is only supported for local-folder instances.");throw Object.assign(e,{statusCode:400,code:"APP_CWD_REQUIRES_LOCAL_FOLDER_SOURCE"}),e}const i=s.resolve(e.source.path),a=s.resolve(t.path),r=s.relative(i,a);if(r.startsWith("..")||s.isAbsolute(r)){const s=new Error(`Local folder ${t.path} is outside the instance workspace ${e.source.path}.`);throw Object.assign(s,{statusCode:400,code:"APP_CWD_OUTSIDE_WORKSPACE"}),s}const o=e.runtime.workspacePath||e.workspace.path||ps(e.source).path||"/workspace";return r?s.posix.join(o,...r.split(s.sep)):o}modelEnvForInstance(e){return function(e,t){const s=Nn(e.sourceSnapshot);return Cn(Oe.parse({id:e.projectId||`project_${e.id}`,name:"string"==typeof e.sourceSnapshot.name?e.sourceSnapshot.name:e.name,source:e.source,defaultImageId:e.imageId,defaultNodeId:e.nodeId,defaultRuntimeId:e.runtimeId,modelSelection:s,workspacePolicy:ps(e.source),labels:{},createdAt:e.createdAt,updatedAt:e.updatedAt}),t)}(e,this.listAllModels())}modelEnvForSourceSnapshot(e,t){return function(e,t,s){const n=Nn(t),i=(new Date).toISOString();return Cn(Oe.parse({id:"string"==typeof t.id?t.id:`source_${pt("tmp")}`,name:"string"==typeof t.name?t.name:"Source",source:e,defaultImageId:"string"==typeof t.defaultImageId?t.defaultImageId:void 0,modelSelection:n,workspacePolicy:ps(e),labels:{},createdAt:i,updatedAt:i}),s)}(e,t,this.listAllModels())}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}validateProjectModelSelection(e){const t=new Map(this.listAllModels().map(e=>[e.id,e])),s={},n=e.codexModelId?.trim(),i=e.claudeModelId?.trim();if(n){const e=t.get(n);if(e||os("MODEL_NOT_FOUND",`Model ${n} was not found.`),"codex"!==e.app){const e=new Error(`Model ${n} is not a Codex model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}s.codexModelId=n}if(i){const e=t.get(i);if(e||os("MODEL_NOT_FOUND",`Model ${i} was not found.`),"claude"!==e.app){const e=new Error(`Model ${i} is not a Claude model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}s.claudeModelId=i}return s}modelEnvForProject(e){return Cn(e,this.listAllModels())}requireProject(e){const t=this.projects.get(e);return t||os("PROJECT_NOT_FOUND",`Project ${e} was not found.`),this.normalizeProjectRecord(t)}normalizeProjectRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};return"defaultRuntimeId"in t||(t.defaultRuntimeId="runtime_local_docker"),Oe.parse(t)}return Oe.parse(e)}(e);return t!==e&&this.projects.put(t),t}requireModel(e,t=!1){const s=this.models.get(e);s||os("MODEL_NOT_FOUND",`Model ${e} was not found.`);const n=this.normalizeModelRecord(s);return t?n:gs(n)}requireModelSecret(e){const t=this.models.get(e);return t||os("MODEL_NOT_FOUND",`Model ${e} was not found.`),this.normalizeModelRecord(t)}normalizeModelRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};if(!("app"in t)){const e=Array.isArray(t.apps)?t.apps:[];t.app=e.includes("claude")&&!e.includes("codex")?"claude":"codex"}return delete t.apps,Re.parse(t)}return Re.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){const t=this.images.get(e);return t||os("IMAGE_NOT_FOUND",`Image ${e} was not found.`),t}requireNode(e){const t=this.nodes.get(e);return t||os("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return ls(this.requireNode(e))}async requireControlledInstance(e,t=!1){const s=await this.requireNodeInstance(e);return t?s:us(s)}}function Kn(e){return m(String(e),"escape").replace(/\n+$/,"")}function Jn(e){const[t,...s]=String(e).split("\n");return[`*${Kn(t)}*`,...s.map(Kn)].join("\n")}function Qn(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Yn(e,t,s,n,i={}){if(t.token)return Zn(s=>e(`https://api.telegram.org/bot${t.token}/sendMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),"sendMessage",n,{chat_id:s,...i.replyToMessageId?{reply_to_message_id:i.replyToMessageId,allow_sending_without_reply:!0}:{},...i.replyMarkup?{reply_markup:i.replyMarkup}:{}},i)}async function Xn(e,t,s){if(!t.token||!s)throw new Error("Telegram file id is required.");const n=await e(`https://api.telegram.org/bot${t.token}/getFile`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({file_id:s})}),i=await n.json().catch(()=>({}));if(!n.ok||!1===i.ok||!i.result?.file_path)throw new Error(i.description||`Telegram getFile failed with HTTP ${n.status}`);return`https://api.telegram.org/file/bot${t.token}/${i.result.file_path}`}async function Zn(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):Kn(s),o=await e({...n,text:r,disable_web_page_preview:!0,parse_mode:a}),d=await o.json().catch(()=>({}));if(o.ok&&!1!==d.ok)return d.result;if("editMessageText"===t&&ei(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:Qn(s),disable_web_page_preview:!0,parse_mode:"Markdown"}),a=await i.json().catch(()=>({}));if(i.ok&&!1!==a.ok)return a.result;if("editMessageText"===t&&ei(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?Qn(s):String(s),disable_web_page_preview:!0}),l=await c.json().catch(()=>({}));if(c.ok&&!1!==l.ok)return l.result;if("editMessageText"!==t||!ei(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function ei(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function ti(e){return e instanceof Error?e.message:String(e)}function si(e){const t=Number(function(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}(e).message_id);return Number.isInteger(t)&&t>0?t:void 0}class ni{entries;updateIntervalMs;send;edit;deleteMessage;messageIdFromResult;retryAfterMs;isMessageTooLong;onLog;constructor(e){this.entries=e.entries||new Map,this.updateIntervalMs=e.updateIntervalMs||1e3,this.send=e.send,this.edit=e.edit,this.deleteMessage=e.delete,this.messageIdFromResult=e.messageIdFromResult||si,this.retryAfterMs=e.retryAfterMs,this.isMessageTooLong=e.isMessageTooLong,this.onLog=e.onLog}remember(e,t,s,n,i,a){const r=this.entries.get(e);return r&&(clearTimeout(r.timer),r.timer=void 0,r.pendingText=void 0),this.entries.set(e,{lastText:s,lastUpdateAt:Date.now(),messageId:t,route:n,options:i,renderOptions:a}),r}rekey(e,t){if(e===t)return this.entries.has(e);const s=this.entries.get(e);if(!s||this.entries.has(t))return!1;if(clearTimeout(s.timer),s.timer=void 0,this.entries.delete(e),this.entries.set(t,s),s.pendingText){const e=s.rateLimitedUntil?Math.max(0,s.rateLimitedUntil-Date.now()):Math.max(0,this.updateIntervalMs-(Date.now()-s.lastUpdateAt));this.scheduleFlush(t,e)}return!0}scheduleFlush(e,t){const s=this.entries.get(e);s&&!s.timer&&(s.timer=setTimeout(()=>{s.timer=void 0;const t=s.pendingText;s.pendingText=void 0,t&&this.applyUpdate(e,t,s.route,s.options)},t))}markRateLimited(e,t,s,n){const i=this.entries.get(e);i&&(i.rateLimitedUntil=Date.now()+s,i.pendingText=t,i.route=n||i.route,this.scheduleFlush(e,s))}async applyUpdate(e,t,s,n){let i=this.entries.get(e);const a=i,r=s||i?.route,o=n||i?.options;try{if(i&&(i.route=r,i.options=o),i?.pending&&await i.pending,i=this.entries.get(e),a&&i!==a)return!1;if(i?.messageId){if(i.lastText===t)return!0;const s=Date.now()-(i.lastUpdateAt||0);return s<this.updateIntervalMs?(i.pendingText=t,i.route=r||i.route,i.options=o||i.options,this.scheduleFlush(e,this.updateIntervalMs-s),!0):(await this.edit(i.messageId,t,r||i.route,o),i.lastText=t,i.lastUpdateAt=Date.now(),i.rateLimitedUntil=void 0,!0)}const s={lastText:t,lastUpdateAt:Date.now(),route:r,options:o};return s.pending=this.send(t,r,o).then(e=>{const t=this.messageIdFromResult(e);t&&(s.messageId=t)}).finally(()=>{s.pending=void 0}),this.entries.set(e,s),await s.pending,this.entries.get(e)===s&&(s.rateLimitedUntil=void 0,Boolean(s.messageId))}catch(s){const n=this.retryAfterMs?.(s);return n&&this.markRateLimited(e,t,n,r),this.onLog?.(`Telegram progress update skipped: ${ti(s)}`),!1}}update(e,t,s,n){const i=String(t||"").trim();if(!i)return;const a=this.entries.get(e);if(a&&(a.route=s||a.route,a.options=n||a.options),a?.lastText===i&&!a.pending&&!a.pendingText)return;const r=Date.now();if(a?.rateLimitedUntil){if(a.rateLimitedUntil>r)return a.pendingText=i,void this.scheduleFlush(e,a.rateLimitedUntil-r);a.rateLimitedUntil=void 0}if(a?.pending&&!a.messageId)return a.pendingText=i,void this.scheduleFlush(e,this.updateIntervalMs);if(!a||!a.messageId)return void this.applyUpdate(e,i,s,n);const o=Date.now()-(a.lastUpdateAt||0);if(o>=this.updateIntervalMs)return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,void this.applyUpdate(e,i,s||a.route,n||a.options);a.pendingText=i,this.scheduleFlush(e,this.updateIntervalMs-o)}async finish(e,t,s,n,i){const a=this.entries.get(e);if(!a)return!1;try{return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,a.pending&&await a.pending,this.entries.delete(e),!!a.messageId&&(a.lastText===t||await this.edit(a.messageId,t,s||a.route,n||a.options,i||a.renderOptions),!0)}catch(t){return this.entries.delete(e),this.isMessageTooLong?.(t)?this.onLog?.("Telegram progress finish too long; sending result as multiple messages"):this.onLog?.(`Telegram progress finish skipped: ${ti(t)}`),!1}}finishPayload(e,t,s,n,i){return this.finish(e,`${t.title}\n\n${t.body}`,s,n,i)}async delete(e,t){const s=this.entries.get(e);if(!s)return!1;try{return clearTimeout(s.timer),s.timer=void 0,s.pendingText=void 0,s.pending&&await s.pending,this.entries.delete(e),!(!this.deleteMessage||!s.messageId)&&(await this.deleteMessage(s.messageId,t||s.route),!0)}catch(t){return this.entries.delete(e),this.onLog?.(`Telegram progress delete skipped: ${ti(t)}`),!1}}async flush(e){const t=this.entries.get(e);if(t?.pending&&await t.pending,t?.timer){clearTimeout(t.timer),t.timer=void 0;const s=t.pendingText;t.pendingText=void 0,s&&await this.applyUpdate(e,s,t.route,t.options)}}async wait(e){await function(e){return new Promise(t=>setTimeout(t,e))}(e)}}const ii=20971520;function ai(e){const t=[],s=[...e.photo||[]].filter(e=>e.file_id).sort((e,t)=>Number(t.file_size||t.width||0)-Number(e.file_size||e.width||0))[0];return s?.file_id&&t.push({fileId:s.file_id,fileName:"telegram-photo.jpg",fileSize:s.file_size}),e.document?.file_id&&String(e.document.mime_type||"").startsWith("image/")&&t.push({fileId:e.document.file_id,fileName:e.document.file_name,fileSize:e.document.file_size}),t}async function ri(e,t,n="telegram-image",i){if(i&&i>ii)throw new Error(`image is too large: ${i} bytes`);const a=await e.getFileLink(t),r=String(a),o=await(e.fetchFile||fetch)(r);if(!o.ok)throw new Error(`download failed with HTTP ${o.status}`);const d=Number(o.headers.get("content-length")||0);if(d>ii)throw new Error(`image is too large: ${d} bytes`);const l=s.extname(n)||".jpg";let p=l;try{p=s.extname(new URL(r).pathname)||l}catch{p=l}const u=s.join(c.tmpdir(),"task-handoff-images");await I.mkdir(u,{recursive:!0});const g=p.replace(/[^.\w-]/g,"")||".jpg",h=s.join(u,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${g}`),m=Buffer.from(await o.arrayBuffer());if(m.length>ii)throw new Error(`image is too large: ${m.length} bytes`);return await I.writeFile(h,m),h}class oi{store;constructor(e){this.store=new ni({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",Jn(t),{rawMarkdownV2:!0,replyMarkup:qi(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,Jn(s),{rawMarkdownV2:!0,replyMarkup:qi(i)}),delete:(t,s)=>e.deleteMessage(e.requireBridge(s?.bridgeId||""),s?.chatId||"",t),onLog:e.onLog})}get entries(){return this.store.entries}remember(e,t,s,n){return this.store.remember(e,t,s,n)}rekey(e,t){return this.store.rekey(e,t)}delete(e){return this.store.delete(e)}async applyUpdate(e){return this.store.applyUpdate(e.key,e.text,{bridgeId:e.bridge.id,chatId:e.chatId},{actionRows:e.actionRows})}}function di(e,t){if("string"==typeof e&&e.trim())return e.trim();li(`Chat action token is missing ${t}.`)}function ci(e){if("allow"===e||"deny"===e||"skip"===e)return e;li("Chat action token has an invalid decision.")}function li(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class pi{entries=new Map;fetchImpl;constructor(e){this.fetchImpl=e}clear(){this.clearEntries(()=>!0)}clearBridge(e){this.clearEntries(t=>t.bridgeId===e)}clearEntries(e){for(const[t,s]of this.entries)e(s)&&(s.timer&&clearTimeout(s.timer),s.cancelPending?.(new Error("DingDing progress update was cancelled.")),this.entries.delete(t))}async applyUpdate(e){const t=e.dingdingRuntime;if(!t)return!1;const s=$i(e.replyMarkup);let n=this.entries.get(e.key);if(n?.pending&&await n.pending,n=this.entries.get(e.key),n?.outTrackId){if(n.lastText===e.text&&n.lastActionsFingerprint===s)return!0;const t=Date.now()-(n.lastUpdateAt||0);return t<1e3?(n.pendingText=e.text,n.pendingReplyMarkup=e.replyMarkup,n.timer||(n.pending=new Promise((s,i)=>{n.cancelPending=i,n.timer=setTimeout(()=>{n.timer=void 0,n.cancelPending=void 0;const t=n.pendingText,a=n.pendingReplyMarkup;n.pendingText=void 0,n.pendingReplyMarkup=void 0,t?this.updateExistingEntry(e,n,t,a,$i(a)).then(s,i):s(void 0)},1e3-t)}).finally(()=>{n.pending=void 0,n.timer=void 0,n.cancelPending=void 0})),await n.pending,this.entries.has(e.key)):(await this.updateExistingEntry(e,n,e.text,e.replyMarkup,s),!0)}const i={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:s,lastUpdateAt:Date.now()};return i.pending=Ci({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||Pi(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||Pi(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(e=>{e?.outTrackId&&(i.outTrackId=e.outTrackId),e?.delivered&&(i.delivered=!0,i.lastUpdateAt=Date.now())}).catch(t=>{throw this.entries.delete(e.key),t}).finally(()=>{i.pending=void 0}),this.entries.set(e.key,i),await i.pending,Boolean(i.delivered)}async updateExistingEntry(e,t,s,n,i){const a=e.dingdingRuntime;a&&t.outTrackId&&(await async function(e){await Ni(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:Ii({type:"actions",title:e.title,description:e.text,list:Xs(e.replyMarkup),biz_step:e.step,error_msg:""})},cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},userIdType:Number(e.bridge.settings.cardUserIdType||1)})}({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:a,outTrackId:t.outTrackId,text:s,replyMarkup:n,title:"TaskHandoff 执行中",step:"progress"}),t.lastText=s,t.lastActionsFingerprint=i,t.lastUpdateAt=Date.now())}}function ui(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=await Yn(t,s,e,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Di(a).message_id)||void 0}}}:"dingding"===s.channel?{bridge:s,send:async(e,i,a={})=>{const r=a.sessionWebhook||n?.chatWebhooks.get(e)||Pi(s.settings.sessionWebhook);if(a.replyMarkup&&n){const o=await Ci({fetchImpl:t,bridge:s,runtime:n,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||n.senderIds.get(e)||Pi(s.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await Ti(t,r,i,n?.onLog),{provider:"dingding"}}}:"wechat"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=i.contextToken||Pi(s.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=Pi(t.settings.baseUrl)||"https://ilinkai.weixin.qq.com",r=await e(`${a}/ilink/bot/sendmessage`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${t.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({msg:{from_user_id:"",to_user_id:s,client_id:o.randomUUID(),message_type:2,message_state:2,context_token:n,item_list:[{type:1,text_item:{text:i}}]},base_info:{channel_version:"1.0.2"}})}),d=await r.json().catch(()=>({}));if(!r.ok||d.errcode)throw new Error(Pi(d.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(t,s,e,a,n),{provider:"wechat"}}}:{bridge:s,send:async()=>{}}}async function gi(e){return(await async function(e,t,s,n){const i=[];for(const a of ai(s))try{const s=await ri({getFileLink:s=>Xn(e,t,s),fetchFile:e},a.fileId,a.fileName,a.fileSize);i.push(s)}catch(e){n?.({fileId:a.fileId,fileName:a.fileName,error:e})}return i}(e.fetchImpl,e.bridge,e.message,e.onImageDownloadError)).map((e,n)=>{const i=t.readFileSync(e);return{id:`tg_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:s.basename(e)||`telegram-image-${n+1}`,mime:hi(e),size:i.length,data:i.toString("base64")}})}function hi(e){const t=s.extname(e).toLowerCase();return".png"===t?"image/png":".webp"===t?"image/webp":".gif"===t?"image/gif":".bmp"===t?"image/bmp":"image/jpeg"}function mi(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function Ii(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function fi(e){return`dtv1.card//IM_GROUP.${e}`}function yi(e){return`dtv1.card//IM_ROBOT.${e}`}function bi(e,t){return Pi(t.biz_conversation_id).trim()||Pi(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function vi(e,t={}){const s=Pi(e).trim().toUpperCase();return"2"===s||"IM_GROUP"===s?"IM_GROUP":"1"===s||"IM_SINGLE"===s?t.robotMessage?"IM_ROBOT":"IM_SINGLE":"IM_ROBOT"===s?"IM_ROBOT":void 0}function wi(e){if(e.startsWith("task_handoff:"))return e;if(!e.startsWith("th_cb_"))return"";try{return Buffer.from(e.slice(6),"base64url").toString("utf8")}catch{return""}}function Si(e,t,s,n,i){const a=bi(n,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:Ii({type:"actions",title:"TaskHandoff",description:e,list:Xs(t),biz_out_track_id:n.outTrackId||i.biz_out_track_id||"",biz_conversation_id:a,biz_sender_id:i.biz_sender_id||n.userId||"",biz_session_webhook:i.biz_session_webhook||"",biz_conversation_type:i.biz_conversation_type||"",biz_step:s,error_msg:""})}}}function Ai(e,t,s){const n=[];return ki(n,s),ki(n,t),ki(n,e),n.find(e=>wi(e))||""}function ki(e,t){for(const s of["actionIdList","actionIds"]){const n=t[s];Array.isArray(n)&&e.push(...n.map(e=>Pi(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=Pi(t[s]);n&&e.push(n)}}async function Ti(e,t,s,n){n?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:xi(s)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:mi(s),text:s}})}),a=await i.json().catch(()=>{});if(n?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:Ri(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function Ei(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Di(e);if(1!==t.type)continue;const s=Pi(Di(t.text_item).text).trim();if(s)return s}return""}function _i(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function Ci(e){const t=Xs(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await Ti(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const s=e.bridge.settings||{},n=Pi(s.robotCode),i=Pi(s.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=Pi(s.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||Pi(s.senderId),o=(d=e.runtime,c=e.chatId,d.conversationTypes?.get(c)||"IM_GROUP");var d,c;if(!n||!i||!r){if(e.sessionWebhook&&!e.forceCard)return await Ti(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),{delivered:!0};throw new Error("DingDing card target is incomplete.")}const l=`task_handoff_cp_${Date.now()}_${Math.random().toString(16).slice(2)}`,p=Ii({type:"actions",title:e.title||mi(e.text),description:e.text,list:t,biz_out_track_id:l,biz_step:e.step||"task_handoff_actions",biz_conversation_id:e.chatId,biz_sender_id:r,biz_session_webhook:e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)||"",biz_conversation_type:o,error_msg:""}),u=function(e,t,s,n){return"IM_SINGLE"===e?{openSpaceId:(i=t,`dtv1.card//IM_SINGLE.${i}`),imSingleOpenSpaceModel:{},imSingleOpenDeliverModel:{extension:{}}}:"IM_ROBOT"===e?{openSpaceId:yi(s),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:n,spaceType:"IM_ROBOT"}}:{openSpaceId:fi(t),imGroupOpenSpaceModel:{supportForward:!1},imGroupOpenDeliverModel:{extension:{},robotCode:n}};var i}(o,e.chatId,r,n);return e.runtime.onLog?.("info",{target:"card",chatId:e.chatId,senderId:r,conversationType:o,openSpaceId:u.openSpaceId,hasSessionWebhook:Boolean(e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)),hasReplyMarkup:Boolean(e.replyMarkup),actionCount:t.length,textPreview:xi(e.text)},"dingding card send requested"),await Ni(e.fetchImpl,e.bridge,e.runtime,"POST","/v1.0/card/instances/createAndDeliver",{userId:r,cardTemplateId:i,outTrackId:l,callbackType:"STREAM",callbackRouteKey:a,cardData:{cardParamMap:p},userIdType:Number(s.cardUserIdType||1),...u}),{delivered:!0,outTrackId:l}}async function Ni(e,t,s,n,i,a){const r=await async function(e,t,s){if(s.accessToken&&s.accessToken.expiresAt>Date.now()+6e4)return s.accessToken.value;const n=Pi(t.settings.clientSecret);if(!t.token||!n)throw new Error("DingDing client id/secret is not configured.");const i=await e("https://api.dingtalk.com/v1.0/oauth2/accessToken",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appKey:t.token,appSecret:n})}),a=await i.json().catch(()=>({}));if(!i.ok||!a.accessToken)throw new Error(`DingDing access token failed with HTTP ${i.status}`);const r=Number(a.expireIn||7200);return s.accessToken={value:a.accessToken,expiresAt:Date.now()+1e3*Math.max(60,r-120)},s.accessToken.value}(e,t,s);s.onLog?.("info",{method:n,path:i,body:zi(a)},"dingding api request");const o=await e(`https://api.dingtalk.com${i}`,{method:n,headers:{"content-type":"application/json","x-acs-dingtalk-access-token":r},body:JSON.stringify(a)}),d=await o.json().catch(()=>{});if(s.onLog?.(o.ok?"info":"warn",{method:n,path:i,status:o.status,response:Ri(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=Di(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error(Pi(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=Di(Di(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===Di(e).success);if(!s)return"";const n=Di(s),i=Pi(n.spaceId),a=Pi(n.errorMsg||n.message||n.errmsg)||"delivery failed";return i?`DingDing card delivery failed for ${i}: ${a}`:`DingDing card delivery failed: ${a}`}(d);if(u)throw new Error(u);return o}function zi(e){const t=Di(e),s=Di(t.cardData),n=Di(s.cardParamMap);return{openSpaceId:Pi(t.openSpaceId),outTrackId:Pi(t.outTrackId),userId:Pi(t.userId),userIdType:t.userIdType,callbackType:Pi(t.callbackType),callbackRouteKey:Pi(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:Oi(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:Oi(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:Oi(t.imRobotOpenDeliverModel),bizConversationId:Pi(n.biz_conversation_id),bizConversationType:Pi(n.biz_conversation_type),bizSenderId:Pi(n.biz_sender_id),hasBizSessionWebhook:Boolean(Pi(n.biz_session_webhook))}}function Oi(e){const t=Di(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean(Pi(t.robotCode)),spaceType:Pi(t.spaceType),recipients:Array.isArray(t.recipients)?t.recipients.length:void 0,atUserIds:t.atUserIds&&"object"==typeof t.atUserIds?Object.keys(t.atUserIds).length:void 0}}function Ri(e){const t=Di(e),s=Di(t.result);return{code:t.code??t.errcode,message:xi(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(s).length?s:void 0}}function xi(e,t=160){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function Di(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function ji(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?Mi(e):{}}function Pi(e){return"string"==typeof e?e:""}function Mi(e){try{return Di(JSON.parse(String(e||"{}")))}catch{return{}}}function $i(e){return JSON.stringify(Xs(e))}function qi(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Li(e){return Buffer.from(e,"base64url").toString("utf8")}function Ui(e){return new h.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class Fi{service;fetchImpl;createDingdingClient;logger;bridgeTimers=new Map;dingdingBridges=new Map;bridgeErrors=new Map;telegramOffsets=new Map;seenTelegramUpdates=new Set;wechatCursors=new Map;pendingTimer;announcedPending=new Set;deliveredAiSessionFingerprints=new Map;aiSessionCancelTokens=new Map;aiSessionQueueActionTokens=new Map;telegramMessageAggregates=new Map;telegramMessageAggregateEndTokens=new Map;telegramProgressMessageTargets=new Map;telegramProgress;dingdingProgress;stopAiSessionListener;constructor(e,t=fetch,s={}){var n;this.service=e,this.fetchImpl=t,this.logger=(n=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(n||"").trim().toLowerCase())?s.logger:void 0),this.telegramProgress=new oi({updateIntervalMs:s.telegramProgressUpdateIntervalMs,requireBridge:e=>this.service.requireChatBridge(e),send:(e,t,s,n={})=>this.sendTelegramMessage(e,t,s,n),edit:(e,t,s,n,i={})=>this.editTelegramMessage(e,t,s,n,i),deleteMessage:(e,t,s)=>this.deleteTelegramMessage(e,t,s),onLog:e=>this.logWarn({message:e},"telegram progress store warning")}),this.dingdingProgress=new pi(this.fetchImpl),this.stopAiSessionListener=s.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:Xi(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=s.createDingdingClient||Ui}logInfo(e,t){this.logger?.info?.({component:"control-plane-chat-gateway",...e},t)}logWarn(e,t){this.logger?.warn?.({component:"control-plane-chat-gateway",...e},t)}updateChatBridge(e,t){this.service.updateChatBridge?.(e,t)}startEnabled(){this.logAiSessionDelivery({stage:"runtime-start-enabled",bridgeCount:this.service.listChatBridges().length,enabledBridgeCount:this.service.listChatBridges().filter(e=>e.enabled).length});for(const e of this.service.listChatBridges())e.enabled&&this.startBridge(e.id);this.startPendingPolling()}startBridge(e){if(this.bridgeTimers.has(e)||this.dingdingBridges.has(e))return this.logAiSessionDelivery({stage:"bridge-start-skipped-running",bridgeId:e}),this.startPendingPolling(),this.status();const t=this.service.requireChatBridge(e);return this.logAiSessionDelivery({stage:"bridge-start",bridgeId:t.id,channel:t.channel,enabled:t.enabled,hasToken:Boolean(t.token)}),"wechat"===t.channel?this.startPollingBridge(t,e=>this.pollWechat(e)):"dingding"===t.channel?this.startDingdingBridge(t):"telegram"!==t.channel?(this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status()):t.token?this.startPollingBridge(t,e=>this.pollTelegram(e)):(this.bridgeErrors.set(e,"Telegram token is not configured."),this.status())}stopBridge(e){const t=this.bridgeTimers.get(e);t&&(clearInterval(t),this.bridgeTimers.delete(e));const s=this.dingdingBridges.get(e);if(s){try{s.client.disconnect()}catch{}this.dingdingBridges.delete(e)}return this.dingdingProgress.clearBridge(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.keys()]))this.stopBridge(e);this.pendingTimer&&(clearInterval(this.pendingTimer),this.pendingTimer=void 0),this.dingdingProgress.clear();for(const e of this.telegramMessageAggregates.values())e.timer&&clearTimeout(e.timer);this.telegramMessageAggregates.clear(),this.telegramMessageAggregateEndTokens.clear(),this.stopAiSessionListener?.(),this.stopAiSessionListener=void 0}async pollBridgeNow(e){const t=this.service.requireChatBridge(e);return"telegram"===t.channel?(await this.pollTelegram(t),this.status()):"wechat"===t.channel?(await this.pollWechat(t),this.status()):("dingding"===t.channel||this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status())}async pollAiSessionsNow(){return await this.pollAiSessionUpdates(),this.status()}status(){const e=this.service.listChatBridges().map(e=>({id:e.id,channel:e.channel,name:e.name,running:this.bridgeTimers.has(e.id)||this.dingdingBridges.has(e.id),tokenSet:Boolean(e.tokenSet),defaultChatId:e.defaultChatId,lastUpdateId:this.telegramOffsets.get(e.id),error:this.bridgeErrors.get(e.id)}));return{running:e.some(e=>e.running),bridges:e}}startPendingPolling(){this.pendingTimer?this.logAiSessionDelivery({stage:"pending-poll-already-started"}):(this.logAiSessionDelivery({stage:"pending-poll-started",intervalMs:5e3}),this.pendingTimer=setInterval(()=>{this.pollPendingRoutes()},5e3))}startPollingBridge(e,t){if(!e.token)return this.bridgeErrors.set(e.id,`${e.channel} token is not configured.`),this.status();this.bridgeErrors.delete(e.id);const s=setInterval(()=>{const s=this.service.requireChatBridge(e.id);s.enabled?t(s).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))):this.stopBridge(e.id)},e.pollIntervalMs);if(this.bridgeTimers.set(e.id,s),this.logAiSessionDelivery({stage:"bridge-poll-started",bridgeId:e.id,channel:e.channel,intervalMs:e.pollIntervalMs}),e.enabled){const s=this.service.requireChatBridge(e.id);t(s).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}return this.startPendingPolling(),this.status()}startDingdingBridge(e){const t=Yi((e.settings||{}).clientSecret);if(!e.token||!t)return this.bridgeErrors.set(e.id,"DingDing client id/secret is not configured."),this.status();const s={client:this.createDingdingClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,s,n)=>{"warn"!==t?this.logInfo({bridgeId:e.id,...s},n):this.logWarn({bridgeId:e.id,...s},n)}};return s.client.registerCallbackListener(h.TOPIC_ROBOT,t=>{this.handleDingdingRobotMessage(e,s,t).catch(t=>{this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))})}),s.client.registerCallbackListener(h.TOPIC_CARD,t=>{this.handleDingdingCardCallback(e,s,t).then(e=>s.client.socketCallBackResponse(t.headers.messageId,e||{})).catch(n=>{this.bridgeErrors.set(e.id,n instanceof Error?n.message:String(n)),s.client.socketCallBackResponse(t.headers.messageId,{})})}),this.bridgeErrors.delete(e.id),this.dingdingBridges.set(e.id,s),s.client.connect().then(()=>{this.dingdingBridges.get(e.id)===s&&this.bridgeErrors.delete(e.id)}).catch(t=>{this.dingdingBridges.get(e.id)===s&&(this.dingdingBridges.delete(e.id),this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}),this.startPendingPolling(),this.status()}async handleDingdingRobotMessage(e,t,s){const n=function(e){const t=Mi(e),s=Pi(t.conversationId),n=Pi(Di(t.text).content).trim();if(!s||!n)return;const i=Pi(t.senderStaffId||t.senderId);return{chatId:s,userId:i||void 0,senderId:i||void 0,sessionWebhook:Pi(t.sessionWebhook)||void 0,conversationType:vi(t.conversationType,{robotMessage:!0}),text:n,raw:t}}(s.data);if(t.client.socketCallBackResponse(s.headers.messageId,{}),!n||!this.dingdingAllowed(e,n.senderId))return void this.logWarn({bridgeId:e.id,hasEvent:Boolean(n),senderId:n?.senderId,allowed:n?this.dingdingAllowed(e,n.senderId):void 0},"dingding robot message ignored");const{chatId:i,text:a,senderId:r,sessionWebhook:o}=n;o&&t.chatWebhooks.set(i,o),n.conversationType&&t.conversationTypes.set(i,n.conversationType);const d=o||t.chatWebhooks.get(i)||Yi(e.settings.sessionWebhook);let c;r&&t.senderIds.set(i,r),e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:i}),this.logInfo({bridgeId:e.id,chatId:i,senderId:r,conversationType:n.conversationType,hasSessionWebhook:Boolean(o),hasCachedSessionWebhook:Boolean(t.chatWebhooks.get(i)),hasResolvedSessionWebhook:Boolean(d),textPreview:Zi(a)},"dingding chat gateway message received");try{c=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:r||void 0},message:{text:a,attachments:[]}})}catch(e){if(d)return void await Ti(this.fetchImpl,d,`Failed to handle message: ${Xi(e)}`,t.onLog);throw e}const l=Bi(c);if(l){const s=Gi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=ui({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:t}),o=await n.send(i,l,{replyMarkup:s,sessionWebhook:d,senderId:r});if(o)return void this.logInfo({bridgeId:e.id,chatId:i,senderId:r,provider:o.provider,interactionId:o.interactionId,routed:!0===sa(c).routed,hasReplyMarkup:Boolean(s),replyPreview:Zi(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(Yi(e.settings.robotCode)),replyPreview:Zi(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,s,n,i,a={}){if(!0!==e.routed)return!1;const r=aa(e),{instanceId:o,sessionId:d,turnId:c}=r;if(!o||!d||!c)return this.logWarn({bridgeId:t.id,chatId:n,instanceId:o,sessionId:d,turnId:c,turnIdSources:r.sources,routed:e.routed},"dingding ai session progress card not started"),!1;const l=ua(o,d,c,t.id,n);return!!await this.dingdingProgress.applyUpdate({bridge:t,dingdingRuntime:s,key:l,chatId:n,text:i,replyMarkup:a.replyMarkup})&&(this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:n,instanceId:o,sessionId:d,turnId:c,key:l,hasSessionWebhook:Boolean(a.sessionWebhook),hasSenderId:Boolean(a.senderId),sourcePrompt:Zi(a.sourcePrompt,80),textPreview:Zi(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=Mi(e),s=ji(t.cardActionData||t.content),n=ji(s.cardPrivateData||t.cardPrivateData||t.content),i=ji(n.params||s.params||t.params),a=Pi(t.userId),r=Pi(i.biz_sender_id);return{userId:a||void 0,chatId:bi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:Pi(i.biz_session_webhook)||void 0,conversationType:vi(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:wi(Ai(t,s,n)),body:t,params:i}}(s.data);if(!this.dingdingAllowed(e,n.userId))return Si("not authorized",void 0,"unauthorized",n.body,n.params);const i=n.chatId||Yi(e.defaultChatId),a=n.deliverySenderId||n.senderId,r=n.sessionWebhook;i&&r&&t.chatWebhooks.set(i,r),i&&n.conversationType&&t.conversationTypes.set(i,n.conversationType),i&&a&&t.senderIds.set(i,a);const o=this.parseChatGatewayCallbackAction(n.callbackData);if(!o)return this.logWarn({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,...ea(n)},"dingding card callback action unsupported"),Si("Unsupported action",void 0,"unsupported",n.body,n.params);this.logInfo({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,actionType:o.type,...ea(n)},"dingding card callback action parsed");const d=await this.service.handleChatGatewayAction({source:{channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:n.userId||void 0},action:o});return"pending-decision"===o.type&&Hi(d)?Si(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):Si(Bi(d)||Yi(d.message)||"updated",Gi(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||Yi(t.updatesBuf),n=await async function(e){const t=Pi((e.bridge.settings||{}).baseUrl)||"https://ilinkai.weixin.qq.com",s=await e.fetchImpl(`${t}/ilink/bot/getupdates`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${e.bridge.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({get_updates_buf:e.cursor,base_info:{channel_version:"1.0.2"}})}),n=await s.json().catch(()=>({}));if(!s.ok)throw new Error(`HTTP ${s.status}`);const i=Number(n.errcode??n.ret??0);if(0!==i)throw new Error(Pi(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=Di(e);if(1!==t.message_type)continue;const s=Pi(t.from_user_id),n=Ei(t);s&&n&&a.push({chatId:s,text:n,contextToken:Pi(t.context_token)})}return{cursor:Pi(n.get_updates_buf),messages:a}}({fetchImpl:this.fetchImpl,bridge:e,cursor:s});n.cursor&&(this.wechatCursors.set(e.id,n.cursor),this.updateChatBridge(e.id,{settings:{updatesBuf:n.cursor}}));for(const s of n.messages){const{chatId:n,contextToken:i,text:a}=s;if(e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:n,settings:{contextToken:i}}),e.defaultChatId&&e.defaultChatId!==n)continue;const r=Bi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=ui({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||Yi(t.contextToken)})}}}async pollPendingRoutes(){const e=await this.service.listPendingRoutes().catch(()=>[]);for(const t of e){if(this.announcedPending.has(t.id))continue;const e=ra(t,(e,t)=>this.service.pendingDecisionCallbackData(e,t)),s=t.aiSessionId?this.service.listChatSessions().filter(e=>e.activeInstanceId===t.instanceId&&e.activeAiSessionId===t.aiSessionId):[],n=new Set;for(const t of s)t.bridgeId&&await this.sendToChatBinding(t,e).then(e=>{e&&n.add(t.id)});(0!==n.size||await this.sendToDefaultBridgeChats(e))&&this.announcedPending.add(t.id)}}async sendToDefaultBridgeChats(e){let t=!1;for(const s of this.service.listChatBridges()){if(!s.enabled||!s.defaultChatId)continue;let n=!1;await this.sendViaBridge(s,s.defaultChatId,e).catch(e=>{this.bridgeErrors.set(s.id,e instanceof Error?e.message:String(e)),n=!0})?t=!0:n||this.bridgeErrors.set(s.id,"Chat bridge message was not delivered.")}return t}async sendToChatBinding(e,t){if(!e.bridgeId)return!1;const s=this.service.requireChatBridge(e.bridgeId);if(!s.enabled)return!1;const n=await this.sendViaBridge(s,e.chatSessionId,t).catch(e=>{this.bridgeErrors.set(s.id,e instanceof Error?e.message:String(e))});return Boolean(n)}async sendViaBridge(e,t,s){const n="string"==typeof s?s:s.text,i="string"==typeof s?void 0:s.replyMarkup;return ui({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:this.dingdingBridges.get(e.id)}).send(t,n,{replyMarkup:i})}async pollTelegram(e){if(!e.token)return;const t=Number(e.settings?.telegramLastUpdateId),s=this.telegramOffsets.get(e.id)||(Number.isInteger(t)?t:void 0),n=await async function(e){const t=new URLSearchParams({timeout:"0",...e.offset?{offset:String(e.offset+1)}:{}}),s=await e.fetchImpl(`https://api.telegram.org/bot${e.bridge.token}/getUpdates?${t.toString()}`),n=await s.json().catch(()=>({}));if(!s.ok||!1===n.ok){if(409===s.status||/terminated by other getUpdates request/i.test(String(n.description||"")))return{updates:[],conflict:n.description||"Telegram getUpdates conflict: another bot instance is polling this token."};throw new Error(n.description||`Telegram getUpdates failed with HTTP ${s.status}`)}const i=[];for(const t of n.result||[]){const s=Number(t.update_id),n=Number.isInteger(s)?s:void 0,a=Di(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Di(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Di(r.chat),d=Di(r.from),c=void 0!==o.id?String(o.id):e.bridge.defaultChatId,l=void 0!==d.id?String(d.id):void 0;if(!c)continue;const p=Pi(r.text??r.caption),u=ai(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:_i(r),replyToMessageId:_i(Di(r.reply_to_message)),quoteText:Pi(Di(r.quote).text),autoBegin:1===u.length&&!p.trim(),hasSingleImageWithoutText:1===u.length&&!p.trim()})}return{updates:i}}({fetchImpl:this.fetchImpl,bridge:e,offset:s});if(n.conflict)this.bridgeErrors.set(e.id,n.conflict);else for(const t of n.updates){if(void 0!==t.updateId){const s=`${e.id}:${t.updateId}`;if(this.seenTelegramUpdates.has(s)||t.updateId<=(this.telegramOffsets.get(e.id)||0))continue;this.seenTelegramUpdates.add(s),this.telegramOffsets.set(e.id,t.updateId),this.updateChatBridge(e.id,{settings:{...e.settings,telegramLastUpdateId:t.updateId}})}if("callbackQuery"in t){const s=t.callbackQuery;await this.handleTelegramCallback(e,s).catch(async t=>{const n=sa(s.message),i=sa(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,Xi(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${Xi(t)}`).catch(t=>{this.bridgeErrors.set(e.id,Xi(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await gi({fetchImpl:this.fetchImpl,bridge:e,message:t.message,onImageDownloadError:({fileId:t,fileName:s,error:n})=>{this.logWarn({bridgeId:e.id,fileId:t,fileName:s,error:Xi(n)},"telegram image download skipped")}}),n=t.rawText.trim();(n||s.length)&&(this.logInfo({bridgeId:e.id,chatId:t.chatId,messageId:t.messageId,replyToMessageId:t.replyToMessageId,hasQuote:Boolean(t.quoteText),textPreview:Zi(n)},"telegram chat gateway message received"),await this.handleTelegramIncomingText(e,t.chatId,t.userId,n,s,{sourceMessageId:t.messageId,replyToMessageId:t.replyToMessageId,quoteText:t.quoteText},{autoBegin:t.autoBegin}))}}async handleTelegramIncomingText(e,t,s,n,i=[],a={},r={}){const o=n.trim(),d=o.split(/\s+/,1)[0]?.toLowerCase()||"",c=Ki(e.id,t,s),l=this.telegramMessageAggregates.get(c);return r.autoBegin&&!l?(this.startTelegramAggregate(c,e,t,s,a),this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!0),void await this.sendTelegramMessage(e,t,"Image received. Continue sending messages, then send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Js([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):"/begin"===d?(this.startTelegramAggregate(c,e,t,s,a),void await this.sendTelegramMessage(e,t,"Started collecting messages. Send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Js([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):void("/end"!==d?l?.explicit?this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!0):this.isImmediateTelegramCommand(o)?await this.dispatchTelegramGatewayMessage(e,t,s,n,i,a):this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!1):await this.flushTelegramAggregate(c,"manual"))}isImmediateTelegramCommand(e){return e.trim().startsWith("/")}startTelegramAggregate(e,t,s,n,i={}){const a=this.telegramMessageAggregates.get(e);a?.timer&&clearTimeout(a.timer),this.telegramMessageAggregates.set(e,{bridgeId:t.id,chatId:s,userId:n,texts:[],attachments:[],context:i,explicit:!0})}enqueueTelegramAggregate(e,t,s,n,i,a,r,o){const d=this.telegramMessageAggregates.get(e);d?.timer&&clearTimeout(d.timer);const c=d||{bridgeId:t.id,chatId:s,userId:n,texts:[],attachments:[],context:r,explicit:o};var l,p;c.bridgeId=t.id,c.chatId=s,c.userId=n,c.context=(l=c.context,p=r,{sourceMessageId:l?.sourceMessageId??p.sourceMessageId,replyToMessageId:l?.replyToMessageId??p.replyToMessageId,quoteText:l?.quoteText||p.quoteText}),c.explicit=c.explicit||o,i.trim()&&c.texts.push(i),c.attachments.push(...a),c.explicit?c.timer=void 0:c.timer=setTimeout(()=>{this.flushTelegramAggregate(e,"timer")},1e3),this.telegramMessageAggregates.set(e,c)}async flushTelegramAggregate(e,t){const s=this.telegramMessageAggregates.get(e);if(!s)return!1;s.timer&&clearTimeout(s.timer),this.telegramMessageAggregates.delete(e);const n=this.service.requireChatBridge(s.bridgeId),i=s.texts.join("\n\n").trim();return i||s.attachments.length?(await this.dispatchTelegramGatewayMessage(n,s.chatId,s.userId,i,s.attachments,s.context),!0):("timer"!==t&&await this.sendTelegramMessage(n,s.chatId,"No messages collected.",{replyToMessageId:s.context?.sourceMessageId}),!1)}async handleTelegramAggregateEndCallback(e,t,s,n,i){const a=this.telegramMessageAggregateEndTokens.get(n)||"";if(a!==Ki(e.id,t,i))return void await this.answerTelegramCallback(e,s,"This collection is not active here");const r=await this.flushTelegramAggregate(a,"callback");await this.answerTelegramCallback(e,s,r?"Collected message sent":"No messages collected")}telegramAggregateEndCallbackData(e){const t=o.createHash("sha256").update(e).digest("base64url").slice(0,16);return this.telegramMessageAggregateEndTokens.set(t,e),`task_handoff:cp_msg_end:${t}`}async dispatchTelegramGatewayMessage(e,t,s,n,i=[],a={}){try{const r=void 0!==a.replyToMessageId?this.telegramReplyAiSessionTarget(e.id,t,a.replyToMessageId):void 0,o=function(e,t){const s=String(t||"").trim(),n=e.trim();return s?[`引用:${s}`,n].filter(Boolean).join("\n"):n}(n,a.quoteText),d=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"telegram",bridgeId:e.id,chatSessionId:t,userId:s},message:{text:o,attachments:i},...r?{target:{instanceId:r.instanceId,aiSessionId:r.sessionId}}:{}}),c=Bi(d),l=Gi(d);if(c){const s=await this.sendTelegramMessage(e,t,c,{replyMarkup:l,replyToMessageId:a.sourceMessageId});this.logInfo({bridgeId:e.id,chatId:t,sourceMessageId:a.sourceMessageId,replyToMessageId:a.replyToMessageId,replyTargetInstanceId:r?.instanceId,replyTargetSessionId:r?.sessionId,sentMessageId:Number(sa(s).message_id)||void 0,routed:!0===sa(d).routed,replyPreview:Zi(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,Xi(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${Xi(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,Xi(t))})}}async handleTelegramCallback(e,t){const s=Yi(t.data),n=sa(t.from),i=sa(t.message),a=sa(i.chat),r=void 0!==a.id?String(a.id):e.defaultChatId,o=void 0!==n.id?String(n.id):void 0;if(!r||!this.telegramAllowed(e,o))return;const d=s.match(/^task_handoff:cp_ai_cancel:([^:]+)$/);if(d){const s=this.aiSessionCancelTokens.get(d[1]);return s?void await this.handleTelegramAiSessionCancelCallback(e,r,Yi(t.id),s.instanceId,s.sessionId,o,Vi(i)):void await this.answerTelegramCallback(e,Yi(t.id),"This AI session action expired")}const c=s.match(/^task_handoff:cp_ai_steer:([^:]+)$/);if(c){const s=this.aiSessionQueueActionTokens.get(c[1]);return s&&"steer"===s.type?void await this.handleTelegramAiSessionQueueSteerCallback(e,r,Yi(t.id),s.instanceId,s.sessionId,s.queueId,o,Vi(i)):void await this.answerTelegramCallback(e,Yi(t.id),"This AI session action expired")}const l=s.match(/^task_handoff:cp_ai_qdel_menu:([^:]+)$/);if(l){const s=this.aiSessionQueueActionTokens.get(l[1]);return s&&"delete-menu"===s.type?void await this.handleTelegramAiSessionQueueDeleteMenuCallback(e,r,Yi(t.id),s.instanceId,s.sessionId,o,Vi(i)):void await this.answerTelegramCallback(e,Yi(t.id),"This AI session action expired")}const p=s.match(/^task_handoff:cp_ai_qdel:([^:]+)$/);if(p){const s=this.aiSessionQueueActionTokens.get(p[1]);return s&&"delete-item"===s.type?void await this.handleTelegramAiSessionQueueDeleteItemCallback(e,r,Yi(t.id),Vi(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,Yi(t.id),"This AI session action expired")}const u=s.match(/^task_handoff:cp_msg_end:([^:]+)$/);if(u)return void await this.handleTelegramAggregateEndCallback(e,r,Yi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(s);if(!g)return void await this.answerTelegramCallback(e,Yi(t.id),"Unsupported action");const h=await this.service.handleChatGatewayAction({source:{channel:"telegram",bridgeId:e.id,chatSessionId:r,userId:o},action:g});if("pending-decision"===g.type&&Hi(h)){await this.answerTelegramCallback(e,Yi(t.id),"");const s=Vi(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,Yi(t.id),Yi(h.message)||"updated");const m=Bi(h),I=Gi(h),f=i.message_id;m&&"number"==typeof f?await this.editTelegramMessage(e,r,f,m,{replyMarkup:I}):m&&await this.sendTelegramMessage(e,r,m,{replyMarkup:I})}async deleteTelegramStandaloneApprovalMessage(e,t,s){[...this.telegramProgress.entries.values()].find(n=>n.messageId===s&&n.route?.bridgeId===e.id&&n.route?.chatId===t)||await this.deleteTelegramMessage(e,t,s).catch(n=>{this.logWarn({bridgeId:e.id,chatId:t,messageId:s,error:Xi(n)},"telegram callback message delete failed")})}async handleTelegramAiSessionCancelCallback(e,t,s,n,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,n,i,r))try{await this.service.interruptAiSession(n,i),await this.answerTelegramCallback(e,s,"Interrupt sent"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,userId:a},"telegram ai session cancel sent")}catch(t){this.bridgeErrors.set(e.id,Xi(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${Zi(Xi(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueSteerCallback(e,t,s,n,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,n,i,o))try{await this.service.steerAiSessionQueuedMessage(n,i,a),await this.answerTelegramCallback(e,s,"Steered queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,queueId:a,userId:r},"telegram ai session queued message steered")}catch(t){this.bridgeErrors.set(e.id,Xi(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${Zi(Xi(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteMenuCallback(e,t,s,n,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,n,i,r))try{const r=sa(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>sa(e)).filter(e=>"queued"===Yi(e.status)&&Yi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,oa).map((e,t)=>[{text:pa(Yi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,Yi(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:Js(d)}),await this.answerTelegramCallback(e,s,"Select a queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,itemCount:o.length,userId:a},"telegram ai session queue delete menu sent")}catch(t){this.bridgeErrors.set(e.id,Xi(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${Zi(Xi(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteItemCallback(e,t,s,n,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,i,a,n))try{await this.service.removeAiSessionQueuedMessage(i,a,r),await this.answerTelegramCallback(e,s,"Queued message deleted"),Number.isInteger(n)&&await this.deleteTelegramMessage(e,t,n).catch(s=>{this.logWarn({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,messageId:n,error:Xi(s)},"telegram ai session queue delete menu message delete failed")}),this.logInfo({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,userId:o},"telegram ai session queued message deleted")}catch(t){this.bridgeErrors.set(e.id,Xi(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${Zi(Xi(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}telegramAiSessionBindingActive(e,t,s,n){return this.service.listChatSessions().some(i=>i.bridgeId===e.id&&i.chatSessionId===t&&i.activeInstanceId===s&&i.activeAiSessionId===n)}telegramAiSessionActionAllowed(e,t,s,n,i){if(this.telegramAiSessionBindingActive(e,t,s,n))return!0;if(!Number.isInteger(i))return!1;const a=this.telegramReplyAiSessionTarget(e.id,t,i);return Boolean(a&&a.instanceId===s&&a.sessionId===n)}parseChatGatewayCallbackAction(e){return function(e,t){const s=e.match(/^task_handoff:cp_session:(\d+)$/);if(s)return{type:"ai-session",index:Number(s[1])};const n=e.match(/^task_handoff:cp_i:([^:]+)$/)||e.match(/^task_handoff:cp_instance_apps:([^:]+)$/);if(n)return n[0].startsWith("task_handoff:cp_i:")?{type:"instance-app-menu",instanceId:di(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Li(n[1])};const i=e.match(/^task_handoff:cp_a:([^:]+)$/)||e.match(/^task_handoff:cp_launch_app:([^:]+):([^:]+)$/);if(i){if(i[0].startsWith("task_handoff:cp_a:")){const e=t(i[1],"launch-app");return{type:"launch-app",instanceId:di(e.instanceId,"instanceId"),appId:di(e.appId,"appId")}}return{type:"launch-app",instanceId:Li(i[1]),appId:Li(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:di(e.routeId,"routeId"),decision:ci(e.decision)}}const r=e.match(/^task_handoff:approval:([^:]+:ai:[^:]+):(allow|deny|skip)$/);return r?{type:"pending-decision",routeId:r[1],decision:r[2]}:void 0}(e,(e,t)=>this.service.resolveChatActionToken(e,t))}dingdingAllowed(e,t){const s=String(t||"").trim();return!!s&&(0===e.allowedUserIds.length?(e.allowedUserIds=[s],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:s},"dingding user bound"),!0):e.allowedUserIds.includes(s))}telegramAllowed(e,t){const s=String(t||"").trim();return!!s&&(0===e.allowedUserIds.length?(e.allowedUserIds=[s],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:s},"telegram user bound"),!0):e.allowedUserIds.includes(s))}async answerTelegramCallback(e,t,s){return async function(e,t,s,n){t.token&&s&&await e(`https://api.telegram.org/bot${t.token}/answerCallbackQuery`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({callback_query_id:s,text:n.slice(0,200)})})}(this.fetchImpl,e,t,s)}async editTelegramMessage(e,t,s,n,i={}){return async function(e,t,s,n,i,a={}){if(t.token)return Zn(s=>e(`https://api.telegram.org/bot${t.token}/editMessageText`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),"editMessageText",i,{chat_id:s,message_id:n,...a.replyMarkup?{reply_markup:a.replyMarkup}:{}},a)}(this.fetchImpl,e,t,s,n,i)}async sendTelegramMessage(e,t,s,n={}){return Yn(this.fetchImpl,e,t,s,n)}async deleteTelegramMessage(e,t,s){return async function(e,t,s,n){if(!t.token||!Number.isInteger(n))return;const i=await e(`https://api.telegram.org/bot${t.token}/deleteMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({chat_id:s,message_id:n})}),a=await i.json().catch(()=>({}));if(!i.ok||!1===a.ok)throw new Error(a.description||`Telegram deleteMessage failed with HTTP ${i.status}`)}(this.fetchImpl,e,t,s)}async rememberTelegramProgressMessage(e,t,s,n,i){if(!0!==e.routed)return;const a=aa(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(sa(n).message_id);if(!(r&&o&&d&&Number.isInteger(c)))return void this.logWarn({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,messageId:Number.isInteger(c)?c:void 0,routed:e.routed},"telegram ai session progress message not remembered");const l=ua(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set(Ji(t.id,s,c),{instanceId:r,sessionId:o,turnId:d,messageId:c}),this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,key:l,messageId:c,progressKeys:this.telegramProgressKeysFor(r,o,t.id,s),textPreview:Zi(i)},"telegram ai session progress message remembered"),"queue"===function(e){return e.aiSession&&"action"in e.aiSession?e.aiSession.action:void 0}(e)&&p?.messageId&&p.messageId!==c&&(this.telegramProgressMessageTargets.delete(Ji(t.id,s,p.messageId)),await this.deleteTelegramMessage(t,s,p.messageId).catch(e=>{this.logWarn({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,replacedMessageId:p.messageId,replacementMessageId:c,error:Xi(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get(Ji(e,t,s));if(n)return n;for(const[n,i]of this.telegramProgress.entries)if(i.messageId===s&&i.route?.bridgeId===e&&i.route?.chatId===t){const i=Qi(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set(Ji(e,t,s),n),n}}}async pollAiSessionUpdates(){this.logAiSessionDelivery({stage:"poll-tick"});const e=await this.service.listAiSessions({refresh:!0}).catch(()=>{});if(!e)return void this.logAiSessionDelivery({stage:"poll-no-snapshot"});const t=new Map(e.instances.map(e=>[e.instanceId,e.aiSessions]));this.logAiSessionDelivery({stage:"poll-snapshot",instanceCount:t.size,instances:[...t.entries()].map(([e,t])=>({instanceId:e,sessionCount:t.sessions?.length||0,runningCount:t.runningCount,waitingCount:t.waitingCount,updatedAt:t.updatedAt}))});const s=await this.aiSessionInstanceNames();for(const[e,n]of t)await this.deliverAiSessionSnapshot(e,n,s)}async deliverAiSessionSnapshot(e,t,s){const n=Array.isArray(t.sessions)?t.sessions:[];if(this.pruneAiSessionDeliveryState(e,new Set(n.map(e=>e.id))),0===n.length)return;const i=s||await this.aiSessionInstanceNames(),a=await this.aiSessionPendingApprovalRoutes(e);await this.deliverOwnedTelegramProgress(e,n,i,a),await this.deliverActiveBindingTelegramProgress(e,n,i,a)}pruneAiSessionDeliveryState(e,t){for(const[s,n]of this.telegramProgress.entries){const i=n.route?.bridgeId&&n.route.chatId?this.telegramProgressMessageTargets.get(Ji(n.route.bridgeId,n.route.chatId,n.messageId||0))||Qi(s,n.route.bridgeId,n.route.chatId):void 0;i&&i.instanceId===e&&!t.has(i.sessionId)&&(n.timer&&clearTimeout(n.timer),this.telegramProgress.entries.delete(s),this.deliveredAiSessionFingerprints.delete(s),n.route?.bridgeId&&n.route.chatId&&n.messageId&&this.telegramProgressMessageTargets.delete(Ji(n.route.bridgeId,n.route.chatId,n.messageId)))}}async deliverActiveBindingTelegramProgress(e,t,s,n){for(const i of this.service.listChatSessions()){if(!i.bridgeId||i.activeInstanceId!==e||!i.activeAiSessionId)continue;const a=t.find(e=>e.id===i.activeAiSessionId);if(!a)continue;const r=fa(s.get(e)||e,a),o=Ia(a,r);if(!o){this.logAiSessionDelivery({stage:"skip-empty-text",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:ta(ya(a))});continue}const d=ga(a);if(!d){this.logAiSessionDelivery({stage:"skip-empty-turn-id",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:ta(ya(a))});continue}if(this.listTelegramOwnedProgressEntries(e).some(e=>e.route.bridgeId===i.bridgeId&&e.route.chatId===i.chatSessionId&&e.owner.sessionId===a.id&&e.owner.turnId===d))continue;const c=ua(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=ha(a,r);if(!l||this.deliveredAiSessionFingerprints.get(c)===l){this.logAiSessionDelivery({stage:"skip-duplicate-fingerprint",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,hasFingerprint:Boolean(l),fingerprintMatched:this.deliveredAiSessionFingerprints.get(c)===l,latestTurn:ta(ya(a)),textPreview:Zi(o)});continue}const p=ca({cancelCallbackData:this.aiSessionCancelCallbackData(e,a.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,a.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,a.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:n.get(a.id)||la(e,a),session:a});if(this.logAiSessionDelivery({stage:"attempt",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),hasProgressEntry:this.telegramProgress.entries.has(c),status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:ta(ya(a)),textPreview:Zi(o)}),await this.updateChatAiSessionProgress(i,o,c,p).catch(t=>(this.bridgeErrors.set(i.bridgeId||"",t instanceof Error?t.message:String(t)),this.logWarn({bridgeId:i.bridgeId,chatId:i.chatSessionId,instanceId:e,sessionId:a.id,turnId:d,key:c,error:Xi(t)},"chat ai session progress update failed"),!1)))this.logAiSessionDelivery({stage:"delivered",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:Zi(o)}),this.deliveredAiSessionFingerprints.set(c,l);else{if("telegram"===this.service.requireChatBridge(i.bridgeId).channel){const t=this.telegramProgress.entries.get(c),s=this.service.requireChatBridge(i.bridgeId);this.logAiSessionDelivery({stage:t?"telegram-update-not-delivered":"telegram-no-progress-entry",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),bridgeEnabled:s.enabled,bridgeChannel:s.channel,progressMessageId:t?.messageId,progressLastText:Zi(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:ta(ya(a)),textPreview:Zi(o)});continue}await this.sendToChatBinding(i,{text:o}).catch(e=>(this.bridgeErrors.set(i.bridgeId||"",e instanceof Error?e.message:String(e)),!1))&&(this.logAiSessionDelivery({stage:"fallback-sent",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:Zi(o)}),this.deliveredAiSessionFingerprints.set(c,l))}}}async deliverOwnedTelegramProgress(e,t,s,n){for(const i of this.listTelegramOwnedProgressEntries(e)){const a=this.service.requireChatBridge(i.route.bridgeId);if("telegram"!==a.channel||!a.enabled)continue;const r=this.matchTelegramOwnedProgressSession(i,t);if(!r)continue;const o={bridgeId:i.route.bridgeId,chatSessionId:i.route.chatId},d=fa(s.get(e)||e,r),c=Ia(r,d),l=ga(r);if(!c||!l){this.logAiSessionDelivery({stage:"remembered-skip-turn-or-text",key:i.key,parsedTurnId:i.owner.turnId,currentTurnId:l,hasText:Boolean(c),instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}if(i.owner.turnId&&l!==i.owner.turnId){this.logAiSessionDelivery({stage:"remembered-skip-different-active-turn",key:i.key,ownerTurnId:i.owner.turnId,currentTurnId:l,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const p=l===i.owner.turnId?i.key:ua(e,r.id,l,i.route.bridgeId,i.route.chatId);if(p!==i.key&&this.telegramProgress.entries.has(p))continue;if(p!==i.key&&!this.rekeyTelegramOwnedProgress(i,p,{instanceId:e,sessionId:r.id,turnId:l}))continue;const u=ha(r,d);if(!u||this.deliveredAiSessionFingerprints.get(p)===u){this.logAiSessionDelivery({stage:"remembered-skip-fingerprint",key:i.key,hasFingerprint:Boolean(u),fingerprintMatched:this.deliveredAiSessionFingerprints.get(p)===u,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const g=ca({cancelCallbackData:this.aiSessionCancelCallbackData(e,r.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,r.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,r.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:n.get(r.id)||la(e,r),session:r}),h=await this.updateChatAiSessionProgress(o,c,p,g).catch(e=>(this.bridgeErrors.set(o.bridgeId||"",e instanceof Error?e.message:String(e)),!1));this.logAiSessionDelivery({stage:h?"remembered-delivered":"remembered-not-delivered",key:p,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId,textPreview:Zi(c)}),h&&(this.telegramProgressMessageTargets.set(Ji(i.route.bridgeId,i.route.chatId,i.messageId),{instanceId:e,sessionId:r.id,turnId:l,messageId:i.messageId}),this.deliveredAiSessionFingerprints.set(p,u))}}rekeyTelegramOwnedProgress(e,t,s){return!!this.telegramProgress.rekey(e.key,t)&&(this.deliveredAiSessionFingerprints.delete(e.key),this.telegramProgressMessageTargets.set(Ji(e.route.bridgeId,e.route.chatId,e.messageId),{...s,messageId:e.messageId}),!0)}logAiSessionDelivery(e){this.logInfo(e,"telegram ai session delivery flow")}telegramProgressKeysFor(e,t,s,n){const i=`${e}:${t}:`,a=`:${s}:${n}`;return[...this.telegramProgress.entries.keys()].filter(e=>e.startsWith(i)&&e.endsWith(a))}listTelegramOwnedProgressEntries(e){const t=[];for(const[s,n]of this.telegramProgress.entries){if(!n.route?.bridgeId||!n.route.chatId||!Number.isInteger(n.messageId))continue;const i=this.telegramProgressMessageTargets.get(Ji(n.route.bridgeId,n.route.chatId,n.messageId))||Qi(s,n.route.bridgeId,n.route.chatId);i&&i.instanceId===e&&t.push({key:s,route:{bridgeId:n.route.bridgeId,chatId:n.route.chatId},messageId:n.messageId,owner:{...i,messageId:n.messageId}})}return t}matchTelegramOwnedProgressSession(e,t){const s=t.find(t=>t.id===e.owner.sessionId);if(s)return s;if(e.owner.turnId){const s=t.find(t=>t.activeTurnId===e.owner.turnId||ya(t)?.id===e.owner.turnId||ya(t)?.providerTurnId===e.owner.turnId);if(s)return s}}async updateChatAiSessionProgress(e,t,s,n){if(!e.bridgeId)return this.logAiSessionDelivery({stage:"chat-update-skip-missing-bridge",chatId:e.chatSessionId,key:s}),!1;const i=this.service.requireChatBridge(e.bridgeId);if(!i.enabled)return this.logAiSessionDelivery({stage:"chat-update-skip-bridge-state",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,bridgeEnabled:i.enabled,bridgeChannel:i.channel}),!1;if("dingding"===i.channel){const a=this.dingdingBridges.get(i.id);if(!a)return!1;const r=await this.dingdingProgress.applyUpdate({bridge:i,dingdingRuntime:a,key:s,chatId:e.chatSessionId,text:t,replyMarkup:Wi({actionRows:n})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:Zi(t)},"dingding ai session progress updated"),r}if("telegram"!==i.channel)return!1;const a=this.telegramProgress.entries.get(s);if(!await this.telegramProgress.applyUpdate({bridge:i,key:s,chatId:e.chatSessionId,text:t,actionRows:n})){const t=this.telegramProgress.entries.get(s);return this.logAiSessionDelivery({stage:"telegram-update-returned-false",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,progressMessageId:a?.messageId||t?.messageId,progressLastText:Zi(a?.lastText||t?.lastText),progressHasPending:Boolean(a?.pending||t?.pending)}),!1}return this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,messageId:a?.messageId||this.telegramProgress.entries.get(s)?.messageId,textPreview:Zi(t)},a?"telegram ai session progress edited":"telegram ai session progress sent"),!0}async aiSessionInstanceNames(){const e=await this.service.boardAsync().catch(()=>[]);return new Map(e.map(e=>[e.id,e.name||e.id]))}async aiSessionPendingApprovalRoutes(e){const t=await this.service.listPendingRoutes().catch(()=>[]);return new Map(t.filter(t=>t.instanceId===e&&t.aiSessionId&&"approval"===t.kind).map(e=>[e.aiSessionId||"",e]))}aiSessionCancelCallbackData(e,t){const s=o.createHash("sha256").update(`${e}\0${t}`).digest("base64url").slice(0,16);return this.aiSessionCancelTokens.set(s,{instanceId:e,sessionId:t}),`task_handoff:cp_ai_cancel:${s}`}aiSessionQueueSteerCallbackData(e,t,s){return`task_handoff:cp_ai_steer:${this.aiSessionQueueActionToken({type:"steer",instanceId:e,sessionId:t,queueId:s})}`}aiSessionQueueDeleteMenuCallbackData(e,t){return`task_handoff:cp_ai_qdel_menu:${this.aiSessionQueueActionToken({type:"delete-menu",instanceId:e,sessionId:t})}`}aiSessionQueueDeleteItemCallbackData(e,t,s){return`task_handoff:cp_ai_qdel:${this.aiSessionQueueActionToken({type:"delete-item",instanceId:e,sessionId:t,queueId:s})}`}aiSessionQueueActionToken(e){const t=o.createHash("sha256").update([e.type,e.instanceId,e.sessionId,"queueId"in e?e.queueId:""].join("\0")).digest("base64url").slice(0,16);return this.aiSessionQueueActionTokens.set(t,e),t}}function Bi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function Hi(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function Gi(e){const t="object"==typeof e&&e&&"replyMarkup"in e?e.replyMarkup:void 0;if(t&&"object"==typeof t&&!Array.isArray(t))return t}function Wi(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Vi(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Ki(e,t,s){return[e,t,s||""].join(":")}function Ji(e,t,s){return[e,t,String(s)].join(":")}function Qi(e,t,s){const n=`:${t}:${s}`;if(!e.endsWith(n))return;const i=e.slice(0,-n.length).split(":"),a=i[0]||"",r=i[1]||"",o=i.slice(2).join(":")||void 0;return a&&r?{instanceId:a,sessionId:r,turnId:o}:void 0}function Yi(e){return"string"==typeof e?e:""}function Xi(e){return e instanceof Error?e.message:String(e)}function Zi(e,t=160){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function ea(e){const t=na(e.body.cardActionData||e.body.content),s=na(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:Yi(e.body.outTrackId),spaceId:Yi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:Zi(e.callbackData,120),actionIds:[...ia(s.actionIdList),...ia(t.actionIdList),Yi(s.actionId),Yi(t.actionId),Yi(e.body.actionId)].filter(Boolean).slice(0,5),cardActionDataKeys:Object.keys(t).sort(),privateDataKeys:Object.keys(s).sort(),paramKeys:Object.keys(e.params).sort(),hasSessionWebhook:Boolean(e.sessionWebhook)}}function ta(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:Zi(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:Zi(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function sa(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function na(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return sa(JSON.parse(e))}catch{return{}}return{}}function ia(e){return Array.isArray(e)?e.map(e=>Yi(e)).filter(Boolean):[]}function aa(e){const t=e.aiSession&&"session"in e.aiSession?e.aiSession:void 0,s=t?.session||(e.aiSession&&"id"in e.aiSession?e.aiSession:void 0);return{instanceId:e.instanceId||e.instance?.id||"",sessionId:e.aiSessionId||s?.id||e.binding?.activeAiSessionId||"",turnId:e.turnId||e.providerTurnId||t?.turnId||t?.providerTurnId||(s?ga(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?ya(s)?.id:void 0}}}function ra(e,t){const s=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,n="approval"===e.kind?`/approve ${e.id}\n/deny ${e.id}\n/skip ${e.id}`:`/reply ${e.id} <message>`,i=`[${s}]\n${e.result}\n\n${n}`;return"approval"!==e.kind?{text:i}:{text:i,replyMarkup:Js([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const oa=5,da=48;function ca(e){const{cancelCallbackData:t,queueSteerCallbackData:s,queueDeleteMenuCallbackData:n,permissionCallbackData:i,pendingApproval:a,session:r}=e,o="approval"===a?.kind?[[{text:"Allow",callbackData:i(a.id,"allow")},{text:"Skip",callbackData:i(a.id,"skip")},{text:"Deny",callbackData:i(a.id,"deny")}]]:[];if("running"!==r.status)return o;const d=(r.queue?.items||[]).filter(e=>"queued"===e.status).slice(0,oa);return[...d.map((e,t)=>[{text:pa(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function la(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function pa(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,da-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function ua(e,t,...s){return[e,t,...s].join(":")}function ga(e){const t=ya(e);return t?.id?t.id:Yi(e.activeTurnId)}function ha(e,t=""){const s=ya(e);return[t,s?.id||"",String(s?.revision??""),s?.status||e.status||"",s?.phase||e.phase||"",s?.lastMessage||"",s?.summary||"",s?.userPrompt||"",e.error||"",e.currentTool?.name||"",e.currentTool?.inputPreview||"",ma(e)].join("\0")}function ma(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function Ia(e,t){const s=function(e,t){return t?t.lastMessage?.trim()?t.lastMessage.trim():t.summary?.trim()?t.summary.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:"":e.lastMessage?.trim()?e.lastMessage.trim():e.summary?.trim()?e.summary.trim():e.error?.trim()?"failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:""}(e,ya(e));return s?`${t}\n${s}`:""}function fa(e,t){const s=[t.status,t.phase&&"unknown"!==t.phase?t.phase:void 0].filter(Boolean).join("/");return`${e} · ${t.agent||"ai"} ${s||"unknown"}`}function ya(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}class ba{clients=new Set;listeners=new Set;seq=0;connect(e){e.topics=new Set(["*"]),this.clients.add(e),e.on("close",()=>{this.clients.delete(e)}),e.on("message",t=>{const s=function(e){try{const t=JSON.parse(String(e||"{}"));return t&&"object"==typeof t?t:void 0}catch{return}}(t);"subscribe"===s?.type&&(e.topics=new Set(s.topics?.length?s.topics:["*"]))})}publish(e,t,s={}){const n=this.createEvent(e,t,s),i=n.topic,a=JSON.stringify(n);for(const e of this.listeners)try{e(n)}catch{this.listeners.delete(e)}for(const t of this.clients)if(t.readyState===t.OPEN&&va(t.topics,i,e))try{t.send(a)}catch{this.clients.delete(t)}return n}on(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}send(e,t,s,n={}){const i=this.createEvent(t,s,n);return e.send(JSON.stringify(i)),i}createEvent(e,t,s={}){return{v:1,id:`evt_${Date.now().toString(36)}_${Math.random().toString(16).slice(2)}`,seq:++this.seq,type:e,topic:s.topic||tt(e),createdAt:(new Date).toISOString(),payload:t,scope:s.scope}}}function va(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const wa={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},Sa=r.z.object({instanceId:r.z.string().trim().min(1).max(120),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.literal("image").default("image"),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),data:r.z.string().min(1).max(31457280)}).strict();class Aa{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const n=Sa.parse(e),i=function(e,t){const s=/^data:([^;,]+);base64,(.+)$/i.exec(e.trim()),n=s?.[1]||t,i=s?.[2]||e;return{mime:n,buffer:Buffer.from(i,"base64")}}(n.data,n.mime),a=i.mime.toLowerCase(),r=wa[a];if(!r)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>f)throw new Error("Image must be between 1 byte and 20971520 bytes.");const d=`att_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,l=new Date,p=new Date(l.getTime()+this.ttlMs),u=s.join(c.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(u,{recursive:!0});const g=function(e){return s.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(n.name),h=s.join(u,`${d}-${g.endsWith(r)?g:`${g}${r}`}`);t.writeFileSync(h,i.buffer);const m={id:d,instanceId:n.instanceId,sessionId:n.sessionId,kind:"image",name:g,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:h,createdAt:l.toISOString(),expiresAt:p.toISOString()};return this.items.set(d,m),this.publicAttachment(m)}resolveRefs(e,t,s){this.pruneExpired();const n=r.z.array(z).max(6).default([]).parse(e),i=n.map(e=>this.requireAttachment(e,t,s));if(i.reduce((e,t)=>e+t.size,0)>y)throw new Error("Images must be 41943040 bytes or less in total.");for(const e of n)this.consumeAttachment(e.id);return i}requireAttachment(e,t,s){const n=this.items.get(e.id);if(!n)throw new Error(`AI session attachment not found or expired: ${e.id}`);if(e.kind&&e.kind!==n.kind)throw new Error(`AI session attachment kind mismatch: ${e.id}`);if(n.instanceId!==t||n.sessionId!==s)throw new Error(`AI session attachment scope mismatch: ${e.id}`);return C.parse({id:n.id,kind:n.kind,name:n.name,mime:n.mime,size:n.size,data:n.data})}consumeAttachment(e){const s=this.items.get(e);if(s){this.items.delete(e);try{t.unlinkSync(s.path)}catch{}}}publicAttachment(e){return{id:e.id,kind:e.kind,name:e.name,mime:e.mime,size:e.size,expiresAt:e.expiresAt}}pruneExpired(){const e=Date.now();for(const[s,n]of this.items)if(!(Date.parse(n.expiresAt)>e)){this.items.delete(s);try{t.unlinkSync(n.path)}catch{}}}}function ka(e){return e.readyState===(e.OPEN??1)}function Ta(e,t,s){e.close(function(e){if("number"==typeof e&&Number.isInteger(e))return e>=3e3&&e<=4999||e>=1e3&&e<=1014&&1004!==e&&1005!==e&&1006!==e?e:void 0}(t),function(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):"string"==typeof e?e:"";if(Buffer.byteLength(t,"utf8")<=123)return t;let s="";for(const e of t){const t=`${s}${e}`;if(Buffer.byteLength(t,"utf8")>123)break;s=t}return s}(s)||void 0)}function Ea(e,t){e.send(t.data,{binary:t.isBinary})}function _a(e,t){if(ka(e))for(const s of t.splice(0))Ea(e,s)}class Ca{attempt=0;timer;get pending(){return Boolean(this.timer)}get attempts(){return this.attempt}schedule(e,t={}){if(this.timer)return;const s=function(e,t=Math.random){const s=Math.min(3e4,1e3*2**Math.max(0,e));return Math.min(3e4,Math.round(s*(.75+.5*t())))}(this.attempt,t.random);this.attempt+=1;const n=t.setTimeoutFn||setTimeout;return this.timer=n(()=>{this.timer=void 0,e()},s),{attempt:this.attempt,delay:s}}reset(e=clearTimeout){this.timer&&e(this.timer),this.timer=void 0,this.attempt=0}cancel(e=clearTimeout){this.timer&&e(this.timer),this.timer=void 0}}const Na=r.z.object({nodeId:r.z.string().trim().min(1).max(120)}).strict();function za(e,t){if(!t)throw Object.assign(new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`),{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"});return t}function Oa(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Ra=new Map;function xa(e,t){if("paired-hmac"!==e.auth.mode||!e.auth.keyId||!e.auth.secret){const e=new Error("Reverse tunnel requires paired-HMAC node auth.");throw Object.assign(e,{statusCode:401,code:"NODE_AGENT_TUNNEL_REQUIRES_PAIRED_HMAC"}),e}const s=function(e){const t=new Map;for(const[s,n]of Object.entries(e)){const e=Array.isArray(n)?n.join(","):"string"==typeof n?n:void 0===n?"":String(n);t.set(s.toLowerCase(),e)}return{nodeId:t.get("x-taskhandoff-node-id")||"",keyId:t.get("x-taskhandoff-key-id")||"",timestamp:t.get("x-taskhandoff-timestamp")||"",nonce:t.get("x-taskhandoff-nonce")||"",bodySha256:t.get("x-taskhandoff-body-sha256")||"",signature:t.get("x-taskhandoff-signature")||""}}(t.headers);if(s.nodeId!==e.id)throw Object.assign(new Error("Invalid reverse tunnel node id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NODE_MISMATCH"});if(s.keyId!==e.auth.keyId)throw Object.assign(new Error("Invalid reverse tunnel key id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_KEY_INVALID"});const n=Date.parse(s.timestamp);if(!Number.isFinite(n)||Math.abs(Date.now()-n)>xn)throw Object.assign(new Error("Reverse tunnel timestamp is outside the allowed window."),{statusCode:401,code:"NODE_AGENT_TUNNEL_TIMESTAMP_INVALID"});const i=Date.now()-xn;for(const[e,t]of Ra)t<=i&&Ra.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||Ra.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=Dn("");if(s.bodySha256!==r)throw Object.assign(new Error("Reverse tunnel body hash is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_BODY_HASH_INVALID"});const d=t.url||`/api/node-agent/tunnel?nodeId=${encodeURIComponent(e.id)}`,c=jn(e.auth.secret,{keyId:s.keyId,method:"GET",pathWithQuery:d,timestamp:s.timestamp,nonce:s.nonce,bodySha256:r});if(!function(e,t){const s=Buffer.from(e,"hex"),n=Buffer.from(t,"hex");return s.length===n.length&&o.timingSafeEqual(s,n)}(s.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});Ra.set(a,Date.now()+xn)}class Da{sockets=new Map;events;onStreamsHello;constructor(e,t={}){this.events=e,this.onStreamsHello=t.onStreamsHello}attach(e,t){const s=this.sockets.get(e);if(s){for(const t of s.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} was replaced.`));for(const[e,t]of s.streams)s.streams.delete(e),t.downstream.close(1011,"Reverse tunnel was replaced.")}const n=new Map;this.sockets.set(e,{socket:t,pending:n,streams:new Map}),t.on?.("close",()=>this.detach(e,t)),t.on?.("error",()=>this.detach(e,t))}detach(e,t){const s=this.sockets.get(e);if(s&&(!t||s.socket===t)){this.sockets.delete(e);for(const t of s.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} disconnected.`));for(const[e,t]of s.streams)s.streams.delete(e),t.downstream.close(1011,"Reverse tunnel disconnected.")}}handleMessage(e,t){if(this.handleNodeAgentEvent(e,t))return!0;if(this.handleWebSocketMessage(e,t))return!0;if("node-agent.response"!==t.type)return!1;const s="string"==typeof t.requestId?t.requestId:"",n=this.sockets.get(e),i=n?.pending.get(s);if(!i||!n)return!0;n.pending.delete(s),clearTimeout(i.timer);const a=Number(t.status)||502,r=t.headers&&"object"==typeof t.headers?t.headers:{},o="string"==typeof t.body?t.body:"";if(t.error&&"object"==typeof t.error){const e=t.error,s=new Error("string"==typeof e.message?e.message:`Reverse tunnel request failed with HTTP ${a}`);return Object.assign(s,{statusCode:a,code:"string"==typeof e.code?e.code:"NODE_AGENT_REVERSE_REQUEST_FAILED"}),i.reject(s),!0}return i.resolve(new Response(o,{status:a,headers:r})),!0}handleNodeAgentEvent(e,t){const s="string"==typeof t.type?t.type:"";if("node-agent.streams.hello"===s){const e="string"==typeof t.instanceId?t.instanceId:"",s=it.safeParse(t.payload);return e&&s.success&&this.onStreamsHello?.(e,s.data),!0}if(!s.startsWith("node-agent.event."))return!1;const n=t.event&&"object"==typeof t.event&&!Array.isArray(t.event)?t.event:void 0,i="string"==typeof n?.type?n.type:"";if(!i)return!0;const a="payload"in n?n.payload:{},r=n.scope&&"object"==typeof n.scope&&!Array.isArray(n.scope)?n.scope:{},o="string"==typeof t.instanceId?t.instanceId:void 0;return this.events?.publish(i,a,{topic:"string"==typeof n.topic?n.topic:void 0,scope:{...r,nodeId:e,instanceId:"string"==typeof r.instanceId?r.instanceId:"string"==typeof n.instanceId?n.instanceId:o}}),!0}async request(e,t,s={}){const n=this.sockets.get(e.id);if(!n){const t=new Error(`Reverse tunnel for node ${e.id} is not connected.`);throw Object.assign(t,{statusCode:503,code:"NODE_AGENT_REVERSE_TUNNEL_OFFLINE"}),t}const i=Oa(),a="string"==typeof s.body?s.body:void 0===s.body||null===s.body?void 0:String(s.body),r={type:"control-plane.request",requestId:i,route:t,init:{method:s.method||"GET",headers:(o=s.headers,o?o instanceof Headers?Object.fromEntries(o.entries()):Array.isArray(o)?Object.fromEntries(o.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(o).map(([e,t])=>[e,String(t)])):{}),body:a}};var o;return new Promise((s,a)=>{const o=setTimeout(()=>{n.pending.delete(i);const s=new Error(`Reverse tunnel request to ${e.id}${t} timed out.`);Object.assign(s,{statusCode:504,code:"NODE_AGENT_REVERSE_REQUEST_TIMEOUT"}),a(s)},3e4);n.pending.set(i,{resolve:s,reject:a,timer:o}),n.socket.send(JSON.stringify(r))})}proxyWebSocket(e,t,s,n,i={}){const a=e.id,r=this.sockets.get(a);if(!r)return void t.close(1011,"Reverse tunnel is not connected.");const o=Oa(),d=e=>{try{r.socket.send(JSON.stringify(e))}catch{r.streams.delete(o),t.close(1011,"Reverse tunnel send failed.")}},c={downstream:t,pendingFrames:[]};r.streams.set(o,c),d({type:"control-plane.websocket.open",streamId:o,route:s,protocols:Array.isArray(n)?n:n?[n]:[],headers:i}),t.on("message",(e,s)=>{const n=r.streams.get(o);if(n&&!n.upstream)return n.pendingFrames.length>=256?(r.streams.delete(o),t.close(1011,"Reverse websocket stream pending frame limit exceeded."),void d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Reverse websocket stream pending frame limit exceeded."})):void n.pendingFrames.push({data:e,isBinary:Boolean(s)})}),t.on("close",(e,t)=>{const s=r.streams.get(o);s&&(r.streams.delete(o),s.upstream?.close("number"==typeof e?e:1e3,Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""),d({type:"control-plane.websocket.close",streamId:o,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""}))}),t.on("error",()=>{const e=r.streams.get(o);e&&(r.streams.delete(o),e.upstream?.close(1011,"Downstream websocket failed."),d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Downstream websocket failed."}))})}attachWebSocketStream(e,t,s){const n=this.sockets.get(e),i=n?.streams.get(t);if(!n||!i)return s.close(1008,"Unknown reverse websocket stream."),!1;i.upstream=s;for(const e of i.pendingFrames.splice(0))s.send(e.data,{binary:e.isBinary});return function(e,t,s={}){const n=s.pendingFrameLimit??256;let i=!1,a=ka(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return Ta(e,1011,"WebSocket bridge pending frame limit exceeded."),void Ta(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(Ta(e,1011,"Upstream websocket did not open."),Ta(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>_a(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),_a(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};ka(t)?Ea(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};ka(e)?Ea(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&Ta(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&Ta(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),Ta(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),Ta(t,1011,"WebSocket bridge client failed.")})}(i.downstream,s,{pendingFrameLimit:256,onClientClose:()=>{n.streams.delete(t)},onClientError:()=>{n.streams.delete(t)},onUpstreamClose:()=>{n.streams.delete(t)},onUpstreamError:()=>{n.streams.delete(t)}}),!0}handleWebSocketMessage(e,t){const s="string"==typeof t.type?t.type:"";if(!s.startsWith("node-agent.websocket."))return!1;const n=this.sockets.get(e),i="string"==typeof t.streamId?t.streamId:"",a=n?.streams.get(i);return!a||("node-agent.websocket.close"===s?(n?.streams.delete(i),a.downstream.close("number"==typeof t.code?t.code:1e3,"string"==typeof t.reason?t.reason:""),!0):"node-agent.websocket.error"!==s||(n?.streams.delete(i),a.downstream.close(1011,"string"==typeof t.message?t.message:"Reverse websocket proxy failed."),!0))}}class ja{sockets=new Map;socketUrls=new Map;retries=new Map;service;tunnel;safetyIntervalMs;logger;reconnectAttempts=0;safetyReconciliations=0;timer;constructor(e,t,s={}){var n;this.service=e,this.tunnel=t,this.safetyIntervalMs=(n=s.safetyIntervalMs,Math.min(6e4,Math.max(3e4,n??45e3))),this.logger=s.logger}start(){this.sync(),this.timer=setInterval(()=>{this.safetyReconciliations+=1,this.sync()},this.safetyIntervalMs)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const e of this.sockets.values())e.close();this.sockets.clear(),this.socketUrls.clear();for(const e of this.retries.values())e.timer.cancel();this.retries.clear()}syncNow(){this.sync()}diagnostics(){return{reconnectAttempts:this.reconnectAttempts,safetyReconciliations:this.safetyReconciliations,activeConnections:this.sockets.size,pendingRetries:[...this.retries.values()].filter(e=>e.timer.pending).length,safetyIntervalMs:this.safetyIntervalMs}}sync(){const e=new Set;for(const t of this.service.listNodes()){const s=this.nodeEventsUrl(t);if(!s)continue;e.add(t.id);const n=this.retries.get(t.id);n?.timer.pending&&n.url!==s&&(n.timer.cancel(),this.retries.delete(t.id)),this.socketUrls.get(t.id)!==s&&this.sockets.has(t.id)&&(this.sockets.get(t.id)?.close(),this.sockets.delete(t.id)),this.sockets.has(t.id)||this.retries.get(t.id)?.timer.pending||this.connect(t,s)}for(const[t,s]of this.sockets)if(!e.has(t)){s.close(),this.sockets.delete(t),this.socketUrls.delete(t);const e=this.retries.get(t);e?.timer.cancel(),this.retries.delete(t)}for(const[t,s]of this.retries)e.has(t)||(s.timer.cancel(),this.retries.delete(t),this.socketUrls.delete(t))}connect(e,t){const s=Mn(t),n=s?void 0:new URL(t),i=function(e,t){const s=function(e){return"paired-hmac"===e.auth.mode&&e.auth.secret?{keyId:e.auth.keyId,secret:e.auth.secret}:void 0}(e);if(s)return Pn({nodeId:e.id,keyId:za(e,s.keyId),secret:s.secret,method:t.method,pathWithQuery:t.pathWithQuery});const n=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return n?{authorization:`Bearer ${n}`}:void 0}(e,{method:"GET",pathWithQuery:n?`${n.pathname}${n.search}`:"/api/node-agent/events"}),a=s?qn(s,"/events",void 0,i):new p.WebSocket(t,{headers:i});this.sockets.set(e.id,a),this.socketUrls.set(e.id,t),a.on("open",()=>{const s=this.retries.get(e.id);s?.timer.reset(),this.retries.set(e.id,{timer:s?.timer||new Ca,url:t})}),a.on("message",t=>{let s;try{s=JSON.parse(String(t))}catch{return}if(s&&"object"==typeof s&&!Array.isArray(s)){const t=s,n="string"==typeof t.type&&t.type.startsWith("node-agent.")?t:{type:"node-agent.event.forwarded",event:t};this.tunnel.handleMessage(e.id,n)}}),a.on("close",()=>{this.sockets.get(e.id)===a&&(this.sockets.delete(e.id),this.socketUrls.delete(e.id),this.scheduleReconnect(e.id))}),a.on("error",()=>{a.close()})}scheduleReconnect(e){const t=this.service.listNodes().find(t=>t.id===e),s=t&&this.nodeEventsUrl(t);if(!t||!s)return;const n=this.retries.get(e)??{timer:new Ca};if(n.timer.pending)return;n.url=s;const i=n.timer.schedule(()=>{this.sockets.has(e)||this.connect(t,s)});i&&(this.reconnectAttempts+=1,this.logger?.info?.({nodeId:e,url:s,attempt:i.attempt,delay:i.delay,reconnectAttempts:this.reconnectAttempts},"session-stream.node-connection.reconnect-scheduled")),this.retries.set(e,n)}nodeEventsUrl(e){const t=e.controlEndpoint||e.endpoint;if(t&&"reverse-wss"!==e.connectionMode){if(Mn(t))return t;try{const e=new URL("/api/node-agent/events",t.replace(/\/$/,""));return e.protocol="https:"===e.protocol?"wss:":"ws:",e.toString()}catch{return}}}}class Pa extends Error{code;lockPath;owner;constructor(e,t,s="process"){super(`${"control-plane"===s?"Control plane":"node-agent"===s?"Node agent":"Process"} is already running${t?.pid?` pid ${t.pid}`:""}. Lock: ${e}`),this.name="ProcessSingletonError",this.code="control-plane"===s?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===s?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function Ma(e){return s.join(e,"owner.json")}function $a(e){try{return JSON.parse(t.readFileSync(Ma(e),"utf8"))}catch{return}}function qa(e){if(!Number.isInteger(e)||Number(e)<=0)return!1;try{return process.kill(Number(e),0),!0}catch(e){return"EPERM"===e.code}}function La(e,s){t.writeFileSync(Ma(e),`${JSON.stringify(s,null,2)}\n`,"utf8")}function Ua(e,t,s){if(function(e,t,s){return"system"===e.type||("chat-bridge"===e.type?function(e,t){return"ai-session"===t.type?["read","send-message","approve","interrupt"].includes(e):"instance"===t.type?["read","start","stop","restart"].includes(e):"read"===e}(t,s):"admin"===e.role||("viewer"===e.role?"read"===t:"operator"===e.role&&function(e,t){return"read"===e||("instance"===t.type?["create","update","delete","start","stop","restart","proxy"].includes(e):"ai-session"===t.type?["send-message","approve","interrupt"].includes(e):("trigger"===t.type||"runtime"===t.type)&&["create","update","delete"].includes(e))}(t,s)))}(e,t,s))return;const n=new Error(`Actor is not allowed to ${t} ${s.type}${s.id?` ${s.id}`:""}.`);throw Object.assign(n,{statusCode:403,code:"CONTROL_PLANE_FORBIDDEN"}),n}const Fa=r.z.string().trim().min(1),Ba=r.z.object({id:Fa}).strict(),Ha=r.z.object({id:Fa,keyId:Fa}).strict(),Ga=r.z.object({id:Fa,runtimeId:Fa}).strict(),Wa=r.z.object({id:Fa,folderId:Fa}).strict(),Va=r.z.object({id:Fa,sessionId:Fa}).strict(),Ka=r.z.object({id:Fa,sessionId:Fa,queueId:Fa}).strict(),Ja=r.z.object({configHash:Fa}).strict(),Qa=r.z.object({id:Fa,configHash:Fa}).strict(),Ya=r.z.object({id:Fa,sessionId:Fa,configHash:Fa}).strict(),Xa=r.z.object({path:r.z.string().optional(),depth:r.z.coerce.number().int().min(0).max(32).optional()}).strict(),Za=r.z.object({ids:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),er=r.z.object({source:r.z.object({channel:Qe,bridgeId:r.z.string().trim().min(1).max(120).optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),action:r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("ai-session"),index:r.z.number().int().nonnegative()}).strict(),r.z.object({type:r.z.literal("instance-app-menu"),instanceId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("launch-app"),instanceId:r.z.string().trim().min(1).max(120),appId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("pending-decision"),routeId:r.z.string().trim().min(1).max(120),decision:r.z.enum(["allow","deny","skip"])}).strict()])}).strict();function tr(e){const t=er.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const sr=r.z.object({appId:r.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:r.z.string().trim().min(1).max(120).optional(),options:r.z.record(r.z.string(),r.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),nr=r.z.object({title:r.z.string().trim().min(1).max(120)}).strict(),ir=r.z.object({id:r.z.string().trim().min(1),direction:r.z.enum(["import","export"]),preset:r.z.string().trim().min(1).max(120)});function ar({app:e,service:t,events:s,appSessionAggregator:n,aiSessionAggregator:i,chatGateway:a,aiSessionAttachments:o,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}){!function({app:e,service:t,events:s}){e.get("/api/projects",async()=>({data:t.listProjects()})),e.post("/api/projects",async(e,n)=>{const i=t.createProject(e.body);return s.publish("project.created",{projectId:i.id}),n.code(201).send({data:i})}),e.get("/api/projects/:id",async e=>({data:t.requireProject(Ba.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(Ba.parse(e.params).id,e.body);return s.publish("project.updated",{projectId:n.id}),{data:n}}),e.delete("/api/projects/:id",async e=>{const n=Ba.parse(e.params).id,i=t.deleteProject(n);return s.publish("project.deleted",{projectId:n,deleted:i}),{data:{deleted:i}}}),e.get("/api/models",async()=>({data:t.listModels()})),e.post("/api/models",async(e,n)=>{const i=t.createModel(e.body);return s.publish("model.created",{modelId:i.id}),n.code(201).send({data:i})}),e.post("/api/models/reorder",async e=>{const n=Za.parse(e.body),i=t.reorderModels(n.ids);return s.publish("model.reordered",{ids:n.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(Ba.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=t.updateModel(Ba.parse(e.params).id,e.body);return s.publish("model.updated",{modelId:n.id}),{data:n}}),e.delete("/api/models/:id",async e=>{const n=Ba.parse(e.params).id,i=t.deleteModel(n);return s.publish("model.deleted",{modelId:n,deleted:i}),{data:{deleted:i}}}),e.get("/api/images",async()=>({data:t.listImages()})),e.post("/api/images",async(e,n)=>{const i=t.createImage(e.body);return s.publish("image.created",{imageId:i.id}),n.code(201).send({data:i})}),e.get("/api/images/:id",async e=>({data:t.requireImage(Ba.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(Ba.parse(e.params).id,e.body);return s.publish("image.updated",{imageId:n.id}),{data:n}}),e.delete("/api/images/:id",async e=>{const n=Ba.parse(e.params).id,i=t.deleteImage(n);return s.publish("image.deleted",{imageId:n,deleted:i}),{data:{deleted:i}}})}({app:e,service:t,events:s}),function({app:e,service:t,events:s,nodeAgentTunnel:n,nodeEventSubscriber:i,errorPayload:a}){e.get("/api/nodes",async()=>({data:t.listPublicNodes()})),e.post("/api/nodes/local/sync",async()=>{const e=await t.syncLocalNodeConnection();return i.syncNow(),s.publish("node.local-synced",{nodeId:e.id}),{data:t.requirePublicNode(e.id)}}),e.post("/api/nodes",async(e,n)=>{const a=await t.createNode(e.body);return i.syncNow(),s.publish("node.created",{nodeId:a.id}),n.code(201).send({data:t.requirePublicNode(a.id)})}),e.post("/api/nodes/:id/check",async e=>{const n=await t.checkNode(Ba.parse(e.params).id);return s.publish("node.checked",{nodeId:n.id}),{data:n}}),e.get("/api/nodes/:id/updates/jobs",async e=>({data:await t.listNodeUpdateJobs(Ba.parse(e.params).id)})),e.get("/api/nodes/:id/settings/external-listener",async e=>({data:await t.getLocalNodeExternalListener(Ba.parse(e.params).id)})),e.patch("/api/nodes/:id/settings/external-listener",async e=>{const n=Ba.parse(e.params).id,i=await t.updateLocalNodeExternalListener(n,e.body);return s.publish("node.external-listener.updated",{nodeId:n,bindScope:i.bindScope,port:i.port}),{data:i}}),e.post("/api/nodes/:id/updates/check",async e=>{const n=Ba.parse(e.params).id,i=await t.checkNodeUpdate(n,we.parse(e.body));return s.publish("node.update.checked",{nodeId:n,target:i.target}),{data:i}}),e.post("/api/nodes/:id/updates/apply",async(e,n)=>{const i=Ba.parse(e.params).id,a=await t.applyNodeUpdate(i,we.parse(e.body));return s.publish("node.update.queued",{nodeId:i,updateJobId:a.id,target:a.target}),n.code(202).send({data:a})}),e.post("/api/nodes/:id/pairing/invites",async(e,n)=>{const i=Ba.parse(e.params),a=await t.createNodePairingInvite(i.id,e.body);return s.publish("node.pairing-invite.created",{nodeId:i.id}),n.code(201).send({data:a})}),e.post("/api/nodes/:id/remotes/connect",async e=>{const n=Ba.parse(e.params),i=await t.connectNodeToControlPlane(n.id,e.body);return s.publish("node.remote-connect.requested",{nodeId:n.id}),{data:i}}),e.get("/api/nodes/:id/remotes",async e=>{const s=Ba.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=Ha.parse(e.params),i=await t.deleteNodeRemoteControlPlane(n.id,n.keyId);return s.publish("node.remote-key.deleted",{nodeId:n.id,keyId:n.keyId}),{data:i}}),e.post("/api/node-join/invites",async(e,n)=>{const i=t.createNodeJoinInvite(e.body);return s.publish("node-join.invite.created",{inviteId:i.id}),n.code(201).send({data:i})}),e.post("/api/node-join/complete",async(e,n)=>{const a=t.completeNodeJoin(e.body);return i.syncNow(),s.publish("node.joined",{nodeId:a.id}),n.code(201).send({data:t.requirePublicNode(a.id)})}),e.get("/api/nodes/:id/runtimes",async e=>({data:await t.listNodeRuntimes(Ba.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=Ba.parse(e.params),a=await t.createNodeRuntime(i.id,e.body);return s.publish("node.runtime.created",{nodeId:i.id,runtimeId:a.id}),n.code(201).send({data:a})}),e.patch("/api/nodes/:id/runtimes/:runtimeId",async e=>{const n=Ga.parse(e.params),i=await t.updateNodeRuntime(n.id,n.runtimeId,e.body);return s.publish("node.runtime.updated",{nodeId:n.id,runtimeId:i.id}),{data:i}}),e.delete("/api/nodes/:id/runtimes/:runtimeId",async e=>{const n=Ga.parse(e.params),i=await t.deleteNodeRuntime(n.id,n.runtimeId);return s.publish("node.runtime.deleted",{nodeId:n.id,runtimeId:n.runtimeId}),{data:i}}),e.post("/api/nodes/:id/runtimes/:runtimeId/check",async e=>{const n=Ga.parse(e.params),i=await t.checkNodeRuntime(n.id,n.runtimeId);return s.publish("node.runtime.checked",{nodeId:n.id,runtimeId:i.id}),{data:i}}),e.get("/api/nodes/:id/docker/images",async e=>({data:await t.listNodeDockerImages(Ba.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Ba.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=Ba.parse(e.params),n=Xa.parse(e.query);return{data:await t.listNodeFolderTree(s.id,{path:n.path,depth:n.depth})}}),e.post("/api/nodes/:id/local-folders",async(e,n)=>{const i=Ba.parse(e.params),a=await t.createNodeLocalFolder(i.id,e.body);return s.publish("node.local-folder.created",{nodeId:i.id,folderId:a.id}),n.code(201).send({data:a})}),e.delete("/api/nodes/:id/local-folders/:folderId",async e=>{const n=Wa.parse(e.params),i=await t.deleteNodeLocalFolder(n.id,n.folderId);return s.publish("node.local-folder.deleted",{nodeId:n.id,folderId:n.folderId}),{data:i}}),e.get("/api/nodes/:id",async e=>({data:t.requirePublicNode(Ba.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(Ba.parse(e.params).id,e.body);return i.syncNow(),s.publish("node.updated",{nodeId:n.id}),{data:t.requirePublicNode(n.id)}}),e.delete("/api/nodes/:id",async e=>{const n=Ba.parse(e.params).id,a=t.deleteNode(n);return i.syncNow(),s.publish("node.deleted",{nodeId:n,deleted:a}),{data:{deleted:a}}}),e.get("/api/node-runtimes",async()=>{const e=await t.listNodeRuntimesWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),function(e){const{app:t,service:s,nodeAgentTunnel:n,errorPayload:i}=e;t.get("/api/node-agent/tunnel",{websocket:!0},(e,t)=>{try{const i=Na.parse(t.query),a=s.requireNode(i.nodeId);xa(a,t),n.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:ue,nodeId:a.id,serverTime:(new Date).toISOString(),capabilities:{reverseTunnel:"request-response",lifecycleCommands:!0,instanceApiProxy:!0}})),e.on("message",t=>{let s;try{s=JSON.parse(String(t))}catch{return void e.send(JSON.stringify({type:"control-plane.error",code:"INVALID_JSON"}))}const i=s&&"object"==typeof s?s:{};n.handleMessage(a.id,i)||("node-agent.ping"!==i.type?"node-agent.identify"!==i.type?e.send(JSON.stringify({type:"control-plane.error",code:"UNSUPPORTED_MESSAGE"})):e.send(JSON.stringify({type:"control-plane.identified",nodeId:a.id,serverTime:(new Date).toISOString()})):e.send(JSON.stringify({type:"control-plane.pong",nodeId:a.id,serverTime:(new Date).toISOString()})))})}catch(t){const s=i(t);e.send(JSON.stringify({type:"control-plane.error",code:s.code,message:s.message})),e.close(1008,s.message)}}),t.get("/api/node-agent/tunnel/streams/:streamId",{websocket:!0},(e,t)=>{try{const i=Na.parse(t.query),a=s.requireNode(i.nodeId);xa(a,t);const r=t.params.streamId;n.attachWebSocketStream(a.id,r,e)}catch(t){const s=i(t);e.close(1008,s.message)}})}({app:e,service:t,nodeAgentTunnel:n,errorPayload:a})}({app:e,service:t,events:s,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}),e.get("/api/app-sessions",async e=>{const t=r.z.object({refresh:r.z.string().optional(),includeTombstones:r.z.string().optional(),instanceId:r.z.string().trim().min(1).optional(),streamId:r.z.string().trim().min(1).optional(),sinceRevision:r.z.string().optional()}).parse(e.query||{});if(void 0!==t.sinceRevision){const e=Number(t.sinceRevision);if(!Number.isInteger(e)||e<0){const e=new Error("sinceRevision must be a non-negative integer.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}if(!t.streamId){const e=new Error("streamId is required with sinceRevision.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}return{data:await n.delta({instanceId:t.instanceId,streamId:t.streamId,sinceRevision:e})}}return{data:await n.list({refresh:"true"===t.refresh||"1"===t.refresh,includeTombstones:"true"===t.includeTombstones||"1"===t.includeTombstones})}}),e.get("/api/controlled-instances",async()=>({data:await t.listControlledInstances()})),e.post("/api/controlled-instances",async(e,n)=>{const i=await t.createControlledInstance(e.body);return s.publish("instance.created",{instanceId:i.id}),n.code(201).send({data:i})}),e.get("/api/controlled-instances/:id",async e=>({data:await t.requireControlledInstance(Ba.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(Ba.parse(e.params).id,e.body);return s.publish("instance.updated",{instanceId:n.id}),{data:n}}),e.delete("/api/controlled-instances/:id",async e=>{const n=Ba.parse(e.params).id,i=await t.deleteControlledInstance(n);return s.publish("instance.deleted",{instanceId:n,deleted:i}),{data:{deleted:i}}}),e.post("/api/controlled-instances/:id/start",async e=>{const n=await t.startControlledInstance(Ba.parse(e.params).id);return s.publish("instance.started",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/stop",async e=>{const n=await t.stopControlledInstance(Ba.parse(e.params).id);return s.publish("instance.stopped",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/restart",async e=>{const n=await t.restartControlledInstance(Ba.parse(e.params).id);return s.publish("instance.restarted",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/apps/sessions",async e=>{const n=Ba.parse(e.params),i=sr.parse(e.body),a=await t.launchAppSession(n.id,i.appId,{...i.options,...i.cwdFolderId?{cwdFolderId:i.cwdFolderId}:{}});return s.publish("instance.app-session.launched",{instanceId:n.id,sessionId:"string"==typeof a.id?a.id:void 0,appId:i.appId}),{data:a}}),e.post("/api/controlled-instances/:id/apps/sessions/:sessionId/stop",async e=>{const n=Va.parse(e.params),i=await t.stopAppSession(n.id,n.sessionId);return s.publish("instance.app-session.stopped",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.patch("/api/controlled-instances/:id/apps/sessions/:sessionId",async e=>{const n=Va.parse(e.params),i=nr.parse(e.body),a=await t.renameAppSession(n.id,n.sessionId,i.title);return s.publish("instance.app-session.renamed",{instanceId:n.id,sessionId:n.sessionId,title:i.title}),{data:a}}),e.get("/api/controlled-instances/:id/app-sessions",async e=>{const t=Ba.parse(e.params),s=(await n.list()).instances.find(e=>e.instanceId===t.id);return{data:s?.appSessions||{runningCount:0,problemCount:0,sessions:[],updatedAt:(new Date).toISOString()}}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/messages",async e=>{const n=Va.parse(e.params),i=x.parse(e.body||{}),a=o.resolveRefs(i.attachments,n.id,n.sessionId),r=await t.sendAiSessionMessage(n.id,n.sessionId,i.message,i.mode,a);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:r}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const s=Va.parse(e.params);return{data:await t.aiSessionQueue(s.id,s.sessionId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/steer",async e=>{const n=Ka.parse(e.params),i=await t.steerAiSessionQueuedMessage(n.id,n.sessionId,n.queueId);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/retry",async e=>{const s=Ka.parse(e.params);return{data:await t.retryAiSessionQueuedMessage(s.id,s.sessionId,s.queueId)}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId",async e=>{const s=Ka.parse(e.params);return{data:await t.removeAiSessionQueuedMessage(s.id,s.sessionId,s.queueId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/interrupt",async e=>{const n=Va.parse(e.params),i=await t.interruptAiSession(n.id,n.sessionId);return s.publish("instance.ai-session.interrupted",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/approval",async e=>{const n=Va.parse(e.params),i=D.parse(e.body||{}),a=await t.resolveAiSessionApproval(n.id,n.sessionId,i.decision);return s.publish("instance.ai-session.approval-resolved",{instanceId:n.id,sessionId:n.sessionId,decision:i.decision}),{data:a}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const n=ir.parse(e.params),i=await t.syncInstanceConfig(n.id,n.direction,n.preset);return s.publish("instance.config-synced",{instanceId:n.id,direction:n.direction,preset:n.preset}),{data:i}}),e.get("/api/instance-board",async()=>{const e=await t.boardWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),e.get("/api/ai-sessions",async(e,t)=>{const s=e.query;if(void 0!==s.sinceRevision){const e=Number(s.sinceRevision);if(!Number.isInteger(e)||e<0)return t.code(400).send({error:"invalid_since_revision"});try{return s.streamId?{data:await i.delta({instanceId:s.instanceId,streamId:s.streamId,sinceRevision:e})}:t.code(400).send({error:"stream_id_required"})}catch(e){if("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED"===e.message)return t.code(400).send({error:"instance_id_required"});throw e}}return{data:await i.list({refresh:"true"===s.refresh||"1"===s.refresh})}}),e.get("/api/triggers",async()=>({data:await t.listTriggers()})),e.post("/api/triggers",async(e,n)=>{const i=t.createTrigger(e.body);return s.publish("trigger.created",{configHash:i.configHash}),n.code(201).send({data:i})}),e.delete("/api/triggers/:configHash",async e=>{const n=Ja.parse(e.params),i=await t.deleteTrigger(n.configHash);return s.publish("trigger.deleted",{configHash:n.configHash}),{data:i}}),e.post("/api/triggers/:configHash/apply",async e=>{const n=Ja.parse(e.params),i=await t.applyTrigger(n.configHash,e.body||{});return s.publish("trigger.applied",{configHash:n.configHash}),{data:i}}),e.get("/api/controlled-instances/:id/triggers",async e=>{const s=Ba.parse(e.params);return{data:await t.listInstanceTriggers(s.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const n=Va.parse(e.params),i=await t.bindAiSessionTrigger(n.id,n.sessionId,e.body||{});return s.publish("instance.ai-session.trigger-bound",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers/:configHash",async e=>{const n=Ya.parse(e.params),i=await t.unbindAiSessionTrigger(n.id,n.sessionId,n.configHash);return s.publish("instance.ai-session.trigger-unbound",{instanceId:n.id,sessionId:n.sessionId,configHash:n.configHash}),{data:i}}),e.post("/api/controlled-instances/:id/triggers/:configHash/run",async e=>{const n=Qa.parse(e.params),i=await t.runInstanceTrigger(n.id,n.configHash,e.body||{});return s.publish("instance.trigger.run",{instanceId:n.id,configHash:n.configHash}),{data:i}}),function({app:e,service:t,chatGateway:s}){e.get("/api/chat/sessions",async()=>({data:t.listChatSessions()})),e.get("/api/chat/sessions/:id",async e=>({data:t.requireChatSession(Ba.parse(e.params).id)})),e.get("/api/chat-gateway/status",async()=>({data:s.status()})),e.post("/api/chat-gateway/poll-ai-sessions",async()=>({data:await s.pollAiSessionsNow()})),e.get("/api/chat-gateway/bridges",async()=>({data:t.listChatBridges()})),e.post("/api/chat-gateway/bridges",async e=>({data:t.createChatBridge(e.body)})),e.patch("/api/chat-gateway/bridges/:id",async n=>{const i=Ba.parse(n.params).id,a=t.requireChatBridge(i),r=zs.parse(n.body),o=t.updateChatBridge(i,n.body),d="boolean"==typeof r.enabled?r.enabled:void 0;return e.log.info({component:"control-plane-chat-gateway",action:"bridge-patch",bridgeId:i,hasEnabled:void 0!==d,requestedEnabled:d,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),void 0!==d&&d!==a.enabled&&(d?s.startBridge(i):s.stopBridge(i)),{data:o}}),e.post("/api/chat-gateway/bridges/:id/start",async n=>{const i=Ba.parse(n.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!0}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-start-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:s.startBridge(i)}}),e.post("/api/chat-gateway/bridges/:id/stop",async n=>{const i=Ba.parse(n.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!1}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-stop-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:s.stopBridge(i)}}),e.delete("/api/chat-gateway/bridges/:id",async e=>({data:{deleted:t.deleteChatBridge(Ba.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(Ze.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(tr(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:a})}const rr=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),or=new Set(["content-encoding"]);function dr(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!rr.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function cr(e){return Object.fromEntries(Object.entries(dr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function lr(e){const t=e["sec-websocket-protocol"],s=(Array.isArray(t)?t.join(","):"string"==typeof t?t:"").split(",").map(e=>e.trim()).filter(Boolean);return s.length?s:void 0}function pr(e,t){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();rr.has(t)||or.has(t)||e.header(s,n)}return e.code(t.status).send(t.body)}function ur(e){return`/instances/${encodeURIComponent(e)}`}function gr(e,t,s,n){const i=function(e,t){const s=e.split("?",1)[0]||"/";return t.toLowerCase().includes("text/html")&&!s.startsWith("/api/")}(n,Object.entries(t.headers).find(([e])=>"content-type"===e.toLowerCase())?.[1]||"")?Buffer.from(function(e,t){if(e.includes("__TASK_HANDOFF_PUBLIC_BASE__"))return e;const s=`<script>window.__TASK_HANDOFF_PUBLIC_BASE__=${JSON.stringify(t)};<\/script>`;return/<head(\s[^>]*)?>/i.test(e)?e.replace(/<head(\s[^>]*)?>/i,e=>`${e}\n${s}`):`${s}\n${e}`}(t.body.toString("utf8"),ur(s)),"utf8"):t.body;return pr(e,{...t,body:i})}function hr(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):e instanceof URLSearchParams?e.toString():"string"==typeof e?e:null==e?void 0:JSON.stringify(e)}function mr(e,t){if("string"!=typeof t||!e.hasContentTypeParser(t))try{e.addContentTypeParser(t,{parseAs:"buffer"},(e,t,s)=>s(null,t))}catch(e){if(e instanceof Error&&/Content type parser .* already present/i.test(e.message))return;throw e}}function Ir(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function fr(e,t,s){const n=encodeURIComponent(e),i=encodeURIComponent(t),a=s.vnc&&"object"==typeof s.vnc?s.vnc:{};return"kasmvnc"===("string"==typeof a.backend?a.backend:"")?`/apps/access/vnc/proxy/api/apps/sessions/${i}/web/?token=${n}`:`/apps/access/vnc/proxy/api/novnc/vnc.html?token=${n}&path=${encodeURIComponent(`apps/access/vnc/proxy/api/apps/sessions/${i}/vnc?token=${n}`)}&autoconnect=1&resize=scale`}class yr{snapshots=new Map;history=new Map;advertisedStreams=new Map;recoveries=new Map;listeners=new Set;bootstrap;logger;recoverDelta;recoverSnapshot;onRecoveredEvent;recoveryCount=0;counters={streamResets:0,gaps:0,deltaRecoveries:0,snapshotRecoveries:0,recoveryFailures:0};constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger,this.recoverDelta=e.recoverDelta,this.recoverSnapshot=e.recoverSnapshot,this.onRecoveredEvent=e.onRecoveredEvent}handleEvent(e){const t=e.type===b?K:e.type===v?J:e.type===w?Q:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===b&&this.applySnapshot(s.data),e.type===v&&this.applyPatch(s.data),e.type===w&&this.applyRemoved(s.data),!0):(this.logger?.warn?.({eventType:e.type,issues:s.error.issues,errorCode:"AI_SESSION_EVENT_INVALID"},"ai-session.aggregator.event.invalid"),!0)}onSnapshot(e){return this.listeners.add(e),()=>this.listeners.delete(e)}applySnapshot(e){const t=this.advertisedStreams.get(e.meta.instanceId)?.streamId;return t&&t!==e.meta.streamId?(this.logger?.warn?.({instanceId:e.meta.instanceId,advertisedStreamId:t,streamId:e.meta.streamId,revision:e.meta.revision},"ai-session.aggregator.snapshot.obsolete-stream"),!1):this.apply({type:b,payload:e})}advertiseStream(e,t){return this.advertisedStreams.set(e,t),this.recoverStream(e,t.streamId,t.latestRevision).catch(s=>{this.counters.recoveryFailures+=1,this.logger?.warn?.({instanceId:e,streamId:t.streamId,error:s instanceof Error?s.message:String(s)},"ai-session.aggregator.recovery.failed")})}diagnostics(){return{...this.counters,recoveriesStarted:this.recoveryCount,activeRecoveries:this.recoveries.size}}removeInstance(e){this.snapshots.delete(e),this.history.delete(e),this.advertisedStreams.delete(e)}applyPatch(e){return this.apply({type:v,payload:e})}applyRemoved(e){return this.apply({type:w,payload:e})}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view()}async streamDescriptors(){return 0===this.snapshots.size&&await this.bootstrapFromInstances(),[...this.snapshots.entries()].map(([e,t])=>({topic:"ai.sessions",instanceId:e,streamId:t.streamId,latestRevision:t.revision,earliestRetainedRevision:this.history.get(e)?.[0]?.payload.meta.revision??t.revision+1}))}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0,n=this.snapshots.get(t)?.streamId||"unavailable",i=this.history.get(t)?.[0]?.payload.meta.revision??s+1;if(e.streamId!==n||e.sinceRevision>s)return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!1,events:[]});const a=(this.history.get(t)??[]).filter(t=>t.payload.meta.revision>e.sinceRevision),r=!a.length||a[0].payload.meta.revision!==e.sinceRevision+1;return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:r,events:r?[]:a.map(({type:e,payload:t})=>({type:e,payload:t}))})}apply(e,t=!1){const{meta:s}=e.payload,n=this.advertisedStreams.get(s.instanceId)?.streamId;if(n&&n!==s.streamId)return this.logger?.warn?.({instanceId:s.instanceId,advertisedStreamId:n,streamId:s.streamId,revision:s.revision},"ai-session.aggregator.event.obsolete-stream"),!1;const i=this.recoveries.get(s.instanceId);if(i&&!t)return i.streamId===s.streamId&&(i.highWater=Math.max(i.highWater,s.revision)),!1;const a=this.snapshots.get(s.instanceId);e.type===b&&a&&a.streamId!==s.streamId&&(this.counters.streamResets+=1,this.logger?.info?.({instanceId:s.instanceId,previousStreamId:a.streamId,streamId:s.streamId,previousRevision:a.revision,revision:s.revision,traceId:s.traceId,resetCount:this.counters.streamResets},"ai-session.aggregator.stream-reset"));const r=function(e,t){const{meta:s}=t.payload;if(t.type===b)return e?.streamId===s.streamId&&s.revision===e.revision?{kind:"duplicate",projection:e}:e?.streamId===s.streamId&&s.revision<e.revision?{kind:"stale",projection:e}:{kind:"applied",projection:B.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:t.payload.snapshot})};if(!e||e.streamId!==s.streamId)return{kind:"snapshot-required",projection:e,streamId:s.streamId};if(s.revision===e.revision)return{kind:"duplicate",projection:e};if(s.revision<e.revision)return{kind:"stale",projection:e};if(s.revision!==e.revision+1||s.previousRevision!==e.revision)return{kind:"gap",projection:e,expectedRevision:e.revision+1,receivedRevision:s.revision};const n=new Map(e.snapshot.sessions.map(e=>[e.id,e]));if(t.type===v){for(const e of t.payload.upserted)n.set(e.id,e);for(const e of t.payload.removed)n.delete(e)}else for(const e of t.payload.sessionIds)n.delete(e);const i=[...n.values()],a=F.parse({runningCount:i.filter(e=>"running"===e.status).length,waitingCount:i.filter(e=>"waiting"===e.status).length,staleCount:e.snapshot.staleCount,sessions:i,updatedAt:s.generatedAt});return{kind:"applied",projection:B.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:a})}}(a,e);if("applied"!==r.kind)return this.logger?.warn?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,currentRevision:a?.revision,revision:s.revision,outcome:r.kind},`ai-session.aggregator.event.${r.kind}`),"gap"!==r.kind&&"snapshot-required"!==r.kind||("gap"===r.kind&&(this.counters.gaps+=1),this.recoverStream(s.instanceId,s.streamId,s.revision).catch(e=>{this.logger?.warn?.({instanceId:s.instanceId,streamId:s.streamId,revision:s.revision,error:e instanceof Error?e.message:String(e)},"ai-session.aggregator.recovery.failed")})),!1;const o=r.projection;this.snapshots.set(s.instanceId,o),this.rememberEvent(s.instanceId,e);const d={instanceId:s.instanceId,streamId:o.streamId,aiSessions:o.snapshot,revision:o.revision,lastEventAt:o.lastEventAt};for(const e of this.listeners)try{e(d)}catch{this.listeners.delete(e)}return this.logger?.info?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,revision:s.revision,reason:s.reason,sessionCount:o.snapshot.sessions.length},"ai-session.aggregator.event.accepted"),!0}async bootstrapFromInstances(){this.logger?.info?.({},"ai-session.aggregator.bootstrap");const e=new Map(this.snapshots),t=await this.bootstrap(),s=new Set(t.instances.map(e=>e.instanceId));for(const[t,n]of e)s.has(t)||this.snapshots.get(t)!==n||(this.snapshots.delete(t),this.history.delete(t),this.advertisedStreams.delete(t));for(const s of t.instances){const t=this.snapshots.get(s.instanceId);if(t!==e.get(s.instanceId))continue;const n=this.advertisedStreams.get(s.instanceId)?.streamId;n&&n!==s.streamId||t?.streamId===s.streamId&&t.revision>=s.revision||(t?.streamId!==s.streamId&&this.history.delete(s.instanceId),this.snapshots.set(s.instanceId,{streamId:s.streamId,snapshot:s.aiSessions,revision:s.revision,lastEventAt:s.lastEventAt}))}}recoverStream(e,t,s){const n=this.recoveries.get(e);if(n)return n.streamId===t?n.highWater=Math.max(n.highWater,s):(n.streamId=t,n.highWater=s),n.promise;if(!this.recoverDelta||!this.recoverSnapshot)return Promise.resolve();const i={promise:Promise.resolve(),streamId:t,highWater:s},a=++this.recoveryCount;return this.logger?.info?.({instanceId:e,highWater:s,recoveryNumber:a},"ai-session.aggregator.recovery.started"),i.promise=(async()=>{for(;;){const t=this.advertisedStreams.get(e);if(!t)return;i.streamId!==t.streamId&&(i.streamId=t.streamId,i.highWater=t.latestRevision);const s=this.snapshots.get(e);if(s&&s.streamId===t.streamId){if(s.revision<Math.max(t.latestRevision,i.highWater)){const t=await this.recoverDelta(e,s.streamId,s.revision);if(this.advertisedStreams.get(e)?.streamId!==t.streamId||i.streamId!==t.streamId)continue;if(t.syncRequired){const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:b,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`ais_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot-fallback"},"ai-session.aggregator.recovery.snapshot")}else{for(const e of t.events)this.apply(e,!0)&&this.onRecoveredEvent?.(e);this.counters.deltaRecoveries+=1,i.highWater=Math.max(i.highWater,t.latestRevision),this.logger?.info?.({instanceId:e,streamId:t.streamId,sinceRevision:t.sinceRevision,latestRevision:t.latestRevision,eventCount:t.events.length,recoveryNumber:a,outcome:"delta"},"ai-session.aggregator.recovery.delta")}}}else{const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:b,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`ais_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot"},"ai-session.aggregator.recovery.snapshot")}const n=this.snapshots.get(e),r=this.advertisedStreams.get(e);if(n&&r&&n.streamId===r.streamId&&n.revision>=Math.max(r.latestRevision,i.highWater))return void this.logger?.info?.({instanceId:e,streamId:n.streamId,revision:n.revision,recoveryNumber:a,outcome:"success"},"ai-session.aggregator.recovery.completed")}})().finally(()=>this.recoveries.delete(e)),this.recoveries.set(e,i),i.promise}rememberEvent(e,t){const s=this.history.get(e)??[],n=s.length&&s[0].payload.meta.streamId!==t.payload.meta.streamId?[]:s;n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const s=t.filter(e=>e.createdAtMs>=Date.now()-36e5);s.length?this.history.set(e,s):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED")}view(){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([e,t])=>({instanceId:e,streamId:t.streamId,aiSessions:t.snapshot,revision:t.revision,lastEventAt:t.lastEventAt}))}}}class br{snapshots=new Map;history=new Map;advertisedStreams=new Map;recoveries=new Map;bootstrap;logger;recoverDelta;recoverSnapshot;onRecoveredEvent;recoveryCount=0;counters={streamResets:0,gaps:0,deltaRecoveries:0,snapshotRecoveries:0,recoveryFailures:0};constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger,this.recoverDelta=e.recoverDelta,this.recoverSnapshot=e.recoverSnapshot,this.onRecoveredEvent=e.onRecoveredEvent}handleEvent(e){const t=e.type===zt?Lt:e.type===Ot?Ut:e.type===Rt?Ft:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===zt&&this.applySnapshot(s.data),e.type===Ot&&this.applyPatch(s.data),e.type===Rt&&this.applyRemoved(s.data),!0):(this.logger?.warn?.({eventType:e.type,issues:s.error.issues,errorCode:"APP_SESSION_EVENT_INVALID"},"app-session.aggregator.event.invalid"),!0)}applySnapshot(e){const t=this.advertisedStreams.get(e.meta.instanceId)?.streamId;return t&&t!==e.meta.streamId?(this.logger?.warn?.({instanceId:e.meta.instanceId,advertisedStreamId:t,streamId:e.meta.streamId,revision:e.meta.revision},"app-session.aggregator.snapshot.obsolete-stream"),!1):this.apply({type:zt,payload:e})}advertiseStream(e,t){return this.advertisedStreams.set(e,t),this.recoverStream(e,t.streamId,t.latestRevision).catch(s=>{this.counters.recoveryFailures+=1,this.logger?.warn?.({instanceId:e,streamId:t.streamId,error:s instanceof Error?s.message:String(s)},"app-session.aggregator.recovery.failed")})}diagnostics(){return{...this.counters,recoveriesStarted:this.recoveryCount,activeRecoveries:this.recoveries.size}}removeInstance(e){this.snapshots.delete(e),this.history.delete(e),this.advertisedStreams.delete(e)}applyPatch(e){return this.apply({type:Ot,payload:e})}applyRemoved(e){return this.apply({type:Rt,payload:e})}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view({includeTombstones:e.includeTombstones})}async streamDescriptors(){return 0===this.snapshots.size&&await this.bootstrapFromInstances(),[...this.snapshots.entries()].map(([e,t])=>({topic:"app.sessions",instanceId:e,streamId:t.streamId,latestRevision:t.revision,earliestRetainedRevision:this.history.get(e)?.[0]?.payload.meta.revision??t.revision+1}))}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0,n=this.snapshots.get(t)?.streamId||"unavailable",i=this.history.get(t)?.[0]?.payload.meta.revision??s+1;if(e.streamId!==n||e.sinceRevision>s)return Bt.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Bt.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!1,events:[]});const a=(this.history.get(t)??[]).filter(t=>t.payload.meta.revision>e.sinceRevision),r=!a.length||a[0].payload.meta.revision!==e.sinceRevision+1;return Bt.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:r,events:r?[]:a.map(({type:e,payload:t})=>({type:e,payload:t}))})}apply(e,t=!1){const{meta:s}=e.payload,n=this.advertisedStreams.get(s.instanceId)?.streamId;if(n&&n!==s.streamId)return this.logger?.warn?.({instanceId:s.instanceId,advertisedStreamId:n,streamId:s.streamId,revision:s.revision},"app-session.aggregator.event.obsolete-stream"),!1;const i=this.recoveries.get(s.instanceId);if(i&&!t)return i.streamId===s.streamId&&(i.highWater=Math.max(i.highWater,s.revision)),!1;const a=this.snapshots.get(s.instanceId);e.type===zt&&a&&a.streamId!==s.streamId&&(this.counters.streamResets+=1,this.logger?.info?.({instanceId:s.instanceId,previousStreamId:a.streamId,streamId:s.streamId,previousRevision:a.revision,revision:s.revision,traceId:s.traceId,resetCount:this.counters.streamResets},"app-session.aggregator.stream-reset"));const r=function(e,t){const{meta:s}=t.payload;if(t.type===zt)return e?.streamId===s.streamId&&s.revision===e.revision?{kind:"duplicate",projection:e}:e?.streamId===s.streamId&&s.revision<e.revision?{kind:"stale",projection:e}:{kind:"applied",projection:Mt.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:t.payload.snapshot})};if(!e||e.streamId!==s.streamId)return{kind:"snapshot-required",projection:e,streamId:s.streamId};if(s.revision===e.revision)return{kind:"duplicate",projection:e};if(s.revision<e.revision)return{kind:"stale",projection:e};if(s.revision!==e.revision+1||s.previousRevision!==e.revision)return{kind:"gap",projection:e,expectedRevision:e.revision+1,receivedRevision:s.revision};const n=[...e.snapshot.sessions];if(t.type===Ot){const e=n.findIndex(e=>e.id===t.payload.session.id);e<0?n.push(t.payload.session):n[e]=t.payload.session}else{const e=n.findIndex(e=>e.id===t.payload.sessionId);e>=0&&n.splice(e,1),t.payload.tombstone&&n.push(t.payload.tombstone)}const i=Ht(n,s.generatedAt);return{kind:"applied",projection:Mt.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:i})}}(a,e);if("applied"!==r.kind)return this.logger?.warn?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,currentRevision:a?.revision,revision:s.revision,outcome:r.kind},`app-session.aggregator.event.${r.kind}`),"gap"!==r.kind&&"snapshot-required"!==r.kind||("gap"===r.kind&&(this.counters.gaps+=1),this.recoverStream(s.instanceId,s.streamId,s.revision).catch(e=>{this.logger?.warn?.({instanceId:s.instanceId,streamId:s.streamId,revision:s.revision,error:e instanceof Error?e.message:String(e)},"app-session.aggregator.recovery.failed")})),!1;const o=r.projection;return this.snapshots.set(s.instanceId,o),this.rememberEvent(s.instanceId,e),this.logger?.info?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,revision:s.revision,reason:s.reason,sessionCount:o.snapshot.sessions.length,runningCount:o.snapshot.runningCount,problemCount:o.snapshot.problemCount},"app-session.aggregator.event.accepted"),!0}async bootstrapFromInstances(){this.logger?.info?.({},"app-session.aggregator.bootstrap");const e=new Map(this.snapshots),t=await this.bootstrap(),s=new Set(t.instances.map(e=>e.instanceId));for(const[t,n]of e)s.has(t)||this.snapshots.get(t)!==n||(this.snapshots.delete(t),this.history.delete(t),this.advertisedStreams.delete(t));for(const s of t.instances){const t=this.snapshots.get(s.instanceId);if(t!==e.get(s.instanceId))continue;const n=this.advertisedStreams.get(s.instanceId)?.streamId;n&&n!==s.streamId||t?.streamId===s.streamId&&t.revision>=s.revision||(t?.streamId!==s.streamId&&this.history.delete(s.instanceId),this.snapshots.set(s.instanceId,{streamId:s.streamId,snapshot:s.appSessions,revision:s.revision,lastEventAt:s.lastEventAt}))}}recoverStream(e,t,s){const n=this.recoveries.get(e);if(n)return n.streamId===t?n.highWater=Math.max(n.highWater,s):(n.streamId=t,n.highWater=s),n.promise;if(!this.recoverDelta||!this.recoverSnapshot)return Promise.resolve();const i={promise:Promise.resolve(),streamId:t,highWater:s},a=++this.recoveryCount;return this.logger?.info?.({instanceId:e,highWater:s,recoveryNumber:a},"app-session.aggregator.recovery.started"),i.promise=(async()=>{for(;;){const t=this.advertisedStreams.get(e);if(!t)return;i.streamId!==t.streamId&&(i.streamId=t.streamId,i.highWater=t.latestRevision);const s=this.snapshots.get(e);if(s&&s.streamId===t.streamId){if(s.revision<Math.max(t.latestRevision,i.highWater)){const t=await this.recoverDelta(e,s.streamId,s.revision);if(this.advertisedStreams.get(e)?.streamId!==t.streamId||i.streamId!==t.streamId)continue;if(t.syncRequired){const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:zt,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`aps_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot-fallback"},"app-session.aggregator.recovery.snapshot")}else{for(const e of t.events)this.apply(e,!0)&&this.onRecoveredEvent?.(e);this.counters.deltaRecoveries+=1,i.highWater=Math.max(i.highWater,t.latestRevision),this.logger?.info?.({instanceId:e,streamId:t.streamId,sinceRevision:t.sinceRevision,latestRevision:t.latestRevision,eventCount:t.events.length,recoveryNumber:a,outcome:"delta"},"app-session.aggregator.recovery.delta")}}}else{const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:zt,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`aps_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot"},"app-session.aggregator.recovery.snapshot")}const n=this.snapshots.get(e),r=this.advertisedStreams.get(e);if(n&&r&&n.streamId===r.streamId&&n.revision>=Math.max(r.latestRevision,i.highWater))return void this.logger?.info?.({instanceId:e,streamId:n.streamId,revision:n.revision,recoveryNumber:a,outcome:"success"},"app-session.aggregator.recovery.completed")}})().finally(()=>this.recoveries.delete(e)),this.recoveries.set(e,i),i.promise}rememberEvent(e,t){const s=this.history.get(e)??[],n=s.length&&s[0].payload.meta.streamId!==t.payload.meta.streamId?[]:s;n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const s=t.filter(e=>e.createdAtMs>=Date.now()-36e5);s.length?this.history.set(e,s):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("APP_SESSION_DELTA_INSTANCE_ID_REQUIRED")}visibleSnapshot(e){return Ht(e.sessions.filter(e=>sn("string"==typeof e.status?e.status:void 0)),e.updatedAt)}view(e={}){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([t,s])=>({instanceId:t,streamId:s.streamId,appSessions:e.includeTombstones?s.snapshot:this.visibleSnapshot(s.snapshot),revision:s.revision,lastEventAt:s.lastEventAt}))}}}function vr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function wr(e){const t=process.env[e]?.trim();return t||void 0}function Sr(){try{const e=s.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function Ar(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:Sr(),protocolVersion:ue,buildId:wr("TASK_HANDOFF_BUILD_ID"),builtAt:wr("TASK_HANDOFF_BUILT_AT"),gitCommit:wr("TASK_HANDOFF_GIT_COMMIT"),imageRef:wr("TASK_HANDOFF_IMAGE_REF"),imageDigest:wr("TASK_HANDOFF_IMAGE_DIGEST")}}const kr={".css":"text/css; charset=utf-8",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".png":"image/png",".svg":"image/svg+xml",".webp":"image/webp"};function Tr(e,n){const i=s.resolve(e),a=s.resolve(i,n);if(a===i||a.startsWith(`${i}${s.sep}`))return t.existsSync(a)&&t.statSync(a).isFile()?a:void 0}function Er(e,n,i){const a=Tr(n,i);return a?(e.header("cache-control","no-store"),e.type(kr[s.extname(a).toLowerCase()]||"application/octet-stream"),e.send(t.createReadStream(a))):e.code(404).send({error:{code:"STATIC_FILE_NOT_FOUND",message:`${i} was not found in the Control Plane UI static directory.`}})}function _r(e){if(e instanceof r.z.ZodError)return{statusCode:400,code:"VALIDATION_ERROR",message:e.issues.map(e=>`${e.path.join(".")||"body"}: ${e.message}`).join("; ")};const t=e&&"object"==typeof e?e:{};return{statusCode:"number"==typeof t.statusCode?t.statusCode:500,code:"string"==typeof t.code?t.code:"CONTROL_PLANE_ERROR",message:e instanceof Error?e.message:String(e)}}async function Cr(e,t){if(!e.enabled())return{type:"system",reason:"auth-disabled"};const s=await e.userForSessionToken(t);return s?{type:"user",userId:s.id,role:s.role}:void 0}function Nr(e,t){const s=t.split("?")[0]||"/";if(!s.startsWith("/api/"))return;if(s.startsWith("/api/auth/")||"/api/health"===s||"/api/events"===s)return;const n=function(e){return"GET"===e||"HEAD"===e?"read":"POST"===e?"create":"PATCH"===e||"PUT"===e?"update":"DELETE"===e?"delete":"read"}(e);return s.startsWith("/api/control-plane/settings")?{action:"GET"===e?"read":"manage-settings",resource:{type:"control-plane-settings"}}:s.startsWith("/api/models")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"model"}}:s.startsWith("/api/chat-gateway/bridges")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"chat-bridge"}}:"/api/node-join/invites"===s?{action:"manage-node-auth",resource:{type:"node"}}:s.startsWith("/api/nodes")?/\/(runtimes|local-folders)(\/|$)/.test(s)?{action:n,resource:{type:"runtime"}}:{action:"GET"===e?"read":"manage-node-auth",resource:{type:"node"}}:s.startsWith("/api/controlled-instances")?s.includes("/ai-sessions/")?s.endsWith("/approval")?{action:"approve",resource:{type:"ai-session"}}:s.endsWith("/interrupt")?{action:"interrupt",resource:{type:"ai-session"}}:s.includes("/messages")||s.includes("/queue/")?{action:"send-message",resource:{type:"ai-session"}}:{action:"read",resource:{type:"ai-session"}}:s.includes("/start")?{action:"start",resource:{type:"instance"}}:s.includes("/stop")?{action:"stop",resource:{type:"instance"}}:s.includes("/restart")?{action:"restart",resource:{type:"instance"}}:s.includes("/config-sync/")?{action:"update",resource:{type:"instance"}}:{action:n,resource:{type:"instance"}}:s.startsWith("/api/projects")?{action:n,resource:{type:"project"}}:s.startsWith("/api/images")?{action:n,resource:{type:"runtime"}}:s.startsWith("/api/triggers")?{action:n,resource:{type:"trigger"}}:{action:"read",resource:{type:"control-plane-settings"}}}async function zr(e={}){const r=at(e.dataDir),o=n({logger:e.logger??!0,bodyLimit:67108864}),d=new Vn(r,{...e.service,logger:o.log}),c=new Nt(r,e.auth),l=new ba,p=new yr({bootstrap:()=>d.bootstrapAiSessionsFromInstances(),logger:o.log,recoverDelta:(e,t,s)=>d.recoverAiSessionDelta(e,t,s),recoverSnapshot:e=>d.recoverAiSessionSnapshot(e),onRecoveredEvent:e=>l.publish(e.type,e.payload)}),u=new br({bootstrap:()=>d.bootstrapAppSessionsFromInstances(),logger:o.log,recoverDelta:(e,t,s)=>d.recoverAppSessionDelta(e,t,s),recoverSnapshot:e=>d.recoverAppSessionSnapshot(e),onRecoveredEvent:e=>l.publish(e.type,e.payload)});d.setAppSessionSnapshotProvider(e=>u.list(e)),d.setAiSessionSnapshotProvider(e=>p.list(e)),l.on(e=>{if("instance.deleted"===e.type){const t=e.payload&&"object"==typeof e.payload&&"instanceId"in e.payload?String(e.payload.instanceId||""):"";t&&(u.removeInstance(t),p.removeInstance(t))}u.handleEvent(e),p.handleEvent(e)});const g=new Aa,h=new Da(l,{onStreamsHello:(e,t)=>{for(const s of t.streams)"ai.sessions"===s.topic&&p.advertiseStream(e,s),"app.sessions"===s.topic&&u.advertiseStream(e,s)}});d.setNodeAgentTransport(h),d.init(),await d.syncLocalNodeConnection().catch(()=>{}),c.init();const m=new Fi(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const I=new ja(d,h,{safetyIntervalMs:Number(process.env.TASK_HANDOFF_EVENT_CONNECTION_SAFETY_INTERVAL_MS)||void 0,logger:o.log});I.start(),await o.register(i),await o.register(a),o.addHook("onClose",async()=>{I.stop(),m.stopAll()});const f=(y=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(y||"").trim().toLowerCase()));var y;o.setErrorHandler((e,t,s)=>{const n=_r(e);(f||n.statusCode>=500)&&(n.statusCode>=500?o.log.error.bind(o.log):o.log.warn.bind(o.log))({method:t.method,url:t.url,statusCode:n.statusCode,errorCode:n.code,error:n.message},"control plane request failed"),s.code(n.statusCode).send({error:{code:n.code,message:n.message}})}),o.addHook("preHandler",async(e,t)=>{if(!c.enabled()||function(e,t){const s=t.split("?")[0];return!("/api/health"!==s&&!s.startsWith("/api/auth/"))||"GET"===e&&"/install-node-agent.sh"===s||"POST"===e&&"/api/node-join/complete"===s||"/api/app-access/session"===s||!!s.startsWith("/apps/access/")||!("/favicon.ico"!==s&&!s.startsWith("/assets/"))||"GET"===e&&!s.startsWith("/api/")&&!s.startsWith("/instances/")}(e.method,e.url)){const t=await Cr(c,e.cookies[It]),s=Nr(e.method,e.url);return void(s&&Ua(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await Cr(c,e.cookies[It]);if(!s)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const n=Nr(e.method,e.url);n&&Ua(s,n.action,n.resource)});const b=s.resolve(e.staticDir||process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||s.resolve(process.cwd(),"packages","control-plane-ui","dist"));return t.existsSync(b)&&(o.get("/assets/*",async(e,t)=>{const n=e.params["*"];return Er(t,b,s.join("assets",n||""))}),o.get("/favicon.ico",async(e,t)=>Tr(b,"favicon.ico")?Er(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:ue,build:Ar(),dataDir:r.dataDir,serverTime:(new Date).toISOString()}})),o.get("/api/session-streams/diagnostics",async()=>({data:{aiSessions:p.diagnostics(),appSessions:u.diagnostics(),nodeConnections:I.diagnostics()}})),o.get("/install-node-agent.sh",async(e,t)=>(t.header("cache-control","no-store"),t.type("text/x-shellscript; charset=utf-8"),vr`#!/bin/sh
|
|
2
2
|
set -eu
|
|
3
3
|
|
|
4
4
|
CONTROL_PLANE_URL=""
|
|
@@ -214,4 +214,4 @@ fi
|
|
|
214
214
|
|
|
215
215
|
echo "TaskHandoff node-agent is installed and running."
|
|
216
216
|
echo "Service: task-handoff-node-agent.service"
|
|
217
|
-
`)),o.get("/api/events",{websocket:!0},e=>{l.connect(
|
|
217
|
+
`)),o.get("/api/events",{websocket:!0},async e=>{const t=[];let s=!1;const n={get readyState(){return e.readyState},OPEN:e.OPEN,send(n){s?e.send(n):t.push(n)},on(t,s){e.on(t,s)}};l.connect(n);const[i,a]=await Promise.all([p.streamDescriptors(),u.streamDescriptors()]);l.send(e,"streams.hello",{protocolVersion:1,streams:[...i,...a]}),s=!0;for(const s of t)e.send(s)}),o.get("/api/auth/session",async e=>({data:await c.currentSession(e.cookies[It])})),o.post("/api/auth/bootstrap-admin",async(e,t)=>{const s=await c.bootstrapAdmin(e.body);return t.code(201).send({data:s})}),o.post("/api/auth/login",async(e,t)=>{const s=await c.login(e.body);return t.setCookie(It,s.sessionToken,{path:"/",httpOnly:!0,sameSite:"lax",expires:new Date(s.expiresAt)}),{data:{user:s.user}}}),o.post("/api/auth/logout",async(e,t)=>{const s=c.logout(e.cookies[It]);return t.clearCookie(It,{path:"/"}),{data:s}}),o.get("/api/control-plane/status",async()=>({data:{protocolVersion:ue,build:Ar(),storage:r,counts:{projects:d.listProjects().length,models:d.listModels().length,images:d.listImages().length,nodes:d.listNodes().length,nodeRuntimes:(await d.listNodeRuntimes()).length,controlledInstances:(await d.listControlledInstances()).length,chatSessions:d.listChatSessions().length}}})),o.get("/api/control-plane/settings",async()=>({data:d.getSettings()})),o.patch("/api/control-plane/settings",async e=>({data:d.updateSettings(e.body||{})})),o.post("/api/ai-session-attachments",async(e,t)=>{try{return t.code(201).send({data:g.upload(e.body)})}catch(e){return t.code(400).send({error:{code:"AI_SESSION_ATTACHMENT_INVALID",message:e instanceof Error?e.message:String(e)}})}}),ar({app:o,service:d,events:l,appSessionAggregator:u,aiSessionAggregator:p,chatGateway:m,aiSessionAttachments:g,nodeAgentTunnel:h,nodeEventSubscriber:I,errorPayload:_r}),function({app:e,service:t}){!function(e){for(const t of["application/octet-stream","application/x-www-form-urlencoded"])mr(e,t);mr(e,/^multipart\/form-data(?:;.*)?$/i)}(e),e.get("/api/app-access/session",async e=>{const s=Ir(e.url),n="vnc"===e.query.mode?"vnc":"web"===e.query.mode?"web":"tty",i=await t.appAccessProxyTarget(s,n);return{data:{mode:n,instance:{id:i.instance.id,name:i.instance.name,projectId:i.instance.projectId,projectName:"string"==typeof i.instance.sourceSnapshot.name?i.instance.sourceSnapshot.name:void 0},session:i.session,expiresAt:i.access.expiresAt,ttySocketPath:"tty"===n?`/apps/access/tty/ws?token=${encodeURIComponent(s)}`:void 0,vncFramePath:"vnc"===n?fr(s,i.access.sessionId,i.session):void 0}}}),e.get("/apps/access/tty/ws",{websocket:!0},async(e,s)=>{try{const n=await t.appAccessProxyTarget(Ir(s.url),"tty");await t.proxyInstanceWebSocket(n.instance.id,e,n.path,lr(s.headers),cr(s.headers))}catch{e.close(1011,"TTY access link is invalid or expired.")}}),e.route({method:"GET",url:"/apps/access/vnc/proxy/*",wsHandler:async(e,s)=>{const n=s.params;try{const i=await t.appAccessProxyTarget(Ir(s.url),"vnc",n["*"]||"");await t.proxyInstanceWebSocket(i.instance.id,e,i.path,lr(s.headers),cr(s.headers))}catch{e.close(1011,"VNC access link is invalid or expired.")}},handler:async(e,s)=>{const n=e.params,i=await t.appAccessProxyTarget(Ir(e.url),"vnc",n["*"]||"");return gr(s,await t.proxyInstanceHttp(i.instance.id,i.path,{method:e.method,headers:dr(e.headers)}),i.instance.id,i.path)}}),e.all("/instances/:id",async(e,s)=>{const n=e.params;return"GET"===e.method||"HEAD"===e.method?s.redirect(`${ur(n.id)}/`):gr(s,await t.proxyInstanceHttp(n.id,"/",{method:e.method,headers:dr(e.headers),body:hr(e.body)}),n.id,"/")}),e.route({method:"GET",url:"/instances/:id/*",wsHandler:async(e,s)=>{const n=s.params,i=n["*"]||"",a=s.url.indexOf("?"),r=a>=0?s.url.slice(a):"";try{await t.proxyInstanceWebSocket(n.id,e,`/${i}${r}`,lr(s.headers),cr(s.headers))}catch{e.close(1011,"Instance websocket endpoint is not reachable.")}},handler:async(e,s)=>{const n=e.params,i=n["*"]||"",a=e.url.indexOf("?"),r=`/${i}${a>=0?e.url.slice(a):""}`;return gr(s,await t.proxyInstanceHttp(n.id,r,{method:e.method,headers:dr(e.headers)}),n.id,r)}}),e.route({method:["POST","PUT","PATCH","DELETE","OPTIONS"],url:"/instances/:id/*",handler:async(e,s)=>{const n=e.params,i=n["*"]||"",a=e.url.indexOf("?"),r=a>=0?e.url.slice(a):"";return pr(s,await t.proxyInstanceHttp(n.id,`/${i}${r}`,{method:e.method,headers:dr(e.headers),body:hr(e.body)}))}})}({app:o,service:d}),o.get("*",async(e,s)=>t.existsSync(b)?Er(s,b,"index.html"):s.code(404).send({error:{code:"CONTROL_PLANE_UI_NOT_BUILT",message:"Control plane API is running, but the Web UI static directory was not found."}})),o}function Or(e){const t=Number(e);if(!Number.isInteger(t)||t<=0||t>65535)throw new Error("Port must be a valid TCP port.");return t}function Rr(e){if("disabled"!==e&&"password"!==e)throw new Error("Auth mode must be disabled or password.");return e}(async function(){const n=new e.Command;n.name("task-handoff-control-plane").description("Run the TaskHandoff control plane.").version(function(){try{return JSON.parse(t.readFileSync(s.resolve(__dirname,"..","package.json"),"utf8")).version||"unknown"}catch{return"unknown"}}()).option("--host <host>","Control plane host",process.env.TASK_HANDOFF_CONTROL_PLANE_HOST||"127.0.0.1").option("-p, --port <port>","Control plane port",Or,Number(process.env.TASK_HANDOFF_CONTROL_PLANE_PORT)||8081).option("--data-dir <path>","Control plane data directory").option("--static-dir <path>","Control plane Web UI static directory",process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||s.resolve(__dirname,"..","ui")).option("--auth-mode <mode>","Control plane auth mode: disabled or password",Rr,process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"password").action(async e=>{await async function(e){const n=at(e.dataDir),i=function(e,n={}){t.mkdirSync(s.dirname(e),{recursive:!0});const i={pid:process.pid,hostname:c.hostname(),component:n.component,command:process.argv.join(" "),acquiredAt:(new Date).toISOString(),token:o.randomUUID(),dataDir:n.dataDir,host:n.host,port:n.port};for(let s=0;s<2;s+=1)try{return t.mkdirSync(e),La(e,i),{lockPath:e,owner:i,release(){const s=$a(e);s?.token===i.token&&t.rmSync(e,{recursive:!0,force:!0})}}}catch(s){if("EEXIST"!==s.code)throw s;const i=$a(e);if(qa(i?.pid))throw new Pa(e,i,n.component);t.rmSync(e,{recursive:!0,force:!0})}const a=$a(e);throw new Pa(e,a,n.component)}(s.join(c.tmpdir(),`task-handoff-control-plane-${process.getuid?.()??"user"}.lock`),{component:"control-plane",dataDir:n.dataDir,host:e.host,port:e.port});try{const t=await zr(e);t.addHook("onClose",async()=>{i.release()}),await t.listen({host:e.host,port:e.port})}catch(e){throw i.release(),e}}({host:e.host,port:e.port,dataDir:e.dataDir,staticDir:e.staticDir,auth:{mode:e.authMode}})}),await n.parseAsync(process.argv)})().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});
|