@slock-ai/daemon 0.46.2 → 0.47.0-staging.20260511155804

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,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";
@@ -576,6 +580,66 @@ function summarizeToolInput(toolName, input) {
576
580
  // ../shared/src/attachmentPreview.ts
577
581
  var CSV_PREVIEW_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
578
582
 
583
+ // ../shared/src/actionCards.ts
584
+ import { z } from "zod";
585
+ var uuidSchema = z.uuid();
586
+ var idOrHandleSchema = z.string().min(1).max(120);
587
+ var draftHintSchema = z.string().trim().max(2e3).optional().describe(
588
+ "Why the agent prepared this for you. Shows below the form on the card; not the action itself."
589
+ );
590
+ var channelCreateOperationSchema = z.object({
591
+ type: z.literal("channel:create"),
592
+ name: z.string().trim().min(1).max(80),
593
+ visibility: z.enum(["public", "private"]).default("public"),
594
+ description: z.string().trim().max(500).optional(),
595
+ /**
596
+ * Humans to add to the channel on creation. Each entry is a handle
597
+ * (`@alice` or bare `alice`) or a UUID. Server resolves via
598
+ * `resolveUserByName` at prepare time; resolved UUIDs are stored.
599
+ */
600
+ initialHumans: z.array(idOrHandleSchema).max(64).optional(),
601
+ /**
602
+ * Agents to add to the channel on creation. Each entry is a handle
603
+ * (`@scout` or bare `scout`) or a UUID. Server resolves via
604
+ * `resolveAgentByName` at prepare time; resolved UUIDs are stored.
605
+ */
606
+ initialAgents: z.array(idOrHandleSchema).max(64).optional(),
607
+ draftHint: draftHintSchema
608
+ });
609
+ var agentCreateOperationSchema = z.object({
610
+ type: z.literal("agent:create"),
611
+ name: z.string().trim().min(1).max(60),
612
+ description: z.string().trim().max(500).optional(),
613
+ /**
614
+ * Agent can only suggest semantic intent (name + description). Technical
615
+ * configuration (which computer / runtime / model / reasoning effort) is
616
+ * a user prerogative — the human picks those in the create dialog when
617
+ * they click "Create Agent" on the card. Per stdrc 2026-05-10
618
+ * #proj-approval msg=ae4ecedd: "为什么 computer、runtime 和 model 还是
619
+ * 帮用户选了?" — don't let the agent prefill those.
620
+ */
621
+ draftHint: draftHintSchema
622
+ });
623
+ var channelAddMemberOperationSchema = z.object({
624
+ type: z.literal("channel:add_member"),
625
+ /**
626
+ * Target channel. Handle (`#general` or bare `general`) or UUID.
627
+ * Server resolves via `resolveChannelByName` at prepare time; the
628
+ * resolved UUID is stored in the card metadata.
629
+ */
630
+ channel: idOrHandleSchema,
631
+ /** Same resolution rule as `channelCreateOperationSchema.initialHumans`. */
632
+ humans: z.array(idOrHandleSchema).max(64).optional(),
633
+ /** Same resolution rule as `channelCreateOperationSchema.initialAgents`. */
634
+ agents: z.array(idOrHandleSchema).max(64).optional(),
635
+ draftHint: draftHintSchema
636
+ });
637
+ var actionCardActionSchema = z.discriminatedUnion("type", [
638
+ channelCreateOperationSchema,
639
+ agentCreateOperationSchema,
640
+ channelAddMemberOperationSchema
641
+ ]);
642
+
579
643
  // ../shared/src/testing/failpoints.ts
580
644
  var NoopFailpointRegistry = class {
581
645
  get enabled() {
@@ -686,8 +750,9 @@ var DISPLAY_PLAN_CONFIG = {
686
750
  };
687
751
 
688
752
  // src/agentProcessManager.ts
689
- import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
753
+ import { mkdirSync as mkdirSync4, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
690
754
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
755
+ import { createHash as createHash2 } from "crypto";
691
756
  import path11 from "path";
692
757
  import os5 from "os";
693
758
 
@@ -796,6 +861,7 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
796
861
  21. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
797
862
  22. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
798
863
  23. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
864
+ 24. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
799
865
 
800
866
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
801
867
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -1272,6 +1338,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1272
1338
  ...ctx.config.envVars || {},
1273
1339
  ...extraEnv,
1274
1340
  ...runtimeContextEnv(ctx.config),
1341
+ [SLOCK_HOME_ENV]: resolveSlockHome(),
1275
1342
  SLOCK_AGENT_ID: ctx.agentId,
1276
1343
  ...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
1277
1344
  SLOCK_SERVER_URL: ctx.config.serverUrl,
@@ -1705,7 +1772,7 @@ var ClaudeDriver = class {
1705
1772
 
1706
1773
  // src/drivers/codex.ts
1707
1774
  import { spawn as spawn2, execSync } from "child_process";
1708
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1775
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1709
1776
  import os2 from "os";
1710
1777
  import path4 from "path";
1711
1778
  function getCodexNotificationErrorMessage(params) {
@@ -1734,11 +1801,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
1734
1801
  );
1735
1802
  }
1736
1803
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
1804
+ function resolveWindowsSandboxRunner(deps = {}) {
1805
+ const homeDir = deps.homeDir ?? os2.homedir();
1806
+ const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
1807
+ if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
1808
+ try {
1809
+ const files = readdirSync2(sandboxBinDir);
1810
+ const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
1811
+ if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
1812
+ } catch {
1813
+ }
1814
+ return null;
1815
+ }
1737
1816
  function resolveCodexCommand(deps = {}) {
1738
1817
  const pathCommand = resolveCommandOnPath("codex", deps);
1739
1818
  if (pathCommand) return pathCommand;
1740
- if ((deps.platform ?? process.platform) !== "darwin") return null;
1741
- return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1819
+ const platform = deps.platform ?? process.platform;
1820
+ if (platform === "darwin") {
1821
+ return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1822
+ }
1823
+ if (platform === "win32") {
1824
+ return resolveWindowsSandboxRunner(deps);
1825
+ }
1826
+ return null;
1742
1827
  }
1743
1828
  function probeCodex(deps = {}) {
1744
1829
  if ((deps.platform ?? process.platform) === "win32") {
@@ -1779,6 +1864,13 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1779
1864
  }
1780
1865
  }
1781
1866
  if (!codexEntry) {
1867
+ const sandboxRunner = resolveWindowsSandboxRunner(deps);
1868
+ if (sandboxRunner) {
1869
+ return {
1870
+ command: sandboxRunner,
1871
+ args: commandArgs
1872
+ };
1873
+ }
1782
1874
  throw new Error(
1783
1875
  "Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
1784
1876
  );
@@ -2228,6 +2320,15 @@ function detectCodexModels(home = os2.homedir()) {
2228
2320
  import { spawn as spawn3 } from "child_process";
2229
2321
  import path5 from "path";
2230
2322
  import { writeFileSync as writeFileSync3 } from "fs";
2323
+ function buildCopilotSpawnEnv(ctx) {
2324
+ return {
2325
+ ...process.env,
2326
+ FORCE_COLOR: "0",
2327
+ NO_COLOR: "1",
2328
+ ...ctx.config.envVars || {},
2329
+ [SLOCK_HOME_ENV]: resolveSlockHome()
2330
+ };
2331
+ }
2231
2332
  var CopilotDriver = class {
2232
2333
  id = "copilot";
2233
2334
  lifecycle = {
@@ -2286,7 +2387,7 @@ var CopilotDriver = class {
2286
2387
  if (ctx.config.sessionId) {
2287
2388
  args.push(`--resume=${ctx.config.sessionId}`);
2288
2389
  }
2289
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
2390
+ const spawnEnv = buildCopilotSpawnEnv(ctx);
2290
2391
  const proc = spawn3("copilot", args, {
2291
2392
  cwd: ctx.workingDirectory,
2292
2393
  stdio: ["pipe", "pipe", "pipe"],
@@ -2383,6 +2484,15 @@ var CopilotDriver = class {
2383
2484
  import { spawn as spawn4, spawnSync } from "child_process";
2384
2485
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
2385
2486
  import path6 from "path";
2487
+ function buildCursorSpawnEnv(ctx) {
2488
+ return {
2489
+ ...process.env,
2490
+ FORCE_COLOR: "0",
2491
+ NO_COLOR: "1",
2492
+ ...ctx.config.envVars || {},
2493
+ [SLOCK_HOME_ENV]: resolveSlockHome()
2494
+ };
2495
+ }
2386
2496
  var CursorDriver = class {
2387
2497
  id = "cursor";
2388
2498
  lifecycle = {
@@ -2437,7 +2547,7 @@ var CursorDriver = class {
2437
2547
  args.push("--resume", ctx.config.sessionId);
2438
2548
  }
2439
2549
  args.push(ctx.prompt);
2440
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
2550
+ const spawnEnv = buildCursorSpawnEnv(ctx);
2441
2551
  const proc = spawn4("cursor-agent", args, {
2442
2552
  cwd: ctx.workingDirectory,
2443
2553
  stdio: ["pipe", "pipe", "pipe"],
@@ -3527,8 +3637,86 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
3527
3637
  }
3528
3638
  }
3529
3639
 
3640
+ // src/runtimeErrorDiagnostics.ts
3641
+ import { createHash } from "crypto";
3642
+ var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
3643
+ function buildRuntimeErrorDiagnosticEnvelope(message) {
3644
+ const rawMessage = String(message || "");
3645
+ const scrubbed = scrubRuntimeErrorDiagnosticText(rawMessage);
3646
+ const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
3647
+ const httpStatus = extractHttpStatus(rawMessage);
3648
+ const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
3649
+ const fingerprint = fingerprintRuntimeError(scrubbed);
3650
+ const spanAttrs = {
3651
+ runtime_error_class: runtimeErrorClass,
3652
+ runtime_error_fingerprint: fingerprint,
3653
+ runtime_error_message_present: rawMessage.length > 0,
3654
+ runtime_error_message_length_bucket: bucketLength(rawMessage.length),
3655
+ runtime_error_message_truncated: truncated
3656
+ };
3657
+ if (httpStatus !== null) {
3658
+ spanAttrs.runtime_error_http_status = httpStatus;
3659
+ }
3660
+ return {
3661
+ spanAttrs,
3662
+ eventAttrs: {
3663
+ ...spanAttrs,
3664
+ runtime_error_message_excerpt: excerpt
3665
+ }
3666
+ };
3667
+ }
3668
+ function scrubRuntimeErrorDiagnosticText(value) {
3669
+ 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]");
3670
+ }
3671
+ function truncateDiagnosticText(value, maxChars) {
3672
+ if (value.length <= maxChars) return { value, truncated: false };
3673
+ return { value: value.slice(0, maxChars), truncated: true };
3674
+ }
3675
+ function extractHttpStatus(message) {
3676
+ 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);
3677
+ if (!match) return null;
3678
+ const status = Number(match[1]);
3679
+ return Number.isInteger(status) ? status : null;
3680
+ }
3681
+ function classifyRuntimeError(message, httpStatus) {
3682
+ const explicit = /\b([A-Z][A-Za-z0-9_]*(?:Error|Exception))\b/.exec(message);
3683
+ if (explicit) return explicit[1];
3684
+ if (httpStatus !== null) {
3685
+ if (httpStatus === 429) return "RateLimitError";
3686
+ if (httpStatus === 401 || httpStatus === 403) return "AuthError";
3687
+ if (httpStatus === 404) return "NotFoundError";
3688
+ if (httpStatus >= 500) return "ProviderServerError";
3689
+ return "ProviderApiError";
3690
+ }
3691
+ if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
3692
+ if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
3693
+ if (/\bnot found\b/i.test(message)) return "NotFoundError";
3694
+ return "RuntimeError";
3695
+ }
3696
+ function fingerprintRuntimeError(value) {
3697
+ const normalized = value.toLowerCase().replace(/[0-9a-f]{12,}/g, "<hex>").replace(/\b\d+\b/g, "<num>").replace(/\s+/g, " ").trim();
3698
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
3699
+ }
3700
+ function bucketLength(length) {
3701
+ if (length === 0) return "0";
3702
+ if (length < 1024) return "<1k";
3703
+ if (length < 4096) return "1k-4k";
3704
+ if (length < 16384) return "4k-16k";
3705
+ return "16k+";
3706
+ }
3707
+ function redactUrlQuery(value) {
3708
+ try {
3709
+ const url = new URL(value);
3710
+ if (url.search) url.search = "?[REDACTED_QUERY]";
3711
+ if (url.username) url.username = "[REDACTED_USER]";
3712
+ if (url.password) url.password = "[REDACTED_PASSWORD]";
3713
+ return url.toString();
3714
+ } catch {
3715
+ return value.replace(/\?.*$/, "?[REDACTED_QUERY]");
3716
+ }
3717
+ }
3718
+
3530
3719
  // src/agentProcessManager.ts
3531
- var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
3532
3720
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
3533
3721
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
3534
3722
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
@@ -3609,7 +3797,7 @@ function findSessionJsonl(root, predicate) {
3609
3797
  if (depth < 0 || visited >= maxEntries) return null;
3610
3798
  let entries;
3611
3799
  try {
3612
- entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
3800
+ entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
3613
3801
  } catch {
3614
3802
  return null;
3615
3803
  }
@@ -3857,15 +4045,27 @@ After confirming language preference, do not give a generic product introduction
3857
4045
  - Use a low-pressure prompt and guide to one concrete starter action.
3858
4046
 
3859
4047
  ### Starter Plan Output
3860
- A starter plan should make the next action executable, not just descriptive:
3861
- - agent name + role description that can be copied into the create-agent form
3862
- - suggested channel or workstream
3863
- - first task to send after creation
3864
- - next UI action if the user needs to create an agent or channel
4048
+ A starter plan should make the next action executable, not just descriptive.
4049
+ For new channels, new agents, and adding members to an existing channel, post an **action card** rather than a copyable spec:
4050
+
4051
+ - Use \`slock action prepare --target <onboarding-channel>\` and pipe an \`ActionCardAction\` JSON. Identity references are handles (\`@alice\` / \`@scout\` / \`#general\` \u2014 bare names work too), never UUIDs. Server resolves at prepare time.
4052
+ - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
4053
+ - \`{type: "agent:create", name, description?, draftHint?}\`
4054
+ - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty
4055
+ - The owner clicks the button on the card; the matching dialog opens **prefilled with your values** (editable, deselectable for add_member). They review, adjust, and submit; the action is committed under their identity.
4056
+ - Technical fields the owner must pick themselves are NOT yours to prefill on \`agent:create\`: computer, runtime, model, reasoning effort. Stay on semantic intent (name + description).
4057
+ - For \`channel:add_member\`, only suggest people who are actually likely candidates (already in the server, relevant to the channel's topic). The owner will deselect anyone they don't want \u2014 make their default-yes list useful, not exhaustive.
4058
+ - Do not just describe or list copyable specs once action cards are available \u2014 the human input cost should land at "click the card, review, submit", not "copy this name into the dialog yourself".
4059
+ - Do not imply the resource has been created or members added until the card flips to "Done".
4060
+
4061
+ Other plan elements still apply:
4062
+ - suggested channel or workstream pairing
4063
+ - first task to send after the card is committed
4064
+ - who to pull in once the channel exists (often a follow-up \`channel:add_member\` card)
3865
4065
 
3866
4066
  Do not use a rigid keyword routing table. Use examples as inspiration, then adapt to the user's context.
3867
- If details are missing but not blocking, state reasonable defaults and invite correction.
3868
- Only ask one blocking question first if the answer is required before any useful starter plan can be proposed.
4067
+ If details are missing but not blocking, state reasonable defaults inside the action card payload (the owner can edit) and invite correction in chat.
4068
+ Only ask one blocking question first if the answer is required before any useful card can be prepared.
3869
4069
  Do not imply you have already created agents or channels unless the action has actually happened.
3870
4070
 
3871
4071
  ### Capability Boundary Pivot
@@ -4103,16 +4303,22 @@ Do not copy these answers verbatim.
4103
4303
 
4104
4304
  ## FAQ 15: How do I create agents or channels?
4105
4305
  ### Answer idea
4106
- - The user can create agents from the Agents section in the People tab by clicking the + button, or from a computer/machine context menu via Create Agent.
4107
- - The user can create channels from the Channels section by clicking the + button.
4108
- - When recommending setup, provide copyable agent names/descriptions, suggested channel names, and the first task to send after creation.
4306
+ - When the owner agrees to a new agent or channel, **post an action card** with \`slock action prepare\`. The card lives inline in chat; the owner clicks the action button, the matching create dialog opens prefilled with your values (editable), and the resource is created under their identity when they submit.
4307
+ - v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
4308
+ - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
4309
+ - \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
4310
+ - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty. The owner clicks "Add Members" on the card; an AddMembers dialog opens with your suggested list (each row toggleable) and the owner submits to actually add them.
4311
+
4312
+ - **Identity references are handles, not UUIDs.** Use \`@alice\` / \`@scout\` / \`#general\` (or bare \`alice\` / \`scout\` / \`general\`). The server resolves to UUIDs at prepare time. If a handle doesn't match a real human / agent / channel in this server you get a 422 INVALID_HANDLE error pointing at the field \u2014 fix the handle and retry. You should never see or write UUIDs in action card payloads.
4313
+ - Manual fallback (only when the owner explicitly wants to do it themselves or asks how to repeat it): the + buttons in the Agents and Channels sidebar sections. Lead with the action card; mention the + button only on request.
4109
4314
 
4110
4315
  ### Next step
4111
- - Give the exact agent/channel spec the user can copy, then guide them to the relevant + button or creation dialog.
4316
+ - Prefill the values you have, post the action card with a short \`draftHint\` explaining why these values, and tell the owner: "click the button on the card to review and commit." Then propose the first task to send once the card flips to Done.
4112
4317
 
4113
4318
  ### Guardrail
4114
- - Do not imply you already created agents or channels unless that action actually happened.
4115
- - If you cannot directly create something, avoid a long permissions explanation; give the copyable spec and the next UI action.
4319
+ - Do not imply you already created agents or channels unless the card state is \`executed\`.
4320
+ - Do not prefill technical fields on \`agent:create\` (runtime / model / computer / reasoning effort). The owner picks those in the dialog.
4321
+ - If the action type the user wants is not yet supported (e.g. \`channel:add_member\`), say so plainly and offer the manual UI path; do not invent action types the schema does not accept.
4116
4322
  `;
4117
4323
  }
4118
4324
  function buildOnboardingSeedFiles() {
@@ -4212,6 +4418,20 @@ function runtimeProfileNotificationFromMessage(message) {
4212
4418
  function runtimeProfileNotificationTitle(kind) {
4213
4419
  return kind === "migration" ? "Runtime Profile migration" : "Runtime Profile notice";
4214
4420
  }
4421
+ function hashRuntimeProfileKey(key) {
4422
+ if (!key) return void 0;
4423
+ return createHash2("sha256").update(key).digest("hex").slice(0, 16);
4424
+ }
4425
+ function runtimeProfileTurnControl(kind, key, source) {
4426
+ return {
4427
+ kind,
4428
+ source,
4429
+ keyHash: hashRuntimeProfileKey(key) ?? null,
4430
+ keyPresent: Boolean(key),
4431
+ injectedAtMs: Date.now(),
4432
+ migrationDoneToolObserved: false
4433
+ };
4434
+ }
4215
4435
  function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
4216
4436
  const next = [...lines];
4217
4437
  for (const rawLine of chunk.split(/\r?\n/)) {
@@ -4400,13 +4620,14 @@ var AgentProcessManager = class _AgentProcessManager {
4400
4620
  defaultAgentEnvVarsProvider;
4401
4621
  tracer;
4402
4622
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
4623
+ processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
4403
4624
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
4404
4625
  this.chatBridgePath = chatBridgePath;
4405
4626
  this.slockCliPath = opts.slockCliPath ?? "";
4406
4627
  this.sendToServer = sendToServer;
4407
4628
  this.daemonApiKey = daemonApiKey;
4408
4629
  this.serverUrl = opts.serverUrl;
4409
- this.dataDir = opts.dataDir || DATA_DIR;
4630
+ this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
4410
4631
  this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
4411
4632
  this.driverResolver = opts.driverResolver || getDriver;
4412
4633
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
@@ -4798,6 +5019,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4798
5019
  exitCode: null,
4799
5020
  exitSignal: null,
4800
5021
  expectedTerminationReason: null,
5022
+ runtimeProfileTurnControl: runtimeConfig.runtimeProfileControl ? runtimeProfileTurnControl(runtimeConfig.runtimeProfileControl.kind, runtimeConfig.runtimeProfileControl.key, "agent_config") : null,
4801
5023
  pendingTrajectory: null,
4802
5024
  gatedSteering: createGatedSteeringState()
4803
5025
  };
@@ -4872,7 +5094,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4872
5094
  clean_exit: code === 0,
4873
5095
  runtime_trace_active: Boolean(current?.runtimeTraceSpan),
4874
5096
  inbox_count: current?.inbox.length ?? 0,
4875
- pending_notification_count: current?.pendingNotificationCount ?? 0
5097
+ pending_notification_count: current?.pendingNotificationCount ?? 0,
5098
+ ...this.processExitTraceAttrs.get(proc)
4876
5099
  });
4877
5100
  logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
4878
5101
  });
@@ -4900,7 +5123,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4900
5123
  outcome: processEndedCleanly ? "process-exit" : "process-crash",
4901
5124
  expectedTerminationReason: ap.expectedTerminationReason || void 0,
4902
5125
  exitCode: finalCode,
4903
- exitSignal: finalSignal
5126
+ exitSignal: finalSignal,
5127
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
4904
5128
  });
4905
5129
  if (processEndedCleanly) {
4906
5130
  this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
@@ -5063,6 +5287,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5063
5287
  agentId,
5064
5288
  kind,
5065
5289
  key_present: Boolean(key),
5290
+ key_hash: hashRuntimeProfileKey(key),
5066
5291
  outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
5067
5292
  runtime: ap.config.runtime,
5068
5293
  session_id_present: Boolean(ap.sessionId),
@@ -5085,6 +5310,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5085
5310
  agentId,
5086
5311
  kind,
5087
5312
  key_present: Boolean(key),
5313
+ key_hash: hashRuntimeProfileKey(key),
5088
5314
  outcome: "queued_during_start",
5089
5315
  startup_pending: true,
5090
5316
  starting_inbox_count: pending.length,
@@ -5121,6 +5347,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5121
5347
  }
5122
5348
  this.clearCompactionWatchdog(ap);
5123
5349
  this.agents.delete(agentId);
5350
+ this.processExitTraceAttrs.set(ap.process, {
5351
+ stop_source: silent ? "daemon_internal" : "explicit_request",
5352
+ stop_wait_requested: wait,
5353
+ stop_silent: silent
5354
+ });
5124
5355
  ap.process.kill("SIGTERM");
5125
5356
  if (!silent) {
5126
5357
  this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
@@ -5411,7 +5642,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5411
5642
  attrs: {
5412
5643
  agentId,
5413
5644
  control_kind: kind,
5414
- key_present: Boolean(key)
5645
+ key_present: Boolean(key),
5646
+ key_hash: hashRuntimeProfileKey(key)
5415
5647
  }
5416
5648
  });
5417
5649
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5444,7 +5676,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5444
5676
  }
5445
5677
  if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
5446
5678
  ap.isIdle = false;
5447
- this.startRuntimeTrace(agentId, ap, "runtime-profile");
5679
+ this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
5448
5680
  const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
5449
5681
  span.end(written ? "ok" : "error", {
5450
5682
  attrs: {
@@ -5539,6 +5771,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5539
5771
  agentId,
5540
5772
  control_kind: control.kind,
5541
5773
  key_present: Boolean(control.key),
5774
+ key_hash: hashRuntimeProfileKey(control.key),
5542
5775
  launchId: launchId || void 0,
5543
5776
  source: "agent_config"
5544
5777
  }
@@ -5903,7 +6136,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5903
6136
  deliveryId: context.deliveryId,
5904
6137
  delivery_correlation_id: context.deliveryId,
5905
6138
  control_kind: notification.kind,
5906
- key_present: Boolean(notification.key)
6139
+ key_present: Boolean(notification.key),
6140
+ runtime_profile_control_kind: notification.kind,
6141
+ runtime_profile_key_hash: hashRuntimeProfileKey(notification.key),
6142
+ runtime_profile_key_present: Boolean(notification.key)
5907
6143
  };
5908
6144
  }
5909
6145
  return {
@@ -5914,8 +6150,63 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5914
6150
  delivery_correlation_id: context.deliveryId ?? first.message_id
5915
6151
  };
5916
6152
  }
6153
+ runtimeProfileTurnControlTraceAttrs(control) {
6154
+ if (!control) return {};
6155
+ const pendingAgeMs = Math.max(0, Date.now() - control.injectedAtMs);
6156
+ return {
6157
+ runtime_profile_control_kind: control.kind,
6158
+ runtime_profile_control_source: control.source,
6159
+ runtime_profile_key_hash: control.keyHash || void 0,
6160
+ runtime_profile_key_present: control.keyPresent,
6161
+ runtime_profile_pending_age_ms: pendingAgeMs,
6162
+ runtime_profile_requires_ack: control.kind === "migration",
6163
+ runtime_profile_migration_done_observed: control.kind === "migration" ? control.migrationDoneToolObserved : void 0
6164
+ };
6165
+ }
6166
+ activateRuntimeProfileTurnControl(ap, control) {
6167
+ ap.runtimeProfileTurnControl = control;
6168
+ }
6169
+ runtimeProfileTurnControlFromMessages(messages, source = "message") {
6170
+ const notifications = messages?.map((message) => runtimeProfileNotificationFromMessage(message)).filter((candidate) => Boolean(candidate)) ?? [];
6171
+ const notification = notifications.find((candidate) => candidate.kind === "migration") ?? notifications[0];
6172
+ if (!notification) return null;
6173
+ return runtimeProfileTurnControl(notification.kind, notification.key, source);
6174
+ }
6175
+ noteRuntimeProfileToolCall(agentId, ap, toolName) {
6176
+ const control = ap.runtimeProfileTurnControl;
6177
+ if (!control || control.kind !== "migration") return;
6178
+ if (!toolName.includes("runtime_profile_migration_done")) return;
6179
+ control.migrationDoneToolObserved = true;
6180
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration_done_tool.observed", {
6181
+ ...this.runtimeProfileTurnControlTraceAttrs(control),
6182
+ tool: toolName
6183
+ });
6184
+ }
6185
+ finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
6186
+ const control = ap.runtimeProfileTurnControl;
6187
+ if (!control) return {};
6188
+ const attrs = this.runtimeProfileTurnControlTraceAttrs(control);
6189
+ if (control.kind === "migration" && !control.migrationDoneToolObserved) {
6190
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration.turn_without_ack", {
6191
+ ...attrs,
6192
+ terminal,
6193
+ inbox_count: ap.inbox.length,
6194
+ pending_notification_count: ap.pendingNotificationCount
6195
+ });
6196
+ }
6197
+ ap.runtimeProfileTurnControl = null;
6198
+ return {
6199
+ ...attrs,
6200
+ runtime_profile_turn_terminal: terminal,
6201
+ runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
6202
+ };
6203
+ }
5917
6204
  startRuntimeTrace(agentId, ap, reason, messages) {
5918
6205
  if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
6206
+ const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
6207
+ if (messageControl) {
6208
+ this.activateRuntimeProfileTurnControl(ap, messageControl);
6209
+ }
5919
6210
  const span = this.tracer.startSpan("daemon.runtime.turn", {
5920
6211
  surface: "daemon",
5921
6212
  kind: "internal",
@@ -5925,10 +6216,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5925
6216
  model: ap.config.model,
5926
6217
  reason,
5927
6218
  hasSession: Boolean(ap.sessionId),
5928
- ...this.messagesTraceAttrs(messages)
6219
+ ...this.messagesTraceAttrs(messages),
6220
+ ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
5929
6221
  }
5930
6222
  });
5931
- span.addEvent("daemon.turn.started", { reason, ...this.messagesTraceAttrs(messages) });
6223
+ span.addEvent("daemon.turn.started", {
6224
+ reason,
6225
+ ...this.messagesTraceAttrs(messages),
6226
+ ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6227
+ });
5932
6228
  ap.runtimeTraceSpan = span;
5933
6229
  return span;
5934
6230
  }
