bdy 1.23.3-stage → 1.23.4-dev

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.3-stage",
4
+ "version": "1.23.4-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -25,6 +25,7 @@ class Agent extends events_1.default {
25
25
  api;
26
26
  socket;
27
27
  manager;
28
+ updateStatusExpiry;
28
29
  onRefresh;
29
30
  constructor(id, host, token, service, target, tunneling, proxy, tags, api, disabled) {
30
31
  super();
@@ -41,6 +42,7 @@ class Agent extends events_1.default {
41
42
  this.tunnelsUpdating = false;
42
43
  this.api = api;
43
44
  this.manager = null;
45
+ this.updateStatusExpiry = null;
44
46
  this.onRefresh = null;
45
47
  this.socket = new client_1.default(host, id, token);
46
48
  this.socket.on(tunnel_1.TUNNEL_SOCKET_EVENT.FETCH_SUCCESS, (data) => this._onSocketFetch(data));
@@ -56,6 +58,10 @@ class Agent extends events_1.default {
56
58
  this.socket.destroy();
57
59
  this.socket = null;
58
60
  }
61
+ // Would fire on a socket that is already gone
62
+ if (this.updateStatusExpiry)
63
+ clearTimeout(this.updateStatusExpiry);
64
+ this.updateStatusExpiry = null;
59
65
  this.manager = null;
60
66
  this.onRefresh = null;
61
67
  this.api = null;
@@ -260,8 +266,35 @@ class Agent extends events_1.default {
260
266
  // previous value in place, which is why the front reads STARTED on an offline agent as
261
267
  // an unknown outcome rather than as an update still running.
262
268
  reportUpdateStatus(status) {
263
- if (this.socket)
264
- this.socket.setPendingAction(status);
269
+ if (!this.socket)
270
+ return;
271
+ this.socket.setPendingAction(status);
272
+ this.expireUpdateStatusIn(status);
273
+ }
274
+ // Takes the outcome back off the record after a while, so it reads as the confirmation
275
+ // it is rather than as a standing fact about the agent. Done here rather than by hiding
276
+ // it in the front because nothing on the record dates the write: the only timestamps a
277
+ // front could compare against are the agent's boot time and its heartbeat, and neither
278
+ // is when this happened.
279
+ //
280
+ // A timer is sound for this where it would not be for the update itself: the process
281
+ // that reports an outcome is the one still running, so nothing has to survive a restart
282
+ // - and if it dies first, the next start() sends an empty action anyway.
283
+ expireUpdateStatusIn(status) {
284
+ if (this.updateStatusExpiry)
285
+ clearTimeout(this.updateStatusExpiry);
286
+ this.updateStatusExpiry = null;
287
+ // STARTED is not an outcome. It is what stays behind when a restart never lands, and
288
+ // clearing it would turn the one state the front can flag as unknown into silence.
289
+ if (status === tunnel_1.TUNNEL_AGENT_UPDATE_STATUS.STARTED)
290
+ return;
291
+ this.updateStatusExpiry = setTimeout(() => {
292
+ this.updateStatusExpiry = null;
293
+ this.ackAction();
294
+ }, utils_1.AGENT_UPDATE_STATUS_TTL_MS);
295
+ // A foreground tunnel reporting UNSUPPORTED exits as soon as it has acked, and must
296
+ // not be held open for the rest of the window just to tidy up after itself
297
+ this.updateStatusExpiry.unref();
265
298
  }
266
299
  // For a status with no ack to ride on - STARTED, reported while the download is still
267
300
  // running. Passes null so that if the status has somehow already gone out, this does
@@ -141,6 +141,11 @@ class AgentManagerClass {
141
141
  // Every path that gives up reports why as well as acking: the ack alone clears the
142
142
  // request, which on the front is indistinguishable from an update that never ran.
143
143
  const report = (status) => {
144
+ // Except when the agent is going away. There is nothing left to tell, and the write
145
+ // would land on top of the DELETE this process has not finished acting on - which
146
+ // is also how the swap below reports being cut short by one.
147
+ if (this.exiting)
148
+ return;
144
149
  this.agent?.reportUpdateStatus(status);
145
150
  return this.agent?.ackAction();
146
151
  };
@@ -540,6 +545,12 @@ class AgentManagerClass {
540
545
  }
541
546
  async disableAgentAndExit(txt) {
542
547
  logger_1.default.error(txt);
548
+ // Set before the first await, not just before the exit: uninstalling and unregistering
549
+ // both take a while, and a remote update arriving in that window would otherwise start
550
+ // downloading a release for an agent that is being taken apart - and could rename the
551
+ // new binary into place after the files it lives among have been removed. The update
552
+ // path already refuses to start while this is set.
553
+ this.exiting = true;
543
554
  try {
544
555
  await this.system.uninstall();
545
556
  }
@@ -407,7 +407,12 @@ class AgentSystem {
407
407
  // Last check before touching the live binary. The download and the smoke test take
408
408
  // long enough for an uninstall to have run in the meantime, and finishing the swap
409
409
  // then would put a binary back for an agent that is deliberately gone.
410
- if (!(0, node_fs_1.existsSync)(this.getSystemConfigPath())) {
410
+ //
411
+ // The config file alone is not enough to tell: an uninstall removes it only after
412
+ // stopping the service and unregistering, so a delete that is under way still looks
413
+ // like a live install from here for as long as those take. The flag is set before
414
+ // any of that starts.
415
+ if (!(0, node_fs_1.existsSync)(this.getSystemConfigPath()) || manager_1.default.exiting) {
411
416
  throw new Error(texts_1.LOG_AGENT_UPDATE_UNINSTALLED);
412
417
  }
413
418
  (0, node_fs_1.rmSync)(backup, { force: true });
@@ -36,8 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getHomeDirectory = exports.isDomainAvailable = exports.newCommand = exports.formatBytes = exports.formatHelp = exports.getPlatform = exports.getHostname = exports.isStringRegExp = exports.getWorkingDir = exports.getRootDir = exports.TARGET_ONLY_PORT_REGEX = exports.TARGET_HTTP_REGEX = exports.TARGET_TCP_TLS_REGEX = exports.ApiErrorTunnelsDisabled = exports.ApiErrorWorkspaceFlagged = exports.ApiErrorTunnelLimitReached = exports.ApiErrorAgentLimitReached = exports.ApiErrorDomainRestricted = exports.ApiErrorTargetInvalid = exports.ApiErrorWrongToken = exports.ApiErrorFailedToConnect = exports.ApiErrorAgentNotFound = exports.ARTIFACT_AUTH_TYPE = exports.ARTIFACT_TYPE = exports.SANDBOX_SNAPSHOT_STATUS = exports.SANDBOX_APP_STATUS = exports.SANDBOX_SETUP_STATUS = exports.SANDBOX_EXEC_STATUS = exports.SANDBOX_EXEC_RUNTIME = exports.SANDBOX_STATUS = exports.REST_API_ENDPOINT = exports.REST_API_REGION = exports.DEFAULT_SANDBOX_CP_USER = exports.ARTIFACT_DEFAULT_VERSION = exports.SUGGESTED_BROWSER_VERSION = exports.AGENT_UPDATE_PROBATION_MS = exports.AGENT_UPDATE_MAX_ATTEMPTS = exports.AGENT_FETCH_RETRY_MAX = exports.AGENT_FETCH_RETRY_MIN = exports.SSH_HANDSHAKE_TIMEOUT = exports.DEFAULT_TIMEOUT = exports.TUNNEL_HTTP_RETRY_AFTER = exports.TUNNEL_HTTP_MAX_INFLIGHT = exports.TUNNEL_HTTP_CB_MIN_REQUESTS = exports.TUNNEL_HTTP_CB_WINDOW = exports.TUNNEL_MAX_REQUEST_SIZE_TO_SYNC = exports.TUNNEL_HTTP_LOG_MAX_REQUESTS = exports.TUNNEL_HTTP_LOG_MAX_BODY = exports.TUNNEL_HTTP_RATE_WINDOW = exports.TUNNEL_HTTP_RATE_LIMIT = void 0;
40
- exports.getGitName = exports.getGitEmail = exports.MCP_TOOL_CATEGORIES = exports.getBasicCommandTls = exports.getBasicCommandHttp = exports.getBasicCommandSandboxEndpoint = exports.getBasicCommandTcp = exports.getCurrentUser = exports.getServiceUser = exports.getRealTargetHost = exports.isWindows = exports.isLinux = exports.isOsx = exports.isDocker = exports.sleep = exports.getLatestVersion = exports.getVersionEnv = exports.getCurrentVersionWithoutEnv = exports.getVersionWithoutEnv = exports.getVersion = exports.isInstalledByChoco = exports.isInstalledByApt = exports.isInstalledByBrew = exports.isInstalledByNpm = exports.tryGetEmail = exports.execLocally = void 0;
39
+ exports.isDomainAvailable = exports.newCommand = exports.formatBytes = exports.formatHelp = exports.getPlatform = exports.getHostname = exports.isStringRegExp = exports.getWorkingDir = exports.getRootDir = exports.TARGET_ONLY_PORT_REGEX = exports.TARGET_HTTP_REGEX = exports.TARGET_TCP_TLS_REGEX = exports.ApiErrorTunnelsDisabled = exports.ApiErrorWorkspaceFlagged = exports.ApiErrorTunnelLimitReached = exports.ApiErrorAgentLimitReached = exports.ApiErrorDomainRestricted = exports.ApiErrorTargetInvalid = exports.ApiErrorWrongToken = exports.ApiErrorFailedToConnect = exports.ApiErrorAgentNotFound = exports.ARTIFACT_AUTH_TYPE = exports.ARTIFACT_TYPE = exports.SANDBOX_SNAPSHOT_STATUS = exports.SANDBOX_APP_STATUS = exports.SANDBOX_SETUP_STATUS = exports.SANDBOX_EXEC_STATUS = exports.SANDBOX_EXEC_RUNTIME = exports.SANDBOX_STATUS = exports.REST_API_ENDPOINT = exports.REST_API_REGION = exports.DEFAULT_SANDBOX_CP_USER = exports.ARTIFACT_DEFAULT_VERSION = exports.SUGGESTED_BROWSER_VERSION = exports.AGENT_UPDATE_STATUS_TTL_MS = exports.AGENT_UPDATE_PROBATION_MS = exports.AGENT_UPDATE_MAX_ATTEMPTS = exports.AGENT_FETCH_RETRY_MAX = exports.AGENT_FETCH_RETRY_MIN = exports.SSH_HANDSHAKE_TIMEOUT = exports.DEFAULT_TIMEOUT = exports.TUNNEL_HTTP_RETRY_AFTER = exports.TUNNEL_HTTP_MAX_INFLIGHT = exports.TUNNEL_HTTP_CB_MIN_REQUESTS = exports.TUNNEL_HTTP_CB_WINDOW = exports.TUNNEL_MAX_REQUEST_SIZE_TO_SYNC = exports.TUNNEL_HTTP_LOG_MAX_REQUESTS = exports.TUNNEL_HTTP_LOG_MAX_BODY = exports.TUNNEL_HTTP_RATE_WINDOW = exports.TUNNEL_HTTP_RATE_LIMIT = void 0;
40
+ exports.getGitName = exports.getGitEmail = exports.MCP_TOOL_CATEGORIES = exports.getBasicCommandTls = exports.getBasicCommandHttp = exports.getBasicCommandSandboxEndpoint = exports.getBasicCommandTcp = exports.getCurrentUser = exports.getServiceUser = exports.getRealTargetHost = exports.isWindows = exports.isLinux = exports.isOsx = exports.isDocker = exports.sleep = exports.getLatestVersion = exports.getVersionEnv = exports.getCurrentVersionWithoutEnv = exports.getVersionWithoutEnv = exports.getVersion = exports.isInstalledByChoco = exports.isInstalledByApt = exports.isInstalledByBrew = exports.isInstalledByNpm = exports.tryGetEmail = exports.execLocally = exports.getHomeDirectory = void 0;
41
41
  exports.apiErrorCodeToClass = apiErrorCodeToClass;
42
42
  exports.isFile = isFile;
43
43
  exports.getAppWorkspaceUrl = getAppWorkspaceUrl;
@@ -92,6 +92,11 @@ exports.AGENT_UPDATE_MAX_ATTEMPTS = 3;
92
92
  // only has to be read. Generous on purpose: it is only ever reached when the counter
93
93
  // cannot be persisted, so it must not fire on an agent that is merely slow to come up.
94
94
  exports.AGENT_UPDATE_PROBATION_MS = 30 * 60 * 1000;
95
+ // How long a reported update outcome stays on the agent record before the agent clears it
96
+ // again. An outcome is a confirmation for whoever is watching the update, not a property of
97
+ // the agent: nothing re-evaluates it, so one left in place would still claim a machine is
98
+ // missing admin rights long after that was fixed. The agent log keeps the durable record.
99
+ exports.AGENT_UPDATE_STATUS_TTL_MS = 30 * 1000;
95
100
  exports.SUGGESTED_BROWSER_VERSION = '130.0.6723.69';
96
101
  exports.ARTIFACT_DEFAULT_VERSION = 'latest';
97
102
  exports.DEFAULT_SANDBOX_CP_USER = 'buddy';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.3-stage",
4
+ "version": "1.23.4-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",