@slock-ai/daemon 0.46.1 → 0.46.2-play.20260510065637

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.
@@ -4,8 +4,9 @@ import {
4
4
  buildFetchDispatcher,
5
5
  executeJsonRequest,
6
6
  executeResponseRequest,
7
- logger
8
- } from "./chunk-Z3PCMYZO.js";
7
+ logger,
8
+ resolveSlockHomePath
9
+ } from "./chunk-B7XIMLOT.js";
9
10
 
10
11
  // src/chat-bridge.ts
11
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -722,8 +723,7 @@ Use this ID in send_message's attachment_ids parameter to include it in a messag
722
723
  try {
723
724
  const fs = await import("fs");
724
725
  const path = await import("path");
725
- const os = await import("os");
726
- const cacheDir = path.join(os.homedir(), ".slock", "attachments");
726
+ const cacheDir = resolveSlockHomePath("attachments");
727
727
  fs.mkdirSync(cacheDir, { recursive: true });
728
728
  const existing = fs.readdirSync(cacheDir).find((f) => f.startsWith(attachment_id));
729
729
  if (existing) {
@@ -1,10 +1,14 @@
1
1
  import {
2
2
  DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
3
+ SLOCK_HOME_ENV,
3
4
  buildWebSocketOptions,
4
5
  executeJsonRequest,
5
6
  executeResponseRequest,
6
- logger
7
- } from "./chunk-Z3PCMYZO.js";
7
+ listLegacySlockStatePaths,
8
+ logger,
9
+ resolveSlockHome,
10
+ resolveSlockHomePath
11
+ } from "./chunk-B7XIMLOT.js";
8
12
 
9
13
  // src/core.ts
10
14
  import path15 from "path";
@@ -688,6 +692,7 @@ var DISPLAY_PLAN_CONFIG = {
688
692
  // src/agentProcessManager.ts
689
693
  import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
690
694
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
695
+ import { createHash as createHash2 } from "crypto";
691
696
  import path11 from "path";
692
697
  import os5 from "os";
693
698
 
@@ -1093,6 +1098,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1093
1098
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1094
1099
  - When done, summarize the result.
1095
1100
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1101
+ - For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
1102
+
1103
+ \`\`\`html
1104
+ <details>
1105
+ <summary>Evidence, logs, or edge cases</summary>
1106
+
1107
+ Detailed notes go here.
1108
+ </details>
1109
+ \`\`\`
1110
+
1111
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1096
1112
 
1097
1113
  ### Conversation etiquette
1098
1114
 
@@ -1272,6 +1288,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1272
1288
  ...ctx.config.envVars || {},
1273
1289
  ...extraEnv,
1274
1290
  ...runtimeContextEnv(ctx.config),
1291
+ [SLOCK_HOME_ENV]: resolveSlockHome(),
1275
1292
  SLOCK_AGENT_ID: ctx.agentId,
1276
1293
  ...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
1277
1294
  SLOCK_SERVER_URL: ctx.config.serverUrl,
@@ -2228,6 +2245,15 @@ function detectCodexModels(home = os2.homedir()) {
2228
2245
  import { spawn as spawn3 } from "child_process";
2229
2246
  import path5 from "path";
2230
2247
  import { writeFileSync as writeFileSync3 } from "fs";
2248
+ function buildCopilotSpawnEnv(ctx) {
2249
+ return {
2250
+ ...process.env,
2251
+ FORCE_COLOR: "0",
2252
+ NO_COLOR: "1",
2253
+ ...ctx.config.envVars || {},
2254
+ [SLOCK_HOME_ENV]: resolveSlockHome()
2255
+ };
2256
+ }
2231
2257
  var CopilotDriver = class {
2232
2258
  id = "copilot";
2233
2259
  lifecycle = {
@@ -2286,7 +2312,7 @@ var CopilotDriver = class {
2286
2312
  if (ctx.config.sessionId) {
2287
2313
  args.push(`--resume=${ctx.config.sessionId}`);
2288
2314
  }
2289
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
2315
+ const spawnEnv = buildCopilotSpawnEnv(ctx);
2290
2316
  const proc = spawn3("copilot", args, {
2291
2317
  cwd: ctx.workingDirectory,
2292
2318
  stdio: ["pipe", "pipe", "pipe"],
@@ -2383,6 +2409,15 @@ var CopilotDriver = class {
2383
2409
  import { spawn as spawn4, spawnSync } from "child_process";
2384
2410
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
2385
2411
  import path6 from "path";
2412
+ function buildCursorSpawnEnv(ctx) {
2413
+ return {
2414
+ ...process.env,
2415
+ FORCE_COLOR: "0",
2416
+ NO_COLOR: "1",
2417
+ ...ctx.config.envVars || {},
2418
+ [SLOCK_HOME_ENV]: resolveSlockHome()
2419
+ };
2420
+ }
2386
2421
  var CursorDriver = class {
2387
2422
  id = "cursor";
2388
2423
  lifecycle = {
@@ -2437,7 +2472,7 @@ var CursorDriver = class {
2437
2472
  args.push("--resume", ctx.config.sessionId);
2438
2473
  }
2439
2474
  args.push(ctx.prompt);
2440
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
2475
+ const spawnEnv = buildCursorSpawnEnv(ctx);
2441
2476
  const proc = spawn4("cursor-agent", args, {
2442
2477
  cwd: ctx.workingDirectory,
2443
2478
  stdio: ["pipe", "pipe", "pipe"],
@@ -2772,13 +2807,16 @@ var GeminiDriver = class {
2772
2807
  // src/drivers/kimi.ts
2773
2808
  import { randomUUID } from "crypto";
2774
2809
  import { spawn as spawn6 } from "child_process";
2775
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2810
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2776
2811
  import os3 from "os";
2777
2812
  import path8 from "path";
2778
2813
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
2779
2814
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
2780
2815
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
2781
2816
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
2817
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
2818
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
2819
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
2782
2820
  function parseToolArguments(raw) {
2783
2821
  if (typeof raw !== "string") return raw;
2784
2822
  try {
@@ -2787,6 +2825,73 @@ function parseToolArguments(raw) {
2787
2825
  return raw;
2788
2826
  }
2789
2827
  }
2828
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
2829
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2830
+ if (inlineConfig && inlineConfig.trim()) {
2831
+ return {
2832
+ raw: inlineConfig,
2833
+ explicitPath: null,
2834
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
2835
+ };
2836
+ }
2837
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
2838
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
2839
+ try {
2840
+ return {
2841
+ raw: readFileSync3(configPath, "utf8"),
2842
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2843
+ sourcePath: configPath
2844
+ };
2845
+ } catch {
2846
+ return {
2847
+ raw: null,
2848
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2849
+ sourcePath: configPath
2850
+ };
2851
+ }
2852
+ }
2853
+ function buildKimiSpawnEnv(env = process.env) {
2854
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
2855
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2856
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
2857
+ return spawnEnv;
2858
+ }
2859
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
2860
+ return {
2861
+ ...process.env,
2862
+ ...ctx.config.envVars || {},
2863
+ ...overrideEnv || {}
2864
+ };
2865
+ }
2866
+ function buildKimiLaunchOptions(ctx, opts = {}) {
2867
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
2868
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
2869
+ const args = [];
2870
+ let configFilePath = null;
2871
+ let configContent = null;
2872
+ if (source.explicitPath) {
2873
+ configFilePath = source.explicitPath;
2874
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
2875
+ configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
2876
+ configContent = source.raw;
2877
+ if (opts.writeGeneratedConfig !== false) {
2878
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
2879
+ chmodSync(configFilePath, 384);
2880
+ }
2881
+ }
2882
+ if (configFilePath) {
2883
+ args.push("--config-file", configFilePath);
2884
+ }
2885
+ if (ctx.config.model && ctx.config.model !== "default") {
2886
+ args.push("--model", ctx.config.model);
2887
+ }
2888
+ return {
2889
+ args,
2890
+ env: buildKimiSpawnEnv(env),
2891
+ configFilePath,
2892
+ configContent
2893
+ };
2894
+ }
2790
2895
  var KimiDriver = class {
2791
2896
  id = "kimi";
2792
2897
  lifecycle = {
@@ -2803,7 +2908,25 @@ var KimiDriver = class {
2803
2908
  };
2804
2909
  model = {
2805
2910
  detectedModelsVerifiedAs: "launchable",
2806
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
2911
+ toLaunchSpec: (modelId, ctx, opts) => {
2912
+ if (!ctx) return { args: ["--model", modelId] };
2913
+ const launchCtx = {
2914
+ ...ctx,
2915
+ config: {
2916
+ ...ctx.config,
2917
+ model: modelId
2918
+ }
2919
+ };
2920
+ const launch = buildKimiLaunchOptions(launchCtx, {
2921
+ home: opts?.home,
2922
+ writeGeneratedConfig: false
2923
+ });
2924
+ return {
2925
+ args: launch.args,
2926
+ env: launch.env,
2927
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
2928
+ };
2929
+ }
2807
2930
  };
2808
2931
  supportsStdinNotification = true;
2809
2932
  mcpToolPrefix = "";
@@ -2855,6 +2978,7 @@ var KimiDriver = class {
2855
2978
  }
2856
2979
  }
2857
2980
  }), "utf8");
2981
+ const launch = buildKimiLaunchOptions(ctx);
2858
2982
  const args = [
2859
2983
  "--wire",
2860
2984
  "--yolo",
@@ -2863,16 +2987,13 @@ var KimiDriver = class {
2863
2987
  "--mcp-config-file",
2864
2988
  mcpConfigPath,
2865
2989
  "--session",
2866
- this.sessionId
2990
+ this.sessionId,
2991
+ ...launch.args
2867
2992
  ];
2868
- if (ctx.config.model && ctx.config.model !== "default") {
2869
- args.push("--model", ctx.config.model);
2870
- }
2871
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
2872
2993
  const proc = spawn6("kimi", args, {
2873
2994
  cwd: ctx.workingDirectory,
2874
2995
  stdio: ["pipe", "pipe", "pipe"],
2875
- env: spawnEnv,
2996
+ env: launch.env,
2876
2997
  shell: process.platform === "win32"
2877
2998
  });
2878
2999
  proc.stdin?.write(JSON.stringify({
@@ -2989,14 +3110,9 @@ var KimiDriver = class {
2989
3110
  return detectKimiModels();
2990
3111
  }
2991
3112
  };
2992
- function detectKimiModels(home = os3.homedir()) {
2993
- const configPath = path8.join(home, ".kimi", "config.toml");
2994
- let raw;
2995
- try {
2996
- raw = readFileSync3(configPath, "utf8");
2997
- } catch {
2998
- return null;
2999
- }
3113
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
3114
+ const raw = readKimiConfigSource(home, opts.env).raw;
3115
+ if (raw === null) return null;
3000
3116
  const models = [];
3001
3117
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3002
3118
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3527,8 +3643,86 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
3527
3643
  }
3528
3644
  }
3529
3645
 
3646
+ // src/runtimeErrorDiagnostics.ts
3647
+ import { createHash } from "crypto";
3648
+ var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
3649
+ function buildRuntimeErrorDiagnosticEnvelope(message) {
3650
+ const rawMessage = String(message || "");
3651
+ const scrubbed = scrubRuntimeErrorDiagnosticText(rawMessage);
3652
+ const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
3653
+ const httpStatus = extractHttpStatus(rawMessage);
3654
+ const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
3655
+ const fingerprint = fingerprintRuntimeError(scrubbed);
3656
+ const spanAttrs = {
3657
+ runtime_error_class: runtimeErrorClass,
3658
+ runtime_error_fingerprint: fingerprint,
3659
+ runtime_error_message_present: rawMessage.length > 0,
3660
+ runtime_error_message_length_bucket: bucketLength(rawMessage.length),
3661
+ runtime_error_message_truncated: truncated
3662
+ };
3663
+ if (httpStatus !== null) {
3664
+ spanAttrs.runtime_error_http_status = httpStatus;
3665
+ }
3666
+ return {
3667
+ spanAttrs,
3668
+ eventAttrs: {
3669
+ ...spanAttrs,
3670
+ runtime_error_message_excerpt: excerpt
3671
+ }
3672
+ };
3673
+ }
3674
+ function scrubRuntimeErrorDiagnosticText(value) {
3675
+ return value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED_TOKEN]").replace(/\b(?:sk|sk-ant|sk-proj|xox[baprs]?)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED_TOKEN]").replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[REDACTED_EMAIL]").replace(/https?:\/\/[^\s"'<>]+/gi, (url) => redactUrlQuery(url)).replace(/\/Users\/[^\s"'<>:]+(?:\/[^\s"'<>:]+)*/g, "[REDACTED_PATH]").replace(/\/home\/[^\s"'<>:]+(?:\/[^\s"'<>:]+)*/g, "[REDACTED_PATH]").replace(/[A-Za-z]:\\Users\\[^\s"'<>:]+(?:\\[^\s"'<>:]+)*/g, "[REDACTED_PATH]");
3676
+ }
3677
+ function truncateDiagnosticText(value, maxChars) {
3678
+ if (value.length <= maxChars) return { value, truncated: false };
3679
+ return { value: value.slice(0, maxChars), truncated: true };
3680
+ }
3681
+ function extractHttpStatus(message) {
3682
+ const match = /\b(?:HTTP|status(?:\s+code)?|API\s+Error)[:\s]+([45]\d{2})\b/i.exec(message) ?? /\b([45]\d{2})\s+(?:Bad Request|Unauthorized|Forbidden|Not Found|Conflict|Too Many Requests|Internal Server Error|Service Unavailable)\b/i.exec(message);
3683
+ if (!match) return null;
3684
+ const status = Number(match[1]);
3685
+ return Number.isInteger(status) ? status : null;
3686
+ }
3687
+ function classifyRuntimeError(message, httpStatus) {
3688
+ const explicit = /\b([A-Z][A-Za-z0-9_]*(?:Error|Exception))\b/.exec(message);
3689
+ if (explicit) return explicit[1];
3690
+ if (httpStatus !== null) {
3691
+ if (httpStatus === 429) return "RateLimitError";
3692
+ if (httpStatus === 401 || httpStatus === 403) return "AuthError";
3693
+ if (httpStatus === 404) return "NotFoundError";
3694
+ if (httpStatus >= 500) return "ProviderServerError";
3695
+ return "ProviderApiError";
3696
+ }
3697
+ if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
3698
+ if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
3699
+ if (/\bnot found\b/i.test(message)) return "NotFoundError";
3700
+ return "RuntimeError";
3701
+ }
3702
+ function fingerprintRuntimeError(value) {
3703
+ const normalized = value.toLowerCase().replace(/[0-9a-f]{12,}/g, "<hex>").replace(/\b\d+\b/g, "<num>").replace(/\s+/g, " ").trim();
3704
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
3705
+ }
3706
+ function bucketLength(length) {
3707
+ if (length === 0) return "0";
3708
+ if (length < 1024) return "<1k";
3709
+ if (length < 4096) return "1k-4k";
3710
+ if (length < 16384) return "4k-16k";
3711
+ return "16k+";
3712
+ }
3713
+ function redactUrlQuery(value) {
3714
+ try {
3715
+ const url = new URL(value);
3716
+ if (url.search) url.search = "?[REDACTED_QUERY]";
3717
+ if (url.username) url.username = "[REDACTED_USER]";
3718
+ if (url.password) url.password = "[REDACTED_PASSWORD]";
3719
+ return url.toString();
3720
+ } catch {
3721
+ return value.replace(/\?.*$/, "?[REDACTED_QUERY]");
3722
+ }
3723
+ }
3724
+
3530
3725
  // src/agentProcessManager.ts
3531
- var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
3532
3726
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
3533
3727
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
3534
3728
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
@@ -4212,6 +4406,20 @@ function runtimeProfileNotificationFromMessage(message) {
4212
4406
  function runtimeProfileNotificationTitle(kind) {
4213
4407
  return kind === "migration" ? "Runtime Profile migration" : "Runtime Profile notice";
4214
4408
  }
4409
+ function hashRuntimeProfileKey(key) {
4410
+ if (!key) return void 0;
4411
+ return createHash2("sha256").update(key).digest("hex").slice(0, 16);
4412
+ }
4413
+ function runtimeProfileTurnControl(kind, key, source) {
4414
+ return {
4415
+ kind,
4416
+ source,
4417
+ keyHash: hashRuntimeProfileKey(key) ?? null,
4418
+ keyPresent: Boolean(key),
4419
+ injectedAtMs: Date.now(),
4420
+ migrationDoneToolObserved: false
4421
+ };
4422
+ }
4215
4423
  function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
4216
4424
  const next = [...lines];
4217
4425
  for (const rawLine of chunk.split(/\r?\n/)) {
@@ -4400,13 +4608,14 @@ var AgentProcessManager = class _AgentProcessManager {
4400
4608
  defaultAgentEnvVarsProvider;
4401
4609
  tracer;
4402
4610
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
4611
+ processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
4403
4612
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
4404
4613
  this.chatBridgePath = chatBridgePath;
4405
4614
  this.slockCliPath = opts.slockCliPath ?? "";
4406
4615
  this.sendToServer = sendToServer;
4407
4616
  this.daemonApiKey = daemonApiKey;
4408
4617
  this.serverUrl = opts.serverUrl;
4409
- this.dataDir = opts.dataDir || DATA_DIR;
4618
+ this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
4410
4619
  this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
4411
4620
  this.driverResolver = opts.driverResolver || getDriver;
4412
4621
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
@@ -4798,6 +5007,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4798
5007
  exitCode: null,
4799
5008
  exitSignal: null,
4800
5009
  expectedTerminationReason: null,
5010
+ runtimeProfileTurnControl: runtimeConfig.runtimeProfileControl ? runtimeProfileTurnControl(runtimeConfig.runtimeProfileControl.kind, runtimeConfig.runtimeProfileControl.key, "agent_config") : null,
4801
5011
  pendingTrajectory: null,
4802
5012
  gatedSteering: createGatedSteeringState()
4803
5013
  };
@@ -4872,7 +5082,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4872
5082
  clean_exit: code === 0,
4873
5083
  runtime_trace_active: Boolean(current?.runtimeTraceSpan),
4874
5084
  inbox_count: current?.inbox.length ?? 0,
4875
- pending_notification_count: current?.pendingNotificationCount ?? 0
5085
+ pending_notification_count: current?.pendingNotificationCount ?? 0,
5086
+ ...this.processExitTraceAttrs.get(proc)
4876
5087
  });
4877
5088
  logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
4878
5089
  });
@@ -4900,7 +5111,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4900
5111
  outcome: processEndedCleanly ? "process-exit" : "process-crash",
4901
5112
  expectedTerminationReason: ap.expectedTerminationReason || void 0,
4902
5113
  exitCode: finalCode,
4903
- exitSignal: finalSignal
5114
+ exitSignal: finalSignal,
5115
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
4904
5116
  });
4905
5117
  if (processEndedCleanly) {
4906
5118
  this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
@@ -5063,6 +5275,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5063
5275
  agentId,
5064
5276
  kind,
5065
5277
  key_present: Boolean(key),
5278
+ key_hash: hashRuntimeProfileKey(key),
5066
5279
  outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
5067
5280
  runtime: ap.config.runtime,
5068
5281
  session_id_present: Boolean(ap.sessionId),
@@ -5085,6 +5298,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5085
5298
  agentId,
5086
5299
  kind,
5087
5300
  key_present: Boolean(key),
5301
+ key_hash: hashRuntimeProfileKey(key),
5088
5302
  outcome: "queued_during_start",
5089
5303
  startup_pending: true,
5090
5304
  starting_inbox_count: pending.length,
@@ -5121,6 +5335,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5121
5335
  }
5122
5336
  this.clearCompactionWatchdog(ap);
5123
5337
  this.agents.delete(agentId);
5338
+ this.processExitTraceAttrs.set(ap.process, {
5339
+ stop_source: silent ? "daemon_internal" : "explicit_request",
5340
+ stop_wait_requested: wait,
5341
+ stop_silent: silent
5342
+ });
5124
5343
  ap.process.kill("SIGTERM");
5125
5344
  if (!silent) {
5126
5345
  this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
@@ -5411,7 +5630,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5411
5630
  attrs: {
5412
5631
  agentId,
5413
5632
  control_kind: kind,
5414
- key_present: Boolean(key)
5633
+ key_present: Boolean(key),
5634
+ key_hash: hashRuntimeProfileKey(key)
5415
5635
  }
5416
5636
  });
5417
5637
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5444,7 +5664,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5444
5664
  }
5445
5665
  if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
5446
5666
  ap.isIdle = false;
5447
- this.startRuntimeTrace(agentId, ap, "runtime-profile");
5667
+ this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
5448
5668
  const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
5449
5669
  span.end(written ? "ok" : "error", {
5450
5670
  attrs: {
@@ -5539,6 +5759,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5539
5759
  agentId,
5540
5760
  control_kind: control.kind,
5541
5761
  key_present: Boolean(control.key),
5762
+ key_hash: hashRuntimeProfileKey(control.key),
5542
5763
  launchId: launchId || void 0,
5543
5764
  source: "agent_config"
5544
5765
  }
@@ -5903,7 +6124,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5903
6124
  deliveryId: context.deliveryId,
5904
6125
  delivery_correlation_id: context.deliveryId,
5905
6126
  control_kind: notification.kind,
5906
- key_present: Boolean(notification.key)
6127
+ key_present: Boolean(notification.key),
6128
+ runtime_profile_control_kind: notification.kind,
6129
+ runtime_profile_key_hash: hashRuntimeProfileKey(notification.key),
6130
+ runtime_profile_key_present: Boolean(notification.key)
5907
6131
  };
5908
6132
  }
5909
6133
  return {
@@ -5914,8 +6138,63 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5914
6138
  delivery_correlation_id: context.deliveryId ?? first.message_id
5915
6139
  };
5916
6140
  }
6141
+ runtimeProfileTurnControlTraceAttrs(control) {
6142
+ if (!control) return {};
6143
+ const pendingAgeMs = Math.max(0, Date.now() - control.injectedAtMs);
6144
+ return {
6145
+ runtime_profile_control_kind: control.kind,
6146
+ runtime_profile_control_source: control.source,
6147
+ runtime_profile_key_hash: control.keyHash || void 0,
6148
+ runtime_profile_key_present: control.keyPresent,
6149
+ runtime_profile_pending_age_ms: pendingAgeMs,
6150
+ runtime_profile_requires_ack: control.kind === "migration",
6151
+ runtime_profile_migration_done_observed: control.kind === "migration" ? control.migrationDoneToolObserved : void 0
6152
+ };
6153
+ }
6154
+ activateRuntimeProfileTurnControl(ap, control) {
6155
+ ap.runtimeProfileTurnControl = control;
6156
+ }
6157
+ runtimeProfileTurnControlFromMessages(messages, source = "message") {
6158
+ const notifications = messages?.map((message) => runtimeProfileNotificationFromMessage(message)).filter((candidate) => Boolean(candidate)) ?? [];
6159
+ const notification = notifications.find((candidate) => candidate.kind === "migration") ?? notifications[0];
6160
+ if (!notification) return null;
6161
+ return runtimeProfileTurnControl(notification.kind, notification.key, source);
6162
+ }
6163
+ noteRuntimeProfileToolCall(agentId, ap, toolName) {
6164
+ const control = ap.runtimeProfileTurnControl;
6165
+ if (!control || control.kind !== "migration") return;
6166
+ if (!toolName.includes("runtime_profile_migration_done")) return;
6167
+ control.migrationDoneToolObserved = true;
6168
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration_done_tool.observed", {
6169
+ ...this.runtimeProfileTurnControlTraceAttrs(control),
6170
+ tool: toolName
6171
+ });
6172
+ }
6173
+ finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
6174
+ const control = ap.runtimeProfileTurnControl;
6175
+ if (!control) return {};
6176
+ const attrs = this.runtimeProfileTurnControlTraceAttrs(control);
6177
+ if (control.kind === "migration" && !control.migrationDoneToolObserved) {
6178
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration.turn_without_ack", {
6179
+ ...attrs,
6180
+ terminal,
6181
+ inbox_count: ap.inbox.length,
6182
+ pending_notification_count: ap.pendingNotificationCount
6183
+ });
6184
+ }
6185
+ ap.runtimeProfileTurnControl = null;
6186
+ return {
6187
+ ...attrs,
6188
+ runtime_profile_turn_terminal: terminal,
6189
+ runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
6190
+ };
6191
+ }
5917
6192
  startRuntimeTrace(agentId, ap, reason, messages) {
5918
6193
  if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
6194
+ const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
6195
+ if (messageControl) {
6196
+ this.activateRuntimeProfileTurnControl(ap, messageControl);
6197
+ }
5919
6198
  const span = this.tracer.startSpan("daemon.runtime.turn", {
5920
6199
  surface: "daemon",
5921
6200
  kind: "internal",
@@ -5925,10 +6204,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5925
6204
  model: ap.config.model,
5926
6205
  reason,
5927
6206
  hasSession: Boolean(ap.sessionId),
5928
- ...this.messagesTraceAttrs(messages)
6207
+ ...this.messagesTraceAttrs(messages),
6208
+ ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
5929
6209
  }
5930
6210
  });
5931
- span.addEvent("daemon.turn.started", { reason, ...this.messagesTraceAttrs(messages) });
6211
+ span.addEvent("daemon.turn.started", {
6212
+ reason,
6213
+ ...this.messagesTraceAttrs(messages),
6214
+ ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6215
+ });
5932
6216
  ap.runtimeTraceSpan = span;
5933
6217
  return span;
5934
6218
  }
@@ -6034,7 +6318,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6034
6318
  ageMs: staleForMs,
6035
6319
  lastActivity: ap.lastActivity,
6036
6320
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6037
- lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
6321
+ lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6322
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6038
6323
  });
6039
6324
  this.broadcastActivity(agentId, "error", diagnostic.detail);
6040
6325
  return true;
@@ -6065,7 +6350,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6065
6350
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6066
6351
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6067
6352
  pendingMessages: ap.inbox.length,
6068
- recovery: "terminate_for_queued_message"
6353
+ recovery: "terminate_for_queued_message",
6354
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6069
6355
  });
6070
6356
  ap.expectedTerminationReason = "stalled_recovery";
6071
6357
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
@@ -6074,6 +6360,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6074
6360
  );
6075
6361
  this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
6076
6362
  try {
6363
+ this.processExitTraceAttrs.set(ap.process, {
6364
+ stop_source: "stalled_recovery",
6365
+ expectedTerminationReason: "stalled_recovery",
6366
+ queued_messages_count: ap.inbox.length
6367
+ });
6077
6368
  ap.process.kill("SIGTERM");
6078
6369
  } catch (err) {
6079
6370
  const reason = err instanceof Error ? err.message : String(err);
@@ -6122,6 +6413,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6122
6413
  if (ap) {
6123
6414
  ap.gatedSteering.outstandingToolUses++;
6124
6415
  this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6416
+ this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
6125
6417
  this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
6126
6418
  this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
6127
6419
  event: "tool_call",
@@ -6211,11 +6503,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6211
6503
  this.broadcastActivity(agentId, "online", "Idle");
6212
6504
  }
6213
6505
  }
6214
- this.endRuntimeTrace(ap, "ok", { outcome: "turn-completed" });
6506
+ this.endRuntimeTrace(ap, "ok", {
6507
+ outcome: "turn-completed",
6508
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
6509
+ });
6215
6510
  if (ap.driver.terminateProcessOnTurnEnd) {
6216
6511
  ap.expectedTerminationReason = "turn_end";
6217
6512
  logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
6218
6513
  try {
6514
+ this.processExitTraceAttrs.set(ap.process, {
6515
+ stop_source: "turn_end",
6516
+ expectedTerminationReason: "turn_end"
6517
+ });
6219
6518
  ap.process.kill("SIGTERM");
6220
6519
  } catch (err) {
6221
6520
  const reason = err instanceof Error ? err.message : String(err);
@@ -6233,6 +6532,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6233
6532
  this.flushPendingTrajectory(agentId);
6234
6533
  if (ap) ap.lastRuntimeError = event.message;
6235
6534
  if (ap) {
6535
+ const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
6236
6536
  this.setGatedSteeringPhase(agentId, ap, "error", { event: "error" });
6237
6537
  if (ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message)) {
6238
6538
  this.requeueGatedInFlightBatch(agentId, ap, "thinking_block_mutation_error");
@@ -6246,8 +6546,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6246
6546
  `[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
6247
6547
  );
6248
6548
  }
6249
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", { message: event.message });
6250
- this.endRuntimeTrace(ap, "error", { outcome: "runtime-error", errorMessage: event.message });
6549
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
6550
+ this.endRuntimeTrace(ap, "error", {
6551
+ outcome: "runtime-error",
6552
+ ...runtimeErrorDiagnostics.spanAttrs,
6553
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
6554
+ });
6251
6555
  if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
6252
6556
  ap.isIdle = true;
6253
6557
  ap.pendingNotificationCount = 0;
@@ -6721,11 +7025,10 @@ var ReminderCache = class {
6721
7025
  };
6722
7026
 
6723
7027
  // src/machineLock.ts
6724
- import { createHash, randomUUID as randomUUID2 } from "crypto";
7028
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
6725
7029
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
6726
7030
  import os6 from "os";
6727
7031
  import path12 from "path";
6728
- var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
6729
7032
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
6730
7033
  var DaemonMachineLockConflictError = class extends Error {
6731
7034
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -6738,11 +7041,14 @@ var DaemonMachineLockConflictError = class extends Error {
6738
7041
  }
6739
7042
  };
6740
7043
  function apiKeyFingerprint(apiKey) {
6741
- return createHash("sha256").update(apiKey).digest("hex");
7044
+ return createHash3("sha256").update(apiKey).digest("hex");
6742
7045
  }
6743
7046
  function getDaemonMachineLockId(apiKey) {
6744
7047
  return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
6745
7048
  }
7049
+ function resolveDefaultMachineStateRoot() {
7050
+ return resolveSlockHomePath("machines");
7051
+ }
6746
7052
  function ownerPath(lockDir) {
6747
7053
  return path12.join(lockDir, "owner.json");
6748
7054
  }
@@ -6771,7 +7077,7 @@ function isProcessAlive(pid) {
6771
7077
  }
6772
7078
  }
