@camstack/server 1.2.708 → 1.2.710

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/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.710] - 2026-10-31
11
+
12
+ ### Changed
13
+
14
+ - The schedulers capability is served over the API (list, create, update, delete, enable, preview, conflicts, status, traces)
15
+
16
+ ## [1.2.709] - 2026-10-31
17
+
18
+ ### Changed
19
+
20
+ - A wedged agent shutdown now ends with a self-SIGKILL so exit-time teardown cannot keep the process alive after an update.
21
+ - getSiteLocation also returns the site's IANA time zone
22
+
10
23
  ## [1.2.708] - 2026-10-31
11
24
 
12
25
  ### Changed
@@ -1,9 +1,74 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.SHUTDOWN_DEADLINE_MS = exports.SHUTDOWN_STEP_TIMEOUT_MS = void 0;
37
+ exports.killSelfHard = killSelfHard;
4
38
  exports.createAgentShutdown = createAgentShutdown;
39
+ /**
40
+ * The agent's graceful shutdown, as a testable sequence.
41
+ *
42
+ * A SIGTERM once started a shutdown that awaited one addon's `shutdown()` for 9.5 minutes: the node
43
+ * stayed alive but deregistered, and its desktop shell never noticed. So the pass is re-entry guarded
44
+ * (a second signal forces the exit instead of starting a second pass), every step is bounded, the
45
+ * whole pass has a hard deadline that ends in a non-zero `exit`, and every step logs its elapsed time
46
+ * next to the reason the shutdown started.
47
+ */
48
+ const fs = __importStar(require("node:fs"));
5
49
  exports.SHUTDOWN_STEP_TIMEOUT_MS = 5_000;
6
50
  exports.SHUTDOWN_DEADLINE_MS = 15_000;
51
+ /**
52
+ * End this process WITHOUT running exit-time teardown. `process.exit` joins the threadpool and tears
53
+ * down workers and native addons, and on a node that is already wedged that join can itself hang
54
+ * (observed: a desktop agent alive for minutes after its shutdown deadline). A SIGKILL to ourselves
55
+ * cannot be waited on. The last line is written synchronously first, because a buffered log write
56
+ * would be lost.
57
+ */
58
+ function killSelfHard(finalLine, deps = {}) {
59
+ const write = deps.write ??
60
+ ((line) => {
61
+ fs.writeSync(2, `${line}\n`);
62
+ });
63
+ try {
64
+ write(finalLine);
65
+ }
66
+ catch {
67
+ /* a closed stderr must not stop the kill */
68
+ }
69
+ const kill = deps.kill ?? ((pid, signal) => void process.kill(pid, signal));
70
+ kill(process.pid, 'SIGKILL');
71
+ }
7
72
  function errMessage(err) {
8
73
  return err instanceof Error ? err.message : String(err);
9
74
  }
