@task-handoff/control-plane 0.0.3-alpha.7 → 0.0.3
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-BSVJkVyi.js → abnfDiagram-VRR7QNED-DljE7ggn.js} +1 -1
- package/ui/assets/{arc-D9eId-Qo.js → arc-is85lsBN.js} +1 -1
- package/ui/assets/{architectureDiagram-ZJ3FMSHR-NwxKFD_i.js → architectureDiagram-ZJ3FMSHR-CdFyfJeG.js} +1 -1
- package/ui/assets/{blockDiagram-677ZJIJ3-BxvLh-7H.js → blockDiagram-677ZJIJ3-Dp8WI_pP.js} +1 -1
- package/ui/assets/{c4Diagram-LMCZKHZV-CU4VHpZ_.js → c4Diagram-LMCZKHZV-CVrU_nF6.js} +1 -1
- package/ui/assets/channel-CIYuzaLo.js +1 -0
- package/ui/assets/{chunk-2Q5K7J3B-DDPeSCKz.js → chunk-2Q5K7J3B-BS32WHPh.js} +1 -1
- package/ui/assets/{chunk-32BRIVSS-K4MrXabe.js → chunk-32BRIVSS-VzNdB_Ar.js} +1 -1
- package/ui/assets/{chunk-5VM5RSS4-CzTC32Op.js → chunk-5VM5RSS4-Bd-_sEp1.js} +1 -1
- package/ui/assets/{chunk-EX3LRPZG-VXAn5ISx.js → chunk-EX3LRPZG-BJgUlJrE.js} +1 -1
- package/ui/assets/{chunk-JWPE2WC7-BmDI9Fxa.js → chunk-JWPE2WC7-llTrgHQ-.js} +1 -1
- package/ui/assets/{chunk-MOJQB5TN-2ZffjNig.js → chunk-MOJQB5TN-CFbyd4CU.js} +1 -1
- package/ui/assets/{chunk-RYQCIY6F-Dxjo9s2i.js → chunk-RYQCIY6F-Skv2sP0G.js} +1 -1
- package/ui/assets/{chunk-V7JOEXUC-DkphDFF9.js → chunk-V7JOEXUC-C1o-ZRTU.js} +1 -1
- package/ui/assets/{chunk-VR4S4FIN-aRvLnnVF.js → chunk-VR4S4FIN-CPv4V8jP.js} +1 -1
- package/ui/assets/{chunk-XXDRQBXY-l8Efr6jh.js → chunk-XXDRQBXY-tch5MGX5.js} +1 -1
- package/ui/assets/classDiagram-OUVF2IWQ-CqY9zJ-v.js +1 -0
- package/ui/assets/classDiagram-v2-EOCWNBFH-CqY9zJ-v.js +1 -0
- package/ui/assets/{cose-bilkent-JH36ORCC-DJdmWL8n.js → cose-bilkent-JH36ORCC-BIiRCHgl.js} +1 -1
- package/ui/assets/{cynefin-VYW2F7L2-oExeZ8XG.js → cynefin-VYW2F7L2-C97QdUlN.js} +1 -1
- package/ui/assets/{cynefinDiagram-TSTJHNR4-CPvo4LvB.js → cynefinDiagram-TSTJHNR4-DQS8K4GU.js} +1 -1
- package/ui/assets/{dagre-VKFMJZFB-CsYuNVv2.js → dagre-VKFMJZFB-Dob7-AHm.js} +1 -1
- package/ui/assets/{diagram-FQU43EPY-Bk-avdiV.js → diagram-FQU43EPY-BghWk9H5.js} +1 -1
- package/ui/assets/{diagram-G47NLZAW-CP4ubafo.js → diagram-G47NLZAW-CMj8Fcir.js} +1 -1
- package/ui/assets/{diagram-NH7WQ7WH-D373SJ8o.js → diagram-NH7WQ7WH-DePNkIES.js} +1 -1
- package/ui/assets/{diagram-OA4YK3LP-C9SlZPNZ.js → diagram-OA4YK3LP-CxZ3kWpy.js} +1 -1
- package/ui/assets/{diagram-WEI45ONY-DFdTkLUB.js → diagram-WEI45ONY-Dzjp1Z3i.js} +1 -1
- package/ui/assets/{ebnfDiagram-CCIWWBDH-aM3dPMWg.js → ebnfDiagram-CCIWWBDH-DqtGL_iw.js} +1 -1
- package/ui/assets/{erDiagram-Q63AITRT-DKtXjm4k.js → erDiagram-Q63AITRT-CWD_5gWD.js} +1 -1
- package/ui/assets/{flowDiagram-23GEKE2U-D7mKHdBz.js → flowDiagram-23GEKE2U-BzazrmWJ.js} +1 -1
- package/ui/assets/{ganttDiagram-NO4QXBWP-BRT1O3S0.js → ganttDiagram-NO4QXBWP-C_Jjp9Gi.js} +1 -1
- package/ui/assets/{gitGraphDiagram-IHSO6WYX-wxF1dbvt.js → gitGraphDiagram-IHSO6WYX-6TZhhs8l.js} +1 -1
- package/ui/assets/{index-D7USaoPf.css → index-CiRNpe9y.css} +1 -1
- package/ui/assets/index-ojah1HkG.js +400 -0
- package/ui/assets/{infoDiagram-FWYZ7A6U-pd8tL4Vk.js → infoDiagram-FWYZ7A6U-TvVY6dWk.js} +1 -1
- package/ui/assets/{ishikawaDiagram-FXEZZL3T-VTYUzxP3.js → ishikawaDiagram-FXEZZL3T-Fp6ETuY1.js} +1 -1
- package/ui/assets/{journeyDiagram-5HDEW3XC-CZ0EEPfV.js → journeyDiagram-5HDEW3XC-Dh6MPEWW.js} +1 -1
- package/ui/assets/{kanban-definition-HUTT4EX6-DEzIu9ly.js → kanban-definition-HUTT4EX6-DKszraig.js} +1 -1
- package/ui/assets/{linear-CcRQjM0v.js → linear-DoHDkQGv.js} +1 -1
- package/ui/assets/{mermaid.core-pPXLKTF5.js → mermaid.core-Cv4TaY1N.js} +4 -4
- package/ui/assets/{mindmap-definition-LN4V7U3C-BAKqfr6E.js → mindmap-definition-LN4V7U3C-azyqsigq.js} +1 -1
- package/ui/assets/{pegDiagram-2B236MQR-KDwzfUqx.js → pegDiagram-2B236MQR-wJ7rMADa.js} +1 -1
- package/ui/assets/{pieDiagram-ENE6RG2P-CRT5UMVH.js → pieDiagram-ENE6RG2P-BCESVCnx.js} +1 -1
- package/ui/assets/{quadrantDiagram-ABIIQ3AL-Dr5uMmZ7.js → quadrantDiagram-ABIIQ3AL-BC1hzbjj.js} +1 -1
- package/ui/assets/{railroadDiagram-RFXS5EU6-B7PcJawb.js → railroadDiagram-RFXS5EU6-BVkSq2pG.js} +1 -1
- package/ui/assets/{requirementDiagram-TGXJPOKE-BlhonxXF.js → requirementDiagram-TGXJPOKE-4unXWRfu.js} +1 -1
- package/ui/assets/{sankeyDiagram-HTMAVEWB-sE8CiLtJ.js → sankeyDiagram-HTMAVEWB-C9ogPBbF.js} +1 -1
- package/ui/assets/{sequenceDiagram-DBY2YBRQ-5jhEJoxS.js → sequenceDiagram-DBY2YBRQ-C2qjxIRl.js} +1 -1
- package/ui/assets/{sizeCapture-X5ZJPWSS-DzBZB_qw.js → sizeCapture-X5ZJPWSS-DZRmQWCl.js} +1 -1
- package/ui/assets/{stateDiagram-2N3HPSRC-BdXO594c.js → stateDiagram-2N3HPSRC-sQB-wYHK.js} +1 -1
- package/ui/assets/stateDiagram-v2-6OUMAXLB-Cr3u8gHX.js +1 -0
- package/ui/assets/{swimlanes-5IMT3BWC-DECVG5ez.js → swimlanes-5IMT3BWC-DzUzzuCW.js} +2 -2
- package/ui/assets/swimlanesDiagram-G3AALYLV-OYVxdsAS.js +8 -0
- package/ui/assets/{timeline-definition-FHXFAJF6-BF93Qpan.js → timeline-definition-FHXFAJF6-B8PEeNnQ.js} +1 -1
- package/ui/assets/{vennDiagram-L72KCM5P-B3GOpYIJ.js → vennDiagram-L72KCM5P-CqMmkX7n.js} +1 -1
- package/ui/assets/{wardleyDiagram-EHGQE667-DB3WNS_s.js → wardleyDiagram-EHGQE667-BGwwQABN.js} +1 -1
- package/ui/assets/{xychartDiagram-FW5EYKEG-CFJU8a9w.js → xychartDiagram-FW5EYKEG-Bymkd4G6.js} +1 -1
- package/ui/index.html +2 -2
- package/ui/assets/channel-CjEppeG1.js +0 -1
- package/ui/assets/classDiagram-OUVF2IWQ-OzvGf6kz.js +0 -1
- package/ui/assets/classDiagram-v2-EOCWNBFH-OzvGf6kz.js +0 -1
- package/ui/assets/index-LTf8vwed.js +0 -400
- package/ui/assets/stateDiagram-v2-6OUMAXLB-5FQrwrf9.js +0 -1
- package/ui/assets/swimlanesDiagram-G3AALYLV-Bo4jwoyf.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
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}),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({streamId:r.z.string().trim().min(1).max(240),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 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: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({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(),Be=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(),Fe=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(Fe)}).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: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 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(),Bt=r.z.object({meta:qt,sessionId:r.z.string().trim().min(1).max(160),tombstone:jt.optional(),expiresAt:r.z.string().datetime().optional()}).strict(),Ft=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:Bt}).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(Be))}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(Fe))}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=r.z.object({name:De.shape.name.optional(),connectionMode:De.shape.connectionMode.unwrap().optional(),auth:ms.optional(),endpoint:De.shape.endpoint,controlEndpoint:De.shape.controlEndpoint,containerEndpoint:De.shape.containerEndpoint,publicWebBase:De.shape.publicWebBase,status:De.shape.status.unwrap().optional(),health:De.shape.health.unwrap().optional(),capabilities:De.shape.capabilities.unwrap().optional(),labels:De.shape.labels.unwrap().optional(),lastSeenAt:De.shape.lastSeenAt}).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=>Bs(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):Bs(Us(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Bs(Us(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Us(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Bs(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Fs{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/^[a-z]:[\\/]/i.test(e)||/^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/.test(e)}function Rn(e){return On(e)?s.win32:s.posix}function xn(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 Dn="TASK_HANDOFF_NODE_AGENT_V1",jn=6e4;function Pn(e=""){return o.createHash("sha256").update(e).digest("hex")}function Mn(e,t){return o.createHmac("sha256",e).update(function(e){return[Dn,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function $n(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||o.randomBytes(18).toString("base64url"),n=Pn(e.body||""),i=Mn(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 qn(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function Ln(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 Un(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 Bn=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(),Fn="task-handoff.control-plane.local",Hn="task-handoff.control-plane.builtin";function Gn(e){return"true"===e.labels[Fn]}function Wn(e){return"true"===e.labels[Hn]}function Vn(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 Kn(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return $n({nodeId:e.id,keyId:Vn(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 Jn{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new Fs;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(Bn)),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(Gn)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?Kn(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=qn(e);return n?(Ln(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={[Fn]:"true",[Hn]:"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 qn(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=>Gn(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(!Wn(t)||!Gn(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(Wn(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 F.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 F.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 Ft.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=Kn(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=qn(a),o=`/api/node-agent${s}`,d=Kn(e,{method:"GET",pathWithQuery:o});if(r){Ln(r);const e=Un(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:xn(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 Rn(i=t.path).resolve(i);var i;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 a=function(e,t){if(On(e)!==On(t))return;const s=Rn(e),n=s.resolve(e),i=s.resolve(t),a=s.relative(n,i);return a?".."===a||a.startsWith(`..${s.sep}`)||s.isAbsolute(a)?void 0:a.split(s.sep).filter(Boolean):[]}(e.source.path,t.path);if(!a){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 r=e.runtime.workspacePath||e.workspace.path||ps(e.source).path||"/workspace";return a.length?s.posix.join(r,...a):r}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 Qn(e){return m(String(e),"escape").replace(/\n+$/,"")}function Yn(e){const[t,...s]=String(e).split("\n");return[`*${Qn(t)}*`,...s.map(Qn)].join("\n")}function Xn(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Zn(e,t,s,n,i={}){if(t.token)return ti(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 ei(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 ti(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):Qn(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&&si(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:Xn(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&&si(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?Xn(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||!si(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function si(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function ni(e){return e instanceof Error?e.message:String(e)}function ii(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 ai{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||ii,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: ${ni(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: ${ni(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: ${ni(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 ri=20971520;function oi(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 di(e,t,n="telegram-image",i){if(i&&i>ri)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>ri)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>ri)throw new Error(`image is too large: ${m.length} bytes`);return await I.writeFile(h,m),h}class ci{store;constructor(e){this.store=new ai({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",Yn(t),{rawMarkdownV2:!0,replyMarkup:Ui(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,Yn(s),{rawMarkdownV2:!0,replyMarkup:Ui(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 li(e,t){if("string"==typeof e&&e.trim())return e.trim();ui(`Chat action token is missing ${t}.`)}function pi(e){if("allow"===e||"deny"===e||"skip"===e)return e;ui("Chat action token has an invalid decision.")}function ui(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class gi{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=Li(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,Li(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=zi({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||$i(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||$i(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 Oi(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:yi({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 hi(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=await Zn(t,s,e,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Pi(a).message_id)||void 0}}}:"dingding"===s.channel?{bridge:s,send:async(e,i,a={})=>{const r=a.sessionWebhook||n?.chatWebhooks.get(e)||$i(s.settings.sessionWebhook);if(a.replyMarkup&&n){const o=await zi({fetchImpl:t,bridge:s,runtime:n,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||n.senderIds.get(e)||$i(s.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await _i(t,r,i,n?.onLog),{provider:"dingding"}}}:"wechat"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=i.contextToken||$i(s.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=$i(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($i(d.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(t,s,e,a,n),{provider:"wechat"}}}:{bridge:s,send:async()=>{}}}async function mi(e){return(await async function(e,t,s,n){const i=[];for(const a of oi(s))try{const s=await di({getFileLink:s=>ei(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:Ii(e),size:i.length,data:i.toString("base64")}})}function Ii(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 fi(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function yi(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function bi(e){return`dtv1.card//IM_GROUP.${e}`}function vi(e){return`dtv1.card//IM_ROBOT.${e}`}function wi(e,t){return $i(t.biz_conversation_id).trim()||$i(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function Si(e,t={}){const s=$i(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 Ai(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 ki(e,t,s,n,i){const a=wi(n,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:yi({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 Ti(e,t,s){const n=[];return Ei(n,s),Ei(n,t),Ei(n,e),n.find(e=>Ai(e))||""}function Ei(e,t){for(const s of["actionIdList","actionIds"]){const n=t[s];Array.isArray(n)&&e.push(...n.map(e=>$i(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=$i(t[s]);n&&e.push(n)}}async function _i(e,t,s,n){n?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:ji(s)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:fi(s),text:s}})}),a=await i.json().catch(()=>{});if(n?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:Di(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function Ci(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Pi(e);if(1!==t.type)continue;const s=$i(Pi(t.text_item).text).trim();if(s)return s}return""}function Ni(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function zi(e){const t=Xs(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await _i(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const s=e.bridge.settings||{},n=$i(s.robotCode),i=$i(s.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=$i(s.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||$i(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 _i(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=yi({type:"actions",title:e.title||fi(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:vi(s),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:n,spaceType:"IM_ROBOT"}}:{openSpaceId:bi(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:ji(e.text)},"dingding card send requested"),await Oi(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 Oi(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=$i(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:Ri(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:Di(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=Pi(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error($i(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=Pi(Pi(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===Pi(e).success);if(!s)return"";const n=Pi(s),i=$i(n.spaceId),a=$i(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 Ri(e){const t=Pi(e),s=Pi(t.cardData),n=Pi(s.cardParamMap);return{openSpaceId:$i(t.openSpaceId),outTrackId:$i(t.outTrackId),userId:$i(t.userId),userIdType:t.userIdType,callbackType:$i(t.callbackType),callbackRouteKey:$i(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:xi(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:xi(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:xi(t.imRobotOpenDeliverModel),bizConversationId:$i(n.biz_conversation_id),bizConversationType:$i(n.biz_conversation_type),bizSenderId:$i(n.biz_sender_id),hasBizSessionWebhook:Boolean($i(n.biz_session_webhook))}}function xi(e){const t=Pi(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean($i(t.robotCode)),spaceType:$i(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 Di(e){const t=Pi(e),s=Pi(t.result);return{code:t.code??t.errcode,message:ji(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(s).length?s:void 0}}function ji(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 Pi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Mi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?qi(e):{}}function $i(e){return"string"==typeof e?e:""}function qi(e){try{return Pi(JSON.parse(String(e||"{}")))}catch{return{}}}function Li(e){return JSON.stringify(Xs(e))}function Ui(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Bi(e){return Buffer.from(e,"base64url").toString("utf8")}function Fi(e){return new h.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class Hi{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 ci({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 gi(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:ea(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=s.createDingdingClient||Fi}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=Zi((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=qi(e),s=$i(t.conversationId),n=$i(Pi(t.text).content).trim();if(!s||!n)return;const i=$i(t.senderStaffId||t.senderId);return{chatId:s,userId:i||void 0,senderId:i||void 0,sessionWebhook:$i(t.sessionWebhook)||void 0,conversationType:Si(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)||Zi(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:ta(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 _i(this.fetchImpl,d,`Failed to handle message: ${ea(e)}`,t.onLog);throw e}const l=Gi(c);if(l){const s=Vi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=hi({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===ia(c).routed,hasReplyMarkup:Boolean(s),replyPreview:ta(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(Zi(e.settings.robotCode)),replyPreview:ta(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,s,n,i,a={}){if(!0!==e.routed)return!1;const r=oa(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=ha(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:ta(a.sourcePrompt,80),textPreview:ta(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=qi(e),s=Mi(t.cardActionData||t.content),n=Mi(s.cardPrivateData||t.cardPrivateData||t.content),i=Mi(n.params||s.params||t.params),a=$i(t.userId),r=$i(i.biz_sender_id);return{userId:a||void 0,chatId:wi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:$i(i.biz_session_webhook)||void 0,conversationType:Si(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:Ai(Ti(t,s,n)),body:t,params:i}}(s.data);if(!this.dingdingAllowed(e,n.userId))return ki("not authorized",void 0,"unauthorized",n.body,n.params);const i=n.chatId||Zi(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,...sa(n)},"dingding card callback action unsupported"),ki("Unsupported action",void 0,"unsupported",n.body,n.params);this.logInfo({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,actionType:o.type,...sa(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&&Wi(d)?ki(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):ki(Gi(d)||Zi(d.message)||"updated",Vi(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||Zi(t.updatesBuf),n=await async function(e){const t=$i((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($i(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=Pi(e);if(1!==t.message_type)continue;const s=$i(t.from_user_id),n=Ci(t);s&&n&&a.push({chatId:s,text:n,contextToken:$i(t.context_token)})}return{cursor:$i(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=Gi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=hi({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||Zi(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=da(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 hi({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=Pi(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Pi(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Pi(r.chat),d=Pi(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=$i(r.text??r.caption),u=oi(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:Ni(r),replyToMessageId:Ni(Pi(r.reply_to_message)),quoteText:$i(Pi(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=ia(s.message),i=ia(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,ea(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${ea(t)}`).catch(t=>{this.bridgeErrors.set(e.id,ea(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await mi({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:ea(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:ta(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=Qi(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!==Qi(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=Gi(d),l=Vi(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(ia(s).message_id)||void 0,routed:!0===ia(d).routed,replyPreview:ta(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,ea(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${ea(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,ea(t))})}}async handleTelegramCallback(e,t){const s=Zi(t.data),n=ia(t.from),i=ia(t.message),a=ia(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,Zi(t.id),s.instanceId,s.sessionId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(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,Zi(t.id),s.instanceId,s.sessionId,s.queueId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(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,Zi(t.id),s.instanceId,s.sessionId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(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,Zi(t.id),Ji(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,Zi(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,Zi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(s);if(!g)return void await this.answerTelegramCallback(e,Zi(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&&Wi(h)){await this.answerTelegramCallback(e,Zi(t.id),"");const s=Ji(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,Zi(t.id),Zi(h.message)||"updated");const m=Gi(h),I=Vi(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:ea(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,ea(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${ta(ea(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,ea(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${ta(ea(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=ia(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>ia(e)).filter(e=>"queued"===Zi(e.status)&&Zi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,ca).map((e,t)=>[{text:ga(Zi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,Zi(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,ea(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${ta(ea(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:ea(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,ea(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${ta(ea(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:li(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Bi(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:li(e.instanceId,"instanceId"),appId:li(e.appId,"appId")}}return{type:"launch-app",instanceId:Bi(i[1]),appId:Bi(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:li(e.routeId,"routeId"),decision:pi(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 ti(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 Zn(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=oa(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(ia(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=ha(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set(Yi(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:ta(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(Yi(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:ea(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get(Yi(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=Xi(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set(Yi(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(Yi(n.route.bridgeId,n.route.chatId,n.messageId||0))||Xi(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(Yi(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=ba(s.get(e)||e,a),o=ya(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:na(va(a))});continue}const d=ma(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:na(va(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=ha(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=Ia(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:na(va(a)),textPreview:ta(o)});continue}const p=pa({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)||ua(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:na(va(a)),textPreview:ta(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:ea(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:ta(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:ta(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:na(va(a)),textPreview:ta(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:ta(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=ba(s.get(e)||e,r),c=ya(r,d),l=ma(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:ha(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=Ia(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=pa({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)||ua(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:ta(c)}),h&&(this.telegramProgressMessageTargets.set(Yi(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(Yi(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(Yi(n.route.bridgeId,n.route.chatId,n.messageId))||Xi(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||va(t)?.id===e.owner.turnId||va(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:Ki({actionRows:n})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:ta(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:ta(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:ta(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 Gi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function Wi(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function Vi(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 Ki(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Ji(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Qi(e,t,s){return[e,t,s||""].join(":")}function Yi(e,t,s){return[e,t,String(s)].join(":")}function Xi(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 Zi(e){return"string"==typeof e?e:""}function ea(e){return e instanceof Error?e.message:String(e)}function ta(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 sa(e){const t=aa(e.body.cardActionData||e.body.content),s=aa(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:Zi(e.body.outTrackId),spaceId:Zi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:ta(e.callbackData,120),actionIds:[...ra(s.actionIdList),...ra(t.actionIdList),Zi(s.actionId),Zi(t.actionId),Zi(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 na(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:ta(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:ta(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function ia(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function aa(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return ia(JSON.parse(e))}catch{return{}}return{}}function ra(e){return Array.isArray(e)?e.map(e=>Zi(e)).filter(Boolean):[]}function oa(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?ma(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?va(s)?.id:void 0}}}function da(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 ca=5,la=48;function pa(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,ca);return[...d.map((e,t)=>[{text:ga(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function ua(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function ga(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,la-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function ha(e,t,...s){return[e,t,...s].join(":")}function ma(e){const t=va(e);return t?.id?t.id:Zi(e.activeTurnId)}function Ia(e,t=""){const s=va(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||"",fa(e)].join("\0")}function fa(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function ya(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,va(e));return s?`${t}\n${s}`:""}function ba(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 va(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}class wa{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&&Sa(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 Sa(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const Aa={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},ka=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 Ta{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const n=ka.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=Aa[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 Ea(e){return e.readyState===(e.OPEN??1)}function _a(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 Ca(e,t){e.send(t.data,{binary:t.isBinary})}function Na(e,t){if(Ea(e))for(const s of t.splice(0))Ca(e,s)}class za{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 Oa=r.z.object({nodeId:r.z.string().trim().min(1).max(120)}).strict();function Ra(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 xa(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Da=new Map;function ja(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)>jn)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()-jn;for(const[e,t]of Da)t<=i&&Da.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||Da.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=Pn("");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=Mn(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"});Da.set(a,Date.now()+jn)}class Pa{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=xa(),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=xa(),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=Ea(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return _a(e,1011,"WebSocket bridge pending frame limit exceeded."),void _a(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(_a(e,1011,"Upstream websocket did not open."),_a(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>Na(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),Na(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};Ea(t)?Ca(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};Ea(e)?Ca(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&_a(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&_a(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),_a(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),_a(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 Ma{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=qn(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 $n({nodeId:e.id,keyId:Ra(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?Un(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 za,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 za};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(qn(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 $a 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 qa(e){return s.join(e,"owner.json")}function La(e){try{return JSON.parse(t.readFileSync(qa(e),"utf8"))}catch{return}}function Ua(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 Ba(e,s){t.writeFileSync(qa(e),`${JSON.stringify(s,null,2)}\n`,"utf8")}function Fa(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 Ha=r.z.string().trim().min(1),Ga=r.z.object({id:Ha}).strict(),Wa=r.z.object({id:Ha,keyId:Ha}).strict(),Va=r.z.object({id:Ha,runtimeId:Ha}).strict(),Ka=r.z.object({id:Ha,folderId:Ha}).strict(),Ja=r.z.object({id:Ha,sessionId:Ha}).strict(),Qa=r.z.object({id:Ha,sessionId:Ha,queueId:Ha}).strict(),Ya=r.z.object({configHash:Ha}).strict(),Xa=r.z.object({id:Ha,configHash:Ha}).strict(),Za=r.z.object({id:Ha,sessionId:Ha,configHash:Ha}).strict(),er=r.z.object({path:r.z.string().optional(),depth:r.z.coerce.number().int().min(0).max(32).optional()}).strict(),tr=r.z.object({ids:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),sr=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 nr(e){const t=sr.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const ir=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:{}}),ar=r.z.object({title:r.z.string().trim().min(1).max(120)}).strict(),rr=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 or({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(Ga.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(Ga.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=Ga.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=tr.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(Ga.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=t.updateModel(Ga.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=Ga.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(Ga.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(Ga.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=Ga.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(Ga.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(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/settings/external-listener",async e=>({data:await t.getLocalNodeExternalListener(Ga.parse(e.params).id)})),e.patch("/api/nodes/:id/settings/external-listener",async e=>{const n=Ga.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=Ga.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=Ga.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=Ga.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=Ga.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=Ga.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=Wa.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(Ga.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=Ga.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=Va.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=Va.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=Va.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(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=Ga.parse(e.params),n=er.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=Ga.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=Ka.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(Ga.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(Ga.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=Ga.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=Oa.parse(t.query),a=s.requireNode(i.nodeId);ja(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=Oa.parse(t.query),a=s.requireNode(i.nodeId);ja(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(Ga.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(Ga.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=Ga.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(Ga.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(Ga.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(Ga.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=Ga.parse(e.params),i=ir.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=Ja.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=Ja.parse(e.params),i=ar.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=Ga.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=Ja.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=Ja.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=Qa.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=Qa.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=Qa.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=Ja.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=Ja.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=rr.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=Ya.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=Ya.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=Ga.parse(e.params);return{data:await t.listInstanceTriggers(s.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const n=Ja.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=Za.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=Xa.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(Ga.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=Ga.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=Ga.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=Ga.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(Ga.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(nr(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:a})}const dr=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),cr=new Set(["content-encoding"]);function lr(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!dr.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function pr(e){return Object.fromEntries(Object.entries(lr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function ur(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 gr(e,t){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();dr.has(t)||cr.has(t)||e.header(s,n)}return e.code(t.status).send(t.body)}function hr(e){return`/instances/${encodeURIComponent(e)}`}function mr(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"),hr(s)),"utf8"):t.body;return gr(e,{...t,body:i})}function Ir(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 fr(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 yr(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function br(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 vr{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:F.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=B.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:F.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 wr{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?Bt: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 Ft.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Ft.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 Ft.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 Sr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function Ar(e){const t=process.env[e]?.trim();return t||void 0}function kr(){try{const e=s.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function Tr(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:kr(),protocolVersion:ue,buildId:Ar("TASK_HANDOFF_BUILD_ID"),builtAt:Ar("TASK_HANDOFF_BUILT_AT"),gitCommit:Ar("TASK_HANDOFF_GIT_COMMIT"),imageRef:Ar("TASK_HANDOFF_IMAGE_REF"),imageDigest:Ar("TASK_HANDOFF_IMAGE_DIGEST")}}const Er={".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 _r(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 Cr(e,n,i){const a=_r(n,i);return a?(e.header("cache-control","no-store"),e.type(Er[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 Nr(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 zr(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 Or(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 Rr(e={}){const r=at(e.dataDir),o=n({logger:e.logger??!0,bodyLimit:67108864}),d=new Jn(r,{...e.service,logger:o.log}),c=new Nt(r,e.auth),l=new wa,p=new vr({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 wr({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 Ta,h=new Pa(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 Hi(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const I=new Ma(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=Nr(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 zr(c,e.cookies[It]),s=Or(e.method,e.url);return void(s&&Fa(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await zr(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=Or(e.method,e.url);n&&Fa(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 Cr(t,b,s.join("assets",n||""))}),o.get("/favicon.ico",async(e,t)=>_r(b,"favicon.ico")?Cr(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:ue,build:Tr(),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"),Sr`#!/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},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:
|
|
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:Tr(),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)}})}}),or({app:o,service:d,events:l,appSessionAggregator:u,aiSessionAggregator:p,chatGateway:m,aiSessionAttachments:g,nodeAgentTunnel:h,nodeEventSubscriber:I,errorPayload:Nr}),function({app:e,service:t}){!function(e){for(const t of["application/octet-stream","application/x-www-form-urlencoded"])fr(e,t);fr(e,/^multipart\/form-data(?:;.*)?$/i)}(e),e.get("/api/app-access/session",async e=>{const s=yr(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?br(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(yr(s.url),"tty");await t.proxyInstanceWebSocket(n.instance.id,e,n.path,ur(s.headers),pr(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(yr(s.url),"vnc",n["*"]||"");await t.proxyInstanceWebSocket(i.instance.id,e,i.path,ur(s.headers),pr(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(yr(e.url),"vnc",n["*"]||"");return mr(s,await t.proxyInstanceHttp(i.instance.id,i.path,{method:e.method,headers:lr(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(`${hr(n.id)}/`):mr(s,await t.proxyInstanceHttp(n.id,"/",{method:e.method,headers:lr(e.headers),body:Ir(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}`,ur(s.headers),pr(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 mr(s,await t.proxyInstanceHttp(n.id,r,{method:e.method,headers:lr(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 gr(s,await t.proxyInstanceHttp(n.id,`/${i}${r}`,{method:e.method,headers:lr(e.headers),body:Ir(e.body)}))}})}({app:o,service:d}),o.get("*",async(e,s)=>t.existsSync(b)?Cr(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 xr(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 Dr(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",xr,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",Dr,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),Ba(e,i),{lockPath:e,owner:i,release(){const s=La(e);s?.token===i.token&&t.rmSync(e,{recursive:!0,force:!0})}}}catch(s){if("EEXIST"!==s.code)throw s;const i=La(e);if(Ua(i?.pid))throw new $a(e,i,n.component);t.rmSync(e,{recursive:!0,force:!0})}const a=La(e);throw new $a(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 Rr(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});
|