6773
7079
  function acquireDaemonMachineLock(options) {
6774
- const rootDir = options.rootDir ?? DEFAULT_MACHINE_STATE_ROOT;
7080
+ const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
6775
7081
  const fingerprint = apiKeyFingerprint(options.apiKey);
6776
7082
  const lockId = getDaemonMachineLockId(options.apiKey);
6777
7083
  const machineDir = path12.join(rootDir, lockId);
@@ -6830,6 +7136,7 @@ function acquireDaemonMachineLock(options) {
6830
7136
  import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
6831
7137
  import path13 from "path";
6832
7138
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
7139
+ var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
6833
7140
  var DEFAULT_MAX_FILES = 8;
6834
7141
  var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
6835
7142
  "serverId",
@@ -6843,17 +7150,37 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
6843
7150
  "deliveryCorrelationId",
6844
7151
  "delivery_correlation_id"
6845
7152
  ]);
7153
+ var DIAGNOSTIC_ERROR_ATTRS = /* @__PURE__ */ new Set([
7154
+ "runtime_error_class",
7155
+ "runtime_error_fingerprint",
7156
+ "runtime_error_http_status",
7157
+ "runtime_error_message_present",
7158
+ "runtime_error_message_length_bucket",
7159
+ "runtime_error_message_truncated",
7160
+ "runtime_error_message_excerpt"
7161
+ ]);
6846
7162
  var LocalRotatingTraceSink = class {
6847
7163
  traceDir;
6848
7164
  maxFileBytes;
7165
+ maxFileAgeMs;
6849
7166
  maxFiles;
7167
+ nowMsProvider;
6850
7168
  currentFile = null;
7169
+ currentFileOpenedAtMs = null;
6851
7170
  currentSize = 0;
6852
7171
  sequence = 0;
6853
7172
  constructor(options) {
6854
7173
  this.traceDir = path13.join(options.machineDir, "traces");
6855
7174
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
7175
+ const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
7176
+ const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
7177
+ this.maxFileAgeMs = baseAgeMs + ageJitterMs;
6856
7178
  this.maxFiles = Math.max(1, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES));
7179
+ this.nowMsProvider = options.nowMsProvider ?? Date.now;
7180
+ }
7181
+ /** Exposed for observability — the effective rotation age after jitter. */
7182
+ getMaxFileAgeMs() {
7183
+ return this.maxFileAgeMs;
6857
7184
  }
