oh-my-opencode 5.0.0-beta.30 → 5.0.0-beta.31

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 (134) hide show
  1. package/dist/cli/index.js +75 -26
  2. package/dist/cli-node/index.js +75 -26
  3. package/dist/config/schema/background-task.d.ts +1 -0
  4. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  5. package/dist/features/background-agent/constants.d.ts +0 -7
  6. package/dist/features/background-agent/manager.d.ts +2 -1
  7. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  8. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  9. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  10. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  11. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  12. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  13. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  14. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  15. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  16. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  17. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  18. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  19. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  20. package/dist/hooks/keyword-detector/hook.d.ts +7 -0
  21. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  22. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  23. package/dist/index.js +439 -218
  24. package/dist/oh-my-opencode.schema.json +5 -0
  25. package/dist/plugin-dispose.d.ts +3 -0
  26. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  27. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  28. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  29. package/dist/tools/delegate-task/types.d.ts +1 -0
  30. package/dist/tui.js +56 -3
  31. package/package.json +15 -15
  32. package/packages/git-bash-mcp/dist/cli.js +1 -0
  33. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
  34. package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
  35. package/packages/lsp-core/src/lsp/constants.ts +5 -0
  36. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  37. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  38. package/packages/lsp-core/src/lsp/manager.ts +106 -13
  39. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
  40. package/packages/lsp-core/src/mcp.ts +3 -0
  41. package/packages/lsp-daemon/dist/cli.js +114 -18
  42. package/packages/lsp-daemon/dist/client.js +114 -18
  43. package/packages/lsp-daemon/dist/index.js +114 -18
  44. package/packages/lsp-daemon/dist/proxy.js +3 -0
  45. package/packages/lsp-tools-mcp/dist/cli.js +114 -18
  46. package/packages/lsp-tools-mcp/dist/lsp/manager.js +93 -12
  47. package/packages/lsp-tools-mcp/dist/mcp.js +114 -18
  48. package/packages/lsp-tools-mcp/dist/tools.js +113 -18
  49. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  50. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  52. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +31 -2
  53. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +31 -2
  54. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
  56. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
  57. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  58. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  60. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  62. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  64. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -18
  65. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  66. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  67. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  68. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  72. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  73. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  74. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  75. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  76. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +1 -0
  78. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -0
  79. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +100 -54
  80. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  81. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +9 -6
  82. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  83. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +7 -10
  84. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +3 -2
  85. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +15 -0
  86. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +50 -0
  87. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  88. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  89. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +2 -0
  90. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +16 -5
  91. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +11 -14
  92. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +3 -2
  93. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +73 -0
  94. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +44 -1
  95. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +25 -0
  96. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
  97. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +45 -2
  98. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +20 -0
  99. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  100. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  116. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  117. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  118. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  119. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  120. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  121. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  122. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  123. package/packages/omo-codex/plugin/package-lock.json +13 -13
  124. package/packages/omo-codex/plugin/package.json +1 -1
  125. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  126. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  127. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  128. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  129. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  130. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  131. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  132. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  133. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  134. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
@@ -253,6 +253,16 @@ class LspProcessSpawnError extends Error {
253
253
  this.name = "LspProcessSpawnError";
254
254
  }
255
255
  }
256
+
257
+ class LspClientRespawnBudgetExceededError extends Error {
258
+ constructor(serverId, root, retryLimit) {
259
+ super(`LSP server ${serverId} at ${root} failed to stay alive; respawn budget exhausted ` + `(${retryLimit} consecutive dead generations). Retrying after the cooldown may succeed.`);
260
+ this.serverId = serverId;
261
+ this.root = root;
262
+ this.retryLimit = retryLimit;
263
+ this.name = "LspClientRespawnBudgetExceededError";
264
+ }
265
+ }
256
266
  function isLspDeadConnectionError(err) {
257
267
  return err instanceof LspConnectionClosedError || err instanceof LspProcessExitedError;
258
268
  }
@@ -286,6 +296,8 @@ var MAX_RESIDENT_CLIENTS = 6;
286
296
  var REAPER_INTERVAL_MS = 60000;
287
297
  var STOP_HARD_KILL_TIMEOUT_MS = 5000;
288
298
  var STOP_SIGKILL_GRACE_MS = 1000;
299
+ var CLIENT_RESPAWN_RETRY_LIMIT = 2;
300
+ var CLIENT_RESPAWN_COOLDOWN_MS = 60000;
289
301
 
