@task-handoff/control-plane 0.0.3 → 0.0.4
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/cli.js +2 -2
- package/package.json +5 -3
- package/ui/assets/{abnfDiagram-VRR7QNED-DljE7ggn.js → abnfDiagram-VRR7QNED-CoMxIAmy.js} +1 -1
- package/ui/assets/{arc-is85lsBN.js → arc-DnCt1Ea-.js} +1 -1
- package/ui/assets/{architectureDiagram-ZJ3FMSHR-CdFyfJeG.js → architectureDiagram-ZJ3FMSHR-yRvJlGXH.js} +1 -1
- package/ui/assets/{blockDiagram-677ZJIJ3-Dp8WI_pP.js → blockDiagram-677ZJIJ3-CS_aJw4c.js} +1 -1
- package/ui/assets/{c4Diagram-LMCZKHZV-CVrU_nF6.js → c4Diagram-LMCZKHZV-D1tV6V_g.js} +1 -1
- package/ui/assets/channel-DMwuXOvE.js +1 -0
- package/ui/assets/{chunk-2Q5K7J3B-BS32WHPh.js → chunk-2Q5K7J3B-0c6eAKNe.js} +1 -1
- package/ui/assets/{chunk-32BRIVSS-VzNdB_Ar.js → chunk-32BRIVSS-BP9Lft1K.js} +1 -1
- package/ui/assets/{chunk-5VM5RSS4-Bd-_sEp1.js → chunk-5VM5RSS4-Diy5Ny1E.js} +1 -1
- package/ui/assets/{chunk-EX3LRPZG-BJgUlJrE.js → chunk-EX3LRPZG-Cf48GnF4.js} +1 -1
- package/ui/assets/{chunk-JWPE2WC7-llTrgHQ-.js → chunk-JWPE2WC7-Dcjr1117.js} +1 -1
- package/ui/assets/{chunk-MOJQB5TN-CFbyd4CU.js → chunk-MOJQB5TN-DF9osPlS.js} +1 -1
- package/ui/assets/{chunk-RYQCIY6F-Skv2sP0G.js → chunk-RYQCIY6F-D6LalwK3.js} +1 -1
- package/ui/assets/{chunk-V7JOEXUC-C1o-ZRTU.js → chunk-V7JOEXUC-Enj5J1P7.js} +1 -1
- package/ui/assets/{chunk-VR4S4FIN-CPv4V8jP.js → chunk-VR4S4FIN-DzdsyVKR.js} +1 -1
- package/ui/assets/{chunk-XXDRQBXY-tch5MGX5.js → chunk-XXDRQBXY-B8CDGPYh.js} +1 -1
- package/ui/assets/classDiagram-OUVF2IWQ-B85IV-lf.js +1 -0
- package/ui/assets/classDiagram-v2-EOCWNBFH-B85IV-lf.js +1 -0
- package/ui/assets/{cose-bilkent-JH36ORCC-BIiRCHgl.js → cose-bilkent-JH36ORCC-B89qwuy1.js} +1 -1
- package/ui/assets/{cynefin-VYW2F7L2-C97QdUlN.js → cynefin-VYW2F7L2-BXoR8-nv.js} +1 -1
- package/ui/assets/{cynefinDiagram-TSTJHNR4-DQS8K4GU.js → cynefinDiagram-TSTJHNR4-DhvNQP3B.js} +1 -1
- package/ui/assets/{dagre-VKFMJZFB-Dob7-AHm.js → dagre-VKFMJZFB-Cb0HPrLT.js} +1 -1
- package/ui/assets/{diagram-FQU43EPY-BghWk9H5.js → diagram-FQU43EPY-DP-bQOmX.js} +1 -1
- package/ui/assets/{diagram-G47NLZAW-CMj8Fcir.js → diagram-G47NLZAW-CkJHykzJ.js} +1 -1
- package/ui/assets/{diagram-NH7WQ7WH-DePNkIES.js → diagram-NH7WQ7WH-D26LydRl.js} +1 -1
- package/ui/assets/{diagram-OA4YK3LP-CxZ3kWpy.js → diagram-OA4YK3LP-BN7bgs-A.js} +1 -1
- package/ui/assets/{diagram-WEI45ONY-Dzjp1Z3i.js → diagram-WEI45ONY-BCMfbhfX.js} +1 -1
- package/ui/assets/{ebnfDiagram-CCIWWBDH-DqtGL_iw.js → ebnfDiagram-CCIWWBDH-SMCuHRUG.js} +1 -1
- package/ui/assets/{erDiagram-Q63AITRT-CWD_5gWD.js → erDiagram-Q63AITRT-D0cFqR_R.js} +1 -1
- package/ui/assets/{flowDiagram-23GEKE2U-BzazrmWJ.js → flowDiagram-23GEKE2U-CLez4rR-.js} +1 -1
- package/ui/assets/{ganttDiagram-NO4QXBWP-C_Jjp9Gi.js → ganttDiagram-NO4QXBWP-szhQlSDY.js} +1 -1
- package/ui/assets/{gitGraphDiagram-IHSO6WYX-6TZhhs8l.js → gitGraphDiagram-IHSO6WYX-Dax89jEp.js} +1 -1
- package/ui/assets/index-D0U6u6ZE.css +1 -0
- package/ui/assets/index-_N_vFS94.js +400 -0
- package/ui/assets/{infoDiagram-FWYZ7A6U-TvVY6dWk.js → infoDiagram-FWYZ7A6U-BpTgGfH7.js} +1 -1
- package/ui/assets/{ishikawaDiagram-FXEZZL3T-Fp6ETuY1.js → ishikawaDiagram-FXEZZL3T-Ds23KYYM.js} +1 -1
- package/ui/assets/{journeyDiagram-5HDEW3XC-Dh6MPEWW.js → journeyDiagram-5HDEW3XC-Da6rosAH.js} +1 -1
- package/ui/assets/{kanban-definition-HUTT4EX6-DKszraig.js → kanban-definition-HUTT4EX6-CTeBSGYZ.js} +1 -1
- package/ui/assets/{linear-DoHDkQGv.js → linear-C3umSqKV.js} +1 -1
- package/ui/assets/{mermaid.core-Cv4TaY1N.js → mermaid.core-BjgG3HbH.js} +4 -4
- package/ui/assets/{mindmap-definition-LN4V7U3C-azyqsigq.js → mindmap-definition-LN4V7U3C-D0Uf7jcp.js} +1 -1
- package/ui/assets/{pegDiagram-2B236MQR-wJ7rMADa.js → pegDiagram-2B236MQR-XtSizu9E.js} +1 -1
- package/ui/assets/{pieDiagram-ENE6RG2P-BCESVCnx.js → pieDiagram-ENE6RG2P-Qyt6pQg5.js} +1 -1
- package/ui/assets/{quadrantDiagram-ABIIQ3AL-BC1hzbjj.js → quadrantDiagram-ABIIQ3AL-q23nS35R.js} +1 -1
- package/ui/assets/{railroadDiagram-RFXS5EU6-BVkSq2pG.js → railroadDiagram-RFXS5EU6-BeWGfkAx.js} +1 -1
- package/ui/assets/{requirementDiagram-TGXJPOKE-4unXWRfu.js → requirementDiagram-TGXJPOKE-Bp33KEBm.js} +1 -1
- package/ui/assets/{sankeyDiagram-HTMAVEWB-C9ogPBbF.js → sankeyDiagram-HTMAVEWB-Bj5nUlQH.js} +1 -1
- package/ui/assets/{sequenceDiagram-DBY2YBRQ-C2qjxIRl.js → sequenceDiagram-DBY2YBRQ-COnS5enK.js} +1 -1
- package/ui/assets/{sizeCapture-X5ZJPWSS-DZRmQWCl.js → sizeCapture-X5ZJPWSS-ChFiLYIq.js} +1 -1
- package/ui/assets/{stateDiagram-2N3HPSRC-sQB-wYHK.js → stateDiagram-2N3HPSRC-CLghJsIY.js} +1 -1
- package/ui/assets/stateDiagram-v2-6OUMAXLB-Vs-nKlLB.js +1 -0
- package/ui/assets/{swimlanes-5IMT3BWC-DzUzzuCW.js → swimlanes-5IMT3BWC-BM0kePsz.js} +2 -2
- package/ui/assets/swimlanesDiagram-G3AALYLV-Bf7y6XsE.js +8 -0
- package/ui/assets/{timeline-definition-FHXFAJF6-B8PEeNnQ.js → timeline-definition-FHXFAJF6-CnaYehNM.js} +1 -1
- package/ui/assets/{vennDiagram-L72KCM5P-CqMmkX7n.js → vennDiagram-L72KCM5P-hcntCV0l.js} +1 -1
- package/ui/assets/{wardleyDiagram-EHGQE667-BGwwQABN.js → wardleyDiagram-EHGQE667-CmVUCl57.js} +1 -1
- package/ui/assets/{xychartDiagram-FW5EYKEG-Bymkd4G6.js → xychartDiagram-FW5EYKEG-D2Ic_Fag.js} +1 -1
- package/ui/index.html +2 -2
- package/ui/assets/channel-CIYuzaLo.js +0 -1
- package/ui/assets/classDiagram-OUVF2IWQ-CqY9zJ-v.js +0 -1
- package/ui/assets/classDiagram-v2-EOCWNBFH-CqY9zJ-v.js +0 -1
- package/ui/assets/index-CiRNpe9y.css +0 -1
- package/ui/assets/index-ojah1HkG.js +0 -400
- package/ui/assets/stateDiagram-v2-6OUMAXLB-Cr3u8gHX.js +0 -1
- package/ui/assets/swimlanesDiagram-G3AALYLV-OYVxdsAS.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var e=require("commander"),t=require("node:fs"),s=require("node:path"),n=require("fastify"),i=require("@fastify/cookie"),a=require("@fastify/websocket"),r=require("zod"),o=require("node:crypto"),d=require("node:util"),c=require("node:os"),l=require("write-file-atomic"),p=require("ws"),u=require("node:http"),g=require("node:net"),h=require("dingtalk-stream"),m=require("telegramify-markdown"),I=require("node:fs/promises");const f=20971520,y=41943040,b="ai-session.snapshot",v="ai-session.patch",w="ai-session.removed",S=r.z.string().trim().min(1).max(80),A=r.z.enum(["running","waiting","idle","failed"]),k=r.z.enum(["thinking","tool","editing","approval","responding","unknown"]),T=r.z.object({name:r.z.string().trim().min(1).max(120),inputPreview:r.z.string().trim().max(500).optional(),startedAt:r.z.string().datetime().optional()}).strict(),E=r.z.object({toolCalls:r.z.number().int().min(0).default(0),edits:r.z.number().int().min(0).default(0),approvals:r.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),_=r.z.object({send:r.z.boolean().optional(),interrupt:r.z.boolean().optional(),approval:r.z.boolean().optional()}).strict(),C=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),size:r.z.number().int().min(0).max(f),data:r.z.string().min(1).max(31457280)}).strict(),N=C.omit({data:!0}),z=r.z.object({id:r.z.string().trim().min(1).max(120),kind:r.z.enum(["image"]).optional()}).strict(),O=r.z.enum(["auto","queue","steer","immediate"]),R=r.z.object({message:r.z.string().trim().min(1).max(2e4),mode:O.optional()});R.extend({attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const x=R.extend({attachments:r.z.array(z).max(6).default([])}).strict(),D=r.z.object({decision:r.z.enum(["allow","deny","skip"])}).strict();r.z.object({queueIds:r.z.array(r.z.string().trim().min(1).max(120)).max(100)}).strict(),r.z.object({code:r.z.string().trim().min(1).max(160),message:r.z.string(),instanceId:r.z.string().trim().min(1).max(160).optional(),nodeId:r.z.string().trim().min(1).max(160).optional(),route:r.z.string().trim().min(1).max(4096).optional()}).strict();const j=r.z.object({id:r.z.string().trim().min(1).max(120),message:r.z.string().trim().min(1).max(2e4),attachments:r.z.array(N).max(6).default([]),status:r.z.enum(["queued","sending","failed"]).default("queued"),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),error:r.z.string().trim().max(4e3).optional()}).strict(),P=r.z.object({pendingCount:r.z.number().int().min(0).default(0),items:r.z.array(j).max(100).default([])}).strict().default({pendingCount:0,items:[]}),M=r.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),$=r.z.object({id:r.z.string().trim().min(1).max(240),providerTurnId:r.z.string().trim().max(240).optional(),source:M.optional(),userPrompt:r.z.string().trim().optional(),status:r.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),revision:r.z.number().int().min(0).default(0),sourcePriority:r.z.number().int().min(0).max(100).optional(),snapshotVersion:r.z.number().int().min(0).optional(),observedAt:r.z.string().datetime().optional(),startedAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional(),completedAt:r.z.string().datetime().optional()}).strict(),q=r.z.object({id:r.z.string().trim().min(1).max(120),agent:S,appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),activeTurnId:r.z.string().trim().max(240).optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:A.default("running"),phase:k.default("unknown"),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),currentTool:T.optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),startedAt:r.z.string().datetime(),updatedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional(),error:r.z.string().trim().max(4e3).optional(),counters:E,queue:P}).strict(),L=r.z.object({session:q,provider:r.z.string().trim().min(1).max(80),action:r.z.enum(["send","queue","steer","interrupt","approval"]),decision:r.z.enum(["allow","deny","skip"]).optional(),turnId:r.z.string().trim().min(1).max(240).optional(),providerTurnId:r.z.string().trim().min(1).max(240).optional(),queueId:r.z.string().trim().min(1).max(120).optional()}).strict(),U=q.pick({id:!0,agent:!0,appSessionId:!0,appId:!0,providerSessionId:!0,providerMeta:!0,appBindingKeys:!0,actions:!0,activeTurnId:!0,conversationId:!0,title:!0,cwd:!0,userPrompt:!0,turns:!0,status:!0,phase:!0,summary:!0,lastMessage:!0,currentTool:!0,queue:!0,startedAt:!0,updatedAt:!0,error:!0}),B=r.z.object({runningCount:r.z.number().int().min(0).default(0),waitingCount:r.z.number().int().min(0).default(0),staleCount:r.z.number().int().min(0).default(0),sessions:r.z.array(U).default([]),updatedAt:r.z.string().datetime()}).strict(),F=r.z.object({streamId:r.z.string().trim().min(1).max(240),revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:B}).strict(),H=B.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),revision:r.z.number().int().min(0).optional()});function G(e,t={}){const s=e.sessions.filter(e=>"idle"===e.status).length,n=e.sessions.filter(e=>"failed"===e.status).length;return H.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:s,problemCount:n,updatedAt:e.updatedAt,revision:t.revision})}const W=r.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),V=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),revision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:W}).strict(),K=r.z.object({meta:V,snapshot:B}).strict(),J=r.z.object({meta:V,upserted:r.z.array(U).default([]),removed:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),Q=r.z.object({meta:V,sessionIds:r.z.array(r.z.string().trim().min(1).max(120)).default([]),expiresAt:r.z.string().datetime()}).strict(),Y=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(b),payload:K}).strict(),r.z.object({type:r.z.literal(v),payload:J}).strict(),r.z.object({type:r.z.literal(w),payload:Q}).strict()])).default([])}).strict(),X=r.z.object({source:M,sourcePriority:r.z.number().int().min(0).max(100).optional(),observedAt:r.z.string().datetime().optional(),snapshotVersion:r.z.number().int().min(0).optional()}).strict(),Z=X.extend({type:r.z.literal("snapshot"),agent:r.z.string().trim().min(1).max(80),appSessionId:r.z.string().trim().max(120).optional(),appId:r.z.string().trim().max(120).optional(),providerSessionId:r.z.string().trim().max(240).optional(),providerMeta:r.z.record(r.z.string(),r.z.unknown()).optional(),appBindingKeys:r.z.array(r.z.string().trim().min(1).max(240)).max(20).optional(),actions:_.optional(),conversationId:r.z.number().int().positive().optional(),title:r.z.string().trim().max(240).optional(),cwd:r.z.string().trim().max(4096).optional(),activeTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),turns:r.z.array($).max(50).optional(),status:A.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),lastMessage:r.z.string().trim().optional(),transcriptPath:r.z.string().trim().max(4096).optional(),transcriptSize:r.z.number().int().min(0).optional(),replaceActivity:r.z.boolean().optional()}).strict(),ee=X.extend({type:r.z.literal("event"),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:r.z.string().trim().max(240).optional(),providerTurnId:r.z.string().trim().max(240).optional(),userPrompt:r.z.string().trim().optional(),text:r.z.string().optional(),status:A.optional(),phase:k.optional(),summary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),counters:r.z.object({toolCalls:r.z.number().int().min(0).optional(),edits:r.z.number().int().min(0).optional(),approvals:r.z.number().int().min(0).optional()}).strict().optional()}).strict();r.z.discriminatedUnion("type",[Z,ee]);const te=r.z.union([r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("interval").default("interval"),intervalMs:r.z.number().int().min(1e3)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("daily"),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("schedule"),scheduleKind:r.z.literal("weekly"),weekdays:r.z.array(r.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:r.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:r.z.string().trim().min(1).max(120)}).strict()]),se=r.z.union([te,r.z.object({type:r.z.literal("file-change"),roots:r.z.array(r.z.string().trim().min(1).max(4096)).min(1).max(50),globs:r.z.array(r.z.string().trim().min(1).max(500)).min(1).max(100),ignore:r.z.array(r.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:r.z.number().int().min(100).max(6e4).default(1500)}).strict(),r.z.object({type:r.z.literal("ai-session"),conversationId:r.z.number().int().positive().optional(),agent:r.z.string().trim().min(1).max(80).optional(),statuses:r.z.array(r.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:r.z.array(r.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),ne=r.z.object({promptTemplate:r.z.string().trim().min(1).max(2e4)}).strict(),ie=r.z.object({cooldownMs:r.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:r.z.number().int().min(1).max(20).default(1),whenBusy:r.z.enum(["skip","queue"]).default("skip")}).strict(),ae=r.z.object({configHash:r.z.string().trim().min(8).max(80),name:r.z.string().trim().min(1).max(160),description:r.z.string().trim().max(1e3).optional(),source:se,action:ne,policy:ie.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),re=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("conversation"),conversationId:r.z.number().int().positive()}).strict(),r.z.object({type:r.z.literal("ai-session"),aiSessionId:r.z.string().trim().min(1).max(160)}).strict()]),oe=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),origin:r.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:r.z.boolean().default(!0),target:re,localName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),de=r.z.object({configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),status:r.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:r.z.string().datetime().optional(),lastCompletedAt:r.z.string().datetime().optional(),lastSkippedAt:r.z.string().datetime().optional(),lastError:r.z.string().trim().max(4e3).optional(),runCount:r.z.number().int().min(0).default(0),skippedCount:r.z.number().int().min(0).default(0)}).strict(),ce=r.z.object({id:r.z.string().trim().min(1).max(160),configHash:r.z.string().trim().min(8).max(80),deploymentId:r.z.string().trim().min(1).max(240).optional(),instanceId:r.z.string().trim().min(1).max(160),eventType:r.z.enum(["manual","schedule","file-change","ai-session"]),status:r.z.enum(["started","completed","failed","skipped"]),target:re,promptPreview:r.z.string().trim().max(500),eventSummary:r.z.string().trim().max(1e3).optional(),error:r.z.string().trim().max(4e3).optional(),startedAt:r.z.string().datetime(),completedAt:r.z.string().datetime().optional()}).strict(),le=r.z.object({schemaVersion:r.z.literal(1).default(1),configs:r.z.array(ae).default([]),deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([]),recentRuns:r.z.array(ce).default([])}).strict();function pe(e){return Array.isArray(e)?e.map(pe):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,pe(t)])):e}const ue="2026-07-13",ge=r.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),he=r.z.string().datetime(),me=r.z.record(r.z.string(),r.z.string()).default({}),Ie=r.z.record(r.z.string(),r.z.string()).default({}),fe=r.z.object({codexModelId:ge.optional(),claudeModelId:ge.optional()}).strict().default({}),ye=r.z.object({component:r.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:r.z.string().trim().max(160).optional(),packageVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80).optional(),buildId:r.z.string().trim().max(160).optional(),builtAt:r.z.string().trim().max(120).optional(),gitCommit:r.z.string().trim().max(120).optional(),imageRef:r.z.string().trim().max(512).optional(),imageDigest:r.z.string().trim().max(240).optional()}).strict(),be=r.z.enum(["stable","beta","alpha"]),ve=r.z.discriminatedUnion("component",[r.z.object({component:r.z.literal("node-agent")}).strict(),r.z.object({component:r.z.literal("controlled-instance"),instanceId:ge}).strict()]),we=r.z.object({target:ve,channel:be.default("stable")}).strict(),Se=r.z.object({target:ve,source:r.z.enum(["npm","docker-registry"]),channel:be,currentVersion:r.z.string().trim().max(240).optional(),availableVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),updateAvailable:r.z.boolean(),supported:r.z.boolean().default(!0),reason:r.z.string().trim().max(2048).optional(),checkedAt:he}).strict(),Ae=r.z.object({id:ge,nodeId:ge,target:ve,source:r.z.enum(["npm","docker-registry"]),channel:be,fromVersion:r.z.string().trim().max(240).optional(),toVersion:r.z.string().trim().min(1).max(240),artifactRef:r.z.string().trim().min(1).max(512).optional(),status:r.z.enum(["queued","updating","restarting","succeeded","failed"]),error:r.z.string().trim().max(4096).optional(),startedAt:he.optional(),completedAt:he.optional(),createdAt:he,updatedAt:he}).strict(),ke=r.z.object({type:r.z.enum(["branch","tag","commit"]),name:r.z.string().trim().min(1).max(240).optional(),commit:r.z.string().trim().max(80).optional()}).strict(),Te=r.z.object({type:r.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:r.z.string().trim().min(1).max(120).optional()}).strict(),Ee=r.z.object({depth:r.z.number().int().positive().max(1e5).optional(),submodules:r.z.boolean().default(!1),lfs:r.z.boolean().default(!1),subdirectory:r.z.string().trim().max(240).default("")}).strict();r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:ge.optional(),modelSelection:fe,labels:me,createdAt:he,updatedAt:he}).strict();const _e=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),path:r.z.string().trim().min(1).max(4096),defaultImageId:ge.optional(),modelSelection:fe,labels:me,createdAt:he,updatedAt:he}).strict(),Ce=r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("git-repository"),repositoryId:ge.optional(),url:r.z.string().trim().min(1).max(2048),provider:r.z.string().trim().max(80).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("git-template"),url:r.z.string().trim().min(1).max(2048),templateId:r.z.string().trim().min(1).max(120).optional(),ref:ke.default({type:"branch",name:"main"}),auth:Te.default({type:"none"}),clone:Ee.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),r.z.object({type:r.z.literal("local-folder"),localFolderId:ge.optional(),path:r.z.string().trim().min(1).max(4096),ownerNodeId:ge.optional()}).strict()]),Ne=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:r.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:r.z.string().trim().max(240).optional(),readOnly:r.z.boolean().default(!1)}).strict(),ze=r.z.enum(["codex","claude"]),Oe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),source:Ce,defaultImageId:ge.optional(),defaultNodeId:ge.optional(),defaultRuntimeId:ge.optional(),modelSelection:fe,workspacePolicy:Ne,labels:me,createdAt:he,updatedAt:he}).strict(),Re=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),endpoint:r.z.string().trim().min(1).max(2048),key:r.z.string().trim().min(1).max(4096),model:r.z.string().trim().min(1).max(240),app:ze,enabled:r.z.boolean().default(!0),order:r.z.number().int().min(0).max(1e6).default(0),labels:me,createdAt:he,updatedAt:he}).strict(),xe=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),image:r.z.string().trim().min(1).max(512),registry:r.z.string().trim().min(1).max(160).default("local"),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).default([]),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).default([]),defaultEnv:Ie,labels:me,createdAt:he,updatedAt:he}).strict(),De=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),connectionMode:r.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:ge.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:he.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:he.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:r.z.string().trim().max(2048).optional(),controlEndpoint:r.z.string().trim().max(2048).optional(),containerEndpoint:r.z.string().trim().max(2048).optional(),publicWebBase:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,lastSeenAt:he.optional(),createdAt:he,updatedAt:he}).strict(),je=r.z.object({id:ge,nodeId:ge,name:r.z.string().trim().min(1).max(160),type:r.z.enum(["docker","kubernetes","local"]),status:r.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),labels:me,createdAt:he,updatedAt:he}).strict(),Pe=r.z.object({ok:r.z.boolean().optional(),role:r.z.string().optional(),nodeId:ge.optional()}).passthrough(),Me=r.z.object({bindScope:r.z.enum(["loopback","all-ipv4"]),port:r.z.number().int().min(1).max(65535)}).strict().extend({host:r.z.enum(["127.0.0.1","0.0.0.0"]),status:r.z.enum(["listening","error"]),source:r.z.enum(["bootstrap","persisted"]),error:r.z.string().trim().max(2048).optional()}).strict(),$e=r.z.object({nodeId:ge,joinToken:r.z.string().trim().min(1),expiresAt:he}).passthrough(),qe=r.z.object({id:ge,keyId:ge,url:r.z.string().trim().min(1).max(2048).optional(),name:r.z.string().trim().min(1).max(160).optional(),pairedAt:he,updatedAt:he.optional(),active:r.z.boolean().optional(),current:r.z.boolean().optional()}).passthrough(),Le=r.z.object({remote:r.z.object({id:ge,url:r.z.string().trim().min(1).max(2048).optional(),keyId:ge,pairedAt:he,active:r.z.boolean()}).passthrough(),tunnel:r.z.object({status:r.z.enum(["disabled","saved","connecting","connected","failed"]),error:r.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),Ue=r.z.object({deleted:r.z.boolean().optional()}).passthrough(),Be=r.z.object({repository:r.z.string().trim().optional(),tag:r.z.string().trim().optional(),id:r.z.string().trim(),createdSince:r.z.string().trim().optional(),size:r.z.string().trim().optional(),reference:r.z.string().trim().min(1)}).passthrough(),Fe=r.z.lazy(()=>r.z.object({name:r.z.string().trim().min(1),path:r.z.string().trim().min(1).max(4096),children:r.z.array(Fe)}).passthrough()),He=r.z.object({status:r.z.number().int().min(100).max(599).optional(),headers:r.z.record(r.z.string(),r.z.string()).optional(),bodyBase64:r.z.string().optional()}).passthrough(),Ge=r.z.object({strategy:r.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),vnc:r.z.string().trim().max(2048).optional(),tty:r.z.string().trim().max(2048).optional(),logs:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),We=r.z.object({strategy:r.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:r.z.string().trim().max(2048).optional(),api:r.z.string().trim().max(2048).optional(),ws:r.z.string().trim().max(2048).optional(),status:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),Ve=r.z.object({mode:r.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:r.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:r.z.string().trim().max(4096).optional(),resolvedCommit:r.z.string().trim().max(120).optional(),error:r.z.string().trim().max(2048).optional()}).passthrough(),Ke=r.z.object({id:ge,name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce,sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).default({}),nodeId:ge,runtimeId:ge,imageId:ge.optional(),imageSnapshot:xe.optional(),status:r.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:r.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:r.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:r.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:r.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:r.z.string().trim().max(80).optional(),instanceVersion:r.z.string().trim().max(80).optional(),build:ye.optional(),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),config:r.z.object({autoImportAgentConfigs:r.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:Ve.default({status:"unknown"}),target:Ge.default({strategy:"direct-port",status:"unknown"}),access:We,receiver:r.z.object({status:r.z.string().trim().max(80).default("unknown"),pendingCount:r.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),updatedAt:he.optional(),revision:r.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:B.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:r.z.object({enabledCount:r.z.number().int().min(0).default(0),runningCount:r.z.number().int().min(0).default(0),errorCount:r.z.number().int().min(0).default(0),configs:r.z.array(r.z.object({configHash:r.z.string().trim().min(1),config:ae,deployments:r.z.array(oe).default([]),runtime:r.z.array(de).default([])}).strict()).default([]),recentRuns:r.z.array(ce).default([])}).strict().optional(),runtime:r.z.object({kind:r.z.string().trim().max(80).optional(),containerName:r.z.string().trim().max(240).optional(),containerId:r.z.string().trim().max(240).optional(),workspacePath:r.z.string().trim().max(4096).optional(),pid:r.z.number().int().positive().optional(),port:r.z.number().int().positive().max(65535).optional(),labels:r.z.record(r.z.string(),r.z.string()).default({})}).strict().default({labels:{}}),registrationToken:r.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:he.optional(),createdAt:he,updatedAt:he}).strict();function Je(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const s=e;return Object.fromEntries(t.filter(e=>Object.prototype.hasOwnProperty.call(s,e)).map(e=>[e,s[e]]))}r.z.object({instanceId:ge.optional(),name:r.z.string().trim().min(1).max(160),projectId:ge.optional(),source:Ce.optional(),nodeId:ge.optional(),runtimeId:ge.optional(),imageId:ge.optional(),instanceVersion:r.z.string().trim().max(80).optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),controlMode:r.z.enum(["standalone","controlled"]).default("controlled"),capabilities:r.z.record(r.z.string(),r.z.unknown()).default({}),target:Ge.default({strategy:"direct-port",status:"unknown"}),workspace:Ve.default({status:"unknown"}),registrationToken:r.z.string().trim().max(240).optional()}).strict(),r.z.object({status:Ke.shape.status.optional(),health:Ke.shape.health.optional(),protocolVersion:r.z.string().trim().max(80),build:ye.optional(),capabilities:Ke.shape.capabilities.optional(),receiver:Ke.shape.receiver.optional(),apps:Ke.shape.apps.optional(),aiSessions:Ke.shape.aiSessions.optional(),triggers:Ke.shape.triggers.optional(),workspace:Ve.optional(),target:Ge.partial().optional()}).strict();const Qe=r.z.enum(["web","telegram","wechat","dingding"]),Ye=r.z.object({id:ge,channel:Qe,name:r.z.string().trim().min(1).max(160),enabled:r.z.boolean().default(!1),token:r.z.string().trim().max(4096).optional(),defaultChatId:r.z.string().trim().max(240).optional(),allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:r.z.number().int().positive().max(6e4).default(3e3),settings:r.z.record(r.z.string(),r.z.unknown()).default({}),createdAt:he,updatedAt:he}).strict(),Xe=r.z.object({id:ge,channel:Qe,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional(),activeProjectId:ge.optional(),activeInstanceId:ge.optional(),activeAiSessionId:r.z.string().trim().min(1).max(120).optional(),lastUsedAt:he,createdAt:he,updatedAt:he}).strict(),Ze=r.z.object({source:r.z.object({type:r.z.literal("chat-gateway").default("chat-gateway"),channel:Qe,bridgeId:ge.optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),message:r.z.object({text:r.z.string().trim().max(2e4).default(""),attachments:r.z.array(C).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>y&&t.addIssue({code:r.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:r.z.object({instanceId:ge,aiSessionId:r.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),et=r.z.object({id:ge,instanceId:ge,projectId:ge.optional(),aiSessionId:r.z.string().trim().min(1).max(120),providerSessionId:r.z.string().trim().max(240).optional(),result:r.z.string().default(""),timeoutMs:r.z.number().positive().optional(),source:r.z.string().trim().max(120).optional(),kind:r.z.enum(["task","approval"]).default("task"),status:r.z.enum(["pending","unreachable"]).default("pending"),error:r.z.string().trim().max(2048).optional(),lastSeenAt:he}).strict();function tt(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("receiver.")?"receiver":e.startsWith("conversation.")?"conversations":e.startsWith("trigger.")?"triggers":e.startsWith("channel.")?"channels":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}const st=r.z.enum(["ai.sessions","app.sessions"]),nt=r.z.object({topic:st,instanceId:r.z.string().trim().min(1).max(160),streamId:r.z.string().trim().min(1).max(240),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0)}).strict(),it=r.z.object({protocolVersion:r.z.literal(1),streams:r.z.array(nt)}).strict();function at(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||s.join(c.homedir(),".config","task-handoff","control-plane")}()){const t=s.resolve(e);return{dataDir:t,settingsPath:s.join(t,"control-plane-settings.json"),projectsDir:s.join(t,"projects"),modelsDir:s.join(t,"models"),imagesDir:s.join(t,"images"),nodesDir:s.join(t,"nodes"),nodeRuntimesDir:s.join(t,"node-runtimes"),controlledInstancesDir:s.join(t,"controlled-instances"),chatSessionsDir:s.join(t,"chat-sessions"),chatBridgesDir:s.join(t,"chat-bridges"),triggersDir:s.join(t,"triggers"),nodeJoinInvitesDir:s.join(t,"node-join-invites"),authUsersDir:s.join(t,"auth-users"),authSessionsDir:s.join(t,"auth-sessions"),logsDir:s.join(t,"logs")}}function rt(e){t.mkdirSync(e,{recursive:!0})}function ot(e,t){rt(s.dirname(e)),l.sync(e,`${JSON.stringify(t,null,2)}\n`,{encoding:"utf8"})}function dt(e,t){console.warn(JSON.stringify({message:e,...t}))}function ct(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function lt(e,s){const n=s.logger||dt;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void n("stored JSON could not be read",{filePath:e,...ct(t)})}if(!s.schema)return i;const a=s.sanitize?s.sanitize(i):i,r=s.schema.safeParse(a);if(r.success)return r.data;const o="strip"in s.schema&&"function"==typeof s.schema.strip?s.schema.strip().safeParse(a):void 0;if(o?.success)return n("unknown stored fields were ignored",{filePath:e,issues:r.error.issues}),o.data;n("stored record failed validation and was isolated",{filePath:e,issues:r.error.issues})}function pt(e){return`${e}_${o.randomUUID().replace(/-/g,"").slice(0,20)}`}function ut(){return o.randomBytes(24).toString("base64url")}class gt{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){rt(this.directory)}filePath(e){return s.join(this.directory,`${e}.json`)}list(){return this.init(),t.readdirSync(this.directory).filter(e=>e.endsWith(".json")).sort().flatMap(e=>{const t=lt(s.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const s=this.filePath(e);return t.existsSync(s)?lt(s,this.options):void 0}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,s=this.options.schema?this.options.schema.parse(t):t;return ot(this.filePath(s.id),s),s}patch(e,t){const s=this.get(e);if(!s)return;const n={...s,...t,id:s.id,createdAt:s.createdAt,updatedAt:(new Date).toISOString()};return this.put(n)}delete(e){const s=this.filePath(e);return!!t.existsSync(s)&&(t.unlinkSync(s),!0)}}class ht{filePathValue;defaults;options;constructor(e,t,s={}){this.filePathValue=e,this.defaults=t,this.options=s}init(){rt(s.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?lt(this.filePathValue,this.options)??this.defaults():this.defaults()}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,s=this.options.schema?this.options.schema.parse(t):t;return ot(this.filePathValue,s),s}}const mt=d.promisify(o.scrypt),It="task_handoff_cp_session",ft=r.z.enum(["disabled","password"]),yt=r.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),bt=r.z.string().min(8).max(4096),vt=r.z.object({username:yt,password:bt}).strict(),wt=r.z.object({username:r.z.string().trim().min(1).max(80),password:r.z.string().min(1).max(4096)}).strict(),St=r.z.object({id:r.z.string().trim().min(1),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}),At=St.extend({username:yt,passwordHash:r.z.string().trim().min(1),role:r.z.enum(["viewer","operator","admin"]),lastLoginAt:r.z.string().datetime().optional()}).strict(),kt=St.extend({userId:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),lastSeenAt:r.z.string().datetime().optional()}).strict();function Tt(){return(new Date).toISOString()}function Et(e){return o.createHash("sha256").update(e).digest("base64url")}async function _t(e){const t=o.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await mt(e,t,64)).toString("base64url")}`}function Ct(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}class Nt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return ft.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new gt(e.authUsersDir,{schema:At}),this.sessions=new gt(e.authSessionsDir,{schema:kt})}init(){this.users.init(),this.sessions.init()}enabled(){return"password"===this.mode}state(){return{mode:this.mode,enabled:this.enabled(),requiresBootstrap:this.enabled()&&0===this.users.list().length}}async bootstrapAdmin(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}if(this.users.list().length>0){const e=new Error("Control Plane admin user already exists.");throw Object.assign(e,{statusCode:409,code:"AUTH_BOOTSTRAP_ALREADY_DONE"}),e}const t=vt.parse(e),s=Tt();return Ct(this.users.put({id:pt("user"),username:t.username,passwordHash:await _t(t.password),role:"admin",createdAt:s,updatedAt:s}))}async login(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}const t=wt.parse(e),s=this.users.list().find(e=>e.username===t.username);if(!s||!await async function(e,t){const[s,n,i]=t.split("$");if("scrypt"!==s||!n||!i)return!1;const a=await mt(e,n,64),r=Buffer.from(i,"base64url");return r.length===a.length&&o.timingSafeEqual(r,a)}(t.password,s.passwordHash)){const e=new Error("Invalid username or password.");throw Object.assign(e,{statusCode:401,code:"AUTH_LOGIN_FAILED"}),e}const n=Tt(),i=ut(),a=new Date(Date.now()+12096e5).toISOString(),r=this.sessions.put({id:pt("sess"),userId:s.id,tokenHash:Et(i),expiresAt:a,createdAt:n,updatedAt:n,lastSeenAt:n});return this.users.put({...s,lastLoginAt:n,updatedAt:n}),{user:Ct({...s,lastLoginAt:n,updatedAt:n}),sessionToken:`${r.id}.${i}`,expiresAt:a}}async userForSessionToken(e){if(!this.enabled())return;if(!e)return;const[t,s]=e.split(".");if(!t||!s)return;const n=this.sessions.get(t);if(!n||n.expiresAt<=Tt()||n.tokenHash!==Et(s))return;const i=this.users.get(n.userId);return i?(this.sessions.put({...n,lastSeenAt:Tt(),updatedAt:Tt()}),Ct(i)):void 0}async currentSession(e){const t=this.state(),s=await this.userForSessionToken(e);return{...t,authenticated:!this.enabled()||Boolean(s),user:s}}logout(e){const t=e?.split(".")[0];return t&&this.sessions.delete(t),{ok:!0}}}const zt="app-session.snapshot",Ot="app-session.patch",Rt="app-session.removed",xt=r.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Dt=r.z.object({type:r.z.enum(["app-session","provider-session","adapter-key"]),id:r.z.string().trim().min(1).max(240),agent:r.z.string().trim().min(1).max(80).optional(),adapter:r.z.string().trim().min(1).max(80).optional(),key:r.z.string().trim().min(1).max(240).optional()}).strict(),jt=r.z.record(r.z.string(),r.z.unknown()).and(r.z.object({id:r.z.string().trim().min(1).max(160),appId:r.z.string().trim().min(1).max(160).optional(),title:r.z.string().trim().max(240).optional(),kind:r.z.string().trim().max(80).optional(),status:xt.default("unknown"),bindings:r.z.array(Dt).max(40).default([]),workspace:r.z.object({cwd:r.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:r.z.string().datetime().optional(),updatedAt:r.z.string().datetime().optional()})),Pt=r.z.object({runningCount:r.z.number().int().min(0).default(0),problemCount:r.z.number().int().min(0).default(0),sessions:r.z.array(jt).default([]),updatedAt:r.z.string().datetime()}).strict(),Mt=r.z.object({streamId:r.z.string().trim().min(1).max(240),revision:r.z.number().int().min(0),lastEventAt:r.z.string().datetime(),snapshot:Pt}).strict();Pt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:r.z.number().int().min(0).optional()});const $t=r.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),qt=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),nodeId:r.z.string().trim().min(1).max(160).optional(),revision:r.z.number().int().min(0),previousRevision:r.z.number().int().min(0).optional(),traceId:r.z.string().trim().min(1).max(160),generatedAt:r.z.string().datetime(),reason:$t}).strict(),Lt=r.z.object({meta:qt,snapshot:Pt}).strict(),Ut=r.z.object({meta:qt,session:jt}).strict(),Bt=r.z.object({meta:qt,sessionId:r.z.string().trim().min(1).max(160),tombstone:jt.optional(),expiresAt:r.z.string().datetime().optional()}).strict(),Ft=r.z.object({streamId:r.z.string().trim().min(1).max(240),instanceId:r.z.string().trim().min(1).max(160),sinceRevision:r.z.number().int().min(0),latestRevision:r.z.number().int().min(0),earliestRetainedRevision:r.z.number().int().min(0),syncRequired:r.z.boolean().default(!1),events:r.z.array(r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal(zt),payload:Lt}).strict(),r.z.object({type:r.z.literal(Ot),payload:Ut}).strict(),r.z.object({type:r.z.literal(Rt),payload:Bt}).strict()])).default([])}).strict();function Ht(e,t=(new Date).toISOString()){const s=e.map(e=>Gt(e));return Pt.parse({runningCount:s.filter(e=>"running"===e.status).length,problemCount:s.filter(e=>"failed"===e.status).length,sessions:s,updatedAt:t})}function Gt(e){const t=Wt("string"==typeof e.status?e.status:void 0),s="string"==typeof e.id?e.id:"",n=function(e){const t=Yt(e.workspace),s=Yt(e.tty),n=Yt(e.launch),i=Yt(e.ai),a=Yt(i.claude),r=Xt(t.cwd)||Xt(s.cwd)||Xt(a.cwd)||Xt(n.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return jt.parse({...a,status:t,bindings:Qt([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=Dt.safeParse(e);return t.success?[t.data]:[]}):[]),...s?[{type:"app-session",id:s}]:[],...Kt(e)]),...n?{workspace:n}:{}});var r}function Wt(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":xt.options.includes(t)?t:"unknown"}function Vt(e){return"app-session"===e.type?`app:${e.id}`:"provider-session"===e.type?`provider:${e.agent||""}:${e.id}`:`adapter:${e.adapter||""}:${e.key||e.id}`}function Kt(e){const t=Yt(e.ai),s=Yt(t.claude),n=Yt(t.appServer),i=[],a=Xt(s.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Xt(s.providerSessionId);r&&i.push({type:"provider-session",agent:"claude",id:r});const o=Array.isArray(t.threadIds)?t.threadIds.filter(e=>"string"==typeof e&&Boolean(e.trim())):[],d=Xt(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Xt(n.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Jt(e){const t="string"==typeof e.id?e.id:"",s=Yt(e.ai),n=Xt(Yt(s.claude).short);return[t?`app:${t}`:"",n?`claude-short:${n}`:""]}function Qt(e){const t=new Map;for(const s of e)t.set(Vt(s),s);return[...t.values()]}function Yt(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Xt(e){return"string"==typeof e&&e.trim()?e.trim():""}function Zt(e=(new Date).toISOString()){return Pt.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function es(e,t,s,n){const i=n&&"object"==typeof n?n:{};return{nodeId:e.id,route:"string"==typeof i.route?i.route:t,method:s,code:"string"==typeof i.code?i.code:"NODE_AGENT_REQUEST_FAILED",message:n instanceof Error?n.message:String(n),statusCode:"number"==typeof i.statusCode?i.statusCode:void 0,issues:Array.isArray(i.issues)?i.issues:void 0}}class ts{requestImpl;logger;constructor(e){this.requestImpl=e.request,this.logger=e.logger}async request(e,t,s={}){const n=s.method||"GET";let i,a;try{i=await this.requestImpl(e,t,s)}catch(s){throw this.logger?.warn?.({nodeId:e.id,route:t,method:n,error:s instanceof Error?s.message:String(s),errorCode:"NODE_AGENT_TRANSPORT_FAILED"},"node agent transport failed"),s}try{a=await i.json()}catch(s){this.logger?.warn?.({nodeId:e.id,route:t,method:n,statusCode:i.status,error:s instanceof Error?s.message:String(s),errorCode:"NODE_AGENT_RESPONSE_INVALID_JSON"},"node agent response was not valid json");const a=new Error(`Node agent ${e.id} returned invalid JSON for ${t}.`);throw Object.assign(a,{statusCode:502,code:"NODE_AGENT_RESPONSE_INVALID_JSON",nodeId:e.id,route:t}),a}if(!i.ok){const s=new Error(a.error?.message||`Node agent request failed with HTTP ${i.status}`);throw Object.assign(s,{statusCode:i.status,code:a.error?.code||"NODE_AGENT_REQUEST_FAILED",nodeId:e.id,route:t}),this.logger?.warn?.({nodeId:e.id,route:t,method:n,statusCode:i.status,errorCode:a.error?.code||"NODE_AGENT_REQUEST_FAILED",error:s.message},"node agent request failed"),s}return this.logger?.info?.({nodeId:e.id,route:t,method:n,statusCode:i.status},"node agent request ok"),a.data??a}async requestSchema(e,t,s,n={}){return this.parse(e,t,s,await this.request(e,t,n),n.method||"GET")}parse(e,t,s,n,i="GET"){const a=s.safeParse(n);if(a.success)return a.data;this.logger?.warn?.({nodeId:e.id,route:t,method:i,errorCode:"NODE_AGENT_PROTOCOL_INVALID",issues:a.error.issues},"node agent protocol response did not match schema");const r=new Error(`Node agent ${e.id} returned invalid protocol data for ${t}.`);throw Object.assign(r,{statusCode:502,code:"NODE_AGENT_PROTOCOL_INVALID",nodeId:e.id,route:t,issues:a.error.issues}),r}}class ss{client;constructor(e){this.client=e}request(e,t,s={}){return this.client.request(e,t,s)}health(e){return this.client.requestSchema(e,"/health",Pe)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",Se,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",Ae,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",r.z.array(Ae))}getExternalListener(e){return this.client.requestSchema(e,"/settings/external-listener",Me)}updateExternalListener(e,t){return this.client.requestSchema(e,"/settings/external-listener",Me,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",$e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t&&"object"==typeof t?t:{})})}connectRemote(e,t){return this.client.requestSchema(e,"/remotes/connect",Le,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",r.z.array(qe))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",r.z.array(Be))}listFolderTree(e,t={}){const s=new URLSearchParams;t.path&&s.set("path",t.path),void 0!==t.depth&&s.set("depth",String(t.depth));const n=s.toString();return this.client.requestSchema(e,"/folders/tree"+(n?`?${n}`:""),r.z.array(Fe))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",r.z.array(_e))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}async requireLocalFolder(e,t){const s=(await this.listLocalFolders(e)).find(e=>e.id===t);if(!s){const s=new Error(`Local folder ${t} was not found on node ${e.id}.`);throw Object.assign(s,{statusCode:404,code:"NODE_LOCAL_FOLDER_NOT_FOUND",nodeId:e.id,route:"/local-folders"}),s}return s}listRuntimes(e){return this.client.requestSchema(e,"/runtimes",r.z.array(je))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",je,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateRuntime(e,t,s){const n=`/runtimes/${encodeURIComponent(t)}`;return this.client.requestSchema(e,n,je,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}checkRuntime(e,t){const s=`/runtimes/${encodeURIComponent(t)}/check`;return this.client.requestSchema(e,s,je,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,Ue,{method:"DELETE"})}async listFleetRuntimes(e){return(await Promise.allSettled(e.map(async e=>({node:e,runtimes:await this.listRuntimes(e)})))).reduce((t,s,n)=>{if("fulfilled"===s.status)t.items.push(...s.value.runtimes);else{const i=e[n];i&&t.nodeErrors.push(es(i,"/runtimes","GET",s.reason))}return t},{items:[],nodeErrors:[]})}listInstances(e){return this.listInstancesWithDiagnostics(e).then(e=>e.items)}async listInstancesWithDiagnostics(e){return this.client.requestSchema(e,"/instances",r.z.array(r.z.unknown())).then(t=>t.reduce((t,s)=>{const n=this.normalizeInstance(e,s);return n.instance&&t.items.push(n.instance),n.error&&t.nodeErrors.push(n.error),t},{items:[],nodeErrors:[]}))}createInstance(e,t){return this.client.requestSchema(e,"/instances",Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Ke,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,Ue,{method:"POST"})}startInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Ke,{method:"POST"})}restartInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Ke,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}proxyRawInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/proxy/raw`,He,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}async listFleetInstances(e){return(await Promise.allSettled(e.map(async e=>({node:e,result:await this.listInstancesWithDiagnostics(e)})))).reduce((t,s,n)=>("fulfilled"===s.status?(t.items.push(...s.value.result.items),t.nodeErrors.push(...s.value.result.nodeErrors)):t.nodeErrors.push(es(e[n],"/instances","GET",s.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const s="/instances",n=(i=t,Ke.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,s=new Set(Object.keys(Ke.shape)),n={};for(const[e,i]of Object.entries(t))s.has(e)&&(n[e]=i);return n.apps=Je(t.apps,["runningCount","problemCount","updatedAt","revision"]),n.config=Je(t.config,["autoImportAgentConfigs"]),n.receiver=Je(t.receiver,["status","pendingCount"]),n.runtime=Je(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),n.target=Je(t.target??t.endpoints,["strategy","web","api","status"]),n.access=Je(t.access,["strategy","web","api","ws","status"]),n}(i)));var i;if(n.success)return n.data.protocolVersion&&n.data.protocolVersion!==ue?(this.client.logger?.warn?.({nodeId:e.id,instanceId:n.data.id,expectedProtocolVersion:ue,actualProtocolVersion:n.data.protocolVersion,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node instance protocol version mismatch"),{instance:n.data,error:{nodeId:e.id,route:s,method:"GET",code:"PROTOCOL_VERSION_MISMATCH",message:`Node instance ${n.data.id} protocol version ${n.data.protocolVersion} does not match ${ue}.`,statusCode:502}}):{instance:n.data};const a=t&&"object"==typeof t?t:{},r="string"==typeof a.id?a.id:void 0;return this.client.logger?.warn?.({nodeId:e.id,instanceId:r,errorCode:"NODE_INSTANCE_PAYLOAD_INVALID",issues:n.error.issues.map(e=>({path:e.path.join("."),message:e.message}))},"node instance payload invalid"),{error:{nodeId:e.id,route:s,method:"GET",code:"NODE_INSTANCE_PAYLOAD_INVALID",message:r?`Node instance ${r} payload is invalid.`:"Node instance payload is invalid.",statusCode:502,issues:n.error.issues}}}}function ns(){return(new Date).toISOString()}function is(e){return e?e instanceof Headers?Object.fromEntries(e.entries()):Array.isArray(e)?Object.fromEntries(e.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(e).map(([e,t])=>[e,String(t)])):{}}function as(e){const t=String(e||"").split(":");if(3===t.length&&"ai"===t[1]&&t[0]&&t[2])return{instanceId:t[0],aiSessionId:t[2]}}function rs(e){return e instanceof Error?e.message:String(e)}function os(e,t){const s=new Error(t);throw Object.assign(s,{statusCode:404,code:e}),s}class ds{requireNode;nodeAgentGateway;nodeAgentRequest;fetchImpl;constructor(e){this.requireNode=e.requireNode,this.nodeAgentGateway=e.nodeAgentGateway,this.nodeAgentRequest=e.nodeAgentRequest,this.fetchImpl=e.fetchImpl}async request(e,t,s={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_UNREACHABLE"}),t}const n=this.requireNode(e.nodeId),i=await this.nodeAgentRequest(n,`/instances/${encodeURIComponent(e.id)}/proxy`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({path:`/api/${t.replace(/^\/+/,"")}`,method:s.method||"GET",headers:is(s.headers),body:(a=s.body,"string"==typeof a?a:null==a?void 0:String(a))})});var a;const r=await i.json().catch(()=>({}));if(!i.ok){const s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,s="string"==typeof t.code&&t.code.trim()?t.code:void 0,n="string"==typeof t.message&&t.message?t.message:void 0;return s||n?{code:s,message:n}:void 0}(r.error),n=s?.message||function(e){if(!e||"object"!=typeof e||Array.isArray(e))return"";const t=e.message;return"string"==typeof t?t:""}(r.error)||`Instance request failed with HTTP ${i.status}`,a=new Error(n);throw Object.assign(a,{statusCode:i.status,code:s?.code||"INSTANCE_REQUEST_FAILED",instanceId:e.id,nodeId:e.nodeId,route:t}),a}return r.data??r}async proxyHttp(e,t,s={}){if(!e.target.web){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const n=this.requireNode(e.nodeId),i=await this.nodeAgentGateway.proxyRawInstance(n,e.id,{path:t,method:s.method||"GET",headers:is(s.headers),...(a=s.body,null==a?{}:"string"==typeof a?{body:a}:Buffer.isBuffer(a)?{bodyBase64:a.toString("base64")}:a instanceof Uint8Array?{bodyBase64:Buffer.from(a).toString("base64")}:{body:String(a)})});var a;return{status:i.status||502,headers:i.headers||{},body:Buffer.from(i.bodyBase64||"","base64")}}proxyWebSocket(e,t,s,n,i,a={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const r=this.requireNode(e.nodeId),o=n.startsWith("/")?n.slice(1):n;t.proxyWebSocket(r,s,`/instances/${encodeURIComponent(e.id)}/proxy/ws/${o}`,i,a)}async reportHeartbeat(e,t,s){if(!e.registrationToken)return;const n=this.requireNode(e.nodeId),i=`/instances/${encodeURIComponent(e.id)}/heartbeat`,a={method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.registrationToken}`},body:JSON.stringify(t)},r="reverse-wss"===n.connectionMode?await(s?.request(n,i,a)):await this.fetchImpl(`${(n.controlEndpoint||n.endpoint||"").replace(/\/$/,"")}/api/node-agent${i}`,a);if(!r)return;const o=await r.json().catch(()=>({}));if(!r.ok){const e=new Error(o.error?.message||`Instance heartbeat sync failed with HTTP ${r.status}`);throw Object.assign(e,{statusCode:r.status,code:"INSTANCE_HEARTBEAT_SYNC_FAILED"}),e}}}function cs(e){if(!e||"object"!=typeof e)return;const t=e,s=ye.safeParse(t.build);return{..."boolean"==typeof t.ok?{ok:t.ok}:{},..."string"==typeof t.role?{role:t.role}:{},..."string"==typeof t.nodeId?{nodeId:t.nodeId}:{},..."string"==typeof t.protocolVersion?{protocolVersion:t.protocolVersion}:{},...s.success?{build:s.data}:{},..."string"==typeof t.serverTime?{serverTime:t.serverTime}:{}}}function ls(e){const{agent:t,...s}=e.capabilities,{secret:n,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=cs(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...s,agent:r}:s}}function ps(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function us(e){return function(e){const{registrationToken:t,target:s,agentStatus:n,targetStatus:i,uiAccessStatus:a,...r}=e;return r}(function(e){const t=`/instances/${encodeURIComponent(e.id)}`,s={strategy:"control-plane-proxy",web:`${t}/`,api:`${t}/api`,ws:`${t}/api`,status:"online"===e.connectionStatus||"online"===e.agentStatus?"reachable":"endpoint-unreachable"};return{...e,access:s}}(e))}function gs(e){const{key:t,...s}=e;return{...s,keyPreview:(n=e.key,n.length<=8?"set":`${n.slice(0,4)}...${n.slice(-4)}`),keySet:!0};var n}class hs{read(e){const t=new Map(e.projects.map(e=>[e.id,e])),s=new Map(e.images.map(e=>[e.id,e])),n=new Map(e.nodes.map(e=>[e.id,e])),i=new Map(e.runtimes.map(e=>[`${e.nodeId}:${e.id}`,e])),a=Date.now();return{items:e.instances.map(e=>function(e){const t=e.instance.lastHeartbeatAt?e.currentTime-Date.parse(e.instance.lastHeartbeatAt):void 0,s=void 0!==t&&t>3e4?"offline":e.instance.connectionStatus,n=us(e.instance);return{...n,aiSessions:G(n.aiSessions),connectionStatus:s,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?ls(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===ue}}({instance:e,currentTime:a,project:e.projectId?t.get(e.projectId):void 0,image:e.imageId&&s.get(e.imageId)||e.imageSnapshot,node:n.get(e.nodeId),runtime:i.get(`${e.nodeId}:${e.runtimeId}`)})),nodeErrors:e.nodeErrors||[]}}}const ms=r.z.object({mode:r.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:De.shape.id.optional(),secret:r.z.string().trim().max(4096).optional(),pairedAt:De.shape.createdAt.optional(),pairing:r.z.object({status:r.z.enum(["pending","paired","expired"]).default("pending"),joinToken:r.z.string().trim().max(4096).optional(),expiresAt:De.shape.createdAt.optional()}).strict().optional()}).strict(),Is=r.z.object({id:Oe.shape.id.optional(),name:Oe.shape.name,source:Ce,defaultImageId:Oe.shape.defaultImageId,defaultNodeId:Oe.shape.defaultNodeId,defaultRuntimeId:Oe.shape.defaultRuntimeId,modelSelection:Oe.shape.modelSelection.optional(),workspacePolicy:Ne.optional(),labels:Oe.shape.labels.optional()}).strict(),fs=Is.omit({id:!0}).partial().strict(),ys=r.z.object({id:Ke.shape.id.optional(),name:Ke.shape.name.optional(),projectId:Ke.shape.projectId,source:Ce.optional(),sourceSnapshot:r.z.record(r.z.string(),r.z.unknown()).optional(),nodeId:Ke.shape.nodeId.optional(),runtimeId:Ke.shape.runtimeId.optional(),imageId:Ke.shape.imageId,config:Ke.shape.config.optional()}).strict(),bs=r.z.object({name:Ke.shape.name.optional()}).strict(),vs=r.z.object({publicBaseUrl:r.z.string().trim().max(2048).optional(),updateChannel:be.default("stable")}).strict(),ws=vs.partial().strict(),Ss=r.z.object({id:Re.shape.id.optional(),name:Re.shape.name,endpoint:Re.shape.endpoint,key:Re.shape.key,model:Re.shape.model,app:Re.shape.app,enabled:Re.shape.enabled.optional(),order:Re.shape.order.optional(),labels:Re.shape.labels.optional()}).strict(),As=Ss.omit({id:!0}).partial().strict(),ks=r.z.object({id:xe.shape.id.optional(),name:xe.shape.name,image:xe.shape.image,registry:xe.shape.registry.optional(),capabilities:r.z.array(r.z.string().trim().min(1).max(80)).optional(),optionalApps:r.z.array(r.z.string().trim().min(1).max(120)).optional(),defaultEnv:xe.shape.defaultEnv.optional(),labels:xe.shape.labels.optional()}).strict(),Ts=ks.omit({id:!0}).partial().strict(),Es=r.z.object({id:De.shape.id.optional(),name:De.shape.name,connectionMode:De.shape.connectionMode.optional(),auth:ms.optional(),joinToken:r.z.string().trim().min(1).max(4096).optional(),endpoint:De.shape.endpoint,controlEndpoint:De.shape.controlEndpoint,containerEndpoint:De.shape.containerEndpoint,publicWebBase:De.shape.publicWebBase,status:r.z.enum(["unknown","online","offline","degraded"]).optional(),health:r.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:De.shape.capabilities.optional(),labels:De.shape.labels.optional(),lastSeenAt:De.shape.lastSeenAt}).strict(),_s=r.z.object({name:De.shape.name.optional(),connectionMode:De.shape.connectionMode.unwrap().optional(),auth:ms.optional(),endpoint:De.shape.endpoint,controlEndpoint:De.shape.controlEndpoint,containerEndpoint:De.shape.containerEndpoint,publicWebBase:De.shape.publicWebBase,status:De.shape.status.unwrap().optional(),health:De.shape.health.unwrap().optional(),capabilities:De.shape.capabilities.unwrap().optional(),labels:De.shape.labels.unwrap().optional(),lastSeenAt:De.shape.lastSeenAt}).strict(),Cs=r.z.object({controlPlaneUrl:r.z.string().trim().url().max(2048),joinToken:r.z.string().trim().min(1).max(4096),controlPlaneName:r.z.string().trim().min(1).max(160).optional(),activate:r.z.boolean().optional()}).strict(),Ns=r.z.object({nodeName:r.z.string().trim().min(1).max(160).optional(),expiresInMs:r.z.number().int().positive().max(36e5).optional()}).strict(),zs=r.z.object({name:Ye.shape.name.optional(),channel:Ye.shape.channel.optional(),enabled:r.z.boolean().optional(),token:Ye.shape.token,defaultChatId:Ye.shape.defaultChatId,allowedUserIds:r.z.array(r.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Ye.shape.pollIntervalMs.optional(),settings:Ye.shape.settings.optional()}).strict(),Os=zs.extend({channel:Ye.shape.channel,name:Ye.shape.name.optional()}),Rs=ae.extend({id:ae.shape.configHash}),xs=r.z.object({name:ae.shape.name,description:ae.shape.description,source:se,action:ne,policy:ie.partial().optional()}).strict(),Ds=r.z.object({instanceIds:r.z.array(Ke.shape.id).min(1),target:re.optional(),enabled:r.z.boolean().optional()}).strict(),js=r.z.object({configHash:ae.shape.configHash,enabled:r.z.boolean().optional()}).strict();class Ps{triggers;listInstances;requireInstance;instanceRequest;constructor(e){this.triggers=e.triggers,this.listInstances=e.listInstances,this.requireInstance=e.requireInstance,this.instanceRequest=e.instanceRequest}async listTriggers(){const e=await this.listInstances(),t=new Map;for(const e of this.triggers.list())t.set(e.configHash,{configHash:e.configHash,config:e,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!0,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]});for(const s of e){const e=s.triggers;for(const n of e?.configs||[]){const i=t.get(n.configHash)||{configHash:n.configHash,config:n.config,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!1,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]},a=(n.deployments||[]).map(e=>({instanceId:s.id,instanceName:s.name,deployment:e,runtime:(n.runtime||[]).find(t=>t.deploymentId?t.deploymentId===e.deploymentId:t.configHash===e.configHash)}));i.deploymentCount+=a.length,i.enabledCount+=a.filter(e=>Boolean(e.deployment.enabled)).length,i.controlPlaneDeploymentCount+=a.filter(e=>"control-plane"===e.deployment.origin).length,i.runningCount+=(n.runtime||[]).filter(e=>"running"===e.status).length,i.errorCount+=(n.runtime||[]).filter(e=>"error"===e.status).length,i.deployments.push(...a),i.recentRuns.push(...(e.recentRuns||[]).filter(e=>e.configHash===n.configHash).map(e=>({...e,instanceId:s.id,instanceName:s.name}))),t.set(n.configHash,i)}}return{updatedAt:(new Date).toISOString(),triggers:[...t.values()].sort((e,t)=>String(e.config.name||e.configHash).localeCompare(String(t.config.name||t.configHash)))}}createTrigger(e){const t=xs.parse(e||{}),s=ns(),n={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=pe({source:e.source,action:e.action,policy:ie.parse(e.policy||{})});return`trg_${o.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:n}),a=Rs.parse({id:i,configHash:i,name:t.name,description:t.description,source:t.source,action:t.action,policy:n,createdAt:s,updatedAt:s});return this.triggers.put(a)}async deleteTrigger(e){const t=this.triggers.delete(e),s=await this.listInstances(),n=[];for(const t of s){const s=t.triggers?.configs?.find(t=>t.configHash===e);if(!s)continue;const i=(s.deployments||[]).filter(e=>"control-plane"===e.origin);if(!i.length)continue;const a=s.deployments.every(e=>"control-plane"===e.origin);for(const s of i)try{const i=s.deploymentId?`/triggers/${encodeURIComponent(e)}/deployments/${encodeURIComponent(s.deploymentId)}`:a?`/triggers/${encodeURIComponent(e)}`:void 0;if(!i){n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!1,skipped:"missing-deployment-id"});continue}const r=await this.instanceRequest(t,i,{method:"DELETE"});if(n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!0,data:r}),!s.deploymentId)break}catch(i){n.push({instanceId:t.id,configHash:e,deploymentId:s.deploymentId,deleted:!1,error:i instanceof Error?i.message:String(i)})}}return t||n.length||os("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:n}}async applyTrigger(e,t){const s=this.requireTrigger(e),n=Ds.parse(t||{}),i=[];for(const e of n.instanceIds){const t=await this.requireInstance(e);i.push({instanceId:e,data:await this.instanceRequest(t,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:s.name,description:s.description,source:s.source,action:s.action,policy:s.policy,deployment:{origin:"control-plane",enabled:n.enabled??!0,target:n.target}})})})}return{configHash:e,results:i}}async listInstanceTriggers(e){const t=await this.requireInstance(e);return this.instanceRequest(t,"/triggers")}async bindAiSessionTrigger(e,t,s){const n=js.parse(s||{}),i=this.requireTrigger(n.configHash),a=await this.requireInstance(e);return this.instanceRequest(a,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:i.name,description:i.description,source:i.source,action:i.action,policy:i.policy,deployment:{deploymentId:Ms(t,n.configHash),origin:"control-plane",enabled:n.enabled??!0,target:{type:"ai-session",aiSessionId:t}}})})}async unbindAiSessionTrigger(e,t,s){const n=await this.requireInstance(e),i=Ms(t,s);return this.instanceRequest(n,`/triggers/${encodeURIComponent(s)}/deployments/${encodeURIComponent(i)}`,{method:"DELETE"})}async runInstanceTrigger(e,t,s={}){const n=await this.requireInstance(e);return this.instanceRequest(n,`/triggers/${encodeURIComponent(t)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s||{})})}requireTrigger(e){const t=this.triggers.get(e);return t||os("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function Ms(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}class $s{records=new Map;options;constructor(e){this.options=e}create(e){const t=o.randomBytes(this.options.tokenBytes).toString("base64url"),s={...e,token:t,expiresAt:new Date(Date.now()+(e.ttlMs||this.options.ttlMs)).toISOString()};return this.records.set(t,s),s}resolve(e,t){const s=this.records.get(e);if(!s)throw this.invalidError();if(s.expiresAt<=(new Date).toISOString())throw this.records.delete(e),this.invalidError();if(t&&!t(s))throw this.invalidError();return s}invalidError(){const e=new Error(this.options.invalidMessage);return Object.assign(e,{statusCode:this.options.invalidStatusCode||400,code:this.options.invalidCode}),e}}class qs{requireInstance;listAppSessions;tokens=new $s({invalidMessage:"App access link is invalid or expired.",invalidCode:"APP_ACCESS_TOKEN_INVALID",invalidStatusCode:401,tokenBytes:24,ttlMs:9e5});constructor(e){this.requireInstance=e.requireInstance,this.listAppSessions=e.listAppSessions}createToken(e){return this.tokens.create({instanceId:e.instanceId,sessionId:e.sessionId,mode:e.mode,ttlMs:e.ttlMs})}resolveToken(e,t){return this.tokens.resolve(e,e=>!t||e.mode===t)}async proxyTarget(e,t,s=""){const n=this.resolveToken(e,t),i=await this.requireInstance(n.instanceId),a=await this.listAppSessions(),r=a.instances.find(e=>e.instanceId===n.instanceId)?.appSessions.sessions.find(e=>Bs(e.id)===n.sessionId);if(!r){const e=new Error("App session was not found.");throw Object.assign(e,{statusCode:404,code:"APP_SESSION_NOT_FOUND"}),e}return{access:n,instance:i,session:r,path:Ls(t,n.sessionId,r,s)}}}function Ls(e,t,s,n=""){return"tty"===e?`/api/apps/sessions/${encodeURIComponent(t)}/tty`:"vnc"===e?n?function(e,t){const s=`/${t.replace(/^\/+/,"")}`,n=s.split("?",1)[0]||"/",i=encodeURIComponent(e);if(!["/api/novnc/",`/api/apps/sessions/${i}/web/`,`/api/apps/sessions/${i}/vnc`,`/api/apps/sessions/${i}/novnc/`].some(e=>n===e.replace(/\/$/,"")||n.startsWith(e))){const e=new Error("App access token cannot be used for this path.");throw Object.assign(e,{statusCode:403,code:"APP_ACCESS_PATH_FORBIDDEN"}),e}return s}(t,n):Bs(Us(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Bs(Us(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Us(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Bs(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Fs{tokens=new $s({invalidMessage:"Chat action expired.",invalidCode:"CHAT_ACTION_TOKEN_INVALID",tokenBytes:9,ttlMs:6e5});create(e){return this.tokens.create({type:e.type,instanceId:"instanceId"in e?e.instanceId:void 0,appId:"appId"in e?e.appId:void 0,routeId:"routeId"in e?e.routeId:void 0,decision:"decision"in e?e.decision:void 0,ttlMs:e.ttlMs})}resolve(e,t){return this.tokens.resolve(e,e=>!t||e.type===t)}pendingDecisionCallbackData(e,t){return`task_handoff:cp_p:${this.create({type:"pending-decision",routeId:e,decision:t}).token}`}}function Hs(e){const{token:t,...s}=e;return{...s,settings:Ws(e.settings),tokenSet:Boolean(e.token)}}function Gs(e,t){if(!t)return e;const s={...e};for(const[e,n]of Object.entries(t))void 0!==n&&""!==n&&(s[e]=n);return s}function Ws(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}class Vs{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(Hs)}require(e){const t=this.chatBridges.get(e);return t||os("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=Os.parse(e),s=ns(),n=Ye.parse({...t,id:pt(`chat_${t.channel}`),name:t.name||(i=t.channel,a=this.chatBridges.list().filter(e=>e.channel===t.channel).length+1,`${"telegram"===i?"Telegram":"wechat"===i?"WeChat":"dingding"===i?"DingDing":"Web"} ${a}`),enabled:t.enabled??!1,allowedUserIds:t.allowedUserIds||[],pollIntervalMs:t.pollIntervalMs||3e3,settings:Gs({},t.settings),createdAt:s,updatedAt:s});var i,a;return this.chatBridges.put(n),Hs(n)}update(e,t){const s=zs.parse(t),n=this.require(e),i=Gs(n.settings,s.settings),a=Ye.parse({...n,...s,id:e,channel:n.channel,name:s.name||n.name,settings:i,updatedAt:ns()});return this.chatBridges.put(a),Hs(a)}delete(e){const t=this.chatBridges.delete(e);if(t)for(const t of this.chatSessions.list())t.bridgeId===e&&this.chatSessions.delete(t.id);return t}}class Ks{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||os("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=ns(),s=(n=e.channel,i=e.chatSessionId,`${e.bridgeId||n}:${i.replace(/[^a-zA-Z0-9_.:-]/g,"_")}`);var n,i;const a=this.chatSessions.get(s),r=Xe.parse({...a||{},...e,id:s,channel:e.channel,bridgeId:e.bridgeId,chatSessionId:e.chatSessionId,lastUsedAt:t,createdAt:a?.createdAt||t,updatedAt:t});return this.chatSessions.put(r),r}}function Js(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function Qs(e,t=40){return Js(e.slice(0,t).map(e=>[e]))}function Ys(e){return Buffer.from(e,"utf8").toString("base64url")}function Xs(e){const t=en(e).inline_keyboard,s=Array.isArray(t)?t:[],n=[];for(const e of s)if(Array.isArray(e))for(const t of e){const e=en(t),s=String(e.callback_data||""),i=String(e.text||"").trim();i&&s&&n.push({text:i,id:`th_cb_${Ys(s)}`})}return n.slice(0,40)}function Zs(e,t){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function en(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const tn=new Set(["stopped","failed","exited","closed","terminated"]);function sn(e){return!function(e){return tn.has(Wt(e))}(e)}function nn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const s=(e.apps?.sessions||[]).filter(ln);if(t.appSessionId&&s.some(e=>e.id===t.appSessionId))return!0;const n=new Set(t.appBindingKeys||[]);return Boolean(n.size&&s.some(e=>function(e){return[...Gt(e).bindings.map(Vt),...Jt(e)].filter(Boolean)}(e).some(e=>n.has(e))))}(e,t))}function an(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function rn(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function on(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=t?` - ${Zs(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${s}`}function dn(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return Qs(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Zs(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Zs(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Zs(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function cn(e){const t=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,s="approval"===e.kind?`/approve ${e.id} | /deny ${e.id} | /skip ${e.id}`:`/reply ${e.id} <message>`;return`[${t}] ${e.kind} ${e.id}: ${e.result}\n${s}`}function ln(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return sn("string"==typeof t.status?t.status:void 0)}class pn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleAiSessionsCommand(e){const t=await this.targets.aiSessionChoices(e),s=t.find(e=>e.active),n=t.map(e=>function(e){const t=e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=e.active?"* ":"",n=e.session.phase&&"unknown"!==e.session.phase?`/${e.session.phase}`:"";return`${s}${e.index+1}. ${e.projectName} / ${e.instance.name} - ${e.session.agent} - ${e.session.status}${n} - ${e.session.id}${t?` - ${t}`:""}`}(e));return{accepted:!0,routed:!1,binding:e,instances:t.map(e=>e.instance),reply:t.length?["Select AI session",`Current ${s?`${s.projectName} / ${s.instance.name} / ${s.session.id}`:await this.targets.renderChatTarget(e)}, total ${t.length}`,"",...n].join("\n"):"No AI sessions found.",replyMarkup:t.length?Qs(t.map(e=>({text:`${e.active?"✓ ":""}${on(e)}`,callbackData:`task_handoff:cp_session:${e.index}`}))):void 0}}async handleAiSessionCommand(e,t){const s=String(t[0]||"").trim();if(!s)return this.handleAiSessionsCommand(e);const n=await this.targets.findAiSession(s);if(!n)return{accepted:!1,routed:!1,binding:e,reply:`AI session ${s} was not found. Try /sessions.`};const i=n.instance.projectId?this.deps.getProject(n.instance.projectId):void 0;return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i?.id||e.activeProjectId,activeInstanceId:n.instance.id,activeAiSessionId:n.session.id}),instance:n.instance,aiSession:n.session,reply:`Active AI session: ${n.instance.name} / ${n.session.agent} ${n.session.id}`}}async handleAiSessionIndexAction(e,t){const s=(await this.targets.aiSessionChoices(e)).find(e=>e.index===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"AI session was not found. Use /session to refresh the list.",replyMarkup:void 0};const n=this.deps.upsertChatSession({...e,activeProjectId:s.projectId||e.activeProjectId,activeInstanceId:s.instance.id,activeAiSessionId:s.session.id}),i=await this.handleAiSessionsCommand(n);return{...i,accepted:!0,binding:n,instance:s.instance,aiSession:s.session,message:`selected ${s.session.id}`,reply:[`Current chat is bound to AI session ${s.session.id}`,"",i.reply].join("\n")}}async handleAiSessionInterruptCommand(e){if(!e.activeInstanceId||!e.activeAiSessionId)return{accepted:!1,routed:!1,binding:e,reply:"No active AI session is selected. Use /sessions and /session <id> first."};const t=await this.deps.requireControlledInstance(e.activeInstanceId,!0),s=await this.deps.interruptAiSession(t.id,e.activeAiSessionId);return{accepted:!0,routed:!0,binding:e,instance:us(t),aiSession:s,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function un(e){return e.providerTurnId||e.turnId||""}function gn(e){return sn(yn(e.status))}function hn(e,t){const s=yn(t.appId)||"App",n=yn(t.title).trim();if(n&&n!==s)return n;const i=function(e,t){for(const s of function(e){const t=e.apps;return Array.isArray(t)?t.filter(e=>"object"==typeof e&&null!==e&&!Array.isArray(e)):[]}(e.sourceSnapshot))if(yn(s.id)===t)return yn(s.name).trim();return""}(e,s);return i||s}function mn(e){const t=function(e){const t=wn(e.capabilities).apps;if(Array.isArray(t)&&t.length>0)return t;const s=wn(e.image).optionalApps;return Array.isArray(s)&&s.length>0?s:["terminal-tty"]}(e),s=new Map;for(const e of t){const t=bn(e);t&&!s.has(t.id)&&s.set(t.id,t)}return[...s.values()]}function In(e,t,s,n){const i=yn(t.id)?function(e,t){return`/apps/access/${fn(e)}?token=${encodeURIComponent(t())}`}(t,n):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const s=yn(t);if(s&&new RegExp("^https?://","i").test(s))return new URL(e,s.endsWith("/")?s:s+"/").toString()}(i,s);return{text:a||i,url:a}}function fn(e){const t=yn(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function yn(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function bn(e){if("string"==typeof e&&e.trim())return{id:e,label:vn(e)};const t=wn(e),s=yn(t.id).trim();return s?{id:s,label:yn(t.name).trim()||vn(s)}:void 0}function vn(e){return{"terminal-tty":"Terminal","gui-terminal":"GUI Terminal",chromium:"Chromium",browser:"Browser","vscode-web":"VS Code"}[e]||e.replace(/[-_]+/g," ").replace(/\b\w/g,e=>e.toUpperCase())}function wn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}class Sn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleInstancesCommand(e){const t=await this.targets.instancesForBinding(e);return{accepted:!0,routed:!1,binding:e,instances:t,reply:(s=t.length,0===s?"No controlled instances are registered yet.":`Instances: ${s}. Tap an instance to create an app.`),replyMarkup:t.length?Qs(t.map(e=>{return{text:Zs(e.name||e.id,56),callbackData:(t=this.deps.createChatActionToken({type:"instance-app-menu",instanceId:e.id}).token,`task_handoff:cp_i:${t}`)};var t})):void 0};var s}async handleInstanceAppMenuAction(e,t){const s=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const n=mn(s);return{accepted:!0,routed:!1,binding:e,instance:s,message:`Select an app for ${s.name}.`,reply:n.length?`New app for ${s.name}`:`No launchable apps for ${s.name}.`,replyMarkup:n.length?Qs(n.map(e=>{return{text:Zs(e.label,56),callbackData:(t=this.deps.createChatActionToken({type:"launch-app",instanceId:s.id,appId:e.id}).token,`task_handoff:cp_a:${t}`)};var t})):void 0}}async handleLaunchAppAction(e,t,s){const n=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const i=mn(n).find(e=>e.id===s);return i?{accepted:!0,routed:!1,binding:e,instance:n,session:await this.deps.launchAppSession(n.id,i.id),message:`${i.label} launched`,reply:`Launched ${i.label} on ${n.name}.`}:{accepted:!1,routed:!1,binding:e,reply:`App ${s} is not launchable for ${n.name}.`}}async handleAppsCommand(e){const t=await this.deps.boardAsync(),s=new Map(t.map(e=>[e.id,e])),n=(await this.deps.listAppSessions()).instances.flatMap(e=>{const t=s.get(e.instanceId);return t?e.appSessions.sessions.filter(gn).filter(e=>Boolean(yn(e.id))).map(e=>({instance:t,session:e,link:In(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:yn(e.id)||"",mode:fn(e)}).token)})):[]}),i=n.filter(e=>e.link.url).map(e=>({text:Zs(`${e.instance.name} · ${hn(e.instance,e.session)}`,56),url:e.link.url}));return{accepted:!0,routed:!1,binding:e,instances:t,reply:(a=n.length,r=i.length,0===a?"No app sessions found.":0===r?`App sessions: ${a}. Set the control plane public URL in Settings > Appearance to open them from chat.`:`App sessions: ${a}. Tap a button to open.`),replyMarkup:i.length?Qs(i):void 0};var a,r}}class An{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async forwardChatMessage(e,t){const s=t.target?.instanceId||e.activeInstanceId||"",n=t.target?.aiSessionId||e.activeAiSessionId,i=await this.deps.requireControlledInstance(s,!0),a=i.projectId?this.deps.requireProject(i.projectId):void 0;if("online"!==i.connectionStatus){const e=new Error(`Instance ${i.name} is not reachable.`);throw Object.assign(e,{statusCode:409,code:"CHAT_TARGET_UNREACHABLE"}),e}if(n){const s=await this.trySendAiSessionMessage(e,i.id,n,t.message.text,t.message.attachments,!t.target);return s.ok?this.sentResult(e,a,i,n,s.aiSession):this.failedSendResult(s.binding,a,i,s.reply)}const r=await this.targets.defaultAiSessionIdForInstance(i.id);if(r){const s=this.deps.upsertChatSession({...e,activeAiSessionId:r}),n=await this.trySendAiSessionMessage(s,i.id,r,t.message.text,t.message.attachments);return n.ok?this.sentResult(s,a,i,r,n.aiSession):this.failedSendResult(n.binding,a,i,n.reply)}return{accepted:!1,routed:!1,binding:e,project:a||void 0,instance:us(i),reply:`No active AI session is selected for ${i.name}. Use /sessions and /session <id> first.`}}async trySendAiSessionMessage(e,t,s,n,i=[],a=!0){try{return{ok:!0,binding:e,aiSession:await this.deps.sendAiSessionMessage(t,s,n||"请查看附件图片。",void 0,i)}}catch(t){return function(e){return"AI_SESSION_NOT_FOUND"===(e&&"object"==typeof e?e:{}).code}(t)?{ok:!1,binding:a?this.deps.upsertChatSession({...e,activeAiSessionId:void 0}):e,reply:`The selected AI session ${s} no longer exists. Use /session to choose an active AI session.`}:{ok:!1,binding:e,reply:`Failed to send to AI session ${s}: ${rs(t)}`}}}failedSendResult(e,t,s,n){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:us(s),reply:n}}sentResult(e,t,s,n,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:us(s),aiSession:i,instanceId:s.id,aiSessionId:n,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:un(i),reply:`Sent to ${t?.name||s.sourceSnapshot.name||"Source"} / ${s.name} / ${n}.`};var a}}class kn{deps;constructor(e){this.deps=e}async handlePendingCommand(e){const t=await this.deps.listPendingRoutes(),s=e.activeInstanceId?t.filter(t=>t.instanceId===e.activeInstanceId):t;return{accepted:!0,routed:!1,binding:e,pending:s,reply:s.length?s.map(cn).join("\n"):"No pending routes.",replyMarkup:s.length?dn(s,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,s){const n=as(t[0]);return n?n.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:n,aiSession:await this.deps.resolveAiSessionApproval(n.instanceId,n.aiSessionId,s),reply:`${s} sent for ${n.instanceId}:${n.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:n,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:`Usage: /${"allow"===s?"approve":s} <instance-id:pending-id>`}}async handlePendingReplyCommand(e,t){const s=as(t[0]),n=t.slice(1).join(" ").trim();return s&&n?s.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:s,aiSession:await this.deps.sendAiSessionMessage(s.instanceId,s.aiSessionId,n),reply:`Reply sent for ${s.instanceId}:${s.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:s,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:"Usage: /reply <instance-id:pending-id> <message>"}}}class Tn{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleUseCommand(e,t){if(0===t.length)return{accepted:!0,routed:!1,binding:e,instances:await this.targets.instancesForBinding(e),reply:await this.targets.renderUseHelp(e)};if(t.length>=2){const s=this.targets.resolveProject(t[0]),n=await this.targets.resolveInstance(t[1],s.id),i=this.deps.upsertChatSession({...e,activeProjectId:s.id,activeInstanceId:n.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(n.id)});return{accepted:!0,routed:!1,binding:i,reply:await this.targets.renderTargetSelected(i)}}const s=t[0],n=await this.targets.findInstance(s);if(n){const t=n.projectId?this.deps.requireProject(n.projectId):void 0,s=this.deps.upsertChatSession({...e,activeProjectId:t?.id,activeInstanceId:n.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(n.id)});return{accepted:!0,routed:!1,binding:s,reply:await this.targets.renderTargetSelected(s)}}const i=this.targets.resolveProject(s),a=(await this.deps.boardAsync()).filter(e=>e.projectId===i.id),r=a.filter(e=>"running"===e.status&&"online"===e.connectionStatus);if(1===r.length){const t=this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:r[0].id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(r[0].id)});return{accepted:!0,routed:!1,binding:t,reply:await this.targets.renderTargetSelected(t)}}return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:void 0,activeAiSessionId:void 0}),instances:a,reply:`${i.name} selected. Choose an instance with /use ${i.id} <instance-id-or-name>.\n${this.targets.renderInstanceLines(a)}`}}async handleProjectCommand(e,t){if(0===t.length)return{accepted:!1,routed:!1,binding:e,reply:"Usage: /project <project-id-or-name>"};const s=this.targets.resolveProject(t[0]),n=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,i=this.deps.upsertChatSession({...e,activeProjectId:s.id,activeInstanceId:n?.projectId===s.id?n.id:void 0,activeAiSessionId:n?.projectId===s.id?e.activeAiSessionId:void 0});return{accepted:!0,routed:!1,binding:i,reply:`Active project: ${s.name}${i.activeInstanceId?"":". Choose an instance with /use."}`}}}class En{deps;constructor(e){this.deps=e}async instancesForBinding(e){const t=await this.deps.boardAsync();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async fullInstancesForBinding(e){const t=await this.deps.listNodeInstances();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async renderUseHelp(e){return`Active target: ${await this.renderChatTarget(e)}\nUse /instances to list targets, then /use <instance-id-or-name>. Use /sessions to choose an AI session when there is more than one.`}async renderChatStatus(e){const t=e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0;return t?`Active target: ${await this.renderChatTarget(e)}\nStatus: ${t.status}, connection: ${t.connectionStatus}, pending: ${t.receiver.pendingCount}`:`Active target: ${await this.renderChatTarget(e)}`}async renderChatHelp(e){return["TaskHandoff chat commands:","/help - show this help","/status or /target - show the active project, instance, and AI session","/instances - list controlled instances","/apps - list app sessions","/use <instance-id-or-name> - choose an active instance","/use <project-id-or-name> <instance-id-or-name> - choose a project and instance","/project <project-id-or-name> - narrow instance selection to a project","/sessions - list all AI sessions","/session <ai-session-id> - choose an active AI session","/pending - list waiting approvals","/approve <instance-id:pending-id>, /deny <...>, /skip <...> - answer approvals","/reply <instance-id:pending-id> <message> - reply to a waiting session","/cancel - interrupt the active AI session","","Send normal text after selecting a target; if there is only one AI session, it is selected automatically.",`Current target: ${await this.renderChatTarget(e)}`].join("\n")}renderInstanceLines(e){return e.map(e=>`${e.name} (${e.id}) - ${e.status}/${e.connectionStatus}`).join("\n")}async renderChatTarget(e){const t=e.activeProjectId?this.deps.getProject(e.activeProjectId):void 0,s=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,n=e.activeAiSessionId||"none";return`${t?.name||e.activeProjectId||"none"} / ${s?.name||e.activeInstanceId||"none"} / ${n}`}async renderTargetSelected(e){const t=await this.renderChatTarget(e);return e.activeAiSessionId?`Active target: ${t}`:`Active target: ${t}\nNo AI session selected yet. Use /sessions and /session <id>, or send a message once exactly one session is available.`}async defaultAiSessionIdForInstance(e){const t=await this.aiSessionSnapshotForInstance(e),s=(await this.deps.boardAsync()).find(t=>t.id===e),n=(await this.deps.listAppSessions()).instances.find(t=>t.instanceId===e),i=s?{...s,apps:{...s.apps,sessions:n?.appSessions.sessions||[]}}:void 0,a=t&&s?nn({...i,aiSessions:t}):t?.sessions||[];return 1===a.length?a[0].id:void 0}async aiSessionChoices(e){return(await this.boardInstancesForBinding({},{refresh:!0})).flatMap(t=>nn(t).map(s=>({index:0,projectId:t.projectId,projectName:t.project?.name||("string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:"Source"),instance:t,session:s,active:e.activeInstanceId===t.id&&e.activeAiSessionId===s.id}))).sort((e,t)=>{const s=an(t.session)-an(e.session);return s||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||rn(e.session).localeCompare(rn(t.session)))}).map((e,t)=>({...e,index:t}))}async boardInstancesForBinding(e,t={}){const s=await this.deps.boardAsync(),n=await this.deps.listAiSessions(t),i=await this.deps.listAppSessions(t),a=new Map(n.instances.map(e=>[e.instanceId,e.aiSessions])),r=new Map(i.instances.map(e=>[e.instanceId,e.appSessions])),o=s.map(e=>{return{...e,apps:{...e.apps,sessions:r.get(e.id)?.sessions||[]},aiSessions:a.get(e.id)||(t=e.aiSessions.updatedAt,{runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:t})};var t});return e.activeProjectId?o.filter(t=>t.projectId===e.activeProjectId):o}async findAiSession(e){const t=await this.boardInstancesForBinding({},{refresh:!0});for(const s of t){const t=(s.aiSessions?.sessions||[]).find(t=>t.id===e||t.providerSessionId===e);if(t)return{instance:s,session:t}}}async aiSessionSnapshotForInstance(e){const t=await this.deps.listAiSessions();return t.instances.find(t=>t.instanceId===e)?.aiSessions}resolveProject(e){const t=this.deps.getProject(e)||this.deps.listProjects().find(t=>t.name===e);return t||os("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const s=await this.findInstance(e,t);return s||os("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`),s}async findInstance(e,t){return(await this.deps.listNodeInstances()).find(s=>!(s.id!==e&&s.name!==e||t&&s.projectId!==t))}}class _n{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new En(e),this.sender=new An(e,this.targets),this.appCommands=new Sn(e,this.targets),this.pendingCommands=new kn(e),this.aiSessionCommands=new pn(e,this.targets),this.targetCommands=new Tn(e,this.targets)}async handleChatGatewayMessage(e){const t=Ze.parse(e),s=this.deps.upsertChatSession({channel:t.source.channel,bridgeId:t.source.bridgeId,chatSessionId:t.source.chatSessionId,userId:t.source.userId}),n=t.message.text.trim();return n.startsWith("/")?this.handleChatCommand(s,n):n||t.message.attachments.length?s.activeInstanceId?this.sender.forwardChatMessage(s,t):{accepted:!1,routed:!1,binding:s,reply:await this.targets.renderUseHelp(s)}:{accepted:!1,routed:!1,binding:s,reply:""}}async handleChatGatewayAction(e){const t=this.deps.upsertChatSession({channel:e.source.channel,bridgeId:e.source.bridgeId,chatSessionId:e.source.chatSessionId,userId:e.source.userId});return"ai-session"===e.action.type?this.aiSessionCommands.handleAiSessionIndexAction(t,e.action.index):"instance-app-menu"===e.action.type?this.appCommands.handleInstanceAppMenuAction(t,e.action.instanceId):"launch-app"===e.action.type?this.appCommands.handleLaunchAppAction(t,e.action.instanceId,e.action.appId):"pending-decision"===e.action.type?this.pendingCommands.handlePendingDecisionCommand(t,[e.action.routeId],e.action.decision):{accepted:!1,routed:!1,binding:t,reply:"Unknown action."}}async handleChatCommand(e,t){const[s="",...n]=t.split(/\s+/),i=s.replace(/@[\w_]+$/,"");switch(i.toLowerCase()){case"/help":case"/start":return{accepted:!0,routed:!1,binding:e,reply:await this.targets.renderChatHelp(e)};case"/sessions":return this.aiSessionCommands.handleAiSessionsCommand(e);case"/session":return this.aiSessionCommands.handleAiSessionCommand(e,n);case"/cancel":case"/interrupt":return this.aiSessionCommands.handleAiSessionInterruptCommand(e);case"/pending":return this.pendingCommands.handlePendingCommand(e);case"/approve":return this.pendingCommands.handlePendingDecisionCommand(e,n,"allow");case"/deny":return this.pendingCommands.handlePendingDecisionCommand(e,n,"deny");case"/skip":return this.pendingCommands.handlePendingDecisionCommand(e,n,"skip");case"/reply":return this.pendingCommands.handlePendingReplyCommand(e,n);case"/instances":return this.appCommands.handleInstancesCommand(e);case"/apps":return this.appCommands.handleAppsCommand(e);case"/use":return this.targetCommands.handleUseCommand(e,n);case"/project":return this.targetCommands.handleProjectCommand(e,n);case"/status":case"/target":return{accepted:!0,routed:!1,binding:e,instance:e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0,reply:await this.targets.renderChatStatus(e)};default:return{accepted:!1,routed:!1,binding:e,reply:`Unknown command: ${i||s}. Try /help.`}}}}function Cn(e,t){const s=t.filter(e=>e.enabled),n={},i=zn(s,"codex",e.modelSelection.codexModelId);i&&(n.OPENAI_API_KEY=i.key,n.OPENAI_BASE_URL=i.endpoint,n.CODEX_MODEL=i.model,n.TASK_HANDOFF_CODEX_MODEL=i.model,n.TASK_HANDOFF_CODEX_MODEL_ID=i.id);const a=zn(s,"claude",e.modelSelection.claudeModelId);return a&&(n.ANTHROPIC_API_KEY=a.key,n.ANTHROPIC_BASE_URL=a.endpoint,n.CLAUDE_MODEL=a.model,n.TASK_HANDOFF_CLAUDE_MODEL=a.model,n.TASK_HANDOFF_CLAUDE_MODEL_ID=a.id),n}function Nn(e){const t=e.modelSelection;if(t&&"object"==typeof t&&!Array.isArray(t)){const e=t;return{..."string"==typeof e.codexModelId?{codexModelId:e.codexModelId}:{},..."string"==typeof e.claudeModelId?{claudeModelId:e.claudeModelId}:{}}}return{}}function zn(e,t,s){const n=e.filter(e=>e.app===t);return n.find(e=>s&&e.id===s)||n[0]}function On(e){return/^[a-z]:[\\/]/i.test(e)||/^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/.test(e)}function Rn(e){return On(e)?s.win32:s.posix}function xn(e){return{enabledCount:e.deployments.filter(e=>e.enabled).length,runningCount:e.runtime.filter(e=>"running"===e.status).length,errorCount:e.runtime.filter(e=>"error"===e.status).length,configs:e.configs.map(t=>({configHash:t.configHash,config:t,deployments:e.deployments.filter(e=>e.configHash===t.configHash),runtime:e.runtime.filter(e=>e.configHash===t.configHash)})),recentRuns:e.recentRuns.slice(0,20)}}const Dn="TASK_HANDOFF_NODE_AGENT_V1",jn=6e4;function Pn(e=""){return o.createHash("sha256").update(e).digest("hex")}function Mn(e,t){return o.createHmac("sha256",e).update(function(e){return[Dn,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function $n(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||o.randomBytes(18).toString("base64url"),n=Pn(e.body||""),i=Mn(e.secret,{keyId:e.keyId,method:e.method,pathWithQuery:e.pathWithQuery,timestamp:t,nonce:s,bodySha256:n});return{"x-taskhandoff-node-id":e.nodeId,"x-taskhandoff-key-id":e.keyId,"x-taskhandoff-timestamp":t,"x-taskhandoff-nonce":s,"x-taskhandoff-body-sha256":n,"x-taskhandoff-signature":i}}function qn(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function Ln(e,s=process.platform){if(!function(e,s=process.platform){if("win32"===s)return e.startsWith("\\\\.\\pipe\\");const n=t.statSync(e),i=process.getuid?.();return void 0===i||n.uid===i}(e,s)){const e=new Error("Local node agent IPC socket is not owned by the current user.");throw Object.assign(e,{statusCode:403,code:"NODE_AGENT_IPC_OWNER_INVALID"}),e}}function Un(e,t,s,n={}){const i=`/api/node-agent${t}`,a={createConnection:()=>g.connect(e),headers:{host:"task-handoff-node-agent.local",...n}};return s&&(Array.isArray(s)?s.length>0:s.trim().length>0)?new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,s,a):new p.WebSocket(`ws://task-handoff-node-agent.local${i}`,a)}const Bn=r.z.object({id:r.z.string().trim().min(1),tokenHash:r.z.string().trim().min(1),expiresAt:r.z.string().datetime(),nodeName:r.z.string().trim().min(1).max(160).optional(),createdAt:r.z.string().datetime(),updatedAt:r.z.string().datetime()}).strict(),Fn="task-handoff.control-plane.local",Hn="task-handoff.control-plane.builtin";function Gn(e){return"true"===e.labels[Fn]}function Wn(e){return"true"===e.labels[Hn]}function Vn(e){const t=function(e){return"paired-hmac"===e.auth.mode?e.auth.keyId:void 0}(e);if(!t){const t=new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`);throw Object.assign(t,{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"}),t}return t}function Kn(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return $n({nodeId:e.id,keyId:Vn(e),secret:s,method:t.method,pathWithQuery:t.pathWithQuery,body:t.body});const n=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return n?{authorization:`Bearer ${n}`}:{}}class Jn{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new Fs;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new hs;controlPlaneTriggerService;chatBridgeService;chatSessionService;chatSessionRuntime;aiSessionSnapshotProvider;appSessionSnapshotProvider;constructor(e,t={}){var s;this.paths=e,this.fetchImpl=t.fetchImpl||fetch,this.dockerCommandRunner=t.dockerCommandRunner,this.nodeAgentTransport=t.nodeAgentTransport,this.logger=(s=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(s||"").trim().toLowerCase())?t.logger:void 0);const n=new ts({request:(e,t,s)=>this.nodeAgentFetch(e,t,s),logger:this.logger});this.nodeAgentGateway=new ss(n),this.controlledInstanceGateway=new ds({requireNode:e=>this.requireNode(e),nodeAgentGateway:this.nodeAgentGateway,nodeAgentRequest:(e,t,s)=>this.nodeAgentFetch(e,t,s),fetchImpl:this.fetchImpl});const i=e=>({schema:e,logger:(e,t)=>this.logWarn(t,e)});this.projects=new gt(e.projectsDir,i(Oe)),this.models=new gt(e.modelsDir,i(Re)),this.images=new gt(e.imagesDir,i(xe)),this.nodes=new gt(e.nodesDir,i(De)),this.chatSessions=new gt(e.chatSessionsDir,i(Xe)),this.chatBridges=new gt(e.chatBridgesDir,i(Ye)),this.triggers=new gt(e.triggersDir,i(Rs)),this.nodeJoinInvites=new gt(e.nodeJoinInvitesDir,i(Bn)),this.settings=new ht(e.settingsPath,()=>vs.parse({}),i(vs)),this.controlPlaneTriggerService=new Ps({triggers:this.triggers,listInstances:()=>this.listNodeInstances(),requireInstance:async e=>this.requireControlledInstance(e,!0),instanceRequest:(e,t,s)=>this.instanceRequest(e,t,s)}),this.appAccessService=new qs({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Vs({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new Ks({chatSessions:this.chatSessions}),this.chatSessionRuntime=new _n({upsertChatSession:e=>this.upsertChatSession(e),boardAsync:()=>this.boardAsync(),listAiSessions:e=>this.listAiSessions(e),listAppSessions:e=>this.listAppSessions(e),listNodeInstances:()=>this.listNodeInstances(),listPendingRoutes:()=>this.listPendingRoutes(),pendingDecisionCallbackData:(e,t)=>this.pendingDecisionCallbackData(e,t),createChatActionToken:e=>this.createChatActionToken(e),createAppAccessToken:e=>this.createAppAccessToken(e),controlPlanePublicBaseUrl:()=>this.getSettings().publicBaseUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_URL||process.env.TASK_HANDOFF_PUBLIC_BASE||process.env.TASK_HANDOFF_CONTROL_PLANE_PUBLIC_URL,requireControlledInstance:(e,t)=>this.requireControlledInstance(e,t),requireProject:e=>this.requireProject(e),getProject:e=>this.projects.get(e),listProjects:()=>this.projects.list(),resolveAiSessionApproval:(e,t,s)=>this.resolveAiSessionApproval(e,t,s),sendAiSessionMessage:(e,t,s,n,i)=>this.sendAiSessionMessage(e,t,s,n,i),interruptAiSession:(e,t)=>this.interruptAiSession(e,t),launchAppSession:(e,t,s)=>this.launchAppSession(e,t,s)})}logInfo(e,t){this.logger?.info?.(e,t)}logWarn(e,t){this.logger?.warn?.(e,t)}logError(e,t){this.logger?.error?.(e,t)}setNodeAgentTransport(e){this.nodeAgentTransport=e}setAiSessionSnapshotProvider(e){this.aiSessionSnapshotProvider=e}async listAiSessions(e={}){return this.aiSessionSnapshotProvider?this.aiSessionSnapshotProvider(e):this.bootstrapAiSessionsFromInstances()}setAppSessionSnapshotProvider(e){this.appSessionSnapshotProvider=e}async listAppSessions(e={}){return this.appSessionSnapshotProvider?this.appSessionSnapshotProvider(e):this.bootstrapAppSessionsFromInstances()}init(){this.projects.init(),this.models.init(),this.images.init(),this.nodes.init(),this.chatSessions.init(),this.chatBridges.init(),this.triggers.init(),this.nodeJoinInvites.init(),this.settings.init(),this.seedDefaults()}getSettings(){return vs.parse(this.settings.get())}updateSettings(e){const t=ws.parse(e),s=vs.parse({...this.getSettings(),...t,..."publicBaseUrl"in t?{publicBaseUrl:t.publicBaseUrl?.trim()||void 0}:{}});return this.settings.put(s)}seedDefaults(){if(!this.images.get("img_default")){const e=ns();this.images.put(xe.parse({id:"img_default",name:"TaskHandoff Browser + Terminal",image:process.env.TASK_HANDOFF_CONTROLLED_IMAGE||"task-handoff-web:local",registry:"local",capabilities:["browser","terminal","gui-terminal","codex","claude"],optionalApps:["chromium","terminal-tty","gui-terminal","vscode-web"],defaultEnv:{},labels:{"task-handoff.image.kind":"controlled-instance"},createdAt:e,updatedAt:e}))}}listProjects(){return this.projects.list().map(e=>this.normalizeProjectRecord(e))}defaultNodeId(){return this.nodes.list().find(Gn)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?Kn(t,{method:"GET",pathWithQuery:`/api/node-agent${s}`}):{},i=await this.fetchNodeAgentEndpoint(e,s,{headers:n}),a=await i.json().catch(()=>({}));if(!i.ok){const t=new Error(a.error?.message||`Node agent health check failed with HTTP ${i.status}`);throw Object.assign(t,{statusCode:i.status,code:"NODE_AGENT_HEALTH_FAILED"}),this.logWarn({nodeEndpoint:e,statusCode:i.status,errorCode:"NODE_AGENT_HEALTH_FAILED"},"node agent health check failed"),t}const r="string"==typeof a.data?.nodeId&&a.data.nodeId.trim()?a.data.nodeId.trim():void 0;if(!r){const t=new Error("Node agent health response did not include nodeId.");throw Object.assign(t,{statusCode:502,code:"NODE_AGENT_ID_MISSING"}),this.logWarn({nodeEndpoint:e,payload:a.data,errorCode:"NODE_AGENT_ID_MISSING"},"node agent health response missing node id"),t}if(a.data?.protocolVersion!==ue){const t=function(e,t){const s="string"==typeof t&&t.trim()?t:"missing",n=new Error(`${e} protocol version ${s} is not compatible with ${ue}.`);return Object.assign(n,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),n}(`Node agent ${r}`,a.data?.protocolVersion);throw this.logWarn({nodeId:r,nodeEndpoint:e,expectedProtocolVersion:ue,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=ye.safeParse(a.data?.build);return this.logInfo({nodeId:r,nodeEndpoint:e,protocolVersion:a.data.protocolVersion,build:o.success?o.data:a.data?.build},"node agent health check ok"),{nodeId:r,data:a.data||{}}}async fetchNodeAgentEndpoint(e,t,s={}){const n=qn(e);return n?(Ln(n),async function(e,t,s={}){const n=s.method||"GET",i="string"==typeof s.body||s.body instanceof Buffer?s.body:void 0===s.body||null===s.body?void 0:String(s.body),a=new Headers(s.headers||{});return void 0===i||a.has("content-length")||a.set("content-length",String(Buffer.byteLength(i))),new Promise((s,r)=>{const o=u.request({socketPath:e,method:n,path:`/api/node-agent${t}`,headers:Object.fromEntries(a.entries())},e=>{const t=[];e.on("data",e=>t.push(Buffer.isBuffer(e)?e:Buffer.from(e))),e.on("end",()=>{s(new Response(Buffer.concat(t),{status:e.statusCode||500,headers:e.headers}))})});o.on("error",r),void 0!==i&&o.write(i),o.end()})}(n,t,s)):this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent${t}`,s)}async completeNodeAgentPairing(e,t){const s=await this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent/pairing/complete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({joinToken:t.joinToken,controlPlaneName:t.controlPlaneName||"Control Plane",controlPlaneUrl:t.controlPlaneUrl})}),n=await s.json().catch(()=>({}));if(!s.ok){const e=new Error(n.error?.message||`Node agent pairing failed with status ${s.status}.`);throw Object.assign(e,{statusCode:s.status,code:"NODE_AGENT_PAIRING_FAILED"}),e}if("string"!=typeof n.data?.nodeId||!n.data.nodeId.trim()||"string"!=typeof n.data?.keyId||!n.data.keyId.trim()||"string"!=typeof n.data?.secret||!n.data.secret.trim()){const e=new Error("Node agent pairing response did not include nodeId, keyId, and secret.");throw Object.assign(e,{statusCode:502,code:"NODE_AGENT_PAIRING_RESPONSE_INVALID"}),e}return{nodeId:n.data.nodeId.trim(),keyId:n.data.keyId.trim(),secret:n.data.secret.trim(),pairedAt:"string"==typeof n.data.pairedAt?n.data.pairedAt:ns()}}async syncLocalNodeConnection(){const e={[Fn]:"true",[Hn]:"true"},t=process.env.TASK_HANDOFF_NODE_AGENT_TOKEN?.trim()||void 0,s=process.env.TASK_HANDOFF_NODE_AGENT_CONTROL_ENDPOINT||process.env.TASK_HANDOFF_NODE_AGENT_ENDPOINT||"http://127.0.0.1:8091",n=function(e){return qn(e)?"local-ipc":"local-loopback"}(s),i=De.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:n,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:ns(),updatedAt:ns()}),a=await this.inspectNodeAgent(s,i),r=ns(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>Gn(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=De.parse({...o||{},id:a.nodeId,name:o?.name||"Local Node",connectionMode:n,auth:{mode:"local-static-key",secret:t},endpoint:s,controlEndpoint:s,containerEndpoint:process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL,publicWebBase:process.env.TASK_HANDOFF_INSTANCE_PUBLIC_WEB_BASE,status:"online",health:"ok",capabilities:{...o?.capabilities||{},agent:cs(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(gs)}listAllModels(){return this.models.list().map(e=>this.normalizeModelRecord(e)).sort((e,t)=>e.order-t.order||e.createdAt.localeCompare(t.createdAt)||e.id.localeCompare(t.id))}createModel(e){const t=Ss.parse(e),s=ns(),n=this.nextModelOrder(),i=Re.parse({...t,id:t.id||pt("model"),enabled:t.enabled??!0,order:t.order??n,labels:t.labels||{},createdAt:s,updatedAt:s});return gs(this.models.put(i))}updateModel(e,t){const s=As.parse(t),n=this.requireModelSecret(e),i=Re.parse({...n,...s,id:e,key:s.key?.trim()?s.key:n.key,createdAt:n.createdAt,updatedAt:ns()});return gs(this.models.put(i))}deleteModel(e){const t=this.models.delete(e);if(t)for(const t of this.listProjects())t.modelSelection.codexModelId!==e&&t.modelSelection.claudeModelId!==e||this.projects.put(Oe.parse({...t,modelSelection:{...t.modelSelection.codexModelId===e?{}:{codexModelId:t.modelSelection.codexModelId},...t.modelSelection.claudeModelId===e?{}:{claudeModelId:t.modelSelection.claudeModelId}},updatedAt:ns()}));return t}reorderModels(e){const t=[...new Set(e)],s=new Map(this.models.list().map(e=>[e.id,e]));for(const e of t)s.has(e)||os("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const n=s.get(e);this.models.put(Re.parse({...n,order:100*(t+1),updatedAt:ns()}))}),this.listModels()}createProject(e){const t=Is.parse(e),s=ns(),n=Ce.parse(t.source),i=this.validateProjectModelSelection(t.modelSelection||{}),a=Oe.parse({...t,id:t.id||pt("proj"),source:n,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",modelSelection:i,workspacePolicy:Ne.parse(t.workspacePolicy||ps(n)),labels:t.labels||{},createdAt:s,updatedAt:s});return this.projects.put(a)}updateProject(e,t){const s=fs.parse(t),n=this.requireProject(e),i=s.source?Ce.parse(s.source):n.source,a=s.modelSelection?this.validateProjectModelSelection(s.modelSelection):n.modelSelection,r=Oe.parse({...n,...s,id:e,source:i,modelSelection:a,workspacePolicy:s.workspacePolicy?Ne.parse(s.workspacePolicy):n.workspacePolicy,createdAt:n.createdAt,updatedAt:ns()});return this.projects.put(r)}deleteProject(e){return this.projects.delete(e)}listImages(){return this.images.list()}createImage(e){const t=ks.parse(e),s=ns(),n=xe.parse({...t,id:t.id||pt("img"),registry:t.registry||"local",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:s,updatedAt:s});return this.images.put(n)}async listNodeDockerImages(e){const t=this.requireNode(e);return this.nodeAgentGateway.listDockerImages(t)}async listNodeLocalFolders(e){const t=this.requireNode(e);return this.nodeAgentGateway.listLocalFolders(t)}async listNodeFolderTree(e,t={}){const s=this.requireNode(e);return this.nodeAgentGateway.listFolderTree(s,t)}async createNodeLocalFolder(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.createLocalFolder(s,t)}async deleteNodeLocalFolder(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteLocalFolder(s,t)}async checkNode(e){const t=this.requireNode(e);try{const e=await this.nodeAgentGateway.health(t),s=e&&"object"==typeof e?e:{},n="string"==typeof s.nodeId&&s.nodeId.trim()?s.nodeId.trim():t.id,i=De.parse({...t,id:n,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:cs(e)}:t.capabilities,lastSeenAt:ns(),updatedAt:ns()});return n!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:ns(),agent:e}}catch(e){const s=De.parse({...t,status:"offline",health:"failed",updatedAt:ns()});return this.nodes.put(s),{id:t.id,status:"offline",checkedAt:ns(),error:e instanceof Error?e.message:String(e)}}}async checkNodeUpdate(e,t){return this.nodeAgentGateway.checkUpdate(this.requireNode(e),t)}async applyNodeUpdate(e,t){const s=this.requireNode(e);if("controlled-instance"===t.target.component){const e=await this.requireNodeInstance(t.target.instanceId);if(e.nodeId!==s.id){const t=new Error(`Instance ${e.id} does not belong to node ${s.id}.`);throw Object.assign(t,{statusCode:400,code:"UPDATE_TARGET_NODE_MISMATCH"}),t}return this.nodeAgentGateway.applyUpdate(s,{...t,modelEnv:this.modelEnvForInstance(e)})}return this.nodeAgentGateway.applyUpdate(s,t)}async listNodeUpdateJobs(e){return this.nodeAgentGateway.listUpdateJobs(this.requireNode(e))}async getLocalNodeExternalListener(e){return this.nodeAgentGateway.getExternalListener(this.requireLocalListenerNode(e))}async updateLocalNodeExternalListener(e,t){return this.nodeAgentGateway.updateExternalListener(this.requireLocalListenerNode(e),t)}async createNodePairingInvite(e,t={}){const s=this.requireNode(e);return this.nodeAgentGateway.createPairingInvite(s,t)}requireLocalListenerNode(e){const t=this.requireNode(e);if(!Wn(t)||!Gn(t)){const e=new Error("Node agent TCP listener settings are available only for the built-in local node.");throw Object.assign(e,{statusCode:403,code:"LOCAL_NODE_LISTENER_ONLY"}),e}return t}pruneNodeJoinInvites(){const e=Date.now();for(const t of this.nodeJoinInvites.list())Date.parse(t.expiresAt)<=e&&this.nodeJoinInvites.delete(t.id)}createNodeJoinInvite(e={}){this.pruneNodeJoinInvites();const t=Ns.parse(e&&"object"==typeof e?e:{}),s=ns(),n=ut(),i={id:pt("node_join"),tokenHash:o.createHash("sha256").update(n).digest("hex"),expiresAt:new Date(Date.now()+(t.expiresInMs||6e5)).toISOString(),...t.nodeName?{nodeName:t.nodeName}:{},createdAt:s,updatedAt:s};return this.nodeJoinInvites.put(i),{id:i.id,joinToken:n,expiresAt:i.expiresAt}}completeNodeJoin(e){this.pruneNodeJoinInvites();const t=r.z.object({joinToken:r.z.string().trim().min(1).max(4096),nodeId:De.shape.id,nodeName:De.shape.name.optional(),keyId:r.z.string().trim().min(1).max(160),secret:r.z.string().trim().min(1).max(4096),pairedAt:r.z.string().datetime().optional()}).parse(e),s=o.createHash("sha256").update(t.joinToken).digest("hex"),n=this.nodeJoinInvites.list().find(e=>e.tokenHash===s&&Date.parse(e.expiresAt)>Date.now());if(!n){const e=new Error("Node join token is invalid or expired.");throw Object.assign(e,{statusCode:401,code:"NODE_JOIN_TOKEN_INVALID"}),e}this.nodeJoinInvites.delete(n.id);const i=ns();if(this.nodes.get(t.nodeId)){const e=new Error(`Node ${t.nodeId} already exists in this control-plane.`);throw Object.assign(e,{statusCode:409,code:"NODE_JOIN_NODE_ALREADY_EXISTS"}),e}const a=De.parse({id:t.nodeId,name:t.nodeName||n.nodeName||t.nodeId,connectionMode:"reverse-wss",auth:{mode:"paired-hmac",keyId:t.keyId,secret:t.secret,pairedAt:t.pairedAt||i,pairing:{status:"paired"}},status:"unknown",health:"unknown",capabilities:{},labels:{},createdAt:i,updatedAt:i});return this.nodes.put(a)}async connectNodeToControlPlane(e,t){const s=this.requireNode(e),n=Cs.parse(t);return this.nodeAgentGateway.connectRemote(s,n)}async listNodeRemoteControlPlanes(e){const t=this.requireNode(e);return this.nodeAgentGateway.listRemotes(t)}async deleteNodeRemoteControlPlane(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteRemote(s,t)}updateImage(e,t){const s=Ts.parse(t),n=this.requireImage(e),i=xe.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:ns()});return this.images.put(i)}deleteImage(e){return this.images.delete(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(ls)}async createNode(e){const t=Es.parse(e),s=ns(),n=t.connectionMode||"direct-http";let i="auth"in(e&&"object"==typeof e&&!Array.isArray(e)?e:{})?t.auth:{mode:"local-ipc"===n||"local-loopback"===n?"local-static-key":"paired-hmac"};if(("direct-http"===n||"reverse-wss"===n)&&"paired-hmac"!==i.mode){const e=new Error("Remote node connections require paired-HMAC authentication.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_REQUIRES_PAIRED_HMAC"}),e}const a=t.controlEndpoint||t.endpoint;if(!a&&"reverse-wss"!==n){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const r=t.joinToken&&a?await this.completeNodeAgentPairing(a,{joinToken:t.joinToken}):void 0;if(r&&(i={mode:"paired-hmac",keyId:r.keyId,secret:r.secret,pairedAt:r.pairedAt,pairing:{status:"paired"}}),("direct-http"===n||"reverse-wss"===n)&&!i.secret){const e=new Error("Remote node connections require a paired node secret or join token.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_SECRET_REQUIRED"}),e}if(("direct-http"===n||"reverse-wss"===n)&&!i.keyId){const e=new Error("Remote node connections require a paired key id.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_KEY_ID_REQUIRED"}),e}const o=t.id||r?.nodeId||pt("node"),d=De.parse({id:o,name:t.name,connectionMode:n,auth:i,labels:t.labels||{},createdAt:s,updatedAt:s}),c="reverse-wss"===n&&(t.id||r?.nodeId)?void 0:await this.inspectNodeAgent(a||"",d),l=t.id||r?.nodeId||c?.nodeId||o;if(!l){const e=new Error("Node id is required.");throw Object.assign(e,{statusCode:400,code:"NODE_ID_REQUIRED"}),e}if(t.id&&c?.nodeId&&c.nodeId!==t.id){const e=new Error(`Node agent id ${c.nodeId} does not match requested node id ${t.id}.`);throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ID_MISMATCH"}),e}const{joinToken:p,...u}=t,g=De.parse({...u,id:l,connectionMode:n,auth:i,endpoint:a,controlEndpoint:a,status:t.status||(c?"online":"unknown"),health:t.health||(c?"ok":"unknown"),capabilities:{...t.capabilities||{},...c?{agent:cs(c.data)}:{}},labels:t.labels||{},createdAt:s,updatedAt:s});return this.nodes.put(g)}updateNode(e,t){const s=_s.parse(t),n=this.requireNode(e),i=De.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:ns()});return this.nodes.put(i)}deleteNode(e){if(Wn(this.requireNode(e))){const e=new Error("The built-in local node connection cannot be deleted.");throw Object.assign(e,{statusCode:400,code:"LOCAL_NODE_CANNOT_BE_DELETED"}),e}return this.nodes.delete(e)}async listNodeRuntimes(e){const t=e?[this.requireNode(e)]:this.listNodes();return e?this.nodeAgentGateway.listRuntimes(t[0]):(await this.nodeAgentGateway.listFleetRuntimes(t)).items}async listNodeRuntimesWithDiagnostics(){return this.nodeAgentGateway.listFleetRuntimes(this.listNodes())}async createNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.createRuntime(s,t)}async updateNodeRuntime(e,t,s){const n=this.requireNode(e);return this.nodeAgentGateway.updateRuntime(n,t,s)}async deleteNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.deleteRuntime(s,t)}async checkNodeRuntime(e,t){const s=this.requireNode(e);return this.nodeAgentGateway.checkRuntime(s,t)}async listControlledInstances(){return(await this.listNodeInstances()).map(us)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(us),nodeErrors:e.nodeErrors}}async createControlledInstance(e){const t=ys.parse(e),s=t.projectId?this.requireProject(t.projectId):void 0,n=t.runtimeId||s?.defaultRuntimeId||"runtime_local_docker",i=t.nodeId||s?.defaultNodeId||this.defaultNodeId();if(!i){const e=new Error("At least one node connection is required before creating an instance.");throw Object.assign(e,{statusCode:400,code:"NODE_REQUIRED"}),e}const a=this.requireNode(i),r=await this.requireNodeRuntimeOnNode(a.id,n),o="boolean"==typeof r.capabilities.requiresImage?r.capabilities.requiresImage:"local"!==r.type,d=t.imageId||(o?s?.defaultImageId:void 0),c=d?this.requireImage(d):void 0;if(o&&!c){const e=new Error(`Runtime ${r.name} requires an image.`);throw Object.assign(e,{statusCode:400,code:"RUNTIME_IMAGE_REQUIRED"}),e}let l=t.source||s?.source,p=t.sourceSnapshot||s||{};if(!l){const e=new Error("Instance source is required.");throw Object.assign(e,{statusCode:400,code:"INSTANCE_SOURCE_REQUIRED"}),e}if("local"===r.type&&"local-folder"!==l.type){const e=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(e,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),e}if("local-folder"===l.type){if(l.ownerNodeId&&l.ownerNodeId!==a.id){const e=new Error(`Local folder ${l.path} belongs to node ${l.ownerNodeId}, not ${a.id}.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),e}if(l.localFolderId){const e=await this.requireNodeLocalFolder(a,l.localFolderId);l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}else{const e=await this.nodeAgentGateway.createLocalFolder(a,{name:"string"==typeof p.name?p.name:"Local folder",path:l.path,defaultImageId:c?.id,modelSelection:(u=p,u.modelSelection&&"object"==typeof u.modelSelection&&!Array.isArray(u.modelSelection)?u.modelSelection:{})});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}var u;const g=await this.nodeAgentGateway.createInstance(a,{id:t.id,name:t.name,runtimeId:n,...c?{imageId:c.id,image:c}:{},projectId:s?.id,source:l,sourceSnapshot:p,config:t.config,modelEnv:s?this.modelEnvForProject(s):this.modelEnvForSourceSnapshot(l,p)});return{...us(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const s=bs.parse(t),n=await this.requireNodeInstance(e),i=this.requireNode(n.nodeId);return us(await this.nodeAgentGateway.updateInstance(i,e,s))}async deleteControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId),n=await this.nodeAgentGateway.deleteInstance(s,e);return Boolean(n.deleted)}async startControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.startInstance(s,e,{modelEnv:this.modelEnvForInstance(t)}))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.stopInstance(s,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return us(await this.nodeAgentGateway.restartInstance(s,e,{modelEnv:this.modelEnvForInstance(t)}))}async boardAsync(){return(await this.boardWithDiagnostics()).items}async boardWithDiagnostics(){const e=await this.listNodeRuntimesWithDiagnostics(),t=await this.listNodeInstancesWithDiagnostics();return this.instanceBoardReader.read({projects:this.listProjects(),images:this.images.list(),nodes:this.nodes.list(),runtimes:e.items,instances:t.items,nodeErrors:[...e.nodeErrors,...t.nodeErrors]})}async bootstrapAiSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus)return{streamId:`bootstrap:${e.id}:ai.sessions`,revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions};try{return F.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:rs(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{streamId:`bootstrap:${e.id}:ai.sessions`,revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,streamId:t[s].streamId,aiSessions:t[s].snapshot,revision:t[s].revision,lastEventAt:t[s].lastEventAt}))}}async bootstrapAppSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=Zt();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:t.updatedAt,snapshot:t}}try{return Mt.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:rs(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const s=Zt();return{streamId:`bootstrap:${e.id}:app.sessions`,revision:0,lastEventAt:s.updatedAt,snapshot:s}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,streamId:t[s].streamId,appSessions:t[s].snapshot,revision:t[s].revision,lastEventAt:t[s].lastEventAt}))}}async recoverAiSessionDelta(e,t,s){const n=await this.requireControlledInstance(e,!0),i=new URLSearchParams({streamId:t,sinceRevision:String(s)});return Y.parse(await this.instanceRequest(n,`/ai-sessions?${i}`))}async recoverAiSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return F.parse(await this.instanceRequest(t,"/ai-sessions/state"))}async recoverAppSessionDelta(e,t,s){const n=await this.requireControlledInstance(e,!0),i=new URLSearchParams({streamId:t,sinceRevision:String(s)});return Ft.parse(await this.instanceRequest(n,`/apps/sessions?${i}`))}async recoverAppSessionSnapshot(e){const t=await this.requireControlledInstance(e,!0);return Mt.parse(await this.instanceRequest(t,"/apps/sessions/state"))}async listTriggers(){return this.controlPlaneTriggerService.listTriggers()}createTrigger(e){return this.controlPlaneTriggerService.createTrigger(e)}async deleteTrigger(e){return this.controlPlaneTriggerService.deleteTrigger(e)}async applyTrigger(e,t){return this.controlPlaneTriggerService.applyTrigger(e,t)}async listInstanceTriggers(e){return this.controlPlaneTriggerService.listInstanceTriggers(e)}async bindAiSessionTrigger(e,t,s){return this.controlPlaneTriggerService.bindAiSessionTrigger(e,t,s)}async unbindAiSessionTrigger(e,t,s){return this.controlPlaneTriggerService.unbindAiSessionTrigger(e,t,s)}async runInstanceTrigger(e,t,s={}){return this.controlPlaneTriggerService.runInstanceTrigger(e,t,s)}listChatSessions(){return this.chatSessionService.list()}createAppAccessToken(e){return this.appAccessService.createToken(e)}resolveAppAccessToken(e,t){return this.appAccessService.resolveToken(e,t)}createChatActionToken(e){return this.chatActionTokenService.create(e)}resolveChatActionToken(e,t){return this.chatActionTokenService.resolve(e,t)}pendingDecisionCallbackData(e,t){return this.chatActionTokenService.pendingDecisionCallbackData(e,t)}async appAccessProxyTarget(e,t,s=""){return this.appAccessService.proxyTarget(e,t,s)}listChatBridges(){return this.chatBridgeService.list()}requireChatBridge(e){return this.chatBridgeService.require(e)}createChatBridge(e){return this.chatBridgeService.create(e)}updateChatBridge(e,t){return this.chatBridgeService.update(e,t)}deleteChatBridge(e){return this.chatBridgeService.delete(e)}requireChatSession(e){return this.chatSessionService.require(e)}upsertChatSession(e){return this.chatSessionService.upsert(e)}async handleChatGatewayMessage(e){return this.chatSessionRuntime.handleChatGatewayMessage(e)}async handleChatGatewayAction(e){return this.chatSessionRuntime.handleChatGatewayAction(e)}async handleChatCommand(e,t){return this.chatSessionRuntime.handleChatCommand(e,t)}async listPendingRoutes(){const e=ns(),t=[],s=await this.listAiSessions(),n=new Map(s.instances.map(e=>[e.instanceId,e.aiSessions]));for(const s of await this.listNodeInstances()){if("online"!==s.connectionStatus&&"online"!==s.agentStatus||!s.target.web)continue;const i=s.projectId?this.projects.get(s.projectId):void 0;for(const a of n.get(s.id)?.sessions||[]){if("waiting"!==a.status)continue;const n=et.parse({id:`${s.id}:ai:${a.id}`,instanceId:s.id,projectId:s.projectId,aiSessionId:a.id,providerSessionId:a.providerSessionId,result:a.summary||a.currentTool?.inputPreview||a.lastMessage||`${a.agent} is waiting for approval`,source:a.agent,kind:"approval",status:"pending",lastSeenAt:e});t.push({...n,project:i,instance:us(s)})}}return t}async resolveAiSessionApproval(e,t,s){const n=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/approval`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({decision:s})}))}async launchAppSession(e,t="terminal-tty",s={}){const n=await this.requireControlledInstance(e,!0);if("online"!==n.connectionStatus&&"online"!==n.agentStatus){const e=new Error(`Instance ${n.name} is still starting. Wait for it to connect before launching apps.`);throw Object.assign(e,{statusCode:409,code:"INSTANCE_NOT_CONNECTED"}),e}const i=await this.resolveAppLaunchOptions(n,s),a=await this.instanceRequest(n,"/apps/sessions",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appId:t,...i})});return await this.listAppSessions({refresh:!0}),a}async stopAppSession(e,t){const s=await this.requireControlledInstance(e,!0),n=await this.instanceRequest(s,`/apps/sessions/${encodeURIComponent(t)}/stop`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});return await this.listAppSessions({refresh:!0}),n}async renameAppSession(e,t,s){const n=await this.requireControlledInstance(e,!0),i=await this.instanceRequest(n,`/apps/sessions/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({title:s})});return await this.listAppSessions({refresh:!0}),i}async sendAiSessionMessage(e,t,s,n,i=[]){!function(e){if(e.reduce((e,t)=>e+t.size,0)<=y)return;const t=new Error("Images must be 41943040 bytes or less in total.");throw Object.assign(t,{statusCode:400,code:"AI_SESSION_ATTACHMENTS_TOO_LARGE"}),t}(i);const a=await this.requireControlledInstance(e,!0),r={message:s,...n?{mode:n}:{},...i.length?{attachments:i}:{}};return L.parse(await this.instanceRequest(a,`/ai-sessions/${encodeURIComponent(t)}/messages`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(r)}))}async aiSessionQueue(e,t){const s=await this.requireControlledInstance(e,!0);return P.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}/steer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async retryAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}/retry`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async removeAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return q.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(s)}`,{method:"DELETE"}))}async interruptAiSession(e,t){const s=await this.requireControlledInstance(e,!0);return L.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/interrupt`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async proxyInstanceHttp(e,t,s={}){const n=await this.requireControlledInstance(e,!0);return this.controlledInstanceGateway.proxyHttp(n,t,s)}async proxyInstanceWebSocket(e,t,s,n,i={}){const a=await this.requireControlledInstance(e,!0);this.controlledInstanceGateway.proxyWebSocket(a,this.nodeAgentTransportFor(this.requireNode(a.nodeId)),t,s,n,i)}listConfigSyncPresets(){return[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}]}async syncInstanceConfig(e,t,s){const n=await this.requireControlledInstance(e,!0);if("export"===t&&"local-folder"!==n.source.type){const e=new Error("Exporting instance config is only available for local folder projects.");throw Object.assign(e,{statusCode:400,code:"CONFIG_SYNC_EXPORT_REQUIRES_LOCAL_PROJECT"}),e}const i=[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}].find(e=>e.id===s);if(!i){const e=new Error(`Config sync preset ${s} was not found.`);throw Object.assign(e,{statusCode:404,code:"CONFIG_SYNC_PRESET_NOT_FOUND"}),e}return this.instanceRequest(n,`/config-sync/${encodeURIComponent(t)}/${encodeURIComponent(s)}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({preset:i})})}async instanceRequest(e,t,s={}){return this.controlledInstanceGateway.request(e,t,s)}async reportInstanceHeartbeat(e,t){await this.controlledInstanceGateway.reportHeartbeat(e,t,this.nodeAgentTransport)}async nodeAgentFetch(e,t,s={}){return this.nodeAgentTransportFor(e).request(e,t,s)}nodeAgentTransportFor(e){if("reverse-wss"===e.connectionMode){if(!this.nodeAgentTransport){const e=new Error("Reverse node agent transport is not available.");throw Object.assign(e,{statusCode:503,code:"NODE_AGENT_REVERSE_TRANSPORT_UNAVAILABLE"}),e}return this.nodeAgentTransport}return this.directNodeAgentTransport()}directNodeAgentTransport(){return{request:async(e,t,s={})=>{const n=e.controlEndpoint||e.endpoint;if(!n){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const i=s.method||"GET",a="string"==typeof s.body||s.body instanceof Buffer?s.body:void 0===s.body||null===s.body?void 0:String(s.body),r=Kn(e,{method:i,pathWithQuery:`/api/node-agent${t}`,body:a||""});return this.fetchNodeAgentEndpoint(n,t,{...s,body:a,headers:{...s.headers||{},...r}})},proxyWebSocket:(e,t,s,n,i={})=>{const a=e.controlEndpoint||e.endpoint;if(!a)throw Object.assign(new Error("Node agent direct HTTP mode requires an endpoint."),{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"});const r=qn(a),o=`/api/node-agent${s}`,d=Kn(e,{method:"GET",pathWithQuery:o});if(r){Ln(r);const e=Un(r,s,n,{...i,...d});return e.on("open",()=>{t.on("message",(t,s)=>e.readyState===p.WebSocket.OPEN&&e.send(s?t:String(t))),t.on("close",()=>e.close()),t.on("error",()=>e.close())}),e.on("message",(e,s)=>{t.readyState===p.WebSocket.OPEN&&t.send(s?e:e.toString())}),e.on("close",()=>t.close()),void e.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}const c=new URL(o,a.replace(/\/$/,""));c.protocol="https:"===c.protocol?"wss:":"ws:";const l=n?new p.WebSocket(c.toString(),n,{headers:{...i,...d}}):new p.WebSocket(c.toString(),{headers:{...i,...d}});l.on("open",()=>{t.on("message",(e,t)=>l.readyState===p.WebSocket.OPEN&&l.send(t?e:String(e))),t.on("close",()=>l.close()),t.on("error",()=>l.close())}),l.on("message",(e,s)=>{t.readyState===p.WebSocket.OPEN&&t.send(s?e:e.toString())}),l.on("close",()=>t.close()),l.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}}}async listNodeInstances(){return(await this.listNodeInstancesWithDiagnostics()).items}async listNodeInstancesWithDiagnostics(){const e=await this.nodeAgentGateway.listFleetInstances(this.listNodes());return{items:await Promise.all(e.items.map(e=>this.withFreshTriggerSnapshot(e))),nodeErrors:e.nodeErrors}}async withFreshTriggerSnapshot(e){if("online"!==e.connectionStatus&&"online"!==e.agentStatus)return e;try{const t=le.parse(await this.instanceRequest(e,"/triggers"));return Ke.parse({...e,triggers:xn(t)})}catch{return e}}async requireNodeInstance(e){for(const t of this.listNodes())try{const s=(await this.nodeAgentGateway.listInstances(t)).find(t=>t.id===e);if(s)return s}catch{}os("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`)}async requireNodeRuntimeOnNode(e,t){const s=(await this.listNodeRuntimes(e)).find(e=>e.id===t);return s||os("NODE_RUNTIME_NOT_FOUND",`Runtime ${t} was not found on node ${e}.`),s}async requireNodeLocalFolder(e,t){return this.nodeAgentGateway.requireLocalFolder(e,t)}async resolveAppLaunchOptions(e,t){const s="string"==typeof t.cwdFolderId?t.cwdFolderId.trim():"",n={...t};if(delete n.cwdFolderId,!s)return n;const i=this.requireNode(e.nodeId),a=await this.requireNodeLocalFolder(i,s);if(a.nodeId!==e.nodeId){const t=new Error(`Local folder ${a.id} belongs to node ${a.nodeId}, not ${e.nodeId}.`);throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_INSTANCE_NODE"}),t}const r=await this.requireNodeRuntimeOnNode(e.nodeId,e.runtimeId);return{...n,cwd:this.appLaunchCwdForFolder(e,a,r)}}appLaunchCwdForFolder(e,t,n){if("local"===n.type)return Rn(i=t.path).resolve(i);var i;if("local-folder"!==e.source.type){const e=new Error("App cwd folder selection is only supported for local-folder instances.");throw Object.assign(e,{statusCode:400,code:"APP_CWD_REQUIRES_LOCAL_FOLDER_SOURCE"}),e}const a=function(e,t){if(On(e)!==On(t))return;const s=Rn(e),n=s.resolve(e),i=s.resolve(t),a=s.relative(n,i);return a?".."===a||a.startsWith(`..${s.sep}`)||s.isAbsolute(a)?void 0:a.split(s.sep).filter(Boolean):[]}(e.source.path,t.path);if(!a){const s=new Error(`Local folder ${t.path} is outside the instance workspace ${e.source.path}.`);throw Object.assign(s,{statusCode:400,code:"APP_CWD_OUTSIDE_WORKSPACE"}),s}const r=e.runtime.workspacePath||e.workspace.path||ps(e.source).path||"/workspace";return a.length?s.posix.join(r,...a):r}modelEnvForInstance(e){return function(e,t){const s=Nn(e.sourceSnapshot);return Cn(Oe.parse({id:e.projectId||`project_${e.id}`,name:"string"==typeof e.sourceSnapshot.name?e.sourceSnapshot.name:e.name,source:e.source,defaultImageId:e.imageId,defaultNodeId:e.nodeId,defaultRuntimeId:e.runtimeId,modelSelection:s,workspacePolicy:ps(e.source),labels:{},createdAt:e.createdAt,updatedAt:e.updatedAt}),t)}(e,this.listAllModels())}modelEnvForSourceSnapshot(e,t){return function(e,t,s){const n=Nn(t),i=(new Date).toISOString();return Cn(Oe.parse({id:"string"==typeof t.id?t.id:`source_${pt("tmp")}`,name:"string"==typeof t.name?t.name:"Source",source:e,defaultImageId:"string"==typeof t.defaultImageId?t.defaultImageId:void 0,modelSelection:n,workspacePolicy:ps(e),labels:{},createdAt:i,updatedAt:i}),s)}(e,t,this.listAllModels())}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}validateProjectModelSelection(e){const t=new Map(this.listAllModels().map(e=>[e.id,e])),s={},n=e.codexModelId?.trim(),i=e.claudeModelId?.trim();if(n){const e=t.get(n);if(e||os("MODEL_NOT_FOUND",`Model ${n} was not found.`),"codex"!==e.app){const e=new Error(`Model ${n} is not a Codex model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}s.codexModelId=n}if(i){const e=t.get(i);if(e||os("MODEL_NOT_FOUND",`Model ${i} was not found.`),"claude"!==e.app){const e=new Error(`Model ${i} is not a Claude model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}s.claudeModelId=i}return s}modelEnvForProject(e){return Cn(e,this.listAllModels())}requireProject(e){const t=this.projects.get(e);return t||os("PROJECT_NOT_FOUND",`Project ${e} was not found.`),this.normalizeProjectRecord(t)}normalizeProjectRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};return"defaultRuntimeId"in t||(t.defaultRuntimeId="runtime_local_docker"),Oe.parse(t)}return Oe.parse(e)}(e);return t!==e&&this.projects.put(t),t}requireModel(e,t=!1){const s=this.models.get(e);s||os("MODEL_NOT_FOUND",`Model ${e} was not found.`);const n=this.normalizeModelRecord(s);return t?n:gs(n)}requireModelSecret(e){const t=this.models.get(e);return t||os("MODEL_NOT_FOUND",`Model ${e} was not found.`),this.normalizeModelRecord(t)}normalizeModelRecord(e){const t=function(e){if(e&&"object"==typeof e&&!Array.isArray(e)){const t={...e};if(!("app"in t)){const e=Array.isArray(t.apps)?t.apps:[];t.app=e.includes("claude")&&!e.includes("codex")?"claude":"codex"}return delete t.apps,Re.parse(t)}return Re.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){const t=this.images.get(e);return t||os("IMAGE_NOT_FOUND",`Image ${e} was not found.`),t}requireNode(e){const t=this.nodes.get(e);return t||os("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return ls(this.requireNode(e))}async requireControlledInstance(e,t=!1){const s=await this.requireNodeInstance(e);return t?s:us(s)}}function Qn(e){return m(String(e),"escape").replace(/\n+$/,"")}function Yn(e){const[t,...s]=String(e).split("\n");return[`*${Qn(t)}*`,...s.map(Qn)].join("\n")}function Xn(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Zn(e,t,s,n,i={}){if(t.token)return ti(s=>e(`https://api.telegram.org/bot${t.token}/sendMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),"sendMessage",n,{chat_id:s,...i.replyToMessageId?{reply_to_message_id:i.replyToMessageId,allow_sending_without_reply:!0}:{},...i.replyMarkup?{reply_markup:i.replyMarkup}:{}},i)}async function ei(e,t,s){if(!t.token||!s)throw new Error("Telegram file id is required.");const n=await e(`https://api.telegram.org/bot${t.token}/getFile`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({file_id:s})}),i=await n.json().catch(()=>({}));if(!n.ok||!1===i.ok||!i.result?.file_path)throw new Error(i.description||`Telegram getFile failed with HTTP ${n.status}`);return`https://api.telegram.org/file/bot${t.token}/${i.result.file_path}`}async function ti(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):Qn(s),o=await e({...n,text:r,disable_web_page_preview:!0,parse_mode:a}),d=await o.json().catch(()=>({}));if(o.ok&&!1!==d.ok)return d.result;if("editMessageText"===t&&si(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:Xn(s),disable_web_page_preview:!0,parse_mode:"Markdown"}),a=await i.json().catch(()=>({}));if(i.ok&&!1!==a.ok)return a.result;if("editMessageText"===t&&si(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?Xn(s):String(s),disable_web_page_preview:!0}),l=await c.json().catch(()=>({}));if(c.ok&&!1!==l.ok)return l.result;if("editMessageText"!==t||!si(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function si(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function ni(e){return e instanceof Error?e.message:String(e)}function ii(e){const t=Number(function(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}(e).message_id);return Number.isInteger(t)&&t>0?t:void 0}class ai{entries;updateIntervalMs;send;edit;deleteMessage;messageIdFromResult;retryAfterMs;isMessageTooLong;onLog;constructor(e){this.entries=e.entries||new Map,this.updateIntervalMs=e.updateIntervalMs||1e3,this.send=e.send,this.edit=e.edit,this.deleteMessage=e.delete,this.messageIdFromResult=e.messageIdFromResult||ii,this.retryAfterMs=e.retryAfterMs,this.isMessageTooLong=e.isMessageTooLong,this.onLog=e.onLog}remember(e,t,s,n,i,a){const r=this.entries.get(e);return r&&(clearTimeout(r.timer),r.timer=void 0,r.pendingText=void 0),this.entries.set(e,{lastText:s,lastUpdateAt:Date.now(),messageId:t,route:n,options:i,renderOptions:a}),r}rekey(e,t){if(e===t)return this.entries.has(e);const s=this.entries.get(e);if(!s||this.entries.has(t))return!1;if(clearTimeout(s.timer),s.timer=void 0,this.entries.delete(e),this.entries.set(t,s),s.pendingText){const e=s.rateLimitedUntil?Math.max(0,s.rateLimitedUntil-Date.now()):Math.max(0,this.updateIntervalMs-(Date.now()-s.lastUpdateAt));this.scheduleFlush(t,e)}return!0}scheduleFlush(e,t){const s=this.entries.get(e);s&&!s.timer&&(s.timer=setTimeout(()=>{s.timer=void 0;const t=s.pendingText;s.pendingText=void 0,t&&this.applyUpdate(e,t,s.route,s.options)},t))}markRateLimited(e,t,s,n){const i=this.entries.get(e);i&&(i.rateLimitedUntil=Date.now()+s,i.pendingText=t,i.route=n||i.route,this.scheduleFlush(e,s))}async applyUpdate(e,t,s,n){let i=this.entries.get(e);const a=i,r=s||i?.route,o=n||i?.options;try{if(i&&(i.route=r,i.options=o),i?.pending&&await i.pending,i=this.entries.get(e),a&&i!==a)return!1;if(i?.messageId){if(i.lastText===t)return!0;const s=Date.now()-(i.lastUpdateAt||0);return s<this.updateIntervalMs?(i.pendingText=t,i.route=r||i.route,i.options=o||i.options,this.scheduleFlush(e,this.updateIntervalMs-s),!0):(await this.edit(i.messageId,t,r||i.route,o),i.lastText=t,i.lastUpdateAt=Date.now(),i.rateLimitedUntil=void 0,!0)}const s={lastText:t,lastUpdateAt:Date.now(),route:r,options:o};return s.pending=this.send(t,r,o).then(e=>{const t=this.messageIdFromResult(e);t&&(s.messageId=t)}).finally(()=>{s.pending=void 0}),this.entries.set(e,s),await s.pending,this.entries.get(e)===s&&(s.rateLimitedUntil=void 0,Boolean(s.messageId))}catch(s){const n=this.retryAfterMs?.(s);return n&&this.markRateLimited(e,t,n,r),this.onLog?.(`Telegram progress update skipped: ${ni(s)}`),!1}}update(e,t,s,n){const i=String(t||"").trim();if(!i)return;const a=this.entries.get(e);if(a&&(a.route=s||a.route,a.options=n||a.options),a?.lastText===i&&!a.pending&&!a.pendingText)return;const r=Date.now();if(a?.rateLimitedUntil){if(a.rateLimitedUntil>r)return a.pendingText=i,void this.scheduleFlush(e,a.rateLimitedUntil-r);a.rateLimitedUntil=void 0}if(a?.pending&&!a.messageId)return a.pendingText=i,void this.scheduleFlush(e,this.updateIntervalMs);if(!a||!a.messageId)return void this.applyUpdate(e,i,s,n);const o=Date.now()-(a.lastUpdateAt||0);if(o>=this.updateIntervalMs)return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,void this.applyUpdate(e,i,s||a.route,n||a.options);a.pendingText=i,this.scheduleFlush(e,this.updateIntervalMs-o)}async finish(e,t,s,n,i){const a=this.entries.get(e);if(!a)return!1;try{return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,a.pending&&await a.pending,this.entries.delete(e),!!a.messageId&&(a.lastText===t||await this.edit(a.messageId,t,s||a.route,n||a.options,i||a.renderOptions),!0)}catch(t){return this.entries.delete(e),this.isMessageTooLong?.(t)?this.onLog?.("Telegram progress finish too long; sending result as multiple messages"):this.onLog?.(`Telegram progress finish skipped: ${ni(t)}`),!1}}finishPayload(e,t,s,n,i){return this.finish(e,`${t.title}\n\n${t.body}`,s,n,i)}async delete(e,t){const s=this.entries.get(e);if(!s)return!1;try{return clearTimeout(s.timer),s.timer=void 0,s.pendingText=void 0,s.pending&&await s.pending,this.entries.delete(e),!(!this.deleteMessage||!s.messageId)&&(await this.deleteMessage(s.messageId,t||s.route),!0)}catch(t){return this.entries.delete(e),this.onLog?.(`Telegram progress delete skipped: ${ni(t)}`),!1}}async flush(e){const t=this.entries.get(e);if(t?.pending&&await t.pending,t?.timer){clearTimeout(t.timer),t.timer=void 0;const s=t.pendingText;t.pendingText=void 0,s&&await this.applyUpdate(e,s,t.route,t.options)}}async wait(e){await function(e){return new Promise(t=>setTimeout(t,e))}(e)}}const ri=20971520;function oi(e){const t=[],s=[...e.photo||[]].filter(e=>e.file_id).sort((e,t)=>Number(t.file_size||t.width||0)-Number(e.file_size||e.width||0))[0];return s?.file_id&&t.push({fileId:s.file_id,fileName:"telegram-photo.jpg",fileSize:s.file_size}),e.document?.file_id&&String(e.document.mime_type||"").startsWith("image/")&&t.push({fileId:e.document.file_id,fileName:e.document.file_name,fileSize:e.document.file_size}),t}async function di(e,t,n="telegram-image",i){if(i&&i>ri)throw new Error(`image is too large: ${i} bytes`);const a=await e.getFileLink(t),r=String(a),o=await(e.fetchFile||fetch)(r);if(!o.ok)throw new Error(`download failed with HTTP ${o.status}`);const d=Number(o.headers.get("content-length")||0);if(d>ri)throw new Error(`image is too large: ${d} bytes`);const l=s.extname(n)||".jpg";let p=l;try{p=s.extname(new URL(r).pathname)||l}catch{p=l}const u=s.join(c.tmpdir(),"task-handoff-images");await I.mkdir(u,{recursive:!0});const g=p.replace(/[^.\w-]/g,"")||".jpg",h=s.join(u,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${g}`),m=Buffer.from(await o.arrayBuffer());if(m.length>ri)throw new Error(`image is too large: ${m.length} bytes`);return await I.writeFile(h,m),h}class ci{store;constructor(e){this.store=new ai({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",Yn(t),{rawMarkdownV2:!0,replyMarkup:Ui(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,Yn(s),{rawMarkdownV2:!0,replyMarkup:Ui(i)}),delete:(t,s)=>e.deleteMessage(e.requireBridge(s?.bridgeId||""),s?.chatId||"",t),onLog:e.onLog})}get entries(){return this.store.entries}remember(e,t,s,n){return this.store.remember(e,t,s,n)}rekey(e,t){return this.store.rekey(e,t)}delete(e){return this.store.delete(e)}async applyUpdate(e){return this.store.applyUpdate(e.key,e.text,{bridgeId:e.bridge.id,chatId:e.chatId},{actionRows:e.actionRows})}}function li(e,t){if("string"==typeof e&&e.trim())return e.trim();ui(`Chat action token is missing ${t}.`)}function pi(e){if("allow"===e||"deny"===e||"skip"===e)return e;ui("Chat action token has an invalid decision.")}function ui(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class gi{entries=new Map;fetchImpl;constructor(e){this.fetchImpl=e}clear(){this.clearEntries(()=>!0)}clearBridge(e){this.clearEntries(t=>t.bridgeId===e)}clearEntries(e){for(const[t,s]of this.entries)e(s)&&(s.timer&&clearTimeout(s.timer),s.cancelPending?.(new Error("DingDing progress update was cancelled.")),this.entries.delete(t))}async applyUpdate(e){const t=e.dingdingRuntime;if(!t)return!1;const s=Li(e.replyMarkup);let n=this.entries.get(e.key);if(n?.pending&&await n.pending,n=this.entries.get(e.key),n?.outTrackId){if(n.lastText===e.text&&n.lastActionsFingerprint===s)return!0;const t=Date.now()-(n.lastUpdateAt||0);return t<1e3?(n.pendingText=e.text,n.pendingReplyMarkup=e.replyMarkup,n.timer||(n.pending=new Promise((s,i)=>{n.cancelPending=i,n.timer=setTimeout(()=>{n.timer=void 0,n.cancelPending=void 0;const t=n.pendingText,a=n.pendingReplyMarkup;n.pendingText=void 0,n.pendingReplyMarkup=void 0,t?this.updateExistingEntry(e,n,t,a,Li(a)).then(s,i):s(void 0)},1e3-t)}).finally(()=>{n.pending=void 0,n.timer=void 0,n.cancelPending=void 0})),await n.pending,this.entries.has(e.key)):(await this.updateExistingEntry(e,n,e.text,e.replyMarkup,s),!0)}const i={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:s,lastUpdateAt:Date.now()};return i.pending=zi({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||$i(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||$i(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(e=>{e?.outTrackId&&(i.outTrackId=e.outTrackId),e?.delivered&&(i.delivered=!0,i.lastUpdateAt=Date.now())}).catch(t=>{throw this.entries.delete(e.key),t}).finally(()=>{i.pending=void 0}),this.entries.set(e.key,i),await i.pending,Boolean(i.delivered)}async updateExistingEntry(e,t,s,n,i){const a=e.dingdingRuntime;a&&t.outTrackId&&(await async function(e){await Oi(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:yi({type:"actions",title:e.title,description:e.text,list:Xs(e.replyMarkup),biz_step:e.step,error_msg:""})},cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},userIdType:Number(e.bridge.settings.cardUserIdType||1)})}({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:a,outTrackId:t.outTrackId,text:s,replyMarkup:n,title:"TaskHandoff 执行中",step:"progress"}),t.lastText=s,t.lastActionsFingerprint=i,t.lastUpdateAt=Date.now())}}function hi(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=await Zn(t,s,e,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Pi(a).message_id)||void 0}}}:"dingding"===s.channel?{bridge:s,send:async(e,i,a={})=>{const r=a.sessionWebhook||n?.chatWebhooks.get(e)||$i(s.settings.sessionWebhook);if(a.replyMarkup&&n){const o=await zi({fetchImpl:t,bridge:s,runtime:n,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||n.senderIds.get(e)||$i(s.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await _i(t,r,i,n?.onLog),{provider:"dingding"}}}:"wechat"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=i.contextToken||$i(s.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=$i(t.settings.baseUrl)||"https://ilinkai.weixin.qq.com",r=await e(`${a}/ilink/bot/sendmessage`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${t.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({msg:{from_user_id:"",to_user_id:s,client_id:o.randomUUID(),message_type:2,message_state:2,context_token:n,item_list:[{type:1,text_item:{text:i}}]},base_info:{channel_version:"1.0.2"}})}),d=await r.json().catch(()=>({}));if(!r.ok||d.errcode)throw new Error($i(d.errmsg)||`WeChat send failed with HTTP ${r.status}`)}(t,s,e,a,n),{provider:"wechat"}}}:{bridge:s,send:async()=>{}}}async function mi(e){return(await async function(e,t,s,n){const i=[];for(const a of oi(s))try{const s=await di({getFileLink:s=>ei(e,t,s),fetchFile:e},a.fileId,a.fileName,a.fileSize);i.push(s)}catch(e){n?.({fileId:a.fileId,fileName:a.fileName,error:e})}return i}(e.fetchImpl,e.bridge,e.message,e.onImageDownloadError)).map((e,n)=>{const i=t.readFileSync(e);return{id:`tg_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:s.basename(e)||`telegram-image-${n+1}`,mime:Ii(e),size:i.length,data:i.toString("base64")}})}function Ii(e){const t=s.extname(e).toLowerCase();return".png"===t?"image/png":".webp"===t?"image/webp":".gif"===t?"image/gif":".bmp"===t?"image/bmp":"image/jpeg"}function fi(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function yi(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function bi(e){return`dtv1.card//IM_GROUP.${e}`}function vi(e){return`dtv1.card//IM_ROBOT.${e}`}function wi(e,t){return $i(t.biz_conversation_id).trim()||$i(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function Si(e,t={}){const s=$i(e).trim().toUpperCase();return"2"===s||"IM_GROUP"===s?"IM_GROUP":"1"===s||"IM_SINGLE"===s?t.robotMessage?"IM_ROBOT":"IM_SINGLE":"IM_ROBOT"===s?"IM_ROBOT":void 0}function Ai(e){if(e.startsWith("task_handoff:"))return e;if(!e.startsWith("th_cb_"))return"";try{return Buffer.from(e.slice(6),"base64url").toString("utf8")}catch{return""}}function ki(e,t,s,n,i){const a=wi(n,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:yi({type:"actions",title:"TaskHandoff",description:e,list:Xs(t),biz_out_track_id:n.outTrackId||i.biz_out_track_id||"",biz_conversation_id:a,biz_sender_id:i.biz_sender_id||n.userId||"",biz_session_webhook:i.biz_session_webhook||"",biz_conversation_type:i.biz_conversation_type||"",biz_step:s,error_msg:""})}}}function Ti(e,t,s){const n=[];return Ei(n,s),Ei(n,t),Ei(n,e),n.find(e=>Ai(e))||""}function Ei(e,t){for(const s of["actionIdList","actionIds"]){const n=t[s];Array.isArray(n)&&e.push(...n.map(e=>$i(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=$i(t[s]);n&&e.push(n)}}async function _i(e,t,s,n){n?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:ji(s)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:fi(s),text:s}})}),a=await i.json().catch(()=>{});if(n?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:Di(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function Ci(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Pi(e);if(1!==t.type)continue;const s=$i(Pi(t.text_item).text).trim();if(s)return s}return""}function Ni(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function zi(e){const t=Xs(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await _i(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const s=e.bridge.settings||{},n=$i(s.robotCode),i=$i(s.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=$i(s.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||$i(s.senderId),o=(d=e.runtime,c=e.chatId,d.conversationTypes?.get(c)||"IM_GROUP");var d,c;if(!n||!i||!r){if(e.sessionWebhook&&!e.forceCard)return await _i(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),{delivered:!0};throw new Error("DingDing card target is incomplete.")}const l=`task_handoff_cp_${Date.now()}_${Math.random().toString(16).slice(2)}`,p=yi({type:"actions",title:e.title||fi(e.text),description:e.text,list:t,biz_out_track_id:l,biz_step:e.step||"task_handoff_actions",biz_conversation_id:e.chatId,biz_sender_id:r,biz_session_webhook:e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)||"",biz_conversation_type:o,error_msg:""}),u=function(e,t,s,n){return"IM_SINGLE"===e?{openSpaceId:(i=t,`dtv1.card//IM_SINGLE.${i}`),imSingleOpenSpaceModel:{},imSingleOpenDeliverModel:{extension:{}}}:"IM_ROBOT"===e?{openSpaceId:vi(s),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:n,spaceType:"IM_ROBOT"}}:{openSpaceId:bi(t),imGroupOpenSpaceModel:{supportForward:!1},imGroupOpenDeliverModel:{extension:{},robotCode:n}};var i}(o,e.chatId,r,n);return e.runtime.onLog?.("info",{target:"card",chatId:e.chatId,senderId:r,conversationType:o,openSpaceId:u.openSpaceId,hasSessionWebhook:Boolean(e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)),hasReplyMarkup:Boolean(e.replyMarkup),actionCount:t.length,textPreview:ji(e.text)},"dingding card send requested"),await Oi(e.fetchImpl,e.bridge,e.runtime,"POST","/v1.0/card/instances/createAndDeliver",{userId:r,cardTemplateId:i,outTrackId:l,callbackType:"STREAM",callbackRouteKey:a,cardData:{cardParamMap:p},userIdType:Number(s.cardUserIdType||1),...u}),{delivered:!0,outTrackId:l}}async function Oi(e,t,s,n,i,a){const r=await async function(e,t,s){if(s.accessToken&&s.accessToken.expiresAt>Date.now()+6e4)return s.accessToken.value;const n=$i(t.settings.clientSecret);if(!t.token||!n)throw new Error("DingDing client id/secret is not configured.");const i=await e("https://api.dingtalk.com/v1.0/oauth2/accessToken",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appKey:t.token,appSecret:n})}),a=await i.json().catch(()=>({}));if(!i.ok||!a.accessToken)throw new Error(`DingDing access token failed with HTTP ${i.status}`);const r=Number(a.expireIn||7200);return s.accessToken={value:a.accessToken,expiresAt:Date.now()+1e3*Math.max(60,r-120)},s.accessToken.value}(e,t,s);s.onLog?.("info",{method:n,path:i,body:Ri(a)},"dingding api request");const o=await e(`https://api.dingtalk.com${i}`,{method:n,headers:{"content-type":"application/json","x-acs-dingtalk-access-token":r},body:JSON.stringify(a)}),d=await o.json().catch(()=>{});if(s.onLog?.(o.ok?"info":"warn",{method:n,path:i,status:o.status,response:Di(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=Pi(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error($i(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=Pi(Pi(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===Pi(e).success);if(!s)return"";const n=Pi(s),i=$i(n.spaceId),a=$i(n.errorMsg||n.message||n.errmsg)||"delivery failed";return i?`DingDing card delivery failed for ${i}: ${a}`:`DingDing card delivery failed: ${a}`}(d);if(u)throw new Error(u);return o}function Ri(e){const t=Pi(e),s=Pi(t.cardData),n=Pi(s.cardParamMap);return{openSpaceId:$i(t.openSpaceId),outTrackId:$i(t.outTrackId),userId:$i(t.userId),userIdType:t.userIdType,callbackType:$i(t.callbackType),callbackRouteKey:$i(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:xi(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:xi(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:xi(t.imRobotOpenDeliverModel),bizConversationId:$i(n.biz_conversation_id),bizConversationType:$i(n.biz_conversation_type),bizSenderId:$i(n.biz_sender_id),hasBizSessionWebhook:Boolean($i(n.biz_session_webhook))}}function xi(e){const t=Pi(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean($i(t.robotCode)),spaceType:$i(t.spaceType),recipients:Array.isArray(t.recipients)?t.recipients.length:void 0,atUserIds:t.atUserIds&&"object"==typeof t.atUserIds?Object.keys(t.atUserIds).length:void 0}}function Di(e){const t=Pi(e),s=Pi(t.result);return{code:t.code??t.errcode,message:ji(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(s).length?s:void 0}}function ji(e,t=160){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function Pi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Mi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?qi(e):{}}function $i(e){return"string"==typeof e?e:""}function qi(e){try{return Pi(JSON.parse(String(e||"{}")))}catch{return{}}}function Li(e){return JSON.stringify(Xs(e))}function Ui(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Bi(e){return Buffer.from(e,"base64url").toString("utf8")}function Fi(e){return new h.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class Hi{service;fetchImpl;createDingdingClient;logger;bridgeTimers=new Map;dingdingBridges=new Map;bridgeErrors=new Map;telegramOffsets=new Map;seenTelegramUpdates=new Set;wechatCursors=new Map;pendingTimer;announcedPending=new Set;deliveredAiSessionFingerprints=new Map;aiSessionCancelTokens=new Map;aiSessionQueueActionTokens=new Map;telegramMessageAggregates=new Map;telegramMessageAggregateEndTokens=new Map;telegramProgressMessageTargets=new Map;telegramProgress;dingdingProgress;stopAiSessionListener;constructor(e,t=fetch,s={}){var n;this.service=e,this.fetchImpl=t,this.logger=(n=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(n||"").trim().toLowerCase())?s.logger:void 0),this.telegramProgress=new ci({updateIntervalMs:s.telegramProgressUpdateIntervalMs,requireBridge:e=>this.service.requireChatBridge(e),send:(e,t,s,n={})=>this.sendTelegramMessage(e,t,s,n),edit:(e,t,s,n,i={})=>this.editTelegramMessage(e,t,s,n,i),deleteMessage:(e,t,s)=>this.deleteTelegramMessage(e,t,s),onLog:e=>this.logWarn({message:e},"telegram progress store warning")}),this.dingdingProgress=new gi(this.fetchImpl),this.stopAiSessionListener=s.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:ea(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=s.createDingdingClient||Fi}logInfo(e,t){this.logger?.info?.({component:"control-plane-chat-gateway",...e},t)}logWarn(e,t){this.logger?.warn?.({component:"control-plane-chat-gateway",...e},t)}updateChatBridge(e,t){this.service.updateChatBridge?.(e,t)}startEnabled(){this.logAiSessionDelivery({stage:"runtime-start-enabled",bridgeCount:this.service.listChatBridges().length,enabledBridgeCount:this.service.listChatBridges().filter(e=>e.enabled).length});for(const e of this.service.listChatBridges())e.enabled&&this.startBridge(e.id);this.startPendingPolling()}startBridge(e){if(this.bridgeTimers.has(e)||this.dingdingBridges.has(e))return this.logAiSessionDelivery({stage:"bridge-start-skipped-running",bridgeId:e}),this.startPendingPolling(),this.status();const t=this.service.requireChatBridge(e);return this.logAiSessionDelivery({stage:"bridge-start",bridgeId:t.id,channel:t.channel,enabled:t.enabled,hasToken:Boolean(t.token)}),"wechat"===t.channel?this.startPollingBridge(t,e=>this.pollWechat(e)):"dingding"===t.channel?this.startDingdingBridge(t):"telegram"!==t.channel?(this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status()):t.token?this.startPollingBridge(t,e=>this.pollTelegram(e)):(this.bridgeErrors.set(e,"Telegram token is not configured."),this.status())}stopBridge(e){const t=this.bridgeTimers.get(e);t&&(clearInterval(t),this.bridgeTimers.delete(e));const s=this.dingdingBridges.get(e);if(s){try{s.client.disconnect()}catch{}this.dingdingBridges.delete(e)}return this.dingdingProgress.clearBridge(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.keys()]))this.stopBridge(e);this.pendingTimer&&(clearInterval(this.pendingTimer),this.pendingTimer=void 0),this.dingdingProgress.clear();for(const e of this.telegramMessageAggregates.values())e.timer&&clearTimeout(e.timer);this.telegramMessageAggregates.clear(),this.telegramMessageAggregateEndTokens.clear(),this.stopAiSessionListener?.(),this.stopAiSessionListener=void 0}async pollBridgeNow(e){const t=this.service.requireChatBridge(e);return"telegram"===t.channel?(await this.pollTelegram(t),this.status()):"wechat"===t.channel?(await this.pollWechat(t),this.status()):("dingding"===t.channel||this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status())}async pollAiSessionsNow(){return await this.pollAiSessionUpdates(),this.status()}status(){const e=this.service.listChatBridges().map(e=>({id:e.id,channel:e.channel,name:e.name,running:this.bridgeTimers.has(e.id)||this.dingdingBridges.has(e.id),tokenSet:Boolean(e.tokenSet),defaultChatId:e.defaultChatId,lastUpdateId:this.telegramOffsets.get(e.id),error:this.bridgeErrors.get(e.id)}));return{running:e.some(e=>e.running),bridges:e}}startPendingPolling(){this.pendingTimer?this.logAiSessionDelivery({stage:"pending-poll-already-started"}):(this.logAiSessionDelivery({stage:"pending-poll-started",intervalMs:5e3}),this.pendingTimer=setInterval(()=>{this.pollPendingRoutes()},5e3))}startPollingBridge(e,t){if(!e.token)return this.bridgeErrors.set(e.id,`${e.channel} token is not configured.`),this.status();this.bridgeErrors.delete(e.id);const s=setInterval(()=>{const s=this.service.requireChatBridge(e.id);s.enabled?t(s).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))):this.stopBridge(e.id)},e.pollIntervalMs);if(this.bridgeTimers.set(e.id,s),this.logAiSessionDelivery({stage:"bridge-poll-started",bridgeId:e.id,channel:e.channel,intervalMs:e.pollIntervalMs}),e.enabled){const s=this.service.requireChatBridge(e.id);t(s).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}return this.startPendingPolling(),this.status()}startDingdingBridge(e){const t=Zi((e.settings||{}).clientSecret);if(!e.token||!t)return this.bridgeErrors.set(e.id,"DingDing client id/secret is not configured."),this.status();const s={client:this.createDingdingClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,s,n)=>{"warn"!==t?this.logInfo({bridgeId:e.id,...s},n):this.logWarn({bridgeId:e.id,...s},n)}};return s.client.registerCallbackListener(h.TOPIC_ROBOT,t=>{this.handleDingdingRobotMessage(e,s,t).catch(t=>{this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))})}),s.client.registerCallbackListener(h.TOPIC_CARD,t=>{this.handleDingdingCardCallback(e,s,t).then(e=>s.client.socketCallBackResponse(t.headers.messageId,e||{})).catch(n=>{this.bridgeErrors.set(e.id,n instanceof Error?n.message:String(n)),s.client.socketCallBackResponse(t.headers.messageId,{})})}),this.bridgeErrors.delete(e.id),this.dingdingBridges.set(e.id,s),s.client.connect().then(()=>{this.dingdingBridges.get(e.id)===s&&this.bridgeErrors.delete(e.id)}).catch(t=>{this.dingdingBridges.get(e.id)===s&&(this.dingdingBridges.delete(e.id),this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}),this.startPendingPolling(),this.status()}async handleDingdingRobotMessage(e,t,s){const n=function(e){const t=qi(e),s=$i(t.conversationId),n=$i(Pi(t.text).content).trim();if(!s||!n)return;const i=$i(t.senderStaffId||t.senderId);return{chatId:s,userId:i||void 0,senderId:i||void 0,sessionWebhook:$i(t.sessionWebhook)||void 0,conversationType:Si(t.conversationType,{robotMessage:!0}),text:n,raw:t}}(s.data);if(t.client.socketCallBackResponse(s.headers.messageId,{}),!n||!this.dingdingAllowed(e,n.senderId))return void this.logWarn({bridgeId:e.id,hasEvent:Boolean(n),senderId:n?.senderId,allowed:n?this.dingdingAllowed(e,n.senderId):void 0},"dingding robot message ignored");const{chatId:i,text:a,senderId:r,sessionWebhook:o}=n;o&&t.chatWebhooks.set(i,o),n.conversationType&&t.conversationTypes.set(i,n.conversationType);const d=o||t.chatWebhooks.get(i)||Zi(e.settings.sessionWebhook);let c;r&&t.senderIds.set(i,r),e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:i}),this.logInfo({bridgeId:e.id,chatId:i,senderId:r,conversationType:n.conversationType,hasSessionWebhook:Boolean(o),hasCachedSessionWebhook:Boolean(t.chatWebhooks.get(i)),hasResolvedSessionWebhook:Boolean(d),textPreview:ta(a)},"dingding chat gateway message received");try{c=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:r||void 0},message:{text:a,attachments:[]}})}catch(e){if(d)return void await _i(this.fetchImpl,d,`Failed to handle message: ${ea(e)}`,t.onLog);throw e}const l=Gi(c);if(l){const s=Vi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=hi({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:t}),o=await n.send(i,l,{replyMarkup:s,sessionWebhook:d,senderId:r});if(o)return void this.logInfo({bridgeId:e.id,chatId:i,senderId:r,provider:o.provider,interactionId:o.interactionId,routed:!0===ia(c).routed,hasReplyMarkup:Boolean(s),replyPreview:ta(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(Zi(e.settings.robotCode)),replyPreview:ta(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,s,n,i,a={}){if(!0!==e.routed)return!1;const r=oa(e),{instanceId:o,sessionId:d,turnId:c}=r;if(!o||!d||!c)return this.logWarn({bridgeId:t.id,chatId:n,instanceId:o,sessionId:d,turnId:c,turnIdSources:r.sources,routed:e.routed},"dingding ai session progress card not started"),!1;const l=ha(o,d,c,t.id,n);return!!await this.dingdingProgress.applyUpdate({bridge:t,dingdingRuntime:s,key:l,chatId:n,text:i,replyMarkup:a.replyMarkup})&&(this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:n,instanceId:o,sessionId:d,turnId:c,key:l,hasSessionWebhook:Boolean(a.sessionWebhook),hasSenderId:Boolean(a.senderId),sourcePrompt:ta(a.sourcePrompt,80),textPreview:ta(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=qi(e),s=Mi(t.cardActionData||t.content),n=Mi(s.cardPrivateData||t.cardPrivateData||t.content),i=Mi(n.params||s.params||t.params),a=$i(t.userId),r=$i(i.biz_sender_id);return{userId:a||void 0,chatId:wi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:$i(i.biz_session_webhook)||void 0,conversationType:Si(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:Ai(Ti(t,s,n)),body:t,params:i}}(s.data);if(!this.dingdingAllowed(e,n.userId))return ki("not authorized",void 0,"unauthorized",n.body,n.params);const i=n.chatId||Zi(e.defaultChatId),a=n.deliverySenderId||n.senderId,r=n.sessionWebhook;i&&r&&t.chatWebhooks.set(i,r),i&&n.conversationType&&t.conversationTypes.set(i,n.conversationType),i&&a&&t.senderIds.set(i,a);const o=this.parseChatGatewayCallbackAction(n.callbackData);if(!o)return this.logWarn({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,...sa(n)},"dingding card callback action unsupported"),ki("Unsupported action",void 0,"unsupported",n.body,n.params);this.logInfo({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,actionType:o.type,...sa(n)},"dingding card callback action parsed");const d=await this.service.handleChatGatewayAction({source:{channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:n.userId||void 0},action:o});return"pending-decision"===o.type&&Wi(d)?ki(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):ki(Gi(d)||Zi(d.message)||"updated",Vi(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||Zi(t.updatesBuf),n=await async function(e){const t=$i((e.bridge.settings||{}).baseUrl)||"https://ilinkai.weixin.qq.com",s=await e.fetchImpl(`${t}/ilink/bot/getupdates`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${e.bridge.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({get_updates_buf:e.cursor,base_info:{channel_version:"1.0.2"}})}),n=await s.json().catch(()=>({}));if(!s.ok)throw new Error(`HTTP ${s.status}`);const i=Number(n.errcode??n.ret??0);if(0!==i)throw new Error($i(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=Pi(e);if(1!==t.message_type)continue;const s=$i(t.from_user_id),n=Ci(t);s&&n&&a.push({chatId:s,text:n,contextToken:$i(t.context_token)})}return{cursor:$i(n.get_updates_buf),messages:a}}({fetchImpl:this.fetchImpl,bridge:e,cursor:s});n.cursor&&(this.wechatCursors.set(e.id,n.cursor),this.updateChatBridge(e.id,{settings:{updatesBuf:n.cursor}}));for(const s of n.messages){const{chatId:n,contextToken:i,text:a}=s;if(e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:n,settings:{contextToken:i}}),e.defaultChatId&&e.defaultChatId!==n)continue;const r=Gi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=hi({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||Zi(t.contextToken)})}}}async pollPendingRoutes(){const e=await this.service.listPendingRoutes().catch(()=>[]);for(const t of e){if(this.announcedPending.has(t.id))continue;const e=da(t,(e,t)=>this.service.pendingDecisionCallbackData(e,t)),s=t.aiSessionId?this.service.listChatSessions().filter(e=>e.activeInstanceId===t.instanceId&&e.activeAiSessionId===t.aiSessionId):[],n=new Set;for(const t of s)t.bridgeId&&await this.sendToChatBinding(t,e).then(e=>{e&&n.add(t.id)});(0!==n.size||await this.sendToDefaultBridgeChats(e))&&this.announcedPending.add(t.id)}}async sendToDefaultBridgeChats(e){let t=!1;for(const s of this.service.listChatBridges()){if(!s.enabled||!s.defaultChatId)continue;let n=!1;await this.sendViaBridge(s,s.defaultChatId,e).catch(e=>{this.bridgeErrors.set(s.id,e instanceof Error?e.message:String(e)),n=!0})?t=!0:n||this.bridgeErrors.set(s.id,"Chat bridge message was not delivered.")}return t}async sendToChatBinding(e,t){if(!e.bridgeId)return!1;const s=this.service.requireChatBridge(e.bridgeId);if(!s.enabled)return!1;const n=await this.sendViaBridge(s,e.chatSessionId,t).catch(e=>{this.bridgeErrors.set(s.id,e instanceof Error?e.message:String(e))});return Boolean(n)}async sendViaBridge(e,t,s){const n="string"==typeof s?s:s.text,i="string"==typeof s?void 0:s.replyMarkup;return hi({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:this.dingdingBridges.get(e.id)}).send(t,n,{replyMarkup:i})}async pollTelegram(e){if(!e.token)return;const t=Number(e.settings?.telegramLastUpdateId),s=this.telegramOffsets.get(e.id)||(Number.isInteger(t)?t:void 0),n=await async function(e){const t=new URLSearchParams({timeout:"0",...e.offset?{offset:String(e.offset+1)}:{}}),s=await e.fetchImpl(`https://api.telegram.org/bot${e.bridge.token}/getUpdates?${t.toString()}`),n=await s.json().catch(()=>({}));if(!s.ok||!1===n.ok){if(409===s.status||/terminated by other getUpdates request/i.test(String(n.description||"")))return{updates:[],conflict:n.description||"Telegram getUpdates conflict: another bot instance is polling this token."};throw new Error(n.description||`Telegram getUpdates failed with HTTP ${s.status}`)}const i=[];for(const t of n.result||[]){const s=Number(t.update_id),n=Number.isInteger(s)?s:void 0,a=Pi(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Pi(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Pi(r.chat),d=Pi(r.from),c=void 0!==o.id?String(o.id):e.bridge.defaultChatId,l=void 0!==d.id?String(d.id):void 0;if(!c)continue;const p=$i(r.text??r.caption),u=oi(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:Ni(r),replyToMessageId:Ni(Pi(r.reply_to_message)),quoteText:$i(Pi(r.quote).text),autoBegin:1===u.length&&!p.trim(),hasSingleImageWithoutText:1===u.length&&!p.trim()})}return{updates:i}}({fetchImpl:this.fetchImpl,bridge:e,offset:s});if(n.conflict)this.bridgeErrors.set(e.id,n.conflict);else for(const t of n.updates){if(void 0!==t.updateId){const s=`${e.id}:${t.updateId}`;if(this.seenTelegramUpdates.has(s)||t.updateId<=(this.telegramOffsets.get(e.id)||0))continue;this.seenTelegramUpdates.add(s),this.telegramOffsets.set(e.id,t.updateId),this.updateChatBridge(e.id,{settings:{...e.settings,telegramLastUpdateId:t.updateId}})}if("callbackQuery"in t){const s=t.callbackQuery;await this.handleTelegramCallback(e,s).catch(async t=>{const n=ia(s.message),i=ia(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,ea(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${ea(t)}`).catch(t=>{this.bridgeErrors.set(e.id,ea(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await mi({fetchImpl:this.fetchImpl,bridge:e,message:t.message,onImageDownloadError:({fileId:t,fileName:s,error:n})=>{this.logWarn({bridgeId:e.id,fileId:t,fileName:s,error:ea(n)},"telegram image download skipped")}}),n=t.rawText.trim();(n||s.length)&&(this.logInfo({bridgeId:e.id,chatId:t.chatId,messageId:t.messageId,replyToMessageId:t.replyToMessageId,hasQuote:Boolean(t.quoteText),textPreview:ta(n)},"telegram chat gateway message received"),await this.handleTelegramIncomingText(e,t.chatId,t.userId,n,s,{sourceMessageId:t.messageId,replyToMessageId:t.replyToMessageId,quoteText:t.quoteText},{autoBegin:t.autoBegin}))}}async handleTelegramIncomingText(e,t,s,n,i=[],a={},r={}){const o=n.trim(),d=o.split(/\s+/,1)[0]?.toLowerCase()||"",c=Qi(e.id,t,s),l=this.telegramMessageAggregates.get(c);return r.autoBegin&&!l?(this.startTelegramAggregate(c,e,t,s,a),this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!0),void await this.sendTelegramMessage(e,t,"Image received. Continue sending messages, then send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Js([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):"/begin"===d?(this.startTelegramAggregate(c,e,t,s,a),void await this.sendTelegramMessage(e,t,"Started collecting messages. Send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Js([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):void("/end"!==d?l?.explicit?this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!0):this.isImmediateTelegramCommand(o)?await this.dispatchTelegramGatewayMessage(e,t,s,n,i,a):this.enqueueTelegramAggregate(c,e,t,s,n,i,a,!1):await this.flushTelegramAggregate(c,"manual"))}isImmediateTelegramCommand(e){return e.trim().startsWith("/")}startTelegramAggregate(e,t,s,n,i={}){const a=this.telegramMessageAggregates.get(e);a?.timer&&clearTimeout(a.timer),this.telegramMessageAggregates.set(e,{bridgeId:t.id,chatId:s,userId:n,texts:[],attachments:[],context:i,explicit:!0})}enqueueTelegramAggregate(e,t,s,n,i,a,r,o){const d=this.telegramMessageAggregates.get(e);d?.timer&&clearTimeout(d.timer);const c=d||{bridgeId:t.id,chatId:s,userId:n,texts:[],attachments:[],context:r,explicit:o};var l,p;c.bridgeId=t.id,c.chatId=s,c.userId=n,c.context=(l=c.context,p=r,{sourceMessageId:l?.sourceMessageId??p.sourceMessageId,replyToMessageId:l?.replyToMessageId??p.replyToMessageId,quoteText:l?.quoteText||p.quoteText}),c.explicit=c.explicit||o,i.trim()&&c.texts.push(i),c.attachments.push(...a),c.explicit?c.timer=void 0:c.timer=setTimeout(()=>{this.flushTelegramAggregate(e,"timer")},1e3),this.telegramMessageAggregates.set(e,c)}async flushTelegramAggregate(e,t){const s=this.telegramMessageAggregates.get(e);if(!s)return!1;s.timer&&clearTimeout(s.timer),this.telegramMessageAggregates.delete(e);const n=this.service.requireChatBridge(s.bridgeId),i=s.texts.join("\n\n").trim();return i||s.attachments.length?(await this.dispatchTelegramGatewayMessage(n,s.chatId,s.userId,i,s.attachments,s.context),!0):("timer"!==t&&await this.sendTelegramMessage(n,s.chatId,"No messages collected.",{replyToMessageId:s.context?.sourceMessageId}),!1)}async handleTelegramAggregateEndCallback(e,t,s,n,i){const a=this.telegramMessageAggregateEndTokens.get(n)||"";if(a!==Qi(e.id,t,i))return void await this.answerTelegramCallback(e,s,"This collection is not active here");const r=await this.flushTelegramAggregate(a,"callback");await this.answerTelegramCallback(e,s,r?"Collected message sent":"No messages collected")}telegramAggregateEndCallbackData(e){const t=o.createHash("sha256").update(e).digest("base64url").slice(0,16);return this.telegramMessageAggregateEndTokens.set(t,e),`task_handoff:cp_msg_end:${t}`}async dispatchTelegramGatewayMessage(e,t,s,n,i=[],a={}){try{const r=void 0!==a.replyToMessageId?this.telegramReplyAiSessionTarget(e.id,t,a.replyToMessageId):void 0,o=function(e,t){const s=String(t||"").trim(),n=e.trim();return s?[`引用:${s}`,n].filter(Boolean).join("\n"):n}(n,a.quoteText),d=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"telegram",bridgeId:e.id,chatSessionId:t,userId:s},message:{text:o,attachments:i},...r?{target:{instanceId:r.instanceId,aiSessionId:r.sessionId}}:{}}),c=Gi(d),l=Vi(d);if(c){const s=await this.sendTelegramMessage(e,t,c,{replyMarkup:l,replyToMessageId:a.sourceMessageId});this.logInfo({bridgeId:e.id,chatId:t,sourceMessageId:a.sourceMessageId,replyToMessageId:a.replyToMessageId,replyTargetInstanceId:r?.instanceId,replyTargetSessionId:r?.sessionId,sentMessageId:Number(ia(s).message_id)||void 0,routed:!0===ia(d).routed,replyPreview:ta(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,ea(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${ea(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,ea(t))})}}async handleTelegramCallback(e,t){const s=Zi(t.data),n=ia(t.from),i=ia(t.message),a=ia(i.chat),r=void 0!==a.id?String(a.id):e.defaultChatId,o=void 0!==n.id?String(n.id):void 0;if(!r||!this.telegramAllowed(e,o))return;const d=s.match(/^task_handoff:cp_ai_cancel:([^:]+)$/);if(d){const s=this.aiSessionCancelTokens.get(d[1]);return s?void await this.handleTelegramAiSessionCancelCallback(e,r,Zi(t.id),s.instanceId,s.sessionId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(t.id),"This AI session action expired")}const c=s.match(/^task_handoff:cp_ai_steer:([^:]+)$/);if(c){const s=this.aiSessionQueueActionTokens.get(c[1]);return s&&"steer"===s.type?void await this.handleTelegramAiSessionQueueSteerCallback(e,r,Zi(t.id),s.instanceId,s.sessionId,s.queueId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(t.id),"This AI session action expired")}const l=s.match(/^task_handoff:cp_ai_qdel_menu:([^:]+)$/);if(l){const s=this.aiSessionQueueActionTokens.get(l[1]);return s&&"delete-menu"===s.type?void await this.handleTelegramAiSessionQueueDeleteMenuCallback(e,r,Zi(t.id),s.instanceId,s.sessionId,o,Ji(i)):void await this.answerTelegramCallback(e,Zi(t.id),"This AI session action expired")}const p=s.match(/^task_handoff:cp_ai_qdel:([^:]+)$/);if(p){const s=this.aiSessionQueueActionTokens.get(p[1]);return s&&"delete-item"===s.type?void await this.handleTelegramAiSessionQueueDeleteItemCallback(e,r,Zi(t.id),Ji(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,Zi(t.id),"This AI session action expired")}const u=s.match(/^task_handoff:cp_msg_end:([^:]+)$/);if(u)return void await this.handleTelegramAggregateEndCallback(e,r,Zi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(s);if(!g)return void await this.answerTelegramCallback(e,Zi(t.id),"Unsupported action");const h=await this.service.handleChatGatewayAction({source:{channel:"telegram",bridgeId:e.id,chatSessionId:r,userId:o},action:g});if("pending-decision"===g.type&&Wi(h)){await this.answerTelegramCallback(e,Zi(t.id),"");const s=Ji(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,Zi(t.id),Zi(h.message)||"updated");const m=Gi(h),I=Vi(h),f=i.message_id;m&&"number"==typeof f?await this.editTelegramMessage(e,r,f,m,{replyMarkup:I}):m&&await this.sendTelegramMessage(e,r,m,{replyMarkup:I})}async deleteTelegramStandaloneApprovalMessage(e,t,s){[...this.telegramProgress.entries.values()].find(n=>n.messageId===s&&n.route?.bridgeId===e.id&&n.route?.chatId===t)||await this.deleteTelegramMessage(e,t,s).catch(n=>{this.logWarn({bridgeId:e.id,chatId:t,messageId:s,error:ea(n)},"telegram callback message delete failed")})}async handleTelegramAiSessionCancelCallback(e,t,s,n,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,n,i,r))try{await this.service.interruptAiSession(n,i),await this.answerTelegramCallback(e,s,"Interrupt sent"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,userId:a},"telegram ai session cancel sent")}catch(t){this.bridgeErrors.set(e.id,ea(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${ta(ea(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueSteerCallback(e,t,s,n,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,n,i,o))try{await this.service.steerAiSessionQueuedMessage(n,i,a),await this.answerTelegramCallback(e,s,"Steered queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,queueId:a,userId:r},"telegram ai session queued message steered")}catch(t){this.bridgeErrors.set(e.id,ea(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${ta(ea(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteMenuCallback(e,t,s,n,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,n,i,r))try{const r=ia(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>ia(e)).filter(e=>"queued"===Zi(e.status)&&Zi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,ca).map((e,t)=>[{text:ga(Zi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,Zi(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:Js(d)}),await this.answerTelegramCallback(e,s,"Select a queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:n,sessionId:i,itemCount:o.length,userId:a},"telegram ai session queue delete menu sent")}catch(t){this.bridgeErrors.set(e.id,ea(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${ta(ea(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteItemCallback(e,t,s,n,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,i,a,n))try{await this.service.removeAiSessionQueuedMessage(i,a,r),await this.answerTelegramCallback(e,s,"Queued message deleted"),Number.isInteger(n)&&await this.deleteTelegramMessage(e,t,n).catch(s=>{this.logWarn({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,messageId:n,error:ea(s)},"telegram ai session queue delete menu message delete failed")}),this.logInfo({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,userId:o},"telegram ai session queued message deleted")}catch(t){this.bridgeErrors.set(e.id,ea(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${ta(ea(t),120)}`)}else await this.answerTelegramCallback(e,s,"This chat is not bound to that AI session")}telegramAiSessionBindingActive(e,t,s,n){return this.service.listChatSessions().some(i=>i.bridgeId===e.id&&i.chatSessionId===t&&i.activeInstanceId===s&&i.activeAiSessionId===n)}telegramAiSessionActionAllowed(e,t,s,n,i){if(this.telegramAiSessionBindingActive(e,t,s,n))return!0;if(!Number.isInteger(i))return!1;const a=this.telegramReplyAiSessionTarget(e.id,t,i);return Boolean(a&&a.instanceId===s&&a.sessionId===n)}parseChatGatewayCallbackAction(e){return function(e,t){const s=e.match(/^task_handoff:cp_session:(\d+)$/);if(s)return{type:"ai-session",index:Number(s[1])};const n=e.match(/^task_handoff:cp_i:([^:]+)$/)||e.match(/^task_handoff:cp_instance_apps:([^:]+)$/);if(n)return n[0].startsWith("task_handoff:cp_i:")?{type:"instance-app-menu",instanceId:li(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Bi(n[1])};const i=e.match(/^task_handoff:cp_a:([^:]+)$/)||e.match(/^task_handoff:cp_launch_app:([^:]+):([^:]+)$/);if(i){if(i[0].startsWith("task_handoff:cp_a:")){const e=t(i[1],"launch-app");return{type:"launch-app",instanceId:li(e.instanceId,"instanceId"),appId:li(e.appId,"appId")}}return{type:"launch-app",instanceId:Bi(i[1]),appId:Bi(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:li(e.routeId,"routeId"),decision:pi(e.decision)}}const r=e.match(/^task_handoff:approval:([^:]+:ai:[^:]+):(allow|deny|skip)$/);return r?{type:"pending-decision",routeId:r[1],decision:r[2]}:void 0}(e,(e,t)=>this.service.resolveChatActionToken(e,t))}dingdingAllowed(e,t){const s=String(t||"").trim();return!!s&&(0===e.allowedUserIds.length?(e.allowedUserIds=[s],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:s},"dingding user bound"),!0):e.allowedUserIds.includes(s))}telegramAllowed(e,t){const s=String(t||"").trim();return!!s&&(0===e.allowedUserIds.length?(e.allowedUserIds=[s],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:s},"telegram user bound"),!0):e.allowedUserIds.includes(s))}async answerTelegramCallback(e,t,s){return async function(e,t,s,n){t.token&&s&&await e(`https://api.telegram.org/bot${t.token}/answerCallbackQuery`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({callback_query_id:s,text:n.slice(0,200)})})}(this.fetchImpl,e,t,s)}async editTelegramMessage(e,t,s,n,i={}){return async function(e,t,s,n,i,a={}){if(t.token)return ti(s=>e(`https://api.telegram.org/bot${t.token}/editMessageText`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),"editMessageText",i,{chat_id:s,message_id:n,...a.replyMarkup?{reply_markup:a.replyMarkup}:{}},a)}(this.fetchImpl,e,t,s,n,i)}async sendTelegramMessage(e,t,s,n={}){return Zn(this.fetchImpl,e,t,s,n)}async deleteTelegramMessage(e,t,s){return async function(e,t,s,n){if(!t.token||!Number.isInteger(n))return;const i=await e(`https://api.telegram.org/bot${t.token}/deleteMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({chat_id:s,message_id:n})}),a=await i.json().catch(()=>({}));if(!i.ok||!1===a.ok)throw new Error(a.description||`Telegram deleteMessage failed with HTTP ${i.status}`)}(this.fetchImpl,e,t,s)}async rememberTelegramProgressMessage(e,t,s,n,i){if(!0!==e.routed)return;const a=oa(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(ia(n).message_id);if(!(r&&o&&d&&Number.isInteger(c)))return void this.logWarn({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,messageId:Number.isInteger(c)?c:void 0,routed:e.routed},"telegram ai session progress message not remembered");const l=ha(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set(Yi(t.id,s,c),{instanceId:r,sessionId:o,turnId:d,messageId:c}),this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,key:l,messageId:c,progressKeys:this.telegramProgressKeysFor(r,o,t.id,s),textPreview:ta(i)},"telegram ai session progress message remembered"),"queue"===function(e){return e.aiSession&&"action"in e.aiSession?e.aiSession.action:void 0}(e)&&p?.messageId&&p.messageId!==c&&(this.telegramProgressMessageTargets.delete(Yi(t.id,s,p.messageId)),await this.deleteTelegramMessage(t,s,p.messageId).catch(e=>{this.logWarn({bridgeId:t.id,chatId:s,instanceId:r,sessionId:o,turnId:d,replacedMessageId:p.messageId,replacementMessageId:c,error:ea(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get(Yi(e,t,s));if(n)return n;for(const[n,i]of this.telegramProgress.entries)if(i.messageId===s&&i.route?.bridgeId===e&&i.route?.chatId===t){const i=Xi(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set(Yi(e,t,s),n),n}}}async pollAiSessionUpdates(){this.logAiSessionDelivery({stage:"poll-tick"});const e=await this.service.listAiSessions({refresh:!0}).catch(()=>{});if(!e)return void this.logAiSessionDelivery({stage:"poll-no-snapshot"});const t=new Map(e.instances.map(e=>[e.instanceId,e.aiSessions]));this.logAiSessionDelivery({stage:"poll-snapshot",instanceCount:t.size,instances:[...t.entries()].map(([e,t])=>({instanceId:e,sessionCount:t.sessions?.length||0,runningCount:t.runningCount,waitingCount:t.waitingCount,updatedAt:t.updatedAt}))});const s=await this.aiSessionInstanceNames();for(const[e,n]of t)await this.deliverAiSessionSnapshot(e,n,s)}async deliverAiSessionSnapshot(e,t,s){const n=Array.isArray(t.sessions)?t.sessions:[];if(this.pruneAiSessionDeliveryState(e,new Set(n.map(e=>e.id))),0===n.length)return;const i=s||await this.aiSessionInstanceNames(),a=await this.aiSessionPendingApprovalRoutes(e);await this.deliverOwnedTelegramProgress(e,n,i,a),await this.deliverActiveBindingTelegramProgress(e,n,i,a)}pruneAiSessionDeliveryState(e,t){for(const[s,n]of this.telegramProgress.entries){const i=n.route?.bridgeId&&n.route.chatId?this.telegramProgressMessageTargets.get(Yi(n.route.bridgeId,n.route.chatId,n.messageId||0))||Xi(s,n.route.bridgeId,n.route.chatId):void 0;i&&i.instanceId===e&&!t.has(i.sessionId)&&(n.timer&&clearTimeout(n.timer),this.telegramProgress.entries.delete(s),this.deliveredAiSessionFingerprints.delete(s),n.route?.bridgeId&&n.route.chatId&&n.messageId&&this.telegramProgressMessageTargets.delete(Yi(n.route.bridgeId,n.route.chatId,n.messageId)))}}async deliverActiveBindingTelegramProgress(e,t,s,n){for(const i of this.service.listChatSessions()){if(!i.bridgeId||i.activeInstanceId!==e||!i.activeAiSessionId)continue;const a=t.find(e=>e.id===i.activeAiSessionId);if(!a)continue;const r=ba(s.get(e)||e,a),o=ya(a,r);if(!o){this.logAiSessionDelivery({stage:"skip-empty-text",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:na(va(a))});continue}const d=ma(a);if(!d){this.logAiSessionDelivery({stage:"skip-empty-turn-id",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:na(va(a))});continue}if(this.listTelegramOwnedProgressEntries(e).some(e=>e.route.bridgeId===i.bridgeId&&e.route.chatId===i.chatSessionId&&e.owner.sessionId===a.id&&e.owner.turnId===d))continue;const c=ha(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=Ia(a,r);if(!l||this.deliveredAiSessionFingerprints.get(c)===l){this.logAiSessionDelivery({stage:"skip-duplicate-fingerprint",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,hasFingerprint:Boolean(l),fingerprintMatched:this.deliveredAiSessionFingerprints.get(c)===l,latestTurn:na(va(a)),textPreview:ta(o)});continue}const p=pa({cancelCallbackData:this.aiSessionCancelCallbackData(e,a.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,a.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,a.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:n.get(a.id)||ua(e,a),session:a});if(this.logAiSessionDelivery({stage:"attempt",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),hasProgressEntry:this.telegramProgress.entries.has(c),status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:na(va(a)),textPreview:ta(o)}),await this.updateChatAiSessionProgress(i,o,c,p).catch(t=>(this.bridgeErrors.set(i.bridgeId||"",t instanceof Error?t.message:String(t)),this.logWarn({bridgeId:i.bridgeId,chatId:i.chatSessionId,instanceId:e,sessionId:a.id,turnId:d,key:c,error:ea(t)},"chat ai session progress update failed"),!1)))this.logAiSessionDelivery({stage:"delivered",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:ta(o)}),this.deliveredAiSessionFingerprints.set(c,l);else{if("telegram"===this.service.requireChatBridge(i.bridgeId).channel){const t=this.telegramProgress.entries.get(c),s=this.service.requireChatBridge(i.bridgeId);this.logAiSessionDelivery({stage:t?"telegram-update-not-delivered":"telegram-no-progress-entry",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),bridgeEnabled:s.enabled,bridgeChannel:s.channel,progressMessageId:t?.messageId,progressLastText:ta(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:na(va(a)),textPreview:ta(o)});continue}await this.sendToChatBinding(i,{text:o}).catch(e=>(this.bridgeErrors.set(i.bridgeId||"",e instanceof Error?e.message:String(e)),!1))&&(this.logAiSessionDelivery({stage:"fallback-sent",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,textPreview:ta(o)}),this.deliveredAiSessionFingerprints.set(c,l))}}}async deliverOwnedTelegramProgress(e,t,s,n){for(const i of this.listTelegramOwnedProgressEntries(e)){const a=this.service.requireChatBridge(i.route.bridgeId);if("telegram"!==a.channel||!a.enabled)continue;const r=this.matchTelegramOwnedProgressSession(i,t);if(!r)continue;const o={bridgeId:i.route.bridgeId,chatSessionId:i.route.chatId},d=ba(s.get(e)||e,r),c=ya(r,d),l=ma(r);if(!c||!l){this.logAiSessionDelivery({stage:"remembered-skip-turn-or-text",key:i.key,parsedTurnId:i.owner.turnId,currentTurnId:l,hasText:Boolean(c),instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}if(i.owner.turnId&&l!==i.owner.turnId){this.logAiSessionDelivery({stage:"remembered-skip-different-active-turn",key:i.key,ownerTurnId:i.owner.turnId,currentTurnId:l,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const p=l===i.owner.turnId?i.key:ha(e,r.id,l,i.route.bridgeId,i.route.chatId);if(p!==i.key&&this.telegramProgress.entries.has(p))continue;if(p!==i.key&&!this.rekeyTelegramOwnedProgress(i,p,{instanceId:e,sessionId:r.id,turnId:l}))continue;const u=Ia(r,d);if(!u||this.deliveredAiSessionFingerprints.get(p)===u){this.logAiSessionDelivery({stage:"remembered-skip-fingerprint",key:i.key,hasFingerprint:Boolean(u),fingerprintMatched:this.deliveredAiSessionFingerprints.get(p)===u,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId});continue}const g=pa({cancelCallbackData:this.aiSessionCancelCallbackData(e,r.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,r.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,r.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:n.get(r.id)||ua(e,r),session:r}),h=await this.updateChatAiSessionProgress(o,c,p,g).catch(e=>(this.bridgeErrors.set(o.bridgeId||"",e instanceof Error?e.message:String(e)),!1));this.logAiSessionDelivery({stage:h?"remembered-delivered":"remembered-not-delivered",key:p,instanceId:e,sessionId:r.id,bridgeId:i.route.bridgeId,chatId:i.route.chatId,textPreview:ta(c)}),h&&(this.telegramProgressMessageTargets.set(Yi(i.route.bridgeId,i.route.chatId,i.messageId),{instanceId:e,sessionId:r.id,turnId:l,messageId:i.messageId}),this.deliveredAiSessionFingerprints.set(p,u))}}rekeyTelegramOwnedProgress(e,t,s){return!!this.telegramProgress.rekey(e.key,t)&&(this.deliveredAiSessionFingerprints.delete(e.key),this.telegramProgressMessageTargets.set(Yi(e.route.bridgeId,e.route.chatId,e.messageId),{...s,messageId:e.messageId}),!0)}logAiSessionDelivery(e){this.logInfo(e,"telegram ai session delivery flow")}telegramProgressKeysFor(e,t,s,n){const i=`${e}:${t}:`,a=`:${s}:${n}`;return[...this.telegramProgress.entries.keys()].filter(e=>e.startsWith(i)&&e.endsWith(a))}listTelegramOwnedProgressEntries(e){const t=[];for(const[s,n]of this.telegramProgress.entries){if(!n.route?.bridgeId||!n.route.chatId||!Number.isInteger(n.messageId))continue;const i=this.telegramProgressMessageTargets.get(Yi(n.route.bridgeId,n.route.chatId,n.messageId))||Xi(s,n.route.bridgeId,n.route.chatId);i&&i.instanceId===e&&t.push({key:s,route:{bridgeId:n.route.bridgeId,chatId:n.route.chatId},messageId:n.messageId,owner:{...i,messageId:n.messageId}})}return t}matchTelegramOwnedProgressSession(e,t){const s=t.find(t=>t.id===e.owner.sessionId);if(s)return s;if(e.owner.turnId){const s=t.find(t=>t.activeTurnId===e.owner.turnId||va(t)?.id===e.owner.turnId||va(t)?.providerTurnId===e.owner.turnId);if(s)return s}}async updateChatAiSessionProgress(e,t,s,n){if(!e.bridgeId)return this.logAiSessionDelivery({stage:"chat-update-skip-missing-bridge",chatId:e.chatSessionId,key:s}),!1;const i=this.service.requireChatBridge(e.bridgeId);if(!i.enabled)return this.logAiSessionDelivery({stage:"chat-update-skip-bridge-state",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,bridgeEnabled:i.enabled,bridgeChannel:i.channel}),!1;if("dingding"===i.channel){const a=this.dingdingBridges.get(i.id);if(!a)return!1;const r=await this.dingdingProgress.applyUpdate({bridge:i,dingdingRuntime:a,key:s,chatId:e.chatSessionId,text:t,replyMarkup:Ki({actionRows:n})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:ta(t)},"dingding ai session progress updated"),r}if("telegram"!==i.channel)return!1;const a=this.telegramProgress.entries.get(s);if(!await this.telegramProgress.applyUpdate({bridge:i,key:s,chatId:e.chatSessionId,text:t,actionRows:n})){const t=this.telegramProgress.entries.get(s);return this.logAiSessionDelivery({stage:"telegram-update-returned-false",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,progressMessageId:a?.messageId||t?.messageId,progressLastText:ta(a?.lastText||t?.lastText),progressHasPending:Boolean(a?.pending||t?.pending)}),!1}return this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,messageId:a?.messageId||this.telegramProgress.entries.get(s)?.messageId,textPreview:ta(t)},a?"telegram ai session progress edited":"telegram ai session progress sent"),!0}async aiSessionInstanceNames(){const e=await this.service.boardAsync().catch(()=>[]);return new Map(e.map(e=>[e.id,e.name||e.id]))}async aiSessionPendingApprovalRoutes(e){const t=await this.service.listPendingRoutes().catch(()=>[]);return new Map(t.filter(t=>t.instanceId===e&&t.aiSessionId&&"approval"===t.kind).map(e=>[e.aiSessionId||"",e]))}aiSessionCancelCallbackData(e,t){const s=o.createHash("sha256").update(`${e}\0${t}`).digest("base64url").slice(0,16);return this.aiSessionCancelTokens.set(s,{instanceId:e,sessionId:t}),`task_handoff:cp_ai_cancel:${s}`}aiSessionQueueSteerCallbackData(e,t,s){return`task_handoff:cp_ai_steer:${this.aiSessionQueueActionToken({type:"steer",instanceId:e,sessionId:t,queueId:s})}`}aiSessionQueueDeleteMenuCallbackData(e,t){return`task_handoff:cp_ai_qdel_menu:${this.aiSessionQueueActionToken({type:"delete-menu",instanceId:e,sessionId:t})}`}aiSessionQueueDeleteItemCallbackData(e,t,s){return`task_handoff:cp_ai_qdel:${this.aiSessionQueueActionToken({type:"delete-item",instanceId:e,sessionId:t,queueId:s})}`}aiSessionQueueActionToken(e){const t=o.createHash("sha256").update([e.type,e.instanceId,e.sessionId,"queueId"in e?e.queueId:""].join("\0")).digest("base64url").slice(0,16);return this.aiSessionQueueActionTokens.set(t,e),t}}function Gi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function Wi(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function Vi(e){const t="object"==typeof e&&e&&"replyMarkup"in e?e.replyMarkup:void 0;if(t&&"object"==typeof t&&!Array.isArray(t))return t}function Ki(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Js(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Ji(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Qi(e,t,s){return[e,t,s||""].join(":")}function Yi(e,t,s){return[e,t,String(s)].join(":")}function Xi(e,t,s){const n=`:${t}:${s}`;if(!e.endsWith(n))return;const i=e.slice(0,-n.length).split(":"),a=i[0]||"",r=i[1]||"",o=i.slice(2).join(":")||void 0;return a&&r?{instanceId:a,sessionId:r,turnId:o}:void 0}function Zi(e){return"string"==typeof e?e:""}function ea(e){return e instanceof Error?e.message:String(e)}function ta(e,t=160){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function sa(e){const t=aa(e.body.cardActionData||e.body.content),s=aa(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:Zi(e.body.outTrackId),spaceId:Zi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:ta(e.callbackData,120),actionIds:[...ra(s.actionIdList),...ra(t.actionIdList),Zi(s.actionId),Zi(t.actionId),Zi(e.body.actionId)].filter(Boolean).slice(0,5),cardActionDataKeys:Object.keys(t).sort(),privateDataKeys:Object.keys(s).sort(),paramKeys:Object.keys(e.params).sort(),hasSessionWebhook:Boolean(e.sessionWebhook)}}function na(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:ta(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:ta(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function ia(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function aa(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return ia(JSON.parse(e))}catch{return{}}return{}}function ra(e){return Array.isArray(e)?e.map(e=>Zi(e)).filter(Boolean):[]}function oa(e){const t=e.aiSession&&"session"in e.aiSession?e.aiSession:void 0,s=t?.session||(e.aiSession&&"id"in e.aiSession?e.aiSession:void 0);return{instanceId:e.instanceId||e.instance?.id||"",sessionId:e.aiSessionId||s?.id||e.binding?.activeAiSessionId||"",turnId:e.turnId||e.providerTurnId||t?.turnId||t?.providerTurnId||(s?ma(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?va(s)?.id:void 0}}}function da(e,t){const s=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,n="approval"===e.kind?`/approve ${e.id}\n/deny ${e.id}\n/skip ${e.id}`:`/reply ${e.id} <message>`,i=`[${s}]\n${e.result}\n\n${n}`;return"approval"!==e.kind?{text:i}:{text:i,replyMarkup:Js([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const ca=5,la=48;function pa(e){const{cancelCallbackData:t,queueSteerCallbackData:s,queueDeleteMenuCallbackData:n,permissionCallbackData:i,pendingApproval:a,session:r}=e,o="approval"===a?.kind?[[{text:"Allow",callbackData:i(a.id,"allow")},{text:"Skip",callbackData:i(a.id,"skip")},{text:"Deny",callbackData:i(a.id,"deny")}]]:[];if("running"!==r.status)return o;const d=(r.queue?.items||[]).filter(e=>"queued"===e.status).slice(0,ca);return[...d.map((e,t)=>[{text:ga(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function ua(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function ga(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,la-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function ha(e,t,...s){return[e,t,...s].join(":")}function ma(e){const t=va(e);return t?.id?t.id:Zi(e.activeTurnId)}function Ia(e,t=""){const s=va(e);return[t,s?.id||"",String(s?.revision??""),s?.status||e.status||"",s?.phase||e.phase||"",s?.lastMessage||"",s?.summary||"",s?.userPrompt||"",e.error||"",e.currentTool?.name||"",e.currentTool?.inputPreview||"",fa(e)].join("\0")}function fa(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function ya(e,t){const s=function(e,t){return t?t.lastMessage?.trim()?t.lastMessage.trim():t.summary?.trim()?t.summary.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:"":e.lastMessage?.trim()?e.lastMessage.trim():e.summary?.trim()?e.summary.trim():e.error?.trim()?"failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:""}(e,va(e));return s?`${t}\n${s}`:""}function ba(e,t){const s=[t.status,t.phase&&"unknown"!==t.phase?t.phase:void 0].filter(Boolean).join("/");return`${e} · ${t.agent||"ai"} ${s||"unknown"}`}function va(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}class wa{clients=new Set;listeners=new Set;seq=0;connect(e){e.topics=new Set(["*"]),this.clients.add(e),e.on("close",()=>{this.clients.delete(e)}),e.on("message",t=>{const s=function(e){try{const t=JSON.parse(String(e||"{}"));return t&&"object"==typeof t?t:void 0}catch{return}}(t);"subscribe"===s?.type&&(e.topics=new Set(s.topics?.length?s.topics:["*"]))})}publish(e,t,s={}){const n=this.createEvent(e,t,s),i=n.topic,a=JSON.stringify(n);for(const e of this.listeners)try{e(n)}catch{this.listeners.delete(e)}for(const t of this.clients)if(t.readyState===t.OPEN&&Sa(t.topics,i,e))try{t.send(a)}catch{this.clients.delete(t)}return n}on(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}send(e,t,s,n={}){const i=this.createEvent(t,s,n);return e.send(JSON.stringify(i)),i}createEvent(e,t,s={}){return{v:1,id:`evt_${Date.now().toString(36)}_${Math.random().toString(16).slice(2)}`,seq:++this.seq,type:e,topic:s.topic||tt(e),createdAt:(new Date).toISOString(),payload:t,scope:s.scope}}}function Sa(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const Aa={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},ka=r.z.object({instanceId:r.z.string().trim().min(1).max(120),sessionId:r.z.string().trim().min(1).max(120),kind:r.z.literal("image").default("image"),name:r.z.string().trim().min(1).max(240),mime:r.z.string().trim().min(1).max(120),data:r.z.string().min(1).max(31457280)}).strict();class Ta{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const n=ka.parse(e),i=function(e,t){const s=/^data:([^;,]+);base64,(.+)$/i.exec(e.trim()),n=s?.[1]||t,i=s?.[2]||e;return{mime:n,buffer:Buffer.from(i,"base64")}}(n.data,n.mime),a=i.mime.toLowerCase(),r=Aa[a];if(!r)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>f)throw new Error("Image must be between 1 byte and 20971520 bytes.");const d=`att_${o.randomUUID().replace(/-/g,"").slice(0,24)}`,l=new Date,p=new Date(l.getTime()+this.ttlMs),u=s.join(c.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(u,{recursive:!0});const g=function(e){return s.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(n.name),h=s.join(u,`${d}-${g.endsWith(r)?g:`${g}${r}`}`);t.writeFileSync(h,i.buffer);const m={id:d,instanceId:n.instanceId,sessionId:n.sessionId,kind:"image",name:g,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:h,createdAt:l.toISOString(),expiresAt:p.toISOString()};return this.items.set(d,m),this.publicAttachment(m)}resolveRefs(e,t,s){this.pruneExpired();const n=r.z.array(z).max(6).default([]).parse(e),i=n.map(e=>this.requireAttachment(e,t,s));if(i.reduce((e,t)=>e+t.size,0)>y)throw new Error("Images must be 41943040 bytes or less in total.");for(const e of n)this.consumeAttachment(e.id);return i}requireAttachment(e,t,s){const n=this.items.get(e.id);if(!n)throw new Error(`AI session attachment not found or expired: ${e.id}`);if(e.kind&&e.kind!==n.kind)throw new Error(`AI session attachment kind mismatch: ${e.id}`);if(n.instanceId!==t||n.sessionId!==s)throw new Error(`AI session attachment scope mismatch: ${e.id}`);return C.parse({id:n.id,kind:n.kind,name:n.name,mime:n.mime,size:n.size,data:n.data})}consumeAttachment(e){const s=this.items.get(e);if(s){this.items.delete(e);try{t.unlinkSync(s.path)}catch{}}}publicAttachment(e){return{id:e.id,kind:e.kind,name:e.name,mime:e.mime,size:e.size,expiresAt:e.expiresAt}}pruneExpired(){const e=Date.now();for(const[s,n]of this.items)if(!(Date.parse(n.expiresAt)>e)){this.items.delete(s);try{t.unlinkSync(n.path)}catch{}}}}function Ea(e){return e.readyState===(e.OPEN??1)}function _a(e,t,s){e.close(function(e){if("number"==typeof e&&Number.isInteger(e))return e>=3e3&&e<=4999||e>=1e3&&e<=1014&&1004!==e&&1005!==e&&1006!==e?e:void 0}(t),function(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):"string"==typeof e?e:"";if(Buffer.byteLength(t,"utf8")<=123)return t;let s="";for(const e of t){const t=`${s}${e}`;if(Buffer.byteLength(t,"utf8")>123)break;s=t}return s}(s)||void 0)}function Ca(e,t){e.send(t.data,{binary:t.isBinary})}function Na(e,t){if(Ea(e))for(const s of t.splice(0))Ca(e,s)}class za{attempt=0;timer;get pending(){return Boolean(this.timer)}get attempts(){return this.attempt}schedule(e,t={}){if(this.timer)return;const s=function(e,t=Math.random){const s=Math.min(3e4,1e3*2**Math.max(0,e));return Math.min(3e4,Math.round(s*(.75+.5*t())))}(this.attempt,t.random);this.attempt+=1;const n=t.setTimeoutFn||setTimeout;return this.timer=n(()=>{this.timer=void 0,e()},s),{attempt:this.attempt,delay:s}}reset(e=clearTimeout){this.timer&&e(this.timer),this.timer=void 0,this.attempt=0}cancel(e=clearTimeout){this.timer&&e(this.timer),this.timer=void 0}}const Oa=r.z.object({nodeId:r.z.string().trim().min(1).max(120)}).strict();function Ra(e,t){if(!t)throw Object.assign(new Error(`Node ${e.id} paired-HMAC auth is missing keyId.`),{statusCode:500,code:"NODE_AGENT_REMOTE_KEY_ID_MISSING"});return t}function xa(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Da=new Map;function ja(e,t){if("paired-hmac"!==e.auth.mode||!e.auth.keyId||!e.auth.secret){const e=new Error("Reverse tunnel requires paired-HMAC node auth.");throw Object.assign(e,{statusCode:401,code:"NODE_AGENT_TUNNEL_REQUIRES_PAIRED_HMAC"}),e}const s=function(e){const t=new Map;for(const[s,n]of Object.entries(e)){const e=Array.isArray(n)?n.join(","):"string"==typeof n?n:void 0===n?"":String(n);t.set(s.toLowerCase(),e)}return{nodeId:t.get("x-taskhandoff-node-id")||"",keyId:t.get("x-taskhandoff-key-id")||"",timestamp:t.get("x-taskhandoff-timestamp")||"",nonce:t.get("x-taskhandoff-nonce")||"",bodySha256:t.get("x-taskhandoff-body-sha256")||"",signature:t.get("x-taskhandoff-signature")||""}}(t.headers);if(s.nodeId!==e.id)throw Object.assign(new Error("Invalid reverse tunnel node id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NODE_MISMATCH"});if(s.keyId!==e.auth.keyId)throw Object.assign(new Error("Invalid reverse tunnel key id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_KEY_INVALID"});const n=Date.parse(s.timestamp);if(!Number.isFinite(n)||Math.abs(Date.now()-n)>jn)throw Object.assign(new Error("Reverse tunnel timestamp is outside the allowed window."),{statusCode:401,code:"NODE_AGENT_TUNNEL_TIMESTAMP_INVALID"});const i=Date.now()-jn;for(const[e,t]of Da)t<=i&&Da.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||Da.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const r=Pn("");if(s.bodySha256!==r)throw Object.assign(new Error("Reverse tunnel body hash is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_BODY_HASH_INVALID"});const d=t.url||`/api/node-agent/tunnel?nodeId=${encodeURIComponent(e.id)}`,c=Mn(e.auth.secret,{keyId:s.keyId,method:"GET",pathWithQuery:d,timestamp:s.timestamp,nonce:s.nonce,bodySha256:r});if(!function(e,t){const s=Buffer.from(e,"hex"),n=Buffer.from(t,"hex");return s.length===n.length&&o.timingSafeEqual(s,n)}(s.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});Da.set(a,Date.now()+jn)}class Pa{sockets=new Map;events;onStreamsHello;constructor(e,t={}){this.events=e,this.onStreamsHello=t.onStreamsHello}attach(e,t){const s=this.sockets.get(e);if(s){for(const t of s.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} was replaced.`));for(const[e,t]of s.streams)s.streams.delete(e),t.downstream.close(1011,"Reverse tunnel was replaced.")}const n=new Map;this.sockets.set(e,{socket:t,pending:n,streams:new Map}),t.on?.("close",()=>this.detach(e,t)),t.on?.("error",()=>this.detach(e,t))}detach(e,t){const s=this.sockets.get(e);if(s&&(!t||s.socket===t)){this.sockets.delete(e);for(const t of s.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} disconnected.`));for(const[e,t]of s.streams)s.streams.delete(e),t.downstream.close(1011,"Reverse tunnel disconnected.")}}handleMessage(e,t){if(this.handleNodeAgentEvent(e,t))return!0;if(this.handleWebSocketMessage(e,t))return!0;if("node-agent.response"!==t.type)return!1;const s="string"==typeof t.requestId?t.requestId:"",n=this.sockets.get(e),i=n?.pending.get(s);if(!i||!n)return!0;n.pending.delete(s),clearTimeout(i.timer);const a=Number(t.status)||502,r=t.headers&&"object"==typeof t.headers?t.headers:{},o="string"==typeof t.body?t.body:"";if(t.error&&"object"==typeof t.error){const e=t.error,s=new Error("string"==typeof e.message?e.message:`Reverse tunnel request failed with HTTP ${a}`);return Object.assign(s,{statusCode:a,code:"string"==typeof e.code?e.code:"NODE_AGENT_REVERSE_REQUEST_FAILED"}),i.reject(s),!0}return i.resolve(new Response(o,{status:a,headers:r})),!0}handleNodeAgentEvent(e,t){const s="string"==typeof t.type?t.type:"";if("node-agent.streams.hello"===s){const e="string"==typeof t.instanceId?t.instanceId:"",s=it.safeParse(t.payload);return e&&s.success&&this.onStreamsHello?.(e,s.data),!0}if(!s.startsWith("node-agent.event."))return!1;const n=t.event&&"object"==typeof t.event&&!Array.isArray(t.event)?t.event:void 0,i="string"==typeof n?.type?n.type:"";if(!i)return!0;const a="payload"in n?n.payload:{},r=n.scope&&"object"==typeof n.scope&&!Array.isArray(n.scope)?n.scope:{},o="string"==typeof t.instanceId?t.instanceId:void 0;return this.events?.publish(i,a,{topic:"string"==typeof n.topic?n.topic:void 0,scope:{...r,nodeId:e,instanceId:"string"==typeof r.instanceId?r.instanceId:"string"==typeof n.instanceId?n.instanceId:o}}),!0}async request(e,t,s={}){const n=this.sockets.get(e.id);if(!n){const t=new Error(`Reverse tunnel for node ${e.id} is not connected.`);throw Object.assign(t,{statusCode:503,code:"NODE_AGENT_REVERSE_TUNNEL_OFFLINE"}),t}const i=xa(),a="string"==typeof s.body?s.body:void 0===s.body||null===s.body?void 0:String(s.body),r={type:"control-plane.request",requestId:i,route:t,init:{method:s.method||"GET",headers:(o=s.headers,o?o instanceof Headers?Object.fromEntries(o.entries()):Array.isArray(o)?Object.fromEntries(o.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(o).map(([e,t])=>[e,String(t)])):{}),body:a}};var o;return new Promise((s,a)=>{const o=setTimeout(()=>{n.pending.delete(i);const s=new Error(`Reverse tunnel request to ${e.id}${t} timed out.`);Object.assign(s,{statusCode:504,code:"NODE_AGENT_REVERSE_REQUEST_TIMEOUT"}),a(s)},3e4);n.pending.set(i,{resolve:s,reject:a,timer:o}),n.socket.send(JSON.stringify(r))})}proxyWebSocket(e,t,s,n,i={}){const a=e.id,r=this.sockets.get(a);if(!r)return void t.close(1011,"Reverse tunnel is not connected.");const o=xa(),d=e=>{try{r.socket.send(JSON.stringify(e))}catch{r.streams.delete(o),t.close(1011,"Reverse tunnel send failed.")}},c={downstream:t,pendingFrames:[]};r.streams.set(o,c),d({type:"control-plane.websocket.open",streamId:o,route:s,protocols:Array.isArray(n)?n:n?[n]:[],headers:i}),t.on("message",(e,s)=>{const n=r.streams.get(o);if(n&&!n.upstream)return n.pendingFrames.length>=256?(r.streams.delete(o),t.close(1011,"Reverse websocket stream pending frame limit exceeded."),void d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Reverse websocket stream pending frame limit exceeded."})):void n.pendingFrames.push({data:e,isBinary:Boolean(s)})}),t.on("close",(e,t)=>{const s=r.streams.get(o);s&&(r.streams.delete(o),s.upstream?.close("number"==typeof e?e:1e3,Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""),d({type:"control-plane.websocket.close",streamId:o,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""}))}),t.on("error",()=>{const e=r.streams.get(o);e&&(r.streams.delete(o),e.upstream?.close(1011,"Downstream websocket failed."),d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Downstream websocket failed."}))})}attachWebSocketStream(e,t,s){const n=this.sockets.get(e),i=n?.streams.get(t);if(!n||!i)return s.close(1008,"Unknown reverse websocket stream."),!1;i.upstream=s;for(const e of i.pendingFrames.splice(0))s.send(e.data,{binary:e.isBinary});return function(e,t,s={}){const n=s.pendingFrameLimit??256;let i=!1,a=Ea(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return _a(e,1011,"WebSocket bridge pending frame limit exceeded."),void _a(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(_a(e,1011,"Upstream websocket did not open."),_a(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>Na(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),Na(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};Ea(t)?Ca(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};Ea(e)?Ca(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&_a(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&_a(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),_a(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),_a(t,1011,"WebSocket bridge client failed.")})}(i.downstream,s,{pendingFrameLimit:256,onClientClose:()=>{n.streams.delete(t)},onClientError:()=>{n.streams.delete(t)},onUpstreamClose:()=>{n.streams.delete(t)},onUpstreamError:()=>{n.streams.delete(t)}}),!0}handleWebSocketMessage(e,t){const s="string"==typeof t.type?t.type:"";if(!s.startsWith("node-agent.websocket."))return!1;const n=this.sockets.get(e),i="string"==typeof t.streamId?t.streamId:"",a=n?.streams.get(i);return!a||("node-agent.websocket.close"===s?(n?.streams.delete(i),a.downstream.close("number"==typeof t.code?t.code:1e3,"string"==typeof t.reason?t.reason:""),!0):"node-agent.websocket.error"!==s||(n?.streams.delete(i),a.downstream.close(1011,"string"==typeof t.message?t.message:"Reverse websocket proxy failed."),!0))}}class Ma{sockets=new Map;socketUrls=new Map;retries=new Map;service;tunnel;safetyIntervalMs;logger;reconnectAttempts=0;safetyReconciliations=0;timer;constructor(e,t,s={}){var n;this.service=e,this.tunnel=t,this.safetyIntervalMs=(n=s.safetyIntervalMs,Math.min(6e4,Math.max(3e4,n??45e3))),this.logger=s.logger}start(){this.sync(),this.timer=setInterval(()=>{this.safetyReconciliations+=1,this.sync()},this.safetyIntervalMs)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const e of this.sockets.values())e.close();this.sockets.clear(),this.socketUrls.clear();for(const e of this.retries.values())e.timer.cancel();this.retries.clear()}syncNow(){this.sync()}diagnostics(){return{reconnectAttempts:this.reconnectAttempts,safetyReconciliations:this.safetyReconciliations,activeConnections:this.sockets.size,pendingRetries:[...this.retries.values()].filter(e=>e.timer.pending).length,safetyIntervalMs:this.safetyIntervalMs}}sync(){const e=new Set;for(const t of this.service.listNodes()){const s=this.nodeEventsUrl(t);if(!s)continue;e.add(t.id);const n=this.retries.get(t.id);n?.timer.pending&&n.url!==s&&(n.timer.cancel(),this.retries.delete(t.id)),this.socketUrls.get(t.id)!==s&&this.sockets.has(t.id)&&(this.sockets.get(t.id)?.close(),this.sockets.delete(t.id)),this.sockets.has(t.id)||this.retries.get(t.id)?.timer.pending||this.connect(t,s)}for(const[t,s]of this.sockets)if(!e.has(t)){s.close(),this.sockets.delete(t),this.socketUrls.delete(t);const e=this.retries.get(t);e?.timer.cancel(),this.retries.delete(t)}for(const[t,s]of this.retries)e.has(t)||(s.timer.cancel(),this.retries.delete(t),this.socketUrls.delete(t))}connect(e,t){const s=qn(t),n=s?void 0:new URL(t),i=function(e,t){const s=function(e){return"paired-hmac"===e.auth.mode&&e.auth.secret?{keyId:e.auth.keyId,secret:e.auth.secret}:void 0}(e);if(s)return $n({nodeId:e.id,keyId:Ra(e,s.keyId),secret:s.secret,method:t.method,pathWithQuery:t.pathWithQuery});const n=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return n?{authorization:`Bearer ${n}`}:void 0}(e,{method:"GET",pathWithQuery:n?`${n.pathname}${n.search}`:"/api/node-agent/events"}),a=s?Un(s,"/events",void 0,i):new p.WebSocket(t,{headers:i});this.sockets.set(e.id,a),this.socketUrls.set(e.id,t),a.on("open",()=>{const s=this.retries.get(e.id);s?.timer.reset(),this.retries.set(e.id,{timer:s?.timer||new za,url:t})}),a.on("message",t=>{let s;try{s=JSON.parse(String(t))}catch{return}if(s&&"object"==typeof s&&!Array.isArray(s)){const t=s,n="string"==typeof t.type&&t.type.startsWith("node-agent.")?t:{type:"node-agent.event.forwarded",event:t};this.tunnel.handleMessage(e.id,n)}}),a.on("close",()=>{this.sockets.get(e.id)===a&&(this.sockets.delete(e.id),this.socketUrls.delete(e.id),this.scheduleReconnect(e.id))}),a.on("error",()=>{a.close()})}scheduleReconnect(e){const t=this.service.listNodes().find(t=>t.id===e),s=t&&this.nodeEventsUrl(t);if(!t||!s)return;const n=this.retries.get(e)??{timer:new za};if(n.timer.pending)return;n.url=s;const i=n.timer.schedule(()=>{this.sockets.has(e)||this.connect(t,s)});i&&(this.reconnectAttempts+=1,this.logger?.info?.({nodeId:e,url:s,attempt:i.attempt,delay:i.delay,reconnectAttempts:this.reconnectAttempts},"session-stream.node-connection.reconnect-scheduled")),this.retries.set(e,n)}nodeEventsUrl(e){const t=e.controlEndpoint||e.endpoint;if(t&&"reverse-wss"!==e.connectionMode){if(qn(t))return t;try{const e=new URL("/api/node-agent/events",t.replace(/\/$/,""));return e.protocol="https:"===e.protocol?"wss:":"ws:",e.toString()}catch{return}}}}class $a extends Error{code;lockPath;owner;constructor(e,t,s="process"){super(`${"control-plane"===s?"Control plane":"node-agent"===s?"Node agent":"Process"} is already running${t?.pid?` pid ${t.pid}`:""}. Lock: ${e}`),this.name="ProcessSingletonError",this.code="control-plane"===s?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===s?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function qa(e){return s.join(e,"owner.json")}function La(e){try{return JSON.parse(t.readFileSync(qa(e),"utf8"))}catch{return}}function Ua(e){if(!Number.isInteger(e)||Number(e)<=0)return!1;try{return process.kill(Number(e),0),!0}catch(e){return"EPERM"===e.code}}function Ba(e,s){t.writeFileSync(qa(e),`${JSON.stringify(s,null,2)}\n`,"utf8")}function Fa(e,t,s){if(function(e,t,s){return"system"===e.type||("chat-bridge"===e.type?function(e,t){return"ai-session"===t.type?["read","send-message","approve","interrupt"].includes(e):"instance"===t.type?["read","start","stop","restart"].includes(e):"read"===e}(t,s):"admin"===e.role||("viewer"===e.role?"read"===t:"operator"===e.role&&function(e,t){return"read"===e||("instance"===t.type?["create","update","delete","start","stop","restart","proxy"].includes(e):"ai-session"===t.type?["send-message","approve","interrupt"].includes(e):("trigger"===t.type||"runtime"===t.type)&&["create","update","delete"].includes(e))}(t,s)))}(e,t,s))return;const n=new Error(`Actor is not allowed to ${t} ${s.type}${s.id?` ${s.id}`:""}.`);throw Object.assign(n,{statusCode:403,code:"CONTROL_PLANE_FORBIDDEN"}),n}const Ha=r.z.string().trim().min(1),Ga=r.z.object({id:Ha}).strict(),Wa=r.z.object({id:Ha,keyId:Ha}).strict(),Va=r.z.object({id:Ha,runtimeId:Ha}).strict(),Ka=r.z.object({id:Ha,folderId:Ha}).strict(),Ja=r.z.object({id:Ha,sessionId:Ha}).strict(),Qa=r.z.object({id:Ha,sessionId:Ha,queueId:Ha}).strict(),Ya=r.z.object({configHash:Ha}).strict(),Xa=r.z.object({id:Ha,configHash:Ha}).strict(),Za=r.z.object({id:Ha,sessionId:Ha,configHash:Ha}).strict(),er=r.z.object({path:r.z.string().optional(),depth:r.z.coerce.number().int().min(0).max(32).optional()}).strict(),tr=r.z.object({ids:r.z.array(r.z.string().trim().min(1).max(120)).default([])}).strict(),sr=r.z.object({source:r.z.object({channel:Qe,bridgeId:r.z.string().trim().min(1).max(120).optional(),chatSessionId:r.z.string().trim().min(1).max(240),userId:r.z.string().trim().max(240).optional()}).strict(),action:r.z.discriminatedUnion("type",[r.z.object({type:r.z.literal("ai-session"),index:r.z.number().int().nonnegative()}).strict(),r.z.object({type:r.z.literal("instance-app-menu"),instanceId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("launch-app"),instanceId:r.z.string().trim().min(1).max(120),appId:r.z.string().trim().min(1).max(120)}).strict(),r.z.object({type:r.z.literal("pending-decision"),routeId:r.z.string().trim().min(1).max(120),decision:r.z.enum(["allow","deny","skip"])}).strict()])}).strict();function nr(e){const t=sr.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const ir=r.z.object({appId:r.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:r.z.string().trim().min(1).max(120).optional(),options:r.z.record(r.z.string(),r.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),ar=r.z.object({title:r.z.string().trim().min(1).max(120)}).strict(),rr=r.z.object({id:r.z.string().trim().min(1),direction:r.z.enum(["import","export"]),preset:r.z.string().trim().min(1).max(120)});function or({app:e,service:t,events:s,appSessionAggregator:n,aiSessionAggregator:i,chatGateway:a,aiSessionAttachments:o,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}){!function({app:e,service:t,events:s}){e.get("/api/projects",async()=>({data:t.listProjects()})),e.post("/api/projects",async(e,n)=>{const i=t.createProject(e.body);return s.publish("project.created",{projectId:i.id}),n.code(201).send({data:i})}),e.get("/api/projects/:id",async e=>({data:t.requireProject(Ga.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(Ga.parse(e.params).id,e.body);return s.publish("project.updated",{projectId:n.id}),{data:n}}),e.delete("/api/projects/:id",async e=>{const n=Ga.parse(e.params).id,i=t.deleteProject(n);return s.publish("project.deleted",{projectId:n,deleted:i}),{data:{deleted:i}}}),e.get("/api/models",async()=>({data:t.listModels()})),e.post("/api/models",async(e,n)=>{const i=t.createModel(e.body);return s.publish("model.created",{modelId:i.id}),n.code(201).send({data:i})}),e.post("/api/models/reorder",async e=>{const n=tr.parse(e.body),i=t.reorderModels(n.ids);return s.publish("model.reordered",{ids:n.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(Ga.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=t.updateModel(Ga.parse(e.params).id,e.body);return s.publish("model.updated",{modelId:n.id}),{data:n}}),e.delete("/api/models/:id",async e=>{const n=Ga.parse(e.params).id,i=t.deleteModel(n);return s.publish("model.deleted",{modelId:n,deleted:i}),{data:{deleted:i}}}),e.get("/api/images",async()=>({data:t.listImages()})),e.post("/api/images",async(e,n)=>{const i=t.createImage(e.body);return s.publish("image.created",{imageId:i.id}),n.code(201).send({data:i})}),e.get("/api/images/:id",async e=>({data:t.requireImage(Ga.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(Ga.parse(e.params).id,e.body);return s.publish("image.updated",{imageId:n.id}),{data:n}}),e.delete("/api/images/:id",async e=>{const n=Ga.parse(e.params).id,i=t.deleteImage(n);return s.publish("image.deleted",{imageId:n,deleted:i}),{data:{deleted:i}}})}({app:e,service:t,events:s}),function({app:e,service:t,events:s,nodeAgentTunnel:n,nodeEventSubscriber:i,errorPayload:a}){e.get("/api/nodes",async()=>({data:t.listPublicNodes()})),e.post("/api/nodes/local/sync",async()=>{const e=await t.syncLocalNodeConnection();return i.syncNow(),s.publish("node.local-synced",{nodeId:e.id}),{data:t.requirePublicNode(e.id)}}),e.post("/api/nodes",async(e,n)=>{const a=await t.createNode(e.body);return i.syncNow(),s.publish("node.created",{nodeId:a.id}),n.code(201).send({data:t.requirePublicNode(a.id)})}),e.post("/api/nodes/:id/check",async e=>{const n=await t.checkNode(Ga.parse(e.params).id);return s.publish("node.checked",{nodeId:n.id}),{data:n}}),e.get("/api/nodes/:id/updates/jobs",async e=>({data:await t.listNodeUpdateJobs(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/settings/external-listener",async e=>({data:await t.getLocalNodeExternalListener(Ga.parse(e.params).id)})),e.patch("/api/nodes/:id/settings/external-listener",async e=>{const n=Ga.parse(e.params).id,i=await t.updateLocalNodeExternalListener(n,e.body);return s.publish("node.external-listener.updated",{nodeId:n,bindScope:i.bindScope,port:i.port}),{data:i}}),e.post("/api/nodes/:id/updates/check",async e=>{const n=Ga.parse(e.params).id,i=await t.checkNodeUpdate(n,we.parse(e.body));return s.publish("node.update.checked",{nodeId:n,target:i.target}),{data:i}}),e.post("/api/nodes/:id/updates/apply",async(e,n)=>{const i=Ga.parse(e.params).id,a=await t.applyNodeUpdate(i,we.parse(e.body));return s.publish("node.update.queued",{nodeId:i,updateJobId:a.id,target:a.target}),n.code(202).send({data:a})}),e.post("/api/nodes/:id/pairing/invites",async(e,n)=>{const i=Ga.parse(e.params),a=await t.createNodePairingInvite(i.id,e.body);return s.publish("node.pairing-invite.created",{nodeId:i.id}),n.code(201).send({data:a})}),e.post("/api/nodes/:id/remotes/connect",async e=>{const n=Ga.parse(e.params),i=await t.connectNodeToControlPlane(n.id,e.body);return s.publish("node.remote-connect.requested",{nodeId:n.id}),{data:i}}),e.get("/api/nodes/:id/remotes",async e=>{const s=Ga.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=Wa.parse(e.params),i=await t.deleteNodeRemoteControlPlane(n.id,n.keyId);return s.publish("node.remote-key.deleted",{nodeId:n.id,keyId:n.keyId}),{data:i}}),e.post("/api/node-join/invites",async(e,n)=>{const i=t.createNodeJoinInvite(e.body);return s.publish("node-join.invite.created",{inviteId:i.id}),n.code(201).send({data:i})}),e.post("/api/node-join/complete",async(e,n)=>{const a=t.completeNodeJoin(e.body);return i.syncNow(),s.publish("node.joined",{nodeId:a.id}),n.code(201).send({data:t.requirePublicNode(a.id)})}),e.get("/api/nodes/:id/runtimes",async e=>({data:await t.listNodeRuntimes(Ga.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=Ga.parse(e.params),a=await t.createNodeRuntime(i.id,e.body);return s.publish("node.runtime.created",{nodeId:i.id,runtimeId:a.id}),n.code(201).send({data:a})}),e.patch("/api/nodes/:id/runtimes/:runtimeId",async e=>{const n=Va.parse(e.params),i=await t.updateNodeRuntime(n.id,n.runtimeId,e.body);return s.publish("node.runtime.updated",{nodeId:n.id,runtimeId:i.id}),{data:i}}),e.delete("/api/nodes/:id/runtimes/:runtimeId",async e=>{const n=Va.parse(e.params),i=await t.deleteNodeRuntime(n.id,n.runtimeId);return s.publish("node.runtime.deleted",{nodeId:n.id,runtimeId:n.runtimeId}),{data:i}}),e.post("/api/nodes/:id/runtimes/:runtimeId/check",async e=>{const n=Va.parse(e.params),i=await t.checkNodeRuntime(n.id,n.runtimeId);return s.publish("node.runtime.checked",{nodeId:n.id,runtimeId:i.id}),{data:i}}),e.get("/api/nodes/:id/docker/images",async e=>({data:await t.listNodeDockerImages(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Ga.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=Ga.parse(e.params),n=er.parse(e.query);return{data:await t.listNodeFolderTree(s.id,{path:n.path,depth:n.depth})}}),e.post("/api/nodes/:id/local-folders",async(e,n)=>{const i=Ga.parse(e.params),a=await t.createNodeLocalFolder(i.id,e.body);return s.publish("node.local-folder.created",{nodeId:i.id,folderId:a.id}),n.code(201).send({data:a})}),e.delete("/api/nodes/:id/local-folders/:folderId",async e=>{const n=Ka.parse(e.params),i=await t.deleteNodeLocalFolder(n.id,n.folderId);return s.publish("node.local-folder.deleted",{nodeId:n.id,folderId:n.folderId}),{data:i}}),e.get("/api/nodes/:id",async e=>({data:t.requirePublicNode(Ga.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(Ga.parse(e.params).id,e.body);return i.syncNow(),s.publish("node.updated",{nodeId:n.id}),{data:t.requirePublicNode(n.id)}}),e.delete("/api/nodes/:id",async e=>{const n=Ga.parse(e.params).id,a=t.deleteNode(n);return i.syncNow(),s.publish("node.deleted",{nodeId:n,deleted:a}),{data:{deleted:a}}}),e.get("/api/node-runtimes",async()=>{const e=await t.listNodeRuntimesWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),function(e){const{app:t,service:s,nodeAgentTunnel:n,errorPayload:i}=e;t.get("/api/node-agent/tunnel",{websocket:!0},(e,t)=>{try{const i=Oa.parse(t.query),a=s.requireNode(i.nodeId);ja(a,t),n.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:ue,nodeId:a.id,serverTime:(new Date).toISOString(),capabilities:{reverseTunnel:"request-response",lifecycleCommands:!0,instanceApiProxy:!0}})),e.on("message",t=>{let s;try{s=JSON.parse(String(t))}catch{return void e.send(JSON.stringify({type:"control-plane.error",code:"INVALID_JSON"}))}const i=s&&"object"==typeof s?s:{};n.handleMessage(a.id,i)||("node-agent.ping"!==i.type?"node-agent.identify"!==i.type?e.send(JSON.stringify({type:"control-plane.error",code:"UNSUPPORTED_MESSAGE"})):e.send(JSON.stringify({type:"control-plane.identified",nodeId:a.id,serverTime:(new Date).toISOString()})):e.send(JSON.stringify({type:"control-plane.pong",nodeId:a.id,serverTime:(new Date).toISOString()})))})}catch(t){const s=i(t);e.send(JSON.stringify({type:"control-plane.error",code:s.code,message:s.message})),e.close(1008,s.message)}}),t.get("/api/node-agent/tunnel/streams/:streamId",{websocket:!0},(e,t)=>{try{const i=Oa.parse(t.query),a=s.requireNode(i.nodeId);ja(a,t);const r=t.params.streamId;n.attachWebSocketStream(a.id,r,e)}catch(t){const s=i(t);e.close(1008,s.message)}})}({app:e,service:t,nodeAgentTunnel:n,errorPayload:a})}({app:e,service:t,events:s,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}),e.get("/api/app-sessions",async e=>{const t=r.z.object({refresh:r.z.string().optional(),includeTombstones:r.z.string().optional(),instanceId:r.z.string().trim().min(1).optional(),streamId:r.z.string().trim().min(1).optional(),sinceRevision:r.z.string().optional()}).parse(e.query||{});if(void 0!==t.sinceRevision){const e=Number(t.sinceRevision);if(!Number.isInteger(e)||e<0){const e=new Error("sinceRevision must be a non-negative integer.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}if(!t.streamId){const e=new Error("streamId is required with sinceRevision.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}return{data:await n.delta({instanceId:t.instanceId,streamId:t.streamId,sinceRevision:e})}}return{data:await n.list({refresh:"true"===t.refresh||"1"===t.refresh,includeTombstones:"true"===t.includeTombstones||"1"===t.includeTombstones})}}),e.get("/api/controlled-instances",async()=>({data:await t.listControlledInstances()})),e.post("/api/controlled-instances",async(e,n)=>{const i=await t.createControlledInstance(e.body);return s.publish("instance.created",{instanceId:i.id}),n.code(201).send({data:i})}),e.get("/api/controlled-instances/:id",async e=>({data:await t.requireControlledInstance(Ga.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(Ga.parse(e.params).id,e.body);return s.publish("instance.updated",{instanceId:n.id}),{data:n}}),e.delete("/api/controlled-instances/:id",async e=>{const n=Ga.parse(e.params).id,i=await t.deleteControlledInstance(n);return s.publish("instance.deleted",{instanceId:n,deleted:i}),{data:{deleted:i}}}),e.post("/api/controlled-instances/:id/start",async e=>{const n=await t.startControlledInstance(Ga.parse(e.params).id);return s.publish("instance.started",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/stop",async e=>{const n=await t.stopControlledInstance(Ga.parse(e.params).id);return s.publish("instance.stopped",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/restart",async e=>{const n=await t.restartControlledInstance(Ga.parse(e.params).id);return s.publish("instance.restarted",{instanceId:n.id}),{data:n}}),e.post("/api/controlled-instances/:id/apps/sessions",async e=>{const n=Ga.parse(e.params),i=ir.parse(e.body),a=await t.launchAppSession(n.id,i.appId,{...i.options,...i.cwdFolderId?{cwdFolderId:i.cwdFolderId}:{}});return s.publish("instance.app-session.launched",{instanceId:n.id,sessionId:"string"==typeof a.id?a.id:void 0,appId:i.appId}),{data:a}}),e.post("/api/controlled-instances/:id/apps/sessions/:sessionId/stop",async e=>{const n=Ja.parse(e.params),i=await t.stopAppSession(n.id,n.sessionId);return s.publish("instance.app-session.stopped",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.patch("/api/controlled-instances/:id/apps/sessions/:sessionId",async e=>{const n=Ja.parse(e.params),i=ar.parse(e.body),a=await t.renameAppSession(n.id,n.sessionId,i.title);return s.publish("instance.app-session.renamed",{instanceId:n.id,sessionId:n.sessionId,title:i.title}),{data:a}}),e.get("/api/controlled-instances/:id/app-sessions",async e=>{const t=Ga.parse(e.params),s=(await n.list()).instances.find(e=>e.instanceId===t.id);return{data:s?.appSessions||{runningCount:0,problemCount:0,sessions:[],updatedAt:(new Date).toISOString()}}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/messages",async e=>{const n=Ja.parse(e.params),i=x.parse(e.body||{}),a=o.resolveRefs(i.attachments,n.id,n.sessionId),r=await t.sendAiSessionMessage(n.id,n.sessionId,i.message,i.mode,a);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:r}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const s=Ja.parse(e.params);return{data:await t.aiSessionQueue(s.id,s.sessionId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/steer",async e=>{const n=Qa.parse(e.params),i=await t.steerAiSessionQueuedMessage(n.id,n.sessionId,n.queueId);return s.publish("instance.ai-session.message-sent",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/retry",async e=>{const s=Qa.parse(e.params);return{data:await t.retryAiSessionQueuedMessage(s.id,s.sessionId,s.queueId)}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId",async e=>{const s=Qa.parse(e.params);return{data:await t.removeAiSessionQueuedMessage(s.id,s.sessionId,s.queueId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/interrupt",async e=>{const n=Ja.parse(e.params),i=await t.interruptAiSession(n.id,n.sessionId);return s.publish("instance.ai-session.interrupted",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/approval",async e=>{const n=Ja.parse(e.params),i=D.parse(e.body||{}),a=await t.resolveAiSessionApproval(n.id,n.sessionId,i.decision);return s.publish("instance.ai-session.approval-resolved",{instanceId:n.id,sessionId:n.sessionId,decision:i.decision}),{data:a}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const n=rr.parse(e.params),i=await t.syncInstanceConfig(n.id,n.direction,n.preset);return s.publish("instance.config-synced",{instanceId:n.id,direction:n.direction,preset:n.preset}),{data:i}}),e.get("/api/instance-board",async()=>{const e=await t.boardWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),e.get("/api/ai-sessions",async(e,t)=>{const s=e.query;if(void 0!==s.sinceRevision){const e=Number(s.sinceRevision);if(!Number.isInteger(e)||e<0)return t.code(400).send({error:"invalid_since_revision"});try{return s.streamId?{data:await i.delta({instanceId:s.instanceId,streamId:s.streamId,sinceRevision:e})}:t.code(400).send({error:"stream_id_required"})}catch(e){if("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED"===e.message)return t.code(400).send({error:"instance_id_required"});throw e}}return{data:await i.list({refresh:"true"===s.refresh||"1"===s.refresh})}}),e.get("/api/triggers",async()=>({data:await t.listTriggers()})),e.post("/api/triggers",async(e,n)=>{const i=t.createTrigger(e.body);return s.publish("trigger.created",{configHash:i.configHash}),n.code(201).send({data:i})}),e.delete("/api/triggers/:configHash",async e=>{const n=Ya.parse(e.params),i=await t.deleteTrigger(n.configHash);return s.publish("trigger.deleted",{configHash:n.configHash}),{data:i}}),e.post("/api/triggers/:configHash/apply",async e=>{const n=Ya.parse(e.params),i=await t.applyTrigger(n.configHash,e.body||{});return s.publish("trigger.applied",{configHash:n.configHash}),{data:i}}),e.get("/api/controlled-instances/:id/triggers",async e=>{const s=Ga.parse(e.params);return{data:await t.listInstanceTriggers(s.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const n=Ja.parse(e.params),i=await t.bindAiSessionTrigger(n.id,n.sessionId,e.body||{});return s.publish("instance.ai-session.trigger-bound",{instanceId:n.id,sessionId:n.sessionId}),{data:i}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers/:configHash",async e=>{const n=Za.parse(e.params),i=await t.unbindAiSessionTrigger(n.id,n.sessionId,n.configHash);return s.publish("instance.ai-session.trigger-unbound",{instanceId:n.id,sessionId:n.sessionId,configHash:n.configHash}),{data:i}}),e.post("/api/controlled-instances/:id/triggers/:configHash/run",async e=>{const n=Xa.parse(e.params),i=await t.runInstanceTrigger(n.id,n.configHash,e.body||{});return s.publish("instance.trigger.run",{instanceId:n.id,configHash:n.configHash}),{data:i}}),function({app:e,service:t,chatGateway:s}){e.get("/api/chat/sessions",async()=>({data:t.listChatSessions()})),e.get("/api/chat/sessions/:id",async e=>({data:t.requireChatSession(Ga.parse(e.params).id)})),e.get("/api/chat-gateway/status",async()=>({data:s.status()})),e.post("/api/chat-gateway/poll-ai-sessions",async()=>({data:await s.pollAiSessionsNow()})),e.get("/api/chat-gateway/bridges",async()=>({data:t.listChatBridges()})),e.post("/api/chat-gateway/bridges",async e=>({data:t.createChatBridge(e.body)})),e.patch("/api/chat-gateway/bridges/:id",async n=>{const i=Ga.parse(n.params).id,a=t.requireChatBridge(i),r=zs.parse(n.body),o=t.updateChatBridge(i,n.body),d="boolean"==typeof r.enabled?r.enabled:void 0;return e.log.info({component:"control-plane-chat-gateway",action:"bridge-patch",bridgeId:i,hasEnabled:void 0!==d,requestedEnabled:d,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),void 0!==d&&d!==a.enabled&&(d?s.startBridge(i):s.stopBridge(i)),{data:o}}),e.post("/api/chat-gateway/bridges/:id/start",async n=>{const i=Ga.parse(n.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!0}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-start-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:s.startBridge(i)}}),e.post("/api/chat-gateway/bridges/:id/stop",async n=>{const i=Ga.parse(n.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!1}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-stop-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:s.stopBridge(i)}}),e.delete("/api/chat-gateway/bridges/:id",async e=>({data:{deleted:t.deleteChatBridge(Ga.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(Ze.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(nr(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:a})}const dr=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),cr=new Set(["content-encoding"]);function lr(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!dr.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function pr(e){return Object.fromEntries(Object.entries(lr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function ur(e){const t=e["sec-websocket-protocol"],s=(Array.isArray(t)?t.join(","):"string"==typeof t?t:"").split(",").map(e=>e.trim()).filter(Boolean);return s.length?s:void 0}function gr(e,t){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();dr.has(t)||cr.has(t)||e.header(s,n)}return e.code(t.status).send(t.body)}function hr(e){return`/instances/${encodeURIComponent(e)}`}function mr(e,t,s,n){const i=function(e,t){const s=e.split("?",1)[0]||"/";return t.toLowerCase().includes("text/html")&&!s.startsWith("/api/")}(n,Object.entries(t.headers).find(([e])=>"content-type"===e.toLowerCase())?.[1]||"")?Buffer.from(function(e,t){if(e.includes("__TASK_HANDOFF_PUBLIC_BASE__"))return e;const s=`<script>window.__TASK_HANDOFF_PUBLIC_BASE__=${JSON.stringify(t)};<\/script>`;return/<head(\s[^>]*)?>/i.test(e)?e.replace(/<head(\s[^>]*)?>/i,e=>`${e}\n${s}`):`${s}\n${e}`}(t.body.toString("utf8"),hr(s)),"utf8"):t.body;return gr(e,{...t,body:i})}function Ir(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):e instanceof URLSearchParams?e.toString():"string"==typeof e?e:null==e?void 0:JSON.stringify(e)}function fr(e,t){if("string"!=typeof t||!e.hasContentTypeParser(t))try{e.addContentTypeParser(t,{parseAs:"buffer"},(e,t,s)=>s(null,t))}catch(e){if(e instanceof Error&&/Content type parser .* already present/i.test(e.message))return;throw e}}function yr(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function br(e,t,s){const n=encodeURIComponent(e),i=encodeURIComponent(t),a=s.vnc&&"object"==typeof s.vnc?s.vnc:{};return"kasmvnc"===("string"==typeof a.backend?a.backend:"")?`/apps/access/vnc/proxy/api/apps/sessions/${i}/web/?token=${n}`:`/apps/access/vnc/proxy/api/novnc/vnc.html?token=${n}&path=${encodeURIComponent(`apps/access/vnc/proxy/api/apps/sessions/${i}/vnc?token=${n}`)}&autoconnect=1&resize=scale`}class vr{snapshots=new Map;history=new Map;advertisedStreams=new Map;recoveries=new Map;listeners=new Set;bootstrap;logger;recoverDelta;recoverSnapshot;onRecoveredEvent;recoveryCount=0;counters={streamResets:0,gaps:0,deltaRecoveries:0,snapshotRecoveries:0,recoveryFailures:0};constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger,this.recoverDelta=e.recoverDelta,this.recoverSnapshot=e.recoverSnapshot,this.onRecoveredEvent=e.onRecoveredEvent}handleEvent(e){const t=e.type===b?K:e.type===v?J:e.type===w?Q:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===b&&this.applySnapshot(s.data),e.type===v&&this.applyPatch(s.data),e.type===w&&this.applyRemoved(s.data),!0):(this.logger?.warn?.({eventType:e.type,issues:s.error.issues,errorCode:"AI_SESSION_EVENT_INVALID"},"ai-session.aggregator.event.invalid"),!0)}onSnapshot(e){return this.listeners.add(e),()=>this.listeners.delete(e)}applySnapshot(e){const t=this.advertisedStreams.get(e.meta.instanceId)?.streamId;return t&&t!==e.meta.streamId?(this.logger?.warn?.({instanceId:e.meta.instanceId,advertisedStreamId:t,streamId:e.meta.streamId,revision:e.meta.revision},"ai-session.aggregator.snapshot.obsolete-stream"),!1):this.apply({type:b,payload:e})}advertiseStream(e,t){return this.advertisedStreams.set(e,t),this.recoverStream(e,t.streamId,t.latestRevision).catch(s=>{this.counters.recoveryFailures+=1,this.logger?.warn?.({instanceId:e,streamId:t.streamId,error:s instanceof Error?s.message:String(s)},"ai-session.aggregator.recovery.failed")})}diagnostics(){return{...this.counters,recoveriesStarted:this.recoveryCount,activeRecoveries:this.recoveries.size}}removeInstance(e){this.snapshots.delete(e),this.history.delete(e),this.advertisedStreams.delete(e)}applyPatch(e){return this.apply({type:v,payload:e})}applyRemoved(e){return this.apply({type:w,payload:e})}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view()}async streamDescriptors(){return 0===this.snapshots.size&&await this.bootstrapFromInstances(),[...this.snapshots.entries()].map(([e,t])=>({topic:"ai.sessions",instanceId:e,streamId:t.streamId,latestRevision:t.revision,earliestRetainedRevision:this.history.get(e)?.[0]?.payload.meta.revision??t.revision+1}))}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0,n=this.snapshots.get(t)?.streamId||"unavailable",i=this.history.get(t)?.[0]?.payload.meta.revision??s+1;if(e.streamId!==n||e.sinceRevision>s)return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!1,events:[]});const a=(this.history.get(t)??[]).filter(t=>t.payload.meta.revision>e.sinceRevision),r=!a.length||a[0].payload.meta.revision!==e.sinceRevision+1;return Y.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:r,events:r?[]:a.map(({type:e,payload:t})=>({type:e,payload:t}))})}apply(e,t=!1){const{meta:s}=e.payload,n=this.advertisedStreams.get(s.instanceId)?.streamId;if(n&&n!==s.streamId)return this.logger?.warn?.({instanceId:s.instanceId,advertisedStreamId:n,streamId:s.streamId,revision:s.revision},"ai-session.aggregator.event.obsolete-stream"),!1;const i=this.recoveries.get(s.instanceId);if(i&&!t)return i.streamId===s.streamId&&(i.highWater=Math.max(i.highWater,s.revision)),!1;const a=this.snapshots.get(s.instanceId);e.type===b&&a&&a.streamId!==s.streamId&&(this.counters.streamResets+=1,this.logger?.info?.({instanceId:s.instanceId,previousStreamId:a.streamId,streamId:s.streamId,previousRevision:a.revision,revision:s.revision,traceId:s.traceId,resetCount:this.counters.streamResets},"ai-session.aggregator.stream-reset"));const r=function(e,t){const{meta:s}=t.payload;if(t.type===b)return e?.streamId===s.streamId&&s.revision===e.revision?{kind:"duplicate",projection:e}:e?.streamId===s.streamId&&s.revision<e.revision?{kind:"stale",projection:e}:{kind:"applied",projection:F.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:t.payload.snapshot})};if(!e||e.streamId!==s.streamId)return{kind:"snapshot-required",projection:e,streamId:s.streamId};if(s.revision===e.revision)return{kind:"duplicate",projection:e};if(s.revision<e.revision)return{kind:"stale",projection:e};if(s.revision!==e.revision+1||s.previousRevision!==e.revision)return{kind:"gap",projection:e,expectedRevision:e.revision+1,receivedRevision:s.revision};const n=new Map(e.snapshot.sessions.map(e=>[e.id,e]));if(t.type===v){for(const e of t.payload.upserted)n.set(e.id,e);for(const e of t.payload.removed)n.delete(e)}else for(const e of t.payload.sessionIds)n.delete(e);const i=[...n.values()],a=B.parse({runningCount:i.filter(e=>"running"===e.status).length,waitingCount:i.filter(e=>"waiting"===e.status).length,staleCount:e.snapshot.staleCount,sessions:i,updatedAt:s.generatedAt});return{kind:"applied",projection:F.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:a})}}(a,e);if("applied"!==r.kind)return this.logger?.warn?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,currentRevision:a?.revision,revision:s.revision,outcome:r.kind},`ai-session.aggregator.event.${r.kind}`),"gap"!==r.kind&&"snapshot-required"!==r.kind||("gap"===r.kind&&(this.counters.gaps+=1),this.recoverStream(s.instanceId,s.streamId,s.revision).catch(e=>{this.logger?.warn?.({instanceId:s.instanceId,streamId:s.streamId,revision:s.revision,error:e instanceof Error?e.message:String(e)},"ai-session.aggregator.recovery.failed")})),!1;const o=r.projection;this.snapshots.set(s.instanceId,o),this.rememberEvent(s.instanceId,e);const d={instanceId:s.instanceId,streamId:o.streamId,aiSessions:o.snapshot,revision:o.revision,lastEventAt:o.lastEventAt};for(const e of this.listeners)try{e(d)}catch{this.listeners.delete(e)}return this.logger?.info?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,revision:s.revision,reason:s.reason,sessionCount:o.snapshot.sessions.length},"ai-session.aggregator.event.accepted"),!0}async bootstrapFromInstances(){this.logger?.info?.({},"ai-session.aggregator.bootstrap");const e=new Map(this.snapshots),t=await this.bootstrap(),s=new Set(t.instances.map(e=>e.instanceId));for(const[t,n]of e)s.has(t)||this.snapshots.get(t)!==n||(this.snapshots.delete(t),this.history.delete(t),this.advertisedStreams.delete(t));for(const s of t.instances){const t=this.snapshots.get(s.instanceId);if(t!==e.get(s.instanceId))continue;const n=this.advertisedStreams.get(s.instanceId)?.streamId;n&&n!==s.streamId||t?.streamId===s.streamId&&t.revision>=s.revision||(t?.streamId!==s.streamId&&this.history.delete(s.instanceId),this.snapshots.set(s.instanceId,{streamId:s.streamId,snapshot:s.aiSessions,revision:s.revision,lastEventAt:s.lastEventAt}))}}recoverStream(e,t,s){const n=this.recoveries.get(e);if(n)return n.streamId===t?n.highWater=Math.max(n.highWater,s):(n.streamId=t,n.highWater=s),n.promise;if(!this.recoverDelta||!this.recoverSnapshot)return Promise.resolve();const i={promise:Promise.resolve(),streamId:t,highWater:s},a=++this.recoveryCount;return this.logger?.info?.({instanceId:e,highWater:s,recoveryNumber:a},"ai-session.aggregator.recovery.started"),i.promise=(async()=>{for(;;){const t=this.advertisedStreams.get(e);if(!t)return;i.streamId!==t.streamId&&(i.streamId=t.streamId,i.highWater=t.latestRevision);const s=this.snapshots.get(e);if(s&&s.streamId===t.streamId){if(s.revision<Math.max(t.latestRevision,i.highWater)){const t=await this.recoverDelta(e,s.streamId,s.revision);if(this.advertisedStreams.get(e)?.streamId!==t.streamId||i.streamId!==t.streamId)continue;if(t.syncRequired){const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:b,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`ais_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot-fallback"},"ai-session.aggregator.recovery.snapshot")}else{for(const e of t.events)this.apply(e,!0)&&this.onRecoveredEvent?.(e);this.counters.deltaRecoveries+=1,i.highWater=Math.max(i.highWater,t.latestRevision),this.logger?.info?.({instanceId:e,streamId:t.streamId,sinceRevision:t.sinceRevision,latestRevision:t.latestRevision,eventCount:t.events.length,recoveryNumber:a,outcome:"delta"},"ai-session.aggregator.recovery.delta")}}}else{const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:b,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`ais_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot"},"ai-session.aggregator.recovery.snapshot")}const n=this.snapshots.get(e),r=this.advertisedStreams.get(e);if(n&&r&&n.streamId===r.streamId&&n.revision>=Math.max(r.latestRevision,i.highWater))return void this.logger?.info?.({instanceId:e,streamId:n.streamId,revision:n.revision,recoveryNumber:a,outcome:"success"},"ai-session.aggregator.recovery.completed")}})().finally(()=>this.recoveries.delete(e)),this.recoveries.set(e,i),i.promise}rememberEvent(e,t){const s=this.history.get(e)??[],n=s.length&&s[0].payload.meta.streamId!==t.payload.meta.streamId?[]:s;n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const s=t.filter(e=>e.createdAtMs>=Date.now()-36e5);s.length?this.history.set(e,s):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED")}view(){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([e,t])=>({instanceId:e,streamId:t.streamId,aiSessions:t.snapshot,revision:t.revision,lastEventAt:t.lastEventAt}))}}}class wr{snapshots=new Map;history=new Map;advertisedStreams=new Map;recoveries=new Map;bootstrap;logger;recoverDelta;recoverSnapshot;onRecoveredEvent;recoveryCount=0;counters={streamResets:0,gaps:0,deltaRecoveries:0,snapshotRecoveries:0,recoveryFailures:0};constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger,this.recoverDelta=e.recoverDelta,this.recoverSnapshot=e.recoverSnapshot,this.onRecoveredEvent=e.onRecoveredEvent}handleEvent(e){const t=e.type===zt?Lt:e.type===Ot?Ut:e.type===Rt?Bt:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===zt&&this.applySnapshot(s.data),e.type===Ot&&this.applyPatch(s.data),e.type===Rt&&this.applyRemoved(s.data),!0):(this.logger?.warn?.({eventType:e.type,issues:s.error.issues,errorCode:"APP_SESSION_EVENT_INVALID"},"app-session.aggregator.event.invalid"),!0)}applySnapshot(e){const t=this.advertisedStreams.get(e.meta.instanceId)?.streamId;return t&&t!==e.meta.streamId?(this.logger?.warn?.({instanceId:e.meta.instanceId,advertisedStreamId:t,streamId:e.meta.streamId,revision:e.meta.revision},"app-session.aggregator.snapshot.obsolete-stream"),!1):this.apply({type:zt,payload:e})}advertiseStream(e,t){return this.advertisedStreams.set(e,t),this.recoverStream(e,t.streamId,t.latestRevision).catch(s=>{this.counters.recoveryFailures+=1,this.logger?.warn?.({instanceId:e,streamId:t.streamId,error:s instanceof Error?s.message:String(s)},"app-session.aggregator.recovery.failed")})}diagnostics(){return{...this.counters,recoveriesStarted:this.recoveryCount,activeRecoveries:this.recoveries.size}}removeInstance(e){this.snapshots.delete(e),this.history.delete(e),this.advertisedStreams.delete(e)}applyPatch(e){return this.apply({type:Ot,payload:e})}applyRemoved(e){return this.apply({type:Rt,payload:e})}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view({includeTombstones:e.includeTombstones})}async streamDescriptors(){return 0===this.snapshots.size&&await this.bootstrapFromInstances(),[...this.snapshots.entries()].map(([e,t])=>({topic:"app.sessions",instanceId:e,streamId:t.streamId,latestRevision:t.revision,earliestRetainedRevision:this.history.get(e)?.[0]?.payload.meta.revision??t.revision+1}))}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0,n=this.snapshots.get(t)?.streamId||"unavailable",i=this.history.get(t)?.[0]?.payload.meta.revision??s+1;if(e.streamId!==n||e.sinceRevision>s)return Ft.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Ft.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:!1,events:[]});const a=(this.history.get(t)??[]).filter(t=>t.payload.meta.revision>e.sinceRevision),r=!a.length||a[0].payload.meta.revision!==e.sinceRevision+1;return Ft.parse({streamId:n,instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,earliestRetainedRevision:i,syncRequired:r,events:r?[]:a.map(({type:e,payload:t})=>({type:e,payload:t}))})}apply(e,t=!1){const{meta:s}=e.payload,n=this.advertisedStreams.get(s.instanceId)?.streamId;if(n&&n!==s.streamId)return this.logger?.warn?.({instanceId:s.instanceId,advertisedStreamId:n,streamId:s.streamId,revision:s.revision},"app-session.aggregator.event.obsolete-stream"),!1;const i=this.recoveries.get(s.instanceId);if(i&&!t)return i.streamId===s.streamId&&(i.highWater=Math.max(i.highWater,s.revision)),!1;const a=this.snapshots.get(s.instanceId);e.type===zt&&a&&a.streamId!==s.streamId&&(this.counters.streamResets+=1,this.logger?.info?.({instanceId:s.instanceId,previousStreamId:a.streamId,streamId:s.streamId,previousRevision:a.revision,revision:s.revision,traceId:s.traceId,resetCount:this.counters.streamResets},"app-session.aggregator.stream-reset"));const r=function(e,t){const{meta:s}=t.payload;if(t.type===zt)return e?.streamId===s.streamId&&s.revision===e.revision?{kind:"duplicate",projection:e}:e?.streamId===s.streamId&&s.revision<e.revision?{kind:"stale",projection:e}:{kind:"applied",projection:Mt.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:t.payload.snapshot})};if(!e||e.streamId!==s.streamId)return{kind:"snapshot-required",projection:e,streamId:s.streamId};if(s.revision===e.revision)return{kind:"duplicate",projection:e};if(s.revision<e.revision)return{kind:"stale",projection:e};if(s.revision!==e.revision+1||s.previousRevision!==e.revision)return{kind:"gap",projection:e,expectedRevision:e.revision+1,receivedRevision:s.revision};const n=[...e.snapshot.sessions];if(t.type===Ot){const e=n.findIndex(e=>e.id===t.payload.session.id);e<0?n.push(t.payload.session):n[e]=t.payload.session}else{const e=n.findIndex(e=>e.id===t.payload.sessionId);e>=0&&n.splice(e,1),t.payload.tombstone&&n.push(t.payload.tombstone)}const i=Ht(n,s.generatedAt);return{kind:"applied",projection:Mt.parse({streamId:s.streamId,revision:s.revision,lastEventAt:s.generatedAt,snapshot:i})}}(a,e);if("applied"!==r.kind)return this.logger?.warn?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,currentRevision:a?.revision,revision:s.revision,outcome:r.kind},`app-session.aggregator.event.${r.kind}`),"gap"!==r.kind&&"snapshot-required"!==r.kind||("gap"===r.kind&&(this.counters.gaps+=1),this.recoverStream(s.instanceId,s.streamId,s.revision).catch(e=>{this.logger?.warn?.({instanceId:s.instanceId,streamId:s.streamId,revision:s.revision,error:e instanceof Error?e.message:String(e)},"app-session.aggregator.recovery.failed")})),!1;const o=r.projection;return this.snapshots.set(s.instanceId,o),this.rememberEvent(s.instanceId,e),this.logger?.info?.({traceId:s.traceId,streamId:s.streamId,instanceId:s.instanceId,revision:s.revision,reason:s.reason,sessionCount:o.snapshot.sessions.length,runningCount:o.snapshot.runningCount,problemCount:o.snapshot.problemCount},"app-session.aggregator.event.accepted"),!0}async bootstrapFromInstances(){this.logger?.info?.({},"app-session.aggregator.bootstrap");const e=new Map(this.snapshots),t=await this.bootstrap(),s=new Set(t.instances.map(e=>e.instanceId));for(const[t,n]of e)s.has(t)||this.snapshots.get(t)!==n||(this.snapshots.delete(t),this.history.delete(t),this.advertisedStreams.delete(t));for(const s of t.instances){const t=this.snapshots.get(s.instanceId);if(t!==e.get(s.instanceId))continue;const n=this.advertisedStreams.get(s.instanceId)?.streamId;n&&n!==s.streamId||t?.streamId===s.streamId&&t.revision>=s.revision||(t?.streamId!==s.streamId&&this.history.delete(s.instanceId),this.snapshots.set(s.instanceId,{streamId:s.streamId,snapshot:s.appSessions,revision:s.revision,lastEventAt:s.lastEventAt}))}}recoverStream(e,t,s){const n=this.recoveries.get(e);if(n)return n.streamId===t?n.highWater=Math.max(n.highWater,s):(n.streamId=t,n.highWater=s),n.promise;if(!this.recoverDelta||!this.recoverSnapshot)return Promise.resolve();const i={promise:Promise.resolve(),streamId:t,highWater:s},a=++this.recoveryCount;return this.logger?.info?.({instanceId:e,highWater:s,recoveryNumber:a},"app-session.aggregator.recovery.started"),i.promise=(async()=>{for(;;){const t=this.advertisedStreams.get(e);if(!t)return;i.streamId!==t.streamId&&(i.streamId=t.streamId,i.highWater=t.latestRevision);const s=this.snapshots.get(e);if(s&&s.streamId===t.streamId){if(s.revision<Math.max(t.latestRevision,i.highWater)){const t=await this.recoverDelta(e,s.streamId,s.revision);if(this.advertisedStreams.get(e)?.streamId!==t.streamId||i.streamId!==t.streamId)continue;if(t.syncRequired){const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:zt,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`aps_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot-fallback"},"app-session.aggregator.recovery.snapshot")}else{for(const e of t.events)this.apply(e,!0)&&this.onRecoveredEvent?.(e);this.counters.deltaRecoveries+=1,i.highWater=Math.max(i.highWater,t.latestRevision),this.logger?.info?.({instanceId:e,streamId:t.streamId,sinceRevision:t.sinceRevision,latestRevision:t.latestRevision,eventCount:t.events.length,recoveryNumber:a,outcome:"delta"},"app-session.aggregator.recovery.delta")}}}else{const t=await this.recoverSnapshot(e);if(this.advertisedStreams.get(e)?.streamId!==t.streamId)continue;const s={type:zt,payload:{meta:{streamId:t.streamId,instanceId:e,revision:t.revision,traceId:`aps_recovery_${Date.now().toString(36)}`,generatedAt:t.lastEventAt,reason:"startup"},snapshot:t.snapshot}};this.apply(s,!0)&&this.onRecoveredEvent?.(s),this.counters.snapshotRecoveries+=1,this.logger?.info?.({instanceId:e,streamId:t.streamId,revision:t.revision,recoveryNumber:a,outcome:"snapshot"},"app-session.aggregator.recovery.snapshot")}const n=this.snapshots.get(e),r=this.advertisedStreams.get(e);if(n&&r&&n.streamId===r.streamId&&n.revision>=Math.max(r.latestRevision,i.highWater))return void this.logger?.info?.({instanceId:e,streamId:n.streamId,revision:n.revision,recoveryNumber:a,outcome:"success"},"app-session.aggregator.recovery.completed")}})().finally(()=>this.recoveries.delete(e)),this.recoveries.set(e,i),i.promise}rememberEvent(e,t){const s=this.history.get(e)??[],n=s.length&&s[0].payload.meta.streamId!==t.payload.meta.streamId?[]:s;n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const s=t.filter(e=>e.createdAtMs>=Date.now()-36e5);s.length?this.history.set(e,s):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("APP_SESSION_DELTA_INSTANCE_ID_REQUIRED")}visibleSnapshot(e){return Ht(e.sessions.filter(e=>sn("string"==typeof e.status?e.status:void 0)),e.updatedAt)}view(e={}){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([t,s])=>({instanceId:t,streamId:s.streamId,appSessions:e.includeTombstones?s.snapshot:this.visibleSnapshot(s.snapshot),revision:s.revision,lastEventAt:s.lastEventAt}))}}}function Sr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function Ar(e){const t=process.env[e]?.trim();return t||void 0}function kr(){try{const e=s.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function Tr(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:kr(),protocolVersion:ue,buildId:Ar("TASK_HANDOFF_BUILD_ID"),builtAt:Ar("TASK_HANDOFF_BUILT_AT"),gitCommit:Ar("TASK_HANDOFF_GIT_COMMIT"),imageRef:Ar("TASK_HANDOFF_IMAGE_REF"),imageDigest:Ar("TASK_HANDOFF_IMAGE_DIGEST")}}const Er={".css":"text/css; charset=utf-8",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".png":"image/png",".svg":"image/svg+xml",".webp":"image/webp"};function _r(e,n){const i=s.resolve(e),a=s.resolve(i,n);if(a===i||a.startsWith(`${i}${s.sep}`))return t.existsSync(a)&&t.statSync(a).isFile()?a:void 0}function Cr(e,n,i){const a=_r(n,i);return a?(e.header("cache-control","no-store"),e.type(Er[s.extname(a).toLowerCase()]||"application/octet-stream"),e.send(t.createReadStream(a))):e.code(404).send({error:{code:"STATIC_FILE_NOT_FOUND",message:`${i} was not found in the Control Plane UI static directory.`}})}function Nr(e){if(e instanceof r.z.ZodError)return{statusCode:400,code:"VALIDATION_ERROR",message:e.issues.map(e=>`${e.path.join(".")||"body"}: ${e.message}`).join("; ")};const t=e&&"object"==typeof e?e:{};return{statusCode:"number"==typeof t.statusCode?t.statusCode:500,code:"string"==typeof t.code?t.code:"CONTROL_PLANE_ERROR",message:e instanceof Error?e.message:String(e)}}async function zr(e,t){if(!e.enabled())return{type:"system",reason:"auth-disabled"};const s=await e.userForSessionToken(t);return s?{type:"user",userId:s.id,role:s.role}:void 0}function Or(e,t){const s=t.split("?")[0]||"/";if(!s.startsWith("/api/"))return;if(s.startsWith("/api/auth/")||"/api/health"===s||"/api/events"===s)return;const n=function(e){return"GET"===e||"HEAD"===e?"read":"POST"===e?"create":"PATCH"===e||"PUT"===e?"update":"DELETE"===e?"delete":"read"}(e);return s.startsWith("/api/control-plane/settings")?{action:"GET"===e?"read":"manage-settings",resource:{type:"control-plane-settings"}}:s.startsWith("/api/models")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"model"}}:s.startsWith("/api/chat-gateway/bridges")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"chat-bridge"}}:"/api/node-join/invites"===s?{action:"manage-node-auth",resource:{type:"node"}}:s.startsWith("/api/nodes")?/\/(runtimes|local-folders)(\/|$)/.test(s)?{action:n,resource:{type:"runtime"}}:{action:"GET"===e?"read":"manage-node-auth",resource:{type:"node"}}:s.startsWith("/api/controlled-instances")?s.includes("/ai-sessions/")?s.endsWith("/approval")?{action:"approve",resource:{type:"ai-session"}}:s.endsWith("/interrupt")?{action:"interrupt",resource:{type:"ai-session"}}:s.includes("/messages")||s.includes("/queue/")?{action:"send-message",resource:{type:"ai-session"}}:{action:"read",resource:{type:"ai-session"}}:s.includes("/start")?{action:"start",resource:{type:"instance"}}:s.includes("/stop")?{action:"stop",resource:{type:"instance"}}:s.includes("/restart")?{action:"restart",resource:{type:"instance"}}:s.includes("/config-sync/")?{action:"update",resource:{type:"instance"}}:{action:n,resource:{type:"instance"}}:s.startsWith("/api/projects")?{action:n,resource:{type:"project"}}:s.startsWith("/api/images")?{action:n,resource:{type:"runtime"}}:s.startsWith("/api/triggers")?{action:n,resource:{type:"trigger"}}:{action:"read",resource:{type:"control-plane-settings"}}}async function Rr(e={}){const r=at(e.dataDir),o=n({logger:e.logger??!0,bodyLimit:67108864}),d=new Jn(r,{...e.service,logger:o.log}),c=new Nt(r,e.auth),l=new wa,p=new vr({bootstrap:()=>d.bootstrapAiSessionsFromInstances(),logger:o.log,recoverDelta:(e,t,s)=>d.recoverAiSessionDelta(e,t,s),recoverSnapshot:e=>d.recoverAiSessionSnapshot(e),onRecoveredEvent:e=>l.publish(e.type,e.payload)}),u=new wr({bootstrap:()=>d.bootstrapAppSessionsFromInstances(),logger:o.log,recoverDelta:(e,t,s)=>d.recoverAppSessionDelta(e,t,s),recoverSnapshot:e=>d.recoverAppSessionSnapshot(e),onRecoveredEvent:e=>l.publish(e.type,e.payload)});d.setAppSessionSnapshotProvider(e=>u.list(e)),d.setAiSessionSnapshotProvider(e=>p.list(e)),l.on(e=>{if("instance.deleted"===e.type){const t=e.payload&&"object"==typeof e.payload&&"instanceId"in e.payload?String(e.payload.instanceId||""):"";t&&(u.removeInstance(t),p.removeInstance(t))}u.handleEvent(e),p.handleEvent(e)});const g=new Ta,h=new Pa(l,{onStreamsHello:(e,t)=>{for(const s of t.streams)"ai.sessions"===s.topic&&p.advertiseStream(e,s),"app.sessions"===s.topic&&u.advertiseStream(e,s)}});d.setNodeAgentTransport(h),d.init(),await d.syncLocalNodeConnection().catch(()=>{}),c.init();const m=new Hi(d,e.service?.fetchImpl,{aiSessions:p,logger:o.log});m.startEnabled();const I=new Ma(d,h,{safetyIntervalMs:Number(process.env.TASK_HANDOFF_EVENT_CONNECTION_SAFETY_INTERVAL_MS)||void 0,logger:o.log});I.start(),await o.register(i),await o.register(a),o.addHook("onClose",async()=>{I.stop(),m.stopAll()});const f=(y=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(y||"").trim().toLowerCase()));var y;o.setErrorHandler((e,t,s)=>{const n=Nr(e);(f||n.statusCode>=500)&&(n.statusCode>=500?o.log.error.bind(o.log):o.log.warn.bind(o.log))({method:t.method,url:t.url,statusCode:n.statusCode,errorCode:n.code,error:n.message},"control plane request failed"),s.code(n.statusCode).send({error:{code:n.code,message:n.message}})}),o.addHook("preHandler",async(e,t)=>{if(!c.enabled()||function(e,t){const s=t.split("?")[0];return!("/api/health"!==s&&!s.startsWith("/api/auth/"))||"GET"===e&&"/install-node-agent.sh"===s||"POST"===e&&"/api/node-join/complete"===s||"/api/app-access/session"===s||!!s.startsWith("/apps/access/")||!("/favicon.ico"!==s&&!s.startsWith("/assets/"))||"GET"===e&&!s.startsWith("/api/")&&!s.startsWith("/instances/")}(e.method,e.url)){const t=await zr(c,e.cookies[It]),s=Or(e.method,e.url);return void(s&&Fa(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await zr(c,e.cookies[It]);if(!s)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const n=Or(e.method,e.url);n&&Fa(s,n.action,n.resource)});const b=s.resolve(e.staticDir||process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||s.resolve(process.cwd(),"packages","control-plane-ui","dist"));return t.existsSync(b)&&(o.get("/assets/*",async(e,t)=>{const n=e.params["*"];return Cr(t,b,s.join("assets",n||""))}),o.get("/favicon.ico",async(e,t)=>_r(b,"favicon.ico")?Cr(t,b,"favicon.ico"):t.code(204).send())),o.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:ue,build:Tr(),dataDir:r.dataDir,serverTime:(new Date).toISOString()}})),o.get("/api/session-streams/diagnostics",async()=>({data:{aiSessions:p.diagnostics(),appSessions:u.diagnostics(),nodeConnections:I.diagnostics()}})),o.get("/install-node-agent.sh",async(e,t)=>(t.header("cache-control","no-store"),t.type("text/x-shellscript; charset=utf-8"),Sr`#!/bin/sh
|
|
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
|
|
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[_t])})),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(_t,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[_t]);return t.clearCookie(_t,{path:"/"}),{data:s}}),o.get("/api/control-plane/status",async()=>({data:{protocolVersion:ge,build:Xr(),storage:n,counts:{projects:d.listProjects().length,models:d.listModels().length,images:d.listImages().length,nodes:d.listNodes().length,nodeRuntimes:(await d.listNodeRuntimes()).length,controlledInstances:(await d.listControlledInstances()).length,chatSessions:d.listChatSessions().length}}})),o.get("/api/control-plane/settings",async()=>({data:d.getSettings()})),o.patch("/api/control-plane/settings",async e=>({data:d.updateSettings(e.body||{})})),o.post("/api/ai-session-attachments",async(e,t)=>{try{return t.code(201).send({data:h.upload(e.body)})}catch(e){return t.code(400).send({error:{code:"AI_SESSION_ATTACHMENT_INVALID",message:e instanceof Error?e.message:String(e)}})}}),jr({app:o,service:d,events:l,appSessionAggregator:u,aiSessionAggregator:p,chatGateway:m,aiSessionAttachments:h,nodeAgentTunnel:g,nodeEventSubscriber:I,errorPayload:so}),function({app:e,service:t}){!function(e){for(const t of["application/octet-stream","application/x-www-form-urlencoded"])Hr(e,t);Hr(e,/^multipart\/form-data(?:;.*)?$/i)}(e),e.get("/api/app-access/session",async e=>{const s=Gr(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?Wr(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(Gr(s.url),"tty");await t.proxyInstanceWebSocket(n.instance.id,e,n.path,qr(s.headers),$r(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(Gr(s.url),"vnc",n["*"]||"");await t.proxyInstanceWebSocket(i.instance.id,e,i.path,qr(s.headers),$r(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(Gr(e.url),"vnc",n["*"]||"");return Br(s,await t.proxyInstanceHttp(i.instance.id,i.path,{method:e.method,headers:Mr(e.headers)}),i.instance.id,i.path)}}),e.all("/instances/:id",async(e,s)=>{const n=e.params;return"GET"===e.method||"HEAD"===e.method?s.redirect(`${Ur(n.id)}/`):Br(s,await t.proxyInstanceHttp(n.id,"/",{method:e.method,headers:Mr(e.headers),body:Fr(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}`,qr(s.headers),$r(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 Br(s,await t.proxyInstanceHttp(n.id,r,{method:e.method,headers:Mr(e.headers)}),n.id,r)}}),e.route({method:["POST","PUT","PATCH","DELETE","OPTIONS"],url:"/instances/:id/*",handler:async(e,s)=>{const n=e.params,i=n["*"]||"",a=e.url.indexOf("?"),r=a>=0?e.url.slice(a):"";return Lr(s,await t.proxyInstanceHttp(n.id,`/${i}${r}`,{method:e.method,headers:Mr(e.headers),body:Fr(e.body)}))}})}({app:o,service:d}),o.get("*",async(e,s)=>t.existsSync(b)?to(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 ro(e){const n=er(e.dataDir),i=function(e,n={}){return function(e,n={}){t.mkdirSync(s.dirname(e),{recursive:!0});const i={pid:process.pid,hostname:I.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(nr(e),`${JSON.stringify({token:i.token,startedAt:(new Date).toISOString()})}\n`,{encoding:"utf8",mode:384,flag:"wx"}),or(e,i),{lockPath:e,owner:i,release(){const s=ar(e);s?.token===i.token&&t.rmSync(e,{recursive:!0,force:!0})}}}catch(i){if("EEXIST"!==i.code)throw i;const a=ar(e);if(rr(a?.pid))throw new tr(e,a,n.error);if(!a&&pr(e)<2e3){if(s+1<5){lr();continue}throw new tr(e,void 0,n.error)}const r=d.randomUUID();if(!ur(e,r)){if(s+1<5){lr();continue}throw new tr(e,ar(e),n.error)}const o=ar(e);if(rr(o?.pid))throw cr(ir(e)),new tr(e,o,n.error);if(a?.token&&o?.token!==a.token){cr(ir(e));continue}if(!gr(e,r))continue;t.rmSync(e,{recursive:!0,force:!0})}const a=ar(e);throw new tr(e,a,n.error)}(e,{...n,component:"control-plane",error:{label:"Control plane",code:"CONTROL_PLANE_ALREADY_RUNNING"}})}(s.join(I.tmpdir(),`task-handoff-control-plane-${process.getuid?.()??"user"}.lock`),{dataDir:n.dataDir,host:e.host,port:e.port});try{const t=await ao(e);t.addHook("onClose",async()=>{i.release()}),await t.listen({host:e.host,port:e.port})}catch(e){throw i.release(),e}}function oo(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 co(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",oo,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",co,process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"password").action(async e=>{await ro({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});
|