@schoolai/shipyard 3.11.0 → 3.11.1

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.
Files changed (60) hide show
  1. package/dist/{auth-GGM253LQ.js → auth-AUY74PMB.js} +3 -3
  2. package/dist/capability-detector-worker.js +8 -8
  3. package/dist/{chunk-R3XQ6W7L.js → chunk-4SYLDZTY.js} +4 -4
  4. package/dist/{chunk-C6QOTETH.js → chunk-5LWD5W7O.js} +24 -10
  5. package/dist/chunk-5LWD5W7O.js.map +1 -0
  6. package/dist/{chunk-IJHF4OM4.js → chunk-5W5N5U2S.js} +2 -2
  7. package/dist/{chunk-L7ELOV3S.js → chunk-FVZ5BDZS.js} +4 -4
  8. package/dist/chunk-FVZ5BDZS.js.map +1 -0
  9. package/dist/{chunk-RW2OTTUA.js → chunk-KYLYGFMH.js} +4 -4
  10. package/dist/{chunk-QJP7JCIS.js → chunk-LRNGLC4V.js} +41 -3
  11. package/dist/chunk-LRNGLC4V.js.map +1 -0
  12. package/dist/{chunk-A2UK6TW2.js → chunk-LZSMNUAI.js} +18 -1
  13. package/dist/{chunk-A2UK6TW2.js.map → chunk-LZSMNUAI.js.map} +1 -1
  14. package/dist/{chunk-Z37T5W6S.js → chunk-P2HZDIN7.js} +12 -7
  15. package/dist/chunk-P2HZDIN7.js.map +1 -0
  16. package/dist/{chunk-ZRJTZLRF.js → chunk-QKJNVVQ3.js} +4 -4
  17. package/dist/{chunk-2EQOL57Z.js → chunk-TFRYQDDG.js} +2 -2
  18. package/dist/{chunk-YXPPZQBJ.js → chunk-VL5RUCRF.js} +164 -37
  19. package/dist/chunk-VL5RUCRF.js.map +1 -0
  20. package/dist/{chunk-3WEEGJJN.js → chunk-X5KCX6ZS.js} +2 -2
  21. package/dist/{chunk-GM6MH4CD.js → chunk-XIEOWUPV.js} +2 -2
  22. package/dist/{chunk-6LINHACK.js → chunk-Y5UWRARP.js} +47 -24
  23. package/dist/chunk-Y5UWRARP.js.map +1 -0
  24. package/dist/cursor-runner.js +88 -62
  25. package/dist/cursor-runner.js.map +1 -1
  26. package/dist/electron-utility.js +5 -5
  27. package/dist/{git-repo-QNGPCJLI.js → git-repo-CTZJS3ER.js} +6 -4
  28. package/dist/index.js +8 -8
  29. package/dist/{logger-2F3CBS3V.js → logger-AN7EUK2B.js} +7 -5
  30. package/dist/{login-U256OVOJ.js → login-YB34LF4L.js} +6 -6
  31. package/dist/{logout-HY3MPOY5.js → logout-GUXVSWLZ.js} +5 -5
  32. package/dist/{mcp-servers-ICHOWXZB.js → mcp-servers-OAPQNDA7.js} +4 -4
  33. package/dist/{roi-YM5OOWHG.js → roi-NXJHL5X2.js} +3 -3
  34. package/dist/{serve-D5GKV2RU.js → serve-P3U2C5YH.js} +1175 -739
  35. package/dist/{serve-D5GKV2RU.js.map → serve-P3U2C5YH.js.map} +1 -1
  36. package/dist/{skills-W2Y6TWHA.js → skills-2UBVHFQ5.js} +2 -2
  37. package/dist/{start-JY26XC5R.js → start-Y34X3WVF.js} +10 -10
  38. package/package.json +1 -1
  39. package/dist/chunk-6LINHACK.js.map +0 -1
  40. package/dist/chunk-C6QOTETH.js.map +0 -1
  41. package/dist/chunk-L7ELOV3S.js.map +0 -1
  42. package/dist/chunk-QJP7JCIS.js.map +0 -1
  43. package/dist/chunk-YXPPZQBJ.js.map +0 -1
  44. package/dist/chunk-Z37T5W6S.js.map +0 -1
  45. /package/dist/{auth-GGM253LQ.js.map → auth-AUY74PMB.js.map} +0 -0
  46. /package/dist/{chunk-R3XQ6W7L.js.map → chunk-4SYLDZTY.js.map} +0 -0
  47. /package/dist/{chunk-IJHF4OM4.js.map → chunk-5W5N5U2S.js.map} +0 -0
  48. /package/dist/{chunk-RW2OTTUA.js.map → chunk-KYLYGFMH.js.map} +0 -0
  49. /package/dist/{chunk-ZRJTZLRF.js.map → chunk-QKJNVVQ3.js.map} +0 -0
  50. /package/dist/{chunk-2EQOL57Z.js.map → chunk-TFRYQDDG.js.map} +0 -0
  51. /package/dist/{chunk-3WEEGJJN.js.map → chunk-X5KCX6ZS.js.map} +0 -0
  52. /package/dist/{chunk-GM6MH4CD.js.map → chunk-XIEOWUPV.js.map} +0 -0
  53. /package/dist/{git-repo-QNGPCJLI.js.map → git-repo-CTZJS3ER.js.map} +0 -0
  54. /package/dist/{logger-2F3CBS3V.js.map → logger-AN7EUK2B.js.map} +0 -0
  55. /package/dist/{login-U256OVOJ.js.map → login-YB34LF4L.js.map} +0 -0
  56. /package/dist/{logout-HY3MPOY5.js.map → logout-GUXVSWLZ.js.map} +0 -0
  57. /package/dist/{mcp-servers-ICHOWXZB.js.map → mcp-servers-OAPQNDA7.js.map} +0 -0
  58. /package/dist/{roi-YM5OOWHG.js.map → roi-NXJHL5X2.js.map} +0 -0
  59. /package/dist/{skills-W2Y6TWHA.js.map → skills-2UBVHFQ5.js.map} +0 -0
  60. /package/dist/{start-JY26XC5R.js.map → start-Y34X3WVF.js.map} +0 -0