@@ -6034,7 +6330,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6034
6330
  ageMs: staleForMs,
6035
6331
  lastActivity: ap.lastActivity,
6036
6332
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6037
- lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
6333
+ lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6334
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6038
6335
  });
6039
6336
  this.broadcastActivity(agentId, "error", diagnostic.detail);
6040
6337
  return true;
@@ -6065,7 +6362,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6065
6362
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6066
6363
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6067
6364
  pendingMessages: ap.inbox.length,
6068
- recovery: "terminate_for_queued_message"
6365
+ recovery: "terminate_for_queued_message",
6366
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6069
6367
  });
6070
6368
  ap.expectedTerminationReason = "stalled_recovery";
6071
6369
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
@@ -6074,6 +6372,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6074
6372
  );
6075
6373
  this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
6076
6374
  try {
6375
+ this.processExitTraceAttrs.set(ap.process, {
6376
+ stop_source: "stalled_recovery",
6377
+ expectedTerminationReason: "stalled_recovery",
6378
+ queued_messages_count: ap.inbox.length
6379
+ });
6077
6380
  ap.process.kill("SIGTERM");
6078
6381
  } catch (err) {
6079
6382
  const reason = err instanceof Error ? err.message : String(err);
@@ -6122,6 +6425,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6122
6425
  if (ap) {
6123
6426
  ap.gatedSteering.outstandingToolUses++;
6124
6427
  this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6428
+ this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
6125
6429
  this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
6126
6430
  this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
6127
6431
  event: "tool_call",
@@ -6211,11 +6515,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6211
6515
  this.broadcastActivity(agentId, "online", "Idle");
6212
6516
  }
6213
6517
  }