6858
7185
  record(span) {
6859
7186
  try {
@@ -6870,13 +7197,16 @@ var LocalRotatingTraceSink = class {
6870
7197
  }
6871
7198
  ensureFile(nextBytes) {
6872
7199
  mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
6873
- if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes) {
7200
+ const nowMs = this.nowMsProvider();
7201
+ const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
7202
+ if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
6874
7203
  this.currentFile = path13.join(
6875
7204
  this.traceDir,
6876
- `daemon-trace-${safeTimestamp(Date.now())}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
7205
+ `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
6877
7206
  );
6878
7207
  writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
6879
7208
  this.currentSize = statSync4(this.currentFile).size;
7209
+ this.currentFileOpenedAtMs = nowMs;
6880
7210
  this.pruneOldFiles();
6881
7211
  }
6882
7212
  }
@@ -6926,6 +7256,11 @@ function sanitizeAttrs(attrs) {
6926
7256
  sanitized[key] = sanitizeValue(value);
6927
7257
  continue;
6928
7258
  }
7259
+ if (isDiagnosticErrorAttr(key)) {
7260
+ if (value === null || value === void 0 || value === "") continue;
7261
+ sanitized[key] = sanitizeValue(value);
7262
+ continue;
7263
+ }
6929
7264
  if (shouldDropAttr(key)) continue;
6930
7265
  sanitized[key] = sanitizeValue(value);
6931
7266
  }
