@botiverse/raft-daemon 0.68.0 → 0.69.0-play.20260705140609

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.
@@ -2225,7 +2225,12 @@ var agentApiIntegrationLoginResponseSchema = passthroughObject({
2225
2225
  status: z2.enum(["logged_in", "already_logged_in", "approval_required"]),
2226
2226
  service: agentApiIntegrationServiceSchema,
2227
2227
  scopes: z2.array(z2.string()),
2228
- requestId: z2.string(),
2228
+ requestId: z2.string().optional(),
2229
+ session: passthroughObject({
2230
+ status: z2.literal("stored"),
2231
+ source: z2.enum(["cache", "fresh"]),
2232
+ path: nullableStringSchema
2233
+ }).optional(),
2229
2234
  approval: passthroughObject({
2230
2235
  requestId: z2.string(),
2231
2236
  target: nullableStringSchema,
@@ -3288,6 +3293,7 @@ var AGENT_ACTIVITY_DETAIL_KINDS = [
3288
3293
  "compaction_stale",
3289
3294
  "reviewing_changes",
3290
3295
  "review_finished",
3296
+ "review_stale",
3291
3297
  "runtime_reconnecting",
3292
3298
  "runtime_error",
3293
3299
  "runtime_crashed",
@@ -3738,6 +3744,8 @@ var DISPLAY_PLAN_CONFIG = {
3738
3744
  }
3739
3745
  };
3740
3746
  var FREE_MONTHLY_FILE_UPLOAD_LIMIT_BYTES = 100 * 1024 * 1024;
3747
+ var FREE_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 50 * 1024 * 1024;
3748
+ var PRO_SINGLE_FILE_UPLOAD_LIMIT_BYTES = 200 * 1024 * 1024;
3741
3749
  var TRIAL_START_DATE = /* @__PURE__ */ new Date("2026-04-18T00:00:00Z");
3742
3750
  var TRIAL_END_DATE = /* @__PURE__ */ new Date("2026-06-23T12:00:00Z");
3743
3751
  var TRIAL_DURATION_DAYS = (TRIAL_END_DATE.getTime() - TRIAL_START_DATE.getTime()) / (24 * 60 * 60 * 1e3);
@@ -4520,7 +4528,7 @@ function buildPrompt(config, opts) {
4520
4528
  const channelAwarenessSection = cliGuideSections.channelAwareness;
4521
4529
  const thirdPartyIntegrationsSection = `### Third-party integrations
4522
4530
 
4523
- If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`raft integration list\` and match the app to a listed service before browsing the app. Use \`raft integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes manifest-backed HTTP API actions, prefer \`raft integration invoke --service <service> --list-actions\` and then \`raft integration invoke --service <service> --action <name>\`; the CLI performs the stateless Agent Login callback handoff and service-session setup for you. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`raft integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides; for HTTP API action services, use \`raft integration invoke\` instead. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute local commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When \`raft integration login\` returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes a service callback handoff URL, treat it as a stateless Login with Raft API-action session handoff URL; prefer \`raft integration invoke\` over manually opening or curling it. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes outside the documented CLI flow, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`raft profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
4531
+ If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`raft integration list\` and match the app to a listed service before browsing the app. Use \`raft integration login --service <service>\` to provision or reuse your agent login for that service; the CLI consumes the one-time Agent Login handoff internally and stores the service-owned session for this agent, so the successful output should be \`Agent login ready\` / \`Already logged in\`, not a raw request code you need to keep. If the service exposes manifest-backed HTTP API actions, prefer \`raft integration invoke --service <service> --list-actions\` and then \`raft integration invoke --service <service> --action <name>\`; the CLI uses the stored service session or refreshes it internally. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`raft integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides; for HTTP API action services, use \`raft integration invoke\` instead. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute local commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. Do not store or reuse raw \`oauth_access_request\` request codes; those are one-time compatibility handoffs for services to exchange once for their own session/token. Prefer \`raft integration invoke\` over manually opening or curling callback URLs. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes outside the documented CLI flow, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`raft profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
4524
4532
  const readingHistorySection = cliGuideSections.readingHistory;
4525
4533
  const historicalReferenceSection = cliGuideSections.historicalReferences;
4526
4534
  const tasksSection = cliGuideSections.tasks;
@@ -4708,6 +4716,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
4708
4716
  return candidates.filter((candidate) => existsSync(candidate.path));
4709
4717
  }
4710
4718
 
4719
+ // src/authEnv.ts
4720
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
4721
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
4722
+ function scrubDaemonAuthEnv(env) {
4723
+ delete env[DAEMON_API_KEY_ENV];
4724
+ return env;
4725
+ }
4726
+ function scrubDaemonChildEnv(env) {
4727
+ delete env[DAEMON_API_KEY_ENV];
4728
+ delete env[SLOCK_AGENT_TOKEN_ENV];
4729
+ return env;
4730
+ }
4731
+
4711
4732
  // src/agentCredentialProxy.ts
4712
4733
  import { randomBytes } from "crypto";
4713
4734
  import http from "http";
@@ -6282,7 +6303,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
6282
6303
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
6283
6304
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
6284
6305
  var RAW_CREDENTIAL_ENV_DENYLIST = [
6285
- "SLOCK_AGENT_CREDENTIAL_KEY"
6306
+ "SLOCK_AGENT_TOKEN",
6307
+ "SLOCK_AGENT_CREDENTIAL_KEY",
6308
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
6286
6309
  ];
6287
6310
  var WORKSPACE_CLI_TRANSPORT_FILENAMES = [
6288
6311
  "agent-token",
@@ -6637,7 +6660,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
6637
6660
  SLOCK_SERVER_URL: ctx.config.serverUrl,
6638
6661
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
6639
6662
  };
6640
- delete spawnEnv.SLOCK_AGENT_TOKEN;
6663
+ scrubDaemonChildEnv(spawnEnv);
6641
6664
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
6642
6665
  delete spawnEnv[key];
6643
6666
  }
@@ -7205,7 +7228,7 @@ function requiresWindowsShell(command, platform = process.platform) {
7205
7228
  }
7206
7229
  function resolveCommandOnPath(command, deps = {}) {
7207
7230
  const platform = deps.platform ?? process.platform;
7208
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
7231
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
7209
7232
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
7210
7233
  const existsSyncFn = deps.existsSyncFn ?? existsSync3;
7211
7234
  if (platform === "win32") {
@@ -7231,7 +7254,7 @@ function firstExistingPath(candidates, deps = {}) {
7231
7254
  return null;
7232
7255
  }
7233
7256
  function readCommandVersion(command, args = [], deps = {}) {
7234
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
7257
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
7235
7258
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
7236
7259
  try {
7237
7260
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -9315,11 +9338,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
9315
9338
  return parseCursorModelsOutput(String(result.stdout || ""));
9316
9339
  }
9317
9340
  function buildCursorModelProbeEnv(deps = {}) {
9318
- return withWindowsUserEnvironment({
9341
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
9319
9342
  ...deps.env ?? process.env,
9320
9343
  FORCE_COLOR: "0",
9321
9344
  NO_COLOR: "1"
9322
- }, deps);
9345
+ }, deps));
9323
9346
  }
9324
9347
  function runCursorModelsCommand() {
9325
9348
  return spawnSync("cursor-agent", ["models"], {
@@ -9375,7 +9398,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
9375
9398
  }
9376
9399
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
9377
9400
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
9378
- const env = deps.env ?? process.env;
9401
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
9379
9402
  const winPath = path8.win32;
9380
9403
  let geminiEntry = null;
9381
9404
  try {
@@ -9512,12 +9535,15 @@ var GeminiDriver = class {
9512
9535
  // src/drivers/kimi.ts
9513
9536
  import { randomUUID as randomUUID2 } from "crypto";
9514
9537
  import { spawn as spawn7 } from "child_process";
9515
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
9538
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
9516
9539
  import os4 from "os";
9517
9540
  import path9 from "path";
9518
9541
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
9519
9542
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
9520
9543
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
9544
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
9545
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
9546
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
9521
9547
  function parseToolArguments(raw) {
9522
9548
  if (typeof raw !== "string") return raw;
9523
9549
  try {
@@ -9526,6 +9552,73 @@ function parseToolArguments(raw) {
9526
9552
  return raw;
9527
9553
  }
9528
9554
  }
9555
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
9556
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
9557
+ if (inlineConfig && inlineConfig.trim()) {
9558
+ return {
9559
+ raw: inlineConfig,
9560
+ explicitPath: null,
9561
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
9562
+ };
9563
+ }
9564
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
9565
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
9566
+ try {
9567
+ return {
9568
+ raw: readFileSync3(configPath, "utf8"),
9569
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
9570
+ sourcePath: configPath
9571
+ };
9572
+ } catch {
9573
+ return {
9574
+ raw: null,
9575
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
9576
+ sourcePath: configPath
9577
+ };
9578
+ }
9579
+ }
9580
+ function buildKimiSpawnEnv(env = process.env) {
9581
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
9582
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
9583
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
9584
+ return scrubDaemonChildEnv(spawnEnv);
9585
+ }
9586
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
9587
+ return {
9588
+ ...process.env,
9589
+ ...ctx.config.envVars || {},
9590
+ ...overrideEnv || {}
9591
+ };
9592
+ }
9593
+ function buildKimiLaunchOptions(ctx, opts = {}) {
9594
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
9595
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
9596
+ const args = [];
9597
+ let configFilePath = null;
9598
+ let configContent = null;
9599
+ if (source.explicitPath) {
9600
+ configFilePath = source.explicitPath;
9601
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
9602
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
9603
+ configContent = source.raw;
9604
+ if (opts.writeGeneratedConfig !== false) {
9605
+ writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
9606
+ chmodSync(configFilePath, 384);
9607
+ }
9608
+ }
9609
+ if (configFilePath) {
9610
+ args.push("--config-file", configFilePath);
9611
+ }
9612
+ if (ctx.config.model && ctx.config.model !== "default") {
9613
+ args.push("--model", ctx.config.model);
9614
+ }
9615
+ return {
9616
+ args,
9617
+ env: buildKimiSpawnEnv(env),
9618
+ configFilePath,
9619
+ configContent
9620
+ };
9621
+ }
9529
9622
  function resolveKimiSpawn(commandArgs, deps = {}) {
9530
9623
  return {
9531
9624
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -9549,7 +9642,25 @@ var KimiDriver = class {
9549
9642
  };
9550
9643
  model = {
9551
9644
  detectedModelsVerifiedAs: "launchable",
9552
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
9645
+ toLaunchSpec: (modelId, ctx, opts) => {
9646
+ if (!ctx) return { args: ["--model", modelId] };
9647
+ const launchCtx = {
9648
+ ...ctx,
9649
+ config: {
9650
+ ...ctx.config,
9651
+ model: modelId
9652
+ }
9653
+ };
9654
+ const launch = buildKimiLaunchOptions(launchCtx, {
9655
+ home: opts?.home,
9656
+ writeGeneratedConfig: false
9657
+ });
9658
+ return {
9659
+ args: launch.args,
9660
+ env: launch.env,
9661
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
9662
+ };
9663
+ }
9553
9664
  };
9554
9665
  supportsStdinNotification = true;
9555
9666
  busyDeliveryMode = "direct";
@@ -9573,21 +9684,23 @@ var KimiDriver = class {
9573
9684
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
9574
9685
  ""
9575
9686
  ].join("\n"), "utf8");
9687
+ const launch = buildKimiLaunchOptions(ctx);
9576
9688
  const args = [
9577
9689
  "--wire",
9578
9690
  "--yolo",
9579
9691
  "--agent-file",
9580
9692
  agentFilePath,
9581
9693
  "--session",
9582
- this.sessionId
9694
+ this.sessionId,
9695
+ ...launch.args
9583
9696
  ];
9584
9697
  const launchRuntimeFields = runtimeConfigToLaunchFields(hydrateRuntimeConfig(ctx.config));
9585
9698
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
9586
9699
  args.push("--model", launchRuntimeFields.model);
9587
9700
  }
9588
9701
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
9589
- const launch = resolveKimiSpawn(args);
9590
- const proc = spawn7(launch.command, launch.args, {
9702
+ const spawnTarget = resolveKimiSpawn(args);
9703
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
9591
9704
  cwd: ctx.workingDirectory,
9592
9705
  stdio: ["pipe", "pipe", "pipe"],
9593
9706
  env: spawnEnv,
@@ -9595,7 +9708,7 @@ var KimiDriver = class {
9595
9708
  // and has an 8191-character command-line limit. Kimi's official
9596
9709
  // installer/uv entrypoint is an executable, so launch it directly and
9597
9710
  // keep prompts on stdin / files instead of routing through cmd.exe.
9598
- shell: launch.shell
9711
+ shell: spawnTarget.shell
9599
9712
  });
9600
9713
  proc.stdin?.write(JSON.stringify({
9601
9714
  jsonrpc: "2.0",
@@ -9708,14 +9821,9 @@ var KimiDriver = class {
9708
9821
  return detectKimiModels();
9709
9822
  }
9710
9823
  };
9711
- function detectKimiModels(home = os4.homedir()) {
9712
- const configPath = path9.join(home, ".kimi", "config.toml");
9713
- let raw;
9714
- try {
9715
- raw = readFileSync3(configPath, "utf8");
9716
- } catch {
9717
- return null;
9718
- }
9824
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
9825
+ const raw = readKimiConfigSource(home, opts.env).raw;
9826
+ if (raw === null) return null;
9719
9827
  const models = [];
9720
9828
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
9721
9829
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -10381,7 +10489,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
10381
10489
  const platform = deps.platform ?? process.platform;
10382
10490
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
10383
10491
  const result = spawnSyncFn("opencode", ["models"], {
10384
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
10492
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
10385
10493
  encoding: "utf8",
10386
10494
  timeout: 5e3,
10387
10495
  shell: platform === "win32"
@@ -13370,24 +13478,38 @@ function safeSessionFilename(value) {
13370
13478
  const normalized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
13371
13479
  return normalized || "unknown-session";
13372
13480
  }
13373
- function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
13481
+ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir, join, resolve) {
13374
13482
  try {
13375
13483
  const dir = path14.join(fallbackDir, ".slock", "runtime-sessions");
13376
13484
  mkdirSync4(dir, { recursive: true });
13377
13485
  const filePath = path14.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
13486
+ const searchedPaths = resolve?.searchedPaths ?? [];
13378
13487
  writeFileSync4(filePath, JSON.stringify({
13379
13488
  type: "runtime_session_handoff",
13380
13489
  runtime,
13381
13490
  sessionId,
13491
+ // Persisted L1<->L2 join keys (#460 V3): without these the
13492
+ // session->launch mapping lives only in daemon memory and old L1
13493
+ // transcripts can never re-join their launch after restart/adopt.
13494
+ // ids-only, closed fields (no content payload).
13495
+ launchId: join?.launchId ?? null,
13496
+ processInstanceId: join?.processInstanceId ?? null,
13497
+ // Negative-space (fruit#0 pt2): a resolve-miss is explicit evidence, not a
13498
+ // silent "not found". Recording the lookup method + the directories checked
13499
+ // makes "looked in the wrong place" distinguishable from "looked in the right
13500
+ // place and it is genuinely absent". ids/paths only — no transcript content.
13501
+ resolveStatus: "transcript_resolve_missing",
13502
+ lookupMethod: resolve?.lookupMethod ?? "none",
13503
+ searchedPaths,
13382
13504
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13383
- note: "The native runtime transcript file was not found on this machine; this daemon-created handoff records the runtime session identity for diagnostics."
13505
+ note: "The native runtime transcript file was not found on this machine; this daemon-created handoff records the runtime session identity + the directories checked for diagnostics."
13384
13506
  }) + "\n", { mode: 384 });
13385
13507
  return {
13386
13508
  label: sessionId,
13387
13509
  path: filePath,
13388
13510
  runtime,
13389
13511
  reachable: true,
13390
- reason: "native session file path not found; using daemon handoff file"
13512
+ reason: `native session file path not found; using daemon handoff file; searched=[${searchedPaths.join(", ")}]`
13391
13513
  };
13392
13514
  } catch {
13393
13515
  return null;
@@ -13412,12 +13534,16 @@ function ensureRuntimeHomeDir(config, defaultHomeDir, workspacePath, opts = {})
13412
13534
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir, opts) {
13413
13535
  let resolvedPath = null;
13414
13536
  let lookupMethod = "none";
13537
+ const searchedPaths = [];
13415
13538
  if (runtime === "claude") {
13416
13539
  lookupMethod = "claude_jsonl";
13417
- resolvedPath = findSessionJsonl(path14.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`);
13540
+ const claudeRoot = path14.join(homeDir, ".claude", "projects");
13541
+ searchedPaths.push(claudeRoot);
13542
+ resolvedPath = findSessionJsonl(claudeRoot, (filename) => filename === `${sessionId}.jsonl`);
13418
13543
  } else if (runtime === "codex") {
13419
13544
  lookupMethod = "codex_jsonl";
13420
13545
  for (const root of codexSessionRootCandidates(homeDir)) {
13546
+ searchedPaths.push(root);
13421
13547
  resolvedPath = findSessionJsonl(root, (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId));
13422
13548
  if (resolvedPath) break;
13423
13549
  }
@@ -13429,7 +13555,13 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13429
13555
  resolvedPath = findPiSessionFile2(sessionId, opts?.workingDirectory, homeDir);
13430
13556
  }
13431
13557
  if (!resolvedPath && fallbackDir) {
13432
- const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
13558
+ const fallback = writeRuntimeSessionHandoff(
13559
+ runtime,
13560
+ sessionId,
13561
+ fallbackDir,
13562
+ { launchId: opts?.launchId, processInstanceId: opts?.processInstanceId },
13563
+ { lookupMethod, searchedPaths }
13564
+ );
13433
13565
  if (fallback) {
13434
13566
  return {
13435
13567
  ...fallback,
@@ -13444,7 +13576,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
13444
13576
  reachable: Boolean(resolvedPath)
13445
13577
  };
13446
13578
  if (!resolvedPath) {
13447
- ref.reason = `session file path not found; attempted_lookup=${lookupMethod}`;
13579
+ ref.reason = `session file path not found; attempted_lookup=${lookupMethod}; searched=[${searchedPaths.join(", ")}]`;
13448
13580
  }
13449
13581
  return ref;
13450
13582
  }
@@ -13560,11 +13692,12 @@ var RUNTIME_ERROR_DELIVERY_BACKOFF_MAX_MS = 5 * 6e4;
13560
13692
  var RUNTIME_ERROR_FINGERPRINT_FENCE_THRESHOLD = 3;
13561
13693
  var SPAWN_FAIL_BACKOFF_BASE_MS = 1e3;
13562
13694
  var SPAWN_FAIL_BACKOFF_MAX_MS = 3e4;
13563
- var SPAWN_FAIL_BACKOFF_THRESHOLD = 3;
13695
+ var SPAWN_FAIL_BACKOFF_THRESHOLD = 0;
13564
13696
  var RUNNER_CREDENTIAL_MINT_BACKOFF_BASE_MS = 6e4;
13565
13697
  var RUNNER_CREDENTIAL_MINT_BACKOFF_MAX_MS = 10 * 6e4;
13566
13698
  var RUNNER_CREDENTIAL_MINT_BACKOFF_THRESHOLD = 0;
13567
13699
  var COMPACTION_STALE_MS = 5 * 6e4;
13700
+ var REVIEW_STALE_MS = 10 * 6e4;
13568
13701
  var RUNTIME_PROGRESS_STALE_MS = 15 * 6e4;
13569
13702
  var DEFAULT_RUNTIME_START_TIMEOUT_MS = 2 * 6e4;
13570
13703
  var DEFAULT_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS = 1e4;
@@ -14653,10 +14786,10 @@ var AgentProcessManager = class _AgentProcessManager {
14653
14786
  }
14654
14787
  // ----- SPAWN-FAIL BACKOFF (per-agent) ----------------------------------
14655
14788
  // Anchored at auto_restart_from_idle (apm:3596) + runtime_profile_auto_restart (apm:4137).
14656
- // Generic spawn errors keep a threshold > 1 so single-transient hiccups behave as today.
14657
- // Runner credential mint failures are different: one outer spawn failure already means the
14658
- // inner credential-mint loop exhausted RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS. Those enter a
14659
- // longer cooldown immediately so repeated wakes don't keep burning three network fetches.
14789
+ // One outer spawn failure enters cooldown immediately so repeated wakes cannot burn one
14790
+ // full spawn attempt per delivery. Runner credential mint failures keep a longer cooldown
14791
+ // because one outer failure already means the inner credential-mint loop exhausted
14792
+ // RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS.
14660
14793
  // Successful spawn resets state. State lives outside AgentProcess because spawn fails BEFORE
14661
14794
  // ap exists and the rate-window must persist across stop/respawn (else stop/start churn
14662
14795
  // bypasses the cap — CC1 lifecycle pattern). Never advances any consume cursor or model-seen
@@ -14809,14 +14942,22 @@ var AgentProcessManager = class _AgentProcessManager {
14809
14942
  this.sendStdinNotification(agentId);
14810
14943
  }, delayMs);
14811
14944
  }
14812
- flushAsyncRejectedIdleDelivery(agentId) {
14945
+ scheduleIdleInboxDeliveryRetry(agentId, ap, notificationCount, source, traceName) {
14946
+ if (notificationCount <= 0 || !ap.driver.supportsStdinNotification || !ap.sessionId) return false;
14947
+ ap.notifications.add(notificationCount);
14948
+ ap.notifications.clearTimer();
14949
+ return ap.notifications.schedule(() => {
14950
+ this.flushIdleInboxDeliveryRetry(agentId, source, traceName);
14951
+ }, this.stdinNotificationRetryMs);
14952
+ }
14953
+ flushIdleInboxDeliveryRetry(agentId, source, traceName) {
14813
14954
  const ap = this.agents.get(agentId);
14814
14955
  if (!ap) return false;
14815
14956
  const count = ap.notifications.takePendingAndClearTimer();
14816
14957
  if (count === 0) return false;
14817
14958
  if (!ap.driver.supportsStdinNotification || !ap.sessionId || ap.inbox.length === 0) {
14818
14959
  ap.notifications.add(count);
14819
- this.recordDaemonTrace("daemon.agent.stdin_delivery.async_rejected.retry", {
14960
+ this.recordDaemonTrace(traceName, {
14820
14961
  agentId,
14821
14962
  runtime: ap.config.runtime,
14822
14963
  model: ap.config.model,
@@ -14837,7 +14978,7 @@ var AgentProcessManager = class _AgentProcessManager {
14837
14978
  ap.notifications.pruneContributedToPending(ap.inbox, ap.sessionId);
14838
14979
  const messages = ap.notifications.filterUncontributedMessages(ap.inbox, ap.sessionId);
14839
14980
  if (messages.length === 0) {
14840
- this.recordDaemonTrace("daemon.agent.stdin_delivery.async_rejected.retry", {
14981
+ this.recordDaemonTrace(traceName, {
14841
14982
  agentId,
14842
14983
  runtime: ap.config.runtime,
14843
14984
  model: ap.config.model,
@@ -14851,16 +14992,16 @@ var AgentProcessManager = class _AgentProcessManager {
14851
14992
  return false;
14852
14993
  }
14853
14994
  this.commitApmIdleState(agentId, ap, false);
14854
- this.startRuntimeTrace(agentId, ap, "async-rejected-idle-delivery-retry", messages);
14995
+ this.startRuntimeTrace(agentId, ap, source.replaceAll("_", "-"), messages);
14855
14996
  this.broadcastMessageReceivedActivity(agentId);
14856
14997
  const accepted = this.deliverInboxUpdateViaStdin(
14857
14998
  agentId,
14858
14999
  ap,
14859
15000
  messages,
14860
15001
  "idle",
14861
- "async_rejected_idle_delivery_retry"
15002
+ source
14862
15003
  );
14863
- this.recordDaemonTrace("daemon.agent.stdin_delivery.async_rejected.retry", {
15004
+ this.recordDaemonTrace(traceName, {
14864
15005
  agentId,
14865
15006
  runtime: ap.config.runtime,
14866
15007
  model: ap.config.model,
@@ -14873,6 +15014,13 @@ var AgentProcessManager = class _AgentProcessManager {
14873
15014
  });
14874
15015
  return accepted;
14875
15016
  }
15017
+ flushAsyncRejectedIdleDelivery(agentId) {
15018
+ return this.flushIdleInboxDeliveryRetry(
15019
+ agentId,
15020
+ "async_rejected_idle_delivery_retry",
15021
+ "daemon.agent.stdin_delivery.async_rejected.retry"
15022
+ );
15023
+ }
14876
15024
  isApmIdle(ap) {
14877
15025
  return ap.gatedSteering.isIdle;
14878
15026
  }
@@ -14936,6 +15084,7 @@ var AgentProcessManager = class _AgentProcessManager {
14936
15084
  if (options.includeCompactionWatchdog ?? true) {
14937
15085
  this.clearCompactionWatchdog(ap);
14938
15086
  }
15087
+ this.clearReviewWatchdog(ap);
14939
15088
  this.clearStalledRecoverySigtermWatchdog(ap);
14940
15089
  }
14941
15090
  clearRuntimeErrorDeliveryBackoffWithTrace(agentId, ap, resetSource) {
@@ -15311,7 +15460,8 @@ var AgentProcessManager = class _AgentProcessManager {
15311
15460
  detailKind: activity.statusEntry.detailKind ?? "daemon_activity",
15312
15461
  entries: activity.entries,
15313
15462
  launchId: ap?.launchId || void 0,
15314
- clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
15463
+ clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0,
15464
+ isHeartbeat: false
15315
15465
  });
15316
15466
  }
15317
15467
  recordRuntimeDiagnosticActivity(agentId, ap, event) {
@@ -15324,7 +15474,8 @@ var AgentProcessManager = class _AgentProcessManager {
15324
15474
  detailKind: ap.lastActivityDetailKind || "none",
15325
15475
  entries: [runtimeDiagnosticTrajectoryEntry(event)],
15326
15476
  launchId: ap.launchId || void 0,
15327
- clientSeq: this.nextActivityClientSeq(agentId)
15477
+ clientSeq: this.nextActivityClientSeq(agentId),
15478
+ isHeartbeat: false
15328
15479
  });
15329
15480
  this.recordDaemonTrace("daemon.runtime.diagnostic", {
15330
15481
  agentId,
@@ -15375,7 +15526,8 @@ var AgentProcessManager = class _AgentProcessManager {
15375
15526
  detailKind: ap.lastActivityDetailKind || "none",
15376
15527
  entries: [runtimeRecoveryTrajectoryEntry(event)],
15377
15528
  launchId: ap.launchId || void 0,
15378
- clientSeq: this.nextActivityClientSeq(agentId)
15529
+ clientSeq: this.nextActivityClientSeq(agentId),
15530
+ isHeartbeat: false
15379
15531
  });
15380
15532
  this.recordDaemonTrace("daemon.runtime.recovery.visible", {
15381
15533
  agentId,
@@ -15623,7 +15775,8 @@ var AgentProcessManager = class _AgentProcessManager {
15623
15775
  this.lifecycleRecords.setRestartSnapshot(agentId, {
15624
15776
  config: this.buildRestartSafeConfig(ap.config, nextConfigSessionId),
15625
15777
  sessionId: nextConfigSessionId,
15626
- launchId: nextLaunchId
15778
+ launchId: nextLaunchId,
15779
+ processInstanceId: ap.processInstanceId
15627
15780
  });
15628
15781
  this.recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, nextSessionId);
15629
15782
  this.sendAgentStatus(agentId, "active", nextLaunchId);
@@ -16054,6 +16207,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16054
16207
  config: this.buildRestartSafeConfig(runtimeConfig, runtimeConfig.sessionId || null),
16055
16208
  sessionId: runtimeConfig.sessionId || null,
16056
16209
  launchId: effectiveLaunchId
16210
+ // No live AgentProcess in this adoption branch — omit rather than
16211
+ // fabricate; the marker's processInstanceId is nullable by contract.
16057
16212
  });
16058
16213
  this.sendAgentStatus(agentId, "active", effectiveLaunchId);
16059
16214
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
@@ -16112,6 +16267,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16112
16267
  readinessTransition: null,
16113
16268
  activation: { kind: "idle" },
16114
16269
  compaction: { kind: "none" },
16270
+ review: { kind: "none" },
16115
16271
  runtimeProgress: new RuntimeProgressState(Date.now()),
16116
16272
  runtimeTraceSpan: null,
16117
16273
  runtimeTraceCounters: createRuntimeTraceCounters(),
@@ -16142,7 +16298,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16142
16298
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16143
16299
  config: this.buildRestartSafeConfig(runtimeConfig, restartSessionId),
16144
16300
  sessionId: restartSessionId,
16145
- launchId: effectiveLaunchId
16301
+ launchId: effectiveLaunchId,
16302
+ processInstanceId: agentProcess.processInstanceId
16146
16303
  });
16147
16304
  if (pendingStartRebind) {
16148
16305
  this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", originalLaunchId, effectiveLaunchId, agentProcess.sessionId);
@@ -16344,7 +16501,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16344
16501
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16345
16502
  config: nextConfig,
16346
16503
  sessionId: ap.sessionId,
16347
- launchId: ap.launchId
16504
+ launchId: ap.launchId,
16505
+ processInstanceId: ap.processInstanceId
16348
16506
  });
16349
16507
  this.broadcastMessageReceivedActivity(agentId);
16350
16508
  this.lifecycleRecords.deleteRestartSnapshot(agentId);
@@ -16362,7 +16520,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16362
16520
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16363
16521
  config: nextConfig,
16364
16522
  sessionId: ap.sessionId,
16365
- launchId: ap.launchId
16523
+ launchId: ap.launchId,
16524
+ processInstanceId: ap.processInstanceId
16366
16525
  });
16367
16526
  const report = this.recordSpawnFailure(agentId, "runner_credential_mint");
16368
16527
  this.assertStartPendingDeliveryInvariants("queued-continuation-runner-credential-mint-failure");
@@ -16382,7 +16541,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16382
16541
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16383
16542
  config: nextConfig,
16384
16543
  sessionId: ap.sessionId,
16385
- launchId: ap.launchId
16544
+ launchId: ap.launchId,
16545
+ processInstanceId: ap.processInstanceId
16386
16546
  });
16387
16547
  this.broadcastActivity(agentId, "online", "Process idle", [], void 0, "idle");
16388
16548
  });
@@ -16391,7 +16551,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16391
16551
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16392
16552
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16393
16553
  sessionId: ap.sessionId,
16394
- launchId: ap.launchId
16554
+ launchId: ap.launchId,
16555
+ processInstanceId: ap.processInstanceId
16395
16556
  });
16396
16557
  if (!ap.driver.supportsStdinNotification) {
16397
16558
  logger.info(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
@@ -16403,7 +16564,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16403
16564
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16404
16565
  config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
16405
16566
  sessionId: ap.sessionId,
16406
- launchId: ap.launchId
16567
+ launchId: ap.launchId,
16568
+ processInstanceId: ap.processInstanceId
16407
16569
  });
16408
16570
  logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
16409
16571
  this.sendAgentStatus(agentId, "active", ap.launchId);
@@ -16619,7 +16781,8 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
16619
16781
  this.lifecycleRecords.setRestartSnapshot(agentId, {
16620
16782
  config: retryConfig,
16621
16783
  sessionId: retrySessionId,
16622
- launchId: ap.launchId
16784
+ launchId: ap.launchId,
16785
+ processInstanceId: ap.processInstanceId
16623
16786
  });
16624
16787
  this.recordDaemonTrace("daemon.agent.startup_timeout.retry_config_cached", {
16625
16788
  agentId,
@@ -17533,7 +17696,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17533
17696
  }
17534
17697
  return result;
17535
17698
  }
17536
- buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir) {
17699
+ buildRuntimeProfileReport(agentId, config, sessionId, launchId, observedRuntimeHomeDir, processInstanceId) {
17537
17700
  const workspacePath = path14.join(this.dataDir, agentId);
17538
17701
  const runtimeHomeDir = observedRuntimeHomeDir || resolveRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspacePath, { agentId, slockHome: this.slockHome });
17539
17702
  return {
@@ -17549,7 +17712,16 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17549
17712
  path: workspacePath,
17550
17713
  reachable: true
17551
17714
  },
17552
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, { agentId, workingDirectory: workspacePath }) : null
17715
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, {
17716
+ agentId,
17717
+ workingDirectory: workspacePath,
17718
+ // Join keys come from the CALLER's context (running agent OR idle
17719
+ // restart snapshot) — reading only live state here left the
17720
+ // restart/adopt path writing launchId:null markers, exactly the
17721
+ // V3 class this exists to close (Leiysky review on #3863).
17722
+ launchId: launchId || void 0,
17723
+ processInstanceId: processInstanceId ?? this.agents.get(agentId)?.processInstanceId
17724
+ }) : null
17553
17725
  }
17554
17726
  };
17555
17727
  }
@@ -17561,12 +17733,13 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17561
17733
  running.config,
17562
17734
  running.sessionId,
17563
17735
  running.launchId,
17564
- running.runtime.currentRuntimeHomeDir
17736
+ running.runtime.currentRuntimeHomeDir,
17737
+ running.processInstanceId
17565
17738
  );
17566
17739
  }
17567
17740
  const idle = this.lifecycleRecords.getRestartSnapshot(agentId);
17568
17741
  if (idle) {
17569
- return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId);
17742
+ return this.buildRuntimeProfileReport(agentId, idle.config, idle.sessionId, idle.launchId, null, idle.processInstanceId);
17570
17743
  }
17571
17744
  return null;
17572
17745
  }
@@ -17964,7 +18137,9 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
17964
18137
  }
17965
18138
  const ref = resolveRuntimeSessionRef(runtime, actualSessionId, homeDir, workspaceDir, {
17966
18139
  agentId,
17967
- workingDirectory: workspaceDir
18140
+ workingDirectory: workspaceDir,
18141
+ launchId: this.agents.get(agentId)?.launchId || void 0,
18142
+ processInstanceId: this.agents.get(agentId)?.processInstanceId
17968
18143
  });
17969
18144
  const tier = runtimeTier(runtime);
17970
18145
  const span = this.tracer.startSpan("daemon.session_transcript.read", {
@@ -18232,9 +18407,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18232
18407
  entries,
18233
18408
  launchId,
18234
18409
  clientSeq,
18235
- producerFactId
18410
+ producerFactId,
18411
+ isHeartbeat: false
18236
18412
  });
18237
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId);
18413
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, false);
18238
18414
  if (ap) {
18239
18415
  ap.lastActivityKind = activityKind;
18240
18416
  ap.lastActivity = activityDisplay;
@@ -18260,9 +18436,12 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18260
18436
  detailKind: ap.lastActivityDetailKind,
18261
18437
  launchId: heartbeatLaunchId,
18262
18438
  clientSeq: heartbeatClientSeq,
18263
- producerFactId: heartbeatProducerFactId
18439
+ producerFactId: heartbeatProducerFactId,
18440
+ // The one knowing site: this timer re-broadcasts stale
18441
+ // lastActivity, so it declares its replay provenance.
18442
+ isHeartbeat: true
18264
18443
  });
18265
- this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId);
18444
+ this.recordActivityProducedTrace(agentId, ap.lastActivityKind, ap.lastActivityDetail, ap.lastActivityDetailKind, [], ap, heartbeatLaunchId, heartbeatClientSeq, heartbeatProducerFactId, true);
18266
18445
  }, ACTIVITY_HEARTBEAT_MS);
18267
18446
  ap.activityHeartbeat = { kind: "active", timer };
18268
18447
  }
@@ -18271,7 +18450,7 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18271
18450
  }
18272
18451
  }
18273
18452
  }
18274
- recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId) {
18453
+ recordActivityProducedTrace(agentId, activityKind, detail, detailKind, entries, ap, launchId, clientSeq, producerFactId, isHeartbeat) {
18275
18454
  const runtimeContext = ap?.config.runtimeContext;
18276
18455
  this.recordDaemonTrace("daemon.agent.activity.produced", {
18277
18456
  agentId,
@@ -18293,6 +18472,11 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18293
18472
  client_seq_present: typeof clientSeq === "number",
18294
18473
  producerFactId,
18295
18474
  producer_fact_id: producerFactId,
18475
+ // #460 V1: the wire's producer-declared replay-provenance bit must be
18476
+ // independently verifiable from daemon-side evidence (L2<->L3 seam);
18477
+ // closed boolean, mirrors the agent:activity isHeartbeat field exactly.
18478
+ isHeartbeat,
18479
+ is_heartbeat: isHeartbeat,
18296
18480
  correlation_id: `agent:${agentId}:daemonActivity:${launchId ?? "legacy"}:${clientSeq ?? "unsequenced"}`,
18297
18481
  session_id_present: Boolean(ap?.sessionId),
18298
18482
  runtime: ap?.config.runtime
@@ -18341,9 +18525,10 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18341
18525
  launchId,
18342
18526
  probeId,
18343
18527
  clientSeq,
18344
- producerFactId
18528
+ producerFactId,
18529
+ isHeartbeat: false
18345
18530
  });
18346
- this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId);
18531
+ this.recordActivityProducedTrace(agentId, activityKind, detail, detailKind, [], ap, launchId, clientSeq, producerFactId, false);
18347
18532
  }
18348
18533
  flushPendingTrajectory(agentId) {
18349
18534
  const ap = this.agents.get(agentId);
@@ -18489,6 +18674,29 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
18489
18674
  ap.compaction = { ...ap.compaction, watchdog: null };
18490
18675
  this.broadcastActivity(agentId, "working", "Context compaction still running; no finish event observed", [], void 0, "compaction_stale");
18491
18676
  }
18677
+ startReviewWatchdog(agentId, ap) {
18678
+ this.clearReviewWatchdog(ap);
18679
+ const startedAt = Date.now();
18680
+ const watchdog = setTimeout(() => {
18681
+ this.markReviewStale(agentId, startedAt);
18682
+ }, REVIEW_STALE_MS);
18683
+ ap.review = { kind: "active", startedAt, watchdog };
18684
+ }
18685
+ clearReviewWatchdog(ap) {
18686
+ if (ap.review.kind === "active" && ap.review.watchdog) {
18687
+ clearTimeout(ap.review.watchdog);
18688
+ }
18689
+ ap.review = { kind: "none" };
18690
+ }
18691
+ markReviewStale(agentId, startedAt) {
18692
+ const ap = this.agents.get(agentId);
18693
+ if (!ap || ap.review.kind !== "active" || ap.review.startedAt !== startedAt) return;
18694
+ ap.review = { ...ap.review, watchdog: null };
18695
+ this.broadcastActivity(agentId, "working", "Review mode still active; no finish event observed", [], void 0, "review_stale");
18696
+ const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_finished" });
18697
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "review_finished", inferred: true });
18698
+ this.flushReviewBoundaryMessages(agentId, ap);
18699
+ }
18492
18700
  completeCompactionIfActive(agentId, detail = "Context compaction finished", options = {}) {
18493
18701
  const ap = this.agents.get(agentId);
18494
18702
  if (!ap || ap.compaction.kind !== "active") return;
@@ -19428,11 +19636,15 @@ Use \`raft message read\` to catch up on the channels listed above, then stop. R
19428
19636
  if (ap) {
19429
19637
  const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_started" });
19430
19638
  this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "review_started" });
19639
+ this.startReviewWatchdog(agentId, ap);
19431
19640
  }
19432
19641
  break;
19433
19642
  case "review_finished":
19434
19643
  this.flushPendingTrajectory(agentId);
19435
- if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_mode.finished");
19644
+ if (ap) {
19645
+ this.clearReviewWatchdog(ap);
19646
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_mode.finished");
19647
+ }
19436
19648
  this.broadcastActivity(agentId, "working", "Review finished", [], void 0, "review_finished");
19437
19649
  if (ap) {
19438
19650
  const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_finished" });
@@ -19953,6 +20165,15 @@ ${RESPONSE_TARGET_HINT}`;
19953
20165
  `${mode} inbox update`
19954
20166
  );
19955
20167
  if (!sendResult.ok) {
20168
+ const retryNotificationCount = mode === "idle" && ap.driver.supportsStdinNotification && ap.sessionId ? messages.length : 0;
20169
+ const retrySource = source.endsWith("_retry") ? source : `${source}_retry`;
20170
+ const retryScheduled = mode === "idle" ? this.scheduleIdleInboxDeliveryRetry(
20171
+ agentId,
20172
+ ap,
20173
+ retryNotificationCount,
20174
+ retrySource,
20175
+ "daemon.agent.stdin_delivery.idle_retry"
20176
+ ) : false;
19956
20177
  if (mode === "idle") {
19957
20178
  this.commitApmIdleState(agentId, ap, true);
19958
20179
  }
@@ -19977,7 +20198,9 @@ ${RESPONSE_TARGET_HINT}`;
19977
20198
  outcome: runtimeSendFailureOutcome(sendResult),
19978
20199
  failure_reason: sendResult.reason,
19979
20200
  failure_error: sendResult.error,
19980
- requeued_messages_count: 0,
20201
+ requeued_messages_count: retryNotificationCount,
20202
+ retry_scheduled: retryScheduled,
20203
+ notification_timer_present: ap.notifications.hasTimer,
19981
20204
  cursors_advanced: "none"
19982
20205
  }, "error");
19983
20206
  return false;
@@ -20162,6 +20385,14 @@ var systemClock = {
20162
20385
  clearTimeout: (timer) => clearTimeout(timer)
20163
20386
  };
20164
20387
  var INBOUND_WATCHDOG_MS = 7e4;
20388
+ function normalizeHandshakeReason(value) {
20389
+ const raw = Array.isArray(value) ? value[0] : value;
20390
+ if (typeof raw !== "string") return null;
20391
+ const trimmed = raw.trim();
20392
+ if (!trimmed) return null;
20393
+ if (!/^[a-z0-9_:-]{1,80}$/i.test(trimmed)) return "invalid_header";
20394
+ return trimmed;
20395
+ }
20165
20396
  function durationMsBucket(ms) {
20166
20397
  if (ms == null || !Number.isFinite(ms) || ms < 0) return "unknown";
20167
20398
  if (ms === 0) return "0";
@@ -20254,6 +20485,7 @@ var DaemonConnection = class {
20254
20485
  });
20255
20486
  const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl, wsOptions) : new WebSocket(wsUrl, wsOptions);
20256
20487
  this.ws = ws;
20488
+ let handshakeRejected = false;
20257
20489
  ws.on("open", () => {
20258
20490
  if (this.ws !== ws) return;
20259
20491
  if (!this.shouldConnect) return;
@@ -20314,8 +20546,27 @@ var DaemonConnection = class {
20314
20546
  this.options.onDisconnect();
20315
20547
  this.scheduleReconnect();
20316
20548
  });
20549
+ ws.on("unexpected-response", (_request, response) => {
20550
+ if (this.ws !== ws) return;
20551
+ handshakeRejected = true;
20552
+ const reason = normalizeHandshakeReason(response.headers["slock-reason"]);
20553
+ const statusCode = response.statusCode ?? 0;
20554
+ const reasonText = reason ? `, slock_reason=${reason}` : "";
20555
+ logger.error(`[Daemon] WebSocket handshake rejected (status=${statusCode}${reasonText})`);
20556
+ this.trace("daemon.connection.handshake_rejected", {
20557
+ status_code: statusCode,
20558
+ slock_reason_present: Boolean(reason),
20559
+ slock_reason: reason
20560
+ }, "error");
20561
+ response.resume();
20562
+ try {
20563
+ ws.terminate();
20564
+ } catch {
20565
+ }
20566
+ });
20317
20567
  ws.on("error", (err) => {
20318
20568
  if (this.ws !== ws) return;
20569
+ if (handshakeRejected) return;
20319
20570
  logger.error(`[Daemon] WebSocket error: ${err.message}`);
20320
20571
  this.trace("daemon.connection.error", {
20321
20572
  error_class: err.name || "Error"
@@ -21324,7 +21575,17 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
21324
21575
  spanAttrs: ["running_agents_count", "idle_agents_count"]
21325
21576
  },
21326
21577
  "daemon.agent.activity.produced": {
21327
- spanAttrs: ["agentId", "agent_id", "server_id", "machine_id", "activity", "detail_present", "entry_kinds", "ap_present", "launchId", "launch_id", "launch_id_present", "clientSeq", "client_seq", "client_seq_present", "correlation_id", "session_id_present", "runtime"]
21578
+ // isHeartbeat/is_heartbeat (#460 V1) and process_instance_id (#460 V3)
21579
+ // were emitted by agentProcessManager but scrubbed here (pilot violation
21580
+ // V4): this list is a RUNTIME allowlist (SpanAttrContractTracer), so an
21581
+ // emission-site key that is not added here silently dies before disk.
21582
+ // Witness for the pair lives in agentProcessManager.builtin.e2e.test.ts
21583
+ // behind a contract-wrapped tracer (production-isomorphic oracle).
21584
+ // producerFactId/producer_fact_id and activity_kind/detail_kind are ALSO
21585
+ // emitted-and-scrubbed today; deliberately NOT added here — banned-join-
21586
+ // key discipline for the fact id (#460 classification ruling) means
21587
+ // widening needs its own ruling, not a drive-by.
21588
+ spanAttrs: ["agentId", "agent_id", "server_id", "machine_id", "activity", "detail_present", "entry_kinds", "ap_present", "launchId", "launch_id", "launch_id_present", "clientSeq", "client_seq", "client_seq_present", "correlation_id", "session_id_present", "runtime", "isHeartbeat", "is_heartbeat", "process_instance_id"]
21328
21589
  },
21329
21590
  "daemon.agent.activity.skipped": {
21330
21591
  spanAttrs: ["agentId", "event_kind", "reason", "text_length"]
@@ -21333,7 +21594,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
21333
21594
  spanAttrs: ["agentId", "event_kind", "runtime"]
21334
21595
  }
21335
21596
  };
21336
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
21597
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
21337
21598
  var RunnerCredentialMintError2 = class extends Error {
21338
21599
  code;
21339
21600
  retryable;
@@ -21369,9 +21630,9 @@ function runnerCredentialErrorDetail2(error) {
21369
21630
  async function waitForRunnerCredentialRetry2() {
21370
21631
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
21371
21632
  }
21372
- function parseDaemonCliArgs(args) {
21633
+ function parseDaemonCliArgs(args, env = {}) {
21373
21634
  let serverUrl = "";
21374
- let apiKey = "";
21635
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
21375
21636
  for (let i = 0; i < args.length; i++) {
21376
21637
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
21377
21638
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -21408,7 +21669,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
21408
21669
  }
21409
21670
  async function runBundledSlockCli(argv) {
21410
21671
  process.argv = [process.execPath, "slock", ...argv];
21411
- await import("./dist-Y3VRAU5E.js");
21672
+ await import("./dist-RD3WLJSB.js");
21412
21673
  }
21413
21674
  function detectRuntimes(tracer = noopTracer) {
21414
21675
  const ids = [];
@@ -22350,6 +22611,8 @@ var DaemonCore = class {
22350
22611
 
22351
22612
  export {
22352
22613
  subscribeDaemonLogs,
22614
+ DAEMON_API_KEY_ENV,
22615
+ scrubDaemonAuthEnv,
22353
22616
  resolveWorkspaceDirectoryPath,
22354
22617
  scanWorkspaceDirectories,
22355
22618
  deleteWorkspaceDirectory,