290
302
  // ../lsp-core/src/lsp/json-rpc-connection.ts
291
303
  var HEADER_SEPARATOR = `\r
@@ -2959,6 +2971,8 @@ function awaitWithSignal(promise, signal) {
2959
2971
  class LspManager {
2960
2972
  constructor(options = {}) {
2961
2973
  this.clients = new Map;
2974
+ this.pendingStops = new Map;
2975
+ this.respawnBudgets = new Map;
2962
2976
  this.reaperHandle = null;
2963
2977
  this.signalDisposer = null;
2964
2978
  this.disposed = false;
@@ -2984,17 +2998,52 @@ class LspManager {
2984
2998
  getKey(root, serverId) {
2985
2999
  return `${root}::${serverId}`;
2986
3000
  }
3001
+ tombstoneStop(key, client) {
3002
+ const stop = stopClientBestEffort(client);
3003
+ this.pendingStops.set(key, stop);
3004
+ stop.finally(() => {
3005
+ if (this.pendingStops.get(key) === stop) {
3006
+ this.pendingStops.delete(key);
3007
+ }
3008
+ });
3009
+ return stop;
3010
+ }
3011
+ recordDeadGeneration(key) {
3012
+ let budget = this.respawnBudgets.get(key);
3013
+ if (budget === undefined) {
3014
+ budget = { deadGenerations: 0, exhaustedAt: null };
3015
+ this.respawnBudgets.set(key, budget);
3016
+ }
3017
+ budget.deadGenerations += 1;
3018
+ }
3019
+ markHealthyGeneration(key) {
3020
+ this.respawnBudgets.delete(key);
3021
+ }
3022
+ ensureRespawnAllowed(key, root, serverId) {
3023
+ const budget = this.respawnBudgets.get(key);
3024
+ if (budget === undefined || budget.deadGenerations < CLIENT_RESPAWN_RETRY_LIMIT)
3025
+ return;
3026
+ if (budget.exhaustedAt === null) {
3027
+ budget.exhaustedAt = this.now();
3028
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
3029
+ }
3030
+ if (this.now() - budget.exhaustedAt < CLIENT_RESPAWN_COOLDOWN_MS) {
3031
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
3032
+ }
3033
+ budget.deadGenerations = 0;
3034
+ budget.exhaustedAt = null;
3035
+ }
2987
3036
  reapStale() {
2988
3037
  const t = this.now();
2989
3038
  for (const [key, managed] of this.clients) {
2990
3039
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
2991
- stopClientBestEffort(managed.client);
2992
3040
  this.clients.delete(key);
3041
+ this.tombstoneStop(key, managed.client);
2993
3042
  continue;
2994
3043
  }
2995
3044
  if (!managed.isInitializing && managed.refCount === 0 && managed.pendingWaiters === 0 && t - managed.lastUsedAt > this.idleTimeoutMs) {
2996
- stopClientBestEffort(managed.client);
2997
3045
  this.clients.delete(key);
3046
+ this.tombstoneStop(key, managed.client);
2998
3047
  }
2999
3048
  }
3000
3049
  }
@@ -3004,7 +3053,7 @@ class LspManager {
3004
3053
  if (!victim)
3005
3054
  return;
3006
3055
  this.clients.delete(victim.key);
3007
- stopClientBestEffort(victim.managed.client);
3056
+ this.tombstoneStop(victim.key, victim.managed.client);
3008
3057
  }
3009
3058
  }
3010
3059
  leastRecentlyUsedIdleClient() {
@@ -3021,7 +3070,7 @@ class LspManager {
3021
3070
  async tryDeleteIfOrphaned(key, managed) {
3022
3071
  if (managed.refCount === 0 && managed.pendingWaiters === 0 && !managed.isInitializing && this.clients.get(key) === managed) {
3023
3072
  this.clients.delete(key);
3024
- await stopClientBestEffort(managed.client);
3073
+ await this.tombstoneStop(key, managed.client);
3025
3074
  }
3026
3075
  }
3027
3076
  async getClient(root, server, signal) {
@@ -3030,12 +3079,22 @@ class LspManager {
3030
3079
  }
3031
3080
  signal?.throwIfAborted();
3032
3081
  const key = this.getKey(root, server.id);
3082
+ for (;; ) {
3083
+ const pendingStop = this.pendingStops.get(key);
3084
+ if (pendingStop === undefined)
3085
+ break;
3086
+ await awaitWithSignal(pendingStop, signal);
3087
+ signal?.throwIfAborted();
3088
+ }
3089
+ if (this.disposed) {
3090
+ throw new Error("LspManager has been disposed");
3091
+ }
3033
3092
  let managed = this.clients.get(key);
3034
3093
  if (managed) {
3035
3094
  const t = this.now();
3036
3095
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
3037
- await stopClientBestEffort(managed.client);
3038
3096
  this.clients.delete(key);
3097
+ await this.tombstoneStop(key, managed.client);
3039
3098
  managed = undefined;
3040
3099
  }
3041
3100
  }
@@ -3056,15 +3115,20 @@ class LspManager {
3056
3115
  signal.throwIfAborted();
3057
3116
  }
3058
3117
  if (!managed.client.isAlive()) {
3059
- await stopClientBestEffort(managed.client);
3060
- this.clients.delete(key);
3118
+ this.recordDeadGeneration(key);
3119
+ if (this.clients.get(key) === managed) {
3120
+ this.clients.delete(key);
3121
+ }
3122
+ await this.tombstoneStop(key, managed.client);
3061
3123
  return this.getClient(root, server, signal);
3062
3124
  }
3125
+ this.markHealthyGeneration(key);
3063
3126
  managed.refCount++;
3064
3127
  managed.lastUsedAt = this.now();
3065
3128
  return managed.client;
3066
3129
  }
3067
3130
  this.evictForAdmission();
3131
+ this.ensureRespawnAllowed(key, root, server.id);
3068
3132
  const client = this.clientFactory(root, server);
3069
3133
  const initStartedAt = this.now();
3070
3134
  const initPromise = (async () => {
@@ -3088,7 +3152,7 @@ class LspManager {
3088
3152
  if (this.clients.get(key) === newManaged) {
3089
3153
  this.clients.delete(key);
3090
3154
  }
3091
- await stopClientBestEffort(client);
3155
+ await this.tombstoneStop(key, client);
3092
3156
  throw err;
3093
3157
  }
3094
3158
  newManaged.pendingWaiters--;
@@ -3099,6 +3163,15 @@ class LspManager {
3099
3163
  await this.tryDeleteIfOrphaned(key, newManaged);
3100
3164
  signal.throwIfAborted();
3101
3165
  }
3166
+ if (!client.isAlive()) {
3167
+ this.recordDeadGeneration(key);
3168
+ if (this.clients.get(key) === newManaged) {
3169
+ this.clients.delete(key);
3170
+ }
3171
+ await this.tombstoneStop(key, client);
3172
+ return this.getClient(root, server, signal);
3173
+ }
3174
+ this.markHealthyGeneration(key);
3102
3175
  newManaged.refCount++;
3103
3176
  newManaged.lastUsedAt = this.now();
3104
3177
  return client;
@@ -3119,15 +3192,20 @@ class LspManager {
3119
3192
  if (client && managed.client !== client)
3120
3193
  return;
3121
3194
  this.clients.delete(key);
3122
- stopClientBestEffort(managed.client);
3195
+ this.tombstoneStop(key, managed.client);
3123
3196
  }
3124
3197
  warmupClient(root, server) {
3125
3198
  if (this.disposed)
3126
3199
  return;
3127
3200
  const key = this.getKey(root, server.id);
3128
- if (this.clients.has(key))
3201
+ if (this.clients.has(key) || this.pendingStops.has(key))
3129
3202
  return;
3130
3203
  this.evictForAdmission();
3204
+ try {
3205
+ this.ensureRespawnAllowed(key, root, server.id);
3206
+ } catch {
3207
+ return;
3208
+ }
3131
3209
  const client = this.clientFactory(root, server);
3132
3210
  const initStartedAt = this.now();
3133
3211
  const initPromise = (async () => {
@@ -3153,7 +3231,7 @@ class LspManager {
3153
3231
  if (this.clients.get(key) === managed) {
3154
3232
  this.clients.delete(key);
3155
3233
  }
3156
- stopClientBestEffort(client);
3234
+ this.tombstoneStop(key, client);
3157
3235
  });
3158
3236
  }
3159
3237
  isServerInitializing(root, serverId) {
@@ -3197,8 +3275,11 @@ class LspManager {
3197
3275
  for (const managed of this.clients.values()) {
3198
3276
  stopPromises.push(stopClientBestEffort(managed.client));
3199
3277
  }
3278
+ const tombstonedStops = [...this.pendingStops.values()];
3200
3279
  this.clients.clear();
3201
- await Promise.allSettled(stopPromises);
3280
+ this.respawnBudgets.clear();
3281
+ await Promise.allSettled([...stopPromises, ...tombstonedStops]);
3282
+ this.pendingStops.clear();
3202
3283
  }
3203
3284
  }
3204
3285
  var _defaultInstance = null;
@@ -3221,7 +3302,16 @@ import { existsSync as existsSync6, realpathSync as realpathSync4 } from "node:f
3221
3302
  import { dirname as dirname5, join as join3 } from "node:path";
3222
3303
 
3223
3304
  // ../lsp-core/src/lsp/workspace-markers.ts
3224
- var WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
3305
+ var GIT_WORKSPACE_MARKER = ".git";
3306
+ var PROJECT_WORKSPACE_MARKERS = [
3307
+ "package.json",
3308
+ "pyproject.toml",
3309
+ "Cargo.toml",
3310
+ "go.mod",
3311
+ "pom.xml",
3312
+ "build.gradle"
3313
+ ];
3314
+ var WORKSPACE_MARKERS = [GIT_WORKSPACE_MARKER, ...PROJECT_WORKSPACE_MARKERS];
3225
3315
 
3226
3316
  // ../lsp-core/src/lsp/outside-context-workspace.ts
3227
3317
  function findWorkspaceRootOutsideContext(directory) {
@@ -3910,20 +4000,25 @@ function findWorkspaceRoot(filePath) {
3910
4000
  if (!isPathInside(cwd, abs))
3911
4001
  return findWorkspaceRootOutsideContext(dir);
3912
4002
  const fallbackRoot = nearestExistingDirectoryInsideContext(dir, cwd) ?? cwd;
4003
+ let nearestPackageRoot;
3913
4004
  while (isPathInside(cwd, dir)) {
3914
4005
  const canonicalDir = existingDirectoryInsideContext(dir, cwd);
3915
4006
  if (canonicalDir !== undefined) {
3916
- for (const marker of WORKSPACE_MARKERS) {
3917
- if (existsSync10(join5(dir, marker))) {
3918
- return canonicalDir;
3919
- }
4007
+ if (existsSync10(join5(dir, GIT_WORKSPACE_MARKER))) {
4008
+ return canonicalDir;
4009
+ }
4010
+ if (nearestPackageRoot === undefined && hasProjectWorkspaceMarker(dir)) {
4011
+ nearestPackageRoot = canonicalDir;
3920
4012
  }
3921
4013
  }
3922
4014
  if (dir === cwd)
3923
4015
  break;
3924
4016
  dir = dirname8(dir);
3925
4017
  }
3926
- return fallbackRoot;
4018
+ return nearestPackageRoot ?? fallbackRoot;
4019
+ }
4020
+ function hasProjectWorkspaceMarker(directory) {
4021
+ return PROJECT_WORKSPACE_MARKERS.some((marker) => existsSync10(join5(directory, marker)));
3927
4022
  }
3928
4023
  function resolveReadablePathInsideContext(filePath) {
3929
4024
  const cwd = contextCwd();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "One Codex plugin namespace for Yeongyu's local Codex components.",
5
5
  "author": {
6
6
  "name": "Yeongyu Kim",
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.30) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.0.0-beta.31) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-bootstrap",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "Codex SessionStart bootstrap component that provisions LazyCodex runtime dependencies from a detached worker.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -5580,6 +5580,14 @@ var OmoMemorySyncSchema = object({
5580
5580
  var OmoMemorySearchSchema = object({
5581
5581
  enabled: boolean2().default(true)
5582
5582
  }).strict();
5583
+ var OmoMemoryRecallSchema = object({
5584
+ enabled: boolean2().default(true),
5585
+ max_items: number2().int().min(1).max(5).default(2),
5586
+ budget_tokens: number2().int().positive().default(600),
5587
+ excerpt_chars: number2().int().positive().default(200),
5588
+ min_score: number2().optional(),
5589
+ exclude: array(string2()).default([])
5590
+ }).strict();
5583
5591
  var OmoMemoryNudgeSchema = object({
5584
5592
  enabled: boolean2().default(true),
5585
5593
  every_user_turns: number2().int().min(1).default(10)
@@ -5626,6 +5634,14 @@ var OmoMemorySyncLayerSchema = object({
5626
5634
  var OmoMemorySearchLayerSchema = object({
5627
5635
  enabled: boolean2().optional()
5628
5636
  }).strict();
5637
+ var OmoMemoryRecallLayerSchema = object({
5638
+ enabled: boolean2().optional(),
5639
+ max_items: number2().int().min(1).max(5).optional(),
5640
+ budget_tokens: number2().int().positive().optional(),
5641
+ excerpt_chars: number2().int().positive().optional(),
5642
+ min_score: number2().optional(),
5643
+ exclude: array(string2()).optional()
5644
+ }).strict();
5629
5645
  var OmoMemoryNudgeLayerSchema = object({
5630
5646
  enabled: boolean2().optional(),
5631
5647
  every_user_turns: number2().int().min(1).optional()
@@ -5665,6 +5681,7 @@ var OmoMemoryAgentOverridesSchema = object({
5665
5681
  write_notice: OmoMemoryWriteNoticeLayerSchema.optional(),
5666
5682
  sync: OmoMemorySyncLayerSchema.optional(),
5667
5683
  search: OmoMemorySearchLayerSchema.optional(),
5684
+ recall: OmoMemoryRecallLayerSchema.optional(),
5668
5685
  compile_warn_tokens: number2().int().positive().optional()
5669
5686
  }).strict();
5670
5687
  var OmoMemorySettingsSchema = object({
@@ -5694,6 +5711,13 @@ var OmoMemorySettingsSchema = object({
5694
5711
  write_notice: OmoMemoryWriteNoticeSchema.default({ enabled: true }),
5695
5712
  sync: OmoMemorySyncSchema.default({ enabled: true }),
5696
5713
  search: OmoMemorySearchSchema.default({ enabled: true }),
5714
+ recall: OmoMemoryRecallSchema.default({
5715
+ enabled: true,
5716
+ max_items: 2,
5717
+ budget_tokens: 600,
5718
+ excerpt_chars: 200,
5719
+ exclude: []
5720
+ }),
5697
5721
  compile_warn_tokens: number2().int().positive().default(30000),
5698
5722
  agents: record(string2(), OmoMemoryAgentOverridesSchema).default({})
5699
5723
  }).strict();
@@ -5710,6 +5734,7 @@ var OmoMemorySettingsLayerSchema = object({
5710
5734
  write_notice: OmoMemoryWriteNoticeLayerSchema.optional(),
5711
5735
  sync: OmoMemorySyncLayerSchema.optional(),
5712
5736
  search: OmoMemorySearchLayerSchema.optional(),
5737
+ recall: OmoMemoryRecallLayerSchema.optional(),
5713
5738
  compile_warn_tokens: number2().int().positive().optional(),
5714
5739
  agents: record(string2(), OmoMemoryAgentOverridesSchema).optional()
5715
5740
  }).strict();
@@ -5732,6 +5757,7 @@ var OmoModelCatalogLayerSchema = record(string2(), OmoModelCatalogEntryLayerSche
5732
5757
 
5733
5758
  // ../../omo-config-core/src/schema/task.ts
5734
5759
  import { availableParallelism } from "node:os";
5760
+ var DEFAULT_RESIDENCY_MAX_CHILDREN = 16;
5735
5761
  var ResidencyMaxChildrenInputSchema = union([number2().int().nonnegative(), literal("unlimited")]);
5736
5762
  var OmoTaskWaitSchema = object({
5737
5763
  min_ms: number2().int().positive().default(5000),
@@ -5821,7 +5847,7 @@ function resolveOmoTaskSettings(input, resolveParallelism = availableParallelism
5821
5847
  const record2 = record(string2(), unknown()).parse(input);
5822
5848
  return OmoTaskSettingsSchema.parse({
5823
5849
  ...record2,
5824
- residency_max_children: record2["residency_max_children"] ?? Math.max(8, resolveParallelism() * 3),
5850
+ residency_max_children: record2["residency_max_children"] ?? Math.min(DEFAULT_RESIDENCY_MAX_CHILDREN, Math.max(8, resolveParallelism() * 2)),
5825
5851
  global_concurrency: record2["global_concurrency"] ?? Math.max(8, resolveParallelism() * 2)
5826
5852
  });
5827
5853
  }
@@ -11608,6 +11634,7 @@ async function runJsonRpcStdioServer(config2) {
11608
11634
  const idleTimer = createIdleTimer(idleTimeoutMs, log, () => {
11609
11635
  isClosed = true;
11610
11636
  config2.onIdleTimeout?.();
11637
+ config2.input.destroy();
11611
11638
  });
11612
11639
  const watchdog = createParentWatchdog(config2.parentWatchdog, (parentPid, pollIntervalMs) => {
11613
11640
  isClosed = true;
@@ -12001,7 +12028,9 @@ async function runUnavailableCodegraphMcpServer(options) {
12001
12028
  },
12002
12029
  input: options.input,
12003
12030
  output: options.output,
12004
- parentWatchdog: options.parentWatchdog ?? {}
12031
+ idleTimeoutMs: 0,
12032
+ parentWatchdog: options.parentWatchdog ?? {},
12033
+ ...options.lifecycleLog === undefined ? {} : { log: options.lifecycleLog }
12005
12034
  });
12006
12035
  }
12007
12036
  async function handleUnavailableCodegraphMcpRequest(input, options) {
@@ -5846,6 +5846,14 @@ var OmoMemorySyncSchema = object({
5846
5846
  var OmoMemorySearchSchema = object({
5847
5847
  enabled: boolean2().default(true)
5848
5848
  }).strict();
5849
+ var OmoMemoryRecallSchema = object({
5850
+ enabled: boolean2().default(true),
5851
+ max_items: number2().int().min(1).max(5).default(2),
5852
+ budget_tokens: number2().int().positive().default(600),
5853
+ excerpt_chars: number2().int().positive().default(200),
5854
+ min_score: number2().optional(),
5855
+ exclude: array(string2()).default([])
5856
+ }).strict();
5849
5857
  var OmoMemoryNudgeSchema = object({
5850
5858
  enabled: boolean2().default(true),
5851
5859
  every_user_turns: number2().int().min(1).default(10)
@@ -5892,6 +5900,14 @@ var OmoMemorySyncLayerSchema = object({
5892
5900
  var OmoMemorySearchLayerSchema = object({
5893
5901
  enabled: boolean2().optional()
5894
5902
  }).strict();
5903
+ var OmoMemoryRecallLayerSchema = object({
5904
+ enabled: boolean2().optional(),
5905
+ max_items: number2().int().min(1).max(5).optional(),
5906
+ budget_tokens: number2().int().positive().optional(),
5907
+ excerpt_chars: number2().int().positive().optional(),
5908
+ min_score: number2().optional(),
5909
+ exclude: array(string2()).optional()
5910
+ }).strict();
5895
5911
  var OmoMemoryNudgeLayerSchema = object({
5896
5912
  enabled: boolean2().optional(),
5897
5913
  every_user_turns: number2().int().min(1).optional()
@@ -5931,6 +5947,7 @@ var OmoMemoryAgentOverridesSchema = object({
5931
5947
  write_notice: OmoMemoryWriteNoticeLayerSchema.optional(),
5932
5948
  sync: OmoMemorySyncLayerSchema.optional(),
5933
5949
  search: OmoMemorySearchLayerSchema.optional(),
5950
+ recall: OmoMemoryRecallLayerSchema.optional(),
5934
5951
  compile_warn_tokens: number2().int().positive().optional()
5935
5952
  }).strict();
5936
5953
  var OmoMemorySettingsSchema = object({
@@ -5960,6 +5977,13 @@ var OmoMemorySettingsSchema = object({
5960
5977
  write_notice: OmoMemoryWriteNoticeSchema.default({ enabled: true }),
5961
5978
  sync: OmoMemorySyncSchema.default({ enabled: true }),
5962
5979
  search: OmoMemorySearchSchema.default({ enabled: true }),
5980
+ recall: OmoMemoryRecallSchema.default({
5981
+ enabled: true,
5982
+ max_items: 2,
5983
+ budget_tokens: 600,
5984
+ excerpt_chars: 200,
5985
+ exclude: []
5986
+ }),
5963
5987
  compile_warn_tokens: number2().int().positive().default(30000),
5964
5988
  agents: record(string2(), OmoMemoryAgentOverridesSchema).default({})
5965
5989
  }).strict();
@@ -5976,6 +6000,7 @@ var OmoMemorySettingsLayerSchema = object({
5976
6000
  write_notice: OmoMemoryWriteNoticeLayerSchema.optional(),
5977
6001
  sync: OmoMemorySyncLayerSchema.optional(),
5978
6002
  search: OmoMemorySearchLayerSchema.optional(),
6003
+ recall: OmoMemoryRecallLayerSchema.optional(),
5979
6004
  compile_warn_tokens: number2().int().positive().optional(),
5980
6005
  agents: record(string2(), OmoMemoryAgentOverridesSchema).optional()
5981
6006
  }).strict();
@@ -5998,6 +6023,7 @@ var OmoModelCatalogLayerSchema = record(string2(), OmoModelCatalogEntryLayerSche
5998
6023
 
5999
6024
  // ../../../../omo-config-core/src/schema/task.ts
6000
6025
  import { availableParallelism } from "node:os";
6026
+ var DEFAULT_RESIDENCY_MAX_CHILDREN = 16;
6001
6027
  var ResidencyMaxChildrenInputSchema = union([number2().int().nonnegative(), literal("unlimited")]);
6002
6028
  var OmoTaskWaitSchema = object({
6003
6029
  min_ms: number2().int().positive().default(5000),
@@ -6087,7 +6113,7 @@ function resolveOmoTaskSettings(input, resolveParallelism = availableParallelism
6087
6113
  const record2 = record(string2(), unknown()).parse(input);
6088
6114
  return OmoTaskSettingsSchema.parse({
6089
6115
  ...record2,
6090
- residency_max_children: record2["residency_max_children"] ?? Math.max(8, resolveParallelism() * 3),
6116
+ residency_max_children: record2["residency_max_children"] ?? Math.min(DEFAULT_RESIDENCY_MAX_CHILDREN, Math.max(8, resolveParallelism() * 2)),
6091
6117
  global_concurrency: record2["global_concurrency"] ?? Math.max(8, resolveParallelism() * 2)
6092
6118
  });
6093
6119
  }
@@ -9274,6 +9300,7 @@ async function runJsonRpcStdioServer(config2) {
9274
9300
  const idleTimer = createIdleTimer(idleTimeoutMs, log, () => {
9275
9301
  isClosed = true;
9276
9302
  config2.onIdleTimeout?.();
9303
+ config2.input.destroy();
9277
9304
  });
9278
9305
  const watchdog = createParentWatchdog(config2.parentWatchdog, (parentPid, pollIntervalMs) => {
9279
9306
  isClosed = true;
@@ -9685,7 +9712,9 @@ async function runUnavailableCodegraphMcpServer(options) {
9685
9712
  },
9686
9713
  input: options.input,
9687
9714
  output: options.output,
9688
- parentWatchdog: options.parentWatchdog ?? {}
9715
+ idleTimeoutMs: 0,
9716
+ parentWatchdog: options.parentWatchdog ?? {},
9717
+ ...options.lifecycleLog === undefined ? {} : { log: options.lifecycleLog }
9689
9718
  });
9690
9719
  }
9691
9720
  async function handleUnavailableCodegraphMcpRequest(input, options) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-codegraph",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "Codex plugin MCP wrapper for CodeGraph.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -7,6 +7,7 @@ import {
7
7
  runJsonRpcStdioServer,
8
8
  successResponse,
9
9
  type JsonRpcResponse,
10
+ type McpLifecycleLog,
10
11
  type ParentWatchdogConfig,
11
12
  } from "../../../../../mcp-stdio-core/src/index.ts";
12
13
 
@@ -18,6 +19,9 @@ export interface UnavailableCodegraphMcpOptions {
18
19
  // Test seam: production callers leave this unset so the watchdog uses its
19
20
  // defaults (parentPid = process.ppid, 30s poll).
20
21
  readonly parentWatchdog?: ParentWatchdogConfig;
22
+ // Test seam: surfaces the mcp-stdio-core lifecycle log so tests can pin the
23
+ // resolved idle timeout without waiting a real timeout out.
24
+ readonly lifecycleLog?: McpLifecycleLog;
21
25
  }
22
26
 
23
27
  interface UnavailableCodegraphMcpHandlerOptions {
@@ -36,7 +40,15 @@ export async function runUnavailableCodegraphMcpServer(
36
40
  },
37
41
  input: options.input,
38
42
  output: options.output,
43
+ // Idle stays disabled: the codex host never respawns an exited stdio
44
+ // MCP server (codex-rs auto-reconnects only its own codex_apps server),
45
+ // and since #6548 the default idle timer actually destroys stdin — so an
46
+ // inherited 10-minute default would silently kill this stub mid-session
47
+ // and turn later codegraph tool calls into dead-connection errors.
48
+ idleTimeoutMs: 0,
39
49
  parentWatchdog: options.parentWatchdog ?? {},
50
+ // Conditional spread keeps this assignable under exactOptionalPropertyTypes.
51
+ ...(options.lifecycleLog === undefined ? {} : { log: options.lifecycleLog }),
40
52
  });
41
53
  }
42
54
 
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { PassThrough } from "node:stream";
3
+
4
+ import { runUnavailableCodegraphMcpServer } from "../src/mcp-unavailable.ts";
5
+
6
+ describe("unavailable codegraph MCP idle teardown", () => {
7
+ it("#given the codex host (no respawn for exited stdio servers) #when the unavailable stub starts #then the idle timeout is disabled", async () => {
8
+ // given — codex never respawns a user-configured stdio MCP server
9
+ // (codex-rs reconnects only its own codex_apps server), so if the stub
10
+ // inherited the mcp-stdio-core default idle timeout it would destroy its
11
+ // own stdin after 10 idle minutes and every later codegraph tool call in
12
+ // a long-lived session would fail on a dead connection instead of
13
+ // answering with the skip reason.
14
+ const stdin = new PassThrough();
15
+ const stdout = new PassThrough();
16
+ stdout.resume();
17
+ const lifecycle: Array<{ readonly event: string; readonly data?: unknown }> = [];
18
+
19
+ try {
20
+ // when
21
+ const served = runUnavailableCodegraphMcpServer({
22
+ input: stdin,
23
+ output: stdout,
24
+ reason: "CodeGraph MCP skipped: test reason",
25
+ serverVersion: "0.0.0-test",
26
+ lifecycleLog: (event, data) => {
27
+ lifecycle.push({ event, data });
28
+ },
29
+ });
30
+ const outcome = await Promise.race([
31
+ served.then(() => "settled" as const),
32
+ Bun.sleep(500).then(() => "parked" as const),
33
+ ]);
34
+
35
+ // then — the server must park on the held-open pipe (no idle timer
36
+ // tore it down) and must have started with the timeout disabled.
37
+ expect(outcome).toBe("parked");
38
+ expect(lifecycle).toContainEqual({
39
+ event: "stdio_started",
40
+ data: expect.objectContaining({ idle_timeout_ms: 0 }),
41
+ });
42
+ } finally {
43
+ stdin.destroy();
44
+ stdout.destroy();
45
+ }
46
+ });
47
+ });
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.30) Checking Comments"
11
+ "statusMessage": "(OmO 5.0.0-beta.31) Checking Comments"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-comment-checker",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "Codex plugin that runs comment-checker after edit-like PostToolUse hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.0.0-beta.30) Recommending Git Bash MCP"
11
+ "statusMessage": "(OmO 5.0.0-beta.31) Recommending Git Bash MCP"
12
12
  }
13
13
  ]
14
14
  }
@@ -20,7 +20,7 @@
20
20
  "type": "command",
21
21
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
22
22
  "timeout": 5,
23
- "statusMessage": "(OmO 5.0.0-beta.30) Resetting Git Bash MCP Reminder"
23
+ "statusMessage": "(OmO 5.0.0-beta.31) Resetting Git Bash MCP Reminder"
24
24
  }
25
25
  ]
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-git-bash-hook",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.0.0-beta.30) Verifying LazyCodex Executor Evidence"
11
+ "statusMessage": "(OmO 5.0.0-beta.31) Verifying LazyCodex Executor Evidence"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
3
- "version": "5.0.0-beta.30",
3
+ "version": "5.0.0-beta.31",
4
4
  "description": "Codex SubagentStop evidence verifier for LazyCodex executor completions.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "5.0.0-beta.30",
4
- "inputDigest": "sha256:e51c462bed771b67810446ab27ee35a6009800cb9e3869f41234c80ca6818caa",
3
+ "version": "5.0.0-beta.31",
4
+ "inputDigest": "sha256:858b6dbf85523225a57282289db35b4a82d608613a4a5930ce1d85a86b4296b4",
5
5
  "outputs": [
6
6
  {
7
7
  "path": "cli.d.ts",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "cli.js",
12
- "sha256": "5e3645a0299f523876c758e645e70f7a122a46da6ec7d3994473ec19f9048430"
12
+ "sha256": "d20c513dadc159abf9ecbc16198769b6b872a77472ac64a78d137e6c73c5debb"
13
13
  },
14
14
  {
15
15
  "path": "codex-hook-cli.d.ts",