@@ -28,7 +93,7 @@ function createAgentShutdown(deps) {
28
93
  return async (reason) => {
29
94
  if (startedAt !== null) {
30
95
  logger.error(`shutdown already in progress for ${Date.now() - startedAt}ms; ${reason} forces exit`);
31
- deps.exit(1);
96
+ deps.hardExit(1, `shutdown already in progress; ${reason} forces exit`);
32
97
  return;
33
98
  }
34
99
  const began = Date.now();
@@ -36,7 +101,7 @@ function createAgentShutdown(deps) {
36
101
  logger.info(`shutdown started by ${reason}`);
37
102
  const deadline = setTimeout(() => {
38
103
  logger.error(`shutdown deadline of ${deadlineMs}ms exceeded (started by ${reason}); exiting 1`);
39
- deps.exit(1);
104
+ deps.hardExit(1, `shutdown deadline of ${deadlineMs}ms exceeded (started by ${reason}); SIGKILL`);
40
105
  }, deadlineMs);
41
106
  for (const step of deps.steps) {
42
107
  const stepStart = Date.now();
@@ -73,6 +73,7 @@ const update_availability_store_js_1 = require("../core/update-availability-stor
73
73
  const index_js_1 = require("../server-root/index.js");
74
74
  const node_update_announcement_js_1 = require("../core/updates/node-update-announcement.js");
75
75
  const restart_marker_js_1 = require("../core/updates/restart-marker.js");
76
+ const agent_shutdown_js_1 = require("./agent-shutdown.js");
76
77
  /**
77
78
  * The synthetic addonId the agent's OWN runtime registers infra providers
78
79
  * under (no addon owns them — the bootstrap does). Appears in the agent's
@@ -145,7 +146,7 @@ function scheduleAgentRestart(logger, requestedBy, dataDir) {
145
146
  });
146
147
  const grace = setTimeout(() => {
147
148
  process.kill(process.pid, 'SIGTERM');
148
- const hard = setTimeout(() => process.exit(0), 15_000);
149
+ const hard = setTimeout(() => (0, agent_shutdown_js_1.killSelfHard)('agent restart: shutdown did not finish in 15000ms; SIGKILL'), 15_000);
149
150
  hard.unref();
150
151
  }, exports.AGENT_RESTART_GRACE_MS);
151
152
  grace.unref();
@@ -1076,6 +1076,7 @@ async function startAgent(configPath) {
1076
1076
  const shutdown = (0, agent_shutdown_js_1.createAgentShutdown)({
1077
1077
  logger: consoleLogger,
1078
1078
  exit: (code) => process.exit(code),
1079
+ hardExit: (_code, finalLine) => (0, agent_shutdown_js_1.killSelfHard)(finalLine),
1079
1080
  steps: [
1080
1081
  {
1081
1082
  name: 'abort-registration',
@@ -48,6 +48,7 @@
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
49
  exports.SiteLocationService = exports.GEO_IP_ENDPOINTS = exports.GEO_IP_TIMEOUT_MS = exports.SITE_LOCATION_KEY = void 0;
50
50
  exports.isUsableCoordinate = isUsableCoordinate;
51
+ exports.hubTimeZone = hubTimeZone;
51
52
  exports.toStatus = toStatus;
52
53
  exports.parseGeoIpPayload = parseGeoIpPayload;
53
54
  exports.geolocatePublicIp = geolocatePublicIp;
@@ -98,10 +99,15 @@ function isUsableCoordinate(lat, lng) {
98
99
  return false;
99
100
  return lat !== 0 || lng !== 0;
100
101
  }
102
+ /** The hub's own IANA zone, `UTC` when the runtime cannot name one. */
103
+ function hubTimeZone() {
104
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
105
+ return typeof zone === 'string' && zone.length > 0 ? zone : 'UTC';
106
+ }
101
107
  /** Project a stored row onto the wire shape. */
102
- function toStatus(record) {
108
+ function toStatus(record, timeZone = hubTimeZone()) {
103
109
  if (record === null) {
104
- return { location: null, derivationAttemptedAt: null, derivationError: null };
110
+ return { location: null, timeZone, derivationAttemptedAt: null, derivationError: null };
105
111
  }
106
112
  const attempted = typeof record.derivationAttemptedAt === 'number';
107
113
  const status = {
@@ -117,6 +123,7 @@ function toStatus(record) {
117
123
  ...(record.label !== undefined ? { label: record.label } : {}),
118
124
  }
119
125
  : null,
126
+ timeZone,
120
127
  derivationAttemptedAt: attempted ? Number(record.derivationAttemptedAt) : null,
121
128
  derivationError: typeof record.derivationError === 'string' ? record.derivationError : null,
122
129
  };
@@ -2,7 +2,7 @@
2
2
  // AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
3
3
  // Re-run: npx tsx scripts/generate-cap-mounts.ts
4
4
  //
5
- // Mounted: 190 Skipped (legacy): 3
5
+ // Mounted: 191 Skipped (legacy): 3
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.LEGACY_SHAPE_SKIP = void 0;
8
8
  exports.mountAllCaps = mountAllCaps;
@@ -549,6 +549,7 @@ function mountAllCaps(services) {
549
549
  }, remoteCapProxy),
550
550
  runningState: (0, generated_cap_routers_js_1.createCapRouter_runningState)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'running-state'), remoteCapProxy),
551
551
  sceneMonitor: (0, generated_cap_routers_js_1.createCapRouter_sceneMonitor)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'scene-monitor'), remoteCapProxy),
552
+ schedulers: (0, generated_cap_routers_js_1.createCapRouter_schedulers)((_ctx) => reg?.getSingleton('schedulers') ?? null, remoteCapProxy),
552
553
  scriptRunner: (0, generated_cap_routers_js_1.createCapRouter_scriptRunner)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'script-runner'), remoteCapProxy),
553
554
  serverManagement: (0, generated_cap_routers_js_1.createCapRouter_serverManagement)((_ctx) => reg?.getSingleton('server-management') ??
554
555
  null, remoteCapProxy),