@slock-ai/daemon 0.55.2 → 0.55.3-play.20260531155023

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.
@@ -8,11 +8,11 @@ import {
8
8
  } from "./chunk-M2KQBJR3.js";
9
9
 
10
10
  // src/core.ts
11
- import path16 from "path";
11
+ import path17 from "path";
12
12
  import os7 from "os";
13
13
  import { createRequire as createRequire2 } from "module";
14
14
  import { accessSync } from "fs";
15
- import { fileURLToPath } from "url";
15
+ import { fileURLToPath as fileURLToPath2 } from "url";
16
16
 
17
17
  // ../shared/src/slockRefs.ts
18
18
  var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
@@ -990,6 +990,7 @@ var RUNTIME_CONFIG_VERSION = 1;
990
990
  var RUNTIMES = [
991
991
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
992
992
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
993
+ { id: "pi", displayName: "Pi", binary: "pi", supported: true },
993
994
  { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
994
995
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
995
996
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
@@ -1042,6 +1043,13 @@ var RUNTIME_MODELS = {
1042
1043
  { id: "openrouter/anthropic/claude-opus-4.5", label: "Claude Opus 4.5 via OpenRouter", verified: "suggestion_only" },
1043
1044
  { id: "fusecode/opus[1m]", label: "Opus 1M via FuseCode", verified: "suggestion_only" }
1044
1045
  ],
1046
+ pi: [
1047
+ { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro" },
1048
+ { id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash" },
1049
+ { id: "kimi-coding/kimi-for-coding", label: "Kimi for Coding" },
1050
+ { id: "zai/glm-5.1", label: "GLM-5.1" },
1051
+ { id: "zai/glm-4.7", label: "GLM-4.7" }
1052
+ ],
1045
1053
  // Kimi CLI resolves model keys from each user's local config, so the safest
1046
1054
  // built-in option is to defer to whatever default model the CLI already uses.
1047
1055
  kimi: [
@@ -1190,10 +1198,10 @@ var DISPLAY_PLAN_CONFIG = {
1190
1198
  };
1191
1199
 
1192
1200
  // src/agentProcessManager.ts
1193
- import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
1201
+ import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync8 } from "fs";
1194
1202
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1195
1203
  import { createHash as createHash3 } from "crypto";
1196
- import path12 from "path";
1204
+ import path13 from "path";
1197
1205
  import os5 from "os";
1198
1206
 
1199
1207
  // src/drivers/claude.ts
@@ -1778,6 +1786,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1778
1786
  return candidates.filter((candidate) => existsSync(candidate.path));
1779
1787
  }
1780
1788
 
1789
+ // src/authEnv.ts
1790
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1791
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1792
+ function scrubDaemonAuthEnv(env) {
1793
+ delete env[DAEMON_API_KEY_ENV];
1794
+ return env;
1795
+ }
1796
+ function scrubDaemonChildEnv(env) {
1797
+ delete env[DAEMON_API_KEY_ENV];
1798
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1799
+ return env;
1800
+ }
1801
+
1781
1802
  // src/agentCredentialProxy.ts
1782
1803
  import { randomBytes } from "crypto";
1783
1804
  import http from "http";
@@ -2172,7 +2193,7 @@ async function handleProxyRequest(req, res) {
2172
2193
  const bodyBuffer = new ArrayBuffer(rawBodyBuffer.byteLength);
2173
2194
  new Uint8Array(bodyBuffer).set(rawBodyBuffer);
2174
2195
  body = bodyBuffer;
2175
- headers.set("content-length", String(rawBodyBuffer.byteLength));
2196
+ headers.delete("content-length");
2176
2197
  }
2177
2198
  let sendTarget;
2178
2199
  const sideEffectAction = agentApiSideEffectAction(target.pathname);
@@ -2196,7 +2217,7 @@ async function handleProxyRequest(req, res) {
2196
2217
  body = prepared.bodyText;
2197
2218
  if (sideEffectAction === "send") sendTarget = prepared.target;
2198
2219
  headers.set("content-type", "application/json");
2199
- headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
2220
+ headers.delete("content-length");
2200
2221
  }
2201
2222
  const upstream = await daemonFetch(target, {
2202
2223
  method,
@@ -2385,6 +2406,20 @@ function maxMessageSeq(messages) {
2385
2406
  }
2386
2407
  return maxSeq > 0 ? maxSeq : void 0;
2387
2408
  }
2409
+ function messageSenderId(message) {
2410
+ if (typeof message.sender_id === "string" && message.sender_id.length > 0) return message.sender_id;
2411
+ if (typeof message.senderId === "string" && message.senderId.length > 0) return message.senderId;
2412
+ return void 0;
2413
+ }
2414
+ function isSelfAuthoredMessage(registration, message) {
2415
+ return messageSenderId(message) === registration.agentId;
2416
+ }
2417
+ function isMessageModelSeen(coordinator, target, message) {
2418
+ const seq = Math.floor(messageSeq(message));
2419
+ const boundary = coordinator.getBoundary(target);
2420
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= seq) return true;
2421
+ return coordinator.isMessageModelSeen?.({ target, message }) === true;
2422
+ }
2388
2423
  function resolveFreshnessBoundary(messages) {
2389
2424
  const seenUpToSeq = maxMessageSeq(messages);
2390
2425
  return typeof seenUpToSeq === "number" ? { ok: true, seenUpToSeq } : { ok: false, reason: "missing_seq_boundary" };
@@ -2525,7 +2560,7 @@ function parseTargetFields(target) {
2525
2560
  }
2526
2561
  function normalizeVisibleMessage(message, target) {
2527
2562
  const targetFields = target ? parseTargetFields(target) : {};
2528
- return {
2563
+ const normalized = {
2529
2564
  ...targetFields,
2530
2565
  ...message,
2531
2566
  message_id: message.message_id ?? message.id,
@@ -2534,6 +2569,9 @@ function normalizeVisibleMessage(message, target) {
2534
2569
  sender_name: message.sender_name ?? message.senderName,
2535
2570
  sender_description: message.sender_description ?? message.senderDescription ?? null
2536
2571
  };
2572
+ const senderId = messageSenderId(message);
2573
+ if (senderId) normalized.sender_id = senderId;
2574
+ return normalized;
2537
2575
  }
2538
2576
  function normalizeVisibleMessages(messages, target) {
2539
2577
  return messages.map((message) => normalizeVisibleMessage(message, target));
@@ -2634,6 +2672,9 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2634
2672
  }
2635
2673
  const recent = await loadRecentTargetMessages(registration, headers, target);
2636
2674
  if (recent.length > 0) {
2675
+ const unconsumedCounterparty = recent.filter(
2676
+ (message) => !isSelfAuthoredMessage(registration, message) && !isMessageModelSeen(coordinator, target, message)
2677
+ );
2637
2678
  const boundary2 = resolveFreshnessBoundary(recent);
2638
2679
  if (!boundary2.ok) {
2639
2680
  recordFreshnessDecision(coordinator, {
@@ -2647,6 +2688,39 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2647
2688
  });
2648
2689
  return { bodyText: JSON.stringify(body), target };
2649
2690
  }
2691
+ if (unconsumedCounterparty.length === 0) {
2692
+ const { seenUpToSeq: seenUpToSeq2 } = boundary2;
2693
+ if (action === "send") body.seenUpToSeq = seenUpToSeq2;
2694
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq2, source: "side_effect_preflight_context" });
2695
+ recordFreshnessDecision(coordinator, {
2696
+ action,
2697
+ decision: "forward",
2698
+ target,
2699
+ inboxTrustState: "untrusted",
2700
+ reason: "target_first_touch_recent_context_already_seen",
2701
+ pendingCount: 0,
2702
+ pendingMaxSeq: seenUpToSeq2,
2703
+ modelSeenSeq: seenUpToSeq2,
2704
+ heldMessageCount: 0,
2705
+ omittedMessageCount: 0
2706
+ });
2707
+ return { bodyText: JSON.stringify(body), target };
2708
+ }
2709
+ const heldBoundary = resolveFreshnessBoundary(unconsumedCounterparty);
2710
+ if (!heldBoundary.ok) {
2711
+ recordFreshnessDecision(coordinator, {
2712
+ action,
2713
+ decision: "forward",
2714
+ target,
2715
+ inboxTrustState: "untrusted",
2716
+ reason: "target_first_touch_counterparty_context_without_seq_boundary",
2717
+ pendingCount: 0,
2718
+ modelSeenSeq: 0
2719
+ });
2720
+ return { bodyText: JSON.stringify(body), target };
2721
+ }
2722
+ const heldMessages = latestVisibleMessages(unconsumedCounterparty, LOCAL_HELD_CONTEXT_LIMIT);
2723
+ const omittedMessageCount = Math.max(0, unconsumedCounterparty.length - heldMessages.length);
2650
2724
  const { seenUpToSeq } = boundary2;
2651
2725
  coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
2652
2726
  const decision = {
@@ -2656,10 +2730,10 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2656
2730
  inboxTrustState: "untrusted",
2657
2731
  reason: "target_first_touch_recent_context",
2658
2732
  pendingCount: 0,
2659
- pendingMaxSeq: seenUpToSeq,
2733
+ pendingMaxSeq: heldBoundary.seenUpToSeq,
2660
2734
  modelSeenSeq: 0,
2661
- heldMessageCount: recent.length,
2662
- omittedMessageCount: 0
2735
+ heldMessageCount: heldMessages.length,
2736
+ omittedMessageCount
2663
2737
  };
2664
2738
  const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
2665
2739
  recordFreshnessDecision(coordinator, { ...decision, producerFactId });
@@ -2669,9 +2743,9 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2669
2743
  localResponse: projectApmHeldFreshnessEnvelope({
2670
2744
  producerFactId,
2671
2745
  action,
2672
- heldMessages: recent,
2673
- newMessageCount: recent.length,
2674
- omittedMessageCount: 0,
2746
+ heldMessages,
2747
+ newMessageCount: unconsumedCounterparty.length,
2748
+ omittedMessageCount,
2675
2749
  seenUpToSeq
2676
2750
  }).body
2677
2751
  };
@@ -2777,7 +2851,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
2777
2851
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
2778
2852
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
2779
2853
  var RAW_CREDENTIAL_ENV_DENYLIST = [
2780
- "SLOCK_AGENT_CREDENTIAL_KEY"
2854
+ "SLOCK_AGENT_TOKEN",
2855
+ "SLOCK_AGENT_CREDENTIAL_KEY",
2856
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
2781
2857
  ];
2782
2858
  var cachedOpencliBinPath;
2783
2859
  function resolveOpencliBinPath() {
@@ -2992,7 +3068,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2992
3068
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
2993
3069
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
2994
3070
  };
2995
- delete spawnEnv.SLOCK_AGENT_TOKEN;
3071
+ scrubDaemonChildEnv(spawnEnv);
2996
3072
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
2997
3073
  delete spawnEnv[key];
2998
3074
  }
@@ -3026,7 +3102,7 @@ function collectResultErrorDetail(message, fallback) {
3026
3102
  return parts.join(" | ") || fallback;
3027
3103
  }
3028
3104
  function isProviderApiFailureText(value, hasToolUse) {
3029
- return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
3105
+ return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b4\d{2}\b/.test(value) || /\b5\d{2}\b/.test(value));
3030
3106
  }
3031
3107
  var ClaudeEventNormalizer = class {
3032
3108
  normalizeLine(line) {
@@ -3291,7 +3367,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
3291
3367
  }
3292
3368
  function resolveCommandOnPath(command, deps = {}) {
3293
3369
  const platform = deps.platform ?? process.platform;
3294
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3370
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3295
3371
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3296
3372
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
3297
3373
  if (platform === "win32") {
@@ -3317,7 +3393,7 @@ function firstExistingPath(candidates, deps = {}) {
3317
3393
  return null;
3318
3394
  }
3319
3395
  function readCommandVersion(command, args = [], deps = {}) {
3320
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3396
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3321
3397
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3322
3398
  try {
3323
3399
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3388,7 +3464,7 @@ function buildClaudeArgs(config, opts) {
3388
3464
  args.push("--effort", launchRuntimeFields.reasoningEffort);
3389
3465
  }
3390
3466
  if (launchRuntimeFields.mode.kind === "fast") {
3391
- args.push("--bare");
3467
+ args.push("--settings", JSON.stringify({ fastMode: true }));
3392
3468
  }
3393
3469
  if (config.sessionId) {
3394
3470
  args.push("--resume", config.sessionId);
@@ -4052,6 +4128,7 @@ var CodexDriver = class {
4052
4128
  cwd: ctx.workingDirectory,
4053
4129
  approvalPolicy: "never",
4054
4130
  sandbox: "danger-full-access",
4131
+ sandbox_mode: "danger-full-access",
4055
4132
  developerInstructions: ctx.standingPrompt,
4056
4133
  // Raw response items are used only as payload-free liveness signals in
4057
4134
  // the daemon. They replace the previous transcript-mtime heuristic.
@@ -4748,11 +4825,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
4748
4825
  return parseCursorModelsOutput(String(result.stdout || ""));
4749
4826
  }
4750
4827
  function buildCursorModelProbeEnv(deps = {}) {
4751
- return withWindowsUserEnvironment({
4828
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
4752
4829
  ...deps.env ?? process.env,
4753
4830
  FORCE_COLOR: "0",
4754
4831
  NO_COLOR: "1"
4755
- }, deps);
4832
+ }, deps));
4756
4833
  }
4757
4834
  function runCursorModelsCommand() {
4758
4835
  return spawnSync("cursor-agent", ["models"], {
@@ -4808,7 +4885,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
4808
4885
  }
4809
4886
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
4810
4887
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
4811
- const env = deps.env ?? process.env;
4888
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
4812
4889
  const winPath = path8.win32;
4813
4890
  let geminiEntry = null;
4814
4891
  try {
@@ -4980,13 +5057,16 @@ var GeminiDriver = class {
4980
5057
  // src/drivers/kimi.ts
4981
5058
  import { randomUUID as randomUUID2 } from "crypto";
4982
5059
  import { spawn as spawn7 } from "child_process";
4983
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5060
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
4984
5061
  import os3 from "os";
4985
5062
  import path9 from "path";
4986
5063
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
4987
5064
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
4988
5065
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
4989
5066
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
5067
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5068
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5069
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
4990
5070
  function parseToolArguments(raw) {
4991
5071
  if (typeof raw !== "string") return raw;
4992
5072
  try {
@@ -4995,6 +5075,73 @@ function parseToolArguments(raw) {
4995
5075
  return raw;
4996
5076
  }
4997
5077
  }
5078
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
5079
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5080
+ if (inlineConfig && inlineConfig.trim()) {
5081
+ return {
5082
+ raw: inlineConfig,
5083
+ explicitPath: null,
5084
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5085
+ };
5086
+ }
5087
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5088
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
5089
+ try {
5090
+ return {
5091
+ raw: readFileSync3(configPath, "utf8"),
5092
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5093
+ sourcePath: configPath
5094
+ };
5095
+ } catch {
5096
+ return {
5097
+ raw: null,
5098
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5099
+ sourcePath: configPath
5100
+ };
5101
+ }
5102
+ }
5103
+ function buildKimiSpawnEnv(env = process.env) {
5104
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5105
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5106
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5107
+ return scrubDaemonChildEnv(spawnEnv);
5108
+ }
5109
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
5110
+ return {
5111
+ ...process.env,
5112
+ ...ctx.config.envVars || {},
5113
+ ...overrideEnv || {}
5114
+ };
5115
+ }
5116
+ function buildKimiLaunchOptions(ctx, opts = {}) {
5117
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
5118
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
5119
+ const args = [];
5120
+ let configFilePath = null;
5121
+ let configContent = null;
5122
+ if (source.explicitPath) {
5123
+ configFilePath = source.explicitPath;
5124
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5125
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5126
+ configContent = source.raw;
5127
+ if (opts.writeGeneratedConfig !== false) {
5128
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5129
+ chmodSync(configFilePath, 384);
5130
+ }
5131
+ }
5132
+ if (configFilePath) {
5133
+ args.push("--config-file", configFilePath);
5134
+ }
5135
+ if (ctx.config.model && ctx.config.model !== "default") {
5136
+ args.push("--model", ctx.config.model);
5137
+ }
5138
+ return {
5139
+ args,
5140
+ env: buildKimiSpawnEnv(env),
5141
+ configFilePath,
5142
+ configContent
5143
+ };
5144
+ }
4998
5145
  function resolveKimiSpawn(commandArgs, deps = {}) {
4999
5146
  return {
5000
5147
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5018,7 +5165,25 @@ var KimiDriver = class {
5018
5165
  };
5019
5166
  model = {
5020
5167
  detectedModelsVerifiedAs: "launchable",
5021
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5168
+ toLaunchSpec: (modelId, ctx, opts) => {
5169
+ if (!ctx) return { args: ["--model", modelId] };
5170
+ const launchCtx = {
5171
+ ...ctx,
5172
+ config: {
5173
+ ...ctx.config,
5174
+ model: modelId
5175
+ }
5176
+ };
5177
+ const launch = buildKimiLaunchOptions(launchCtx, {
5178
+ home: opts?.home,
5179
+ writeGeneratedConfig: false
5180
+ });
5181
+ return {
5182
+ args: launch.args,
5183
+ env: launch.env,
5184
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5185
+ };
5186
+ }
5022
5187
  };
5023
5188
  supportsStdinNotification = true;
5024
5189
  mcpToolPrefix = "";
@@ -5072,6 +5237,7 @@ var KimiDriver = class {
5072
5237
  }
5073
5238
  }
5074
5239
  }), "utf8");
5240
+ const launch = buildKimiLaunchOptions(ctx);
5075
5241
  const args = [
5076
5242
  "--wire",
5077
5243
  "--yolo",
@@ -5080,15 +5246,16 @@ var KimiDriver = class {
5080
5246
  "--mcp-config-file",
5081
5247
  mcpConfigPath,
5082
5248
  "--session",
5083
- this.sessionId
5249
+ this.sessionId,
5250
+ ...launch.args
5084
5251
  ];
5085
5252
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5086
5253
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5087
5254
  args.push("--model", launchRuntimeFields.model);
5088
5255
  }
5089
5256
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5090
- const launch = resolveKimiSpawn(args);
5091
- const proc = spawn7(launch.command, launch.args, {
5257
+ const spawnTarget = resolveKimiSpawn(args);
5258
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5092
5259
  cwd: ctx.workingDirectory,
5093
5260
  stdio: ["pipe", "pipe", "pipe"],
5094
5261
  env: spawnEnv,
@@ -5096,7 +5263,7 @@ var KimiDriver = class {
5096
5263
  // and has an 8191-character command-line limit. Kimi's official
5097
5264
  // installer/uv entrypoint is an executable, so launch it directly and
5098
5265
  // keep prompts on stdin / files instead of routing through cmd.exe.
5099
- shell: launch.shell
5266
+ shell: spawnTarget.shell
5100
5267
  });
5101
5268
  proc.stdin?.write(JSON.stringify({
5102
5269
  jsonrpc: "2.0",
@@ -5210,14 +5377,9 @@ var KimiDriver = class {
5210
5377
  return detectKimiModels();
5211
5378
  }
5212
5379
  };
5213
- function detectKimiModels(home = os3.homedir()) {
5214
- const configPath = path9.join(home, ".kimi", "config.toml");
5215
- let raw;
5216
- try {
5217
- raw = readFileSync3(configPath, "utf8");
5218
- } catch {
5219
- return null;
5220
- }
5380
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
5381
+ const raw = readKimiConfigSource(home, opts.env).raw;
5382
+ if (raw === null) return null;
5221
5383
  const models = [];
5222
5384
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
5223
5385
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -5481,7 +5643,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
5481
5643
  const platform = deps.platform ?? process.platform;
5482
5644
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
5483
5645
  const result = spawnSyncFn("opencode", ["models"], {
5484
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
5646
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
5485
5647
  encoding: "utf8",
5486
5648
  timeout: 5e3,
5487
5649
  shell: platform === "win32"
@@ -5740,6 +5902,297 @@ var OpenCodeDriver = class {
5740
5902
  }
5741
5903
  };
5742
5904
 
5905
+ // src/drivers/pi.ts
5906
+ import { spawn as spawn9 } from "child_process";
5907
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
5908
+ import path11 from "path";
5909
+ import { fileURLToPath } from "url";
5910
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
5911
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
5912
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
5913
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
5914
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
5915
+ function parseSemver2(version) {
5916
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
5917
+ if (!match) return null;
5918
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
5919
+ }
5920
+ function isSupportedPiVersion(version) {
5921
+ if (!version) return true;
5922
+ const actual = parseSemver2(version);
5923
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
5924
+ if (!actual || !minimum) return true;
5925
+ for (let i = 0; i < 3; i += 1) {
5926
+ if (actual[i] > minimum[i]) return true;
5927
+ if (actual[i] < minimum[i]) return false;
5928
+ }
5929
+ return true;
5930
+ }
5931
+ function unsupportedPiVersionMessage(version) {
5932
+ if (!version || isSupportedPiVersion(version)) return null;
5933
+ return `Pi SDK ${version} is unsupported; requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION}. Upgrade the daemon Pi dependency before starting this runtime.`;
5934
+ }
5935
+ function probePi(version = PI_SDK_VERSION) {
5936
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
5937
+ if (unsupportedMessage) {
5938
+ return {
5939
+ available: false,
5940
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
5941
+ };
5942
+ }
5943
+ return { available: true, version };
5944
+ }
5945
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
5946
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
5947
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
5948
+ if (existsSync8(sourceSibling)) return sourceSibling;
5949
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
5950
+ if (existsSync8(bundledEntry)) return bundledEntry;
5951
+ return path11.join(moduleDir, "piSdkRunner.js");
5952
+ }
5953
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
5954
+ if (runnerPath.endsWith(".ts")) {
5955
+ return [...process.execArgv, runnerPath];
5956
+ }
5957
+ return [runnerPath];
5958
+ }
5959
+ async function buildPiLaunchOptions(ctx, opts = {}) {
5960
+ const command = opts.command ?? process.execPath;
5961
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
5962
+ const sessionDir = path11.join(piDir, "sessions");
5963
+ mkdirSync4(sessionDir, { recursive: true });
5964
+ const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
5965
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
5966
+ const agentDir = opts.agentDir ?? getAgentDir();
5967
+ const runnerConfigPath = path11.join(
5968
+ piDir,
5969
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
5970
+ );
5971
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
5972
+ const runnerConfig = {
5973
+ cwd: ctx.workingDirectory,
5974
+ agentDir,
5975
+ sessionDir,
5976
+ sessionId: ctx.config.sessionId || null,
5977
+ standingPrompt: ctx.standingPrompt,
5978
+ prompt: turnPrompt,
5979
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
5980
+ };
5981
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
5982
+ `, { encoding: "utf8", mode: 384 });
5983
+ const args = [
5984
+ ...buildPiSdkNodeArgs(runnerPath),
5985
+ "--config",
5986
+ runnerConfigPath
5987
+ ];
5988
+ return {
5989
+ command,
5990
+ args,
5991
+ env: slock.spawnEnv,
5992
+ sessionDir,
5993
+ agentDir,
5994
+ runnerConfigPath,
5995
+ sdkVersion: PI_SDK_VERSION
5996
+ };
5997
+ }
5998
+ function isSystemFirstMessageTask2(message) {
5999
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
6000
+ }
6001
+ function buildPiSystemPrompt(config) {
6002
+ return buildCliTransportSystemPrompt(config, {
6003
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
6004
+ extraCriticalRules: [
6005
+ "- Runtime Profile migration controls are not available in the Pi runtime yet. If asked to acknowledge a runtime migration, explain the blocker instead of inventing a command."
6006
+ ],
6007
+ postStartupNotes: [
6008
+ "**Pi runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
6009
+ ],
6010
+ includeStdinNotificationSection: false,
6011
+ messageNotificationStyle: "poll"
6012
+ });
6013
+ }
6014
+ function contentText(content) {
6015
+ if (!content) return "";
6016
+ const chunks = [];
6017
+ for (const item of content) {
6018
+ if (item.type === "text" && typeof item.text === "string") {
6019
+ chunks.push(item.text);
6020
+ }
6021
+ }
6022
+ return chunks.join("");
6023
+ }
6024
+ function apiKeyErrorMessage(line) {
6025
+ const trimmed = line.trim();
6026
+ if (!trimmed) return null;
6027
+ if (/no api key found/i.test(trimmed)) return trimmed;
6028
+ if (/api key.+required/i.test(trimmed)) return trimmed;
6029
+ if (/no models available/i.test(trimmed)) return trimmed;
6030
+ return null;
6031
+ }
6032
+ var PiDriver = class {
6033
+ id = "pi";
6034
+ lifecycle = {
6035
+ kind: "per_turn",
6036
+ start: "defer_until_concrete_message",
6037
+ exit: "terminate_on_turn_end",
6038
+ inFlightWake: "coalesce_into_pending"
6039
+ };
6040
+ communication = {
6041
+ chat: "slock_cli",
6042
+ runtimeControl: "none"
6043
+ };
6044
+ session = {
6045
+ recovery: "resume_or_fresh"
6046
+ };
6047
+ model = {
6048
+ detectedModelsVerifiedAs: "launchable",
6049
+ toLaunchSpec: async (modelId, ctx) => {
6050
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
6051
+ const launchCtx = {
6052
+ ...ctx,
6053
+ config: {
6054
+ ...ctx.config,
6055
+ model: modelId
6056
+ }
6057
+ };
6058
+ const launch = await buildPiLaunchOptions(launchCtx);
6059
+ return {
6060
+ args: launch.args,
6061
+ env: launch.env,
6062
+ configFiles: [launch.runnerConfigPath],
6063
+ params: {
6064
+ agentDir: launch.agentDir,
6065
+ sessionDir: launch.sessionDir,
6066
+ sdkVersion: launch.sdkVersion,
6067
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
6068
+ }
6069
+ };
6070
+ }
6071
+ };
6072
+ supportsStdinNotification = false;
6073
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
6074
+ busyDeliveryMode = "none";
6075
+ terminateProcessOnTurnEnd = true;
6076
+ deferSpawnUntilMessage = true;
6077
+ usesSlockCliForCommunication = true;
6078
+ sessionId = null;
6079
+ sessionAnnounced = false;
6080
+ apiKeyErrorAnnounced = false;
6081
+ turnEnded = false;
6082
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
6083
+ shouldDeferWakeMessage(message) {
6084
+ return isSystemFirstMessageTask2(message);
6085
+ }
6086
+ probe() {
6087
+ return probePi();
6088
+ }
6089
+ async detectModels() {
6090
+ return null;
6091
+ }
6092
+ async spawn(ctx) {
6093
+ this.sessionId = ctx.config.sessionId || null;
6094
+ this.sessionAnnounced = false;
6095
+ this.apiKeyErrorAnnounced = false;
6096
+ this.turnEnded = false;
6097
+ this.assistantTextByMessageId.clear();
6098
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
6099
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
6100
+ const launch = await buildPiLaunchOptions(ctx);
6101
+ const proc = spawn9(launch.command, launch.args, {
6102
+ cwd: ctx.workingDirectory,
6103
+ stdio: ["pipe", "pipe", "pipe"],
6104
+ env: launch.env,
6105
+ shell: false
6106
+ });
6107
+ proc.stdin?.end();
6108
+ return { process: proc };
6109
+ }
6110
+ parseLine(line) {
6111
+ let event;
6112
+ try {
6113
+ event = JSON.parse(line);
6114
+ } catch {
6115
+ if (this.apiKeyErrorAnnounced) return [];
6116
+ const message = apiKeyErrorMessage(line);
6117
+ if (!message) return [];
6118
+ this.apiKeyErrorAnnounced = true;
6119
+ this.turnEnded = true;
6120
+ return [
6121
+ { kind: "error", message },
6122
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
6123
+ ];
6124
+ }
6125
+ const events = [];
6126
+ if (event.type === "session" && event.id) {
6127
+ this.sessionId = event.id;
6128
+ }
6129
+ if (!this.sessionAnnounced && this.sessionId) {
6130
+ events.push({ kind: "session_init", sessionId: this.sessionId });
6131
+ this.sessionAnnounced = true;
6132
+ }
6133
+ switch (event.type) {
6134
+ case "agent_start":
6135
+ case "turn_start":
6136
+ events.push({ kind: "thinking", text: "" });
6137
+ break;
6138
+ case "message_update":
6139
+ case "message_end":
6140
+ if (event.message?.role === "assistant") {
6141
+ const key = event.message.id || "current";
6142
+ const currentText = contentText(event.message.content);
6143
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
6144
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
6145
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
6146
+ } else if (currentText && currentText !== previousText) {
6147
+ events.push({ kind: "text", text: currentText });
6148
+ }
6149
+ this.assistantTextByMessageId.set(key, currentText);
6150
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
6151
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
6152
+ }
6153
+ }
6154
+ break;
6155
+ case "tool_execution_start":
6156
+ events.push({
6157
+ kind: "tool_call",
6158
+ name: event.toolName || "unknown_tool",
6159
+ input: event.args
6160
+ });
6161
+ break;
6162
+ case "tool_execution_end":
6163
+ events.push({
6164
+ kind: "tool_output",
6165
+ name: event.toolName || "unknown_tool"
6166
+ });
6167
+ if (event.isError) {
6168
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
6169
+ }
6170
+ break;
6171
+ case "compaction_start":
6172
+ events.push({ kind: "compaction_started" });
6173
+ break;
6174
+ case "compaction_end":
6175
+ events.push({ kind: "compaction_finished" });
6176
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
6177
+ break;
6178
+ case "turn_end":
6179
+ case "agent_end":
6180
+ if (!this.turnEnded) {
6181
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6182
+ this.turnEnded = true;
6183
+ }
6184
+ break;
6185
+ }
6186
+ return events;
6187
+ }
6188
+ encodeStdinMessage(_text, _sessionId, _opts) {
6189
+ return null;
6190
+ }
6191
+ buildSystemPrompt(config, _agentId) {
6192
+ return buildPiSystemPrompt(config);
6193
+ }
6194
+ };
6195
+
5743
6196
  // src/drivers/index.ts
5744
6197
  var driverFactories = {
5745
6198
  claude: () => new ClaudeDriver(),
@@ -5749,7 +6202,8 @@ var driverFactories = {
5749
6202
  cursor: () => new CursorDriver(),
5750
6203
  gemini: () => new GeminiDriver(),
5751
6204
  kimi: () => new KimiDriver(),
5752
- opencode: () => new OpenCodeDriver()
6205
+ opencode: () => new OpenCodeDriver(),
6206
+ pi: () => new PiDriver()
5753
6207
  };
5754
6208
  function getDriver(runtimeId) {
5755
6209
  const createDriver = driverFactories[runtimeId];
@@ -5762,7 +6216,7 @@ function getDriver(runtimeId) {
5762
6216
 
5763
6217
  // src/workspaces.ts
5764
6218
  import { readdir, rm, stat } from "fs/promises";
5765
- import path11 from "path";
6219
+ import path12 from "path";
5766
6220
  function isValidWorkspaceDirectoryName(directoryName) {
5767
6221
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
5768
6222
  }
@@ -5770,7 +6224,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
5770
6224
  if (!isValidWorkspaceDirectoryName(directoryName)) {
5771
6225
  return null;
5772
6226
  }
5773
- return path11.join(dataDir, directoryName);
6227
+ return path12.join(dataDir, directoryName);
5774
6228
  }
5775
6229
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
5776
6230
  return {
@@ -5819,7 +6273,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
5819
6273
  return summary;
5820
6274
  }
5821
6275
  const childSummaries = await Promise.all(
5822
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
6276
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
5823
6277
  );
5824
6278
  for (const childSummary of childSummaries) {
5825
6279
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -5838,7 +6292,7 @@ async function scanWorkspaceDirectories(dataDir) {
5838
6292
  if (!entry.isDirectory()) {
5839
6293
  return null;
5840
6294
  }
5841
- const dirPath = path11.join(dataDir, entry.name);
6295
+ const dirPath = path12.join(dataDir, entry.name);
5842
6296
  try {
5843
6297
  const summary = await summarizeWorkspaceDirectory(dirPath);
5844
6298
  return {
@@ -6278,12 +6732,12 @@ function findSessionJsonl(root, predicate) {
6278
6732
  for (const entry of entries) {
6279
6733
  if (++visited > maxEntries) return null;
6280
6734
  if (!entry.isFile() || !predicate(entry.name)) continue;
6281
- return path12.join(dir, entry.name);
6735
+ return path13.join(dir, entry.name);
6282
6736
  }
6283
6737
  for (const entry of entries) {
6284
6738
  if (++visited > maxEntries) return null;
6285
6739
  if (!entry.isDirectory()) continue;
6286
- const found = visit(path12.join(dir, entry.name), depth - 1);
6740
+ const found = visit(path13.join(dir, entry.name), depth - 1);
6287
6741
  if (found) return found;
6288
6742
  }
6289
6743
  return null;
@@ -6296,10 +6750,10 @@ function safeSessionFilename(value) {
6296
6750
  }
6297
6751
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6298
6752
  try {
6299
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
6300
- mkdirSync4(dir, { recursive: true });
6301
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6302
- writeFileSync7(filePath, JSON.stringify({
6753
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6754
+ mkdirSync5(dir, { recursive: true });
6755
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6756
+ writeFileSync8(filePath, JSON.stringify({
6303
6757
  type: "runtime_session_handoff",
6304
6758
  runtime,
6305
6759
  sessionId,
@@ -6318,7 +6772,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6318
6772
  }
6319
6773
  }
6320
6774
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
6321
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
6775
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
6322
6776
  if (directPath) {
6323
6777
  try {
6324
6778
  if (statSync(directPath).isFile()) {
@@ -6327,7 +6781,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
6327
6781
  } catch {
6328
6782
  }
6329
6783
  }
6330
- const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
6784
+ const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
6331
6785
  if (!resolvedPath && fallbackDir) {
6332
6786
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
6333
6787
  if (fallback) return fallback;
@@ -7280,6 +7734,7 @@ var AgentProcessManager = class _AgentProcessManager {
7280
7734
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
7281
7735
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
7282
7736
  agentVisibleBoundaries = /* @__PURE__ */ new Map();
7737
+ agentVisibleMessageIds = /* @__PURE__ */ new Map();
7283
7738
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
7284
7739
  this.chatBridgePath = chatBridgePath;
7285
7740
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -7325,6 +7780,24 @@ var AgentProcessManager = class _AgentProcessManager {
7325
7780
  getVisibleBoundary(agentId, target) {
7326
7781
  return this.agentVisibleBoundaries.get(agentId)?.get(target);
7327
7782
  }
7783
+ visibleMessageIdMap(agentId) {
7784
+ let map = this.agentVisibleMessageIds.get(agentId);
7785
+ if (!map) {
7786
+ map = /* @__PURE__ */ new Map();
7787
+ this.agentVisibleMessageIds.set(agentId, map);
7788
+ }
7789
+ return map;
7790
+ }
7791
+ getVisibleMessageIdSet(agentId, target) {
7792
+ return this.agentVisibleMessageIds.get(agentId)?.get(target);
7793
+ }
7794
+ isVisibleMessageModelSeen(agentId, target, message) {
7795
+ const seq = Number(message.seq ?? 0);
7796
+ const boundary = this.getVisibleBoundary(agentId, target);
7797
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= Math.floor(seq)) return true;
7798
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : "";
7799
+ return id.length > 0 && this.getVisibleMessageIdSet(agentId, target)?.has(id) === true;
7800
+ }
7328
7801
  scheduleStdinNotification(agentId, ap, delayMs) {
7329
7802
  return ap.notifications.schedule(() => {
7330
7803
  this.sendStdinNotification(agentId);
@@ -7359,13 +7832,14 @@ var AgentProcessManager = class _AgentProcessManager {
7359
7832
  };
7360
7833
  for (const message of input.messages) {
7361
7834
  const seq = Number(message.seq ?? 0);
7362
- if (!Number.isFinite(seq) || seq <= 0) continue;
7363
7835
  const target = input.target ?? formatVisibleMessageTarget(message);
7364
7836
  if (!target) continue;
7365
7837
  const bucket = ensureBucket(target);
7366
- bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
7367
- bucket.seqs.add(Math.floor(seq));
7368
- const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
7838
+ if (Number.isFinite(seq) && seq > 0) {
7839
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
7840
+ bucket.seqs.add(Math.floor(seq));
7841
+ }
7842
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : null;
7369
7843
  if (id) bucket.ids.add(id);
7370
7844
  }
7371
7845
  if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
@@ -7374,10 +7848,19 @@ var AgentProcessManager = class _AgentProcessManager {
7374
7848
  }
7375
7849
  if (byTarget.size === 0) return;
7376
7850
  const boundaryMap = this.visibleBoundaryMap(agentId);
7851
+ const visibleIds = this.visibleMessageIdMap(agentId);
7377
7852
  for (const [target, bucket] of byTarget) {
7378
7853
  const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
7379
7854
  const previous = boundaryMap.get(target) ?? 0;
7380
7855
  boundaryMap.set(target, Math.max(previous, highWaterSeq));
7856
+ if (bucket.ids.size > 0) {
7857
+ let targetIds = visibleIds.get(target);
7858
+ if (!targetIds) {
7859
+ targetIds = /* @__PURE__ */ new Set();
7860
+ visibleIds.set(target, targetIds);
7861
+ }
7862
+ for (const id of bucket.ids) targetIds.add(id);
7863
+ }
7381
7864
  }
7382
7865
  const suppress = (messages) => {
7383
7866
  if (!messages || messages.length === 0) return 0;
@@ -7410,6 +7893,7 @@ var AgentProcessManager = class _AgentProcessManager {
7410
7893
  return {
7411
7894
  getBoundary: (target) => this.getVisibleBoundary(agentId, target),
7412
7895
  getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
7896
+ isMessageModelSeen: ({ target, message }) => this.isVisibleMessageModelSeen(agentId, target, message),
7413
7897
  getAllPendingMessages: () => this.allPendingVisibleMessages(agentId),
7414
7898
  consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input),
7415
7899
  recordDrainOutcome: (input) => {
@@ -7721,7 +8205,7 @@ var AgentProcessManager = class _AgentProcessManager {
7721
8205
  );
7722
8206
  wakeMessage = void 0;
7723
8207
  }
7724
- const agentDataDir = path12.join(this.dataDir, agentId);
8208
+ const agentDataDir = path13.join(this.dataDir, agentId);
7725
8209
  await mkdir(agentDataDir, { recursive: true });
7726
8210
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
7727
8211
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -7735,23 +8219,23 @@ var AgentProcessManager = class _AgentProcessManager {
7735
8219
  );
7736
8220
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
7737
8221
  }
7738
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
8222
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
7739
8223
  try {
7740
8224
  await access(memoryMdPath);
7741
8225
  } catch {
7742
8226
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
7743
8227
  await writeFile(memoryMdPath, initialMemoryMd);
7744
8228
  }
7745
- const notesDir = path12.join(agentDataDir, "notes");
8229
+ const notesDir = path13.join(agentDataDir, "notes");
7746
8230
  await mkdir(notesDir, { recursive: true });
7747
8231
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
7748
8232
  const seedFiles = buildOnboardingSeedFiles();
7749
8233
  for (const { relativePath, content } of seedFiles) {
7750
- const fullPath = path12.join(agentDataDir, relativePath);
8234
+ const fullPath = path13.join(agentDataDir, relativePath);
7751
8235
  try {
7752
8236
  await access(fullPath);
7753
8237
  } catch {
7754
- await mkdir(path12.dirname(fullPath), { recursive: true });
8238
+ await mkdir(path13.dirname(fullPath), { recursive: true });
7755
8239
  await writeFile(fullPath, content);
7756
8240
  }
7757
8241
  }
@@ -8623,7 +9107,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8623
9107
  return true;
8624
9108
  }
8625
9109
  async resetWorkspace(agentId) {
8626
- const agentDataDir = path12.join(this.dataDir, agentId);
9110
+ const agentDataDir = path13.join(this.dataDir, agentId);
8627
9111
  try {
8628
9112
  await rm2(agentDataDir, { recursive: true, force: true });
8629
9113
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -8684,7 +9168,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8684
9168
  return result;
8685
9169
  }
8686
9170
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
8687
- const workspacePath = path12.join(this.dataDir, agentId);
9171
+ const workspacePath = path13.join(this.dataDir, agentId);
8688
9172
  return {
8689
9173
  agentId,
8690
9174
  launchId,
@@ -8941,7 +9425,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8941
9425
  }
8942
9426
  // Workspace file browsing
8943
9427
  async getFileTree(agentId, dirPath) {
8944
- const agentDir = path12.join(this.dataDir, agentId);
9428
+ const agentDir = path13.join(this.dataDir, agentId);
8945
9429
  try {
8946
9430
  await stat2(agentDir);
8947
9431
  } catch {
@@ -8949,8 +9433,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8949
9433
  }
8950
9434
  let targetDir = agentDir;
8951
9435
  if (dirPath) {
8952
- const resolved = path12.resolve(agentDir, dirPath);
8953
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9436
+ const resolved = path13.resolve(agentDir, dirPath);
9437
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
8954
9438
  return [];
8955
9439
  }
8956
9440
  targetDir = resolved;
@@ -8958,14 +9442,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8958
9442
  return this.listDirectoryChildren(targetDir, agentDir);
8959
9443
  }
8960
9444
  async readFile(agentId, filePath) {
8961
- const agentDir = path12.join(this.dataDir, agentId);
8962
- const resolved = path12.resolve(agentDir, filePath);
8963
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9445
+ const agentDir = path13.join(this.dataDir, agentId);
9446
+ const resolved = path13.resolve(agentDir, filePath);
9447
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
8964
9448
  throw new Error("Access denied");
8965
9449
  }
8966
9450
  const info = await stat2(resolved);
8967
9451
  if (info.isDirectory()) throw new Error("Cannot read a directory");
8968
- const ext = path12.extname(resolved).toLowerCase();
9452
+ const ext = path13.extname(resolved).toLowerCase();
8969
9453
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
8970
9454
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
8971
9455
  const content = await readFile(resolved, "utf-8");
@@ -9000,13 +9484,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9000
9484
  const agent = this.agents.get(agentId);
9001
9485
  const runtime = runtimeHint || agent?.config.runtime || "claude";
9002
9486
  const home = os5.homedir();
9003
- const workspaceDir = path12.join(this.dataDir, agentId);
9487
+ const workspaceDir = path13.join(this.dataDir, agentId);
9004
9488
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
9005
9489
  const globalResults = await Promise.all(
9006
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
9490
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
9007
9491
  );
9008
9492
  const workspaceResults = await Promise.all(
9009
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
9493
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
9010
9494
  );
9011
9495
  const dedup = (skills) => {
9012
9496
  const seen = /* @__PURE__ */ new Set();
@@ -9035,7 +9519,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9035
9519
  const skills = [];
9036
9520
  for (const entry of entries) {
9037
9521
  if (entry.isDirectory() || entry.isSymbolicLink()) {
9038
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
9522
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
9039
9523
  try {
9040
9524
  const content = await readFile(skillMd, "utf-8");
9041
9525
  const skill = this.parseSkillMd(entry.name, content);
@@ -9046,7 +9530,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9046
9530
  } else if (entry.name.endsWith(".md")) {
9047
9531
  const cmdName = entry.name.replace(/\.md$/, "");
9048
9532
  try {
9049
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
9533
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
9050
9534
  const skill = this.parseSkillMd(cmdName, content);
9051
9535
  skill.sourcePath = dir;
9052
9536
  skills.push(skill);
@@ -9606,7 +10090,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9606
10090
  }
9607
10091
  }
9608
10092
  isThinkingBlockMutationError(message) {
9609
- return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message);
10093
+ return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message) || /messages\.\d+\.content\.\d+\.text\.start_timestamp:\s*Extra inputs are not permitted/i.test(message);
9610
10094
  }
9611
10095
  markRuntimeProgressStaleIfNeeded(agentId, ap) {
9612
10096
  if (ap.lastActivity !== "working" && ap.lastActivity !== "thinking") return false;
@@ -10164,8 +10648,8 @@ ${RESPONSE_TARGET_HINT}`);
10164
10648
  const nodes = [];
10165
10649
  for (const entry of entries) {
10166
10650
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
10167
- const fullPath = path12.join(dir, entry.name);
10168
- const relativePath = path12.relative(rootDir, fullPath);
10651
+ const fullPath = path13.join(dir, entry.name);
10652
+ const relativePath = path13.relative(rootDir, fullPath);
10169
10653
  let info;
10170
10654
  try {
10171
10655
  info = await stat2(fullPath);
@@ -10470,9 +10954,9 @@ var ReminderCache = class {
10470
10954
 
10471
10955
  // src/machineLock.ts
10472
10956
  import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
10473
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
10957
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10474
10958
  import os6 from "os";
10475
- import path13 from "path";
10959
+ import path14 from "path";
10476
10960
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
10477
10961
  var DaemonMachineLockConflictError = class extends Error {
10478
10962
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -10494,7 +10978,7 @@ function resolveDefaultMachineStateRoot() {
10494
10978
  return resolveSlockHomePath("machines");
10495
10979
  }
10496
10980
  function ownerPath(lockDir) {
10497
- return path13.join(lockDir, "owner.json");
10981
+ return path14.join(lockDir, "owner.json");
10498
10982
  }
10499
10983
  function readOwner(lockDir) {
10500
10984
  try {
@@ -10524,13 +11008,13 @@ function acquireDaemonMachineLock(options) {
10524
11008
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
10525
11009
  const fingerprint = apiKeyFingerprint(options.apiKey);
10526
11010
  const lockId = getDaemonMachineLockId(options.apiKey);
10527
- const machineDir = path13.join(rootDir, lockId);
10528
- const lockDir = path13.join(machineDir, "daemon.lock");
11011
+ const machineDir = path14.join(rootDir, lockId);
11012
+ const lockDir = path14.join(machineDir, "daemon.lock");
10529
11013
  const token = randomUUID3();
10530
- mkdirSync5(machineDir, { recursive: true });
11014
+ mkdirSync6(machineDir, { recursive: true });
10531
11015
  for (let attempt = 0; attempt < 2; attempt += 1) {
10532
11016
  try {
10533
- mkdirSync5(lockDir);
11017
+ mkdirSync6(lockDir);
10534
11018
  const owner = {
10535
11019
  pid: process.pid,
10536
11020
  token,
@@ -10540,7 +11024,7 @@ function acquireDaemonMachineLock(options) {
10540
11024
  apiKeyFingerprint: fingerprint.slice(0, 16)
10541
11025
  };
10542
11026
  try {
10543
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
11027
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
10544
11028
  `, { mode: 384 });
10545
11029
  } catch (err) {
10546
11030
  rmSync3(lockDir, { recursive: true, force: true });
@@ -10577,8 +11061,8 @@ function acquireDaemonMachineLock(options) {
10577
11061
  }
10578
11062
 
10579
11063
  // src/localTraceSink.ts
10580
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
10581
- import path14 from "path";
11064
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
11065
+ import path15 from "path";
10582
11066
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
10583
11067
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
10584
11068
  var DEFAULT_MAX_FILES = 8;
@@ -10615,7 +11099,7 @@ var LocalRotatingTraceSink = class {
10615
11099
  currentSize = 0;
10616
11100
  sequence = 0;
10617
11101
  constructor(options) {
10618
- this.traceDir = path14.join(options.machineDir, "traces");
11102
+ this.traceDir = path15.join(options.machineDir, "traces");
10619
11103
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
10620
11104
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
10621
11105
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -10641,15 +11125,15 @@ var LocalRotatingTraceSink = class {
10641
11125
  return this.currentFile;
10642
11126
  }
10643
11127
  ensureFile(nextBytes) {
10644
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
11128
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
10645
11129
  const nowMs = this.nowMsProvider();
10646
11130
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
10647
11131
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
10648
- this.currentFile = path14.join(
11132
+ this.currentFile = path15.join(
10649
11133
  this.traceDir,
10650
11134
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
10651
11135
  );
10652
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
11136
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
10653
11137
  this.currentSize = statSync3(this.currentFile).size;
10654
11138
  this.currentFileOpenedAtMs = nowMs;
10655
11139
  this.pruneOldFiles();
@@ -10660,7 +11144,7 @@ var LocalRotatingTraceSink = class {
10660
11144
  const excess = files.length - this.maxFiles;
10661
11145
  if (excess <= 0) return;
10662
11146
  for (const file of files.slice(0, excess)) {
10663
- rmSync4(path14.join(this.traceDir, file), { force: true });
11147
+ rmSync4(path15.join(this.traceDir, file), { force: true });
10664
11148
  }
10665
11149
  }
10666
11150
  };
@@ -10751,11 +11235,11 @@ function isDiagnosticErrorAttr(key) {
10751
11235
  import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
10752
11236
  import { gzipSync } from "zlib";
10753
11237
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
10754
- import path15 from "path";
11238
+ import path16 from "path";
10755
11239
 
10756
11240
  // src/directUploadCapability.ts
10757
- function joinUrl(base, path17) {
10758
- return `${base.replace(/\/+$/, "")}${path17}`;
11241
+ function joinUrl(base, path18) {
11242
+ return `${base.replace(/\/+$/, "")}${path18}`;
10759
11243
  }
10760
11244
  function jsonHeaders(apiKey) {
10761
11245
  return {
@@ -10974,7 +11458,7 @@ var DaemonTraceBundleUploader = class {
10974
11458
  }, nextMs);
10975
11459
  }
10976
11460
  async findUploadCandidates() {
10977
- const traceDir = path15.join(this.options.machineDir, "traces");
11461
+ const traceDir = path16.join(this.options.machineDir, "traces");
10978
11462
  let names;
10979
11463
  try {
10980
11464
  names = await readdir3(traceDir);
@@ -10986,8 +11470,8 @@ var DaemonTraceBundleUploader = class {
10986
11470
  const currentFile = this.options.currentFileProvider?.();
10987
11471
  const candidates = [];
10988
11472
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
10989
- const file = path15.join(traceDir, name);
10990
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
11473
+ const file = path16.join(traceDir, name);
11474
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
10991
11475
  if (await this.isUploaded(file)) continue;
10992
11476
  try {
10993
11477
  const info = await stat3(file);
@@ -11061,8 +11545,8 @@ var DaemonTraceBundleUploader = class {
11061
11545
  }
11062
11546
  }
11063
11547
  uploadStatePath(file) {
11064
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
11065
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
11548
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
11549
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
11066
11550
  }
11067
11551
  async isUploaded(file) {
11068
11552
  try {
@@ -11074,9 +11558,9 @@ var DaemonTraceBundleUploader = class {
11074
11558
  }
11075
11559
  async markUploaded(file, metadata) {
11076
11560
  const stateFile = this.uploadStatePath(file);
11077
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
11561
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
11078
11562
  await writeFile2(stateFile, `${JSON.stringify({
11079
- file: path15.basename(file),
11563
+ file: path16.basename(file),
11080
11564
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
11081
11565
  ...metadata
11082
11566
  }, null, 2)}
@@ -11098,7 +11582,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
11098
11582
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
11099
11583
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
11100
11584
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
11101
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
11585
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
11102
11586
  var RunnerCredentialMintError2 = class extends Error {
11103
11587
  code;
11104
11588
  retryable;
@@ -11134,9 +11618,9 @@ function runnerCredentialErrorDetail2(error) {
11134
11618
  async function waitForRunnerCredentialRetry2() {
11135
11619
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
11136
11620
  }
11137
- function parseDaemonCliArgs(args) {
11621
+ function parseDaemonCliArgs(args, env = {}) {
11138
11622
  let serverUrl = "";
11139
- let apiKey = "";
11623
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
11140
11624
  for (let i = 0; i < args.length; i++) {
11141
11625
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
11142
11626
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -11153,23 +11637,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
11153
11637
  }
11154
11638
  }
11155
11639
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
11156
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
11157
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
11640
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
11641
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
11158
11642
  try {
11159
11643
  accessSync(jsPath);
11160
11644
  return jsPath;
11161
11645
  } catch {
11162
- return path16.resolve(dirname, "chat-bridge.ts");
11646
+ return path17.resolve(dirname, "chat-bridge.ts");
11163
11647
  }
11164
11648
  }
11165
11649
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
11166
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
11167
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
11650
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
11651
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
11168
11652
  try {
11169
11653
  accessSync(bundledDistPath);
11170
11654
  return bundledDistPath;
11171
11655
  } catch {
11172
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11656
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11173
11657
  accessSync(workspaceDistPath);
11174
11658
  return workspaceDistPath;
11175
11659
  }
@@ -11299,6 +11783,11 @@ function summarizeIncomingMessage(msg) {
11299
11783
  var DaemonCore = class {
11300
11784
  options;
11301
11785
  daemonVersion;
11786
+ // When this runner is launched by a managed Computer service, the
11787
+ // service exports SLOCK_COMPUTER_VERSION (the `@slock-ai/computer`
11788
+ // bundle version). Reported in `ready` so the server can surface the
11789
+ // Computer's own version (distinct from the underlying daemonVersion).
11790
+ computerVersion;
11302
11791
  chatBridgePath;
11303
11792
  slockCliPath;
11304
11793
  slockHome;
@@ -11314,6 +11803,7 @@ var DaemonCore = class {
11314
11803
  constructor(options) {
11315
11804
  this.options = options;
11316
11805
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
11806
+ this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
11317
11807
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
11318
11808
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
11319
11809
  this.slockHome = resolveSlockHome();
@@ -11348,7 +11838,7 @@ var DaemonCore = class {
11348
11838
  }
11349
11839
  resolveMachineStateRoot() {
11350
11840
  if (this.options.machineStateDir) return this.options.machineStateDir;
11351
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
11841
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
11352
11842
  return resolveDefaultMachineStateRoot();
11353
11843
  }
11354
11844
  shouldEnableLocalTrace() {
@@ -11374,7 +11864,7 @@ var DaemonCore = class {
11374
11864
  sink: this.localTraceSink
11375
11865
  });
11376
11866
  this.agentManager.setTracer(this.tracer);
11377
- this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
11867
+ this.agentManager.setCliTransportTraceDir(path17.join(machineDir, "traces"));
11378
11868
  }
11379
11869
  installTraceBundleUploader(machineDir) {
11380
11870
  if (!this.shouldEnableLocalTrace()) return;
@@ -11773,6 +12263,26 @@ var DaemonCore = class {
11773
12263
  case "ping":
11774
12264
  this.connection.send({ type: "pong" });
11775
12265
  break;
12266
+ case "computer:restart":
12267
+ case "computer:upgrade": {
12268
+ const action = msg.type === "computer:restart" ? "restart" : "upgrade";
12269
+ this.recordDaemonTrace("daemon.computer_control.received", {
12270
+ action,
12271
+ handled: Boolean(this.options.onComputerControl)
12272
+ });
12273
+ if (this.options.onComputerControl) {
12274
+ try {
12275
+ this.options.onComputerControl(action);
12276
+ } catch (err) {
12277
+ logger.error(
12278
+ `[Daemon] computer:${action} control handler failed: ${err instanceof Error ? err.message : String(err)}`
12279
+ );
12280
+ }
12281
+ } else {
12282
+ logger.info(`[Daemon] Ignoring computer:${action} \u2014 not launched by a Computer service.`);
12283
+ }
12284
+ break;
12285
+ }
11776
12286
  }
11777
12287
  }
11778
12288
  onReminderFire(job) {
@@ -11799,7 +12309,8 @@ var DaemonCore = class {
11799
12309
  runningAgents: runningAgentIds,
11800
12310
  hostname: this.options.hostname ?? os7.hostname(),
11801
12311
  os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
11802
- daemonVersion: this.daemonVersion
12312
+ daemonVersion: this.daemonVersion,
12313
+ ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
11803
12314
  });
11804
12315
  this.recordDaemonTrace("daemon.ready.sent", {
11805
12316
  runtimes_count: runtimes.length,
@@ -11860,6 +12371,8 @@ var DaemonCore = class {
11860
12371
  };
11861
12372
 
11862
12373
  export {
12374
+ DAEMON_API_KEY_ENV,
12375
+ scrubDaemonAuthEnv,
11863
12376
  resolveWorkspaceDirectoryPath,
11864
12377
  scanWorkspaceDirectories,
11865
12378
  deleteWorkspaceDirectory,