@agentsky/sky 2.10.0 → 2.12.0

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.
@@ -0,0 +1,190 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/worker.ts
4
+ import { mkdirSync, writeFileSync, rmSync } from "fs";
5
+ import { tmpdir } from "os";
6
+ import { join } from "path";
7
+ import { randomBytes } from "crypto";
8
+ var RealDockerRunner = class {
9
+ async run({ image, envFile, port, signal }) {
10
+ const { spawn } = await import("child_process");
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(
13
+ "docker",
14
+ [
15
+ "run",
16
+ "--rm",
17
+ "--pull=always",
18
+ `--env-file=${envFile}`,
19
+ `-p`,
20
+ `127.0.0.1:${port}:8080`,
21
+ image
22
+ ],
23
+ { stdio: "inherit" }
24
+ );
25
+ signal.addEventListener("abort", () => {
26
+ child.kill("SIGTERM");
27
+ });
28
+ child.on("error", reject);
29
+ child.on("exit", (code) => {
30
+ if (code === 0 || signal.aborted) resolve();
31
+ else reject(new Error(`docker exited with code ${code}`));
32
+ });
33
+ });
34
+ }
35
+ };
36
+ var POD_PORT = 18080;
37
+ var HEARTBEAT_INTERVAL_MS = 15e3;
38
+ async function startWorker(opts) {
39
+ const docker = opts.docker ?? new RealDockerRunner();
40
+ const signal = opts.signal ?? new AbortController().signal;
41
+ const wsUrl = opts.apiBase.replace(/^https?:\/\//, (m) => m === "https://" ? "wss://" : "ws://").replace(/\/$/, "");
42
+ const connectUrl = `${wsUrl}/api/v1/worker-pools/${opts.poolId}/connect`;
43
+ return new Promise((resolve, reject) => {
44
+ import("ws").then(({ default: WS }) => {
45
+ const ws = new WS(connectUrl, {
46
+ headers: { authorization: `Bearer ${opts.token}` }
47
+ });
48
+ let currentContainer = null;
49
+ const heartbeatTimer = setInterval(() => {
50
+ if (ws.readyState === WS.OPEN) {
51
+ ws.send(JSON.stringify({ type: "heartbeat" }));
52
+ }
53
+ }, HEARTBEAT_INTERVAL_MS);
54
+ signal.addEventListener("abort", () => {
55
+ clearInterval(heartbeatTimer);
56
+ currentContainer?.abort();
57
+ ws.close();
58
+ resolve();
59
+ });
60
+ ws.on("open", () => {
61
+ ws.send(JSON.stringify({
62
+ type: "hello",
63
+ label: opts.label,
64
+ imageDigest: "unknown",
65
+ capacity: 1
66
+ }));
67
+ console.log(`[sky worker] connected to pool ${opts.poolId}`);
68
+ });
69
+ ws.on("message", async (raw) => {
70
+ let msg;
71
+ try {
72
+ msg = JSON.parse(raw.toString());
73
+ } catch {
74
+ return;
75
+ }
76
+ if (msg.type === "start") {
77
+ await handleStart(msg, ws, WS, docker, currentContainer);
78
+ } else if (msg.type === "stop") {
79
+ currentContainer?.abort();
80
+ currentContainer = null;
81
+ } else if (msg.type === "http") {
82
+ await handleHttp(msg, ws);
83
+ } else if (msg.type === "stream_open") {
84
+ void handleStreamOpen(msg, ws);
85
+ }
86
+ });
87
+ ws.on("close", () => {
88
+ clearInterval(heartbeatTimer);
89
+ currentContainer?.abort();
90
+ resolve();
91
+ });
92
+ ws.on("error", (err) => {
93
+ clearInterval(heartbeatTimer);
94
+ currentContainer?.abort();
95
+ reject(err);
96
+ });
97
+ }).catch(reject);
98
+ });
99
+ async function handleStart(msg, ws, WS, docker2, existingContainer) {
100
+ if (existingContainer) existingContainer.abort();
101
+ const dir = join(tmpdir(), `sky-worker-${randomBytes(6).toString("hex")}`);
102
+ mkdirSync(dir, { mode: 448 });
103
+ const envFile = join(dir, "pod.env");
104
+ const envLines = Object.entries(msg.env).map(([k, v]) => `${k}=${v.replace(/\n/g, "\\n")}`).join("\n");
105
+ writeFileSync(envFile, envLines, { mode: 384 });
106
+ const containerAbort = new AbortController();
107
+ try {
108
+ void docker2.run({
109
+ image: msg.image,
110
+ envFile,
111
+ port: POD_PORT,
112
+ signal: containerAbort.signal
113
+ }).finally(() => {
114
+ try {
115
+ rmSync(dir, { recursive: true });
116
+ } catch {
117
+ }
118
+ });
119
+ } catch (err) {
120
+ try {
121
+ rmSync(dir, { recursive: true });
122
+ } catch {
123
+ }
124
+ console.error("[sky worker] docker run failed:", err);
125
+ }
126
+ }
127
+ async function handleHttp(msg, ws) {
128
+ const url = `http://127.0.0.1:${POD_PORT}${msg.path}`;
129
+ try {
130
+ const response = await fetch(url, {
131
+ method: msg.method,
132
+ headers: msg.headers,
133
+ body: msg.body || void 0,
134
+ signal: AbortSignal.timeout(3e4)
135
+ });
136
+ const body = await response.text();
137
+ const headers = {};
138
+ response.headers.forEach((v, k) => {
139
+ headers[k] = v;
140
+ });
141
+ const resp = {
142
+ type: "http_response",
143
+ id: msg.id,
144
+ status: response.status,
145
+ headers,
146
+ body
147
+ };
148
+ ws.send(JSON.stringify(resp));
149
+ } catch (err) {
150
+ ws.send(JSON.stringify({
151
+ type: "http_response",
152
+ id: msg.id,
153
+ status: 502,
154
+ headers: {},
155
+ body: String(err)
156
+ }));
157
+ }
158
+ }
159
+ async function handleStreamOpen(msg, ws) {
160
+ const url = `http://127.0.0.1:${POD_PORT}${msg.path}`;
161
+ try {
162
+ const response = await fetch(url, {
163
+ headers: { accept: "text/event-stream" },
164
+ signal: AbortSignal.timeout(3e5)
165
+ });
166
+ const reader = response.body?.getReader();
167
+ if (!reader) {
168
+ ws.send(JSON.stringify({ type: "stream_close", id: msg.id }));
169
+ return;
170
+ }
171
+ const decoder = new TextDecoder();
172
+ for (; ; ) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+ ws.send(JSON.stringify({
176
+ type: "stream_event",
177
+ id: msg.id,
178
+ data: decoder.decode(value, { stream: true })
179
+ }));
180
+ }
181
+ ws.send(JSON.stringify({ type: "stream_close", id: msg.id }));
182
+ } catch {
183
+ ws.send(JSON.stringify({ type: "stream_close", id: msg.id }));
184
+ }
185
+ }
186
+ }
187
+ export {
188
+ RealDockerRunner,
189
+ startWorker
190
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentsky/sky",
3
- "version": "2.10.0",
4
- "description": "sky — the AgentSky developer CLI: launch and talk to long-lived agents from your terminal.",
3
+ "version": "2.12.0",
4
+ "description": "sky \u2014 the AgentSky developer CLI: launch and talk to long-lived agents from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -22,7 +22,8 @@
22
22
  "dependencies": {
23
23
  "commander": "^14.0.0",
24
24
  "smol-toml": "^1.4.2",
25
- "zod": "4.5.4"
25
+ "zod": "4.5.4",
26
+ "ws": "^8.18.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@agent/chat": "*",