@slock-ai/daemon 0.57.2-play.20260608142014 → 0.57.3

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.
@@ -1438,9 +1438,9 @@ Error code prefixes tell you the layer:
1438
1438
  function buildCredentialHygieneSection() {
1439
1439
  return `### Credential hygiene
1440
1440
 
1441
- **Never paste credentials into Slock messages, attachments, or task fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must never appear in chat \u2014 not in debug traces, error reports, "for context" snippets, or screenshots. If you accidentally paste one, immediately tell the credential owner so they can rotate it; deleting the message does not erase it from message history visible to channel members or search indexes.
1441
+ **Never paste credentials into public Slock channels, public-channel threads, or public-channel task/attachment fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must not appear in public channel chat. DMs and private channels are allowed for authorized secret handoff, but verify the audience first. If you accidentally paste one into a public channel, immediately tell the credential owner so they can rotate it.
1442
1442
 
1443
- If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.
1443
+ If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before posting to a public channel.
1444
1444
 
1445
1445
  **Profile credential resolution is strict.** When invoked as \`slock --profile <slug>\` or with \`SLOCK_PROFILE=<slug>\`, the CLI resolves credentials from \`$SLOCK_PROFILE_DIR\` \u2192 \`$SLOCK_HOME/profiles/<slug>\` \u2192 \`$HOME/.slock/profiles/<slug>\` in that order. It does **not** fall back to a different profile's credential, to an ambient user-level token, or to environment-leaked secrets \u2014 if your designated profile credential is missing or unreadable, the CLI fails closed rather than authenticating as someone else.`;
1446
1446
  }
@@ -1808,9 +1808,9 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
1808
1808
  17. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
1809
1809
  const credentialHygieneSection = isCli ? cliGuideSections.credentialHygiene : `### Credential hygiene
1810
1810
 
1811
- **Never paste credentials into Slock messages, attachments, or task fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must never appear in chat \u2014 not in debug traces, error reports, "for context" snippets, or screenshots. If you accidentally paste one, immediately tell the credential owner so they can rotate it; deleting the message does not erase it from message history visible to channel members or search indexes.
1811
+ **Never paste credentials into public Slock channels, public-channel threads, or public-channel task/attachment fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must not appear in public channel chat. DMs and private channels are allowed for authorized secret handoff, but verify the audience first. If you accidentally paste one into a public channel, immediately tell the credential owner so they can rotate it.
1812
1812
 
1813
- If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.`;
1813
+ If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before posting to a public channel.`;
1814
1814
  const reminderSection = isCli ? cliGuideSections.reminders : `### Reminders
1815
1815
 
1816
1816
  Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
@@ -2081,19 +2081,6 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
2081
2081
  return candidates.filter((candidate) => existsSync(candidate.path));
2082
2082
  }
2083
2083
 
2084
- // src/authEnv.ts
2085
- var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
2086
- var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
2087
- function scrubDaemonAuthEnv(env) {
2088
- delete env[DAEMON_API_KEY_ENV];
2089
- return env;
2090
- }
2091
- function scrubDaemonChildEnv(env) {
2092
- delete env[DAEMON_API_KEY_ENV];
2093
- delete env[SLOCK_AGENT_TOKEN_ENV];
2094
- return env;
2095
- }
2096
-
2097
2084
  // src/agentCredentialProxy.ts
2098
2085
  import { randomBytes } from "crypto";
2099
2086
  import http from "http";
@@ -3580,9 +3567,7 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
3580
3567
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
3581
3568
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
3582
3569
  var RAW_CREDENTIAL_ENV_DENYLIST = [
3583
- "SLOCK_AGENT_TOKEN",
3584
- "SLOCK_AGENT_CREDENTIAL_KEY",
3585
- "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
3570
+ "SLOCK_AGENT_CREDENTIAL_KEY"
3586
3571
  ];
3587
3572
  var cachedOpencliBinPath;
3588
3573
  function resolveOpencliBinPath() {
@@ -3797,7 +3782,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
3797
3782
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
3798
3783
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
3799
3784
  };
3800
- scrubDaemonChildEnv(spawnEnv);
3785
+ delete spawnEnv.SLOCK_AGENT_TOKEN;
3801
3786
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
3802
3787
  delete spawnEnv[key];
3803
3788
  }
@@ -4226,7 +4211,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
4226
4211
  }