@@ -6959,9 +7294,12 @@ function shouldDropAttr(key) {
6959
7294
  function isDiagnosticIdAttr(key) {
6960
7295
  return DIAGNOSTIC_ID_ATTRS.has(key);
6961
7296
  }
7297
+ function isDiagnosticErrorAttr(key) {
7298
+ return DIAGNOSTIC_ERROR_ATTRS.has(key);
7299
+ }
6962
7300
 
6963
7301
  // src/traceBundleUpload.ts
6964
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
7302
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
6965
7303
  import { gzipSync } from "zlib";
6966
7304
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
6967
7305
  import path14 from "path";
@@ -7090,6 +7428,35 @@ async function uploadWithSignedCapability({
7090
7428
  return { capability, session, uploadResponse };
7091
7429
  }
7092
7430
 
7431
+ // src/traceJitter.ts
7432
+ import { createHash as createHash4 } from "crypto";
7433
+ var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
7434
+ var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
7435
+ var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
7436
+ function computeTraceJitter(lockId) {
7437
+ const seed = createHash4("sha256").update(lockId).digest();
7438
+ return {
7439
+ initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
7440
+ uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
7441
+ maxFileAgeJitterMs: seed.readUInt32BE(8) % MAX_FILE_AGE_JITTER_SPAN_MS
7442
+ };
7443
+ }
7444
+ var NO_JITTER = {
7445
+ initialUploadDelayMs: 0,
7446
+ uploadIntervalJitterMs: 0,
7447
+ maxFileAgeJitterMs: 0
7448
+ };
7449
+ function bucketDelayMs(delayMs) {
7450
+ if (delayMs < 1e3) return "0-1s";
7451
+ if (delayMs < 5e3) return "1-5s";
7452
+ if (delayMs < 15e3) return "5-15s";
7453
+ if (delayMs < 3e4) return "15-30s";
7454
+ if (delayMs < 6e4) return "30-60s";
7455
+ if (delayMs < 3e5) return "60s-5m";
7456
+ if (delayMs < 6e5) return "5-10m";
7457
+ return "10m+";
7458
+ }
7459
+
7093
7460
  // src/traceBundleUpload.ts
7094
7461
  var TRACE_UPLOAD_SCOPE = "daemon-trace-bundle:create";
7095
7462
  var DEFAULT_UPLOAD_INTERVAL_MS = 5 * 60 * 1e3;
@@ -7097,30 +7464,66 @@ var DEFAULT_MIN_FILE_AGE_MS = 60 * 1e3;
7097
7464
  var DEFAULT_MAX_FILES_PER_RUN = 4;
7098
7465
  var DaemonTraceBundleUploader = class {
7099
7466
  options;
7100
- timer = null;
7467
+ jitter;
7468
+ timers;
7469
+ initialDelayTimer = null;
7470
+ intervalTimer = null;
7471
+ stopped = false;
7101
7472
  constructor(options) {
7102
7473
  this.options = options;
7474
+ this.jitter = options.jitter ?? (options.lockId ? computeTraceJitter(options.lockId) : NO_JITTER);
7475
+ this.timers = options.timers ?? {
7476
+ setTimeout: globalThis.setTimeout.bind(globalThis),
7477
+ setInterval: globalThis.setInterval.bind(globalThis),
7478
+ clearTimeout: globalThis.clearTimeout.bind(globalThis),
7479
+ clearInterval: globalThis.clearInterval.bind(globalThis)
7480
+ };
7103
7481
  }
7104
7482
  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));
7483
+ if (this.stopped) return;
7484
+ if (this.initialDelayTimer || this.intervalTimer) return;
7485
+ const initialDelayMs = this.jitter.initialUploadDelayMs;
7486
+ this.initialDelayTimer = this.timers.setTimeout(() => {
7487
+ this.initialDelayTimer = null;
7488
+ if (this.stopped) return;
7489
+ void this.uploadOnce("initial");
7490
+ this.scheduleNextTick();
7491
+ }, initialDelayMs);
7110
7492
  }
