@task-handoff/control-plane 0.0.3-alpha.1 → 0.0.3-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +3 -3
- package/package.json +3 -4
- package/ui/assets/{abnfDiagram-VRR7QNED-CFqGI-zP.js → abnfDiagram-VRR7QNED-Cr99lWSj.js} +1 -1
- package/ui/assets/{arc-CtRs5c2j.js → arc-SCB_ie-3.js} +1 -1
- package/ui/assets/{architectureDiagram-ZJ3FMSHR-Bv9RRoI6.js → architectureDiagram-ZJ3FMSHR-uqeuFaES.js} +1 -1
- package/ui/assets/{blockDiagram-677ZJIJ3-DhWEYWLK.js → blockDiagram-677ZJIJ3-DXZEfdw7.js} +1 -1
- package/ui/assets/{c4Diagram-LMCZKHZV-BT49PUGj.js → c4Diagram-LMCZKHZV-C95FNwvR.js} +1 -1
- package/ui/assets/channel-hd9WPuIH.js +1 -0
- package/ui/assets/{chunk-2Q5K7J3B-Dd5D78p7.js → chunk-2Q5K7J3B-Ci5-T8EV.js} +1 -1
- package/ui/assets/{chunk-32BRIVSS-oLbCklmC.js → chunk-32BRIVSS-Bb1hZ2nB.js} +1 -1
- package/ui/assets/{chunk-5VM5RSS4-CpuzYXEm.js → chunk-5VM5RSS4-zi7wW90z.js} +1 -1
- package/ui/assets/{chunk-EX3LRPZG-8lh_Qh5f.js → chunk-EX3LRPZG-D9QmzTfB.js} +1 -1
- package/ui/assets/{chunk-JWPE2WC7-BWNuUTKy.js → chunk-JWPE2WC7-CWZyvapM.js} +1 -1
- package/ui/assets/{chunk-MOJQB5TN-4JLfNf2b.js → chunk-MOJQB5TN-Bwkk-xn-.js} +1 -1
- package/ui/assets/{chunk-RYQCIY6F-BujA9Ow9.js → chunk-RYQCIY6F-DPQquVy_.js} +1 -1
- package/ui/assets/{chunk-V7JOEXUC-CvBDX2sb.js → chunk-V7JOEXUC-BZxplK_2.js} +1 -1
- package/ui/assets/{chunk-VR4S4FIN-Dc-Wj9PW.js → chunk-VR4S4FIN-CJWn0yJf.js} +1 -1
- package/ui/assets/{chunk-XXDRQBXY-B6RoIPWq.js → chunk-XXDRQBXY-XroGPKr4.js} +1 -1
- package/ui/assets/classDiagram-OUVF2IWQ-DG267B87.js +1 -0
- package/ui/assets/classDiagram-v2-EOCWNBFH-DG267B87.js +1 -0
- package/ui/assets/{cose-bilkent-JH36ORCC-BSrsfYkn.js → cose-bilkent-JH36ORCC-9wb-lkZk.js} +1 -1
- package/ui/assets/{cynefin-VYW2F7L2-Dbr0eCIz.js → cynefin-VYW2F7L2-DEK-S5Lm.js} +1 -1
- package/ui/assets/{cynefinDiagram-TSTJHNR4-DP741M5u.js → cynefinDiagram-TSTJHNR4-CFvR31Ku.js} +1 -1
- package/ui/assets/{dagre-VKFMJZFB-BzVC3ZFs.js → dagre-VKFMJZFB-u-XVaFlI.js} +1 -1
- package/ui/assets/{diagram-FQU43EPY-BnyymKd6.js → diagram-FQU43EPY-Cihmj8tU.js} +1 -1
- package/ui/assets/{diagram-G47NLZAW-DHcwA4tv.js → diagram-G47NLZAW-ZHjbgZuW.js} +1 -1
- package/ui/assets/{diagram-NH7WQ7WH-CyNMAzzs.js → diagram-NH7WQ7WH-yR_4vFz9.js} +1 -1
- package/ui/assets/{diagram-OA4YK3LP-C7NDki_B.js → diagram-OA4YK3LP-DSg17cP7.js} +1 -1
- package/ui/assets/{diagram-WEI45ONY-2eg4GGO_.js → diagram-WEI45ONY-BdymrUi-.js} +1 -1
- package/ui/assets/{ebnfDiagram-CCIWWBDH-BDXvo2s_.js → ebnfDiagram-CCIWWBDH-B1SJBawW.js} +1 -1
- package/ui/assets/{erDiagram-Q63AITRT-CIUgrzx2.js → erDiagram-Q63AITRT-DzrzX-4V.js} +1 -1
- package/ui/assets/{flowDiagram-23GEKE2U-DfVRPkLD.js → flowDiagram-23GEKE2U-Bwt9-YA5.js} +1 -1
- package/ui/assets/{ganttDiagram-NO4QXBWP-DIzx907v.js → ganttDiagram-NO4QXBWP-yigNFYZb.js} +1 -1
- package/ui/assets/{gitGraphDiagram-IHSO6WYX-BO-0EOCE.js → gitGraphDiagram-IHSO6WYX-DJoDX06I.js} +1 -1
- package/ui/assets/index-CZvmXcip.js +396 -0
- package/ui/assets/{index-FEJwVSw1.css → index-DW-dIOL3.css} +1 -1
- package/ui/assets/{infoDiagram-FWYZ7A6U-aWTlxS7z.js → infoDiagram-FWYZ7A6U-zn4zp3D7.js} +1 -1
- package/ui/assets/{ishikawaDiagram-FXEZZL3T-DLxGxqCh.js → ishikawaDiagram-FXEZZL3T-DIkSQtFs.js} +1 -1
- package/ui/assets/{journeyDiagram-5HDEW3XC-BicmInSf.js → journeyDiagram-5HDEW3XC-mawpzzrL.js} +1 -1
- package/ui/assets/{kanban-definition-HUTT4EX6-Cuus2DNT.js → kanban-definition-HUTT4EX6-02jt9hDg.js} +1 -1
- package/ui/assets/{linear-BKK2jQO-.js → linear-C2GWn0gK.js} +1 -1
- package/ui/assets/{mermaid.core-BPC0nwBJ.js → mermaid.core-DKkYtgqn.js} +4 -4
- package/ui/assets/{mindmap-definition-LN4V7U3C-B-sCjUDM.js → mindmap-definition-LN4V7U3C-CyuIaeA7.js} +1 -1
- package/ui/assets/{pegDiagram-2B236MQR-CGHJoTW1.js → pegDiagram-2B236MQR-Bn06SclM.js} +1 -1
- package/ui/assets/{pieDiagram-ENE6RG2P-BkbqntJ2.js → pieDiagram-ENE6RG2P-BaZmXSm-.js} +1 -1
- package/ui/assets/{quadrantDiagram-ABIIQ3AL-DQXbv-TE.js → quadrantDiagram-ABIIQ3AL-Cjp0hRue.js} +1 -1
- package/ui/assets/{railroadDiagram-RFXS5EU6-CzD12xdv.js → railroadDiagram-RFXS5EU6-DYWFY1mg.js} +1 -1
- package/ui/assets/{requirementDiagram-TGXJPOKE-RwU9qDjk.js → requirementDiagram-TGXJPOKE-BumOJG8V.js} +1 -1
- package/ui/assets/{sankeyDiagram-HTMAVEWB-0l_UrhuE.js → sankeyDiagram-HTMAVEWB-Bzdj-4WT.js} +1 -1
- package/ui/assets/{sequenceDiagram-DBY2YBRQ-6NcSmOLg.js → sequenceDiagram-DBY2YBRQ-DFB8qzeh.js} +1 -1
- package/ui/assets/{sizeCapture-X5ZJPWSS-BqF-pWOl.js → sizeCapture-X5ZJPWSS-CNO35dpl.js} +1 -1
- package/ui/assets/{stateDiagram-2N3HPSRC-DavrTL0P.js → stateDiagram-2N3HPSRC-wy8gT9ap.js} +1 -1
- package/ui/assets/stateDiagram-v2-6OUMAXLB-BZul8xf5.js +1 -0
- package/ui/assets/{swimlanes-5IMT3BWC-B5OmFtyK.js → swimlanes-5IMT3BWC-DRtsBYOK.js} +2 -2
- package/ui/assets/swimlanesDiagram-G3AALYLV-Cx57MPWj.js +8 -0
- package/ui/assets/{timeline-definition-FHXFAJF6-Cm4Swf3C.js → timeline-definition-FHXFAJF6-DDpoZVCF.js} +1 -1
- package/ui/assets/{vennDiagram-L72KCM5P-DJ0OpGA-.js → vennDiagram-L72KCM5P-szFUXHqT.js} +1 -1
- package/ui/assets/{wardleyDiagram-EHGQE667-SsUQrcDN.js → wardleyDiagram-EHGQE667-BNVt2gwT.js} +1 -1
- package/ui/assets/{xychartDiagram-FW5EYKEG-CWp_a7PG.js → xychartDiagram-FW5EYKEG-QQn9PKYP.js} +1 -1
- package/ui/index.html +2 -2
- package/bin/task-handoff-install-server +0 -240
- package/ui/assets/channel-I_NjHJ8V.js +0 -1
- package/ui/assets/classDiagram-OUVF2IWQ-D6nx1o3S.js +0 -1
- package/ui/assets/classDiagram-v2-EOCWNBFH-D6nx1o3S.js +0 -1
- package/ui/assets/index-V-ThLVqc.js +0 -396
- package/ui/assets/stateDiagram-v2-6OUMAXLB-XTqB4H3z.js +0 -1
- package/ui/assets/swimlanesDiagram-G3AALYLV-DNTYtDpF.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var e=require("commander"),t=require("node:fs"),s=require("node:path"),n=require("fastify"),i=require("@fastify/websocket"),a=require("zod"),r=require("node:crypto"),o=require("node:util"),d=require("node:os"),c=require("ws"),l=require("node:http"),p=require("node:net"),u=require("dingtalk-stream"),g=require("telegramify-markdown"),h=require("node:fs/promises");const m=20971520,f=41943040,I="ai-session.snapshot",y="ai-session.patch",b="ai-session.removed",w=a.z.string().trim().min(1).max(80),S=a.z.enum(["running","waiting","idle","failed"]),A=a.z.enum(["thinking","tool","editing","approval","responding","unknown"]),v=a.z.object({name:a.z.string().trim().min(1).max(120),inputPreview:a.z.string().trim().max(500).optional(),startedAt:a.z.string().datetime().optional()}).strict(),k=a.z.object({toolCalls:a.z.number().int().min(0).default(0),edits:a.z.number().int().min(0).default(0),approvals:a.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),T=a.z.object({send:a.z.boolean().optional(),interrupt:a.z.boolean().optional(),approval:a.z.boolean().optional()}).strict(),E=a.z.object({id:a.z.string().trim().min(1).max(120),kind:a.z.enum(["image"]),name:a.z.string().trim().min(1).max(240),mime:a.z.string().trim().min(1).max(120),size:a.z.number().int().min(0).max(m),data:a.z.string().min(1).max(31457280)}).strict(),_=E.omit({data:!0}),C=a.z.object({id:a.z.string().trim().min(1).max(120),kind:a.z.enum(["image"]).optional()}).strict(),N=a.z.enum(["auto","queue","steer","immediate"]),z=a.z.object({message:a.z.string().trim().min(1).max(2e4),mode:N.optional()});z.extend({attachments:a.z.array(E).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>f&&t.addIssue({code:a.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const O=z.extend({attachments:a.z.array(C).max(6).default([])}).strict(),D=a.z.object({decision:a.z.enum(["allow","deny","skip"])}).strict();a.z.object({queueIds:a.z.array(a.z.string().trim().min(1).max(120)).max(100)}).strict(),a.z.object({code:a.z.string().trim().min(1).max(160),message:a.z.string(),instanceId:a.z.string().trim().min(1).max(160).optional(),nodeId:a.z.string().trim().min(1).max(160).optional(),route:a.z.string().trim().min(1).max(4096).optional()}).strict();const x=a.z.object({id:a.z.string().trim().min(1).max(120),message:a.z.string().trim().min(1).max(2e4),attachments:a.z.array(_).max(6).default([]),status:a.z.enum(["queued","sending","failed"]).default("queued"),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime(),error:a.z.string().trim().max(4e3).optional()}).strict(),R=a.z.object({pendingCount:a.z.number().int().min(0).default(0),items:a.z.array(x).max(100).default([])}).strict().default({pendingCount:0,items:[]}),j=a.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),P=a.z.object({id:a.z.string().trim().min(1).max(240),providerTurnId:a.z.string().trim().max(240).optional(),source:j.optional(),userPrompt:a.z.string().trim().optional(),status:a.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),revision:a.z.number().int().min(0).default(0),sourcePriority:a.z.number().int().min(0).max(100).optional(),snapshotVersion:a.z.number().int().min(0).optional(),observedAt:a.z.string().datetime().optional(),startedAt:a.z.string().datetime().optional(),updatedAt:a.z.string().datetime().optional(),completedAt:a.z.string().datetime().optional()}).strict(),$=a.z.object({id:a.z.string().trim().min(1).max(120),agent:w,appSessionId:a.z.string().trim().max(120).optional(),appId:a.z.string().trim().max(120).optional(),providerSessionId:a.z.string().trim().max(240).optional(),providerMeta:a.z.record(a.z.string(),a.z.unknown()).optional(),appBindingKeys:a.z.array(a.z.string().trim().min(1).max(240)).max(20).optional(),actions:T.optional(),activeTurnId:a.z.string().trim().max(240).optional(),conversationId:a.z.number().int().positive().optional(),title:a.z.string().trim().max(240).optional(),cwd:a.z.string().trim().max(4096).optional(),userPrompt:a.z.string().trim().optional(),turns:a.z.array(P).max(50).optional(),status:S.default("running"),phase:A.default("unknown"),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),currentTool:v.optional(),transcriptPath:a.z.string().trim().max(4096).optional(),transcriptSize:a.z.number().int().min(0).optional(),startedAt:a.z.string().datetime(),updatedAt:a.z.string().datetime(),completedAt:a.z.string().datetime().optional(),error:a.z.string().trim().max(4e3).optional(),counters:k,queue:R}).strict(),M=a.z.object({session:$,provider:a.z.string().trim().min(1).max(80),action:a.z.enum(["send","queue","steer","interrupt","approval"]),decision:a.z.enum(["allow","deny","skip"]).optional(),turnId:a.z.string().trim().min(1).max(240).optional(),providerTurnId:a.z.string().trim().min(1).max(240).optional(),queueId:a.z.string().trim().min(1).max(120).optional()}).strict(),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}),L=a.z.object({runningCount:a.z.number().int().min(0).default(0),waitingCount:a.z.number().int().min(0).default(0),staleCount:a.z.number().int().min(0).default(0),sessions:a.z.array(q).default([]),updatedAt:a.z.string().datetime()}).strict(),U=a.z.object({revision:a.z.number().int().min(0),lastEventAt:a.z.string().datetime(),snapshot:L}).strict(),B=L.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),revision:a.z.number().int().min(0).optional()});function F(e,t={}){const s=e.sessions.filter(e=>"idle"===e.status).length,n=e.sessions.filter(e=>"failed"===e.status).length;return B.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:s,problemCount:n,updatedAt:e.updatedAt,revision:t.revision})}const H=a.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),G=a.z.object({instanceId:a.z.string().trim().min(1).max(160),nodeId:a.z.string().trim().min(1).max(160).optional(),snapshotRevision:a.z.number().int().min(0),previousRevision:a.z.number().int().min(0).optional(),eventSeq:a.z.number().int().min(1),traceId:a.z.string().trim().min(1).max(160),generatedAt:a.z.string().datetime(),reason:H}).strict(),W=a.z.object({meta:G,snapshot:L}).strict(),K=a.z.object({meta:G,upserted:a.z.array(q).default([]),removed:a.z.array(a.z.string().trim().min(1).max(120)).default([])}).strict(),V=a.z.object({meta:G,sessionIds:a.z.array(a.z.string().trim().min(1).max(120)).default([]),expiresAt:a.z.string().datetime()}).strict(),J=a.z.object({instanceId:a.z.string().trim().min(1).max(160),sinceRevision:a.z.number().int().min(0),latestRevision:a.z.number().int().min(0),syncRequired:a.z.boolean().default(!1),events:a.z.array(a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal(I),payload:W}).strict(),a.z.object({type:a.z.literal(y),payload:K}).strict(),a.z.object({type:a.z.literal(b),payload:V}).strict()])).default([])}).strict(),Q=a.z.object({source:j,sourcePriority:a.z.number().int().min(0).max(100).optional(),observedAt:a.z.string().datetime().optional(),snapshotVersion:a.z.number().int().min(0).optional()}).strict(),Y=Q.extend({type:a.z.literal("snapshot"),agent:a.z.string().trim().min(1).max(80),appSessionId:a.z.string().trim().max(120).optional(),appId:a.z.string().trim().max(120).optional(),providerSessionId:a.z.string().trim().max(240).optional(),providerMeta:a.z.record(a.z.string(),a.z.unknown()).optional(),appBindingKeys:a.z.array(a.z.string().trim().min(1).max(240)).max(20).optional(),actions:T.optional(),conversationId:a.z.number().int().positive().optional(),title:a.z.string().trim().max(240).optional(),cwd:a.z.string().trim().max(4096).optional(),activeTurnId:a.z.string().trim().max(240).optional(),userPrompt:a.z.string().trim().optional(),turns:a.z.array(P).max(50).optional(),status:S.optional(),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),transcriptPath:a.z.string().trim().max(4096).optional(),transcriptSize:a.z.number().int().min(0).optional(),replaceActivity:a.z.boolean().optional()}).strict(),X=Q.extend({type:a.z.literal("event"),sessionId:a.z.string().trim().min(1).max(120),kind:a.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:a.z.string().trim().max(240).optional(),providerTurnId:a.z.string().trim().max(240).optional(),userPrompt:a.z.string().trim().optional(),text:a.z.string().optional(),status:S.optional(),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),error:a.z.string().trim().max(4e3).optional(),counters:a.z.object({toolCalls:a.z.number().int().min(0).optional(),edits:a.z.number().int().min(0).optional(),approvals:a.z.number().int().min(0).optional()}).strict().optional()}).strict();a.z.discriminatedUnion("type",[Y,X]);const Z=a.z.union([a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("interval").default("interval"),intervalMs:a.z.number().int().min(1e3)}).strict(),a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("daily"),timeOfDay:a.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("weekly"),weekdays:a.z.array(a.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:a.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:a.z.string().trim().min(1).max(120)}).strict()]),ee=a.z.union([Z,a.z.object({type:a.z.literal("file-change"),roots:a.z.array(a.z.string().trim().min(1).max(4096)).min(1).max(50),globs:a.z.array(a.z.string().trim().min(1).max(500)).min(1).max(100),ignore:a.z.array(a.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:a.z.number().int().min(100).max(6e4).default(1500)}).strict(),a.z.object({type:a.z.literal("ai-session"),conversationId:a.z.number().int().positive().optional(),agent:a.z.string().trim().min(1).max(80).optional(),statuses:a.z.array(a.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:a.z.array(a.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),te=a.z.object({promptTemplate:a.z.string().trim().min(1).max(2e4)}).strict(),se=a.z.object({cooldownMs:a.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:a.z.number().int().min(1).max(20).default(1),whenBusy:a.z.enum(["skip","queue"]).default("skip")}).strict(),ne=a.z.object({configHash:a.z.string().trim().min(8).max(80),name:a.z.string().trim().min(1).max(160),description:a.z.string().trim().max(1e3).optional(),source:ee,action:te,policy:se.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),ie=a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("conversation"),conversationId:a.z.number().int().positive()}).strict(),a.z.object({type:a.z.literal("ai-session"),aiSessionId:a.z.string().trim().min(1).max(160)}).strict()]),ae=a.z.object({configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),origin:a.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:a.z.boolean().default(!0),target:ie,localName:a.z.string().trim().min(1).max(160).optional(),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),re=a.z.object({configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),status:a.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:a.z.string().datetime().optional(),lastCompletedAt:a.z.string().datetime().optional(),lastSkippedAt:a.z.string().datetime().optional(),lastError:a.z.string().trim().max(4e3).optional(),runCount:a.z.number().int().min(0).default(0),skippedCount:a.z.number().int().min(0).default(0)}).strict(),oe=a.z.object({id:a.z.string().trim().min(1).max(160),configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),eventType:a.z.enum(["manual","schedule","file-change","ai-session"]),status:a.z.enum(["started","completed","failed","skipped"]),target:ie,promptPreview:a.z.string().trim().max(500),eventSummary:a.z.string().trim().max(1e3).optional(),error:a.z.string().trim().max(4e3).optional(),startedAt:a.z.string().datetime(),completedAt:a.z.string().datetime().optional()}).strict(),de=a.z.object({schemaVersion:a.z.literal(1).default(1),configs:a.z.array(ne).default([]),deployments:a.z.array(ae).default([]),runtime:a.z.array(re).default([]),recentRuns:a.z.array(oe).default([])}).strict();function ce(e){return Array.isArray(e)?e.map(ce):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,ce(t)])):e}const le="2026-06-23",pe=a.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),ue=a.z.string().datetime(),ge=a.z.record(a.z.string(),a.z.string()).default({}),he=a.z.record(a.z.string(),a.z.string()).default({}),me=a.z.object({codexModelId:pe.optional(),claudeModelId:pe.optional()}).strict().default({}),fe=a.z.object({component:a.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:a.z.string().trim().max(160).optional(),packageVersion:a.z.string().trim().max(80).optional(),protocolVersion:a.z.string().trim().max(80).optional(),buildId:a.z.string().trim().max(160).optional(),builtAt:a.z.string().trim().max(120).optional(),gitCommit:a.z.string().trim().max(120).optional(),imageRef:a.z.string().trim().max(512).optional(),imageDigest:a.z.string().trim().max(240).optional()}).strict(),Ie=a.z.object({type:a.z.enum(["branch","tag","commit"]),name:a.z.string().trim().min(1).max(240).optional(),commit:a.z.string().trim().max(80).optional()}).strict(),ye=a.z.object({type:a.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:a.z.string().trim().min(1).max(120).optional()}).strict(),be=a.z.object({depth:a.z.number().int().positive().max(1e5).optional(),submodules:a.z.boolean().default(!1),lfs:a.z.boolean().default(!1),subdirectory:a.z.string().trim().max(240).default("")}).strict();a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),url:a.z.string().trim().min(1).max(2048),provider:a.z.string().trim().max(80).optional(),ref:Ie.default({type:"branch",name:"main"}),auth:ye.default({type:"none"}),clone:be.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:pe.optional(),modelSelection:me,labels:ge,createdAt:ue,updatedAt:ue}).strict();const we=a.z.object({id:pe,nodeId:pe,name:a.z.string().trim().min(1).max(160),path:a.z.string().trim().min(1).max(4096),defaultImageId:pe.optional(),modelSelection:me,labels:ge,createdAt:ue,updatedAt:ue}).strict(),Se=a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("git-repository"),repositoryId:pe.optional(),url:a.z.string().trim().min(1).max(2048),provider:a.z.string().trim().max(80).optional(),ref:Ie.default({type:"branch",name:"main"}),auth:ye.default({type:"none"}),clone:be.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),a.z.object({type:a.z.literal("git-template"),url:a.z.string().trim().min(1).max(2048),templateId:a.z.string().trim().min(1).max(120).optional(),ref:Ie.default({type:"branch",name:"main"}),auth:ye.default({type:"none"}),clone:be.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),a.z.object({type:a.z.literal("local-folder"),localFolderId:pe.optional(),path:a.z.string().trim().min(1).max(4096),ownerNodeId:pe.optional()}).strict()]),Ae=a.z.object({mode:a.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:a.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:a.z.string().trim().max(240).optional(),readOnly:a.z.boolean().default(!1)}).strict(),ve=a.z.enum(["codex","claude"]),ke=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),source:Se,defaultImageId:pe.optional(),defaultNodeId:pe.optional(),defaultRuntimeId:pe.optional(),modelSelection:me,workspacePolicy:Ae,labels:ge,createdAt:ue,updatedAt:ue}).strict(),Te=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),endpoint:a.z.string().trim().min(1).max(2048),key:a.z.string().trim().min(1).max(4096),model:a.z.string().trim().min(1).max(240),app:ve,enabled:a.z.boolean().default(!0),order:a.z.number().int().min(0).max(1e6).default(0),labels:ge,createdAt:ue,updatedAt:ue}).strict(),Ee=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),image:a.z.string().trim().min(1).max(512),registry:a.z.string().trim().min(1).max(160).default("local"),capabilities:a.z.array(a.z.string().trim().min(1).max(80)).default([]),optionalApps:a.z.array(a.z.string().trim().min(1).max(120)).default([]),defaultEnv:he,labels:ge,createdAt:ue,updatedAt:ue}).strict(),_e=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),connectionMode:a.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:a.z.object({mode:a.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:pe.optional(),secret:a.z.string().trim().max(4096).optional(),pairedAt:ue.optional(),pairing:a.z.object({status:a.z.enum(["pending","paired","expired"]).default("pending"),joinToken:a.z.string().trim().max(4096).optional(),expiresAt:ue.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:a.z.string().trim().max(2048).optional(),controlEndpoint:a.z.string().trim().max(2048).optional(),containerEndpoint:a.z.string().trim().max(2048).optional(),publicWebBase:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:a.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),labels:ge,lastSeenAt:ue.optional(),createdAt:ue,updatedAt:ue}).strict(),Ce=a.z.object({id:pe,nodeId:pe,name:a.z.string().trim().min(1).max(160),type:a.z.enum(["docker","kubernetes","local"]),status:a.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:a.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),labels:ge,createdAt:ue,updatedAt:ue}).strict(),Ne=a.z.object({ok:a.z.boolean().optional(),role:a.z.string().optional(),nodeId:pe.optional()}).passthrough(),ze=a.z.object({nodeId:pe,joinToken:a.z.string().trim().min(1),expiresAt:ue}).passthrough(),Oe=a.z.object({id:pe,keyId:pe,url:a.z.string().trim().min(1).max(2048).optional(),name:a.z.string().trim().min(1).max(160).optional(),pairedAt:ue,updatedAt:ue.optional(),active:a.z.boolean().optional(),current:a.z.boolean().optional()}).passthrough(),De=a.z.object({remote:a.z.object({id:pe,url:a.z.string().trim().min(1).max(2048).optional(),keyId:pe,pairedAt:ue,active:a.z.boolean()}).passthrough(),tunnel:a.z.object({status:a.z.enum(["disabled","saved","connecting","connected","failed"]),error:a.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),xe=a.z.object({deleted:a.z.boolean().optional()}).passthrough(),Re=a.z.object({repository:a.z.string().trim().optional(),tag:a.z.string().trim().optional(),id:a.z.string().trim(),createdSince:a.z.string().trim().optional(),size:a.z.string().trim().optional(),reference:a.z.string().trim().min(1)}).passthrough(),je=a.z.lazy(()=>a.z.object({name:a.z.string().trim().min(1),path:a.z.string().trim().min(1).max(4096),children:a.z.array(je)}).passthrough()),Pe=a.z.object({status:a.z.number().int().min(100).max(599).optional(),headers:a.z.record(a.z.string(),a.z.string()).optional(),bodyBase64:a.z.string().optional()}).passthrough(),$e=a.z.object({strategy:a.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:a.z.string().trim().max(2048).optional(),api:a.z.string().trim().max(2048).optional(),vnc:a.z.string().trim().max(2048).optional(),tty:a.z.string().trim().max(2048).optional(),logs:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),Me=a.z.object({strategy:a.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:a.z.string().trim().max(2048).optional(),api:a.z.string().trim().max(2048).optional(),ws:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),qe=a.z.object({mode:a.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:a.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:a.z.string().trim().max(4096).optional(),resolvedCommit:a.z.string().trim().max(120).optional(),error:a.z.string().trim().max(2048).optional()}).passthrough(),Le=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),projectId:pe.optional(),source:Se,sourceSnapshot:a.z.record(a.z.string(),a.z.unknown()).default({}),nodeId:pe,runtimeId:pe,imageId:pe.optional(),imageSnapshot:Ee.optional(),status:a.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:a.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:a.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:a.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:a.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:a.z.string().trim().max(80).optional(),instanceVersion:a.z.string().trim().max(80).optional(),build:fe.optional(),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),config:a.z.object({autoImportAgentConfigs:a.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:qe.default({status:"unknown"}),target:$e.default({strategy:"direct-port",status:"unknown"}),access:Me,receiver:a.z.object({status:a.z.string().trim().max(80).default("unknown"),pendingCount:a.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:a.z.object({runningCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),updatedAt:ue.optional(),revision:a.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:L.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:a.z.object({enabledCount:a.z.number().int().min(0).default(0),runningCount:a.z.number().int().min(0).default(0),errorCount:a.z.number().int().min(0).default(0),configs:a.z.array(a.z.object({configHash:a.z.string().trim().min(1),config:ne,deployments:a.z.array(ae).default([]),runtime:a.z.array(re).default([])}).strict()).default([]),recentRuns:a.z.array(oe).default([])}).strict().optional(),runtime:a.z.object({kind:a.z.string().trim().max(80).optional(),containerName:a.z.string().trim().max(240).optional(),containerId:a.z.string().trim().max(240).optional(),workspacePath:a.z.string().trim().max(4096).optional(),pid:a.z.number().int().positive().optional(),port:a.z.number().int().positive().max(65535).optional(),labels:a.z.record(a.z.string(),a.z.string()).default({})}).strict().default({labels:{}}),registrationToken:a.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:ue.optional(),createdAt:ue,updatedAt:ue}).strict();function Ue(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]]))}a.z.object({instanceId:pe.optional(),name:a.z.string().trim().min(1).max(160),projectId:pe.optional(),source:Se.optional(),nodeId:pe.optional(),runtimeId:pe.optional(),imageId:pe.optional(),instanceVersion:a.z.string().trim().max(80).optional(),protocolVersion:a.z.string().trim().max(80),build:fe.optional(),controlMode:a.z.enum(["standalone","controlled"]).default("controlled"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),target:$e.default({strategy:"direct-port",status:"unknown"}),workspace:qe.default({status:"unknown"}),registrationToken:a.z.string().trim().max(240).optional()}).strict(),a.z.object({status:Le.shape.status.optional(),health:Le.shape.health.optional(),protocolVersion:a.z.string().trim().max(80),build:fe.optional(),capabilities:Le.shape.capabilities.optional(),receiver:Le.shape.receiver.optional(),apps:Le.shape.apps.optional(),aiSessions:Le.shape.aiSessions.optional(),triggers:Le.shape.triggers.optional(),workspace:qe.optional(),target:$e.partial().optional()}).strict();const Be=a.z.enum(["web","telegram","wechat","dingding"]),Fe=a.z.object({id:pe,channel:Be,name:a.z.string().trim().min(1).max(160),enabled:a.z.boolean().default(!1),token:a.z.string().trim().max(4096).optional(),defaultChatId:a.z.string().trim().max(240).optional(),allowedUserIds:a.z.array(a.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:a.z.number().int().positive().max(6e4).default(3e3),settings:a.z.record(a.z.string(),a.z.unknown()).default({}),createdAt:ue,updatedAt:ue}).strict(),He=a.z.object({id:pe,channel:Be,bridgeId:pe.optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional(),activeProjectId:pe.optional(),activeInstanceId:pe.optional(),activeAiSessionId:a.z.string().trim().min(1).max(120).optional(),lastUsedAt:ue,createdAt:ue,updatedAt:ue}).strict(),Ge=a.z.object({source:a.z.object({type:a.z.literal("chat-gateway").default("chat-gateway"),channel:Be,bridgeId:pe.optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional()}).strict(),message:a.z.object({text:a.z.string().trim().max(2e4).default(""),attachments:a.z.array(E).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>f&&t.addIssue({code:a.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:a.z.object({instanceId:pe,aiSessionId:a.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),We=a.z.object({id:pe,instanceId:pe,projectId:pe.optional(),aiSessionId:a.z.string().trim().min(1).max(120),providerSessionId:a.z.string().trim().max(240).optional(),result:a.z.string().default(""),timeoutMs:a.z.number().positive().optional(),source:a.z.string().trim().max(120).optional(),kind:a.z.enum(["task","approval"]).default("task"),status:a.z.enum(["pending","unreachable"]).default("pending"),error:a.z.string().trim().max(2048).optional(),lastSeenAt:ue}).strict();function Ke(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||s.join(d.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 Ve(e){t.mkdirSync(e,{recursive:!0})}function Je(e,n){Ve(s.dirname(e));const i=`${e}.${process.pid}.${Date.now()}.tmp`;t.writeFileSync(i,`${JSON.stringify(n,null,2)}\n`,"utf8"),t.renameSync(i,e)}function Qe(e,t){console.warn(JSON.stringify({message:e,...t}))}function Ye(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function Xe(e,s){const n=s.logger||Qe;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void n("stored JSON could not be read",{filePath:e,...Ye(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 Ze(e){return`${e}_${r.randomUUID().replace(/-/g,"").slice(0,20)}`}function et(){return r.randomBytes(24).toString("base64url")}class tt{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){Ve(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=Xe(s.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const s=this.filePath(e);return t.existsSync(s)?Xe(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 Je(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 st{filePathValue;defaults;options;constructor(e,t,s={}){this.filePathValue=e,this.defaults=t,this.options=s}init(){Ve(s.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?Xe(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 Je(this.filePathValue,s),s}}const nt=o.promisify(r.scrypt),it="task_handoff_cp_session",at=a.z.enum(["disabled","password"]),rt=a.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),ot=a.z.string().min(8).max(4096),dt=a.z.object({username:rt,password:ot}).strict(),ct=a.z.object({username:a.z.string().trim().min(1).max(80),password:a.z.string().min(1).max(4096)}).strict(),lt=a.z.object({id:a.z.string().trim().min(1),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}),pt=lt.extend({username:rt,passwordHash:a.z.string().trim().min(1),role:a.z.enum(["viewer","operator","admin"]),lastLoginAt:a.z.string().datetime().optional()}).strict(),ut=lt.extend({userId:a.z.string().trim().min(1),tokenHash:a.z.string().trim().min(1),expiresAt:a.z.string().datetime(),lastSeenAt:a.z.string().datetime().optional()}).strict();function gt(){return(new Date).toISOString()}function ht(e){return r.createHash("sha256").update(e).digest("base64url")}async function mt(e){const t=r.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await nt(e,t,64)).toString("base64url")}`}function ft(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}function It(e,t){return`${it}=${encodeURIComponent(e)}; Path=/; HttpOnly; SameSite=Lax; Expires=${new Date(t).toUTCString()}`}class yt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return at.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new tt(e.authUsersDir,{schema:pt}),this.sessions=new tt(e.authSessionsDir,{schema:ut})}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=dt.parse(e),s=gt();return ft(this.users.put({id:Ze("user"),username:t.username,passwordHash:await mt(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=ct.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),o=Buffer.from(i,"base64url");return o.length===a.length&&r.timingSafeEqual(o,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=gt(),i=et(),a=new Date(Date.now()+12096e5).toISOString(),o=this.sessions.put({id:Ze("sess"),userId:s.id,tokenHash:ht(i),expiresAt:a,createdAt:n,updatedAt:n,lastSeenAt:n});return this.users.put({...s,lastLoginAt:n,updatedAt:n}),{user:ft({...s,lastLoginAt:n,updatedAt:n}),session:o,cookie:It(`${o.id}.${i}`,a)}}sessionToken(e){return function(e){const t=(e||"").split(";").map(e=>e.trim()).filter(Boolean).map(e=>{const t=e.indexOf("=");return t>=0?[e.slice(0,t),decodeURIComponent(e.slice(t+1))]:[e,""]});return Object.fromEntries(t)}("string"==typeof e.cookie?e.cookie:void 0)[it]}async userForRequest(e){if(!this.enabled())return;const t=this.sessionToken(e);if(!t)return;const[s,n]=t.split(".");if(!s||!n)return;const i=this.sessions.get(s);if(!i||i.expiresAt<=gt()||i.tokenHash!==ht(n))return;const a=this.users.get(i.userId);return a?(this.sessions.put({...i,lastSeenAt:gt(),updatedAt:gt()}),ft(a)):void 0}async currentSession(e){const t=this.state(),s=await this.userForRequest(e);return{...t,authenticated:!this.enabled()||Boolean(s),user:s}}logout(e){const t=this.sessionToken(e),s=t?.split(".")[0];return s&&this.sessions.delete(s),{ok:!0}}}const bt="app-session.snapshot",wt="app-session.patch",St="app-session.removed",At=a.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),vt=a.z.object({type:a.z.enum(["app-session","provider-session","adapter-key"]),id:a.z.string().trim().min(1).max(240),agent:a.z.string().trim().min(1).max(80).optional(),adapter:a.z.string().trim().min(1).max(80).optional(),key:a.z.string().trim().min(1).max(240).optional()}).strict(),kt=a.z.record(a.z.string(),a.z.unknown()).and(a.z.object({id:a.z.string().trim().min(1).max(160),appId:a.z.string().trim().min(1).max(160).optional(),title:a.z.string().trim().max(240).optional(),kind:a.z.string().trim().max(80).optional(),status:At.default("unknown"),bindings:a.z.array(vt).max(40).default([]),workspace:a.z.object({cwd:a.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:a.z.string().datetime().optional(),updatedAt:a.z.string().datetime().optional()})),Tt=a.z.object({runningCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),sessions:a.z.array(kt).default([]),updatedAt:a.z.string().datetime()}).strict(),Et=a.z.object({revision:a.z.number().int().min(0),lastEventAt:a.z.string().datetime(),snapshot:Tt}).strict();Tt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:a.z.number().int().min(0).optional()});const _t=a.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Ct=a.z.object({instanceId:a.z.string().trim().min(1).max(160),nodeId:a.z.string().trim().min(1).max(160).optional(),snapshotRevision:a.z.number().int().min(0),previousRevision:a.z.number().int().min(0).optional(),eventSeq:a.z.number().int().min(1),traceId:a.z.string().trim().min(1).max(160),generatedAt:a.z.string().datetime(),reason:_t}).strict(),Nt=a.z.object({meta:Ct,snapshot:Tt}).strict(),zt=a.z.object({meta:Ct,session:kt}).strict(),Ot=a.z.object({meta:Ct,sessionId:a.z.string().trim().min(1).max(160),tombstone:kt.optional(),expiresAt:a.z.string().datetime().optional()}).strict(),Dt=a.z.object({instanceId:a.z.string().trim().min(1).max(160),sinceRevision:a.z.number().int().min(0),latestRevision:a.z.number().int().min(0),syncRequired:a.z.boolean().default(!1),events:a.z.array(a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal(bt),payload:Nt}).strict(),a.z.object({type:a.z.literal(wt),payload:zt}).strict(),a.z.object({type:a.z.literal(St),payload:Ot}).strict()])).default([])}).strict();function xt(e,t=(new Date).toISOString()){const s=e.map(e=>Rt(e));return Tt.parse({runningCount:s.filter(e=>"running"===e.status).length,problemCount:s.filter(e=>"failed"===e.status).length,sessions:s,updatedAt:t})}function Rt(e){const t=jt("string"==typeof e.status?e.status:void 0),s="string"==typeof e.id?e.id:"",n=function(e){const t=Lt(e.workspace),s=Lt(e.tty),n=Lt(e.launch),i=Lt(e.ai),a=Lt(i.claude),r=Ut(t.cwd)||Ut(s.cwd)||Ut(a.cwd)||Ut(n.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return kt.parse({...a,status:t,bindings:qt([...(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}]:[],...$t(e)]),...n?{workspace:n}:{}});var r}function jt(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":At.options.includes(t)?t:"unknown"}function Pt(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 $t(e){const t=Lt(e.ai),s=Lt(t.claude),n=Lt(t.appServer),i=[],a=Ut(s.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Ut(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=Ut(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Ut(n.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Mt(e){const t="string"==typeof e.id?e.id:"",s=Lt(e.ai),n=Ut(Lt(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(Pt(s),s);return[...t.values()]}function Lt(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Ut(e){return"string"==typeof e&&e.trim()?e.trim():""}function Bt(e=(new Date).toISOString()){return Tt.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function Ft(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 Ht{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 Gt{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",Ne)}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",ze,{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",De,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",a.z.array(Oe))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,xe,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",a.z.array(Re))}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}`:""),a.z.array(je))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",a.z.array(we))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",we,{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",a.z.array(Ce))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",Ce,{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,Ce,{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,Ce,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,xe,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,xe,{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(Ft(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",a.z.array(a.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",Le,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,s){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Le,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,xe,{method:"POST"})}startInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Le,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Le,{method:"POST"})}restartInstance(e,t,s={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Le,{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`,Pe,{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(Ft(e[n],"/instances","GET",s.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const s="/instances",n=(i=t,Le.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,s=new Set(Object.keys(Le.shape)),n={};for(const[e,i]of Object.entries(t))s.has(e)&&(n[e]=i);return n.apps=Ue(t.apps,["runningCount","problemCount","updatedAt","revision"]),n.config=Ue(t.config,["autoImportAgentConfigs"]),n.receiver=Ue(t.receiver,["status","pendingCount"]),n.runtime=Ue(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),n.target=Ue(t.target??t.endpoints,["strategy","web","api","status"]),n.access=Ue(t.access,["strategy","web","api","ws","status"]),n}(i)));var i;if(n.success)return n.data.protocolVersion&&n.data.protocolVersion!==le?(this.client.logger?.warn?.({nodeId:e.id,instanceId:n.data.id,expectedProtocolVersion:le,actualProtocolVersion:n.data.protocolVersion,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node instance protocol version mismatch"),{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 ${le}.`,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 Wt(){return(new Date).toISOString()}function Kt(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 Vt(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 Jt(e){return e instanceof Error?e.message:String(e)}function Qt(e,t){const s=new Error(t);throw Object.assign(s,{statusCode:404,code:e}),s}class Yt{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:Kt(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:Kt(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 Xt(e){if(!e||"object"!=typeof e)return;const t=e,s=fe.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 Zt(e){const{agent:t,...s}=e.capabilities,{secret:n,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=Xt(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...s,agent:r}:s}}function es(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 ss(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 ns{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:F(n.aiSessions),connectionStatus:s,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?Zt(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===le}}({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 is=a.z.object({mode:a.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:_e.shape.id.optional(),secret:a.z.string().trim().max(4096).optional(),pairedAt:_e.shape.createdAt.optional(),pairing:a.z.object({status:a.z.enum(["pending","paired","expired"]).default("pending"),joinToken:a.z.string().trim().max(4096).optional(),expiresAt:_e.shape.createdAt.optional()}).strict().optional()}).strict(),as=a.z.object({id:ke.shape.id.optional(),name:ke.shape.name,source:Se,defaultImageId:ke.shape.defaultImageId,defaultNodeId:ke.shape.defaultNodeId,defaultRuntimeId:ke.shape.defaultRuntimeId,modelSelection:ke.shape.modelSelection.optional(),workspacePolicy:Ae.optional(),labels:ke.shape.labels.optional()}).strict(),rs=as.omit({id:!0}).partial().strict(),os=a.z.object({id:Le.shape.id.optional(),name:Le.shape.name.optional(),projectId:Le.shape.projectId,source:Se.optional(),sourceSnapshot:a.z.record(a.z.string(),a.z.unknown()).optional(),nodeId:Le.shape.nodeId.optional(),runtimeId:Le.shape.runtimeId.optional(),imageId:Le.shape.imageId,config:Le.shape.config.optional()}).strict(),ds=a.z.object({name:Le.shape.name.optional()}).strict(),cs=a.z.object({publicBaseUrl:a.z.string().trim().max(2048).optional()}).strict(),ls=cs.partial().strict(),ps=a.z.object({id:Te.shape.id.optional(),name:Te.shape.name,endpoint:Te.shape.endpoint,key:Te.shape.key,model:Te.shape.model,app:Te.shape.app,enabled:Te.shape.enabled.optional(),order:Te.shape.order.optional(),labels:Te.shape.labels.optional()}).strict(),us=ps.omit({id:!0}).partial().strict(),gs=a.z.object({id:Ee.shape.id.optional(),name:Ee.shape.name,image:Ee.shape.image,registry:Ee.shape.registry.optional(),capabilities:a.z.array(a.z.string().trim().min(1).max(80)).optional(),optionalApps:a.z.array(a.z.string().trim().min(1).max(120)).optional(),defaultEnv:Ee.shape.defaultEnv.optional(),labels:Ee.shape.labels.optional()}).strict(),hs=gs.omit({id:!0}).partial().strict(),ms=a.z.object({id:_e.shape.id.optional(),name:_e.shape.name,connectionMode:_e.shape.connectionMode.optional(),auth:is.optional(),joinToken:a.z.string().trim().min(1).max(4096).optional(),endpoint:_e.shape.endpoint,controlEndpoint:_e.shape.controlEndpoint,containerEndpoint:_e.shape.containerEndpoint,publicWebBase:_e.shape.publicWebBase,status:a.z.enum(["unknown","online","offline","degraded"]).optional(),health:a.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:_e.shape.capabilities.optional(),labels:_e.shape.labels.optional(),lastSeenAt:_e.shape.lastSeenAt}).strict(),fs=ms.omit({id:!0,joinToken:!0}).partial().strict(),Is=a.z.object({controlPlaneUrl:a.z.string().trim().url().max(2048),joinToken:a.z.string().trim().min(1).max(4096),controlPlaneName:a.z.string().trim().min(1).max(160).optional(),activate:a.z.boolean().optional()}).strict(),ys=a.z.object({nodeName:a.z.string().trim().min(1).max(160).optional(),expiresInMs:a.z.number().int().positive().max(36e5).optional()}).strict(),bs=a.z.object({name:Fe.shape.name.optional(),channel:Fe.shape.channel.optional(),enabled:a.z.boolean().optional(),token:Fe.shape.token,defaultChatId:Fe.shape.defaultChatId,allowedUserIds:a.z.array(a.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Fe.shape.pollIntervalMs.optional(),settings:Fe.shape.settings.optional()}).strict(),ws=bs.extend({channel:Fe.shape.channel,name:Fe.shape.name.optional()}),Ss=ne.extend({id:ne.shape.configHash}),As=a.z.object({name:ne.shape.name,description:ne.shape.description,source:ee,action:te,policy:se.partial().optional()}).strict(),vs=a.z.object({instanceIds:a.z.array(Le.shape.id).min(1),target:ie.optional(),enabled:a.z.boolean().optional()}).strict(),ks=a.z.object({configHash:ne.shape.configHash,enabled:a.z.boolean().optional()}).strict();class Ts{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=As.parse(e||{}),s=Wt(),n={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=ce({source:e.source,action:e.action,policy:se.parse(e.policy||{})});return`trg_${r.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:n}),a=Ss.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||Qt("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:n}}async applyTrigger(e,t){const s=this.requireTrigger(e),n=vs.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=ks.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:Es(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=Es(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||Qt("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function Es(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=r.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 Cs{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=>Os(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:Ns(t,n.sessionId,r,s)}}}function Ns(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):Os(zs(s.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:Os(zs(s.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function zs(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Os(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Ds{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 xs(e){const{token:t,...s}=e;return{...s,settings:js(e.settings),tokenSet:Boolean(e.token)}}function Rs(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}class Ps{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(xs)}require(e){const t=this.chatBridges.get(e);return t||Qt("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=ws.parse(e),s=Wt(),n=Fe.parse({...t,id:Ze(`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:Rs({},t.settings),createdAt:s,updatedAt:s});var i,a;return this.chatBridges.put(n),xs(n)}update(e,t){const s=bs.parse(t),n=this.require(e),i=Rs(n.settings,s.settings),a=Fe.parse({...n,...s,id:e,channel:n.channel,name:s.name||n.name,settings:i,updatedAt:Wt()});return this.chatBridges.put(a),xs(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 $s{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||Qt("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=Wt(),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=He.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 Ms(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 Ms(e.slice(0,t).map(e=>[e]))}function Ls(e){return Buffer.from(e,"utf8").toString("base64url")}function Us(e){const t=Fs(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=Fs(t),s=String(e.callback_data||""),i=String(e.text||"").trim();i&&s&&n.push({text:i,id:`th_cb_${Ls(s)}`})}return n.slice(0,40)}function Bs(e,t){const s=String(e||"").replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,Math.max(0,t-3))}...`}function Fs(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const Hs=new Set(["stopped","failed","exited","closed","terminated"]);function Gs(e){return!function(e){return Hs.has(jt(e))}(e)}function Ws(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const s=(e.apps?.sessions||[]).filter(Xs);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[...Rt(e).bindings.map(Pt),...Mt(e)].filter(Boolean)}(e).some(e=>n.has(e))))}(e,t))}function Ks(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function Vs(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function Js(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",s=t?` - ${Bs(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${s}`}function Qs(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return qs(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Bs(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Bs(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Bs(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function Ys(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 Xs(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return Gs("string"==typeof t.status?t.status:void 0)}class Zs{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?"✓ ":""}${Js(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 en(e){return e.providerTurnId||e.turnId||""}function tn(e){return Gs(on(e.status))}function sn(e,t){const s=on(t.appId)||"App",n=on(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(on(s.id)===t)return on(s.name).trim();return""}(e,s);return i||s}function nn(e){const t=function(e){const t=ln(e.capabilities).apps;if(Array.isArray(t)&&t.length>0)return t;const s=ln(e.image).optionalApps;return Array.isArray(s)&&s.length>0?s:["terminal-tty"]}(e),s=new Map;for(const e of t){const t=dn(e);t&&!s.has(t.id)&&s.set(t.id,t)}return[...s.values()]}function an(e,t,s,n){const i=on(t.id)?function(e,t){return`/apps/access/${rn(e)}?token=${encodeURIComponent(t())}`}(t,n):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const s=on(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 rn(e){const t=on(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function on(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function dn(e){if("string"==typeof e&&e.trim())return{id:e,label:cn(e)};const t=ln(e),s=on(t.id).trim();return s?{id:s,label:on(t.name).trim()||cn(s)}:void 0}function cn(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 ln(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}class pn{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:Bs(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=nn(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:Bs(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=nn(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(tn).filter(e=>Boolean(on(e.id))).map(e=>({instance:t,session:e,link:an(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:on(e.id)||"",mode:rn(e)}).token)})):[]}),i=n.filter(e=>e.link.url).map(e=>({text:Bs(`${e.instance.name} · ${sn(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 un{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}: ${Jt(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:en(i),reply:`Sent to ${t?.name||s.sourceSnapshot.name||"Source"} / ${s.name} / ${n}.`};var a}}class gn{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(Ys).join("\n"):"No pending routes.",replyMarkup:s.length?Qs(s,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,s){const n=Vt(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=Vt(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 hn{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 mn{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?Ws({...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=>Ws(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=Ks(t.session)-Ks(e.session);return s||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||Vs(e.session).localeCompare(Vs(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||Qt("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const s=await this.findInstance(e,t);return s||Qt("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 fn{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new mn(e),this.sender=new un(e,this.targets),this.appCommands=new pn(e,this.targets),this.pendingCommands=new gn(e),this.aiSessionCommands=new Zs(e,this.targets),this.targetCommands=new hn(e,this.targets)}async handleChatGatewayMessage(e){const t=Ge.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 In(e,t){const s=t.filter(e=>e.enabled),n={},i=bn(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=bn(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 yn(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 bn(e,t,s){const n=e.filter(e=>e.app===t);return n.find(e=>s&&e.id===s)||n[0]}function wn(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 Sn="TASK_HANDOFF_NODE_AGENT_V1",An=6e4;function vn(e=""){return r.createHash("sha256").update(e).digest("hex")}function kn(e,t){return r.createHmac("sha256",e).update(function(e){return[Sn,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function Tn(e){const t=e.timestamp||(new Date).toISOString(),s=e.nonce||r.randomBytes(18).toString("base64url"),n=vn(e.body||""),i=kn(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 En(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function _n(e,s=process.platform){if(!function(e,s=process.platform){if("win32"===s)return e.startsWith("\\\\.\\pipe\\");const n=t.statSync(e),i=process.getuid?.();return void 0===i||n.uid===i}(e,s)){const e=new Error("Local node agent IPC socket is not owned by the current user.");throw Object.assign(e,{statusCode:403,code:"NODE_AGENT_IPC_OWNER_INVALID"}),e}}function Cn(e,t,s,n={}){const i=`/api/node-agent${t}`,a={createConnection:()=>p.connect(e),headers:{host:"task-handoff-node-agent.local",...n}};return s&&(Array.isArray(s)?s.length>0:s.trim().length>0)?new c.WebSocket(`ws://task-handoff-node-agent.local${i}`,s,a):new c.WebSocket(`ws://task-handoff-node-agent.local${i}`,a)}const Nn=a.z.object({id:a.z.string().trim().min(1),tokenHash:a.z.string().trim().min(1),expiresAt:a.z.string().datetime(),nodeName:a.z.string().trim().min(1).max(160).optional(),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),zn="task-handoff.control-plane.local",On="task-handoff.control-plane.builtin";function Dn(e){return"true"===e.labels[zn]}function xn(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 Rn(e,t){const s=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(s)return Tn({nodeId:e.id,keyId:xn(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 Ds;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new ns;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 Ht({request:(e,t,s)=>this.nodeAgentFetch(e,t,s),logger:this.logger});this.nodeAgentGateway=new Gt(n),this.controlledInstanceGateway=new Yt({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 tt(e.projectsDir,i(ke)),this.models=new tt(e.modelsDir,i(Te)),this.images=new tt(e.imagesDir,i(Ee)),this.nodes=new tt(e.nodesDir,i(_e)),this.chatSessions=new tt(e.chatSessionsDir,i(He)),this.chatBridges=new tt(e.chatBridgesDir,i(Fe)),this.triggers=new tt(e.triggersDir,i(Ss)),this.nodeJoinInvites=new tt(e.nodeJoinInvitesDir,i(Nn)),this.settings=new st(e.settingsPath,()=>cs.parse({}),i(cs)),this.controlPlaneTriggerService=new Ts({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 Cs({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Ps({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new $s({chatSessions:this.chatSessions}),this.chatSessionRuntime=new fn({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 cs.parse(this.settings.get())}updateSettings(e){const t=ls.parse(e),s=cs.parse({...this.getSettings(),...t,publicBaseUrl:t.publicBaseUrl?.trim()||void 0});return this.settings.put(s)}seedDefaults(){if(!this.images.get("img_default")){const e=Wt();this.images.put(Ee.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(Dn)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const s="/health",n=t?Rn(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!==le){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 ${le}.`);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:le,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=fe.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=En(e);return n?(_n(n),async function(e,t,s={}){const n=s.method||"GET",i="string"==typeof s.body||s.body instanceof Buffer?s.body:void 0===s.body||null===s.body?void 0:String(s.body),a=new Headers(s.headers||{});return void 0===i||a.has("content-length")||a.set("content-length",String(Buffer.byteLength(i))),new Promise((s,r)=>{const o=l.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:Wt()}}async syncLocalNodeConnection(){const e={[zn]:"true",[On]:"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 En(e)?"local-ipc":"local-loopback"}(s),i=_e.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:n,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:Wt(),updatedAt:Wt()}),a=await this.inspectNodeAgent(s,i),r=Wt(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>Dn(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=_e.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:Xt(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(ss)}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=ps.parse(e),s=Wt(),n=this.nextModelOrder(),i=Te.parse({...t,id:t.id||Ze("model"),enabled:t.enabled??!0,order:t.order??n,labels:t.labels||{},createdAt:s,updatedAt:s});return ss(this.models.put(i))}updateModel(e,t){const s=us.parse(t),n=this.requireModelSecret(e),i=Te.parse({...n,...s,id:e,key:s.key?.trim()?s.key:n.key,createdAt:n.createdAt,updatedAt:Wt()});return ss(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(ke.parse({...t,modelSelection:{...t.modelSelection.codexModelId===e?{}:{codexModelId:t.modelSelection.codexModelId},...t.modelSelection.claudeModelId===e?{}:{claudeModelId:t.modelSelection.claudeModelId}},updatedAt:Wt()}));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)||Qt("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const n=s.get(e);this.models.put(Te.parse({...n,order:100*(t+1),updatedAt:Wt()}))}),this.listModels()}createProject(e){const t=as.parse(e),s=Wt(),n=Se.parse(t.source),i=this.validateProjectModelSelection(t.modelSelection||{}),a=ke.parse({...t,id:t.id||Ze("proj"),source:n,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",modelSelection:i,workspacePolicy:Ae.parse(t.workspacePolicy||es(n)),labels:t.labels||{},createdAt:s,updatedAt:s});return this.projects.put(a)}updateProject(e,t){const s=rs.parse(t),n=this.requireProject(e),i=s.source?Se.parse(s.source):n.source,a=s.modelSelection?this.validateProjectModelSelection(s.modelSelection):n.modelSelection,r=ke.parse({...n,...s,id:e,source:i,modelSelection:a,workspacePolicy:s.workspacePolicy?Ae.parse(s.workspacePolicy):n.workspacePolicy,createdAt:n.createdAt,updatedAt:Wt()});return this.projects.put(r)}deleteProject(e){return this.projects.delete(e)}listImages(){return this.images.list()}createImage(e){const t=gs.parse(e),s=Wt(),n=Ee.parse({...t,id:t.id||Ze("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=_e.parse({...t,id:n,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:Xt(e)}:t.capabilities,lastSeenAt:Wt(),updatedAt:Wt()});return n!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:Wt(),agent:e}}catch(e){const s=_e.parse({...t,status:"offline",health:"failed",updatedAt:Wt()});return this.nodes.put(s),{id:t.id,status:"offline",checkedAt:Wt(),error:e instanceof Error?e.message:String(e)}}}async createNodePairingInvite(e,t={}){const s=this.requireNode(e);return this.nodeAgentGateway.createPairingInvite(s,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=ys.parse(e&&"object"==typeof e?e:{}),s=Wt(),n=et(),i={id:Ze("node_join"),tokenHash:r.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=a.z.object({joinToken:a.z.string().trim().min(1).max(4096),nodeId:_e.shape.id,nodeName:_e.shape.name.optional(),keyId:a.z.string().trim().min(1).max(160),secret:a.z.string().trim().min(1).max(4096),pairedAt:a.z.string().datetime().optional()}).parse(e),s=r.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=Wt();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 o=_e.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(o)}async connectNodeToControlPlane(e,t){const s=this.requireNode(e),n=Is.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=hs.parse(t),n=this.requireImage(e),i=Ee.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Wt()});return this.images.put(i)}deleteImage(e){return this.images.delete(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(Zt)}async createNode(e){const t=ms.parse(e),s=Wt(),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||Ze("node"),d=_e.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=_e.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:Xt(c.data)}:{}},labels:t.labels||{},createdAt:s,updatedAt:s});return this.nodes.put(g)}updateNode(e,t){const s=fs.parse(t),n=this.requireNode(e),i=_e.parse({...n,...s,id:e,createdAt:n.createdAt,updatedAt:Wt()});return this.nodes.put(i)}deleteNode(e){if("true"===this.requireNode(e).labels[On]){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=os.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{...ts(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const s=ds.parse(t),n=await this.requireNodeInstance(e),i=this.requireNode(n.nodeId);return ts(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 ts(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 ts(await this.nodeAgentGateway.stopInstance(s,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),s=this.requireNode(t.nodeId);return ts(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{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions};try{return U.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:Jt(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,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 e=Bt();return{revision:0,lastEventAt:e.updatedAt,snapshot:e}}try{return Et.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:Jt(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const s=Bt();return{revision:0,lastEventAt:s.updatedAt,snapshot:s}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,s)=>({instanceId:e.id,appSessions:t[s].snapshot,revision:t[s].revision,lastEventAt:t[s].lastEventAt}))}}async listTriggers(){return this.controlPlaneTriggerService.listTriggers()}createTrigger(e){return this.controlPlaneTriggerService.createTrigger(e)}async deleteTrigger(e){return this.controlPlaneTriggerService.deleteTrigger(e)}async applyTrigger(e,t){return this.controlPlaneTriggerService.applyTrigger(e,t)}async listInstanceTriggers(e){return this.controlPlaneTriggerService.listInstanceTriggers(e)}async bindAiSessionTrigger(e,t,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=Wt(),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=We.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 M.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)<=f)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 M.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 R.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,s){const n=await this.requireControlledInstance(e,!0);return M.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 $.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 $.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 M.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=Rn(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=En(a),o=`/api/node-agent${s}`,d=Rn(e,{method:"GET",pathWithQuery:o});if(r){_n(r);const e=Cn(r,s,n,{...i,...d});return e.on("open",()=>{t.on("message",(t,s)=>e.readyState===c.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===c.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 l=new URL(o,a.replace(/\/$/,""));l.protocol="https:"===l.protocol?"wss:":"ws:";const p=n?new c.WebSocket(l.toString(),n,{headers:{...i,...d}}):new c.WebSocket(l.toString(),{headers:{...i,...d}});p.on("open",()=>{t.on("message",(e,t)=>p.readyState===c.WebSocket.OPEN&&p.send(t?e:String(e))),t.on("close",()=>p.close()),t.on("error",()=>p.close())}),p.on("message",(e,s)=>{t.readyState===c.WebSocket.OPEN&&t.send(s?e:e.toString())}),p.on("close",()=>t.close()),p.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=de.parse(await this.instanceRequest(e,"/triggers"));return Le.parse({...e,triggers:wn(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{}Qt("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||Qt("NODE_RUNTIME_NOT_FOUND",`Runtime ${t} was not found on node ${e}.`),s}async requireNodeLocalFolder(e,t){return this.nodeAgentGateway.requireLocalFolder(e,t)}async resolveAppLaunchOptions(e,t){const s="string"==typeof t.cwdFolderId?t.cwdFolderId.trim():"",n={...t};if(delete n.cwdFolderId,!s)return n;const i=this.requireNode(e.nodeId),a=await this.requireNodeLocalFolder(i,s);if(a.nodeId!==e.nodeId){const t=new Error(`Local folder ${a.id} belongs to node ${a.nodeId}, not ${e.nodeId}.`);throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_INSTANCE_NODE"}),t}const r=await this.requireNodeRuntimeOnNode(e.nodeId,e.runtimeId);return{...n,cwd:this.appLaunchCwdForFolder(e,a,r)}}appLaunchCwdForFolder(e,t,n){if("local"===n.type)return s.resolve(t.path);if("local-folder"!==e.source.type){const e=new Error("App cwd folder selection is only supported for local-folder instances.");throw Object.assign(e,{statusCode:400,code:"APP_CWD_REQUIRES_LOCAL_FOLDER_SOURCE"}),e}const i=s.resolve(e.source.path),a=s.resolve(t.path),r=s.relative(i,a);if(r.startsWith("..")||s.isAbsolute(r)){const s=new Error(`Local folder ${t.path} is outside the instance workspace ${e.source.path}.`);throw Object.assign(s,{statusCode:400,code:"APP_CWD_OUTSIDE_WORKSPACE"}),s}const o=e.runtime.workspacePath||e.workspace.path||es(e.source).path||"/workspace";return r?s.posix.join(o,...r.split(s.sep)):o}modelEnvForInstance(e){return function(e,t){const s=yn(e.sourceSnapshot);return In(ke.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:es(e.source),labels:{},createdAt:e.createdAt,updatedAt:e.updatedAt}),t)}(e,this.listAllModels())}modelEnvForSourceSnapshot(e,t){return function(e,t,s){const n=yn(t),i=(new Date).toISOString();return In(ke.parse({id:"string"==typeof t.id?t.id:`source_${Ze("tmp")}`,name:"string"==typeof t.name?t.name:"Source",source:e,defaultImageId:"string"==typeof t.defaultImageId?t.defaultImageId:void 0,modelSelection:n,workspacePolicy:es(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||Qt("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||Qt("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 In(e,this.listAllModels())}requireProject(e){const t=this.projects.get(e);return t||Qt("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"),ke.parse(t)}return ke.parse(e)}(e);return t!==e&&this.projects.put(t),t}requireModel(e,t=!1){const s=this.models.get(e);s||Qt("MODEL_NOT_FOUND",`Model ${e} was not found.`);const n=this.normalizeModelRecord(s);return t?n:ss(n)}requireModelSecret(e){const t=this.models.get(e);return t||Qt("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,Te.parse(t)}return Te.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){const t=this.images.get(e);return t||Qt("IMAGE_NOT_FOUND",`Image ${e} was not found.`),t}requireNode(e){const t=this.nodes.get(e);return t||Qt("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return Zt(this.requireNode(e))}async requireControlledInstance(e,t=!1){const s=await this.requireNodeInstance(e);return t?s:ts(s)}}function Pn(e){return g(String(e),"escape").replace(/\n+$/,"")}function $n(e){const[t,...s]=String(e).split("\n");return[`*${Pn(t)}*`,...s.map(Pn)].join("\n")}function Mn(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function qn(e,t,s,n,i={}){if(t.token)return Un(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 Ln(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 Un(e,t,s,n,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(s):Pn(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&&Bn(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...n,text:Mn(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&&Bn(a.description))return}const c=await e({...n,text:i.rawMarkdownV2?Mn(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||!Bn(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function Bn(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function Fn(e){return e instanceof Error?e.message:String(e)}function Hn(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 Gn{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||Hn,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: ${Fn(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: ${Fn(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: ${Fn(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 Wn=20971520;function Kn(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 Vn(e,t,n="telegram-image",i){if(i&&i>Wn)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 c=Number(o.headers.get("content-length")||0);if(c>Wn)throw new Error(`image is too large: ${c} 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(d.tmpdir(),"task-handoff-images");await h.mkdir(u,{recursive:!0});const g=p.replace(/[^.\w-]/g,"")||".jpg",m=s.join(u,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${g}`),f=Buffer.from(await o.arrayBuffer());if(f.length>Wn)throw new Error(`image is too large: ${f.length} bytes`);return await h.writeFile(m,f),m}class Jn{store;constructor(e){this.store=new Gn({updateIntervalMs:e.updateIntervalMs,send:(t,s,n)=>e.send(e.requireBridge(s?.bridgeId||""),s?.chatId||"",$n(t),{rawMarkdownV2:!0,replyMarkup:_i(n)}),edit:(t,s,n,i)=>e.edit(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t,$n(s),{rawMarkdownV2:!0,replyMarkup:_i(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 Qn(e,t){if("string"==typeof e&&e.trim())return e.trim();Xn(`Chat action token is missing ${t}.`)}function Yn(e){if("allow"===e||"deny"===e||"skip"===e)return e;Xn("Chat action token has an invalid decision.")}function Xn(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class Zn{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=Ei(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,Ei(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=fi({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||ki(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||ki(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 Ii(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:ii({type:"actions",title:e.title,description:e.text,list:Us(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 ei(e){const{fetchImpl:t,bridge:s,dingdingRuntime:n}=e;return"telegram"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=await qn(t,s,e,n,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(Ai(a).message_id)||void 0}}}:"dingding"===s.channel?{bridge:s,send:async(e,i,a={})=>{const r=a.sessionWebhook||n?.chatWebhooks.get(e)||ki(s.settings.sessionWebhook);if(a.replyMarkup&&n){const o=await fi({fetchImpl:t,bridge:s,runtime:n,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||n.senderIds.get(e)||ki(s.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await gi(t,r,i,n?.onLog),{provider:"dingding"}}}:"wechat"===s.channel?{bridge:s,send:async(e,n,i={})=>{const a=i.contextToken||ki(s.settings.contextToken);if(a)return await async function(e,t,s,n,i){const a=ki(t.settings.baseUrl)||"https://ilinkai.weixin.qq.com",o=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:r.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 o.json().catch(()=>({}));if(!o.ok||d.errcode)throw new Error(ki(d.errmsg)||`WeChat send failed with HTTP ${o.status}`)}(t,s,e,a,n),{provider:"wechat"}}}:{bridge:s,send:async()=>{}}}async function ti(e){return(await async function(e,t,s,n){const i=[];for(const a of Kn(s))try{const s=await Vn({getFileLink:s=>Ln(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_${r.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:s.basename(e)||`telegram-image-${n+1}`,mime:si(e),size:i.length,data:i.toString("base64")}})}function si(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 ni(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function ii(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function ai(e){return`dtv1.card//IM_GROUP.${e}`}function ri(e){return`dtv1.card//IM_ROBOT.${e}`}function oi(e,t){return ki(t.biz_conversation_id).trim()||ki(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function di(e,t={}){const s=ki(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 ci(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 li(e,t,s,n,i){const a=oi(n,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:ii({type:"actions",title:"TaskHandoff",description:e,list:Us(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 pi(e,t,s){const n=[];return ui(n,s),ui(n,t),ui(n,e),n.find(e=>ci(e))||""}function ui(e,t){for(const s of["actionIdList","actionIds"]){const n=t[s];Array.isArray(n)&&e.push(...n.map(e=>ki(e)).filter(Boolean))}for(const s of["actionId","action_id","id","actionValue"]){const n=ki(t[s]);n&&e.push(n)}}async function gi(e,t,s,n){n?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:Si(s)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:ni(s),text:s}})}),a=await i.json().catch(()=>{});if(n?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:wi(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function hi(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=Ai(e);if(1!==t.type)continue;const s=ki(Ai(t.text_item).text).trim();if(s)return s}return""}function mi(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function fi(e){const t=Us(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await gi(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const s=e.bridge.settings||{},n=ki(s.robotCode),i=ki(s.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=ki(s.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||ki(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 gi(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),{delivered:!0};throw new Error("DingDing card target is incomplete.")}const l=`task_handoff_cp_${Date.now()}_${Math.random().toString(16).slice(2)}`,p=ii({type:"actions",title:e.title||ni(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:ri(s),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:n,spaceType:"IM_ROBOT"}}:{openSpaceId:ai(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:Si(e.text)},"dingding card send requested"),await Ii(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 Ii(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=ki(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:yi(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:wi(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=Ai(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error(ki(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=Ai(Ai(e).result).deliverResults;if(!Array.isArray(t))return"";const s=t.find(e=>!1===Ai(e).success);if(!s)return"";const n=Ai(s),i=ki(n.spaceId),a=ki(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 yi(e){const t=Ai(e),s=Ai(t.cardData),n=Ai(s.cardParamMap);return{openSpaceId:ki(t.openSpaceId),outTrackId:ki(t.outTrackId),userId:ki(t.userId),userIdType:t.userIdType,callbackType:ki(t.callbackType),callbackRouteKey:ki(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:bi(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:bi(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:bi(t.imRobotOpenDeliverModel),bizConversationId:ki(n.biz_conversation_id),bizConversationType:ki(n.biz_conversation_type),bizSenderId:ki(n.biz_sender_id),hasBizSessionWebhook:Boolean(ki(n.biz_session_webhook))}}function bi(e){const t=Ai(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean(ki(t.robotCode)),spaceType:ki(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 wi(e){const t=Ai(e),s=Ai(t.result);return{code:t.code??t.errcode,message:Si(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(s).length?s:void 0}}function Si(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 Ai(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function vi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?Ti(e):{}}function ki(e){return"string"==typeof e?e:""}function Ti(e){try{return Ai(JSON.parse(String(e||"{}")))}catch{return{}}}function Ei(e){return JSON.stringify(Us(e))}function _i(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Ms(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Ci(e){return Buffer.from(e,"base64url").toString("utf8")}function Ni(e){return new u.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class zi{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 Jn({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 Zn(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:Li(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=s.createDingdingClient||Ni}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=qi((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(u.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(u.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=Ti(e),s=ki(t.conversationId),n=ki(Ai(t.text).content).trim();if(!s||!n)return;const i=ki(t.senderStaffId||t.senderId);return{chatId:s,userId:i||void 0,senderId:i||void 0,sessionWebhook:ki(t.sessionWebhook)||void 0,conversationType:di(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)||qi(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:Ui(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 gi(this.fetchImpl,d,`Failed to handle message: ${Li(e)}`,t.onLog);throw e}const l=Oi(c);if(l){const s=xi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:s,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const n=ei({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===Hi(c).routed,hasReplyMarkup:Boolean(s),replyPreview:Ui(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(s),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(qi(e.settings.robotCode)),replyPreview:Ui(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,s,n,i,a={}){if(!0!==e.routed)return!1;const r=Ki(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=ea(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:Ui(a.sourcePrompt,80),textPreview:Ui(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,s){const n=function(e){const t=Ti(e),s=vi(t.cardActionData||t.content),n=vi(s.cardPrivateData||t.cardPrivateData||t.content),i=vi(n.params||s.params||t.params),a=ki(t.userId),r=ki(i.biz_sender_id);return{userId:a||void 0,chatId:oi(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:ki(i.biz_session_webhook)||void 0,conversationType:di(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:ci(pi(t,s,n)),body:t,params:i}}(s.data);if(!this.dingdingAllowed(e,n.userId))return li("not authorized",void 0,"unauthorized",n.body,n.params);const i=n.chatId||qi(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,...Bi(n)},"dingding card callback action unsupported"),li("Unsupported action",void 0,"unsupported",n.body,n.params);this.logInfo({bridgeId:e.id,chatId:i,userId:n.userId,senderId:a,actionType:o.type,...Bi(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&&Di(d)?li(`${o.decision} sent`,void 0,"approval_done",n.body,n.params):li(Oi(d)||qi(d.message)||"updated",xi(d),"updated",n.body,n.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},s=this.wechatCursors.get(e.id)||qi(t.updatesBuf),n=await async function(e){const t=ki((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(ki(n.errmsg||n.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(n.msgs)?n.msgs:[]){const t=Ai(e);if(1!==t.message_type)continue;const s=ki(t.from_user_id),n=hi(t);s&&n&&a.push({chatId:s,text:n,contextToken:ki(t.context_token)})}return{cursor:ki(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=Oi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:n},message:{text:a,attachments:[]}}));if(r){const s=ei({fetchImpl:this.fetchImpl,bridge:e});await s.send(n,r,{contextToken:i||qi(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=Vi(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 ei({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=Ai(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:n,callbackQuery:a});continue}const r=Ai(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=Ai(r.chat),d=Ai(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=ki(r.text??r.caption),u=Kn(r);(p||0!==u.length)&&i.push({updateId:n,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:mi(r),replyToMessageId:mi(Ai(r.reply_to_message)),quoteText:ki(Ai(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=Hi(s.message),i=Hi(n.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,Li(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${Li(t)}`).catch(t=>{this.bridgeErrors.set(e.id,Li(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const s=await ti({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:Li(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:Ui(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=Pi(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:Ms([[{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:Ms([[{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!==Pi(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=r.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=Oi(d),l=xi(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(Hi(s).message_id)||void 0,routed:!0===Hi(d).routed,replyPreview:Ui(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,s,c)}}catch(s){this.bridgeErrors.set(e.id,Li(s)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${Li(s)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,Li(t))})}}async handleTelegramCallback(e,t){const s=qi(t.data),n=Hi(t.from),i=Hi(t.message),a=Hi(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,qi(t.id),s.instanceId,s.sessionId,o,ji(i)):void await this.answerTelegramCallback(e,qi(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,qi(t.id),s.instanceId,s.sessionId,s.queueId,o,ji(i)):void await this.answerTelegramCallback(e,qi(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,qi(t.id),s.instanceId,s.sessionId,o,ji(i)):void await this.answerTelegramCallback(e,qi(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,qi(t.id),ji(i),s.instanceId,s.sessionId,s.queueId,o):void await this.answerTelegramCallback(e,qi(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,qi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(s);if(!g)return void await this.answerTelegramCallback(e,qi(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&&Di(h)){await this.answerTelegramCallback(e,qi(t.id),"");const s=ji(i);return void(void 0!==s&&await this.deleteTelegramStandaloneApprovalMessage(e,r,s))}await this.answerTelegramCallback(e,qi(t.id),qi(h.message)||"updated");const m=Oi(h),f=xi(h),I=i.message_id;m&&"number"==typeof I?await this.editTelegramMessage(e,r,I,m,{replyMarkup:f}):m&&await this.sendTelegramMessage(e,r,m,{replyMarkup:f})}async deleteTelegramStandaloneApprovalMessage(e,t,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:Li(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,Li(t)),await this.answerTelegramCallback(e,s,`Interrupt failed: ${Ui(Li(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,Li(t)),await this.answerTelegramCallback(e,s,`Steer failed: ${Ui(Li(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=Hi(await this.service.aiSessionQueue(n,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>Hi(e)).filter(e=>"queued"===qi(e.status)&&qi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,s,"Queue is empty");const d=o.slice(0,Ji).map((e,t)=>[{text:Zi(qi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(n,i,qi(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:Ms(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,Li(t)),await this.answerTelegramCallback(e,s,`Queue menu failed: ${Ui(Li(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:Li(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,Li(t)),await this.answerTelegramCallback(e,s,`Delete failed: ${Ui(Li(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:Qn(t(n[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Ci(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:Qn(e.instanceId,"instanceId"),appId:Qn(e.appId,"appId")}}return{type:"launch-app",instanceId:Ci(i[1]),appId:Ci(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:Qn(e.routeId,"routeId"),decision:Yn(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 Un(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 qn(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=Ki(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(Hi(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=ea(r,o,d,t.id,s),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:s});this.telegramProgressMessageTargets.set($i(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:Ui(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($i(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:Li(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,s){const n=this.telegramProgressMessageTargets.get($i(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=Mi(n,e,t);if(i){const n={...i,messageId:s};return this.telegramProgressMessageTargets.set($i(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($i(n.route.bridgeId,n.route.chatId,n.messageId||0))||Mi(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($i(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=aa(s.get(e)||e,a),o=ia(a,r);if(!o){this.logAiSessionDelivery({stage:"skip-empty-text",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,status:a.status,phase:a.phase,activeTurnId:a.activeTurnId,latestTurn:Fi(ra(a))});continue}const d=ta(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:Fi(ra(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=ea(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=sa(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:Fi(ra(a)),textPreview:Ui(o)});continue}const p=Yi({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)||Xi(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:Fi(ra(a)),textPreview:Ui(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:Li(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:Ui(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:Ui(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:Fi(ra(a)),textPreview:Ui(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:Ui(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=aa(s.get(e)||e,r),c=ia(r,d),l=ta(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:ea(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=sa(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=Yi({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)||Xi(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:Ui(c)}),h&&(this.telegramProgressMessageTargets.set($i(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($i(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($i(n.route.bridgeId,n.route.chatId,n.messageId))||Mi(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||ra(t)?.id===e.owner.turnId||ra(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:Ri({actionRows:n})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:s,textPreview:Ui(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:Ui(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:Ui(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=r.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=r.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 Oi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function Di(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function xi(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 Ri(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Ms(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 Pi(e,t,s){return[e,t,s||""].join(":")}function $i(e,t,s){return[e,t,String(s)].join(":")}function Mi(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 qi(e){return"string"==typeof e?e:""}function Li(e){return e instanceof Error?e.message:String(e)}function Ui(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 Bi(e){const t=Gi(e.body.cardActionData||e.body.content),s=Gi(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:qi(e.body.outTrackId),spaceId:qi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:Ui(e.callbackData,120),actionIds:[...Wi(s.actionIdList),...Wi(t.actionIdList),qi(s.actionId),qi(t.actionId),qi(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 Fi(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:Ui(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:Ui(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function Hi(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Gi(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return Hi(JSON.parse(e))}catch{return{}}return{}}function Wi(e){return Array.isArray(e)?e.map(e=>qi(e)).filter(Boolean):[]}function Ki(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?ta(s):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:s?.activeTurnId,latestTurnId:s?ra(s)?.id:void 0}}}function Vi(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:Ms([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const Ji=5,Qi=48;function Yi(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,Ji);return[...d.map((e,t)=>[{text:Zi(e.message,t),callbackData:s(e.id)}]),d.length?[{text:"Delete Queue",callbackData:n()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function Xi(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function Zi(e,t){const s=`${t+1}. `,n=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,Qi-s.length),a=Array.from(n);return`${s}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:n}`}function ea(e,t,...s){return[e,t,...s].join(":")}function ta(e){const t=ra(e);return t?.id?t.id:qi(e.activeTurnId)}function sa(e,t=""){const s=ra(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||"",na(e)].join("\0")}function na(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function ia(e,t){const s=function(e,t){return t?t.lastMessage?.trim()?t.lastMessage.trim():t.summary?.trim()?t.summary.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:"":e.lastMessage?.trim()?e.lastMessage.trim():e.summary?.trim()?e.summary.trim():e.error?.trim()?"failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:""}(e,ra(e));return s?`${t}\n${s}`:""}function aa(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 ra(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}function oa(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("receiver.")?"receiver":e.startsWith("conversation.")?"conversations":e.startsWith("trigger.")?"triggers":e.startsWith("channel.")?"channels":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}class da{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&&ca(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||oa(e),createdAt:(new Date).toISOString(),payload:t,scope:s.scope}}}function ca(e,t,s){return!e||e.has("*")||e.has(t)||e.has(s)}const la={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},pa=a.z.object({instanceId:a.z.string().trim().min(1).max(120),sessionId:a.z.string().trim().min(1).max(120),kind:a.z.literal("image").default("image"),name:a.z.string().trim().min(1).max(240),mime:a.z.string().trim().min(1).max(120),data:a.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=pa.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(),o=la[a];if(!o)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>m)throw new Error("Image must be between 1 byte and 20971520 bytes.");const c=`att_${r.randomUUID().replace(/-/g,"").slice(0,24)}`,l=new Date,p=new Date(l.getTime()+this.ttlMs),u=s.join(d.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,`${c}-${g.endsWith(o)?g:`${g}${o}`}`);t.writeFileSync(h,i.buffer);const f={id:c,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(c,f),this.publicAttachment(f)}resolveRefs(e,t,s){this.pruneExpired();const n=a.z.array(C).max(6).default([]).parse(e),i=n.map(e=>this.requireAttachment(e,t,s));if(i.reduce((e,t)=>e+t.size,0)>f)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 E.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 ga(e){return e.readyState===(e.OPEN??1)}function ha(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 ma(e,t){e.send(t.data,{binary:t.isBinary})}function fa(e,t){if(ga(e))for(const s of t.splice(0))ma(e,s)}const Ia=a.z.object({nodeId:a.z.string().trim().min(1).max(120)}).strict();function ya(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 ba(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const wa=new Map;function Sa(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)>An)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()-An;for(const[e,t]of wa)t<=i&&wa.delete(e);const a=`${e.id}:${s.keyId}:${s.nonce}`;if(!s.nonce||wa.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const o=vn("");if(s.bodySha256!==o)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=kn(e.auth.secret,{keyId:s.keyId,method:"GET",pathWithQuery:d,timestamp:s.timestamp,nonce:s.nonce,bodySha256:o});if(!function(e,t){const s=Buffer.from(e,"hex"),n=Buffer.from(t,"hex");return s.length===n.length&&r.timingSafeEqual(s,n)}(s.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});wa.set(a,Date.now()+An)}class Aa{sockets=new Map;events;constructor(e){this.events=e}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){if(!("string"==typeof t.type?t.type:"").startsWith("node-agent.event."))return!1;const s=t.event&&"object"==typeof t.event&&!Array.isArray(t.event)?t.event:void 0,n="string"==typeof s?.type?s.type:"";if(!n)return!0;const i="payload"in s?s.payload:{},a=s.scope&&"object"==typeof s.scope&&!Array.isArray(s.scope)?s.scope:{},r="string"==typeof t.instanceId?t.instanceId:void 0;return this.events?.publish(n,i,{topic:"string"==typeof s.topic?s.topic:void 0,scope:{...a,nodeId:e,instanceId:"string"==typeof a.instanceId?a.instanceId:"string"==typeof s.instanceId?s.instanceId:r}}),!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=ba(),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=ba(),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=ga(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(s,i)=>{if(s.length>=n)return ha(e,1011,"WebSocket bridge pending frame limit exceeded."),void ha(t,1011,"WebSocket bridge pending frame limit exceeded.");s.push(i)};s.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(ha(e,1011,"Upstream websocket did not open."),ha(t))},s.upstreamOpenTimeoutMs)),e.on("open",()=>fa(e,o)),t.on("open",()=>{c(),a=!0,s.onUpstreamOpen?.(),fa(t,r)}),e.on("message",(e,s=!1)=>{const n={data:e,isBinary:Boolean(s)};ga(t)?ma(t,n):l(r,n)}),t.on("message",(t,s=!1)=>{const n={data:t,isBinary:Boolean(s)};ga(e)?ma(e,n):l(o,n)}),t.on("close",(t,n)=>{c(),s.onUpstreamClose?.(t,n),(a||i||!s.onUpstreamCloseBeforeOpen?.())&&ha(e,t,n)}),t.on("error",t=>{s.onUpstreamError?.(t),(a||i||!s.onUpstreamErrorBeforeOpen?.(t))&&ha(e)}),e.on("close",(e,n)=>{c(),i=!0,s.onClientClose?.(e,n),ha(t,e,n)}),e.on("error",e=>{c(),i=!0,s.onClientError?.(e),ha(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 va{sockets=new Map;service;tunnel;timer;constructor(e,t){this.service=e,this.tunnel=t}start(){this.sync(),this.timer=setInterval(()=>this.sync(),5e3)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const e of this.sockets.values())e.close();this.sockets.clear()}syncNow(){this.sync()}sync(){const e=new Set;for(const t of this.service.listNodes()){const s=this.nodeEventsUrl(t);s&&(e.add(t.id),this.sockets.has(t.id)||this.connect(t,s))}for(const[t,s]of this.sockets)e.has(t)||(s.close(),this.sockets.delete(t))}connect(e,t){const s=En(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 Tn({nodeId:e.id,keyId:ya(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?Cn(s,"/events",void 0,i):new c.WebSocket(t,{headers:i});this.sockets.set(e.id,a),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)}),a.on("error",()=>{a.close()})}nodeEventsUrl(e){const t=e.controlEndpoint||e.endpoint;if(t&&"reverse-wss"!==e.connectionMode){if(En(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 ka 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 Ta(e){return s.join(e,"owner.json")}function Ea(e){try{return JSON.parse(t.readFileSync(Ta(e),"utf8"))}catch{return}}function _a(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 Ca(e,s){t.writeFileSync(Ta(e),`${JSON.stringify(s,null,2)}\n`,"utf8")}function Na(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 za=a.z.string().trim().min(1),Oa=a.z.object({id:za}).strict(),Da=a.z.object({id:za,keyId:za}).strict(),xa=a.z.object({id:za,runtimeId:za}).strict(),Ra=a.z.object({id:za,folderId:za}).strict(),ja=a.z.object({id:za,sessionId:za}).strict(),Pa=a.z.object({id:za,sessionId:za,queueId:za}).strict(),$a=a.z.object({configHash:za}).strict(),Ma=a.z.object({id:za,configHash:za}).strict(),qa=a.z.object({id:za,sessionId:za,configHash:za}).strict(),La=a.z.object({path:a.z.string().optional(),depth:a.z.coerce.number().int().min(0).max(32).optional()}).strict(),Ua=a.z.object({ids:a.z.array(a.z.string().trim().min(1).max(120)).default([])}).strict(),Ba=a.z.object({source:a.z.object({channel:Be,bridgeId:a.z.string().trim().min(1).max(120).optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional()}).strict(),action:a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("ai-session"),index:a.z.number().int().nonnegative()}).strict(),a.z.object({type:a.z.literal("instance-app-menu"),instanceId:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("launch-app"),instanceId:a.z.string().trim().min(1).max(120),appId:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("pending-decision"),routeId:a.z.string().trim().min(1).max(120),decision:a.z.enum(["allow","deny","skip"])}).strict()])}).strict();function Fa(e){const t=Ba.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const Ha=a.z.object({appId:a.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:a.z.string().trim().min(1).max(120).optional(),options:a.z.record(a.z.string(),a.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),Ga=a.z.object({title:a.z.string().trim().min(1).max(120)}).strict(),Wa=a.z.object({id:a.z.string().trim().min(1),direction:a.z.enum(["import","export"]),preset:a.z.string().trim().min(1).max(120)});function Ka({app:e,service:t,events:s,appSessionAggregator:n,aiSessionAggregator:i,chatGateway:r,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(Oa.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const n=t.updateProject(Oa.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=Oa.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=Ua.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(Oa.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const n=t.updateModel(Oa.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=Oa.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(Oa.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const n=t.updateImage(Oa.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=Oa.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(Oa.parse(e.params).id);return s.publish("node.checked",{nodeId:n.id}),{data:n}}),e.post("/api/nodes/:id/pairing/invites",async(e,n)=>{const i=Oa.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=Oa.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=Oa.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(s.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const n=Da.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(Oa.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,n)=>{const i=Oa.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=xa.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=xa.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=xa.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(Oa.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Oa.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const s=Oa.parse(e.params),n=La.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=Oa.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=Ra.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(Oa.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const n=t.updateNode(Oa.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=Oa.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=Ia.parse(t.query),a=s.requireNode(i.nodeId);Sa(a,t),n.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:le,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=Ia.parse(t.query),a=s.requireNode(i.nodeId);Sa(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=a.z.object({refresh:a.z.string().optional(),includeTombstones:a.z.string().optional(),instanceId:a.z.string().trim().min(1).optional(),sinceRevision:a.z.string().optional()}).parse(e.query||{});if(void 0!==t.sinceRevision){const e=Number(t.sinceRevision);if(!Number.isInteger(e)||e<0){const e=new Error("sinceRevision must be a non-negative integer.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}return{data:await n.delta({instanceId:t.instanceId,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(Oa.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const n=await t.updateControlledInstance(Oa.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=Oa.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(Oa.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(Oa.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(Oa.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=Oa.parse(e.params),i=Ha.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=Ga.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=Oa.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=O.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=Pa.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=Pa.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=Pa.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=Wa.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{data:await i.delta({instanceId:s.instanceId,sinceRevision:e})}}catch(e){if("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED"===e.message)return t.code(400).send({error:"instance_id_required"});throw e}}return{data:await i.list({refresh:"true"===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=$a.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=$a.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=Oa.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=qa.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=Ma.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(Oa.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=Oa.parse(n.params).id,a=t.requireChatBridge(i),r=bs.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=Oa.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=Oa.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(Oa.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(Ge.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(Fa(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:r})}const Va=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),Ja=new Set(["content-encoding"]);function Qa(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!Va.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function Ya(e){return Object.fromEntries(Object.entries(Qa(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function Xa(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 Za(e,t){for(const[s,n]of Object.entries(t.headers)){const t=s.toLowerCase();Va.has(t)||Ja.has(t)||e.header(s,n)}return e.code(t.status).send(t.body)}function er(e){return`/instances/${encodeURIComponent(e)}`}function tr(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"),er(s)),"utf8"):t.body;return Za(e,{...t,body:i})}function sr(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 nr(e,t){if("string"!=typeof t||!e.hasContentTypeParser(t))try{e.addContentTypeParser(t,{parseAs:"buffer"},(e,t,s)=>s(null,t))}catch(e){if(e instanceof Error&&/Content type parser .* already present/i.test(e.message))return;throw e}}function ir(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function ar(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 rr{snapshots=new Map;history=new Map;listeners=new Set;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===I?W:e.type===y?K:e.type===b?V:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===I&&this.applySnapshot(s.data),e.type===y&&this.applyPatch(s.data),e.type===b&&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){return this.accept({type:I,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const s=new Map(t.snapshot.sessions.map(e=>[e.id,e]));for(const t of e.upserted)s.set(t.id,t);for(const t of e.removed)s.delete(t);return this.accept({type:y,payload:e},or([...s.values()],e.meta.generatedAt,t.snapshot.staleCount))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const s=new Set(e.sessionIds);return this.accept({type:b,payload:e},or(t.snapshot.sessions.filter(e=>!s.has(e.id)),e.meta.generatedAt,t.snapshot.staleCount))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view()}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>s)return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:!0,events:[]});if(e.sinceRevision===s)return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:!1,events:[]});const n=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!n.length||n[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:i,events:i?[]:n.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:s}=e.payload,n=this.snapshots.get(s.instanceId);if(n&&n.revision>=s.snapshotRevision)return this.logger?.warn?.({traceId:s.traceId,instanceId:s.instanceId,currentRevision:n.revision,snapshotRevision:s.snapshotRevision},"ai-session.aggregator.event.stale"),!1;this.snapshots.set(s.instanceId,{snapshot:t,revision:s.snapshotRevision,lastEventAt:s.generatedAt}),this.rememberEvent(s.instanceId,e);const i={instanceId:s.instanceId,aiSessions:t,revision:s.snapshotRevision,lastEventAt:s.generatedAt};for(const e of this.listeners)try{e(i)}catch{this.listeners.delete(e)}return this.logger?.info?.({traceId:s.traceId,instanceId:s.instanceId,snapshotRevision:s.snapshotRevision,eventSeq:s.eventSeq,reason:s.reason,sessionCount:t.sessions.length},"ai-session.aggregator.event.accepted"),!0}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"ai-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"ai-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.aiSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const s=this.history.get(e)??[];s.push({...t,createdAtMs:Date.now()}),this.history.set(e,s),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,aiSessions:t.snapshot,revision:t.revision,lastEventAt:t.lastEventAt}))}}}function or(e,t,s){return L.parse({sessions:e,runningCount:e.filter(e=>"running"===e.status).length,waitingCount:e.filter(e=>"waiting"===e.status).length,staleCount:s,updatedAt:t})}class dr{snapshots=new Map;history=new Map;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===bt?Nt:e.type===wt?zt:e.type===St?Ot:void 0;if(!t)return!1;const s=t.safeParse(e.payload);return s.success?(e.type===bt&&this.applySnapshot(s.data),e.type===wt&&this.applyPatch(s.data),e.type===St&&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){return this.accept({type:bt,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const s=[...t.snapshot.sessions],n=s.findIndex(t=>t.id===e.session.id);return n<0?s.push(e.session):s[n]={...s[n],...e.session},this.accept({type:wt,payload:e},xt(s,e.meta.generatedAt))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const s=t.snapshot.sessions.filter(t=>t.id!==e.sessionId);return e.tombstone&&s.push(e.tombstone),this.accept({type:St,payload:e},xt(s,e.meta.generatedAt))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view({includeTombstones:e.includeTombstones})}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const s=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>s)return Dt.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:!0,events:[]});if(e.sinceRevision===s)return Dt.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:!1,events:[]});const n=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!n.length||n[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return Dt.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:s,syncRequired:i,events:i?[]:n.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:s}=e.payload,n=this.snapshots.get(s.instanceId);return n&&n.revision>=s.snapshotRevision?(this.logger?.warn?.({traceId:s.traceId,instanceId:s.instanceId,currentRevision:n.revision,snapshotRevision:s.snapshotRevision},"app-session.aggregator.event.stale"),!1):(this.snapshots.set(s.instanceId,{snapshot:t,revision:s.snapshotRevision,lastEventAt:s.generatedAt}),this.rememberEvent(s.instanceId,e),this.logger?.info?.({traceId:s.traceId,instanceId:s.instanceId,snapshotRevision:s.snapshotRevision,eventSeq:s.eventSeq,reason:s.reason,sessionCount:t.sessions.length,runningCount:t.runningCount,problemCount:t.problemCount},"app-session.aggregator.event.accepted"),!0)}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"app-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"app-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.appSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const s=this.history.get(e)??[];s.push({...t,createdAtMs:Date.now()}),this.history.set(e,s),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 xt(e.sessions.filter(e=>Gs("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,appSessions:e.includeTombstones?s.snapshot:this.visibleSnapshot(s.snapshot),revision:s.revision,lastEventAt:s.lastEventAt}))}}}function cr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function lr(e){const t=process.env[e]?.trim();return t||void 0}function pr(){try{const e=s.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function ur(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:pr(),protocolVersion:le,buildId:lr("TASK_HANDOFF_BUILD_ID"),builtAt:lr("TASK_HANDOFF_BUILT_AT"),gitCommit:lr("TASK_HANDOFF_GIT_COMMIT"),imageRef:lr("TASK_HANDOFF_IMAGE_REF"),imageDigest:lr("TASK_HANDOFF_IMAGE_DIGEST")}}const gr={".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 hr(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 mr(e,n,i){const a=hr(n,i);return a?(e.header("cache-control","no-store"),e.type(gr[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 fr(e){if(e instanceof a.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 Ir(e,t){if(!e.enabled())return{type:"system",reason:"auth-disabled"};const s=await e.userForRequest(t);return s?{type:"user",userId:s.id,role:s.role}:void 0}function yr(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 br(e={}){const a=Ke(e.dataDir),r=n({logger:e.logger??!0,bodyLimit:67108864}),o=new jn(a,{...e.service,logger:r.log}),d=new yt(a,e.auth),c=new da,l=new rr({bootstrap:()=>o.bootstrapAiSessionsFromInstances(),logger:r.log}),p=new dr({bootstrap:()=>o.bootstrapAppSessionsFromInstances(),logger:r.log});o.setAppSessionSnapshotProvider(e=>p.list(e)),o.setAiSessionSnapshotProvider(e=>l.list(e)),c.on(e=>{p.handleEvent(e),l.handleEvent(e)});const u=new ua,g=new Aa(c);o.setNodeAgentTransport(g),o.init(),await o.syncLocalNodeConnection().catch(()=>{}),d.init();const h=new zi(o,e.service?.fetchImpl,{aiSessions:l,logger:r.log});h.startEnabled();const m=new va(o,g);m.start(),await r.register(i),r.addHook("onClose",async()=>{m.stop(),h.stopAll()});const f=(I=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(I||"").trim().toLowerCase()));var I;r.setErrorHandler((e,t,s)=>{const n=fr(e);(f||n.statusCode>=500)&&(n.statusCode>=500?r.log.error.bind(r.log):r.log.warn.bind(r.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}})}),r.addHook("preHandler",async(e,t)=>{if(!d.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 Ir(d,e.headers),s=yr(e.method,e.url);return void(s&&Na(t||{type:"system",reason:"auth-disabled"},s.action,s.resource))}const s=await Ir(d,e.headers);if(!s)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const n=yr(e.method,e.url);n&&Na(s,n.action,n.resource)});const y=s.resolve(e.staticDir||process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||s.resolve(process.cwd(),"packages","control-plane-ui","dist"));return t.existsSync(y)&&(r.get("/assets/*",async(e,t)=>{const n=e.params["*"];return mr(t,y,s.join("assets",n||""))}),r.get("/favicon.ico",async(e,t)=>hr(y,"favicon.ico")?mr(t,y,"favicon.ico"):t.code(204).send())),r.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:le,build:ur(),dataDir:a.dataDir,serverTime:(new Date).toISOString()}})),r.get("/install-node-agent.sh",async(e,t)=>(t.header("cache-control","no-store"),t.type("text/x-shellscript; charset=utf-8"),cr`#!/bin/sh
|
|
1
|
+
"use strict";var e=require("commander"),t=require("node:fs"),n=require("node:path"),s=require("fastify"),i=require("@fastify/websocket"),a=require("zod"),r=require("node:crypto"),o=require("node:util"),d=require("node:os"),c=require("ws"),l=require("node:http"),p=require("node:net"),u=require("dingtalk-stream"),g=require("telegramify-markdown"),h=require("node:fs/promises");const m=20971520,f=41943040,I="ai-session.snapshot",y="ai-session.patch",b="ai-session.removed",w=a.z.string().trim().min(1).max(80),S=a.z.enum(["running","waiting","idle","failed"]),A=a.z.enum(["thinking","tool","editing","approval","responding","unknown"]),v=a.z.object({name:a.z.string().trim().min(1).max(120),inputPreview:a.z.string().trim().max(500).optional(),startedAt:a.z.string().datetime().optional()}).strict(),k=a.z.object({toolCalls:a.z.number().int().min(0).default(0),edits:a.z.number().int().min(0).default(0),approvals:a.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),T=a.z.object({send:a.z.boolean().optional(),interrupt:a.z.boolean().optional(),approval:a.z.boolean().optional()}).strict(),E=a.z.object({id:a.z.string().trim().min(1).max(120),kind:a.z.enum(["image"]),name:a.z.string().trim().min(1).max(240),mime:a.z.string().trim().min(1).max(120),size:a.z.number().int().min(0).max(m),data:a.z.string().min(1).max(31457280)}).strict(),_=E.omit({data:!0}),C=a.z.object({id:a.z.string().trim().min(1).max(120),kind:a.z.enum(["image"]).optional()}).strict(),N=a.z.enum(["auto","queue","steer","immediate"]),z=a.z.object({message:a.z.string().trim().min(1).max(2e4),mode:N.optional()});z.extend({attachments:a.z.array(E).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>f&&t.addIssue({code:a.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})});const O=z.extend({attachments:a.z.array(C).max(6).default([])}).strict(),x=a.z.object({decision:a.z.enum(["allow","deny","skip"])}).strict();a.z.object({queueIds:a.z.array(a.z.string().trim().min(1).max(120)).max(100)}).strict(),a.z.object({code:a.z.string().trim().min(1).max(160),message:a.z.string(),instanceId:a.z.string().trim().min(1).max(160).optional(),nodeId:a.z.string().trim().min(1).max(160).optional(),route:a.z.string().trim().min(1).max(4096).optional()}).strict();const D=a.z.object({id:a.z.string().trim().min(1).max(120),message:a.z.string().trim().min(1).max(2e4),attachments:a.z.array(_).max(6).default([]),status:a.z.enum(["queued","sending","failed"]).default("queued"),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime(),error:a.z.string().trim().max(4e3).optional()}).strict(),R=a.z.object({pendingCount:a.z.number().int().min(0).default(0),items:a.z.array(D).max(100).default([])}).strict().default({pendingCount:0,items:[]}),j=a.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),P=a.z.object({id:a.z.string().trim().min(1).max(240),providerTurnId:a.z.string().trim().max(240).optional(),source:j.optional(),userPrompt:a.z.string().trim().optional(),status:a.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),revision:a.z.number().int().min(0).default(0),sourcePriority:a.z.number().int().min(0).max(100).optional(),snapshotVersion:a.z.number().int().min(0).optional(),observedAt:a.z.string().datetime().optional(),startedAt:a.z.string().datetime().optional(),updatedAt:a.z.string().datetime().optional(),completedAt:a.z.string().datetime().optional()}).strict(),$=a.z.object({id:a.z.string().trim().min(1).max(120),agent:w,appSessionId:a.z.string().trim().max(120).optional(),appId:a.z.string().trim().max(120).optional(),providerSessionId:a.z.string().trim().max(240).optional(),providerMeta:a.z.record(a.z.string(),a.z.unknown()).optional(),appBindingKeys:a.z.array(a.z.string().trim().min(1).max(240)).max(20).optional(),actions:T.optional(),activeTurnId:a.z.string().trim().max(240).optional(),conversationId:a.z.number().int().positive().optional(),title:a.z.string().trim().max(240).optional(),cwd:a.z.string().trim().max(4096).optional(),userPrompt:a.z.string().trim().optional(),turns:a.z.array(P).max(50).optional(),status:S.default("running"),phase:A.default("unknown"),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),currentTool:v.optional(),transcriptPath:a.z.string().trim().max(4096).optional(),transcriptSize:a.z.number().int().min(0).optional(),startedAt:a.z.string().datetime(),updatedAt:a.z.string().datetime(),completedAt:a.z.string().datetime().optional(),error:a.z.string().trim().max(4e3).optional(),counters:k,queue:R}).strict(),M=a.z.object({session:$,provider:a.z.string().trim().min(1).max(80),action:a.z.enum(["send","queue","steer","interrupt","approval"]),decision:a.z.enum(["allow","deny","skip"]).optional(),turnId:a.z.string().trim().min(1).max(240).optional(),providerTurnId:a.z.string().trim().min(1).max(240).optional(),queueId:a.z.string().trim().min(1).max(120).optional()}).strict(),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}),L=a.z.object({runningCount:a.z.number().int().min(0).default(0),waitingCount:a.z.number().int().min(0).default(0),staleCount:a.z.number().int().min(0).default(0),sessions:a.z.array(q).default([]),updatedAt:a.z.string().datetime()}).strict(),U=a.z.object({revision:a.z.number().int().min(0),lastEventAt:a.z.string().datetime(),snapshot:L}).strict(),B=L.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),revision:a.z.number().int().min(0).optional()});function F(e,t={}){const n=e.sessions.filter(e=>"idle"===e.status).length,s=e.sessions.filter(e=>"failed"===e.status).length;return B.parse({runningCount:e.runningCount,waitingCount:e.waitingCount,staleCount:e.staleCount,idleCount:n,problemCount:s,updatedAt:e.updatedAt,revision:t.revision})}const H=a.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),G=a.z.object({instanceId:a.z.string().trim().min(1).max(160),nodeId:a.z.string().trim().min(1).max(160).optional(),snapshotRevision:a.z.number().int().min(0),previousRevision:a.z.number().int().min(0).optional(),eventSeq:a.z.number().int().min(1),traceId:a.z.string().trim().min(1).max(160),generatedAt:a.z.string().datetime(),reason:H}).strict(),W=a.z.object({meta:G,snapshot:L}).strict(),V=a.z.object({meta:G,upserted:a.z.array(q).default([]),removed:a.z.array(a.z.string().trim().min(1).max(120)).default([])}).strict(),K=a.z.object({meta:G,sessionIds:a.z.array(a.z.string().trim().min(1).max(120)).default([]),expiresAt:a.z.string().datetime()}).strict(),J=a.z.object({instanceId:a.z.string().trim().min(1).max(160),sinceRevision:a.z.number().int().min(0),latestRevision:a.z.number().int().min(0),syncRequired:a.z.boolean().default(!1),events:a.z.array(a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal(I),payload:W}).strict(),a.z.object({type:a.z.literal(y),payload:V}).strict(),a.z.object({type:a.z.literal(b),payload:K}).strict()])).default([])}).strict(),Q=a.z.object({source:j,sourcePriority:a.z.number().int().min(0).max(100).optional(),observedAt:a.z.string().datetime().optional(),snapshotVersion:a.z.number().int().min(0).optional()}).strict(),Y=Q.extend({type:a.z.literal("snapshot"),agent:a.z.string().trim().min(1).max(80),appSessionId:a.z.string().trim().max(120).optional(),appId:a.z.string().trim().max(120).optional(),providerSessionId:a.z.string().trim().max(240).optional(),providerMeta:a.z.record(a.z.string(),a.z.unknown()).optional(),appBindingKeys:a.z.array(a.z.string().trim().min(1).max(240)).max(20).optional(),actions:T.optional(),conversationId:a.z.number().int().positive().optional(),title:a.z.string().trim().max(240).optional(),cwd:a.z.string().trim().max(4096).optional(),activeTurnId:a.z.string().trim().max(240).optional(),userPrompt:a.z.string().trim().optional(),turns:a.z.array(P).max(50).optional(),status:S.optional(),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),lastMessage:a.z.string().trim().optional(),transcriptPath:a.z.string().trim().max(4096).optional(),transcriptSize:a.z.number().int().min(0).optional(),replaceActivity:a.z.boolean().optional()}).strict(),X=Q.extend({type:a.z.literal("event"),sessionId:a.z.string().trim().min(1).max(120),kind:a.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:a.z.string().trim().max(240).optional(),providerTurnId:a.z.string().trim().max(240).optional(),userPrompt:a.z.string().trim().optional(),text:a.z.string().optional(),status:S.optional(),phase:A.optional(),summary:a.z.string().trim().max(1e3).optional(),error:a.z.string().trim().max(4e3).optional(),counters:a.z.object({toolCalls:a.z.number().int().min(0).optional(),edits:a.z.number().int().min(0).optional(),approvals:a.z.number().int().min(0).optional()}).strict().optional()}).strict();a.z.discriminatedUnion("type",[Y,X]);const Z=a.z.union([a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("interval").default("interval"),intervalMs:a.z.number().int().min(1e3)}).strict(),a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("daily"),timeOfDay:a.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("schedule"),scheduleKind:a.z.literal("weekly"),weekdays:a.z.array(a.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:a.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:a.z.string().trim().min(1).max(120)}).strict()]),ee=a.z.union([Z,a.z.object({type:a.z.literal("file-change"),roots:a.z.array(a.z.string().trim().min(1).max(4096)).min(1).max(50),globs:a.z.array(a.z.string().trim().min(1).max(500)).min(1).max(100),ignore:a.z.array(a.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:a.z.number().int().min(100).max(6e4).default(1500)}).strict(),a.z.object({type:a.z.literal("ai-session"),conversationId:a.z.number().int().positive().optional(),agent:a.z.string().trim().min(1).max(80).optional(),statuses:a.z.array(a.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:a.z.array(a.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),te=a.z.object({promptTemplate:a.z.string().trim().min(1).max(2e4)}).strict(),ne=a.z.object({cooldownMs:a.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:a.z.number().int().min(1).max(20).default(1),whenBusy:a.z.enum(["skip","queue"]).default("skip")}).strict(),se=a.z.object({configHash:a.z.string().trim().min(8).max(80),name:a.z.string().trim().min(1).max(160),description:a.z.string().trim().max(1e3).optional(),source:ee,action:te,policy:ne.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),ie=a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("conversation"),conversationId:a.z.number().int().positive()}).strict(),a.z.object({type:a.z.literal("ai-session"),aiSessionId:a.z.string().trim().min(1).max(160)}).strict()]),ae=a.z.object({configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),origin:a.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:a.z.boolean().default(!0),target:ie,localName:a.z.string().trim().min(1).max(160).optional(),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),re=a.z.object({configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),status:a.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:a.z.string().datetime().optional(),lastCompletedAt:a.z.string().datetime().optional(),lastSkippedAt:a.z.string().datetime().optional(),lastError:a.z.string().trim().max(4e3).optional(),runCount:a.z.number().int().min(0).default(0),skippedCount:a.z.number().int().min(0).default(0)}).strict(),oe=a.z.object({id:a.z.string().trim().min(1).max(160),configHash:a.z.string().trim().min(8).max(80),deploymentId:a.z.string().trim().min(1).max(240).optional(),instanceId:a.z.string().trim().min(1).max(160),eventType:a.z.enum(["manual","schedule","file-change","ai-session"]),status:a.z.enum(["started","completed","failed","skipped"]),target:ie,promptPreview:a.z.string().trim().max(500),eventSummary:a.z.string().trim().max(1e3).optional(),error:a.z.string().trim().max(4e3).optional(),startedAt:a.z.string().datetime(),completedAt:a.z.string().datetime().optional()}).strict(),de=a.z.object({schemaVersion:a.z.literal(1).default(1),configs:a.z.array(se).default([]),deployments:a.z.array(ae).default([]),runtime:a.z.array(re).default([]),recentRuns:a.z.array(oe).default([])}).strict();function ce(e){return Array.isArray(e)?e.map(ce):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,ce(t)])):e}const le="2026-06-23",pe=a.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),ue=a.z.string().datetime(),ge=a.z.record(a.z.string(),a.z.string()).default({}),he=a.z.record(a.z.string(),a.z.string()).default({}),me=a.z.object({codexModelId:pe.optional(),claudeModelId:pe.optional()}).strict().default({}),fe=a.z.object({component:a.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:a.z.string().trim().max(160).optional(),packageVersion:a.z.string().trim().max(80).optional(),protocolVersion:a.z.string().trim().max(80).optional(),buildId:a.z.string().trim().max(160).optional(),builtAt:a.z.string().trim().max(120).optional(),gitCommit:a.z.string().trim().max(120).optional(),imageRef:a.z.string().trim().max(512).optional(),imageDigest:a.z.string().trim().max(240).optional()}).strict(),Ie=a.z.enum(["stable","beta","alpha"]),ye=a.z.discriminatedUnion("component",[a.z.object({component:a.z.literal("node-agent")}).strict(),a.z.object({component:a.z.literal("controlled-instance"),instanceId:pe}).strict()]),be=a.z.object({target:ye,channel:Ie.default("stable")}).strict(),we=a.z.object({target:ye,source:a.z.enum(["npm","docker-registry"]),channel:Ie,currentVersion:a.z.string().trim().max(240).optional(),availableVersion:a.z.string().trim().min(1).max(240),artifactRef:a.z.string().trim().min(1).max(512).optional(),updateAvailable:a.z.boolean(),supported:a.z.boolean().default(!0),reason:a.z.string().trim().max(2048).optional(),checkedAt:ue}).strict(),Se=a.z.object({id:pe,nodeId:pe,target:ye,source:a.z.enum(["npm","docker-registry"]),channel:Ie,fromVersion:a.z.string().trim().max(240).optional(),toVersion:a.z.string().trim().min(1).max(240),artifactRef:a.z.string().trim().min(1).max(512).optional(),status:a.z.enum(["queued","updating","restarting","succeeded","failed"]),error:a.z.string().trim().max(4096).optional(),startedAt:ue.optional(),completedAt:ue.optional(),createdAt:ue,updatedAt:ue}).strict(),Ae=a.z.object({type:a.z.enum(["branch","tag","commit"]),name:a.z.string().trim().min(1).max(240).optional(),commit:a.z.string().trim().max(80).optional()}).strict(),ve=a.z.object({type:a.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:a.z.string().trim().min(1).max(120).optional()}).strict(),ke=a.z.object({depth:a.z.number().int().positive().max(1e5).optional(),submodules:a.z.boolean().default(!1),lfs:a.z.boolean().default(!1),subdirectory:a.z.string().trim().max(240).default("")}).strict();a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),url:a.z.string().trim().min(1).max(2048),provider:a.z.string().trim().max(80).optional(),ref:Ae.default({type:"branch",name:"main"}),auth:ve.default({type:"none"}),clone:ke.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:pe.optional(),modelSelection:me,labels:ge,createdAt:ue,updatedAt:ue}).strict();const Te=a.z.object({id:pe,nodeId:pe,name:a.z.string().trim().min(1).max(160),path:a.z.string().trim().min(1).max(4096),defaultImageId:pe.optional(),modelSelection:me,labels:ge,createdAt:ue,updatedAt:ue}).strict(),Ee=a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("git-repository"),repositoryId:pe.optional(),url:a.z.string().trim().min(1).max(2048),provider:a.z.string().trim().max(80).optional(),ref:Ae.default({type:"branch",name:"main"}),auth:ve.default({type:"none"}),clone:ke.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),a.z.object({type:a.z.literal("git-template"),url:a.z.string().trim().min(1).max(2048),templateId:a.z.string().trim().min(1).max(120).optional(),ref:Ae.default({type:"branch",name:"main"}),auth:ve.default({type:"none"}),clone:ke.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),a.z.object({type:a.z.literal("local-folder"),localFolderId:pe.optional(),path:a.z.string().trim().min(1).max(4096),ownerNodeId:pe.optional()}).strict()]),_e=a.z.object({mode:a.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:a.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:a.z.string().trim().max(240).optional(),readOnly:a.z.boolean().default(!1)}).strict(),Ce=a.z.enum(["codex","claude"]),Ne=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),source:Ee,defaultImageId:pe.optional(),defaultNodeId:pe.optional(),defaultRuntimeId:pe.optional(),modelSelection:me,workspacePolicy:_e,labels:ge,createdAt:ue,updatedAt:ue}).strict(),ze=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),endpoint:a.z.string().trim().min(1).max(2048),key:a.z.string().trim().min(1).max(4096),model:a.z.string().trim().min(1).max(240),app:Ce,enabled:a.z.boolean().default(!0),order:a.z.number().int().min(0).max(1e6).default(0),labels:ge,createdAt:ue,updatedAt:ue}).strict(),Oe=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),image:a.z.string().trim().min(1).max(512),registry:a.z.string().trim().min(1).max(160).default("local"),capabilities:a.z.array(a.z.string().trim().min(1).max(80)).default([]),optionalApps:a.z.array(a.z.string().trim().min(1).max(120)).default([]),defaultEnv:he,labels:ge,createdAt:ue,updatedAt:ue}).strict(),xe=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),connectionMode:a.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:a.z.object({mode:a.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:pe.optional(),secret:a.z.string().trim().max(4096).optional(),pairedAt:ue.optional(),pairing:a.z.object({status:a.z.enum(["pending","paired","expired"]).default("pending"),joinToken:a.z.string().trim().max(4096).optional(),expiresAt:ue.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:a.z.string().trim().max(2048).optional(),controlEndpoint:a.z.string().trim().max(2048).optional(),containerEndpoint:a.z.string().trim().max(2048).optional(),publicWebBase:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:a.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),labels:ge,lastSeenAt:ue.optional(),createdAt:ue,updatedAt:ue}).strict(),De=a.z.object({id:pe,nodeId:pe,name:a.z.string().trim().min(1).max(160),type:a.z.enum(["docker","kubernetes","local"]),status:a.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:a.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),labels:ge,createdAt:ue,updatedAt:ue}).strict(),Re=a.z.object({ok:a.z.boolean().optional(),role:a.z.string().optional(),nodeId:pe.optional()}).passthrough(),je=a.z.object({nodeId:pe,joinToken:a.z.string().trim().min(1),expiresAt:ue}).passthrough(),Pe=a.z.object({id:pe,keyId:pe,url:a.z.string().trim().min(1).max(2048).optional(),name:a.z.string().trim().min(1).max(160).optional(),pairedAt:ue,updatedAt:ue.optional(),active:a.z.boolean().optional(),current:a.z.boolean().optional()}).passthrough(),$e=a.z.object({remote:a.z.object({id:pe,url:a.z.string().trim().min(1).max(2048).optional(),keyId:pe,pairedAt:ue,active:a.z.boolean()}).passthrough(),tunnel:a.z.object({status:a.z.enum(["disabled","saved","connecting","connected","failed"]),error:a.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),Me=a.z.object({deleted:a.z.boolean().optional()}).passthrough(),qe=a.z.object({repository:a.z.string().trim().optional(),tag:a.z.string().trim().optional(),id:a.z.string().trim(),createdSince:a.z.string().trim().optional(),size:a.z.string().trim().optional(),reference:a.z.string().trim().min(1)}).passthrough(),Le=a.z.lazy(()=>a.z.object({name:a.z.string().trim().min(1),path:a.z.string().trim().min(1).max(4096),children:a.z.array(Le)}).passthrough()),Ue=a.z.object({status:a.z.number().int().min(100).max(599).optional(),headers:a.z.record(a.z.string(),a.z.string()).optional(),bodyBase64:a.z.string().optional()}).passthrough(),Be=a.z.object({strategy:a.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:a.z.string().trim().max(2048).optional(),api:a.z.string().trim().max(2048).optional(),vnc:a.z.string().trim().max(2048).optional(),tty:a.z.string().trim().max(2048).optional(),logs:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),Fe=a.z.object({strategy:a.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:a.z.string().trim().max(2048).optional(),api:a.z.string().trim().max(2048).optional(),ws:a.z.string().trim().max(2048).optional(),status:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),He=a.z.object({mode:a.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:a.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:a.z.string().trim().max(4096).optional(),resolvedCommit:a.z.string().trim().max(120).optional(),error:a.z.string().trim().max(2048).optional()}).passthrough(),Ge=a.z.object({id:pe,name:a.z.string().trim().min(1).max(160),projectId:pe.optional(),source:Ee,sourceSnapshot:a.z.record(a.z.string(),a.z.unknown()).default({}),nodeId:pe,runtimeId:pe,imageId:pe.optional(),imageSnapshot:Oe.optional(),status:a.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:a.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:a.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:a.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:a.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:a.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:a.z.string().trim().max(80).optional(),instanceVersion:a.z.string().trim().max(80).optional(),build:fe.optional(),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),config:a.z.object({autoImportAgentConfigs:a.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:He.default({status:"unknown"}),target:Be.default({strategy:"direct-port",status:"unknown"}),access:Fe,receiver:a.z.object({status:a.z.string().trim().max(80).default("unknown"),pendingCount:a.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:a.z.object({runningCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),updatedAt:ue.optional(),revision:a.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:L.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:a.z.object({enabledCount:a.z.number().int().min(0).default(0),runningCount:a.z.number().int().min(0).default(0),errorCount:a.z.number().int().min(0).default(0),configs:a.z.array(a.z.object({configHash:a.z.string().trim().min(1),config:se,deployments:a.z.array(ae).default([]),runtime:a.z.array(re).default([])}).strict()).default([]),recentRuns:a.z.array(oe).default([])}).strict().optional(),runtime:a.z.object({kind:a.z.string().trim().max(80).optional(),containerName:a.z.string().trim().max(240).optional(),containerId:a.z.string().trim().max(240).optional(),workspacePath:a.z.string().trim().max(4096).optional(),pid:a.z.number().int().positive().optional(),port:a.z.number().int().positive().max(65535).optional(),labels:a.z.record(a.z.string(),a.z.string()).default({})}).strict().default({labels:{}}),registrationToken:a.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:ue.optional(),createdAt:ue,updatedAt:ue}).strict();function We(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return e;const n=e;return Object.fromEntries(t.filter(e=>Object.prototype.hasOwnProperty.call(n,e)).map(e=>[e,n[e]]))}a.z.object({instanceId:pe.optional(),name:a.z.string().trim().min(1).max(160),projectId:pe.optional(),source:Ee.optional(),nodeId:pe.optional(),runtimeId:pe.optional(),imageId:pe.optional(),instanceVersion:a.z.string().trim().max(80).optional(),protocolVersion:a.z.string().trim().max(80),build:fe.optional(),controlMode:a.z.enum(["standalone","controlled"]).default("controlled"),capabilities:a.z.record(a.z.string(),a.z.unknown()).default({}),target:Be.default({strategy:"direct-port",status:"unknown"}),workspace:He.default({status:"unknown"}),registrationToken:a.z.string().trim().max(240).optional()}).strict(),a.z.object({status:Ge.shape.status.optional(),health:Ge.shape.health.optional(),protocolVersion:a.z.string().trim().max(80),build:fe.optional(),capabilities:Ge.shape.capabilities.optional(),receiver:Ge.shape.receiver.optional(),apps:Ge.shape.apps.optional(),aiSessions:Ge.shape.aiSessions.optional(),triggers:Ge.shape.triggers.optional(),workspace:He.optional(),target:Be.partial().optional()}).strict();const Ve=a.z.enum(["web","telegram","wechat","dingding"]),Ke=a.z.object({id:pe,channel:Ve,name:a.z.string().trim().min(1).max(160),enabled:a.z.boolean().default(!1),token:a.z.string().trim().max(4096).optional(),defaultChatId:a.z.string().trim().max(240).optional(),allowedUserIds:a.z.array(a.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:a.z.number().int().positive().max(6e4).default(3e3),settings:a.z.record(a.z.string(),a.z.unknown()).default({}),createdAt:ue,updatedAt:ue}).strict(),Je=a.z.object({id:pe,channel:Ve,bridgeId:pe.optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional(),activeProjectId:pe.optional(),activeInstanceId:pe.optional(),activeAiSessionId:a.z.string().trim().min(1).max(120).optional(),lastUsedAt:ue,createdAt:ue,updatedAt:ue}).strict(),Qe=a.z.object({source:a.z.object({type:a.z.literal("chat-gateway").default("chat-gateway"),channel:Ve,bridgeId:pe.optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional()}).strict(),message:a.z.object({text:a.z.string().trim().max(2e4).default(""),attachments:a.z.array(E).max(6).default([])}).strict().superRefine((e,t)=>{e.attachments.reduce((e,t)=>e+t.size,0)>f&&t.addIssue({code:a.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:a.z.object({instanceId:pe,aiSessionId:a.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),Ye=a.z.object({id:pe,instanceId:pe,projectId:pe.optional(),aiSessionId:a.z.string().trim().min(1).max(120),providerSessionId:a.z.string().trim().max(240).optional(),result:a.z.string().default(""),timeoutMs:a.z.number().positive().optional(),source:a.z.string().trim().max(120).optional(),kind:a.z.enum(["task","approval"]).default("task"),status:a.z.enum(["pending","unreachable"]).default("pending"),error:a.z.string().trim().max(2048).optional(),lastSeenAt:ue}).strict();function Xe(e=function(){return process.env.TASK_HANDOFF_CONTROL_PLANE_DATA_DIR||n.join(d.homedir(),".config","task-handoff","control-plane")}()){const t=n.resolve(e);return{dataDir:t,settingsPath:n.join(t,"control-plane-settings.json"),projectsDir:n.join(t,"projects"),modelsDir:n.join(t,"models"),imagesDir:n.join(t,"images"),nodesDir:n.join(t,"nodes"),nodeRuntimesDir:n.join(t,"node-runtimes"),controlledInstancesDir:n.join(t,"controlled-instances"),chatSessionsDir:n.join(t,"chat-sessions"),chatBridgesDir:n.join(t,"chat-bridges"),triggersDir:n.join(t,"triggers"),nodeJoinInvitesDir:n.join(t,"node-join-invites"),authUsersDir:n.join(t,"auth-users"),authSessionsDir:n.join(t,"auth-sessions"),logsDir:n.join(t,"logs")}}function Ze(e){t.mkdirSync(e,{recursive:!0})}function et(e,s){Ze(n.dirname(e));const i=`${e}.${process.pid}.${Date.now()}.tmp`;t.writeFileSync(i,`${JSON.stringify(s,null,2)}\n`,"utf8"),t.renameSync(i,e)}function tt(e,t){console.warn(JSON.stringify({message:e,...t}))}function nt(e){return e&&"object"==typeof e&&"issues"in e?{issues:e.issues}:{error:e instanceof Error?e.message:String(e)}}function st(e,n){const s=n.logger||tt;let i;try{i=JSON.parse(t.readFileSync(e,"utf8"))}catch(t){return void s("stored JSON could not be read",{filePath:e,...nt(t)})}if(!n.schema)return i;const a=n.sanitize?n.sanitize(i):i,r=n.schema.safeParse(a);if(r.success)return r.data;const o="strip"in n.schema&&"function"==typeof n.schema.strip?n.schema.strip().safeParse(a):void 0;if(o?.success)return s("unknown stored fields were ignored",{filePath:e,issues:r.error.issues}),o.data;s("stored record failed validation and was isolated",{filePath:e,issues:r.error.issues})}function it(e){return`${e}_${r.randomUUID().replace(/-/g,"").slice(0,20)}`}function at(){return r.randomBytes(24).toString("base64url")}class rt{directory;options;constructor(e,t={}){this.directory=e,this.options=t}init(){Ze(this.directory)}filePath(e){return n.join(this.directory,`${e}.json`)}list(){return this.init(),t.readdirSync(this.directory).filter(e=>e.endsWith(".json")).sort().flatMap(e=>{const t=st(n.join(this.directory,e),this.options);return t?[t]:[]})}get(e){const n=this.filePath(e);return t.existsSync(n)?st(n,this.options):void 0}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,n=this.options.schema?this.options.schema.parse(t):t;return et(this.filePath(n.id),n),n}patch(e,t){const n=this.get(e);if(!n)return;const s={...n,...t,id:n.id,createdAt:n.createdAt,updatedAt:(new Date).toISOString()};return this.put(s)}delete(e){const n=this.filePath(e);return!!t.existsSync(n)&&(t.unlinkSync(n),!0)}}class ot{filePathValue;defaults;options;constructor(e,t,n={}){this.filePathValue=e,this.defaults=t,this.options=n}init(){Ze(n.dirname(this.filePathValue)),t.existsSync(this.filePathValue)||this.put(this.defaults())}get(){return this.init(),t.existsSync(this.filePathValue)?st(this.filePathValue,this.options)??this.defaults():this.defaults()}put(e){const t=this.options.sanitize?this.options.sanitize(e):e,n=this.options.schema?this.options.schema.parse(t):t;return et(this.filePathValue,n),n}}const dt=o.promisify(r.scrypt),ct="task_handoff_cp_session",lt=a.z.enum(["disabled","password"]),pt=a.z.string().trim().min(1).max(80).regex(/^[a-zA-Z0-9_.@-]+$/),ut=a.z.string().min(8).max(4096),gt=a.z.object({username:pt,password:ut}).strict(),ht=a.z.object({username:a.z.string().trim().min(1).max(80),password:a.z.string().min(1).max(4096)}).strict(),mt=a.z.object({id:a.z.string().trim().min(1),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}),ft=mt.extend({username:pt,passwordHash:a.z.string().trim().min(1),role:a.z.enum(["viewer","operator","admin"]),lastLoginAt:a.z.string().datetime().optional()}).strict(),It=mt.extend({userId:a.z.string().trim().min(1),tokenHash:a.z.string().trim().min(1),expiresAt:a.z.string().datetime(),lastSeenAt:a.z.string().datetime().optional()}).strict();function yt(){return(new Date).toISOString()}function bt(e){return r.createHash("sha256").update(e).digest("base64url")}async function wt(e){const t=r.randomBytes(16).toString("base64url");return`scrypt$${t}$${(await dt(e,t,64)).toString("base64url")}`}function St(e){return{id:e.id,username:e.username,role:e.role,createdAt:e.createdAt,updatedAt:e.updatedAt,lastLoginAt:e.lastLoginAt}}function At(e,t){return`${ct}=${encodeURIComponent(e)}; Path=/; HttpOnly; SameSite=Lax; Expires=${new Date(t).toUTCString()}`}class vt{mode;users;sessions;constructor(e,t={}){this.mode=function(e={}){return lt.parse(e.mode||process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"disabled")}(t),this.users=new rt(e.authUsersDir,{schema:ft}),this.sessions=new rt(e.authSessionsDir,{schema:It})}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=gt.parse(e),n=yt();return St(this.users.put({id:it("user"),username:t.username,passwordHash:await wt(t.password),role:"admin",createdAt:n,updatedAt:n}))}async login(e){if(!this.enabled()){const e=new Error("Control Plane authentication is disabled.");throw Object.assign(e,{statusCode:400,code:"AUTH_DISABLED"}),e}const t=ht.parse(e),n=this.users.list().find(e=>e.username===t.username);if(!n||!await async function(e,t){const[n,s,i]=t.split("$");if("scrypt"!==n||!s||!i)return!1;const a=await dt(e,s,64),o=Buffer.from(i,"base64url");return o.length===a.length&&r.timingSafeEqual(o,a)}(t.password,n.passwordHash)){const e=new Error("Invalid username or password.");throw Object.assign(e,{statusCode:401,code:"AUTH_LOGIN_FAILED"}),e}const s=yt(),i=at(),a=new Date(Date.now()+12096e5).toISOString(),o=this.sessions.put({id:it("sess"),userId:n.id,tokenHash:bt(i),expiresAt:a,createdAt:s,updatedAt:s,lastSeenAt:s});return this.users.put({...n,lastLoginAt:s,updatedAt:s}),{user:St({...n,lastLoginAt:s,updatedAt:s}),session:o,cookie:At(`${o.id}.${i}`,a)}}sessionToken(e){return function(e){const t=(e||"").split(";").map(e=>e.trim()).filter(Boolean).map(e=>{const t=e.indexOf("=");return t>=0?[e.slice(0,t),decodeURIComponent(e.slice(t+1))]:[e,""]});return Object.fromEntries(t)}("string"==typeof e.cookie?e.cookie:void 0)[ct]}async userForRequest(e){if(!this.enabled())return;const t=this.sessionToken(e);if(!t)return;const[n,s]=t.split(".");if(!n||!s)return;const i=this.sessions.get(n);if(!i||i.expiresAt<=yt()||i.tokenHash!==bt(s))return;const a=this.users.get(i.userId);return a?(this.sessions.put({...i,lastSeenAt:yt(),updatedAt:yt()}),St(a)):void 0}async currentSession(e){const t=this.state(),n=await this.userForRequest(e);return{...t,authenticated:!this.enabled()||Boolean(n),user:n}}logout(e){const t=this.sessionToken(e),n=t?.split(".")[0];return n&&this.sessions.delete(n),{ok:!0}}}const kt="app-session.snapshot",Tt="app-session.patch",Et="app-session.removed",_t=a.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Ct=a.z.object({type:a.z.enum(["app-session","provider-session","adapter-key"]),id:a.z.string().trim().min(1).max(240),agent:a.z.string().trim().min(1).max(80).optional(),adapter:a.z.string().trim().min(1).max(80).optional(),key:a.z.string().trim().min(1).max(240).optional()}).strict(),Nt=a.z.record(a.z.string(),a.z.unknown()).and(a.z.object({id:a.z.string().trim().min(1).max(160),appId:a.z.string().trim().min(1).max(160).optional(),title:a.z.string().trim().max(240).optional(),kind:a.z.string().trim().max(80).optional(),status:_t.default("unknown"),bindings:a.z.array(Ct).max(40).default([]),workspace:a.z.object({cwd:a.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:a.z.string().datetime().optional(),updatedAt:a.z.string().datetime().optional()})),zt=a.z.object({runningCount:a.z.number().int().min(0).default(0),problemCount:a.z.number().int().min(0).default(0),sessions:a.z.array(Nt).default([]),updatedAt:a.z.string().datetime()}).strict(),Ot=a.z.object({revision:a.z.number().int().min(0),lastEventAt:a.z.string().datetime(),snapshot:zt}).strict();zt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:a.z.number().int().min(0).optional()});const xt=a.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Dt=a.z.object({instanceId:a.z.string().trim().min(1).max(160),nodeId:a.z.string().trim().min(1).max(160).optional(),snapshotRevision:a.z.number().int().min(0),previousRevision:a.z.number().int().min(0).optional(),eventSeq:a.z.number().int().min(1),traceId:a.z.string().trim().min(1).max(160),generatedAt:a.z.string().datetime(),reason:xt}).strict(),Rt=a.z.object({meta:Dt,snapshot:zt}).strict(),jt=a.z.object({meta:Dt,session:Nt}).strict(),Pt=a.z.object({meta:Dt,sessionId:a.z.string().trim().min(1).max(160),tombstone:Nt.optional(),expiresAt:a.z.string().datetime().optional()}).strict(),$t=a.z.object({instanceId:a.z.string().trim().min(1).max(160),sinceRevision:a.z.number().int().min(0),latestRevision:a.z.number().int().min(0),syncRequired:a.z.boolean().default(!1),events:a.z.array(a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal(kt),payload:Rt}).strict(),a.z.object({type:a.z.literal(Tt),payload:jt}).strict(),a.z.object({type:a.z.literal(Et),payload:Pt}).strict()])).default([])}).strict();function Mt(e,t=(new Date).toISOString()){const n=e.map(e=>qt(e));return zt.parse({runningCount:n.filter(e=>"running"===e.status).length,problemCount:n.filter(e=>"failed"===e.status).length,sessions:n,updatedAt:t})}function qt(e){const t=Lt("string"==typeof e.status?e.status:void 0),n="string"==typeof e.id?e.id:"",s=function(e){const t=Gt(e.workspace),n=Gt(e.tty),s=Gt(e.launch),i=Gt(e.ai),a=Gt(i.claude),r=Wt(t.cwd)||Wt(n.cwd)||Wt(a.cwd)||Wt(s.cwd);return r?{cwd:r}:void 0}(e),{workspace:i,...a}=e;return Nt.parse({...a,status:t,bindings:Ht([...(r=e.bindings,Array.isArray(r)?r.flatMap(e=>{const t=Ct.safeParse(e);return t.success?[t.data]:[]}):[]),...n?[{type:"app-session",id:n}]:[],...Bt(e)]),...s?{workspace:s}:{}});var r}function Lt(e){const t=(e||"unknown").trim().toLowerCase();return"ended"===t?"closed":"terminating"===t?"stopping":_t.options.includes(t)?t:"unknown"}function Ut(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 Bt(e){const t=Gt(e.ai),n=Gt(t.claude),s=Gt(t.appServer),i=[],a=Wt(n.short);a&&i.push({type:"adapter-key",adapter:"claude",agent:"claude",id:`short:${a}`,key:`short:${a}`});const r=Wt(n.providerSessionId);r&&i.push({type:"provider-session",agent:"claude",id:r});const o=Array.isArray(t.threadIds)?t.threadIds.filter(e=>"string"==typeof e&&Boolean(e.trim())):[],d=Wt(t.activeThreadId);for(const e of[...new Set([d,...o].filter(Boolean))])i.push({type:"provider-session",agent:"codex",id:e});const c=Wt(s.socketPath);return c&&i.push({type:"adapter-key",adapter:"codex-app-server",agent:"codex",id:`socket:${c}`,key:`socket:${c}`}),i}function Ft(e){const t="string"==typeof e.id?e.id:"",n=Gt(e.ai),s=Wt(Gt(n.claude).short);return[t?`app:${t}`:"",s?`claude-short:${s}`:""]}function Ht(e){const t=new Map;for(const n of e)t.set(Ut(n),n);return[...t.values()]}function Gt(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Wt(e){return"string"==typeof e&&e.trim()?e.trim():""}function Vt(e=(new Date).toISOString()){return zt.parse({runningCount:0,problemCount:0,sessions:[],updatedAt:e})}function Kt(e,t,n,s){const i=s&&"object"==typeof s?s:{};return{nodeId:e.id,route:"string"==typeof i.route?i.route:t,method:n,code:"string"==typeof i.code?i.code:"NODE_AGENT_REQUEST_FAILED",message:s instanceof Error?s.message:String(s),statusCode:"number"==typeof i.statusCode?i.statusCode:void 0,issues:Array.isArray(i.issues)?i.issues:void 0}}class Jt{requestImpl;logger;constructor(e){this.requestImpl=e.request,this.logger=e.logger}async request(e,t,n={}){const s=n.method||"GET";let i,a;try{i=await this.requestImpl(e,t,n)}catch(n){throw this.logger?.warn?.({nodeId:e.id,route:t,method:s,error:n instanceof Error?n.message:String(n),errorCode:"NODE_AGENT_TRANSPORT_FAILED"},"node agent transport failed"),n}try{a=await i.json()}catch(n){this.logger?.warn?.({nodeId:e.id,route:t,method:s,statusCode:i.status,error:n instanceof Error?n.message:String(n),errorCode:"NODE_AGENT_RESPONSE_INVALID_JSON"},"node agent response was not valid json");const a=new Error(`Node agent ${e.id} returned invalid JSON for ${t}.`);throw Object.assign(a,{statusCode:502,code:"NODE_AGENT_RESPONSE_INVALID_JSON",nodeId:e.id,route:t}),a}if(!i.ok){const n=new Error(a.error?.message||`Node agent request failed with HTTP ${i.status}`);throw Object.assign(n,{statusCode:i.status,code:a.error?.code||"NODE_AGENT_REQUEST_FAILED",nodeId:e.id,route:t}),this.logger?.warn?.({nodeId:e.id,route:t,method:s,statusCode:i.status,errorCode:a.error?.code||"NODE_AGENT_REQUEST_FAILED",error:n.message},"node agent request failed"),n}return this.logger?.info?.({nodeId:e.id,route:t,method:s,statusCode:i.status},"node agent request ok"),a.data??a}async requestSchema(e,t,n,s={}){return this.parse(e,t,n,await this.request(e,t,s),s.method||"GET")}parse(e,t,n,s,i="GET"){const a=n.safeParse(s);if(a.success)return a.data;this.logger?.warn?.({nodeId:e.id,route:t,method:i,errorCode:"NODE_AGENT_PROTOCOL_INVALID",issues:a.error.issues},"node agent protocol response did not match schema");const r=new Error(`Node agent ${e.id} returned invalid protocol data for ${t}.`);throw Object.assign(r,{statusCode:502,code:"NODE_AGENT_PROTOCOL_INVALID",nodeId:e.id,route:t,issues:a.error.issues}),r}}class Qt{client;constructor(e){this.client=e}request(e,t,n={}){return this.client.request(e,t,n)}health(e){return this.client.requestSchema(e,"/health",Re)}checkUpdate(e,t){return this.client.requestSchema(e,"/updates/check",we,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}applyUpdate(e,t){return this.client.requestSchema(e,"/updates/apply",Se,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listUpdateJobs(e){return this.client.requestSchema(e,"/updates/jobs",a.z.array(Se))}createPairingInvite(e,t={}){return this.client.requestSchema(e,"/pairing/invites",je,{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",$e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}listRemotes(e){return this.client.requestSchema(e,"/remotes",a.z.array(Pe))}deleteRemote(e,t){return this.client.requestSchema(e,`/remotes/${encodeURIComponent(t)}`,Me,{method:"DELETE"})}listDockerImages(e){return this.client.requestSchema(e,"/docker/images",a.z.array(qe))}listFolderTree(e,t={}){const n=new URLSearchParams;t.path&&n.set("path",t.path),void 0!==t.depth&&n.set("depth",String(t.depth));const s=n.toString();return this.client.requestSchema(e,"/folders/tree"+(s?`?${s}`:""),a.z.array(Le))}listLocalFolders(e){return this.client.requestSchema(e,"/local-folders",a.z.array(Te))}createLocalFolder(e,t){return this.client.requestSchema(e,"/local-folders",Te,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}async requireLocalFolder(e,t){const n=(await this.listLocalFolders(e)).find(e=>e.id===t);if(!n){const n=new Error(`Local folder ${t} was not found on node ${e.id}.`);throw Object.assign(n,{statusCode:404,code:"NODE_LOCAL_FOLDER_NOT_FOUND",nodeId:e.id,route:"/local-folders"}),n}return n}listRuntimes(e){return this.client.requestSchema(e,"/runtimes",a.z.array(De))}createRuntime(e,t){return this.client.requestSchema(e,"/runtimes",De,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateRuntime(e,t,n){const s=`/runtimes/${encodeURIComponent(t)}`;return this.client.requestSchema(e,s,De,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}checkRuntime(e,t){const n=`/runtimes/${encodeURIComponent(t)}/check`;return this.client.requestSchema(e,n,De,{method:"POST"})}deleteRuntime(e,t){return this.client.requestSchema(e,`/runtimes/${encodeURIComponent(t)}`,Me,{method:"DELETE"})}deleteLocalFolder(e,t){return this.client.requestSchema(e,`/local-folders/${encodeURIComponent(t)}`,Me,{method:"DELETE"})}async listFleetRuntimes(e){return(await Promise.allSettled(e.map(async e=>({node:e,runtimes:await this.listRuntimes(e)})))).reduce((t,n,s)=>{if("fulfilled"===n.status)t.items.push(...n.value.runtimes);else{const i=e[s];i&&t.nodeErrors.push(Kt(i,"/runtimes","GET",n.reason))}return t},{items:[],nodeErrors:[]})}listInstances(e){return this.listInstancesWithDiagnostics(e).then(e=>e.items)}async listInstancesWithDiagnostics(e){return this.client.requestSchema(e,"/instances",a.z.array(a.z.unknown())).then(t=>t.reduce((t,n)=>{const s=this.normalizeInstance(e,n);return s.instance&&t.items.push(s.instance),s.error&&t.nodeErrors.push(s.error),t},{items:[],nodeErrors:[]}))}createInstance(e,t){return this.client.requestSchema(e,"/instances",Ge,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})}updateInstance(e,t,n){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}`,Ge,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}deleteInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/delete`,Me,{method:"POST"})}startInstance(e,t,n={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/start`,Ge,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}stopInstance(e,t){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/stop`,Ge,{method:"POST"})}restartInstance(e,t,n={}){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/restart`,Ge,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}proxyRawInstance(e,t,n){return this.client.requestSchema(e,`/instances/${encodeURIComponent(t)}/proxy/raw`,Ue,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)})}async listFleetInstances(e){return(await Promise.allSettled(e.map(async e=>({node:e,result:await this.listInstancesWithDiagnostics(e)})))).reduce((t,n,s)=>("fulfilled"===n.status?(t.items.push(...n.value.result.items),t.nodeErrors.push(...n.value.result.nodeErrors)):t.nodeErrors.push(Kt(e[s],"/instances","GET",n.reason)),t),{items:[],nodeErrors:[]})}normalizeInstance(e,t){const n="/instances",s=(i=t,Ge.safeParse(function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;const t=e,n=new Set(Object.keys(Ge.shape)),s={};for(const[e,i]of Object.entries(t))n.has(e)&&(s[e]=i);return s.apps=We(t.apps,["runningCount","problemCount","updatedAt","revision"]),s.config=We(t.config,["autoImportAgentConfigs"]),s.receiver=We(t.receiver,["status","pendingCount"]),s.runtime=We(t.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),s.target=We(t.target??t.endpoints,["strategy","web","api","status"]),s.access=We(t.access,["strategy","web","api","ws","status"]),s}(i)));var i;if(s.success)return s.data.protocolVersion&&s.data.protocolVersion!==le?(this.client.logger?.warn?.({nodeId:e.id,instanceId:s.data.id,expectedProtocolVersion:le,actualProtocolVersion:s.data.protocolVersion,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node instance protocol version mismatch"),{error:{nodeId:e.id,route:n,method:"GET",code:"PROTOCOL_VERSION_MISMATCH",message:`Node instance ${s.data.id} protocol version ${s.data.protocolVersion} does not match ${le}.`,statusCode:502}}):{instance:s.data};const a=t&&"object"==typeof t?t:{},r="string"==typeof a.id?a.id:void 0;return this.client.logger?.warn?.({nodeId:e.id,instanceId:r,errorCode:"NODE_INSTANCE_PAYLOAD_INVALID",issues:s.error.issues.map(e=>({path:e.path.join("."),message:e.message}))},"node instance payload invalid"),{error:{nodeId:e.id,route:n,method:"GET",code:"NODE_INSTANCE_PAYLOAD_INVALID",message:r?`Node instance ${r} payload is invalid.`:"Node instance payload is invalid.",statusCode:502,issues:s.error.issues}}}}function Yt(){return(new Date).toISOString()}function Xt(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 Zt(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 en(e){return e instanceof Error?e.message:String(e)}function tn(e,t){const n=new Error(t);throw Object.assign(n,{statusCode:404,code:e}),n}class nn{requireNode;nodeAgentGateway;nodeAgentRequest;fetchImpl;constructor(e){this.requireNode=e.requireNode,this.nodeAgentGateway=e.nodeAgentGateway,this.nodeAgentRequest=e.nodeAgentRequest,this.fetchImpl=e.fetchImpl}async request(e,t,n={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_UNREACHABLE"}),t}const s=this.requireNode(e.nodeId),i=await this.nodeAgentRequest(s,`/instances/${encodeURIComponent(e.id)}/proxy`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({path:`/api/${t.replace(/^\/+/,"")}`,method:n.method||"GET",headers:Xt(n.headers),body:(a=n.body,"string"==typeof a?a:null==a?void 0:String(a))})});var a;const r=await i.json().catch(()=>({}));if(!i.ok){const n=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t=e,n="string"==typeof t.code&&t.code.trim()?t.code:void 0,s="string"==typeof t.message&&t.message?t.message:void 0;return n||s?{code:n,message:s}:void 0}(r.error),s=n?.message||function(e){if(!e||"object"!=typeof e||Array.isArray(e))return"";const t=e.message;return"string"==typeof t?t:""}(r.error)||`Instance request failed with HTTP ${i.status}`,a=new Error(s);throw Object.assign(a,{statusCode:i.status,code:n?.code||"INSTANCE_REQUEST_FAILED",instanceId:e.id,nodeId:e.nodeId,route:t}),a}return r.data??r}async proxyHttp(e,t,n={}){if(!e.target.web){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const s=this.requireNode(e.nodeId),i=await this.nodeAgentGateway.proxyRawInstance(s,e.id,{path:t,method:n.method||"GET",headers:Xt(n.headers),...(a=n.body,null==a?{}:"string"==typeof a?{body:a}:Buffer.isBuffer(a)?{bodyBase64:a.toString("base64")}:a instanceof Uint8Array?{bodyBase64:Buffer.from(a).toString("base64")}:{body:String(a)})});var a;return{status:i.status||502,headers:i.headers||{},body:Buffer.from(i.bodyBase64||"","base64")}}proxyWebSocket(e,t,n,s,i,a={}){if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const t=new Error(`Instance ${e.name} web endpoint is not reachable.`);throw Object.assign(t,{statusCode:409,code:"INSTANCE_WEB_UNREACHABLE"}),t}const r=this.requireNode(e.nodeId),o=s.startsWith("/")?s.slice(1):s;t.proxyWebSocket(r,n,`/instances/${encodeURIComponent(e.id)}/proxy/ws/${o}`,i,a)}async reportHeartbeat(e,t,n){if(!e.registrationToken)return;const s=this.requireNode(e.nodeId),i=`/instances/${encodeURIComponent(e.id)}/heartbeat`,a={method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.registrationToken}`},body:JSON.stringify(t)},r="reverse-wss"===s.connectionMode?await(n?.request(s,i,a)):await this.fetchImpl(`${(s.controlEndpoint||s.endpoint||"").replace(/\/$/,"")}/api/node-agent${i}`,a);if(!r)return;const o=await r.json().catch(()=>({}));if(!r.ok){const e=new Error(o.error?.message||`Instance heartbeat sync failed with HTTP ${r.status}`);throw Object.assign(e,{statusCode:r.status,code:"INSTANCE_HEARTBEAT_SYNC_FAILED"}),e}}}function sn(e){if(!e||"object"!=typeof e)return;const t=e,n=fe.safeParse(t.build);return{..."boolean"==typeof t.ok?{ok:t.ok}:{},..."string"==typeof t.role?{role:t.role}:{},..."string"==typeof t.nodeId?{nodeId:t.nodeId}:{},..."string"==typeof t.protocolVersion?{protocolVersion:t.protocolVersion}:{},...n.success?{build:n.data}:{},..."string"==typeof t.serverTime?{serverTime:t.serverTime}:{}}}function an(e){const{agent:t,...n}=e.capabilities,{secret:s,...i}=e.auth,a=i.pairing?{...i.pairing,joinToken:void 0}:void 0,r=sn(t);return{...e,auth:a?{...i,pairing:a}:i,capabilities:r?{...n,agent:r}:n}}function rn(e){return"local-folder"===e.type?{mode:"local-bind",path:"/workspace",readOnly:!1}:{mode:"git-clone",path:"/workspace",readOnly:!1}}function on(e){return function(e){const{registrationToken:t,target:n,agentStatus:s,targetStatus:i,uiAccessStatus:a,...r}=e;return r}(function(e){const t=`/instances/${encodeURIComponent(e.id)}`,n={strategy:"control-plane-proxy",web:`${t}/`,api:`${t}/api`,ws:`${t}/api`,status:"online"===e.connectionStatus||"online"===e.agentStatus?"reachable":"endpoint-unreachable"};return{...e,access:n}}(e))}function dn(e){const{key:t,...n}=e;return{...n,keyPreview:(s=e.key,s.length<=8?"set":`${s.slice(0,4)}...${s.slice(-4)}`),keySet:!0};var s}class cn{read(e){const t=new Map(e.projects.map(e=>[e.id,e])),n=new Map(e.images.map(e=>[e.id,e])),s=new Map(e.nodes.map(e=>[e.id,e])),i=new Map(e.runtimes.map(e=>[`${e.nodeId}:${e.id}`,e])),a=Date.now();return{items:e.instances.map(e=>function(e){const t=e.instance.lastHeartbeatAt?e.currentTime-Date.parse(e.instance.lastHeartbeatAt):void 0,n=void 0!==t&&t>3e4?"offline":e.instance.connectionStatus,s=on(e.instance);return{...s,aiSessions:F(s.aiSessions),connectionStatus:n,heartbeatAgeMs:t,project:e.project,image:e.image,node:e.node?an(e.node):void 0,runtime:e.runtime,protocolCompatible:!e.instance.protocolVersion||e.instance.protocolVersion===le}}({instance:e,currentTime:a,project:e.projectId?t.get(e.projectId):void 0,image:e.imageId&&n.get(e.imageId)||e.imageSnapshot,node:s.get(e.nodeId),runtime:i.get(`${e.nodeId}:${e.runtimeId}`)})),nodeErrors:e.nodeErrors||[]}}}const ln=a.z.object({mode:a.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:xe.shape.id.optional(),secret:a.z.string().trim().max(4096).optional(),pairedAt:xe.shape.createdAt.optional(),pairing:a.z.object({status:a.z.enum(["pending","paired","expired"]).default("pending"),joinToken:a.z.string().trim().max(4096).optional(),expiresAt:xe.shape.createdAt.optional()}).strict().optional()}).strict(),pn=a.z.object({id:Ne.shape.id.optional(),name:Ne.shape.name,source:Ee,defaultImageId:Ne.shape.defaultImageId,defaultNodeId:Ne.shape.defaultNodeId,defaultRuntimeId:Ne.shape.defaultRuntimeId,modelSelection:Ne.shape.modelSelection.optional(),workspacePolicy:_e.optional(),labels:Ne.shape.labels.optional()}).strict(),un=pn.omit({id:!0}).partial().strict(),gn=a.z.object({id:Ge.shape.id.optional(),name:Ge.shape.name.optional(),projectId:Ge.shape.projectId,source:Ee.optional(),sourceSnapshot:a.z.record(a.z.string(),a.z.unknown()).optional(),nodeId:Ge.shape.nodeId.optional(),runtimeId:Ge.shape.runtimeId.optional(),imageId:Ge.shape.imageId,config:Ge.shape.config.optional()}).strict(),hn=a.z.object({name:Ge.shape.name.optional()}).strict(),mn=a.z.object({publicBaseUrl:a.z.string().trim().max(2048).optional()}).strict(),fn=mn.partial().strict(),In=a.z.object({id:ze.shape.id.optional(),name:ze.shape.name,endpoint:ze.shape.endpoint,key:ze.shape.key,model:ze.shape.model,app:ze.shape.app,enabled:ze.shape.enabled.optional(),order:ze.shape.order.optional(),labels:ze.shape.labels.optional()}).strict(),yn=In.omit({id:!0}).partial().strict(),bn=a.z.object({id:Oe.shape.id.optional(),name:Oe.shape.name,image:Oe.shape.image,registry:Oe.shape.registry.optional(),capabilities:a.z.array(a.z.string().trim().min(1).max(80)).optional(),optionalApps:a.z.array(a.z.string().trim().min(1).max(120)).optional(),defaultEnv:Oe.shape.defaultEnv.optional(),labels:Oe.shape.labels.optional()}).strict(),wn=bn.omit({id:!0}).partial().strict(),Sn=a.z.object({id:xe.shape.id.optional(),name:xe.shape.name,connectionMode:xe.shape.connectionMode.optional(),auth:ln.optional(),joinToken:a.z.string().trim().min(1).max(4096).optional(),endpoint:xe.shape.endpoint,controlEndpoint:xe.shape.controlEndpoint,containerEndpoint:xe.shape.containerEndpoint,publicWebBase:xe.shape.publicWebBase,status:a.z.enum(["unknown","online","offline","degraded"]).optional(),health:a.z.enum(["unknown","ok","degraded","failed"]).optional(),capabilities:xe.shape.capabilities.optional(),labels:xe.shape.labels.optional(),lastSeenAt:xe.shape.lastSeenAt}).strict(),An=Sn.omit({id:!0,joinToken:!0}).partial().strict(),vn=a.z.object({controlPlaneUrl:a.z.string().trim().url().max(2048),joinToken:a.z.string().trim().min(1).max(4096),controlPlaneName:a.z.string().trim().min(1).max(160).optional(),activate:a.z.boolean().optional()}).strict(),kn=a.z.object({nodeName:a.z.string().trim().min(1).max(160).optional(),expiresInMs:a.z.number().int().positive().max(36e5).optional()}).strict(),Tn=a.z.object({name:Ke.shape.name.optional(),channel:Ke.shape.channel.optional(),enabled:a.z.boolean().optional(),token:Ke.shape.token,defaultChatId:Ke.shape.defaultChatId,allowedUserIds:a.z.array(a.z.string().trim().min(1).max(240)).optional(),pollIntervalMs:Ke.shape.pollIntervalMs.optional(),settings:Ke.shape.settings.optional()}).strict(),En=Tn.extend({channel:Ke.shape.channel,name:Ke.shape.name.optional()}),_n=se.extend({id:se.shape.configHash}),Cn=a.z.object({name:se.shape.name,description:se.shape.description,source:ee,action:te,policy:ne.partial().optional()}).strict(),Nn=a.z.object({instanceIds:a.z.array(Ge.shape.id).min(1),target:ie.optional(),enabled:a.z.boolean().optional()}).strict(),zn=a.z.object({configHash:se.shape.configHash,enabled:a.z.boolean().optional()}).strict();class On{triggers;listInstances;requireInstance;instanceRequest;constructor(e){this.triggers=e.triggers,this.listInstances=e.listInstances,this.requireInstance=e.requireInstance,this.instanceRequest=e.instanceRequest}async listTriggers(){const e=await this.listInstances(),t=new Map;for(const e of this.triggers.list())t.set(e.configHash,{configHash:e.configHash,config:e,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!0,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]});for(const n of e){const e=n.triggers;for(const s of e?.configs||[]){const i=t.get(s.configHash)||{configHash:s.configHash,config:s.config,deploymentCount:0,enabledCount:0,runningCount:0,errorCount:0,ownedByControlPlane:!1,controlPlaneDeploymentCount:0,deployments:[],recentRuns:[]},a=(s.deployments||[]).map(e=>({instanceId:n.id,instanceName:n.name,deployment:e,runtime:(s.runtime||[]).find(t=>t.deploymentId?t.deploymentId===e.deploymentId:t.configHash===e.configHash)}));i.deploymentCount+=a.length,i.enabledCount+=a.filter(e=>Boolean(e.deployment.enabled)).length,i.controlPlaneDeploymentCount+=a.filter(e=>"control-plane"===e.deployment.origin).length,i.runningCount+=(s.runtime||[]).filter(e=>"running"===e.status).length,i.errorCount+=(s.runtime||[]).filter(e=>"error"===e.status).length,i.deployments.push(...a),i.recentRuns.push(...(e.recentRuns||[]).filter(e=>e.configHash===s.configHash).map(e=>({...e,instanceId:n.id,instanceName:n.name}))),t.set(s.configHash,i)}}return{updatedAt:(new Date).toISOString(),triggers:[...t.values()].sort((e,t)=>String(e.config.name||e.configHash).localeCompare(String(t.config.name||t.configHash)))}}createTrigger(e){const t=Cn.parse(e||{}),n=Yt(),s={maxConcurrentRuns:t.policy?.maxConcurrentRuns??1,whenBusy:t.policy?.whenBusy??"skip",cooldownMs:t.policy?.cooldownMs},i=function(e){const t=ce({source:e.source,action:e.action,policy:ne.parse(e.policy||{})});return`trg_${r.createHash("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}({source:t.source,action:t.action,policy:s}),a=_n.parse({id:i,configHash:i,name:t.name,description:t.description,source:t.source,action:t.action,policy:s,createdAt:n,updatedAt:n});return this.triggers.put(a)}async deleteTrigger(e){const t=this.triggers.delete(e),n=await this.listInstances(),s=[];for(const t of n){const n=t.triggers?.configs?.find(t=>t.configHash===e);if(!n)continue;const i=(n.deployments||[]).filter(e=>"control-plane"===e.origin);if(!i.length)continue;const a=n.deployments.every(e=>"control-plane"===e.origin);for(const n of i)try{const i=n.deploymentId?`/triggers/${encodeURIComponent(e)}/deployments/${encodeURIComponent(n.deploymentId)}`:a?`/triggers/${encodeURIComponent(e)}`:void 0;if(!i){s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!1,skipped:"missing-deployment-id"});continue}const r=await this.instanceRequest(t,i,{method:"DELETE"});if(s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!0,data:r}),!n.deploymentId)break}catch(i){s.push({instanceId:t.id,configHash:e,deploymentId:n.deploymentId,deleted:!1,error:i instanceof Error?i.message:String(i)})}}return t||s.length||tn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),{configHash:e,deletedTemplate:t,results:s}}async applyTrigger(e,t){const n=this.requireTrigger(e),s=Nn.parse(t||{}),i=[];for(const e of s.instanceIds){const t=await this.requireInstance(e);i.push({instanceId:e,data:await this.instanceRequest(t,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:n.name,description:n.description,source:n.source,action:n.action,policy:n.policy,deployment:{origin:"control-plane",enabled:s.enabled??!0,target:s.target}})})})}return{configHash:e,results:i}}async listInstanceTriggers(e){const t=await this.requireInstance(e);return this.instanceRequest(t,"/triggers")}async bindAiSessionTrigger(e,t,n){const s=zn.parse(n||{}),i=this.requireTrigger(s.configHash),a=await this.requireInstance(e);return this.instanceRequest(a,"/triggers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:i.name,description:i.description,source:i.source,action:i.action,policy:i.policy,deployment:{deploymentId:xn(t,s.configHash),origin:"control-plane",enabled:s.enabled??!0,target:{type:"ai-session",aiSessionId:t}}})})}async unbindAiSessionTrigger(e,t,n){const s=await this.requireInstance(e),i=xn(t,n);return this.instanceRequest(s,`/triggers/${encodeURIComponent(n)}/deployments/${encodeURIComponent(i)}`,{method:"DELETE"})}async runInstanceTrigger(e,t,n={}){const s=await this.requireInstance(e);return this.instanceRequest(s,`/triggers/${encodeURIComponent(t)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n||{})})}requireTrigger(e){const t=this.triggers.get(e);return t||tn("TRIGGER_NOT_FOUND",`Trigger ${e} was not found.`),t}}function xn(e,t){return`ai_${Buffer.from(e).toString("base64url")}_${t}`}class Dn{records=new Map;options;constructor(e){this.options=e}create(e){const t=r.randomBytes(this.options.tokenBytes).toString("base64url"),n={...e,token:t,expiresAt:new Date(Date.now()+(e.ttlMs||this.options.ttlMs)).toISOString()};return this.records.set(t,n),n}resolve(e,t){const n=this.records.get(e);if(!n)throw this.invalidError();if(n.expiresAt<=(new Date).toISOString())throw this.records.delete(e),this.invalidError();if(t&&!t(n))throw this.invalidError();return n}invalidError(){const e=new Error(this.options.invalidMessage);return Object.assign(e,{statusCode:this.options.invalidStatusCode||400,code:this.options.invalidCode}),e}}class Rn{requireInstance;listAppSessions;tokens=new Dn({invalidMessage:"App access link is invalid or expired.",invalidCode:"APP_ACCESS_TOKEN_INVALID",invalidStatusCode:401,tokenBytes:24,ttlMs:9e5});constructor(e){this.requireInstance=e.requireInstance,this.listAppSessions=e.listAppSessions}createToken(e){return this.tokens.create({instanceId:e.instanceId,sessionId:e.sessionId,mode:e.mode,ttlMs:e.ttlMs})}resolveToken(e,t){return this.tokens.resolve(e,e=>!t||e.mode===t)}async proxyTarget(e,t,n=""){const s=this.resolveToken(e,t),i=await this.requireInstance(s.instanceId),a=await this.listAppSessions(),r=a.instances.find(e=>e.instanceId===s.instanceId)?.appSessions.sessions.find(e=>$n(e.id)===s.sessionId);if(!r){const e=new Error("App session was not found.");throw Object.assign(e,{statusCode:404,code:"APP_SESSION_NOT_FOUND"}),e}return{access:s,instance:i,session:r,path:jn(t,s.sessionId,r,n)}}}function jn(e,t,n,s=""){return"tty"===e?`/api/apps/sessions/${encodeURIComponent(t)}/tty`:"vnc"===e?s?function(e,t){const n=`/${t.replace(/^\/+/,"")}`,s=n.split("?",1)[0]||"/",i=encodeURIComponent(e);if(!["/api/novnc/",`/api/apps/sessions/${i}/web/`,`/api/apps/sessions/${i}/vnc`,`/api/apps/sessions/${i}/novnc/`].some(e=>s===e.replace(/\/$/,"")||s.startsWith(e))){const e=new Error("App access token cannot be used for this path.");throw Object.assign(e,{statusCode:403,code:"APP_ACCESS_PATH_FORBIDDEN"}),e}return n}(t,s):$n(Pn(n.vnc).noVncPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`:$n(Pn(n.web).webPath)||`/api/apps/sessions/${encodeURIComponent(t)}/web/`}function Pn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function $n(e){return"string"==typeof e&&e.trim()?e.trim():void 0}class Mn{tokens=new Dn({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 qn(e){const{token:t,...n}=e;return{...n,settings:Un(e.settings),tokenSet:Boolean(e.token)}}function Ln(e,t){if(!t)return e;const n={...e};for(const[e,s]of Object.entries(t))void 0!==s&&""!==s&&(n[e]=s);return n}function Un(e){const t={...e};return"string"==typeof t.clientSecret&&t.clientSecret&&(delete t.clientSecret,t.clientSecretSet=!0),t}class Bn{chatBridges;chatSessions;constructor(e){this.chatBridges=e.chatBridges,this.chatSessions=e.chatSessions}list(){return this.chatBridges.list().map(qn)}require(e){const t=this.chatBridges.get(e);return t||tn("CHAT_BRIDGE_NOT_FOUND",`Chat bridge ${e} was not found.`),t}create(e){const t=En.parse(e),n=Yt(),s=Ke.parse({...t,id:it(`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:Ln({},t.settings),createdAt:n,updatedAt:n});var i,a;return this.chatBridges.put(s),qn(s)}update(e,t){const n=Tn.parse(t),s=this.require(e),i=Ln(s.settings,n.settings),a=Ke.parse({...s,...n,id:e,channel:s.channel,name:n.name||s.name,settings:i,updatedAt:Yt()});return this.chatBridges.put(a),qn(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 Fn{chatSessions;constructor(e){this.chatSessions=e.chatSessions}list(){return this.chatSessions.list()}require(e){const t=this.chatSessions.get(e);return t||tn("CHAT_SESSION_NOT_FOUND",`Chat session ${e} was not found.`),t}upsert(e){const t=Yt(),n=(s=e.channel,i=e.chatSessionId,`${e.bridgeId||s}:${i.replace(/[^a-zA-Z0-9_.:-]/g,"_")}`);var s,i;const a=this.chatSessions.get(n),r=Je.parse({...a||{},...e,id:n,channel:e.channel,bridgeId:e.bridgeId,chatSessionId:e.chatSessionId,lastUsedAt:t,createdAt:a?.createdAt||t,updatedAt:t});return this.chatSessions.put(r),r}}function Hn(e){return{inline_keyboard:e.map(e=>e.map(e=>({text:e.text,...e.url?{url:e.url}:{callback_data:e.callbackData||""}})))}}function Gn(e,t=40){return Hn(e.slice(0,t).map(e=>[e]))}function Wn(e){return Buffer.from(e,"utf8").toString("base64url")}function Vn(e){const t=Jn(e).inline_keyboard,n=Array.isArray(t)?t:[],s=[];for(const e of n)if(Array.isArray(e))for(const t of e){const e=Jn(t),n=String(e.callback_data||""),i=String(e.text||"").trim();i&&n&&s.push({text:i,id:`th_cb_${Wn(n)}`})}return s.slice(0,40)}function Kn(e,t){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function Jn(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}const Qn=new Set(["stopped","failed","exited","closed","terminated"]);function Yn(e){return!function(e){return Qn.has(Lt(e))}(e)}function Xn(e){return(e.aiSessions?.sessions||[]).filter(t=>function(e,t){const n=(e.apps?.sessions||[]).filter(is);if(t.appSessionId&&n.some(e=>e.id===t.appSessionId))return!0;const s=new Set(t.appBindingKeys||[]);return Boolean(s.size&&n.some(e=>function(e){return[...qt(e).bindings.map(Ut),...Ft(e)].filter(Boolean)}(e).some(e=>s.has(e))))}(e,t))}function Zn(e){return"waiting"===e.status?4:"failed"===e.status?3:"running"===e.status?2:"idle"===e.status?1:0}function es(e){return[e.cwd||"",e.agent||"",e.providerSessionId||e.id].join("\0")}function ts(e){const t=e.session.title||e.session.userPrompt||e.session.summary||e.session.lastMessage||"",n=t?` - ${Kn(t,18)}`:"";return`${e.instance.name} - ${e.session.agent} ${e.session.status}${n}`}function ns(e,t=(e,t)=>`task_handoff:approval:${e.id}:${t}`){return Gn(e.flatMap(e=>"approval"!==e.kind?[]:[{text:Kn(`Allow ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"allow")},{text:Kn(`Skip ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"skip")},{text:Kn(`Deny ${e.instance?.name||e.instanceId} / ${e.aiSessionId}`,56),callbackData:t(e,"deny")}]))}function ss(e){const t=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,n="approval"===e.kind?`/approve ${e.id} | /deny ${e.id} | /skip ${e.id}`:`/reply ${e.id} <message>`;return`[${t}] ${e.kind} ${e.id}: ${e.result}\n${n}`}function is(e){const t=e&&"object"==typeof e&&!Array.isArray(e)?e:{};return Yn("string"==typeof t.status?t.status:void 0)}class as{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleAiSessionsCommand(e){const t=await this.targets.aiSessionChoices(e),n=t.find(e=>e.active),s=t.map(e=>function(e){const t=e.session.userPrompt||e.session.summary||e.session.lastMessage||"",n=e.active?"* ":"",s=e.session.phase&&"unknown"!==e.session.phase?`/${e.session.phase}`:"";return`${n}${e.index+1}. ${e.projectName} / ${e.instance.name} - ${e.session.agent} - ${e.session.status}${s} - ${e.session.id}${t?` - ${t}`:""}`}(e));return{accepted:!0,routed:!1,binding:e,instances:t.map(e=>e.instance),reply:t.length?["Select AI session",`Current ${n?`${n.projectName} / ${n.instance.name} / ${n.session.id}`:await this.targets.renderChatTarget(e)}, total ${t.length}`,"",...s].join("\n"):"No AI sessions found.",replyMarkup:t.length?Gn(t.map(e=>({text:`${e.active?"✓ ":""}${ts(e)}`,callbackData:`task_handoff:cp_session:${e.index}`}))):void 0}}async handleAiSessionCommand(e,t){const n=String(t[0]||"").trim();if(!n)return this.handleAiSessionsCommand(e);const s=await this.targets.findAiSession(n);if(!s)return{accepted:!1,routed:!1,binding:e,reply:`AI session ${n} was not found. Try /sessions.`};const i=s.instance.projectId?this.deps.getProject(s.instance.projectId):void 0;return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i?.id||e.activeProjectId,activeInstanceId:s.instance.id,activeAiSessionId:s.session.id}),instance:s.instance,aiSession:s.session,reply:`Active AI session: ${s.instance.name} / ${s.session.agent} ${s.session.id}`}}async handleAiSessionIndexAction(e,t){const n=(await this.targets.aiSessionChoices(e)).find(e=>e.index===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"AI session was not found. Use /session to refresh the list.",replyMarkup:void 0};const s=this.deps.upsertChatSession({...e,activeProjectId:n.projectId||e.activeProjectId,activeInstanceId:n.instance.id,activeAiSessionId:n.session.id}),i=await this.handleAiSessionsCommand(s);return{...i,accepted:!0,binding:s,instance:n.instance,aiSession:n.session,message:`selected ${n.session.id}`,reply:[`Current chat is bound to AI session ${n.session.id}`,"",i.reply].join("\n")}}async handleAiSessionInterruptCommand(e){if(!e.activeInstanceId||!e.activeAiSessionId)return{accepted:!1,routed:!1,binding:e,reply:"No active AI session is selected. Use /sessions and /session <id> first."};const t=await this.deps.requireControlledInstance(e.activeInstanceId,!0),n=await this.deps.interruptAiSession(t.id,e.activeAiSessionId);return{accepted:!0,routed:!0,binding:e,instance:on(t),aiSession:n,reply:`Interrupt sent to ${t.name} / ${e.activeAiSessionId}.`}}}function rs(e){return e.providerTurnId||e.turnId||""}function os(e){return Yn(us(e.status))}function ds(e,t){const n=us(t.appId)||"App",s=us(t.title).trim();if(s&&s!==n)return s;const i=function(e,t){for(const n of function(e){const t=e.apps;return Array.isArray(t)?t.filter(e=>"object"==typeof e&&null!==e&&!Array.isArray(e)):[]}(e.sourceSnapshot))if(us(n.id)===t)return us(n.name).trim();return""}(e,n);return i||n}function cs(e){const t=function(e){const t=ms(e.capabilities).apps;if(Array.isArray(t)&&t.length>0)return t;const n=ms(e.image).optionalApps;return Array.isArray(n)&&n.length>0?n:["terminal-tty"]}(e),n=new Map;for(const e of t){const t=gs(e);t&&!n.has(t.id)&&n.set(t.id,t)}return[...n.values()]}function ls(e,t,n,s){const i=us(t.id)?function(e,t){return`/apps/access/${ps(e)}?token=${encodeURIComponent(t())}`}(t,s):`/instances/${encodeURIComponent(e.id)}/`,a=function(e,t){const n=us(t);if(n&&new RegExp("^https?://","i").test(n))return new URL(e,n.endsWith("/")?n:n+"/").toString()}(i,n);return{text:a||i,url:a}}function ps(e){const t=us(e.kind);return"gui"===t?"vnc":"web"===t?"web":"tty"}function us(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function gs(e){if("string"==typeof e&&e.trim())return{id:e,label:hs(e)};const t=ms(e),n=us(t.id).trim();return n?{id:n,label:us(t.name).trim()||hs(n)}:void 0}function hs(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 ms(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}class fs{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleInstancesCommand(e){const t=await this.targets.instancesForBinding(e);return{accepted:!0,routed:!1,binding:e,instances:t,reply:(n=t.length,0===n?"No controlled instances are registered yet.":`Instances: ${n}. Tap an instance to create an app.`),replyMarkup:t.length?Gn(t.map(e=>{return{text:Kn(e.name||e.id,56),callbackData:(t=this.deps.createChatActionToken({type:"instance-app-menu",instanceId:e.id}).token,`task_handoff:cp_i:${t}`)};var t})):void 0};var n}async handleInstanceAppMenuAction(e,t){const n=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!n)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const s=cs(n);return{accepted:!0,routed:!1,binding:e,instance:n,message:`Select an app for ${n.name}.`,reply:s.length?`New app for ${n.name}`:`No launchable apps for ${n.name}.`,replyMarkup:s.length?Gn(s.map(e=>{return{text:Kn(e.label,56),callbackData:(t=this.deps.createChatActionToken({type:"launch-app",instanceId:n.id,appId:e.id}).token,`task_handoff:cp_a:${t}`)};var t})):void 0}}async handleLaunchAppAction(e,t,n){const s=(await this.targets.instancesForBinding(e)).find(e=>e.id===t);if(!s)return{accepted:!1,routed:!1,binding:e,reply:"Instance not found."};const i=cs(s).find(e=>e.id===n);return i?{accepted:!0,routed:!1,binding:e,instance:s,session:await this.deps.launchAppSession(s.id,i.id),message:`${i.label} launched`,reply:`Launched ${i.label} on ${s.name}.`}:{accepted:!1,routed:!1,binding:e,reply:`App ${n} is not launchable for ${s.name}.`}}async handleAppsCommand(e){const t=await this.deps.boardAsync(),n=new Map(t.map(e=>[e.id,e])),s=(await this.deps.listAppSessions()).instances.flatMap(e=>{const t=n.get(e.instanceId);return t?e.appSessions.sessions.filter(os).filter(e=>Boolean(us(e.id))).map(e=>({instance:t,session:e,link:ls(t,e,this.deps.controlPlanePublicBaseUrl(),()=>this.deps.createAppAccessToken({instanceId:t.id,sessionId:us(e.id)||"",mode:ps(e)}).token)})):[]}),i=s.filter(e=>e.link.url).map(e=>({text:Kn(`${e.instance.name} · ${ds(e.instance,e.session)}`,56),url:e.link.url}));return{accepted:!0,routed:!1,binding:e,instances:t,reply:(a=s.length,r=i.length,0===a?"No app sessions found.":0===r?`App sessions: ${a}. Set the control plane public URL in Settings > Appearance to open them from chat.`:`App sessions: ${a}. Tap a button to open.`),replyMarkup:i.length?Gn(i):void 0};var a,r}}class Is{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async forwardChatMessage(e,t){const n=t.target?.instanceId||e.activeInstanceId||"",s=t.target?.aiSessionId||e.activeAiSessionId,i=await this.deps.requireControlledInstance(n,!0),a=i.projectId?this.deps.requireProject(i.projectId):void 0;if("online"!==i.connectionStatus){const e=new Error(`Instance ${i.name} is not reachable.`);throw Object.assign(e,{statusCode:409,code:"CHAT_TARGET_UNREACHABLE"}),e}if(s){const n=await this.trySendAiSessionMessage(e,i.id,s,t.message.text,t.message.attachments,!t.target);return n.ok?this.sentResult(e,a,i,s,n.aiSession):this.failedSendResult(n.binding,a,i,n.reply)}const r=await this.targets.defaultAiSessionIdForInstance(i.id);if(r){const n=this.deps.upsertChatSession({...e,activeAiSessionId:r}),s=await this.trySendAiSessionMessage(n,i.id,r,t.message.text,t.message.attachments);return s.ok?this.sentResult(n,a,i,r,s.aiSession):this.failedSendResult(s.binding,a,i,s.reply)}return{accepted:!1,routed:!1,binding:e,project:a||void 0,instance:on(i),reply:`No active AI session is selected for ${i.name}. Use /sessions and /session <id> first.`}}async trySendAiSessionMessage(e,t,n,s,i=[],a=!0){try{return{ok:!0,binding:e,aiSession:await this.deps.sendAiSessionMessage(t,n,s||"请查看附件图片。",void 0,i)}}catch(t){return function(e){return"AI_SESSION_NOT_FOUND"===(e&&"object"==typeof e?e:{}).code}(t)?{ok:!1,binding:a?this.deps.upsertChatSession({...e,activeAiSessionId:void 0}):e,reply:`The selected AI session ${n} no longer exists. Use /session to choose an active AI session.`}:{ok:!1,binding:e,reply:`Failed to send to AI session ${n}: ${en(t)}`}}}failedSendResult(e,t,n,s){return{accepted:!1,routed:!1,binding:e,project:t||void 0,instance:on(n),reply:s}}sentResult(e,t,n,s,i){return{accepted:!0,routed:!0,binding:e,project:t||void 0,instance:on(n),aiSession:i,instanceId:n.id,aiSessionId:s,turnId:(a=i,a.turnId||a.providerTurnId||a.session.activeTurnId||""),providerTurnId:rs(i),reply:`Sent to ${t?.name||n.sourceSnapshot.name||"Source"} / ${n.name} / ${s}.`};var a}}class ys{deps;constructor(e){this.deps=e}async handlePendingCommand(e){const t=await this.deps.listPendingRoutes(),n=e.activeInstanceId?t.filter(t=>t.instanceId===e.activeInstanceId):t;return{accepted:!0,routed:!1,binding:e,pending:n,reply:n.length?n.map(ss).join("\n"):"No pending routes.",replyMarkup:n.length?ns(n,(e,t)=>this.deps.pendingDecisionCallbackData(e.id,t)):void 0}}async handlePendingDecisionCommand(e,t,n){const s=Zt(t[0]);return s?s.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:s,aiSession:await this.deps.resolveAiSessionApproval(s.instanceId,s.aiSessionId,n),reply:`${n} sent for ${s.instanceId}:${s.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:s,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:`Usage: /${"allow"===n?"approve":n} <instance-id:pending-id>`}}async handlePendingReplyCommand(e,t){const n=Zt(t[0]),s=t.slice(1).join(" ").trim();return n&&s?n.aiSessionId?{accepted:!0,routed:!0,binding:e,pending:n,aiSession:await this.deps.sendAiSessionMessage(n.instanceId,n.aiSessionId,s),reply:`Reply sent for ${n.instanceId}:${n.aiSessionId}.`}:{accepted:!1,routed:!1,binding:e,pending:n,reply:"Only AI session pending routes are supported here."}:{accepted:!1,routed:!1,binding:e,reply:"Usage: /reply <instance-id:pending-id> <message>"}}}class bs{deps;targets;constructor(e,t){this.deps=e,this.targets=t}async handleUseCommand(e,t){if(0===t.length)return{accepted:!0,routed:!1,binding:e,instances:await this.targets.instancesForBinding(e),reply:await this.targets.renderUseHelp(e)};if(t.length>=2){const n=this.targets.resolveProject(t[0]),s=await this.targets.resolveInstance(t[1],n.id),i=this.deps.upsertChatSession({...e,activeProjectId:n.id,activeInstanceId:s.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(s.id)});return{accepted:!0,routed:!1,binding:i,reply:await this.targets.renderTargetSelected(i)}}const n=t[0],s=await this.targets.findInstance(n);if(s){const t=s.projectId?this.deps.requireProject(s.projectId):void 0,n=this.deps.upsertChatSession({...e,activeProjectId:t?.id,activeInstanceId:s.id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(s.id)});return{accepted:!0,routed:!1,binding:n,reply:await this.targets.renderTargetSelected(n)}}const i=this.targets.resolveProject(n),a=(await this.deps.boardAsync()).filter(e=>e.projectId===i.id),r=a.filter(e=>"running"===e.status&&"online"===e.connectionStatus);if(1===r.length){const t=this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:r[0].id,activeAiSessionId:await this.targets.defaultAiSessionIdForInstance(r[0].id)});return{accepted:!0,routed:!1,binding:t,reply:await this.targets.renderTargetSelected(t)}}return{accepted:!0,routed:!1,binding:this.deps.upsertChatSession({...e,activeProjectId:i.id,activeInstanceId:void 0,activeAiSessionId:void 0}),instances:a,reply:`${i.name} selected. Choose an instance with /use ${i.id} <instance-id-or-name>.\n${this.targets.renderInstanceLines(a)}`}}async handleProjectCommand(e,t){if(0===t.length)return{accepted:!1,routed:!1,binding:e,reply:"Usage: /project <project-id-or-name>"};const n=this.targets.resolveProject(t[0]),s=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,i=this.deps.upsertChatSession({...e,activeProjectId:n.id,activeInstanceId:s?.projectId===n.id?s.id:void 0,activeAiSessionId:s?.projectId===n.id?e.activeAiSessionId:void 0});return{accepted:!0,routed:!1,binding:i,reply:`Active project: ${n.name}${i.activeInstanceId?"":". Choose an instance with /use."}`}}}class ws{deps;constructor(e){this.deps=e}async instancesForBinding(e){const t=await this.deps.boardAsync();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async fullInstancesForBinding(e){const t=await this.deps.listNodeInstances();return e.activeProjectId?t.filter(t=>t.projectId===e.activeProjectId):t}async renderUseHelp(e){return`Active target: ${await this.renderChatTarget(e)}\nUse /instances to list targets, then /use <instance-id-or-name>. Use /sessions to choose an AI session when there is more than one.`}async renderChatStatus(e){const t=e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0;return t?`Active target: ${await this.renderChatTarget(e)}\nStatus: ${t.status}, connection: ${t.connectionStatus}, pending: ${t.receiver.pendingCount}`:`Active target: ${await this.renderChatTarget(e)}`}async renderChatHelp(e){return["TaskHandoff chat commands:","/help - show this help","/status or /target - show the active project, instance, and AI session","/instances - list controlled instances","/apps - list app sessions","/use <instance-id-or-name> - choose an active instance","/use <project-id-or-name> <instance-id-or-name> - choose a project and instance","/project <project-id-or-name> - narrow instance selection to a project","/sessions - list all AI sessions","/session <ai-session-id> - choose an active AI session","/pending - list waiting approvals","/approve <instance-id:pending-id>, /deny <...>, /skip <...> - answer approvals","/reply <instance-id:pending-id> <message> - reply to a waiting session","/cancel - interrupt the active AI session","","Send normal text after selecting a target; if there is only one AI session, it is selected automatically.",`Current target: ${await this.renderChatTarget(e)}`].join("\n")}renderInstanceLines(e){return e.map(e=>`${e.name} (${e.id}) - ${e.status}/${e.connectionStatus}`).join("\n")}async renderChatTarget(e){const t=e.activeProjectId?this.deps.getProject(e.activeProjectId):void 0,n=e.activeInstanceId?await this.deps.requireControlledInstance(e.activeInstanceId).catch(()=>{}):void 0,s=e.activeAiSessionId||"none";return`${t?.name||e.activeProjectId||"none"} / ${n?.name||e.activeInstanceId||"none"} / ${s}`}async renderTargetSelected(e){const t=await this.renderChatTarget(e);return e.activeAiSessionId?`Active target: ${t}`:`Active target: ${t}\nNo AI session selected yet. Use /sessions and /session <id>, or send a message once exactly one session is available.`}async defaultAiSessionIdForInstance(e){const t=await this.aiSessionSnapshotForInstance(e),n=(await this.deps.boardAsync()).find(t=>t.id===e),s=(await this.deps.listAppSessions()).instances.find(t=>t.instanceId===e),i=n?{...n,apps:{...n.apps,sessions:s?.appSessions.sessions||[]}}:void 0,a=t&&n?Xn({...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=>Xn(t).map(n=>({index:0,projectId:t.projectId,projectName:t.project?.name||("string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:"Source"),instance:t,session:n,active:e.activeInstanceId===t.id&&e.activeAiSessionId===n.id}))).sort((e,t)=>{const n=Zn(t.session)-Zn(e.session);return n||(String(e.instance.name||"").localeCompare(String(t.instance.name||""))||e.instance.id.localeCompare(t.instance.id)||es(e.session).localeCompare(es(t.session)))}).map((e,t)=>({...e,index:t}))}async boardInstancesForBinding(e,t={}){const n=await this.deps.boardAsync(),s=await this.deps.listAiSessions(t),i=await this.deps.listAppSessions(t),a=new Map(s.instances.map(e=>[e.instanceId,e.aiSessions])),r=new Map(i.instances.map(e=>[e.instanceId,e.appSessions])),o=n.map(e=>{return{...e,apps:{...e.apps,sessions:r.get(e.id)?.sessions||[]},aiSessions:a.get(e.id)||(t=e.aiSessions.updatedAt,{runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:t})};var t});return e.activeProjectId?o.filter(t=>t.projectId===e.activeProjectId):o}async findAiSession(e){const t=await this.boardInstancesForBinding({},{refresh:!0});for(const n of t){const t=(n.aiSessions?.sessions||[]).find(t=>t.id===e||t.providerSessionId===e);if(t)return{instance:n,session:t}}}async aiSessionSnapshotForInstance(e){const t=await this.deps.listAiSessions();return t.instances.find(t=>t.instanceId===e)?.aiSessions}resolveProject(e){const t=this.deps.getProject(e)||this.deps.listProjects().find(t=>t.name===e);return t||tn("PROJECT_NOT_FOUND",`Project ${e} was not found.`),t}async resolveInstance(e,t){const n=await this.findInstance(e,t);return n||tn("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`),n}async findInstance(e,t){return(await this.deps.listNodeInstances()).find(n=>!(n.id!==e&&n.name!==e||t&&n.projectId!==t))}}class Ss{deps;targets;sender;appCommands;pendingCommands;aiSessionCommands;targetCommands;constructor(e){this.deps=e,this.targets=new ws(e),this.sender=new Is(e,this.targets),this.appCommands=new fs(e,this.targets),this.pendingCommands=new ys(e),this.aiSessionCommands=new as(e,this.targets),this.targetCommands=new bs(e,this.targets)}async handleChatGatewayMessage(e){const t=Qe.parse(e),n=this.deps.upsertChatSession({channel:t.source.channel,bridgeId:t.source.bridgeId,chatSessionId:t.source.chatSessionId,userId:t.source.userId}),s=t.message.text.trim();return s.startsWith("/")?this.handleChatCommand(n,s):s||t.message.attachments.length?n.activeInstanceId?this.sender.forwardChatMessage(n,t):{accepted:!1,routed:!1,binding:n,reply:await this.targets.renderUseHelp(n)}:{accepted:!1,routed:!1,binding:n,reply:""}}async handleChatGatewayAction(e){const t=this.deps.upsertChatSession({channel:e.source.channel,bridgeId:e.source.bridgeId,chatSessionId:e.source.chatSessionId,userId:e.source.userId});return"ai-session"===e.action.type?this.aiSessionCommands.handleAiSessionIndexAction(t,e.action.index):"instance-app-menu"===e.action.type?this.appCommands.handleInstanceAppMenuAction(t,e.action.instanceId):"launch-app"===e.action.type?this.appCommands.handleLaunchAppAction(t,e.action.instanceId,e.action.appId):"pending-decision"===e.action.type?this.pendingCommands.handlePendingDecisionCommand(t,[e.action.routeId],e.action.decision):{accepted:!1,routed:!1,binding:t,reply:"Unknown action."}}async handleChatCommand(e,t){const[n="",...s]=t.split(/\s+/),i=n.replace(/@[\w_]+$/,"");switch(i.toLowerCase()){case"/help":case"/start":return{accepted:!0,routed:!1,binding:e,reply:await this.targets.renderChatHelp(e)};case"/sessions":return this.aiSessionCommands.handleAiSessionsCommand(e);case"/session":return this.aiSessionCommands.handleAiSessionCommand(e,s);case"/cancel":case"/interrupt":return this.aiSessionCommands.handleAiSessionInterruptCommand(e);case"/pending":return this.pendingCommands.handlePendingCommand(e);case"/approve":return this.pendingCommands.handlePendingDecisionCommand(e,s,"allow");case"/deny":return this.pendingCommands.handlePendingDecisionCommand(e,s,"deny");case"/skip":return this.pendingCommands.handlePendingDecisionCommand(e,s,"skip");case"/reply":return this.pendingCommands.handlePendingReplyCommand(e,s);case"/instances":return this.appCommands.handleInstancesCommand(e);case"/apps":return this.appCommands.handleAppsCommand(e);case"/use":return this.targetCommands.handleUseCommand(e,s);case"/project":return this.targetCommands.handleProjectCommand(e,s);case"/status":case"/target":return{accepted:!0,routed:!1,binding:e,instance:e.activeInstanceId?(await this.deps.boardAsync()).find(t=>t.id===e.activeInstanceId):void 0,reply:await this.targets.renderChatStatus(e)};default:return{accepted:!1,routed:!1,binding:e,reply:`Unknown command: ${i||n}. Try /help.`}}}}function As(e,t){const n=t.filter(e=>e.enabled),s={},i=ks(n,"codex",e.modelSelection.codexModelId);i&&(s.OPENAI_API_KEY=i.key,s.OPENAI_BASE_URL=i.endpoint,s.CODEX_MODEL=i.model,s.TASK_HANDOFF_CODEX_MODEL=i.model,s.TASK_HANDOFF_CODEX_MODEL_ID=i.id);const a=ks(n,"claude",e.modelSelection.claudeModelId);return a&&(s.ANTHROPIC_API_KEY=a.key,s.ANTHROPIC_BASE_URL=a.endpoint,s.CLAUDE_MODEL=a.model,s.TASK_HANDOFF_CLAUDE_MODEL=a.model,s.TASK_HANDOFF_CLAUDE_MODEL_ID=a.id),s}function vs(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 ks(e,t,n){const s=e.filter(e=>e.app===t);return s.find(e=>n&&e.id===n)||s[0]}function Ts(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 Es="TASK_HANDOFF_NODE_AGENT_V1",_s=6e4;function Cs(e=""){return r.createHash("sha256").update(e).digest("hex")}function Ns(e,t){return r.createHmac("sha256",e).update(function(e){return[Es,e.keyId,e.method.toUpperCase(),e.pathWithQuery,e.timestamp,e.nonce,e.bodySha256].join("\n")}(t)).digest("hex")}function zs(e){const t=e.timestamp||(new Date).toISOString(),n=e.nonce||r.randomBytes(18).toString("base64url"),s=Cs(e.body||""),i=Ns(e.secret,{keyId:e.keyId,method:e.method,pathWithQuery:e.pathWithQuery,timestamp:t,nonce:n,bodySha256:s});return{"x-taskhandoff-node-id":e.nodeId,"x-taskhandoff-key-id":e.keyId,"x-taskhandoff-timestamp":t,"x-taskhandoff-nonce":n,"x-taskhandoff-body-sha256":s,"x-taskhandoff-signature":i}}function Os(e){if(e.startsWith("ipc://"))return decodeURIComponent(e.slice(6))}function xs(e,n=process.platform){if(!function(e,n=process.platform){if("win32"===n)return e.startsWith("\\\\.\\pipe\\");const s=t.statSync(e),i=process.getuid?.();return void 0===i||s.uid===i}(e,n)){const e=new Error("Local node agent IPC socket is not owned by the current user.");throw Object.assign(e,{statusCode:403,code:"NODE_AGENT_IPC_OWNER_INVALID"}),e}}function Ds(e,t,n,s={}){const i=`/api/node-agent${t}`,a={createConnection:()=>p.connect(e),headers:{host:"task-handoff-node-agent.local",...s}};return n&&(Array.isArray(n)?n.length>0:n.trim().length>0)?new c.WebSocket(`ws://task-handoff-node-agent.local${i}`,n,a):new c.WebSocket(`ws://task-handoff-node-agent.local${i}`,a)}const Rs=a.z.object({id:a.z.string().trim().min(1),tokenHash:a.z.string().trim().min(1),expiresAt:a.z.string().datetime(),nodeName:a.z.string().trim().min(1).max(160).optional(),createdAt:a.z.string().datetime(),updatedAt:a.z.string().datetime()}).strict(),js="task-handoff.control-plane.local",Ps="task-handoff.control-plane.builtin";function $s(e){return"true"===e.labels[js]}function Ms(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 qs(e,t){const n=function(e){return"paired-hmac"===e.auth.mode?e.auth.secret:void 0}(e);if(n)return zs({nodeId:e.id,keyId:Ms(e),secret:n,method:t.method,pathWithQuery:t.pathWithQuery,body:t.body});const s=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return s?{authorization:`Bearer ${s}`}:{}}class Ls{projects;models;images;nodes;chatSessions;chatBridges;triggers;nodeJoinInvites;settings;paths;fetchImpl;dockerCommandRunner;logger;appAccessService;chatActionTokenService=new Mn;nodeAgentTransport;nodeAgentGateway;controlledInstanceGateway;instanceBoardReader=new cn;controlPlaneTriggerService;chatBridgeService;chatSessionService;chatSessionRuntime;aiSessionSnapshotProvider;appSessionSnapshotProvider;constructor(e,t={}){var n;this.paths=e,this.fetchImpl=t.fetchImpl||fetch,this.dockerCommandRunner=t.dockerCommandRunner,this.nodeAgentTransport=t.nodeAgentTransport,this.logger=(n=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(n||"").trim().toLowerCase())?t.logger:void 0);const s=new Jt({request:(e,t,n)=>this.nodeAgentFetch(e,t,n),logger:this.logger});this.nodeAgentGateway=new Qt(s),this.controlledInstanceGateway=new nn({requireNode:e=>this.requireNode(e),nodeAgentGateway:this.nodeAgentGateway,nodeAgentRequest:(e,t,n)=>this.nodeAgentFetch(e,t,n),fetchImpl:this.fetchImpl});const i=e=>({schema:e,logger:(e,t)=>this.logWarn(t,e)});this.projects=new rt(e.projectsDir,i(Ne)),this.models=new rt(e.modelsDir,i(ze)),this.images=new rt(e.imagesDir,i(Oe)),this.nodes=new rt(e.nodesDir,i(xe)),this.chatSessions=new rt(e.chatSessionsDir,i(Je)),this.chatBridges=new rt(e.chatBridgesDir,i(Ke)),this.triggers=new rt(e.triggersDir,i(_n)),this.nodeJoinInvites=new rt(e.nodeJoinInvitesDir,i(Rs)),this.settings=new ot(e.settingsPath,()=>mn.parse({}),i(mn)),this.controlPlaneTriggerService=new On({triggers:this.triggers,listInstances:()=>this.listNodeInstances(),requireInstance:async e=>this.requireControlledInstance(e,!0),instanceRequest:(e,t,n)=>this.instanceRequest(e,t,n)}),this.appAccessService=new Rn({requireInstance:async e=>this.requireControlledInstance(e,!0),listAppSessions:()=>this.listAppSessions()}),this.chatBridgeService=new Bn({chatBridges:this.chatBridges,chatSessions:this.chatSessions}),this.chatSessionService=new Fn({chatSessions:this.chatSessions}),this.chatSessionRuntime=new Ss({upsertChatSession:e=>this.upsertChatSession(e),boardAsync:()=>this.boardAsync(),listAiSessions:e=>this.listAiSessions(e),listAppSessions:e=>this.listAppSessions(e),listNodeInstances:()=>this.listNodeInstances(),listPendingRoutes:()=>this.listPendingRoutes(),pendingDecisionCallbackData:(e,t)=>this.pendingDecisionCallbackData(e,t),createChatActionToken:e=>this.createChatActionToken(e),createAppAccessToken:e=>this.createAppAccessToken(e),controlPlanePublicBaseUrl:()=>this.getSettings().publicBaseUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_URL||process.env.TASK_HANDOFF_PUBLIC_BASE||process.env.TASK_HANDOFF_CONTROL_PLANE_PUBLIC_URL,requireControlledInstance:(e,t)=>this.requireControlledInstance(e,t),requireProject:e=>this.requireProject(e),getProject:e=>this.projects.get(e),listProjects:()=>this.projects.list(),resolveAiSessionApproval:(e,t,n)=>this.resolveAiSessionApproval(e,t,n),sendAiSessionMessage:(e,t,n,s,i)=>this.sendAiSessionMessage(e,t,n,s,i),interruptAiSession:(e,t)=>this.interruptAiSession(e,t),launchAppSession:(e,t,n)=>this.launchAppSession(e,t,n)})}logInfo(e,t){this.logger?.info?.(e,t)}logWarn(e,t){this.logger?.warn?.(e,t)}logError(e,t){this.logger?.error?.(e,t)}setNodeAgentTransport(e){this.nodeAgentTransport=e}setAiSessionSnapshotProvider(e){this.aiSessionSnapshotProvider=e}async listAiSessions(e={}){return this.aiSessionSnapshotProvider?this.aiSessionSnapshotProvider(e):this.bootstrapAiSessionsFromInstances()}setAppSessionSnapshotProvider(e){this.appSessionSnapshotProvider=e}async listAppSessions(e={}){return this.appSessionSnapshotProvider?this.appSessionSnapshotProvider(e):this.bootstrapAppSessionsFromInstances()}init(){this.projects.init(),this.models.init(),this.images.init(),this.nodes.init(),this.chatSessions.init(),this.chatBridges.init(),this.triggers.init(),this.nodeJoinInvites.init(),this.settings.init(),this.seedDefaults()}getSettings(){return mn.parse(this.settings.get())}updateSettings(e){const t=fn.parse(e),n=mn.parse({...this.getSettings(),...t,publicBaseUrl:t.publicBaseUrl?.trim()||void 0});return this.settings.put(n)}seedDefaults(){if(!this.images.get("img_default")){const e=Yt();this.images.put(Oe.parse({id:"img_default",name:"TaskHandoff Browser + Terminal",image:process.env.TASK_HANDOFF_CONTROLLED_IMAGE||"task-handoff-web:local",registry:"local",capabilities:["browser","terminal","gui-terminal","codex","claude"],optionalApps:["chromium","terminal-tty","gui-terminal","vscode-web"],defaultEnv:{},labels:{"task-handoff.image.kind":"controlled-instance"},createdAt:e,updatedAt:e}))}}listProjects(){return this.projects.list().map(e=>this.normalizeProjectRecord(e))}defaultNodeId(){return this.nodes.list().find($s)?.id||this.nodes.list()[0]?.id}async inspectNodeAgent(e,t){const n="/health",s=t?qs(t,{method:"GET",pathWithQuery:`/api/node-agent${n}`}):{},i=await this.fetchNodeAgentEndpoint(e,n,{headers:s}),a=await i.json().catch(()=>({}));if(!i.ok){const t=new Error(a.error?.message||`Node agent health check failed with HTTP ${i.status}`);throw Object.assign(t,{statusCode:i.status,code:"NODE_AGENT_HEALTH_FAILED"}),this.logWarn({nodeEndpoint:e,statusCode:i.status,errorCode:"NODE_AGENT_HEALTH_FAILED"},"node agent health check failed"),t}const r="string"==typeof a.data?.nodeId&&a.data.nodeId.trim()?a.data.nodeId.trim():void 0;if(!r){const t=new Error("Node agent health response did not include nodeId.");throw Object.assign(t,{statusCode:502,code:"NODE_AGENT_ID_MISSING"}),this.logWarn({nodeEndpoint:e,payload:a.data,errorCode:"NODE_AGENT_ID_MISSING"},"node agent health response missing node id"),t}if(a.data?.protocolVersion!==le){const t=function(e,t){const n="string"==typeof t&&t.trim()?t:"missing",s=new Error(`${e} protocol version ${n} is not compatible with ${le}.`);return Object.assign(s,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),s}(`Node agent ${r}`,a.data?.protocolVersion);throw this.logWarn({nodeId:r,nodeEndpoint:e,expectedProtocolVersion:le,actualProtocolVersion:a.data?.protocolVersion,build:a.data?.build,errorCode:"PROTOCOL_VERSION_MISMATCH"},"node agent protocol version mismatch"),t}const o=fe.safeParse(a.data?.build);return this.logInfo({nodeId:r,nodeEndpoint:e,protocolVersion:a.data.protocolVersion,build:o.success?o.data:a.data?.build},"node agent health check ok"),{nodeId:r,data:a.data||{}}}async fetchNodeAgentEndpoint(e,t,n={}){const s=Os(e);return s?(xs(s),async function(e,t,n={}){const s=n.method||"GET",i="string"==typeof n.body||n.body instanceof Buffer?n.body:void 0===n.body||null===n.body?void 0:String(n.body),a=new Headers(n.headers||{});return void 0===i||a.has("content-length")||a.set("content-length",String(Buffer.byteLength(i))),new Promise((n,r)=>{const o=l.request({socketPath:e,method:s,path:`/api/node-agent${t}`,headers:Object.fromEntries(a.entries())},e=>{const t=[];e.on("data",e=>t.push(Buffer.isBuffer(e)?e:Buffer.from(e))),e.on("end",()=>{n(new Response(Buffer.concat(t),{status:e.statusCode||500,headers:e.headers}))})});o.on("error",r),void 0!==i&&o.write(i),o.end()})}(s,t,n)):this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent${t}`,n)}async completeNodeAgentPairing(e,t){const n=await this.fetchImpl(`${e.replace(/\/$/,"")}/api/node-agent/pairing/complete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({joinToken:t.joinToken,controlPlaneName:t.controlPlaneName||"Control Plane",controlPlaneUrl:t.controlPlaneUrl})}),s=await n.json().catch(()=>({}));if(!n.ok){const e=new Error(s.error?.message||`Node agent pairing failed with status ${n.status}.`);throw Object.assign(e,{statusCode:n.status,code:"NODE_AGENT_PAIRING_FAILED"}),e}if("string"!=typeof s.data?.nodeId||!s.data.nodeId.trim()||"string"!=typeof s.data?.keyId||!s.data.keyId.trim()||"string"!=typeof s.data?.secret||!s.data.secret.trim()){const e=new Error("Node agent pairing response did not include nodeId, keyId, and secret.");throw Object.assign(e,{statusCode:502,code:"NODE_AGENT_PAIRING_RESPONSE_INVALID"}),e}return{nodeId:s.data.nodeId.trim(),keyId:s.data.keyId.trim(),secret:s.data.secret.trim(),pairedAt:"string"==typeof s.data.pairedAt?s.data.pairedAt:Yt()}}async syncLocalNodeConnection(){const e={[js]:"true",[Ps]:"true"},t=process.env.TASK_HANDOFF_NODE_AGENT_TOKEN?.trim()||void 0,n=process.env.TASK_HANDOFF_NODE_AGENT_CONTROL_ENDPOINT||process.env.TASK_HANDOFF_NODE_AGENT_ENDPOINT||"http://127.0.0.1:8091",s=function(e){return Os(e)?"local-ipc":"local-loopback"}(n),i=xe.parse({id:"node_local_probe",name:"Local Node Probe",connectionMode:s,auth:{mode:"local-static-key",secret:t},labels:e,createdAt:Yt(),updatedAt:Yt()}),a=await this.inspectNodeAgent(n,i),r=Yt(),o=this.nodes.get(a.nodeId),d=this.nodes.list().filter(e=>$s(e)&&e.id!==a.nodeId);for(const e of d)this.nodes.delete(e.id);const c=xe.parse({...o||{},id:a.nodeId,name:o?.name||"Local Node",connectionMode:s,auth:{mode:"local-static-key",secret:t},endpoint:n,controlEndpoint:n,containerEndpoint:process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL,publicWebBase:process.env.TASK_HANDOFF_INSTANCE_PUBLIC_WEB_BASE,status:"online",health:"ok",capabilities:{...o?.capabilities||{},agent:sn(a.data)},labels:{...o?.labels||{},...e},createdAt:o?.createdAt||r,updatedAt:r});return this.nodes.put(c)}listModels(){return this.listAllModels().map(dn)}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=In.parse(e),n=Yt(),s=this.nextModelOrder(),i=ze.parse({...t,id:t.id||it("model"),enabled:t.enabled??!0,order:t.order??s,labels:t.labels||{},createdAt:n,updatedAt:n});return dn(this.models.put(i))}updateModel(e,t){const n=yn.parse(t),s=this.requireModelSecret(e),i=ze.parse({...s,...n,id:e,key:n.key?.trim()?n.key:s.key,createdAt:s.createdAt,updatedAt:Yt()});return dn(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(Ne.parse({...t,modelSelection:{...t.modelSelection.codexModelId===e?{}:{codexModelId:t.modelSelection.codexModelId},...t.modelSelection.claudeModelId===e?{}:{claudeModelId:t.modelSelection.claudeModelId}},updatedAt:Yt()}));return t}reorderModels(e){const t=[...new Set(e)],n=new Map(this.models.list().map(e=>[e.id,e]));for(const e of t)n.has(e)||tn("MODEL_NOT_FOUND",`Model ${e} was not found.`);return t.forEach((e,t)=>{const s=n.get(e);this.models.put(ze.parse({...s,order:100*(t+1),updatedAt:Yt()}))}),this.listModels()}createProject(e){const t=pn.parse(e),n=Yt(),s=Ee.parse(t.source),i=this.validateProjectModelSelection(t.modelSelection||{}),a=Ne.parse({...t,id:t.id||it("proj"),source:s,defaultImageId:t.defaultImageId||"img_default",defaultNodeId:t.defaultNodeId||this.defaultNodeId(),defaultRuntimeId:t.defaultRuntimeId||"runtime_local_docker",modelSelection:i,workspacePolicy:_e.parse(t.workspacePolicy||rn(s)),labels:t.labels||{},createdAt:n,updatedAt:n});return this.projects.put(a)}updateProject(e,t){const n=un.parse(t),s=this.requireProject(e),i=n.source?Ee.parse(n.source):s.source,a=n.modelSelection?this.validateProjectModelSelection(n.modelSelection):s.modelSelection,r=Ne.parse({...s,...n,id:e,source:i,modelSelection:a,workspacePolicy:n.workspacePolicy?_e.parse(n.workspacePolicy):s.workspacePolicy,createdAt:s.createdAt,updatedAt:Yt()});return this.projects.put(r)}deleteProject(e){return this.projects.delete(e)}listImages(){return this.images.list()}createImage(e){const t=bn.parse(e),n=Yt(),s=Oe.parse({...t,id:t.id||it("img"),registry:t.registry||"local",capabilities:t.capabilities||[],optionalApps:t.optionalApps||[],defaultEnv:t.defaultEnv||{},labels:t.labels||{},createdAt:n,updatedAt:n});return this.images.put(s)}async listNodeDockerImages(e){const t=this.requireNode(e);return this.nodeAgentGateway.listDockerImages(t)}async listNodeLocalFolders(e){const t=this.requireNode(e);return this.nodeAgentGateway.listLocalFolders(t)}async listNodeFolderTree(e,t={}){const n=this.requireNode(e);return this.nodeAgentGateway.listFolderTree(n,t)}async createNodeLocalFolder(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.createLocalFolder(n,t)}async deleteNodeLocalFolder(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteLocalFolder(n,t)}async checkNode(e){const t=this.requireNode(e);try{const e=await this.nodeAgentGateway.health(t),n=e&&"object"==typeof e?e:{},s="string"==typeof n.nodeId&&n.nodeId.trim()?n.nodeId.trim():t.id,i=xe.parse({...t,id:s,status:"online",health:"ok",capabilities:"object"==typeof e&&e?{...t.capabilities,agent:sn(e)}:t.capabilities,lastSeenAt:Yt(),updatedAt:Yt()});return s!==t.id&&this.nodes.delete(t.id),this.nodes.put(i),{id:i.id,status:"online",checkedAt:Yt(),agent:e}}catch(e){const n=xe.parse({...t,status:"offline",health:"failed",updatedAt:Yt()});return this.nodes.put(n),{id:t.id,status:"offline",checkedAt:Yt(),error:e instanceof Error?e.message:String(e)}}}async checkNodeUpdate(e,t){return this.nodeAgentGateway.checkUpdate(this.requireNode(e),t)}async applyNodeUpdate(e,t){const n=this.requireNode(e);if("controlled-instance"===t.target.component){const e=await this.requireNodeInstance(t.target.instanceId);if(e.nodeId!==n.id){const t=new Error(`Instance ${e.id} does not belong to node ${n.id}.`);throw Object.assign(t,{statusCode:400,code:"UPDATE_TARGET_NODE_MISMATCH"}),t}return this.nodeAgentGateway.applyUpdate(n,{...t,modelEnv:this.modelEnvForInstance(e)})}return this.nodeAgentGateway.applyUpdate(n,t)}async listNodeUpdateJobs(e){return this.nodeAgentGateway.listUpdateJobs(this.requireNode(e))}async createNodePairingInvite(e,t={}){const n=this.requireNode(e);return this.nodeAgentGateway.createPairingInvite(n,t)}pruneNodeJoinInvites(){const e=Date.now();for(const t of this.nodeJoinInvites.list())Date.parse(t.expiresAt)<=e&&this.nodeJoinInvites.delete(t.id)}createNodeJoinInvite(e={}){this.pruneNodeJoinInvites();const t=kn.parse(e&&"object"==typeof e?e:{}),n=Yt(),s=at(),i={id:it("node_join"),tokenHash:r.createHash("sha256").update(s).digest("hex"),expiresAt:new Date(Date.now()+(t.expiresInMs||6e5)).toISOString(),...t.nodeName?{nodeName:t.nodeName}:{},createdAt:n,updatedAt:n};return this.nodeJoinInvites.put(i),{id:i.id,joinToken:s,expiresAt:i.expiresAt}}completeNodeJoin(e){this.pruneNodeJoinInvites();const t=a.z.object({joinToken:a.z.string().trim().min(1).max(4096),nodeId:xe.shape.id,nodeName:xe.shape.name.optional(),keyId:a.z.string().trim().min(1).max(160),secret:a.z.string().trim().min(1).max(4096),pairedAt:a.z.string().datetime().optional()}).parse(e),n=r.createHash("sha256").update(t.joinToken).digest("hex"),s=this.nodeJoinInvites.list().find(e=>e.tokenHash===n&&Date.parse(e.expiresAt)>Date.now());if(!s){const e=new Error("Node join token is invalid or expired.");throw Object.assign(e,{statusCode:401,code:"NODE_JOIN_TOKEN_INVALID"}),e}this.nodeJoinInvites.delete(s.id);const i=Yt();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 o=xe.parse({id:t.nodeId,name:t.nodeName||s.nodeName||t.nodeId,connectionMode:"reverse-wss",auth:{mode:"paired-hmac",keyId:t.keyId,secret:t.secret,pairedAt:t.pairedAt||i,pairing:{status:"paired"}},status:"unknown",health:"unknown",capabilities:{},labels:{},createdAt:i,updatedAt:i});return this.nodes.put(o)}async connectNodeToControlPlane(e,t){const n=this.requireNode(e),s=vn.parse(t);return this.nodeAgentGateway.connectRemote(n,s)}async listNodeRemoteControlPlanes(e){const t=this.requireNode(e);return this.nodeAgentGateway.listRemotes(t)}async deleteNodeRemoteControlPlane(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteRemote(n,t)}updateImage(e,t){const n=wn.parse(t),s=this.requireImage(e),i=Oe.parse({...s,...n,id:e,createdAt:s.createdAt,updatedAt:Yt()});return this.images.put(i)}deleteImage(e){return this.images.delete(e)}listNodes(){return this.nodes.list()}listPublicNodes(){return this.listNodes().map(an)}async createNode(e){const t=Sn.parse(e),n=Yt(),s=t.connectionMode||"direct-http";let i="auth"in(e&&"object"==typeof e&&!Array.isArray(e)?e:{})?t.auth:{mode:"local-ipc"===s||"local-loopback"===s?"local-static-key":"paired-hmac"};if(("direct-http"===s||"reverse-wss"===s)&&"paired-hmac"!==i.mode){const e=new Error("Remote node connections require paired-HMAC authentication.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_REQUIRES_PAIRED_HMAC"}),e}const a=t.controlEndpoint||t.endpoint;if(!a&&"reverse-wss"!==s){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const r=t.joinToken&&a?await this.completeNodeAgentPairing(a,{joinToken:t.joinToken}):void 0;if(r&&(i={mode:"paired-hmac",keyId:r.keyId,secret:r.secret,pairedAt:r.pairedAt,pairing:{status:"paired"}}),("direct-http"===s||"reverse-wss"===s)&&!i.secret){const e=new Error("Remote node connections require a paired node secret or join token.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_SECRET_REQUIRED"}),e}if(("direct-http"===s||"reverse-wss"===s)&&!i.keyId){const e=new Error("Remote node connections require a paired key id.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_REMOTE_KEY_ID_REQUIRED"}),e}const o=t.id||r?.nodeId||it("node"),d=xe.parse({id:o,name:t.name,connectionMode:s,auth:i,labels:t.labels||{},createdAt:n,updatedAt:n}),c="reverse-wss"===s&&(t.id||r?.nodeId)?void 0:await this.inspectNodeAgent(a||"",d),l=t.id||r?.nodeId||c?.nodeId||o;if(!l){const e=new Error("Node id is required.");throw Object.assign(e,{statusCode:400,code:"NODE_ID_REQUIRED"}),e}if(t.id&&c?.nodeId&&c.nodeId!==t.id){const e=new Error(`Node agent id ${c.nodeId} does not match requested node id ${t.id}.`);throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ID_MISMATCH"}),e}const{joinToken:p,...u}=t,g=xe.parse({...u,id:l,connectionMode:s,auth:i,endpoint:a,controlEndpoint:a,status:t.status||(c?"online":"unknown"),health:t.health||(c?"ok":"unknown"),capabilities:{...t.capabilities||{},...c?{agent:sn(c.data)}:{}},labels:t.labels||{},createdAt:n,updatedAt:n});return this.nodes.put(g)}updateNode(e,t){const n=An.parse(t),s=this.requireNode(e),i=xe.parse({...s,...n,id:e,createdAt:s.createdAt,updatedAt:Yt()});return this.nodes.put(i)}deleteNode(e){if("true"===this.requireNode(e).labels[Ps]){const e=new Error("The built-in local node connection cannot be deleted.");throw Object.assign(e,{statusCode:400,code:"LOCAL_NODE_CANNOT_BE_DELETED"}),e}return this.nodes.delete(e)}async listNodeRuntimes(e){const t=e?[this.requireNode(e)]:this.listNodes();return e?this.nodeAgentGateway.listRuntimes(t[0]):(await this.nodeAgentGateway.listFleetRuntimes(t)).items}async listNodeRuntimesWithDiagnostics(){return this.nodeAgentGateway.listFleetRuntimes(this.listNodes())}async createNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.createRuntime(n,t)}async updateNodeRuntime(e,t,n){const s=this.requireNode(e);return this.nodeAgentGateway.updateRuntime(s,t,n)}async deleteNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.deleteRuntime(n,t)}async checkNodeRuntime(e,t){const n=this.requireNode(e);return this.nodeAgentGateway.checkRuntime(n,t)}async listControlledInstances(){return(await this.listNodeInstances()).map(on)}async listControlledInstancesWithDiagnostics(){const e=await this.listNodeInstancesWithDiagnostics();return{items:e.items.map(on),nodeErrors:e.nodeErrors}}async createControlledInstance(e){const t=gn.parse(e),n=t.projectId?this.requireProject(t.projectId):void 0,s=t.runtimeId||n?.defaultRuntimeId||"runtime_local_docker",i=t.nodeId||n?.defaultNodeId||this.defaultNodeId();if(!i){const e=new Error("At least one node connection is required before creating an instance.");throw Object.assign(e,{statusCode:400,code:"NODE_REQUIRED"}),e}const a=this.requireNode(i),r=await this.requireNodeRuntimeOnNode(a.id,s),o="boolean"==typeof r.capabilities.requiresImage?r.capabilities.requiresImage:"local"!==r.type,d=t.imageId||(o?n?.defaultImageId:void 0),c=d?this.requireImage(d):void 0;if(o&&!c){const e=new Error(`Runtime ${r.name} requires an image.`);throw Object.assign(e,{statusCode:400,code:"RUNTIME_IMAGE_REQUIRED"}),e}let l=t.source||n?.source,p=t.sourceSnapshot||n||{};if(!l){const e=new Error("Instance source is required.");throw Object.assign(e,{statusCode:400,code:"INSTANCE_SOURCE_REQUIRED"}),e}if("local"===r.type&&"local-folder"!==l.type){const e=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(e,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),e}if("local-folder"===l.type){if(l.ownerNodeId&&l.ownerNodeId!==a.id){const e=new Error(`Local folder ${l.path} belongs to node ${l.ownerNodeId}, not ${a.id}.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),e}if(l.localFolderId){const e=await this.requireNodeLocalFolder(a,l.localFolderId);l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}else{const e=await this.nodeAgentGateway.createLocalFolder(a,{name:"string"==typeof p.name?p.name:"Local folder",path:l.path,defaultImageId:c?.id,modelSelection:(u=p,u.modelSelection&&"object"==typeof u.modelSelection&&!Array.isArray(u.modelSelection)?u.modelSelection:{})});l={...l,localFolderId:e.id,ownerNodeId:e.nodeId,path:e.path},p=e}}var u;const g=await this.nodeAgentGateway.createInstance(a,{id:t.id,name:t.name,runtimeId:s,...c?{imageId:c.id,image:c}:{},projectId:n?.id,source:l,sourceSnapshot:p,config:t.config,modelEnv:n?this.modelEnvForProject(n):this.modelEnvForSourceSnapshot(l,p)});return{...on(g),registrationToken:g.registrationToken}}async updateControlledInstance(e,t){const n=hn.parse(t),s=await this.requireNodeInstance(e),i=this.requireNode(s.nodeId);return on(await this.nodeAgentGateway.updateInstance(i,e,n))}async deleteControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId),s=await this.nodeAgentGateway.deleteInstance(n,e);return Boolean(s.deleted)}async startControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return on(await this.nodeAgentGateway.startInstance(n,e,{modelEnv:this.modelEnvForInstance(t)}))}async stopControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return on(await this.nodeAgentGateway.stopInstance(n,e))}async restartControlledInstance(e){const t=await this.requireNodeInstance(e),n=this.requireNode(t.nodeId);return on(await this.nodeAgentGateway.restartInstance(n,e,{modelEnv:this.modelEnvForInstance(t)}))}async boardAsync(){return(await this.boardWithDiagnostics()).items}async boardWithDiagnostics(){const e=await this.listNodeRuntimesWithDiagnostics(),t=await this.listNodeInstancesWithDiagnostics();return this.instanceBoardReader.read({projects:this.listProjects(),images:this.images.list(),nodes:this.nodes.list(),runtimes:e.items,instances:t.items,nodeErrors:[...e.nodeErrors,...t.nodeErrors]})}async bootstrapAiSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus)return{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions};try{return U.parse(await this.instanceRequest(e,"/ai-sessions/state"))}catch(t){return this.logWarn({instanceId:e.id,error:en(t),errorCode:"AI_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live AI sessions"),{revision:0,lastEventAt:e.aiSessions.updatedAt,snapshot:e.aiSessions}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,n)=>({instanceId:e.id,aiSessions:t[n].snapshot,revision:t[n].revision,lastEventAt:t[n].lastEventAt}))}}async bootstrapAppSessionsFromInstances(){const e=await this.listNodeInstances(),t=await Promise.all(e.map(async e=>{if(!e.target.web||"online"!==e.connectionStatus&&"online"!==e.agentStatus){const e=Vt();return{revision:0,lastEventAt:e.updatedAt,snapshot:e}}try{return Ot.parse(await this.instanceRequest(e,"/apps/sessions/state"))}catch(t){this.logWarn({instanceId:e.id,error:en(t),errorCode:"APP_SESSION_LIVE_REFRESH_FAILED"},"control plane failed to refresh live app sessions");const n=Vt();return{revision:0,lastEventAt:n.updatedAt,snapshot:n}}}));return{updatedAt:(new Date).toISOString(),instances:e.map((e,n)=>({instanceId:e.id,appSessions:t[n].snapshot,revision:t[n].revision,lastEventAt:t[n].lastEventAt}))}}async listTriggers(){return this.controlPlaneTriggerService.listTriggers()}createTrigger(e){return this.controlPlaneTriggerService.createTrigger(e)}async deleteTrigger(e){return this.controlPlaneTriggerService.deleteTrigger(e)}async applyTrigger(e,t){return this.controlPlaneTriggerService.applyTrigger(e,t)}async listInstanceTriggers(e){return this.controlPlaneTriggerService.listInstanceTriggers(e)}async bindAiSessionTrigger(e,t,n){return this.controlPlaneTriggerService.bindAiSessionTrigger(e,t,n)}async unbindAiSessionTrigger(e,t,n){return this.controlPlaneTriggerService.unbindAiSessionTrigger(e,t,n)}async runInstanceTrigger(e,t,n={}){return this.controlPlaneTriggerService.runInstanceTrigger(e,t,n)}listChatSessions(){return this.chatSessionService.list()}createAppAccessToken(e){return this.appAccessService.createToken(e)}resolveAppAccessToken(e,t){return this.appAccessService.resolveToken(e,t)}createChatActionToken(e){return this.chatActionTokenService.create(e)}resolveChatActionToken(e,t){return this.chatActionTokenService.resolve(e,t)}pendingDecisionCallbackData(e,t){return this.chatActionTokenService.pendingDecisionCallbackData(e,t)}async appAccessProxyTarget(e,t,n=""){return this.appAccessService.proxyTarget(e,t,n)}listChatBridges(){return this.chatBridgeService.list()}requireChatBridge(e){return this.chatBridgeService.require(e)}createChatBridge(e){return this.chatBridgeService.create(e)}updateChatBridge(e,t){return this.chatBridgeService.update(e,t)}deleteChatBridge(e){return this.chatBridgeService.delete(e)}requireChatSession(e){return this.chatSessionService.require(e)}upsertChatSession(e){return this.chatSessionService.upsert(e)}async handleChatGatewayMessage(e){return this.chatSessionRuntime.handleChatGatewayMessage(e)}async handleChatGatewayAction(e){return this.chatSessionRuntime.handleChatGatewayAction(e)}async handleChatCommand(e,t){return this.chatSessionRuntime.handleChatCommand(e,t)}async listPendingRoutes(){const e=Yt(),t=[],n=await this.listAiSessions(),s=new Map(n.instances.map(e=>[e.instanceId,e.aiSessions]));for(const n of await this.listNodeInstances()){if("online"!==n.connectionStatus&&"online"!==n.agentStatus||!n.target.web)continue;const i=n.projectId?this.projects.get(n.projectId):void 0;for(const a of s.get(n.id)?.sessions||[]){if("waiting"!==a.status)continue;const s=Ye.parse({id:`${n.id}:ai:${a.id}`,instanceId:n.id,projectId:n.projectId,aiSessionId:a.id,providerSessionId:a.providerSessionId,result:a.summary||a.currentTool?.inputPreview||a.lastMessage||`${a.agent} is waiting for approval`,source:a.agent,kind:"approval",status:"pending",lastSeenAt:e});t.push({...s,project:i,instance:on(n)})}}return t}async resolveAiSessionApproval(e,t,n){const s=await this.requireControlledInstance(e,!0);return M.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/approval`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({decision:n})}))}async launchAppSession(e,t="terminal-tty",n={}){const s=await this.requireControlledInstance(e,!0);if("online"!==s.connectionStatus&&"online"!==s.agentStatus){const e=new Error(`Instance ${s.name} is still starting. Wait for it to connect before launching apps.`);throw Object.assign(e,{statusCode:409,code:"INSTANCE_NOT_CONNECTED"}),e}const i=await this.resolveAppLaunchOptions(s,n),a=await this.instanceRequest(s,"/apps/sessions",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appId:t,...i})});return await this.listAppSessions({refresh:!0}),a}async stopAppSession(e,t){const n=await this.requireControlledInstance(e,!0),s=await this.instanceRequest(n,`/apps/sessions/${encodeURIComponent(t)}/stop`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});return await this.listAppSessions({refresh:!0}),s}async renameAppSession(e,t,n){const s=await this.requireControlledInstance(e,!0),i=await this.instanceRequest(s,`/apps/sessions/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({title:n})});return await this.listAppSessions({refresh:!0}),i}async sendAiSessionMessage(e,t,n,s,i=[]){!function(e){if(e.reduce((e,t)=>e+t.size,0)<=f)return;const t=new Error("Images must be 41943040 bytes or less in total.");throw Object.assign(t,{statusCode:400,code:"AI_SESSION_ATTACHMENTS_TOO_LARGE"}),t}(i);const a=await this.requireControlledInstance(e,!0),r={message:n,...s?{mode:s}:{},...i.length?{attachments:i}:{}};return M.parse(await this.instanceRequest(a,`/ai-sessions/${encodeURIComponent(t)}/messages`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(r)}))}async aiSessionQueue(e,t){const n=await this.requireControlledInstance(e,!0);return R.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/queue`))}async steerAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return M.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}/steer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async retryAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return $.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}/retry`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async removeAiSessionQueuedMessage(e,t,n){const s=await this.requireControlledInstance(e,!0);return $.parse(await this.instanceRequest(s,`/ai-sessions/${encodeURIComponent(t)}/queue/${encodeURIComponent(n)}`,{method:"DELETE"}))}async interruptAiSession(e,t){const n=await this.requireControlledInstance(e,!0);return M.parse(await this.instanceRequest(n,`/ai-sessions/${encodeURIComponent(t)}/interrupt`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})}))}async proxyInstanceHttp(e,t,n={}){const s=await this.requireControlledInstance(e,!0);return this.controlledInstanceGateway.proxyHttp(s,t,n)}async proxyInstanceWebSocket(e,t,n,s,i={}){const a=await this.requireControlledInstance(e,!0);this.controlledInstanceGateway.proxyWebSocket(a,this.nodeAgentTransportFor(this.requireNode(a.nodeId)),t,n,s,i)}listConfigSyncPresets(){return[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}]}async syncInstanceConfig(e,t,n){const s=await this.requireControlledInstance(e,!0);if("export"===t&&"local-folder"!==s.source.type){const e=new Error("Exporting instance config is only available for local folder projects.");throw Object.assign(e,{statusCode:400,code:"CONFIG_SYNC_EXPORT_REQUIRES_LOCAL_PROJECT"}),e}const i=[{id:"codex",label:"Codex",projectRoot:".task-handoff/configs/codex",items:[{id:"config",type:"file",projectPath:"config.toml",containerPath:"${CODEX_HOME}/config.toml"},{id:"auth",type:"file",projectPath:"auth.json",containerPath:"${CODEX_HOME}/auth.json"},{id:"agents",type:"file",projectPath:"AGENTS.md",containerPath:"${CODEX_HOME}/AGENTS.md"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CODEX_HOME}/skills"}]},{id:"claude",label:"Claude",projectRoot:".task-handoff/configs/claude",items:[{id:"claude-json",type:"file",projectPath:".claude.json",containerPath:"${HOME}/.claude.json"},{id:"settings",type:"file",projectPath:"settings.json",containerPath:"${CLAUDE_HOME}/settings.json"},{id:"claude-md",type:"file",projectPath:"CLAUDE.md",containerPath:"${CLAUDE_HOME}/CLAUDE.md"},{id:"commands",type:"dir",projectPath:"commands",containerPath:"${CLAUDE_HOME}/commands"},{id:"agents",type:"dir",projectPath:"agents",containerPath:"${CLAUDE_HOME}/agents"},{id:"skills",type:"dir",projectPath:"skills",containerPath:"${CLAUDE_HOME}/skills"}]},{id:"browser",label:"Browser",projectRoot:".task-handoff/configs/browser",items:[{id:"chromium-profile",type:"dir",projectPath:"chromium",containerPath:"${HOME}/.config/chromium"}]}].find(e=>e.id===n);if(!i){const e=new Error(`Config sync preset ${n} was not found.`);throw Object.assign(e,{statusCode:404,code:"CONFIG_SYNC_PRESET_NOT_FOUND"}),e}return this.instanceRequest(s,`/config-sync/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({preset:i})})}async instanceRequest(e,t,n={}){return this.controlledInstanceGateway.request(e,t,n)}async reportInstanceHeartbeat(e,t){await this.controlledInstanceGateway.reportHeartbeat(e,t,this.nodeAgentTransport)}async nodeAgentFetch(e,t,n={}){return this.nodeAgentTransportFor(e).request(e,t,n)}nodeAgentTransportFor(e){if("reverse-wss"===e.connectionMode){if(!this.nodeAgentTransport){const e=new Error("Reverse node agent transport is not available.");throw Object.assign(e,{statusCode:503,code:"NODE_AGENT_REVERSE_TRANSPORT_UNAVAILABLE"}),e}return this.nodeAgentTransport}return this.directNodeAgentTransport()}directNodeAgentTransport(){return{request:async(e,t,n={})=>{const s=e.controlEndpoint||e.endpoint;if(!s){const e=new Error("Node agent direct HTTP mode requires an endpoint.");throw Object.assign(e,{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"}),e}const i=n.method||"GET",a="string"==typeof n.body||n.body instanceof Buffer?n.body:void 0===n.body||null===n.body?void 0:String(n.body),r=qs(e,{method:i,pathWithQuery:`/api/node-agent${t}`,body:a||""});return this.fetchNodeAgentEndpoint(s,t,{...n,body:a,headers:{...n.headers||{},...r}})},proxyWebSocket:(e,t,n,s,i={})=>{const a=e.controlEndpoint||e.endpoint;if(!a)throw Object.assign(new Error("Node agent direct HTTP mode requires an endpoint."),{statusCode:400,code:"NODE_AGENT_ENDPOINT_REQUIRED"});const r=Os(a),o=`/api/node-agent${n}`,d=qs(e,{method:"GET",pathWithQuery:o});if(r){xs(r);const e=Ds(r,n,s,{...i,...d});return e.on("open",()=>{t.on("message",(t,n)=>e.readyState===c.WebSocket.OPEN&&e.send(n?t:String(t))),t.on("close",()=>e.close()),t.on("error",()=>e.close())}),e.on("message",(e,n)=>{t.readyState===c.WebSocket.OPEN&&t.send(n?e:e.toString())}),e.on("close",()=>t.close()),void e.on("error",()=>t.close(1011,"Instance websocket proxy failed."))}const l=new URL(o,a.replace(/\/$/,""));l.protocol="https:"===l.protocol?"wss:":"ws:";const p=s?new c.WebSocket(l.toString(),s,{headers:{...i,...d}}):new c.WebSocket(l.toString(),{headers:{...i,...d}});p.on("open",()=>{t.on("message",(e,t)=>p.readyState===c.WebSocket.OPEN&&p.send(t?e:String(e))),t.on("close",()=>p.close()),t.on("error",()=>p.close())}),p.on("message",(e,n)=>{t.readyState===c.WebSocket.OPEN&&t.send(n?e:e.toString())}),p.on("close",()=>t.close()),p.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=de.parse(await this.instanceRequest(e,"/triggers"));return Ge.parse({...e,triggers:Ts(t)})}catch{return e}}async requireNodeInstance(e){for(const t of this.listNodes())try{const n=(await this.nodeAgentGateway.listInstances(t)).find(t=>t.id===e);if(n)return n}catch{}tn("CONTROLLED_INSTANCE_NOT_FOUND",`Controlled instance ${e} was not found.`)}async requireNodeRuntimeOnNode(e,t){const n=(await this.listNodeRuntimes(e)).find(e=>e.id===t);return n||tn("NODE_RUNTIME_NOT_FOUND",`Runtime ${t} was not found on node ${e}.`),n}async requireNodeLocalFolder(e,t){return this.nodeAgentGateway.requireLocalFolder(e,t)}async resolveAppLaunchOptions(e,t){const n="string"==typeof t.cwdFolderId?t.cwdFolderId.trim():"",s={...t};if(delete s.cwdFolderId,!n)return s;const i=this.requireNode(e.nodeId),a=await this.requireNodeLocalFolder(i,n);if(a.nodeId!==e.nodeId){const t=new Error(`Local folder ${a.id} belongs to node ${a.nodeId}, not ${e.nodeId}.`);throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_INSTANCE_NODE"}),t}const r=await this.requireNodeRuntimeOnNode(e.nodeId,e.runtimeId);return{...s,cwd:this.appLaunchCwdForFolder(e,a,r)}}appLaunchCwdForFolder(e,t,s){if("local"===s.type)return n.resolve(t.path);if("local-folder"!==e.source.type){const e=new Error("App cwd folder selection is only supported for local-folder instances.");throw Object.assign(e,{statusCode:400,code:"APP_CWD_REQUIRES_LOCAL_FOLDER_SOURCE"}),e}const i=n.resolve(e.source.path),a=n.resolve(t.path),r=n.relative(i,a);if(r.startsWith("..")||n.isAbsolute(r)){const n=new Error(`Local folder ${t.path} is outside the instance workspace ${e.source.path}.`);throw Object.assign(n,{statusCode:400,code:"APP_CWD_OUTSIDE_WORKSPACE"}),n}const o=e.runtime.workspacePath||e.workspace.path||rn(e.source).path||"/workspace";return r?n.posix.join(o,...r.split(n.sep)):o}modelEnvForInstance(e){return function(e,t){const n=vs(e.sourceSnapshot);return As(Ne.parse({id:e.projectId||`project_${e.id}`,name:"string"==typeof e.sourceSnapshot.name?e.sourceSnapshot.name:e.name,source:e.source,defaultImageId:e.imageId,defaultNodeId:e.nodeId,defaultRuntimeId:e.runtimeId,modelSelection:n,workspacePolicy:rn(e.source),labels:{},createdAt:e.createdAt,updatedAt:e.updatedAt}),t)}(e,this.listAllModels())}modelEnvForSourceSnapshot(e,t){return function(e,t,n){const s=vs(t),i=(new Date).toISOString();return As(Ne.parse({id:"string"==typeof t.id?t.id:`source_${it("tmp")}`,name:"string"==typeof t.name?t.name:"Source",source:e,defaultImageId:"string"==typeof t.defaultImageId?t.defaultImageId:void 0,modelSelection:s,workspacePolicy:rn(e),labels:{},createdAt:i,updatedAt:i}),n)}(e,t,this.listAllModels())}nextModelOrder(){return this.models.list().reduce((e,t)=>Math.max(e,t.order),0)+100}validateProjectModelSelection(e){const t=new Map(this.listAllModels().map(e=>[e.id,e])),n={},s=e.codexModelId?.trim(),i=e.claudeModelId?.trim();if(s){const e=t.get(s);if(e||tn("MODEL_NOT_FOUND",`Model ${s} was not found.`),"codex"!==e.app){const e=new Error(`Model ${s} is not a Codex model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}n.codexModelId=s}if(i){const e=t.get(i);if(e||tn("MODEL_NOT_FOUND",`Model ${i} was not found.`),"claude"!==e.app){const e=new Error(`Model ${i} is not a Claude model.`);throw Object.assign(e,{statusCode:400,code:"MODEL_APP_MISMATCH"}),e}n.claudeModelId=i}return n}modelEnvForProject(e){return As(e,this.listAllModels())}requireProject(e){const t=this.projects.get(e);return t||tn("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"),Ne.parse(t)}return Ne.parse(e)}(e);return t!==e&&this.projects.put(t),t}requireModel(e,t=!1){const n=this.models.get(e);n||tn("MODEL_NOT_FOUND",`Model ${e} was not found.`);const s=this.normalizeModelRecord(n);return t?s:dn(s)}requireModelSecret(e){const t=this.models.get(e);return t||tn("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,ze.parse(t)}return ze.parse(e)}(e);return t!==e&&this.models.put(t),t}requireImage(e){const t=this.images.get(e);return t||tn("IMAGE_NOT_FOUND",`Image ${e} was not found.`),t}requireNode(e){const t=this.nodes.get(e);return t||tn("NODE_NOT_FOUND",`Node ${e} was not found.`),t}requirePublicNode(e){return an(this.requireNode(e))}async requireControlledInstance(e,t=!1){const n=await this.requireNodeInstance(e);return t?n:on(n)}}function Us(e){return g(String(e),"escape").replace(/\n+$/,"")}function Bs(e){const[t,...n]=String(e).split("\n");return[`*${Us(t)}*`,...n.map(Us)].join("\n")}function Fs(e){return String(e).replace(/\\([_*\[\]()~`>#+\-=|{}.!\\])/g,"$1")}async function Hs(e,t,n,s,i={}){if(t.token)return Ws(n=>e(`https://api.telegram.org/bot${t.token}/sendMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),"sendMessage",s,{chat_id:n,...i.replyToMessageId?{reply_to_message_id:i.replyToMessageId,allow_sending_without_reply:!0}:{},...i.replyMarkup?{reply_markup:i.replyMarkup}:{}},i)}async function Gs(e,t,n){if(!t.token||!n)throw new Error("Telegram file id is required.");const s=await e(`https://api.telegram.org/bot${t.token}/getFile`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({file_id:n})}),i=await s.json().catch(()=>({}));if(!s.ok||!1===i.ok||!i.result?.file_path)throw new Error(i.description||`Telegram getFile failed with HTTP ${s.status}`);return`https://api.telegram.org/file/bot${t.token}/${i.result.file_path}`}async function Ws(e,t,n,s,i){const a=i.parseMode||"MarkdownV2",r=i.rawMarkdownV2?String(n):Us(n),o=await e({...s,text:r,disable_web_page_preview:!0,parse_mode:a}),d=await o.json().catch(()=>({}));if(o.ok&&!1!==d.ok)return d.result;if("editMessageText"===t&&Vs(d.description))return;if(i.rawMarkdownV2&&"MarkdownV2"===a){const i=await e({...s,text:Fs(n),disable_web_page_preview:!0,parse_mode:"Markdown"}),a=await i.json().catch(()=>({}));if(i.ok&&!1!==a.ok)return a.result;if("editMessageText"===t&&Vs(a.description))return}const c=await e({...s,text:i.rawMarkdownV2?Fs(n):String(n),disable_web_page_preview:!0}),l=await c.json().catch(()=>({}));if(c.ok&&!1!==l.ok)return l.result;if("editMessageText"!==t||!Vs(l.description))throw new Error(l.description||d.description||`Telegram ${t} failed with HTTP ${c.status}`)}function Vs(e){return"string"==typeof e&&e.toLowerCase().includes("message is not modified")}function Ks(e){return e instanceof Error?e.message:String(e)}function Js(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 Qs{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||Js,this.retryAfterMs=e.retryAfterMs,this.isMessageTooLong=e.isMessageTooLong,this.onLog=e.onLog}remember(e,t,n,s,i,a){const r=this.entries.get(e);return r&&(clearTimeout(r.timer),r.timer=void 0,r.pendingText=void 0),this.entries.set(e,{lastText:n,lastUpdateAt:Date.now(),messageId:t,route:s,options:i,renderOptions:a}),r}rekey(e,t){if(e===t)return this.entries.has(e);const n=this.entries.get(e);if(!n||this.entries.has(t))return!1;if(clearTimeout(n.timer),n.timer=void 0,this.entries.delete(e),this.entries.set(t,n),n.pendingText){const e=n.rateLimitedUntil?Math.max(0,n.rateLimitedUntil-Date.now()):Math.max(0,this.updateIntervalMs-(Date.now()-n.lastUpdateAt));this.scheduleFlush(t,e)}return!0}scheduleFlush(e,t){const n=this.entries.get(e);n&&!n.timer&&(n.timer=setTimeout(()=>{n.timer=void 0;const t=n.pendingText;n.pendingText=void 0,t&&this.applyUpdate(e,t,n.route,n.options)},t))}markRateLimited(e,t,n,s){const i=this.entries.get(e);i&&(i.rateLimitedUntil=Date.now()+n,i.pendingText=t,i.route=s||i.route,this.scheduleFlush(e,n))}async applyUpdate(e,t,n,s){let i=this.entries.get(e);const a=i,r=n||i?.route,o=s||i?.options;try{if(i&&(i.route=r,i.options=o),i?.pending&&await i.pending,i=this.entries.get(e),a&&i!==a)return!1;if(i?.messageId){if(i.lastText===t)return!0;const n=Date.now()-(i.lastUpdateAt||0);return n<this.updateIntervalMs?(i.pendingText=t,i.route=r||i.route,i.options=o||i.options,this.scheduleFlush(e,this.updateIntervalMs-n),!0):(await this.edit(i.messageId,t,r||i.route,o),i.lastText=t,i.lastUpdateAt=Date.now(),i.rateLimitedUntil=void 0,!0)}const n={lastText:t,lastUpdateAt:Date.now(),route:r,options:o};return n.pending=this.send(t,r,o).then(e=>{const t=this.messageIdFromResult(e);t&&(n.messageId=t)}).finally(()=>{n.pending=void 0}),this.entries.set(e,n),await n.pending,this.entries.get(e)===n&&(n.rateLimitedUntil=void 0,Boolean(n.messageId))}catch(n){const s=this.retryAfterMs?.(n);return s&&this.markRateLimited(e,t,s,r),this.onLog?.(`Telegram progress update skipped: ${Ks(n)}`),!1}}update(e,t,n,s){const i=String(t||"").trim();if(!i)return;const a=this.entries.get(e);if(a&&(a.route=n||a.route,a.options=s||a.options),a?.lastText===i&&!a.pending&&!a.pendingText)return;const r=Date.now();if(a?.rateLimitedUntil){if(a.rateLimitedUntil>r)return a.pendingText=i,void this.scheduleFlush(e,a.rateLimitedUntil-r);a.rateLimitedUntil=void 0}if(a?.pending&&!a.messageId)return a.pendingText=i,void this.scheduleFlush(e,this.updateIntervalMs);if(!a||!a.messageId)return void this.applyUpdate(e,i,n,s);const o=Date.now()-(a.lastUpdateAt||0);if(o>=this.updateIntervalMs)return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,void this.applyUpdate(e,i,n||a.route,s||a.options);a.pendingText=i,this.scheduleFlush(e,this.updateIntervalMs-o)}async finish(e,t,n,s,i){const a=this.entries.get(e);if(!a)return!1;try{return clearTimeout(a.timer),a.timer=void 0,a.pendingText=void 0,a.pending&&await a.pending,this.entries.delete(e),!!a.messageId&&(a.lastText===t||await this.edit(a.messageId,t,n||a.route,s||a.options,i||a.renderOptions),!0)}catch(t){return this.entries.delete(e),this.isMessageTooLong?.(t)?this.onLog?.("Telegram progress finish too long; sending result as multiple messages"):this.onLog?.(`Telegram progress finish skipped: ${Ks(t)}`),!1}}finishPayload(e,t,n,s,i){return this.finish(e,`${t.title}\n\n${t.body}`,n,s,i)}async delete(e,t){const n=this.entries.get(e);if(!n)return!1;try{return clearTimeout(n.timer),n.timer=void 0,n.pendingText=void 0,n.pending&&await n.pending,this.entries.delete(e),!(!this.deleteMessage||!n.messageId)&&(await this.deleteMessage(n.messageId,t||n.route),!0)}catch(t){return this.entries.delete(e),this.onLog?.(`Telegram progress delete skipped: ${Ks(t)}`),!1}}async flush(e){const t=this.entries.get(e);if(t?.pending&&await t.pending,t?.timer){clearTimeout(t.timer),t.timer=void 0;const n=t.pendingText;t.pendingText=void 0,n&&await this.applyUpdate(e,n,t.route,t.options)}}async wait(e){await function(e){return new Promise(t=>setTimeout(t,e))}(e)}}const Ys=20971520;function Xs(e){const t=[],n=[...e.photo||[]].filter(e=>e.file_id).sort((e,t)=>Number(t.file_size||t.width||0)-Number(e.file_size||e.width||0))[0];return n?.file_id&&t.push({fileId:n.file_id,fileName:"telegram-photo.jpg",fileSize:n.file_size}),e.document?.file_id&&String(e.document.mime_type||"").startsWith("image/")&&t.push({fileId:e.document.file_id,fileName:e.document.file_name,fileSize:e.document.file_size}),t}async function Zs(e,t,s="telegram-image",i){if(i&&i>Ys)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 c=Number(o.headers.get("content-length")||0);if(c>Ys)throw new Error(`image is too large: ${c} bytes`);const l=n.extname(s)||".jpg";let p=l;try{p=n.extname(new URL(r).pathname)||l}catch{p=l}const u=n.join(d.tmpdir(),"task-handoff-images");await h.mkdir(u,{recursive:!0});const g=p.replace(/[^.\w-]/g,"")||".jpg",m=n.join(u,`telegram-${Date.now()}-${Math.random().toString(16).slice(2)}${g}`),f=Buffer.from(await o.arrayBuffer());if(f.length>Ys)throw new Error(`image is too large: ${f.length} bytes`);return await h.writeFile(m,f),m}class ei{store;constructor(e){this.store=new Qs({updateIntervalMs:e.updateIntervalMs,send:(t,n,s)=>e.send(e.requireBridge(n?.bridgeId||""),n?.chatId||"",Bs(t),{rawMarkdownV2:!0,replyMarkup:xi(s)}),edit:(t,n,s,i)=>e.edit(e.requireBridge(s?.bridgeId||""),s?.chatId||"",t,Bs(n),{rawMarkdownV2:!0,replyMarkup:xi(i)}),delete:(t,n)=>e.deleteMessage(e.requireBridge(n?.bridgeId||""),n?.chatId||"",t),onLog:e.onLog})}get entries(){return this.store.entries}remember(e,t,n,s){return this.store.remember(e,t,n,s)}rekey(e,t){return this.store.rekey(e,t)}delete(e){return this.store.delete(e)}async applyUpdate(e){return this.store.applyUpdate(e.key,e.text,{bridgeId:e.bridge.id,chatId:e.chatId},{actionRows:e.actionRows})}}function ti(e,t){if("string"==typeof e&&e.trim())return e.trim();si(`Chat action token is missing ${t}.`)}function ni(e){if("allow"===e||"deny"===e||"skip"===e)return e;si("Chat action token has an invalid decision.")}function si(e){const t=new Error(e);throw Object.assign(t,{statusCode:400,code:"CHAT_ACTION_TOKEN_INVALID"}),t}class ii{entries=new Map;fetchImpl;constructor(e){this.fetchImpl=e}clear(){this.clearEntries(()=>!0)}clearBridge(e){this.clearEntries(t=>t.bridgeId===e)}clearEntries(e){for(const[t,n]of this.entries)e(n)&&(n.timer&&clearTimeout(n.timer),n.cancelPending?.(new Error("DingDing progress update was cancelled.")),this.entries.delete(t))}async applyUpdate(e){const t=e.dingdingRuntime;if(!t)return!1;const n=Oi(e.replyMarkup);let s=this.entries.get(e.key);if(s?.pending&&await s.pending,s=this.entries.get(e.key),s?.outTrackId){if(s.lastText===e.text&&s.lastActionsFingerprint===n)return!0;const t=Date.now()-(s.lastUpdateAt||0);return t<1e3?(s.pendingText=e.text,s.pendingReplyMarkup=e.replyMarkup,s.timer||(s.pending=new Promise((n,i)=>{s.cancelPending=i,s.timer=setTimeout(()=>{s.timer=void 0,s.cancelPending=void 0;const t=s.pendingText,a=s.pendingReplyMarkup;s.pendingText=void 0,s.pendingReplyMarkup=void 0,t?this.updateExistingEntry(e,s,t,a,Oi(a)).then(n,i):n(void 0)},1e3-t)}).finally(()=>{s.pending=void 0,s.timer=void 0,s.cancelPending=void 0})),await s.pending,this.entries.has(e.key)):(await this.updateExistingEntry(e,s,e.text,e.replyMarkup,n),!0)}const i={bridgeId:e.bridge.id,lastText:e.text,lastActionsFingerprint:n,lastUpdateAt:Date.now()};return i.pending=Si({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:t,chatId:e.chatId,text:e.text,replyMarkup:e.replyMarkup,sessionWebhook:t.chatWebhooks.get(e.chatId)||Ni(e.bridge.settings.sessionWebhook),senderId:t.senderIds.get(e.chatId)||Ni(e.bridge.settings.senderId),title:"TaskHandoff 执行中",step:"progress",forceCard:!0}).then(e=>{e?.outTrackId&&(i.outTrackId=e.outTrackId),e?.delivered&&(i.delivered=!0,i.lastUpdateAt=Date.now())}).catch(t=>{throw this.entries.delete(e.key),t}).finally(()=>{i.pending=void 0}),this.entries.set(e.key,i),await i.pending,Boolean(i.delivered)}async updateExistingEntry(e,t,n,s,i){const a=e.dingdingRuntime;a&&t.outTrackId&&(await async function(e){await Ai(e.fetchImpl,e.bridge,e.runtime,"PUT","/v1.0/card/instances",{outTrackId:e.outTrackId,cardData:{cardParamMap:ci({type:"actions",title:e.title,description:e.text,list:Vn(e.replyMarkup),biz_step:e.step,error_msg:""})},cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},userIdType:Number(e.bridge.settings.cardUserIdType||1)})}({fetchImpl:this.fetchImpl,bridge:e.bridge,runtime:a,outTrackId:t.outTrackId,text:n,replyMarkup:s,title:"TaskHandoff 执行中",step:"progress"}),t.lastText=n,t.lastActionsFingerprint=i,t.lastUpdateAt=Date.now())}}function ai(e){const{fetchImpl:t,bridge:n,dingdingRuntime:s}=e;return"telegram"===n.channel?{bridge:n,send:async(e,s,i={})=>{const a=await Hs(t,n,e,s,{replyMarkup:i.replyMarkup,replyToMessageId:i.replyToMessageId,rawMarkdownV2:i.rawMarkdownV2,parseMode:i.parseMode});return{provider:"telegram",raw:a,messageId:Number(_i(a).message_id)||void 0}}}:"dingding"===n.channel?{bridge:n,send:async(e,i,a={})=>{const r=a.sessionWebhook||s?.chatWebhooks.get(e)||Ni(n.settings.sessionWebhook);if(a.replyMarkup&&s){const o=await Si({fetchImpl:t,bridge:n,runtime:s,chatId:e,text:i,replyMarkup:a.replyMarkup,sessionWebhook:r,senderId:a.senderId||s.senderIds.get(e)||Ni(n.settings.senderId)});if(o?.delivered)return{provider:"dingding",interactionId:o.outTrackId}}if(r)return await yi(t,r,i,s?.onLog),{provider:"dingding"}}}:"wechat"===n.channel?{bridge:n,send:async(e,s,i={})=>{const a=i.contextToken||Ni(n.settings.contextToken);if(a)return await async function(e,t,n,s,i){const a=Ni(t.settings.baseUrl)||"https://ilinkai.weixin.qq.com",o=await e(`${a}/ilink/bot/sendmessage`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${t.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({msg:{from_user_id:"",to_user_id:n,client_id:r.randomUUID(),message_type:2,message_state:2,context_token:s,item_list:[{type:1,text_item:{text:i}}]},base_info:{channel_version:"1.0.2"}})}),d=await o.json().catch(()=>({}));if(!o.ok||d.errcode)throw new Error(Ni(d.errmsg)||`WeChat send failed with HTTP ${o.status}`)}(t,n,e,a,s),{provider:"wechat"}}}:{bridge:n,send:async()=>{}}}async function ri(e){return(await async function(e,t,n,s){const i=[];for(const a of Xs(n))try{const n=await Zs({getFileLink:n=>Gs(e,t,n),fetchFile:e},a.fileId,a.fileName,a.fileSize);i.push(n)}catch(e){s?.({fileId:a.fileId,fileName:a.fileName,error:e})}return i}(e.fetchImpl,e.bridge,e.message,e.onImageDownloadError)).map((e,s)=>{const i=t.readFileSync(e);return{id:`tg_${r.randomUUID().replace(/-/g,"").slice(0,24)}`,kind:"image",name:n.basename(e)||`telegram-image-${s+1}`,mime:oi(e),size:i.length,data:i.toString("base64")}})}function oi(e){const t=n.extname(e).toLowerCase();return".png"===t?"image/png":".webp"===t?"image/webp":".gif"===t?"image/gif":".bmp"===t?"image/bmp":"image/jpeg"}function di(e){return String(e||"").split(/\r?\n/,1)[0].replace(/^#+\s*/,"").trim().slice(0,20)||"消息通知"}function ci(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,"string"==typeof t?t:JSON.stringify(t)]))}function li(e){return`dtv1.card//IM_GROUP.${e}`}function pi(e){return`dtv1.card//IM_ROBOT.${e}`}function ui(e,t){return Ni(t.biz_conversation_id).trim()||Ni(e.spaceId).replace(/^dtv1\.card\/\/(?:IM_GROUP|IM_SINGLE|IM_ROBOT)\./,"")}function gi(e,t={}){const n=Ni(e).trim().toUpperCase();return"2"===n||"IM_GROUP"===n?"IM_GROUP":"1"===n||"IM_SINGLE"===n?t.robotMessage?"IM_ROBOT":"IM_SINGLE":"IM_ROBOT"===n?"IM_ROBOT":void 0}function hi(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 mi(e,t,n,s,i){const a=ui(s,i);return{cardUpdateOptions:{updateCardDataByKey:!0,updatePrivateDataByKey:!1},cardData:{cardParamMap:ci({type:"actions",title:"TaskHandoff",description:e,list:Vn(t),biz_out_track_id:s.outTrackId||i.biz_out_track_id||"",biz_conversation_id:a,biz_sender_id:i.biz_sender_id||s.userId||"",biz_session_webhook:i.biz_session_webhook||"",biz_conversation_type:i.biz_conversation_type||"",biz_step:n,error_msg:""})}}}function fi(e,t,n){const s=[];return Ii(s,n),Ii(s,t),Ii(s,e),s.find(e=>hi(e))||""}function Ii(e,t){for(const n of["actionIdList","actionIds"]){const s=t[n];Array.isArray(s)&&e.push(...s.map(e=>Ni(e)).filter(Boolean))}for(const n of["actionId","action_id","id","actionValue"]){const s=Ni(t[n]);s&&e.push(s)}}async function yi(e,t,n,s){s?.("info",{target:"sessionWebhook",hasSessionWebhook:Boolean(t),textPreview:Ei(n)},"dingding webhook send requested");const i=await e(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({msgtype:"markdown",markdown:{title:di(n),text:n}})}),a=await i.json().catch(()=>{});if(s?.(i.ok?"info":"warn",{target:"sessionWebhook",status:i.status,response:Ti(a)},"dingding webhook send completed"),!i.ok)throw new Error(`DingDing webhook failed with HTTP ${i.status}`)}function bi(e){const t=Array.isArray(e.item_list)?e.item_list:[];for(const e of t){const t=_i(e);if(1!==t.type)continue;const n=Ni(_i(t.text_item).text).trim();if(n)return n}return""}function wi(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}async function Si(e){const t=Vn(e.replyMarkup);if(!t.length&&!e.forceCard)return e.sessionWebhook&&await yi(e.fetchImpl,e.sessionWebhook,e.text,e.runtime.onLog),e.sessionWebhook?{delivered:!0}:void 0;const n=e.bridge.settings||{},s=Ni(n.robotCode),i=Ni(n.cardTemplateId)||"13fc6717-12e4-43ed-8533-111a310d4995.schema",a=Ni(n.cardCallbackRouteKey)||"bi_workflow_ticket",r=e.senderId||e.runtime.senderIds.get(e.chatId)||Ni(n.senderId),o=(d=e.runtime,c=e.chatId,d.conversationTypes?.get(c)||"IM_GROUP");var d,c;if(!s||!i||!r){if(e.sessionWebhook&&!e.forceCard)return await yi(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=ci({type:"actions",title:e.title||di(e.text),description:e.text,list:t,biz_out_track_id:l,biz_step:e.step||"task_handoff_actions",biz_conversation_id:e.chatId,biz_sender_id:r,biz_session_webhook:e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)||"",biz_conversation_type:o,error_msg:""}),u=function(e,t,n,s){return"IM_SINGLE"===e?{openSpaceId:(i=t,`dtv1.card//IM_SINGLE.${i}`),imSingleOpenSpaceModel:{},imSingleOpenDeliverModel:{extension:{}}}:"IM_ROBOT"===e?{openSpaceId:pi(n),imRobotOpenSpaceModel:{supportForward:!1},imRobotOpenDeliverModel:{extension:{},robotCode:s,spaceType:"IM_ROBOT"}}:{openSpaceId:li(t),imGroupOpenSpaceModel:{supportForward:!1},imGroupOpenDeliverModel:{extension:{},robotCode:s}};var i}(o,e.chatId,r,s);return e.runtime.onLog?.("info",{target:"card",chatId:e.chatId,senderId:r,conversationType:o,openSpaceId:u.openSpaceId,hasSessionWebhook:Boolean(e.sessionWebhook||e.runtime.chatWebhooks.get(e.chatId)),hasReplyMarkup:Boolean(e.replyMarkup),actionCount:t.length,textPreview:Ei(e.text)},"dingding card send requested"),await Ai(e.fetchImpl,e.bridge,e.runtime,"POST","/v1.0/card/instances/createAndDeliver",{userId:r,cardTemplateId:i,outTrackId:l,callbackType:"STREAM",callbackRouteKey:a,cardData:{cardParamMap:p},userIdType:Number(n.cardUserIdType||1),...u}),{delivered:!0,outTrackId:l}}async function Ai(e,t,n,s,i,a){const r=await async function(e,t,n){if(n.accessToken&&n.accessToken.expiresAt>Date.now()+6e4)return n.accessToken.value;const s=Ni(t.settings.clientSecret);if(!t.token||!s)throw new Error("DingDing client id/secret is not configured.");const i=await e("https://api.dingtalk.com/v1.0/oauth2/accessToken",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({appKey:t.token,appSecret:s})}),a=await i.json().catch(()=>({}));if(!i.ok||!a.accessToken)throw new Error(`DingDing access token failed with HTTP ${i.status}`);const r=Number(a.expireIn||7200);return n.accessToken={value:a.accessToken,expiresAt:Date.now()+1e3*Math.max(60,r-120)},n.accessToken.value}(e,t,n);n.onLog?.("info",{method:s,path:i,body:vi(a)},"dingding api request");const o=await e(`https://api.dingtalk.com${i}`,{method:s,headers:{"content-type":"application/json","x-acs-dingtalk-access-token":r},body:JSON.stringify(a)}),d=await o.json().catch(()=>{});if(n.onLog?.(o.ok?"info":"warn",{method:s,path:i,status:o.status,response:Ti(d)},"dingding api response"),!o.ok)throw new Error(`DingDing API failed with HTTP ${o.status}`);const c=_i(d),l=c.code??c.errcode,p=c.success;if(void 0!==l&&"0"!==String(l)||!1===p)throw new Error(Ni(c.message||c.errmsg)||`DingDing API returned ${String(l||"failure")}`);const u=function(e){const t=_i(_i(e).result).deliverResults;if(!Array.isArray(t))return"";const n=t.find(e=>!1===_i(e).success);if(!n)return"";const s=_i(n),i=Ni(s.spaceId),a=Ni(s.errorMsg||s.message||s.errmsg)||"delivery failed";return i?`DingDing card delivery failed for ${i}: ${a}`:`DingDing card delivery failed: ${a}`}(d);if(u)throw new Error(u);return o}function vi(e){const t=_i(e),n=_i(t.cardData),s=_i(n.cardParamMap);return{openSpaceId:Ni(t.openSpaceId),outTrackId:Ni(t.outTrackId),userId:Ni(t.userId),userIdType:t.userIdType,callbackType:Ni(t.callbackType),callbackRouteKey:Ni(t.callbackRouteKey),hasImGroupTarget:Boolean(t.imGroupOpenSpaceModel||t.imGroupOpenDeliverModel),hasImSingleTarget:Boolean(t.imSingleOpenSpaceModel||t.imSingleOpenDeliverModel),hasImRobotTarget:Boolean(t.imRobotOpenSpaceModel||t.imRobotOpenDeliverModel),imGroupOpenDeliverModel:ki(t.imGroupOpenDeliverModel),imSingleOpenDeliverModel:ki(t.imSingleOpenDeliverModel),imRobotOpenDeliverModel:ki(t.imRobotOpenDeliverModel),bizConversationId:Ni(s.biz_conversation_id),bizConversationType:Ni(s.biz_conversation_type),bizSenderId:Ni(s.biz_sender_id),hasBizSessionWebhook:Boolean(Ni(s.biz_session_webhook))}}function ki(e){const t=_i(e);if(Object.keys(t).length)return{hasExtension:Boolean(t.extension),hasRobotCode:Boolean(Ni(t.robotCode)),spaceType:Ni(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 Ti(e){const t=_i(e),n=_i(t.result);return{code:t.code??t.errcode,message:Ei(t.message||t.errmsg,240),success:t.success,requestId:t.requestId,result:Object.keys(n).length?n:void 0}}function Ei(e,t=160){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function _i(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Ci(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:"string"==typeof e&&e.trim()?zi(e):{}}function Ni(e){return"string"==typeof e?e:""}function zi(e){try{return _i(JSON.parse(String(e||"{}")))}catch{return{}}}function Oi(e){return JSON.stringify(Vn(e))}function xi(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Hn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Di(e){return Buffer.from(e,"base64url").toString("utf8")}function Ri(e){return new u.DWClient({clientId:e.clientId,clientSecret:e.clientSecret,ua:"task-handoff-control-plane",keepAlive:!1})}class ji{service;fetchImpl;createDingdingClient;logger;bridgeTimers=new Map;dingdingBridges=new Map;bridgeErrors=new Map;telegramOffsets=new Map;seenTelegramUpdates=new Set;wechatCursors=new Map;pendingTimer;announcedPending=new Set;deliveredAiSessionFingerprints=new Map;aiSessionCancelTokens=new Map;aiSessionQueueActionTokens=new Map;telegramMessageAggregates=new Map;telegramMessageAggregateEndTokens=new Map;telegramProgressMessageTargets=new Map;telegramProgress;dingdingProgress;stopAiSessionListener;constructor(e,t=fetch,n={}){var s;this.service=e,this.fetchImpl=t,this.logger=(s=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(s||"").trim().toLowerCase())?n.logger:void 0),this.telegramProgress=new ei({updateIntervalMs:n.telegramProgressUpdateIntervalMs,requireBridge:e=>this.service.requireChatBridge(e),send:(e,t,n,s={})=>this.sendTelegramMessage(e,t,n,s),edit:(e,t,n,s,i={})=>this.editTelegramMessage(e,t,n,s,i),deleteMessage:(e,t,n)=>this.deleteTelegramMessage(e,t,n),onLog:e=>this.logWarn({message:e},"telegram progress store warning")}),this.dingdingProgress=new ii(this.fetchImpl),this.stopAiSessionListener=n.aiSessions?.onSnapshot(e=>{this.deliverAiSessionSnapshot(e.instanceId,e.aiSessions).catch(t=>{this.logWarn({instanceId:e.instanceId,snapshotRevision:e.revision,error:Gi(t)},"chat ai session snapshot delivery failed")})}),this.createDingdingClient=n.createDingdingClient||Ri}logInfo(e,t){this.logger?.info?.({component:"control-plane-chat-gateway",...e},t)}logWarn(e,t){this.logger?.warn?.({component:"control-plane-chat-gateway",...e},t)}updateChatBridge(e,t){this.service.updateChatBridge?.(e,t)}startEnabled(){this.logAiSessionDelivery({stage:"runtime-start-enabled",bridgeCount:this.service.listChatBridges().length,enabledBridgeCount:this.service.listChatBridges().filter(e=>e.enabled).length});for(const e of this.service.listChatBridges())e.enabled&&this.startBridge(e.id);this.startPendingPolling()}startBridge(e){if(this.bridgeTimers.has(e)||this.dingdingBridges.has(e))return this.logAiSessionDelivery({stage:"bridge-start-skipped-running",bridgeId:e}),this.startPendingPolling(),this.status();const t=this.service.requireChatBridge(e);return this.logAiSessionDelivery({stage:"bridge-start",bridgeId:t.id,channel:t.channel,enabled:t.enabled,hasToken:Boolean(t.token)}),"wechat"===t.channel?this.startPollingBridge(t,e=>this.pollWechat(e)):"dingding"===t.channel?this.startDingdingBridge(t):"telegram"!==t.channel?(this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status()):t.token?this.startPollingBridge(t,e=>this.pollTelegram(e)):(this.bridgeErrors.set(e,"Telegram token is not configured."),this.status())}stopBridge(e){const t=this.bridgeTimers.get(e);t&&(clearInterval(t),this.bridgeTimers.delete(e));const n=this.dingdingBridges.get(e);if(n){try{n.client.disconnect()}catch{}this.dingdingBridges.delete(e)}return this.dingdingProgress.clearBridge(e),this.status()}stopAll(){for(const e of new Set([...this.bridgeTimers.keys(),...this.dingdingBridges.keys()]))this.stopBridge(e);this.pendingTimer&&(clearInterval(this.pendingTimer),this.pendingTimer=void 0),this.dingdingProgress.clear();for(const e of this.telegramMessageAggregates.values())e.timer&&clearTimeout(e.timer);this.telegramMessageAggregates.clear(),this.telegramMessageAggregateEndTokens.clear(),this.stopAiSessionListener?.(),this.stopAiSessionListener=void 0}async pollBridgeNow(e){const t=this.service.requireChatBridge(e);return"telegram"===t.channel?(await this.pollTelegram(t),this.status()):"wechat"===t.channel?(await this.pollWechat(t),this.status()):("dingding"===t.channel||this.bridgeErrors.set(e,`${t.channel} bridge is not supported in the control plane.`),this.status())}async pollAiSessionsNow(){return await this.pollAiSessionUpdates(),this.status()}status(){const e=this.service.listChatBridges().map(e=>({id:e.id,channel:e.channel,name:e.name,running:this.bridgeTimers.has(e.id)||this.dingdingBridges.has(e.id),tokenSet:Boolean(e.tokenSet),defaultChatId:e.defaultChatId,lastUpdateId:this.telegramOffsets.get(e.id),error:this.bridgeErrors.get(e.id)}));return{running:e.some(e=>e.running),bridges:e}}startPendingPolling(){this.pendingTimer?this.logAiSessionDelivery({stage:"pending-poll-already-started"}):(this.logAiSessionDelivery({stage:"pending-poll-started",intervalMs:5e3}),this.pendingTimer=setInterval(()=>{this.pollPendingRoutes()},5e3))}startPollingBridge(e,t){if(!e.token)return this.bridgeErrors.set(e.id,`${e.channel} token is not configured.`),this.status();this.bridgeErrors.delete(e.id);const n=setInterval(()=>{const n=this.service.requireChatBridge(e.id);n.enabled?t(n).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))):this.stopBridge(e.id)},e.pollIntervalMs);if(this.bridgeTimers.set(e.id,n),this.logAiSessionDelivery({stage:"bridge-poll-started",bridgeId:e.id,channel:e.channel,intervalMs:e.pollIntervalMs}),e.enabled){const n=this.service.requireChatBridge(e.id);t(n).catch(t=>this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}return this.startPendingPolling(),this.status()}startDingdingBridge(e){const t=Hi((e.settings||{}).clientSecret);if(!e.token||!t)return this.bridgeErrors.set(e.id,"DingDing client id/secret is not configured."),this.status();const n={client:this.createDingdingClient({clientId:e.token,clientSecret:t}),chatWebhooks:new Map,senderIds:new Map,conversationTypes:new Map,onLog:(t,n,s)=>{"warn"!==t?this.logInfo({bridgeId:e.id,...n},s):this.logWarn({bridgeId:e.id,...n},s)}};return n.client.registerCallbackListener(u.TOPIC_ROBOT,t=>{this.handleDingdingRobotMessage(e,n,t).catch(t=>{this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t))})}),n.client.registerCallbackListener(u.TOPIC_CARD,t=>{this.handleDingdingCardCallback(e,n,t).then(e=>n.client.socketCallBackResponse(t.headers.messageId,e||{})).catch(s=>{this.bridgeErrors.set(e.id,s instanceof Error?s.message:String(s)),n.client.socketCallBackResponse(t.headers.messageId,{})})}),this.bridgeErrors.delete(e.id),this.dingdingBridges.set(e.id,n),n.client.connect().then(()=>{this.dingdingBridges.get(e.id)===n&&this.bridgeErrors.delete(e.id)}).catch(t=>{this.dingdingBridges.get(e.id)===n&&(this.dingdingBridges.delete(e.id),this.bridgeErrors.set(e.id,t instanceof Error?t.message:String(t)))}),this.startPendingPolling(),this.status()}async handleDingdingRobotMessage(e,t,n){const s=function(e){const t=zi(e),n=Ni(t.conversationId),s=Ni(_i(t.text).content).trim();if(!n||!s)return;const i=Ni(t.senderStaffId||t.senderId);return{chatId:n,userId:i||void 0,senderId:i||void 0,sessionWebhook:Ni(t.sessionWebhook)||void 0,conversationType:gi(t.conversationType,{robotMessage:!0}),text:s,raw:t}}(n.data);if(t.client.socketCallBackResponse(n.headers.messageId,{}),!s||!this.dingdingAllowed(e,s.senderId))return void this.logWarn({bridgeId:e.id,hasEvent:Boolean(s),senderId:s?.senderId,allowed:s?this.dingdingAllowed(e,s.senderId):void 0},"dingding robot message ignored");const{chatId:i,text:a,senderId:r,sessionWebhook:o}=s;o&&t.chatWebhooks.set(i,o),s.conversationType&&t.conversationTypes.set(i,s.conversationType);const d=o||t.chatWebhooks.get(i)||Hi(e.settings.sessionWebhook);let c;r&&t.senderIds.set(i,r),e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:i}),this.logInfo({bridgeId:e.id,chatId:i,senderId:r,conversationType:s.conversationType,hasSessionWebhook:Boolean(o),hasCachedSessionWebhook:Boolean(t.chatWebhooks.get(i)),hasResolvedSessionWebhook:Boolean(d),textPreview:Wi(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 yi(this.fetchImpl,d,`Failed to handle message: ${Gi(e)}`,t.onLog);throw e}const l=Pi(c);if(l){const n=Mi(c);if(await this.sendDingdingRoutedAiSessionProgress(c,e,t,i,l,{replyMarkup:n,sessionWebhook:d,senderId:r,sourcePrompt:a}))return;const s=ai({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:t}),o=await s.send(i,l,{replyMarkup:n,sessionWebhook:d,senderId:r});if(o)return void this.logInfo({bridgeId:e.id,chatId:i,senderId:r,provider:o.provider,interactionId:o.interactionId,routed:!0===Ji(c).routed,hasReplyMarkup:Boolean(n),replyPreview:Wi(l)},"dingding chat gateway reply sent");this.logWarn({bridgeId:e.id,chatId:i,senderId:r,hasReplyMarkup:Boolean(n),hasSessionWebhook:Boolean(d),hasRobotCode:Boolean(Hi(e.settings.robotCode)),replyPreview:Wi(l)},"dingding chat gateway reply skipped")}}async sendDingdingRoutedAiSessionProgress(e,t,n,s,i,a={}){if(!0!==e.routed)return!1;const r=Xi(e),{instanceId:o,sessionId:d,turnId:c}=r;if(!o||!d||!c)return this.logWarn({bridgeId:t.id,chatId:s,instanceId:o,sessionId:d,turnId:c,turnIdSources:r.sources,routed:e.routed},"dingding ai session progress card not started"),!1;const l=aa(o,d,c,t.id,s);return!!await this.dingdingProgress.applyUpdate({bridge:t,dingdingRuntime:n,key:l,chatId:s,text:i,replyMarkup:a.replyMarkup})&&(this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:s,instanceId:o,sessionId:d,turnId:c,key:l,hasSessionWebhook:Boolean(a.sessionWebhook),hasSenderId:Boolean(a.senderId),sourcePrompt:Wi(a.sourcePrompt,80),textPreview:Wi(i)},"dingding ai session progress card started"),!0)}async handleDingdingCardCallback(e,t,n){const s=function(e){const t=zi(e),n=Ci(t.cardActionData||t.content),s=Ci(n.cardPrivateData||t.cardPrivateData||t.content),i=Ci(s.params||n.params||t.params),a=Ni(t.userId),r=Ni(i.biz_sender_id);return{userId:a||void 0,chatId:ui(t,i),senderId:a||void 0,deliverySenderId:r||void 0,sessionWebhook:Ni(i.biz_session_webhook)||void 0,conversationType:gi(i.biz_conversation_type||t.conversationType||t.conversation_type),callbackData:hi(fi(t,n,s)),body:t,params:i}}(n.data);if(!this.dingdingAllowed(e,s.userId))return mi("not authorized",void 0,"unauthorized",s.body,s.params);const i=s.chatId||Hi(e.defaultChatId),a=s.deliverySenderId||s.senderId,r=s.sessionWebhook;i&&r&&t.chatWebhooks.set(i,r),i&&s.conversationType&&t.conversationTypes.set(i,s.conversationType),i&&a&&t.senderIds.set(i,a);const o=this.parseChatGatewayCallbackAction(s.callbackData);if(!o)return this.logWarn({bridgeId:e.id,chatId:i,userId:s.userId,senderId:a,...Vi(s)},"dingding card callback action unsupported"),mi("Unsupported action",void 0,"unsupported",s.body,s.params);this.logInfo({bridgeId:e.id,chatId:i,userId:s.userId,senderId:a,actionType:o.type,...Vi(s)},"dingding card callback action parsed");const d=await this.service.handleChatGatewayAction({source:{channel:"dingding",bridgeId:e.id,chatSessionId:i,userId:s.userId||void 0},action:o});return"pending-decision"===o.type&&$i(d)?mi(`${o.decision} sent`,void 0,"approval_done",s.body,s.params):mi(Pi(d)||Hi(d.message)||"updated",Mi(d),"updated",s.body,s.params)}async pollWechat(e){if(!e.token)return;const t=e.settings||{},n=this.wechatCursors.get(e.id)||Hi(t.updatesBuf),s=await async function(e){const t=Ni((e.bridge.settings||{}).baseUrl)||"https://ilinkai.weixin.qq.com",n=await e.fetchImpl(`${t}/ilink/bot/getupdates`,{method:"POST",headers:{"content-type":"application/json",AuthorizationType:"ilink_bot_token",Authorization:`Bearer ${e.bridge.token}`,"X-WECHAT-UIN":String(Math.floor(Math.random()*2**31))},body:JSON.stringify({get_updates_buf:e.cursor,base_info:{channel_version:"1.0.2"}})}),s=await n.json().catch(()=>({}));if(!n.ok)throw new Error(`HTTP ${n.status}`);const i=Number(s.errcode??s.ret??0);if(0!==i)throw new Error(Ni(s.errmsg||s.msg)||`chat bridge API returned ${i}`);const a=[];for(const e of Array.isArray(s.msgs)?s.msgs:[]){const t=_i(e);if(1!==t.message_type)continue;const n=Ni(t.from_user_id),s=bi(t);n&&s&&a.push({chatId:n,text:s,contextToken:Ni(t.context_token)})}return{cursor:Ni(s.get_updates_buf),messages:a}}({fetchImpl:this.fetchImpl,bridge:e,cursor:n});s.cursor&&(this.wechatCursors.set(e.id,s.cursor),this.updateChatBridge(e.id,{settings:{updatesBuf:s.cursor}}));for(const n of s.messages){const{chatId:s,contextToken:i,text:a}=n;if(e.defaultChatId||this.updateChatBridge(e.id,{defaultChatId:s,settings:{contextToken:i}}),e.defaultChatId&&e.defaultChatId!==s)continue;const r=Pi(await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"wechat",bridgeId:e.id,chatSessionId:s},message:{text:a,attachments:[]}}));if(r){const n=ai({fetchImpl:this.fetchImpl,bridge:e});await n.send(s,r,{contextToken:i||Hi(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=Zi(t,(e,t)=>this.service.pendingDecisionCallbackData(e,t)),n=t.aiSessionId?this.service.listChatSessions().filter(e=>e.activeInstanceId===t.instanceId&&e.activeAiSessionId===t.aiSessionId):[],s=new Set;for(const t of n)t.bridgeId&&await this.sendToChatBinding(t,e).then(e=>{e&&s.add(t.id)});(0!==s.size||await this.sendToDefaultBridgeChats(e))&&this.announcedPending.add(t.id)}}async sendToDefaultBridgeChats(e){let t=!1;for(const n of this.service.listChatBridges()){if(!n.enabled||!n.defaultChatId)continue;let s=!1;await this.sendViaBridge(n,n.defaultChatId,e).catch(e=>{this.bridgeErrors.set(n.id,e instanceof Error?e.message:String(e)),s=!0})?t=!0:s||this.bridgeErrors.set(n.id,"Chat bridge message was not delivered.")}return t}async sendToChatBinding(e,t){if(!e.bridgeId)return!1;const n=this.service.requireChatBridge(e.bridgeId);if(!n.enabled)return!1;const s=await this.sendViaBridge(n,e.chatSessionId,t).catch(e=>{this.bridgeErrors.set(n.id,e instanceof Error?e.message:String(e))});return Boolean(s)}async sendViaBridge(e,t,n){const s="string"==typeof n?n:n.text,i="string"==typeof n?void 0:n.replyMarkup;return ai({fetchImpl:this.fetchImpl,bridge:e,dingdingRuntime:this.dingdingBridges.get(e.id)}).send(t,s,{replyMarkup:i})}async pollTelegram(e){if(!e.token)return;const t=Number(e.settings?.telegramLastUpdateId),n=this.telegramOffsets.get(e.id)||(Number.isInteger(t)?t:void 0),s=await async function(e){const t=new URLSearchParams({timeout:"0",...e.offset?{offset:String(e.offset+1)}:{}}),n=await e.fetchImpl(`https://api.telegram.org/bot${e.bridge.token}/getUpdates?${t.toString()}`),s=await n.json().catch(()=>({}));if(!n.ok||!1===s.ok){if(409===n.status||/terminated by other getUpdates request/i.test(String(s.description||"")))return{updates:[],conflict:s.description||"Telegram getUpdates conflict: another bot instance is polling this token."};throw new Error(s.description||`Telegram getUpdates failed with HTTP ${n.status}`)}const i=[];for(const t of s.result||[]){const n=Number(t.update_id),s=Number.isInteger(n)?n:void 0,a=_i(t.callback_query);if(Object.keys(a).length>0){i.push({updateId:s,callbackQuery:a});continue}const r=_i(t.message||t.edited_message);if(0===Object.keys(r).length)continue;const o=_i(r.chat),d=_i(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=Ni(r.text??r.caption),u=Xs(r);(p||0!==u.length)&&i.push({updateId:s,chatId:c,userId:l,text:p,rawText:p,message:r,messageId:wi(r),replyToMessageId:wi(_i(r.reply_to_message)),quoteText:Ni(_i(r.quote).text),autoBegin:1===u.length&&!p.trim(),hasSingleImageWithoutText:1===u.length&&!p.trim()})}return{updates:i}}({fetchImpl:this.fetchImpl,bridge:e,offset:n});if(s.conflict)this.bridgeErrors.set(e.id,s.conflict);else for(const t of s.updates){if(void 0!==t.updateId){const n=`${e.id}:${t.updateId}`;if(this.seenTelegramUpdates.has(n)||t.updateId<=(this.telegramOffsets.get(e.id)||0))continue;this.seenTelegramUpdates.add(n),this.telegramOffsets.set(e.id,t.updateId),this.updateChatBridge(e.id,{settings:{...e.settings,telegramLastUpdateId:t.updateId}})}if("callbackQuery"in t){const n=t.callbackQuery;await this.handleTelegramCallback(e,n).catch(async t=>{const s=Ji(n.message),i=Ji(s.chat),a=void 0!==i.id?String(i.id):e.defaultChatId;this.bridgeErrors.set(e.id,Gi(t)),a&&await this.sendTelegramMessage(e,a,`Action failed: ${Gi(t)}`).catch(t=>{this.bridgeErrors.set(e.id,Gi(t))})});continue}if(!this.telegramAllowed(e,t.userId))continue;const n=await ri({fetchImpl:this.fetchImpl,bridge:e,message:t.message,onImageDownloadError:({fileId:t,fileName:n,error:s})=>{this.logWarn({bridgeId:e.id,fileId:t,fileName:n,error:Gi(s)},"telegram image download skipped")}}),s=t.rawText.trim();(s||n.length)&&(this.logInfo({bridgeId:e.id,chatId:t.chatId,messageId:t.messageId,replyToMessageId:t.replyToMessageId,hasQuote:Boolean(t.quoteText),textPreview:Wi(s)},"telegram chat gateway message received"),await this.handleTelegramIncomingText(e,t.chatId,t.userId,s,n,{sourceMessageId:t.messageId,replyToMessageId:t.replyToMessageId,quoteText:t.quoteText},{autoBegin:t.autoBegin}))}}async handleTelegramIncomingText(e,t,n,s,i=[],a={},r={}){const o=s.trim(),d=o.split(/\s+/,1)[0]?.toLowerCase()||"",c=Ui(e.id,t,n),l=this.telegramMessageAggregates.get(c);return r.autoBegin&&!l?(this.startTelegramAggregate(c,e,t,n,a),this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!0),void await this.sendTelegramMessage(e,t,"Image received. Continue sending messages, then send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Hn([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):"/begin"===d?(this.startTelegramAggregate(c,e,t,n,a),void await this.sendTelegramMessage(e,t,"Started collecting messages. Send /end when done.",{replyToMessageId:a.sourceMessageId,replyMarkup:Hn([[{text:"/end",callbackData:this.telegramAggregateEndCallbackData(c)}]])})):void("/end"!==d?l?.explicit?this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!0):this.isImmediateTelegramCommand(o)?await this.dispatchTelegramGatewayMessage(e,t,n,s,i,a):this.enqueueTelegramAggregate(c,e,t,n,s,i,a,!1):await this.flushTelegramAggregate(c,"manual"))}isImmediateTelegramCommand(e){return e.trim().startsWith("/")}startTelegramAggregate(e,t,n,s,i={}){const a=this.telegramMessageAggregates.get(e);a?.timer&&clearTimeout(a.timer),this.telegramMessageAggregates.set(e,{bridgeId:t.id,chatId:n,userId:s,texts:[],attachments:[],context:i,explicit:!0})}enqueueTelegramAggregate(e,t,n,s,i,a,r,o){const d=this.telegramMessageAggregates.get(e);d?.timer&&clearTimeout(d.timer);const c=d||{bridgeId:t.id,chatId:n,userId:s,texts:[],attachments:[],context:r,explicit:o};var l,p;c.bridgeId=t.id,c.chatId=n,c.userId=s,c.context=(l=c.context,p=r,{sourceMessageId:l?.sourceMessageId??p.sourceMessageId,replyToMessageId:l?.replyToMessageId??p.replyToMessageId,quoteText:l?.quoteText||p.quoteText}),c.explicit=c.explicit||o,i.trim()&&c.texts.push(i),c.attachments.push(...a),c.explicit?c.timer=void 0:c.timer=setTimeout(()=>{this.flushTelegramAggregate(e,"timer")},1e3),this.telegramMessageAggregates.set(e,c)}async flushTelegramAggregate(e,t){const n=this.telegramMessageAggregates.get(e);if(!n)return!1;n.timer&&clearTimeout(n.timer),this.telegramMessageAggregates.delete(e);const s=this.service.requireChatBridge(n.bridgeId),i=n.texts.join("\n\n").trim();return i||n.attachments.length?(await this.dispatchTelegramGatewayMessage(s,n.chatId,n.userId,i,n.attachments,n.context),!0):("timer"!==t&&await this.sendTelegramMessage(s,n.chatId,"No messages collected.",{replyToMessageId:n.context?.sourceMessageId}),!1)}async handleTelegramAggregateEndCallback(e,t,n,s,i){const a=this.telegramMessageAggregateEndTokens.get(s)||"";if(a!==Ui(e.id,t,i))return void await this.answerTelegramCallback(e,n,"This collection is not active here");const r=await this.flushTelegramAggregate(a,"callback");await this.answerTelegramCallback(e,n,r?"Collected message sent":"No messages collected")}telegramAggregateEndCallbackData(e){const t=r.createHash("sha256").update(e).digest("base64url").slice(0,16);return this.telegramMessageAggregateEndTokens.set(t,e),`task_handoff:cp_msg_end:${t}`}async dispatchTelegramGatewayMessage(e,t,n,s,i=[],a={}){try{const r=void 0!==a.replyToMessageId?this.telegramReplyAiSessionTarget(e.id,t,a.replyToMessageId):void 0,o=function(e,t){const n=String(t||"").trim(),s=e.trim();return n?[`引用:${n}`,s].filter(Boolean).join("\n"):s}(s,a.quoteText),d=await this.service.handleChatGatewayMessage({source:{type:"chat-gateway",channel:"telegram",bridgeId:e.id,chatSessionId:t,userId:n},message:{text:o,attachments:i},...r?{target:{instanceId:r.instanceId,aiSessionId:r.sessionId}}:{}}),c=Pi(d),l=Mi(d);if(c){const n=await this.sendTelegramMessage(e,t,c,{replyMarkup:l,replyToMessageId:a.sourceMessageId});this.logInfo({bridgeId:e.id,chatId:t,sourceMessageId:a.sourceMessageId,replyToMessageId:a.replyToMessageId,replyTargetInstanceId:r?.instanceId,replyTargetSessionId:r?.sessionId,sentMessageId:Number(Ji(n).message_id)||void 0,routed:!0===Ji(d).routed,replyPreview:Wi(c)},"telegram chat gateway reply sent"),await this.rememberTelegramProgressMessage(d,e,t,n,c)}}catch(n){this.bridgeErrors.set(e.id,Gi(n)),await this.sendTelegramMessage(e,t,`Failed to handle message: ${Gi(n)}`,{replyToMessageId:a.sourceMessageId}).catch(t=>{this.bridgeErrors.set(e.id,Gi(t))})}}async handleTelegramCallback(e,t){const n=Hi(t.data),s=Ji(t.from),i=Ji(t.message),a=Ji(i.chat),r=void 0!==a.id?String(a.id):e.defaultChatId,o=void 0!==s.id?String(s.id):void 0;if(!r||!this.telegramAllowed(e,o))return;const d=n.match(/^task_handoff:cp_ai_cancel:([^:]+)$/);if(d){const n=this.aiSessionCancelTokens.get(d[1]);return n?void await this.handleTelegramAiSessionCancelCallback(e,r,Hi(t.id),n.instanceId,n.sessionId,o,Li(i)):void await this.answerTelegramCallback(e,Hi(t.id),"This AI session action expired")}const c=n.match(/^task_handoff:cp_ai_steer:([^:]+)$/);if(c){const n=this.aiSessionQueueActionTokens.get(c[1]);return n&&"steer"===n.type?void await this.handleTelegramAiSessionQueueSteerCallback(e,r,Hi(t.id),n.instanceId,n.sessionId,n.queueId,o,Li(i)):void await this.answerTelegramCallback(e,Hi(t.id),"This AI session action expired")}const l=n.match(/^task_handoff:cp_ai_qdel_menu:([^:]+)$/);if(l){const n=this.aiSessionQueueActionTokens.get(l[1]);return n&&"delete-menu"===n.type?void await this.handleTelegramAiSessionQueueDeleteMenuCallback(e,r,Hi(t.id),n.instanceId,n.sessionId,o,Li(i)):void await this.answerTelegramCallback(e,Hi(t.id),"This AI session action expired")}const p=n.match(/^task_handoff:cp_ai_qdel:([^:]+)$/);if(p){const n=this.aiSessionQueueActionTokens.get(p[1]);return n&&"delete-item"===n.type?void await this.handleTelegramAiSessionQueueDeleteItemCallback(e,r,Hi(t.id),Li(i),n.instanceId,n.sessionId,n.queueId,o):void await this.answerTelegramCallback(e,Hi(t.id),"This AI session action expired")}const u=n.match(/^task_handoff:cp_msg_end:([^:]+)$/);if(u)return void await this.handleTelegramAggregateEndCallback(e,r,Hi(t.id),u[1],o);const g=this.parseChatGatewayCallbackAction(n);if(!g)return void await this.answerTelegramCallback(e,Hi(t.id),"Unsupported action");const h=await this.service.handleChatGatewayAction({source:{channel:"telegram",bridgeId:e.id,chatSessionId:r,userId:o},action:g});if("pending-decision"===g.type&&$i(h)){await this.answerTelegramCallback(e,Hi(t.id),"");const n=Li(i);return void(void 0!==n&&await this.deleteTelegramStandaloneApprovalMessage(e,r,n))}await this.answerTelegramCallback(e,Hi(t.id),Hi(h.message)||"updated");const m=Pi(h),f=Mi(h),I=i.message_id;m&&"number"==typeof I?await this.editTelegramMessage(e,r,I,m,{replyMarkup:f}):m&&await this.sendTelegramMessage(e,r,m,{replyMarkup:f})}async deleteTelegramStandaloneApprovalMessage(e,t,n){[...this.telegramProgress.entries.values()].find(s=>s.messageId===n&&s.route?.bridgeId===e.id&&s.route?.chatId===t)||await this.deleteTelegramMessage(e,t,n).catch(s=>{this.logWarn({bridgeId:e.id,chatId:t,messageId:n,error:Gi(s)},"telegram callback message delete failed")})}async handleTelegramAiSessionCancelCallback(e,t,n,s,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,s,i,r))try{await this.service.interruptAiSession(s,i),await this.answerTelegramCallback(e,n,"Interrupt sent"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,userId:a},"telegram ai session cancel sent")}catch(t){this.bridgeErrors.set(e.id,Gi(t)),await this.answerTelegramCallback(e,n,`Interrupt failed: ${Wi(Gi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueSteerCallback(e,t,n,s,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,s,i,o))try{await this.service.steerAiSessionQueuedMessage(s,i,a),await this.answerTelegramCallback(e,n,"Steered queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,queueId:a,userId:r},"telegram ai session queued message steered")}catch(t){this.bridgeErrors.set(e.id,Gi(t)),await this.answerTelegramCallback(e,n,`Steer failed: ${Wi(Gi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteMenuCallback(e,t,n,s,i,a,r){if(this.telegramAiSessionActionAllowed(e,t,s,i,r))try{const r=Ji(await this.service.aiSessionQueue(s,i)),o=(Array.isArray(r.items)?r.items:[]).map(e=>Ji(e)).filter(e=>"queued"===Hi(e.status)&&Hi(e.id));if(!o.length)return void await this.answerTelegramCallback(e,n,"Queue is empty");const d=o.slice(0,ea).map((e,t)=>[{text:ia(Hi(e.message),t),callbackData:this.aiSessionQueueDeleteItemCallbackData(s,i,Hi(e.id))}]);await this.sendTelegramMessage(e,t,"Delete queued message",{replyMarkup:Hn(d)}),await this.answerTelegramCallback(e,n,"Select a queued message"),this.logInfo({bridgeId:e.id,chatId:t,instanceId:s,sessionId:i,itemCount:o.length,userId:a},"telegram ai session queue delete menu sent")}catch(t){this.bridgeErrors.set(e.id,Gi(t)),await this.answerTelegramCallback(e,n,`Queue menu failed: ${Wi(Gi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}async handleTelegramAiSessionQueueDeleteItemCallback(e,t,n,s,i,a,r,o){if(this.telegramAiSessionActionAllowed(e,t,i,a,s))try{await this.service.removeAiSessionQueuedMessage(i,a,r),await this.answerTelegramCallback(e,n,"Queued message deleted"),Number.isInteger(s)&&await this.deleteTelegramMessage(e,t,s).catch(n=>{this.logWarn({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,messageId:s,error:Gi(n)},"telegram ai session queue delete menu message delete failed")}),this.logInfo({bridgeId:e.id,chatId:t,instanceId:i,sessionId:a,queueId:r,userId:o},"telegram ai session queued message deleted")}catch(t){this.bridgeErrors.set(e.id,Gi(t)),await this.answerTelegramCallback(e,n,`Delete failed: ${Wi(Gi(t),120)}`)}else await this.answerTelegramCallback(e,n,"This chat is not bound to that AI session")}telegramAiSessionBindingActive(e,t,n,s){return this.service.listChatSessions().some(i=>i.bridgeId===e.id&&i.chatSessionId===t&&i.activeInstanceId===n&&i.activeAiSessionId===s)}telegramAiSessionActionAllowed(e,t,n,s,i){if(this.telegramAiSessionBindingActive(e,t,n,s))return!0;if(!Number.isInteger(i))return!1;const a=this.telegramReplyAiSessionTarget(e.id,t,i);return Boolean(a&&a.instanceId===n&&a.sessionId===s)}parseChatGatewayCallbackAction(e){return function(e,t){const n=e.match(/^task_handoff:cp_session:(\d+)$/);if(n)return{type:"ai-session",index:Number(n[1])};const s=e.match(/^task_handoff:cp_i:([^:]+)$/)||e.match(/^task_handoff:cp_instance_apps:([^:]+)$/);if(s)return s[0].startsWith("task_handoff:cp_i:")?{type:"instance-app-menu",instanceId:ti(t(s[1],"instance-app-menu").instanceId,"instanceId")}:{type:"instance-app-menu",instanceId:Di(s[1])};const i=e.match(/^task_handoff:cp_a:([^:]+)$/)||e.match(/^task_handoff:cp_launch_app:([^:]+):([^:]+)$/);if(i){if(i[0].startsWith("task_handoff:cp_a:")){const e=t(i[1],"launch-app");return{type:"launch-app",instanceId:ti(e.instanceId,"instanceId"),appId:ti(e.appId,"appId")}}return{type:"launch-app",instanceId:Di(i[1]),appId:Di(i[2])}}const a=e.match(/^task_handoff:cp_p:([^:]+)$/);if(a){const e=t(a[1],"pending-decision");return{type:"pending-decision",routeId:ti(e.routeId,"routeId"),decision:ni(e.decision)}}const r=e.match(/^task_handoff:approval:([^:]+:ai:[^:]+):(allow|deny|skip)$/);return r?{type:"pending-decision",routeId:r[1],decision:r[2]}:void 0}(e,(e,t)=>this.service.resolveChatActionToken(e,t))}dingdingAllowed(e,t){const n=String(t||"").trim();return!!n&&(0===e.allowedUserIds.length?(e.allowedUserIds=[n],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:n},"dingding user bound"),!0):e.allowedUserIds.includes(n))}telegramAllowed(e,t){const n=String(t||"").trim();return!!n&&(0===e.allowedUserIds.length?(e.allowedUserIds=[n],this.updateChatBridge(e.id,{allowedUserIds:e.allowedUserIds}),this.logInfo({bridgeId:e.id,userId:n},"telegram user bound"),!0):e.allowedUserIds.includes(n))}async answerTelegramCallback(e,t,n){return async function(e,t,n,s){t.token&&n&&await e(`https://api.telegram.org/bot${t.token}/answerCallbackQuery`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({callback_query_id:n,text:s.slice(0,200)})})}(this.fetchImpl,e,t,n)}async editTelegramMessage(e,t,n,s,i={}){return async function(e,t,n,s,i,a={}){if(t.token)return Ws(n=>e(`https://api.telegram.org/bot${t.token}/editMessageText`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),"editMessageText",i,{chat_id:n,message_id:s,...a.replyMarkup?{reply_markup:a.replyMarkup}:{}},a)}(this.fetchImpl,e,t,n,s,i)}async sendTelegramMessage(e,t,n,s={}){return Hs(this.fetchImpl,e,t,n,s)}async deleteTelegramMessage(e,t,n){return async function(e,t,n,s){if(!t.token||!Number.isInteger(s))return;const i=await e(`https://api.telegram.org/bot${t.token}/deleteMessage`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({chat_id:n,message_id:s})}),a=await i.json().catch(()=>({}));if(!i.ok||!1===a.ok)throw new Error(a.description||`Telegram deleteMessage failed with HTTP ${i.status}`)}(this.fetchImpl,e,t,n)}async rememberTelegramProgressMessage(e,t,n,s,i){if(!0!==e.routed)return;const a=Xi(e),{instanceId:r,sessionId:o,turnId:d}=a,c=Number(Ji(s).message_id);if(!(r&&o&&d&&Number.isInteger(c)))return void this.logWarn({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,messageId:Number.isInteger(c)?c:void 0,routed:e.routed},"telegram ai session progress message not remembered");const l=aa(r,o,d,t.id,n),p=this.telegramProgress.remember(l,c,i,{bridgeId:t.id,chatId:n});this.telegramProgressMessageTargets.set(Bi(t.id,n,c),{instanceId:r,sessionId:o,turnId:d,messageId:c}),this.deliveredAiSessionFingerprints.delete(l),this.logInfo({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,turnIdSources:a.sources,key:l,messageId:c,progressKeys:this.telegramProgressKeysFor(r,o,t.id,n),textPreview:Wi(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(Bi(t.id,n,p.messageId)),await this.deleteTelegramMessage(t,n,p.messageId).catch(e=>{this.logWarn({bridgeId:t.id,chatId:n,instanceId:r,sessionId:o,turnId:d,replacedMessageId:p.messageId,replacementMessageId:c,error:Gi(e)},"telegram queued progress message replacement cleanup failed")}))}telegramReplyAiSessionTarget(e,t,n){const s=this.telegramProgressMessageTargets.get(Bi(e,t,n));if(s)return s;for(const[s,i]of this.telegramProgress.entries)if(i.messageId===n&&i.route?.bridgeId===e&&i.route?.chatId===t){const i=Fi(s,e,t);if(i){const s={...i,messageId:n};return this.telegramProgressMessageTargets.set(Bi(e,t,n),s),s}}}async pollAiSessionUpdates(){this.logAiSessionDelivery({stage:"poll-tick"});const e=await this.service.listAiSessions({refresh:!0}).catch(()=>{});if(!e)return void this.logAiSessionDelivery({stage:"poll-no-snapshot"});const t=new Map(e.instances.map(e=>[e.instanceId,e.aiSessions]));this.logAiSessionDelivery({stage:"poll-snapshot",instanceCount:t.size,instances:[...t.entries()].map(([e,t])=>({instanceId:e,sessionCount:t.sessions?.length||0,runningCount:t.runningCount,waitingCount:t.waitingCount,updatedAt:t.updatedAt}))});const n=await this.aiSessionInstanceNames();for(const[e,s]of t)await this.deliverAiSessionSnapshot(e,s,n)}async deliverAiSessionSnapshot(e,t,n){const s=Array.isArray(t.sessions)?t.sessions:[];if(this.pruneAiSessionDeliveryState(e,new Set(s.map(e=>e.id))),0===s.length)return;const i=n||await this.aiSessionInstanceNames(),a=await this.aiSessionPendingApprovalRoutes(e);await this.deliverOwnedTelegramProgress(e,s,i,a),await this.deliverActiveBindingTelegramProgress(e,s,i,a)}pruneAiSessionDeliveryState(e,t){for(const[n,s]of this.telegramProgress.entries){const i=s.route?.bridgeId&&s.route.chatId?this.telegramProgressMessageTargets.get(Bi(s.route.bridgeId,s.route.chatId,s.messageId||0))||Fi(n,s.route.bridgeId,s.route.chatId):void 0;i&&i.instanceId===e&&!t.has(i.sessionId)&&(s.timer&&clearTimeout(s.timer),this.telegramProgress.entries.delete(n),this.deliveredAiSessionFingerprints.delete(n),s.route?.bridgeId&&s.route.chatId&&s.messageId&&this.telegramProgressMessageTargets.delete(Bi(s.route.bridgeId,s.route.chatId,s.messageId)))}}async deliverActiveBindingTelegramProgress(e,t,n,s){for(const i of this.service.listChatSessions()){if(!i.bridgeId||i.activeInstanceId!==e||!i.activeAiSessionId)continue;const a=t.find(e=>e.id===i.activeAiSessionId);if(!a)continue;const r=la(n.get(e)||e,a),o=ca(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:Ki(pa(a))});continue}const d=ra(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:Ki(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=aa(e,a.id,d,i.bridgeId||"",i.chatSessionId),l=oa(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:Ki(pa(a)),textPreview:Wi(o)});continue}const p=na({cancelCallbackData:this.aiSessionCancelCallbackData(e,a.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,a.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,a.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:s.get(a.id)||sa(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:Ki(pa(a)),textPreview:Wi(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:Gi(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:Wi(o)}),this.deliveredAiSessionFingerprints.set(c,l);else{if("telegram"===this.service.requireChatBridge(i.bridgeId).channel){const t=this.telegramProgress.entries.get(c),n=this.service.requireChatBridge(i.bridgeId);this.logAiSessionDelivery({stage:t?"telegram-update-not-delivered":"telegram-no-progress-entry",instanceId:e,sessionId:a.id,bridgeId:i.bridgeId,chatId:i.chatSessionId,turnId:d,key:c,relatedProgressKeys:this.telegramProgressKeysFor(e,a.id,i.bridgeId||"",i.chatSessionId),bridgeEnabled:n.enabled,bridgeChannel:n.channel,progressMessageId:t?.messageId,progressLastText:Wi(t?.lastText),progressHasPending:Boolean(t?.pending),latestTurn:Ki(pa(a)),textPreview:Wi(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:Wi(o)}),this.deliveredAiSessionFingerprints.set(c,l))}}}async deliverOwnedTelegramProgress(e,t,n,s){for(const i of this.listTelegramOwnedProgressEntries(e)){const a=this.service.requireChatBridge(i.route.bridgeId);if("telegram"!==a.channel||!a.enabled)continue;const r=this.matchTelegramOwnedProgressSession(i,t);if(!r)continue;const o={bridgeId:i.route.bridgeId,chatSessionId:i.route.chatId},d=la(n.get(e)||e,r),c=ca(r,d),l=ra(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:aa(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=oa(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=na({cancelCallbackData:this.aiSessionCancelCallbackData(e,r.id),queueSteerCallbackData:t=>this.aiSessionQueueSteerCallbackData(e,r.id,t),queueDeleteMenuCallbackData:()=>this.aiSessionQueueDeleteMenuCallbackData(e,r.id),permissionCallbackData:(e,t)=>this.service.pendingDecisionCallbackData(e,t),pendingApproval:s.get(r.id)||sa(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:Wi(c)}),h&&(this.telegramProgressMessageTargets.set(Bi(i.route.bridgeId,i.route.chatId,i.messageId),{instanceId:e,sessionId:r.id,turnId:l,messageId:i.messageId}),this.deliveredAiSessionFingerprints.set(p,u))}}rekeyTelegramOwnedProgress(e,t,n){return!!this.telegramProgress.rekey(e.key,t)&&(this.deliveredAiSessionFingerprints.delete(e.key),this.telegramProgressMessageTargets.set(Bi(e.route.bridgeId,e.route.chatId,e.messageId),{...n,messageId:e.messageId}),!0)}logAiSessionDelivery(e){this.logInfo(e,"telegram ai session delivery flow")}telegramProgressKeysFor(e,t,n,s){const i=`${e}:${t}:`,a=`:${n}:${s}`;return[...this.telegramProgress.entries.keys()].filter(e=>e.startsWith(i)&&e.endsWith(a))}listTelegramOwnedProgressEntries(e){const t=[];for(const[n,s]of this.telegramProgress.entries){if(!s.route?.bridgeId||!s.route.chatId||!Number.isInteger(s.messageId))continue;const i=this.telegramProgressMessageTargets.get(Bi(s.route.bridgeId,s.route.chatId,s.messageId))||Fi(n,s.route.bridgeId,s.route.chatId);i&&i.instanceId===e&&t.push({key:n,route:{bridgeId:s.route.bridgeId,chatId:s.route.chatId},messageId:s.messageId,owner:{...i,messageId:s.messageId}})}return t}matchTelegramOwnedProgressSession(e,t){const n=t.find(t=>t.id===e.owner.sessionId);if(n)return n;if(e.owner.turnId){const n=t.find(t=>t.activeTurnId===e.owner.turnId||pa(t)?.id===e.owner.turnId||pa(t)?.providerTurnId===e.owner.turnId);if(n)return n}}async updateChatAiSessionProgress(e,t,n,s){if(!e.bridgeId)return this.logAiSessionDelivery({stage:"chat-update-skip-missing-bridge",chatId:e.chatSessionId,key:n}),!1;const i=this.service.requireChatBridge(e.bridgeId);if(!i.enabled)return this.logAiSessionDelivery({stage:"chat-update-skip-bridge-state",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,bridgeEnabled:i.enabled,bridgeChannel:i.channel}),!1;if("dingding"===i.channel){const a=this.dingdingBridges.get(i.id);if(!a)return!1;const r=await this.dingdingProgress.applyUpdate({bridge:i,dingdingRuntime:a,key:n,chatId:e.chatSessionId,text:t,replyMarkup:qi({actionRows:s})});return r&&this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,textPreview:Wi(t)},"dingding ai session progress updated"),r}if("telegram"!==i.channel)return!1;const a=this.telegramProgress.entries.get(n);if(!await this.telegramProgress.applyUpdate({bridge:i,key:n,chatId:e.chatSessionId,text:t,actionRows:s})){const t=this.telegramProgress.entries.get(n);return this.logAiSessionDelivery({stage:"telegram-update-returned-false",bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,progressMessageId:a?.messageId||t?.messageId,progressLastText:Wi(a?.lastText||t?.lastText),progressHasPending:Boolean(a?.pending||t?.pending)}),!1}return this.logInfo({bridgeId:e.bridgeId,chatId:e.chatSessionId,key:n,messageId:a?.messageId||this.telegramProgress.entries.get(n)?.messageId,textPreview:Wi(t)},a?"telegram ai session progress edited":"telegram ai session progress sent"),!0}async aiSessionInstanceNames(){const e=await this.service.boardAsync().catch(()=>[]);return new Map(e.map(e=>[e.id,e.name||e.id]))}async aiSessionPendingApprovalRoutes(e){const t=await this.service.listPendingRoutes().catch(()=>[]);return new Map(t.filter(t=>t.instanceId===e&&t.aiSessionId&&"approval"===t.kind).map(e=>[e.aiSessionId||"",e]))}aiSessionCancelCallbackData(e,t){const n=r.createHash("sha256").update(`${e}\0${t}`).digest("base64url").slice(0,16);return this.aiSessionCancelTokens.set(n,{instanceId:e,sessionId:t}),`task_handoff:cp_ai_cancel:${n}`}aiSessionQueueSteerCallbackData(e,t,n){return`task_handoff:cp_ai_steer:${this.aiSessionQueueActionToken({type:"steer",instanceId:e,sessionId:t,queueId:n})}`}aiSessionQueueDeleteMenuCallbackData(e,t){return`task_handoff:cp_ai_qdel_menu:${this.aiSessionQueueActionToken({type:"delete-menu",instanceId:e,sessionId:t})}`}aiSessionQueueDeleteItemCallbackData(e,t,n){return`task_handoff:cp_ai_qdel:${this.aiSessionQueueActionToken({type:"delete-item",instanceId:e,sessionId:t,queueId:n})}`}aiSessionQueueActionToken(e){const t=r.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 Pi(e){return"object"==typeof e&&e&&"reply"in e?String(e.reply||""):""}function $i(e){return"object"==typeof e&&null!==e&&!0===e.accepted}function Mi(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 qi(e){const t=Array.isArray(e?.actionRows)?e.actionRows:void 0;return Hn(t||(Array.isArray(e?.actions)?e.actions:[]).map(e=>[e]))}function Li(e){const t=Number(e?.message_id);return Number.isInteger(t)?t:void 0}function Ui(e,t,n){return[e,t,n||""].join(":")}function Bi(e,t,n){return[e,t,String(n)].join(":")}function Fi(e,t,n){const s=`:${t}:${n}`;if(!e.endsWith(s))return;const i=e.slice(0,-s.length).split(":"),a=i[0]||"",r=i[1]||"",o=i.slice(2).join(":")||void 0;return a&&r?{instanceId:a,sessionId:r,turnId:o}:void 0}function Hi(e){return"string"==typeof e?e:""}function Gi(e){return e instanceof Error?e.message:String(e)}function Wi(e,t=160){const n=String(e||"").replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,Math.max(0,t-3))}...`}function Vi(e){const t=Qi(e.body.cardActionData||e.body.content),n=Qi(t.cardPrivateData||e.body.cardPrivateData||e.body.content);return{outTrackId:Hi(e.body.outTrackId),spaceId:Hi(e.body.spaceId),conversationType:e.conversationType,hasCallbackData:Boolean(e.callbackData),callbackDataPreview:Wi(e.callbackData,120),actionIds:[...Yi(n.actionIdList),...Yi(t.actionIdList),Hi(n.actionId),Hi(t.actionId),Hi(e.body.actionId)].filter(Boolean).slice(0,5),cardActionDataKeys:Object.keys(t).sort(),privateDataKeys:Object.keys(n).sort(),paramKeys:Object.keys(e.params).sort(),hasSessionWebhook:Boolean(e.sessionWebhook)}}function Ki(e){if(e)return{id:e.id,providerTurnId:e.providerTurnId,source:e.source,status:e.status,phase:e.phase,revision:e.revision,userPrompt:Wi(e.userPrompt,80),hasResponse:Boolean(e.lastMessage?.trim()||e.summary?.trim()),responsePreview:Wi(e.lastMessage||e.summary,80),updatedAt:e.updatedAt,observedAt:e.observedAt}}function Ji(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function Qi(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if("string"==typeof e&&e.trim())try{return Ji(JSON.parse(e))}catch{return{}}return{}}function Yi(e){return Array.isArray(e)?e.map(e=>Hi(e)).filter(Boolean):[]}function Xi(e){const t=e.aiSession&&"session"in e.aiSession?e.aiSession:void 0,n=t?.session||(e.aiSession&&"id"in e.aiSession?e.aiSession:void 0);return{instanceId:e.instanceId||e.instance?.id||"",sessionId:e.aiSessionId||n?.id||e.binding?.activeAiSessionId||"",turnId:e.turnId||e.providerTurnId||t?.turnId||t?.providerTurnId||(n?ra(n):""),sources:{resultTurnId:e.turnId,resultProviderTurnId:e.providerTurnId,actionTurnId:t?.turnId,actionProviderTurnId:t?.providerTurnId,sessionActiveTurnId:n?.activeTurnId,latestTurnId:n?pa(n)?.id:void 0}}}function Zi(e,t){const n=`${e.project?.name||e.projectId} / ${e.instance?.name||e.instanceId}`,s="approval"===e.kind?`/approve ${e.id}\n/deny ${e.id}\n/skip ${e.id}`:`/reply ${e.id} <message>`,i=`[${n}]\n${e.result}\n\n${s}`;return"approval"!==e.kind?{text:i}:{text:i,replyMarkup:Hn([[{text:"Allow",callbackData:t(e.id,"allow")},{text:"Skip",callbackData:t(e.id,"skip")},{text:"Deny",callbackData:t(e.id,"deny")}]])}}const ea=5,ta=48;function na(e){const{cancelCallbackData:t,queueSteerCallbackData:n,queueDeleteMenuCallbackData:s,permissionCallbackData:i,pendingApproval:a,session:r}=e,o="approval"===a?.kind?[[{text:"Allow",callbackData:i(a.id,"allow")},{text:"Skip",callbackData:i(a.id,"skip")},{text:"Deny",callbackData:i(a.id,"deny")}]]:[];if("running"!==r.status)return o;const d=(r.queue?.items||[]).filter(e=>"queued"===e.status).slice(0,ea);return[...d.map((e,t)=>[{text:ia(e.message,t),callbackData:n(e.id)}]),d.length?[{text:"Delete Queue",callbackData:s()},{text:"Cancel",callbackData:t}]:[{text:"Cancel",callbackData:t}]]}function sa(e,t){if("approval"===t.phase)return{id:`${e}:ai:${t.id}`,kind:"approval"}}function ia(e,t){const n=`${t+1}. `,s=e.replace(/\s+/g," ").trim()||"Queued message",i=Math.max(8,ta-n.length),a=Array.from(s);return`${n}${a.length>i?`${a.slice(0,Math.max(1,i-3)).join("")}...`:s}`}function aa(e,t,...n){return[e,t,...n].join(":")}function ra(e){const t=pa(e);return t?.id?t.id:Hi(e.activeTurnId)}function oa(e,t=""){const n=pa(e);return[t,n?.id||"",String(n?.revision??""),n?.status||e.status||"",n?.phase||e.phase||"",n?.lastMessage||"",n?.summary||"",n?.userPrompt||"",e.error||"",e.currentTool?.name||"",e.currentTool?.inputPreview||"",da(e)].join("\0")}function da(e){return(e.queue?.items||[]).map(e=>[e.id,e.status,e.message].join(":")).join("|")}function ca(e,t){const n=function(e,t){return t?t.lastMessage?.trim()?t.lastMessage.trim():t.summary?.trim()?t.summary.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:"":e.lastMessage?.trim()?e.lastMessage.trim():e.summary?.trim()?e.summary.trim():e.error?.trim()?"failed"===e.status?`${e.agent} session failed:\n${e.error.trim()}`:e.error.trim():e.currentTool?.name?`Running ${e.currentTool.name}${e.currentTool.inputPreview?`: ${e.currentTool.inputPreview}`:""}`:""}(e,pa(e));return n?`${t}\n${n}`:""}function la(e,t){const n=[t.status,t.phase&&"unknown"!==t.phase?t.phase:void 0].filter(Boolean).join("/");return`${e} · ${t.agent||"ai"} ${n||"unknown"}`}function pa(e){return(e.turns||[]).filter(e=>e.id||e.userPrompt?.trim()||e.lastMessage?.trim()||e.summary?.trim()).at(-1)}function ua(e){return e.startsWith("ai-session.")||e.startsWith("ai.session.")?"ai.sessions":e.startsWith("app-session.")?"app.sessions":e.startsWith("receiver.")?"receiver":e.startsWith("conversation.")?"conversations":e.startsWith("trigger.")?"triggers":e.startsWith("channel.")?"channels":e.startsWith("instance.")?"instances":e.startsWith("node.")?"nodes":e.startsWith("project.")?"projects":e.startsWith("model.")?"models":e.startsWith("image.")?"images":"system"}class ga{clients=new Set;listeners=new Set;seq=0;connect(e){e.topics=new Set(["*"]),this.clients.add(e),e.on("close",()=>{this.clients.delete(e)}),e.on("message",t=>{const n=function(e){try{const t=JSON.parse(String(e||"{}"));return t&&"object"==typeof t?t:void 0}catch{return}}(t);"subscribe"===n?.type&&(e.topics=new Set(n.topics?.length?n.topics:["*"]))})}publish(e,t,n={}){const s=this.createEvent(e,t,n),i=s.topic,a=JSON.stringify(s);for(const e of this.listeners)try{e(s)}catch{this.listeners.delete(e)}for(const t of this.clients)if(t.readyState===t.OPEN&&ha(t.topics,i,e))try{t.send(a)}catch{this.clients.delete(t)}return s}on(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}send(e,t,n,s={}){const i=this.createEvent(t,n,s);return e.send(JSON.stringify(i)),i}createEvent(e,t,n={}){return{v:1,id:`evt_${Date.now().toString(36)}_${Math.random().toString(16).slice(2)}`,seq:++this.seq,type:e,topic:n.topic||ua(e),createdAt:(new Date).toISOString(),payload:t,scope:n.scope}}}function ha(e,t,n){return!e||e.has("*")||e.has(t)||e.has(n)}const ma={"image/bmp":".bmp","image/gif":".gif","image/jpeg":".jpg","image/png":".png","image/webp":".webp"},fa=a.z.object({instanceId:a.z.string().trim().min(1).max(120),sessionId:a.z.string().trim().min(1).max(120),kind:a.z.literal("image").default("image"),name:a.z.string().trim().min(1).max(240),mime:a.z.string().trim().min(1).max(120),data:a.z.string().min(1).max(31457280)}).strict();class Ia{items=new Map;ttlMs;constructor(e=864e5){this.ttlMs=e}upload(e){this.pruneExpired();const s=fa.parse(e),i=function(e,t){const n=/^data:([^;,]+);base64,(.+)$/i.exec(e.trim()),s=n?.[1]||t,i=n?.[2]||e;return{mime:s,buffer:Buffer.from(i,"base64")}}(s.data,s.mime),a=i.mime.toLowerCase(),o=ma[a];if(!o)throw new Error(`Unsupported image type: ${a}`);if(!i.buffer.length||i.buffer.length>m)throw new Error("Image must be between 1 byte and 20971520 bytes.");const c=`att_${r.randomUUID().replace(/-/g,"").slice(0,24)}`,l=new Date,p=new Date(l.getTime()+this.ttlMs),u=n.join(d.tmpdir(),"task-handoff-ai-session-attachments");t.mkdirSync(u,{recursive:!0});const g=function(e){return n.basename(e).replace(/[^\w.\- ()\u4e00-\u9fff]/g,"_").slice(0,160)||"image"}(s.name),h=n.join(u,`${c}-${g.endsWith(o)?g:`${g}${o}`}`);t.writeFileSync(h,i.buffer);const f={id:c,instanceId:s.instanceId,sessionId:s.sessionId,kind:"image",name:g,mime:a,size:i.buffer.length,data:i.buffer.toString("base64"),path:h,createdAt:l.toISOString(),expiresAt:p.toISOString()};return this.items.set(c,f),this.publicAttachment(f)}resolveRefs(e,t,n){this.pruneExpired();const s=a.z.array(C).max(6).default([]).parse(e),i=s.map(e=>this.requireAttachment(e,t,n));if(i.reduce((e,t)=>e+t.size,0)>f)throw new Error("Images must be 41943040 bytes or less in total.");for(const e of s)this.consumeAttachment(e.id);return i}requireAttachment(e,t,n){const s=this.items.get(e.id);if(!s)throw new Error(`AI session attachment not found or expired: ${e.id}`);if(e.kind&&e.kind!==s.kind)throw new Error(`AI session attachment kind mismatch: ${e.id}`);if(s.instanceId!==t||s.sessionId!==n)throw new Error(`AI session attachment scope mismatch: ${e.id}`);return E.parse({id:s.id,kind:s.kind,name:s.name,mime:s.mime,size:s.size,data:s.data})}consumeAttachment(e){const n=this.items.get(e);if(n){this.items.delete(e);try{t.unlinkSync(n.path)}catch{}}}publicAttachment(e){return{id:e.id,kind:e.kind,name:e.name,mime:e.mime,size:e.size,expiresAt:e.expiresAt}}pruneExpired(){const e=Date.now();for(const[n,s]of this.items)if(!(Date.parse(s.expiresAt)>e)){this.items.delete(n);try{t.unlinkSync(s.path)}catch{}}}}function ya(e){return e.readyState===(e.OPEN??1)}function ba(e,t,n){e.close(function(e){if("number"==typeof e&&Number.isInteger(e))return e>=3e3&&e<=4999||e>=1e3&&e<=1014&&1004!==e&&1005!==e&&1006!==e?e:void 0}(t),function(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):"string"==typeof e?e:"";if(Buffer.byteLength(t,"utf8")<=123)return t;let n="";for(const e of t){const t=`${n}${e}`;if(Buffer.byteLength(t,"utf8")>123)break;n=t}return n}(n)||void 0)}function wa(e,t){e.send(t.data,{binary:t.isBinary})}function Sa(e,t){if(ya(e))for(const n of t.splice(0))wa(e,n)}const Aa=a.z.object({nodeId:a.z.string().trim().min(1).max(120)}).strict();function va(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 ka(){return`req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}const Ta=new Map;function Ea(e,t){if("paired-hmac"!==e.auth.mode||!e.auth.keyId||!e.auth.secret){const e=new Error("Reverse tunnel requires paired-HMAC node auth.");throw Object.assign(e,{statusCode:401,code:"NODE_AGENT_TUNNEL_REQUIRES_PAIRED_HMAC"}),e}const n=function(e){const t=new Map;for(const[n,s]of Object.entries(e)){const e=Array.isArray(s)?s.join(","):"string"==typeof s?s:void 0===s?"":String(s);t.set(n.toLowerCase(),e)}return{nodeId:t.get("x-taskhandoff-node-id")||"",keyId:t.get("x-taskhandoff-key-id")||"",timestamp:t.get("x-taskhandoff-timestamp")||"",nonce:t.get("x-taskhandoff-nonce")||"",bodySha256:t.get("x-taskhandoff-body-sha256")||"",signature:t.get("x-taskhandoff-signature")||""}}(t.headers);if(n.nodeId!==e.id)throw Object.assign(new Error("Invalid reverse tunnel node id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NODE_MISMATCH"});if(n.keyId!==e.auth.keyId)throw Object.assign(new Error("Invalid reverse tunnel key id."),{statusCode:401,code:"NODE_AGENT_TUNNEL_KEY_INVALID"});const s=Date.parse(n.timestamp);if(!Number.isFinite(s)||Math.abs(Date.now()-s)>_s)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()-_s;for(const[e,t]of Ta)t<=i&&Ta.delete(e);const a=`${e.id}:${n.keyId}:${n.nonce}`;if(!n.nonce||Ta.has(a))throw Object.assign(new Error("Reverse tunnel nonce is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_NONCE_INVALID"});const o=Cs("");if(n.bodySha256!==o)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=Ns(e.auth.secret,{keyId:n.keyId,method:"GET",pathWithQuery:d,timestamp:n.timestamp,nonce:n.nonce,bodySha256:o});if(!function(e,t){const n=Buffer.from(e,"hex"),s=Buffer.from(t,"hex");return n.length===s.length&&r.timingSafeEqual(n,s)}(n.signature,c))throw Object.assign(new Error("Reverse tunnel signature is invalid."),{statusCode:401,code:"NODE_AGENT_TUNNEL_SIGNATURE_INVALID"});Ta.set(a,Date.now()+_s)}class _a{sockets=new Map;events;constructor(e){this.events=e}attach(e,t){const n=this.sockets.get(e);if(n){for(const t of n.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} was replaced.`));for(const[e,t]of n.streams)n.streams.delete(e),t.downstream.close(1011,"Reverse tunnel was replaced.")}const s=new Map;this.sockets.set(e,{socket:t,pending:s,streams:new Map}),t.on?.("close",()=>this.detach(e,t)),t.on?.("error",()=>this.detach(e,t))}detach(e,t){const n=this.sockets.get(e);if(n&&(!t||n.socket===t)){this.sockets.delete(e);for(const t of n.pending.values())clearTimeout(t.timer),t.reject(new Error(`Reverse tunnel for node ${e} disconnected.`));for(const[e,t]of n.streams)n.streams.delete(e),t.downstream.close(1011,"Reverse tunnel disconnected.")}}handleMessage(e,t){if(this.handleNodeAgentEvent(e,t))return!0;if(this.handleWebSocketMessage(e,t))return!0;if("node-agent.response"!==t.type)return!1;const n="string"==typeof t.requestId?t.requestId:"",s=this.sockets.get(e),i=s?.pending.get(n);if(!i||!s)return!0;s.pending.delete(n),clearTimeout(i.timer);const a=Number(t.status)||502,r=t.headers&&"object"==typeof t.headers?t.headers:{},o="string"==typeof t.body?t.body:"";if(t.error&&"object"==typeof t.error){const e=t.error,n=new Error("string"==typeof e.message?e.message:`Reverse tunnel request failed with HTTP ${a}`);return Object.assign(n,{statusCode:a,code:"string"==typeof e.code?e.code:"NODE_AGENT_REVERSE_REQUEST_FAILED"}),i.reject(n),!0}return i.resolve(new Response(o,{status:a,headers:r})),!0}handleNodeAgentEvent(e,t){if(!("string"==typeof t.type?t.type:"").startsWith("node-agent.event."))return!1;const n=t.event&&"object"==typeof t.event&&!Array.isArray(t.event)?t.event:void 0,s="string"==typeof n?.type?n.type:"";if(!s)return!0;const i="payload"in n?n.payload:{},a=n.scope&&"object"==typeof n.scope&&!Array.isArray(n.scope)?n.scope:{},r="string"==typeof t.instanceId?t.instanceId:void 0;return this.events?.publish(s,i,{topic:"string"==typeof n.topic?n.topic:void 0,scope:{...a,nodeId:e,instanceId:"string"==typeof a.instanceId?a.instanceId:"string"==typeof n.instanceId?n.instanceId:r}}),!0}async request(e,t,n={}){const s=this.sockets.get(e.id);if(!s){const t=new Error(`Reverse tunnel for node ${e.id} is not connected.`);throw Object.assign(t,{statusCode:503,code:"NODE_AGENT_REVERSE_TUNNEL_OFFLINE"}),t}const i=ka(),a="string"==typeof n.body?n.body:void 0===n.body||null===n.body?void 0:String(n.body),r={type:"control-plane.request",requestId:i,route:t,init:{method:n.method||"GET",headers:(o=n.headers,o?o instanceof Headers?Object.fromEntries(o.entries()):Array.isArray(o)?Object.fromEntries(o.map(([e,t])=>[e,t])):Object.fromEntries(Object.entries(o).map(([e,t])=>[e,String(t)])):{}),body:a}};var o;return new Promise((n,a)=>{const o=setTimeout(()=>{s.pending.delete(i);const n=new Error(`Reverse tunnel request to ${e.id}${t} timed out.`);Object.assign(n,{statusCode:504,code:"NODE_AGENT_REVERSE_REQUEST_TIMEOUT"}),a(n)},3e4);s.pending.set(i,{resolve:n,reject:a,timer:o}),s.socket.send(JSON.stringify(r))})}proxyWebSocket(e,t,n,s,i={}){const a=e.id,r=this.sockets.get(a);if(!r)return void t.close(1011,"Reverse tunnel is not connected.");const o=ka(),d=e=>{try{r.socket.send(JSON.stringify(e))}catch{r.streams.delete(o),t.close(1011,"Reverse tunnel send failed.")}},c={downstream:t,pendingFrames:[]};r.streams.set(o,c),d({type:"control-plane.websocket.open",streamId:o,route:n,protocols:Array.isArray(s)?s:s?[s]:[],headers:i}),t.on("message",(e,n)=>{const s=r.streams.get(o);if(s&&!s.upstream)return s.pendingFrames.length>=256?(r.streams.delete(o),t.close(1011,"Reverse websocket stream pending frame limit exceeded."),void d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Reverse websocket stream pending frame limit exceeded."})):void s.pendingFrames.push({data:e,isBinary:Boolean(n)})}),t.on("close",(e,t)=>{const n=r.streams.get(o);n&&(r.streams.delete(o),n.upstream?.close("number"==typeof e?e:1e3,Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""),d({type:"control-plane.websocket.close",streamId:o,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:""}))}),t.on("error",()=>{const e=r.streams.get(o);e&&(r.streams.delete(o),e.upstream?.close(1011,"Downstream websocket failed."),d({type:"control-plane.websocket.close",streamId:o,code:1011,reason:"Downstream websocket failed."}))})}attachWebSocketStream(e,t,n){const s=this.sockets.get(e),i=s?.streams.get(t);if(!s||!i)return n.close(1008,"Unknown reverse websocket stream."),!1;i.upstream=n;for(const e of i.pendingFrames.splice(0))n.send(e.data,{binary:e.isBinary});return function(e,t,n={}){const s=n.pendingFrameLimit??256;let i=!1,a=ya(t);const r=[],o=[];let d;const c=()=>{d&&(clearTimeout(d),d=void 0)},l=(n,i)=>{if(n.length>=s)return ba(e,1011,"WebSocket bridge pending frame limit exceeded."),void ba(t,1011,"WebSocket bridge pending frame limit exceeded.");n.push(i)};n.upstreamOpenTimeoutMs&&!a&&(d=setTimeout(()=>{a||i||(ba(e,1011,"Upstream websocket did not open."),ba(t))},n.upstreamOpenTimeoutMs)),e.on("open",()=>Sa(e,o)),t.on("open",()=>{c(),a=!0,n.onUpstreamOpen?.(),Sa(t,r)}),e.on("message",(e,n=!1)=>{const s={data:e,isBinary:Boolean(n)};ya(t)?wa(t,s):l(r,s)}),t.on("message",(t,n=!1)=>{const s={data:t,isBinary:Boolean(n)};ya(e)?wa(e,s):l(o,s)}),t.on("close",(t,s)=>{c(),n.onUpstreamClose?.(t,s),(a||i||!n.onUpstreamCloseBeforeOpen?.())&&ba(e,t,s)}),t.on("error",t=>{n.onUpstreamError?.(t),(a||i||!n.onUpstreamErrorBeforeOpen?.(t))&&ba(e)}),e.on("close",(e,s)=>{c(),i=!0,n.onClientClose?.(e,s),ba(t,e,s)}),e.on("error",e=>{c(),i=!0,n.onClientError?.(e),ba(t,1011,"WebSocket bridge client failed.")})}(i.downstream,n,{pendingFrameLimit:256,onClientClose:()=>{s.streams.delete(t)},onClientError:()=>{s.streams.delete(t)},onUpstreamClose:()=>{s.streams.delete(t)},onUpstreamError:()=>{s.streams.delete(t)}}),!0}handleWebSocketMessage(e,t){const n="string"==typeof t.type?t.type:"";if(!n.startsWith("node-agent.websocket."))return!1;const s=this.sockets.get(e),i="string"==typeof t.streamId?t.streamId:"",a=s?.streams.get(i);return!a||("node-agent.websocket.close"===n?(s?.streams.delete(i),a.downstream.close("number"==typeof t.code?t.code:1e3,"string"==typeof t.reason?t.reason:""),!0):"node-agent.websocket.error"!==n||(s?.streams.delete(i),a.downstream.close(1011,"string"==typeof t.message?t.message:"Reverse websocket proxy failed."),!0))}}class Ca{sockets=new Map;service;tunnel;timer;constructor(e,t){this.service=e,this.tunnel=t}start(){this.sync(),this.timer=setInterval(()=>this.sync(),5e3)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const e of this.sockets.values())e.close();this.sockets.clear()}syncNow(){this.sync()}sync(){const e=new Set;for(const t of this.service.listNodes()){const n=this.nodeEventsUrl(t);n&&(e.add(t.id),this.sockets.has(t.id)||this.connect(t,n))}for(const[t,n]of this.sockets)e.has(t)||(n.close(),this.sockets.delete(t))}connect(e,t){const n=Os(t),s=n?void 0:new URL(t),i=function(e,t){const n=function(e){return"paired-hmac"===e.auth.mode&&e.auth.secret?{keyId:e.auth.keyId,secret:e.auth.secret}:void 0}(e);if(n)return zs({nodeId:e.id,keyId:va(e,n.keyId),secret:n.secret,method:t.method,pathWithQuery:t.pathWithQuery});const s=function(e){if("local-ipc"===e.connectionMode||"local-loopback"===e.connectionMode)return"local-static-key"===e.auth.mode?e.auth.secret:void 0}(e);return s?{authorization:`Bearer ${s}`}:void 0}(e,{method:"GET",pathWithQuery:s?`${s.pathname}${s.search}`:"/api/node-agent/events"}),a=n?Ds(n,"/events",void 0,i):new c.WebSocket(t,{headers:i});this.sockets.set(e.id,a),a.on("message",t=>{let n;try{n=JSON.parse(String(t))}catch{return}if(n&&"object"==typeof n&&!Array.isArray(n)){const t=n,s="string"==typeof t.type&&t.type.startsWith("node-agent.")?t:{type:"node-agent.event.forwarded",event:t};this.tunnel.handleMessage(e.id,s)}}),a.on("close",()=>{this.sockets.get(e.id)===a&&this.sockets.delete(e.id)}),a.on("error",()=>{a.close()})}nodeEventsUrl(e){const t=e.controlEndpoint||e.endpoint;if(t&&"reverse-wss"!==e.connectionMode){if(Os(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 Na extends Error{code;lockPath;owner;constructor(e,t,n="process"){super(`${"control-plane"===n?"Control plane":"node-agent"===n?"Node agent":"Process"} is already running${t?.pid?` pid ${t.pid}`:""}. Lock: ${e}`),this.name="ProcessSingletonError",this.code="control-plane"===n?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===n?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=e,this.owner=t}}function za(e){return n.join(e,"owner.json")}function Oa(e){try{return JSON.parse(t.readFileSync(za(e),"utf8"))}catch{return}}function xa(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 Da(e,n){t.writeFileSync(za(e),`${JSON.stringify(n,null,2)}\n`,"utf8")}function Ra(e,t,n){if(function(e,t,n){return"system"===e.type||("chat-bridge"===e.type?function(e,t){return"ai-session"===t.type?["read","send-message","approve","interrupt"].includes(e):"instance"===t.type?["read","start","stop","restart"].includes(e):"read"===e}(t,n):"admin"===e.role||("viewer"===e.role?"read"===t:"operator"===e.role&&function(e,t){return"read"===e||("instance"===t.type?["create","update","delete","start","stop","restart","proxy"].includes(e):"ai-session"===t.type?["send-message","approve","interrupt"].includes(e):("trigger"===t.type||"runtime"===t.type)&&["create","update","delete"].includes(e))}(t,n)))}(e,t,n))return;const s=new Error(`Actor is not allowed to ${t} ${n.type}${n.id?` ${n.id}`:""}.`);throw Object.assign(s,{statusCode:403,code:"CONTROL_PLANE_FORBIDDEN"}),s}const ja=a.z.string().trim().min(1),Pa=a.z.object({id:ja}).strict(),$a=a.z.object({id:ja,keyId:ja}).strict(),Ma=a.z.object({id:ja,runtimeId:ja}).strict(),qa=a.z.object({id:ja,folderId:ja}).strict(),La=a.z.object({id:ja,sessionId:ja}).strict(),Ua=a.z.object({id:ja,sessionId:ja,queueId:ja}).strict(),Ba=a.z.object({configHash:ja}).strict(),Fa=a.z.object({id:ja,configHash:ja}).strict(),Ha=a.z.object({id:ja,sessionId:ja,configHash:ja}).strict(),Ga=a.z.object({path:a.z.string().optional(),depth:a.z.coerce.number().int().min(0).max(32).optional()}).strict(),Wa=a.z.object({ids:a.z.array(a.z.string().trim().min(1).max(120)).default([])}).strict(),Va=a.z.object({source:a.z.object({channel:Ve,bridgeId:a.z.string().trim().min(1).max(120).optional(),chatSessionId:a.z.string().trim().min(1).max(240),userId:a.z.string().trim().max(240).optional()}).strict(),action:a.z.discriminatedUnion("type",[a.z.object({type:a.z.literal("ai-session"),index:a.z.number().int().nonnegative()}).strict(),a.z.object({type:a.z.literal("instance-app-menu"),instanceId:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("launch-app"),instanceId:a.z.string().trim().min(1).max(120),appId:a.z.string().trim().min(1).max(120)}).strict(),a.z.object({type:a.z.literal("pending-decision"),routeId:a.z.string().trim().min(1).max(120),decision:a.z.enum(["allow","deny","skip"])}).strict()])}).strict();function Ka(e){const t=Va.parse(e);if(!t.action)throw new Error("Chat gateway action is required.");return{source:t.source,action:t.action}}const Ja=a.z.object({appId:a.z.string().trim().min(1).max(120).default("terminal-tty"),cwdFolderId:a.z.string().trim().min(1).max(120).optional(),options:a.z.record(a.z.string(),a.z.unknown()).default({})}).strict().default({appId:"terminal-tty",options:{}}),Qa=a.z.object({title:a.z.string().trim().min(1).max(120)}).strict(),Ya=a.z.object({id:a.z.string().trim().min(1),direction:a.z.enum(["import","export"]),preset:a.z.string().trim().min(1).max(120)});function Xa({app:e,service:t,events:n,appSessionAggregator:s,aiSessionAggregator:i,chatGateway:r,aiSessionAttachments:o,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}){!function({app:e,service:t,events:n}){e.get("/api/projects",async()=>({data:t.listProjects()})),e.post("/api/projects",async(e,s)=>{const i=t.createProject(e.body);return n.publish("project.created",{projectId:i.id}),s.code(201).send({data:i})}),e.get("/api/projects/:id",async e=>({data:t.requireProject(Pa.parse(e.params).id)})),e.patch("/api/projects/:id",async e=>{const s=t.updateProject(Pa.parse(e.params).id,e.body);return n.publish("project.updated",{projectId:s.id}),{data:s}}),e.delete("/api/projects/:id",async e=>{const s=Pa.parse(e.params).id,i=t.deleteProject(s);return n.publish("project.deleted",{projectId:s,deleted:i}),{data:{deleted:i}}}),e.get("/api/models",async()=>({data:t.listModels()})),e.post("/api/models",async(e,s)=>{const i=t.createModel(e.body);return n.publish("model.created",{modelId:i.id}),s.code(201).send({data:i})}),e.post("/api/models/reorder",async e=>{const s=Wa.parse(e.body),i=t.reorderModels(s.ids);return n.publish("model.reordered",{ids:s.ids}),{data:i}}),e.get("/api/models/:id",async e=>({data:t.requireModel(Pa.parse(e.params).id)})),e.patch("/api/models/:id",async e=>{const s=t.updateModel(Pa.parse(e.params).id,e.body);return n.publish("model.updated",{modelId:s.id}),{data:s}}),e.delete("/api/models/:id",async e=>{const s=Pa.parse(e.params).id,i=t.deleteModel(s);return n.publish("model.deleted",{modelId:s,deleted:i}),{data:{deleted:i}}}),e.get("/api/images",async()=>({data:t.listImages()})),e.post("/api/images",async(e,s)=>{const i=t.createImage(e.body);return n.publish("image.created",{imageId:i.id}),s.code(201).send({data:i})}),e.get("/api/images/:id",async e=>({data:t.requireImage(Pa.parse(e.params).id)})),e.patch("/api/images/:id",async e=>{const s=t.updateImage(Pa.parse(e.params).id,e.body);return n.publish("image.updated",{imageId:s.id}),{data:s}}),e.delete("/api/images/:id",async e=>{const s=Pa.parse(e.params).id,i=t.deleteImage(s);return n.publish("image.deleted",{imageId:s,deleted:i}),{data:{deleted:i}}})}({app:e,service:t,events:n}),function({app:e,service:t,events:n,nodeAgentTunnel:s,nodeEventSubscriber:i,errorPayload:a}){e.get("/api/nodes",async()=>({data:t.listPublicNodes()})),e.post("/api/nodes/local/sync",async()=>{const e=await t.syncLocalNodeConnection();return i.syncNow(),n.publish("node.local-synced",{nodeId:e.id}),{data:t.requirePublicNode(e.id)}}),e.post("/api/nodes",async(e,s)=>{const a=await t.createNode(e.body);return i.syncNow(),n.publish("node.created",{nodeId:a.id}),s.code(201).send({data:t.requirePublicNode(a.id)})}),e.post("/api/nodes/:id/check",async e=>{const s=await t.checkNode(Pa.parse(e.params).id);return n.publish("node.checked",{nodeId:s.id}),{data:s}}),e.get("/api/nodes/:id/updates/jobs",async e=>({data:await t.listNodeUpdateJobs(Pa.parse(e.params).id)})),e.post("/api/nodes/:id/updates/check",async e=>{const s=Pa.parse(e.params).id,i=await t.checkNodeUpdate(s,be.parse(e.body));return n.publish("node.update.checked",{nodeId:s,target:i.target}),{data:i}}),e.post("/api/nodes/:id/updates/apply",async(e,s)=>{const i=Pa.parse(e.params).id,a=await t.applyNodeUpdate(i,be.parse(e.body));return n.publish("node.update.queued",{nodeId:i,updateJobId:a.id,target:a.target}),s.code(202).send({data:a})}),e.post("/api/nodes/:id/pairing/invites",async(e,s)=>{const i=Pa.parse(e.params),a=await t.createNodePairingInvite(i.id,e.body);return n.publish("node.pairing-invite.created",{nodeId:i.id}),s.code(201).send({data:a})}),e.post("/api/nodes/:id/remotes/connect",async e=>{const s=Pa.parse(e.params),i=await t.connectNodeToControlPlane(s.id,e.body);return n.publish("node.remote-connect.requested",{nodeId:s.id}),{data:i}}),e.get("/api/nodes/:id/remotes",async e=>{const n=Pa.parse(e.params);return{data:await t.listNodeRemoteControlPlanes(n.id)}}),e.delete("/api/nodes/:id/remotes/:keyId",async e=>{const s=$a.parse(e.params),i=await t.deleteNodeRemoteControlPlane(s.id,s.keyId);return n.publish("node.remote-key.deleted",{nodeId:s.id,keyId:s.keyId}),{data:i}}),e.post("/api/node-join/invites",async(e,s)=>{const i=t.createNodeJoinInvite(e.body);return n.publish("node-join.invite.created",{inviteId:i.id}),s.code(201).send({data:i})}),e.post("/api/node-join/complete",async(e,s)=>{const a=t.completeNodeJoin(e.body);return i.syncNow(),n.publish("node.joined",{nodeId:a.id}),s.code(201).send({data:t.requirePublicNode(a.id)})}),e.get("/api/nodes/:id/runtimes",async e=>({data:await t.listNodeRuntimes(Pa.parse(e.params).id)})),e.post("/api/nodes/:id/runtimes",async(e,s)=>{const i=Pa.parse(e.params),a=await t.createNodeRuntime(i.id,e.body);return n.publish("node.runtime.created",{nodeId:i.id,runtimeId:a.id}),s.code(201).send({data:a})}),e.patch("/api/nodes/:id/runtimes/:runtimeId",async e=>{const s=Ma.parse(e.params),i=await t.updateNodeRuntime(s.id,s.runtimeId,e.body);return n.publish("node.runtime.updated",{nodeId:s.id,runtimeId:i.id}),{data:i}}),e.delete("/api/nodes/:id/runtimes/:runtimeId",async e=>{const s=Ma.parse(e.params),i=await t.deleteNodeRuntime(s.id,s.runtimeId);return n.publish("node.runtime.deleted",{nodeId:s.id,runtimeId:s.runtimeId}),{data:i}}),e.post("/api/nodes/:id/runtimes/:runtimeId/check",async e=>{const s=Ma.parse(e.params),i=await t.checkNodeRuntime(s.id,s.runtimeId);return n.publish("node.runtime.checked",{nodeId:s.id,runtimeId:i.id}),{data:i}}),e.get("/api/nodes/:id/docker/images",async e=>({data:await t.listNodeDockerImages(Pa.parse(e.params).id)})),e.get("/api/nodes/:id/local-folders",async e=>({data:await t.listNodeLocalFolders(Pa.parse(e.params).id)})),e.get("/api/nodes/:id/folders/tree",async e=>{const n=Pa.parse(e.params),s=Ga.parse(e.query);return{data:await t.listNodeFolderTree(n.id,{path:s.path,depth:s.depth})}}),e.post("/api/nodes/:id/local-folders",async(e,s)=>{const i=Pa.parse(e.params),a=await t.createNodeLocalFolder(i.id,e.body);return n.publish("node.local-folder.created",{nodeId:i.id,folderId:a.id}),s.code(201).send({data:a})}),e.delete("/api/nodes/:id/local-folders/:folderId",async e=>{const s=qa.parse(e.params),i=await t.deleteNodeLocalFolder(s.id,s.folderId);return n.publish("node.local-folder.deleted",{nodeId:s.id,folderId:s.folderId}),{data:i}}),e.get("/api/nodes/:id",async e=>({data:t.requirePublicNode(Pa.parse(e.params).id)})),e.patch("/api/nodes/:id",async e=>{const s=t.updateNode(Pa.parse(e.params).id,e.body);return i.syncNow(),n.publish("node.updated",{nodeId:s.id}),{data:t.requirePublicNode(s.id)}}),e.delete("/api/nodes/:id",async e=>{const s=Pa.parse(e.params).id,a=t.deleteNode(s);return i.syncNow(),n.publish("node.deleted",{nodeId:s,deleted:a}),{data:{deleted:a}}}),e.get("/api/node-runtimes",async()=>{const e=await t.listNodeRuntimesWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),function(e){const{app:t,service:n,nodeAgentTunnel:s,errorPayload:i}=e;t.get("/api/node-agent/tunnel",{websocket:!0},(e,t)=>{try{const i=Aa.parse(t.query),a=n.requireNode(i.nodeId);Ea(a,t),s.attach(a.id,e),e.send(JSON.stringify({type:"control-plane.hello",protocolVersion:le,nodeId:a.id,serverTime:(new Date).toISOString(),capabilities:{reverseTunnel:"request-response",lifecycleCommands:!0,instanceApiProxy:!0}})),e.on("message",t=>{let n;try{n=JSON.parse(String(t))}catch{return void e.send(JSON.stringify({type:"control-plane.error",code:"INVALID_JSON"}))}const i=n&&"object"==typeof n?n:{};s.handleMessage(a.id,i)||("node-agent.ping"!==i.type?"node-agent.identify"!==i.type?e.send(JSON.stringify({type:"control-plane.error",code:"UNSUPPORTED_MESSAGE"})):e.send(JSON.stringify({type:"control-plane.identified",nodeId:a.id,serverTime:(new Date).toISOString()})):e.send(JSON.stringify({type:"control-plane.pong",nodeId:a.id,serverTime:(new Date).toISOString()})))})}catch(t){const n=i(t);e.send(JSON.stringify({type:"control-plane.error",code:n.code,message:n.message})),e.close(1008,n.message)}}),t.get("/api/node-agent/tunnel/streams/:streamId",{websocket:!0},(e,t)=>{try{const i=Aa.parse(t.query),a=n.requireNode(i.nodeId);Ea(a,t);const r=t.params.streamId;s.attachWebSocketStream(a.id,r,e)}catch(t){const n=i(t);e.close(1008,n.message)}})}({app:e,service:t,nodeAgentTunnel:s,errorPayload:a})}({app:e,service:t,events:n,nodeAgentTunnel:d,nodeEventSubscriber:c,errorPayload:l}),e.get("/api/app-sessions",async e=>{const t=a.z.object({refresh:a.z.string().optional(),includeTombstones:a.z.string().optional(),instanceId:a.z.string().trim().min(1).optional(),sinceRevision:a.z.string().optional()}).parse(e.query||{});if(void 0!==t.sinceRevision){const e=Number(t.sinceRevision);if(!Number.isInteger(e)||e<0){const e=new Error("sinceRevision must be a non-negative integer.");throw Object.assign(e,{statusCode:400,code:"APP_SESSION_DELTA_INVALID"}),e}return{data:await s.delta({instanceId:t.instanceId,sinceRevision:e})}}return{data:await s.list({refresh:"true"===t.refresh||"1"===t.refresh,includeTombstones:"true"===t.includeTombstones||"1"===t.includeTombstones})}}),e.get("/api/controlled-instances",async()=>({data:await t.listControlledInstances()})),e.post("/api/controlled-instances",async(e,s)=>{const i=await t.createControlledInstance(e.body);return n.publish("instance.created",{instanceId:i.id}),s.code(201).send({data:i})}),e.get("/api/controlled-instances/:id",async e=>({data:await t.requireControlledInstance(Pa.parse(e.params).id)})),e.patch("/api/controlled-instances/:id",async e=>{const s=await t.updateControlledInstance(Pa.parse(e.params).id,e.body);return n.publish("instance.updated",{instanceId:s.id}),{data:s}}),e.delete("/api/controlled-instances/:id",async e=>{const s=Pa.parse(e.params).id,i=await t.deleteControlledInstance(s);return n.publish("instance.deleted",{instanceId:s,deleted:i}),{data:{deleted:i}}}),e.post("/api/controlled-instances/:id/start",async e=>{const s=await t.startControlledInstance(Pa.parse(e.params).id);return n.publish("instance.started",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/stop",async e=>{const s=await t.stopControlledInstance(Pa.parse(e.params).id);return n.publish("instance.stopped",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/restart",async e=>{const s=await t.restartControlledInstance(Pa.parse(e.params).id);return n.publish("instance.restarted",{instanceId:s.id}),{data:s}}),e.post("/api/controlled-instances/:id/apps/sessions",async e=>{const s=Pa.parse(e.params),i=Ja.parse(e.body),a=await t.launchAppSession(s.id,i.appId,{...i.options,...i.cwdFolderId?{cwdFolderId:i.cwdFolderId}:{}});return n.publish("instance.app-session.launched",{instanceId:s.id,sessionId:"string"==typeof a.id?a.id:void 0,appId:i.appId}),{data:a}}),e.post("/api/controlled-instances/:id/apps/sessions/:sessionId/stop",async e=>{const s=La.parse(e.params),i=await t.stopAppSession(s.id,s.sessionId);return n.publish("instance.app-session.stopped",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.patch("/api/controlled-instances/:id/apps/sessions/:sessionId",async e=>{const s=La.parse(e.params),i=Qa.parse(e.body),a=await t.renameAppSession(s.id,s.sessionId,i.title);return n.publish("instance.app-session.renamed",{instanceId:s.id,sessionId:s.sessionId,title:i.title}),{data:a}}),e.get("/api/controlled-instances/:id/app-sessions",async e=>{const t=Pa.parse(e.params),n=(await s.list()).instances.find(e=>e.instanceId===t.id);return{data:n?.appSessions||{runningCount:0,problemCount:0,sessions:[],updatedAt:(new Date).toISOString()}}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/messages",async e=>{const s=La.parse(e.params),i=O.parse(e.body||{}),a=o.resolveRefs(i.attachments,s.id,s.sessionId),r=await t.sendAiSessionMessage(s.id,s.sessionId,i.message,i.mode,a);return n.publish("instance.ai-session.message-sent",{instanceId:s.id,sessionId:s.sessionId}),{data:r}}),e.get("/api/controlled-instances/:id/ai-sessions/:sessionId/queue",async e=>{const n=La.parse(e.params);return{data:await t.aiSessionQueue(n.id,n.sessionId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/steer",async e=>{const s=Ua.parse(e.params),i=await t.steerAiSessionQueuedMessage(s.id,s.sessionId,s.queueId);return n.publish("instance.ai-session.message-sent",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId/retry",async e=>{const n=Ua.parse(e.params);return{data:await t.retryAiSessionQueuedMessage(n.id,n.sessionId,n.queueId)}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/queue/:queueId",async e=>{const n=Ua.parse(e.params);return{data:await t.removeAiSessionQueuedMessage(n.id,n.sessionId,n.queueId)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/interrupt",async e=>{const s=La.parse(e.params),i=await t.interruptAiSession(s.id,s.sessionId);return n.publish("instance.ai-session.interrupted",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/approval",async e=>{const s=La.parse(e.params),i=x.parse(e.body||{}),a=await t.resolveAiSessionApproval(s.id,s.sessionId,i.decision);return n.publish("instance.ai-session.approval-resolved",{instanceId:s.id,sessionId:s.sessionId,decision:i.decision}),{data:a}}),e.get("/api/config-sync/presets",async()=>({data:t.listConfigSyncPresets()})),e.post("/api/controlled-instances/:id/config-sync/:direction/:preset",async e=>{const s=Ya.parse(e.params),i=await t.syncInstanceConfig(s.id,s.direction,s.preset);return n.publish("instance.config-synced",{instanceId:s.id,direction:s.direction,preset:s.preset}),{data:i}}),e.get("/api/instance-board",async()=>{const e=await t.boardWithDiagnostics();return{data:e.items,meta:{nodeErrors:e.nodeErrors}}}),e.get("/api/ai-sessions",async(e,t)=>{const n=e.query;if(void 0!==n.sinceRevision){const e=Number(n.sinceRevision);if(!Number.isInteger(e)||e<0)return t.code(400).send({error:"invalid_since_revision"});try{return{data:await i.delta({instanceId:n.instanceId,sinceRevision:e})}}catch(e){if("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED"===e.message)return t.code(400).send({error:"instance_id_required"});throw e}}return{data:await i.list({refresh:"true"===n.refresh||"1"===n.refresh})}}),e.get("/api/triggers",async()=>({data:await t.listTriggers()})),e.post("/api/triggers",async(e,s)=>{const i=t.createTrigger(e.body);return n.publish("trigger.created",{configHash:i.configHash}),s.code(201).send({data:i})}),e.delete("/api/triggers/:configHash",async e=>{const s=Ba.parse(e.params),i=await t.deleteTrigger(s.configHash);return n.publish("trigger.deleted",{configHash:s.configHash}),{data:i}}),e.post("/api/triggers/:configHash/apply",async e=>{const s=Ba.parse(e.params),i=await t.applyTrigger(s.configHash,e.body||{});return n.publish("trigger.applied",{configHash:s.configHash}),{data:i}}),e.get("/api/controlled-instances/:id/triggers",async e=>{const n=Pa.parse(e.params);return{data:await t.listInstanceTriggers(n.id)}}),e.post("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers",async e=>{const s=La.parse(e.params),i=await t.bindAiSessionTrigger(s.id,s.sessionId,e.body||{});return n.publish("instance.ai-session.trigger-bound",{instanceId:s.id,sessionId:s.sessionId}),{data:i}}),e.delete("/api/controlled-instances/:id/ai-sessions/:sessionId/triggers/:configHash",async e=>{const s=Ha.parse(e.params),i=await t.unbindAiSessionTrigger(s.id,s.sessionId,s.configHash);return n.publish("instance.ai-session.trigger-unbound",{instanceId:s.id,sessionId:s.sessionId,configHash:s.configHash}),{data:i}}),e.post("/api/controlled-instances/:id/triggers/:configHash/run",async e=>{const s=Fa.parse(e.params),i=await t.runInstanceTrigger(s.id,s.configHash,e.body||{});return n.publish("instance.trigger.run",{instanceId:s.id,configHash:s.configHash}),{data:i}}),function({app:e,service:t,chatGateway:n}){e.get("/api/chat/sessions",async()=>({data:t.listChatSessions()})),e.get("/api/chat/sessions/:id",async e=>({data:t.requireChatSession(Pa.parse(e.params).id)})),e.get("/api/chat-gateway/status",async()=>({data:n.status()})),e.post("/api/chat-gateway/poll-ai-sessions",async()=>({data:await n.pollAiSessionsNow()})),e.get("/api/chat-gateway/bridges",async()=>({data:t.listChatBridges()})),e.post("/api/chat-gateway/bridges",async e=>({data:t.createChatBridge(e.body)})),e.patch("/api/chat-gateway/bridges/:id",async s=>{const i=Pa.parse(s.params).id,a=t.requireChatBridge(i),r=Tn.parse(s.body),o=t.updateChatBridge(i,s.body),d="boolean"==typeof r.enabled?r.enabled:void 0;return e.log.info({component:"control-plane-chat-gateway",action:"bridge-patch",bridgeId:i,hasEnabled:void 0!==d,requestedEnabled:d,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),void 0!==d&&d!==a.enabled&&(d?n.startBridge(i):n.stopBridge(i)),{data:o}}),e.post("/api/chat-gateway/bridges/:id/start",async s=>{const i=Pa.parse(s.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!0}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-start-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:n.startBridge(i)}}),e.post("/api/chat-gateway/bridges/:id/stop",async s=>{const i=Pa.parse(s.params).id,a=t.requireChatBridge(i);return t.updateChatBridge(i,{enabled:!1}),e.log.info({component:"control-plane-chat-gateway",action:"bridge-stop-request",bridgeId:i,previousEnabled:a.enabled,updatedEnabled:t.requireChatBridge(i).enabled},"chat gateway bridge state changed"),{data:n.stopBridge(i)}}),e.delete("/api/chat-gateway/bridges/:id",async e=>({data:{deleted:t.deleteChatBridge(Pa.parse(e.params).id)}})),e.post("/api/chat-gateway/messages",async e=>({data:await t.handleChatGatewayMessage(Qe.parse(e.body))})),e.post("/api/chat-gateway/actions",async e=>({data:await t.handleChatGatewayAction(Ka(e.body))})),e.get("/api/pending-routes",async()=>({data:await t.listPendingRoutes()}))}({app:e,service:t,chatGateway:r})}const Za=new Set(["connection","content-length","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),er=new Set(["content-encoding"]);function tr(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>!Za.has(e.toLowerCase())&&void 0!==t).map(([e,t])=>[e,Array.isArray(t)?t.join(", "):String(t)]))}function nr(e){return Object.fromEntries(Object.entries(tr(e)).filter(([e])=>{const t=e.toLowerCase();return"host"!==t&&"cookie"!==t&&"authorization"!==t&&(!t.startsWith("sec-websocket-")||"sec-websocket-origin"===t)}))}function sr(e){const t=e["sec-websocket-protocol"],n=(Array.isArray(t)?t.join(","):"string"==typeof t?t:"").split(",").map(e=>e.trim()).filter(Boolean);return n.length?n:void 0}function ir(e,t){for(const[n,s]of Object.entries(t.headers)){const t=n.toLowerCase();Za.has(t)||er.has(t)||e.header(n,s)}return e.code(t.status).send(t.body)}function ar(e){return`/instances/${encodeURIComponent(e)}`}function rr(e,t,n,s){const i=function(e,t){const n=e.split("?",1)[0]||"/";return t.toLowerCase().includes("text/html")&&!n.startsWith("/api/")}(s,Object.entries(t.headers).find(([e])=>"content-type"===e.toLowerCase())?.[1]||"")?Buffer.from(function(e,t){if(e.includes("__TASK_HANDOFF_PUBLIC_BASE__"))return e;const n=`<script>window.__TASK_HANDOFF_PUBLIC_BASE__=${JSON.stringify(t)};<\/script>`;return/<head(\s[^>]*)?>/i.test(e)?e.replace(/<head(\s[^>]*)?>/i,e=>`${e}\n${n}`):`${n}\n${e}`}(t.body.toString("utf8"),ar(n)),"utf8"):t.body;return ir(e,{...t,body:i})}function or(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 dr(e,t){if("string"!=typeof t||!e.hasContentTypeParser(t))try{e.addContentTypeParser(t,{parseAs:"buffer"},(e,t,n)=>n(null,t))}catch(e){if(e instanceof Error&&/Content type parser .* already present/i.test(e.message))return;throw e}}function cr(e){return new URL(e,"http://control-plane.local").searchParams.get("token")||""}function lr(e,t,n){const s=encodeURIComponent(e),i=encodeURIComponent(t),a=n.vnc&&"object"==typeof n.vnc?n.vnc:{};return"kasmvnc"===("string"==typeof a.backend?a.backend:"")?`/apps/access/vnc/proxy/api/apps/sessions/${i}/web/?token=${s}`:`/apps/access/vnc/proxy/api/novnc/vnc.html?token=${s}&path=${encodeURIComponent(`apps/access/vnc/proxy/api/apps/sessions/${i}/vnc?token=${s}`)}&autoconnect=1&resize=scale`}class pr{snapshots=new Map;history=new Map;listeners=new Set;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===I?W:e.type===y?V:e.type===b?K:void 0;if(!t)return!1;const n=t.safeParse(e.payload);return n.success?(e.type===I&&this.applySnapshot(n.data),e.type===y&&this.applyPatch(n.data),e.type===b&&this.applyRemoved(n.data),!0):(this.logger?.warn?.({eventType:e.type,issues:n.error.issues,errorCode:"AI_SESSION_EVENT_INVALID"},"ai-session.aggregator.event.invalid"),!0)}onSnapshot(e){return this.listeners.add(e),()=>this.listeners.delete(e)}applySnapshot(e){return this.accept({type:I,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=new Map(t.snapshot.sessions.map(e=>[e.id,e]));for(const t of e.upserted)n.set(t.id,t);for(const t of e.removed)n.delete(t);return this.accept({type:y,payload:e},ur([...n.values()],e.meta.generatedAt,t.snapshot.staleCount))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=new Set(e.sessionIds);return this.accept({type:b,payload:e},ur(t.snapshot.sessions.filter(e=>!n.has(e.id)),e.meta.generatedAt,t.snapshot.staleCount))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view()}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const n=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>n)return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!0,events:[]});if(e.sinceRevision===n)return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!1,events:[]});const s=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!s.length||s[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return J.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:i,events:i?[]:s.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:n}=e.payload,s=this.snapshots.get(n.instanceId);if(s&&s.revision>=n.snapshotRevision)return this.logger?.warn?.({traceId:n.traceId,instanceId:n.instanceId,currentRevision:s.revision,snapshotRevision:n.snapshotRevision},"ai-session.aggregator.event.stale"),!1;this.snapshots.set(n.instanceId,{snapshot:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt}),this.rememberEvent(n.instanceId,e);const i={instanceId:n.instanceId,aiSessions:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt};for(const e of this.listeners)try{e(i)}catch{this.listeners.delete(e)}return this.logger?.info?.({traceId:n.traceId,instanceId:n.instanceId,snapshotRevision:n.snapshotRevision,eventSeq:n.eventSeq,reason:n.reason,sessionCount:t.sessions.length},"ai-session.aggregator.event.accepted"),!0}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"ai-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"ai-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.aiSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const n=this.history.get(e)??[];n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const n=t.filter(e=>e.createdAtMs>=Date.now()-36e5);n.length?this.history.set(e,n):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("AI_SESSION_DELTA_INSTANCE_ID_REQUIRED")}view(){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([e,t])=>({instanceId:e,aiSessions:t.snapshot,revision:t.revision,lastEventAt:t.lastEventAt}))}}}function ur(e,t,n){return L.parse({sessions:e,runningCount:e.filter(e=>"running"===e.status).length,waitingCount:e.filter(e=>"waiting"===e.status).length,staleCount:n,updatedAt:t})}class gr{snapshots=new Map;history=new Map;bootstrap;logger;constructor(e){this.bootstrap=e.bootstrap,this.logger=e.logger}handleEvent(e){const t=e.type===kt?Rt:e.type===Tt?jt:e.type===Et?Pt:void 0;if(!t)return!1;const n=t.safeParse(e.payload);return n.success?(e.type===kt&&this.applySnapshot(n.data),e.type===Tt&&this.applyPatch(n.data),e.type===Et&&this.applyRemoved(n.data),!0):(this.logger?.warn?.({eventType:e.type,issues:n.error.issues,errorCode:"APP_SESSION_EVENT_INVALID"},"app-session.aggregator.event.invalid"),!0)}applySnapshot(e){return this.accept({type:kt,payload:e},e.snapshot)}applyPatch(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=[...t.snapshot.sessions],s=n.findIndex(t=>t.id===e.session.id);return s<0?n.push(e.session):n[s]={...n[s],...e.session},this.accept({type:Tt,payload:e},Mt(n,e.meta.generatedAt))}applyRemoved(e){const t=this.currentForIncremental(e.meta);if(!t)return!1;const n=t.snapshot.sessions.filter(t=>t.id!==e.sessionId);return e.tombstone&&n.push(e.tombstone),this.accept({type:Et,payload:e},Mt(n,e.meta.generatedAt))}async list(e={}){return(e.refresh||0===this.snapshots.size)&&await this.bootstrapFromInstances(),this.view({includeTombstones:e.includeTombstones})}async delta(e){0===this.snapshots.size&&await this.bootstrapFromInstances();const t=this.resolveDeltaInstanceId(e.instanceId);this.pruneHistory(t);const n=this.snapshots.get(t)?.revision??0;if(e.sinceRevision>n)return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!0,events:[]});if(e.sinceRevision===n)return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:!1,events:[]});const s=(this.history.get(t)??[]).filter(t=>t.payload.meta.snapshotRevision>e.sinceRevision),i=!s.length||s[0].payload.meta.snapshotRevision!==e.sinceRevision+1;return $t.parse({instanceId:t,sinceRevision:e.sinceRevision,latestRevision:n,syncRequired:i,events:i?[]:s.map(({type:e,payload:t})=>({type:e,payload:t}))})}accept(e,t){const{meta:n}=e.payload,s=this.snapshots.get(n.instanceId);return s&&s.revision>=n.snapshotRevision?(this.logger?.warn?.({traceId:n.traceId,instanceId:n.instanceId,currentRevision:s.revision,snapshotRevision:n.snapshotRevision},"app-session.aggregator.event.stale"),!1):(this.snapshots.set(n.instanceId,{snapshot:t,revision:n.snapshotRevision,lastEventAt:n.generatedAt}),this.rememberEvent(n.instanceId,e),this.logger?.info?.({traceId:n.traceId,instanceId:n.instanceId,snapshotRevision:n.snapshotRevision,eventSeq:n.eventSeq,reason:n.reason,sessionCount:t.sessions.length,runningCount:t.runningCount,problemCount:t.problemCount},"app-session.aggregator.event.accepted"),!0)}currentForIncremental(e){const t=this.snapshots.get(e.instanceId);if(t&&e.snapshotRevision===t.revision+1&&(void 0===e.previousRevision||e.previousRevision===t.revision))return t;this.logger?.warn?.({instanceId:e.instanceId,currentRevision:t?.revision,previousRevision:e.previousRevision,snapshotRevision:e.snapshotRevision,traceId:e.traceId},"app-session.aggregator.event.gap")}async bootstrapFromInstances(){this.logger?.info?.({},"app-session.aggregator.bootstrap");const e=await this.bootstrap();for(const t of e.instances){const e=this.snapshots.get(t.instanceId);e&&e.revision>=t.revision||this.snapshots.set(t.instanceId,{snapshot:t.appSessions,revision:t.revision,lastEventAt:t.lastEventAt})}}rememberEvent(e,t){const n=this.history.get(e)??[];n.push({...t,createdAtMs:Date.now()}),this.history.set(e,n),this.pruneHistory(e)}pruneHistory(e){const t=this.history.get(e);if(!t?.length)return;const n=t.filter(e=>e.createdAtMs>=Date.now()-36e5);n.length?this.history.set(e,n):this.history.delete(e)}resolveDeltaInstanceId(e){if(e)return e;const t=[...this.snapshots.keys()];if(1===t.length)return t[0];throw new Error("APP_SESSION_DELTA_INSTANCE_ID_REQUIRED")}visibleSnapshot(e){return Mt(e.sessions.filter(e=>Yn("string"==typeof e.status?e.status:void 0)),e.updatedAt)}view(e={}){return{updatedAt:(new Date).toISOString(),instances:[...this.snapshots.entries()].map(([t,n])=>({instanceId:t,appSessions:e.includeTombstones?n.snapshot:this.visibleSnapshot(n.snapshot),revision:n.revision,lastEventAt:n.lastEventAt}))}}}function hr(e){return e.raw[0].replace(/\\\$\{/g,"${")}function mr(e){const t=process.env[e]?.trim();return t||void 0}function fr(){try{const e=n.resolve(__dirname,"..","package.json");return JSON.parse(t.readFileSync(e,"utf8")).version||"unknown"}catch{return"unknown"}}function Ir(){return{component:"control-plane",packageName:"@task-handoff/control-plane",packageVersion:fr(),protocolVersion:le,buildId:mr("TASK_HANDOFF_BUILD_ID"),builtAt:mr("TASK_HANDOFF_BUILT_AT"),gitCommit:mr("TASK_HANDOFF_GIT_COMMIT"),imageRef:mr("TASK_HANDOFF_IMAGE_REF"),imageDigest:mr("TASK_HANDOFF_IMAGE_DIGEST")}}const yr={".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 br(e,s){const i=n.resolve(e),a=n.resolve(i,s);if(a===i||a.startsWith(`${i}${n.sep}`))return t.existsSync(a)&&t.statSync(a).isFile()?a:void 0}function wr(e,s,i){const a=br(s,i);return a?(e.header("cache-control","no-store"),e.type(yr[n.extname(a).toLowerCase()]||"application/octet-stream"),e.send(t.createReadStream(a))):e.code(404).send({error:{code:"STATIC_FILE_NOT_FOUND",message:`${i} was not found in the Control Plane UI static directory.`}})}function Sr(e){if(e instanceof a.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 Ar(e,t){if(!e.enabled())return{type:"system",reason:"auth-disabled"};const n=await e.userForRequest(t);return n?{type:"user",userId:n.id,role:n.role}:void 0}function vr(e,t){const n=t.split("?")[0]||"/";if(!n.startsWith("/api/"))return;if(n.startsWith("/api/auth/")||"/api/health"===n||"/api/events"===n)return;const s=function(e){return"GET"===e||"HEAD"===e?"read":"POST"===e?"create":"PATCH"===e||"PUT"===e?"update":"DELETE"===e?"delete":"read"}(e);return n.startsWith("/api/control-plane/settings")?{action:"GET"===e?"read":"manage-settings",resource:{type:"control-plane-settings"}}:n.startsWith("/api/models")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"model"}}:n.startsWith("/api/chat-gateway/bridges")?{action:"GET"===e?"read":"manage-secrets",resource:{type:"chat-bridge"}}:"/api/node-join/invites"===n?{action:"manage-node-auth",resource:{type:"node"}}:n.startsWith("/api/nodes")?/\/(runtimes|local-folders)(\/|$)/.test(n)?{action:s,resource:{type:"runtime"}}:{action:"GET"===e?"read":"manage-node-auth",resource:{type:"node"}}:n.startsWith("/api/controlled-instances")?n.includes("/ai-sessions/")?n.endsWith("/approval")?{action:"approve",resource:{type:"ai-session"}}:n.endsWith("/interrupt")?{action:"interrupt",resource:{type:"ai-session"}}:n.includes("/messages")||n.includes("/queue/")?{action:"send-message",resource:{type:"ai-session"}}:{action:"read",resource:{type:"ai-session"}}:n.includes("/start")?{action:"start",resource:{type:"instance"}}:n.includes("/stop")?{action:"stop",resource:{type:"instance"}}:n.includes("/restart")?{action:"restart",resource:{type:"instance"}}:n.includes("/config-sync/")?{action:"update",resource:{type:"instance"}}:{action:s,resource:{type:"instance"}}:n.startsWith("/api/projects")?{action:s,resource:{type:"project"}}:n.startsWith("/api/images")?{action:s,resource:{type:"runtime"}}:n.startsWith("/api/triggers")?{action:s,resource:{type:"trigger"}}:{action:"read",resource:{type:"control-plane-settings"}}}async function kr(e={}){const a=Xe(e.dataDir),r=s({logger:e.logger??!0,bodyLimit:67108864}),o=new Ls(a,{...e.service,logger:r.log}),d=new vt(a,e.auth),c=new ga,l=new pr({bootstrap:()=>o.bootstrapAiSessionsFromInstances(),logger:r.log}),p=new gr({bootstrap:()=>o.bootstrapAppSessionsFromInstances(),logger:r.log});o.setAppSessionSnapshotProvider(e=>p.list(e)),o.setAiSessionSnapshotProvider(e=>l.list(e)),c.on(e=>{p.handleEvent(e),l.handleEvent(e)});const u=new Ia,g=new _a(c);o.setNodeAgentTransport(g),o.init(),await o.syncLocalNodeConnection().catch(()=>{}),d.init();const h=new ji(o,e.service?.fetchImpl,{aiSessions:l,logger:r.log});h.startEnabled();const m=new Ca(o,g);m.start(),await r.register(i),r.addHook("onClose",async()=>{m.stop(),h.stopAll()});const f=(I=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(I||"").trim().toLowerCase()));var I;r.setErrorHandler((e,t,n)=>{const s=Sr(e);(f||s.statusCode>=500)&&(s.statusCode>=500?r.log.error.bind(r.log):r.log.warn.bind(r.log))({method:t.method,url:t.url,statusCode:s.statusCode,errorCode:s.code,error:s.message},"control plane request failed"),n.code(s.statusCode).send({error:{code:s.code,message:s.message}})}),r.addHook("preHandler",async(e,t)=>{if(!d.enabled()||function(e,t){const n=t.split("?")[0];return!("/api/health"!==n&&!n.startsWith("/api/auth/"))||"GET"===e&&"/install-node-agent.sh"===n||"POST"===e&&"/api/node-join/complete"===n||"/api/app-access/session"===n||!!n.startsWith("/apps/access/")||!("/favicon.ico"!==n&&!n.startsWith("/assets/"))||"GET"===e&&!n.startsWith("/api/")&&!n.startsWith("/instances/")}(e.method,e.url)){const t=await Ar(d,e.headers),n=vr(e.method,e.url);return void(n&&Ra(t||{type:"system",reason:"auth-disabled"},n.action,n.resource))}const n=await Ar(d,e.headers);if(!n)return t.code(401).send({error:{code:"CONTROL_PLANE_AUTH_REQUIRED",message:"Sign in to access the Control Plane."}});const s=vr(e.method,e.url);s&&Ra(n,s.action,s.resource)});const y=n.resolve(e.staticDir||process.env.TASK_HANDOFF_CONTROL_PLANE_STATIC_DIR||n.resolve(process.cwd(),"packages","control-plane-ui","dist"));return t.existsSync(y)&&(r.get("/assets/*",async(e,t)=>{const s=e.params["*"];return wr(t,y,n.join("assets",s||""))}),r.get("/favicon.ico",async(e,t)=>br(y,"favicon.ico")?wr(t,y,"favicon.ico"):t.code(204).send())),r.get("/api/health",async()=>({data:{ok:!0,role:"control-plane",protocolVersion:le,build:Ir(),dataDir:a.dataDir,serverTime:(new Date).toISOString()}})),r.get("/install-node-agent.sh",async(e,t)=>(t.header("cache-control","no-store"),t.type("text/x-shellscript; charset=utf-8"),hr`#!/bin/sh
|
|
2
2
|
set -eu
|
|
3
3
|
|
|
4
4
|
CONTROL_PLANE_URL=""
|
|
5
5
|
JOIN_TOKEN=""
|
|
6
|
-
SERVICE_USER="
|
|
6
|
+
SERVICE_USER="root"
|
|
7
7
|
DATA_DIR="/var/lib/task-handoff/node-agent"
|
|
8
8
|
ENV_DIR="/etc/task-handoff"
|
|
9
9
|
HOST="127.0.0.1"
|
|
@@ -211,4 +211,4 @@ fi
|
|
|
211
211
|
|
|
212
212
|
echo "TaskHandoff node-agent is installed and running."
|
|
213
213
|
echo "Service: task-handoff-node-agent.service"
|
|
214
|
-
`)),r.get("/api/events",{websocket:!0},e=>{c.connect(e),c.send(e,"events.connected",{})}),r.get("/api/auth/session",async e=>({data:await d.currentSession(e.headers)})),r.post("/api/auth/bootstrap-admin",async(e,t)=>{const
|
|
214
|
+
`)),r.get("/api/events",{websocket:!0},e=>{c.connect(e),c.send(e,"events.connected",{})}),r.get("/api/auth/session",async e=>({data:await d.currentSession(e.headers)})),r.post("/api/auth/bootstrap-admin",async(e,t)=>{const n=await d.bootstrapAdmin(e.body);return t.code(201).send({data:n})}),r.post("/api/auth/login",async(e,t)=>{const n=await d.login(e.body);return t.header("set-cookie",n.cookie),{data:{user:n.user}}}),r.post("/api/auth/logout",async(e,t)=>{const n=d.logout(e.headers);return t.header("set-cookie",`${ct}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`),{data:n}}),r.get("/api/control-plane/status",async()=>({data:{protocolVersion:le,build:Ir(),storage:a,counts:{projects:o.listProjects().length,models:o.listModels().length,images:o.listImages().length,nodes:o.listNodes().length,nodeRuntimes:(await o.listNodeRuntimes()).length,controlledInstances:(await o.listControlledInstances()).length,chatSessions:o.listChatSessions().length}}})),r.get("/api/control-plane/settings",async()=>({data:o.getSettings()})),r.patch("/api/control-plane/settings",async e=>({data:o.updateSettings(e.body||{})})),r.post("/api/ai-session-attachments",async(e,t)=>{try{return t.code(201).send({data:u.upload(e.body)})}catch(e){return t.code(400).send({error:{code:"AI_SESSION_ATTACHMENT_INVALID",message:e instanceof Error?e.message:String(e)}})}}),Xa({app:r,service:o,events:c,appSessionAggregator:p,aiSessionAggregator:l,chatGateway:h,aiSessionAttachments:u,nodeAgentTunnel:g,nodeEventSubscriber:m,errorPayload:Sr}),function({app:e,service:t}){!function(e){for(const t of["application/octet-stream","application/x-www-form-urlencoded"])dr(e,t);dr(e,/^multipart\/form-data(?:;.*)?$/i)}(e),e.get("/api/app-access/session",async e=>{const n=cr(e.url),s="vnc"===e.query.mode?"vnc":"web"===e.query.mode?"web":"tty",i=await t.appAccessProxyTarget(n,s);return{data:{mode:s,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"===s?`/apps/access/tty/ws?token=${encodeURIComponent(n)}`:void 0,vncFramePath:"vnc"===s?lr(n,i.access.sessionId,i.session):void 0}}}),e.get("/apps/access/tty/ws",{websocket:!0},async(e,n)=>{try{const s=await t.appAccessProxyTarget(cr(n.url),"tty");await t.proxyInstanceWebSocket(s.instance.id,e,s.path,sr(n.headers),nr(n.headers))}catch{e.close(1011,"TTY access link is invalid or expired.")}}),e.route({method:"GET",url:"/apps/access/vnc/proxy/*",wsHandler:async(e,n)=>{const s=n.params;try{const i=await t.appAccessProxyTarget(cr(n.url),"vnc",s["*"]||"");await t.proxyInstanceWebSocket(i.instance.id,e,i.path,sr(n.headers),nr(n.headers))}catch{e.close(1011,"VNC access link is invalid or expired.")}},handler:async(e,n)=>{const s=e.params,i=await t.appAccessProxyTarget(cr(e.url),"vnc",s["*"]||"");return rr(n,await t.proxyInstanceHttp(i.instance.id,i.path,{method:e.method,headers:tr(e.headers)}),i.instance.id,i.path)}}),e.all("/instances/:id",async(e,n)=>{const s=e.params;return"GET"===e.method||"HEAD"===e.method?n.redirect(`${ar(s.id)}/`):rr(n,await t.proxyInstanceHttp(s.id,"/",{method:e.method,headers:tr(e.headers),body:or(e.body)}),s.id,"/")}),e.route({method:"GET",url:"/instances/:id/*",wsHandler:async(e,n)=>{const s=n.params,i=s["*"]||"",a=n.url.indexOf("?"),r=a>=0?n.url.slice(a):"";try{await t.proxyInstanceWebSocket(s.id,e,`/${i}${r}`,sr(n.headers),nr(n.headers))}catch{e.close(1011,"Instance websocket endpoint is not reachable.")}},handler:async(e,n)=>{const s=e.params,i=s["*"]||"",a=e.url.indexOf("?"),r=`/${i}${a>=0?e.url.slice(a):""}`;return rr(n,await t.proxyInstanceHttp(s.id,r,{method:e.method,headers:tr(e.headers)}),s.id,r)}}),e.route({method:["POST","PUT","PATCH","DELETE","OPTIONS"],url:"/instances/:id/*",handler:async(e,n)=>{const s=e.params,i=s["*"]||"",a=e.url.indexOf("?"),r=a>=0?e.url.slice(a):"";return ir(n,await t.proxyInstanceHttp(s.id,`/${i}${r}`,{method:e.method,headers:tr(e.headers),body:or(e.body)}))}})}({app:r,service:o}),r.get("*",async(e,n)=>t.existsSync(y)?wr(n,y,"index.html"):n.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."}})),r}function Tr(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 Er(e){if("disabled"!==e&&"password"!==e)throw new Error("Auth mode must be disabled or password.");return e}(async function(){const s=new e.Command;s.name("task-handoff-control-plane").description("Run the TaskHandoff control plane.").version(function(){try{return JSON.parse(t.readFileSync(n.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",Tr,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||n.resolve(__dirname,"..","ui")).option("--auth-mode <mode>","Control plane auth mode: disabled or password",Er,process.env.TASK_HANDOFF_CONTROL_PLANE_AUTH_MODE||"password").action(async e=>{await async function(e){const s=Xe(e.dataDir),i=function(e,s={}){t.mkdirSync(n.dirname(e),{recursive:!0});const i={pid:process.pid,hostname:d.hostname(),component:s.component,command:process.argv.join(" "),acquiredAt:(new Date).toISOString(),token:r.randomUUID(),dataDir:s.dataDir,host:s.host,port:s.port};for(let n=0;n<2;n+=1)try{return t.mkdirSync(e),Da(e,i),{lockPath:e,owner:i,release(){const n=Oa(e);n?.token===i.token&&t.rmSync(e,{recursive:!0,force:!0})}}}catch(n){if("EEXIST"!==n.code)throw n;const i=Oa(e);if(xa(i?.pid))throw new Na(e,i,s.component);t.rmSync(e,{recursive:!0,force:!0})}const a=Oa(e);throw new Na(e,a,s.component)}(n.join(d.tmpdir(),`task-handoff-control-plane-${process.getuid?.()??"user"}.lock`),{component:"control-plane",dataDir:s.dataDir,host:e.host,port:e.port});try{const t=await kr(e);t.addHook("onClose",async()=>{i.release()}),await t.listen({host:e.host,port:e.port})}catch(e){throw i.release(),e}}({host:e.host,port:e.port,dataDir:e.dataDir,staticDir:e.staticDir,auth:{mode:e.authMode}})}),await s.parseAsync(process.argv)})().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});
|