@homespunapps/cli 1.6.53 → 1.6.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/work.js +58 -8
- package/package.json +2 -2
package/dist/commands/work.js
CHANGED
|
@@ -80,7 +80,7 @@ export async function runWork(args) {
|
|
|
80
80
|
process.on("SIGTERM", stop);
|
|
81
81
|
const socket = opts.once
|
|
82
82
|
? null
|
|
83
|
-
: openWakeSocket(opts, cfg.apiKey, base, () => wake?.());
|
|
83
|
+
: await openWakeSocket(opts, cfg.apiKey, base, () => wake?.());
|
|
84
84
|
try {
|
|
85
85
|
for (;;) {
|
|
86
86
|
const claimed = await claim(base, cfg.apiKey, opts);
|
|
@@ -238,22 +238,66 @@ async function report(base, apiKey, taskId, verb, text) {
|
|
|
238
238
|
* worker that killed itself over a lost optimisation would be worse than one that
|
|
239
239
|
* kept polling.
|
|
240
240
|
*/
|
|
241
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Exported for testing. This function is the one part of `work` that had NO test and
|
|
243
|
+
* shipped broken twice over (a URL built from the app id rather than its real
|
|
244
|
+
* location, and a swallowed connect error), so it is worth being able to point a test
|
|
245
|
+
* straight at it rather than only at the command that calls it.
|
|
246
|
+
*/
|
|
247
|
+
export async function openWakeSocket(opts, apiKey, base, onWake) {
|
|
242
248
|
if (opts.appIds.length !== 1)
|
|
243
249
|
return null;
|
|
244
250
|
const appId = opts.appIds[0];
|
|
251
|
+
// ASK THE RELAY where the app lives. This was reconstructed as `${base}/a/${appId}/`,
|
|
252
|
+
// which is wrong in two ways at once: an app is served under its SLUG, not its id,
|
|
253
|
+
// and on a usercontent deployment it is a different origin entirely
|
|
254
|
+
// (`<slug>.homespunapps.com`) rather than a path on the API host. The socket
|
|
255
|
+
// therefore never connected, and the reconnect logic below reported it as
|
|
256
|
+
// "wake socket lost", which reads like a transient blip rather than a URL that
|
|
257
|
+
// could never work. Found by running a worker against a live relay; no test
|
|
258
|
+
// covered this function at all.
|
|
259
|
+
//
|
|
260
|
+
// A failure here is NOT fatal: polling still drains the queue, so a worker that
|
|
261
|
+
// cannot resolve its app keeps working, just without the wake hint.
|
|
262
|
+
let appUrl;
|
|
263
|
+
try {
|
|
264
|
+
const res = await fetch(`${base}/v1/apps/${appId}`, {
|
|
265
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
266
|
+
});
|
|
267
|
+
if (!res.ok) {
|
|
268
|
+
warn(`cannot resolve app ${appId} for the wake socket (${res.status}); polling only`);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
appUrl = (await res.json()).url ?? "";
|
|
272
|
+
if (!appUrl) {
|
|
273
|
+
warn(`app ${appId} reports no url; polling only`);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
warn(`cannot resolve app ${appId} for the wake socket (${err instanceof Error ? err.message : String(err)}); polling only`);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
245
281
|
let closed = false;
|
|
282
|
+
let connectedOnce = false;
|
|
246
283
|
let delay = RECONNECT_MIN_MS;
|
|
247
284
|
let handle = null;
|
|
248
285
|
let announcedOutage = false;
|
|
249
286
|
const connect = () => {
|
|
250
287
|
if (closed)
|
|
251
288
|
return;
|
|
252
|
-
const wsUrl = appWsUrlFromAppUrl(
|
|
253
|
-
handle = openAppStream(
|
|
289
|
+
const wsUrl = appWsUrlFromAppUrl(appUrl);
|
|
290
|
+
handle = openAppStream(
|
|
291
|
+
// `since: 0`, not a huge sentinel. The intent was "do not replay history", and
|
|
292
|
+
// MAX_SAFE_INTEGER expressed that by sending a cursor the relay will not accept,
|
|
293
|
+
// so the socket was refused and the wake frame never arrived. The worker sets no
|
|
294
|
+
// `onEntry` handler at all, so a replayed batch is discarded as it is parsed:
|
|
295
|
+
// there is nothing to avoid.
|
|
296
|
+
{ wsUrl, apiKey, since: 0 }, {
|
|
254
297
|
onHello: () => {
|
|
255
298
|
// A successful connect resets the backoff, so a flapping link does not
|
|
256
299
|
// inherit the previous outage's delay.
|
|
300
|
+
connectedOnce = true;
|
|
257
301
|
delay = RECONNECT_MIN_MS;
|
|
258
302
|
if (announcedOutage) {
|
|
259
303
|
warn("wake socket reconnected");
|
|
@@ -261,15 +305,21 @@ function openWakeSocket(opts, apiKey, base, onWake) {
|
|
|
261
305
|
}
|
|
262
306
|
},
|
|
263
307
|
onAgentTaskAvailable: onWake,
|
|
264
|
-
onClose: () => scheduleReconnect(),
|
|
265
|
-
|
|
308
|
+
onClose: ({ code, reason }) => scheduleReconnect(`closed ${code}${reason ? ": " + reason : ""}`),
|
|
309
|
+
// The error is REPORTED, not swallowed. Discarding it is what made the
|
|
310
|
+
// original URL bug take three guesses to find: every failure looked
|
|
311
|
+
// identical from the outside.
|
|
312
|
+
onError: (err) => scheduleReconnect(err instanceof Error ? err.message : String(err)),
|
|
266
313
|
});
|
|
267
314
|
};
|
|
268
|
-
const scheduleReconnect = () => {
|
|
315
|
+
const scheduleReconnect = (why) => {
|
|
269
316
|
if (closed)
|
|
270
317
|
return;
|
|
271
318
|
if (!announcedOutage) {
|
|
272
|
-
warn(
|
|
319
|
+
warn((connectedOnce
|
|
320
|
+
? "wake socket lost; polling continues while it reconnects"
|
|
321
|
+
: "wake socket could not connect; polling only until it does") +
|
|
322
|
+
` (${why})`);
|
|
273
323
|
announcedOutage = true;
|
|
274
324
|
}
|
|
275
325
|
const wait = delay;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homespunapps/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.54",
|
|
4
4
|
"description": "Command-line client for the Homespun relay: deploy a real multi-user web app from your agent, then keep reading and writing its data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"test:unit": "vitest run"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@homespunapps/core": "^1.6.
|
|
39
|
+
"@homespunapps/core": "^1.6.54",
|
|
40
40
|
"qrcode-terminal": "^0.12.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|