7111
7493
  stop() {
7112
- if (!this.timer) return;
7113
- clearInterval(this.timer);
7114
- this.timer = null;
7494
+ this.stopped = true;
7495
+ if (this.initialDelayTimer) {
7496
+ this.timers.clearTimeout(this.initialDelayTimer);
7497
+ this.initialDelayTimer = null;
7498
+ }
7499
+ if (this.intervalTimer) {
7500
+ this.timers.clearTimeout(this.intervalTimer);
7501
+ this.intervalTimer = null;
7502
+ }
7115
7503
  }
7116
- async uploadOnce() {
7504
+ /**
7505
+ * Drive a single upload pass. `trigger` is surfaced as a span attribute so
7506
+ * we can distinguish startup drain vs steady-state ticks in ScopeDB.
7507
+ */
7508
+ async uploadOnce(trigger = "manual") {
7117
7509
  const files = await this.findUploadCandidates();
7118
7510
  let uploaded = 0;
7119
7511
  for (const file of files.slice(0, this.options.maxFilesPerRun ?? DEFAULT_MAX_FILES_PER_RUN)) {
7120
- if (await this.uploadFile(file)) uploaded += 1;
7512
+ if (await this.uploadFile(file, trigger)) uploaded += 1;
7121
7513
  }
7122
7514
  return { attempted: files.length, uploaded };
7123
7515
  }
7516
+ scheduleNextTick() {
7517
+ if (this.stopped) return;
7518
+ const baseIntervalMs = this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS);
7519
+ const nextMs = baseIntervalMs + this.jitter.uploadIntervalJitterMs;
7520
+ this.intervalTimer = this.timers.setTimeout(() => {
7521
+ this.intervalTimer = null;
7522
+ if (this.stopped) return;
7523
+ void this.uploadOnce("interval");
7524
+ this.scheduleNextTick();
7525
+ }, nextMs);
7526
+ }
7124
7527
  async findUploadCandidates() {
7125
7528
  const traceDir = path14.join(this.options.machineDir, "traces");
7126
7529
  let names;
@@ -7147,13 +7550,16 @@ var DaemonTraceBundleUploader = class {
7147
7550
  }
7148
7551
  return candidates;
7149
7552
  }
