@task-handoff/control-plane 0.0.4 → 0.0.6
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 -4
- package/ui/assets/{abnfDiagram-VRR7QNED-CoMxIAmy.js → abnfDiagram-VRR7QNED-BRsxtDTi.js} +1 -1
- package/ui/assets/{arc-DnCt1Ea-.js → arc-Da19wFMA.js} +1 -1
- package/ui/assets/{architectureDiagram-ZJ3FMSHR-yRvJlGXH.js → architectureDiagram-ZJ3FMSHR-CL1Ckkm7.js} +1 -1
- package/ui/assets/{blockDiagram-677ZJIJ3-CS_aJw4c.js → blockDiagram-677ZJIJ3-DybDwQd8.js} +1 -1
- package/ui/assets/{c4Diagram-LMCZKHZV-D1tV6V_g.js → c4Diagram-LMCZKHZV-C3taiPiq.js} +1 -1
- package/ui/assets/channel-B2PbH6JM.js +1 -0
- package/ui/assets/{chunk-2Q5K7J3B-0c6eAKNe.js → chunk-2Q5K7J3B-MgUqElWg.js} +1 -1
- package/ui/assets/{chunk-32BRIVSS-BP9Lft1K.js → chunk-32BRIVSS-BMcRzX0Y.js} +1 -1
- package/ui/assets/{chunk-5VM5RSS4-Diy5Ny1E.js → chunk-5VM5RSS4-BObQThQp.js} +1 -1
- package/ui/assets/{chunk-EX3LRPZG-Cf48GnF4.js → chunk-EX3LRPZG-DAc5Cb9H.js} +1 -1
- package/ui/assets/{chunk-JWPE2WC7-Dcjr1117.js → chunk-JWPE2WC7-D0AQXca2.js} +1 -1
- package/ui/assets/{chunk-MOJQB5TN-DF9osPlS.js → chunk-MOJQB5TN-DHQpnMxV.js} +1 -1
- package/ui/assets/{chunk-RYQCIY6F-D6LalwK3.js → chunk-RYQCIY6F-C_IbayOE.js} +1 -1
- package/ui/assets/{chunk-V7JOEXUC-Enj5J1P7.js → chunk-V7JOEXUC-BaSMCWPB.js} +1 -1
- package/ui/assets/{chunk-VR4S4FIN-DzdsyVKR.js → chunk-VR4S4FIN-C45acCl-.js} +1 -1
- package/ui/assets/{chunk-XXDRQBXY-B8CDGPYh.js → chunk-XXDRQBXY-BxUtYJBr.js} +1 -1
- package/ui/assets/classDiagram-OUVF2IWQ-CwwUm1UN.js +1 -0
- package/ui/assets/classDiagram-v2-EOCWNBFH-CwwUm1UN.js +1 -0
- package/ui/assets/{cose-bilkent-JH36ORCC-B89qwuy1.js → cose-bilkent-JH36ORCC-DN8xapyK.js} +1 -1
- package/ui/assets/{cynefin-VYW2F7L2-BXoR8-nv.js → cynefin-VYW2F7L2-hpp3f7sf.js} +1 -1
- package/ui/assets/{cynefinDiagram-TSTJHNR4-DhvNQP3B.js → cynefinDiagram-TSTJHNR4-BhPeqbDt.js} +1 -1
- package/ui/assets/{dagre-VKFMJZFB-Cb0HPrLT.js → dagre-VKFMJZFB-JkmMdVxr.js} +1 -1
- package/ui/assets/{diagram-FQU43EPY-DP-bQOmX.js → diagram-FQU43EPY-DyA_eX2V.js} +1 -1
- package/ui/assets/{diagram-G47NLZAW-CkJHykzJ.js → diagram-G47NLZAW-BVtl8djA.js} +1 -1
- package/ui/assets/{diagram-NH7WQ7WH-D26LydRl.js → diagram-NH7WQ7WH-FMASfKDQ.js} +1 -1
- package/ui/assets/{diagram-OA4YK3LP-BN7bgs-A.js → diagram-OA4YK3LP-CSVIIj2f.js} +1 -1
- package/ui/assets/{diagram-WEI45ONY-BCMfbhfX.js → diagram-WEI45ONY-DnBx_dMr.js} +1 -1
- package/ui/assets/{ebnfDiagram-CCIWWBDH-SMCuHRUG.js → ebnfDiagram-CCIWWBDH-D-8VEf-k.js} +1 -1
- package/ui/assets/{erDiagram-Q63AITRT-D0cFqR_R.js → erDiagram-Q63AITRT-CTPv51Ie.js} +1 -1
- package/ui/assets/{flowDiagram-23GEKE2U-CLez4rR-.js → flowDiagram-23GEKE2U-C8rSLlES.js} +1 -1
- package/ui/assets/{ganttDiagram-NO4QXBWP-szhQlSDY.js → ganttDiagram-NO4QXBWP-DGIbdMyo.js} +1 -1
- package/ui/assets/{gitGraphDiagram-IHSO6WYX-Dax89jEp.js → gitGraphDiagram-IHSO6WYX-B3vd7BTx.js} +1 -1
- package/ui/assets/index-C5ldB4Ty.js +401 -0
- package/ui/assets/index-Ch1pnbjI.css +1 -0
- package/ui/assets/{infoDiagram-FWYZ7A6U-BpTgGfH7.js → infoDiagram-FWYZ7A6U-DOMz1M7-.js} +1 -1
- package/ui/assets/{ishikawaDiagram-FXEZZL3T-Ds23KYYM.js → ishikawaDiagram-FXEZZL3T-CZZ1zv3H.js} +1 -1
- package/ui/assets/{journeyDiagram-5HDEW3XC-Da6rosAH.js → journeyDiagram-5HDEW3XC-CJZk3x5r.js} +1 -1
- package/ui/assets/{kanban-definition-HUTT4EX6-CTeBSGYZ.js → kanban-definition-HUTT4EX6-JBK2RWzX.js} +1 -1
- package/ui/assets/{linear-C3umSqKV.js → linear--MALmBul.js} +1 -1
- package/ui/assets/{mermaid.core-BjgG3HbH.js → mermaid.core-C09sxEav.js} +4 -4
- package/ui/assets/{mindmap-definition-LN4V7U3C-D0Uf7jcp.js → mindmap-definition-LN4V7U3C-CiwCvwRD.js} +1 -1
- package/ui/assets/{pegDiagram-2B236MQR-XtSizu9E.js → pegDiagram-2B236MQR-DyHzGEAp.js} +1 -1
- package/ui/assets/{pieDiagram-ENE6RG2P-Qyt6pQg5.js → pieDiagram-ENE6RG2P-B2oTM0Tu.js} +1 -1
- package/ui/assets/{quadrantDiagram-ABIIQ3AL-q23nS35R.js → quadrantDiagram-ABIIQ3AL-B0ttxg1y.js} +1 -1
- package/ui/assets/{railroadDiagram-RFXS5EU6-BeWGfkAx.js → railroadDiagram-RFXS5EU6-Cbb-XHop.js} +1 -1
- package/ui/assets/{requirementDiagram-TGXJPOKE-Bp33KEBm.js → requirementDiagram-TGXJPOKE-BVpQy_kI.js} +1 -1
- package/ui/assets/{sankeyDiagram-HTMAVEWB-Bj5nUlQH.js → sankeyDiagram-HTMAVEWB-twp8OgNq.js} +1 -1
- package/ui/assets/{sequenceDiagram-DBY2YBRQ-COnS5enK.js → sequenceDiagram-DBY2YBRQ-DFqfnQsf.js} +1 -1
- package/ui/assets/{sizeCapture-X5ZJPWSS-ChFiLYIq.js → sizeCapture-X5ZJPWSS-CKi1Gbcu.js} +1 -1
- package/ui/assets/{stateDiagram-2N3HPSRC-CLghJsIY.js → stateDiagram-2N3HPSRC-BRzK72Ic.js} +1 -1
- package/ui/assets/stateDiagram-v2-6OUMAXLB-BfIPzqjE.js +1 -0
- package/ui/assets/{swimlanes-5IMT3BWC-BM0kePsz.js → swimlanes-5IMT3BWC-BPPMyDO3.js} +2 -2
- package/ui/assets/swimlanesDiagram-G3AALYLV-CnGv7Q4F.js +8 -0
- package/ui/assets/{timeline-definition-FHXFAJF6-CnaYehNM.js → timeline-definition-FHXFAJF6-B2NwAfdF.js} +1 -1
- package/ui/assets/ui-vendor-D3bT0cC5.js +8 -0
- package/ui/assets/{vennDiagram-L72KCM5P-hcntCV0l.js → vennDiagram-L72KCM5P-jL58xzzb.js} +1 -1
- package/ui/assets/{wardleyDiagram-EHGQE667-CmVUCl57.js → wardleyDiagram-EHGQE667-BVEkPReU.js} +1 -1
- package/ui/assets/{xychartDiagram-FW5EYKEG-D2Ic_Fag.js → xychartDiagram-FW5EYKEG-C1Fggi1D.js} +1 -1
- package/ui/index.html +3 -3
- package/ui/assets/channel-DMwuXOvE.js +0 -1
- package/ui/assets/classDiagram-OUVF2IWQ-B85IV-lf.js +0 -1
- package/ui/assets/classDiagram-v2-EOCWNBFH-B85IV-lf.js +0 -1
- package/ui/assets/index-D0U6u6ZE.css +0 -1
- package/ui/assets/index-_N_vFS94.js +0 -400
- package/ui/assets/stateDiagram-v2-6OUMAXLB-Vs-nKlLB.js +0 -1
- package/ui/assets/swimlanesDiagram-G3AALYLV-Bf7y6XsE.js +0 -8
- package/ui/assets/ui-vendor-CxIbXZyS.js +0 -3
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("node:url"),i=require("fastify"),a=require("@fastify/cookie"),r=require("@fastify/websocket"),o=require("zod"),d=require("node:crypto"),c=require("node:util"),l=require("write-file-atomic"),p=require("ws"),u=require("node:http"),h=require("node:net"),g=require("telegramify-markdown"),m=require("node:fs/promises"),I=require("node:os"),f=require("dingtalk-stream"),y="undefined"!=typeof document?document.currentScript:null;const b=20971520,v=41943040,w="ai-session.snapshot",S="ai-session.patch",A="ai-session.removed",k=o.z.string().trim().min(1).max(80),T=o.z.enum(["running","waiting","idle","failed"]),E=o.z.enum(["thinking","tool","editing","approval","responding","unknown"]),C=o.z.object({name:o.z.string().trim().min(1).max(120),inputPreview:o.z.string().trim().max(500).optional(),startedAt:o.z.string().datetime().optional()}).strict(),N=o.z.object({toolCalls:o.z.number().int().min(0).default(0),edits:o.z.number().int().min(0).default(0),approvals:o.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),_=o.z.object({send:o.z.boolean().optional(),interrupt:o.z.boolean().optional(),approval:o.z.boolean().optional()}).strict(),z=o.z.object({id:o.z.string().trim().min(1).max(120),kind:o.z.enum(["image"]),name:o.z.string().trim().min(1).max(240),mime:o.z.string().trim().min(1).max(120),size:o.z.number().int().min(0).max(b),data:o.z.string().min(1).max(31457280)}).strict(),O=z.omit({data:!0}),R=o.z.object({id:o.z.string().trim().min(1).max(120),kind:o.z.enum(["image"]).optional()}).strict(),x=o.z.enum(["auto","queue","steer","immediate"]),j=o.z.object({message:o.z.string().trim().min(1).max(2e4),mode:x.optional()});j.extend({attachments:o.z.array(z).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>v&&t.addIssue({code:o.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const D=j.extend({attachments:o.z.array(R).max(6).default([])}).strict(),P=o.z.object({decision:o.z.enum(["allow","deny","skip"])}).strict();o.z.object({queueIds:o.z.array(o.z.string().trim().min(1).max(120)).max(100)}).strict(),o.z.object({code:o.z.string().trim().min(1).max(160),message:o.z.string(),instanceId:o.z.string().trim().min(1).max(160).optional(),nodeId:o.z.string().trim().min(1).max(160).optional(),route:o.z.string().trim().min(1).max(4096).optional()}).strict();const M=o.z.object({id:o.z.string().trim().min(1).max(120),message:o.z.string().trim().min(1).max(2e4),attachments:o.z.array(O).max(6).default([]),status:o.z.enum(["queued","sending","failed"]).default("queued"),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime(),error:o.z.string().trim().max(4e3).optional()}).strict(),$=o.z.object({pendingCount:o.z.number().int().min(0).default(0),items:o.z.array(M).max(100).default([])}).strict().default({pendingCount:0,items:[]}),q=o.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),L=o.z.object({id:o.z.string().trim().min(1).max(240),providerTurnId:o.z.string().trim().max(240).optional(),source:q.optional(),userPrompt:o.z.string().trim().optional(),status:o.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:E.optional(),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),revision:o.z.number().int().min(0).default(0),sourcePriority:o.z.number().int().min(0).max(100).optional(),snapshotVersion:o.z.number().int().min(0).optional(),observedAt:o.z.string().datetime().optional(),startedAt:o.z.string().datetime().optional(),updatedAt:o.z.string().datetime().optional(),completedAt:o.z.string().datetime().optional()}).strict(),U=o.z.object({id:o.z.string().trim().min(1).max(120),agent:k,appSessionId:o.z.string().trim().max(120).optional(),appId:o.z.string().trim().max(120).optional(),providerSessionId:o.z.string().trim().max(240).optional(),providerMeta:o.z.record(o.z.string(),o.z.unknown()).optional(),appBindingKeys:o.z.array(o.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),activeTurnId:o.z.string().trim().max(240).optional(),conversationId:o.z.number().int().positive().optional(),title:o.z.string().trim().max(240).optional(),cwd:o.z.string().trim().max(4096).optional(),userPrompt:o.z.string().trim().optional(),turns:o.z.array(L).max(50).optional(),status:T.default("running"),phase:E.default("unknown"),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),currentTool:C.optional(),transcriptPath:o.z.string().trim().max(4096).optional(),transcriptSize:o.z.number().int().min(0).optional(),startedAt:o.z.string().datetime(),updatedAt:o.z.string().datetime(),completedAt:o.z.string().datetime().optional(),error:o.z.string().trim().max(4e3).optional(),counters:N,queue:$}).strict(),B=o.z.object({session:U,provider:o.z.string().trim().min(1).max(80),action:o.z.enum(["send","queue","steer","interrupt","approval"]),decision:o.z.enum(["allow","deny","skip"]).optional(),turnId:o.z.string().trim().min(1).max(240).optional(),providerTurnId:o.z.string().trim().min(1).max(240).optional(),queueId:o.z.string().trim().min(1).max(120).optional()}).strict(),F=U.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}),H=o.z.object({runningCount:o.z.number().int().min(0).default(0),waitingCount:o.z.number().int().min(0).default(0),staleCount:o.z.number().int().min(0).default(0),sessions:o.z.array(F).default([]),updatedAt:o.z.string().datetime()}).strict(),G=o.z.object({streamId:o.z.string().trim().min(1).max(240),revision:o.z.number().int().min(0),lastEventAt:o.z.string().datetime(),snapshot:H}).strict(),W=H.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),revision:o.z.number().int().min(0).optional()});function V(e,t={}){const s=e.sessions.filter(e=>"idle"===e.status).length,n=e.sessions.filter(e=>"failed"===e.status).length;return W.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:s,problemCount:n,updatedAt:e.updatedAt,revision:t.revision})}const K=o.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),J=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),nodeId:o.z.string().trim().min(1).max(160).optional(),revision:o.z.number().int().min(0),previousRevision:o.z.number().int().min(0).optional(),traceId:o.z.string().trim().min(1).max(160),generatedAt:o.z.string().datetime(),reason:K}).strict(),Q=o.z.object({meta:J,snapshot:H}).strict(),Y=o.z.object({meta:J,upserted:o.z.array(F).default([]),removed:o.z.array(o.z.string().trim().min(1).max(120)).default([])}).strict(),X=o.z.object({meta:J,sessionIds:o.z.array(o.z.string().trim().min(1).max(120)).default([]),expiresAt:o.z.string().datetime()}).strict(),Z=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),sinceRevision:o.z.number().int().min(0),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0),syncRequired:o.z.boolean().default(!1),events:o.z.array(o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal(w),payload:Q}).strict(),o.z.object({type:o.z.literal(S),payload:Y}).strict(),o.z.object({type:o.z.literal(A),payload:X}).strict()])).default([])}).strict(),ee=o.z.object({source:q,sourcePriority:o.z.number().int().min(0).max(100).optional(),observedAt:o.z.string().datetime().optional(),snapshotVersion:o.z.number().int().min(0).optional()}).strict(),te=ee.extend({type:o.z.literal("snapshot"),agent:o.z.string().trim().min(1).max(80),appSessionId:o.z.string().trim().max(120).optional(),appId:o.z.string().trim().max(120).optional(),providerSessionId:o.z.string().trim().max(240).optional(),providerMeta:o.z.record(o.z.string(),o.z.unknown()).optional(),appBindingKeys:o.z.array(o.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),conversationId:o.z.number().int().positive().optional(),title:o.z.string().trim().max(240).optional(),cwd:o.z.string().trim().max(4096).optional(),activeTurnId:o.z.string().trim().max(240).optional(),userPrompt:o.z.string().trim().optional(),turns:o.z.array(L).max(50).optional(),status:T.optional(),phase:E.optional(),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),transcriptPath:o.z.string().trim().max(4096).optional(),transcriptSize:o.z.number().int().min(0).optional(),replaceActivity:o.z.boolean().optional()}).strict(),se=ee.extend({type:o.z.literal("event"),sessionId:o.z.string().trim().min(1).max(120),kind:o.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:o.z.string().trim().max(240).optional(),providerTurnId:o.z.string().trim().max(240).optional(),userPrompt:o.z.string().trim().optional(),text:o.z.string().optional(),status:T.optional(),phase:E.optional(),summary:o.z.string().trim().max(1e3).optional(),error:o.z.string().trim().max(4e3).optional(),counters:o.z.object({toolCalls:o.z.number().int().min(0).optional(),edits:o.z.number().int().min(0).optional(),approvals:o.z.number().int().min(0).optional()}).strict().optional()}).strict();o.z.discriminatedUnion("type",[te,se]);const ne=o.z.union([o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("interval").default("interval"),intervalMs:o.z.number().int().min(1e3)}).strict(),o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("daily"),timeOfDay:o.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("weekly"),weekdays:o.z.array(o.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:o.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:o.z.string().trim().min(1).max(120)}).strict()]),ie=o.z.union([ne,o.z.object({type:o.z.literal("file-change"),roots:o.z.array(o.z.string().trim().min(1).max(4096)).min(1).max(50),globs:o.z.array(o.z.string().trim().min(1).max(500)).min(1).max(100),ignore:o.z.array(o.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:o.z.number().int().min(100).max(6e4).default(1500)}).strict(),o.z.object({type:o.z.literal("ai-session"),agent:o.z.string().trim().min(1).max(80).optional(),statuses:o.z.array(o.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:o.z.array(o.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),ae=o.z.object({promptTemplate:o.z.string().trim().min(1).max(2e4)}).strict(),re=o.z.object({cooldownMs:o.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:o.z.number().int().min(1).max(20).default(1),whenBusy:o.z.enum(["skip","queue"]).default("skip")}).strict(),oe=o.z.object({configHash:o.z.string().trim().min(8).max(80),name:o.z.string().trim().min(1).max(160),description:o.z.string().trim().max(1e3).optional(),source:ie,action:ae,policy:re.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),de=o.z.object({type:o.z.literal("ai-session"),aiSessionId:o.z.string().trim().min(1).max(160)}).strict(),ce=o.z.object({configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),origin:o.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:o.z.boolean().default(!0),target:de,localName:o.z.string().trim().min(1).max(160).optional(),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),le=o.z.object({configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),status:o.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:o.z.string().datetime().optional(),lastCompletedAt:o.z.string().datetime().optional(),lastSkippedAt:o.z.string().datetime().optional(),lastError:o.z.string().trim().max(4e3).optional(),runCount:o.z.number().int().min(0).default(0),skippedCount:o.z.number().int().min(0).default(0)}).strict(),pe=o.z.object({id:o.z.string().trim().min(1).max(160),configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),eventType:o.z.enum(["manual","schedule","file-change","ai-session"]),status:o.z.enum(["started","completed","failed","skipped"]),target:de,promptPreview:o.z.string().trim().max(500),eventSummary:o.z.string().trim().max(1e3).optional(),error:o.z.string().trim().max(4e3).optional(),startedAt:o.z.string().datetime(),completedAt:o.z.string().datetime().optional()}).strict(),ue=o.z.object({schemaVersion:o.z.literal(1).default(1),configs:o.z.array(oe).default([]),deployments:o.z.array(ce).default([]),runtime:o.z.array(le).default([]),recentRuns:o.z.array(pe).default([])}).strict();function he(e){return Array.isArray(e)?e.map(he):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,he(t)])):e}const ge="2026-07-15",me=o.z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/,"Protocol version must use YYYY-MM-DD format."),Ie=o.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),fe=o.z.string().datetime(),ye=o.z.record(o.z.string(),o.z.string()).default({}),be=o.z.record(o.z.string(),o.z.string()).default({}),ve=o.z.object({codexModelHash:Ie.optional(),claudeModelHash:Ie.optional()}).strict().default({}),we=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(120),kind:o.z.enum(["tty","gui","web"]),source:o.z.enum(["builtin","custom"]),availability:o.z.enum(["available","missing-dependency"]),capabilities:o.z.object({automation:o.z.enum(["cdp"]).optional(),supportsCwdSelection:o.z.boolean().default(!1)}).strict().default({supportsCwdSelection:!1}),diagnosticCode:o.z.enum(["APP_EXECUTABLE_NOT_FOUND"]).optional()}).strict(),Se=o.z.object({code:o.z.enum(["APP_CATALOG_INVALID"]),message:o.z.string().trim().min(1).max(500)}).strict(),Ae=o.z.object({items:o.z.array(we).max(256),observedAt:fe,issues:o.z.array(Se).max(32).default([])}).strict(),ke=o.z.object({component:o.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:o.z.string().trim().max(160).optional(),packageVersion:o.z.string().trim().max(80).optional(),protocolVersion:me.optional(),buildId:o.z.string().trim().max(160).optional(),builtAt:o.z.string().trim().max(120).optional(),gitCommit:o.z.string().trim().max(120).optional(),imageRef:o.z.string().trim().max(512).optional(),imageDigest:o.z.string().trim().max(240).optional()}).strict(),Te=o.z.enum(["stable","beta","alpha"]),Ee=o.z.discriminatedUnion("component",[o.z.object({component:o.z.literal("node-agent")}).strict(),o.z.object({component:o.z.literal("controlled-instance"),instanceId:Ie}).strict()]),Ce=o.z.object({target:Ee,channel:Te.default("stable")}).strict(),Ne=o.z.object({target:Ee,source:o.z.enum(["npm","docker-registry"]),channel:Te,currentVersion:o.z.string().trim().max(240).optional(),availableVersion:o.z.string().trim().min(1).max(240),artifactRef:o.z.string().trim().min(1).max(512).optional(),updateAvailable:o.z.boolean(),supported:o.z.boolean().default(!0),reason:o.z.string().trim().max(2048).optional(),checkedAt:fe}).strict(),_e=o.z.object({id:Ie,nodeId:Ie,target:Ee,source:o.z.enum(["npm","docker-registry"]),channel:Te,fromVersion:o.z.string().trim().max(240).optional(),toVersion:o.z.string().trim().min(1).max(240),artifactRef:o.z.string().trim().min(1).max(512).optional(),status:o.z.enum(["queued","updating","restarting","succeeded","failed"]),error:o.z.string().trim().max(4096).optional(),startedAt:fe.optional(),completedAt:fe.optional(),createdAt:fe,updatedAt:fe}).strict(),ze=o.z.object({type:o.z.enum(["branch","tag","commit"]),name:o.z.string().trim().min(1).max(240).optional(),commit:o.z.string().trim().max(80).optional()}).strict(),Oe=o.z.object({type:o.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:o.z.string().trim().min(1).max(120).optional()}).strict(),Re=o.z.object({depth:o.z.number().int().positive().max(1e5).optional(),submodules:o.z.boolean().default(!1),lfs:o.z.boolean().default(!1),subdirectory:o.z.string().trim().max(240).default("")}).strict();o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),url:o.z.string().trim().min(1).max(2048),provider:o.z.string().trim().max(80).optional(),ref:ze.default({type:"branch",name:"main"}),auth:Oe.default({type:"none"}),clone:Re.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:Ie.optional(),labels:ye,createdAt:fe,updatedAt:fe}).strict();const xe=o.z.object({id:Ie,nodeId:Ie,name:o.z.string().trim().min(1).max(160),path:o.z.string().trim().min(1).max(4096),defaultImageId:Ie.optional(),labels:ye,createdAt:fe,updatedAt:fe}).strict(),je=o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("git-repository"),repositoryId:Ie.optional(),url:o.z.string().trim().min(1).max(2048),provider:o.z.string().trim().max(80).optional(),ref:ze.default({type:"branch",name:"main"}),auth:Oe.default({type:"none"}),clone:Re.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),o.z.object({type:o.z.literal("git-template"),url:o.z.string().trim().min(1).max(2048),templateId:o.z.string().trim().min(1).max(120).optional(),ref:ze.default({type:"branch",name:"main"}),auth:Oe.default({type:"none"}),clone:Re.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),o.z.object({type:o.z.literal("local-folder"),localFolderId:Ie.optional(),path:o.z.string().trim().min(1).max(4096),ownerNodeId:Ie.optional()}).strict()]),De=o.z.object({mode:o.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:o.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:o.z.string().trim().max(240).optional(),readOnly:o.z.boolean().default(!1)}).strict(),Pe=o.z.enum(["codex","claude"]),Me=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),source:je,defaultImageId:Ie.optional(),defaultNodeId:Ie.optional(),defaultRuntimeId:Ie.optional(),workspacePolicy:De,labels:ye,createdAt:fe,updatedAt:fe}).strict(),$e=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),endpoint:o.z.string().trim().min(1).max(2048),key:o.z.string().trim().min(1).max(4096),model:o.z.string().trim().min(1).max(240),app:Pe,enabled:o.z.boolean().default(!0),order:o.z.number().int().min(0).max(1e6).default(0),labels:ye,createdAt:fe,updatedAt:fe}).strict(),qe=$e.omit({key:!0}).extend({keyPreview:o.z.string().trim().min(1).max(160),keySet:o.z.boolean()}).strict(),Le=qe.extend({referenceCount:o.z.number().int().min(0)}).strict(),Ue=o.z.object({instanceId:Ie,codexModelHash:Ie.optional(),claudeModelHash:Ie.optional(),updatedAt:fe}).strict(),Be=o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("control-plane"),name:$e.shape.name,enabled:$e.shape.enabled,order:$e.shape.order}).strict(),o.z.object({type:o.z.literal("node"),nodeId:Ie,name:$e.shape.name,enabled:$e.shape.enabled,order:$e.shape.order,referenceCount:o.z.number().int().min(0)}).strict()]),Fe=o.z.object({id:Ie,model:qe,locations:o.z.array(Be).min(1),referenceCount:o.z.number().int().min(0).default(0)}).strict(),He=o.z.object({nodeId:Ie,code:o.z.string().trim().min(1).max(120),message:o.z.string().trim().min(1).max(2e3)}).strict(),Ge=o.z.object({models:o.z.array(Fe),nodeDiagnostics:o.z.array(He).default([]),updatedAt:fe}).strict();function We(e){const t={app:Pe.parse(e.app),endpoint:$e.shape.endpoint.parse(e.endpoint),key:$e.shape.key.parse(e.key),model:$e.shape.model.parse(e.model)};return`mdl_${d.createHash("sha256").update(JSON.stringify(t)).digest("hex")}`}$e.omit({id:!0,createdAt:!0,updatedAt:!0}).strict().partial().strict(),o.z.object({modelSelection:ve,codexModelHash:Ie.optional(),claudeModelHash:Ie.optional()}).strict();const Ve=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),image:o.z.string().trim().min(1).max(512),registry:o.z.string().trim().min(1).max(160).default("local"),capabilities:o.z.array(o.z.string().trim().min(1).max(80)).default([]),optionalApps:o.z.array(o.z.string().trim().min(1).max(120)).default([]),defaultEnv:be,labels:ye,createdAt:fe,updatedAt:fe}).strict(),Ke=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),connectionMode:o.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:o.z.object({mode:o.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Ie.optional(),secret:o.z.string().trim().max(4096).optional(),pairedAt:fe.optional(),pairing:o.z.object({status:o.z.enum(["pending","paired","expired"]).default("pending"),joinToken:o.z.string().trim().max(4096).optional(),expiresAt:fe.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:o.z.string().trim().max(2048).optional(),controlEndpoint:o.z.string().trim().max(2048).optional(),containerEndpoint:o.z.string().trim().max(2048).optional(),publicWebBase:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:o.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:Ae.optional(),labels:ye,lastSeenAt:fe.optional(),createdAt:fe,updatedAt:fe}).strict(),Je=o.z.object({id:Ie,nodeId:Ie,name:o.z.string().trim().min(1).max(160),type:o.z.enum(["docker","kubernetes","local"]),status:o.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:o.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),labels:ye,createdAt:fe,updatedAt:fe}).strict(),Qe=o.z.object({ok:o.z.boolean().optional(),role:o.z.string().optional(),nodeId:Ie.optional()}).passthrough(),Ye=o.z.object({bindScope:o.z.enum(["loopback","all-ipv4"]),port:o.z.number().int().min(1).max(65535)}).strict().extend({host:o.z.enum(["127.0.0.1","0.0.0.0"]),status:o.z.enum(["listening","error"]),source:o.z.enum(["bootstrap","persisted"]),error:o.z.string().trim().max(2048).optional()}).strict(),Xe=o.z.object({nodeId:Ie,joinToken:o.z.string().trim().min(1),expiresAt:fe}).passthrough(),Ze=o.z.object({id:Ie,keyId:Ie,url:o.z.string().trim().min(1).max(2048).optional(),name:o.z.string().trim().min(1).max(160).optional(),pairedAt:fe,updatedAt:fe.optional(),active:o.z.boolean().optional(),current:o.z.boolean().optional()}).passthrough(),et=o.z.object({remote:o.z.object({id:Ie,url:o.z.string().trim().min(1).max(2048).optional(),keyId:Ie,pairedAt:fe,active:o.z.boolean()}).passthrough(),tunnel:o.z.object({status:o.z.enum(["disabled","saved","connecting","connected","failed"]),error:o.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),tt=o.z.object({deleted:o.z.boolean().optional()}).passthrough(),st=o.z.object({repository:o.z.string().trim().optional(),tag:o.z.string().trim().optional(),id:o.z.string().trim(),createdSince:o.z.string().trim().optional(),size:o.z.string().trim().optional(),reference:o.z.string().trim().min(1)}).passthrough(),nt=o.z.lazy(()=>o.z.object({name:o.z.string().trim().min(1),path:o.z.string().trim().min(1).max(4096),children:o.z.array(nt)}).passthrough()),it=o.z.object({status:o.z.number().int().min(100).max(599).optional(),headers:o.z.record(o.z.string(),o.z.string()).optional(),bodyBase64:o.z.string().optional()}).passthrough(),at=o.z.object({strategy:o.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:o.z.string().trim().max(2048).optional(),api:o.z.string().trim().max(2048).optional(),vnc:o.z.string().trim().max(2048).optional(),tty:o.z.string().trim().max(2048).optional(),logs:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),rt=o.z.object({strategy:o.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:o.z.string().trim().max(2048).optional(),api:o.z.string().trim().max(2048).optional(),ws:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),ot=o.z.object({mode:o.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:o.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:o.z.string().trim().max(4096).optional(),resolvedCommit:o.z.string().trim().max(120).optional(),error:o.z.string().trim().max(2048).optional()}).passthrough(),dt=o.z.object({id:Ie,name:o.z.string().trim().min(1).max(160),projectId:Ie.optional(),source:je,sourceSnapshot:o.z.record(o.z.string(),o.z.unknown()).default({}),modelSelection:ve,nodeId:Ie,runtimeId:Ie,imageId:Ie.optional(),imageSnapshot:Ve.optional(),status:o.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:o.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:o.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:o.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:o.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:me.optional(),instanceVersion:o.z.string().trim().max(80).optional(),build:ke.optional(),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:Ae.optional(),config:o.z.object({autoImportAgentConfigs:o.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:ot.default({status:"unknown"}),target:at.default({strategy:"direct-port",status:"unknown"}),access:rt,apps:o.z.object({runningCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),updatedAt:fe.optional(),revision:o.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:H.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:o.z.object({enabledCount:o.z.number().int().min(0).default(0),runningCount:o.z.number().int().min(0).default(0),errorCount:o.z.number().int().min(0).default(0),configs:o.z.array(o.z.object({configHash:o.z.string().trim().min(1),config:oe,deployments:o.z.array(ce).default([]),runtime:o.z.array(le).default([])}).strict()).default([]),recentRuns:o.z.array(pe).default([])}).strict().optional(),runtime:o.z.object({kind:o.z.string().trim().max(80).optional(),containerName:o.z.string().trim().max(240).optional(),containerId:o.z.string().trim().max(240).optional(),workspacePath:o.z.string().trim().max(4096).optional(),pid:o.z.number().int().positive().optional(),port:o.z.number().int().positive().max(65535).optional(),labels:o.z.record(o.z.string(),o.z.string()).default({})}).strict().default({labels:{}}),registrationToken:o.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:fe.optional(),createdAt:fe,updatedAt:fe}).strict();function ct(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]]))}o.z.object({instanceId:Ie.optional(),name:o.z.string().trim().min(1).max(160),projectId:Ie.optional(),source:je.optional(),nodeId:Ie.optional(),runtimeId:Ie.optional(),imageId:Ie.optional(),instanceVersion:o.z.string().trim().max(80).optional(),protocolVersion:me,build:ke.optional(),controlMode:o.z.enum(["standalone","controlled"]).default("controlled"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:Ae.optional(),target:at.default({strategy:"direct-port",status:"unknown"}),workspace:ot.default({status:"unknown"}),registrationToken:o.z.string().trim().max(240).optional()}).strict().superRefine((e,t)=>{e.protocolVersion!==ge||e.appInventory||t.addIssue({code:"custom",path:["appInventory"],message:"Current protocol register payload requires appInventory."})}),o.z.object({status:dt.shape.status.optional(),health:dt.shape.health.optional(),protocolVersion:me,build:ke.optional(),capabilities:dt.shape.capabilities.optional(),appInventory:Ae.optional(),apps:dt.shape.apps.optional(),aiSessions:dt.shape.aiSessions.optional(),triggers:dt.shape.triggers.optional(),workspace:ot.optional(),target:at.partial().optional()}).strict().superRefine((e,t)=>{e.protocolVersion!==ge||e.appInventory||t.addIssue({code:"custom",path:["appInventory"],message:"Current protocol heartbeat payload requires appInventory."})});const lt=o.z.enum(["web","telegram","wechat","dingding"]),pt=o.z.object({id:Ie,channel:lt,name:o.z.string().trim().min(1).max(160),enabled:o.z.boolean().default(!1),token:o.z.string().trim().max(4096).optional(),defaultChatId:o.z.string().trim().max(240).optional(),allowedUserIds:o.z.array(o.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:o.z.number().int().positive().max(6e4).default(3e3),settings:o.z.record(o.z.string(),o.z.unknown()).default({}),createdAt:fe,updatedAt:fe}).strict(),ut=o.z.object({id:Ie,channel:lt,bridgeId:Ie.optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional(),activeProjectId:Ie.optional(),activeInstanceId:Ie.optional(),activeAiSessionId:o.z.string().trim().min(1).max(120).optional(),lastUsedAt:fe,createdAt:fe,updatedAt:fe}).strict(),ht=o.z.object({source:o.z.object({type:o.z.literal("chat-gateway").default("chat-gateway"),channel:lt,bridgeId:Ie.optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional()}).strict(),message:o.z.object({text:o.z.string().trim().max(2e4).default(""),attachments:o.z.array(z).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>v&&t.addIssue({code:o.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:o.z.object({instanceId:Ie,aiSessionId:o.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),gt=o.z.object({id:Ie,instanceId:Ie,projectId:Ie.optional(),aiSessionId:o.z.string().trim().min(1).max(120),providerSessionId:o.z.string().trim().max(240).optional(),result:o.z.string().default(""),timeoutMs:o.z.number().positive().optional(),source:o.z.string().trim().max(120).optional(),kind:o.z.enum(["task","approval"]).default("task"),status:o.z.enum(["pending","unreachable"]).default("pending"),error:o.z.string().trim().max(2048).optional(),lastSeenAt:fe}).strict();function mt(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 It=o.z.enum(["ai.sessions","app.sessions"]),ft=o.z.object({topic:It,instanceId:o.z.string().trim().min(1).max(160),streamId:o.z.string().trim().min(1).max(240),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0)}).strict(),yt=o.z.object({protocolVersion:o.z.literal(1),streams:o.z.array(ft)}).strict();function bt(e){t.mkdirSync(e,{recursive:!0})}function vt(e,t){bt(s.dirname(e)),l.sync(e,`${JSON.stringify(t,null,2)}\n`,{encoding:"utf8"})}function wt(e,t){console.warn(JSON.stringify({message:e,...t}))}function St(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function At(e,s){const n=s.logger||wt;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void n("stored JSON could not be read",{filePath:e,...St(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 kt(e){return`${e}_${d.randomUUID().replace(/-/g,"").slice(0,20)}`}function Tt(){return d.randomBytes(24).toString("base64url")}class Et{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){bt(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=At(s.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const s=this.filePath(e);return t.existsSync(s)?At(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 vt(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 Ct{filePathValue;defaults;options;constructor(e,t,s={}){this.filePathValue=e,this.defaults=t,this.options=s}init(){bt(s.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?At(this.filePathValue,this.options)??this.defaults():this.defaults()}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,s=this.options.schema?this.options.schema.parse(t):t;return vt(this.filePathValue,s),s}}const Nt=c.promisify(d.scrypt),_t="task_handoff_cp_session",zt=o.z.enum(["disabled","password"]),Ot=o.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),Rt=o.z.string().min(8).max(4096),xt=o.z.object({username:Ot,password:Rt}).strict(),jt=o.z.object({username:o.z.string().trim().min(1).max(80),password:o.z.string().min(1).max(4096)}).strict(),Dt=o.z.object({id:o.z.string().trim().min(1),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}),Pt=Dt.extend({username:Ot,passwordHash:o.z.string().trim().min(1),role:o.z.enum(["viewer","operator","admin"]),lastLoginAt:o.z.string().datetime().optional()}).strict(),Mt=Dt.extend({userId:o.z.string().trim().min(1),tokenHash:o.z.string().trim().min(1),expiresAt:o.z.string().datetime(),lastSeenAt:o.z.string().datetime().optional()}).strict();function $t(){return(new Date).toISOString()}function qt(e){return d.createHash("sha256").update(e).digest("base64url")}async function Lt(e){const t=d.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await Nt(e,t,64)).toString("base64url")}`}function Ut(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}class Bt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return zt.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new Et(e.authUsersDir,{schema:Pt}),this.sessions=new Et(e.authSessionsDir,{schema:Mt})}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=xt.parse(e),s=$t();return Ut(this.users.put({id:kt("user"),username:t.username,passwordHash:await Lt(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=jt.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 Nt(e,n,64),r=Buffer.from(i,"base64url");return r.length===a.length&&d.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=$t(),i=Tt(),a=new Date(Date.now()+12096e5).toISOString(),r=this.sessions.put({id:kt("sess"),userId:s.id,tokenHash:qt(i),expiresAt:a,createdAt:n,updatedAt:n,lastSeenAt:n});return this.users.put({...s,lastLoginAt:n,updatedAt:n}),{user:Ut({...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<=$t()||n.tokenHash!==qt(s))return;const i=this.users.get(n.userId);return i?(this.sessions.put({...n,lastSeenAt:$t(),updatedAt:$t()}),Ut(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 Ft="app-session.snapshot",Ht="app-session.patch",Gt="app-session.removed",Wt=o.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Vt=o.z.object({type:o.z.enum(["app-session","provider-session","adapter-key"]),id:o.z.string().trim().min(1).max(240),agent:o.z.string().trim().min(1).max(80).optional(),adapter:o.z.string().trim().min(1).max(80).optional(),key:o.z.string().trim().min(1).max(240).optional()}).strict(),Kt=o.z.record(o.z.string(),o.z.unknown()).and(o.z.object({id:o.z.string().trim().min(1).max(160),appId:o.z.string().trim().min(1).max(160).optional(),title:o.z.string().trim().max(240).optional(),kind:o.z.string().trim().max(80).optional(),status:Wt.default("unknown"),bindings:o.z.array(Vt).max(40).default([]),workspace:o.z.object({cwd:o.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:o.z.string().datetime().optional(),updatedAt:o.z.string().datetime().optional()})),Jt=o.z.object({runningCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),sessions:o.z.array(Kt).default([]),updatedAt:o.z.string().datetime()}).strict(),Qt=o.z.object({streamId:o.z.string().trim().min(1).max(240),revision:o.z.number().int().min(0),lastEventAt:o.z.string().datetime(),snapshot:Jt}).strict();Jt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:o.z.number().int().min(0).optional()});const Yt=o.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Xt=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),nodeId:o.z.string().trim().min(1).max(160).optional(),revision:o.z.number().int().min(0),previousRevision:o.z.number().int().min(0).optional(),traceId:o.z.string().trim().min(1).max(160),generatedAt:o.z.string().datetime(),reason:Yt}).strict(),Zt=o.z.object({meta:Xt,snapshot:Jt}).strict(),es=o.z.object({meta:Xt,session:Kt}).strict(),ts=o.z.object({meta:Xt,sessionId:o.z.string().trim().min(1).max(160),tombstone:Kt.optional(),expiresAt:o.z.string().datetime().optional()}).strict(),ss=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),sinceRevision:o.z.number().int().min(0),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0),syncRequired:o.z.boolean().default(!1),events:o.z.array(o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal(Ft),payload:Zt}).strict(),o.z.object({type:o.z.literal(Ht),payload:es}).strict(),o.z.object({type:o.z.literal(Gt),payload:ts}).strict()])).default([])}).strict();function ns(e,t=(new Date).toISOString()){const s=e.map(e=>is(e));return Jt.parse({runningCount:s.filter(e=>"running"===e.status).length,problemCount:s.filter(e=>"failed"===e.status).length,sessions:s,updatedAt:t})}function is(e){const t=as("string"==typeof e.status?e.status:void 0),s="string"==typeof e.id?e.id:"",n=function(e){const t=ls(e.workspace),s=ls(e.tty),n=ls(e.launch),i=ls(e.ai),a=ls(i.claude),r=ps(t.cwd)||ps(s.cwd)||ps(a.cwd)||ps(n.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return Kt.parse({...a,status:t,bindings:cs([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=Vt.safeParse(e);return t.success?[t.data]:[]}):[]),...s?[{type:"app-session",id:s}]:[],...os(e)]),...n?{workspace:n}:{}});var r}function as(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":Wt.options.includes(t)?t:"unknown"}function rs(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 os(e){const t=ls(e.ai),s=ls(t.claude),n=ls(t.appServer),i=[],a=ps(s.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=ps(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=ps(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=ps(n.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function ds(e){const t="string"==typeof e.id?e.id:"",s=ls(e.ai),n=ps(ls(s.claude).short);return[t?`app:${t}`:"",n?`claude-short:${n}`:""]}function cs(e){const t=new Map;for(const s of e)t.set(rs(s),s);return[...t.values()]}function ls(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function ps(e){return"string"==typeof e&&e.trim()?e.trim():""}function us(e=(new Date).toISOString()){return Jt.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function hs(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 gs{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 ms{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",Qe)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",Ne,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",o.z.array(_e))}getExternalListener(e){return this.client.requestSchema(e,"/settings/external-listener",Ye)}updateExternalListener(e,t){return this.client.requestSchema(e,"/settings/external-listener",Ye,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",Xe,{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",et,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",o.z.array(Ze))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,tt,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",o.z.array(st))}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}`:""),o.z.array(nt))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",o.z.array(xe))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",xe,{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",o.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)}`,tt,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,tt,{method:"DELETE"})}listModels(e){return this.client.requestSchema(e,"/models",o.z.array(Le))}createModel(e,t){return this.client.requestSchema(e,"/models",Le,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}deployModel(e,t,s){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}/deploy`,Le,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}updateModel(e,t,s){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}`,Le,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteModel(e,t){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}`,tt,{method:"DELETE"})}assignInstanceModels(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/model-assignment`,o.z.object({assignment:Ue,instance:dt}).strict(),{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}async listFleetModels(e){return(await Promise.allSettled(e.map(async e=>({node:e,models:await this.listModels(e)})))).reduce((t,s,n)=>{if("fulfilled"===s.status)t.items.push(...s.value.models.map(e=>({nodeId:s.value.node.id,model:e})));else{const i=e[n];i&&t.nodeErrors.push(hs(i,"/models","GET",s.reason))}return t},{items:[],nodeErrors:[]})}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(hs(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",o.z.array(o.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",dt,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,dt,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,tt,{method:"POST"})}startInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,dt,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,dt,{method:"POST"})}restartInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,dt,{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`,it,{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(hs(e[n],"/instances","GET",s.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const s="/instances",n=(i=t,dt.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e;Object.prototype.hasOwnProperty.call(t,"receiver");const s=new Set(Object.keys(dt.shape)),n={};for(const[e,i]of Object.entries(t))s.has(e)&&(n[e]=i);if(t.capabilities&&"object"==typeof t.capabilities&&!Array.isArray(t.capabilities)){const e={...t.capabilities};Object.prototype.hasOwnProperty.call(e,"apps")&&delete e.apps,n.capabilities=e}return n.appInventory=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,s={observedAt:t.observedAt,items:Array.isArray(t.items)?t.items.map(e=>{const t=ct(e,["id","name","kind","source","availability","diagnosticCode"]);return!t||"object"!=typeof t||Array.isArray(t)?t:{...t,capabilities:ct(e.capabilities,["automation","supportsCwdSelection"])}}):t.items,issues:Array.isArray(t.issues)?t.issues.map(e=>ct(e,["code","message"])):t.issues},n=Ae.safeParse(s);return n.success?n.data:void 0}(t.appInventory),n.apps=ct(t.apps,["runningCount","problemCount","updatedAt","revision"]),n.config=ct(t.config,["autoImportAgentConfigs"]),n.modelSelection=ct(t.modelSelection,["codexModelHash","claudeModelHash"]),n.runtime=ct(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),n.target=ct(t.target??t.endpoints,["strategy","web","api","status"]),n.access=ct(t.access,["strategy","web","api","ws","status"]),n}(i)));var i;if(n.success)return n.data.protocolVersion&&n.data.protocolVersion!==ge?(this.client.logger?.warn?.({nodeId:e.id,instanceId:n.data.id,expectedProtocolVersion:ge,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 ${ge}.`,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 Is(){return(new Date).toISOString()}function fs(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 ys(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 bs(e){return e instanceof Error?e.message:String(e)}function vs(e,t){const s=new Error(t);throw Object.assign(s,{statusCode:404,code:e}),s}class ws{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:fs(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:fs(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 Ss(e){if(!e||"object"!=typeof e)return;const t=e,s=ke.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 As(e){const{agent:t,...s}=e.capabilities,{secret:n,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=Ss(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...s,agent:r}:s}}function ks(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function Ts(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 Es(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 Cs{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=Ts(e.instance);return{...n,aiSessions:V(n.aiSessions),connectionStatus:s,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?As(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===ge}}({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 Ns=oe.extend({id:oe.shape.configHash}),_s=o.z.object({name:oe.shape.name,description:oe.shape.description,source:ie,action:ae,policy:re.partial().optional()}).strict(),zs=o.z.object({instanceIds:o.z.array(dt.shape.id).min(1),target:de,enabled:o.z.boolean().optional()}).strict(),Os=o.z.object({configHash:oe.shape.configHash,enabled:o.z.boolean().optional()}).strict();class Rs{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=_s.parse(e||{}),s=Is(),n={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=he({source:e.source,action:e.action,policy:re.parse(e.policy||{})});return`trg_${d.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:n}),a=Ns.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||vs("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:n}}async applyTrigger(e,t){const s=this.requireTrigger(e),n=zs.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=Os.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:xs(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=xs(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||vs("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function xs(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}const js=o.z.object({id:Me.shape.id.optional(),name:Me.shape.name,source:je,defaultImageId:Me.shape.defaultImageId,defaultNodeId:Me.shape.defaultNodeId,defaultRuntimeId:Me.shape.defaultRuntimeId,workspacePolicy:De.optional(),labels:Me.shape.labels.optional()}).strict(),Ds=js.omit({id:!0}).partial().strict(),Ps=o.z.object({publicBaseUrl:o.z.string().trim().max(2048).optional(),updateChannel:Te.default("stable")}).strict(),Ms=Ps.partial().strict(),$s=o.z.object({id:Ve.shape.id.optional(),name:Ve.shape.name,image:Ve.shape.image,registry:Ve.shape.registry.optional(),capabilities:o.z.array(o.z.string().trim().min(1).max(80)).optional(),optionalApps:o.z.array(o.z.string().trim().min(1).max(120)).optional(),defaultEnv:Ve.shape.defaultEnv.optional(),labels:Ve.shape.labels.optional()}).strict(),qs=$s.omit({id:!0}).partial().strict();class Ls{options;constructor(e){this.options=e}getSettings(){return Ps.parse(this.options.settings.get())}updateSettings(e){const t=Ms.parse(e),s=Ps.parse({...this.getSettings(),...t,..."publicBaseUrl"in t?{publicBaseUrl:t.publicBaseUrl?.trim()||void 0}:{}});return this.options.settings.put(s)}seedDefaults(){if(this.options.images.get("img_default"))return;const e=Is();this.options.images.put(Ve.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.options.projects.list().map(e=>this.normalizeProjectRecord(e))}getProject(e){const t=this.options.projects.get(e);return t?this.normalizeProjectRecord(t):void 0}createProject(e){const t=js.parse(e),s=Is(),n=je.parse(t.source);return this.options.projects.put(Me.parse({...t,id:t.id||kt("proj"),source:n,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.options.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",workspacePolicy:De.parse(t.workspacePolicy||ks(n)),labels:t.labels||{},createdAt:s,updatedAt:s}))}updateProject(e,t){const s=Ds.parse(t),n=this.requireProject(e),i=s.source?je.parse(s.source):n.source;return this.options.projects.put(Me.parse({...n,...s,id:e,source:i,workspacePolicy:s.workspacePolicy?De.parse(s.workspacePolicy):n.workspacePolicy,createdAt:n.createdAt,updatedAt:Is()}))}deleteProject(e){return this.options.projects.delete(e)}requireProject(e){const t=this.getProject(e);return t||vs("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}listImages(){return this.options.images.list()}createImage(e){const t=$s.parse(e),s=Is();return this.options.images.put(Ve.parse({...t,id:t.id||kt("img"),registry:t.registry||"local",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:s,updatedAt:s}))}updateImage(e,t){const s=qs.parse(t),n=this.requireImage(e);return this.options.images.put(Ve.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Is()}))}deleteImage(e){return this.options.images.delete(e)}requireImage(e){const t=this.options.images.get(e);return t||vs("IMAGE_NOT_FOUND",`Image ${e} was not found.`),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"),Me.parse(t)}return Me.parse(e)}(e);return t!==e&&this.options.projects.put(t),t}}class Us{records=new Map;options;constructor(e){this.options=e}create(e){const t=d.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 Bs{requireInstance;listAppSessions;tokens=new Us({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=>Gs(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:Fs(t,n.sessionId,r,s)}}}function Fs(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):Gs(Hs(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Gs(Hs(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Hs(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Gs(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Ws{tokens=new Us({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 Vs(e){const{token:t,...s}=e;return{...s,settings:Js(e.settings),tokenSet:Boolean(e.token)}}function Ks(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 Js(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}const Qs=o.z.object({name:pt.shape.name.optional(),channel:pt.shape.channel.optional(),enabled:o.z.boolean().optional(),token:pt.shape.token,defaultChatId:pt.shape.defaultChatId,allowedUserIds:o.z.array(o.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:pt.shape.pollIntervalMs.optional(),settings:pt.shape.settings.optional()}).strict(),Ys=Qs.extend({channel:pt.shape.channel,name:pt.shape.name.optional()});class Xs{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(Vs)}require(e){const t=this.chatBridges.get(e);return t||vs("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=Ys.parse(e),s=Is(),n=pt.parse({...t,id:kt(`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:Ks({},t.settings),createdAt:s,updatedAt:s});var i,a;return this.chatBridges.put(n),Vs(n)}update(e,t){const s=Qs.parse(t),n=this.require(e),i=Ks(n.settings,s.settings),a=pt.parse({...n,...s,id:e,channel:n.channel,name:s.name||n.name,settings:i,updatedAt:Is()});return this.chatBridges.put(a),Vs(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 Zs{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||vs("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=Is(),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=ut.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 en(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function tn(e,t=40){return en(e.slice(0,t).map(e=>[e]))}function sn(e){return Buffer.from(e,"utf8").toString("base64url")}function nn(e){const t=rn(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=rn(t),s=String(e.callback_data||""),i=String(e.text||"").trim();i&&s&&n.push({text:i,id:`th_cb_${sn(s)}`})}return n.slice(0,40)}function an(e,t){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function rn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const on=new Set(["stopped","failed","exited","closed","terminated"]);function dn(e){return!function(e){return on.has(as(e))}(e)}function cn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const s=(e.apps?.sessions||[]).filter(mn);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[...is(e).bindings.map(rs),...ds(e)].filter(Boolean)}(e).some(e=>n.has(e))))}(e,t))}function ln(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function pn(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function un(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=t?` - ${an(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${s}`}function hn(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return tn(e.flatMap(e=>"approval"!==e.kind?[]:[{text:an(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:an(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:an(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function gn(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 mn(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return dn("string"==typeof t.status?t.status:void 0)}class In{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?tn(t.map(e=>({text:`${e.active?"✓ ":""}${un(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:Ts(t),aiSession:s,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function fn(e){return e.providerTurnId||e.turnId||""}function yn(e){return dn(An(e.status))}function bn(e,t){const s=An(t.appId)||"App",n=An(t.title).trim();if(n&&n!==s)return n;const i=function(e,t){for(const s of e.appInventory?.items||[])if(s.id===t)return s.name;return""}(e,s);return i||s}function vn(e){const t=function(e){return"online"!==e.connectionStatus?[]:(e.appInventory?.items||[]).filter(e=>"available"===e.availability)}(e),s=new Map;for(const e of t){const t=kn(e);t&&!s.has(t.id)&&s.set(t.id,t)}return[...s.values()]}function wn(e,t,s,n){const i=An(t.id)?function(e,t){return`/apps/access/${Sn(e)}?token=${encodeURIComponent(t())}`}(t,n):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const s=An(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 Sn(e){const t=An(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function An(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function kn(e){if("string"==typeof e&&e.trim())return{id:e,label:Tn(e)};const t=function(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}(e),s=An(t.id).trim();return s?{id:s,label:An(t.name).trim()||Tn(s)}:void 0}function Tn(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())}class En{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?tn(t.map(e=>{return{text:an(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=vn(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?tn(n.map(e=>{return{text:an(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=vn(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(yn).filter(e=>Boolean(An(e.id))).map(e=>({instance:t,session:e,link:wn(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:An(e.id)||"",mode:Sn(e)}).token)})):[]}),i=n.filter(e=>e.link.url).map(e=>({text:an(`${e.instance.name} · ${bn(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?tn(i):void 0};var a,r}}class Cn{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:Ts(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}: ${bs(t)}`}}}failedSendResult(e,t,s,n){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:Ts(s),reply:n}}sentResult(e,t,s,n,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:Ts(s),aiSession:i,instanceId:s.id,aiSessionId:n,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:fn(i),reply:`Sent to ${t?.name||s.sourceSnapshot.name||"Source"} / ${s.name} / ${n}.`};var a}}class Nn{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(gn).join("\n"):"No pending routes.",replyMarkup:s.length?hn(s,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,s){const n=ys(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=ys(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 _n{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 zn{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}`:`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?cn({...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=>cn(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=ln(t.session)-ln(e.session);return s||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||pn(e.session).localeCompare(pn(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||vs("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const s=await this.findInstance(e,t);return s||vs("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 On{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new zn(e),this.sender=new Cn(e,this.targets),this.appCommands=new En(e,this.targets),this.pendingCommands=new Nn(e),this.aiSessionCommands=new In(e,this.targets),this.targetCommands=new _n(e,this.targets)}async handleChatGatewayMessage(e){const t=ht.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 Rn(e){return/^[a-z]:[\\/]/i.test(e)||/^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/.test(e)}function xn(e){return Rn(e)?s.win32:s.posix}const jn=o.z.object({mode:o.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Ke.shape.id.optional(),secret:o.z.string().trim().max(4096).optional(),pairedAt:Ke.shape.createdAt.optional(),pairing:o.z.object({status:o.z.enum(["pending","paired","expired"]).default("pending"),joinToken:o.z.string().trim().max(4096).optional(),expiresAt:Ke.shape.createdAt.optional()}).strict().optional()}).strict(),Dn=o.z.object({id:dt.shape.id.optional(),name:dt.shape.name.optional(),projectId:dt.shape.projectId,source:je.optional(),sourceSnapshot:o.z.record(o.z.string(),o.z.unknown()).optional(),nodeId:dt.shape.nodeId.optional(),runtimeId:dt.shape.runtimeId.optional(),imageId:dt.shape.imageId,config:dt.shape.config.optional(),modelSelection:dt.shape.modelSelection.optional()}).strict(),Pn=o.z.object({name:dt.shape.name.optional(),config:dt.shape.config.optional(),modelSelection:dt.shape.modelSelection.optional()}).strict(),Mn=o.z.object({name:$e.shape.name,endpoint:$e.shape.endpoint,key:$e.shape.key,model:$e.shape.model,app:$e.shape.app,enabled:$e.shape.enabled.optional(),order:$e.shape.order.optional(),labels:$e.shape.labels.optional()}).strict(),$n=Mn.partial().strict(),qn=o.z.object({id:Ke.shape.id.optional(),name:Ke.shape.name,connectionMode:Ke.shape.connectionMode.optional(),auth:jn.optional(),joinToken:o.z.string().trim().min(1).max(4096).optional(),endpoint:Ke.shape.endpoint,controlEndpoint:Ke.shape.controlEndpoint,containerEndpoint:Ke.shape.containerEndpoint,publicWebBase:Ke.shape.publicWebBase,status:o.z.enum(["unknown","online","offline","degraded"]).optional(),health:o.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:Ke.shape.capabilities.optional(),labels:Ke.shape.labels.optional(),lastSeenAt:Ke.shape.lastSeenAt}).strict(),Ln=o.z.object({name:Ke.shape.name.optional(),connectionMode:Ke.shape.connectionMode.unwrap().optional(),auth:jn.optional(),endpoint:Ke.shape.endpoint,controlEndpoint:Ke.shape.controlEndpoint,containerEndpoint:Ke.shape.containerEndpoint,publicWebBase:Ke.shape.publicWebBase,status:Ke.shape.status.unwrap().optional(),health:Ke.shape.health.unwrap().optional(),capabilities:Ke.shape.capabilities.unwrap().optional(),labels:Ke.shape.labels.unwrap().optional(),lastSeenAt:Ke.shape.lastSeenAt}).strict(),Un=o.z.object({controlPlaneUrl:o.z.string().trim().url().max(2048),joinToken:o.z.string().trim().min(1).max(4096),controlPlaneName:o.z.string().trim().min(1).max(160).optional(),activate:o.z.boolean().optional()}).strict(),Bn=o.z.object({nodeName:o.z.string().trim().min(1).max(160).optional(),expiresInMs:o.z.number().int().positive().max(36e5).optional()}).strict();function Fn(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 Hn="TASK_HANDOFF_NODE_AGENT_V1",Gn=6e4;function Wn(e=""){return d.createHash("sha256").update(e).digest("hex")}function Vn(e,t){return d.createHmac("sha256",e).update(function(e){return[Hn,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function Kn(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||d.randomBytes(18).toString("base64url"),n=Wn(e.body||""),i=Vn(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 Jn(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function Qn(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 Yn(e,t,s,n={}){const i=`/api/node-agent${t}`,a={createConnection:()=>h.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 Xn=o.z.object({id:o.z.string().trim().min(1),tokenHash:o.z.string().trim().min(1),expiresAt:o.z.string().datetime(),nodeName:o.z.string().trim().min(1).max(160).optional(),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),Zn="task-handoff.control-plane.local",ei="task-handoff.control-plane.builtin";function ti(e){return"true"===e.labels[Zn]}function si(e){return"true"===e.labels[ei]}function ni(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 ii(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return Kn({nodeId:e.id,keyId:ni(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 ai{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new Ws;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new Cs;controlPlaneTriggerService;catalogService;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 gs({request:(e,t,s)=>this.nodeAgentFetch(e,t,s),logger:this.logger});this.nodeAgentGateway=new ms(n),this.controlledInstanceGateway=new ws({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 Et(e.projectsDir,i(Me)),this.models=new Et(e.modelsDir,i($e)),this.images=new Et(e.imagesDir,i(Ve)),this.nodes=new Et(e.nodesDir,i(Ke)),this.chatSessions=new Et(e.chatSessionsDir,i(ut)),this.chatBridges=new Et(e.chatBridgesDir,i(pt)),this.triggers=new Et(e.triggersDir,i(Ns)),this.nodeJoinInvites=new Et(e.nodeJoinInvitesDir,i(Xn)),this.settings=new Ct(e.settingsPath,()=>Ps.parse({}),i(Ps)),this.catalogService=new Ls({projects:this.projects,images:this.images,settings:this.settings,defaultNodeId:()=>this.defaultNodeId()}),this.controlPlaneTriggerService=new Rs({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 Bs({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Xs({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new Zs({chatSessions:this.chatSessions}),this.chatSessionRuntime=new On({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.catalogService.getProject(e),listProjects:()=>this.catalogService.listProjects(),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 this.catalogService.getSettings()}updateSettings(e){return this.catalogService.updateSettings(e)}seedDefaults(){return this.catalogService.seedDefaults()}listProjects(){return this.catalogService.listProjects()}defaultNodeId(){return this.nodes.list().find(ti)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?ii(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!==ge){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 ${ge}.`);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:ge,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=ke.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=Jn(e);return n?(Qn(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:Is()}}async syncLocalNodeConnection(){const e={[Zn]:"true",[ei]:"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 Jn(e)?"local-ipc":"local-loopback"}(s),i=Ke.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:n,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:Is(),updatedAt:Is()}),a=await this.inspectNodeAgent(s,i),r=Is(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>ti(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=Ke.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:Ss(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(Es)}async listFederatedModels(){const e=this.listNodes(),t=await this.nodeAgentGateway.listFleetModels(e),s=new Map;for(const e of this.listAllModels())s.set(e.id,{id:e.id,model:Es(e),locations:[{type:"control-plane",name:e.name,enabled:e.enabled,order:e.order}],referenceCount:0});for(const{nodeId:e,model:n}of t.items){const{referenceCount:t,...i}=n,a=s.get(n.id);a?(a.locations.push({type:"node",nodeId:e,name:n.name,enabled:n.enabled,order:n.order,referenceCount:n.referenceCount}),a.referenceCount+=n.referenceCount):s.set(n.id,{id:n.id,model:i,locations:[{type:"node",nodeId:e,name:n.name,enabled:n.enabled,order:n.order,referenceCount:n.referenceCount}],referenceCount:n.referenceCount})}return Ge.parse({models:[...s.values()].sort((e,t)=>e.model.order-t.model.order||e.model.name.localeCompare(t.model.name)),nodeDiagnostics:t.nodeErrors.map(e=>({nodeId:e.nodeId,code:e.code,message:e.message})),updatedAt:Is()})}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))}async createModel(e){const t=Mn.parse(e),s=Is(),n=this.nextModelOrder(),i=We(t),a=this.models.get(i),r=$e.parse({...t,id:i,enabled:t.enabled??!0,order:t.order??n,labels:t.labels||{},createdAt:a?.createdAt||s,updatedAt:s});return Es(this.models.put(r))}async updateModel(e,t){const s=$n.parse(t),n=this.requireModelSecret(e),i=$e.parse({...n,...s,key:s.key?.trim()?s.key:n.key,createdAt:n.createdAt,updatedAt:Is()}),a=We(i);return Es(this.models.put($e.parse({...i,id:a})))}async deleteModel(e){return this.requireModelSecret(e),this.models.delete(e)}async 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)||vs("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const n=s.get(e);this.models.put($e.parse({...n,order:100*(t+1),updatedAt:Is()}))}),this.listModels()}createNodeModel(e,t){return this.nodeAgentGateway.createModel(this.requireNode(e),t)}updateNodeModel(e,t,s){return this.nodeAgentGateway.updateModel(this.requireNode(e),t,s)}deleteNodeModel(e,t){return this.nodeAgentGateway.deleteModel(this.requireNode(e),t)}createProject(e){return this.catalogService.createProject(e)}updateProject(e,t){return this.catalogService.updateProject(e,t)}deleteProject(e){return this.catalogService.deleteProject(e)}listImages(){return this.catalogService.listImages()}createImage(e){return this.catalogService.createImage(e)}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=Ke.parse({...t,id:n,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:Ss(e)}:t.capabilities,lastSeenAt:Is(),updatedAt:Is()});return n!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:Is(),agent:e}}catch(e){const s=Ke.parse({...t,status:"offline",health:"failed",updatedAt:Is()});return this.nodes.put(s),{id:t.id,status:"offline",checkedAt:Is(),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 await this.ensureInstanceModelAssignment(e),this.nodeAgentGateway.applyUpdate(s,t)}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(!si(t)||!ti(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=Bn.parse(e&&"object"==typeof e?e:{}),s=Is(),n=Tt(),i={id:kt("node_join"),tokenHash:d.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=o.z.object({joinToken:o.z.string().trim().min(1).max(4096),nodeId:Ke.shape.id,nodeName:Ke.shape.name.optional(),keyId:o.z.string().trim().min(1).max(160),secret:o.z.string().trim().min(1).max(4096),pairedAt:o.z.string().datetime().optional()}).parse(e),s=d.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=Is();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=Ke.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=Un.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){return this.catalogService.updateImage(e,t)}deleteImage(e){return this.catalogService.deleteImage(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(As)}async createNode(e){const t=qn.parse(e),s=Is(),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||kt("node"),d=Ke.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,h=Ke.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:Ss(c.data)}:{}},labels:t.labels||{},createdAt:s,updatedAt:s});return this.nodes.put(h)}updateNode(e,t){const s=Ln.parse(t),n=this.requireNode(e),i=Ke.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Is()});return this.nodes.put(i)}deleteNode(e){if(si(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(Ts)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(Ts),nodeErrors:e.nodeErrors}}async createControlledInstance(e){const t=Dn.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});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}const u=await this.prepareInstanceModels(a,t.modelSelection||{}),h=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,modelSelection:{}});let g=h;try{g=(await this.nodeAgentGateway.assignInstanceModels(a,h.id,u)).instance}catch(e){throw await this.nodeAgentGateway.deleteInstance(a,h.id).catch(()=>{}),e}return{...Ts(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const s=Pn.parse(t),n=await this.requireNodeInstance(e),i=this.requireNode(n.nodeId),{modelSelection:a,...r}=s;let o=Object.keys(r).length?await this.nodeAgentGateway.updateInstance(i,e,r):n;if(a){const t=await this.prepareInstanceModels(i,a);o=(await this.nodeAgentGateway.assignInstanceModels(i,e,t)).instance}return Ts(o)}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 await this.ensureInstanceModelAssignment(t),Ts(await this.nodeAgentGateway.startInstance(s,e))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return Ts(await this.nodeAgentGateway.stopInstance(s,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return await this.ensureInstanceModelAssignment(t),Ts(await this.nodeAgentGateway.restartInstance(s,e))}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.listImages(),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 G.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:bs(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=us();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:t.updatedAt,snapshot:t}}try{return Qt.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:bs(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const s=us();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 Z.parse(await this.instanceRequest(n,`/ai-sessions?${i}`))}async recoverAiSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return G.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 ss.parse(await this.instanceRequest(n,`/apps/sessions?${i}`))}async recoverAppSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return Qt.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=Is(),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.catalogService.getProject(s.projectId):void 0;for(const a of n.get(s.id)?.sessions||[]){if("waiting"!==a.status)continue;const n=gt.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:Ts(s)})}}return t}async resolveAiSessionApproval(e,t,s){const n=await this.requireControlledInstance(e,!0);return B.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)<=v)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 B.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 $.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return B.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 U.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 U.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 B.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=ii(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=Jn(a),o=`/api/node-agent${s}`,d=ii(e,{method:"GET",pathWithQuery:o});if(r){Qn(r);const e=Yn(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=ue.parse(await this.instanceRequest(e,"/triggers"));return dt.parse({...e,triggers:Fn(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{}vs("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||vs("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 xn(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(Rn(e)!==Rn(t))return;const s=xn(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||ks(e.source).path||"/workspace";return a.length?s.posix.join(r,...a):r}async prepareInstanceModels(e,t){const s=await this.nodeAgentGateway.listModels(e),n={...t.codexModelHash?.trim()?{codexModelHash:t.codexModelHash.trim()}:{},...t.claudeModelHash?.trim()?{claudeModelHash:t.claudeModelHash.trim()}:{}},i=async(t,n)=>{const i=n?this.listAllModels().find(e=>e.id===n):this.listAllModels().find(e=>e.enabled&&e.app===t);if(i){if(i.app!==t)throw Object.assign(new Error(`Model ${i.id} is not a ${t} model.`),{statusCode:400,code:"MODEL_APP_MISMATCH"});if(!i.enabled)throw Object.assign(new Error(`Model ${i.id} is disabled.`),{statusCode:400,code:"MODEL_DISABLED"});return await this.nodeAgentGateway.deployModel(e,i.id,i),i.id}if(!n)return;const a=s.find(e=>e.id===n);if(a||vs("MODEL_NOT_FOUND",`Model ${n} was not found on control-plane or node ${e.id}.`),a.app!==t)throw Object.assign(new Error(`Model ${n} is not a ${t} model.`),{statusCode:400,code:"MODEL_APP_MISMATCH"});if(!a.enabled)throw Object.assign(new Error(`Model ${n} is disabled.`),{statusCode:400,code:"MODEL_DISABLED"});return a.id};return{modelSelection:n,codexModelHash:await i("codex",n.codexModelHash),claudeModelHash:await i("claude",n.claudeModelHash)}}async ensureInstanceModelAssignment(e){const t=this.requireNode(e.nodeId),s=await this.prepareInstanceModels(t,e.modelSelection);return this.nodeAgentGateway.assignInstanceModels(t,e.id,s)}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}requireProject(e){return this.catalogService.requireProject(e)}requireModel(e,t=!1){const s=this.models.get(e);s||vs("MODEL_NOT_FOUND",`Model ${e} was not found.`);const n=this.normalizeModelRecord(s);return t?n:Es(n)}requireModelSecret(e){const t=this.models.get(e);return t||vs("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,$e.parse(t)}return $e.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){return this.catalogService.requireImage(e)}requireNode(e){const t=this.nodes.get(e);return t||vs("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return As(this.requireNode(e))}async requireControlledInstance(e,t=!1){const s=await this.requireNodeInstance(e);return t?s:Ts(s)}}function ri(e){return g(String(e),"escape").replace(/\n+$/,"")}function oi(e){const[t,...s]=String(e).split("\n");return[`*${ri(t)}*`,...s.map(ri)].join("\n")}function di(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function ci(e,t,s,n,i={}){if(t.token)return pi(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 li(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 pi(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):ri(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&&ui(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:di(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&&ui(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?di(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||!ui(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function ui(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function hi(e,t){if("string"==typeof e&&e.trim())return e.trim();mi(`Chat action token is missing ${t}.`)}function gi(e){if("allow"===e||"deny"===e||"skip"===e)return e;mi("Chat action token has an invalid decision.")}function mi(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}function Ii(e){return Buffer.from(e,"base64url").toString("utf8")}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 Pi(t.biz_conversation_id).trim()||Pi(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function Si(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 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:nn(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=>Pi(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=Pi(t[s]);n&&e.push(n)}}async function Ci(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:fi(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}`)}async function Ni(e){const t=nn(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await Ci(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 Ci(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:xi(e.text)},"dingding card send requested"),await _i(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 _i(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=ji(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=ji(ji(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===ji(e).success);if(!s)return"";const n=ji(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=ji(e),s=ji(t.cardData),n=ji(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=ji(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=ji(e),s=ji(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 ji(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Di(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 ji(JSON.parse(String(e||"{}")))}catch{return{}}}function $i(e){return JSON.stringify(nn(e))}function qi(e){return e instanceof Error?e.message:String(e)}function Li(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 Ui{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||Li,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: ${qi(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: ${qi(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: ${qi(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 Bi=20971520;function Fi(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 Hi(e,t,n="telegram-image",i){if(i&&i>Bi)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>Bi)throw new Error(`image is too large: ${d} bytes`);const c=s.extname(n)||".jpg";let l=c;try{l=s.extname(new URL(r).pathname)||c}catch{l=c}const p=s.join(I.tmpdir(),"task-handoff-images");await m.mkdir(p,{recursive:!0});const u=l.replace(/[^.\w-]/g,"")||".jpg",h=s.join(p,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${u}`),g=Buffer.from(await o.arrayBuffer());if(g.length>Bi)throw new Error(`image is too large: ${g.length} bytes`);return await m.writeFile(h,g),h}class Gi{store;constructor(e){this.store=new Ui({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",oi(t),{rawMarkdownV2:!0,replyMarkup:Yi(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,oi(s),{rawMarkdownV2:!0,replyMarkup:Yi(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})}}async function Wi(e){return(await async function(e,t,s,n){const i=[];for(const a of Fi(s))try{const s=await Hi({getFileLink:s=>li(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_${d.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:s.basename(e)||`telegram-image-${n+1}`,mime:Vi(e),size:i.length,data:i.toString("base64")}})}function Vi(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 Ki(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Ji(e){return e&&"object"==typeof e?e:{}}function Qi(e){return"string"==typeof e?e:""}function Yi(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return en(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Xi(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Zi(e);if(1!==t.type)continue;const s=ea(Zi(t.text_item).text).trim();if(s)return s}return""}function Zi(e){return e&&"object"==typeof e?e:{}}function ea(e){return"string"==typeof e?e:""}function ta(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?function(e,t){return{bridge:t,send:async(s,n,i={})=>{const a=await ci(e,t,s,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Ji(a).message_id)||void 0}}}}(t,s):"dingding"===s.channel?function(e,t,s){return{bridge:t,send:async(n,i,a={})=>{const r=a.sessionWebhook||s?.chatWebhooks.get(n)||Pi(t.settings.sessionWebhook);if(a.replyMarkup&&s){const o=await Ni({fetchImpl:e,bridge:t,runtime:s,chatId:n,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||s.senderIds.get(n)||Pi(t.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await Ci(e,r,i,s?.onLog),{provider:"dingding"}}}}(t,s,n):"wechat"===s.channel?function(e,t){return{bridge:t,send:async(s,n,i={})=>{const a=i.contextToken||ea(t.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=ea(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:d.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"}})}),o=await r.json().catch(()=>({}));if(!r.ok||o.errcode)throw new Error(ea(o.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(e,t,s,a,n),{provider:"wechat"}}}}(t,s):{bridge:s,send:async()=>{}}}class sa{entries=new Map;fetchImpl;generations=new Map;constructor(e){this.fetchImpl=e}clear(){for(const e of new Set([...this.generations.keys(),...[...this.entries.values()].map(e=>e.bridgeId)]))this.clearBridge(e)}clearBridge(e){this.generations.set(e,this.generation(e)+1),this.clearEntries(t=>t.bridgeId===e)}generation(e){return this.generations.get(e)||0}isCurrent(e,t){return this.generation(e)===t}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,t){const s=this.generation(e.bridge.id),n=$i(e.replyMarkup);let i=this.entries.get(e.key);if(i?.pending&&await i.pending,!this.isCurrent(e.bridge.id,s))return!1;if(i=this.entries.get(e.key),i?.outTrackId){if(i.lastText===e.text&&i.lastActionsFingerprint===n)return!0;const a=Date.now()-(i.lastUpdateAt||0);return a<1e3?(i.pendingText=e.text,i.pendingReplyMarkup=e.replyMarkup,i.timer||(i.pending=new Promise((n,r)=>{i.cancelPending=r,i.timer=setTimeout(()=>{i.timer=void 0,i.cancelPending=void 0;const a=i.pendingText,o=i.pendingReplyMarkup;if(i.pendingText=void 0,i.pendingReplyMarkup=void 0,!a)return n(void 0);this.updateExistingEntry(e,t,i,a,o,$i(o),s).then(n,r)},1e3-a)}).finally(()=>{i.pending=void 0,i.timer=void 0,i.cancelPending=void 0})),await i.pending,this.isCurrent(e.bridge.id,s)&&this.entries.has(e.key)):(await this.updateExistingEntry(e,t,i,e.text,e.replyMarkup,n,s),this.isCurrent(e.bridge.id,s))}const a={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:n,lastUpdateAt:Date.now()};return a.pending=Ni({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||na(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||na(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(t=>{this.isCurrent(e.bridge.id,s)&&(t?.outTrackId&&(a.outTrackId=t.outTrackId),t?.delivered&&(a.delivered=!0,a.lastUpdateAt=Date.now()))}).catch(t=>{throw this.isCurrent(e.bridge.id,s)&&this.entries.delete(e.key),t}).finally(()=>{a.pending=void 0}),this.entries.set(e.key,a),await a.pending,this.isCurrent(e.bridge.id,s)&&Boolean(a.delivered)}async updateExistingEntry(e,t,s,n,i,a,r){s.outTrackId&&(await async function(e){await _i(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:nn(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:t,outTrackId:s.outTrackId,text:n,replyMarkup:i,title:"TaskHandoff 执行中",step:"progress"}),this.isCurrent(e.bridge.id,r)&&(s.lastText=n,s.lastActionsFingerprint=a,s.lastUpdateAt=Date.now()))}}function na(e){return"string"==typeof e?e:""}function ia(e){const t=new f.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1,autoReconnect:!1});let s,n=!1;return{async connect(){n=!1,await t.getEndpoint(),await t._connect();const e=t.socket;e?.once("close",()=>{n||s?.(new Error("DingDing stream connection closed."))})},disconnect(){n=!0,t.disconnect()},onDisconnect(e){s=e},getConfig:()=>t.getConfig(),registerCallbackListener(e,s){t.registerCallbackListener(e,s)},socketCallBackResponse(e,s){t.socketCallBackResponse(e,s)}}}class aa{runtimes=new Map;connected=new Set;reconnectTimers=new Map;progress;options;createClient;constructor(e){this.options=e,this.createClient=e.createClient||ia,this.progress=new sa(e.fetchImpl)}has(e){return this.runtimes.has(e)}isRunning(e){return this.connected.has(e)}get(e){return this.runtimes.get(e)}ids(){return this.runtimes.keys()}start(e){const t=(s=e.settings?.clientSecret,"string"==typeof s?s:"");var s;if(!e.token||!t)return this.options.onError(e.id,"DingDing client id/secret is not configured."),!1;const n={client:this.createClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,s,n)=>{this.options.logger["warn"===t?"warn":"info"]({bridgeId:e.id,...s},n)}};return n.client.onDisconnect?.(t=>{this.runtimes.get(e.id)===n&&this.handleConnectionFailure(e,n,t)}),n.client.registerCallbackListener(f.TOPIC_ROBOT,t=>{this.options.onRobotMessage(e,n,t).catch(t=>{this.options.onError(e.id,t)})}),n.client.registerCallbackListener(f.TOPIC_CARD,t=>{const s=t;this.options.onCardCallback(e,n,s).then(e=>n.client.socketCallBackResponse(s.headers.messageId,e||{})).catch(t=>{this.options.onError(e.id,t),n.client.socketCallBackResponse(s.headers.messageId,{})})}),this.options.clearError(e.id),this.runtimes.set(e.id,n),this.connect(e,n),!0}connect(e,t){this.reconnectTimers.delete(e.id),this.connected.add(e.id),t.client.connect().then(()=>{this.runtimes.get(e.id)===t&&(this.connected.add(e.id),this.options.clearError(e.id))}).catch(s=>{this.runtimes.get(e.id)===t&&this.handleConnectionFailure(e,t,s)})}handleConnectionFailure(e,t,s){if(this.connected.delete(e.id),this.progress.clearBridge(e.id),this.options.onError(e.id,s||"DingDing stream connection failed."),this.reconnectTimers.has(e.id))return;const n=setTimeout(()=>{this.runtimes.get(e.id)===t&&this.connect(e,t)},this.options.reconnectDelayMs??1e3);n.unref?.(),this.reconnectTimers.set(e.id,n)}stop(e){const t=this.reconnectTimers.get(e);t&&clearTimeout(t),this.reconnectTimers.delete(e),this.connected.delete(e);const s=this.runtimes.get(e);if(s){try{s.client.disconnect()}catch{}this.runtimes.delete(e)}this.progress.clearBridge(e)}stopAll(){for(const e of[...this.runtimes.keys()])this.stop(e);this.progress.clear()}applyUpdate(e){const t=this.runtimes.get(e.bridge.id);return t?this.progress.applyUpdate(e,t):Promise.resolve(!1)}applyProgressUpdate(e){return this.applyUpdate(e)}}class ra{service;fetchImpl;logger;bridgeTimers=new Map;dingdingBridges;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;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 Gi({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.dingdingBridges=new aa({fetchImpl:this.fetchImpl,createClient:s.createDingdingClient,logger:{info:(e,t)=>this.logInfo(e,t),warn:(e,t)=>this.logWarn(e,t)},onRobotMessage:(e,t,s)=>this.handleDingdingRobotMessage(e,t,s),onCardCallback:(e,t,s)=>this.handleDingdingCardCallback(e,t,s),onError:(e,t)=>this.bridgeErrors.set(e,Ia(t)),clearError:e=>this.bridgeErrors.delete(e)}),this.stopAiSessionListener=s.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:Ia(t)},"chat ai session snapshot delivery failed")})})}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)}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);return t&&(clearInterval(t),this.bridgeTimers.delete(e)),this.dingdingBridges.stop(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.ids()]))this.stopBridge(e);this.dingdingBridges.stopAll(),this.pendingTimer&&(clearInterval(this.pendingTimer),this.pendingTimer=void 0);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.isRunning(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){return this.dingdingBridges.start(e)&&this.startPendingPolling(),this.status()}async handleDingdingRobotMessage(e,t,s){const n=function(e){const t=Mi(e),s=Pi(t.conversationId),n=Pi(ji(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: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)||ma(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:fa(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 Ci(this.fetchImpl,d,`Failed to handle message: ${Ia(e)}`,t.onLog);throw e}const l=oa(c);if(l){const s=ca(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=ta({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===va(c).routed,hasReplyMarkup:Boolean(s),replyPreview:fa(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(ma(e.settings.robotCode)),replyPreview:fa(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=za(o,d,c,t.id,n);return!!await this.dingdingBridges.applyProgressUpdate({bridge:t,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:fa(a.sourcePrompt,80),textPreview:fa(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=Mi(e),s=Di(t.cardActionData||t.content),n=Di(s.cardPrivateData||t.cardPrivateData||t.content),i=Di(n.params||s.params||t.params),a=Pi(t.userId),r=Pi(i.biz_sender_id);return{userId:a||void 0,chatId:wi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:Pi(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||ma(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,...ya(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,...ya(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&&da(d)?ki(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):ki(oa(d)||ma(d.message)||"updated",ca(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||ma(t.updatesBuf),n=await async function(e){const t=ea((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(ea(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=Zi(e);if(1!==t.message_type)continue;const s=ea(t.from_user_id),n=Xi(t);s&&n&&a.push({chatId:s,text:n,contextToken:ea(t.context_token)})}return{cursor:ea(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=oa(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=ta({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||ma(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=ka(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 ta({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=Ji(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Ji(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Ji(r.chat),d=Ji(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=Qi(r.text??r.caption),u=Fi(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:Ki(r),replyToMessageId:Ki(Ji(r.reply_to_message)),quoteText:Qi(Ji(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=va(s.message),i=va(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,Ia(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${Ia(t)}`).catch(t=>{this.bridgeErrors.set(e.id,Ia(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await Wi({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:Ia(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:fa(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=ua(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:en([[{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:en([[{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!==ua(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=d.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=oa(d),l=ca(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(va(s).message_id)||void 0,routed:!0===va(d).routed,replyPreview:fa(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,Ia(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${Ia(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,Ia(t))})}}async handleTelegramCallback(e,t){const s=ma(t.data),n=va(t.from),i=va(t.message),a=va(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,ma(t.id),s.instanceId,s.sessionId,o,pa(i)):void await this.answerTelegramCallback(e,ma(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,ma(t.id),s.instanceId,s.sessionId,s.queueId,o,pa(i)):void await this.answerTelegramCallback(e,ma(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,ma(t.id),s.instanceId,s.sessionId,o,pa(i)):void await this.answerTelegramCallback(e,ma(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,ma(t.id),pa(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,ma(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,ma(t.id),u[1],o);const h=this.parseChatGatewayCallbackAction(s);if(!h)return void await this.answerTelegramCallback(e,ma(t.id),"Unsupported action");const g=await this.service.handleChatGatewayAction({source:{channel:"telegram",bridgeId:e.id,chatSessionId:r,userId:o},action:h});if("pending-decision"===h.type&&da(g)){await this.answerTelegramCallback(e,ma(t.id),"");const s=pa(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,ma(t.id),ma(g.message)||"updated");const m=oa(g),I=ca(g),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:Ia(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,Ia(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${fa(Ia(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,Ia(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${fa(Ia(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=va(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>va(e)).filter(e=>"queued"===ma(e.status)&&ma(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,Ta).map((e,t)=>[{text:_a(ma(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,ma(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:en(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,Ia(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${fa(Ia(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:Ia(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,Ia(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${fa(Ia(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:hi(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Ii(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:hi(e.instanceId,"instanceId"),appId:hi(e.appId,"appId")}}return{type:"launch-app",instanceId:Ii(i[1]),appId:Ii(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:hi(e.routeId,"routeId"),decision:gi(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 pi(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 ci(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(va(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=za(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set(ha(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:fa(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(ha(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:Ia(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get(ha(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=ga(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set(ha(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(ha(n.route.bridgeId,n.route.chatId,n.messageId||0))||ga(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(ha(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=Da(s.get(e)||e,a),o=ja(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:ba(Pa(a))});continue}const d=Oa(a);if(!d){this.logAiSessionDelivery({stage:"skip-empty-turn-id",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:ba(Pa(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=za(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=Ra(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:ba(Pa(a)),textPreview:fa(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)||Na(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:ba(Pa(a)),textPreview:fa(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:Ia(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:fa(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:fa(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:ba(Pa(a)),textPreview:fa(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:fa(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=Da(s.get(e)||e,r),c=ja(r,d),l=Oa(r);if(!c||!l){this.logAiSessionDelivery({stage:"remembered-skip-turn-or-text",key:i.key,parsedTurnId:i.owner.turnId,currentTurnId:l,hasText:Boolean(c),instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}if(i.owner.turnId&&l!==i.owner.turnId){this.logAiSessionDelivery({stage:"remembered-skip-different-active-turn",key:i.key,ownerTurnId:i.owner.turnId,currentTurnId:l,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const p=l===i.owner.turnId?i.key:za(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=Ra(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 h=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)||Na(e,r),session:r}),g=await this.updateChatAiSessionProgress(o,c,p,h).catch(e=>(this.bridgeErrors.set(o.bridgeId||"",e instanceof Error?e.message:String(e)),!1));this.logAiSessionDelivery({stage:g?"remembered-delivered":"remembered-not-delivered",key:p,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId,textPreview:fa(c)}),g&&(this.telegramProgressMessageTargets.set(ha(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(ha(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(ha(n.route.bridgeId,n.route.chatId,n.messageId))||ga(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||Pa(t)?.id===e.owner.turnId||Pa(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){if(!this.dingdingBridges.get(i.id))return!1;const a=await this.dingdingBridges.applyProgressUpdate({bridge:i,key:s,chatId:e.chatSessionId,text:t,replyMarkup:la({actionRows:n})});return a&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:fa(t)},"dingding ai session progress updated"),a}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:fa(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:fa(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=d.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=d.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 oa(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function da(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function ca(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 la(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return en(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function pa(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function ua(e,t,s){return[e,t,s||""].join(":")}function ha(e,t,s){return[e,t,String(s)].join(":")}function ga(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 ma(e){return"string"==typeof e?e:""}function Ia(e){return e instanceof Error?e.message:String(e)}function fa(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 ya(e){const t=wa(e.body.cardActionData||e.body.content),s=wa(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:ma(e.body.outTrackId),spaceId:ma(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:fa(e.callbackData,120),actionIds:[...Sa(s.actionIdList),...Sa(t.actionIdList),ma(s.actionId),ma(t.actionId),ma(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 ba(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:fa(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:fa(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function va(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function wa(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return va(JSON.parse(e))}catch{return{}}return{}}function Sa(e){return Array.isArray(e)?e.map(e=>ma(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?Oa(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?Pa(s)?.id:void 0}}}function ka(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:en([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const Ta=5,Ea=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,Ta);return[...d.map((e,t)=>[{text:_a(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function Na(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function _a(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,Ea-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function za(e,t,...s){return[e,t,...s].join(":")}function Oa(e){const t=Pa(e);return t?.id?t.id:ma(e.activeTurnId)}function Ra(e,t=""){const s=Pa(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||"",xa(e)].join("\0")}function xa(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function ja(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,Pa(e));return s?`${t}\n${s}`:""}function Da(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 Pa(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}class Ma{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&&$a(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||mt(e),createdAt:(new Date).toISOString(),payload:t,scope:s.scope}}}function $a(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const qa={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},La=o.z.object({instanceId:o.z.string().trim().min(1).max(120),sessionId:o.z.string().trim().min(1).max(120),kind:o.z.literal("image").default("image"),name:o.z.string().trim().min(1).max(240),mime:o.z.string().trim().min(1).max(120),data:o.z.string().min(1).max(31457280)}).strict();class Ua{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const n=La.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=qa[a];if(!r)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>b)throw new Error("Image must be between 1 byte and 20971520 bytes.");const o=`att_${d.randomUUID().replace(/-/g,"").slice(0,24)}`,c=new Date,l=new Date(c.getTime()+this.ttlMs),p=s.join(I.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(p,{recursive:!0});const u=function(e){return s.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(n.name),h=s.join(p,`${o}-${u.endsWith(r)?u:`${u}${r}`}`);t.writeFileSync(h,i.buffer);const g={id:o,instanceId:n.instanceId,sessionId:n.sessionId,kind:"image",name:u,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:h,createdAt:c.toISOString(),expiresAt:l.toISOString()};return this.items.set(o,g),this.publicAttachment(g)}resolveRefs(e,t,s){this.pruneExpired();const n=o.z.array(R).max(6).default([]).parse(e),i=n.map(e=>this.requireAttachment(e,t,s));if(i.reduce((e,t)=>e+t.size,0)>v)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 z.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 Ba(e){return e.readyState===(e.OPEN??1)}function Fa(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 Ha(e,t){e.send(t.data,{binary:t.isBinary})}function Ga(e,t){if(Ba(e))for(const s of t.splice(0))Ha(e,s)}class Wa{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 Va=o.z.object({nodeId:o.z.string().trim().min(1).max(120)}).strict();function Ka(e,t){if(!t)throw Object.assign(new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`),{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"});return t}function Ja(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Qa=new Map;function Ya(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)>Gn)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()-Gn;for(const[e,t]of Qa)t<=i&&Qa.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||Qa.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=Wn("");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 o=t.url||`/api/node-agent/tunnel?nodeId=${encodeURIComponent(e.id)}`,c=Vn(e.auth.secret,{keyId:s.keyId,method:"GET",pathWithQuery:o,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&&d.timingSafeEqual(s,n)}(s.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});Qa.set(a,Date.now()+Gn)}class Xa{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=yt.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=Ja(),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=Ja(),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=Ba(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return Fa(e,1011,"WebSocket bridge pending frame limit exceeded."),void Fa(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(Fa(e,1011,"Upstream websocket did not open."),Fa(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>Ga(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),Ga(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};Ba(t)?Ha(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};Ba(e)?Ha(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&Fa(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&Fa(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),Fa(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),Fa(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 Za{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=Jn(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 Kn({nodeId:e.id,keyId:Ka(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?Yn(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 Wa,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 Wa};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(Jn(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}}}}function er(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||s.join(I.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")}}class tr extends Error{code;lockPath;owner;constructor(e,t,s={}){const n=t?.pid?` pid ${t.pid}`:"";super(`${s.label||"Process"} is already running${n}. Lock: ${e}`),this.name="ProcessSingletonError",this.code=s.code||"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function sr(e){return s.join(e,"owner.json")}function nr(e){return s.join(e,"initializing.json")}function ir(e){return s.join(e,"recovering.json")}function ar(e){try{const s=JSON.parse(t.readFileSync(sr(e),"utf8"));if(!Number.isInteger(s.pid)||Number(s.pid)<=0||"string"!=typeof s.hostname||!s.hostname||"string"!=typeof s.command||"string"!=typeof s.acquiredAt||!Number.isFinite(Date.parse(s.acquiredAt))||"string"!=typeof s.token||!s.token)return;return s}catch{return}}function rr(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 or(e,n){const i=s.join(e,`.owner-${n.token}.tmp`);t.writeFileSync(i,`${JSON.stringify(n,null,2)}\n`,{encoding:"utf8",mode:384,flag:"wx"});const a=dr(e);if(a?.token!==n.token)throw cr(i),new Error(`Process lock initialization was superseded: ${e}`);t.renameSync(i,sr(e)),cr(nr(e))}function dr(e){try{return JSON.parse(t.readFileSync(nr(e),"utf8"))}catch{return}}function cr(e){try{t.unlinkSync(e)}catch(e){if("ENOENT"!==e.code)throw e}}function lr(){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,20)}function pr(e){try{const s=dr(e),n=Date.parse(s?.startedAt||"");return Number.isFinite(n)?Math.max(0,Date.now()-n):Math.max(0,Date.now()-t.statSync(e).mtimeMs)}catch{return 0}}function ur(e,n){try{return t.writeFileSync(ir(e),`${JSON.stringify({token:n,pid:process.pid,startedAt:(new Date).toISOString()})}\n`,{encoding:"utf8",mode:384,flag:"wx"}),!0}catch(i){if("EEXIST"===i.code)return!!function(e){const n=ir(e),i=hr(n),a=Date.parse(i?.startedAt||"");if(rr(i?.pid)||Number.isFinite(a)&&Date.now()-a<2e3)return!1;const r=s.join(e,`.recovering-${d.randomUUID()}.stale`);try{t.renameSync(n,r)}catch(e){if(["ENOENT","EEXIST"].includes(e.code||""))return!1;throw e}const o=hr(r);if(o?.token===i?.token)return cr(r),!0;try{t.linkSync(r,n)}catch(e){if("EEXIST"!==e.code)throw e}finally{cr(r)}return!1}(e)&&ur(e,n);if("ENOENT"===i.code)return!1;throw i}}function hr(e){try{return JSON.parse(t.readFileSync(e,"utf8"))}catch{return}}function gr(e,t){return hr(ir(e))?.token===t}function mr(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 Ir=o.z.string().trim().min(1),fr=o.z.object({id:Ir}).strict(),yr=o.z.object({id:Ir,keyId:Ir}).strict(),br=o.z.object({id:Ir,runtimeId:Ir}).strict(),vr=o.z.object({id:Ir,folderId:Ir}).strict(),wr=o.z.object({id:Ir,sessionId:Ir}).strict(),Sr=o.z.object({id:Ir,sessionId:Ir,queueId:Ir}).strict(),Ar=o.z.object({configHash:Ir}).strict(),kr=o.z.object({id:Ir,configHash:Ir}).strict(),Tr=o.z.object({id:Ir,sessionId:Ir,configHash:Ir}).strict(),Er=o.z.object({path:o.z.string().optional(),depth:o.z.coerce.number().int().min(0).max(32).optional()}).strict(),Cr=o.z.object({ids:o.z.array(o.z.string().trim().min(1).max(120)).default([])}).strict(),Nr=o.z.object({nodeId:o.z.string().trim().min(1).max(120),modelId:o.z.string().trim().min(1).max(120).optional()}).strict(),_r=o.z.object({source:o.z.object({channel:lt,bridgeId:o.z.string().trim().min(1).max(120).optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional()}).strict(),action:o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("ai-session"),index:o.z.number().int().nonnegative()}).strict(),o.z.object({type:o.z.literal("instance-app-menu"),instanceId:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("launch-app"),instanceId:o.z.string().trim().min(1).max(120),appId:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("pending-decision"),routeId:o.z.string().trim().min(1).max(120),decision:o.z.enum(["allow","deny","skip"])}).strict()])}).strict();function zr(e){const t=_r.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const Or=o.z.object({id:o.z.string().trim().min(1),direction:o.z.enum(["import","export"]),preset:o.z.string().trim().min(1).max(120)});const Rr=o.z.object({appId:o.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:o.z.string().trim().min(1).max(120).optional(),options:o.z.record(o.z.string(),o.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),xr=o.z.object({title:o.z.string().trim().min(1).max(120)}).strict();function jr(e){const{app:t,service:s,events:n,appSessionAggregator:i,aiSessionAggregator:a,chatGateway:r,aiSessionAttachments:d,nodeAgentTunnel:c,nodeEventSubscriber:l,errorPayload:p}=e;!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(fr.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(fr.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=fr.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:await t.listFederatedModels()})),e.post("/api/models",async(e,n)=>{const i=await 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=Cr.parse(e.body),i=await t.reorderModels(n.ids);return s.publish("model.reordered",{ids:n.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(fr.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=await t.updateModel(fr.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=fr.parse(e.params).id,i=await t.deleteModel(n);return s.publish("model.deleted",{modelId:n,deleted:i}),{data:{deleted:i}}}),e.post("/api/nodes/:nodeId/models",async(e,n)=>{const{nodeId:i}=Nr.parse(e.params),a=await t.createNodeModel(i,e.body);return s.publish("model.created",{modelId:a.id,nodeId:i}),n.code(201).send({data:a})}),e.patch("/api/nodes/:nodeId/models/:modelId",async e=>{const{nodeId:n,modelId:i}=Nr.parse(e.params),a=await t.updateNodeModel(n,i,e.body);return s.publish("model.updated",{modelId:i,nodeId:n}),{data:a}}),e.delete("/api/nodes/:nodeId/models/:modelId",async e=>{const{nodeId:n,modelId:i}=Nr.parse(e.params),a=await t.deleteNodeModel(n,i);return s.publish("model.deleted",{modelId:i,nodeId:n,deleted:a.deleted}),{data:a}}),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(fr.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(fr.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=fr.parse(e.params).id,i=t.deleteImage(n);return s.publish("image.deleted",{imageId:n,deleted:i}),{data:{deleted:i}}})}({app:t,service:s,events:n}),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(fr.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(fr.parse(e.params).id)})),e.get("/api/nodes/:id/settings/external-listener",async e=>({data:await t.getLocalNodeExternalListener(fr.parse(e.params).id)})),e.patch("/api/nodes/:id/settings/external-listener",async e=>{const n=fr.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=fr.parse(e.params).id,i=await t.checkNodeUpdate(n,Ce.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=fr.parse(e.params).id,a=await t.applyNodeUpdate(i,Ce.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=fr.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=fr.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=fr.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=yr.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(fr.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=fr.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=br.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=br.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=br.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(fr.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(fr.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=fr.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=fr.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=vr.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(fr.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(fr.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=fr.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=Va.parse(t.query),a=s.requireNode(i.nodeId);Ya(a,t),n.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:ge,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=Va.parse(t.query),a=s.requireNode(i.nodeId);Ya(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:t,service:s,events:n,nodeAgentTunnel:c,nodeEventSubscriber:l,errorPayload:p}),function({app:e,service:t,events:s}){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(fr.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(fr.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=fr.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(fr.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(fr.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(fr.parse(e.params).id);return s.publish("instance.restarted",{instanceId:n.id}),{data:n}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const n=Or.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}}})}({app:t,service:s,events:n}),function({app:e,service:t,events:s,appSessionAggregator:n,aiSessionAggregator:i,aiSessionAttachments:a}){e.get("/api/app-sessions",async e=>{const t=o.z.object({refresh:o.z.string().optional(),includeTombstones:o.z.string().optional(),instanceId:o.z.string().trim().min(1).optional(),streamId:o.z.string().trim().min(1).optional(),sinceRevision:o.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.post("/api/controlled-instances/:id/apps/sessions",async e=>{const n=fr.parse(e.params),i=Rr.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=wr.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=wr.parse(e.params),i=xr.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=fr.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=wr.parse(e.params),i=D.parse(e.body||{}),r=a.resolveRefs(i.attachments,n.id,n.sessionId),o=await t.sendAiSessionMessage(n.id,n.sessionId,i.message,i.mode,r);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:o}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const s=wr.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=Sr.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=Sr.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=Sr.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=wr.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=wr.parse(e.params),i=P.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/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})}})}({app:t,service:s,events:n,appSessionAggregator:i,aiSessionAggregator:a,aiSessionAttachments:d}),function({app:e,service:t,events:s}){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=Ar.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=Ar.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=fr.parse(e.params);return{data:await t.listInstanceTriggers(s.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const n=wr.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=Tr.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=kr.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}})}({app:t,service:s,events:n}),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(fr.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=fr.parse(n.params).id,a=t.requireChatBridge(i),r=Qs.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=fr.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=fr.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(fr.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(ht.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(zr(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:t,service:s,chatGateway:r})}const Dr=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),Pr=new Set(["content-encoding"]);function Mr(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 $r(e){return Object.fromEntries(Object.entries(Mr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function qr(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 Lr(e,t){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();Dr.has(t)||Pr.has(t)||e.header(s,n)}return e.code(t.status).send(t.body)}function Ur(e){return`/instances/${encodeURIComponent(e)}`}function Br(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 Lr(e,{...t,body:i})}function Fr(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 Hr(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 Gr(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function Wr(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===w?Q:e.type===S?Y:e.type===A?X:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===w&&this.applySnapshot(s.data),e.type===S&&this.applyPatch(s.data),e.type===A&&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:w,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:S,payload:e})}applyRemoved(e){return this.apply({type:A,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 Z.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Z.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 Z.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===w&&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===w)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:G.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===S){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=H.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:G.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:w,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:w,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 Kr{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===Ft?Zt:e.type===Ht?es:e.type===Gt?ts:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===Ft&&this.applySnapshot(s.data),e.type===Ht&&this.applyPatch(s.data),e.type===Gt&&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:Ft,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:Ht,payload:e})}applyRemoved(e){return this.apply({type:Gt,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 ss.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return ss.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 ss.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===Ft&&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===Ft)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:Qt.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===Ht){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=ns(n,s.generatedAt);return{kind:"applied",projection:Qt.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:Ft,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:Ft,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 ns(e.sessions.filter(e=>dn("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 Jr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function Qr(e){const t=process.env[e]?.trim();return t||void 0}function Yr(){try{const e=("undefined"==typeof document?require("url").pathToFileURL(__filename).href:y&&"SCRIPT"===y.tagName.toUpperCase()&&y.src||new URL("cli.js",document.baseURI).href)?s.dirname(n.fileURLToPath("undefined"==typeof document?require("url").pathToFileURL(__filename).href:y&&"SCRIPT"===y.tagName.toUpperCase()&&y.src||new URL("cli.js",document.baseURI).href)):__dirname,i=[s.resolve(e,"..","package.json"),s.resolve(e,"..","..","..","package.json")].find(e=>t.existsSync(e));return i&&JSON.parse(t.readFileSync(i,"utf8")).version||"unknown"}catch{return"unknown"}}function Xr(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:Yr(),protocolVersion:ge,buildId:Qr("TASK_HANDOFF_BUILD_ID"),builtAt:Qr("TASK_HANDOFF_BUILT_AT"),gitCommit:Qr("TASK_HANDOFF_GIT_COMMIT"),imageRef:Qr("TASK_HANDOFF_IMAGE_REF"),imageDigest:Qr("TASK_HANDOFF_IMAGE_DIGEST")}}const Zr={".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 eo(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 to(e,n,i){const a=eo(n,i);return a?(e.header("cache-control","no-store"),e.type(Zr[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 so(e){if(e instanceof o.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 no(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 io(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 ao(e={}){const n=er(e.dataDir),o=i({logger:e.logger??!0,bodyLimit:67108864}),d=new ai(n,{...e.service,logger:o.log}),c=new Bt(n,e.auth),l=new Ma,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 Kr({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 h=new Ua,g=new Xa(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(g),d.init(),await d.syncLocalNodeConnection().catch(()=>{}),c.init();const m=new ra(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const I=new Za(d,g,{safetyIntervalMs:Number(process.env.TASK_HANDOFF_EVENT_CONNECTION_SAFETY_INTERVAL_MS)||void 0,logger:o.log});I.start(),await o.register(a),await o.register(r),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=so(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 no(c,e.cookies[_t]),s=io(e.method,e.url);return void(s&&mr(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await no(c,e.cookies[_t]);if(!s)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const n=io(e.method,e.url);n&&mr(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 to(t,b,s.join("assets",n||""))}),o.get("/favicon.ico",async(e,t)=>eo(b,"favicon.ico")?to(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:ge,build:Xr(),dataDir:n.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"),Jr`#!/bin/sh
|
|
1
|
+
"use strict";var e=require("commander"),t=require("node:fs"),s=require("node:path"),n=require("node:url"),i=require("fastify"),a=require("@fastify/cookie"),r=require("@fastify/websocket"),o=require("zod"),d=require("node:crypto"),c=require("node:util"),l=require("write-file-atomic"),p=require("ws"),u=require("node:http"),g=require("node:net"),h=require("node:stream"),m=require("telegramify-markdown"),I=require("node:fs/promises"),f=require("node:os"),y=require("dingtalk-stream"),b="undefined"!=typeof document?document.currentScript:null;const v=20971520,w=41943040,S="ai-session.snapshot",A="ai-session.patch",k="ai-session.removed",T=o.z.string().trim().min(1).max(80),E=o.z.enum(["running","waiting","idle","failed"]),C=o.z.enum(["thinking","tool","editing","approval","responding","unknown"]),N=o.z.object({id:o.z.string().trim().min(1).max(240).optional(),kind:o.z.string().trim().min(1).max(80).optional(),name:o.z.string().trim().min(1).max(120),inputPreview:o.z.string().trim().max(500).optional(),startedAt:o.z.string().datetime().optional()}).strict(),z=o.z.enum(["pending-init","running","interrupted","completed","errored","shutdown","not-found"]),_=o.z.enum(["started","interacted","interrupted"]),O=o.z.object({threadId:o.z.string().trim().min(1).max(240),path:o.z.string().trim().max(4096).optional(),status:z,activity:_.optional(),message:o.z.string().trim().max(1e3).optional(),updatedAt:o.z.string().datetime()}).strict(),R=o.z.object({toolCalls:o.z.number().int().min(0).default(0),edits:o.z.number().int().min(0).default(0),approvals:o.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),x=o.z.object({send:o.z.boolean().optional(),interrupt:o.z.boolean().optional(),approval:o.z.boolean().optional()}).strict(),j=o.z.object({id:o.z.string().trim().min(1).max(120),kind:o.z.enum(["image"]),name:o.z.string().trim().min(1).max(240),mime:o.z.string().trim().min(1).max(120),size:o.z.number().int().min(0).max(v),data:o.z.string().min(1).max(31457280)}).strict(),D=j.omit({data:!0}),P=o.z.object({id:o.z.string().trim().min(1).max(120),kind:o.z.enum(["image"]).optional()}).strict(),M=o.z.enum(["auto","queue","steer","immediate"]),$=o.z.object({message:o.z.string().trim().min(1).max(2e4),mode:M.optional()});$.extend({attachments:o.z.array(j).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>w&&t.addIssue({code:o.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const q=$.extend({attachments:o.z.array(P).max(6).default([])}).strict(),L=o.z.object({decision:o.z.enum(["allow","deny","skip"])}).strict();o.z.object({queueIds:o.z.array(o.z.string().trim().min(1).max(120)).max(100)}).strict(),o.z.object({code:o.z.string().trim().min(1).max(160),message:o.z.string(),instanceId:o.z.string().trim().min(1).max(160).optional(),nodeId:o.z.string().trim().min(1).max(160).optional(),route:o.z.string().trim().min(1).max(4096).optional()}).strict();const U=o.z.object({id:o.z.string().trim().min(1).max(120),message:o.z.string().trim().min(1).max(2e4),attachments:o.z.array(D).max(6).default([]),status:o.z.enum(["queued","sending","failed"]).default("queued"),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime(),error:o.z.string().trim().max(4e3).optional()}).strict(),B=o.z.object({pendingCount:o.z.number().int().min(0).default(0),items:o.z.array(U).max(100).default([])}).strict().default({pendingCount:0,items:[]}),F=o.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),H=o.z.object({id:o.z.string().trim().min(1).max(240),providerTurnId:o.z.string().trim().max(240).optional(),source:F.optional(),userPrompt:o.z.string().trim().optional(),status:o.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:C.optional(),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),revision:o.z.number().int().min(0).default(0),sourcePriority:o.z.number().int().min(0).max(100).optional(),snapshotVersion:o.z.number().int().min(0).optional(),observedAt:o.z.string().datetime().optional(),startedAt:o.z.string().datetime().optional(),updatedAt:o.z.string().datetime().optional(),completedAt:o.z.string().datetime().optional()}).strict(),G=o.z.object({id:o.z.string().trim().min(1).max(120),agent:T,appSessionId:o.z.string().trim().max(120).optional(),appId:o.z.string().trim().max(120).optional(),providerSessionId:o.z.string().trim().max(240).optional(),providerMeta:o.z.record(o.z.string(),o.z.unknown()).optional(),appBindingKeys:o.z.array(o.z.string().trim().min(1).max(240)).max(20).optional(),actions:x.optional(),activeTurnId:o.z.string().trim().max(240).optional(),title:o.z.string().trim().max(240).optional(),cwd:o.z.string().trim().max(4096).optional(),userPrompt:o.z.string().trim().optional(),turns:o.z.array(H).max(50).optional(),status:E.default("running"),phase:C.default("unknown"),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),currentTool:N.optional(),toolCallsSinceLastMessage:o.z.number().int().min(0).default(0),subAgents:o.z.array(O).max(50).default([]),transcriptPath:o.z.string().trim().max(4096).optional(),transcriptSize:o.z.number().int().min(0).optional(),startedAt:o.z.string().datetime(),updatedAt:o.z.string().datetime(),completedAt:o.z.string().datetime().optional(),error:o.z.string().trim().max(4e3).optional(),counters:R,queue:B}).strict(),W=o.z.object({session:G,provider:o.z.string().trim().min(1).max(80),action:o.z.enum(["send","queue","steer","interrupt","approval"]),decision:o.z.enum(["allow","deny","skip"]).optional(),turnId:o.z.string().trim().min(1).max(240).optional(),providerTurnId:o.z.string().trim().min(1).max(240).optional(),queueId:o.z.string().trim().min(1).max(120).optional()}).strict(),V=G.pick({id:!0,agent:!0,appSessionId:!0,appId:!0,providerSessionId:!0,providerMeta:!0,appBindingKeys:!0,actions:!0,activeTurnId:!0,title:!0,cwd:!0,userPrompt:!0,turns:!0,status:!0,phase:!0,summary:!0,lastMessage:!0,currentTool:!0,toolCallsSinceLastMessage:!0,subAgents:!0,queue:!0,startedAt:!0,updatedAt:!0,error:!0}),J=o.z.object({runningCount:o.z.number().int().min(0).default(0),waitingCount:o.z.number().int().min(0).default(0),staleCount:o.z.number().int().min(0).default(0),sessions:o.z.array(V).default([]),updatedAt:o.z.string().datetime()}).strict(),K=o.z.object({streamId:o.z.string().trim().min(1).max(240),revision:o.z.number().int().min(0),lastEventAt:o.z.string().datetime(),snapshot:J}).strict(),Q=J.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),revision:o.z.number().int().min(0).optional()});function Y(e,t={}){const s=e.sessions.filter(e=>"idle"===e.status).length,n=e.sessions.filter(e=>"failed"===e.status).length;return Q.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:s,problemCount:n,updatedAt:e.updatedAt,revision:t.revision})}const X=o.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),Z=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),nodeId:o.z.string().trim().min(1).max(160).optional(),revision:o.z.number().int().min(0),previousRevision:o.z.number().int().min(0).optional(),traceId:o.z.string().trim().min(1).max(160),generatedAt:o.z.string().datetime(),reason:X}).strict(),ee=o.z.object({meta:Z,snapshot:J}).strict(),te=o.z.object({meta:Z,upserted:o.z.array(V).default([]),removed:o.z.array(o.z.string().trim().min(1).max(120)).default([])}).strict(),se=o.z.object({meta:Z,sessionIds:o.z.array(o.z.string().trim().min(1).max(120)).default([]),expiresAt:o.z.string().datetime()}).strict(),ne=o.z.object({instanceId:o.z.string().trim().min(1).max(160),nodeId:o.z.string().trim().min(1).max(160).optional(),sessionId:o.z.string().trim().min(1).max(120),providerSessionId:o.z.string().trim().min(1).max(240),turnId:o.z.string().trim().min(1).max(240).optional(),itemId:o.z.string().trim().min(1).max(240).optional(),delta:o.z.string().min(1),generatedAt:o.z.string().datetime()}).strict(),ie=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),sinceRevision:o.z.number().int().min(0),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0),syncRequired:o.z.boolean().default(!1),events:o.z.array(o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal(S),payload:ee}).strict(),o.z.object({type:o.z.literal(A),payload:te}).strict(),o.z.object({type:o.z.literal(k),payload:se}).strict()])).default([])}).strict(),ae=o.z.object({source:F,sourcePriority:o.z.number().int().min(0).max(100).optional(),observedAt:o.z.string().datetime().optional(),snapshotVersion:o.z.number().int().min(0).optional()}).strict(),re=ae.extend({type:o.z.literal("snapshot"),agent:o.z.string().trim().min(1).max(80),appSessionId:o.z.string().trim().max(120).optional(),appId:o.z.string().trim().max(120).optional(),providerSessionId:o.z.string().trim().max(240).optional(),providerMeta:o.z.record(o.z.string(),o.z.unknown()).optional(),appBindingKeys:o.z.array(o.z.string().trim().min(1).max(240)).max(20).optional(),actions:x.optional(),title:o.z.string().trim().max(240).optional(),cwd:o.z.string().trim().max(4096).optional(),activeTurnId:o.z.string().trim().max(240).optional(),userPrompt:o.z.string().trim().optional(),turns:o.z.array(H).max(50).optional(),status:E.optional(),phase:C.optional(),summary:o.z.string().trim().max(1e3).optional(),lastMessage:o.z.string().trim().optional(),currentTool:N.optional(),toolCallsSinceLastMessage:o.z.number().int().min(0).optional(),subAgents:o.z.array(O).max(50).optional(),transcriptPath:o.z.string().trim().max(4096).optional(),transcriptSize:o.z.number().int().min(0).optional(),replaceActivity:o.z.boolean().optional()}).strict(),oe=ae.extend({type:o.z.literal("event"),sessionId:o.z.string().trim().min(1).max(120),kind:o.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed","tool-activity","sub-agent-activity"]),activeTurnId:o.z.string().trim().max(240).optional(),providerTurnId:o.z.string().trim().max(240).optional(),userPrompt:o.z.string().trim().optional(),text:o.z.string().optional(),status:E.optional(),phase:C.optional(),summary:o.z.string().trim().max(1e3).optional(),error:o.z.string().trim().max(4e3).optional(),currentTool:N.nullable().optional(),toolCallsSinceLastMessage:o.z.number().int().min(0).optional(),subAgents:o.z.array(O).max(50).optional(),counters:o.z.object({toolCalls:o.z.number().int().min(0).optional(),edits:o.z.number().int().min(0).optional(),approvals:o.z.number().int().min(0).optional()}).strict().optional()}).strict().superRefine((e,t)=>{if("sub-agent-activity"===e.kind?e.subAgents||t.addIssue({code:"custom",path:["subAgents"],message:"sub-agent-activity events require subAgents"}):void 0!==e.subAgents&&t.addIssue({code:"custom",path:["subAgents"],message:"subAgents is only valid for sub-agent-activity events"}),"tool-activity"!==e.kind)return void 0!==e.currentTool&&t.addIssue({code:"custom",path:["currentTool"],message:"currentTool is only valid for tool-activity events"}),void(void 0!==e.toolCallsSinceLastMessage&&t.addIssue({code:"custom",path:["toolCallsSinceLastMessage"],message:"toolCallsSinceLastMessage is only valid for tool-activity events"}));"currentTool"in e||t.addIssue({code:"custom",path:["currentTool"],message:"tool-activity events require currentTool"}),void 0===e.toolCallsSinceLastMessage&&t.addIssue({code:"custom",path:["toolCallsSinceLastMessage"],message:"tool-activity events require toolCallsSinceLastMessage"})});o.z.discriminatedUnion("type",[re,oe]);const de=o.z.union([o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("interval").default("interval"),intervalMs:o.z.number().int().min(1e3)}).strict(),o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("daily"),timeOfDay:o.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("schedule"),scheduleKind:o.z.literal("weekly"),weekdays:o.z.array(o.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:o.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:o.z.string().trim().min(1).max(120)}).strict()]),ce=o.z.union([de,o.z.object({type:o.z.literal("file-change"),roots:o.z.array(o.z.string().trim().min(1).max(4096)).min(1).max(50),globs:o.z.array(o.z.string().trim().min(1).max(500)).min(1).max(100),ignore:o.z.array(o.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:o.z.number().int().min(100).max(6e4).default(1500)}).strict(),o.z.object({type:o.z.literal("ai-session"),agent:o.z.string().trim().min(1).max(80).optional(),statuses:o.z.array(o.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:o.z.array(o.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),le=o.z.object({promptTemplate:o.z.string().trim().min(1).max(2e4)}).strict(),pe=o.z.object({cooldownMs:o.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:o.z.number().int().min(1).max(20).default(1),whenBusy:o.z.enum(["skip","queue"]).default("skip")}).strict(),ue=o.z.object({configHash:o.z.string().trim().min(8).max(80),name:o.z.string().trim().min(1).max(160),description:o.z.string().trim().max(1e3).optional(),source:ce,action:le,policy:pe.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),ge=o.z.object({type:o.z.literal("ai-session"),aiSessionId:o.z.string().trim().min(1).max(160)}).strict(),he=o.z.object({configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),origin:o.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:o.z.boolean().default(!0),target:ge,localName:o.z.string().trim().min(1).max(160).optional(),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),me=o.z.object({configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),status:o.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:o.z.string().datetime().optional(),lastCompletedAt:o.z.string().datetime().optional(),lastSkippedAt:o.z.string().datetime().optional(),lastError:o.z.string().trim().max(4e3).optional(),runCount:o.z.number().int().min(0).default(0),skippedCount:o.z.number().int().min(0).default(0)}).strict(),Ie=o.z.object({id:o.z.string().trim().min(1).max(160),configHash:o.z.string().trim().min(8).max(80),deploymentId:o.z.string().trim().min(1).max(240).optional(),instanceId:o.z.string().trim().min(1).max(160),eventType:o.z.enum(["manual","schedule","file-change","ai-session"]),status:o.z.enum(["started","completed","failed","skipped"]),target:ge,promptPreview:o.z.string().trim().max(500),eventSummary:o.z.string().trim().max(1e3).optional(),error:o.z.string().trim().max(4e3).optional(),startedAt:o.z.string().datetime(),completedAt:o.z.string().datetime().optional()}).strict(),fe=o.z.object({schemaVersion:o.z.literal(1).default(1),configs:o.z.array(ue).default([]),deployments:o.z.array(he).default([]),runtime:o.z.array(me).default([]),recentRuns:o.z.array(Ie).default([])}).strict();function ye(e){return Array.isArray(e)?e.map(ye):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,ye(t)])):e}const be="2026-07-18",ve=o.z.string(),we=o.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),Se=o.z.string().datetime(),Ae=o.z.record(o.z.string(),o.z.string()).default({}),ke=o.z.record(o.z.string(),o.z.string()).default({}),Te=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,Ee=/^sha256:([a-fA-F0-9]{64})$/,Ce=/^(?:localhost|[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?$/,Ne=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/;function ze(e){const t=e.trim();if(!t||t.length>512||t.includes("://")||/\s/.test(t))throw new Error("Docker image reference must not contain a URL scheme or whitespace.");const s=t.indexOf("@");if(-1!==s&&s!==t.lastIndexOf("@"))throw new Error("Docker image reference contains more than one digest separator.");const n=-1===s?void 0:t.slice(s+1),i=-1===s?t:t.slice(0,s),a=n?Ee.exec(n):void 0;if(n&&!a)throw new Error("Docker image digest must use sha256 followed by 64 hexadecimal characters.");const r=i.lastIndexOf("/"),o=i.lastIndexOf(":"),d=o>r,c=d?i.slice(o+1):void 0,l=d?i.slice(0,o):i;if(c&&!Te.test(c))throw new Error("Docker image tag is invalid.");if(!c&&!a)throw new Error("Docker image reference must include an explicit tag or sha256 digest.");const p=l.split("/");if(!p.length||p.some(e=>!e))throw new Error("Docker image repository path is invalid.");const u=p.length>1&&(p[0].includes(".")||p[0].includes(":")||"localhost"===p[0]),g=u?p.slice(1):p;if(u&&!Ce.test(p[0].toLowerCase()))throw new Error("Docker image registry host or port is invalid.");if(!g.length||g.some(e=>!Ne.test(e)))throw new Error("Docker image repository names must be lowercase and use valid separators.");const h=p.map((e,t)=>u&&0===t?e.toLowerCase():e).join("/"),m=a?`sha256:${a[1].toLowerCase()}`:void 0;return`${h}${c?`:${c}`:""}${m?`@${m}`:""}`}const _e=o.z.string().trim().min(1).max(512).transform((e,t)=>{try{return ze(e)}catch(e){return t.addIssue({code:"custom",message:e instanceof Error?e.message:String(e)}),o.z.NEVER}}),Oe=o.z.string().trim().transform((e,t)=>{const s=Ee.exec(e);return s?`sha256:${s[1].toLowerCase()}`:(t.addIssue({code:"custom",message:"Docker image digest must use sha256 followed by 64 hexadecimal characters."}),o.z.NEVER)}),Re=o.z.object({codexModelHash:we.nullable().optional(),claudeModelHash:we.nullable().optional()}).strict().default({}),xe=o.z.object({id:we,name:o.z.string().trim().min(1).max(120),kind:o.z.enum(["tty","gui","web"]),source:o.z.enum(["builtin","custom"]),availability:o.z.enum(["available","missing-dependency"]),capabilities:o.z.object({automation:o.z.enum(["cdp"]).optional(),supportsCwdSelection:o.z.boolean().default(!1)}).strict().default({supportsCwdSelection:!1}),diagnosticCode:o.z.enum(["APP_EXECUTABLE_NOT_FOUND"]).optional()}).strict(),je=o.z.object({code:o.z.enum(["APP_CATALOG_INVALID"]),message:o.z.string().trim().min(1).max(500)}).strict(),De=o.z.object({items:o.z.array(xe).max(256),observedAt:Se,issues:o.z.array(je).max(32).default([])}).strict(),Pe=o.z.enum(["linux","darwin","win32","freebsd","openbsd","aix","sunos","unknown"]),Me=o.z.enum(["x64","arm64","arm","ia32","ppc64","s390x","riscv64","unknown"]),$e=o.z.enum(["apt","dnf","brew","npm"]),qe=o.z.enum(["user","passwordless-sudo","root"]),Le=o.z.object({platform:Pe,arch:Me,installers:o.z.array($e).max(8),privilege:qe,installerAccess:o.z.object({npmGlobalWritable:o.z.boolean().optional()}).strict().optional()}).strict(),Ue=o.z.enum(["installed","not-installed","broken","unsupported"]),Be=o.z.enum(["recipe","bundled","external","none"]),Fe=o.z.object({code:o.z.enum(["BUNDLED","EXTERNALLY_MANAGED","ALREADY_INSTALLED","NOT_INSTALLED","UNSUPPORTED_PLATFORM","INSTALLER_UNAVAILABLE","INSTALLER_NOT_WRITABLE","INSUFFICIENT_PRIVILEGE","OPERATION_IN_PROGRESS"]),message:o.z.string().trim().min(1).max(500)}).strict(),He=o.z.object({id:we,name:o.z.string().trim().min(1).max(120),kind:o.z.enum(["tty","gui","web"]),description:o.z.string().trim().max(500).optional(),state:Ue,managementSource:Be,version:o.z.string().trim().max(120).optional(),canInstall:o.z.boolean(),canUninstall:o.z.boolean(),installReason:Fe.optional(),uninstallReason:Fe.optional(),activeJobId:we.optional()}).strict(),Ge=o.z.enum(["install","uninstall"]),We=o.z.enum(["queued","running","succeeded","failed","cancelled","interrupted"]),Ve=o.z.object({current:o.z.number().finite().nonnegative().optional(),total:o.z.number().finite().positive().optional(),unit:o.z.string().trim().min(1).max(40).optional()}).strict().refine(e=>void 0===e.total||void 0===e.current||e.current<=e.total,{message:"Progress current must not exceed total.",path:["current"]}),Je=o.z.object({executable:o.z.string().trim().min(1).max(4096),args:o.z.array(o.z.string().max(4096)).max(128)}).strict(),Ke=o.z.object({code:o.z.string().trim().min(1).max(120).regex(/^[a-z0-9][a-z0-9._-]*$/),message:o.z.string().trim().min(1).max(4096),retryable:o.z.boolean(),sessionIds:o.z.array(we).max(64).optional()}).strict(),Qe=o.z.object({id:we,requestId:we.optional(),appId:we,operation:Ge,state:We,phase:o.z.string().trim().min(1).max(120).optional(),progress:Ve.optional(),command:Je.optional(),logTail:o.z.string().max(32768).optional(),logTruncated:o.z.boolean().optional(),error:Ke.optional(),requestedAt:Se,startedAt:Se.optional(),finishedAt:Se.optional(),updatedAt:Se}).strict(),Ye=o.z.object({streamId:we,sequence:o.z.number().int().nonnegative(),capabilities:Le,apps:o.z.array(He).max(256),activeJobs:o.z.array(Qe).max(256),recentJobs:o.z.array(Qe).max(256),observedAt:Se}).strict(),Xe=o.z.object({requestId:we.optional()}).strict(),Ze=o.z.object({job:Qe}).strict();o.z.object({type:o.z.literal("app-management"),streamId:we,sequence:o.z.number().int().nonnegative(),observedAt:Se,job:Qe.optional(),snapshot:Ye.optional()}).strict().refine(e=>void 0!==e.job||void 0!==e.snapshot,{message:"An app management event requires a job or snapshot."});const et=o.z.object({component:o.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:o.z.string().trim().max(160).optional(),packageVersion:o.z.string().trim().max(80).optional(),protocolVersion:ve.optional(),buildId:o.z.string().trim().max(160).optional(),builtAt:o.z.string().trim().max(120).optional(),gitCommit:o.z.string().trim().max(120).optional(),imageRef:o.z.string().trim().max(512).optional(),imageDigest:o.z.string().trim().max(240).optional()}).strict(),tt=o.z.enum(["stable","beta","alpha"]),st=o.z.discriminatedUnion("component",[o.z.object({component:o.z.literal("node-agent")}).strict(),o.z.object({component:o.z.literal("controlled-instance"),instanceId:we}).strict()]),nt=o.z.object({target:st,channel:tt.default("stable")}).strict(),it=o.z.object({target:st,source:o.z.enum(["npm","docker-registry"]),channel:tt,currentVersion:o.z.string().trim().max(240).optional(),availableVersion:o.z.string().trim().min(1).max(240),artifactRef:o.z.string().trim().min(1).max(512).optional(),updateAvailable:o.z.boolean(),supported:o.z.boolean().default(!0),reason:o.z.string().trim().max(2048).optional(),checkedAt:Se}).strict(),at=o.z.object({id:we,nodeId:we,target:st,source:o.z.enum(["npm","docker-registry"]),channel:tt,fromVersion:o.z.string().trim().max(240).optional(),toVersion:o.z.string().trim().min(1).max(240),artifactRef:o.z.string().trim().min(1).max(512).optional(),status:o.z.enum(["queued","updating","restarting","succeeded","failed"]),error:o.z.string().trim().max(4096).optional(),startedAt:Se.optional(),completedAt:Se.optional(),createdAt:Se,updatedAt:Se}).strict(),rt=o.z.object({type:o.z.enum(["branch","tag","commit"]),name:o.z.string().trim().min(1).max(240).optional(),commit:o.z.string().trim().max(80).optional()}).strict(),ot=o.z.object({type:o.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:o.z.string().trim().min(1).max(120).optional()}).strict(),dt=o.z.object({depth:o.z.number().int().positive().max(1e5).optional(),submodules:o.z.boolean().default(!1),lfs:o.z.boolean().default(!1),subdirectory:o.z.string().trim().max(240).default("")}).strict();o.z.object({id:we,name:o.z.string().trim().min(1).max(160),url:o.z.string().trim().min(1).max(2048),provider:o.z.string().trim().max(80).optional(),ref:rt.default({type:"branch",name:"main"}),auth:ot.default({type:"none"}),clone:dt.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:we.optional(),labels:Ae,createdAt:Se,updatedAt:Se}).strict();const ct=o.z.object({id:we,nodeId:we,name:o.z.string().trim().min(1).max(160),path:o.z.string().trim().min(1).max(4096),defaultImageId:we.optional(),labels:Ae,createdAt:Se,updatedAt:Se}).strict(),lt=o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("git-repository"),repositoryId:we.optional(),url:o.z.string().trim().min(1).max(2048),provider:o.z.string().trim().max(80).optional(),ref:rt.default({type:"branch",name:"main"}),auth:ot.default({type:"none"}),clone:dt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),o.z.object({type:o.z.literal("git-template"),url:o.z.string().trim().min(1).max(2048),templateId:o.z.string().trim().min(1).max(120).optional(),ref:rt.default({type:"branch",name:"main"}),auth:ot.default({type:"none"}),clone:dt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),o.z.object({type:o.z.literal("local-folder"),localFolderId:we.optional(),path:o.z.string().trim().min(1).max(4096),ownerNodeId:we.optional()}).strict()]),pt=o.z.object({mode:o.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:o.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:o.z.string().trim().max(240).optional(),readOnly:o.z.boolean().default(!1)}).strict(),ut=o.z.enum(["codex","claude"]),gt=o.z.object({id:we,name:o.z.string().trim().min(1).max(160),source:lt,defaultImageId:we.optional(),defaultNodeId:we.optional(),defaultRuntimeId:we.optional(),workspacePolicy:pt,labels:Ae,createdAt:Se,updatedAt:Se}).strict(),ht=o.z.object({id:we,name:o.z.string().trim().min(1).max(160),endpoint:o.z.string().trim().min(1).max(2048),key:o.z.string().trim().min(1).max(4096),model:o.z.string().trim().min(1).max(240),app:ut,enabled:o.z.boolean().default(!0),order:o.z.number().int().min(0).max(1e6).default(0),labels:Ae,createdAt:Se,updatedAt:Se}).strict(),mt=ht.omit({key:!0}).extend({keyPreview:o.z.string().trim().min(1).max(160),keySet:o.z.boolean()}).strict(),It=mt.extend({referenceCount:o.z.number().int().min(0)}).strict(),ft=o.z.object({instanceId:we,codexModelHash:we.optional(),claudeModelHash:we.optional(),updatedAt:Se}).strict(),yt=o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("control-plane"),name:ht.shape.name,enabled:ht.shape.enabled,order:ht.shape.order}).strict(),o.z.object({type:o.z.literal("node"),nodeId:we,name:ht.shape.name,enabled:ht.shape.enabled,order:ht.shape.order,referenceCount:o.z.number().int().min(0)}).strict()]),bt=o.z.object({id:we,model:mt,locations:o.z.array(yt).min(1),referenceCount:o.z.number().int().min(0).default(0)}).strict(),vt=o.z.object({nodeId:we,code:o.z.string().trim().min(1).max(120),message:o.z.string().trim().min(1).max(2e3)}).strict(),wt=o.z.object({models:o.z.array(bt),nodeDiagnostics:o.z.array(vt).default([]),updatedAt:Se}).strict();function St(e){const t={app:ut.parse(e.app),endpoint:ht.shape.endpoint.parse(e.endpoint),key:ht.shape.key.parse(e.key),model:ht.shape.model.parse(e.model)};return`mdl_${d.createHash("sha256").update(JSON.stringify(t)).digest("hex")}`}ht.omit({id:!0,createdAt:!0,updatedAt:!0}).strict().partial().strict(),o.z.object({modelSelection:Re,codexModelHash:we.optional(),claudeModelHash:we.optional()}).strict();const At=o.z.object({id:we,name:o.z.string().trim().min(1).max(160),reference:_e,pullPolicy:o.z.literal("if-not-present").default("if-not-present"),capabilities:o.z.array(o.z.string().trim().min(1).max(80)).default([]),optionalApps:o.z.array(o.z.string().trim().min(1).max(120)).default([]),defaultEnv:ke,labels:Ae,createdAt:Se,updatedAt:Se}).strict(),kt=At.omit({reference:!0}).extend({requestedReference:_e,resolvedDigest:Oe.optional(),resolvedReference:_e.optional()}).strict(),Tt=o.z.object({phase:o.z.enum(["checking-image","pulling-image","resolving-image","ready","failed"]),requestedReference:_e,generation:o.z.number().int().nonnegative().default(0),error:o.z.string().trim().max(4096).optional(),startedAt:Se,updatedAt:Se}).strict();function Et(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const s=e,n="string"==typeof s.id?s.id:void 0,i="string"==typeof s.image?s.image:void 0;void 0!==i&&t?.({imageId:n,field:"image"}),Object.prototype.hasOwnProperty.call(s,"registry")&&t?.({imageId:n,field:"registry"});let a="string"==typeof s.reference?s.reference:i;if(a)try{a=ze(a)}catch{if(!a.includes("@")&&a.lastIndexOf(":")<=a.lastIndexOf("/"))try{a=ze(`${a}:latest`),t?.({imageId:n,field:"reference:implicit-latest"})}catch{}}return{id:s.id,name:s.name,reference:a,pullPolicy:s.pullPolicy??"if-not-present",capabilities:s.capabilities,optionalApps:s.optionalApps,defaultEnv:s.defaultEnv,labels:s.labels,createdAt:s.createdAt,updatedAt:s.updatedAt}}const Ct=o.z.object({id:we,name:o.z.string().trim().min(1).max(160),connectionMode:o.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:o.z.object({mode:o.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:we.optional(),secret:o.z.string().trim().max(4096).optional(),pairedAt:Se.optional(),pairing:o.z.object({status:o.z.enum(["pending","paired","expired"]).default("pending"),joinToken:o.z.string().trim().max(4096).optional(),expiresAt:Se.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:o.z.string().trim().max(2048).optional(),controlEndpoint:o.z.string().trim().max(2048).optional(),containerEndpoint:o.z.string().trim().max(2048).optional(),publicWebBase:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:o.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:De.optional(),labels:Ae,lastSeenAt:Se.optional(),createdAt:Se,updatedAt:Se}).strict(),Nt=o.z.object({id:we,nodeId:we,name:o.z.string().trim().min(1).max(160),type:o.z.enum(["docker","kubernetes","local"]),status:o.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:o.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),labels:Ae,createdAt:Se,updatedAt:Se}).strict(),zt=o.z.object({instanceId:we,runtimeKind:o.z.literal("docker"),state:o.z.enum(["pending","available","stopped","unavailable"]),sampledAt:Se,cpu:o.z.object({usagePercent:o.z.number().nonnegative()}).strict().optional(),memory:o.z.object({usageBytes:o.z.number().nonnegative(),limitBytes:o.z.number().nonnegative().optional(),usagePercent:o.z.number().nonnegative().optional()}).strict().optional(),network:o.z.object({rxBytes:o.z.number().nonnegative(),txBytes:o.z.number().nonnegative()}).strict().optional(),blockIo:o.z.object({readBytes:o.z.number().nonnegative(),writeBytes:o.z.number().nonnegative()}).strict().optional(),pids:o.z.number().int().nonnegative().optional(),error:o.z.string().trim().max(2048).optional()}).strict(),_t="instance.metrics.snapshot",Ot=o.z.object({ok:o.z.boolean().optional(),role:o.z.string().optional(),nodeId:we.optional()}).passthrough(),Rt=o.z.object({bindScope:o.z.enum(["loopback","all-ipv4"]),port:o.z.number().int().min(1).max(65535)}).strict().extend({host:o.z.enum(["127.0.0.1","0.0.0.0"]),status:o.z.enum(["listening","error"]),source:o.z.enum(["bootstrap","persisted"]),error:o.z.string().trim().max(2048).optional()}).strict(),xt=o.z.object({nodeId:we,joinToken:o.z.string().trim().min(1),expiresAt:Se}).passthrough(),jt=o.z.object({id:we,keyId:we,url:o.z.string().trim().min(1).max(2048).optional(),name:o.z.string().trim().min(1).max(160).optional(),pairedAt:Se,updatedAt:Se.optional(),active:o.z.boolean().optional(),current:o.z.boolean().optional()}).passthrough(),Dt=o.z.object({remote:o.z.object({id:we,url:o.z.string().trim().min(1).max(2048).optional(),keyId:we,pairedAt:Se,active:o.z.boolean()}).passthrough(),tunnel:o.z.object({status:o.z.enum(["disabled","saved","connecting","connected","failed"]),error:o.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),Pt=o.z.object({deleted:o.z.boolean().optional()}).passthrough(),Mt=o.z.object({repository:o.z.string().trim().optional(),tag:o.z.string().trim().optional(),id:o.z.string().trim(),createdSince:o.z.string().trim().optional(),size:o.z.string().trim().optional(),reference:o.z.string().trim().min(1),repoDigests:o.z.array(o.z.string().trim().min(1).max(512)).default([])}).passthrough(),$t=o.z.object({image:At,status:o.z.enum(["available","pull-required","unknown"]),localImage:Mt.optional(),error:o.z.string().trim().max(2048).optional()}).strict(),qt=o.z.lazy(()=>o.z.object({name:o.z.string().trim().min(1),path:o.z.string().trim().min(1).max(4096),children:o.z.array(qt)}).passthrough()),Lt=o.z.object({status:o.z.number().int().min(100).max(599).optional(),headers:o.z.record(o.z.string(),o.z.string()).optional(),bodyBase64:o.z.string().optional()}).passthrough(),Ut=o.z.object({strategy:o.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:o.z.string().trim().max(2048).optional(),api:o.z.string().trim().max(2048).optional(),vnc:o.z.string().trim().max(2048).optional(),tty:o.z.string().trim().max(2048).optional(),logs:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),Bt=o.z.object({strategy:o.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:o.z.string().trim().max(2048).optional(),api:o.z.string().trim().max(2048).optional(),ws:o.z.string().trim().max(2048).optional(),status:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),Ft=o.z.object({mode:o.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:o.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:o.z.string().trim().max(4096).optional(),resolvedCommit:o.z.string().trim().max(120).optional(),error:o.z.string().trim().max(2048).optional()}).passthrough(),Ht=o.z.object({id:we,name:o.z.string().trim().min(1).max(160),projectId:we.optional(),source:lt,sourceSnapshot:o.z.record(o.z.string(),o.z.unknown()).default({}),modelSelection:Re,nodeId:we,runtimeId:we,imageId:we.optional(),imageSnapshot:kt.optional(),imageProvisioning:Tt.optional(),status:o.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:o.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:o.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:o.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:o.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:o.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:ve.optional(),instanceVersion:o.z.string().trim().max(80).optional(),build:et.optional(),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:De.optional(),config:o.z.object({autoImportAgentConfigs:o.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:Ft.default({status:"unknown"}),target:Ut.default({strategy:"direct-port",status:"unknown"}),access:Bt,apps:o.z.object({runningCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),updatedAt:Se.optional(),revision:o.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:J.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:o.z.object({enabledCount:o.z.number().int().min(0).default(0),runningCount:o.z.number().int().min(0).default(0),errorCount:o.z.number().int().min(0).default(0),configs:o.z.array(o.z.object({configHash:o.z.string().trim().min(1),config:ue,deployments:o.z.array(he).default([]),runtime:o.z.array(me).default([])}).strict()).default([]),recentRuns:o.z.array(Ie).default([])}).strict().optional(),runtime:o.z.object({kind:o.z.string().trim().max(80).optional(),containerName:o.z.string().trim().max(240).optional(),containerId:o.z.string().trim().max(240).optional(),workspacePath:o.z.string().trim().max(4096).optional(),pid:o.z.number().int().positive().optional(),port:o.z.number().int().positive().max(65535).optional(),labels:o.z.record(o.z.string(),o.z.string()).default({})}).strict().default({labels:{}}),registrationToken:o.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:Se.optional(),createdAt:Se,updatedAt:Se}).strict();function Gt(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]]))}o.z.object({instanceId:we.optional(),name:o.z.string().trim().min(1).max(160),projectId:we.optional(),source:lt.optional(),nodeId:we.optional(),runtimeId:we.optional(),imageId:we.optional(),instanceVersion:o.z.string().trim().max(80).optional(),protocolVersion:ve,build:et.optional(),controlMode:o.z.enum(["standalone","controlled"]).default("controlled"),capabilities:o.z.record(o.z.string(),o.z.unknown()).default({}),appInventory:De.optional(),target:Ut.default({strategy:"direct-port",status:"unknown"}),workspace:Ft.default({status:"unknown"}),registrationToken:o.z.string().trim().max(240).optional()}).strict().superRefine((e,t)=>{e.protocolVersion!==be||e.appInventory||t.addIssue({code:"custom",path:["appInventory"],message:"Current protocol register payload requires appInventory."})}),o.z.object({status:Ht.shape.status.optional(),health:Ht.shape.health.optional(),protocolVersion:ve,build:et.optional(),capabilities:Ht.shape.capabilities.optional(),appInventory:De.optional(),apps:Ht.shape.apps.optional(),aiSessions:Ht.shape.aiSessions.optional(),triggers:Ht.shape.triggers.optional(),workspace:Ft.optional(),target:Ut.partial().optional()}).strict().superRefine((e,t)=>{e.protocolVersion!==be||e.appInventory||t.addIssue({code:"custom",path:["appInventory"],message:"Current protocol heartbeat payload requires appInventory."})});const Wt=o.z.enum(["web","telegram","wechat","dingding"]),Vt=o.z.object({id:we,channel:Wt,name:o.z.string().trim().min(1).max(160),enabled:o.z.boolean().default(!1),token:o.z.string().trim().max(4096).optional(),defaultChatId:o.z.string().trim().max(240).optional(),allowedUserIds:o.z.array(o.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:o.z.number().int().positive().max(6e4).default(3e3),settings:o.z.record(o.z.string(),o.z.unknown()).default({}),createdAt:Se,updatedAt:Se}).strict(),Jt=o.z.object({id:we,channel:Wt,bridgeId:we.optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional(),activeProjectId:we.optional(),activeInstanceId:we.optional(),activeAiSessionId:o.z.string().trim().min(1).max(120).optional(),lastUsedAt:Se,createdAt:Se,updatedAt:Se}).strict(),Kt=o.z.object({source:o.z.object({type:o.z.literal("chat-gateway").default("chat-gateway"),channel:Wt,bridgeId:we.optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional()}).strict(),message:o.z.object({text:o.z.string().trim().max(2e4).default(""),attachments:o.z.array(j).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>w&&t.addIssue({code:o.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:o.z.object({instanceId:we,aiSessionId:o.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),Qt=o.z.object({id:we,instanceId:we,projectId:we.optional(),aiSessionId:o.z.string().trim().min(1).max(120),providerSessionId:o.z.string().trim().max(240).optional(),result:o.z.string().default(""),timeoutMs:o.z.number().positive().optional(),source:o.z.string().trim().max(120).optional(),kind:o.z.enum(["task","approval"]).default("task"),status:o.z.enum(["pending","unreachable"]).default("pending"),error:o.z.string().trim().max(2048).optional(),lastSeenAt:Se}).strict();function Yt(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("app.management")?"apps":e.startsWith("trigger.")?"triggers":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}const Xt=o.z.enum(["ai.sessions","app.sessions"]),Zt=o.z.object({topic:Xt,instanceId:o.z.string().trim().min(1).max(160),streamId:o.z.string().trim().min(1).max(240),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0)}).strict(),es=o.z.object({protocolVersion:o.z.literal(1),streams:o.z.array(Zt)}).strict();function ts(e){t.mkdirSync(e,{recursive:!0})}function ss(e,t){ts(s.dirname(e)),l.sync(e,`${JSON.stringify(t,null,2)}\n`,{encoding:"utf8"})}function ns(e,t){console.warn(JSON.stringify({message:e,...t}))}function is(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function as(e,s){const n=s.logger||ns;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void n("stored JSON could not be read",{filePath:e,...is(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 rs(e){return`${e}_${d.randomUUID().replace(/-/g,"").slice(0,20)}`}function os(){return d.randomBytes(24).toString("base64url")}class ds{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){ts(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=as(s.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const s=this.filePath(e);return t.existsSync(s)?as(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 ss(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 cs{filePathValue;defaults;options;constructor(e,t,s={}){this.filePathValue=e,this.defaults=t,this.options=s}init(){ts(s.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?as(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 ss(this.filePathValue,s),s}}const ls=c.promisify(d.scrypt),ps="task_handoff_cp_session",us=o.z.enum(["disabled","password"]),gs=o.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),hs=o.z.string().min(8).max(4096),ms=o.z.object({username:gs,password:hs}).strict(),Is=o.z.object({username:o.z.string().trim().min(1).max(80),password:o.z.string().min(1).max(4096)}).strict(),fs=o.z.object({id:o.z.string().trim().min(1),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}),ys=fs.extend({username:gs,passwordHash:o.z.string().trim().min(1),role:o.z.enum(["viewer","operator","admin"]),lastLoginAt:o.z.string().datetime().optional()}).strict(),bs=fs.extend({userId:o.z.string().trim().min(1),tokenHash:o.z.string().trim().min(1),expiresAt:o.z.string().datetime(),lastSeenAt:o.z.string().datetime().optional()}).strict();function vs(){return(new Date).toISOString()}function ws(e){return d.createHash("sha256").update(e).digest("base64url")}async function Ss(e){const t=d.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await ls(e,t,64)).toString("base64url")}`}function As(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}class ks{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return us.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new ds(e.authUsersDir,{schema:ys}),this.sessions=new ds(e.authSessionsDir,{schema:bs})}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=ms.parse(e),s=vs();return As(this.users.put({id:rs("user"),username:t.username,passwordHash:await Ss(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=Is.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 ls(e,n,64),r=Buffer.from(i,"base64url");return r.length===a.length&&d.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=vs(),i=os(),a=new Date(Date.now()+12096e5).toISOString(),r=this.sessions.put({id:rs("sess"),userId:s.id,tokenHash:ws(i),expiresAt:a,createdAt:n,updatedAt:n,lastSeenAt:n});return this.users.put({...s,lastLoginAt:n,updatedAt:n}),{user:As({...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<=vs()||n.tokenHash!==ws(s))return;const i=this.users.get(n.userId);return i?(this.sessions.put({...n,lastSeenAt:vs(),updatedAt:vs()}),As(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 Ts="app-session.snapshot",Es="app-session.patch",Cs="app-session.removed",Ns=o.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),zs=o.z.object({type:o.z.enum(["app-session","provider-session","adapter-key"]),id:o.z.string().trim().min(1).max(240),agent:o.z.string().trim().min(1).max(80).optional(),adapter:o.z.string().trim().min(1).max(80).optional(),key:o.z.string().trim().min(1).max(240).optional()}).strict(),_s=o.z.record(o.z.string(),o.z.unknown()).and(o.z.object({id:o.z.string().trim().min(1).max(160),appId:o.z.string().trim().min(1).max(160).optional(),title:o.z.string().trim().max(240).optional(),kind:o.z.string().trim().max(80).optional(),status:Ns.default("unknown"),bindings:o.z.array(zs).max(40).default([]),workspace:o.z.object({cwd:o.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:o.z.string().datetime().optional(),updatedAt:o.z.string().datetime().optional()})),Os=o.z.object({runningCount:o.z.number().int().min(0).default(0),problemCount:o.z.number().int().min(0).default(0),sessions:o.z.array(_s).default([]),updatedAt:o.z.string().datetime()}).strict(),Rs=o.z.object({streamId:o.z.string().trim().min(1).max(240),revision:o.z.number().int().min(0),lastEventAt:o.z.string().datetime(),snapshot:Os}).strict();Os.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:o.z.number().int().min(0).optional()});const xs=o.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),js=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),nodeId:o.z.string().trim().min(1).max(160).optional(),revision:o.z.number().int().min(0),previousRevision:o.z.number().int().min(0).optional(),traceId:o.z.string().trim().min(1).max(160),generatedAt:o.z.string().datetime(),reason:xs}).strict(),Ds=o.z.object({meta:js,snapshot:Os}).strict(),Ps=o.z.object({meta:js,session:_s}).strict(),Ms=o.z.object({meta:js,sessionId:o.z.string().trim().min(1).max(160),tombstone:_s.optional(),expiresAt:o.z.string().datetime().optional()}).strict(),$s=o.z.object({streamId:o.z.string().trim().min(1).max(240),instanceId:o.z.string().trim().min(1).max(160),sinceRevision:o.z.number().int().min(0),latestRevision:o.z.number().int().min(0),earliestRetainedRevision:o.z.number().int().min(0),syncRequired:o.z.boolean().default(!1),events:o.z.array(o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal(Ts),payload:Ds}).strict(),o.z.object({type:o.z.literal(Es),payload:Ps}).strict(),o.z.object({type:o.z.literal(Cs),payload:Ms}).strict()])).default([])}).strict();function qs(e,t=(new Date).toISOString()){const s=e.map(e=>Ls(e));return Os.parse({runningCount:s.filter(e=>"running"===e.status).length,problemCount:s.filter(e=>"failed"===e.status).length,sessions:s,updatedAt:t})}function Ls(e){const t=Us("string"==typeof e.status?e.status:void 0),s="string"==typeof e.id?e.id:"",n=function(e){const t=Ws(e.workspace),s=Ws(e.tty),n=Ws(e.launch),i=Ws(e.ai),a=Ws(i.claude),r=Vs(t.cwd)||Vs(s.cwd)||Vs(a.cwd)||Vs(n.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return _s.parse({...a,status:t,bindings:Gs([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=zs.safeParse(e);return t.success?[t.data]:[]}):[]),...s?[{type:"app-session",id:s}]:[],...Fs(e)]),...n?{workspace:n}:{}});var r}function Us(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":Ns.options.includes(t)?t:"unknown"}function Bs(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 Fs(e){const t=Ws(e.ai),s=Ws(t.claude),n=Ws(t.appServer),i=[],a=Vs(s.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Vs(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=Vs(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Vs(n.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Hs(e){const t="string"==typeof e.id?e.id:"",s=Ws(e.ai),n=Vs(Ws(s.claude).short);return[t?`app:${t}`:"",n?`claude-short:${n}`:""]}function Gs(e){const t=new Map;for(const s of e)t.set(Bs(s),s);return[...t.values()]}function Ws(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Vs(e){return"string"==typeof e&&e.trim()?e.trim():""}function Js(e=(new Date).toISOString()){return Os.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function Ks(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 Qs{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 Ys{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",Ot)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",it,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",at,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",o.z.array(at))}getExternalListener(e){return this.client.requestSchema(e,"/settings/external-listener",Rt)}updateExternalListener(e,t){return this.client.requestSchema(e,"/settings/external-listener",Rt,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",xt,{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",Dt,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",o.z.array(jt))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,Pt,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",o.z.array(Mt))}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}`:""),o.z.array(qt))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",o.z.array(ct))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",ct,{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",o.z.array(Nt))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",Nt,{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,Nt,{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,Nt,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,Pt,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,Pt,{method:"DELETE"})}listModels(e){return this.client.requestSchema(e,"/models",o.z.array(It))}createModel(e,t){return this.client.requestSchema(e,"/models",It,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}deployModel(e,t,s){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}/deploy`,It,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}updateModel(e,t,s){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}`,It,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteModel(e,t){return this.client.requestSchema(e,`/models/${encodeURIComponent(t)}`,Pt,{method:"DELETE"})}assignInstanceModels(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/model-assignment`,o.z.object({assignment:ft,instance:Ht}).strict(),{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}async listFleetModels(e){return(await Promise.allSettled(e.map(async e=>({node:e,models:await this.listModels(e)})))).reduce((t,s,n)=>{if("fulfilled"===s.status)t.items.push(...s.value.models.map(e=>({nodeId:s.value.node.id,model:e})));else{const i=e[n];i&&t.nodeErrors.push(Ks(i,"/models","GET",s.reason))}return t},{items:[],nodeErrors:[]})}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(Ks(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",o.z.array(o.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",Ht,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Ht,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,Pt,{method:"POST"})}startInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Ht,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}retryInstanceImageProvisioning(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/image-provisioning/retry`,Ht,{method:"POST"})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Ht,{method:"POST"})}restartInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Ht,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}instanceResourceMetrics(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/metrics`,zt)}proxyRawInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/proxy/raw`,Lt,{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(Ks(e[n],"/instances","GET",s.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const s="/instances",n=(i=t,Ht.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,s=new Set(Object.keys(Ht.shape)),n={};for(const[e,i]of Object.entries(t))s.has(e)&&(n[e]=i);if(t.capabilities&&"object"==typeof t.capabilities&&!Array.isArray(t.capabilities)){const e={...t.capabilities};Object.prototype.hasOwnProperty.call(e,"apps")&&delete e.apps,n.capabilities=e}return n.appInventory=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,s={observedAt:t.observedAt,items:Array.isArray(t.items)?t.items.map(e=>{const t=Gt(e,["id","name","kind","source","availability","diagnosticCode"]);return!t||"object"!=typeof t||Array.isArray(t)?t:{...t,capabilities:Gt(e.capabilities,["automation","supportsCwdSelection"])}}):t.items,issues:Array.isArray(t.issues)?t.issues.map(e=>Gt(e,["code","message"])):t.issues},n=De.safeParse(s);return n.success?n.data:void 0}(t.appInventory),n.apps=Gt(t.apps,["runningCount","problemCount","updatedAt","revision"]),n.config=Gt(t.config,["autoImportAgentConfigs"]),n.modelSelection=Gt(t.modelSelection,["codexModelHash","claudeModelHash"]),n.imageSnapshot=function(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const s=e,n=Et(s),{reference:i,...a}=n,r=s.requestedReference??i;return void 0!==s.image||s.reference,{...a,id:s.id??t,requestedReference:r,resolvedDigest:s.resolvedDigest,resolvedReference:s.resolvedReference}}(t.imageSnapshot,t.imageId,"string"==typeof t.id&&t.id),n.imageProvisioning=Gt(t.imageProvisioning,["phase","requestedReference","generation","error","startedAt","updatedAt"]),n.runtime=Gt(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),n.target=Gt(t.target??t.endpoints,["strategy","web","api","status"]),n.access=Gt(t.access,["strategy","web","api","ws","status"]),n}(i)));var i;if(n.success)return n.data.protocolVersion&&n.data.protocolVersion!==be?(this.client.logger?.warn?.({nodeId:e.id,instanceId:n.data.id,expectedProtocolVersion:be,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 ${be}.`,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 Xs(){return(new Date).toISOString()}function Zs(e){return e?e instanceof Headers?Object.fromEntries(e.entries()):Array.isArray(e)?Object.fromEntries(e.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(e).map(([e,t])=>[e,String(t)])):{}}function en(e){const t=String(e||"").split(":");if(3===t.length&&"ai"===t[1]&&t[0]&&t[2])return{instanceId:t[0],aiSessionId:t[2]}}function tn(e){return e instanceof Error?e.message:String(e)}function sn(e,t){const s=new Error(t);throw Object.assign(s,{statusCode:404,code:e}),s}class nn{requireNode;nodeAgentRequest;nodeAgentStreamRequest;fetchImpl;constructor(e){this.requireNode=e.requireNode,this.nodeAgentRequest=e.nodeAgentRequest,this.nodeAgentStreamRequest=e.nodeAgentStreamRequest,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:Zs(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,i=t.details&&"object"==typeof t.details&&!Array.isArray(t.details)?t.details:void 0;return s||n?{code:s,message:n,details:i}: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",...s?.details?{details:s.details}:{},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.nodeAgentStreamRequest(n,`/instances/${encodeURIComponent(e.id)}/proxy/stream`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({path:t,method:s.method||"GET",headers:Zs(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:Object.fromEntries(i.headers.entries()),body:i.body}}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 an(e){if(!e||"object"!=typeof e)return;const t=e,s=et.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 rn(e){const{agent:t,...s}=e.capabilities,{secret:n,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=an(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...s,agent:r}:s}}function on(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function dn(e){return function(e){const{registrationToken:t,target: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 cn(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 ln{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=dn(e.instance);return{...n,aiSessions:Y(n.aiSessions),connectionStatus:s,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?rn(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===be}}({instance:e,currentTime:a,project:e.projectId?t.get(e.projectId):void 0,image:e.imageSnapshot||(e.imageId?s.get(e.imageId):void 0),node:n.get(e.nodeId),runtime:i.get(`${e.nodeId}:${e.runtimeId}`)})),nodeErrors:e.nodeErrors||[]}}}const pn=ue.extend({id:ue.shape.configHash}),un=o.z.object({name:ue.shape.name,description:ue.shape.description,source:ce,action:le,policy:pe.partial().optional()}).strict(),gn=o.z.object({instanceIds:o.z.array(Ht.shape.id).min(1),target:ge,enabled:o.z.boolean().optional()}).strict(),hn=o.z.object({configHash:ue.shape.configHash,enabled:o.z.boolean().optional()}).strict();class mn{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=un.parse(e||{}),s=Xs(),n={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=ye({source:e.source,action:e.action,policy:pe.parse(e.policy||{})});return`trg_${d.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:n}),a=pn.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||sn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:n}}async applyTrigger(e,t){const s=this.requireTrigger(e),n=gn.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=hn.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:In(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=In(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||sn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function In(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}const fn=o.z.object({id:gt.shape.id.optional(),name:gt.shape.name,source:lt,defaultImageId:gt.shape.defaultImageId,defaultNodeId:gt.shape.defaultNodeId,defaultRuntimeId:gt.shape.defaultRuntimeId,workspacePolicy:pt.optional(),labels:gt.shape.labels.optional()}).strict(),yn=fn.omit({id:!0}).partial().strict(),bn=o.z.object({publicBaseUrl:o.z.string().trim().max(2048).optional(),updateChannel:tt.default("stable")}).strict(),vn=bn.partial().strict(),wn=o.z.object({id:At.shape.id.optional(),name:At.shape.name,reference:At.shape.reference,pullPolicy:At.shape.pullPolicy.optional(),capabilities:o.z.array(o.z.string().trim().min(1).max(80)).optional(),optionalApps:o.z.array(o.z.string().trim().min(1).max(120)).optional(),defaultEnv:At.shape.defaultEnv.optional(),labels:At.shape.labels.optional()}).strict(),Sn=wn.omit({id:!0}).partial().strict();class An{options;constructor(e){this.options=e}getSettings(){return bn.parse(this.options.settings.get())}updateSettings(e){const t=vn.parse(e),s=bn.parse({...this.getSettings(),...t,..."publicBaseUrl"in t?{publicBaseUrl:t.publicBaseUrl?.trim()||void 0}:{}});return this.options.settings.put(s)}seedDefaults(){if(this.options.images.get("img_default"))return;const e=Xs();this.options.images.put(At.parse({id:"img_default",name:"TaskHandoff Browser + Terminal",reference:process.env.TASK_HANDOFF_CONTROLLED_IMAGE||"huadream/task-handoff-controlled-instance:latest",pullPolicy:"if-not-present",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.options.projects.list().map(e=>this.normalizeProjectRecord(e))}getProject(e){const t=this.options.projects.get(e);return t?this.normalizeProjectRecord(t):void 0}createProject(e){const t=fn.parse(e),s=Xs(),n=lt.parse(t.source);return this.options.projects.put(gt.parse({...t,id:t.id||rs("proj"),source:n,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.options.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",workspacePolicy:pt.parse(t.workspacePolicy||on(n)),labels:t.labels||{},createdAt:s,updatedAt:s}))}updateProject(e,t){const s=yn.parse(t),n=this.requireProject(e),i=s.source?lt.parse(s.source):n.source;return this.options.projects.put(gt.parse({...n,...s,id:e,source:i,workspacePolicy:s.workspacePolicy?pt.parse(s.workspacePolicy):n.workspacePolicy,createdAt:n.createdAt,updatedAt:Xs()}))}deleteProject(e){return this.options.projects.delete(e)}requireProject(e){const t=this.getProject(e);return t||sn("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}listImages(){return this.options.images.list()}createImage(e){const t=wn.parse(e),s=Xs();return this.options.images.put(At.parse({...t,id:t.id||rs("img"),pullPolicy:t.pullPolicy||"if-not-present",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:s,updatedAt:s}))}updateImage(e,t){const s=Sn.parse(t),n=this.requireImage(e);return this.options.images.put(At.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Xs()}))}deleteImage(e){return this.options.images.delete(e)}requireImage(e){const t=this.options.images.get(e);return t||sn("IMAGE_NOT_FOUND",`Image ${e} was not found.`),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"),gt.parse(t)}return gt.parse(e)}(e);return t!==e&&this.options.projects.put(t),t}}class kn{records=new Map;options;constructor(e){this.options=e}create(e){const t=d.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 Tn{requireInstance;listAppSessions;tokens=new kn({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=>Nn(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:En(t,n.sessionId,r,s)}}}function En(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):Nn(Cn(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Nn(Cn(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Cn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Nn(e){return"string"==typeof e&&e.trim()?e.trim():void 0}const zn={allow:"a",deny:"d",skip:"s"};function _n(e){return d.createHash("sha256").update(e).digest("base64url").slice(0,Math.ceil(16))}class On{tokens=new kn({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:v1:${zn[t]}:${_n(e)}`}}function Rn(e){const{token:t,...s}=e;return{...s,settings:jn(e.settings),tokenSet:Boolean(e.token)}}function xn(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 jn(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}const Dn=o.z.object({name:Vt.shape.name.optional(),channel:Vt.shape.channel.optional(),enabled:o.z.boolean().optional(),token:Vt.shape.token,defaultChatId:Vt.shape.defaultChatId,allowedUserIds:o.z.array(o.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Vt.shape.pollIntervalMs.optional(),settings:Vt.shape.settings.optional()}).strict(),Pn=Dn.extend({channel:Vt.shape.channel,name:Vt.shape.name.optional()});class Mn{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(Rn)}require(e){const t=this.chatBridges.get(e);return t||sn("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=Pn.parse(e),s=Xs(),n=Vt.parse({...t,id:rs(`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:xn({},t.settings),createdAt:s,updatedAt:s});var i,a;return this.chatBridges.put(n),Rn(n)}update(e,t){const s=Dn.parse(t),n=this.require(e),i=xn(n.settings,s.settings),a=Vt.parse({...n,...s,id:e,channel:n.channel,name:s.name||n.name,settings:i,updatedAt:Xs()});return this.chatBridges.put(a),Rn(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 $n{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||sn("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=Xs(),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=Jt.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 qn(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function Ln(e,t=40){return qn(e.slice(0,t).map(e=>[e]))}function Un(e){return Buffer.from(e,"utf8").toString("base64url")}function Bn(e){const t=Hn(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=Hn(t),s=String(e.callback_data||""),i=String(e.text||"").trim();i&&s&&n.push({text:i,id:`th_cb_${Un(s)}`})}return n.slice(0,40)}function Fn(e,t){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function Hn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const Gn=new Set(["stopped","failed","exited","closed","terminated"]);function Wn(e){return!function(e){return Gn.has(Us(e))}(e)}function Vn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const s=(e.apps?.sessions||[]).filter(Zn);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[...Ls(e).bindings.map(Bs),...Hs(e)].filter(Boolean)}(e).some(e=>n.has(e))))}(e,t))}function Jn(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function Kn(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function Qn(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=t?` - ${Fn(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${s}`}function Yn(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return Ln(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Fn(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Fn(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Fn(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function Xn(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 Zn(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return Wn("string"==typeof t.status?t.status:void 0)}class ei{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?Ln(t.map(e=>({text:`${e.active?"✓ ":""}${Qn(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:dn(t),aiSession:s,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function ti(e){return e.providerTurnId||e.turnId||""}function si(e){return Wn(oi(e.status))}function ni(e,t){const s=oi(t.appId)||"App",n=oi(t.title).trim();if(n&&n!==s)return n;const i=function(e,t){for(const s of e.appInventory?.items||[])if(s.id===t)return s.name;return""}(e,s);return i||s}function ii(e){const t=function(e){return"online"!==e.connectionStatus?[]:(e.appInventory?.items||[]).filter(e=>"available"===e.availability)}(e),s=new Map;for(const e of t){const t=di(e);t&&!s.has(t.id)&&s.set(t.id,t)}return[...s.values()]}function ai(e,t,s,n){const i=oi(t.id)?function(e,t){return`/apps/access/${ri(e)}?token=${encodeURIComponent(t())}`}(t,n):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const s=oi(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 ri(e){const t=oi(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function oi(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function di(e){if("string"==typeof e&&e.trim())return{id:e,label:ci(e)};const t=function(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}(e),s=oi(t.id).trim();return s?{id:s,label:oi(t.name).trim()||ci(s)}:void 0}function ci(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())}class li{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?Ln(t.map(e=>{return{text:Fn(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=ii(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?Ln(n.map(e=>{return{text:Fn(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=ii(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(si).filter(e=>Boolean(oi(e.id))).map(e=>({instance:t,session:e,link:ai(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:oi(e.id)||"",mode:ri(e)}).token)})):[]}),i=n.filter(e=>e.link.url).map(e=>({text:Fn(`${e.instance.name} · ${ni(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?Ln(i):void 0};var a,r}}class pi{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:dn(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}: ${tn(t)}`}}}failedSendResult(e,t,s,n){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:dn(s),reply:n}}sentResult(e,t,s,n,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:dn(s),aiSession:i,instanceId:s.id,aiSessionId:n,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:ti(i),reply:`Sent to ${t?.name||s.sourceSnapshot.name||"Source"} / ${s.name} / ${n}.`};var a}}class ui{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(Xn).join("\n"):"No pending routes.",replyMarkup:s.length?Yn(s,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,s){const n=en(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=en(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 gi{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 hi{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}`:`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?Vn({...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=>Vn(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=Jn(t.session)-Jn(e.session);return s||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||Kn(e.session).localeCompare(Kn(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||sn("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const s=await this.findInstance(e,t);return s||sn("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 mi{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new hi(e),this.sender=new pi(e,this.targets),this.appCommands=new li(e,this.targets),this.pendingCommands=new ui(e),this.aiSessionCommands=new ei(e,this.targets),this.targetCommands=new gi(e,this.targets)}async handleChatGatewayMessage(e){const t=Kt.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 Ii(e){return/^[a-z]:[\\/]/i.test(e)||/^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/.test(e)}function fi(e){return Ii(e)?s.win32:s.posix}const yi=o.z.object({mode:o.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Ct.shape.id.optional(),secret:o.z.string().trim().max(4096).optional(),pairedAt:Ct.shape.createdAt.optional(),pairing:o.z.object({status:o.z.enum(["pending","paired","expired"]).default("pending"),joinToken:o.z.string().trim().max(4096).optional(),expiresAt:Ct.shape.createdAt.optional()}).strict().optional()}).strict(),bi=o.z.object({id:Ht.shape.id.optional(),name:Ht.shape.name.optional(),projectId:Ht.shape.projectId,source:lt.optional(),sourceSnapshot:o.z.record(o.z.string(),o.z.unknown()).optional(),nodeId:Ht.shape.nodeId.optional(),runtimeId:Ht.shape.runtimeId.optional(),imageId:Ht.shape.imageId,config:Ht.shape.config.optional(),modelSelection:Ht.shape.modelSelection.optional()}).strict(),vi=o.z.object({name:Ht.shape.name.optional(),config:Ht.shape.config.optional(),modelSelection:Ht.shape.modelSelection.optional()}).strict(),wi=o.z.object({name:ht.shape.name,endpoint:ht.shape.endpoint,key:ht.shape.key,model:ht.shape.model,app:ht.shape.app,enabled:ht.shape.enabled.optional(),order:ht.shape.order.optional(),labels:ht.shape.labels.optional()}).strict(),Si=wi.partial().strict(),Ai=o.z.object({id:Ct.shape.id.optional(),name:Ct.shape.name,connectionMode:Ct.shape.connectionMode.optional(),auth:yi.optional(),joinToken:o.z.string().trim().min(1).max(4096).optional(),endpoint:Ct.shape.endpoint,controlEndpoint:Ct.shape.controlEndpoint,containerEndpoint:Ct.shape.containerEndpoint,publicWebBase:Ct.shape.publicWebBase,status:o.z.enum(["unknown","online","offline","degraded"]).optional(),health:o.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:Ct.shape.capabilities.optional(),labels:Ct.shape.labels.optional(),lastSeenAt:Ct.shape.lastSeenAt}).strict(),ki=o.z.object({name:Ct.shape.name.optional(),connectionMode:Ct.shape.connectionMode.unwrap().optional(),auth:yi.optional(),endpoint:Ct.shape.endpoint,controlEndpoint:Ct.shape.controlEndpoint,containerEndpoint:Ct.shape.containerEndpoint,publicWebBase:Ct.shape.publicWebBase,status:Ct.shape.status.unwrap().optional(),health:Ct.shape.health.unwrap().optional(),capabilities:Ct.shape.capabilities.unwrap().optional(),labels:Ct.shape.labels.unwrap().optional(),lastSeenAt:Ct.shape.lastSeenAt}).strict(),Ti=o.z.object({controlPlaneUrl:o.z.string().trim().url().max(2048),joinToken:o.z.string().trim().min(1).max(4096),controlPlaneName:o.z.string().trim().min(1).max(160).optional(),activate:o.z.boolean().optional()}).strict(),Ei=o.z.object({nodeName:o.z.string().trim().min(1).max(160).optional(),expiresInMs:o.z.number().int().positive().max(36e5).optional()}).strict();function Ci(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 Ni="TASK_HANDOFF_NODE_AGENT_V1",zi=6e4;function _i(e=""){return d.createHash("sha256").update(e).digest("hex")}function Oi(e,t){return d.createHmac("sha256",e).update(function(e){return[Ni,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function Ri(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||d.randomBytes(18).toString("base64url"),n=_i(e.body||""),i=Oi(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 xi(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function ji(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 Di(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 Pi=o.z.object({id:o.z.string().trim().min(1),tokenHash:o.z.string().trim().min(1),expiresAt:o.z.string().datetime(),nodeName:o.z.string().trim().min(1).max(160).optional(),createdAt:o.z.string().datetime(),updatedAt:o.z.string().datetime()}).strict(),Mi="task-handoff.control-plane.local",$i="task-handoff.control-plane.builtin";function qi(e){return"true"===e.labels[Mi]}function Li(e){return"true"===e.labels[$i]}function Ui(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 Bi(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return Ri({nodeId:e.id,keyId:Ui(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 Fi{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new On;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new ln;controlPlaneTriggerService;catalogService;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 Qs({request:(e,t,s)=>this.nodeAgentFetch(e,t,s),logger:this.logger});this.nodeAgentGateway=new Ys(n),this.controlledInstanceGateway=new nn({requireNode:e=>this.requireNode(e),nodeAgentRequest:(e,t,s)=>this.nodeAgentFetch(e,t,s),nodeAgentStreamRequest:(e,t,s)=>this.nodeAgentTransportFor(e).requestStream(e,t,s),fetchImpl:this.fetchImpl});const i=e=>({schema:e,logger:(e,t)=>this.logWarn(t,e)});this.projects=new ds(e.projectsDir,i(gt)),this.models=new ds(e.modelsDir,i(ht)),this.images=new ds(e.imagesDir,{...i(At),sanitize:e=>Et(e,e=>this.logWarn(e,"legacy image profile field was migrated"))}),this.nodes=new ds(e.nodesDir,i(Ct)),this.chatSessions=new ds(e.chatSessionsDir,i(Jt)),this.chatBridges=new ds(e.chatBridgesDir,i(Vt)),this.triggers=new ds(e.triggersDir,i(pn)),this.nodeJoinInvites=new ds(e.nodeJoinInvitesDir,i(Pi)),this.settings=new cs(e.settingsPath,()=>bn.parse({}),i(bn)),this.catalogService=new An({projects:this.projects,images:this.images,settings:this.settings,defaultNodeId:()=>this.defaultNodeId()}),this.controlPlaneTriggerService=new mn({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 Tn({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Mn({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new $n({chatSessions:this.chatSessions}),this.chatSessionRuntime=new mi({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.catalogService.getProject(e),listProjects:()=>this.catalogService.listProjects(),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 this.catalogService.getSettings()}updateSettings(e){return this.catalogService.updateSettings(e)}seedDefaults(){return this.catalogService.seedDefaults()}listProjects(){return this.catalogService.listProjects()}defaultNodeId(){return this.nodes.list().find(qi)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?Bi(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!==be){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 ${be}.`);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:be,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=et.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=xi(e);return n?(ji(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((r,o)=>{const d=u.request({socketPath:e,method:n,path:`/api/node-agent${t}`,headers:Object.fromEntries(a.entries())},e=>{const t=e.statusCode||500;r(new Response("HEAD"!==n&&204!==t&&205!==t&&304!==t?h.Readable.toWeb(e):null,{status:t,headers:e.headers}))});d.on("error",o);const c=()=>d.destroy(new DOMException("The operation was aborted.","AbortError"));s.signal?.aborted?c():s.signal?.addEventListener("abort",c,{once:!0}),d.once("close",()=>s.signal?.removeEventListener("abort",c)),void 0!==i&&d.write(i),d.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:Xs()}}async syncLocalNodeConnection(){const e={[Mi]:"true",[$i]:"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 xi(e)?"local-ipc":"local-loopback"}(s),i=Ct.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:n,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:Xs(),updatedAt:Xs()}),a=await this.inspectNodeAgent(s,i),r=Xs(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>qi(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=Ct.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:an(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(cn)}async listFederatedModels(){const e=this.listNodes(),t=await this.nodeAgentGateway.listFleetModels(e),s=new Map;for(const e of this.listAllModels())s.set(e.id,{id:e.id,model:cn(e),locations:[{type:"control-plane",name:e.name,enabled:e.enabled,order:e.order}],referenceCount:0});for(const{nodeId:e,model:n}of t.items){const{referenceCount:t,...i}=n,a=s.get(n.id);a?(a.locations.push({type:"node",nodeId:e,name:n.name,enabled:n.enabled,order:n.order,referenceCount:n.referenceCount}),a.referenceCount+=n.referenceCount):s.set(n.id,{id:n.id,model:i,locations:[{type:"node",nodeId:e,name:n.name,enabled:n.enabled,order:n.order,referenceCount:n.referenceCount}],referenceCount:n.referenceCount})}return wt.parse({models:[...s.values()].sort((e,t)=>e.model.order-t.model.order||e.model.name.localeCompare(t.model.name)),nodeDiagnostics:t.nodeErrors.map(e=>({nodeId:e.nodeId,code:e.code,message:e.message})),updatedAt:Xs()})}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))}async createModel(e){const t=wi.parse(e),s=Xs(),n=this.nextModelOrder(),i=St(t),a=this.models.get(i),r=ht.parse({...t,id:i,enabled:t.enabled??!0,order:t.order??n,labels:t.labels||{},createdAt:a?.createdAt||s,updatedAt:s});return cn(this.models.put(r))}async updateModel(e,t){const s=Si.parse(t),n=this.requireModelSecret(e),i=ht.parse({...n,...s,key:s.key?.trim()?s.key:n.key,createdAt:n.createdAt,updatedAt:Xs()}),a=St(i);return cn(this.models.put(ht.parse({...i,id:a})))}async deleteModel(e){return this.requireModelSecret(e),this.models.delete(e)}async 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)||sn("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const n=s.get(e);this.models.put(ht.parse({...n,order:100*(t+1),updatedAt:Xs()}))}),this.listModels()}createNodeModel(e,t){return this.nodeAgentGateway.createModel(this.requireNode(e),t)}updateNodeModel(e,t,s){return this.nodeAgentGateway.updateModel(this.requireNode(e),t,s)}deleteNodeModel(e,t){return this.nodeAgentGateway.deleteModel(this.requireNode(e),t)}createProject(e){return this.catalogService.createProject(e)}updateProject(e,t){return this.catalogService.updateProject(e,t)}deleteProject(e){return this.catalogService.deleteProject(e)}listImages(){return this.catalogService.listImages()}createImage(e){return this.catalogService.createImage(e)}async listNodeDockerImages(e){const t=this.requireNode(e);return this.nodeAgentGateway.listDockerImages(t)}async listNodeImageAvailability(e){const t=this.requireNode(e),s=this.listImages();try{const e=await this.nodeAgentGateway.listDockerImages(t);return s.map(t=>{const s=e.find(e=>e.reference===t.reference||e.repoDigests.includes(t.reference));return $t.parse({image:t,status:s?"available":"pull-required",localImage:s})})}catch(e){const t=e instanceof Error?e.message:String(e);return s.map(e=>$t.parse({image:e,status:"unknown",error: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=Ct.parse({...t,id:n,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:an(e)}:t.capabilities,lastSeenAt:Xs(),updatedAt:Xs()});return n!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:Xs(),agent:e}}catch(e){const s=Ct.parse({...t,status:"offline",health:"failed",updatedAt:Xs()});return this.nodes.put(s),{id:t.id,status:"offline",checkedAt:Xs(),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 await this.ensureInstanceModelAssignment(e),this.nodeAgentGateway.applyUpdate(s,t)}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(!Li(t)||!qi(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=Ei.parse(e&&"object"==typeof e?e:{}),s=Xs(),n=os(),i={id:rs("node_join"),tokenHash:d.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=o.z.object({joinToken:o.z.string().trim().min(1).max(4096),nodeId:Ct.shape.id,nodeName:Ct.shape.name.optional(),keyId:o.z.string().trim().min(1).max(160),secret:o.z.string().trim().min(1).max(4096),pairedAt:o.z.string().datetime().optional()}).parse(e),s=d.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=Xs();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=Ct.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=Ti.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){return this.catalogService.updateImage(e,t)}async deleteImage(e){this.catalogService.requireImage(e);const t=this.listProjects().find(t=>t.defaultImageId===e);if(t)throw Object.assign(new Error(`Image ${e} is the default for project ${t.name}.`),{statusCode:409,code:"IMAGE_IN_USE"});const s=(await Promise.all(this.listNodes().map(e=>this.nodeAgentGateway.listLocalFolders(e)))).flat().find(t=>t.defaultImageId===e);if(s)throw Object.assign(new Error(`Image ${e} is the default for local folder ${s.name}.`),{statusCode:409,code:"IMAGE_IN_USE"});const n=(await this.listNodeInstances()).find(t=>t.imageId===e);if(n)throw Object.assign(new Error(`Image ${e} is used by instance ${n.name}.`),{statusCode:409,code:"IMAGE_IN_USE"});return this.catalogService.deleteImage(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(rn)}async createNode(e){const t=Ai.parse(e),s=Xs(),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||rs("node"),d=Ct.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=Ct.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:an(c.data)}:{}},labels:t.labels||{},createdAt:s,updatedAt:s});return this.nodes.put(g)}updateNode(e,t){const s=ki.parse(t),n=this.requireNode(e),i=Ct.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Xs()});return this.nodes.put(i)}deleteNode(e){if(Li(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(dn)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(dn),nodeErrors:e.nodeErrors}}async instanceResourceMetrics(e){const t=await this.requireNodeInstance(e);return this.nodeAgentGateway.instanceResourceMetrics(this.requireNode(t.nodeId),t.id)}async nodeOwnsInstance(e,t){const s=this.requireNode(e);return(await this.nodeAgentGateway.listInstances(s)).some(s=>s.id===t&&s.nodeId===e)}async createControlledInstance(e){const t=bi.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});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}const u=await this.prepareInstanceModels(a,t.modelSelection||{}),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,modelSelection:{}});let h=g;try{h=(await this.nodeAgentGateway.assignInstanceModels(a,g.id,u)).instance}catch(e){throw await this.nodeAgentGateway.deleteInstance(a,g.id).catch(()=>{}),e}return{...dn(h),registrationToken:h.registrationToken}}async updateControlledInstance(e,t){const s=vi.parse(t),n=await this.requireNodeInstance(e),i=this.requireNode(n.nodeId),{modelSelection:a,...r}=s;let o=Object.keys(r).length?await this.nodeAgentGateway.updateInstance(i,e,r):n;if(a){const t=await this.prepareInstanceModels(i,a);o=(await this.nodeAgentGateway.assignInstanceModels(i,e,t)).instance}return dn(o)}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 await this.ensureInstanceModelAssignment(t),dn(await this.nodeAgentGateway.startInstance(s,e))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return dn(await this.nodeAgentGateway.stopInstance(s,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return await this.ensureInstanceModelAssignment(t),dn(await this.nodeAgentGateway.restartInstance(s,e))}async retryControlledInstanceImageProvisioning(e){const t=await this.requireNodeInstance(e);return dn(await this.nodeAgentGateway.retryInstanceImageProvisioning(this.requireNode(t.nodeId),e))}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.listImages(),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 K.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:tn(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{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=Js();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:t.updatedAt,snapshot:t}}try{return Rs.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:tn(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const s=Js();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 ie.parse(await this.instanceRequest(n,`/ai-sessions?${i}`))}async recoverAiSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return K.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 $s.parse(await this.instanceRequest(n,`/apps/sessions?${i}`))}async recoverAppSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return Rs.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=Xs(),t=[],s=await this.listAiSessions(),n=new Map(s.instances.map(e=>[e.instanceId,e.aiSessions])),i=await this.nodeAgentGateway.listFleetInstances(this.listNodes());for(const s of i.items){if("online"!==s.connectionStatus&&"online"!==s.agentStatus||!s.target.web)continue;const i=s.projectId?this.catalogService.getProject(s.projectId):void 0;for(const a of n.get(s.id)?.sessions||[]){if("waiting"!==a.status)continue;const n=Qt.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:dn(s)})}}return t}async listAiSessionInstanceNames(){return(await this.nodeAgentGateway.listFleetInstances(this.listNodes())).items.map(e=>({id:e.id,name:e.name}))}async resolveAiSessionApproval(e,t,s){const n=await this.requireControlledInstance(e,!0);return W.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 instanceAppManagement(e){const t=await this.requireControlledInstance(e,!0);try{return function(e){try{return Ye.parse(e)}catch(e){if(e instanceof o.z.ZodError){const e=new Error("This controlled instance does not support managed app operations.");throw Object.assign(e,{statusCode:409,code:"INSTANCE_APP_MANAGEMENT_UNSUPPORTED"}),e}throw e}}(await this.instanceRequest(t,"/apps/management"))}catch(e){const t=e&&"object"==typeof e?e:{};if(e instanceof o.z.ZodError||404===t.statusCode){const e=new Error("This controlled instance does not support managed app operations.");throw Object.assign(e,{statusCode:409,code:"INSTANCE_APP_MANAGEMENT_UNSUPPORTED"}),e}throw e}}async requestInstanceAppOperation(e,t,s,n={}){const i=await this.requireControlledInstance(e,!0);return Ze.parse(await this.instanceRequest(i,`/apps/${encodeURIComponent(t)}/${s}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}))}async instanceAppManagementJob(e,t){const s=await this.requireControlledInstance(e,!0);return Ze.parse(await this.instanceRequest(s,`/apps/jobs/${encodeURIComponent(t)}`))}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)<=w)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 W.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 B.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return W.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 G.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 G.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 W.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(){const e=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=Bi(e,{method:i,pathWithQuery:`/api/node-agent${t}`,body:a||""});return this.fetchNodeAgentEndpoint(n,t,{...s,body:a,headers:{...s.headers||{},...r}})};return{request:e,requestStream:e,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=xi(a),o=`/api/node-agent${s}`,d=Bi(e,{method:"GET",pathWithQuery:o});if(r){ji(r);const e=Di(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=fe.parse(await this.instanceRequest(e,"/triggers"));return Ht.parse({...e,triggers:Ci(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{}sn("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||sn("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 fi(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(Ii(e)!==Ii(t))return;const s=fi(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||on(e.source).path||"/workspace";return a.length?s.posix.join(r,...a):r}async prepareInstanceModels(e,t){const s=await this.nodeAgentGateway.listModels(e),n={...null===t.codexModelHash?{codexModelHash:null}:t.codexModelHash?.trim()?{codexModelHash:t.codexModelHash.trim()}:{},...null===t.claudeModelHash?{claudeModelHash:null}:t.claudeModelHash?.trim()?{claudeModelHash:t.claudeModelHash.trim()}:{}},i=async(t,n)=>{if(null===n)return;const i=n?this.listAllModels().find(e=>e.id===n):this.listAllModels().find(e=>e.enabled&&e.app===t);if(i){if(i.app!==t)throw Object.assign(new Error(`Model ${i.id} is not a ${t} model.`),{statusCode:400,code:"MODEL_APP_MISMATCH"});if(!i.enabled)throw Object.assign(new Error(`Model ${i.id} is disabled.`),{statusCode:400,code:"MODEL_DISABLED"});return await this.nodeAgentGateway.deployModel(e,i.id,i),i.id}if(!n)return;const a=s.find(e=>e.id===n);if(a||sn("MODEL_NOT_FOUND",`Model ${n} was not found on control-plane or node ${e.id}.`),a.app!==t)throw Object.assign(new Error(`Model ${n} is not a ${t} model.`),{statusCode:400,code:"MODEL_APP_MISMATCH"});if(!a.enabled)throw Object.assign(new Error(`Model ${n} is disabled.`),{statusCode:400,code:"MODEL_DISABLED"});return a.id};return{modelSelection:n,codexModelHash:await i("codex",n.codexModelHash),claudeModelHash:await i("claude",n.claudeModelHash)}}async ensureInstanceModelAssignment(e){const t=this.requireNode(e.nodeId),s=await this.prepareInstanceModels(t,e.modelSelection);return this.nodeAgentGateway.assignInstanceModels(t,e.id,s)}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}requireProject(e){return this.catalogService.requireProject(e)}requireModel(e,t=!1){const s=this.models.get(e);s||sn("MODEL_NOT_FOUND",`Model ${e} was not found.`);const n=this.normalizeModelRecord(s);return t?n:cn(n)}requireModelSecret(e){const t=this.models.get(e);return t||sn("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,ht.parse(t)}return ht.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){return this.catalogService.requireImage(e)}requireNode(e){const t=this.nodes.get(e);return t||sn("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return rn(this.requireNode(e))}async requireControlledInstance(e,t=!1){const s=await this.requireNodeInstance(e);return t?s:dn(s)}}function Hi(e){return m(String(e),"escape").replace(/\n+$/,"")}function Gi(e){const[t,...s]=String(e).split("\n");return[`*${Hi(t)}*`,...s.map(Hi)].join("\n")}function Wi(e,t=4e3){const s=String(e),n=Gi(s);if(n.length<=t)return n;const[i,...a]=s.split("\n"),r=a.join("\n"),o="...\n";let d=i;for(;Gi(`${d}\n${o}`).length>t&&d.length>0;)d=d.slice(0,Math.floor(.8*d.length));let c=0,l=r.length,p=0,u=Gi(`${d}\n${o}`);for(;c<=l;){const e=Math.floor((c+l)/2),s=Gi(`${d}\n${o}${r.slice(-e)}`);s.length<=t?(u=s,p=e,c=e+1):l=e-1}const g=r.slice(-p),h=g.indexOf("\n");return h>=0&&h<200&&(u=Gi(`${d}\n${o}${g.slice(h+1)}`)),u}function Vi(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Ji(e,t,s,n,i={}){if(!t.token)return;const a=s=>e(`https://api.telegram.org/bot${t.token}/sendMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),r=function(e,t={}){const s=String(e),n=t.rawMarkdownV2?e=>e:Hi;if(n(s).length<=4e3)return[s];const i=[];let a=s;for(;a;){let e=1,t=a.length,s=1;for(;e<=t;){const i=Math.floor((e+t)/2);n(a.slice(0,i)).length<=4e3?(s=i,e=i+1):t=i-1}if(s<a.length){const e=a.slice(0,s),t=Math.max(e.lastIndexOf("\n"),e.lastIndexOf(" "));t>=Math.floor(.6*s)&&(s=t+1)}i.push(a.slice(0,s).trimEnd()),a=a.slice(s).trimStart()}return i.filter(Boolean)}(n,i);let o;for(const[e,t]of r.entries())o=await Qi(a,"sendMessage",t,{chat_id:s,...0===e&&i.replyToMessageId?{reply_to_message_id:i.replyToMessageId,allow_sending_without_reply:!0}:{},...e===r.length-1&&i.replyMarkup?{reply_markup:i.replyMarkup}:{}},i);return o}async function Ki(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 Qi(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):Hi(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&&Yi(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:Vi(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&&Yi(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?Vi(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||!Yi(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function Yi(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function Xi(e,t){if("string"==typeof e&&e.trim())return e.trim();ea(`Chat action token is missing ${t}.`)}function Zi(e){if("allow"===e||"deny"===e||"skip"===e)return e;ea("Chat action token has an invalid decision.")}function ea(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}function ta(e){return Buffer.from(e,"base64url").toString("utf8")}function sa(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function na(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function ia(e){return`dtv1.card//IM_GROUP.${e}`}function aa(e){return`dtv1.card//IM_ROBOT.${e}`}function ra(e,t){return wa(t.biz_conversation_id).trim()||wa(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function oa(e,t={}){const s=wa(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 da(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 ca(e,t,s,n,i){const a=ra(n,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:na({type:"actions",title:"TaskHandoff",description:e,list:Bn(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 la(e,t,s){const n=[];return pa(n,s),pa(n,t),pa(n,e),n.find(e=>da(e))||""}function pa(e,t){for(const s of["actionIdList","actionIds"]){const n=t[s];Array.isArray(n)&&e.push(...n.map(e=>wa(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=wa(t[s]);n&&e.push(n)}}async function ua(e,t,s,n){n?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:ya(s)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:sa(s),text:s}})}),a=await i.json().catch(()=>{});if(n?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:fa(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}async function ga(e){const t=Bn(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await ua(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const s=e.bridge.settings||{},n=wa(s.robotCode),i=wa(s.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=wa(s.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||wa(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 ua(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=na({type:"actions",title:e.title||sa(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:aa(s),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:n,spaceType:"IM_ROBOT"}}:{openSpaceId:ia(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:ya(e.text)},"dingding card send requested"),await ha(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 ha(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=wa(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:ma(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:fa(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=ba(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error(wa(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=ba(ba(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===ba(e).success);if(!s)return"";const n=ba(s),i=wa(n.spaceId),a=wa(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 ma(e){const t=ba(e),s=ba(t.cardData),n=ba(s.cardParamMap);return{openSpaceId:wa(t.openSpaceId),outTrackId:wa(t.outTrackId),userId:wa(t.userId),userIdType:t.userIdType,callbackType:wa(t.callbackType),callbackRouteKey:wa(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:Ia(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:Ia(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:Ia(t.imRobotOpenDeliverModel),bizConversationId:wa(n.biz_conversation_id),bizConversationType:wa(n.biz_conversation_type),bizSenderId:wa(n.biz_sender_id),hasBizSessionWebhook:Boolean(wa(n.biz_session_webhook))}}function Ia(e){const t=ba(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean(wa(t.robotCode)),spaceType:wa(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 fa(e){const t=ba(e),s=ba(t.result);return{code:t.code??t.errcode,message:ya(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(s).length?s:void 0}}function ya(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 ba(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function va(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?Sa(e):{}}function wa(e){return"string"==typeof e?e:""}function Sa(e){try{return ba(JSON.parse(String(e||"{}")))}catch{return{}}}function Aa(e){return JSON.stringify(Bn(e))}function ka(e){return e instanceof Error?e.message:String(e)}function Ta(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}function Ea(e){return Array.isArray(e?.actionRows)?e.actionRows:(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e])}function Ca(e,t){const s=Ea(e),n=Ea(t);return s.length===n.length&&s.every((e,t)=>{const s=n[t];return e.length===s.length&&e.every((e,t)=>{const n=s[t];return e.text===n?.text&&e.callbackData===n.callbackData})})}class Na{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||Ta,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,lastOptions: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&&Ca(i.lastOptions,o))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.lastOptions=o,i.lastUpdateAt=Date.now(),i.rateLimitedUntil=void 0,!0)}const s={lastText:t,lastUpdateAt:Date.now(),route:r,options:o,lastOptions: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: ${ka(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&&Ca(a.lastOptions,n||a.options)&&!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{if(clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,a.pending&&await a.pending,this.entries.delete(e),!a.messageId)return!1;const r=n||a.options;return a.lastText===t&&Ca(a.lastOptions,r)||await this.edit(a.messageId,t,s||a.route,r,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: ${ka(t)}`),!1}}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: ${ka(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 za=20971520;function _a(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 Oa(e,t,n="telegram-image",i){if(i&&i>za)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>za)throw new Error(`image is too large: ${d} bytes`);const c=s.extname(n)||".jpg";let l=c;try{l=s.extname(new URL(r).pathname)||c}catch{l=c}const p=s.join(f.tmpdir(),"task-handoff-images");await I.mkdir(p,{recursive:!0});const u=l.replace(/[^.\w-]/g,"")||".jpg",g=s.join(p,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${u}`),h=Buffer.from(await o.arrayBuffer());if(h.length>za)throw new Error(`image is too large: ${h.length} bytes`);return await I.writeFile(g,h),g}class Ra{store;constructor(e){this.store=new Na({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",Wi(t),{rawMarkdownV2:!0,replyMarkup:$a(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,Wi(s),{rawMarkdownV2:!0,replyMarkup:$a(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})}}async function xa(e){return(await async function(e,t,s,n){const i=[];for(const a of _a(s))try{const s=await Oa({getFileLink:s=>Ki(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_${d.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:s.basename(e)||`telegram-image-${n+1}`,mime:ja(e),size:i.length,data:i.toString("base64")}})}function ja(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 Da(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Pa(e){return e&&"object"==typeof e?e:{}}function Ma(e){return"string"==typeof e?e:""}function $a(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return qn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function qa(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=La(e);if(1!==t.type)continue;const s=Ua(La(t.text_item).text).trim();if(s)return s}return""}function La(e){return e&&"object"==typeof e?e:{}}function Ua(e){return"string"==typeof e?e:""}function Ba(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?function(e,t){return{bridge:t,send:async(s,n,i={})=>{const a=await Ji(e,t,s,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Pa(a).message_id)||void 0}}}}(t,s):"dingding"===s.channel?function(e,t,s){return{bridge:t,send:async(n,i,a={})=>{const r=a.sessionWebhook||s?.chatWebhooks.get(n)||wa(t.settings.sessionWebhook);if(a.replyMarkup&&s){const o=await ga({fetchImpl:e,bridge:t,runtime:s,chatId:n,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||s.senderIds.get(n)||wa(t.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await ua(e,r,i,s?.onLog),{provider:"dingding"}}}}(t,s,n):"wechat"===s.channel?function(e,t){return{bridge:t,send:async(s,n,i={})=>{const a=i.contextToken||Ua(t.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=Ua(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:d.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"}})}),o=await r.json().catch(()=>({}));if(!r.ok||o.errcode)throw new Error(Ua(o.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(e,t,s,a,n),{provider:"wechat"}}}}(t,s):{bridge:s,send:async()=>{}}}class Fa{entries=new Map;fetchImpl;generations=new Map;constructor(e){this.fetchImpl=e}clear(){for(const e of new Set([...this.generations.keys(),...[...this.entries.values()].map(e=>e.bridgeId)]))this.clearBridge(e)}clearBridge(e){this.generations.set(e,this.generation(e)+1),this.clearEntries(t=>t.bridgeId===e)}generation(e){return this.generations.get(e)||0}isCurrent(e,t){return this.generation(e)===t}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,t){const s=this.generation(e.bridge.id),n=Aa(e.replyMarkup);let i=this.entries.get(e.key);if(i?.pending&&await i.pending,!this.isCurrent(e.bridge.id,s))return!1;if(i=this.entries.get(e.key),i?.outTrackId){if(i.lastText===e.text&&i.lastActionsFingerprint===n)return!0;const a=Date.now()-(i.lastUpdateAt||0);return a<1e3?(i.pendingText=e.text,i.pendingReplyMarkup=e.replyMarkup,i.timer||(i.pending=new Promise((n,r)=>{i.cancelPending=r,i.timer=setTimeout(()=>{i.timer=void 0,i.cancelPending=void 0;const a=i.pendingText,o=i.pendingReplyMarkup;if(i.pendingText=void 0,i.pendingReplyMarkup=void 0,!a)return n(void 0);this.updateExistingEntry(e,t,i,a,o,Aa(o),s).then(n,r)},1e3-a)}).finally(()=>{i.pending=void 0,i.timer=void 0,i.cancelPending=void 0})),await i.pending,this.isCurrent(e.bridge.id,s)&&this.entries.has(e.key)):(await this.updateExistingEntry(e,t,i,e.text,e.replyMarkup,n,s),this.isCurrent(e.bridge.id,s))}const a={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:n,lastUpdateAt:Date.now()};return a.pending=ga({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||Ha(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||Ha(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(t=>{this.isCurrent(e.bridge.id,s)&&(t?.outTrackId&&(a.outTrackId=t.outTrackId),t?.delivered&&(a.delivered=!0,a.lastUpdateAt=Date.now()))}).catch(t=>{throw this.isCurrent(e.bridge.id,s)&&this.entries.delete(e.key),t}).finally(()=>{a.pending=void 0}),this.entries.set(e.key,a),await a.pending,this.isCurrent(e.bridge.id,s)&&Boolean(a.delivered)}async updateExistingEntry(e,t,s,n,i,a,r){s.outTrackId&&(await async function(e){await ha(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:na({type:"actions",title:e.title,description:e.text,list:Bn(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:t,outTrackId:s.outTrackId,text:n,replyMarkup:i,title:"TaskHandoff 执行中",step:"progress"}),this.isCurrent(e.bridge.id,r)&&(s.lastText=n,s.lastActionsFingerprint=a,s.lastUpdateAt=Date.now()))}}function Ha(e){return"string"==typeof e?e:""}function Ga(e){const t=new y.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1,autoReconnect:!1});let s,n=!1;return{async connect(){n=!1,await t.getEndpoint(),await t._connect();const e=t.socket;e?.once("close",()=>{n||s?.(new Error("DingDing stream connection closed."))})},disconnect(){n=!0,t.disconnect()},onDisconnect(e){s=e},getConfig:()=>t.getConfig(),registerCallbackListener(e,s){t.registerCallbackListener(e,s)},socketCallBackResponse(e,s){t.socketCallBackResponse(e,s)}}}class Wa{runtimes=new Map;connected=new Set;reconnectTimers=new Map;progress;options;createClient;constructor(e){this.options=e,this.createClient=e.createClient||Ga,this.progress=new Fa(e.fetchImpl)}has(e){return this.runtimes.has(e)}isRunning(e){return this.connected.has(e)}get(e){return this.runtimes.get(e)}ids(){return this.runtimes.keys()}start(e){const t=(s=e.settings?.clientSecret,"string"==typeof s?s:"");var s;if(!e.token||!t)return this.options.onError(e.id,"DingDing client id/secret is not configured."),!1;const n={client:this.createClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,s,n)=>{this.options.logger["warn"===t?"warn":"info"]({bridgeId:e.id,...s},n)}};return n.client.onDisconnect?.(t=>{this.runtimes.get(e.id)===n&&this.handleConnectionFailure(e,n,t)}),n.client.registerCallbackListener(y.TOPIC_ROBOT,t=>{this.options.onRobotMessage(e,n,t).catch(t=>{this.options.onError(e.id,t)})}),n.client.registerCallbackListener(y.TOPIC_CARD,t=>{const s=t;this.options.onCardCallback(e,n,s).then(e=>n.client.socketCallBackResponse(s.headers.messageId,e||{})).catch(t=>{this.options.onError(e.id,t),n.client.socketCallBackResponse(s.headers.messageId,{})})}),this.options.clearError(e.id),this.runtimes.set(e.id,n),this.connect(e,n),!0}connect(e,t){this.reconnectTimers.delete(e.id),this.connected.add(e.id),t.client.connect().then(()=>{this.runtimes.get(e.id)===t&&(this.connected.add(e.id),this.options.clearError(e.id))}).catch(s=>{this.runtimes.get(e.id)===t&&this.handleConnectionFailure(e,t,s)})}handleConnectionFailure(e,t,s){if(this.connected.delete(e.id),this.progress.clearBridge(e.id),this.options.onError(e.id,s||"DingDing stream connection failed."),this.reconnectTimers.has(e.id))return;const n=setTimeout(()=>{this.runtimes.get(e.id)===t&&this.connect(e,t)},this.options.reconnectDelayMs??1e3);n.unref?.(),this.reconnectTimers.set(e.id,n)}stop(e){const t=this.reconnectTimers.get(e);t&&clearTimeout(t),this.reconnectTimers.delete(e),this.connected.delete(e);const s=this.runtimes.get(e);if(s){try{s.client.disconnect()}catch{}this.runtimes.delete(e)}this.progress.clearBridge(e)}stopAll(){for(const e of[...this.runtimes.keys()])this.stop(e);this.progress.clear()}applyUpdate(e){const t=this.runtimes.get(e.bridge.id);return t?this.progress.applyUpdate(e,t):Promise.resolve(!1)}applyProgressUpdate(e){return this.applyUpdate(e)}}class Va{service;fetchImpl;logger;bridgeTimers=new Map;dingdingBridges;bridgeErrors=new Map;telegramOffsets=new Map;seenTelegramUpdates=new Set;wechatCursors=new Map;announcedPending=new Set;deliveredAiSessionFingerprints=new Map;aiSessionCancelTokens=new Map;aiSessionQueueActionTokens=new Map;telegramMessageAggregates=new Map;telegramMessageAggregateEndTokens=new Map;telegramProgressMessageTargets=new Map;aiSessionInstanceNamesCache;aiSessionInstanceNamesPending;aiSessionPendingRoutesCache;aiSessionPendingRoutesPending;telegramProgress;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 Ra({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.dingdingBridges=new Wa({fetchImpl:this.fetchImpl,createClient:s.createDingdingClient,logger:{info:(e,t)=>this.logInfo(e,t),warn:(e,t)=>this.logWarn(e,t)},onRobotMessage:(e,t,s)=>this.handleDingdingRobotMessage(e,t,s),onCardCallback:(e,t,s)=>this.handleDingdingCardCallback(e,t,s),onError:(e,t)=>this.bridgeErrors.set(e,nr(t)),clearError:e=>this.bridgeErrors.delete(e)}),this.stopAiSessionListener=s.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:nr(t)},"chat ai session snapshot delivery failed")})})}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)}startBridge(e){if(this.bridgeTimers.has(e)||this.dingdingBridges.has(e))return this.logAiSessionDelivery({stage:"bridge-start-skipped-running",bridgeId:e}),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);return t&&(clearInterval(t),this.bridgeTimers.delete(e)),this.dingdingBridges.stop(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.ids()]))this.stopBridge(e);this.dingdingBridges.stopAll();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.isRunning(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}}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.status()}startDingdingBridge(e){return this.dingdingBridges.start(e),this.status()}async handleDingdingRobotMessage(e,t,s){const n=function(e){const t=Sa(e),s=wa(t.conversationId),n=wa(ba(t.text).content).trim();if(!s||!n)return;const i=wa(t.senderStaffId||t.senderId);return{chatId:s,userId:i||void 0,senderId:i||void 0,sessionWebhook:wa(t.sessionWebhook)||void 0,conversationType:oa(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)||sr(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:ar(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 ua(this.fetchImpl,d,`Failed to handle message: ${nr(e)}`,t.onLog);throw e}const l=Ja(c);if(l){const s=Qa(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=Ba({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===dr(c).routed,hasReplyMarkup:Boolean(s),replyPreview:ar(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(sr(e.settings.robotCode)),replyPreview:ar(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,s,n,i,a={}){if(!0!==e.routed)return!1;const r=pr(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=yr(o,d,c,t.id,n);return!!await this.dingdingBridges.applyProgressUpdate({bridge:t,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:ar(a.sourcePrompt,80),textPreview:ar(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=Sa(e),s=va(t.cardActionData||t.content),n=va(s.cardPrivateData||t.cardPrivateData||t.content),i=va(n.params||s.params||t.params),a=wa(t.userId),r=wa(i.biz_sender_id);return{userId:a||void 0,chatId:ra(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:wa(i.biz_session_webhook)||void 0,conversationType:oa(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:da(la(t,s,n)),body:t,params:i}}(s.data);if(!this.dingdingAllowed(e,n.userId))return ca("not authorized",void 0,"unauthorized",n.body,n.params);const i=n.chatId||sr(e.defaultChatId),a=n.deliverySenderId||n.senderId,r=n.sessionWebhook;let o;i&&r&&t.chatWebhooks.set(i,r),i&&n.conversationType&&t.conversationTypes.set(i,n.conversationType),i&&a&&t.senderIds.set(i,a);try{o=await this.parseChatGatewayCallbackAction(n.callbackData)}catch(e){if(ir(e,"CHAT_PENDING_ACTION_STALE"))return ca(nr(e),void 0,"approval_stale",n.body,n.params);throw e}if(!o)return this.logWarn({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,...rr(n)},"dingding card callback action unsupported"),ca("Unsupported action",void 0,"unsupported",n.body,n.params);this.logInfo({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,actionType:o.type,...rr(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&&Ka(d)?ca(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):ca(Ja(d)||sr(d.message)||"updated",Qa(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||sr(t.updatesBuf),n=await async function(e){const t=Ua((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(Ua(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=La(e);if(1!==t.message_type)continue;const s=Ua(t.from_user_id),n=qa(t);s&&n&&a.push({chatId:s,text:n,contextToken:Ua(t.context_token)})}return{cursor:Ua(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=Ja(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=Ba({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||sr(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=ur(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 Ba({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=Pa(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Pa(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Pa(r.chat),d=Pa(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=Ma(r.text??r.caption),u=_a(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:Da(r),replyToMessageId:Da(Pa(r.reply_to_message)),quoteText:Ma(Pa(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=dr(s.message),i=dr(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,nr(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${nr(t)}`).catch(t=>{this.bridgeErrors.set(e.id,nr(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await xa({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:nr(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:ar(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=Za(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:qn([[{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:qn([[{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!==Za(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=d.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=Ja(d),l=Qa(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(dr(s).message_id)||void 0,routed:!0===dr(d).routed,replyPreview:ar(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,nr(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${nr(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,nr(t))})}}async handleTelegramCallback(e,t){const s=sr(t.data),n=dr(t.from),i=dr(t.message),a=dr(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,sr(t.id),s.instanceId,s.sessionId,o,Xa(i)):void await this.answerTelegramCallback(e,sr(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,sr(t.id),s.instanceId,s.sessionId,s.queueId,o,Xa(i)):void await this.answerTelegramCallback(e,sr(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,sr(t.id),s.instanceId,s.sessionId,o,Xa(i)):void await this.answerTelegramCallback(e,sr(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,sr(t.id),Xa(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,sr(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,sr(t.id),u[1],o);let g;try{g=await this.parseChatGatewayCallbackAction(s)}catch(s){if(ir(s,"CHAT_PENDING_ACTION_STALE")){await this.answerTelegramCallback(e,sr(t.id),nr(s));const n=Xa(i);return void(void 0!==n&&await this.deleteTelegramStandaloneApprovalMessage(e,r,n))}throw s}if(!g)return void await this.answerTelegramCallback(e,sr(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&&Ka(h)){await this.answerTelegramCallback(e,sr(t.id),"");const s=Xa(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,sr(t.id),sr(h.message)||"updated");const m=Ja(h),I=Qa(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:nr(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,nr(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${ar(nr(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,nr(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${ar(nr(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=dr(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>dr(e)).filter(e=>"queued"===sr(e.status)&&sr(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,gr).map((e,t)=>[{text:fr(sr(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,sr(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:qn(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,nr(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${ar(nr(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:nr(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,nr(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${ar(nr(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)}async parseChatGatewayCallbackAction(e){const t=function(e){const t=e.match(/^task_handoff:cp_p:v1:([ads]):([A-Za-z0-9_-]{16})$/);if(!t)return;const s=Object.entries(zn).find(([,e])=>e===t[1])?.[0];return"allow"===s||"deny"===s||"skip"===s?{routeFingerprint:t[2],decision:s}:void 0}(e);if(t){const e=(await this.service.listPendingRoutes()).filter(e=>"approval"===e.kind&&"pending"===e.status&&_n(e.id)===t.routeFingerprint);if(1!==e.length){const e=new Error("This approval is no longer pending.");throw Object.assign(e,{statusCode:409,code:"CHAT_PENDING_ACTION_STALE"}),e}return{type:"pending-decision",routeId:e[0].id,decision:t.decision}}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:Xi(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:ta(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:Xi(e.instanceId,"instanceId"),appId:Xi(e.appId,"appId")}}return{type:"launch-app",instanceId:ta(i[1]),appId:ta(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:Xi(e.routeId,"routeId"),decision:Zi(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 Qi(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 Ji(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=pr(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(dr(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=yr(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set(er(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:ar(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(er(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:nr(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get(er(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=tr(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set(er(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(er(n.route.bridgeId,n.route.chatId,n.messageId||0))||tr(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(er(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=Ar(s.get(e)||e,a),o=Sr(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:or(Tr(a))});continue}const d=br(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:or(Tr(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=yr(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=vr(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:or(Tr(a)),textPreview:ar(o)});continue}const p=mr({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)||Ir(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:or(Tr(a)),textPreview:ar(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:nr(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:ar(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:ar(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:or(Tr(a)),textPreview:ar(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:ar(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=Ar(s.get(e)||e,r),c=Sr(r,d),l=br(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:yr(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=vr(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=mr({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)||Ir(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:ar(c)}),h&&(this.telegramProgressMessageTargets.set(er(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(er(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(er(n.route.bridgeId,n.route.chatId,n.messageId))||tr(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||Tr(t)?.id===e.owner.turnId||Tr(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){if(!this.dingdingBridges.get(i.id))return!1;const a=await this.dingdingBridges.applyProgressUpdate({bridge:i,key:s,chatId:e.chatSessionId,text:t,replyMarkup:Ya({actionRows:n})});return a&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:ar(t)},"dingding ai session progress updated"),a}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:ar(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:ar(t)},a?"telegram ai session progress edited":"telegram ai session progress sent"),!0}async aiSessionInstanceNames(){return this.aiSessionInstanceNamesCache&&this.aiSessionInstanceNamesCache.expiresAt>Date.now()?this.aiSessionInstanceNamesCache.value:(this.aiSessionInstanceNamesPending||(this.aiSessionInstanceNamesPending=(this.service.listAiSessionInstanceNames?.()||this.service.boardAsync()).catch(()=>[]).then(e=>{const t=new Map(e.map(e=>[e.id,e.name||e.id]));return this.aiSessionInstanceNamesCache={expiresAt:Date.now()+3e4,value:t},t}).finally(()=>{this.aiSessionInstanceNamesPending=void 0})),this.aiSessionInstanceNamesPending)}async aiSessionPendingApprovalRoutes(e){let t=this.aiSessionPendingRoutesCache?.expiresAt&&this.aiSessionPendingRoutesCache.expiresAt>Date.now()?this.aiSessionPendingRoutesCache.value:void 0;return t||(this.aiSessionPendingRoutesPending||=this.service.listPendingRoutes().catch(()=>[]).then(e=>(this.aiSessionPendingRoutesCache={expiresAt:Date.now()+1e3,value:e},e)).finally(()=>{this.aiSessionPendingRoutesPending=void 0}),t=await this.aiSessionPendingRoutesPending),new Map(t.filter(t=>t.instanceId===e&&t.aiSessionId&&"approval"===t.kind).map(e=>[e.aiSessionId||"",e]))}aiSessionCancelCallbackData(e,t){const s=d.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=d.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 Ja(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function Ka(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function Qa(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 Ya(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return qn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Xa(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Za(e,t,s){return[e,t,s||""].join(":")}function er(e,t,s){return[e,t,String(s)].join(":")}function tr(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 sr(e){return"string"==typeof e?e:""}function nr(e){return e instanceof Error?e.message:String(e)}function ir(e,t){return Boolean(e&&"object"==typeof e&&"code"in e&&e.code===t)}function ar(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 rr(e){const t=cr(e.body.cardActionData||e.body.content),s=cr(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:sr(e.body.outTrackId),spaceId:sr(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:ar(e.callbackData,120),actionIds:[...lr(s.actionIdList),...lr(t.actionIdList),sr(s.actionId),sr(t.actionId),sr(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 or(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:ar(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:ar(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function dr(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function cr(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return dr(JSON.parse(e))}catch{return{}}return{}}function lr(e){return Array.isArray(e)?e.map(e=>sr(e)).filter(Boolean):[]}function pr(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?br(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?Tr(s)?.id:void 0}}}function ur(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:qn([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const gr=5,hr=48;function mr(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,gr);return[...d.map((e,t)=>[{text:fr(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function Ir(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function fr(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,hr-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function yr(e,t,...s){return[e,t,...s].join(":")}function br(e){const t=Tr(e);return t?.id?t.id:sr(e.activeTurnId)}function vr(e,t=""){const s=Tr(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?.id||"",e.currentTool?.name||"",e.currentTool?.inputPreview||"",String(e.toolCallsSinceLastMessage??0),wr(e)].join("\0")}function wr(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function Sr(e,t){const s=function(e,t){let s="";t?"waiting"===t.status&&"approval"===t.phase&&t.summary?.trim()?s=t.summary.trim():t.lastMessage?.trim()?s=t.lastMessage.trim():t.summary?.trim()&&(s=t.summary.trim()):"waiting"===e.status&&"approval"===e.phase&&e.summary?.trim()?s=e.summary.trim():e.lastMessage?.trim()?s=e.lastMessage.trim():e.summary?.trim()?s=e.summary.trim():e.error?.trim()&&(s="failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim());const n=function(e){if(e.currentTool?.name)return`Thinking... · ${e.currentTool.name}${e.currentTool.inputPreview?` · ${e.currentTool.inputPreview}`:""}`;const t=e.toolCallsSinceLastMessage??0;return t>0?`Thinking... · ${t} ${1===t?"tool":"tools"} completed`:""}(e);return[s,n&&!kr(s,e)?n:""].filter(Boolean).join("\n\n")}(e,Tr(e));return s?`${t}\n${s}`:""}function Ar(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 kr(e,t){if("waiting"!==t.status||"approval"!==t.phase)return!1;const s=t.currentTool?.inputPreview?.trim();return Boolean(s&&e.includes(s))}function Tr(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}class Er{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&&Cr(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||Yt(e),createdAt:(new Date).toISOString(),payload:t,scope:s.scope}}}function Cr(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const Nr={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},zr=o.z.object({instanceId:o.z.string().trim().min(1).max(120),sessionId:o.z.string().trim().min(1).max(120),kind:o.z.literal("image").default("image"),name:o.z.string().trim().min(1).max(240),mime:o.z.string().trim().min(1).max(120),data:o.z.string().min(1).max(31457280)}).strict();class _r{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const n=zr.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=Nr[a];if(!r)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>v)throw new Error("Image must be between 1 byte and 20971520 bytes.");const o=`att_${d.randomUUID().replace(/-/g,"").slice(0,24)}`,c=new Date,l=new Date(c.getTime()+this.ttlMs),p=s.join(f.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(p,{recursive:!0});const u=function(e){return s.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(n.name),g=s.join(p,`${o}-${u.endsWith(r)?u:`${u}${r}`}`);t.writeFileSync(g,i.buffer);const h={id:o,instanceId:n.instanceId,sessionId:n.sessionId,kind:"image",name:u,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:g,createdAt:c.toISOString(),expiresAt:l.toISOString()};return this.items.set(o,h),this.publicAttachment(h)}resolveRefs(e,t,s){this.pruneExpired();const n=o.z.array(P).max(6).default([]).parse(e),i=n.map(e=>this.requireAttachment(e,t,s));if(i.reduce((e,t)=>e+t.size,0)>w)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 j.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 Or(e){return e.readyState===(e.OPEN??1)}function Rr(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 xr(e,t){e.send(t.data,{binary:t.isBinary})}function jr(e,t){if(Or(e))for(const s of t.splice(0))xr(e,s)}class Dr{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 Pr=o.z.object({nodeId:o.z.string().trim().min(1).max(120)}).strict();function Mr(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 $r(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const qr=new Map;function Lr(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)>zi)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()-zi;for(const[e,t]of qr)t<=i&&qr.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||qr.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=_i("");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 o=t.url||`/api/node-agent/tunnel?nodeId=${encodeURIComponent(e.id)}`,c=Oi(e.auth.secret,{keyId:s.keyId,method:"GET",pathWithQuery:o,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&&d.timingSafeEqual(s,n)}(s.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});qr.set(a,Date.now()+zi)}function Ur(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)])):{}}class Br{sockets=new Map;events;onStreamsHello;validateInstanceScope;validatedMetricScopes=new Map;constructor(e,t={}){this.events=e,this.onStreamsHello=t.onStreamsHello,this.validateInstanceScope=t.validateInstanceScope}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.");for(const t of s.httpStreams.values())clearTimeout(t.timer),t.body?.destroy(new Error(`Reverse tunnel for node ${e} was replaced.`)),t.settled||t.reject(new Error(`Reverse tunnel for node ${e} was replaced.`))}const n=new Map;this.sockets.set(e,{socket:t,pending:n,streams:new Map,httpStreams: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.");for(const t of s.httpStreams.values())clearTimeout(t.timer),t.body?.destroy(new Error(`Reverse tunnel for node ${e} disconnected.`)),t.settled||t.reject(new Error(`Reverse tunnel for node ${e} 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=es.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;if(i===_t){const t=zt.safeParse(a),s="string"==typeof r.instanceId?r.instanceId:"string"==typeof n.instanceId?n.instanceId:o;return!t.success||t.data.instanceId!==s||(this.publishValidatedMetrics(e,t.data,r),!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 publishValidatedMetrics(e,t,s){const n=`${e}:${t.instanceId}`,i=Date.now();let a=(this.validatedMetricScopes.get(n)||0)>i;if(!a){try{a=Boolean(await(this.validateInstanceScope?.(e,t.instanceId)))}catch{a=!1}a&&this.validatedMetricScopes.set(n,i+3e4)}a&&this.events?.publish(_t,t,{topic:"instances",scope:{...s,nodeId:e,instanceId:t.instanceId}})}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=$r(),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:Ur(s.headers),body:a}};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))})}async requestStream(e,t,s={}){const n=this.sockets.get(e.id);if(!n)throw Object.assign(new Error(`Reverse tunnel for node ${e.id} is not connected.`),{statusCode:503,code:"NODE_AGENT_REVERSE_TUNNEL_OFFLINE"});const i=$r(),a="string"==typeof s.body?s.body:void 0===s.body||null===s.body?void 0:String(s.body);return new Promise((r,o)=>{const d=setTimeout(()=>{n.httpStreams.delete(i),o(Object.assign(new Error(`Reverse tunnel stream to ${e.id}${t} timed out.`),{statusCode:504,code:"NODE_AGENT_REVERSE_STREAM_TIMEOUT"}))},3e4);n.httpStreams.set(i,{resolve:r,reject:o,timer:d,settled:!1}),n.socket.send(JSON.stringify({type:"control-plane.http.open",streamId:i,route:t,init:{method:s.method||"GET",headers:Ur(s.headers),body:a}}))})}attachHttpStream(e,t,s){const n=this.sockets.get(e),i=n?.httpStreams.get(t);if(!n||!i)return s.close(1008,"Unknown reverse HTTP stream."),!1;i.socket=s;const a=e=>{clearTimeout(i.timer),n.httpStreams.delete(t),i.settled?i.body?.destroy(e):(i.settled=!0,i.reject(e))};return s.on("message",(e,r)=>{if(r)return i.body?void(i.body.write(Buffer.isBuffer(e)?e:Buffer.from(e))||(s.pause?.(),i.body.once("drain",()=>s.resume?.()))):a(new Error("Reverse HTTP stream received data before response headers."));let o;try{o=JSON.parse(String(e))}catch{return void a(new Error("Reverse HTTP stream sent invalid JSON control data."))}if("node-agent.http.head"===o.type){if(i.settled)return;clearTimeout(i.timer);const e=Number(o.status)||502,a=o.headers&&"object"==typeof o.headers?o.headers:{},r=new h.PassThrough({highWaterMark:65536});return i.body=r,i.settled=!0,r.once("close",()=>{n.httpStreams.delete(t)&&1===s.readyState&&s.close(1e3,"HTTP consumer closed.")}),void i.resolve(new Response(h.Readable.toWeb(r),{status:e,headers:a}))}if("node-agent.http.end"===o.type)return n.httpStreams.delete(t),i.body?.end(),void(i.settled||a(new Error("Reverse HTTP stream ended before response headers.")));"node-agent.http.error"===o.type&&a(new Error("string"==typeof o.message?o.message:"Reverse HTTP stream failed."))}),s.on("close",()=>{n.httpStreams.has(t)&&a(new Error("Reverse HTTP stream disconnected."))}),s.on("error",()=>a(new Error("Reverse HTTP stream failed."))),!0}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=$r(),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=Or(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return Rr(e,1011,"WebSocket bridge pending frame limit exceeded."),void Rr(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(Rr(e,1011,"Upstream websocket did not open."),Rr(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>jr(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),jr(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};Or(t)?xr(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};Or(e)?xr(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&Rr(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&Rr(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),Rr(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),Rr(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 Fr{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=xi(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 Ri({nodeId:e.id,keyId:Mr(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?Di(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 Dr,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 Dr};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(xi(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}}}}function Hr(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||s.join(f.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")}}class Gr extends Error{code;lockPath;owner;constructor(e,t,s={}){const n=t?.pid?` pid ${t.pid}`:"";super(`${s.label||"Process"} is already running${n}. Lock: ${e}`),this.name="ProcessSingletonError",this.code=s.code||"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function Wr(e){return s.join(e,"owner.json")}function Vr(e){return s.join(e,"initializing.json")}function Jr(e){return s.join(e,"recovering.json")}function Kr(e){try{const s=JSON.parse(t.readFileSync(Wr(e),"utf8"));if(!Number.isInteger(s.pid)||Number(s.pid)<=0||"string"!=typeof s.hostname||!s.hostname||"string"!=typeof s.command||"string"!=typeof s.acquiredAt||!Number.isFinite(Date.parse(s.acquiredAt))||"string"!=typeof s.token||!s.token)return;return s}catch{return}}function Qr(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 Yr(e,n){const i=s.join(e,`.owner-${n.token}.tmp`);t.writeFileSync(i,`${JSON.stringify(n,null,2)}\n`,{encoding:"utf8",mode:384,flag:"wx"});const a=Xr(e);if(a?.token!==n.token)throw Zr(i),new Error(`Process lock initialization was superseded: ${e}`);t.renameSync(i,Wr(e)),Zr(Vr(e))}function Xr(e){try{return JSON.parse(t.readFileSync(Vr(e),"utf8"))}catch{return}}function Zr(e){try{t.unlinkSync(e)}catch(e){if("ENOENT"!==e.code)throw e}}function eo(){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,20)}function to(e){try{const s=Xr(e),n=Date.parse(s?.startedAt||"");return Number.isFinite(n)?Math.max(0,Date.now()-n):Math.max(0,Date.now()-t.statSync(e).mtimeMs)}catch{return 0}}function so(e,n){try{return t.writeFileSync(Jr(e),`${JSON.stringify({token:n,pid:process.pid,startedAt:(new Date).toISOString()})}\n`,{encoding:"utf8",mode:384,flag:"wx"}),!0}catch(i){if("EEXIST"===i.code)return!!function(e){const n=Jr(e),i=no(n),a=Date.parse(i?.startedAt||"");if(Qr(i?.pid)||Number.isFinite(a)&&Date.now()-a<2e3)return!1;const r=s.join(e,`.recovering-${d.randomUUID()}.stale`);try{t.renameSync(n,r)}catch(e){if(["ENOENT","EEXIST"].includes(e.code||""))return!1;throw e}const o=no(r);if(o?.token===i?.token)return Zr(r),!0;try{t.linkSync(r,n)}catch(e){if("EEXIST"!==e.code)throw e}finally{Zr(r)}return!1}(e)&&so(e,n);if("ENOENT"===i.code)return!1;throw i}}function no(e){try{return JSON.parse(t.readFileSync(e,"utf8"))}catch{return}}function io(e,t){return no(Jr(e))?.token===t}function ao(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 ro=o.z.string().trim().min(1),oo=o.z.object({id:ro}).strict(),co=o.z.object({id:ro,keyId:ro}).strict(),lo=o.z.object({id:ro,runtimeId:ro}).strict(),po=o.z.object({id:ro,folderId:ro}).strict(),uo=o.z.object({id:ro,sessionId:ro}).strict(),go=o.z.object({id:ro,sessionId:ro,queueId:ro}).strict(),ho=o.z.object({configHash:ro}).strict(),mo=o.z.object({id:ro,configHash:ro}).strict(),Io=o.z.object({id:ro,sessionId:ro,configHash:ro}).strict(),fo=o.z.object({path:o.z.string().optional(),depth:o.z.coerce.number().int().min(0).max(32).optional()}).strict(),yo=o.z.object({ids:o.z.array(o.z.string().trim().min(1).max(120)).default([])}).strict(),bo=o.z.object({nodeId:o.z.string().trim().min(1).max(120),modelId:o.z.string().trim().min(1).max(120).optional()}).strict(),vo=o.z.object({source:o.z.object({channel:Wt,bridgeId:o.z.string().trim().min(1).max(120).optional(),chatSessionId:o.z.string().trim().min(1).max(240),userId:o.z.string().trim().max(240).optional()}).strict(),action:o.z.discriminatedUnion("type",[o.z.object({type:o.z.literal("ai-session"),index:o.z.number().int().nonnegative()}).strict(),o.z.object({type:o.z.literal("instance-app-menu"),instanceId:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("launch-app"),instanceId:o.z.string().trim().min(1).max(120),appId:o.z.string().trim().min(1).max(120)}).strict(),o.z.object({type:o.z.literal("pending-decision"),routeId:o.z.string().trim().min(1).max(120),decision:o.z.enum(["allow","deny","skip"])}).strict()])}).strict();function wo(e){const t=vo.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const So=o.z.object({id:o.z.string().trim().min(1),direction:o.z.enum(["import","export"]),preset:o.z.string().trim().min(1).max(120)}),Ao=o.z.object({id:o.z.string().trim().min(1),appId:o.z.string().trim().min(1)}).strict(),ko=o.z.object({id:o.z.string().trim().min(1),jobId:o.z.string().trim().min(1)}).strict();const To=o.z.object({appId:o.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:o.z.string().trim().min(1).max(120).optional(),options:o.z.record(o.z.string(),o.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),Eo=o.z.object({title:o.z.string().trim().min(1).max(120)}).strict();function Co(e){const{app:t,service:s,events:n,appSessionAggregator:i,aiSessionAggregator:a,chatGateway:r,aiSessionAttachments:d,nodeAgentTunnel:c,nodeEventSubscriber:l,errorPayload:p}=e;!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(oo.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(oo.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=oo.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:await t.listFederatedModels()})),e.post("/api/models",async(e,n)=>{const i=await 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=yo.parse(e.body),i=await t.reorderModels(n.ids);return s.publish("model.reordered",{ids:n.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(oo.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=await t.updateModel(oo.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=oo.parse(e.params).id,i=await t.deleteModel(n);return s.publish("model.deleted",{modelId:n,deleted:i}),{data:{deleted:i}}}),e.post("/api/nodes/:nodeId/models",async(e,n)=>{const{nodeId:i}=bo.parse(e.params),a=await t.createNodeModel(i,e.body);return s.publish("model.created",{modelId:a.id,nodeId:i}),n.code(201).send({data:a})}),e.patch("/api/nodes/:nodeId/models/:modelId",async e=>{const{nodeId:n,modelId:i}=bo.parse(e.params),a=await t.updateNodeModel(n,i,e.body);return s.publish("model.updated",{modelId:i,nodeId:n}),{data:a}}),e.delete("/api/nodes/:nodeId/models/:modelId",async e=>{const{nodeId:n,modelId:i}=bo.parse(e.params),a=await t.deleteNodeModel(n,i);return s.publish("model.deleted",{modelId:i,nodeId:n,deleted:a.deleted}),{data:a}}),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(oo.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(oo.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=oo.parse(e.params).id,i=await t.deleteImage(n);return s.publish("image.deleted",{imageId:n,deleted:i}),{data:{deleted:i}}})}({app:t,service:s,events:n}),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(oo.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(oo.parse(e.params).id)})),e.get("/api/nodes/:id/settings/external-listener",async e=>({data:await t.getLocalNodeExternalListener(oo.parse(e.params).id)})),e.patch("/api/nodes/:id/settings/external-listener",async e=>{const n=oo.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=oo.parse(e.params).id,i=await t.checkNodeUpdate(n,nt.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=oo.parse(e.params).id,a=await t.applyNodeUpdate(i,nt.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=oo.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=oo.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=oo.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=co.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(oo.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=oo.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=lo.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=lo.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=lo.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(oo.parse(e.params).id)})),e.get("/api/nodes/:id/images/catalog",async e=>({data:await t.listNodeImageAvailability(oo.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(oo.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=oo.parse(e.params),n=fo.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=oo.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=po.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(oo.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(oo.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=oo.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=Pr.parse(t.query),a=s.requireNode(i.nodeId);Lr(a,t),n.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:be,nodeId:a.id,serverTime:(new Date).toISOString(),capabilities:{reverseTunnel:"request-response",httpResponseStreaming:!0,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=Pr.parse(t.query),a=s.requireNode(i.nodeId);Lr(a,t);const r=t.params.streamId;n.attachWebSocketStream(a.id,r,e)}catch(t){const s=i(t);e.close(1008,s.message)}}),t.get("/api/node-agent/tunnel/http-streams/:streamId",{websocket:!0},(e,t)=>{try{const i=Pr.parse(t.query),a=s.requireNode(i.nodeId);Lr(a,t);const r=t.params.streamId;n.attachHttpStream(a.id,r,e)}catch(t){const s=i(t);e.close(1008,s.message)}})}({app:e,service:t,nodeAgentTunnel:n,errorPayload:a})}({app:t,service:s,events:n,nodeAgentTunnel:c,nodeEventSubscriber:l,errorPayload:p}),function({app:e,service:t,events:s}){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(oo.parse(e.params).id)})),e.get("/api/controlled-instances/:id/metrics",async e=>({data:await t.instanceResourceMetrics(oo.parse(e.params).id)})),e.get("/api/controlled-instances/:id/apps/management",async e=>({data:await t.instanceAppManagement(oo.parse(e.params).id)})),e.post("/api/controlled-instances/:id/apps/:appId/install",async e=>{const s=Ao.parse(e.params),n=Xe.parse(e.body||{});return{data:await t.requestInstanceAppOperation(s.id,s.appId,"install",n)}}),e.post("/api/controlled-instances/:id/apps/:appId/uninstall",async e=>{const s=Ao.parse(e.params),n=Xe.parse(e.body||{});return{data:await t.requestInstanceAppOperation(s.id,s.appId,"uninstall",n)}}),e.get("/api/controlled-instances/:id/apps/jobs/:jobId",async e=>{const s=ko.parse(e.params);return{data:await t.instanceAppManagementJob(s.id,s.jobId)}}),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(oo.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=oo.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(oo.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(oo.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(oo.parse(e.params).id);return s.publish("instance.restarted",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/image-provisioning/retry",async e=>{const n=await t.retryControlledInstanceImageProvisioning(oo.parse(e.params).id);return s.publish("instance.image-provisioning-retried",{instanceId:n.id}),{data:n}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const n=So.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}}})}({app:t,service:s,events:n}),function({app:e,service:t,events:s,appSessionAggregator:n,aiSessionAggregator:i,aiSessionAttachments:a}){e.get("/api/app-sessions",async e=>{const t=o.z.object({refresh:o.z.string().optional(),includeTombstones:o.z.string().optional(),instanceId:o.z.string().trim().min(1).optional(),streamId:o.z.string().trim().min(1).optional(),sinceRevision:o.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.post("/api/controlled-instances/:id/apps/sessions",async e=>{const n=oo.parse(e.params),i=To.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=uo.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=uo.parse(e.params),i=Eo.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=oo.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=uo.parse(e.params),i=q.parse(e.body||{}),r=a.resolveRefs(i.attachments,n.id,n.sessionId),o=await t.sendAiSessionMessage(n.id,n.sessionId,i.message,i.mode,r);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:o}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const s=uo.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=go.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=go.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=go.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=uo.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=uo.parse(e.params),i=L.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/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})}})}({app:t,service:s,events:n,appSessionAggregator:i,aiSessionAggregator:a,aiSessionAttachments:d}),function({app:e,service:t,events:s}){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=ho.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=ho.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=oo.parse(e.params);return{data:await t.listInstanceTriggers(s.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const n=uo.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=Io.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=mo.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}})}({app:t,service:s,events:n}),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(oo.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=oo.parse(n.params).id,a=t.requireChatBridge(i),r=Dn.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=oo.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=oo.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(oo.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",{bodyLimit:67108864},async e=>({data:await t.handleChatGatewayMessage(Kt.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(wo(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:t,service:s,chatGateway:r})}const No=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),zo=new Set(["content-encoding"]);function _o(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!No.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function Oo(e){return Object.fromEntries(Object.entries(_o(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function Ro(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 xo(e,t,s){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();No.has(t)||zo.has(t)||e.header(s,n)}if(!t.body)return e.code(t.status).send();const n=h.Readable.fromWeb(t.body);return e.raw.once("close",()=>n.destroy()),e.code(t.status).send(s?n.pipe(s):n)}function jo(e){return`/instances/${encodeURIComponent(e)}`}function Do(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]||"")?function(e){const t=[];let s=0,n=!1;const i=(i,a=!1)=>{if(n)return;const r=Buffer.concat(t,s).toString("utf8");!/<head(\s[^>]*)?>/i.exec(r)&&!a&&s<65536||(n=!0,i.push(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}`}(r,e)),t.length=0,s=0)};return new h.Transform({transform(e,a,r){n?this.push(e):(t.push(Buffer.from(e)),s+=e.length,i(this)),r()},flush(e){i(this,!0),e()}})}(jo(s)):void 0;return xo(e,t,i)}function Po(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 Mo(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 $o(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function qo(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 Lo{snapshots=new Map;history=new Map;advertisedStreams=new Map;recoveries=new Map;listeners=new Set;messageDeltaBuffers=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){if("ai-session.message-delta"===e.type){const t=ne.safeParse(e.payload);return t.success?(this.applyMessageDelta(t.data),!0):(this.logger?.warn?.({eventType:e.type,issues:t.error.issues,errorCode:"AI_SESSION_MESSAGE_DELTA_INVALID"},"ai-session.aggregator.message-delta.invalid"),!0)}const t=e.type===S?ee:e.type===A?te:e.type===k?se:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===S&&this.applySnapshot(s.data),e.type===A&&this.applyPatch(s.data),e.type===k&&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)}applyMessageDelta(e){const t=this.snapshots.get(e.instanceId);if(!t)return!1;const s=[e.instanceId,e.sessionId,e.turnId||"",e.itemId||""].join("\0"),n=`${this.messageDeltaBuffers.get(s)||""}${e.delta}`;this.messageDeltaBuffers.set(s,n);let i=!1;return t.snapshot.sessions.map(t=>{if(t.id!==e.sessionId)return t;i=!0;const s=[...t.turns||[]],a=e.turnId?s.findIndex(t=>t.id===e.turnId||t.providerTurnId===e.turnId):s.length-1;return a>=0&&(s[a]={...s[a],lastMessage:n,summary:n,phase:"responding",updatedAt:e.generatedAt}),{...t,lastMessage:n,summary:n,phase:"responding",updatedAt:e.generatedAt,turns:s}}),!!i}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:S,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);const t=`${e}\0`;for(const e of this.messageDeltaBuffers.keys())e.startsWith(t)&&this.messageDeltaBuffers.delete(e)}applyPatch(e){return this.apply({type:A,payload:e})}applyRemoved(e){return this.apply({type:k,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 ie.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return ie.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 ie.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===S&&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===S)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:K.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===A){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=J.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:K.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);for(const e of o.snapshot.sessions){if("running"===e.status)continue;const t=`${s.instanceId}\0${e.id}\0`;for(const e of this.messageDeltaBuffers.keys())e.startsWith(t)&&this.messageDeltaBuffers.delete(e)}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:S,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:S,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 Uo{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===Ts?Ds:e.type===Es?Ps:e.type===Cs?Ms:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===Ts&&this.applySnapshot(s.data),e.type===Es&&this.applyPatch(s.data),e.type===Cs&&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:Ts,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:Es,payload:e})}applyRemoved(e){return this.apply({type:Cs,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 $s.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return $s.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 $s.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===Ts&&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===Ts)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:Rs.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===Es){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=qs(n,s.generatedAt);return{kind:"applied",projection:Rs.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:Ts,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:Ts,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 qs(e.sessions.filter(e=>Wn("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 Bo(e){return e.raw[0].replace(/\\\$\{/g,"${")}function Fo(e){const t=process.env[e]?.trim();return t||void 0}function Ho(){try{const e=("undefined"==typeof document?require("url").pathToFileURL(__filename).href:b&&"SCRIPT"===b.tagName.toUpperCase()&&b.src||new URL("cli.js",document.baseURI).href)?s.dirname(n.fileURLToPath("undefined"==typeof document?require("url").pathToFileURL(__filename).href:b&&"SCRIPT"===b.tagName.toUpperCase()&&b.src||new URL("cli.js",document.baseURI).href)):__dirname,i=[s.resolve(e,"..","package.json"),s.resolve(e,"..","..","..","package.json")].find(e=>t.existsSync(e));return i&&JSON.parse(t.readFileSync(i,"utf8")).version||"unknown"}catch{return"unknown"}}function Go(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:Ho(),protocolVersion:be,buildId:Fo("TASK_HANDOFF_BUILD_ID"),builtAt:Fo("TASK_HANDOFF_BUILT_AT"),gitCommit:Fo("TASK_HANDOFF_GIT_COMMIT"),imageRef:Fo("TASK_HANDOFF_IMAGE_REF"),imageDigest:Fo("TASK_HANDOFF_IMAGE_DIGEST")}}const Wo={".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 Vo(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 Jo(e,n,i){const a=Vo(n,i);return a?(e.header("cache-control","no-store"),e.type(Wo[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 Ko(e){if(e instanceof o.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),...t.details&&"object"==typeof t.details&&!Array.isArray(t.details)?{details:t.details}:{}}}async function Qo(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 Yo(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 Xo(e={}){const n=Hr(e.dataDir),o=i({logger:e.logger??!0}),d=new Fi(n,{...e.service,logger:o.log}),c=new ks(n,e.auth),l=new Er,p=new Lo({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 Uo({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 _r,h=new Br(l,{validateInstanceScope:(e,t)=>d.nodeOwnsInstance(e,t),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 Va(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const I=new Fr(d,h,{safetyIntervalMs:Number(process.env.TASK_HANDOFF_EVENT_CONNECTION_SAFETY_INTERVAL_MS)||void 0,logger:o.log});I.start(),await o.register(a),await o.register(r),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=Ko(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 Qo(c,e.cookies[ps]),s=Yo(e.method,e.url);return void(s&&ao(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await Qo(c,e.cookies[ps]);if(!s)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const n=Yo(e.method,e.url);n&&ao(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 Jo(t,b,s.join("assets",n||""))}),o.get("/favicon.ico",async(e,t)=>Vo(b,"favicon.ico")?Jo(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:be,build:Go(),dataDir:n.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"),Bo`#!/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[
|
|
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[ps])})),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(ps,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[ps]);return t.clearCookie(ps,{path:"/"}),{data:s}}),o.get("/api/control-plane/status",async()=>({data:{protocolVersion:be,build:Go(),storage:n}})),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",{bodyLimit:33554432},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)}})}}),Co({app:o,service:d,events:l,appSessionAggregator:u,aiSessionAggregator:p,chatGateway:m,aiSessionAttachments:g,nodeAgentTunnel:h,nodeEventSubscriber:I,errorPayload:Ko}),function({app:e,service:t}){!function(e){for(const t of["application/octet-stream","application/x-www-form-urlencoded"])Mo(e,t);Mo(e,/^multipart\/form-data(?:;.*)?$/i)}(e),e.get("/api/app-access/session",async e=>{const s=$o(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?qo(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($o(s.url),"tty");await t.proxyInstanceWebSocket(n.instance.id,e,n.path,Ro(s.headers),Oo(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($o(s.url),"vnc",n["*"]||"");await t.proxyInstanceWebSocket(i.instance.id,e,i.path,Ro(s.headers),Oo(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($o(e.url),"vnc",n["*"]||"");return Do(s,await t.proxyInstanceHttp(i.instance.id,i.path,{method:e.method,headers:_o(e.headers)}),i.instance.id,i.path)}}),e.all("/instances/:id",{bodyLimit:67108864},async(e,s)=>{const n=e.params;return"GET"===e.method||"HEAD"===e.method?s.redirect(`${jo(n.id)}/`):Do(s,await t.proxyInstanceHttp(n.id,"/",{method:e.method,headers:_o(e.headers),body:Po(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}`,Ro(s.headers),Oo(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 Do(s,await t.proxyInstanceHttp(n.id,r,{method:e.method,headers:_o(e.headers)}),n.id,r)}}),e.route({method:["POST","PUT","PATCH","DELETE","OPTIONS"],url:"/instances/:id/*",bodyLimit:67108864,handler:async(e,s)=>{const n=e.params,i=n["*"]||"",a=e.url.indexOf("?"),r=a>=0?e.url.slice(a):"";return xo(s,await t.proxyInstanceHttp(n.id,`/${i}${r}`,{method:e.method,headers:_o(e.headers),body:Po(e.body)}))}})}({app:o,service:d}),o.get("*",async(e,s)=>t.existsSync(b)?Jo(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}async function Zo(e){const n=Hr(e.dataDir),i=function(e,n={}){return function(e,n={}){t.mkdirSync(s.dirname(e),{recursive:!0});const i={pid:process.pid,hostname:f.hostname(),component:n.component,command:process.argv.join(" "),acquiredAt:(new Date).toISOString(),token:d.randomUUID(),dataDir:n.dataDir,host:n.host,port:n.port};for(let s=0;s<5;s+=1)try{return t.mkdirSync(e),t.writeFileSync(Vr(e),`${JSON.stringify({token:i.token,startedAt:(new Date).toISOString()})}\n`,{encoding:"utf8",mode:384,flag:"wx"}),Yr(e,i),{lockPath:e,owner:i,release(){const s=Kr(e);s?.token===i.token&&t.rmSync(e,{recursive:!0,force:!0})}}}catch(i){if("EEXIST"!==i.code)throw i;const a=Kr(e);if(Qr(a?.pid))throw new Gr(e,a,n.error);if(!a&&to(e)<2e3){if(s+1<5){eo();continue}throw new Gr(e,void 0,n.error)}const r=d.randomUUID();if(!so(e,r)){if(s+1<5){eo();continue}throw new Gr(e,Kr(e),n.error)}const o=Kr(e);if(Qr(o?.pid))throw Zr(Jr(e)),new Gr(e,o,n.error);if(a?.token&&o?.token!==a.token){Zr(Jr(e));continue}if(!io(e,r))continue;t.rmSync(e,{recursive:!0,force:!0})}const a=Kr(e);throw new Gr(e,a,n.error)}(e,{...n,component:"control-plane",error:{label:"Control plane",code:"CONTROL_PLANE_ALREADY_RUNNING"}})}(s.join(f.tmpdir(),`task-handoff-control-plane-${process.getuid?.()??"user"}.lock`),{dataDir:n.dataDir,host:e.host,port:e.port});try{const t=await Xo(e);t.addHook("onClose",async()=>{i.release()}),await t.listen({host:e.host,port:e.port})}catch(e){throw i.release(),e}}function ed(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 td(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",ed,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",td,process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"password").action(async e=>{await Zo({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});
|