@@ -37,7 +37,7 @@ var EnvSchema = external_exports.object({
37
37
  SHIPYARD_DEV: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
38
38
  SHIPYARD_DAEMON_CHILD: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
39
39
  SHIPYARD_DATA_DIR: external_exports.string().optional().transform((v) => v ?? `~/${shipyardDirName()}/data`),
40
- LOG_LEVEL: external_exports.enum(["debug", "info", "warn", "error"]).default("warn"),
40
+ LOG_LEVEL: external_exports.enum(["debug", "info", "warn", "error"]).default("info"),
41
41
  SHIPYARD_SIGNALING_URL: external_exports.string().url().optional(),
42
42
  SHIPYARD_PRODUCTION_SIGNALING_URL: external_exports.string().url().default(DEFAULT_PRODUCTION_SIGNALING_URL),
43
43
  SHIPYARD_METRICS_WORKER_URL: external_exports.string().url().default("https://shipyard-metrics.jacob-191.workers.dev"),
@@ -83,13 +83,18 @@ var EnvSchema = external_exports.object({
83
83
  SHIPYARD_STALL_PROFILER_ENABLED: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v !== "0" && v !== "false"),
84
84
  /**
85
85
  * Event-loop stall threshold (ms). Once `histogram.max` crosses this,
86
- * the profiler starts a CPU profile. Raised from 250ms to 5000ms in
87
- * May 2026 to prevent the profiler from amplifying the stalls it was
88
- * trying to measure — V8 inspector `post()` itself consumed ~493ms of
89
- * a measured 800ms capture window. At 5s only severe stalls are profiled.
86
+ * the profiler starts a CPU profile. History: raised 250ms 5000ms in
87
+ * May 2026 because the capture itself amplified the stalls it measured
88
+ * (V8 inspector `post()` consumed ~493ms of an 800ms capture window);
89
+ * lowered 5000ms 1000ms in Jun 2026 (#4484) because the dominant fleet
90
+ * stall volume is 1–10s jank that 5s never profiled. Amplification is now
91
+ * bounded by the profiler's own guards instead of the threshold: the 5-min
92
+ * SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS rate limit between captures, a
93
+ * capture-in-progress flag, and the 250ms sample window — worst case 12
94
+ * captures/hour (~3s of profiling per hour).
90
95
  * Override via `SHIPYARD_STALL_PROFILER_THRESHOLD_MS`.
91
96
  */
92
- SHIPYARD_STALL_PROFILER_THRESHOLD_MS: external_exports.coerce.number().int().positive().default(5e3),
97
+ SHIPYARD_STALL_PROFILER_THRESHOLD_MS: external_exports.coerce.number().int().positive().default(1e3),
93
98
  /**
94
99
  * How long to sample the V8 CPU profile after a stall is detected.
95
100
  * Default 250ms — long enough for the post-stall queue drain and the
@@ -220,4 +225,4 @@ export {
220
225
  validateEnv,
221
226
  getStallProfilerConfig
222
227
  };
223
- //# sourceMappingURL=chunk-Z37T5W6S.js.map
228
+ //# sourceMappingURL=chunk-P2HZDIN7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/env.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\n\n/**\n * Enable fine-grained tool streaming so MCP tool inputs stream token-by-token\n * via input_json_delta events instead of being buffered as complete JSON.\n * Set early so the subprocess inherits it.\n */\nprocess.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING = '1';\n\n/**\n * Enable the Task system (TaskCreate/TaskGet/TaskList/TaskUpdate) which provides\n * structured task management with dependency tracking (blocks/blockedBy).\n * Replaces TodoWrite — when enabled, CC only has TaskCreate (not TodoWrite).\n */\nprocess.env.CLAUDE_CODE_ENABLE_TASKS = '1';\n\n/**\n * Bootstrap exception: reads `process.env` directly because it feeds the\n * `SHIPYARD_DATA_DIR` schema transform (via `shipyardDirName`). Routing it\n * through `validateEnv()` would recurse (validateEnv → parse → transform →\n * shipyardDirName → isDevMode → validateEnv).\n */\nexport function isDevMode(): boolean {\n return process.env.SHIPYARD_DEV === '1' || process.env.SHIPYARD_DEV === 'true';\n}\n\n/**\n * When enabled, the agent runs with only Shipyard builtins (system prompt,\n * comment + visualize MCP servers) and no user/plugin MCP servers.\n * Useful for testing the harness in isolation without external interference.\n *\n * Set `SHIPYARD_VANILLA_AGENT=1` to activate.\n */\nexport function isVanillaAgentMode(): boolean {\n return validateEnv().SHIPYARD_VANILLA_AGENT;\n}\n\n/**\n * Reuse Claude Code's macOS Keychain item (\"Claude Code-credentials\") as a\n * source for MCP connector OAuth clients/tokens.\n *\n * Default OFF. The daemon is a different binary than Claude Code, so reading\n * that Keychain item triggers a macOS password prompt in a GUI session (or a\n * silent denial when headless) — unacceptable in the connector status path,\n * which runs on every capability refresh. Shipyard's own token store is the\n * source of truth, mirroring how local MCPs work (no prompt). Opt back into\n * the legacy import behavior with `SHIPYARD_MCP_REUSE_CC_KEYCHAIN=1`.\n */\nexport function isClaudeCodeKeychainReadEnabled(): boolean {\n return validateEnv().SHIPYARD_MCP_REUSE_CC_KEYCHAIN;\n}\n\n/**\n * Reuse Codex's macOS Keychain item (\"Codex MCP Credentials\") as a source for\n * per-MCP-connector OAuth clients/tokens (Codex DCRs its own atomic\n * client_id+token pairs, useful for borrowing on connectors Claude Code hasn't\n * registered with).\n *\n * Default OFF for the same reason as {@link isClaudeCodeKeychainReadEnabled}:\n * the daemon is a different binary than Codex, so reading that Keychain item\n * triggers a macOS password prompt in a GUI session (or a silent denial when\n * headless) — unacceptable in the connector status path. The\n * `~/.codex/.credentials.json` file fallback is read regardless. Opt in with\n * `SHIPYARD_MCP_REUSE_CODEX_KEYCHAIN=1`.\n */\nexport function isCodexKeychainReadEnabled(): boolean {\n return validateEnv().SHIPYARD_MCP_REUSE_CODEX_KEYCHAIN;\n}\n\nfunction shipyardDirName(): string {\n return isDevMode() ? '.shipyard-dev' : '.shipyard';\n}\n\n/**\n * The signaling worker is a RENDEZVOUS point: a browser and this daemon only\n * meet if they hit the SAME worker. This MUST equal the web build's\n * `VITE_SESSION_SERVER_URL` (.github/workflows/ci.yml + publish-npm.yml) — all\n * live clients (web next+stable, daemons all channels) point here. Do not make\n * this per-channel; a staging worker is a CI/E2E fixture only. See AGENTS.md\n * Invariant #22 (the #4186 \"install daemon\" incident).\n */\nconst DEFAULT_PRODUCTION_SIGNALING_URL = 'https://shipyard-session-server.jacob-191.workers.dev';\nconst DEFAULT_PRODUCTION_WEB_URL = 'https://shipyard.computer';\n\n/**\n * Bootstrap exception: reads `process.env` directly (not `validateEnv()`)\n * because it computes the electron-utility startup-breadcrumb path BEFORE\n * `validateEnv()` runs — the breadcrumb must still resolve when validateEnv\n * itself throws (see apps/daemon/src/electron-utility.ts).\n */\nexport function getShipyardHome(): string {\n if (process.env.SHIPYARD_HOME) return process.env.SHIPYARD_HOME;\n return join(homedir(), shipyardDirName());\n}\n\n/**\n * The executable to fork JS subprocesses with (cursor-runner, the TS language\n * server, the cursor-hook shim).\n *\n * `child_process.fork`/`spawn` default to `process.execPath`. Inside an\n * Electron *utility* process (how the packaged daemon runs) that does NOT point\n * at the main app binary — it resolves to a per-platform helper executable\n * (e.g. `…/Frameworks/Shipyard Helper.app/Contents/MacOS/Shipyard Helper`) that\n * may not exist on disk in the shipped bundle. Forking it ENOENTs, which (with\n * the cursor-runner pre-warm running at boot) crash-looped the daemon on\n * 2026-06-01 (Dylan's log).\n *\n * The Electron *main* process has a guaranteed-valid `process.execPath` (the\n * main app binary, which also honors `ELECTRON_RUN_AS_NODE=1`) and forwards it\n * as `SHIPYARD_NODE_EXEC_PATH` (see apps/electron/src/main/daemon-host.ts). The\n * CLI daemon and dev leave it unset and fall back to `process.execPath`, which\n * is already correct there. Declared in `EnvSchema` so a malformed value fails\n * at boot. Re-exported from `spawn-as-node.ts` for call-site ergonomics.\n */\nexport function resolveNodeExecPath(): string {\n return validateEnv().SHIPYARD_NODE_EXEC_PATH || process.execPath;\n}\n\nexport const EnvSchema = z.object({\n ANTHROPIC_API_KEY: z.string().min(1).optional(),\n SHIPYARD_DEV: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_DAEMON_CHILD: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_DATA_DIR: z\n .string()\n .optional()\n .transform((v) => v ?? `~/${shipyardDirName()}/data`),\n LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n SHIPYARD_SIGNALING_URL: z.string().url().optional(),\n SHIPYARD_PRODUCTION_SIGNALING_URL: z.string().url().default(DEFAULT_PRODUCTION_SIGNALING_URL),\n SHIPYARD_METRICS_WORKER_URL: z\n .string()\n .url()\n .default('https://shipyard-metrics.jacob-191.workers.dev'),\n SHIPYARD_TELEMETRY: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n SHIPYARD_USER_TOKEN: z.string().optional(),\n /**\n * Worker URL for the `shipyard roi` report command (distinct from\n * SHIPYARD_METRICS_WORKER_URL, which is the telemetry ingestion endpoint).\n * Falls back to the `--worker-url` CLI flag when unset.\n */\n SHIPYARD_METRICS_URL: z.string().url().optional(),\n SHIPYARD_USER_ID: z.string().optional(),\n SHIPYARD_MACHINE_ID: z.string().optional(),\n SHIPYARD_MACHINE_NAME: z.string().optional(),\n SHIPYARD_USER_DISPLAY_NAME: z.string().optional(),\n SHIPYARD_HOME: z.string().optional(),\n SHIPYARD_WORKTREE_NAME: z.string().optional(),\n /**\n * Electron utility transport mode. Absent for the CLI daemon; set by\n * apps/electron/src/main/daemon-host.ts when the daemon runs as an Electron\n * utility process.\n */\n SHIPYARD_TRANSPORT: z.enum(['electron-port']).optional(),\n /**\n * Build artifact hosting this daemon. Electron disables the daemon's npm\n * self-updater because electron-updater owns app replacement.\n */\n SHIPYARD_ARTIFACT: z.enum(['electron']).optional(),\n SHIPYARD_VANILLA_AGENT: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n /**\n * Opt in to reusing Claude Code's macOS Keychain credentials for MCP OAuth.\n * Default OFF — see {@link isClaudeCodeKeychainReadEnabled} for why (prompt\n * avoidance). Validated here so a typo fails at boot.\n */\n SHIPYARD_MCP_REUSE_CC_KEYCHAIN: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n /**\n * Opt in to reusing Codex's macOS Keychain credentials (\"Codex MCP\n * Credentials\") for MCP OAuth. Default OFF — see\n * {@link isCodexKeychainReadEnabled} for why (prompt avoidance). Validated\n * here so a typo fails at boot.\n */\n SHIPYARD_MCP_REUSE_CODEX_KEYCHAIN: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_STALL_PROFILER_ENABLED: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n /**\n * Event-loop stall threshold (ms). Once `histogram.max` crosses this,\n * the profiler starts a CPU profile. History: raised 250ms → 5000ms in\n * May 2026 because the capture itself amplified the stalls it measured\n * (V8 inspector `post()` consumed ~493ms of an 800ms capture window);\n * lowered 5000ms → 1000ms in Jun 2026 (#4484) because the dominant fleet\n * stall volume is 1–10s jank that 5s never profiled. Amplification is now\n * bounded by the profiler's own guards instead of the threshold: the 5-min\n * SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS rate limit between captures, a\n * capture-in-progress flag, and the 250ms sample window — worst case 12\n * captures/hour (~3s of profiling per hour).\n * Override via `SHIPYARD_STALL_PROFILER_THRESHOLD_MS`.\n */\n SHIPYARD_STALL_PROFILER_THRESHOLD_MS: z.coerce.number().int().positive().default(1_000),\n /**\n * How long to sample the V8 CPU profile after a stall is detected.\n * Default 250ms — long enough for the post-stall queue drain and the\n * cascading sibling stall (if any) to land in the same profile, but\n * short enough that the capture window does not itself amplify the\n * stall on the same event loop. Lowered from 1000 → 250 in May 2026\n * after profile evidence showed `#capture` consumed ~1s of inclusive\n * time inside a 3.5s stall window — the profiler was contributing\n * to the stalls it was trying to measure once #3204 dropped the\n * threshold from 500ms to 250ms.\n */\n SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS: z.coerce.number().int().positive().default(250),\n /**\n * Cascading-stall suppression window. After one capture, subsequent\n * stalls within this window log `stall_profile_suppressed` instead of\n * starting a new profile. Raised from 30s to 300s (5min) in May 2026\n * to prevent capture storms — at 30s a recurring stall every 60s would\n * trigger 2 captures/min, each adding hundreds of ms of inspector overhead.\n */\n SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS: z.coerce.number().int().nonnegative().default(300_000),\n /**\n * Supervisor wedge detection: if the daemon child sends no IPC heartbeats\n * for this many ms, the supervisor declares the event loop wedged, sends\n * SIGUSR2 to capture a CPU profile (the inspector runs off the JS loop),\n * waits 2s, then SIGKILLs and respawns via the normal abnormal-exit flow.\n * Generous default — 30s tolerates GC pauses and other legitimate sync\n * blocks well outside human-noticeable territory.\n */\n SHIPYARD_WEDGE_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),\n /**\n * Daemon child IPC heartbeat interval. Must be smaller than\n * SHIPYARD_WEDGE_TIMEOUT_MS by a comfortable margin so a single missed\n * heartbeat doesn't trip the wedge detector.\n */\n SHIPYARD_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().positive().default(2_000),\n /**\n * When set to `'electron-safe-storage'`, the vault-key-manager wraps the\n * master key via Electron's safeStorage API (macOS Keychain on macOS,\n * Secret Service on Linux). Set by daemon-host.ts in the Electron main\n * process. When absent, the daemon falls back to the existing AES-256-GCM\n * file-based path (CLI daemon path — unchanged).\n */\n SHIPYARD_VAULT_BACKEND: z.enum(['electron-safe-storage']).optional(),\n /**\n * Absolute path to a Node-capable executable for forking JS subprocesses\n * (cursor-runner, the TS language server, the cursor-hook shim). Set by\n * apps/electron/src/main/daemon-host.ts to the Electron MAIN process's\n * `process.execPath` (the main app binary), because the daemon utility\n * process's own `process.execPath` resolves to a helper executable that may\n * not exist in the packaged bundle. Absent for the CLI daemon, which falls\n * back to its own `process.execPath`. See `resolveNodeExecPath()`.\n */\n SHIPYARD_NODE_EXEC_PATH: z.string().optional(),\n /**\n * TTL (ms) for the per-repo worktree list cache. Browser polls at 4-7/sec;\n * this collapses redundant git subprocess invocations to 1 per TTL window.\n * Set to 0 to disable caching (useful for tests that need fresh results).\n */\n SHIPYARD_WORKTREE_LIST_TTL_MS: z.coerce.number().int().min(0).default(30_000),\n /**\n * Override for the web app URL the daemon advertises. Absent → derived from\n * the signaling URL (see `getWebAppUrl`). A malformed value fails at boot\n * rather than propagating into URL concatenation.\n */\n SHIPYARD_WEB_URL: z.string().url().optional(),\n SHIPYARD_PRODUCTION_WEB_URL: z.string().url().default(DEFAULT_PRODUCTION_WEB_URL),\n /**\n * Absolute path override for the Claude Code binary. When set and the file\n * exists, the daemon spawns it instead of the SDK-bundled native binary.\n * See `resolveClaudeCodePath` / `resolveClaudeBinaryPath`.\n */\n CLAUDE_CODE_PATH: z.string().optional(),\n /**\n * Suppress auto-opening the verification URL in the browser during the\n * CLI device-login flow. Default OFF (the browser opens).\n */\n SHIPYARD_NO_OPEN: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n /**\n * Kill-switch for Loro CRDT recovery on startup. Default OFF (recovery runs).\n */\n SHIPYARD_DISABLE_LORO_RECOVERY: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n /**\n * Harness HTTP MCP server (Codex's endpoint). Default ON — Codex ships with\n * every daemon and needs it. `SHIPYARD_HARNESS_HTTP=0` is the kill-switch for\n * Codex-disabled deployments.\n */\n SHIPYARD_HARNESS_HTTP: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n /**\n * Use the shared `codex app-server` engine (single token owner — fixes the\n * 401-storm). Default ON. Set to `0`/`false` for the legacy per-process path.\n */\n SHIPYARD_CODEX_SHARED_ENGINE: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n /**\n * Browser-metrics sampling cadence. Kept as a raw string here — clamp /\n * disable (`0`) / invalid-fallback semantics + logging live in\n * `parseBrowserMetricsInterval` (services/metrics/browser-metrics-config.ts),\n * which consumes this validated value.\n */\n SHIPYARD_BROWSER_METRICS_INTERVAL_MS: z.string().optional(),\n /**\n * Max concurrent file watchers before the guard sheds new subscriptions.\n * Absent → default (450). An invalid value fails at boot.\n */\n SHIPYARD_FILE_WATCHER_MAX: z.coerce.number().int().positive().optional(),\n});\n\nexport type Env = z.infer<typeof EnvSchema>;\n\nexport function validateEnv(): Env {\n return EnvSchema.parse(process.env);\n}\n\n/**\n * Resolved profiler tuning derived from the validated env. Re-parses the\n * schema so a typo in one of the *_MS vars (e.g. `THRESHOLD_MS=abc`) fails\n * here too — boot already failed in `validateEnv()` (start.ts / serve.ts\n * call it), this second parse is cheap and keeps the wiring side free of\n * `process.env.X` reads at the call site.\n */\nexport interface StallProfilerConfig {\n enabled: boolean;\n thresholdMs: number;\n captureMs: number;\n rateLimitMs: number;\n}\n\nexport function getStallProfilerConfig(): StallProfilerConfig {\n const env = validateEnv();\n\n /**\n * Default OFF in packaged Electron when `SHIPYARD_STALL_PROFILER_ENABLED`\n * is unset. Rationale: the V8 inspector `post()` blocks the event loop\n * during capture — in a 2,959ms stall, `node:inspector:117 post` consumed\n * 493.5ms of 800ms captured, i.e. the profiler was profiling itself.\n * Trade: lose production stall visibility in packaged builds in exchange\n * for not amplifying stalls for users. Dev sessions and CLI daemon keep\n * profiling enabled by default. Override with `SHIPYARD_STALL_PROFILER_ENABLED=1`.\n *\n * `env.SHIPYARD_STALL_PROFILER_ENABLED` is `true` when the env var is\n * absent (the Zod transform treats missing/empty as truthy). We detect\n * \"explicitly set\" vs \"defaulted\" by reading `process.env` directly here\n * — the schema transform already validated it, so this read is safe.\n */\n const explicitlySet =\n process.env.SHIPYARD_STALL_PROFILER_ENABLED !== undefined &&\n process.env.SHIPYARD_STALL_PROFILER_ENABLED !== '';\n\n const enabled = explicitlySet\n ? env.SHIPYARD_STALL_PROFILER_ENABLED\n : env.SHIPYARD_ARTIFACT !== 'electron';\n\n return {\n enabled,\n thresholdMs: env.SHIPYARD_STALL_PROFILER_THRESHOLD_MS,\n captureMs: env.SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS,\n rateLimitMs: env.SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  validateEnv
4
- } from "./chunk-Z37T5W6S.js";
4
+ } from "./chunk-P2HZDIN7.js";
5
5
  import {
6
6
  external_exports
7
7
  } from "./chunk-CNR7O5YH.js";
@@ -18,8 +18,8 @@ function getDaemonVersion() {
18
18
  return cached;
19
19
  }
20
20
  function readDaemonVersion() {
21
- if ("3.11.0".length > 0) {
22
- return "3.11.0";
21
+ if ("3.11.1".length > 0) {
22
+ return "3.11.1";
23
23
  }
24
24
  try {
25
25
  const here = dirname(fileURLToPath(import.meta.url));
@@ -48,4 +48,4 @@ function readDaemonVersion() {
48
48
  export {
49
49
  getDaemonVersion
50
50
  };
51
- //# sourceMappingURL=chunk-ZRJTZLRF.js.map
51
+ //# sourceMappingURL=chunk-QKJNVVQ3.js.map
@@ -233,7 +233,7 @@ async function detectSkills(environments, lastKnown, mirrorEnabled = false) {
233
233
  try {
234
234
  return await detectSkillsInner(environments, mirrorEnabled);
235
235
  } catch (err) {
236
- const { logger } = await import("./logger-2F3CBS3V.js");
236
+ const { logger } = await import("./logger-AN7EUK2B.js");
237
237
  if (lastKnown && lastKnown.length > 0) {
238
238
  logger.warn(
239
239
  { err, lastKnownCount: lastKnown.length },
@@ -406,4 +406,4 @@ export {
406
406
  detectSkills,
407
407
  _resolveBucketForTesting
408
408
  };
409
- //# sourceMappingURL=chunk-2EQOL57Z.js.map
409
+ //# sourceMappingURL=chunk-TFRYQDDG.js.map
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-X3MULCV5.js";
9
9
  import {
10
10
  logger
11
- } from "./chunk-QJP7JCIS.js";
11
+ } from "./chunk-LRNGLC4V.js";
12
12
  import {
13
13
  external_exports
14
14
  } from "./chunk-CNR7O5YH.js";
@@ -24,6 +24,7 @@ var PRESENT_TOOL = "present";
24
24
  var VISUALIZE_READ_ME_QUALIFIED = `mcp__${HARNESS_SERVER_NAME}__${VISUALIZE_READ_ME_TOOL}`;
25
25
  var VISUALIZE_TOOL_QUALIFIED = `mcp__${HARNESS_SERVER_NAME}__${VISUALIZE_TOOL}`;
26
26
  var PRESENT_TOOL_QUALIFIED = `mcp__${HARNESS_SERVER_NAME}__${PRESENT_TOOL}`;
27
+ var MERMAID_SECURITY_LEVEL = "loose";
27
28
  var COMMENT_ADD_TOOL = "add_comment";
28
29
  var COMMENT_LIST_TOOL = "list_comments";
29
30
  var COMMENT_READ_THREAD_TOOL = "read_comment_thread";
@@ -17921,6 +17922,12 @@ var CompactionStateSchema = external_exports.discriminatedUnion("kind", [
17921
17922
  external_exports.object({
17922
17923
  kind: external_exports.literal("compacting"),
17923
17924
  startedAt: external_exports.number(),
17925
+ /**
17926
+ * Last runtime/subprocess activity observed while compaction was in
17927
+ * flight. Optional for persisted states written before the liveness
17928
+ * watchdog existed; transition code falls back to `startedAt`.
17929
+ */
17930
+ lastActivityAt: external_exports.number().optional(),
17924
17931
  trigger: triggerSchema
17925
17932
  }),
17926
17933
  external_exports.object({
@@ -17937,11 +17944,12 @@ var CompactionStateSchema = external_exports.discriminatedUnion("kind", [
17937
17944
  trigger: triggerSchema,
17938
17945
  /**
17939
17946
  * Discriminates an ordinary failure (provider error text, subprocess
17940
- * sdk_error) from a watchdog timeout where the runtime went silent
17941
- * mid-compact. Persisted so the browser banner can render copy that
17942
- * reflects the actual stuck-vs-failed cause after daemon restart.
17947
+ * sdk_error) from a liveness watchdog failure where the runtime produced
17948
+ * no activity while compaction was in flight. Persisted so the browser
17949
+ * banner can render copy that reflects the actual cause after daemon
17950
+ * restart. `timeout` is kept for persisted pre-liveness states.
17943
17951
  */
17944
- reason: external_exports.enum(["error", "timeout"]).optional(),
17952
+ reason: external_exports.enum(["error", "timeout", "runtime_silent"]).optional(),
17945
17953
  /**
17946
17954
  * The original `compacting.startedAt` carried forward across the
17947
17955
  * `compacting → failed` transition. Without it, a late `compact_complete`
@@ -17959,7 +17967,8 @@ var CompactionStateSchema = external_exports.discriminatedUnion("kind", [
17959
17967
  ]);
17960
17968
  var PRE_WARN_DURATION_MS = 5e3;
17961
17969
  var POST_FLASH_DURATION_MS = 2e3;
17962
- var COMPACT_TIMEOUT_MS = 3e5;
17970
+ var COMPACTION_SILENCE_TIMEOUT_MS = 3e5;
17971
+ var COMPACTION_ACTIVITY_RESET_MIN_INTERVAL_MS = 3e4;
17963
17972
  function transition(state, event) {
17964
17973
  switch (state.kind) {
17965
17974
  case "idle":
@@ -17995,12 +18004,21 @@ function transitionIdle(event) {
17995
18004
  }
17996
18005
  case "user_compact": {
17997
18006
  return {
17998
- newState: { kind: "compacting", startedAt: event.now, trigger: "manual" },
18007
+ newState: {
18008
+ kind: "compacting",
18009
+ startedAt: event.now,
18010
+ lastActivityAt: event.now,
18011
+ trigger: "manual"
18012
+ },
17999
18013
  effects: [
18000
18014
  { kind: "RunPreCompactHook", trigger: "manual" },
18001
18015
  { kind: "BroadcastStatus", status: "compacting" },
18002
18016
  { kind: "CallProfileForceCompact", instructions: event.instructions },
18003
- { kind: "SetTimer", timerKind: "compact-timeout", durationMs: COMPACT_TIMEOUT_MS },
18017
+ {
18018
+ kind: "SetTimer",
18019
+ timerKind: "compact-silence",
18020
+ durationMs: COMPACTION_SILENCE_TIMEOUT_MS
18021
+ },
18004
18022
  {
18005
18023
  kind: "EmitTelemetry",
18006
18024
  event: {
@@ -18014,11 +18032,20 @@ function transitionIdle(event) {
18014
18032
  }
18015
18033
  case "compact_started": {
18016
18034
  return {
18017
- newState: { kind: "compacting", startedAt: event.now, trigger: "auto" },
18035
+ newState: {
18036
+ kind: "compacting",
18037
+ startedAt: event.now,
18038
+ lastActivityAt: event.now,
18039
+ trigger: "auto"
18040
+ },
18018
18041
  effects: [
18019
18042
  { kind: "RunPreCompactHook", trigger: "auto" },
18020
18043
  { kind: "BroadcastStatus", status: "compacting" },
18021
- { kind: "SetTimer", timerKind: "compact-timeout", durationMs: COMPACT_TIMEOUT_MS },
18044
+ {
18045
+ kind: "SetTimer",
18046
+ timerKind: "compact-silence",
18047
+ durationMs: COMPACTION_SILENCE_TIMEOUT_MS
18048
+ },
18022
18049
  {
18023
18050
  kind: "EmitTelemetry",
18024
18051
  event: {
@@ -18068,13 +18095,14 @@ function transitionIdle(event) {
18068
18095
  case "pre_warn_timer_elapsed":
18069
18096
  case "pre_warn_cancelled":
18070
18097
  case "compact_failed":
18098
+ case "compact_activity":
18071
18099
  case "subprocess_died":
18072
18100
  case "orchestrator_restart_complete":
18073
18101
  case "flash_timer_elapsed":
18074
18102
  case "user_dismiss_failure":
18075
18103
  case "dismiss_compaction_failure":
18076
18104
  case "user_message_after_failure":
18077
- case "compact_timeout":
18105
+ case "compact_silence_timeout":
18078
18106
  return { newState: state_idle, effects: [] };
18079
18107
  default:
18080
18108
  return assertNever2(event);
@@ -18092,7 +18120,12 @@ function transitionPreWarn(state, event) {
18092
18120
  case "user_compact": {
18093
18121
  const trigger = event.kind === "user_compact" ? "manual" : "auto";
18094
18122
  return {
18095
- newState: { kind: "compacting", startedAt: event.now, trigger },
18123
+ newState: {
18124
+ kind: "compacting",
18125
+ startedAt: event.now,
18126
+ lastActivityAt: event.now,
18127
+ trigger
18128
+ },
18096
18129
  effects: [
18097
18130
  { kind: "ClearTimer", timerKind: "pre-warn" },
18098
18131
  { kind: "RunPreCompactHook", trigger },
@@ -18101,7 +18134,11 @@ function transitionPreWarn(state, event) {
18101
18134
  kind: "CallProfileForceCompact",
18102
18135
  instructions: event.kind === "user_compact" ? event.instructions : void 0
18103
18136
  },
18104
- { kind: "SetTimer", timerKind: "compact-timeout", durationMs: COMPACT_TIMEOUT_MS },
18137
+ {
18138
+ kind: "SetTimer",
18139
+ timerKind: "compact-silence",
18140
+ durationMs: COMPACTION_SILENCE_TIMEOUT_MS
18141
+ },
18105
18142
  {
18106
18143
  kind: "EmitTelemetry",
18107
18144
  event: {
@@ -18117,13 +18154,14 @@ function transitionPreWarn(state, event) {
18117
18154
  case "compact_started":
18118
18155
  case "compact_complete":
18119
18156
  case "compact_failed":
18157
+ case "compact_activity":
18120
18158
  case "subprocess_died":
18121
18159
  case "orchestrator_restart_complete":
18122
18160
  case "flash_timer_elapsed":
18123
18161
  case "user_dismiss_failure":
18124
18162
  case "dismiss_compaction_failure":
18125
18163
  case "user_message_after_failure":
18126
- case "compact_timeout":
18164
+ case "compact_silence_timeout":
18127
18165
  return { newState: state, effects: [] };
18128
18166
  default:
18129
18167
  return assertNever2(event);
@@ -18133,8 +18171,31 @@ function transitionCompacting(state, event) {
18133
18171
  switch (event.kind) {
18134
18172
  case "compact_started": {
18135
18173
  return {
18136
- newState: state,
18137
- effects: [{ kind: "BroadcastStatus", status: "compacting" }]
18174
+ newState: { ...state, lastActivityAt: event.now },
18175
+ effects: [
18176
+ { kind: "BroadcastStatus", status: "compacting" },
18177
+ {
18178
+ kind: "SetTimer",
18179
+ timerKind: "compact-silence",
18180
+ durationMs: COMPACTION_SILENCE_TIMEOUT_MS
18181
+ }
18182
+ ]
18183
+ };
18184
+ }
18185
+ case "compact_activity": {
18186
+ const lastActivityAt = state.lastActivityAt ?? state.startedAt;
18187
+ if (event.now - lastActivityAt < COMPACTION_ACTIVITY_RESET_MIN_INTERVAL_MS) {
18188
+ return { newState: state, effects: [] };
18189
+ }
18190
+ return {
18191
+ newState: { ...state, lastActivityAt: event.now },
18192
+ effects: [
18193
+ {
18194
+ kind: "SetTimer",
18195
+ timerKind: "compact-silence",
18196
+ durationMs: COMPACTION_SILENCE_TIMEOUT_MS
18197
+ }
18198
+ ]
18138
18199
  };
18139
18200
  }
18140
18201
  case "compact_complete": {
@@ -18148,7 +18209,7 @@ function transitionCompacting(state, event) {
18148
18209
  trigger
18149
18210
  },
18150
18211
  effects: [
18151
- { kind: "ClearTimer", timerKind: "compact-timeout" },
18212
+ { kind: "ClearTimer", timerKind: "compact-silence" },
18152
18213
  {
18153
18214
  kind: "EmitBoundaryBlock",
18154
18215
  preTokens: event.preTokens,
@@ -18191,7 +18252,7 @@ function transitionCompacting(state, event) {
18191
18252
  startedAt: state.startedAt
18192
18253
  },
18193
18254
  effects: [
18194
- { kind: "ClearTimer", timerKind: "compact-timeout" },
18255
+ { kind: "ClearTimer", timerKind: "compact-silence" },
18195
18256
  /*
18196
18257
  * G-1: a hard/terminal error (subprocess died, real provider error)
18197
18258
  * — no recovery is expected, so write the boundary CARD with a
@@ -18225,28 +18286,30 @@ function transitionCompacting(state, event) {
18225
18286
  ]
18226
18287
  };
18227
18288
  }
18228
- case "compact_timeout": {
18229
- const timeoutErrorMessage = "Compaction timed out \u2014 the runtime stopped responding.";
18289
+ case "compact_silence_timeout": {
18290
+ const lastActivityAt = state.lastActivityAt ?? state.startedAt;
18291
+ const silenceMs = Math.max(0, event.now - lastActivityAt);
18292
+ const runtimeSilentMessage = "Compaction stalled \u2014 the runtime produced no activity and may be unresponsive.";
18230
18293
  return {
18231
18294
  newState: {
18232
18295
  kind: "failed",
18233
18296
  failedAt: event.now,
18234
- error: timeoutErrorMessage,
18297
+ error: runtimeSilentMessage,
18235
18298
  trigger: state.trigger,
18236
- reason: "timeout",
18299
+ reason: "runtime_silent",
18237
18300
  startedAt: state.startedAt
18238
18301
  },
18239
18302
  effects: [
18240
- { kind: "ClearTimer", timerKind: "compact-timeout" },
18303
+ { kind: "ClearTimer", timerKind: "compact-silence" },
18241
18304
  /*
18242
- * FIX 1: a watchdog timeout is NOT terminal — the runtime
18305
+ * A silence-watchdog failure is NOT terminal — the runtime
18243
18306
  * (Codex/Cursor slow case) MAY still land a late `compact_complete`.
18244
18307
  * Do NOT write a boundary CARD here: keep `#pendingBoundaryBlock`
18245
18308
  * so a late `compact_complete` (G-6: `failed → post-flash`) can
18246
- * write the `completed` card once. The durable failed BANNER built
18247
- * in the web wave is what tells the user it timed out; the card is
18248
- * reserved for a real outcome. `ClearPendingStats` clears only the
18249
- * post-compact STATS slots and leaves the boundary block intact.
18309
+ * write the `completed` card once. The durable failed BANNER is what
18310
+ * tells the user the runtime went silent; the card is reserved for a
18311
+ * real outcome. `ClearPendingStats` clears only the post-compact
18312
+ * STATS slots and leaves the boundary block intact.
18250
18313
  */
18251
18314
  { kind: "ClearPendingStats" },
18252
18315
  { kind: "EmitBanner", variant: "failed" },
@@ -18258,8 +18321,9 @@ function transitionCompacting(state, event) {
18258
18321
  timestamp: event.now,
18259
18322
  trigger: state.trigger,
18260
18323
  durationMs: event.now - state.startedAt,
18261
- reason: "timeout",
18262
- error: timeoutErrorMessage
18324
+ reason: "runtime_silent",
18325
+ error: runtimeSilentMessage,
18326
+ silenceMs
18263
18327
  }
18264
18328
  }
18265
18329
  ]
@@ -18269,7 +18333,7 @@ function transitionCompacting(state, event) {
18269
18333
  return {
18270
18334
  newState: { kind: "recovering", recoveryStartedAt: event.now },
18271
18335
  effects: [
18272
- { kind: "ClearTimer", timerKind: "compact-timeout" },
18336
+ { kind: "ClearTimer", timerKind: "compact-silence" },
18273
18337
  {
18274
18338
  kind: "EmitTelemetry",
18275
18339
  event: {
@@ -18311,12 +18375,13 @@ function transitionPostFlash(state, event) {
18311
18375
  case "compact_started":
18312
18376
  case "compact_complete":
18313
18377
  case "compact_failed":
18378
+ case "compact_activity":
18314
18379
  case "subprocess_died":
18315
18380
  case "orchestrator_restart_complete":
18316
18381
  case "user_dismiss_failure":
18317
18382
  case "dismiss_compaction_failure":
18318
18383
  case "user_message_after_failure":
18319
- case "compact_timeout":
18384
+ case "compact_silence_timeout":
18320
18385
  return { newState: state, effects: [] };
18321
18386
  default:
18322
18387
  return assertNever2(event);
@@ -18364,7 +18429,7 @@ function transitionFailed(state, event) {
18364
18429
  trigger
18365
18430
  },
18366
18431
  effects: [
18367
- { kind: "ClearTimer", timerKind: "compact-timeout" },
18432
+ { kind: "ClearTimer", timerKind: "compact-silence" },
18368
18433
  {
18369
18434
  kind: "EmitBoundaryBlock",
18370
18435
  preTokens: event.preTokens,
@@ -18402,9 +18467,10 @@ function transitionFailed(state, event) {
18402
18467
  case "pre_warn_cancelled":
18403
18468
  case "compact_started":
18404
18469
  case "compact_failed":
18470
+ case "compact_activity":
18405
18471
  case "subprocess_died":
18406
18472
  case "flash_timer_elapsed":
18407
- case "compact_timeout":
18473
+ case "compact_silence_timeout":
18408
18474
  return { newState: state, effects: [] };
18409
18475
  default:
18410
18476
  return assertNever2(event);
@@ -18425,12 +18491,13 @@ function transitionRecovering(state, event) {
18425
18491
  case "compact_started":
18426
18492
  case "compact_complete":
18427
18493
  case "compact_failed":
18494
+ case "compact_activity":
18428
18495
  case "subprocess_died":
18429
18496
  case "flash_timer_elapsed":
18430
18497
  case "user_dismiss_failure":
18431
18498
  case "dismiss_compaction_failure":
18432
18499
  case "user_message_after_failure":
18433
- case "compact_timeout":
18500
+ case "compact_silence_timeout":
18434
18501
  return { newState: state, effects: [] };
18435
18502
  default:
18436
18503
  return assertNever2(event);
@@ -25364,6 +25431,15 @@ function controlChannelOutboxChannel(side, sourceId) {
25364
25431
  return side === "daemon" ? `daemon-control-${sourceId}` : `control-${sourceId}`;
25365
25432
  }
25366
25433
 
25434
+ // ../../packages/loro-schema/src/transport/at-least-once/health.ts
25435
+ function shouldEmitHealthSummary(lastEmitMs, now, intervalMs) {
25436
+ return now - lastEmitMs >= intervalMs;
25437
+ }
25438
+ function buildAloHealthPayload(counters) {
25439
+ if (counters.resendsInWindow === 0 && counters.retryStateSize === 0) return null;
25440
+ return { event: "alo_health", ...counters };
25441
+ }
25442
+
25367
25443
  // ../../packages/loro-schema/src/transport/at-least-once/outbox.ts
25368
25444
  var InMemoryOutbox = class {
25369
25445
  #entries = [];
@@ -25423,6 +25499,7 @@ var MAX_DELIVER = 8;
25423
25499
  var DEFAULT_RESEND_TICK_MS = 250;
25424
25500
  var DEFAULT_MAX_RESENDS_PER_TICK = 32;
25425
25501
  var DEFAULT_ORPHAN_THRESHOLD_MS = 5 * 6e4;
25502
+ var DEFAULT_HEALTH_SUMMARY_INTERVAL_MS = 3e4;
25426
25503
  function nextAttemptAtMs(attempt, nowMs, backoffMs = RESEND_BACKOFF_MS) {
25427
25504
  if (backoffMs.length === 0) return nowMs;
25428
25505
  const idx = Math.min(Math.max(attempt - 1, 0), backoffMs.length - 1);
@@ -25753,6 +25830,11 @@ var AtLeastOnceShell = class {
25753
25830
  * is reaped after `orphanThresholdMs` rather than waiting forever.
25754
25831
  */
25755
25832
  #lastAckAtMs;
25833
+ /** Resend count since the last `alo_health` emission; reset each interval. */
25834
+ #resendsInWindow = 0;
25835
+ /** When the last `alo_health` summary was emitted. Seeded at construction so the first summary fires after one full interval, not immediately. */
25836
+ #lastHealthSummaryAtMs;
25837
+ #healthSummaryIntervalMs;
25756
25838
  #tickIntervalMs;
25757
25839
  #backoffMs;
25758
25840
  #maxDeliver;
@@ -25769,9 +25851,11 @@ var AtLeastOnceShell = class {
25769
25851
  this.#maxDeliver = deps.maxDeliver ?? MAX_DELIVER;
25770
25852
  this.#orphanThresholdMs = deps.orphanThresholdMs ?? DEFAULT_ORPHAN_THRESHOLD_MS;
25771
25853
  this.#maxResendsPerTick = deps.maxResendsPerTick ?? DEFAULT_MAX_RESENDS_PER_TICK;
25854
+ this.#healthSummaryIntervalMs = deps.healthSummaryIntervalMs ?? DEFAULT_HEALTH_SUMMARY_INTERVAL_MS;
25772
25855
  this.#setInterval = deps.setInterval ?? ((fn, ms) => setInterval(fn, ms));
25773
25856
  this.#clearInterval = deps.clearInterval ?? ((handle) => clearInterval(handle));
25774
25857
  this.#lastAckAtMs = this.#now();
25858
+ this.#lastHealthSummaryAtMs = this.#now();
25775
25859
  deps.log?.({ event: "alo_shell_created", streamId: deps.streamId });
25776
25860
  }
25777
25861
  /**
@@ -25840,6 +25924,10 @@ var AtLeastOnceShell = class {
25840
25924
  }
25841
25925
  async setConnected(connected) {
25842
25926
  if (this.#disposed) return;
25927
+ const wasConnected = this.#sendState.connected;
25928
+ if (connected && !wasConnected) {
25929
+ this.#retryState.clear();
25930
+ }
25843
25931
  const sendStep = nextSendState(this.#sendState, {
25844
25932
  kind: "connection_state_change",
25845
25933
  connected
@@ -26260,10 +26348,38 @@ var AtLeastOnceShell = class {
26260
26348
  for (const re of capped) {
26261
26349
  this.#processResend(re, now);
26262
26350
  }
26351
+ this.#emitHealthSummary(now);
26263
26352
  } finally {
26264
26353
  this.#tickInFlight = false;
26265
26354
  }
26266
26355
  }
26356
+ /**
26357
+ * Emit a periodic `alo_health` warn log if the summary interval has elapsed.
26358
+ * Delegates to `shouldEmitHealthSummary`/`buildAloHealthPayload` in `health.ts`.
26359
+ * `#disposed` guard: skip log callbacks after external `dispose()` during a
26360
+ * `#processDeadLetter` await. Extracted from `#tick` for complexity budget.
26361
+ */
26362
+ #emitHealthSummary(now) {
26363
+ if (this.#disposed) return;
26364
+ if (!shouldEmitHealthSummary(this.#lastHealthSummaryAtMs, now, this.#healthSummaryIntervalMs))
26365
+ return;
26366
+ let oldestMessageAgeMs = 0;
26367
+ for (const re of this.#retryState.values()) {
26368
+ const age = now - re.entry.ts;
26369
+ if (age > oldestMessageAgeMs) oldestMessageAgeMs = age;
26370
+ }
26371
+ const payload = buildAloHealthPayload({
26372
+ streamId: this.#deps.streamId,
26373
+ resendsInWindow: this.#resendsInWindow,
26374
+ retryStateSize: this.#retryState.size,
26375
+ dropsInWindow: this.#dropsCounter.count(now),
26376
+ oldestMessageAgeMs,
26377
+ msSinceLastAck: now - this.#lastAckAtMs
26378
+ });
26379
+ if (payload !== null) this.#deps.log?.(payload);
26380
+ this.#resendsInWindow = 0;
26381
+ this.#lastHealthSummaryAtMs = now;
26382
+ }
26267
26383
  /**
26268
26384
  * Pure orphan predicate. Reaps when retry state has accumulated and the
26269
26385
  * idle timer has elapsed; an empty retry state means there is nothing
@@ -26298,6 +26414,14 @@ var AtLeastOnceShell = class {
26298
26414
  msgId: re.entry.msgId,
26299
26415
  attempts: re.attempt
26300
26416
  });
26417
+ this.#deps.log?.({
26418
+ event: "alo_resend_exhausted",
26419
+ level: "error",
26420
+ streamId: this.#deps.streamId,
26421
+ seqNo: re.entry.seqNo,
26422
+ msgId: re.entry.msgId,
26423
+ attempts: re.attempt
26424
+ });
26301
26425
  if (this.#deps.onDeadLetter) {
26302
26426
  try {
26303
26427
  this.#deps.onDeadLetter(re.entry, re.attempt);
@@ -26349,8 +26473,10 @@ var AtLeastOnceShell = class {
26349
26473
  re.consecutiveDrainPendingCount = 0;
26350
26474
  re.attempt = nextAttempt;
26351
26475
  re.nextAttemptAtMs = nextAttemptAtMs(re.attempt, now, this.#backoffMs);
26476
+ this.#resendsInWindow += 1;
26352
26477
  this.#deps.log?.({
26353
26478
  event: "alo_resend",
26479
+ level: "debug",
26354
26480
  streamId: this.#deps.streamId,
26355
26481
  seqNo: re.entry.seqNo,
26356
26482
  msgId: re.entry.msgId,
@@ -27755,6 +27881,7 @@ export {
27755
27881
  VISUALIZE_READ_ME_TOOL,
27756
27882
  VISUALIZE_TOOL,
27757
27883
  PRESENT_TOOL,
27884
+ MERMAID_SECURITY_LEVEL,
27758
27885
  COMMENT_ADD_TOOL,
27759
27886
  COMMENT_LIST_TOOL,
27760
27887
  COMMENT_READ_THREAD_TOOL,
@@ -27816,7 +27943,7 @@ export {
27816
27943
  ScheduleRecordSchema,
27817
27944
  SCHEDULE_STORE_VERSION,
27818
27945
  migrateScheduleStore,
27819
- COMPACT_TIMEOUT_MS,
27946
+ COMPACTION_SILENCE_TIMEOUT_MS,
27820
27947
  transition,
27821
27948
  taskStatuses,
27822
27949
  structuredTaskStatuses,
@@ -27930,4 +28057,4 @@ export {
27930
28057
  toRecord,
27931
28058
  buildCursorUserPrompt
27932
28059
  };
27933
- //# sourceMappingURL=chunk-YXPPZQBJ.js.map
28060
+ //# sourceMappingURL=chunk-VL5RUCRF.js.map