@task-handoff/node-agent 0.0.3-alpha.5 → 0.0.3-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/task-handoff-node-update-worker +21 -14
- package/dist/cli.js +1 -1
- package/package.json +3 -1
|
@@ -3,27 +3,34 @@
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const { spawnSync } = require("node:child_process");
|
|
6
|
+
const { Command, InvalidArgumentError } = require("commander");
|
|
7
|
+
const semver = require("semver");
|
|
8
|
+
const writeFileAtomic = require("write-file-atomic");
|
|
6
9
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
return
|
|
10
|
+
function parseExactVersion(value) {
|
|
11
|
+
if (value.trim() !== value || /^[v=]/.test(value) || semver.valid(value) === null) throw new InvalidArgumentError("must be an exact semantic version");
|
|
12
|
+
return value;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
process.
|
|
19
|
-
|
|
15
|
+
const options = new Command()
|
|
16
|
+
.name("task-handoff-node-update-worker")
|
|
17
|
+
.description("Apply a detached TaskHandoff node update.")
|
|
18
|
+
.requiredOption("--job-file <path>", "persisted update job file")
|
|
19
|
+
.requiredOption("--target-version <version>", "exact semantic version", parseExactVersion)
|
|
20
|
+
.option("--service <name>", "systemd service to restart", "task-handoff-node-agent.service")
|
|
21
|
+
.option("--npm-command <path>", "npm executable", process.env.TASK_HANDOFF_NPM_COMMAND || "npm")
|
|
22
|
+
.parse(process.argv)
|
|
23
|
+
.opts();
|
|
24
|
+
|
|
25
|
+
const jobFile = options.jobFile;
|
|
26
|
+
const targetVersion = options.targetVersion;
|
|
27
|
+
const service = options.service;
|
|
28
|
+
const npmCommand = options.npmCommand;
|
|
20
29
|
|
|
21
30
|
function updateJob(patch) {
|
|
22
31
|
const current = JSON.parse(fs.readFileSync(jobFile, "utf8"));
|
|
23
32
|
const next = { ...current, ...patch, updatedAt: new Date().toISOString() };
|
|
24
|
-
|
|
25
|
-
fs.writeFileSync(temp, `${JSON.stringify(next, null, 2)}\n`);
|
|
26
|
-
fs.renameSync(temp, jobFile);
|
|
33
|
+
writeFileAtomic.sync(jobFile, `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8" });
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
function run(command, args) {
|
package/dist/cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("commander"),e=require("node:fs"),n=require("node:path"),o=require("node:http"),r=require("node:net"),a=require("node:child_process"),i=require("fastify"),s=require("@fastify/websocket"),c=require("ws"),d=require("zod"),l=require("node:crypto"),u=require("node:os");const p=41943040,m="ai.sessions",g=d.z.string().trim().min(1).max(80),f=d.z.enum(["running","waiting","idle","failed"]),h=d.z.enum(["thinking","tool","editing","approval","responding","unknown"]),y=d.z.object({name:d.z.string().trim().min(1).max(120),inputPreview:d.z.string().trim().max(500).optional(),startedAt:d.z.string().datetime().optional()}).strict(),z=d.z.object({toolCalls:d.z.number().int().min(0).default(0),edits:d.z.number().int().min(0).default(0),approvals:d.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),I=d.z.object({send:d.z.boolean().optional(),interrupt:d.z.boolean().optional(),approval:d.z.boolean().optional()}).strict(),b=d.z.object({id:d.z.string().trim().min(1).max(120),kind:d.z.enum(["image"]),name:d.z.string().trim().min(1).max(240),mime:d.z.string().trim().min(1).max(120),size:d.z.number().int().min(0).max(20971520),data:d.z.string().min(1).max(31457280)}).strict(),A=b.omit({data:!0}),w=d.z.object({id:d.z.string().trim().min(1).max(120),kind:d.z.enum(["image"]).optional()}).strict(),S=d.z.enum(["auto","queue","steer","immediate"]),k=d.z.object({message:d.z.string().trim().min(1).max(2e4),mode:S.optional()});k.extend({attachments:d.z.array(b).max(6).default([])}).strict().superRefine((t,e)=>{t.attachments.reduce((t,e)=>t+e.size,0)>p&&e.addIssue({code:d.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),k.extend({attachments:d.z.array(w).max(6).default([])}).strict(),d.z.object({decision:d.z.enum(["allow","deny","skip"])}).strict(),d.z.object({queueIds:d.z.array(d.z.string().trim().min(1).max(120)).max(100)}).strict(),d.z.object({code:d.z.string().trim().min(1).max(160),message:d.z.string(),instanceId:d.z.string().trim().min(1).max(160).optional(),nodeId:d.z.string().trim().min(1).max(160).optional(),route:d.z.string().trim().min(1).max(4096).optional()}).strict();const _=d.z.object({id:d.z.string().trim().min(1).max(120),message:d.z.string().trim().min(1).max(2e4),attachments:d.z.array(A).max(6).default([]),status:d.z.enum(["queued","sending","failed"]).default("queued"),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime(),error:d.z.string().trim().max(4e3).optional()}).strict(),N=d.z.object({pendingCount:d.z.number().int().min(0).default(0),items:d.z.array(_).max(100).default([])}).strict().default({pendingCount:0,items:[]}),v=d.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),E=d.z.object({id:d.z.string().trim().min(1).max(240),providerTurnId:d.z.string().trim().max(240).optional(),source:v.optional(),userPrompt:d.z.string().trim().optional(),status:d.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:h.optional(),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),revision:d.z.number().int().min(0).default(0),sourcePriority:d.z.number().int().min(0).max(100).optional(),snapshotVersion:d.z.number().int().min(0).optional(),observedAt:d.z.string().datetime().optional(),startedAt:d.z.string().datetime().optional(),updatedAt:d.z.string().datetime().optional(),completedAt:d.z.string().datetime().optional()}).strict(),O=d.z.object({id:d.z.string().trim().min(1).max(120),agent:g,appSessionId:d.z.string().trim().max(120).optional(),appId:d.z.string().trim().max(120).optional(),providerSessionId:d.z.string().trim().max(240).optional(),providerMeta:d.z.record(d.z.string(),d.z.unknown()).optional(),appBindingKeys:d.z.array(d.z.string().trim().min(1).max(240)).max(20).optional(),actions:I.optional(),activeTurnId:d.z.string().trim().max(240).optional(),conversationId:d.z.number().int().positive().optional(),title:d.z.string().trim().max(240).optional(),cwd:d.z.string().trim().max(4096).optional(),userPrompt:d.z.string().trim().optional(),turns:d.z.array(E).max(50).optional(),status:f.default("running"),phase:h.default("unknown"),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),currentTool:y.optional(),transcriptPath:d.z.string().trim().max(4096).optional(),transcriptSize:d.z.number().int().min(0).optional(),startedAt:d.z.string().datetime(),updatedAt:d.z.string().datetime(),completedAt:d.z.string().datetime().optional(),error:d.z.string().trim().max(4e3).optional(),counters:z,queue:N}).strict();d.z.object({session:O,provider:d.z.string().trim().min(1).max(80),action:d.z.enum(["send","queue","steer","interrupt","approval"]),decision:d.z.enum(["allow","deny","skip"]).optional(),turnId:d.z.string().trim().min(1).max(240).optional(),providerTurnId:d.z.string().trim().min(1).max(240).optional(),queueId:d.z.string().trim().min(1).max(120).optional()}).strict();const x=O.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}),T=d.z.object({runningCount:d.z.number().int().min(0).default(0),waitingCount:d.z.number().int().min(0).default(0),staleCount:d.z.number().int().min(0).default(0),sessions:d.z.array(x).default([]),updatedAt:d.z.string().datetime()}).strict();d.z.object({revision:d.z.number().int().min(0),lastEventAt:d.z.string().datetime(),snapshot:T}).strict(),T.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),revision:d.z.number().int().min(0).optional()});const C=d.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),j=d.z.object({instanceId:d.z.string().trim().min(1).max(160),nodeId:d.z.string().trim().min(1).max(160).optional(),snapshotRevision:d.z.number().int().min(0),previousRevision:d.z.number().int().min(0).optional(),eventSeq:d.z.number().int().min(1),traceId:d.z.string().trim().min(1).max(160),generatedAt:d.z.string().datetime(),reason:C}).strict(),D=d.z.object({meta:j,snapshot:T}).strict(),R=d.z.object({meta:j,upserted:d.z.array(x).default([]),removed:d.z.array(d.z.string().trim().min(1).max(120)).default([])}).strict(),F=d.z.object({meta:j,sessionIds:d.z.array(d.z.string().trim().min(1).max(120)).default([]),expiresAt:d.z.string().datetime()}).strict();d.z.object({instanceId:d.z.string().trim().min(1).max(160),sinceRevision:d.z.number().int().min(0),latestRevision:d.z.number().int().min(0),syncRequired:d.z.boolean().default(!1),events:d.z.array(d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("ai-session.snapshot"),payload:D}).strict(),d.z.object({type:d.z.literal("ai-session.patch"),payload:R}).strict(),d.z.object({type:d.z.literal("ai-session.removed"),payload:F}).strict()])).default([])}).strict();const $=d.z.object({source:v,sourcePriority:d.z.number().int().min(0).max(100).optional(),observedAt:d.z.string().datetime().optional(),snapshotVersion:d.z.number().int().min(0).optional()}).strict(),P=$.extend({type:d.z.literal("snapshot"),agent:d.z.string().trim().min(1).max(80),appSessionId:d.z.string().trim().max(120).optional(),appId:d.z.string().trim().max(120).optional(),providerSessionId:d.z.string().trim().max(240).optional(),providerMeta:d.z.record(d.z.string(),d.z.unknown()).optional(),appBindingKeys:d.z.array(d.z.string().trim().min(1).max(240)).max(20).optional(),actions:I.optional(),conversationId:d.z.number().int().positive().optional(),title:d.z.string().trim().max(240).optional(),cwd:d.z.string().trim().max(4096).optional(),activeTurnId:d.z.string().trim().max(240).optional(),userPrompt:d.z.string().trim().optional(),turns:d.z.array(E).max(50).optional(),status:f.optional(),phase:h.optional(),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),transcriptPath:d.z.string().trim().max(4096).optional(),transcriptSize:d.z.number().int().min(0).optional(),replaceActivity:d.z.boolean().optional()}).strict(),H=$.extend({type:d.z.literal("event"),sessionId:d.z.string().trim().min(1).max(120),kind:d.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:d.z.string().trim().max(240).optional(),providerTurnId:d.z.string().trim().max(240).optional(),userPrompt:d.z.string().trim().optional(),text:d.z.string().optional(),status:f.optional(),phase:h.optional(),summary:d.z.string().trim().max(1e3).optional(),error:d.z.string().trim().max(4e3).optional(),counters:d.z.object({toolCalls:d.z.number().int().min(0).optional(),edits:d.z.number().int().min(0).optional(),approvals:d.z.number().int().min(0).optional()}).strict().optional()}).strict();d.z.discriminatedUnion("type",[P,H]);const U=d.z.union([d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("interval").default("interval"),intervalMs:d.z.number().int().min(1e3)}).strict(),d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("daily"),timeOfDay:d.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:d.z.string().trim().min(1).max(120)}).strict(),d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("weekly"),weekdays:d.z.array(d.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:d.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:d.z.string().trim().min(1).max(120)}).strict()]),M=d.z.union([U,d.z.object({type:d.z.literal("file-change"),roots:d.z.array(d.z.string().trim().min(1).max(4096)).min(1).max(50),globs:d.z.array(d.z.string().trim().min(1).max(500)).min(1).max(100),ignore:d.z.array(d.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:d.z.number().int().min(100).max(6e4).default(1500)}).strict(),d.z.object({type:d.z.literal("ai-session"),conversationId:d.z.number().int().positive().optional(),agent:d.z.string().trim().min(1).max(80).optional(),statuses:d.z.array(d.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:d.z.array(d.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),L=d.z.object({promptTemplate:d.z.string().trim().min(1).max(2e4)}).strict(),q=d.z.object({cooldownMs:d.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:d.z.number().int().min(1).max(20).default(1),whenBusy:d.z.enum(["skip","queue"]).default("skip")}).strict(),K=d.z.object({configHash:d.z.string().trim().min(8).max(80),name:d.z.string().trim().min(1).max(160),description:d.z.string().trim().max(1e3).optional(),source:M,action:L,policy:q.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime()}).strict(),G=d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("conversation"),conversationId:d.z.number().int().positive()}).strict(),d.z.object({type:d.z.literal("ai-session"),aiSessionId:d.z.string().trim().min(1).max(160)}).strict()]),B=d.z.object({configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),origin:d.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:d.z.boolean().default(!0),target:G,localName:d.z.string().trim().min(1).max(160).optional(),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime()}).strict(),V=d.z.object({configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),status:d.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:d.z.string().datetime().optional(),lastCompletedAt:d.z.string().datetime().optional(),lastSkippedAt:d.z.string().datetime().optional(),lastError:d.z.string().trim().max(4e3).optional(),runCount:d.z.number().int().min(0).default(0),skippedCount:d.z.number().int().min(0).default(0)}).strict(),W=d.z.object({id:d.z.string().trim().min(1).max(160),configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),eventType:d.z.enum(["manual","schedule","file-change","ai-session"]),status:d.z.enum(["started","completed","failed","skipped"]),target:G,promptPreview:d.z.string().trim().max(500),eventSummary:d.z.string().trim().max(1e3).optional(),error:d.z.string().trim().max(4e3).optional(),startedAt:d.z.string().datetime(),completedAt:d.z.string().datetime().optional()}).strict();d.z.object({schemaVersion:d.z.literal(1).default(1),configs:d.z.array(K).default([]),deployments:d.z.array(B).default([]),runtime:d.z.array(V).default([]),recentRuns:d.z.array(W).default([])}).strict();const J="2026-06-23",Y=d.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),Q=d.z.string().datetime(),Z=d.z.record(d.z.string(),d.z.string()).default({}),X=d.z.record(d.z.string(),d.z.string()).default({}),tt=d.z.object({codexModelId:Y.optional(),claudeModelId:Y.optional()}).strict().default({}),et=d.z.object({component:d.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:d.z.string().trim().max(160).optional(),packageVersion:d.z.string().trim().max(80).optional(),protocolVersion:d.z.string().trim().max(80).optional(),buildId:d.z.string().trim().max(160).optional(),builtAt:d.z.string().trim().max(120).optional(),gitCommit:d.z.string().trim().max(120).optional(),imageRef:d.z.string().trim().max(512).optional(),imageDigest:d.z.string().trim().max(240).optional()}).strict(),nt=d.z.enum(["stable","beta","alpha"]),ot=d.z.discriminatedUnion("component",[d.z.object({component:d.z.literal("node-agent")}).strict(),d.z.object({component:d.z.literal("controlled-instance"),instanceId:Y}).strict()]),rt=d.z.object({target:ot,channel:nt.default("stable")}).strict(),at=d.z.object({target:ot,source:d.z.enum(["npm","docker-registry"]),channel:nt,currentVersion:d.z.string().trim().max(240).optional(),availableVersion:d.z.string().trim().min(1).max(240),artifactRef:d.z.string().trim().min(1).max(512).optional(),updateAvailable:d.z.boolean(),supported:d.z.boolean().default(!0),reason:d.z.string().trim().max(2048).optional(),checkedAt:Q}).strict(),it=d.z.object({id:Y,nodeId:Y,target:ot,source:d.z.enum(["npm","docker-registry"]),channel:nt,fromVersion:d.z.string().trim().max(240).optional(),toVersion:d.z.string().trim().min(1).max(240),artifactRef:d.z.string().trim().min(1).max(512).optional(),status:d.z.enum(["queued","updating","restarting","succeeded","failed"]),error:d.z.string().trim().max(4096).optional(),startedAt:Q.optional(),completedAt:Q.optional(),createdAt:Q,updatedAt:Q}).strict(),st=rt,ct=d.z.object({type:d.z.enum(["branch","tag","commit"]),name:d.z.string().trim().min(1).max(240).optional(),commit:d.z.string().trim().max(80).optional()}).strict(),dt=d.z.object({type:d.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:d.z.string().trim().min(1).max(120).optional()}).strict(),lt=d.z.object({depth:d.z.number().int().positive().max(1e5).optional(),submodules:d.z.boolean().default(!1),lfs:d.z.boolean().default(!1),subdirectory:d.z.string().trim().max(240).default("")}).strict();d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),url:d.z.string().trim().min(1).max(2048),provider:d.z.string().trim().max(80).optional(),ref:ct.default({type:"branch",name:"main"}),auth:dt.default({type:"none"}),clone:lt.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:Y.optional(),modelSelection:tt,labels:Z,createdAt:Q,updatedAt:Q}).strict();const ut=d.z.object({id:Y,nodeId:Y,name:d.z.string().trim().min(1).max(160),path:d.z.string().trim().min(1).max(4096),defaultImageId:Y.optional(),modelSelection:tt,labels:Z,createdAt:Q,updatedAt:Q}).strict(),pt=d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("git-repository"),repositoryId:Y.optional(),url:d.z.string().trim().min(1).max(2048),provider:d.z.string().trim().max(80).optional(),ref:ct.default({type:"branch",name:"main"}),auth:dt.default({type:"none"}),clone:lt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),d.z.object({type:d.z.literal("git-template"),url:d.z.string().trim().min(1).max(2048),templateId:d.z.string().trim().min(1).max(120).optional(),ref:ct.default({type:"branch",name:"main"}),auth:dt.default({type:"none"}),clone:lt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),d.z.object({type:d.z.literal("local-folder"),localFolderId:Y.optional(),path:d.z.string().trim().min(1).max(4096),ownerNodeId:Y.optional()}).strict()]),mt=d.z.object({mode:d.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:d.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:d.z.string().trim().max(240).optional(),readOnly:d.z.boolean().default(!1)}).strict(),gt=d.z.enum(["codex","claude"]),ft=d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),source:pt,defaultImageId:Y.optional(),defaultNodeId:Y.optional(),defaultRuntimeId:Y.optional(),modelSelection:tt,workspacePolicy:mt,labels:Z,createdAt:Q,updatedAt:Q}).strict();d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),endpoint:d.z.string().trim().min(1).max(2048),key:d.z.string().trim().min(1).max(4096),model:d.z.string().trim().min(1).max(240),app:gt,enabled:d.z.boolean().default(!0),order:d.z.number().int().min(0).max(1e6).default(0),labels:Z,createdAt:Q,updatedAt:Q}).strict();const ht=d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),image:d.z.string().trim().min(1).max(512),registry:d.z.string().trim().min(1).max(160).default("local"),capabilities:d.z.array(d.z.string().trim().min(1).max(80)).default([]),optionalApps:d.z.array(d.z.string().trim().min(1).max(120)).default([]),defaultEnv:X,labels:Z,createdAt:Q,updatedAt:Q}).strict(),yt=d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),connectionMode:d.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:d.z.object({mode:d.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Y.optional(),secret:d.z.string().trim().max(4096).optional(),pairedAt:Q.optional(),pairing:d.z.object({status:d.z.enum(["pending","paired","expired"]).default("pending"),joinToken:d.z.string().trim().max(4096).optional(),expiresAt:Q.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:d.z.string().trim().max(2048).optional(),controlEndpoint:d.z.string().trim().max(2048).optional(),containerEndpoint:d.z.string().trim().max(2048).optional(),publicWebBase:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:d.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:Z,lastSeenAt:Q.optional(),createdAt:Q,updatedAt:Q}).strict(),zt=d.z.object({id:Y,nodeId:Y,name:d.z.string().trim().min(1).max(160),type:d.z.enum(["docker","kubernetes","local"]),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:Z,createdAt:Q,updatedAt:Q}).strict();d.z.object({ok:d.z.boolean().optional(),role:d.z.string().optional(),nodeId:Y.optional()}).passthrough(),d.z.object({nodeId:Y,joinToken:d.z.string().trim().min(1),expiresAt:Q}).passthrough(),d.z.object({id:Y,keyId:Y,url:d.z.string().trim().min(1).max(2048).optional(),name:d.z.string().trim().min(1).max(160).optional(),pairedAt:Q,updatedAt:Q.optional(),active:d.z.boolean().optional(),current:d.z.boolean().optional()}).passthrough(),d.z.object({remote:d.z.object({id:Y,url:d.z.string().trim().min(1).max(2048).optional(),keyId:Y,pairedAt:Q,active:d.z.boolean()}).passthrough(),tunnel:d.z.object({status:d.z.enum(["disabled","saved","connecting","connected","failed"]),error:d.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),d.z.object({deleted:d.z.boolean().optional()}).passthrough(),d.z.object({repository:d.z.string().trim().optional(),tag:d.z.string().trim().optional(),id:d.z.string().trim(),createdSince:d.z.string().trim().optional(),size:d.z.string().trim().optional(),reference:d.z.string().trim().min(1)}).passthrough();const It=d.z.lazy(()=>d.z.object({name:d.z.string().trim().min(1),path:d.z.string().trim().min(1).max(4096),children:d.z.array(It)}).passthrough());d.z.object({status:d.z.number().int().min(100).max(599).optional(),headers:d.z.record(d.z.string(),d.z.string()).optional(),bodyBase64:d.z.string().optional()}).passthrough();const bt=d.z.object({strategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:d.z.string().trim().max(2048).optional(),api:d.z.string().trim().max(2048).optional(),vnc:d.z.string().trim().max(2048).optional(),tty:d.z.string().trim().max(2048).optional(),logs:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),At=d.z.object({strategy:d.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:d.z.string().trim().max(2048).optional(),api:d.z.string().trim().max(2048).optional(),ws:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),wt=d.z.object({mode:d.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:d.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:d.z.string().trim().max(4096).optional(),resolvedCommit:d.z.string().trim().max(120).optional(),error:d.z.string().trim().max(2048).optional()}).passthrough(),St=d.z.object({id:Y,name:d.z.string().trim().min(1).max(160),projectId:Y.optional(),source:pt,sourceSnapshot:d.z.record(d.z.string(),d.z.unknown()).default({}),nodeId:Y,runtimeId:Y,imageId:Y.optional(),imageSnapshot:ht.optional(),status:d.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:d.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:d.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:d.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:d.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:d.z.string().trim().max(80).optional(),instanceVersion:d.z.string().trim().max(80).optional(),build:et.optional(),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),config:d.z.object({autoImportAgentConfigs:d.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:wt.default({status:"unknown"}),target:bt.default({strategy:"direct-port",status:"unknown"}),access:At,receiver:d.z.object({status:d.z.string().trim().max(80).default("unknown"),pendingCount:d.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:d.z.object({runningCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),updatedAt:Q.optional(),revision:d.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:T.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:d.z.object({enabledCount:d.z.number().int().min(0).default(0),runningCount:d.z.number().int().min(0).default(0),errorCount:d.z.number().int().min(0).default(0),configs:d.z.array(d.z.object({configHash:d.z.string().trim().min(1),config:K,deployments:d.z.array(B).default([]),runtime:d.z.array(V).default([])}).strict()).default([]),recentRuns:d.z.array(W).default([])}).strict().optional(),runtime:d.z.object({kind:d.z.string().trim().max(80).optional(),containerName:d.z.string().trim().max(240).optional(),containerId:d.z.string().trim().max(240).optional(),workspacePath:d.z.string().trim().max(4096).optional(),pid:d.z.number().int().positive().optional(),port:d.z.number().int().positive().max(65535).optional(),labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict().default({labels:{}}),registrationToken:d.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:Q.optional(),createdAt:Q,updatedAt:Q}).strict();function kt(t){return St.safeParse(_t(t))}function _t(t){if(!t||"object"!=typeof t||Array.isArray(t))return t;const e=t,n=new Set(Object.keys(St.shape)),o={};for(const[t,r]of Object.entries(e))n.has(t)&&(o[t]=r);return o.apps=Nt(e.apps,["runningCount","problemCount","updatedAt","revision"]),o.config=Nt(e.config,["autoImportAgentConfigs"]),o.receiver=Nt(e.receiver,["status","pendingCount"]),o.runtime=Nt(e.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),o.target=Nt(e.target??e.endpoints,["strategy","web","api","status"]),o.access=Nt(e.access,["strategy","web","api","ws","status"]),o}function Nt(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return t;const n=t;return Object.fromEntries(e.filter(t=>Object.prototype.hasOwnProperty.call(n,t)).map(t=>[t,n[t]]))}const vt=d.z.object({instanceId:Y.optional(),name:d.z.string().trim().min(1).max(160),projectId:Y.optional(),source:pt.optional(),nodeId:Y.optional(),runtimeId:Y.optional(),imageId:Y.optional(),instanceVersion:d.z.string().trim().max(80).optional(),protocolVersion:d.z.string().trim().max(80),build:et.optional(),controlMode:d.z.enum(["standalone","controlled"]).default("controlled"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),target:bt.default({strategy:"direct-port",status:"unknown"}),workspace:wt.default({status:"unknown"}),registrationToken:d.z.string().trim().max(240).optional()}).strict(),Et=d.z.object({status:St.shape.status.optional(),health:St.shape.health.optional(),protocolVersion:d.z.string().trim().max(80),build:et.optional(),capabilities:St.shape.capabilities.optional(),receiver:St.shape.receiver.optional(),apps:St.shape.apps.optional(),aiSessions:St.shape.aiSessions.optional(),triggers:St.shape.triggers.optional(),workspace:wt.optional(),target:bt.partial().optional()}).strict(),Ot=d.z.enum(["web","telegram","wechat","dingding"]);function xt(t){return t.readyState===(t.OPEN??1)}function Tt(t,e,n){t.close(function(t){if("number"==typeof t&&Number.isInteger(t))return t>=3e3&&t<=4999||t>=1e3&&t<=1014&&1004!==t&&1005!==t&&1006!==t?t:void 0}(e),function(t){const e=Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:"";if(Buffer.byteLength(e,"utf8")<=123)return e;let n="";for(const t of e){const e=`${n}${t}`;if(Buffer.byteLength(e,"utf8")>123)break;n=e}return n}(n)||void 0)}function Ct(t,e){t.send(e.data,{binary:e.isBinary})}function jt(t,e){if(xt(t))for(const n of e.splice(0))Ct(t,n)}function Dt(t,e){return new Promise((n,o)=>{const r=a.spawn(t,e,{stdio:["ignore","pipe","pipe"]}),i=[],s=[];r.stdout.on("data",t=>i.push(Buffer.from(t))),r.stderr.on("data",t=>s.push(Buffer.from(t))),r.on("error",o),r.on("close",e=>{const r={stdout:Buffer.concat(i).toString("utf8").trim(),stderr:Buffer.concat(s).toString("utf8").trim()};if(0===e)return void n(r);const a=new Error(r.stderr||`${t} exited with code ${e}`);Object.assign(a,{statusCode:502,code:"RUNTIME_EXECUTOR_FAILED",details:r}),o(a)})})}d.z.object({id:Y,channel:Ot,name:d.z.string().trim().min(1).max(160),enabled:d.z.boolean().default(!1),token:d.z.string().trim().max(4096).optional(),defaultChatId:d.z.string().trim().max(240).optional(),allowedUserIds:d.z.array(d.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:d.z.number().int().positive().max(6e4).default(3e3),settings:d.z.record(d.z.string(),d.z.unknown()).default({}),createdAt:Q,updatedAt:Q}).strict(),d.z.object({id:Y,channel:Ot,bridgeId:Y.optional(),chatSessionId:d.z.string().trim().min(1).max(240),userId:d.z.string().trim().max(240).optional(),activeProjectId:Y.optional(),activeInstanceId:Y.optional(),activeAiSessionId:d.z.string().trim().min(1).max(120).optional(),lastUsedAt:Q,createdAt:Q,updatedAt:Q}).strict(),d.z.object({source:d.z.object({type:d.z.literal("chat-gateway").default("chat-gateway"),channel:Ot,bridgeId:Y.optional(),chatSessionId:d.z.string().trim().min(1).max(240),userId:d.z.string().trim().max(240).optional()}).strict(),message:d.z.object({text:d.z.string().trim().max(2e4).default(""),attachments:d.z.array(b).max(6).default([])}).strict().superRefine((t,e)=>{t.attachments.reduce((t,e)=>t+e.size,0)>p&&e.addIssue({code:d.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:d.z.object({instanceId:Y,aiSessionId:d.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),d.z.object({id:Y,instanceId:Y,projectId:Y.optional(),aiSessionId:d.z.string().trim().min(1).max(120),providerSessionId:d.z.string().trim().max(240).optional(),result:d.z.string().default(""),timeoutMs:d.z.number().positive().optional(),source:d.z.string().trim().max(120).optional(),kind:d.z.enum(["task","approval"]).default("task"),status:d.z.enum(["pending","unreachable"]).default("pending"),error:d.z.string().trim().max(2048).optional(),lastSeenAt:Q}).strict();class Rt{runCommand;publishHost;constructor(t=Dt,e={}){this.runCommand=t,this.publishHost=e.publishHost||"127.0.0.1"}async start(t){Pt(t);const e=t.instance.runtime.containerName||$t(t.instance.id);if(await this.runCommand("docker",["start",e]).catch(()=>{}))return this.runningResult(t,e,t.instance.runtime.containerId);await this.ensureImageAvailable(t.image);const n=await this.runCommand("docker",function(t,e,n={}){const o=n.publishHost||"127.0.0.1",r=t.node?.id||"node_unset",a=t.runtime?.id||"runtime_local_docker",i=["run","-d","--name",e,"--label",`task-handoff.instance-id=${t.instance.id}`,"--label",`task-handoff.project-id=${t.project.id}`,"--label",`task-handoff.node-id=${r}`,"--label",`task-handoff.runtime-id=${a}`,"--label",`task-handoff.image-id=${t.image.id}`,"--shm-size","1gb","--security-opt","seccomp=unconfined","--network","bridge","--add-host","host.docker.internal:host-gateway","--tmpfs","/tmp:rw,mode=1777","-v",`${e}-data:/data`,"-v",`${e}-agent-home:/home/agent`,"-p",`${o}::8080`,"-e","TASK_HANDOFF_CONTROL_MODE=controlled","-e",`TASK_HANDOFF_NODE_AGENT_URL=${t.nodeAgentUrl||t.node?.endpoint||""}`,"-e",`TASK_HANDOFF_INSTANCE_ID=${t.instance.id}`,"-e",`TASK_HANDOFF_INSTANCE_NAME=${t.instance.name}`,"-e",`TASK_HANDOFF_REGISTRATION_TOKEN=${t.instance.registrationToken||""}`,"-e",`TASK_HANDOFF_PROJECT_ID=${t.project.id}`,"-e",`TASK_HANDOFF_NODE_ID=${r}`,"-e",`TASK_HANDOFF_RUNTIME_ID=${a}`,"-e",`TASK_HANDOFF_IMAGE_ID=${t.image.id}`,"-e","TASK_HANDOFF_CHAT_BRIDGES=none","-e",`TASK_HANDOFF_WORKSPACE=${t.project.workspacePolicy.path||"/workspace"}`,"-e",`TASK_HANDOFF_WORKSPACE_MODE=${t.project.workspacePolicy.mode}`];"local-folder"===t.project.source.type?i.push("-v",`${t.project.source.path}:${t.project.workspacePolicy.path||"/workspace"}:${t.project.workspacePolicy.readOnly?"ro":"rw"}`):(i.push("-e",`TASK_HANDOFF_PROJECT_SOURCE=${JSON.stringify(t.project.source)}`),i.push("-e",`TASK_HANDOFF_WORKSPACE_POLICY=${JSON.stringify(t.project.workspacePolicy)}`),i.push("-e",`TASK_HANDOFF_GIT_URL=${t.project.source.url}`),t.project.source.ref?.commit?i.push("-e",`TASK_HANDOFF_GIT_COMMIT=${t.project.source.ref.commit}`):t.project.source.ref?.name&&i.push("-e",`TASK_HANDOFF_GIT_REF=${t.project.source.ref.name}`),t.project.source.clone?.depth&&i.push("-e",`TASK_HANDOFF_GIT_DEPTH=${t.project.source.clone.depth}`),i.push("-e","TASK_HANDOFF_GIT_SUBMODULES="+(t.project.source.clone?.submodules?"true":"false")),i.push("-e","TASK_HANDOFF_GIT_LFS="+(t.project.source.clone?.lfs?"true":"false")),t.project.source.clone?.subdirectory&&i.push("-e",`TASK_HANDOFF_WORKSPACE_SUBDIRECTORY=${t.project.source.clone.subdirectory}`));for(const[e,n]of Object.entries(t.image.defaultEnv))i.push("-e",`${e}=${n}`);for(const[e,n]of Object.entries(t.modelEnv||{}))i.push("-e",`${e}=${n}`);return i.push(t.image.image),i}(t,e,{publishHost:this.publishHost}));return this.runningResult(t,e,n.stdout||void 0)}async runningResult(t,e,n){const o=await this.containerPort(e,"8080/tcp"),r=o?`http://127.0.0.1:${o}`:void 0,a=t.node?.id||"node_unset",i=t.runtime?.id||"runtime_local_docker";return{status:"registering",health:"unknown",connectionStatus:r?"online":"unknown",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",target:{strategy:"direct-port",status:r?"reachable":"unknown",...r?{web:r,api:`${r}/api`}:{}},workspace:{mode:t.project.workspacePolicy.mode,status:"pending",path:t.project.workspacePolicy.path},runtime:{...t.instance.runtime,kind:"docker",containerName:e,...n?{containerId:n}:{},labels:{...t.instance.runtime.labels,"task-handoff.instance-id":t.instance.id,"task-handoff.project-id":t.project.id,"task-handoff.node-id":a,"task-handoff.runtime-id":i,"task-handoff.image-id":t.image.id}}}}async stop(t){return await this.runCommand("docker",["stop",t.instance.runtime.containerName||$t(t.instance.id)]).catch(()=>({stdout:"",stderr:""})),{status:"stopped",health:"unknown",connectionStatus:"offline",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async restart(t){Pt(t);const e=t.instance.runtime.containerName||$t(t.instance.id);return await this.runCommand("docker",["restart",e]),this.runningResult(t,e,t.instance.runtime.containerId)}async delete(t){return await this.runCommand("docker",["rm","-f",t.instance.runtime.containerName||$t(t.instance.id)]).catch(()=>({stdout:"",stderr:""})),{status:"stopped",health:"unknown",connectionStatus:"offline",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async containerPort(t,e){const n=(await this.runCommand("docker",["port",t,e]).catch(()=>({stdout:"",stderr:""}))).stdout.split(/\r?\n/).find(Boolean),o=n?.match(/:(\d+)$/);return o?.[1]}async ensureImageAvailable(t){if(!await this.runCommand("docker",["image","inspect",t.image]).then(()=>!0,()=>!1)){if("local"===t.registry){const e=new Error(`Local Docker image ${t.image} was not found. Build it or add an existing local image before starting an instance.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_IMAGE_NOT_FOUND"}),e}await this.runCommand("docker",["pull",t.image])}}}async function Ft(t=Dt){return(await t("docker",["images","--format","{{json .}}"])).stdout.split(/\r?\n/).map(t=>t.trim()).filter(Boolean).map(t=>{const e=JSON.parse(t),n=e.Repository||"<none>",o=e.Tag||"<none>",r=e.ID||e.IDShort||"",a="<none>"!==n&&"<none>"!==o?`${n}:${o}`:r;return{repository:n,tag:o,id:r,createdSince:e.CreatedSince,size:e.Size,reference:a}}).filter(t=>Boolean(t.reference))}function $t(t){return`task-handoff-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`}function Pt(t){if("local-folder"===t.project.source.type&&t.project.source.ownerNodeId&&t.project.source.ownerNodeId!==t.node.id){const t=new Error("Local folder projects can only start on the node that owns the folder.");throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),t}if(!t.image.image.trim()){const t=new Error("Image profile must reference a registered image.");throw Object.assign(t,{statusCode:400,code:"IMAGE_PROFILE_INVALID"}),t}}const Ht=d.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Ut=d.z.object({type:d.z.enum(["app-session","provider-session","adapter-key"]),id:d.z.string().trim().min(1).max(240),agent:d.z.string().trim().min(1).max(80).optional(),adapter:d.z.string().trim().min(1).max(80).optional(),key:d.z.string().trim().min(1).max(240).optional()}).strict(),Mt=d.z.record(d.z.string(),d.z.unknown()).and(d.z.object({id:d.z.string().trim().min(1).max(160),appId:d.z.string().trim().min(1).max(160).optional(),title:d.z.string().trim().max(240).optional(),kind:d.z.string().trim().max(80).optional(),status:Ht.default("unknown"),bindings:d.z.array(Ut).max(40).default([]),workspace:d.z.object({cwd:d.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:d.z.string().datetime().optional(),updatedAt:d.z.string().datetime().optional()})),Lt=d.z.object({runningCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),sessions:d.z.array(Mt).default([]),updatedAt:d.z.string().datetime()}).strict();d.z.object({revision:d.z.number().int().min(0),lastEventAt:d.z.string().datetime(),snapshot:Lt}).strict(),Lt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:d.z.number().int().min(0).optional()});const qt=d.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Kt=d.z.object({instanceId:d.z.string().trim().min(1).max(160),nodeId:d.z.string().trim().min(1).max(160).optional(),snapshotRevision:d.z.number().int().min(0),previousRevision:d.z.number().int().min(0).optional(),eventSeq:d.z.number().int().min(1),traceId:d.z.string().trim().min(1).max(160),generatedAt:d.z.string().datetime(),reason:qt}).strict(),Gt=d.z.object({meta:Kt,snapshot:Lt}).strict(),Bt=d.z.object({meta:Kt,session:Mt}).strict(),Vt=d.z.object({meta:Kt,sessionId:d.z.string().trim().min(1).max(160),tombstone:Mt.optional(),expiresAt:d.z.string().datetime().optional()}).strict();function Wt(t){return t.startsWith("ai-session.")||t.startsWith("ai.session.")?"ai.sessions":t.startsWith("app-session.")?"app.sessions":t.startsWith("receiver.")?"receiver":t.startsWith("conversation.")?"conversations":t.startsWith("trigger.")?"triggers":t.startsWith("channel.")?"channels":t.startsWith("instance.")?"instances":t.startsWith("node.")?"nodes":t.startsWith("project.")?"projects":t.startsWith("model.")?"models":t.startsWith("image.")?"images":"system"}d.z.object({instanceId:d.z.string().trim().min(1).max(160),sinceRevision:d.z.number().int().min(0),latestRevision:d.z.number().int().min(0),syncRequired:d.z.boolean().default(!1),events:d.z.array(d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("app-session.snapshot"),payload:Gt}).strict(),d.z.object({type:d.z.literal("app-session.patch"),payload:Bt}).strict(),d.z.object({type:d.z.literal("app-session.removed"),payload:Vt}).strict()])).default([])}).strict();class Jt{sockets=new Map;outputs=new Set;state;token;logger;lastAiSessionEventByInstance=new Map;lastAppSessionEventByInstance=new Map;timer;constructor(t,e,n={}){this.state=t,this.token=e,this.logger=n.logger}start(){this.sync(),this.timer=setInterval(()=>this.sync(),5e3)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const t of this.sockets.values())t.close();this.sockets.clear(),this.outputs.clear()}addOutput(t){return this.outputs.add(t),t.on("close",()=>this.outputs.delete(t)),t.on("error",()=>this.outputs.delete(t)),this.syncNow(),()=>{this.outputs.delete(t)}}syncNow(){this.sync()}sync(){if(!this.outputs.size){for(const t of this.sockets.values())t.close();return void this.sockets.clear()}const t=new Set;for(const e of this.state.listInstances()){const n=Qt(e);n&&(t.add(e.id),this.sockets.has(e.id)||this.connect(e.id,n))}for(const[e,n]of this.sockets)t.has(e)||(n.close(),this.sockets.delete(e))}connect(t,e){const n=this.token?{authorization:`Bearer ${this.token}`}:void 0,o=new c(e,{headers:n});this.sockets.set(t,o),o.on("open",()=>{o.readyState===c.OPEN&&(o.send(JSON.stringify({v:1,type:"subscribe",topics:[m,"app.sessions","receiver","instances"]})),this.logger?.info?.({instanceId:t,url:e},"ai-session.event.forward.connect"),this.logger?.info?.({instanceId:t,url:e},"app-session.event.forward.connect"))}),o.on("message",e=>{const n=function(t,e){try{const n=JSON.parse(String(t));if(!n||"object"!=typeof n||Array.isArray(n))return;const o=n,r="string"==typeof o.type?o.type:"";if(!r||"events.connected"===r)return;const a=o.scope&&"object"==typeof o.scope&&!Array.isArray(o.scope)?o.scope:{};return{v:1,id:"string"==typeof o.id?o.id:`evt_${Date.now().toString(36)}`,seq:Number(o.seq)||0,type:r,topic:"string"==typeof o.topic?o.topic:Wt(r),createdAt:"string"==typeof o.createdAt?o.createdAt:(new Date).toISOString(),payload:"payload"in o?o.payload:{},scope:{...a,instanceId:e}}}catch{return}}(e,t);if(!n)return;this.recordAiSessionEvent(t,n),this.recordAppSessionEvent(t,n);const o=JSON.stringify({type:"node-agent.event.forwarded",event:n});for(const t of this.outputs)t.readyState===c.OPEN&&t.send(o)}),o.on("close",()=>{this.sockets.get(t)===o&&this.sockets.delete(t)}),o.on("error",()=>{this.logger?.warn?.({instanceId:t,url:e},"ai-session.event.forward.error"),this.logger?.warn?.({instanceId:t,url:e},"app-session.event.forward.error"),o.close()})}recordAiSessionEvent(t,e){if(e.topic!==m)return;const n=Yt(e.payload),o=this.lastAiSessionEventByInstance.get(t);void 0!==o?.eventSeq&&void 0!==n.eventSeq&&n.eventSeq>o.eventSeq+1&&this.logger?.warn?.({instanceId:t,traceId:n.traceId,previousEventSeq:o.eventSeq,eventSeq:n.eventSeq,previousSnapshotRevision:o.snapshotRevision,snapshotRevision:n.snapshotRevision},"ai-session.event.forward.gap"),this.lastAiSessionEventByInstance.set(t,{eventSeq:n.eventSeq??o?.eventSeq,snapshotRevision:n.snapshotRevision??o?.snapshotRevision}),this.logger?.info?.({instanceId:t,traceId:n.traceId,eventType:e.type,eventSeq:n.eventSeq,snapshotRevision:n.snapshotRevision},"ai-session.event.forward.message")}recordAppSessionEvent(t,e){if("app.sessions"!==e.topic)return;const n=Yt(e.payload),o=this.lastAppSessionEventByInstance.get(t);void 0!==o?.eventSeq&&void 0!==n.eventSeq&&n.eventSeq>o.eventSeq+1&&this.logger?.warn?.({instanceId:t,traceId:n.traceId,previousEventSeq:o.eventSeq,eventSeq:n.eventSeq,previousSnapshotRevision:o.snapshotRevision,snapshotRevision:n.snapshotRevision},"app-session.event.forward.gap"),this.lastAppSessionEventByInstance.set(t,{eventSeq:n.eventSeq??o?.eventSeq,snapshotRevision:n.snapshotRevision??o?.snapshotRevision}),this.logger?.info?.({instanceId:t,traceId:n.traceId,eventType:e.type,eventSeq:n.eventSeq,snapshotRevision:n.snapshotRevision},"app-session.event.forward.message")}}function Yt(t){const e=t&&"object"==typeof t&&!Array.isArray(t)?t:{},n=e.meta&&"object"==typeof e.meta&&!Array.isArray(e.meta)?e.meta:{};return{eventSeq:"number"==typeof n.eventSeq?n.eventSeq:void 0,snapshotRevision:"number"==typeof n.snapshotRevision?n.snapshotRevision:void 0,traceId:"string"==typeof n.traceId?n.traceId:void 0}}function Qt(t){const e=t.target?.api||t.target?.web;if(e)try{const t=new URL("/api/events",e);return t.protocol="https:"===t.protocol?"wss:":"ws:",t.toString()}catch{return}}function Zt(t,o){let r;try{r=e.statSync(t)}catch{return}if(!r.isDirectory())return;const a=n.resolve(t),i=n.basename(a)||a;let s=[];try{s=o>0?e.readdirSync(a,{withFileTypes:!0}):[]}catch{s=[]}const c=o>0?s.filter(t=>t.isDirectory()&&!t.name.startsWith(".")).sort((t,e)=>t.name.localeCompare(e.name)).slice(0,80).flatMap(t=>{const e=Zt(n.join(a,t.name),o-1);return e?[e]:[]}):[];return{name:i,path:a,children:c}}function Xt(t){return(t.path?[t.path]:function(){const t=[u.homedir(),"/workspace"].filter(t=>t&&e.existsSync(t)&&e.statSync(t).isDirectory());return[...new Map(t.map(t=>[n.resolve(t),t])).values()]}()).flatMap(e=>{const n=Zt(e,t.depth);return n?[n]:[]})}const te=d.z.object({path:d.z.string().trim().min(1).max(2048),method:d.z.string().trim().min(1).max(20).default("GET"),headers:d.z.record(d.z.string(),d.z.string()).default({}),body:d.z.string().optional(),bodyBase64:d.z.string().optional()}).strict(),ee=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160),path:d.z.string().trim().min(1).max(4096),defaultImageId:d.z.string().trim().min(1).max(120).optional(),modelSelection:tt,labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),ne=d.z.object({path:d.z.string().trim().min(1).max(4096).optional(),depth:d.z.coerce.number().int().min(0).max(1).default(0)}).strict(),oe=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160).optional(),runtimeId:d.z.string().trim().min(1).max(120),imageId:d.z.string().trim().min(1).max(120).optional(),projectId:d.z.string().trim().min(1).max(120).optional(),image:ht.optional(),source:pt,sourceSnapshot:d.z.record(d.z.string(),d.z.unknown()).default({}),config:St.shape.config.optional(),modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),re=d.z.object({name:d.z.string().trim().min(1).max(160).optional()}).strict(),ae=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160),type:d.z.enum(["docker","kubernetes","local"]),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).optional(),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),ie=ae.omit({id:!0}).partial().strict();function se(t=function(){return process.env.TASK_HANDOFF_NODE_AGENT_DATA_DIR||n.join(u.homedir(),".config","task-handoff","node-agent")}()){const e=n.resolve(t);return{dataDir:e,identityPath:n.join(e,"identity.json"),localFoldersDir:n.join(e,"local-folders"),nodeRuntimesDir:n.join(e,"node-runtimes"),controlledInstancesDir:n.join(e,"controlled-instances"),updatesDir:n.join(e,"updates"),logsDir:n.join(e,"logs")}}function ce(t){e.mkdirSync(t,{recursive:!0})}function de(t,e){console.warn(JSON.stringify({message:t,...e}))}function le(t){return t&&"object"==typeof t&&"issues"in t?{issues:t.issues}:{error:t instanceof Error?t.message:String(t)}}function ue(t,n){const o=n.logger||de;let r;try{r=JSON.parse(e.readFileSync(t,"utf8"))}catch(e){return void o("stored JSON could not be read",{filePath:t,...le(e)})}if(!n.schema)return r;const a=n.sanitize?n.sanitize(r):r,i=n.schema.safeParse(a);if(i.success)return i.data;const s="strip"in n.schema&&"function"==typeof n.schema.strip?n.schema.strip().safeParse(a):void 0;if(s?.success)return o("unknown stored fields were ignored",{filePath:t,issues:i.error.issues}),s.data;o("stored record failed validation and was isolated",{filePath:t,issues:i.error.issues})}function pe(t){return`${t}_${l.randomUUID().replace(/-/g,"").slice(0,20)}`}function me(){return l.randomBytes(24).toString("base64url")}class ge{directory;options;constructor(t,e={}){this.directory=t,this.options=e}init(){ce(this.directory)}filePath(t){return n.join(this.directory,`${t}.json`)}list(){return this.init(),e.readdirSync(this.directory).filter(t=>t.endsWith(".json")).sort().flatMap(t=>{const e=ue(n.join(this.directory,t),this.options);return e?[e]:[]})}get(t){const n=this.filePath(t);return e.existsSync(n)?ue(n,this.options):void 0}put(t){const o=this.options.sanitize?this.options.sanitize(t):t,r=this.options.schema?this.options.schema.parse(o):o;return function(t,o){ce(n.dirname(t));const r=`${t}.${process.pid}.${Date.now()}.tmp`;e.writeFileSync(r,`${JSON.stringify(o,null,2)}\n`,"utf8"),e.renameSync(r,t)}(this.filePath(r.id),r),r}patch(t,e){const n=this.get(t);if(!n)return;const o={...n,...e,id:n.id,createdAt:n.createdAt,updatedAt:(new Date).toISOString()};return this.put(o)}delete(t){const n=this.filePath(t);return!!e.existsSync(n)&&(e.unlinkSync(n),!0)}}class fe extends Error{code;lockPath;owner;constructor(t,e,n="process"){super(`${"control-plane"===n?"Control plane":"node-agent"===n?"Node agent":"Process"} is already running${e?.pid?` pid ${e.pid}`:""}. Lock: ${t}`),this.name="ProcessSingletonError",this.code="control-plane"===n?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===n?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=t,this.owner=e}}function he(t){return n.join(t,"owner.json")}function ye(t){try{return JSON.parse(e.readFileSync(he(t),"utf8"))}catch{return}}function ze(t){if(!Number.isInteger(t)||Number(t)<=0)return!1;try{return process.kill(Number(t),0),!0}catch(t){return"EPERM"===t.code}}function Ie(t,n){e.writeFileSync(he(t),`${JSON.stringify(n,null,2)}\n`,"utf8")}function be(t,e=process.platform){const o=n.resolve(t),r=l.createHash("sha256").update(o).digest("hex").slice(0,16);return"win32"===e?`\\\\.\\pipe\\task-handoff-node-agent-${r}`:n.join("/tmp",`task-handoff-node-agent-${process.getuid?.()??"user"}`,`${r}.sock`)}const Ae="TASK_HANDOFF_NODE_AGENT_V1",we=6e4;function Se(t=""){return l.createHash("sha256").update(t).digest("hex")}function ke(t,e){return l.createHmac("sha256",t).update(function(t){return[Ae,t.keyId,t.method.toUpperCase(),t.pathWithQuery,t.timestamp,t.nonce,t.bodySha256].join("\n")}(e)).digest("hex")}function _e(t){const e=t.timestamp||(new Date).toISOString(),n=t.nonce||l.randomBytes(18).toString("base64url"),o=Se(t.body||""),r=ke(t.secret,{keyId:t.keyId,method:t.method,pathWithQuery:t.pathWithQuery,timestamp:e,nonce:n,bodySha256:o});return{"x-taskhandoff-node-id":t.nodeId,"x-taskhandoff-key-id":t.keyId,"x-taskhandoff-timestamp":e,"x-taskhandoff-nonce":n,"x-taskhandoff-body-sha256":o,"x-taskhandoff-signature":r}}const Ne=new Set(["DELETE","GET","HEAD","PATCH","POST","PUT","OPTIONS"]);function ve(){return(new Date).toISOString()}function Ee(t){const e=t.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);return e?{numbers:[Number(e[1]),Number(e[2]),Number(e[3])],prerelease:e[4]?.split(".")||[]}:void 0}function Oe(t,e){if(!t)return!0;const n=Ee(t),o=Ee(e);if(!n||!o)return t!==e;for(let t=0;t<3;t+=1)if(o.numbers[t]!==n.numbers[t])return o.numbers[t]>n.numbers[t];return!!n.prerelease.length&&(!o.prerelease.length||o.prerelease.join(".").localeCompare(n.prerelease.join("."),void 0,{numeric:!0})>0)}async function xe(t,e,n){const o=je(n),r=await t(Te(),["view",`${e}@${o}`,"version","--json"]).catch(t=>{if(!function(t){if(!t||"object"!=typeof t)return!1;const e=t,n=[e.message,e.details?.stdout,e.details?.stderr].filter(t=>"string"==typeof t).join("\n");return/\bE404\b/i.test(n)}(t))throw t});if(!r)return;const a=JSON.parse(r.stdout);if("string"!=typeof a)throw new Error(`npm did not resolve ${e}@${o} to one version.`);return a}function Te(){return process.env.TASK_HANDOFF_NPM_COMMAND||"npm"}function Ce(t){return at.parse({target:t.target,source:"npm",channel:t.channel,currentVersion:t.currentVersion,availableVersion:t.currentVersion||"unavailable",updateAvailable:!1,reason:`No ${t.channel} release is currently published.`,checkedAt:ve()})}function je(t){return"stable"===t?"latest":t}function De(t){if(Array.isArray(t))return t.map(De).find(Boolean);if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.digest&&e.digest.startsWith("sha256:")?e.digest:"string"==typeof e.Digest&&e.Digest.startsWith("sha256:")?e.Digest:Object.values(e).map(De).find(Boolean)}class Re{records;constructor(t){this.records=new ge(t.updatesDir,{schema:it})}init(){this.records.init()}list(){return this.records.list().sort((t,e)=>e.createdAt.localeCompare(t.createdAt))}create(t,e){const n=ve();return this.records.put(it.parse({id:pe("update"),nodeId:t,target:e.target,source:e.source,channel:e.channel,fromVersion:e.currentVersion,toVersion:e.availableVersion,artifactRef:e.artifactRef,status:"queued",createdAt:n,updatedAt:n}))}patch(t,e){const n=this.records.get(t);if(!n)throw Object.assign(new Error(`Update job ${t} was not found.`),{statusCode:404,code:"UPDATE_JOB_NOT_FOUND"});return this.records.put(it.parse({...n,...e,id:t,createdAt:n.createdAt,updatedAt:ve()}))}run(t,e){(async()=>{this.patch(t.id,{status:"updating",startedAt:ve(),error:void 0});try{await e(t),this.patch(t.id,{status:"succeeded",completedAt:ve()})}catch(e){this.patch(t.id,{status:"failed",error:e instanceof Error?e.message:String(e),completedAt:ve()})}})()}}const Fe=new Set(["content-encoding","content-length","transfer-encoding"]),$e=["codex","claude"],Pe=d.z.object({controlPlaneName:d.z.string().trim().min(1).max(160).optional(),controlPlaneUrl:d.z.string().trim().max(2048).optional(),expiresInMs:d.z.number().int().positive().max(36e5).optional()}).strict(),He=d.z.object({joinToken:d.z.string().trim().min(1).max(4096),controlPlaneId:d.z.string().trim().min(1).max(160).optional(),controlPlaneName:d.z.string().trim().min(1).max(160).optional(),controlPlaneUrl:d.z.string().trim().max(2048).optional()}).strict(),Ue=d.z.object({modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict().default({modelEnv:{}}),Me=st.extend({modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),Le=d.z.object({controlPlaneUrl:d.z.string().trim().url().max(2048),joinToken:d.z.string().trim().min(1).max(4096),controlPlaneName:d.z.string().trim().min(1).max(160).optional(),activate:d.z.boolean().optional()}).strict();function qe(t){const e=process.env[t]?.trim();return e||void 0}function Ke(){try{const t=n.resolve(__dirname,"..","package.json");return JSON.parse(e.readFileSync(t,"utf8")).version||"unknown"}catch{return"unknown"}}function Ge(t){return t.bodyBase64?Buffer.from(t.bodyBase64,"base64"):t.body}function Be(t){const e=t.authorization;return"string"==typeof e&&e.startsWith("Bearer ")?e.slice(7).trim():void 0}function Ve(t){if("string"!=typeof t)return!1;const e=t.trim().toLowerCase();return"localhost"===e||"127.0.0.1"===e||"::1"===e||"::ffff:127.0.0.1"===e}function We(){return(new Date).toISOString()}function Je(t){try{return function(t){if(!t||"object"!=typeof t)return;const e=t,n="string"==typeof e.nodeId&&e.nodeId.trim()?e.nodeId.trim():void 0;return n?{nodeId:n,createdAt:"string"==typeof e.createdAt?e.createdAt:We(),updatedAt:"string"==typeof e.updatedAt?e.updatedAt:We(),pairingInvites:Array.isArray(e.pairingInvites)?e.pairingInvites.filter(t=>{if(!t||"object"!=typeof t)return!1;const e=t;return"string"==typeof e.tokenHash&&"string"==typeof e.expiresAt&&"string"==typeof e.createdAt}):[],remoteControlPlanes:Array.isArray(e.remoteControlPlanes)?e.remoteControlPlanes.filter(t=>{if(!t||"object"!=typeof t)return!1;const e=t;return"string"==typeof e.id&&"string"==typeof e.keyId&&"string"==typeof e.secret&&"string"==typeof e.pairedAt&&"string"==typeof e.updatedAt}).map(t=>({...t,active:"boolean"!=typeof t.active||t.active})):[]}:void 0}(JSON.parse(e.readFileSync(t.identityPath,"utf8")))}catch{return}}function Ye(t,o){e.mkdirSync(n.dirname(t.identityPath),{recursive:!0}),e.writeFileSync(t.identityPath,`${JSON.stringify({...o,updatedAt:We()},null,2)}\n`,{mode:384})}function Qe(t,e){const n=Je(t);Ye(t,{...n||{createdAt:We()},nodeId:e,updatedAt:We(),pairingInvites:n?.pairingInvites||[],remoteControlPlanes:n?.remoteControlPlanes||[]})}function Ze(t,e){if(e?.trim())return Qe(t,e.trim()),e.trim();const n=Je(t);if(n?.nodeId)return n.nodeId;const o=pe("node");return Qe(t,o),o}function Xe(t){const e=Date.now();return{...t,pairingInvites:(t.pairingInvites||[]).filter(t=>Date.parse(t.expiresAt)>e)}}function tn(t,e=""){return(Je(t)?.remoteControlPlanes||[]).map(({secret:t,...n})=>({...n,current:Boolean(e&&n.keyId===e)})).sort((t,e)=>Number(e.current)-Number(t.current)||e.updatedAt.localeCompare(t.updatedAt))}function en(t,e,n=""){if(n&&e===n){const t=new Error("Cannot delete the key currently used by this control-plane connection.");throw Object.assign(t,{statusCode:409,code:"NODE_AGENT_REMOTE_KEY_IN_USE"}),t}const o=Je(t);if(!o)return!1;const r=(o.remoteControlPlanes||[]).filter(t=>t.keyId!==e);return r.length!==(o.remoteControlPlanes||[]).length&&(Ye(t,{...o,remoteControlPlanes:r}),!0)}function nn(t,e,n){return[...(Je(t)?.remoteControlPlanes||[]).map(t=>({keyId:t.keyId,secret:t.secret})),...e&&n?[{keyId:n,secret:e}]:[]]}function on(t){return"local-folder"===t.type?mt.parse({mode:"local-bind",path:"/workspace",readOnly:!1}):mt.parse({mode:"git-clone",path:"/workspace",readOnly:!1})}function rn(t){const e=pt.parse(t.source),n=t.projectId||("local-folder"===e.type?e.localFolderId:void 0)||("git-repository"===e.type?e.repositoryId:void 0)||("git-template"===e.type?e.templateId:void 0)||`project_${t.id}`;return ft.parse({id:n,name:"string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:t.name,source:e,defaultImageId:t.imageId,defaultNodeId:t.nodeId,defaultRuntimeId:t.runtimeId,modelSelection:t.sourceSnapshot.modelSelection&&"object"==typeof t.sourceSnapshot.modelSelection&&!Array.isArray(t.sourceSnapshot.modelSelection)?t.sourceSnapshot.modelSelection:{},workspacePolicy:on(e),labels:{},createdAt:t.createdAt,updatedAt:t.updatedAt})}async function an(t,e){if(!e.target.web)return"unknown";const n=new AbortController,o=setTimeout(()=>n.abort(),2e3);try{return(await t(`${dn(e)}/api/health`,{signal:n.signal})).ok?"reachable":"endpoint-unreachable"}catch{return"endpoint-unreachable"}finally{clearTimeout(o)}}async function sn(t,e,n,o){const r=new AbortController,a=setTimeout(()=>r.abort(),o);try{return await t(e,{...n,signal:r.signal})}finally{clearTimeout(a)}}async function cn(t,e,n,o){if(!e.config.autoImportAgentConfigs)return void o.diagnostic({instanceId:e.id,action:n},"node instance config auto-import skipped");if("reachable"!==e.targetStatus)return;const r=dn(e),a=Number(process.env.TASK_HANDOFF_CONFIG_AUTO_IMPORT_TIMEOUT_MS||5e3);for(const i of $e)try{const s=await sn(t,`${r}/api/config-sync/import/${encodeURIComponent(i)}`,{method:"POST"},Number.isFinite(a)&&a>0?a:5e3);if(!s.ok){o.warn({instanceId:e.id,action:n,preset:i,statusCode:s.status},"node instance config auto-import failed");continue}o.diagnostic({instanceId:e.id,action:n,preset:i},"node instance config auto-import completed")}catch(t){o.warn({instanceId:e.id,action:n,preset:i,error:t instanceof Error?t.message:String(t)},"node instance config auto-import failed")}}function dn(t){const e=t.target.web;if(!e){const e=new Error(`Instance ${t.id} does not have a web endpoint.`);throw Object.assign(e,{statusCode:409,code:"NODE_INSTANCE_WEB_ENDPOINT_MISSING"}),e}if("direct-port"!==t.target.strategy)return e.replace(/\/$/,"");try{const t=new URL(e);return t.port?(t.hostname=process.env.TASK_HANDOFF_NODE_AGENT_PROXY_HOST||"127.0.0.1",t.toString().replace(/\/$/,"")):e.replace(/\/$/,"")}catch{return e.replace(/\/$/,"")}}function ln(t,e){const n=new Error(e);throw Object.assign(n,{statusCode:403,code:t}),n}function un(t,e){if(t===J)return;const n=new Error(`${e} protocol version ${t||"missing"} is not compatible with ${J}.`);throw Object.assign(n,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),n}function pn(t={}){return{requiresImage:!1,supportsControlledInstanceApi:!0,supportsContainerLifecycle:!1,supportsAppSessions:!0,supportsHostSessions:!0,artifactKind:"none",isolation:"none",...t}}function mn(t){return new Promise(e=>{const n=r.createServer();n.once("error",()=>e(!1)),n.listen({host:"127.0.0.1",port:t},()=>{n.close(()=>e(!0))})})}function gn(t,e=3e3){return null!==t.exitCode||null!==t.signalCode?Promise.resolve():new Promise(n=>{const o=setTimeout(()=>{null===t.exitCode&&null===t.signalCode&&t.kill("SIGKILL"),n()},e);o.unref(),t.once("exit",()=>{clearTimeout(o),n()})})}const fn=new Set(["provisioning","starting","registering","registered","running"]);async function hn(t,e){try{const n=await t(e,["--version"]);return{available:!0,command:e,version:(n.stdout||n.stderr).split(/\r?\n/)[0]?.trim()||void 0}}catch{return{available:!1}}}class yn{executor;constructor(t){this.executor=t}start(t){return this.executor.start(t)}stop(t){return this.executor.stop(t)}restart(t){return this.executor.restart(t)}delete(t){return this.executor.delete(t)}}class zn{runCommand;paths;nodeAgentUrl;processByInstanceId=new Map;constructor(t,e,n){this.runCommand=t,this.paths=e,this.nodeAgentUrl=n}async start(t){const o=function(t){if("local-folder"!==t.source.type){const t=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(t,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),t}return n.resolve(t.source.path)}(t.instance),r=t.instance.runtime.port||await async function(){const t=Number(process.env.TASK_HANDOFF_LOCAL_INSTANCE_PORT_START||19e3),e=Number.isInteger(t)&&t>0?t:19e3;for(let t=e;t<e+1e3&&t<=65535;t+=1)if(await mn(t))return t;const n=new Error(`No free localhost port found in range ${e}-${Math.min(e+999,65535)}.`);throw Object.assign(n,{statusCode:503,code:"LOCAL_INSTANCE_PORT_UNAVAILABLE"}),n}(),i=n.join(this.paths.dataDir,"local-instances",t.instance.id),s=n.join(i,"logs");e.mkdirSync(s,{recursive:!0});const[c,...d]=function(){const t=process.env.TASK_HANDOFF_LOCAL_CONTROLLED_COMMAND?.trim();if(t)return t.split(/\s+/);const o=n.resolve(process.cwd(),"bin","task-handoff.js");return e.existsSync(o)?[process.execPath,o,"web"]:["task-handoff-controlled-instance","web"]}(),l=[...d,"--host","127.0.0.1","--port",String(r)],u=e.openSync(n.join(s,"controlled-instance.out.log"),"a"),p=e.openSync(n.join(s,"controlled-instance.err.log"),"a"),m=a.spawn(c,l,{cwd:o,detached:!1,stdio:["ignore",u,p],env:{...process.env,TASK_HANDOFF_CONTROL_MODE:"controlled",TASK_HANDOFF_NODE_AGENT_URL:this.nodeAgentUrl,TASK_HANDOFF_INSTANCE_ID:t.instance.id,TASK_HANDOFF_INSTANCE_NAME:t.instance.name,TASK_HANDOFF_REGISTRATION_TOKEN:t.instance.registrationToken||"",TASK_HANDOFF_PROJECT_ID:t.project.id,TASK_HANDOFF_NODE_ID:t.node.id,TASK_HANDOFF_RUNTIME_ID:t.runtime.id,TASK_HANDOFF_WORKSPACE:o,TASK_HANDOFF_WORKSPACE_MODE:"local-bind",TASK_HANDOFF_DATA_DIR:i,TASK_HANDOFF_LOG_DIR:s,TASK_HANDOFF_APP_SESSION_PERSIST:"1",TASK_HANDOFF_CODEX_APP_SERVER:process.env.TASK_HANDOFF_CODEX_APP_SERVER||"1",TASK_HANDOFF_WEB_PORT:String(r),TASK_HANDOFF_WEB_HOST:"127.0.0.1"}});e.closeSync(u),e.closeSync(p);try{await function(t){return new Promise((e,n)=>{const o=()=>{t.off("error",r),e()},r=e=>{t.off("spawn",o),n(e)};t.once("spawn",o),t.once("error",r)})}(m)}catch(t){const r=t instanceof Error?t.message:String(t);try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] spawn failed command=${c} cwd=${o} error=${r}\n`)}catch{}throw Object.assign(new Error(`Controlled instance process could not start with command ${c} in ${o}: ${r}`),{statusCode:500,code:"LOCAL_INSTANCE_PROCESS_SPAWN_FAILED"})}this.processByInstanceId.set(t.instance.id,m),m.on("error",t=>{try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] process error pid=${m.pid??""} error=${t.message}\n`)}catch{}}),m.once("exit",(o,r)=>{this.processByInstanceId.delete(t.instance.id);try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] exited pid=${m.pid??""} code=${o??""} signal=${r??""}\n`)}catch{}});const g=`http://127.0.0.1:${r}`;return await async function(t,e=3e3){const n=Date.now()+e;for(;Date.now()<n;){try{if((await fetch(`${t}/api/health`)).ok)return!0}catch{}await new Promise(t=>setTimeout(t,100))}return!1}(g),{status:"registering",health:"unknown",connectionStatus:"online",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",target:{strategy:"direct-port",status:"unknown",web:g,api:`${g}/api`},workspace:{mode:"local-bind",status:"pending",path:o},runtime:{kind:"local",workspacePath:o,pid:m.pid,port:r,labels:{...t.instance.runtime.labels,"task-handoff.runtime-kind":"local"}}}}async stop(t){return await async function(t,e){const n=e.get(t.id);if(n&&!n.killed)return n.kill("SIGTERM"),e.delete(t.id),void await gn(n);if(t.runtime.pid)try{process.kill(t.runtime.pid,"SIGTERM")}catch{}}(t.instance,this.processByInstanceId),{status:"stopped",health:"unknown",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async restart(t){return this.start(t)}async delete(t){return this.stop(t)}async check(t){const[e,n]=await Promise.all([hn(this.runCommand,"codex"),hn(this.runCommand,"claude")]);return{status:"online",capabilities:pn({...t.capabilities,apps:{terminal:!0,codex:e,claude:n}})}}async stopAll(){const t=Array.from(this.processByInstanceId.values());for(const t of this.processByInstanceId.values())t.killed||t.kill("SIGTERM");this.processByInstanceId.clear(),await Promise.all(t.map(t=>gn(t)))}}class In{docker;local;constructor(t,e){this.docker=t,this.local=e}forRuntime(t){if("docker"===t.type)return this.docker;if("local"===t.type)return this.local;const e=new Error(`Runtime type ${t.type} is not supported by this node agent.`);throw Object.assign(e,{statusCode:400,code:"NODE_RUNTIME_TYPE_UNSUPPORTED"}),e}async stopAll(){this.local instanceof zn&&await this.local.stopAll()}}class bn{nodeId;paths;localFolders;nodeRuntimes;controlledInstances;updateJobs;node;containerUrl;constructor(t,e,n,o){this.paths=t,this.nodeId=e,this.localFolders=new ge(t.localFoldersDir,{schema:ut}),this.nodeRuntimes=new ge(t.nodeRuntimesDir,{schema:zt}),this.controlledInstances=new ge(t.controlledInstancesDir,{schema:St,sanitize:_t}),this.updateJobs=new Re(t),this.containerUrl=o;const r=We();this.node=yt.parse({id:e,name:e,connectionMode:"direct-http",endpoint:n,controlEndpoint:n,containerEndpoint:o,publicWebBase:n?n.replace(/:\d+$/,""):void 0,status:"online",health:"ok",capabilities:{},labels:{},createdAt:r,updatedAt:r})}init(){if(this.localFolders.init(),this.nodeRuntimes.init(),this.controlledInstances.init(),this.updateJobs.init(),!this.nodeRuntimes.get("runtime_local_docker")){const t=We();this.nodeRuntimes.put(zt.parse({id:"runtime_local_docker",nodeId:this.nodeId,name:"Local Docker",type:"docker",status:"unknown",accessStrategy:"direct-port",capabilities:{},labels:{},createdAt:t,updatedAt:t}))}for(const t of this.nodeRuntimes.list())t.nodeId!==this.nodeId&&this.nodeRuntimes.put(zt.parse({...t,nodeId:this.nodeId,updatedAt:We()}));for(const t of this.localFolders.list())t.nodeId!==this.nodeId&&this.localFolders.put(ut.parse({...t,nodeId:this.nodeId,updatedAt:We()}))}createLocalFolder(t){const e=We(),n=ut.parse({...t,id:t.id||pe("folder"),nodeId:this.nodeId,modelSelection:t.modelSelection||{},labels:t.labels||{},createdAt:e,updatedAt:e});return this.localFolders.put(n)}createRuntime(t){const e=We(),n=zt.parse({...t,id:t.id||pe("runtime"),nodeId:this.nodeId,accessStrategy:t.accessStrategy||(o=t.type,"docker"===o?"direct-port":"node-proxy"),capabilities:"local"===t.type?pn(t.capabilities):t.capabilities||{},labels:t.labels||{},createdAt:e,updatedAt:e});var o;return this.nodeRuntimes.put(n)}updateRuntime(t,e){const n=this.requireRuntime(t),o=zt.parse({...n,...e,id:n.id,nodeId:this.nodeId,accessStrategy:e.accessStrategy||n.accessStrategy,capabilities:"local"===(e.type||n.type)?pn(e.capabilities||n.capabilities):e.capabilities||n.capabilities,createdAt:n.createdAt,updatedAt:We()});return this.nodeRuntimes.put(o)}deleteRuntime(t){this.requireRuntime(t);const e=this.listInstances().filter(e=>e.runtimeId===t);if(e.length){const n=new Error(`Runtime ${t} is used by ${e.length} instance${1===e.length?"":"s"}.`);throw Object.assign(n,{statusCode:409,code:"NODE_RUNTIME_IN_USE"}),n}return this.nodeRuntimes.delete(t)}checkRuntime(t,e){const n=this.requireRuntime(t);return e.check?e.check(n).then(t=>{const e=zt.parse({...n,...t,id:n.id,nodeId:this.nodeId,createdAt:n.createdAt,updatedAt:We()});return this.nodeRuntimes.put(e)}):this.nodeRuntimes.put(zt.parse({...n,status:"unknown",updatedAt:We()}))}requireRuntime(t){const e=this.nodeRuntimes.get(t);if(!e){const e=new Error(`Node runtime ${t} was not found.`);throw Object.assign(e,{statusCode:404,code:"NODE_RUNTIME_NOT_FOUND"}),e}return e}requireInstance(t){const e=this.controlledInstances.get(t);if(!e){const e=new Error(`Instance ${t} was not found on node ${this.nodeId}.`);throw Object.assign(e,{statusCode:404,code:"NODE_INSTANCE_NOT_FOUND"}),e}const n=kt(e);if(!n.success)throw function(t,e){const n=e.map(t=>`${t.path.join(".")||"body"}: ${t.message}`).join("; "),o=new Error(`Stored instance ${t} is not compatible with protocol ${J}: ${n}`);return Object.assign(o,{statusCode:409,code:"NODE_INSTANCE_PAYLOAD_INVALID"}),o}(t,n.error.issues);return n.data}listInstances(){return this.controlledInstances.list().flatMap(t=>{const e=kt(t);return e.success?[e.data]:[]})}createInstance(t){const e=this.requireRuntime(t.runtimeId),o=We(),r=t.id||pe("inst"),a=pt.parse(t.source);if(function(t){return"boolean"==typeof t.capabilities.requiresImage?t.capabilities.requiresImage:"local"!==t.type}(e)&&(!t.imageId||!t.image)){const t=new Error(`Runtime ${e.name} requires an image.`);throw Object.assign(t,{statusCode:400,code:"NODE_RUNTIME_IMAGE_REQUIRED"}),t}if("local"===e.type&&"local-folder"!==a.type){const t=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(t,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),t}if("local"===e.type&&this.listInstances().some(t=>"local"===this.requireRuntime(t.runtimeId).type)){const t=new Error("A localhost instance already exists on this node.");throw Object.assign(t,{statusCode:409,code:"LOCAL_RUNTIME_INSTANCE_EXISTS"}),t}const i=t.image?ht.parse(t.image):void 0,s="local"===e.type&&"local-folder"===a.type?n.resolve(a.path):void 0,c=St.parse({id:r,name:t.name||`instance-${o.replace(/[-:.TZ]/g,"").slice(0,14)}-${r.replace(/^inst_?/,"").slice(0,4)}`,source:a,sourceSnapshot:t.sourceSnapshot||{},projectId:t.projectId,nodeId:this.nodeId,runtimeId:e.id,imageId:i?.id,imageSnapshot:i,status:"created",health:"unknown",connectionStatus:"unknown",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",controlMode:"controlled",capabilities:"local"===e.type?{apps:e.capabilities.apps||{}}:{},config:t.config,workspace:"local"===e.type?{mode:"local-bind",status:"unknown",path:s}:{status:"unknown"},target:{strategy:"node-proxy",status:"unknown"},runtime:"local"===e.type?{kind:"local",workspacePath:s,labels:{"task-handoff.runtime-kind":"local"}}:{labels:{}},registrationToken:me(),createdAt:o,updatedAt:o});return this.controlledInstances.put(c)}registerInstance(t,e,n){const o=vt.parse(e),r=We(),a=this.controlledInstances.get(t);if(!a){const e=new Error(`Instance ${t} was not found on node ${this.nodeId}.`);throw Object.assign(e,{statusCode:404,code:"NODE_INSTANCE_NOT_FOUND"}),e}this.validateInstanceReport(a,o,n),un(o.protocolVersion,`Instance ${t}`);const i=St.parse({...a,name:o.name,status:"registered",health:"ok",connectionStatus:"online",agentStatus:"online",targetStatus:"endpoint-unreachable"===o.target.status?"endpoint-unreachable":"reachable"===o.target.status?"reachable":a.targetStatus,uiAccessStatus:"endpoint-unreachable"===o.target.status?"endpoint-unreachable":a.uiAccessStatus,controlMode:o.controlMode,protocolVersion:o.protocolVersion,instanceVersion:o.instanceVersion,build:o.build,capabilities:o.capabilities,workspace:o.workspace,target:{...a.target,...o.target},registrationToken:a.registrationToken||o.registrationToken,lastHeartbeatAt:r,updatedAt:r});return this.controlledInstances.put(i)}heartbeatInstance(t,e,n){const o=this.requireInstance(t);this.validateInstanceToken(o,n);const r=Et.parse(e);un(r.protocolVersion,`Instance ${t}`);const a=We(),i=r.target?{...o.target,...r.target}:o.target,s={...i,status:"unknown"===i.status&&(i.web||i.api)?"reachable":i.status},c="endpoint-unreachable"===s.status?"endpoint-unreachable":"reachable"===s.status?"reachable":o.targetStatus,d=St.parse({...o,...r,target:s,agentStatus:"online",targetStatus:c,uiAccessStatus:c,connectionStatus:"online",build:r.build||o.build,lastHeartbeatAt:a,updatedAt:a});return this.controlledInstances.put(d)}validateInstanceReport(t,e,n){this.validateInstanceToken(t,n||e.registrationToken),e.instanceId&&e.instanceId!==t.id&&ln("INSTANCE_ID_MISMATCH",`Instance ${e.instanceId} cannot register as ${t.id}.`),e.nodeId&&e.nodeId!==this.nodeId&&ln("INSTANCE_NODE_MISMATCH",`Instance ${t.id} belongs to node ${this.nodeId}.`),e.runtimeId&&e.runtimeId!==t.runtimeId&&ln("INSTANCE_RUNTIME_MISMATCH",`Instance ${t.id} belongs to runtime ${t.runtimeId}.`),e.imageId&&e.imageId!==t.imageId&&ln("INSTANCE_IMAGE_MISMATCH",`Instance ${t.id} belongs to image ${t.imageId}.`)}validateInstanceToken(t,e){t.registrationToken&&e===t.registrationToken||ln("INSTANCE_REGISTRATION_TOKEN_INVALID",`Invalid registration token for instance ${t.id}.`)}context(t,e={}){const n=t.imageSnapshot||ht.parse({id:t.imageId||"img_localhost",name:t.imageId||"Localhost",image:t.imageId||"localhost",registry:"local",capabilities:[],optionalApps:[],defaultEnv:{},labels:{},createdAt:t.createdAt,updatedAt:t.updatedAt});return{project:rn(t),image:n,node:this.node,runtime:this.requireRuntime(t.runtimeId),instance:t,nodeAgentUrl:this.containerUrl,modelEnv:e}}}async function An(t,e,n,o,r,a,i="request"){const s=t.requireInstance(o);a.diagnostic({instanceId:o,action:"start",reason:i,runtimeId:s.runtimeId,imageId:s.imageId},"node instance start requested");const c=t.controlledInstances.put(St.parse({...s,status:"provisioning",updatedAt:We()})),d=e.forRuntime(t.requireRuntime(c.runtimeId)),l=await d.start(t.context(c,r)),u=await an(n,St.parse({...c,...l,target:l.target?{...c.target,...l.target}:c.target,workspace:l.workspace?{...c.workspace,error:void 0,...l.workspace}:c.workspace,runtime:l.runtime?{...c.runtime,...l.runtime}:c.runtime,updatedAt:We()})),p=St.parse({...c,...l,target:l.target?{...c.target,...l.target,status:u}:c.target,workspace:l.workspace?{...c.workspace,error:void 0,...l.workspace}:c.workspace,runtime:l.runtime?{...c.runtime,...l.runtime}:c.runtime,targetStatus:u,uiAccessStatus:u,connectionStatus:l.connectionStatus,updatedAt:We()}),m=t.controlledInstances.put(p);return await cn(n,m,"start",a),a.diagnostic({instanceId:o,action:"start",reason:i,status:m.status,connectionStatus:m.connectionStatus,targetStatus:m.targetStatus,targetWeb:m.target.web,containerName:m.runtime.containerName},"node instance start completed"),m}function wn(t,e){const n=e instanceof Error?e.message:String(e);return St.parse({...t,status:"failed",health:"failed",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",workspace:{...t.workspace,error:n},updatedAt:We()})}function Sn(t){return St.parse({...t,status:"stopped",health:"unknown",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",target:{...t.target,status:"unknown"},updatedAt:We()})}async function kn(t={}){const o=t.token||process.env.TASK_HANDOFF_NODE_AGENT_TOKEN,r=String(t.port||process.env.TASK_HANDOFF_NODE_AGENT_PORT||"8091"),a=`http://127.0.0.1:${r}`,u=t.containerUrl||process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL||`http://host.docker.internal:${r}`,p=se(t.dataDir),m=t.remoteSecret||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_SECRET,g=t.remoteKeyId||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_KEY_ID,f=Ze(p,t.nodeId||process.env.TASK_HANDOFF_NODE_ID),h=t.connectionMode||("local-ipc"===process.env.TASK_HANDOFF_NODE_AGENT_CONNECTION_MODE?"local-ipc":"local-loopback"),y=t.ipcPath||process.env.TASK_HANDOFF_NODE_AGENT_IPC_PATH||be(p.dataDir),z="local-ipc"===h?`ipc://${encodeURIComponent(y)}`:a,I=new bn(p,f,a,u);I.node.connectionMode=h,I.node.controlEndpoint=z,I.node.endpoint=z,I.init();const b=new Rt(t.dockerCommandRunner,{publishHost:"127.0.0.1"}),A=new In(new yn(b),new zn(t.dockerCommandRunner||Dt,p,a)),w=t.updateCommandRunner||t.dockerCommandRunner||Dt,S=t.fetchImpl||fetch,k=i({logger:t.logger??!0});k.decorate("nodeAgentState",I),await k.register(s);const _=new Jt(I,o,{logger:k.log});_.start(),k.decorate("nodeAgentEventForwarder",_);const N=(T=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(T||"").trim().toLowerCase())),v=new Map,E=(t,e)=>{N&&k.log.info(t,e)},O={diagnostic:E,warn:(t,e)=>k.log.warn(t,e)},x=async t=>{if("node-agent"===t.target.component)return async function(t){const e=await xe(t.runCommand,"@task-handoff/node-agent",t.channel);return e?at.parse({target:{component:"node-agent"},source:"npm",channel:t.channel,currentVersion:t.currentVersion,availableVersion:e,updateAvailable:Oe(t.currentVersion,e),checkedAt:ve()}):Ce({target:{component:"node-agent"},channel:t.channel,currentVersion:t.currentVersion})}({channel:t.channel,currentVersion:Ke(),runCommand:w});const e=I.requireInstance(t.target.instanceId);return async function(t){const e={component:"controlled-instance",instanceId:t.instance.id};if("local"===t.runtime.type){const n=await xe(t.runCommand,"@task-handoff/controlled-instance",t.channel),o=t.instance.build?.packageVersion||t.instance.instanceVersion;return n?at.parse({target:e,source:"npm",channel:t.channel,currentVersion:o,availableVersion:n,updateAvailable:Oe(o,n),checkedAt:ve()}):Ce({target:e,channel:t.channel,currentVersion:o})}if("docker"===t.runtime.type){const n=t.instance.imageSnapshot?.image;if(!n||"local"===t.instance.imageSnapshot?.registry)return at.parse({target:e,source:"docker-registry",channel:t.channel,currentVersion:t.instance.build?.imageDigest,availableVersion:t.instance.build?.imageDigest||"unavailable",updateAvailable:!1,supported:!1,reason:"The instance does not use a remote Docker image.",checkedAt:ve()});const o=function(t,e){const n=t.split("@",1)[0],o=n.lastIndexOf("/"),r=n.lastIndexOf(":");return`${r>o?n.slice(0,r):n}:${je(e)}`}(n,t.channel),r=await t.runCommand("docker",["manifest","inspect","--verbose",o]),a=De(JSON.parse(r.stdout));if(!a)throw new Error(`Docker registry did not return a digest for ${o}.`);const i=t.instance.build?.imageDigest;return at.parse({target:e,source:"docker-registry",channel:t.channel,currentVersion:i,availableVersion:a,artifactRef:o,updateAvailable:i!==a,checkedAt:ve()})}return at.parse({target:e,source:"npm",channel:t.channel,availableVersion:"unsupported",updateAvailable:!1,supported:!1,reason:`Runtime type ${t.runtime.type} does not support managed updates.`,checkedAt:ve()})}({channel:t.channel,instance:e,runtime:I.requireRuntime(e.runtimeId),runCommand:w})};var T;return k.setErrorHandler((t,e,n)=>{const o=function(t){if(t instanceof d.z.ZodError)return{statusCode:400,code:"VALIDATION_ERROR",message:t.issues.map(t=>`${t.path.join(".")||"body"}: ${t.message}`).join("; ")};const e=t&&"object"==typeof t?t:{};return{statusCode:"number"==typeof e.statusCode?e.statusCode:500,code:"string"==typeof e.code?e.code:"NODE_AGENT_ERROR",message:t instanceof Error?t.message:String(t)}}(t);(N||o.statusCode>=500)&&(o.statusCode>=500?k.log.error.bind(k.log):k.log.warn.bind(k.log))({method:e.method,url:e.url,statusCode:o.statusCode,errorCode:o.code,error:o.message},"node agent request failed"),n.code(o.statusCode).send({error:{code:o.code,message:o.message}})}),k.addHook("preHandler",async t=>{const e=function(t){const e=function(t){const e=new Map;for(const[n,o]of Object.entries(t)){const t=Array.isArray(o)?o.join(","):"string"==typeof o?o:void 0===o?"":String(o);e.set(n.toLowerCase(),t)}return{nodeId:e.get("x-taskhandoff-node-id")||"",keyId:e.get("x-taskhandoff-key-id")||"",timestamp:e.get("x-taskhandoff-timestamp")||"",nonce:e.get("x-taskhandoff-nonce")||"",bodySha256:e.get("x-taskhandoff-body-sha256")||"",signature:e.get("x-taskhandoff-signature")||""}}(t.headers);if(!e.nodeId&&!e.signature)return!1;const n=nn(p,m,g);if(!n.length)return!1;if(e.nodeId!==f)throw Object.assign(new Error("Invalid node agent HMAC node id."),{statusCode:401,code:"NODE_AGENT_HMAC_NODE_MISMATCH"});if(!e.keyId)throw Object.assign(new Error("Node agent HMAC key id is missing."),{statusCode:401,code:"NODE_AGENT_HMAC_KEY_MISSING"});const o=n.filter(t=>e.keyId===t.keyId);if(!o.length)throw Object.assign(new Error("Invalid node agent HMAC key id."),{statusCode:401,code:"NODE_AGENT_HMAC_KEY_INVALID"});const r=Date.parse(e.timestamp);if(!Number.isFinite(r)||Math.abs(Date.now()-r)>we)throw Object.assign(new Error("Node agent HMAC timestamp is outside the allowed window."),{statusCode:401,code:"NODE_AGENT_HMAC_TIMESTAMP_INVALID"});if(!e.nonce)throw Object.assign(new Error("Node agent HMAC nonce is missing."),{statusCode:401,code:"NODE_AGENT_HMAC_NONCE_MISSING"});!function(){const t=Date.now()-we;for(const[e,n]of v)n<=t&&v.delete(e)}();const a=`${e.nodeId}:${e.keyId}:${e.nonce}`;if(v.has(a))throw Object.assign(new Error("Node agent HMAC nonce was already used."),{statusCode:401,code:"NODE_AGENT_HMAC_NONCE_REPLAY"});const i=Se(null==(s=t.body)?"":"string"==typeof s||Buffer.isBuffer(s)?s:JSON.stringify(s));var s;if(e.bodySha256!==i)throw Object.assign(new Error("Node agent HMAC body hash mismatch."),{statusCode:401,code:"NODE_AGENT_HMAC_BODY_HASH_INVALID"});if(!o.some(n=>function(t,e){const n=Buffer.from(t,"hex"),o=Buffer.from(e,"hex");return n.length===o.length&&l.timingSafeEqual(n,o)}(e.signature,ke(n.secret,{keyId:e.keyId,method:t.method,pathWithQuery:t.url,timestamp:e.timestamp,nonce:e.nonce,bodySha256:i}))))throw Object.assign(new Error("Invalid node agent HMAC signature."),{statusCode:401,code:"NODE_AGENT_HMAC_SIGNATURE_INVALID"});return v.set(a,Date.now()+we),e.keyId}(t);if(e)t.nodeAgentAuthKeyId=e;else if(function(t,e){return"local-ipc"===e&&!t.ip&&!t.socket.remoteAddress&&!t.socket.remoteFamily}(t,h)){if(o&&Be(t.headers)!==o){const t=new Error("Invalid node agent token.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_UNAUTHORIZED"}),t}}else if("/api/node-agent/pairing/complete"!==t.url.split("?")[0]&&!function(t){const e=t.split("?")[0];return/^\/api\/node-agent\/instances\/[^/]+\/(register|heartbeat)$/.test(e)}(t.url)){if(nn(p,m,g).length){const t=new Error("Node agent HMAC signature is required.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_HMAC_SIGNATURE_REQUIRED"}),t}if(!function(t){return Ve(t.ip)||Ve(t.socket.remoteAddress)||!t.ip&&!t.socket.remoteAddress&&!t.socket.remoteFamily}(t)){const t=new Error("Local node agent access is only accepted from loopback or local IPC connections.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_LOCAL_TOKEN_REQUIRES_LOOPBACK"}),t}if(o&&Be(t.headers)!==o){const t=new Error("Invalid node agent token.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_UNAUTHORIZED"}),t}}}),k.addHook("onClose",async()=>{_.stop(),await async function(t,e){await e.stopAll();for(const e of t.listInstances())"local"===t.requireRuntime(e.runtimeId).type&&t.controlledInstances.put(Sn(e))}(I,A)}),k.addHook("onListen",async()=>{await(async()=>{const t=I.listInstances().filter(t=>"local"===I.requireRuntime(t.runtimeId).type&&fn.has(t.status));for(const e of t)try{await An(I,A,S,e.id,{},O,"restore")}catch(t){I.controlledInstances.put(wn(I.requireInstance(e.id),t)),E({instanceId:e.id,action:"restore",error:t instanceof Error?t.message:String(t)},"node local instance restore failed")}})()}),k.get("/api/node-agent/health",async()=>{return{data:{ok:!0,role:"node-agent",nodeId:f,protocolVersion:J,build:(t="node-agent",{component:t,packageName:"@task-handoff/node-agent",packageVersion:Ke(),protocolVersion:J,buildId:qe("TASK_HANDOFF_BUILD_ID"),builtAt:qe("TASK_HANDOFF_BUILT_AT"),gitCommit:qe("TASK_HANDOFF_GIT_COMMIT"),imageRef:qe("TASK_HANDOFF_IMAGE_REF"),imageDigest:qe("TASK_HANDOFF_IMAGE_DIGEST")}),serverTime:(new Date).toISOString()}};var t}),k.get("/api/node-agent/updates/jobs",async()=>({data:I.updateJobs.list()})),k.post("/api/node-agent/updates/check",async t=>({data:await x(rt.parse(t.body))})),k.post("/api/node-agent/updates/apply",async(t,o)=>{const r=Me.parse(t.body),a=await x(r);if(!a.supported){const t=new Error(a.reason||"The requested update is not supported.");throw Object.assign(t,{statusCode:400,code:"UPDATE_UNSUPPORTED"}),t}if(!a.updateAvailable){const t=new Error(a.reason||"No update is available for the selected channel.");throw Object.assign(t,{statusCode:409,code:"UPDATE_NOT_AVAILABLE"}),t}const i=I.updateJobs.create(f,a);if("node-agent"===r.target.component){const t=n.resolve(__dirname,"..","bin","task-handoff-node-update-worker");if(!e.existsSync(t)){I.updateJobs.patch(i.id,{status:"failed",error:`Update worker was not found: ${t}`,completedAt:We()});const e=new Error(`Node agent update worker was not found: ${t}`);throw Object.assign(e,{statusCode:500,code:"UPDATE_WORKER_NOT_FOUND"}),e}await w("systemd-run",["--unit",`task-handoff-update-${i.id}`,"--collect","--property=Type=exec",t,"--job-file",I.updateJobs.records.filePath(i.id),"--target-version",i.toVersion,"--npm-command",Te()])}else I.updateJobs.run(i,t=>(async(t,e)=>{if("controlled-instance"!==t.target.component)return;const n=I.requireInstance(t.target.instanceId),o=I.requireRuntime(n.runtimeId),r=A.forRuntime(o);if("local"===o.type){const e=Te(),n=(await w(e,["prefix","--global"])).stdout.trim();await w(e,["install","--global","--prefix",n,`@task-handoff/controlled-instance@${t.toVersion}`])}else{if("docker"!==o.type)throw Object.assign(new Error(`Runtime type ${o.type} does not support managed updates.`),{code:"INSTANCE_UPDATE_UNSUPPORTED",statusCode:400});{const e=t.artifactRef;if(!e||"local"===n.imageSnapshot?.registry)throw Object.assign(new Error("The instance does not use an updateable remote Docker image."),{code:"INSTANCE_UPDATE_UNSUPPORTED",statusCode:400});await w("docker",["pull",e])}}await r.delete(I.context(n,e)),I.controlledInstances.put(Sn(St.parse({...n,..."docker"===o.type&&t.artifactRef&&n.imageSnapshot?{imageSnapshot:{...n.imageSnapshot,image:t.artifactRef,updatedAt:We()}}:{}}))),await An(I,A,S,n.id,e,O,"update")})(t,r.modelEnv));return o.code(202).send({data:i})}),k.post("/api/node-agent/pairing/invites",async(t,e)=>{const n=function(t,e){const n=Xe(Je(t)||{nodeId:Ze(t),createdAt:We(),updatedAt:We()}),o=We(),r=new Date(Date.now()+(e.expiresInMs||6e5)).toISOString(),a=me(),i={tokenHash:Se(a),createdAt:o,expiresAt:r,...e.controlPlaneName?{controlPlaneName:e.controlPlaneName}:{},...e.controlPlaneUrl?{controlPlaneUrl:e.controlPlaneUrl}:{}};return Ye(t,{...n,pairingInvites:[...n.pairingInvites||[],i]}),{...i,token:a}}(p,Pe.parse(t.body||{}));return e.code(201).send({data:{nodeId:f,joinToken:n.token,expiresAt:n.expiresAt}})}),k.post("/api/node-agent/pairing/complete",async(t,e)=>{const n=function(t,e){const n=Xe(Je(t)||{nodeId:Ze(t),createdAt:We(),updatedAt:We()}),o=Se(e.joinToken),r=(n.pairingInvites||[]).find(t=>t.tokenHash===o);if(!r){const t=new Error("Node agent pairing invite is invalid or expired.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_PAIRING_INVITE_INVALID"}),t}const a=We(),i={id:e.controlPlaneId||pe("cp"),keyId:pe("key"),...e.controlPlaneName||r.controlPlaneName?{name:e.controlPlaneName||r.controlPlaneName}:{},...e.controlPlaneUrl||r.controlPlaneUrl?{url:e.controlPlaneUrl||r.controlPlaneUrl}:{},secret:me(),pairedAt:a,updatedAt:a,active:!0};return Ye(t,{...n,pairingInvites:(n.pairingInvites||[]).filter(t=>t.tokenHash!==o),remoteControlPlanes:[...(n.remoteControlPlanes||[]).filter(t=>t.id!==i.id),i]}),i}(p,He.parse(t.body));return e.code(201).send({data:{nodeId:f,keyId:n.keyId,secret:n.secret,pairedAt:n.pairedAt}})}),k.get("/api/node-agent/remotes",async t=>({data:tn(p,t.nodeAgentAuthKeyId)})),k.delete("/api/node-agent/remotes/:keyId",async t=>{const e=d.z.string().trim().min(1).max(160).parse(t.params.keyId);return{data:{deleted:en(p,e,t.nodeAgentAuthKeyId)}}}),k.post("/api/node-agent/remotes/connect",async(t,e)=>{const n=Le.parse(t.body),o=function(t){const e=new URL(t);if("http:"!==e.protocol&&"https:"!==e.protocol){const t=new Error("Control-plane URL must use http or https.");throw Object.assign(t,{statusCode:400,code:"NODE_AGENT_REMOTE_CONTROL_PLANE_URL_INVALID"}),t}return e.toString().replace(/\/$/,"")}(n.controlPlaneUrl),r=We(),a={id:pe("cp"),keyId:pe("key"),...n.controlPlaneName?{name:n.controlPlaneName}:{},url:o,secret:me(),pairedAt:r,updatedAt:r,active:!1!==n.activate},i=await async function(t,e,n){const o=await t(new URL("/api/node-join/complete",e).toString(),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),r=await o.json().catch(()=>({}));if(!o.ok){const t=new Error(r.error?.message||`Control-plane node join failed with HTTP ${o.status}.`);throw Object.assign(t,{statusCode:o.status,code:r.error?.code||"NODE_AGENT_REMOTE_JOIN_FAILED"}),t}return r.data||{}}(S,o,{joinToken:n.joinToken,nodeId:f,nodeName:I.node.name,keyId:a.keyId,secret:a.secret,pairedAt:a.pairedAt}),s=function(t,e){const n=Je(t)||{nodeId:Ze(t),createdAt:We(),updatedAt:We(),pairingInvites:[],remoteControlPlanes:[]},o=e.url?.replace(/\/$/,"");return Ye(t,{...n,remoteControlPlanes:[...(n.remoteControlPlanes||[]).filter(t=>t.id!==e.id&&(!o||t.url?.replace(/\/$/,"")!==o)),e]}),e}(p,{...a,..."string"==typeof i.name&&i.name?{name:n.controlPlaneName||i.name}:{}});let c,d=!1!==s.active?"saved":"disabled";if(!1!==s.active&&k.nodeAgentReverseTunnels)try{k.nodeAgentReverseTunnels.connect({url:o,keyId:s.keyId,secret:s.secret}),d="connecting"}catch(t){d="failed",c=t instanceof Error?t.message:String(t)}return e.code(201).send({data:{remote:{id:s.id,url:s.url,keyId:s.keyId,pairedAt:s.pairedAt,active:!1!==s.active},tunnel:{status:d,...c?{error:c}:{}}}})}),k.get("/api/node-agent/runtimes",async()=>({data:I.nodeRuntimes.list()})),k.post("/api/node-agent/runtimes",async(t,e)=>e.code(201).send({data:I.createRuntime(ae.parse(t.body))})),k.patch("/api/node-agent/runtimes/:id",async t=>({data:I.updateRuntime(t.params.id,ie.parse(t.body))})),k.delete("/api/node-agent/runtimes/:id",async t=>({data:{deleted:I.deleteRuntime(t.params.id)}})),k.post("/api/node-agent/runtimes/:id/check",async t=>{const e=I.requireRuntime(t.params.id);return{data:await I.checkRuntime(e.id,A.forRuntime(e))}}),k.get("/api/node-agent/local-folders",async()=>({data:I.localFolders.list()})),k.get("/api/node-agent/folders/tree",async t=>({data:Xt(ne.parse(t.query))})),k.post("/api/node-agent/local-folders",async(t,e)=>e.code(201).send({data:I.createLocalFolder(ee.parse(t.body))})),k.delete("/api/node-agent/local-folders/:id",async t=>({data:{deleted:I.localFolders.delete(t.params.id)}})),k.get("/api/node-agent/instances",async()=>({data:I.listInstances()})),k.post("/api/node-agent/instances",async(t,e)=>{const n=I.createInstance(oe.parse(t.body));return _.syncNow(),e.code(201).send({data:n})}),k.patch("/api/node-agent/instances/:id",async t=>{const e=t.params.id,n=I.requireInstance(e),o=re.parse(t.body),r=St.parse({...n,...o,updatedAt:We()}),a=I.controlledInstances.put(r);return _.syncNow(),{data:a}}),k.post("/api/node-agent/instances/:id/register",async(t,e)=>{const n=t.params.id,o=I.registerInstance(n,vt.parse(t.body),Be(t.headers));return _.syncNow(),E({instanceId:n,action:"register",protocolVersion:o.protocolVersion,build:o.build,targetStatus:o.targetStatus,targetStrategy:o.target.strategy},"node instance registered"),e.code(201).send({data:o})}),k.post("/api/node-agent/instances/:id/heartbeat",async t=>{const e=t.params.id,n=I.heartbeatInstance(e,Et.parse(t.body),Be(t.headers));return _.syncNow(),E({instanceId:e,action:"heartbeat",status:n.status,health:n.health,protocolVersion:n.protocolVersion,build:n.build,targetStatus:n.targetStatus,apps:n.apps.runningCount},"node instance heartbeat accepted"),{data:n}}),k.get("/api/node-agent/events",{websocket:!0},t=>{const e=t;e.send(JSON.stringify({type:"node-agent.events.connected",nodeId:f,serverTime:(new Date).toISOString()}));const n=_.addOutput(e);e.on("close",n),e.on("error",n)}),k.get("/api/node-agent/docker/images",async()=>({data:await Ft(t.dockerCommandRunner)})),k.post("/api/node-agent/instances/:id/start",async t=>{const e=t.params.id,n=Ue.parse(t.body);return{data:await An(I,A,S,e,n.modelEnv,O)}}),k.post("/api/node-agent/instances/:id/stop",async t=>{const e=I.requireInstance(t.params.id);E({instanceId:e.id,action:"stop",runtimeId:e.runtimeId,containerName:e.runtime.containerName},"node instance stop requested");const n=A.forRuntime(I.requireRuntime(e.runtimeId)),o=await n.stop(I.context(e)),r=St.parse({...e,...o,agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",updatedAt:We()}),a=I.controlledInstances.put(r);return E({instanceId:e.id,action:"stop",status:a.status,connectionStatus:a.connectionStatus,containerName:a.runtime.containerName},"node instance stop completed"),{data:a}}),k.post("/api/node-agent/instances/:id/restart",async t=>{const e=t.params.id,n=I.requireInstance(e),o=Ue.parse(t.body);E({instanceId:e,action:"restart",runtimeId:n.runtimeId,imageId:n.imageId,containerName:n.runtime.containerName},"node instance restart requested");const r=A.forRuntime(I.requireRuntime(n.runtimeId)),a=await r.restart(I.context(n,o.modelEnv)),i=St.parse({...n,...a,target:a.target?{...n.target,...a.target}:n.target,updatedAt:We()}),s=await an(S,i),c=St.parse({...n,...a,target:a.target?{...n.target,...a.target,status:s}:n.target,targetStatus:s,uiAccessStatus:s,connectionStatus:a.connectionStatus,updatedAt:We()}),d=I.controlledInstances.put(c);return await cn(S,d,"restart",O),E({instanceId:e,action:"restart",status:d.status,connectionStatus:d.connectionStatus,targetStatus:d.targetStatus,targetWeb:d.target.web,containerName:d.runtime.containerName},"node instance restart completed"),{data:d}}),k.post("/api/node-agent/instances/:id/delete",async t=>{const e=t.params.id,n=I.requireInstance(e);E({instanceId:e,action:"delete",runtimeId:n.runtimeId,containerName:n.runtime.containerName},"node instance delete requested");const o=A.forRuntime(I.requireRuntime(n.runtimeId));return await o.delete(I.context(n)),E({instanceId:e,action:"delete"},"node instance delete completed"),{data:{deleted:I.controlledInstances.delete(e)}}}),k.post("/api/node-agent/instances/:id/proxy",async(t,e)=>{const n=t.params.id,o=te.parse(t.body),r=dn(I.requireInstance(n)),a=o.path.startsWith("/")?o.path:`/${o.path}`;E({instanceId:n,action:"proxy",method:o.method,path:a,instanceBase:r},"node instance proxy requested");const i=await S(`${r}${a}`,{method:o.method,headers:{...o.headers},body:Ge(o)}),s=i.headers.get("content-type")||"application/octet-stream",c=await i.text();E({instanceId:n,action:"proxy",method:o.method,path:a,statusCode:i.status,contentType:s},"node instance proxy completed"),e.code(i.status).type(s).send(c)}),k.post("/api/node-agent/instances/:id/proxy/raw",async t=>{const e=t.params.id,n=te.parse(t.body),o=dn(I.requireInstance(e)),r=n.path.startsWith("/")?n.path:`/${n.path}`;E({instanceId:e,action:"proxy.raw",method:n.method,path:r,instanceBase:o},"node instance raw proxy requested");const a=await S(`${o}${r}`,{method:n.method,headers:{...n.headers},body:Ge(n)}),i=Buffer.from(await a.arrayBuffer());return E({instanceId:e,action:"proxy.raw",method:n.method,path:r,statusCode:a.status,byteLength:i.length},"node instance raw proxy completed"),{data:{status:a.status,headers:(s=a.headers,Object.fromEntries([...s.entries()].filter(([t])=>!Fe.has(t.toLowerCase())))),bodyBase64:i.toString("base64")}};var s}),k.get("/api/node-agent/instances/:id/proxy/ws/*",{websocket:!0},(t,e)=>{const n=e.params.id,o=e.params["*"]||"",r=e.url.indexOf("?"),a=r>=0?e.url.slice(r):"";let i;try{const r=dn(I.requireInstance(n)),s=new URL(`/${o}${a}`,`${r}/`);s.protocol="https:"===s.protocol?"wss:":"ws:";const d=function(t){const e=t["sec-websocket-protocol"],n=(Array.isArray(e)?e.join(","):"string"==typeof e?e:"").split(",").map(t=>t.trim()).filter(Boolean);return n.length?n:void 0}(e.headers);E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,upstreamUrl:s.toString(),protocols:d||[]},"node instance websocket proxy opening"),i=d?new c(s,d):new c(s),i.on("open",()=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`},"node instance websocket proxy opened"),t.on("message",(t,e)=>i?.readyState===c.OPEN&&i.send(function(t,e){return e?t:t.toString()}(t,e))),t.on("close",()=>i?.close()),t.on("error",()=>i?.close())}),i.on("message",(e,n)=>{t.readyState===c.OPEN&&t.send(n?e:e.toString())}),i.on("close",()=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`},"node instance websocket proxy closed"),t.close()}),i.on("error",e=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,error:e instanceof Error?e.message:String(e)},"node instance websocket proxy failed"),t.close(1011,"Instance websocket proxy failed.")})}catch(e){E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,error:e instanceof Error?e.message:String(e)},"node instance websocket endpoint unavailable"),i?.close(),t.close(1011,"Instance websocket endpoint is not reachable.")}}),k}function _n(t){const e=new URL("/api/node-agent/tunnel",t);return e.protocol="https:"===e.protocol?"wss:":"ws:",e.toString()}function Nn(t,e){const n=new URL(e.tunnelUrl);n.searchParams.set("nodeId",e.nodeId);const o=e.secret?_e({nodeId:e.nodeId,keyId:e.keyId,secret:e.secret,method:"GET",pathWithQuery:`${n.pathname}${n.search}`}):{},r=new c(n,{headers:o}),a=new Map;let i;const s=t=>{const n=t.startsWith("/")?t:`/${t}`,o=new URL(`/api/node-agent${n}`,`http://127.0.0.1:${e.port}`);return o.protocol="ws:",o},d=t=>e.secret?_e({nodeId:e.nodeId,keyId:e.keyId,secret:e.secret,method:"GET",pathWithQuery:`${t.pathname}${t.search}`}):{},l=(t,e=1e3,n="")=>{const o=a.get(t);a.delete(t),o?.upstream?.close(e,n),o?.tunnel?.close(e,n)};return r.on("open",()=>{r.send(JSON.stringify({type:"node-agent.identify",nodeId:e.nodeId,serverTime:(new Date).toISOString()})),i=t.nodeAgentEventForwarder?.addOutput(r)}),r.on("close",()=>{for(const t of a.keys())l(t,1001,"Reverse tunnel disconnected.");i?.(),i=void 0}),r.on("message",async o=>{let i;try{i=JSON.parse(String(o))}catch{return void r.send(JSON.stringify({type:"node-agent.error",code:"INVALID_JSON"}))}const u=i&&"object"==typeof i?i:{};if("control-plane.websocket.open"===u.type){const t="string"==typeof u.streamId?u.streamId:"",o="string"==typeof u.route?u.route:"",i=Array.isArray(u.protocols)?u.protocols.filter(t=>"string"==typeof t):void 0;try{const l=e.token?{authorization:`Bearer ${e.token}`}:void 0,u=i?.length?new c(s(o),i,{headers:l}):new c(s(o),{headers:l}),p=(t=>{const e=new URL(n);return e.pathname=`${e.pathname.replace(/\/$/,"")}/streams/${encodeURIComponent(t)}`,e})(t),m=new c(p,{headers:d(p)});a.set(t,{upstream:u,tunnel:m,close:(t=1e3,e="")=>{u.close(t,e),m.close(t,e)}}),u.on("open",()=>{r.send(JSON.stringify({type:"node-agent.websocket.open",streamId:t,protocol:u.protocol}))}),function(t,e,n={}){const o=n.pendingFrameLimit??256;let r=!1,a=xt(e);const i=[],s=[];let c;const d=()=>{c&&(clearTimeout(c),c=void 0)},l=(n,r)=>{if(n.length>=o)return Tt(t,1011,"WebSocket bridge pending frame limit exceeded."),void Tt(e,1011,"WebSocket bridge pending frame limit exceeded.");n.push(r)};n.upstreamOpenTimeoutMs&&!a&&(c=setTimeout(()=>{a||r||(Tt(t,1011,"Upstream websocket did not open."),Tt(e))},n.upstreamOpenTimeoutMs)),t.on("open",()=>jt(t,s)),e.on("open",()=>{d(),a=!0,n.onUpstreamOpen?.(),jt(e,i)}),t.on("message",(t,n=!1)=>{const o={data:t,isBinary:Boolean(n)};xt(e)?Ct(e,o):l(i,o)}),e.on("message",(e,n=!1)=>{const o={data:e,isBinary:Boolean(n)};xt(t)?Ct(t,o):l(s,o)}),e.on("close",(e,o)=>{d(),n.onUpstreamClose?.(e,o),(a||r||!n.onUpstreamCloseBeforeOpen?.())&&Tt(t,e,o)}),e.on("error",e=>{n.onUpstreamError?.(e),(a||r||!n.onUpstreamErrorBeforeOpen?.(e))&&Tt(t)}),t.on("close",(t,o)=>{d(),r=!0,n.onClientClose?.(t,o),Tt(e,t,o)}),t.on("error",t=>{d(),r=!0,n.onClientError?.(t),Tt(e,1011,"WebSocket bridge client failed.")})}(m,u,{pendingFrameLimit:256,upstreamOpenTimeoutMs:1e4,onClientClose:()=>{a.delete(t)},onClientError:e=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))},onUpstreamClose:(e,n)=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.close",streamId:t,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(n)?n.toString("utf8"):String(n||"")}))},onUpstreamError:e=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))}})}catch(e){r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))}return}if("control-plane.websocket.close"===u.type){const t="string"==typeof u.streamId?u.streamId:"";return void l(t,"number"==typeof u.code?u.code:1e3,"string"==typeof u.reason?u.reason:"")}if("control-plane.request"===u.type){const n="string"==typeof u.requestId?u.requestId:"",o=u.init&&"object"==typeof u.init?u.init:{},a="string"==typeof u.route&&u.route.startsWith("/")?u.route:"/health",i=o.headers&&"object"==typeof o.headers?o.headers:{};try{const s=await t.inject({method:(p=o.method,"string"==typeof p&&Ne.has(p.toUpperCase())?p.toUpperCase():"GET"),url:`/api/node-agent${a}`,headers:{...i,...e.token?{authorization:`Bearer ${e.token}`}:{}},payload:"string"==typeof o.body?o.body:void 0});r.send(JSON.stringify({type:"node-agent.response",requestId:n,status:s.statusCode,headers:s.headers,body:s.body}))}catch(t){r.send(JSON.stringify({type:"node-agent.response",requestId:n,status:502,error:{code:"NODE_AGENT_REVERSE_INJECT_FAILED",message:t instanceof Error?t.message:String(t)}}))}}var p}),r}async function vn(t){const r=se(t.dataDir),a=function(t,o={}){e.mkdirSync(n.dirname(t),{recursive:!0});const r={pid:process.pid,hostname:u.hostname(),component:o.component,command:process.argv.join(" "),acquiredAt:(new Date).toISOString(),token:l.randomUUID(),dataDir:o.dataDir,host:o.host,port:o.port};for(let n=0;n<2;n+=1)try{return e.mkdirSync(t),Ie(t,r),{lockPath:t,owner:r,release(){const n=ye(t);n?.token===r.token&&e.rmSync(t,{recursive:!0,force:!0})}}}catch(n){if("EEXIST"!==n.code)throw n;const r=ye(t);if(ze(r?.pid))throw new fe(t,r,o.component);e.rmSync(t,{recursive:!0,force:!0})}const a=ye(t);throw new fe(t,a,o.component)}(n.join(u.tmpdir(),`task-handoff-node-agent-${process.getuid?.()??"user"}.lock`),{component:"node-agent",dataDir:r.dataDir,host:t.host,port:t.port});try{const i=await kn(t),s=function(t,e,n,o){const r=e.token||process.env.TASK_HANDOFF_NODE_AGENT_TOKEN,a=new Map,i=n=>{const i=n.url.replace(/\/$/,"");a.get(i)?.close(1e3,"Reverse tunnel reconnecting.");const s=Nn(t,{tunnelUrl:_n(n.url),nodeId:o,port:e.port,token:r,keyId:n.keyId,secret:n.secret});return a.set(i,s),s.on("close",()=>{a.get(i)===s&&a.delete(i)}),s};return{connect:i,connectConfigured:()=>{const s=function(t){const e=t.controlPlaneTunnelUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_TUNNEL_URL;if(e)return e;const n=process.env.TASK_HANDOFF_CONTROL_PLANE_URL;if(!n)return;const o=new URL("/api/node-agent/tunnel",n);return o.protocol="https:"===o.protocol?"wss:":"ws:",o.toString()}(e);if(s){const i=function(t,e,n,o){if(n&&o)return{keyId:o,secret:n};const r=Je(t)?.remoteControlPlanes||[];if(!r.length)return;if(e){const t=r.find(t=>t.url&&t.url.replace(/\/$/,"")===e.replace(/\/$/,""));if(t)return{keyId:t.keyId,secret:t.secret}}const a=[...r].sort((t,e)=>e.updatedAt.localeCompare(t.updatedAt))[0];return a?{keyId:a.keyId,secret:a.secret}:void 0}(n,process.env.TASK_HANDOFF_CONTROL_PLANE_URL,e.remoteSecret||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_SECRET,e.remoteKeyId||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_KEY_ID),c=(process.env.TASK_HANDOFF_CONTROL_PLANE_URL||s).replace(/\/$/,"");a.get(c)?.close(1e3,"Reverse tunnel reconnecting.");const d=Nn(t,{tunnelUrl:s,nodeId:o,port:e.port,token:r,keyId:i?.keyId,secret:i?.secret});a.set(c,d)}for(const t of Je(n)?.remoteControlPlanes||[])t.url&&!1!==t.active&&i({url:t.url,keyId:t.keyId,secret:t.secret})},closeAll:()=>{for(const t of a.values())t.close(1001,"Node agent shutting down.");a.clear()}}}(i,t,r,Ze(r,t.nodeId||process.env.TASK_HANDOFF_NODE_ID));let c;i.decorate("nodeAgentReverseTunnels",s),function(t){let e=!1;const n=async n=>{if(!e){e=!0,s.closeAll(),c?.close();try{await t.close()}finally{process.exitCode=n?0:process.exitCode}}},o=()=>{n("SIGINT")},r=()=>{n("SIGTERM")};process.once("SIGINT",o),process.once("SIGTERM",r),t.addHook("onClose",async()=>{process.off("SIGINT",o),process.off("SIGTERM",r)})}(i),i.addHook("onClose",async()=>{c?.close(),a.release()});try{if(await i.listen({host:t.host,port:t.port}),"local-ipc"===t.connectionMode||"local-ipc"===process.env.TASK_HANDOFF_NODE_AGENT_CONNECTION_MODE){const a=t.ipcPath||process.env.TASK_HANDOFF_NODE_AGENT_IPC_PATH||be(r.dataDir);c=await async function(t,r){!function(t,o=process.platform){if("win32"!==o){e.mkdirSync(n.dirname(t),{recursive:!0,mode:448});try{e.chmodSync(n.dirname(t),448)}catch{}try{e.unlinkSync(t)}catch(t){if("ENOENT"!==t.code)throw t}}}(r);const a=o.createServer((e,n)=>{t.server.emit("request",e,n)});return a.on("upgrade",(e,n,o)=>{t.server.emit("upgrade",e,n,o)}),await new Promise((t,e)=>{a.once("error",e),a.listen(r,()=>{a.off("error",e),t()})}),a}(i,a)}s.connectConfigured()}catch(t){throw await i.close(),t}}catch(t){throw a.release(),t}}function En(t){const e=Number(t);if(!Number.isInteger(e)||e<=0||e>65535)throw new Error("Port must be a valid TCP port.");return e}function On(t){if("local-ipc"!==t&&"local-loopback"!==t)throw new Error("Connection mode must be local-ipc or local-loopback.");return t}(async function(){const o=new t.Command;o.name("task-handoff-node-agent").description("Run a TaskHandoff node agent.").version(function(){try{return JSON.parse(e.readFileSync(n.resolve(__dirname,"..","package.json"),"utf8")).version||"unknown"}catch{return"unknown"}}()).option("--host <host>","Node agent host",process.env.TASK_HANDOFF_NODE_AGENT_HOST||"127.0.0.1").option("-p, --port <port>","Node agent port",En,Number(process.env.TASK_HANDOFF_NODE_AGENT_PORT)||8091).option("--data-dir <path>","Node agent data directory").option("--token <token>","Bearer token required by the control plane").option("--connection-mode <mode>","Local control connection mode",On).option("--ipc-path <path>","Unix socket path used by local-ipc mode").option("--control-plane-tunnel-url <url>","Control plane reverse WebSocket tunnel URL").action(async t=>{await vn({host:t.host,port:t.port,dataDir:t.dataDir,token:t.token,connectionMode:t.connectionMode,ipcPath:t.ipcPath,controlPlaneTunnelUrl:t.controlPlaneTunnelUrl})}),await o.parseAsync(process.argv)})().catch(t=>{console.error(t instanceof Error?t.message:String(t)),process.exitCode=1});
|
|
1
|
+
"use strict";var t=require("commander"),e=require("node:fs"),n=require("node:path"),o=require("node:http"),r=require("node:net"),a=require("node:child_process"),i=require("fastify"),s=require("@fastify/websocket"),c=require("ws"),d=require("zod"),l=require("node:crypto"),u=require("node:os"),p=require("write-file-atomic"),m=require("semver");const g=41943040,f="ai.sessions",h=d.z.string().trim().min(1).max(80),y=d.z.enum(["running","waiting","idle","failed"]),z=d.z.enum(["thinking","tool","editing","approval","responding","unknown"]),I=d.z.object({name:d.z.string().trim().min(1).max(120),inputPreview:d.z.string().trim().max(500).optional(),startedAt:d.z.string().datetime().optional()}).strict(),b=d.z.object({toolCalls:d.z.number().int().min(0).default(0),edits:d.z.number().int().min(0).default(0),approvals:d.z.number().int().min(0).default(0)}).strict().default({toolCalls:0,edits:0,approvals:0}),A=d.z.object({send:d.z.boolean().optional(),interrupt:d.z.boolean().optional(),approval:d.z.boolean().optional()}).strict(),w=d.z.object({id:d.z.string().trim().min(1).max(120),kind:d.z.enum(["image"]),name:d.z.string().trim().min(1).max(240),mime:d.z.string().trim().min(1).max(120),size:d.z.number().int().min(0).max(20971520),data:d.z.string().min(1).max(31457280)}).strict(),S=w.omit({data:!0}),k=d.z.object({id:d.z.string().trim().min(1).max(120),kind:d.z.enum(["image"]).optional()}).strict(),_=d.z.enum(["auto","queue","steer","immediate"]),N=d.z.object({message:d.z.string().trim().min(1).max(2e4),mode:_.optional()});N.extend({attachments:d.z.array(w).max(6).default([])}).strict().superRefine((t,e)=>{t.attachments.reduce((t,e)=>t+e.size,0)>g&&e.addIssue({code:d.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),N.extend({attachments:d.z.array(k).max(6).default([])}).strict(),d.z.object({decision:d.z.enum(["allow","deny","skip"])}).strict(),d.z.object({queueIds:d.z.array(d.z.string().trim().min(1).max(120)).max(100)}).strict(),d.z.object({code:d.z.string().trim().min(1).max(160),message:d.z.string(),instanceId:d.z.string().trim().min(1).max(160).optional(),nodeId:d.z.string().trim().min(1).max(160).optional(),route:d.z.string().trim().min(1).max(4096).optional()}).strict();const v=d.z.object({id:d.z.string().trim().min(1).max(120),message:d.z.string().trim().min(1).max(2e4),attachments:d.z.array(S).max(6).default([]),status:d.z.enum(["queued","sending","failed"]).default("queued"),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime(),error:d.z.string().trim().max(4e3).optional()}).strict(),E=d.z.object({pendingCount:d.z.number().int().min(0).default(0),items:d.z.array(v).max(100).default([])}).strict().default({pendingCount:0,items:[]}),O=d.z.enum(["control","realtime","adapter-snapshot","transcript-tail","transcript-scan","process-scan","app-session"]),x=d.z.object({id:d.z.string().trim().min(1).max(240),providerTurnId:d.z.string().trim().max(240).optional(),source:O.optional(),userPrompt:d.z.string().trim().optional(),status:d.z.enum(["queued","running","waiting","completed","failed"]).default("running"),phase:z.optional(),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),revision:d.z.number().int().min(0).default(0),sourcePriority:d.z.number().int().min(0).max(100).optional(),snapshotVersion:d.z.number().int().min(0).optional(),observedAt:d.z.string().datetime().optional(),startedAt:d.z.string().datetime().optional(),updatedAt:d.z.string().datetime().optional(),completedAt:d.z.string().datetime().optional()}).strict(),T=d.z.object({id:d.z.string().trim().min(1).max(120),agent:h,appSessionId:d.z.string().trim().max(120).optional(),appId:d.z.string().trim().max(120).optional(),providerSessionId:d.z.string().trim().max(240).optional(),providerMeta:d.z.record(d.z.string(),d.z.unknown()).optional(),appBindingKeys:d.z.array(d.z.string().trim().min(1).max(240)).max(20).optional(),actions:A.optional(),activeTurnId:d.z.string().trim().max(240).optional(),conversationId:d.z.number().int().positive().optional(),title:d.z.string().trim().max(240).optional(),cwd:d.z.string().trim().max(4096).optional(),userPrompt:d.z.string().trim().optional(),turns:d.z.array(x).max(50).optional(),status:y.default("running"),phase:z.default("unknown"),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),currentTool:I.optional(),transcriptPath:d.z.string().trim().max(4096).optional(),transcriptSize:d.z.number().int().min(0).optional(),startedAt:d.z.string().datetime(),updatedAt:d.z.string().datetime(),completedAt:d.z.string().datetime().optional(),error:d.z.string().trim().max(4e3).optional(),counters:b,queue:E}).strict();d.z.object({session:T,provider:d.z.string().trim().min(1).max(80),action:d.z.enum(["send","queue","steer","interrupt","approval"]),decision:d.z.enum(["allow","deny","skip"]).optional(),turnId:d.z.string().trim().min(1).max(240).optional(),providerTurnId:d.z.string().trim().min(1).max(240).optional(),queueId:d.z.string().trim().min(1).max(120).optional()}).strict();const C=T.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}),j=d.z.object({runningCount:d.z.number().int().min(0).default(0),waitingCount:d.z.number().int().min(0).default(0),staleCount:d.z.number().int().min(0).default(0),sessions:d.z.array(C).default([]),updatedAt:d.z.string().datetime()}).strict();d.z.object({revision:d.z.number().int().min(0),lastEventAt:d.z.string().datetime(),snapshot:j}).strict(),j.pick({runningCount:!0,waitingCount:!0,staleCount:!0,updatedAt:!0}).extend({idleCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),revision:d.z.number().int().min(0).optional()});const D=d.z.enum(["provider-event","app-session-created","app-session-updated","app-session-deleted","discovery-scan","control-action","heartbeat-sync","startup"]),R=d.z.object({instanceId:d.z.string().trim().min(1).max(160),nodeId:d.z.string().trim().min(1).max(160).optional(),snapshotRevision:d.z.number().int().min(0),previousRevision:d.z.number().int().min(0).optional(),eventSeq:d.z.number().int().min(1),traceId:d.z.string().trim().min(1).max(160),generatedAt:d.z.string().datetime(),reason:D}).strict(),F=d.z.object({meta:R,snapshot:j}).strict(),P=d.z.object({meta:R,upserted:d.z.array(C).default([]),removed:d.z.array(d.z.string().trim().min(1).max(120)).default([])}).strict(),$=d.z.object({meta:R,sessionIds:d.z.array(d.z.string().trim().min(1).max(120)).default([]),expiresAt:d.z.string().datetime()}).strict();d.z.object({instanceId:d.z.string().trim().min(1).max(160),sinceRevision:d.z.number().int().min(0),latestRevision:d.z.number().int().min(0),syncRequired:d.z.boolean().default(!1),events:d.z.array(d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("ai-session.snapshot"),payload:F}).strict(),d.z.object({type:d.z.literal("ai-session.patch"),payload:P}).strict(),d.z.object({type:d.z.literal("ai-session.removed"),payload:$}).strict()])).default([])}).strict();const H=d.z.object({source:O,sourcePriority:d.z.number().int().min(0).max(100).optional(),observedAt:d.z.string().datetime().optional(),snapshotVersion:d.z.number().int().min(0).optional()}).strict(),U=H.extend({type:d.z.literal("snapshot"),agent:d.z.string().trim().min(1).max(80),appSessionId:d.z.string().trim().max(120).optional(),appId:d.z.string().trim().max(120).optional(),providerSessionId:d.z.string().trim().max(240).optional(),providerMeta:d.z.record(d.z.string(),d.z.unknown()).optional(),appBindingKeys:d.z.array(d.z.string().trim().min(1).max(240)).max(20).optional(),actions:A.optional(),conversationId:d.z.number().int().positive().optional(),title:d.z.string().trim().max(240).optional(),cwd:d.z.string().trim().max(4096).optional(),activeTurnId:d.z.string().trim().max(240).optional(),userPrompt:d.z.string().trim().optional(),turns:d.z.array(x).max(50).optional(),status:y.optional(),phase:z.optional(),summary:d.z.string().trim().max(1e3).optional(),lastMessage:d.z.string().trim().optional(),transcriptPath:d.z.string().trim().max(4096).optional(),transcriptSize:d.z.number().int().min(0).optional(),replaceActivity:d.z.boolean().optional()}).strict(),M=H.extend({type:d.z.literal("event"),sessionId:d.z.string().trim().min(1).max(120),kind:d.z.enum(["lifecycle","send-ack","turn-started","user-message","assistant-message","approval-requested","turn-completed"]),activeTurnId:d.z.string().trim().max(240).optional(),providerTurnId:d.z.string().trim().max(240).optional(),userPrompt:d.z.string().trim().optional(),text:d.z.string().optional(),status:y.optional(),phase:z.optional(),summary:d.z.string().trim().max(1e3).optional(),error:d.z.string().trim().max(4e3).optional(),counters:d.z.object({toolCalls:d.z.number().int().min(0).optional(),edits:d.z.number().int().min(0).optional(),approvals:d.z.number().int().min(0).optional()}).strict().optional()}).strict();d.z.discriminatedUnion("type",[U,M]);const L=d.z.union([d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("interval").default("interval"),intervalMs:d.z.number().int().min(1e3)}).strict(),d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("daily"),timeOfDay:d.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:d.z.string().trim().min(1).max(120)}).strict(),d.z.object({type:d.z.literal("schedule"),scheduleKind:d.z.literal("weekly"),weekdays:d.z.array(d.z.number().int().min(0).max(6)).min(1).max(7),timeOfDay:d.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),timezone:d.z.string().trim().min(1).max(120)}).strict()]),q=d.z.union([L,d.z.object({type:d.z.literal("file-change"),roots:d.z.array(d.z.string().trim().min(1).max(4096)).min(1).max(50),globs:d.z.array(d.z.string().trim().min(1).max(500)).min(1).max(100),ignore:d.z.array(d.z.string().trim().min(1).max(500)).max(100).optional(),debounceMs:d.z.number().int().min(100).max(6e4).default(1500)}).strict(),d.z.object({type:d.z.literal("ai-session"),conversationId:d.z.number().int().positive().optional(),agent:d.z.string().trim().min(1).max(80).optional(),statuses:d.z.array(d.z.enum(["running","waiting","idle","failed"])).max(20).optional(),phases:d.z.array(d.z.enum(["thinking","tool","editing","approval","responding","unknown"])).max(20).optional()}).strict()]),K=d.z.object({promptTemplate:d.z.string().trim().min(1).max(2e4)}).strict(),G=d.z.object({cooldownMs:d.z.number().int().min(0).max(864e5).optional(),maxConcurrentRuns:d.z.number().int().min(1).max(20).default(1),whenBusy:d.z.enum(["skip","queue"]).default("skip")}).strict(),B=d.z.object({configHash:d.z.string().trim().min(8).max(80),name:d.z.string().trim().min(1).max(160),description:d.z.string().trim().max(1e3).optional(),source:q,action:K,policy:G.default({maxConcurrentRuns:1,whenBusy:"skip"}),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime()}).strict(),V=d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("conversation"),conversationId:d.z.number().int().positive()}).strict(),d.z.object({type:d.z.literal("ai-session"),aiSessionId:d.z.string().trim().min(1).max(160)}).strict()]),W=d.z.object({configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),origin:d.z.enum(["control-plane","controlled-instance"]).default("controlled-instance"),enabled:d.z.boolean().default(!0),target:V,localName:d.z.string().trim().min(1).max(160).optional(),createdAt:d.z.string().datetime(),updatedAt:d.z.string().datetime()}).strict(),J=d.z.object({configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),status:d.z.enum(["idle","running","disabled","error"]).default("idle"),lastTriggeredAt:d.z.string().datetime().optional(),lastCompletedAt:d.z.string().datetime().optional(),lastSkippedAt:d.z.string().datetime().optional(),lastError:d.z.string().trim().max(4e3).optional(),runCount:d.z.number().int().min(0).default(0),skippedCount:d.z.number().int().min(0).default(0)}).strict(),Y=d.z.object({id:d.z.string().trim().min(1).max(160),configHash:d.z.string().trim().min(8).max(80),deploymentId:d.z.string().trim().min(1).max(240).optional(),instanceId:d.z.string().trim().min(1).max(160),eventType:d.z.enum(["manual","schedule","file-change","ai-session"]),status:d.z.enum(["started","completed","failed","skipped"]),target:V,promptPreview:d.z.string().trim().max(500),eventSummary:d.z.string().trim().max(1e3).optional(),error:d.z.string().trim().max(4e3).optional(),startedAt:d.z.string().datetime(),completedAt:d.z.string().datetime().optional()}).strict();d.z.object({schemaVersion:d.z.literal(1).default(1),configs:d.z.array(B).default([]),deployments:d.z.array(W).default([]),runtime:d.z.array(J).default([]),recentRuns:d.z.array(Y).default([])}).strict();const Q="2026-06-23",Z=d.z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/),X=d.z.string().datetime(),tt=d.z.record(d.z.string(),d.z.string()).default({}),et=d.z.record(d.z.string(),d.z.string()).default({}),nt=d.z.object({codexModelId:Z.optional(),claudeModelId:Z.optional()}).strict().default({}),ot=d.z.object({component:d.z.enum(["control-plane","node-agent","controlled-instance"]),packageName:d.z.string().trim().max(160).optional(),packageVersion:d.z.string().trim().max(80).optional(),protocolVersion:d.z.string().trim().max(80).optional(),buildId:d.z.string().trim().max(160).optional(),builtAt:d.z.string().trim().max(120).optional(),gitCommit:d.z.string().trim().max(120).optional(),imageRef:d.z.string().trim().max(512).optional(),imageDigest:d.z.string().trim().max(240).optional()}).strict(),rt=d.z.enum(["stable","beta","alpha"]),at=d.z.discriminatedUnion("component",[d.z.object({component:d.z.literal("node-agent")}).strict(),d.z.object({component:d.z.literal("controlled-instance"),instanceId:Z}).strict()]),it=d.z.object({target:at,channel:rt.default("stable")}).strict(),st=d.z.object({target:at,source:d.z.enum(["npm","docker-registry"]),channel:rt,currentVersion:d.z.string().trim().max(240).optional(),availableVersion:d.z.string().trim().min(1).max(240),artifactRef:d.z.string().trim().min(1).max(512).optional(),updateAvailable:d.z.boolean(),supported:d.z.boolean().default(!0),reason:d.z.string().trim().max(2048).optional(),checkedAt:X}).strict(),ct=d.z.object({id:Z,nodeId:Z,target:at,source:d.z.enum(["npm","docker-registry"]),channel:rt,fromVersion:d.z.string().trim().max(240).optional(),toVersion:d.z.string().trim().min(1).max(240),artifactRef:d.z.string().trim().min(1).max(512).optional(),status:d.z.enum(["queued","updating","restarting","succeeded","failed"]),error:d.z.string().trim().max(4096).optional(),startedAt:X.optional(),completedAt:X.optional(),createdAt:X,updatedAt:X}).strict(),dt=it,lt=d.z.object({type:d.z.enum(["branch","tag","commit"]),name:d.z.string().trim().min(1).max(240).optional(),commit:d.z.string().trim().max(80).optional()}).strict(),ut=d.z.object({type:d.z.enum(["none","ssh-key","https-token"]).default("none"),secretId:d.z.string().trim().min(1).max(120).optional()}).strict(),pt=d.z.object({depth:d.z.number().int().positive().max(1e5).optional(),submodules:d.z.boolean().default(!1),lfs:d.z.boolean().default(!1),subdirectory:d.z.string().trim().max(240).default("")}).strict();d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),url:d.z.string().trim().min(1).max(2048),provider:d.z.string().trim().max(80).optional(),ref:lt.default({type:"branch",name:"main"}),auth:ut.default({type:"none"}),clone:pt.default({submodules:!1,lfs:!1,subdirectory:""}),defaultImageId:Z.optional(),modelSelection:nt,labels:tt,createdAt:X,updatedAt:X}).strict();const mt=d.z.object({id:Z,nodeId:Z,name:d.z.string().trim().min(1).max(160),path:d.z.string().trim().min(1).max(4096),defaultImageId:Z.optional(),modelSelection:nt,labels:tt,createdAt:X,updatedAt:X}).strict(),gt=d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("git-repository"),repositoryId:Z.optional(),url:d.z.string().trim().min(1).max(2048),provider:d.z.string().trim().max(80).optional(),ref:lt.default({type:"branch",name:"main"}),auth:ut.default({type:"none"}),clone:pt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),d.z.object({type:d.z.literal("git-template"),url:d.z.string().trim().min(1).max(2048),templateId:d.z.string().trim().min(1).max(120).optional(),ref:lt.default({type:"branch",name:"main"}),auth:ut.default({type:"none"}),clone:pt.default({submodules:!1,lfs:!1,subdirectory:""})}).strict(),d.z.object({type:d.z.literal("local-folder"),localFolderId:Z.optional(),path:d.z.string().trim().min(1).max(4096),ownerNodeId:Z.optional()}).strict()]),ft=d.z.object({mode:d.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]),path:d.z.string().trim().min(1).max(4096).default("/workspace"),volumeName:d.z.string().trim().max(240).optional(),readOnly:d.z.boolean().default(!1)}).strict(),ht=d.z.enum(["codex","claude"]),yt=d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),source:gt,defaultImageId:Z.optional(),defaultNodeId:Z.optional(),defaultRuntimeId:Z.optional(),modelSelection:nt,workspacePolicy:ft,labels:tt,createdAt:X,updatedAt:X}).strict();d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),endpoint:d.z.string().trim().min(1).max(2048),key:d.z.string().trim().min(1).max(4096),model:d.z.string().trim().min(1).max(240),app:ht,enabled:d.z.boolean().default(!0),order:d.z.number().int().min(0).max(1e6).default(0),labels:tt,createdAt:X,updatedAt:X}).strict();const zt=d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),image:d.z.string().trim().min(1).max(512),registry:d.z.string().trim().min(1).max(160).default("local"),capabilities:d.z.array(d.z.string().trim().min(1).max(80)).default([]),optionalApps:d.z.array(d.z.string().trim().min(1).max(120)).default([]),defaultEnv:et,labels:tt,createdAt:X,updatedAt:X}).strict(),It=d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),connectionMode:d.z.enum(["local-ipc","local-loopback","direct-http","reverse-wss"]).default("direct-http"),auth:d.z.object({mode:d.z.enum(["local-static-key","paired-hmac"]).default("local-static-key"),keyId:Z.optional(),secret:d.z.string().trim().max(4096).optional(),pairedAt:X.optional(),pairing:d.z.object({status:d.z.enum(["pending","paired","expired"]).default("pending"),joinToken:d.z.string().trim().max(4096).optional(),expiresAt:X.optional()}).strict().optional()}).strict().default({mode:"local-static-key"}),endpoint:d.z.string().trim().max(2048).optional(),controlEndpoint:d.z.string().trim().max(2048).optional(),containerEndpoint:d.z.string().trim().max(2048).optional(),publicWebBase:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),health:d.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:tt,lastSeenAt:X.optional(),createdAt:X,updatedAt:X}).strict(),bt=d.z.object({id:Z,nodeId:Z,name:d.z.string().trim().min(1).max(160),type:d.z.enum(["docker","kubernetes","local"]),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:tt,createdAt:X,updatedAt:X}).strict();d.z.object({ok:d.z.boolean().optional(),role:d.z.string().optional(),nodeId:Z.optional()}).passthrough(),d.z.object({nodeId:Z,joinToken:d.z.string().trim().min(1),expiresAt:X}).passthrough(),d.z.object({id:Z,keyId:Z,url:d.z.string().trim().min(1).max(2048).optional(),name:d.z.string().trim().min(1).max(160).optional(),pairedAt:X,updatedAt:X.optional(),active:d.z.boolean().optional(),current:d.z.boolean().optional()}).passthrough(),d.z.object({remote:d.z.object({id:Z,url:d.z.string().trim().min(1).max(2048).optional(),keyId:Z,pairedAt:X,active:d.z.boolean()}).passthrough(),tunnel:d.z.object({status:d.z.enum(["disabled","saved","connecting","connected","failed"]),error:d.z.string().trim().max(2048).optional()}).passthrough()}).passthrough(),d.z.object({deleted:d.z.boolean().optional()}).passthrough(),d.z.object({repository:d.z.string().trim().optional(),tag:d.z.string().trim().optional(),id:d.z.string().trim(),createdSince:d.z.string().trim().optional(),size:d.z.string().trim().optional(),reference:d.z.string().trim().min(1)}).passthrough();const At=d.z.lazy(()=>d.z.object({name:d.z.string().trim().min(1),path:d.z.string().trim().min(1).max(4096),children:d.z.array(At)}).passthrough());d.z.object({status:d.z.number().int().min(100).max(599).optional(),headers:d.z.record(d.z.string(),d.z.string()).optional(),bodyBase64:d.z.string().optional()}).passthrough();const wt=d.z.object({strategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).default("node-proxy"),web:d.z.string().trim().max(2048).optional(),api:d.z.string().trim().max(2048).optional(),vnc:d.z.string().trim().max(2048).optional(),tty:d.z.string().trim().max(2048).optional(),logs:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict(),St=d.z.object({strategy:d.z.enum(["control-plane-proxy","direct-port","node-proxy","kubernetes-ingress","kubernetes-port-forward"]).default("control-plane-proxy"),web:d.z.string().trim().max(2048).optional(),api:d.z.string().trim().max(2048).optional(),ws:d.z.string().trim().max(2048).optional(),status:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown")}).strict().default({strategy:"control-plane-proxy",status:"unknown"}),kt=d.z.object({mode:d.z.enum(["local-bind","git-clone","empty-volume","persistent-volume"]).optional(),status:d.z.enum(["unknown","pending","ready","failed"]).default("unknown"),path:d.z.string().trim().max(4096).optional(),resolvedCommit:d.z.string().trim().max(120).optional(),error:d.z.string().trim().max(2048).optional()}).passthrough(),_t=d.z.object({id:Z,name:d.z.string().trim().min(1).max(160),projectId:Z.optional(),source:gt,sourceSnapshot:d.z.record(d.z.string(),d.z.unknown()).default({}),nodeId:Z,runtimeId:Z,imageId:Z.optional(),imageSnapshot:zt.optional(),status:d.z.enum(["created","provisioning","starting","registering","registered","running","stopping","stopped","failed","unhealthy"]).default("created"),health:d.z.enum(["unknown","ok","degraded","failed"]).default("unknown"),connectionStatus:d.z.enum(["unknown","online","offline","endpoint-unreachable"]).default("unknown"),agentStatus:d.z.enum(["unknown","online","offline"]).default("unknown"),targetStatus:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),uiAccessStatus:d.z.enum(["unknown","reachable","endpoint-unreachable"]).default("unknown"),controlMode:d.z.enum(["standalone","controlled"]).default("controlled"),protocolVersion:d.z.string().trim().max(80).optional(),instanceVersion:d.z.string().trim().max(80).optional(),build:ot.optional(),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),config:d.z.object({autoImportAgentConfigs:d.z.boolean().default(!0)}).strict().default({autoImportAgentConfigs:!0}),workspace:kt.default({status:"unknown"}),target:wt.default({strategy:"direct-port",status:"unknown"}),access:St,receiver:d.z.object({status:d.z.string().trim().max(80).default("unknown"),pendingCount:d.z.number().int().min(0).default(0)}).strict().default({status:"unknown",pendingCount:0}),apps:d.z.object({runningCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),updatedAt:X.optional(),revision:d.z.number().int().min(0).optional()}).strict().default({runningCount:0,problemCount:0}),aiSessions:j.default(()=>({runningCount:0,waitingCount:0,staleCount:0,sessions:[],updatedAt:(new Date).toISOString()})),triggers:d.z.object({enabledCount:d.z.number().int().min(0).default(0),runningCount:d.z.number().int().min(0).default(0),errorCount:d.z.number().int().min(0).default(0),configs:d.z.array(d.z.object({configHash:d.z.string().trim().min(1),config:B,deployments:d.z.array(W).default([]),runtime:d.z.array(J).default([])}).strict()).default([]),recentRuns:d.z.array(Y).default([])}).strict().optional(),runtime:d.z.object({kind:d.z.string().trim().max(80).optional(),containerName:d.z.string().trim().max(240).optional(),containerId:d.z.string().trim().max(240).optional(),workspacePath:d.z.string().trim().max(4096).optional(),pid:d.z.number().int().positive().optional(),port:d.z.number().int().positive().max(65535).optional(),labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict().default({labels:{}}),registrationToken:d.z.string().trim().min(1).max(240).optional(),lastHeartbeatAt:X.optional(),createdAt:X,updatedAt:X}).strict();function Nt(t){return _t.safeParse(vt(t))}function vt(t){if(!t||"object"!=typeof t||Array.isArray(t))return t;const e=t,n=new Set(Object.keys(_t.shape)),o={};for(const[t,r]of Object.entries(e))n.has(t)&&(o[t]=r);return o.apps=Et(e.apps,["runningCount","problemCount","updatedAt","revision"]),o.config=Et(e.config,["autoImportAgentConfigs"]),o.receiver=Et(e.receiver,["status","pendingCount"]),o.runtime=Et(e.runtime,["kind","containerName","containerId","workspacePath","pid","port","labels"]),o.target=Et(e.target??e.endpoints,["strategy","web","api","status"]),o.access=Et(e.access,["strategy","web","api","ws","status"]),o}function Et(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return t;const n=t;return Object.fromEntries(e.filter(t=>Object.prototype.hasOwnProperty.call(n,t)).map(t=>[t,n[t]]))}const Ot=d.z.object({instanceId:Z.optional(),name:d.z.string().trim().min(1).max(160),projectId:Z.optional(),source:gt.optional(),nodeId:Z.optional(),runtimeId:Z.optional(),imageId:Z.optional(),instanceVersion:d.z.string().trim().max(80).optional(),protocolVersion:d.z.string().trim().max(80),build:ot.optional(),controlMode:d.z.enum(["standalone","controlled"]).default("controlled"),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),target:wt.default({strategy:"direct-port",status:"unknown"}),workspace:kt.default({status:"unknown"}),registrationToken:d.z.string().trim().max(240).optional()}).strict(),xt=d.z.object({status:_t.shape.status.optional(),health:_t.shape.health.optional(),protocolVersion:d.z.string().trim().max(80),build:ot.optional(),capabilities:_t.shape.capabilities.optional(),receiver:_t.shape.receiver.optional(),apps:_t.shape.apps.optional(),aiSessions:_t.shape.aiSessions.optional(),triggers:_t.shape.triggers.optional(),workspace:kt.optional(),target:wt.partial().optional()}).strict(),Tt=d.z.enum(["web","telegram","wechat","dingding"]);function Ct(t){return t.readyState===(t.OPEN??1)}function jt(t,e,n){t.close(function(t){if("number"==typeof t&&Number.isInteger(t))return t>=3e3&&t<=4999||t>=1e3&&t<=1014&&1004!==t&&1005!==t&&1006!==t?t:void 0}(e),function(t){const e=Buffer.isBuffer(t)?t.toString("utf8"):"string"==typeof t?t:"";if(Buffer.byteLength(e,"utf8")<=123)return e;let n="";for(const t of e){const e=`${n}${t}`;if(Buffer.byteLength(e,"utf8")>123)break;n=e}return n}(n)||void 0)}function Dt(t,e){t.send(e.data,{binary:e.isBinary})}function Rt(t,e){if(Ct(t))for(const n of e.splice(0))Dt(t,n)}function Ft(t,e){return new Promise((n,o)=>{const r=a.spawn(t,e,{stdio:["ignore","pipe","pipe"]}),i=[],s=[];r.stdout.on("data",t=>i.push(Buffer.from(t))),r.stderr.on("data",t=>s.push(Buffer.from(t))),r.on("error",o),r.on("close",e=>{const r={stdout:Buffer.concat(i).toString("utf8").trim(),stderr:Buffer.concat(s).toString("utf8").trim()};if(0===e)return void n(r);const a=new Error(r.stderr||`${t} exited with code ${e}`);Object.assign(a,{statusCode:502,code:"RUNTIME_EXECUTOR_FAILED",details:r}),o(a)})})}d.z.object({id:Z,channel:Tt,name:d.z.string().trim().min(1).max(160),enabled:d.z.boolean().default(!1),token:d.z.string().trim().max(4096).optional(),defaultChatId:d.z.string().trim().max(240).optional(),allowedUserIds:d.z.array(d.z.string().trim().min(1).max(240)).default([]),pollIntervalMs:d.z.number().int().positive().max(6e4).default(3e3),settings:d.z.record(d.z.string(),d.z.unknown()).default({}),createdAt:X,updatedAt:X}).strict(),d.z.object({id:Z,channel:Tt,bridgeId:Z.optional(),chatSessionId:d.z.string().trim().min(1).max(240),userId:d.z.string().trim().max(240).optional(),activeProjectId:Z.optional(),activeInstanceId:Z.optional(),activeAiSessionId:d.z.string().trim().min(1).max(120).optional(),lastUsedAt:X,createdAt:X,updatedAt:X}).strict(),d.z.object({source:d.z.object({type:d.z.literal("chat-gateway").default("chat-gateway"),channel:Tt,bridgeId:Z.optional(),chatSessionId:d.z.string().trim().min(1).max(240),userId:d.z.string().trim().max(240).optional()}).strict(),message:d.z.object({text:d.z.string().trim().max(2e4).default(""),attachments:d.z.array(w).max(6).default([])}).strict().superRefine((t,e)=>{t.attachments.reduce((t,e)=>t+e.size,0)>g&&e.addIssue({code:d.z.ZodIssueCode.custom,path:["attachments"],message:"Images must be 41943040 bytes or less in total."})}),target:d.z.object({instanceId:Z,aiSessionId:d.z.string().trim().min(1).max(120)}).strict().optional()}).strict(),d.z.object({id:Z,instanceId:Z,projectId:Z.optional(),aiSessionId:d.z.string().trim().min(1).max(120),providerSessionId:d.z.string().trim().max(240).optional(),result:d.z.string().default(""),timeoutMs:d.z.number().positive().optional(),source:d.z.string().trim().max(120).optional(),kind:d.z.enum(["task","approval"]).default("task"),status:d.z.enum(["pending","unreachable"]).default("pending"),error:d.z.string().trim().max(2048).optional(),lastSeenAt:X}).strict();class Pt{runCommand;publishHost;constructor(t=Ft,e={}){this.runCommand=t,this.publishHost=e.publishHost||"127.0.0.1"}async start(t){Ut(t);const e=t.instance.runtime.containerName||Ht(t.instance.id);if(await this.runCommand("docker",["start",e]).catch(()=>{}))return this.runningResult(t,e,t.instance.runtime.containerId);await this.ensureImageAvailable(t.image);const n=await this.runCommand("docker",function(t,e,n={}){const o=n.publishHost||"127.0.0.1",r=t.node?.id||"node_unset",a=t.runtime?.id||"runtime_local_docker",i=["run","-d","--name",e,"--label",`task-handoff.instance-id=${t.instance.id}`,"--label",`task-handoff.project-id=${t.project.id}`,"--label",`task-handoff.node-id=${r}`,"--label",`task-handoff.runtime-id=${a}`,"--label",`task-handoff.image-id=${t.image.id}`,"--shm-size","1gb","--security-opt","seccomp=unconfined","--network","bridge","--add-host","host.docker.internal:host-gateway","--tmpfs","/tmp:rw,mode=1777","-v",`${e}-data:/data`,"-v",`${e}-agent-home:/home/agent`,"-p",`${o}::8080`,"-e","TASK_HANDOFF_CONTROL_MODE=controlled","-e",`TASK_HANDOFF_NODE_AGENT_URL=${t.nodeAgentUrl||t.node?.endpoint||""}`,"-e",`TASK_HANDOFF_INSTANCE_ID=${t.instance.id}`,"-e",`TASK_HANDOFF_INSTANCE_NAME=${t.instance.name}`,"-e",`TASK_HANDOFF_REGISTRATION_TOKEN=${t.instance.registrationToken||""}`,"-e",`TASK_HANDOFF_PROJECT_ID=${t.project.id}`,"-e",`TASK_HANDOFF_NODE_ID=${r}`,"-e",`TASK_HANDOFF_RUNTIME_ID=${a}`,"-e",`TASK_HANDOFF_IMAGE_ID=${t.image.id}`,"-e","TASK_HANDOFF_CHAT_BRIDGES=none","-e",`TASK_HANDOFF_WORKSPACE=${t.project.workspacePolicy.path||"/workspace"}`,"-e",`TASK_HANDOFF_WORKSPACE_MODE=${t.project.workspacePolicy.mode}`];"local-folder"===t.project.source.type?i.push("-v",`${t.project.source.path}:${t.project.workspacePolicy.path||"/workspace"}:${t.project.workspacePolicy.readOnly?"ro":"rw"}`):(i.push("-e",`TASK_HANDOFF_PROJECT_SOURCE=${JSON.stringify(t.project.source)}`),i.push("-e",`TASK_HANDOFF_WORKSPACE_POLICY=${JSON.stringify(t.project.workspacePolicy)}`),i.push("-e",`TASK_HANDOFF_GIT_URL=${t.project.source.url}`),t.project.source.ref?.commit?i.push("-e",`TASK_HANDOFF_GIT_COMMIT=${t.project.source.ref.commit}`):t.project.source.ref?.name&&i.push("-e",`TASK_HANDOFF_GIT_REF=${t.project.source.ref.name}`),t.project.source.clone?.depth&&i.push("-e",`TASK_HANDOFF_GIT_DEPTH=${t.project.source.clone.depth}`),i.push("-e","TASK_HANDOFF_GIT_SUBMODULES="+(t.project.source.clone?.submodules?"true":"false")),i.push("-e","TASK_HANDOFF_GIT_LFS="+(t.project.source.clone?.lfs?"true":"false")),t.project.source.clone?.subdirectory&&i.push("-e",`TASK_HANDOFF_WORKSPACE_SUBDIRECTORY=${t.project.source.clone.subdirectory}`));for(const[e,n]of Object.entries(t.image.defaultEnv))i.push("-e",`${e}=${n}`);for(const[e,n]of Object.entries(t.modelEnv||{}))i.push("-e",`${e}=${n}`);return i.push(t.image.image),i}(t,e,{publishHost:this.publishHost}));return this.runningResult(t,e,n.stdout||void 0)}async runningResult(t,e,n){const o=await this.containerPort(e,"8080/tcp"),r=o?`http://127.0.0.1:${o}`:void 0,a=t.node?.id||"node_unset",i=t.runtime?.id||"runtime_local_docker";return{status:"registering",health:"unknown",connectionStatus:r?"online":"unknown",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",target:{strategy:"direct-port",status:r?"reachable":"unknown",...r?{web:r,api:`${r}/api`}:{}},workspace:{mode:t.project.workspacePolicy.mode,status:"pending",path:t.project.workspacePolicy.path},runtime:{...t.instance.runtime,kind:"docker",containerName:e,...n?{containerId:n}:{},labels:{...t.instance.runtime.labels,"task-handoff.instance-id":t.instance.id,"task-handoff.project-id":t.project.id,"task-handoff.node-id":a,"task-handoff.runtime-id":i,"task-handoff.image-id":t.image.id}}}}async stop(t){return await this.runCommand("docker",["stop",t.instance.runtime.containerName||Ht(t.instance.id)]).catch(()=>({stdout:"",stderr:""})),{status:"stopped",health:"unknown",connectionStatus:"offline",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async restart(t){Ut(t);const e=t.instance.runtime.containerName||Ht(t.instance.id);return await this.runCommand("docker",["restart",e]),this.runningResult(t,e,t.instance.runtime.containerId)}async delete(t){return await this.runCommand("docker",["rm","-f",t.instance.runtime.containerName||Ht(t.instance.id)]).catch(()=>({stdout:"",stderr:""})),{status:"stopped",health:"unknown",connectionStatus:"offline",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async containerPort(t,e){const n=(await this.runCommand("docker",["port",t,e]).catch(()=>({stdout:"",stderr:""}))).stdout.split(/\r?\n/).find(Boolean),o=n?.match(/:(\d+)$/);return o?.[1]}async ensureImageAvailable(t){if(!await this.runCommand("docker",["image","inspect",t.image]).then(()=>!0,()=>!1)){if("local"===t.registry){const e=new Error(`Local Docker image ${t.image} was not found. Build it or add an existing local image before starting an instance.`);throw Object.assign(e,{statusCode:400,code:"LOCAL_IMAGE_NOT_FOUND"}),e}await this.runCommand("docker",["pull",t.image])}}}async function $t(t=Ft){return(await t("docker",["images","--format","{{json .}}"])).stdout.split(/\r?\n/).map(t=>t.trim()).filter(Boolean).map(t=>{const e=JSON.parse(t),n=e.Repository||"<none>",o=e.Tag||"<none>",r=e.ID||e.IDShort||"",a="<none>"!==n&&"<none>"!==o?`${n}:${o}`:r;return{repository:n,tag:o,id:r,createdSince:e.CreatedSince,size:e.Size,reference:a}}).filter(t=>Boolean(t.reference))}function Ht(t){return`task-handoff-${t.replace(/[^a-zA-Z0-9_.-]/g,"-")}`}function Ut(t){if("local-folder"===t.project.source.type&&t.project.source.ownerNodeId&&t.project.source.ownerNodeId!==t.node.id){const t=new Error("Local folder projects can only start on the node that owns the folder.");throw Object.assign(t,{statusCode:400,code:"LOCAL_FOLDER_REQUIRES_OWNER_NODE"}),t}if(!t.image.image.trim()){const t=new Error("Image profile must reference a registered image.");throw Object.assign(t,{statusCode:400,code:"IMAGE_PROFILE_INVALID"}),t}}const Mt=d.z.enum(["created","starting","running","stopping","stopped","exited","failed","closed","terminated","unknown"]),Lt=d.z.object({type:d.z.enum(["app-session","provider-session","adapter-key"]),id:d.z.string().trim().min(1).max(240),agent:d.z.string().trim().min(1).max(80).optional(),adapter:d.z.string().trim().min(1).max(80).optional(),key:d.z.string().trim().min(1).max(240).optional()}).strict(),qt=d.z.record(d.z.string(),d.z.unknown()).and(d.z.object({id:d.z.string().trim().min(1).max(160),appId:d.z.string().trim().min(1).max(160).optional(),title:d.z.string().trim().max(240).optional(),kind:d.z.string().trim().max(80).optional(),status:Mt.default("unknown"),bindings:d.z.array(Lt).max(40).default([]),workspace:d.z.object({cwd:d.z.string().trim().max(4096).optional()}).strict().optional(),createdAt:d.z.string().datetime().optional(),updatedAt:d.z.string().datetime().optional()})),Kt=d.z.object({runningCount:d.z.number().int().min(0).default(0),problemCount:d.z.number().int().min(0).default(0),sessions:d.z.array(qt).default([]),updatedAt:d.z.string().datetime()}).strict();d.z.object({revision:d.z.number().int().min(0),lastEventAt:d.z.string().datetime(),snapshot:Kt}).strict(),Kt.pick({runningCount:!0,problemCount:!0,updatedAt:!0}).extend({revision:d.z.number().int().min(0).optional()});const Gt=d.z.enum(["app-session-created","app-session-updated","app-session-deleted","app-session-recovered","control-action","heartbeat-sync","startup"]),Bt=d.z.object({instanceId:d.z.string().trim().min(1).max(160),nodeId:d.z.string().trim().min(1).max(160).optional(),snapshotRevision:d.z.number().int().min(0),previousRevision:d.z.number().int().min(0).optional(),eventSeq:d.z.number().int().min(1),traceId:d.z.string().trim().min(1).max(160),generatedAt:d.z.string().datetime(),reason:Gt}).strict(),Vt=d.z.object({meta:Bt,snapshot:Kt}).strict(),Wt=d.z.object({meta:Bt,session:qt}).strict(),Jt=d.z.object({meta:Bt,sessionId:d.z.string().trim().min(1).max(160),tombstone:qt.optional(),expiresAt:d.z.string().datetime().optional()}).strict();function Yt(t){return t.startsWith("ai-session.")||t.startsWith("ai.session.")?"ai.sessions":t.startsWith("app-session.")?"app.sessions":t.startsWith("receiver.")?"receiver":t.startsWith("conversation.")?"conversations":t.startsWith("trigger.")?"triggers":t.startsWith("channel.")?"channels":t.startsWith("instance.")?"instances":t.startsWith("node.")?"nodes":t.startsWith("project.")?"projects":t.startsWith("model.")?"models":t.startsWith("image.")?"images":"system"}d.z.object({instanceId:d.z.string().trim().min(1).max(160),sinceRevision:d.z.number().int().min(0),latestRevision:d.z.number().int().min(0),syncRequired:d.z.boolean().default(!1),events:d.z.array(d.z.discriminatedUnion("type",[d.z.object({type:d.z.literal("app-session.snapshot"),payload:Vt}).strict(),d.z.object({type:d.z.literal("app-session.patch"),payload:Wt}).strict(),d.z.object({type:d.z.literal("app-session.removed"),payload:Jt}).strict()])).default([])}).strict();class Qt{sockets=new Map;outputs=new Set;state;token;logger;lastAiSessionEventByInstance=new Map;lastAppSessionEventByInstance=new Map;timer;constructor(t,e,n={}){this.state=t,this.token=e,this.logger=n.logger}start(){this.sync(),this.timer=setInterval(()=>this.sync(),5e3)}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0);for(const t of this.sockets.values())t.close();this.sockets.clear(),this.outputs.clear()}addOutput(t){return this.outputs.add(t),t.on("close",()=>this.outputs.delete(t)),t.on("error",()=>this.outputs.delete(t)),this.syncNow(),()=>{this.outputs.delete(t)}}syncNow(){this.sync()}sync(){if(!this.outputs.size){for(const t of this.sockets.values())t.close();return void this.sockets.clear()}const t=new Set;for(const e of this.state.listInstances()){const n=Xt(e);n&&(t.add(e.id),this.sockets.has(e.id)||this.connect(e.id,n))}for(const[e,n]of this.sockets)t.has(e)||(n.close(),this.sockets.delete(e))}connect(t,e){const n=this.token?{authorization:`Bearer ${this.token}`}:void 0,o=new c(e,{headers:n});this.sockets.set(t,o),o.on("open",()=>{o.readyState===c.OPEN&&(o.send(JSON.stringify({v:1,type:"subscribe",topics:[f,"app.sessions","receiver","instances"]})),this.logger?.info?.({instanceId:t,url:e},"ai-session.event.forward.connect"),this.logger?.info?.({instanceId:t,url:e},"app-session.event.forward.connect"))}),o.on("message",e=>{const n=function(t,e){try{const n=JSON.parse(String(t));if(!n||"object"!=typeof n||Array.isArray(n))return;const o=n,r="string"==typeof o.type?o.type:"";if(!r||"events.connected"===r)return;const a=o.scope&&"object"==typeof o.scope&&!Array.isArray(o.scope)?o.scope:{};return{v:1,id:"string"==typeof o.id?o.id:`evt_${Date.now().toString(36)}`,seq:Number(o.seq)||0,type:r,topic:"string"==typeof o.topic?o.topic:Yt(r),createdAt:"string"==typeof o.createdAt?o.createdAt:(new Date).toISOString(),payload:"payload"in o?o.payload:{},scope:{...a,instanceId:e}}}catch{return}}(e,t);if(!n)return;this.recordAiSessionEvent(t,n),this.recordAppSessionEvent(t,n);const o=JSON.stringify({type:"node-agent.event.forwarded",event:n});for(const t of this.outputs)t.readyState===c.OPEN&&t.send(o)}),o.on("close",()=>{this.sockets.get(t)===o&&this.sockets.delete(t)}),o.on("error",()=>{this.logger?.warn?.({instanceId:t,url:e},"ai-session.event.forward.error"),this.logger?.warn?.({instanceId:t,url:e},"app-session.event.forward.error"),o.close()})}recordAiSessionEvent(t,e){if(e.topic!==f)return;const n=Zt(e.payload),o=this.lastAiSessionEventByInstance.get(t);void 0!==o?.eventSeq&&void 0!==n.eventSeq&&n.eventSeq>o.eventSeq+1&&this.logger?.warn?.({instanceId:t,traceId:n.traceId,previousEventSeq:o.eventSeq,eventSeq:n.eventSeq,previousSnapshotRevision:o.snapshotRevision,snapshotRevision:n.snapshotRevision},"ai-session.event.forward.gap"),this.lastAiSessionEventByInstance.set(t,{eventSeq:n.eventSeq??o?.eventSeq,snapshotRevision:n.snapshotRevision??o?.snapshotRevision}),this.logger?.info?.({instanceId:t,traceId:n.traceId,eventType:e.type,eventSeq:n.eventSeq,snapshotRevision:n.snapshotRevision},"ai-session.event.forward.message")}recordAppSessionEvent(t,e){if("app.sessions"!==e.topic)return;const n=Zt(e.payload),o=this.lastAppSessionEventByInstance.get(t);void 0!==o?.eventSeq&&void 0!==n.eventSeq&&n.eventSeq>o.eventSeq+1&&this.logger?.warn?.({instanceId:t,traceId:n.traceId,previousEventSeq:o.eventSeq,eventSeq:n.eventSeq,previousSnapshotRevision:o.snapshotRevision,snapshotRevision:n.snapshotRevision},"app-session.event.forward.gap"),this.lastAppSessionEventByInstance.set(t,{eventSeq:n.eventSeq??o?.eventSeq,snapshotRevision:n.snapshotRevision??o?.snapshotRevision}),this.logger?.info?.({instanceId:t,traceId:n.traceId,eventType:e.type,eventSeq:n.eventSeq,snapshotRevision:n.snapshotRevision},"app-session.event.forward.message")}}function Zt(t){const e=t&&"object"==typeof t&&!Array.isArray(t)?t:{},n=e.meta&&"object"==typeof e.meta&&!Array.isArray(e.meta)?e.meta:{};return{eventSeq:"number"==typeof n.eventSeq?n.eventSeq:void 0,snapshotRevision:"number"==typeof n.snapshotRevision?n.snapshotRevision:void 0,traceId:"string"==typeof n.traceId?n.traceId:void 0}}function Xt(t){const e=t.target?.api||t.target?.web;if(e)try{const t=new URL("/api/events",e);return t.protocol="https:"===t.protocol?"wss:":"ws:",t.toString()}catch{return}}function te(t,o){let r;try{r=e.statSync(t)}catch{return}if(!r.isDirectory())return;const a=n.resolve(t),i=n.basename(a)||a;let s=[];try{s=o>0?e.readdirSync(a,{withFileTypes:!0}):[]}catch{s=[]}const c=o>0?s.filter(t=>t.isDirectory()&&!t.name.startsWith(".")).sort((t,e)=>t.name.localeCompare(e.name)).slice(0,80).flatMap(t=>{const e=te(n.join(a,t.name),o-1);return e?[e]:[]}):[];return{name:i,path:a,children:c}}function ee(t){return(t.path?[t.path]:function(){const t=[u.homedir(),"/workspace"].filter(t=>t&&e.existsSync(t)&&e.statSync(t).isDirectory());return[...new Map(t.map(t=>[n.resolve(t),t])).values()]}()).flatMap(e=>{const n=te(e,t.depth);return n?[n]:[]})}const ne=d.z.object({path:d.z.string().trim().min(1).max(2048),method:d.z.string().trim().min(1).max(20).default("GET"),headers:d.z.record(d.z.string(),d.z.string()).default({}),body:d.z.string().optional(),bodyBase64:d.z.string().optional()}).strict(),oe=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160),path:d.z.string().trim().min(1).max(4096),defaultImageId:d.z.string().trim().min(1).max(120).optional(),modelSelection:nt,labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),re=d.z.object({path:d.z.string().trim().min(1).max(4096).optional(),depth:d.z.coerce.number().int().min(0).max(1).default(0)}).strict(),ae=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160).optional(),runtimeId:d.z.string().trim().min(1).max(120),imageId:d.z.string().trim().min(1).max(120).optional(),projectId:d.z.string().trim().min(1).max(120).optional(),image:zt.optional(),source:gt,sourceSnapshot:d.z.record(d.z.string(),d.z.unknown()).default({}),config:_t.shape.config.optional(),modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),ie=d.z.object({name:d.z.string().trim().min(1).max(160).optional()}).strict(),se=d.z.object({id:d.z.string().trim().min(1).max(120).optional(),name:d.z.string().trim().min(1).max(160),type:d.z.enum(["docker","kubernetes","local"]),status:d.z.enum(["unknown","online","offline","degraded"]).default("unknown"),accessStrategy:d.z.enum(["node-proxy","direct-port","kubernetes-ingress","kubernetes-port-forward"]).optional(),capabilities:d.z.record(d.z.string(),d.z.unknown()).default({}),labels:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),ce=se.omit({id:!0}).partial().strict();function de(t=function(){return process.env.TASK_HANDOFF_NODE_AGENT_DATA_DIR||n.join(u.homedir(),".config","task-handoff","node-agent")}()){const e=n.resolve(t);return{dataDir:e,identityPath:n.join(e,"identity.json"),localFoldersDir:n.join(e,"local-folders"),nodeRuntimesDir:n.join(e,"node-runtimes"),controlledInstancesDir:n.join(e,"controlled-instances"),updatesDir:n.join(e,"updates"),logsDir:n.join(e,"logs")}}function le(t){e.mkdirSync(t,{recursive:!0})}function ue(t,e){console.warn(JSON.stringify({message:t,...e}))}function pe(t){return t&&"object"==typeof t&&"issues"in t?{issues:t.issues}:{error:t instanceof Error?t.message:String(t)}}function me(t,n){const o=n.logger||ue;let r;try{r=JSON.parse(e.readFileSync(t,"utf8"))}catch(e){return void o("stored JSON could not be read",{filePath:t,...pe(e)})}if(!n.schema)return r;const a=n.sanitize?n.sanitize(r):r,i=n.schema.safeParse(a);if(i.success)return i.data;const s="strip"in n.schema&&"function"==typeof n.schema.strip?n.schema.strip().safeParse(a):void 0;if(s?.success)return o("unknown stored fields were ignored",{filePath:t,issues:i.error.issues}),s.data;o("stored record failed validation and was isolated",{filePath:t,issues:i.error.issues})}function ge(t){return`${t}_${l.randomUUID().replace(/-/g,"").slice(0,20)}`}function fe(){return l.randomBytes(24).toString("base64url")}class he{directory;options;constructor(t,e={}){this.directory=t,this.options=e}init(){le(this.directory)}filePath(t){return n.join(this.directory,`${t}.json`)}list(){return this.init(),e.readdirSync(this.directory).filter(t=>t.endsWith(".json")).sort().flatMap(t=>{const e=me(n.join(this.directory,t),this.options);return e?[e]:[]})}get(t){const n=this.filePath(t);return e.existsSync(n)?me(n,this.options):void 0}put(t){const e=this.options.sanitize?this.options.sanitize(t):t,o=this.options.schema?this.options.schema.parse(e):e;return function(t,e){le(n.dirname(t)),p.sync(t,`${JSON.stringify(e,null,2)}\n`,{encoding:"utf8"})}(this.filePath(o.id),o),o}patch(t,e){const n=this.get(t);if(!n)return;const o={...n,...e,id:n.id,createdAt:n.createdAt,updatedAt:(new Date).toISOString()};return this.put(o)}delete(t){const n=this.filePath(t);return!!e.existsSync(n)&&(e.unlinkSync(n),!0)}}class ye extends Error{code;lockPath;owner;constructor(t,e,n="process"){super(`${"control-plane"===n?"Control plane":"node-agent"===n?"Node agent":"Process"} is already running${e?.pid?` pid ${e.pid}`:""}. Lock: ${t}`),this.name="ProcessSingletonError",this.code="control-plane"===n?"CONTROL_PLANE_ALREADY_RUNNING":"node-agent"===n?"NODE_AGENT_ALREADY_RUNNING":"PROCESS_ALREADY_RUNNING",this.lockPath=t,this.owner=e}}function ze(t){return n.join(t,"owner.json")}function Ie(t){try{return JSON.parse(e.readFileSync(ze(t),"utf8"))}catch{return}}function be(t){if(!Number.isInteger(t)||Number(t)<=0)return!1;try{return process.kill(Number(t),0),!0}catch(t){return"EPERM"===t.code}}function Ae(t,n){e.writeFileSync(ze(t),`${JSON.stringify(n,null,2)}\n`,"utf8")}function we(t,e=process.platform){const o=n.resolve(t),r=l.createHash("sha256").update(o).digest("hex").slice(0,16);return"win32"===e?`\\\\.\\pipe\\task-handoff-node-agent-${r}`:n.join("/tmp",`task-handoff-node-agent-${process.getuid?.()??"user"}`,`${r}.sock`)}const Se="TASK_HANDOFF_NODE_AGENT_V1",ke=6e4;function _e(t=""){return l.createHash("sha256").update(t).digest("hex")}function Ne(t,e){return l.createHmac("sha256",t).update(function(t){return[Se,t.keyId,t.method.toUpperCase(),t.pathWithQuery,t.timestamp,t.nonce,t.bodySha256].join("\n")}(e)).digest("hex")}function ve(t){const e=t.timestamp||(new Date).toISOString(),n=t.nonce||l.randomBytes(18).toString("base64url"),o=_e(t.body||""),r=Ne(t.secret,{keyId:t.keyId,method:t.method,pathWithQuery:t.pathWithQuery,timestamp:e,nonce:n,bodySha256:o});return{"x-taskhandoff-node-id":t.nodeId,"x-taskhandoff-key-id":t.keyId,"x-taskhandoff-timestamp":e,"x-taskhandoff-nonce":n,"x-taskhandoff-body-sha256":o,"x-taskhandoff-signature":r}}const Ee=new Set(["DELETE","GET","HEAD","PATCH","POST","PUT","OPTIONS"]);function Oe(){return(new Date).toISOString()}function xe(t,e){if(!t)return!0;const n=m.valid(t),o=m.valid(e);return n&&o?m.gt(o,n):t!==e}async function Te(t,e,n){const o=De(n),r=await t(Ce(),["view",`${e}@${o}`,"version","--json"]).catch(t=>{if(!function(t){if(!t||"object"!=typeof t)return!1;const e=t,n=[e.message,e.details?.stdout,e.details?.stderr].filter(t=>"string"==typeof t).join("\n");return/\bE404\b/i.test(n)}(t))throw t});if(!r)return;const a=JSON.parse(r.stdout);if("string"!=typeof a)throw new Error(`npm did not resolve ${e}@${o} to one version.`);return a}function Ce(){return process.env.TASK_HANDOFF_NPM_COMMAND||"npm"}function je(t){return st.parse({target:t.target,source:"npm",channel:t.channel,currentVersion:t.currentVersion,availableVersion:t.currentVersion||"unavailable",updateAvailable:!1,reason:`No ${t.channel} release is currently published.`,checkedAt:Oe()})}function De(t){return"stable"===t?"latest":t}function Re(t){if(Array.isArray(t))return t.map(Re).find(Boolean);if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.digest&&e.digest.startsWith("sha256:")?e.digest:"string"==typeof e.Digest&&e.Digest.startsWith("sha256:")?e.Digest:Object.values(e).map(Re).find(Boolean)}class Fe{records;constructor(t){this.records=new he(t.updatesDir,{schema:ct})}init(){this.records.init()}list(){return this.records.list().sort((t,e)=>e.createdAt.localeCompare(t.createdAt))}create(t,e){const n=Oe();return this.records.put(ct.parse({id:ge("update"),nodeId:t,target:e.target,source:e.source,channel:e.channel,fromVersion:e.currentVersion,toVersion:e.availableVersion,artifactRef:e.artifactRef,status:"queued",createdAt:n,updatedAt:n}))}patch(t,e){const n=this.records.get(t);if(!n)throw Object.assign(new Error(`Update job ${t} was not found.`),{statusCode:404,code:"UPDATE_JOB_NOT_FOUND"});return this.records.put(ct.parse({...n,...e,id:t,createdAt:n.createdAt,updatedAt:Oe()}))}run(t,e){(async()=>{this.patch(t.id,{status:"updating",startedAt:Oe(),error:void 0});try{await e(t),this.patch(t.id,{status:"succeeded",completedAt:Oe()})}catch(e){this.patch(t.id,{status:"failed",error:e instanceof Error?e.message:String(e),completedAt:Oe()})}})()}}const Pe=new Set(["content-encoding","content-length","transfer-encoding"]),$e=["codex","claude"],He=d.z.object({controlPlaneName:d.z.string().trim().min(1).max(160).optional(),controlPlaneUrl:d.z.string().trim().max(2048).optional(),expiresInMs:d.z.number().int().positive().max(36e5).optional()}).strict(),Ue=d.z.object({joinToken:d.z.string().trim().min(1).max(4096),controlPlaneId:d.z.string().trim().min(1).max(160).optional(),controlPlaneName:d.z.string().trim().min(1).max(160).optional(),controlPlaneUrl:d.z.string().trim().max(2048).optional()}).strict(),Me=d.z.object({modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict().default({modelEnv:{}}),Le=dt.extend({modelEnv:d.z.record(d.z.string(),d.z.string()).default({})}).strict(),qe=d.z.object({controlPlaneUrl:d.z.string().trim().url().max(2048),joinToken:d.z.string().trim().min(1).max(4096),controlPlaneName:d.z.string().trim().min(1).max(160).optional(),activate:d.z.boolean().optional()}).strict();function Ke(t){const e=process.env[t]?.trim();return e||void 0}function Ge(){try{const t=n.resolve(__dirname,"..","package.json");return JSON.parse(e.readFileSync(t,"utf8")).version||"unknown"}catch{return"unknown"}}function Be(t){return t.bodyBase64?Buffer.from(t.bodyBase64,"base64"):t.body}function Ve(t){const e=t.authorization;return"string"==typeof e&&e.startsWith("Bearer ")?e.slice(7).trim():void 0}function We(t){if("string"!=typeof t)return!1;const e=t.trim().toLowerCase();return"localhost"===e||"127.0.0.1"===e||"::1"===e||"::ffff:127.0.0.1"===e}function Je(){return(new Date).toISOString()}function Ye(t){try{return function(t){if(!t||"object"!=typeof t)return;const e=t,n="string"==typeof e.nodeId&&e.nodeId.trim()?e.nodeId.trim():void 0;return n?{nodeId:n,createdAt:"string"==typeof e.createdAt?e.createdAt:Je(),updatedAt:"string"==typeof e.updatedAt?e.updatedAt:Je(),pairingInvites:Array.isArray(e.pairingInvites)?e.pairingInvites.filter(t=>{if(!t||"object"!=typeof t)return!1;const e=t;return"string"==typeof e.tokenHash&&"string"==typeof e.expiresAt&&"string"==typeof e.createdAt}):[],remoteControlPlanes:Array.isArray(e.remoteControlPlanes)?e.remoteControlPlanes.filter(t=>{if(!t||"object"!=typeof t)return!1;const e=t;return"string"==typeof e.id&&"string"==typeof e.keyId&&"string"==typeof e.secret&&"string"==typeof e.pairedAt&&"string"==typeof e.updatedAt}).map(t=>({...t,active:"boolean"!=typeof t.active||t.active})):[]}:void 0}(JSON.parse(e.readFileSync(t.identityPath,"utf8")))}catch{return}}function Qe(t,o){e.mkdirSync(n.dirname(t.identityPath),{recursive:!0}),e.writeFileSync(t.identityPath,`${JSON.stringify({...o,updatedAt:Je()},null,2)}\n`,{mode:384})}function Ze(t,e){const n=Ye(t);Qe(t,{...n||{createdAt:Je()},nodeId:e,updatedAt:Je(),pairingInvites:n?.pairingInvites||[],remoteControlPlanes:n?.remoteControlPlanes||[]})}function Xe(t,e){if(e?.trim())return Ze(t,e.trim()),e.trim();const n=Ye(t);if(n?.nodeId)return n.nodeId;const o=ge("node");return Ze(t,o),o}function tn(t){const e=Date.now();return{...t,pairingInvites:(t.pairingInvites||[]).filter(t=>Date.parse(t.expiresAt)>e)}}function en(t,e=""){return(Ye(t)?.remoteControlPlanes||[]).map(({secret:t,...n})=>({...n,current:Boolean(e&&n.keyId===e)})).sort((t,e)=>Number(e.current)-Number(t.current)||e.updatedAt.localeCompare(t.updatedAt))}function nn(t,e,n=""){if(n&&e===n){const t=new Error("Cannot delete the key currently used by this control-plane connection.");throw Object.assign(t,{statusCode:409,code:"NODE_AGENT_REMOTE_KEY_IN_USE"}),t}const o=Ye(t);if(!o)return!1;const r=(o.remoteControlPlanes||[]).filter(t=>t.keyId!==e);return r.length!==(o.remoteControlPlanes||[]).length&&(Qe(t,{...o,remoteControlPlanes:r}),!0)}function on(t,e,n){return[...(Ye(t)?.remoteControlPlanes||[]).map(t=>({keyId:t.keyId,secret:t.secret})),...e&&n?[{keyId:n,secret:e}]:[]]}function rn(t){return"local-folder"===t.type?ft.parse({mode:"local-bind",path:"/workspace",readOnly:!1}):ft.parse({mode:"git-clone",path:"/workspace",readOnly:!1})}function an(t){const e=gt.parse(t.source),n=t.projectId||("local-folder"===e.type?e.localFolderId:void 0)||("git-repository"===e.type?e.repositoryId:void 0)||("git-template"===e.type?e.templateId:void 0)||`project_${t.id}`;return yt.parse({id:n,name:"string"==typeof t.sourceSnapshot.name?t.sourceSnapshot.name:t.name,source:e,defaultImageId:t.imageId,defaultNodeId:t.nodeId,defaultRuntimeId:t.runtimeId,modelSelection:t.sourceSnapshot.modelSelection&&"object"==typeof t.sourceSnapshot.modelSelection&&!Array.isArray(t.sourceSnapshot.modelSelection)?t.sourceSnapshot.modelSelection:{},workspacePolicy:rn(e),labels:{},createdAt:t.createdAt,updatedAt:t.updatedAt})}async function sn(t,e){if(!e.target.web)return"unknown";const n=new AbortController,o=setTimeout(()=>n.abort(),2e3);try{return(await t(`${ln(e)}/api/health`,{signal:n.signal})).ok?"reachable":"endpoint-unreachable"}catch{return"endpoint-unreachable"}finally{clearTimeout(o)}}async function cn(t,e,n,o){const r=new AbortController,a=setTimeout(()=>r.abort(),o);try{return await t(e,{...n,signal:r.signal})}finally{clearTimeout(a)}}async function dn(t,e,n,o){if(!e.config.autoImportAgentConfigs)return void o.diagnostic({instanceId:e.id,action:n},"node instance config auto-import skipped");if("reachable"!==e.targetStatus)return;const r=ln(e),a=Number(process.env.TASK_HANDOFF_CONFIG_AUTO_IMPORT_TIMEOUT_MS||5e3);for(const i of $e)try{const s=await cn(t,`${r}/api/config-sync/import/${encodeURIComponent(i)}`,{method:"POST"},Number.isFinite(a)&&a>0?a:5e3);if(!s.ok){o.warn({instanceId:e.id,action:n,preset:i,statusCode:s.status},"node instance config auto-import failed");continue}o.diagnostic({instanceId:e.id,action:n,preset:i},"node instance config auto-import completed")}catch(t){o.warn({instanceId:e.id,action:n,preset:i,error:t instanceof Error?t.message:String(t)},"node instance config auto-import failed")}}function ln(t){const e=t.target.web;if(!e){const e=new Error(`Instance ${t.id} does not have a web endpoint.`);throw Object.assign(e,{statusCode:409,code:"NODE_INSTANCE_WEB_ENDPOINT_MISSING"}),e}if("direct-port"!==t.target.strategy)return e.replace(/\/$/,"");try{const t=new URL(e);return t.port?(t.hostname=process.env.TASK_HANDOFF_NODE_AGENT_PROXY_HOST||"127.0.0.1",t.toString().replace(/\/$/,"")):e.replace(/\/$/,"")}catch{return e.replace(/\/$/,"")}}function un(t,e){const n=new Error(e);throw Object.assign(n,{statusCode:403,code:t}),n}function pn(t,e){if(t===Q)return;const n=new Error(`${e} protocol version ${t||"missing"} is not compatible with ${Q}.`);throw Object.assign(n,{statusCode:409,code:"PROTOCOL_VERSION_MISMATCH"}),n}function mn(t={}){return{requiresImage:!1,supportsControlledInstanceApi:!0,supportsContainerLifecycle:!1,supportsAppSessions:!0,supportsHostSessions:!0,artifactKind:"none",isolation:"none",...t}}function gn(t){return new Promise(e=>{const n=r.createServer();n.once("error",()=>e(!1)),n.listen({host:"127.0.0.1",port:t},()=>{n.close(()=>e(!0))})})}function fn(t,e=3e3){return null!==t.exitCode||null!==t.signalCode?Promise.resolve():new Promise(n=>{const o=setTimeout(()=>{null===t.exitCode&&null===t.signalCode&&t.kill("SIGKILL"),n()},e);o.unref(),t.once("exit",()=>{clearTimeout(o),n()})})}const hn=new Set(["provisioning","starting","registering","registered","running"]);async function yn(t,e){try{const n=await t(e,["--version"]);return{available:!0,command:e,version:(n.stdout||n.stderr).split(/\r?\n/)[0]?.trim()||void 0}}catch{return{available:!1}}}class zn{executor;constructor(t){this.executor=t}start(t){return this.executor.start(t)}stop(t){return this.executor.stop(t)}restart(t){return this.executor.restart(t)}delete(t){return this.executor.delete(t)}}class In{runCommand;paths;nodeAgentUrl;processByInstanceId=new Map;constructor(t,e,n){this.runCommand=t,this.paths=e,this.nodeAgentUrl=n}async start(t){const o=function(t){if("local-folder"!==t.source.type){const t=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(t,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),t}return n.resolve(t.source.path)}(t.instance),r=t.instance.runtime.port||await async function(){const t=Number(process.env.TASK_HANDOFF_LOCAL_INSTANCE_PORT_START||19e3),e=Number.isInteger(t)&&t>0?t:19e3;for(let t=e;t<e+1e3&&t<=65535;t+=1)if(await gn(t))return t;const n=new Error(`No free localhost port found in range ${e}-${Math.min(e+999,65535)}.`);throw Object.assign(n,{statusCode:503,code:"LOCAL_INSTANCE_PORT_UNAVAILABLE"}),n}(),i=n.join(this.paths.dataDir,"local-instances",t.instance.id),s=n.join(i,"logs");e.mkdirSync(s,{recursive:!0});const[c,...d]=function(){const t=process.env.TASK_HANDOFF_LOCAL_CONTROLLED_COMMAND?.trim();if(t)return t.split(/\s+/);const o=n.resolve(process.cwd(),"bin","task-handoff.js");return e.existsSync(o)?[process.execPath,o,"web"]:["task-handoff-controlled-instance","web"]}(),l=[...d,"--host","127.0.0.1","--port",String(r)],u=e.openSync(n.join(s,"controlled-instance.out.log"),"a"),p=e.openSync(n.join(s,"controlled-instance.err.log"),"a"),m=a.spawn(c,l,{cwd:o,detached:!1,stdio:["ignore",u,p],env:{...process.env,TASK_HANDOFF_CONTROL_MODE:"controlled",TASK_HANDOFF_NODE_AGENT_URL:this.nodeAgentUrl,TASK_HANDOFF_INSTANCE_ID:t.instance.id,TASK_HANDOFF_INSTANCE_NAME:t.instance.name,TASK_HANDOFF_REGISTRATION_TOKEN:t.instance.registrationToken||"",TASK_HANDOFF_PROJECT_ID:t.project.id,TASK_HANDOFF_NODE_ID:t.node.id,TASK_HANDOFF_RUNTIME_ID:t.runtime.id,TASK_HANDOFF_WORKSPACE:o,TASK_HANDOFF_WORKSPACE_MODE:"local-bind",TASK_HANDOFF_DATA_DIR:i,TASK_HANDOFF_LOG_DIR:s,TASK_HANDOFF_APP_SESSION_PERSIST:"1",TASK_HANDOFF_CODEX_APP_SERVER:process.env.TASK_HANDOFF_CODEX_APP_SERVER||"1",TASK_HANDOFF_WEB_PORT:String(r),TASK_HANDOFF_WEB_HOST:"127.0.0.1"}});e.closeSync(u),e.closeSync(p);try{await function(t){return new Promise((e,n)=>{const o=()=>{t.off("error",r),e()},r=e=>{t.off("spawn",o),n(e)};t.once("spawn",o),t.once("error",r)})}(m)}catch(t){const r=t instanceof Error?t.message:String(t);try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] spawn failed command=${c} cwd=${o} error=${r}\n`)}catch{}throw Object.assign(new Error(`Controlled instance process could not start with command ${c} in ${o}: ${r}`),{statusCode:500,code:"LOCAL_INSTANCE_PROCESS_SPAWN_FAILED"})}this.processByInstanceId.set(t.instance.id,m),m.on("error",t=>{try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] process error pid=${m.pid??""} error=${t.message}\n`)}catch{}}),m.once("exit",(o,r)=>{this.processByInstanceId.delete(t.instance.id);try{e.appendFileSync(n.join(s,"controlled-instance.lifecycle.log"),`[${(new Date).toISOString()}] exited pid=${m.pid??""} code=${o??""} signal=${r??""}\n`)}catch{}});const g=`http://127.0.0.1:${r}`;return await async function(t,e=3e3){const n=Date.now()+e;for(;Date.now()<n;){try{if((await fetch(`${t}/api/health`)).ok)return!0}catch{}await new Promise(t=>setTimeout(t,100))}return!1}(g),{status:"registering",health:"unknown",connectionStatus:"online",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",target:{strategy:"direct-port",status:"unknown",web:g,api:`${g}/api`},workspace:{mode:"local-bind",status:"pending",path:o},runtime:{kind:"local",workspacePath:o,pid:m.pid,port:r,labels:{...t.instance.runtime.labels,"task-handoff.runtime-kind":"local"}}}}async stop(t){return await async function(t,e){const n=e.get(t.id);if(n&&!n.killed)return n.kill("SIGTERM"),e.delete(t.id),void await fn(n);if(t.runtime.pid)try{process.kill(t.runtime.pid,"SIGTERM")}catch{}}(t.instance,this.processByInstanceId),{status:"stopped",health:"unknown",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",target:{...t.instance.target,status:"unknown"},runtime:t.instance.runtime}}async restart(t){return this.start(t)}async delete(t){return this.stop(t)}async check(t){const[e,n]=await Promise.all([yn(this.runCommand,"codex"),yn(this.runCommand,"claude")]);return{status:"online",capabilities:mn({...t.capabilities,apps:{terminal:!0,codex:e,claude:n}})}}async stopAll(){const t=Array.from(this.processByInstanceId.values());for(const t of this.processByInstanceId.values())t.killed||t.kill("SIGTERM");this.processByInstanceId.clear(),await Promise.all(t.map(t=>fn(t)))}}class bn{docker;local;constructor(t,e){this.docker=t,this.local=e}forRuntime(t){if("docker"===t.type)return this.docker;if("local"===t.type)return this.local;const e=new Error(`Runtime type ${t.type} is not supported by this node agent.`);throw Object.assign(e,{statusCode:400,code:"NODE_RUNTIME_TYPE_UNSUPPORTED"}),e}async stopAll(){this.local instanceof In&&await this.local.stopAll()}}class An{nodeId;paths;localFolders;nodeRuntimes;controlledInstances;updateJobs;node;containerUrl;constructor(t,e,n,o){this.paths=t,this.nodeId=e,this.localFolders=new he(t.localFoldersDir,{schema:mt}),this.nodeRuntimes=new he(t.nodeRuntimesDir,{schema:bt}),this.controlledInstances=new he(t.controlledInstancesDir,{schema:_t,sanitize:vt}),this.updateJobs=new Fe(t),this.containerUrl=o;const r=Je();this.node=It.parse({id:e,name:e,connectionMode:"direct-http",endpoint:n,controlEndpoint:n,containerEndpoint:o,publicWebBase:n?n.replace(/:\d+$/,""):void 0,status:"online",health:"ok",capabilities:{},labels:{},createdAt:r,updatedAt:r})}init(){if(this.localFolders.init(),this.nodeRuntimes.init(),this.controlledInstances.init(),this.updateJobs.init(),!this.nodeRuntimes.get("runtime_local_docker")){const t=Je();this.nodeRuntimes.put(bt.parse({id:"runtime_local_docker",nodeId:this.nodeId,name:"Local Docker",type:"docker",status:"unknown",accessStrategy:"direct-port",capabilities:{},labels:{},createdAt:t,updatedAt:t}))}for(const t of this.nodeRuntimes.list())t.nodeId!==this.nodeId&&this.nodeRuntimes.put(bt.parse({...t,nodeId:this.nodeId,updatedAt:Je()}));for(const t of this.localFolders.list())t.nodeId!==this.nodeId&&this.localFolders.put(mt.parse({...t,nodeId:this.nodeId,updatedAt:Je()}))}createLocalFolder(t){const e=Je(),n=mt.parse({...t,id:t.id||ge("folder"),nodeId:this.nodeId,modelSelection:t.modelSelection||{},labels:t.labels||{},createdAt:e,updatedAt:e});return this.localFolders.put(n)}createRuntime(t){const e=Je(),n=bt.parse({...t,id:t.id||ge("runtime"),nodeId:this.nodeId,accessStrategy:t.accessStrategy||(o=t.type,"docker"===o?"direct-port":"node-proxy"),capabilities:"local"===t.type?mn(t.capabilities):t.capabilities||{},labels:t.labels||{},createdAt:e,updatedAt:e});var o;return this.nodeRuntimes.put(n)}updateRuntime(t,e){const n=this.requireRuntime(t),o=bt.parse({...n,...e,id:n.id,nodeId:this.nodeId,accessStrategy:e.accessStrategy||n.accessStrategy,capabilities:"local"===(e.type||n.type)?mn(e.capabilities||n.capabilities):e.capabilities||n.capabilities,createdAt:n.createdAt,updatedAt:Je()});return this.nodeRuntimes.put(o)}deleteRuntime(t){this.requireRuntime(t);const e=this.listInstances().filter(e=>e.runtimeId===t);if(e.length){const n=new Error(`Runtime ${t} is used by ${e.length} instance${1===e.length?"":"s"}.`);throw Object.assign(n,{statusCode:409,code:"NODE_RUNTIME_IN_USE"}),n}return this.nodeRuntimes.delete(t)}checkRuntime(t,e){const n=this.requireRuntime(t);return e.check?e.check(n).then(t=>{const e=bt.parse({...n,...t,id:n.id,nodeId:this.nodeId,createdAt:n.createdAt,updatedAt:Je()});return this.nodeRuntimes.put(e)}):this.nodeRuntimes.put(bt.parse({...n,status:"unknown",updatedAt:Je()}))}requireRuntime(t){const e=this.nodeRuntimes.get(t);if(!e){const e=new Error(`Node runtime ${t} was not found.`);throw Object.assign(e,{statusCode:404,code:"NODE_RUNTIME_NOT_FOUND"}),e}return e}requireInstance(t){const e=this.controlledInstances.get(t);if(!e){const e=new Error(`Instance ${t} was not found on node ${this.nodeId}.`);throw Object.assign(e,{statusCode:404,code:"NODE_INSTANCE_NOT_FOUND"}),e}const n=Nt(e);if(!n.success)throw function(t,e){const n=e.map(t=>`${t.path.join(".")||"body"}: ${t.message}`).join("; "),o=new Error(`Stored instance ${t} is not compatible with protocol ${Q}: ${n}`);return Object.assign(o,{statusCode:409,code:"NODE_INSTANCE_PAYLOAD_INVALID"}),o}(t,n.error.issues);return n.data}listInstances(){return this.controlledInstances.list().flatMap(t=>{const e=Nt(t);return e.success?[e.data]:[]})}createInstance(t){const e=this.requireRuntime(t.runtimeId),o=Je(),r=t.id||ge("inst"),a=gt.parse(t.source);if(function(t){return"boolean"==typeof t.capabilities.requiresImage?t.capabilities.requiresImage:"local"!==t.type}(e)&&(!t.imageId||!t.image)){const t=new Error(`Runtime ${e.name} requires an image.`);throw Object.assign(t,{statusCode:400,code:"NODE_RUNTIME_IMAGE_REQUIRED"}),t}if("local"===e.type&&"local-folder"!==a.type){const t=new Error("Localhost runtime currently supports local folder sources only.");throw Object.assign(t,{statusCode:400,code:"LOCAL_RUNTIME_REQUIRES_LOCAL_FOLDER"}),t}if("local"===e.type&&this.listInstances().some(t=>"local"===this.requireRuntime(t.runtimeId).type)){const t=new Error("A localhost instance already exists on this node.");throw Object.assign(t,{statusCode:409,code:"LOCAL_RUNTIME_INSTANCE_EXISTS"}),t}const i=t.image?zt.parse(t.image):void 0,s="local"===e.type&&"local-folder"===a.type?n.resolve(a.path):void 0,c=_t.parse({id:r,name:t.name||`instance-${o.replace(/[-:.TZ]/g,"").slice(0,14)}-${r.replace(/^inst_?/,"").slice(0,4)}`,source:a,sourceSnapshot:t.sourceSnapshot||{},projectId:t.projectId,nodeId:this.nodeId,runtimeId:e.id,imageId:i?.id,imageSnapshot:i,status:"created",health:"unknown",connectionStatus:"unknown",agentStatus:"unknown",targetStatus:"unknown",uiAccessStatus:"unknown",controlMode:"controlled",capabilities:"local"===e.type?{apps:e.capabilities.apps||{}}:{},config:t.config,workspace:"local"===e.type?{mode:"local-bind",status:"unknown",path:s}:{status:"unknown"},target:{strategy:"node-proxy",status:"unknown"},runtime:"local"===e.type?{kind:"local",workspacePath:s,labels:{"task-handoff.runtime-kind":"local"}}:{labels:{}},registrationToken:fe(),createdAt:o,updatedAt:o});return this.controlledInstances.put(c)}registerInstance(t,e,n){const o=Ot.parse(e),r=Je(),a=this.controlledInstances.get(t);if(!a){const e=new Error(`Instance ${t} was not found on node ${this.nodeId}.`);throw Object.assign(e,{statusCode:404,code:"NODE_INSTANCE_NOT_FOUND"}),e}this.validateInstanceReport(a,o,n),pn(o.protocolVersion,`Instance ${t}`);const i=_t.parse({...a,name:o.name,status:"registered",health:"ok",connectionStatus:"online",agentStatus:"online",targetStatus:"endpoint-unreachable"===o.target.status?"endpoint-unreachable":"reachable"===o.target.status?"reachable":a.targetStatus,uiAccessStatus:"endpoint-unreachable"===o.target.status?"endpoint-unreachable":a.uiAccessStatus,controlMode:o.controlMode,protocolVersion:o.protocolVersion,instanceVersion:o.instanceVersion,build:o.build,capabilities:o.capabilities,workspace:o.workspace,target:{...a.target,...o.target},registrationToken:a.registrationToken||o.registrationToken,lastHeartbeatAt:r,updatedAt:r});return this.controlledInstances.put(i)}heartbeatInstance(t,e,n){const o=this.requireInstance(t);this.validateInstanceToken(o,n);const r=xt.parse(e);pn(r.protocolVersion,`Instance ${t}`);const a=Je(),i=r.target?{...o.target,...r.target}:o.target,s={...i,status:"unknown"===i.status&&(i.web||i.api)?"reachable":i.status},c="endpoint-unreachable"===s.status?"endpoint-unreachable":"reachable"===s.status?"reachable":o.targetStatus,d=_t.parse({...o,...r,target:s,agentStatus:"online",targetStatus:c,uiAccessStatus:c,connectionStatus:"online",build:r.build||o.build,lastHeartbeatAt:a,updatedAt:a});return this.controlledInstances.put(d)}validateInstanceReport(t,e,n){this.validateInstanceToken(t,n||e.registrationToken),e.instanceId&&e.instanceId!==t.id&&un("INSTANCE_ID_MISMATCH",`Instance ${e.instanceId} cannot register as ${t.id}.`),e.nodeId&&e.nodeId!==this.nodeId&&un("INSTANCE_NODE_MISMATCH",`Instance ${t.id} belongs to node ${this.nodeId}.`),e.runtimeId&&e.runtimeId!==t.runtimeId&&un("INSTANCE_RUNTIME_MISMATCH",`Instance ${t.id} belongs to runtime ${t.runtimeId}.`),e.imageId&&e.imageId!==t.imageId&&un("INSTANCE_IMAGE_MISMATCH",`Instance ${t.id} belongs to image ${t.imageId}.`)}validateInstanceToken(t,e){t.registrationToken&&e===t.registrationToken||un("INSTANCE_REGISTRATION_TOKEN_INVALID",`Invalid registration token for instance ${t.id}.`)}context(t,e={}){const n=t.imageSnapshot||zt.parse({id:t.imageId||"img_localhost",name:t.imageId||"Localhost",image:t.imageId||"localhost",registry:"local",capabilities:[],optionalApps:[],defaultEnv:{},labels:{},createdAt:t.createdAt,updatedAt:t.updatedAt});return{project:an(t),image:n,node:this.node,runtime:this.requireRuntime(t.runtimeId),instance:t,nodeAgentUrl:this.containerUrl,modelEnv:e}}}async function wn(t,e,n,o,r,a,i="request"){const s=t.requireInstance(o);a.diagnostic({instanceId:o,action:"start",reason:i,runtimeId:s.runtimeId,imageId:s.imageId},"node instance start requested");const c=t.controlledInstances.put(_t.parse({...s,status:"provisioning",updatedAt:Je()})),d=e.forRuntime(t.requireRuntime(c.runtimeId)),l=await d.start(t.context(c,r)),u=await sn(n,_t.parse({...c,...l,target:l.target?{...c.target,...l.target}:c.target,workspace:l.workspace?{...c.workspace,error:void 0,...l.workspace}:c.workspace,runtime:l.runtime?{...c.runtime,...l.runtime}:c.runtime,updatedAt:Je()})),p=_t.parse({...c,...l,target:l.target?{...c.target,...l.target,status:u}:c.target,workspace:l.workspace?{...c.workspace,error:void 0,...l.workspace}:c.workspace,runtime:l.runtime?{...c.runtime,...l.runtime}:c.runtime,targetStatus:u,uiAccessStatus:u,connectionStatus:l.connectionStatus,updatedAt:Je()}),m=t.controlledInstances.put(p);return await dn(n,m,"start",a),a.diagnostic({instanceId:o,action:"start",reason:i,status:m.status,connectionStatus:m.connectionStatus,targetStatus:m.targetStatus,targetWeb:m.target.web,containerName:m.runtime.containerName},"node instance start completed"),m}function Sn(t,e){const n=e instanceof Error?e.message:String(e);return _t.parse({...t,status:"failed",health:"failed",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",workspace:{...t.workspace,error:n},updatedAt:Je()})}function kn(t){return _t.parse({...t,status:"stopped",health:"unknown",connectionStatus:"offline",agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",target:{...t.target,status:"unknown"},updatedAt:Je()})}async function _n(t={}){const o=t.token||process.env.TASK_HANDOFF_NODE_AGENT_TOKEN,r=String(t.port||process.env.TASK_HANDOFF_NODE_AGENT_PORT||"8091"),a=`http://127.0.0.1:${r}`,u=t.containerUrl||process.env.TASK_HANDOFF_NODE_AGENT_CONTAINER_URL||`http://host.docker.internal:${r}`,p=de(t.dataDir),m=t.remoteSecret||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_SECRET,g=t.remoteKeyId||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_KEY_ID,f=Xe(p,t.nodeId||process.env.TASK_HANDOFF_NODE_ID),h=t.connectionMode||("local-ipc"===process.env.TASK_HANDOFF_NODE_AGENT_CONNECTION_MODE?"local-ipc":"local-loopback"),y=t.ipcPath||process.env.TASK_HANDOFF_NODE_AGENT_IPC_PATH||we(p.dataDir),z="local-ipc"===h?`ipc://${encodeURIComponent(y)}`:a,I=new An(p,f,a,u);I.node.connectionMode=h,I.node.controlEndpoint=z,I.node.endpoint=z,I.init();const b=new Pt(t.dockerCommandRunner,{publishHost:"127.0.0.1"}),A=new bn(new zn(b),new In(t.dockerCommandRunner||Ft,p,a)),w=t.updateCommandRunner||t.dockerCommandRunner||Ft,S=t.fetchImpl||fetch,k=i({logger:t.logger??!0});k.decorate("nodeAgentState",I),await k.register(s);const _=new Qt(I,o,{logger:k.log});_.start(),k.decorate("nodeAgentEventForwarder",_);const N=(T=process.env.TASK_HANDOFF_DIAGNOSTIC_LOGS,["1","true","yes","on"].includes(String(T||"").trim().toLowerCase())),v=new Map,E=(t,e)=>{N&&k.log.info(t,e)},O={diagnostic:E,warn:(t,e)=>k.log.warn(t,e)},x=async t=>{if("node-agent"===t.target.component)return async function(t){const e=await Te(t.runCommand,"@task-handoff/node-agent",t.channel);return e?st.parse({target:{component:"node-agent"},source:"npm",channel:t.channel,currentVersion:t.currentVersion,availableVersion:e,updateAvailable:xe(t.currentVersion,e),checkedAt:Oe()}):je({target:{component:"node-agent"},channel:t.channel,currentVersion:t.currentVersion})}({channel:t.channel,currentVersion:Ge(),runCommand:w});const e=I.requireInstance(t.target.instanceId);return async function(t){const e={component:"controlled-instance",instanceId:t.instance.id};if("local"===t.runtime.type){const n=await Te(t.runCommand,"@task-handoff/controlled-instance",t.channel),o=t.instance.build?.packageVersion||t.instance.instanceVersion;return n?st.parse({target:e,source:"npm",channel:t.channel,currentVersion:o,availableVersion:n,updateAvailable:xe(o,n),checkedAt:Oe()}):je({target:e,channel:t.channel,currentVersion:o})}if("docker"===t.runtime.type){const n=t.instance.imageSnapshot?.image;if(!n||"local"===t.instance.imageSnapshot?.registry)return st.parse({target:e,source:"docker-registry",channel:t.channel,currentVersion:t.instance.build?.imageDigest,availableVersion:t.instance.build?.imageDigest||"unavailable",updateAvailable:!1,supported:!1,reason:"The instance does not use a remote Docker image.",checkedAt:Oe()});const o=function(t,e){const n=t.split("@",1)[0],o=n.lastIndexOf("/"),r=n.lastIndexOf(":");return`${r>o?n.slice(0,r):n}:${De(e)}`}(n,t.channel),r=await t.runCommand("docker",["manifest","inspect","--verbose",o]),a=Re(JSON.parse(r.stdout));if(!a)throw new Error(`Docker registry did not return a digest for ${o}.`);const i=t.instance.build?.imageDigest;return st.parse({target:e,source:"docker-registry",channel:t.channel,currentVersion:i,availableVersion:a,artifactRef:o,updateAvailable:i!==a,checkedAt:Oe()})}return st.parse({target:e,source:"npm",channel:t.channel,availableVersion:"unsupported",updateAvailable:!1,supported:!1,reason:`Runtime type ${t.runtime.type} does not support managed updates.`,checkedAt:Oe()})}({channel:t.channel,instance:e,runtime:I.requireRuntime(e.runtimeId),runCommand:w})};var T;return k.setErrorHandler((t,e,n)=>{const o=function(t){if(t instanceof d.z.ZodError)return{statusCode:400,code:"VALIDATION_ERROR",message:t.issues.map(t=>`${t.path.join(".")||"body"}: ${t.message}`).join("; ")};const e=t&&"object"==typeof t?t:{};return{statusCode:"number"==typeof e.statusCode?e.statusCode:500,code:"string"==typeof e.code?e.code:"NODE_AGENT_ERROR",message:t instanceof Error?t.message:String(t)}}(t);(N||o.statusCode>=500)&&(o.statusCode>=500?k.log.error.bind(k.log):k.log.warn.bind(k.log))({method:e.method,url:e.url,statusCode:o.statusCode,errorCode:o.code,error:o.message},"node agent request failed"),n.code(o.statusCode).send({error:{code:o.code,message:o.message}})}),k.addHook("preHandler",async t=>{const e=function(t){const e=function(t){const e=new Map;for(const[n,o]of Object.entries(t)){const t=Array.isArray(o)?o.join(","):"string"==typeof o?o:void 0===o?"":String(o);e.set(n.toLowerCase(),t)}return{nodeId:e.get("x-taskhandoff-node-id")||"",keyId:e.get("x-taskhandoff-key-id")||"",timestamp:e.get("x-taskhandoff-timestamp")||"",nonce:e.get("x-taskhandoff-nonce")||"",bodySha256:e.get("x-taskhandoff-body-sha256")||"",signature:e.get("x-taskhandoff-signature")||""}}(t.headers);if(!e.nodeId&&!e.signature)return!1;const n=on(p,m,g);if(!n.length)return!1;if(e.nodeId!==f)throw Object.assign(new Error("Invalid node agent HMAC node id."),{statusCode:401,code:"NODE_AGENT_HMAC_NODE_MISMATCH"});if(!e.keyId)throw Object.assign(new Error("Node agent HMAC key id is missing."),{statusCode:401,code:"NODE_AGENT_HMAC_KEY_MISSING"});const o=n.filter(t=>e.keyId===t.keyId);if(!o.length)throw Object.assign(new Error("Invalid node agent HMAC key id."),{statusCode:401,code:"NODE_AGENT_HMAC_KEY_INVALID"});const r=Date.parse(e.timestamp);if(!Number.isFinite(r)||Math.abs(Date.now()-r)>ke)throw Object.assign(new Error("Node agent HMAC timestamp is outside the allowed window."),{statusCode:401,code:"NODE_AGENT_HMAC_TIMESTAMP_INVALID"});if(!e.nonce)throw Object.assign(new Error("Node agent HMAC nonce is missing."),{statusCode:401,code:"NODE_AGENT_HMAC_NONCE_MISSING"});!function(){const t=Date.now()-ke;for(const[e,n]of v)n<=t&&v.delete(e)}();const a=`${e.nodeId}:${e.keyId}:${e.nonce}`;if(v.has(a))throw Object.assign(new Error("Node agent HMAC nonce was already used."),{statusCode:401,code:"NODE_AGENT_HMAC_NONCE_REPLAY"});const i=_e(null==(s=t.body)?"":"string"==typeof s||Buffer.isBuffer(s)?s:JSON.stringify(s));var s;if(e.bodySha256!==i)throw Object.assign(new Error("Node agent HMAC body hash mismatch."),{statusCode:401,code:"NODE_AGENT_HMAC_BODY_HASH_INVALID"});if(!o.some(n=>function(t,e){const n=Buffer.from(t,"hex"),o=Buffer.from(e,"hex");return n.length===o.length&&l.timingSafeEqual(n,o)}(e.signature,Ne(n.secret,{keyId:e.keyId,method:t.method,pathWithQuery:t.url,timestamp:e.timestamp,nonce:e.nonce,bodySha256:i}))))throw Object.assign(new Error("Invalid node agent HMAC signature."),{statusCode:401,code:"NODE_AGENT_HMAC_SIGNATURE_INVALID"});return v.set(a,Date.now()+ke),e.keyId}(t);if(e)t.nodeAgentAuthKeyId=e;else if(function(t,e){return"local-ipc"===e&&!t.ip&&!t.socket.remoteAddress&&!t.socket.remoteFamily}(t,h)){if(o&&Ve(t.headers)!==o){const t=new Error("Invalid node agent token.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_UNAUTHORIZED"}),t}}else if("/api/node-agent/pairing/complete"!==t.url.split("?")[0]&&!function(t){const e=t.split("?")[0];return/^\/api\/node-agent\/instances\/[^/]+\/(register|heartbeat)$/.test(e)}(t.url)){if(on(p,m,g).length){const t=new Error("Node agent HMAC signature is required.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_HMAC_SIGNATURE_REQUIRED"}),t}if(!function(t){return We(t.ip)||We(t.socket.remoteAddress)||!t.ip&&!t.socket.remoteAddress&&!t.socket.remoteFamily}(t)){const t=new Error("Local node agent access is only accepted from loopback or local IPC connections.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_LOCAL_TOKEN_REQUIRES_LOOPBACK"}),t}if(o&&Ve(t.headers)!==o){const t=new Error("Invalid node agent token.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_UNAUTHORIZED"}),t}}}),k.addHook("onClose",async()=>{_.stop(),await async function(t,e){await e.stopAll();for(const e of t.listInstances())"local"===t.requireRuntime(e.runtimeId).type&&t.controlledInstances.put(kn(e))}(I,A)}),k.addHook("onListen",async()=>{await(async()=>{const t=I.listInstances().filter(t=>"local"===I.requireRuntime(t.runtimeId).type&&hn.has(t.status));for(const e of t)try{await wn(I,A,S,e.id,{},O,"restore")}catch(t){I.controlledInstances.put(Sn(I.requireInstance(e.id),t)),E({instanceId:e.id,action:"restore",error:t instanceof Error?t.message:String(t)},"node local instance restore failed")}})()}),k.get("/api/node-agent/health",async()=>{return{data:{ok:!0,role:"node-agent",nodeId:f,protocolVersion:Q,build:(t="node-agent",{component:t,packageName:"@task-handoff/node-agent",packageVersion:Ge(),protocolVersion:Q,buildId:Ke("TASK_HANDOFF_BUILD_ID"),builtAt:Ke("TASK_HANDOFF_BUILT_AT"),gitCommit:Ke("TASK_HANDOFF_GIT_COMMIT"),imageRef:Ke("TASK_HANDOFF_IMAGE_REF"),imageDigest:Ke("TASK_HANDOFF_IMAGE_DIGEST")}),serverTime:(new Date).toISOString()}};var t}),k.get("/api/node-agent/updates/jobs",async()=>({data:I.updateJobs.list()})),k.post("/api/node-agent/updates/check",async t=>({data:await x(it.parse(t.body))})),k.post("/api/node-agent/updates/apply",async(t,o)=>{const r=Le.parse(t.body),a=await x(r);if(!a.supported){const t=new Error(a.reason||"The requested update is not supported.");throw Object.assign(t,{statusCode:400,code:"UPDATE_UNSUPPORTED"}),t}if(!a.updateAvailable){const t=new Error(a.reason||"No update is available for the selected channel.");throw Object.assign(t,{statusCode:409,code:"UPDATE_NOT_AVAILABLE"}),t}const i=I.updateJobs.create(f,a);if("node-agent"===r.target.component){const t=n.resolve(__dirname,"..","bin","task-handoff-node-update-worker");if(!e.existsSync(t)){I.updateJobs.patch(i.id,{status:"failed",error:`Update worker was not found: ${t}`,completedAt:Je()});const e=new Error(`Node agent update worker was not found: ${t}`);throw Object.assign(e,{statusCode:500,code:"UPDATE_WORKER_NOT_FOUND"}),e}await w("systemd-run",["--unit",`task-handoff-update-${i.id}`,"--collect","--property=Type=exec",t,"--job-file",I.updateJobs.records.filePath(i.id),"--target-version",i.toVersion,"--npm-command",Ce()])}else I.updateJobs.run(i,t=>(async(t,e)=>{if("controlled-instance"!==t.target.component)return;const n=I.requireInstance(t.target.instanceId),o=I.requireRuntime(n.runtimeId),r=A.forRuntime(o);if("local"===o.type){const e=Ce(),n=(await w(e,["prefix","--global"])).stdout.trim();await w(e,["install","--global","--prefix",n,`@task-handoff/controlled-instance@${t.toVersion}`])}else{if("docker"!==o.type)throw Object.assign(new Error(`Runtime type ${o.type} does not support managed updates.`),{code:"INSTANCE_UPDATE_UNSUPPORTED",statusCode:400});{const e=t.artifactRef;if(!e||"local"===n.imageSnapshot?.registry)throw Object.assign(new Error("The instance does not use an updateable remote Docker image."),{code:"INSTANCE_UPDATE_UNSUPPORTED",statusCode:400});await w("docker",["pull",e])}}await r.delete(I.context(n,e)),I.controlledInstances.put(kn(_t.parse({...n,..."docker"===o.type&&t.artifactRef&&n.imageSnapshot?{imageSnapshot:{...n.imageSnapshot,image:t.artifactRef,updatedAt:Je()}}:{}}))),await wn(I,A,S,n.id,e,O,"update")})(t,r.modelEnv));return o.code(202).send({data:i})}),k.post("/api/node-agent/pairing/invites",async(t,e)=>{const n=function(t,e){const n=tn(Ye(t)||{nodeId:Xe(t),createdAt:Je(),updatedAt:Je()}),o=Je(),r=new Date(Date.now()+(e.expiresInMs||6e5)).toISOString(),a=fe(),i={tokenHash:_e(a),createdAt:o,expiresAt:r,...e.controlPlaneName?{controlPlaneName:e.controlPlaneName}:{},...e.controlPlaneUrl?{controlPlaneUrl:e.controlPlaneUrl}:{}};return Qe(t,{...n,pairingInvites:[...n.pairingInvites||[],i]}),{...i,token:a}}(p,He.parse(t.body||{}));return e.code(201).send({data:{nodeId:f,joinToken:n.token,expiresAt:n.expiresAt}})}),k.post("/api/node-agent/pairing/complete",async(t,e)=>{const n=function(t,e){const n=tn(Ye(t)||{nodeId:Xe(t),createdAt:Je(),updatedAt:Je()}),o=_e(e.joinToken),r=(n.pairingInvites||[]).find(t=>t.tokenHash===o);if(!r){const t=new Error("Node agent pairing invite is invalid or expired.");throw Object.assign(t,{statusCode:401,code:"NODE_AGENT_PAIRING_INVITE_INVALID"}),t}const a=Je(),i={id:e.controlPlaneId||ge("cp"),keyId:ge("key"),...e.controlPlaneName||r.controlPlaneName?{name:e.controlPlaneName||r.controlPlaneName}:{},...e.controlPlaneUrl||r.controlPlaneUrl?{url:e.controlPlaneUrl||r.controlPlaneUrl}:{},secret:fe(),pairedAt:a,updatedAt:a,active:!0};return Qe(t,{...n,pairingInvites:(n.pairingInvites||[]).filter(t=>t.tokenHash!==o),remoteControlPlanes:[...(n.remoteControlPlanes||[]).filter(t=>t.id!==i.id),i]}),i}(p,Ue.parse(t.body));return e.code(201).send({data:{nodeId:f,keyId:n.keyId,secret:n.secret,pairedAt:n.pairedAt}})}),k.get("/api/node-agent/remotes",async t=>({data:en(p,t.nodeAgentAuthKeyId)})),k.delete("/api/node-agent/remotes/:keyId",async t=>{const e=d.z.string().trim().min(1).max(160).parse(t.params.keyId);return{data:{deleted:nn(p,e,t.nodeAgentAuthKeyId)}}}),k.post("/api/node-agent/remotes/connect",async(t,e)=>{const n=qe.parse(t.body),o=function(t){const e=new URL(t);if("http:"!==e.protocol&&"https:"!==e.protocol){const t=new Error("Control-plane URL must use http or https.");throw Object.assign(t,{statusCode:400,code:"NODE_AGENT_REMOTE_CONTROL_PLANE_URL_INVALID"}),t}return e.toString().replace(/\/$/,"")}(n.controlPlaneUrl),r=Je(),a={id:ge("cp"),keyId:ge("key"),...n.controlPlaneName?{name:n.controlPlaneName}:{},url:o,secret:fe(),pairedAt:r,updatedAt:r,active:!1!==n.activate},i=await async function(t,e,n){const o=await t(new URL("/api/node-join/complete",e).toString(),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)}),r=await o.json().catch(()=>({}));if(!o.ok){const t=new Error(r.error?.message||`Control-plane node join failed with HTTP ${o.status}.`);throw Object.assign(t,{statusCode:o.status,code:r.error?.code||"NODE_AGENT_REMOTE_JOIN_FAILED"}),t}return r.data||{}}(S,o,{joinToken:n.joinToken,nodeId:f,nodeName:I.node.name,keyId:a.keyId,secret:a.secret,pairedAt:a.pairedAt}),s=function(t,e){const n=Ye(t)||{nodeId:Xe(t),createdAt:Je(),updatedAt:Je(),pairingInvites:[],remoteControlPlanes:[]},o=e.url?.replace(/\/$/,"");return Qe(t,{...n,remoteControlPlanes:[...(n.remoteControlPlanes||[]).filter(t=>t.id!==e.id&&(!o||t.url?.replace(/\/$/,"")!==o)),e]}),e}(p,{...a,..."string"==typeof i.name&&i.name?{name:n.controlPlaneName||i.name}:{}});let c,d=!1!==s.active?"saved":"disabled";if(!1!==s.active&&k.nodeAgentReverseTunnels)try{k.nodeAgentReverseTunnels.connect({url:o,keyId:s.keyId,secret:s.secret}),d="connecting"}catch(t){d="failed",c=t instanceof Error?t.message:String(t)}return e.code(201).send({data:{remote:{id:s.id,url:s.url,keyId:s.keyId,pairedAt:s.pairedAt,active:!1!==s.active},tunnel:{status:d,...c?{error:c}:{}}}})}),k.get("/api/node-agent/runtimes",async()=>({data:I.nodeRuntimes.list()})),k.post("/api/node-agent/runtimes",async(t,e)=>e.code(201).send({data:I.createRuntime(se.parse(t.body))})),k.patch("/api/node-agent/runtimes/:id",async t=>({data:I.updateRuntime(t.params.id,ce.parse(t.body))})),k.delete("/api/node-agent/runtimes/:id",async t=>({data:{deleted:I.deleteRuntime(t.params.id)}})),k.post("/api/node-agent/runtimes/:id/check",async t=>{const e=I.requireRuntime(t.params.id);return{data:await I.checkRuntime(e.id,A.forRuntime(e))}}),k.get("/api/node-agent/local-folders",async()=>({data:I.localFolders.list()})),k.get("/api/node-agent/folders/tree",async t=>({data:ee(re.parse(t.query))})),k.post("/api/node-agent/local-folders",async(t,e)=>e.code(201).send({data:I.createLocalFolder(oe.parse(t.body))})),k.delete("/api/node-agent/local-folders/:id",async t=>({data:{deleted:I.localFolders.delete(t.params.id)}})),k.get("/api/node-agent/instances",async()=>({data:I.listInstances()})),k.post("/api/node-agent/instances",async(t,e)=>{const n=I.createInstance(ae.parse(t.body));return _.syncNow(),e.code(201).send({data:n})}),k.patch("/api/node-agent/instances/:id",async t=>{const e=t.params.id,n=I.requireInstance(e),o=ie.parse(t.body),r=_t.parse({...n,...o,updatedAt:Je()}),a=I.controlledInstances.put(r);return _.syncNow(),{data:a}}),k.post("/api/node-agent/instances/:id/register",async(t,e)=>{const n=t.params.id,o=I.registerInstance(n,Ot.parse(t.body),Ve(t.headers));return _.syncNow(),E({instanceId:n,action:"register",protocolVersion:o.protocolVersion,build:o.build,targetStatus:o.targetStatus,targetStrategy:o.target.strategy},"node instance registered"),e.code(201).send({data:o})}),k.post("/api/node-agent/instances/:id/heartbeat",async t=>{const e=t.params.id,n=I.heartbeatInstance(e,xt.parse(t.body),Ve(t.headers));return _.syncNow(),E({instanceId:e,action:"heartbeat",status:n.status,health:n.health,protocolVersion:n.protocolVersion,build:n.build,targetStatus:n.targetStatus,apps:n.apps.runningCount},"node instance heartbeat accepted"),{data:n}}),k.get("/api/node-agent/events",{websocket:!0},t=>{const e=t;e.send(JSON.stringify({type:"node-agent.events.connected",nodeId:f,serverTime:(new Date).toISOString()}));const n=_.addOutput(e);e.on("close",n),e.on("error",n)}),k.get("/api/node-agent/docker/images",async()=>({data:await $t(t.dockerCommandRunner)})),k.post("/api/node-agent/instances/:id/start",async t=>{const e=t.params.id,n=Me.parse(t.body);return{data:await wn(I,A,S,e,n.modelEnv,O)}}),k.post("/api/node-agent/instances/:id/stop",async t=>{const e=I.requireInstance(t.params.id);E({instanceId:e.id,action:"stop",runtimeId:e.runtimeId,containerName:e.runtime.containerName},"node instance stop requested");const n=A.forRuntime(I.requireRuntime(e.runtimeId)),o=await n.stop(I.context(e)),r=_t.parse({...e,...o,agentStatus:"offline",targetStatus:"unknown",uiAccessStatus:"unknown",updatedAt:Je()}),a=I.controlledInstances.put(r);return E({instanceId:e.id,action:"stop",status:a.status,connectionStatus:a.connectionStatus,containerName:a.runtime.containerName},"node instance stop completed"),{data:a}}),k.post("/api/node-agent/instances/:id/restart",async t=>{const e=t.params.id,n=I.requireInstance(e),o=Me.parse(t.body);E({instanceId:e,action:"restart",runtimeId:n.runtimeId,imageId:n.imageId,containerName:n.runtime.containerName},"node instance restart requested");const r=A.forRuntime(I.requireRuntime(n.runtimeId)),a=await r.restart(I.context(n,o.modelEnv)),i=_t.parse({...n,...a,target:a.target?{...n.target,...a.target}:n.target,updatedAt:Je()}),s=await sn(S,i),c=_t.parse({...n,...a,target:a.target?{...n.target,...a.target,status:s}:n.target,targetStatus:s,uiAccessStatus:s,connectionStatus:a.connectionStatus,updatedAt:Je()}),d=I.controlledInstances.put(c);return await dn(S,d,"restart",O),E({instanceId:e,action:"restart",status:d.status,connectionStatus:d.connectionStatus,targetStatus:d.targetStatus,targetWeb:d.target.web,containerName:d.runtime.containerName},"node instance restart completed"),{data:d}}),k.post("/api/node-agent/instances/:id/delete",async t=>{const e=t.params.id,n=I.requireInstance(e);E({instanceId:e,action:"delete",runtimeId:n.runtimeId,containerName:n.runtime.containerName},"node instance delete requested");const o=A.forRuntime(I.requireRuntime(n.runtimeId));return await o.delete(I.context(n)),E({instanceId:e,action:"delete"},"node instance delete completed"),{data:{deleted:I.controlledInstances.delete(e)}}}),k.post("/api/node-agent/instances/:id/proxy",async(t,e)=>{const n=t.params.id,o=ne.parse(t.body),r=ln(I.requireInstance(n)),a=o.path.startsWith("/")?o.path:`/${o.path}`;E({instanceId:n,action:"proxy",method:o.method,path:a,instanceBase:r},"node instance proxy requested");const i=await S(`${r}${a}`,{method:o.method,headers:{...o.headers},body:Be(o)}),s=i.headers.get("content-type")||"application/octet-stream",c=await i.text();E({instanceId:n,action:"proxy",method:o.method,path:a,statusCode:i.status,contentType:s},"node instance proxy completed"),e.code(i.status).type(s).send(c)}),k.post("/api/node-agent/instances/:id/proxy/raw",async t=>{const e=t.params.id,n=ne.parse(t.body),o=ln(I.requireInstance(e)),r=n.path.startsWith("/")?n.path:`/${n.path}`;E({instanceId:e,action:"proxy.raw",method:n.method,path:r,instanceBase:o},"node instance raw proxy requested");const a=await S(`${o}${r}`,{method:n.method,headers:{...n.headers},body:Be(n)}),i=Buffer.from(await a.arrayBuffer());return E({instanceId:e,action:"proxy.raw",method:n.method,path:r,statusCode:a.status,byteLength:i.length},"node instance raw proxy completed"),{data:{status:a.status,headers:(s=a.headers,Object.fromEntries([...s.entries()].filter(([t])=>!Pe.has(t.toLowerCase())))),bodyBase64:i.toString("base64")}};var s}),k.get("/api/node-agent/instances/:id/proxy/ws/*",{websocket:!0},(t,e)=>{const n=e.params.id,o=e.params["*"]||"",r=e.url.indexOf("?"),a=r>=0?e.url.slice(r):"";let i;try{const r=ln(I.requireInstance(n)),s=new URL(`/${o}${a}`,`${r}/`);s.protocol="https:"===s.protocol?"wss:":"ws:";const d=function(t){const e=t["sec-websocket-protocol"],n=(Array.isArray(e)?e.join(","):"string"==typeof e?e:"").split(",").map(t=>t.trim()).filter(Boolean);return n.length?n:void 0}(e.headers);E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,upstreamUrl:s.toString(),protocols:d||[]},"node instance websocket proxy opening"),i=d?new c(s,d):new c(s),i.on("open",()=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`},"node instance websocket proxy opened"),t.on("message",(t,e)=>i?.readyState===c.OPEN&&i.send(function(t,e){return e?t:t.toString()}(t,e))),t.on("close",()=>i?.close()),t.on("error",()=>i?.close())}),i.on("message",(e,n)=>{t.readyState===c.OPEN&&t.send(n?e:e.toString())}),i.on("close",()=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`},"node instance websocket proxy closed"),t.close()}),i.on("error",e=>{E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,error:e instanceof Error?e.message:String(e)},"node instance websocket proxy failed"),t.close(1011,"Instance websocket proxy failed.")})}catch(e){E({instanceId:n,action:"proxy.ws",path:`/${o}${a}`,error:e instanceof Error?e.message:String(e)},"node instance websocket endpoint unavailable"),i?.close(),t.close(1011,"Instance websocket endpoint is not reachable.")}}),k}function Nn(t){const e=new URL("/api/node-agent/tunnel",t);return e.protocol="https:"===e.protocol?"wss:":"ws:",e.toString()}function vn(t,e){const n=new URL(e.tunnelUrl);n.searchParams.set("nodeId",e.nodeId);const o=e.secret?ve({nodeId:e.nodeId,keyId:e.keyId,secret:e.secret,method:"GET",pathWithQuery:`${n.pathname}${n.search}`}):{},r=new c(n,{headers:o}),a=new Map;let i;const s=t=>{const n=t.startsWith("/")?t:`/${t}`,o=new URL(`/api/node-agent${n}`,`http://127.0.0.1:${e.port}`);return o.protocol="ws:",o},d=t=>e.secret?ve({nodeId:e.nodeId,keyId:e.keyId,secret:e.secret,method:"GET",pathWithQuery:`${t.pathname}${t.search}`}):{},l=(t,e=1e3,n="")=>{const o=a.get(t);a.delete(t),o?.upstream?.close(e,n),o?.tunnel?.close(e,n)};return r.on("open",()=>{r.send(JSON.stringify({type:"node-agent.identify",nodeId:e.nodeId,serverTime:(new Date).toISOString()})),i=t.nodeAgentEventForwarder?.addOutput(r)}),r.on("close",()=>{for(const t of a.keys())l(t,1001,"Reverse tunnel disconnected.");i?.(),i=void 0}),r.on("message",async o=>{let i;try{i=JSON.parse(String(o))}catch{return void r.send(JSON.stringify({type:"node-agent.error",code:"INVALID_JSON"}))}const u=i&&"object"==typeof i?i:{};if("control-plane.websocket.open"===u.type){const t="string"==typeof u.streamId?u.streamId:"",o="string"==typeof u.route?u.route:"",i=Array.isArray(u.protocols)?u.protocols.filter(t=>"string"==typeof t):void 0;try{const l=e.token?{authorization:`Bearer ${e.token}`}:void 0,u=i?.length?new c(s(o),i,{headers:l}):new c(s(o),{headers:l}),p=(t=>{const e=new URL(n);return e.pathname=`${e.pathname.replace(/\/$/,"")}/streams/${encodeURIComponent(t)}`,e})(t),m=new c(p,{headers:d(p)});a.set(t,{upstream:u,tunnel:m,close:(t=1e3,e="")=>{u.close(t,e),m.close(t,e)}}),u.on("open",()=>{r.send(JSON.stringify({type:"node-agent.websocket.open",streamId:t,protocol:u.protocol}))}),function(t,e,n={}){const o=n.pendingFrameLimit??256;let r=!1,a=Ct(e);const i=[],s=[];let c;const d=()=>{c&&(clearTimeout(c),c=void 0)},l=(n,r)=>{if(n.length>=o)return jt(t,1011,"WebSocket bridge pending frame limit exceeded."),void jt(e,1011,"WebSocket bridge pending frame limit exceeded.");n.push(r)};n.upstreamOpenTimeoutMs&&!a&&(c=setTimeout(()=>{a||r||(jt(t,1011,"Upstream websocket did not open."),jt(e))},n.upstreamOpenTimeoutMs)),t.on("open",()=>Rt(t,s)),e.on("open",()=>{d(),a=!0,n.onUpstreamOpen?.(),Rt(e,i)}),t.on("message",(t,n=!1)=>{const o={data:t,isBinary:Boolean(n)};Ct(e)?Dt(e,o):l(i,o)}),e.on("message",(e,n=!1)=>{const o={data:e,isBinary:Boolean(n)};Ct(t)?Dt(t,o):l(s,o)}),e.on("close",(e,o)=>{d(),n.onUpstreamClose?.(e,o),(a||r||!n.onUpstreamCloseBeforeOpen?.())&&jt(t,e,o)}),e.on("error",e=>{n.onUpstreamError?.(e),(a||r||!n.onUpstreamErrorBeforeOpen?.(e))&&jt(t)}),t.on("close",(t,o)=>{d(),r=!0,n.onClientClose?.(t,o),jt(e,t,o)}),t.on("error",t=>{d(),r=!0,n.onClientError?.(t),jt(e,1011,"WebSocket bridge client failed.")})}(m,u,{pendingFrameLimit:256,upstreamOpenTimeoutMs:1e4,onClientClose:()=>{a.delete(t)},onClientError:e=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))},onUpstreamClose:(e,n)=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.close",streamId:t,code:"number"==typeof e?e:1e3,reason:Buffer.isBuffer(n)?n.toString("utf8"):String(n||"")}))},onUpstreamError:e=>{a.delete(t),r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))}})}catch(e){r.send(JSON.stringify({type:"node-agent.websocket.error",streamId:t,message:e instanceof Error?e.message:String(e)}))}return}if("control-plane.websocket.close"===u.type){const t="string"==typeof u.streamId?u.streamId:"";return void l(t,"number"==typeof u.code?u.code:1e3,"string"==typeof u.reason?u.reason:"")}if("control-plane.request"===u.type){const n="string"==typeof u.requestId?u.requestId:"",o=u.init&&"object"==typeof u.init?u.init:{},a="string"==typeof u.route&&u.route.startsWith("/")?u.route:"/health",i=o.headers&&"object"==typeof o.headers?o.headers:{};try{const s=await t.inject({method:(p=o.method,"string"==typeof p&&Ee.has(p.toUpperCase())?p.toUpperCase():"GET"),url:`/api/node-agent${a}`,headers:{...i,...e.token?{authorization:`Bearer ${e.token}`}:{}},payload:"string"==typeof o.body?o.body:void 0});r.send(JSON.stringify({type:"node-agent.response",requestId:n,status:s.statusCode,headers:s.headers,body:s.body}))}catch(t){r.send(JSON.stringify({type:"node-agent.response",requestId:n,status:502,error:{code:"NODE_AGENT_REVERSE_INJECT_FAILED",message:t instanceof Error?t.message:String(t)}}))}}var p}),r}async function En(t){const r=de(t.dataDir),a=function(t,o={}){e.mkdirSync(n.dirname(t),{recursive:!0});const r={pid:process.pid,hostname:u.hostname(),component:o.component,command:process.argv.join(" "),acquiredAt:(new Date).toISOString(),token:l.randomUUID(),dataDir:o.dataDir,host:o.host,port:o.port};for(let n=0;n<2;n+=1)try{return e.mkdirSync(t),Ae(t,r),{lockPath:t,owner:r,release(){const n=Ie(t);n?.token===r.token&&e.rmSync(t,{recursive:!0,force:!0})}}}catch(n){if("EEXIST"!==n.code)throw n;const r=Ie(t);if(be(r?.pid))throw new ye(t,r,o.component);e.rmSync(t,{recursive:!0,force:!0})}const a=Ie(t);throw new ye(t,a,o.component)}(n.join(u.tmpdir(),`task-handoff-node-agent-${process.getuid?.()??"user"}.lock`),{component:"node-agent",dataDir:r.dataDir,host:t.host,port:t.port});try{const i=await _n(t),s=function(t,e,n,o){const r=e.token||process.env.TASK_HANDOFF_NODE_AGENT_TOKEN,a=new Map,i=n=>{const i=n.url.replace(/\/$/,"");a.get(i)?.close(1e3,"Reverse tunnel reconnecting.");const s=vn(t,{tunnelUrl:Nn(n.url),nodeId:o,port:e.port,token:r,keyId:n.keyId,secret:n.secret});return a.set(i,s),s.on("close",()=>{a.get(i)===s&&a.delete(i)}),s};return{connect:i,connectConfigured:()=>{const s=function(t){const e=t.controlPlaneTunnelUrl||process.env.TASK_HANDOFF_CONTROL_PLANE_TUNNEL_URL;if(e)return e;const n=process.env.TASK_HANDOFF_CONTROL_PLANE_URL;if(!n)return;const o=new URL("/api/node-agent/tunnel",n);return o.protocol="https:"===o.protocol?"wss:":"ws:",o.toString()}(e);if(s){const i=function(t,e,n,o){if(n&&o)return{keyId:o,secret:n};const r=Ye(t)?.remoteControlPlanes||[];if(!r.length)return;if(e){const t=r.find(t=>t.url&&t.url.replace(/\/$/,"")===e.replace(/\/$/,""));if(t)return{keyId:t.keyId,secret:t.secret}}const a=[...r].sort((t,e)=>e.updatedAt.localeCompare(t.updatedAt))[0];return a?{keyId:a.keyId,secret:a.secret}:void 0}(n,process.env.TASK_HANDOFF_CONTROL_PLANE_URL,e.remoteSecret||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_SECRET,e.remoteKeyId||process.env.TASK_HANDOFF_NODE_AGENT_REMOTE_KEY_ID),c=(process.env.TASK_HANDOFF_CONTROL_PLANE_URL||s).replace(/\/$/,"");a.get(c)?.close(1e3,"Reverse tunnel reconnecting.");const d=vn(t,{tunnelUrl:s,nodeId:o,port:e.port,token:r,keyId:i?.keyId,secret:i?.secret});a.set(c,d)}for(const t of Ye(n)?.remoteControlPlanes||[])t.url&&!1!==t.active&&i({url:t.url,keyId:t.keyId,secret:t.secret})},closeAll:()=>{for(const t of a.values())t.close(1001,"Node agent shutting down.");a.clear()}}}(i,t,r,Xe(r,t.nodeId||process.env.TASK_HANDOFF_NODE_ID));let c;i.decorate("nodeAgentReverseTunnels",s),function(t){let e=!1;const n=async n=>{if(!e){e=!0,s.closeAll(),c?.close();try{await t.close()}finally{process.exitCode=n?0:process.exitCode}}},o=()=>{n("SIGINT")},r=()=>{n("SIGTERM")};process.once("SIGINT",o),process.once("SIGTERM",r),t.addHook("onClose",async()=>{process.off("SIGINT",o),process.off("SIGTERM",r)})}(i),i.addHook("onClose",async()=>{c?.close(),a.release()});try{if(await i.listen({host:t.host,port:t.port}),"local-ipc"===t.connectionMode||"local-ipc"===process.env.TASK_HANDOFF_NODE_AGENT_CONNECTION_MODE){const a=t.ipcPath||process.env.TASK_HANDOFF_NODE_AGENT_IPC_PATH||we(r.dataDir);c=await async function(t,r){!function(t,o=process.platform){if("win32"!==o){e.mkdirSync(n.dirname(t),{recursive:!0,mode:448});try{e.chmodSync(n.dirname(t),448)}catch{}try{e.unlinkSync(t)}catch(t){if("ENOENT"!==t.code)throw t}}}(r);const a=o.createServer((e,n)=>{t.server.emit("request",e,n)});return a.on("upgrade",(e,n,o)=>{t.server.emit("upgrade",e,n,o)}),await new Promise((t,e)=>{a.once("error",e),a.listen(r,()=>{a.off("error",e),t()})}),a}(i,a)}s.connectConfigured()}catch(t){throw await i.close(),t}}catch(t){throw a.release(),t}}function On(t){const e=Number(t);if(!Number.isInteger(e)||e<=0||e>65535)throw new Error("Port must be a valid TCP port.");return e}function xn(t){if("local-ipc"!==t&&"local-loopback"!==t)throw new Error("Connection mode must be local-ipc or local-loopback.");return t}(async function(){const o=new t.Command;o.name("task-handoff-node-agent").description("Run a TaskHandoff node agent.").version(function(){try{return JSON.parse(e.readFileSync(n.resolve(__dirname,"..","package.json"),"utf8")).version||"unknown"}catch{return"unknown"}}()).option("--host <host>","Node agent host",process.env.TASK_HANDOFF_NODE_AGENT_HOST||"127.0.0.1").option("-p, --port <port>","Node agent port",On,Number(process.env.TASK_HANDOFF_NODE_AGENT_PORT)||8091).option("--data-dir <path>","Node agent data directory").option("--token <token>","Bearer token required by the control plane").option("--connection-mode <mode>","Local control connection mode",xn).option("--ipc-path <path>","Unix socket path used by local-ipc mode").option("--control-plane-tunnel-url <url>","Control plane reverse WebSocket tunnel URL").action(async t=>{await En({host:t.host,port:t.port,dataDir:t.dataDir,token:t.token,connectionMode:t.connectionMode,ipcPath:t.ipcPath,controlPlaneTunnelUrl:t.controlPlaneTunnelUrl})}),await o.parseAsync(process.argv)})().catch(t=>{console.error(t instanceof Error?t.message:String(t)),process.exitCode=1});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@task-handoff/node-agent",
|
|
3
|
-
"version": "0.0.3-alpha.
|
|
3
|
+
"version": "0.0.3-alpha.6",
|
|
4
4
|
"description": "Prebuilt TaskHandoff node agent runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"node": ">=22.22.2"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@fastify/cookie": "11.1.1",
|
|
19
20
|
"@fastify/static": "8.3.0",
|
|
20
21
|
"@fastify/websocket": "11.2.0",
|
|
21
22
|
"commander": "14.0.3",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"marked": "15.0.12",
|
|
25
26
|
"marked-terminal": "7.3.0",
|
|
26
27
|
"ms": "2.1.3",
|
|
28
|
+
"semver": "7.8.5",
|
|
27
29
|
"telegramify-markdown": "1.3.3",
|
|
28
30
|
"write-file-atomic": "8.0.0",
|
|
29
31
|
"ws": "8.21.0",
|