4227
4212
  function resolveCommandOnPath(command, deps = {}) {
4228
4213
  const platform = deps.platform ?? process.platform;
4229
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4214
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4230
4215
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4231
4216
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
4232
4217
  if (platform === "win32") {
@@ -4252,7 +4237,7 @@ function firstExistingPath(candidates, deps = {}) {
4252
4237
  return null;
4253
4238
  }
4254
4239
  function readCommandVersion(command, args = [], deps = {}) {
4255
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4240
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4256
4241
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4257
4242
  try {
4258
4243
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -5606,11 +5591,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
5606
5591
  return parseCursorModelsOutput(String(result.stdout || ""));
5607
5592
  }
5608
5593
  function buildCursorModelProbeEnv(deps = {}) {
5609
- return scrubDaemonChildEnv(withWindowsUserEnvironment({
5594
+ return withWindowsUserEnvironment({
5610
5595
  ...deps.env ?? process.env,
5611
5596
  FORCE_COLOR: "0",
5612
5597
  NO_COLOR: "1"
5613
- }, deps));
5598
+ }, deps);
5614
5599
  }
5615
5600
  function runCursorModelsCommand() {
5616
5601
  return spawnSync("cursor-agent", ["models"], {
@@ -5666,7 +5651,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
5666
5651
  }
5667
5652
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
5668
5653
  const existsSyncFn = deps.existsSyncFn ?? existsSync4;
5669
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
5654
+ const env = deps.env ?? process.env;
5670
5655
  const winPath = path6.win32;
5671
5656
  let geminiEntry = null;
5672
5657
  try {
@@ -5806,15 +5791,12 @@ var GeminiDriver = class {
5806
5791
  // src/drivers/kimi.ts
5807
5792
  import { randomUUID as randomUUID2 } from "crypto";
5808
5793
  import { spawn as spawn7 } from "child_process";
5809
- import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5794
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5810
5795
  import os3 from "os";
5811
5796
  import path7 from "path";
5812
5797
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
5813
5798
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
5814
5799
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
5815
- var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5816
- var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5817
- var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
5818
5800
  function parseToolArguments(raw) {
5819
5801
  if (typeof raw !== "string") return raw;
5820
5802
  try {
@@ -5823,73 +5805,6 @@ function parseToolArguments(raw) {
5823
5805
  return raw;
5824
5806
  }
5825
5807
  }
5826
- function readKimiConfigSource(home = os3.homedir(), env = process.env) {
5827
- const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5828
- if (inlineConfig && inlineConfig.trim()) {
5829
- return {
5830
- raw: inlineConfig,
5831
- explicitPath: null,
5832
- sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5833
- };
5834
- }
5835
- const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5836
- const configPath = explicitPath && explicitPath.trim() ? explicitPath : path7.join(home, ".kimi", "config.toml");
5837
- try {
5838
- return {
5839
- raw: readFileSync3(configPath, "utf8"),
5840
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5841
- sourcePath: configPath
5842
- };
5843
- } catch {
5844
- return {
5845
- raw: null,
5846
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5847
- sourcePath: configPath
5848
- };
5849
- }
5850
- }
5851
- function buildKimiSpawnEnv(env = process.env) {
5852
- const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5853
- delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5854
- delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5855
- return scrubDaemonChildEnv(spawnEnv);
5856
- }
5857
- function buildKimiEffectiveEnv(ctx, overrideEnv) {
5858
- return {
5859
- ...process.env,
5860
- ...ctx.config.envVars || {},
5861
- ...overrideEnv || {}
5862
- };
5863
- }
5864
- function buildKimiLaunchOptions(ctx, opts = {}) {
5865
- const env = buildKimiEffectiveEnv(ctx, opts.env);
5866
- const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
5867
- const args = [];
5868
- let configFilePath = null;
5869
- let configContent = null;
5870
- if (source.explicitPath) {
5871
- configFilePath = source.explicitPath;
5872
- } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5873
- configFilePath = path7.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5874
- configContent = source.raw;
5875
- if (opts.writeGeneratedConfig !== false) {
5876
- writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5877
- chmodSync(configFilePath, 384);
5878
- }
5879
- }
5880
- if (configFilePath) {
5881
- args.push("--config-file", configFilePath);
5882
- }
5883
- if (ctx.config.model && ctx.config.model !== "default") {
5884
- args.push("--model", ctx.config.model);
5885
- }
5886
- return {
5887
- args,
5888
- env: buildKimiSpawnEnv(env),
5889
- configFilePath,
5890
- configContent
5891
- };
5892
- }
5893
5808
  function resolveKimiSpawn(commandArgs, deps = {}) {
5894
5809
  return {
5895
5810
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5913,25 +5828,7 @@ var KimiDriver = class {
5913
5828
  };
5914
5829
  model = {
5915
5830
  detectedModelsVerifiedAs: "launchable",
5916
- toLaunchSpec: (modelId, ctx, opts) => {
5917
- if (!ctx) return { args: ["--model", modelId] };
5918
- const launchCtx = {
5919
- ...ctx,
5920
- config: {
5921
- ...ctx.config,
5922
- model: modelId
5923
- }
5924
- };
5925
- const launch = buildKimiLaunchOptions(launchCtx, {
5926
- home: opts?.home,
5927
- writeGeneratedConfig: false
5928
- });
5929
- return {
5930
- args: launch.args,
5931
- env: launch.env,
5932
- configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5933
- };
5934
- }
5831
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5935
5832
  };
5936
5833
  supportsStdinNotification = true;
5937
5834
  mcpToolPrefix = "";
@@ -5957,23 +5854,21 @@ var KimiDriver = class {
5957
5854
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
5958
5855
  ""
5959
5856
  ].join("\n"), "utf8");
5960
- const launch = buildKimiLaunchOptions(ctx);
5961
5857
  const args = [
5962
5858
  "--wire",
5963
5859
  "--yolo",
5964
5860
  "--agent-file",
5965
5861
  agentFilePath,
5966
5862
  "--session",
5967
- this.sessionId,
5968
- ...launch.args
5863
+ this.sessionId
5969
5864
  ];
5970
5865
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5971
5866
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5972
5867
  args.push("--model", launchRuntimeFields.model);
5973
5868
  }
5974
5869
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5975
- const spawnTarget = resolveKimiSpawn(args);
5976
- const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5870
+ const launch = resolveKimiSpawn(args);
5871
+ const proc = spawn7(launch.command, launch.args, {
5977
5872
  cwd: ctx.workingDirectory,
5978
5873
  stdio: ["pipe", "pipe", "pipe"],
5979
5874
  env: spawnEnv,
@@ -5981,7 +5876,7 @@ var KimiDriver = class {
5981
5876
  // and has an 8191-character command-line limit. Kimi's official
5982
5877
  // installer/uv entrypoint is an executable, so launch it directly and
5983
5878
  // keep prompts on stdin / files instead of routing through cmd.exe.
5984
- shell: spawnTarget.shell
5879
+ shell: launch.shell
5985
5880
  });
5986
5881
  proc.stdin?.write(JSON.stringify({
5987
5882
  jsonrpc: "2.0",
@@ -6095,9 +5990,14 @@ var KimiDriver = class {
6095
5990
  return detectKimiModels();
6096
5991
  }
6097
5992
  };
6098
- function detectKimiModels(home = os3.homedir(), opts = {}) {
6099
- const raw = readKimiConfigSource(home, opts.env).raw;
6100
- if (raw === null) return null;
5993
+ function detectKimiModels(home = os3.homedir()) {
5994
+ const configPath = path7.join(home, ".kimi", "config.toml");
5995
+ let raw;
5996
+ try {
5997
+ raw = readFileSync3(configPath, "utf8");
5998
+ } catch {
5999
+ return null;
6000
+ }
6101
6001
  const models = [];
6102
6002
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
6103
6003
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -6337,7 +6237,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
6337
6237
  const platform = deps.platform ?? process.platform;
6338
6238
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
6339
6239
  const result = spawnSyncFn("opencode", ["models"], {
6340
- env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
6240
+ env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
6341
6241
  encoding: "utf8",
6342
6242
  timeout: 5e3,
6343
6243
  shell: platform === "win32"
@@ -8085,6 +7985,9 @@ var STDIN_NOTIFICATION_INITIAL_DELAY_MS = 3e3;
8085
7985
  var STDIN_NOTIFICATION_RETRY_DELAY_MS = 15e3;
8086
7986
  var RUNTIME_ERROR_DELIVERY_BACKOFF_BASE_MS = 1e4;
8087
7987
  var RUNTIME_ERROR_DELIVERY_BACKOFF_MAX_MS = 5 * 6e4;
7988
+ var SPAWN_FAIL_BACKOFF_BASE_MS = 1e3;
7989
+ var SPAWN_FAIL_BACKOFF_MAX_MS = 3e4;
7990
+ var SPAWN_FAIL_BACKOFF_THRESHOLD = 3;
8088
7991
  var COMPACTION_STALE_MS = 5 * 6e4;
8089
7992
  var RUNTIME_PROGRESS_STALE_MS = 15 * 6e4;
8090
7993
  var DEFAULT_RUNTIME_START_TIMEOUT_MS = 2 * 6e4;
@@ -8592,6 +8495,12 @@ function summarizeCrash(code, signal) {
8592
8495
  if (typeof code === "number") return `exit code ${code}`;
8593
8496
  return "unknown exit";
8594
8497
  }
8498
+ function currentErrorCandidates(ap) {
8499
+ return [
8500
+ ap.runtimeErrorSinceProgress ? ap.lastRuntimeError : null,
8501
+ ...ap.recentDecisionStderr
8502
+ ].filter((value) => !!value);
8503
+ }
8595
8504
  function classifyTerminalFailure(ap) {
8596
8505
  const candidates = [
8597
8506
  ap.lastRuntimeError,
@@ -8635,21 +8544,16 @@ function isCodexProviderReconnectLog(text) {
8635
8544
  function isCodexBenignTransportLog(text) {
8636
8545
  return /Falling back from WebSockets/i.test(text);
8637
8546
  }
8547
+ function isStdinClassRecoveryLine(text) {
8548
+ return /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text);
8549
+ }
8638
8550
  function hasDirectStdinRecoveryEvidence(ap) {
8639
- const candidates = [
8640
- ap.lastRuntimeError,
8641
- ...ap.recentStderr
8642
- ].filter((value) => !!value);
8643
- return candidates.some(
8644
- (text) => /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text)
8645
- );
8551
+ const candidates = currentErrorCandidates(ap);
8552
+ return candidates.some((text) => isStdinClassRecoveryLine(text));
8646
8553
  }
8647
8554
  function resumeSessionRecoveryReason(ap) {
8648
8555
  if (!ap.sessionId) return null;
8649
- const candidates = [
8650
- ap.lastRuntimeError,
8651
- ...ap.recentStderr
8652
- ].filter((value) => !!value);
8556
+ const candidates = currentErrorCandidates(ap);
8653
8557
  if (ap.driver.id === "claude") {
8654
8558
  return candidates.some((text) => /No conversation found with session ID/i.test(text)) ? "missing" : null;
8655
8559
  }
@@ -8960,6 +8864,11 @@ var AgentProcessManager = class _AgentProcessManager {
8960
8864
  runtimeExitTraceAttrs = /* @__PURE__ */ new WeakMap();
8961
8865
  agentVisibleBoundaries = /* @__PURE__ */ new Map();
8962
8866
  agentVisibleMessageIds = /* @__PURE__ */ new Map();
8867
+ // Spawn-fail backoff state per-agent (lifted outside AgentProcess because spawn fails
8868
+ // BEFORE ap exists; rate-state must persist across stop/respawn so churn can't bypass).
8869
+ // Explicit stop clears (fresh launch resets dedup clock — CC1 lifecycle pattern); silent
8870
+ // stop preserves (same-launch respawn must keep counter or churn bypasses the cap).
8871
+ agentSpawnFailBackoff = /* @__PURE__ */ new Map();
8963
8872
  daemonVersion;
8964
8873
  computerVersion;
8965
8874
  constructor(sendToServer, daemonApiKey, opts) {
@@ -9039,6 +8948,57 @@ var AgentProcessManager = class _AgentProcessManager {
9039
8948
  const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : "";
9040
8949
  return id.length > 0 && this.getVisibleMessageIdSet(agentId, target)?.has(id) === true;
9041
8950
  }
8951
+ // ----- SPAWN-FAIL BACKOFF (per-agent) ----------------------------------
8952
+ // Anchored at auto_restart_from_idle (apm:3596) + runtime_profile_auto_restart (apm:4137).
8953
+ // Threshold > 1 → first SPAWN_FAIL_BACKOFF_THRESHOLD failures behave as today (preserves
8954
+ // single-transient-hiccup); subsequent failure schedules a per-agent cooldown gating new
8955
+ // spawn attempts. Successful spawn resets state. State lives outside AgentProcess because
8956
+ // spawn fails BEFORE ap exists and the rate-window must persist across stop/respawn
8957
+ // (else stop/start churn bypasses the cap — CC1 lifecycle pattern). Never advances any
8958
+ // consume cursor or model-seen state (3-cursor orthogonality with CC1/CC2).
8959
+ getOrCreateSpawnFailBackoff(agentId) {
8960
+ let state = this.agentSpawnFailBackoff.get(agentId);
8961
+ if (!state) {
8962
+ state = createRuntimeErrorDeliveryBackoffState();
8963
+ this.agentSpawnFailBackoff.set(agentId, state);
8964
+ }
8965
+ return state;
8966
+ }
8967
+ isSpawnFailBackoffActive(agentId) {
8968
+ const state = this.agentSpawnFailBackoff.get(agentId);
8969
+ if (!state) return false;
8970
+ return state.untilMs > 0 && this.clockNow() < state.untilMs;
8971
+ }
8972
+ clockNow() {
8973
+ return Date.now();
8974
+ }
8975
+ recordSpawnFailure(agentId, reason) {
8976
+ const state = this.getOrCreateSpawnFailBackoff(agentId);
8977
+ state.attempts += 1;
8978
+ state.reason = reason;
8979
+ if (state.attempts <= SPAWN_FAIL_BACKOFF_THRESHOLD) {
8980
+ state.untilMs = 0;
8981
+ return { backoffActive: false, attempts: state.attempts, untilMs: 0 };
8982
+ }
8983
+ const exponent = Math.min(state.attempts - SPAWN_FAIL_BACKOFF_THRESHOLD, 10);
8984
+ const baseDelay = Math.min(SPAWN_FAIL_BACKOFF_MAX_MS, SPAWN_FAIL_BACKOFF_BASE_MS * Math.pow(2, exponent));
8985
+ state.untilMs = this.clockNow() + Math.floor(baseDelay);
8986
+ if (state.timer) clearTimeout(state.timer);
8987
+ state.timer = setTimeout(() => {
8988
+ const s = this.agentSpawnFailBackoff.get(agentId);
8989
+ if (s) {
8990
+ s.timer = null;
8991
+ s.untilMs = 0;
8992
+ }
8993
+ }, Math.max(1, state.untilMs - this.clockNow()));
8994
+ return { backoffActive: true, attempts: state.attempts, untilMs: state.untilMs };
8995
+ }
8996
+ resetSpawnFailBackoff(agentId) {
8997
+ const state = this.agentSpawnFailBackoff.get(agentId);
8998
+ if (!state) return;
8999
+ if (state.timer) clearTimeout(state.timer);
9000
+ this.agentSpawnFailBackoff.delete(agentId);
9001
+ }
9042
9002
  scheduleStdinNotification(agentId, ap, delayMs) {
9043
9003
  return ap.notifications.schedule(() => {
9044
9004
  this.sendStdinNotification(agentId);
@@ -9893,6 +9853,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9893
9853
  recentStdout: [],
9894
9854
  recentStderr: [],
9895
9855
  lastRuntimeError: null,
9856
+ recentDecisionStderr: [],
9857
+ runtimeErrorSinceProgress: false,
9896
9858
  runtimeErrorDeliveryBackoff: createRuntimeErrorDeliveryBackoffState(),
9897
9859
  spawnError: null,
9898
9860
  exitCode: null,
@@ -9936,6 +9898,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9936
9898
  if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
9937
9899
  if (current) {
9938
9900
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
9901
+ current.recentDecisionStderr = pushRecentStderr(current.recentDecisionStderr, text);
9939
9902
  }
9940
9903
  this.recordDaemonTrace("daemon.agent.provider_reconnect", {
9941
9904
  agentId,
@@ -9952,6 +9915,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9952
9915
  if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
9953
9916
  if (current) {
9954
9917
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
9918
+ current.recentDecisionStderr = pushRecentStderr(current.recentDecisionStderr, text);
9955
9919
  }
9956
9920
  logger.error(`[Agent ${agentId} stderr]: ${text}`);
9957
9921
  });
@@ -10442,6 +10406,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10442
10406
  this.activityClientSeqByAgent.delete(agentId);
10443
10407
  this.agentVisibleBoundaries.delete(agentId);
10444
10408
  this.agentVisibleMessageIds.delete(agentId);
10409
+ this.resetSpawnFailBackoff(agentId);
10445
10410
  }
10446
10411
  this.runtimeExitTraceAttrs.set(ap.runtime, {
10447
10412
  stop_source: silent ? "daemon_internal" : "explicit_request",
@@ -10538,6 +10503,25 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10538
10503
  return true;
10539
10504
  }
10540
10505
  logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
10506
+ if (this.isSpawnFailBackoffActive(agentId)) {
10507
+ const state = this.agentSpawnFailBackoff.get(agentId);
10508
+ const pending = this.startingInboxes.get(agentId) || [];
10509
+ pending.push(message);
10510
+ this.startingInboxes.set(agentId, pending);
10511
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
10512
+ outcome: "spawn_fail_cooldown_active",
10513
+ accepted: true,
10514
+ process_present: false,
10515
+ cached_idle_config_present: true,
10516
+ runtime: cached.config.runtime,
10517
+ session_id_present: Boolean(cached.sessionId),
10518
+ launchId: cached.launchId || void 0,
10519
+ spawn_fail_attempts: state.attempts,
10520
+ spawn_fail_until_ms: state.untilMs,
10521
+ starting_inbox_count: pending.length
10522
+ }));
10523
+ return true;
10524
+ }
10541
10525
  this.idleAgentConfigs.delete(agentId);
10542
10526
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
10543
10527
  outcome: "auto_restart_from_idle",
@@ -10548,12 +10532,33 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10548
10532
  session_id_present: Boolean(cached.sessionId),
10549
10533
  launchId: cached.launchId || void 0
10550
10534
  }));
10551
- return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0, transientDelivery).then(() => true, (err) => {
10535
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0, transientDelivery).then(() => {
10536
+ this.resetSpawnFailBackoff(agentId);
10537
+ return true;
10538
+ }, (err) => {
10552
10539
  logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
10553
10540
  if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
10541
+ const report2 = this.recordSpawnFailure(agentId, "runner_credential_mint");
10542
+ this.recordDaemonTrace("daemon.agent.spawn.fail_backoff", {
10543
+ agentId,
10544
+ source: "idle_auto_restart",
10545
+ reason: "runner_credential_mint",
10546
+ attempts: report2.attempts,
10547
+ cooldown_active: report2.backoffActive,
10548
+ until_ms: report2.untilMs
10549
+ });
10554
10550
  return false;
10555
10551
  }
10556
10552
  this.idleAgentConfigs.set(agentId, cached);
10553
+ const report = this.recordSpawnFailure(agentId, "spawn_error");
10554
+ this.recordDaemonTrace("daemon.agent.spawn.fail_backoff", {
10555
+ agentId,
10556
+ source: "idle_auto_restart",
10557
+ reason: "spawn_error",
10558
+ attempts: report.attempts,
10559
+ cooldown_active: report.backoffActive,
10560
+ until_ms: report.untilMs
10561
+ });
10557
10562
  return false;
10558
10563
  });
10559
10564
  }
@@ -11028,10 +11033,35 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11028
11033
  const cached = this.idleAgentConfigs.get(agentId);
11029
11034
  if (cached) {
11030
11035
  logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
11036
+ if (this.isSpawnFailBackoffActive(agentId)) {
11037
+ const state = this.agentSpawnFailBackoff.get(agentId);
11038
+ span.end("ok", {
11039
+ attrs: {
11040
+ outcome: "spawn_fail_cooldown_active",
11041
+ runtime: cached.config.runtime,
11042
+ launchId: cached.launchId || void 0,
11043
+ spawn_fail_attempts: state.attempts,
11044
+ spawn_fail_until_ms: state.untilMs
11045
+ }
11046
+ });
11047
+ return true;
11048
+ }
11031
11049
  this.idleAgentConfigs.delete(agentId);
11032
- return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
11050
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => {
11051
+ this.resetSpawnFailBackoff(agentId);
11052
+ return true;
11053
+ }, (err) => {
11033
11054
  logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
11034
11055
  if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
11056
+ const report2 = this.recordSpawnFailure(agentId, "runner_credential_mint");
11057
+ this.recordDaemonTrace("daemon.agent.spawn.fail_backoff", {
11058
+ agentId,
11059
+ source: "runtime_profile_auto_restart",
11060
+ reason: "runner_credential_mint",
11061
+ attempts: report2.attempts,
11062
+ cooldown_active: report2.backoffActive,
11063
+ until_ms: report2.untilMs
11064
+ });
11035
11065
  span.end("error", {
11036
11066
  attrs: {
11037
11067
  outcome: "runner_credential_mint_failed",
@@ -11042,6 +11072,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11042
11072
  return false;
11043
11073
  }
11044
11074
  this.idleAgentConfigs.set(agentId, cached);
11075
+ const report = this.recordSpawnFailure(agentId, "spawn_error");
11076
+ this.recordDaemonTrace("daemon.agent.spawn.fail_backoff", {
11077
+ agentId,
11078
+ source: "runtime_profile_auto_restart",
11079
+ reason: "spawn_error",
11080
+ attempts: report.attempts,
11081
+ cooldown_active: report.backoffActive,
11082
+ until_ms: report.untilMs
11083
+ });
11045
11084
  span.end("error", {
11046
11085
  attrs: {
11047
11086
  outcome: "restart_failed",
@@ -11635,6 +11674,21 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11635
11674
  }
11636
11675
  noteRuntimeProgress(ap, eventKind) {
11637
11676
  ap.runtimeProgress.noteRuntimeEvent(eventKind);
11677
+ this.invalidateRecoveryErrorView(ap);
11678
+ }
11679
+ /**
11680
+ * Invalidate the decision-only error view on a liveness signal. The process is
11681
+ * alive and has moved past any error it logged earlier in the turn, so a stale
11682
+ * error must not keep restarting/re-routing a recovered agent. Error-class
11683
+ * agnostic; a genuinely current failure re-populates the view after this point.
11684
+ * Called on BOTH progress paths — ordinary runtime events and
11685
+ * `internal_progress` (raw runtime activity that stale-recovery already treats
11686
+ * as liveness). `recentStderr`/`lastRuntimeError` are untouched and stay full
11687
+ * for diagnostics, user-facing reporting, and sticky terminal-failure gating.
11688
+ */
11689
+ invalidateRecoveryErrorView(ap) {
11690
+ ap.recentDecisionStderr = [];
11691
+ ap.runtimeErrorSinceProgress = false;
11638
11692
  }
11639
11693
  recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
11640
11694
  if (ap.runtime.descriptor.busyDelivery !== "gated") return;
@@ -11825,6 +11879,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11825
11879
  const terminalFailureDetail = classifyTerminalFailure(ap);
11826
11880
  const detail = terminalFailureDetail?.detail ?? formatRuntimeStartTimeoutMessage(ap.driver.id);
11827
11881
  ap.lastRuntimeError = detail;
11882
+ ap.runtimeErrorSinceProgress = true;
11828
11883
  ap.runtimeProgress.markStale();
11829
11884
  const staleForMs = Math.max(timeoutMs, ap.runtimeProgress.ageMs());
11830
11885
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, Math.max(1, Math.floor(staleForMs / 6e4)));
@@ -11979,6 +12034,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11979
12034
  }
11980
12035
  if (event.kind === "internal_progress") {
11981
12036
  ap.runtimeProgress.noteInternalProgress();
12037
+ this.invalidateRecoveryErrorView(ap);
11982
12038
  this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
11983
12039
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
11984
12040
  turn_outcome: "held",
@@ -12153,7 +12209,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
12153
12209
  case "error": {
12154
12210
  this.interruptCompactionIfActive(agentId);
12155
12211
  this.flushPendingTrajectory(agentId);
12156
- if (ap) ap.lastRuntimeError = event.message;
12212
+ if (ap) {
12213
+ ap.lastRuntimeError = event.message;
12214
+ ap.runtimeErrorSinceProgress = true;
12215
+ }
12157
12216
  let visibleErrorMessage = event.message;
12158
12217
  if (ap) {
12159
12218
  const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
@@ -13863,7 +13922,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
13863
13922
  spanAttrs: ["running_agents_count", "idle_agents_count"]
13864
13923
  }
13865
13924
  };
13866
- var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
13925
+ var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
13867
13926
  var RunnerCredentialMintError2 = class extends Error {
13868
13927
  code;
13869
13928
  retryable;
@@ -13899,9 +13958,9 @@ function runnerCredentialErrorDetail2(error) {
13899
13958
  async function waitForRunnerCredentialRetry2() {
13900
13959
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
13901
13960
  }
13902
- function parseDaemonCliArgs(args, env = {}) {
13961
+ function parseDaemonCliArgs(args) {
13903
13962
  let serverUrl = "";
13904
- let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
13963
+ let apiKey = "";
13905
13964
  for (let i = 0; i < args.length; i++) {
13906
13965
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
13907
13966
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -14682,8 +14741,6 @@ var DaemonCore = class {
14682
14741
  };
14683
14742
 
14684
14743
  export {
14685
- DAEMON_API_KEY_ENV,
14686
- scrubDaemonAuthEnv,
14687
14744
  subscribeDaemonLogs,
14688
14745
  resolveWorkspaceDirectoryPath,
14689
14746
  scanWorkspaceDirectories,
package/dist/core.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- DAEMON_API_KEY_ENV,
3
2
  DAEMON_CLI_USAGE,
4
3
  DaemonCore,
5
4
  deleteWorkspaceDirectory,
@@ -9,11 +8,9 @@ import {
9
8
  resolveSlockCliPath,
10
9
  resolveWorkspaceDirectoryPath,
11
10
  scanWorkspaceDirectories,
12
- scrubDaemonAuthEnv,
13
11
  subscribeDaemonLogs
14
- } from "./chunk-JVPMCKSF.js";
12
+ } from "./chunk-H2QU4LAU.js";
15
13
  export {
16
- DAEMON_API_KEY_ENV,
17
14
  DAEMON_CLI_USAGE,
18
15
  DaemonCore,
19
16
  deleteWorkspaceDirectory,
@@ -23,6 +20,5 @@ export {
23
20
  resolveSlockCliPath,
24
21
  resolveWorkspaceDirectoryPath,
25
22
  scanWorkspaceDirectories,
26
- scrubDaemonAuthEnv,
27
23
  subscribeDaemonLogs
28
24
  };
package/dist/index.js CHANGED
@@ -2,13 +2,11 @@
2
2
  import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
- parseDaemonCliArgs,
6
- scrubDaemonAuthEnv
7
- } from "./chunk-JVPMCKSF.js";
5
+ parseDaemonCliArgs
6
+ } from "./chunk-H2QU4LAU.js";
8
7
 
9
8
  // src/index.ts
10
- var parsedArgs = parseDaemonCliArgs(process.argv.slice(2), process.env);
11
- scrubDaemonAuthEnv(process.env);
9
+ var parsedArgs = parseDaemonCliArgs(process.argv.slice(2));
12
10
  if (!parsedArgs) {
13
11
  console.error(DAEMON_CLI_USAGE);
14
12
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.57.2-play.20260608142014",
3
+ "version": "0.57.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"
@@ -31,7 +31,7 @@
31
31
  "prepublishOnly": "pnpm run build",
32
32
  "typecheck": "tsc --noEmit",
33
33
  "generate:slock-cli-guide": "tsx scripts/generate-slock-cli-guide.ts",
34
- "check:slock-cli-guide-fresh": "pnpm generate:slock-cli-guide && git diff --exit-code ../../docs/agent-knowledge/slock-cli-overview.mdx",
34
+ "check:slock-cli-guide-fresh": "pnpm generate:slock-cli-guide && git diff --exit-code ../../manual/agent-knowledge/slock-cli-overview.md",
35
35
  "release:patch": "npm version patch --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
36
36
  "release:minor": "npm version minor --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
37
37
  "release:major": "npm version major --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
@@ -1,96 +0,0 @@
1
- // src/drivers/piSdkRunner.ts
2
- import { readFile } from "fs/promises";
3
- import path from "path";
4
- import {
5
- AuthStorage,
6
- createAgentSessionFromServices,
7
- createAgentSessionServices,
8
- SessionManager
9
- } from "@earendil-works/pi-coding-agent";
10
- function writeJson(value) {
11
- process.stdout.write(`${JSON.stringify(value)}
12
- `);
13
- }
14
- function parseArgs(argv) {
15
- const index = argv.indexOf("--config");
16
- const configPath = index >= 0 ? argv[index + 1] : void 0;
17
- if (!configPath) throw new Error("Missing --config <path>");
18
- return { configPath };
19
- }
20
- function resolveConfiguredModel(modelId, modelRegistry) {
21
- if (!modelId) return void 0;
22
- const [provider, ...rest] = modelId.split("/");
23
- const providerScopedId = rest.join("/");
24
- if (provider && providerScopedId) {
25
- const exact = modelRegistry.find(provider, providerScopedId);
26
- if (exact) return exact;
27
- }
28
- return modelRegistry.getAll().find(
29
- (model) => model.id === modelId || `${model.provider}/${model.id}` === modelId || (providerScopedId ? model.id === providerScopedId : false)
30
- );
31
- }
32
- async function createSessionManager(config) {
33
- if (!config.sessionId) return SessionManager.create(config.cwd, config.sessionDir);
34
- const localSessions = await SessionManager.list(config.cwd, config.sessionDir);
35
- const match = localSessions.find((session) => session.id.startsWith(config.sessionId));
36
- if (match) return SessionManager.open(match.path, config.sessionDir);
37
- return SessionManager.create(config.cwd, config.sessionDir);
38
- }
39
- async function run() {
40
- const { configPath } = parseArgs(process.argv.slice(2));
41
- const config = JSON.parse(await readFile(configPath, "utf8"));
42
- const authStorage = AuthStorage.create(path.join(config.agentDir, "auth.json"));
43
- const services = await createAgentSessionServices({
44
- cwd: config.cwd,
45
- agentDir: config.agentDir,
46
- authStorage,
47
- resourceLoaderOptions: {
48
- appendSystemPrompt: [config.standingPrompt],
49
- noContextFiles: true,
50
- noExtensions: true,
51
- noPromptTemplates: true,
52
- noSkills: true,
53
- noThemes: true
54
- }
55
- });
56
- for (const diagnostic of services.diagnostics) {
57
- const line = `[Pi SDK] ${diagnostic.type}: ${diagnostic.message}`;
58
- if (diagnostic.type === "error") throw new Error(line);
59
- process.stderr.write(`${line}
60
- `);
61
- }
62
- const sessionManager = await createSessionManager(config);
63
- const model = resolveConfiguredModel(config.model, services.modelRegistry);
64
- if (config.model && !model) {
65
- throw new Error(`Configured Pi model '${config.model}' was not found in Pi model registry.`);
66
- }
67
- const { session } = await createAgentSessionFromServices({
68
- services,
69
- sessionManager,
70
- model
71
- });
72
- const header = session.sessionManager.getHeader();
73
- if (header) writeJson(header);
74
- const unsubscribe = session.subscribe((event) => writeJson(event));
75
- try {
76
- await session.prompt(config.prompt);
77
- } finally {
78
- unsubscribe();
79
- session.dispose();
80
- await services.settingsManager.flush();
81
- }
82
- }
83
- run().catch((error) => {
84
- const message = error instanceof Error ? error.message : String(error);
85
- writeJson({
86
- type: "message_end",
87
- message: {
88
- role: "assistant",
89
- content: [],
90
- stopReason: "error",
91
- errorMessage: message
92
- }
93
- });
94
- writeJson({ type: "turn_end" });
95
- process.exitCode = 1;
96
- });