@slock-ai/daemon 0.46.1 → 0.46.2-staging.20260509074830

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.
@@ -6830,6 +6830,7 @@ function acquireDaemonMachineLock(options) {
6830
6830
  import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
6831
6831
  import path13 from "path";
6832
6832
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
6833
+ var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
6833
6834
  var DEFAULT_MAX_FILES = 8;
6834
6835
  var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
6835
6836
  "serverId",
@@ -6846,14 +6847,25 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
6846
6847
  var LocalRotatingTraceSink = class {
6847
6848
  traceDir;
6848
6849
  maxFileBytes;
6850
+ maxFileAgeMs;
6849
6851
  maxFiles;
6852
+ nowMsProvider;
6850
6853
  currentFile = null;
6854
+ currentFileOpenedAtMs = null;
6851
6855
  currentSize = 0;
6852
6856
  sequence = 0;
6853
6857
  constructor(options) {
6854
6858
  this.traceDir = path13.join(options.machineDir, "traces");
6855
6859
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
6860
+ const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
6861
+ const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
6862
+ this.maxFileAgeMs = baseAgeMs + ageJitterMs;
6856
6863
  this.maxFiles = Math.max(1, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES));
6864
+ this.nowMsProvider = options.nowMsProvider ?? Date.now;
6865
+ }
6866
+ /** Exposed for observability — the effective rotation age after jitter. */
6867
+ getMaxFileAgeMs() {
6868
+ return this.maxFileAgeMs;
6857
6869
  }
6858
6870
  record(span) {
6859
6871
  try {
@@ -6870,13 +6882,16 @@ var LocalRotatingTraceSink = class {
6870
6882
  }
6871
6883
  ensureFile(nextBytes) {
6872
6884
  mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
6873
- if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes) {
6885
+ const nowMs = this.nowMsProvider();
6886
+ const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
6887
+ if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
6874
6888
  this.currentFile = path13.join(
6875
6889
  this.traceDir,
6876
- `daemon-trace-${safeTimestamp(Date.now())}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
6890
+ `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
6877
6891
  );
6878
6892
  writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
6879
6893
  this.currentSize = statSync4(this.currentFile).size;
6894
+ this.currentFileOpenedAtMs = nowMs;
6880
6895
  this.pruneOldFiles();
6881
6896
  }
6882
6897
  }
@@ -6961,7 +6976,7 @@ function isDiagnosticIdAttr(key) {
6961
6976
  }
6962
6977
 
6963
6978
  // src/traceBundleUpload.ts
6964
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
6979
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
6965
6980
  import { gzipSync } from "zlib";
6966
6981
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
6967
6982
  import path14 from "path";
@@ -7090,6 +7105,35 @@ async function uploadWithSignedCapability({
7090
7105
  return { capability, session, uploadResponse };
7091
7106
  }
7092
7107
 
7108
+ // src/traceJitter.ts
7109
+ import { createHash as createHash2 } from "crypto";
7110
+ var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
7111
+ var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
7112
+ var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
7113
+ function computeTraceJitter(lockId) {
7114
+ const seed = createHash2("sha256").update(lockId).digest();
7115
+ return {
7116
+ initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
7117
+ uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
7118
+ maxFileAgeJitterMs: seed.readUInt32BE(8) % MAX_FILE_AGE_JITTER_SPAN_MS
7119
+ };
7120
+ }
7121
+ var NO_JITTER = {
7122
+ initialUploadDelayMs: 0,
7123
+ uploadIntervalJitterMs: 0,
7124
+ maxFileAgeJitterMs: 0
7125
+ };
7126
+ function bucketDelayMs(delayMs) {
7127
+ if (delayMs < 1e3) return "0-1s";
7128
+ if (delayMs < 5e3) return "1-5s";
7129
+ if (delayMs < 15e3) return "5-15s";
7130
+ if (delayMs < 3e4) return "15-30s";
7131
+ if (delayMs < 6e4) return "30-60s";
7132
+ if (delayMs < 3e5) return "60s-5m";
7133
+ if (delayMs < 6e5) return "5-10m";
7134
+ return "10m+";
7135
+ }
7136
+
7093
7137
  // src/traceBundleUpload.ts
7094
7138
  var TRACE_UPLOAD_SCOPE = "daemon-trace-bundle:create";
7095
7139
  var DEFAULT_UPLOAD_INTERVAL_MS = 5 * 60 * 1e3;
@@ -7097,30 +7141,66 @@ var DEFAULT_MIN_FILE_AGE_MS = 60 * 1e3;
7097
7141
  var DEFAULT_MAX_FILES_PER_RUN = 4;
7098
7142
  var DaemonTraceBundleUploader = class {
7099
7143
  options;
7100
- timer = null;
7144
+ jitter;
7145
+ timers;
7146
+ initialDelayTimer = null;
7147
+ intervalTimer = null;
7148
+ stopped = false;
7101
7149
  constructor(options) {
7102
7150
  this.options = options;
7151
+ this.jitter = options.jitter ?? (options.lockId ? computeTraceJitter(options.lockId) : NO_JITTER);
7152
+ this.timers = options.timers ?? {
7153
+ setTimeout: globalThis.setTimeout.bind(globalThis),
7154
+ setInterval: globalThis.setInterval.bind(globalThis),
7155
+ clearTimeout: globalThis.clearTimeout.bind(globalThis),
7156
+ clearInterval: globalThis.clearInterval.bind(globalThis)
7157
+ };
7103
7158
  }
7104
7159
  start() {
7105
- if (this.timer) return;
7106
- void this.uploadOnce();
7107
- this.timer = setInterval(() => {
7108
- void this.uploadOnce();
7109
- }, this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS));
7160
+ if (this.stopped) return;
7161
+ if (this.initialDelayTimer || this.intervalTimer) return;
7162
+ const initialDelayMs = this.jitter.initialUploadDelayMs;
7163
+ this.initialDelayTimer = this.timers.setTimeout(() => {
7164
+ this.initialDelayTimer = null;
7165
+ if (this.stopped) return;
7166
+ void this.uploadOnce("initial");
7167
+ this.scheduleNextTick();
7168
+ }, initialDelayMs);
7110
7169
  }
7111
7170
  stop() {
7112
- if (!this.timer) return;
7113
- clearInterval(this.timer);
7114
- this.timer = null;
7171
+ this.stopped = true;
7172
+ if (this.initialDelayTimer) {
7173
+ this.timers.clearTimeout(this.initialDelayTimer);
7174
+ this.initialDelayTimer = null;
7175
+ }
7176
+ if (this.intervalTimer) {
7177
+ this.timers.clearTimeout(this.intervalTimer);
7178
+ this.intervalTimer = null;
7179
+ }
7115
7180
  }
7116
- async uploadOnce() {
7181
+ /**
7182
+ * Drive a single upload pass. `trigger` is surfaced as a span attribute so
7183
+ * we can distinguish startup drain vs steady-state ticks in ScopeDB.
7184
+ */
7185
+ async uploadOnce(trigger = "manual") {
7117
7186
  const files = await this.findUploadCandidates();
7118
7187
  let uploaded = 0;
7119
7188
  for (const file of files.slice(0, this.options.maxFilesPerRun ?? DEFAULT_MAX_FILES_PER_RUN)) {
7120
- if (await this.uploadFile(file)) uploaded += 1;
7189
+ if (await this.uploadFile(file, trigger)) uploaded += 1;
7121
7190
  }
7122
7191
  return { attempted: files.length, uploaded };
7123
7192
  }
7193
+ scheduleNextTick() {
7194
+ if (this.stopped) return;
7195
+ const baseIntervalMs = this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS);
7196
+ const nextMs = baseIntervalMs + this.jitter.uploadIntervalJitterMs;
7197
+ this.intervalTimer = this.timers.setTimeout(() => {
7198
+ this.intervalTimer = null;
7199
+ if (this.stopped) return;
7200
+ void this.uploadOnce("interval");
7201
+ this.scheduleNextTick();
7202
+ }, nextMs);
7203
+ }
7124
7204
  async findUploadCandidates() {
7125
7205
  const traceDir = path14.join(this.options.machineDir, "traces");
7126
7206
  let names;
@@ -7147,13 +7227,16 @@ var DaemonTraceBundleUploader = class {
7147
7227
  }
7148
7228
  return candidates;
7149
7229
  }
7150
- async uploadFile(file) {
7230
+ async uploadFile(file, trigger) {
7151
7231
  const span = this.options.tracer?.startSpan("daemon.bundle.upload", {
7152
7232
  surface: "daemon",
7153
7233
  kind: "producer",
7154
7234
  attrs: {
7155
7235
  file_present: true,
7156
- worker_url_present: Boolean(this.options.workerUrl)
7236
+ worker_url_present: Boolean(this.options.workerUrl),
7237
+ upload_trigger: trigger,
7238
+ initial_delay_ms_bucket: bucketDelayMs(this.jitter.initialUploadDelayMs),
7239
+ interval_jitter_ms_bucket: bucketDelayMs(this.jitter.uploadIntervalJitterMs)
7157
7240
  }
7158
7241
  });
7159
7242
  try {
@@ -7229,7 +7312,7 @@ var DaemonTraceBundleUploader = class {
7229
7312
  }
7230
7313
  };
7231
7314
  function sha256Hex(body) {
7232
- return createHash2("sha256").update(body).digest("hex");
7315
+ return createHash3("sha256").update(body).digest("hex");
7233
7316
  }
7234
7317
  function readPositiveIntegerEnv2(name, fallback) {
7235
7318
  const value = process.env[name];
@@ -7239,6 +7322,7 @@ function readPositiveIntegerEnv2(name, fallback) {
7239
7322
  }
7240
7323
 
7241
7324
  // src/core.ts
7325
+ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
7242
7326
  var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
7243
7327
  function parseDaemonCliArgs(args) {
7244
7328
  let serverUrl = "";
@@ -7459,11 +7543,18 @@ var DaemonCore = class {
7459
7543
  if (!this.options.localTrace) return false;
7460
7544
  return process.env.SLOCK_DAEMON_LOCAL_TRACE !== "0";
7461
7545
  }
7546
+ resolveTraceJitter() {
7547
+ const lockId = this.machineLock?.lockId;
7548
+ return lockId ? computeTraceJitter(lockId) : NO_JITTER;
7549
+ }
7462
7550
  installLocalTraceSink(machineDir) {
7463
7551
  if (!this.shouldEnableLocalTrace()) return;
7552
+ const jitter = this.resolveTraceJitter();
7464
7553
  this.localTraceSink = new LocalRotatingTraceSink({
7465
7554
  machineDir,
7466
7555
  maxFileBytes: this.options.localTraceMaxFileBytes ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_BYTES", 5 * 1024 * 1024),
7556
+ maxFileAgeMs: this.options.localTraceMaxFileAgeMs ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_AGE_MS", 5 * 60 * 1e3),
7557
+ maxFileAgeJitterMs: jitter.maxFileAgeJitterMs,
7467
7558
  maxFiles: this.options.localTraceMaxFiles ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILES", 8)
7468
7559
  });
7469
7560
  this.tracer = new BasicTracer({
@@ -7474,15 +7565,16 @@ var DaemonCore = class {
7474
7565
  installTraceBundleUploader(machineDir) {
7475
7566
  if (!this.shouldEnableLocalTrace()) return;
7476
7567
  if (this.traceBundleUploader) return;
7477
- const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL;
7478
- if (!workerUrl) return;
7568
+ if (process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1") return;
7569
+ const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL || DEFAULT_TRACE_UPLOAD_URL;
7479
7570
  this.traceBundleUploader = new DaemonTraceBundleUploader({
7480
7571
  machineDir,
7481
7572
  serverUrl: this.options.serverUrl,
7482
7573
  apiKey: this.options.apiKey,
7483
7574
  workerUrl,
7484
7575
  tracer: this.tracer,
7485
- currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null
7576
+ currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null,
7577
+ lockId: this.machineLock?.lockId
7486
7578
  });
7487
7579
  this.traceBundleUploader.start();
7488
7580
  }
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-XW57NR6Y.js";
12
+ } from "./chunk-FG5JGA67.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-Z3PCMYZO.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-XW57NR6Y.js";
6
+ } from "./chunk-FG5JGA67.js";
7
7
  import "./chunk-Z3PCMYZO.js";
8
8
 
9
9
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.46.1",
3
+ "version": "0.46.2-staging.20260509074830",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"