6214
- this.endRuntimeTrace(ap, "ok", { outcome: "turn-completed" });
6518
+ this.endRuntimeTrace(ap, "ok", {
6519
+ outcome: "turn-completed",
6520
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
6521
+ });
6215
6522
  if (ap.driver.terminateProcessOnTurnEnd) {
6216
6523
  ap.expectedTerminationReason = "turn_end";
6217
6524
  logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
6218
6525
  try {
6526
+ this.processExitTraceAttrs.set(ap.process, {
6527
+ stop_source: "turn_end",
6528
+ expectedTerminationReason: "turn_end"
6529
+ });
6219
6530
  ap.process.kill("SIGTERM");
6220
6531
  } catch (err) {
6221
6532
  const reason = err instanceof Error ? err.message : String(err);
@@ -6233,6 +6544,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6233
6544
  this.flushPendingTrajectory(agentId);
6234
6545
  if (ap) ap.lastRuntimeError = event.message;
6235
6546
  if (ap) {
6547
+ const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
6236
6548
  this.setGatedSteeringPhase(agentId, ap, "error", { event: "error" });
6237
6549
  if (ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message)) {
6238
6550
  this.requeueGatedInFlightBatch(agentId, ap, "thinking_block_mutation_error");
@@ -6246,8 +6558,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6246
6558
  `[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
6247
6559
  );
6248
6560
  }
6249
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", { message: event.message });
6250
- this.endRuntimeTrace(ap, "error", { outcome: "runtime-error", errorMessage: event.message });
6561
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
6562
+ this.endRuntimeTrace(ap, "error", {
6563
+ outcome: "runtime-error",
6564
+ ...runtimeErrorDiagnostics.spanAttrs,
6565
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
6566
+ });
6251
6567
  if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
6252
6568
  ap.isIdle = true;
6253
6569
  ap.pendingNotificationCount = 0;
@@ -6721,11 +7037,10 @@ var ReminderCache = class {
6721
7037
  };
6722
7038
 
6723
7039
  // src/machineLock.ts
6724
- import { createHash, randomUUID as randomUUID2 } from "crypto";
7040
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
6725
7041
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
6726
7042
  import os6 from "os";
6727
7043
  import path12 from "path";
6728
- var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
6729
7044
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
6730
7045
  var DaemonMachineLockConflictError = class extends Error {
6731
7046
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -6738,11 +7053,14 @@ var DaemonMachineLockConflictError = class extends Error {
6738
7053
  }
6739
7054
  };
6740
7055
  function apiKeyFingerprint(apiKey) {
6741
- return createHash("sha256").update(apiKey).digest("hex");
7056
+ return createHash3("sha256").update(apiKey).digest("hex");
6742
7057
  }
6743
7058
  function getDaemonMachineLockId(apiKey) {
6744
7059
  return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
6745
7060
  }
7061
+ function resolveDefaultMachineStateRoot() {
7062
+ return resolveSlockHomePath("machines");
7063
+ }
6746
7064
  function ownerPath(lockDir) {
6747
7065
  return path12.join(lockDir, "owner.json");
6748
7066
  }
@@ -6771,7 +7089,7 @@ function isProcessAlive(pid) {
6771
7089
  }
6772
7090
  }
6773
7091
  function acquireDaemonMachineLock(options) {
6774
- const rootDir = options.rootDir ?? DEFAULT_MACHINE_STATE_ROOT;
7092
+ const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
6775
7093
  const fingerprint = apiKeyFingerprint(options.apiKey);
6776
7094
  const lockId = getDaemonMachineLockId(options.apiKey);
6777
7095
  const machineDir = path12.join(rootDir, lockId);
@@ -6827,7 +7145,7 @@ function acquireDaemonMachineLock(options) {
6827
7145
  }
6828
7146
 
6829
7147
  // src/localTraceSink.ts
6830
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7148
+ import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
6831
7149
  import path13 from "path";
6832
7150
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
6833
7151
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
@@ -6844,6 +7162,15 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
6844
7162
  "deliveryCorrelationId",
6845
7163
  "delivery_correlation_id"
6846
7164
  ]);
7165
+ var DIAGNOSTIC_ERROR_ATTRS = /* @__PURE__ */ new Set([
7166
+ "runtime_error_class",
7167
+ "runtime_error_fingerprint",
7168
+ "runtime_error_http_status",
7169
+ "runtime_error_message_present",
7170
+ "runtime_error_message_length_bucket",
7171
+ "runtime_error_message_truncated",
7172
+ "runtime_error_message_excerpt"
7173
+ ]);
6847
7174
  var LocalRotatingTraceSink = class {
6848
7175
  traceDir;
6849
7176
  maxFileBytes;
@@ -6896,7 +7223,7 @@ var LocalRotatingTraceSink = class {
6896
7223
  }
6897
7224
  }
6898
7225
  pruneOldFiles() {
6899
- const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
7226
+ const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
6900
7227
  const excess = files.length - this.maxFiles;
6901
7228
  if (excess <= 0) return;
6902
7229
  for (const file of files.slice(0, excess)) {
@@ -6941,6 +7268,11 @@ function sanitizeAttrs(attrs) {
6941
7268
  sanitized[key] = sanitizeValue(value);
6942
7269
  continue;
6943
7270
  }
7271
+ if (isDiagnosticErrorAttr(key)) {
7272
+ if (value === null || value === void 0 || value === "") continue;
7273
+ sanitized[key] = sanitizeValue(value);
7274
+ continue;
7275
+ }
6944
7276
  if (shouldDropAttr(key)) continue;
6945
7277
  sanitized[key] = sanitizeValue(value);
6946
7278
  }
@@ -6974,9 +7306,12 @@ function shouldDropAttr(key) {
6974
7306
  function isDiagnosticIdAttr(key) {
6975
7307
  return DIAGNOSTIC_ID_ATTRS.has(key);
6976
7308
  }
7309
+ function isDiagnosticErrorAttr(key) {
7310
+ return DIAGNOSTIC_ERROR_ATTRS.has(key);
7311
+ }
6977
7312
 
6978
7313
  // src/traceBundleUpload.ts
6979
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
7314
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
6980
7315
  import { gzipSync } from "zlib";
6981
7316
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
6982
7317
  import path14 from "path";
@@ -7106,12 +7441,12 @@ async function uploadWithSignedCapability({
7106
7441
  }
7107
7442
 
7108
7443
  // src/traceJitter.ts
7109
- import { createHash as createHash2 } from "crypto";
7444
+ import { createHash as createHash4 } from "crypto";
7110
7445
  var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
7111
7446
  var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
7112
7447
  var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
7113
7448
  function computeTraceJitter(lockId) {
7114
- const seed = createHash2("sha256").update(lockId).digest();
7449
+ const seed = createHash4("sha256").update(lockId).digest();
7115
7450
  return {
7116
7451
  initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
7117
7452
  uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
@@ -7312,7 +7647,7 @@ var DaemonTraceBundleUploader = class {
7312
7647
  }
7313
7648
  };
7314
7649
  function sha256Hex(body) {
7315
- return createHash3("sha256").update(body).digest("hex");
7650
+ return createHash5("sha256").update(body).digest("hex");
7316
7651
  }
7317
7652
  function readPositiveIntegerEnv2(name, fallback) {
7318
7653
  const value = process.env[name];
@@ -7491,6 +7826,7 @@ var DaemonCore = class {
7491
7826
  daemonVersion;
7492
7827
  chatBridgePath;
7493
7828
  slockCliPath;
7829
+ slockHome;
7494
7830
  runtimeDetector;
7495
7831
  agentManager;
7496
7832
  connection;
@@ -7505,6 +7841,8 @@ var DaemonCore = class {
7505
7841
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
7506
7842
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
7507
7843
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
7844
+ this.slockHome = resolveSlockHome();
7845
+ process.env[SLOCK_HOME_ENV] = this.slockHome;
7508
7846
  this.injectedTracer = Boolean(options.tracer);
7509
7847
  this.tracer = options.tracer ?? noopTracer;
7510
7848
  this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
@@ -7514,7 +7852,7 @@ var DaemonCore = class {
7514
7852
  });
7515
7853
  let connection;
7516
7854
  const agentManagerOptions = {
7517
- dataDir: options.dataDir,
7855
+ dataDir: options.dataDir ?? resolveSlockHomePath("agents", this.slockHome),
7518
7856
  serverUrl: options.serverUrl,
7519
7857
  defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
7520
7858
  slockCliPath: this.slockCliPath,
@@ -7536,7 +7874,7 @@ var DaemonCore = class {
7536
7874
  resolveMachineStateRoot() {
7537
7875
  if (this.options.machineStateDir) return this.options.machineStateDir;
7538
7876
  if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
7539
- return DEFAULT_MACHINE_STATE_ROOT;
7877
+ return resolveDefaultMachineStateRoot();
7540
7878
  }
7541
7879
  shouldEnableLocalTrace() {
7542
7880
  if (this.injectedTracer) return false;
@@ -7580,6 +7918,12 @@ var DaemonCore = class {
7580
7918
  }
7581
7919
  start() {
7582
7920
  logger.info("[Slock Daemon] Starting...");
7921
+ logger.info(`[Slock Daemon] ${SLOCK_HOME_ENV}=${this.slockHome}`);
7922
+ for (const legacy of listLegacySlockStatePaths(this.slockHome)) {
7923
+ logger.warn(
7924
+ `[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.`
7925
+ );
7926
+ }
7583
7927
  if (!this.machineLock) {
7584
7928
  this.machineLock = acquireDaemonMachineLock({
7585
7929
  apiKey: this.options.apiKey,