7150
- async uploadFile(file) {
7553
+ async uploadFile(file, trigger) {
7151
7554
  const span = this.options.tracer?.startSpan("daemon.bundle.upload", {
7152
7555
  surface: "daemon",
7153
7556
  kind: "producer",
7154
7557
  attrs: {
7155
7558
  file_present: true,
7156
- worker_url_present: Boolean(this.options.workerUrl)
7559
+ worker_url_present: Boolean(this.options.workerUrl),
7560
+ upload_trigger: trigger,
7561
+ initial_delay_ms_bucket: bucketDelayMs(this.jitter.initialUploadDelayMs),
7562
+ interval_jitter_ms_bucket: bucketDelayMs(this.jitter.uploadIntervalJitterMs)
7157
7563
  }
7158
7564
  });
7159
7565
  try {
@@ -7229,7 +7635,7 @@ var DaemonTraceBundleUploader = class {
7229
7635
  }
7230
7636
  };
7231
7637
  function sha256Hex(body) {
7232
- return createHash2("sha256").update(body).digest("hex");
7638
+ return createHash5("sha256").update(body).digest("hex");
7233
7639
  }
7234
7640
  function readPositiveIntegerEnv2(name, fallback) {
7235
7641
  const value = process.env[name];
@@ -7239,6 +7645,7 @@ function readPositiveIntegerEnv2(name, fallback) {
7239
7645
  }
7240
7646
 
7241
7647
  // src/core.ts
7648
+ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
7242
7649
  var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
7243
7650
  function parseDaemonCliArgs(args) {
7244
7651
  let serverUrl = "";
@@ -7407,6 +7814,7 @@ var DaemonCore = class {
7407
7814
  daemonVersion;
7408
7815
  chatBridgePath;
7409
7816
  slockCliPath;
7817
+ slockHome;
7410
7818
  runtimeDetector;
7411
7819
  agentManager;
7412
7820
  connection;
@@ -7421,6 +7829,8 @@ var DaemonCore = class {
7421
7829
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
7422
7830
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
7423
7831
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
7832
+ this.slockHome = resolveSlockHome();
7833
+ process.env[SLOCK_HOME_ENV] = this.slockHome;
7424
7834
  this.injectedTracer = Boolean(options.tracer);
7425
7835
  this.tracer = options.tracer ?? noopTracer;
7426
7836
  this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
@@ -7430,7 +7840,7 @@ var DaemonCore = class {
7430
7840
  });
7431
7841
  let connection;
7432
7842
  const agentManagerOptions = {
7433
- dataDir: options.dataDir,
7843
+ dataDir: options.dataDir ?? resolveSlockHomePath("agents", this.slockHome),
7434
7844
  serverUrl: options.serverUrl,
7435
7845
  defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
7436
7846
  slockCliPath: this.slockCliPath,
@@ -7452,18 +7862,25 @@ var DaemonCore = class {
7452
7862
  resolveMachineStateRoot() {
7453
7863
  if (this.options.machineStateDir) return this.options.machineStateDir;
7454
7864
  if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
7455
- return DEFAULT_MACHINE_STATE_ROOT;
7865
+ return resolveDefaultMachineStateRoot();
7456
7866
  }
7457
7867
  shouldEnableLocalTrace() {
7458
7868
  if (this.injectedTracer) return false;
7459
7869
  if (!this.options.localTrace) return false;
7460
7870
  return process.env.SLOCK_DAEMON_LOCAL_TRACE !== "0";
7461
7871
  }
7872
+ resolveTraceJitter() {
7873
+ const lockId = this.machineLock?.lockId;
7874
+ return lockId ? computeTraceJitter(lockId) : NO_JITTER;
7875
+ }
7462
7876
  installLocalTraceSink(machineDir) {
7463
7877
  if (!this.shouldEnableLocalTrace()) return;
7878
+ const jitter = this.resolveTraceJitter();
7464
7879
  this.localTraceSink = new LocalRotatingTraceSink({
7465
7880
  machineDir,
7466
7881
  maxFileBytes: this.options.localTraceMaxFileBytes ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_BYTES", 5 * 1024 * 1024),
7882
+ maxFileAgeMs: this.options.localTraceMaxFileAgeMs ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_AGE_MS", 5 * 60 * 1e3),
7883
+ maxFileAgeJitterMs: jitter.maxFileAgeJitterMs,
7467
7884
  maxFiles: this.options.localTraceMaxFiles ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILES", 8)
7468
7885
  });
7469
7886
  this.tracer = new BasicTracer({
@@ -7474,20 +7891,27 @@ var DaemonCore = class {
7474
7891
  installTraceBundleUploader(machineDir) {
7475
7892
  if (!this.shouldEnableLocalTrace()) return;
7476
7893
  if (this.traceBundleUploader) return;
7477
- const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL;
7478
- if (!workerUrl) return;
7894
+ if (process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1") return;
7895
+ const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL || DEFAULT_TRACE_UPLOAD_URL;
7479
7896
  this.traceBundleUploader = new DaemonTraceBundleUploader({
7480
7897
  machineDir,
7481
7898
  serverUrl: this.options.serverUrl,
7482
7899
  apiKey: this.options.apiKey,
7483
7900
  workerUrl,
7484
7901
  tracer: this.tracer,
7485
- currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null
7902
+ currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null,
7903
+ lockId: this.machineLock?.lockId
7486
7904
  });
7487
7905
  this.traceBundleUploader.start();
7488
7906
  }
7489
7907
  start() {
7490
7908
  logger.info("[Slock Daemon] Starting...");
7909
+ logger.info(`[Slock Daemon] ${SLOCK_HOME_ENV}=${this.slockHome}`);
7910
+ for (const legacy of listLegacySlockStatePaths(this.slockHome)) {
7911
+ logger.warn(
7912
+ `[Slock Daemon] Legacy Slock state exists outside ${SLOCK_HOME_ENV}: ${legacy.path}. This daemon will use ${legacy.destination}; migrate manually if that ${legacy.description} should move with this installation.`
7913
+ );
7914
+ }
7491
7915
  if (!this.machineLock) {
7492
7916
  this.machineLock = acquireDaemonMachineLock({
7493
7917
  apiKey: this.options.apiKey,
@@ -207,6 +207,45 @@ async function executeResponseRequest(url, init, {
207
207
  }
208
208
  }
209
209
 
210
+ // src/slockHome.ts
211
+ import os from "os";
212
+ import path from "path";
213
+ import { existsSync } from "fs";
214
+ var SLOCK_HOME_ENV = "SLOCK_HOME";
215
+ function resolveDefaultSlockHome(homeDir = os.homedir()) {
216
+ return path.resolve(path.join(homeDir, ".slock"));
217
+ }
218
+ function resolveSlockHome(env = process.env, homeDir = os.homedir()) {
219
+ const raw = env[SLOCK_HOME_ENV]?.trim();
220
+ const root = raw && raw.length > 0 ? raw : resolveDefaultSlockHome(homeDir);
221
+ return path.resolve(root);
222
+ }
223
+ function resolveSlockHomePath(childPath, slockHome = resolveSlockHome()) {
224
+ return path.join(slockHome, childPath);
225
+ }
226
+ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.homedir()) {
227
+ const defaultHome = resolveDefaultSlockHome(homeDir);
228
+ if (path.resolve(slockHome) === defaultHome) return [];
229
+ const candidates = [
230
+ {
231
+ path: path.join(defaultHome, "agents"),
232
+ destination: path.join(slockHome, "agents"),
233
+ description: "agent workspaces and per-agent runtime wrapper state"
234
+ },
235
+ {
236
+ path: path.join(defaultHome, "machines"),
237
+ destination: path.join(slockHome, "machines"),
238
+ description: "daemon machine locks, local traces, and machine-scoped state"
239
+ },
240
+ {
241
+ path: path.join(defaultHome, "attachments"),
242
+ destination: path.join(slockHome, "attachments"),
243
+ description: "chat bridge attachment download cache"
244
+ }
245
+ ];
246
+ return candidates.filter((candidate) => existsSync(candidate.path));
247
+ }
248
+
210
249
  export {
211
250
  subscribeDaemonLogs,
212
251
  logger,
@@ -214,5 +253,9 @@ export {
214
253
  buildFetchDispatcher,
215
254
  DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
216
255
  executeJsonRequest,
217
- executeResponseRequest
256
+ executeResponseRequest,
257
+ SLOCK_HOME_ENV,
258
+ resolveSlockHome,
259
+ resolveSlockHomePath,
260
+ listLegacySlockStatePaths
218
261
  };
package/dist/core.js CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-XW57NR6Y.js";
12
+ } from "./chunk-7EZVMA2D.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
- } from "./chunk-Z3PCMYZO.js";
15
+ } from "./chunk-B7XIMLOT.js";
16
16
  export {
17
17
  DAEMON_CLI_USAGE,
18
18
  DaemonCore,
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-XW57NR6Y.js";
7
- import "./chunk-Z3PCMYZO.js";
6
+ } from "./chunk-7EZVMA2D.js";
7
+ import "./chunk-B7XIMLOT.js";
8
8
 
9
9
  // src/index.ts
10
10
  var parsedArgs = parseDaemonCliArgs(process.argv.slice(2));
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-play.20260510065637",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"