oh-my-opencode 5.0.0-beta.67 → 5.0.0-beta.68

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 (74) hide show
  1. package/dist/cli/index.js +14 -14
  2. package/dist/cli-node/index.js +14 -14
  3. package/dist/index.js +13 -13
  4. package/package.json +13 -13
  5. package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +9 -1
  6. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +43 -8
  7. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +29 -0
  8. package/packages/lsp-core/src/lsp/workspace-document-state.ts +5 -0
  9. package/packages/lsp-daemon/dist/cli.js +2 -0
  10. package/packages/lsp-daemon/dist/client.js +2 -0
  11. package/packages/lsp-daemon/dist/index.js +2 -0
  12. package/packages/lsp-tools-mcp/dist/cli.js +2 -0
  13. package/packages/lsp-tools-mcp/dist/lsp/manager.js +2 -0
  14. package/packages/lsp-tools-mcp/dist/mcp.js +2 -0
  15. package/packages/lsp-tools-mcp/dist/tools.js +2 -0
  16. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  17. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  18. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  20. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  22. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  26. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +2 -0
  27. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  28. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  30. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  31. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  32. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  33. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +1 -0
  40. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +26 -2
  41. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +6 -1
  42. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +2 -1
  43. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +2 -1
  44. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +30 -1
  45. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  46. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +6 -1
  48. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +9 -1
  49. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +30 -2
  50. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-legacy-projection.test.ts +228 -0
  51. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  72. package/packages/omo-codex/plugin/package-lock.json +12 -12
  73. package/packages/omo-codex/plugin/package.json +1 -1
  74. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
package/dist/cli/index.js CHANGED
@@ -64,7 +64,7 @@ var package_default;
64
64
  var init_package = __esm(() => {
65
65
  package_default = {
66
66
  name: "oh-my-opencode",
67
- version: "5.0.0-beta.67",
67
+ version: "5.0.0-beta.68",
68
68
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
69
69
  main: "./dist/index.js",
70
70
  types: "dist/index.d.ts",
@@ -285,18 +285,18 @@ var init_package = __esm(() => {
285
285
  typescript: "^7.0.2"
286
286
  },
287
287
  optionalDependencies: {
288
- "oh-my-opencode-darwin-arm64": "5.0.0-beta.67",
289
- "oh-my-opencode-darwin-x64": "5.0.0-beta.67",
290
- "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.67",
291
- "oh-my-opencode-linux-arm64": "5.0.0-beta.67",
292
- "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.67",
293
- "oh-my-opencode-linux-x64": "5.0.0-beta.67",
294
- "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.67",
295
- "oh-my-opencode-linux-x64-musl": "5.0.0-beta.67",
296
- "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.67",
297
- "oh-my-opencode-windows-arm64": "5.0.0-beta.67",
298
- "oh-my-opencode-windows-x64": "5.0.0-beta.67",
299
- "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.67"
288
+ "oh-my-opencode-darwin-arm64": "5.0.0-beta.68",
289
+ "oh-my-opencode-darwin-x64": "5.0.0-beta.68",
290
+ "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.68",
291
+ "oh-my-opencode-linux-arm64": "5.0.0-beta.68",
292
+ "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.68",
293
+ "oh-my-opencode-linux-x64": "5.0.0-beta.68",
294
+ "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.68",
295
+ "oh-my-opencode-linux-x64-musl": "5.0.0-beta.68",
296
+ "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.68",
297
+ "oh-my-opencode-windows-arm64": "5.0.0-beta.68",
298
+ "oh-my-opencode-windows-x64": "5.0.0-beta.68",
299
+ "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.68"
300
300
  },
301
301
  overrides: {
302
302
  "@earendil-works/pi-agent-core": "0.84.2",
@@ -84547,7 +84547,7 @@ var package_default2;
84547
84547
  var init_package2 = __esm(() => {
84548
84548
  package_default2 = {
84549
84549
  name: "@oh-my-opencode/omo-codex",
84550
- version: "5.0.0-beta.67",
84550
+ version: "5.0.0-beta.68",
84551
84551
  type: "module",
84552
84552
  private: true,
84553
84553
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "oh-my-opencode",
68
- version: "5.0.0-beta.67",
68
+ version: "5.0.0-beta.68",
69
69
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
70
70
  main: "./dist/index.js",
71
71
  types: "dist/index.d.ts",
@@ -286,18 +286,18 @@ var init_package = __esm(() => {
286
286
  typescript: "^7.0.2"
287
287
  },
288
288
  optionalDependencies: {
289
- "oh-my-opencode-darwin-arm64": "5.0.0-beta.67",
290
- "oh-my-opencode-darwin-x64": "5.0.0-beta.67",
291
- "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.67",
292
- "oh-my-opencode-linux-arm64": "5.0.0-beta.67",
293
- "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.67",
294
- "oh-my-opencode-linux-x64": "5.0.0-beta.67",
295
- "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.67",
296
- "oh-my-opencode-linux-x64-musl": "5.0.0-beta.67",
297
- "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.67",
298
- "oh-my-opencode-windows-arm64": "5.0.0-beta.67",
299
- "oh-my-opencode-windows-x64": "5.0.0-beta.67",
300
- "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.67"
289
+ "oh-my-opencode-darwin-arm64": "5.0.0-beta.68",
290
+ "oh-my-opencode-darwin-x64": "5.0.0-beta.68",
291
+ "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.68",
292
+ "oh-my-opencode-linux-arm64": "5.0.0-beta.68",
293
+ "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.68",
294
+ "oh-my-opencode-linux-x64": "5.0.0-beta.68",
295
+ "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.68",
296
+ "oh-my-opencode-linux-x64-musl": "5.0.0-beta.68",
297
+ "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.68",
298
+ "oh-my-opencode-windows-arm64": "5.0.0-beta.68",
299
+ "oh-my-opencode-windows-x64": "5.0.0-beta.68",
300
+ "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.68"
301
301
  },
302
302
  overrides: {
303
303
  "@earendil-works/pi-agent-core": "0.84.2",
@@ -84626,7 +84626,7 @@ var package_default2;
84626
84626
  var init_package2 = __esm(() => {
84627
84627
  package_default2 = {
84628
84628
  name: "@oh-my-opencode/omo-codex",
84629
- version: "5.0.0-beta.67",
84629
+ version: "5.0.0-beta.68",
84630
84630
  type: "module",
84631
84631
  private: true,
84632
84632
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
package/dist/index.js CHANGED
@@ -96515,7 +96515,7 @@ function getCachedVersion(options = {}) {
96515
96515
  // package.json
96516
96516
  var package_default = {
96517
96517
  name: "oh-my-opencode",
96518
- version: "5.0.0-beta.67",
96518
+ version: "5.0.0-beta.68",
96519
96519
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
96520
96520
  main: "./dist/index.js",
96521
96521
  types: "dist/index.d.ts",
@@ -96736,18 +96736,18 @@ var package_default = {
96736
96736
  typescript: "^7.0.2"
96737
96737
  },
96738
96738
  optionalDependencies: {
96739
- "oh-my-opencode-darwin-arm64": "5.0.0-beta.67",
96740
- "oh-my-opencode-darwin-x64": "5.0.0-beta.67",
96741
- "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.67",
96742
- "oh-my-opencode-linux-arm64": "5.0.0-beta.67",
96743
- "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.67",
96744
- "oh-my-opencode-linux-x64": "5.0.0-beta.67",
96745
- "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.67",
96746
- "oh-my-opencode-linux-x64-musl": "5.0.0-beta.67",
96747
- "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.67",
96748
- "oh-my-opencode-windows-arm64": "5.0.0-beta.67",
96749
- "oh-my-opencode-windows-x64": "5.0.0-beta.67",
96750
- "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.67"
96739
+ "oh-my-opencode-darwin-arm64": "5.0.0-beta.68",
96740
+ "oh-my-opencode-darwin-x64": "5.0.0-beta.68",
96741
+ "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.68",
96742
+ "oh-my-opencode-linux-arm64": "5.0.0-beta.68",
96743
+ "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.68",
96744
+ "oh-my-opencode-linux-x64": "5.0.0-beta.68",
96745
+ "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.68",
96746
+ "oh-my-opencode-linux-x64-musl": "5.0.0-beta.68",
96747
+ "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.68",
96748
+ "oh-my-opencode-windows-arm64": "5.0.0-beta.68",
96749
+ "oh-my-opencode-windows-x64": "5.0.0-beta.68",
96750
+ "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.68"
96751
96751
  },
96752
96752
  overrides: {
96753
96753
  "@earendil-works/pi-agent-core": "0.84.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "5.0.0-beta.67",
3
+ "version": "5.0.0-beta.68",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -221,18 +221,18 @@
221
221
  "typescript": "^7.0.2"
222
222
  },
223
223
  "optionalDependencies": {
224
- "oh-my-opencode-darwin-arm64": "5.0.0-beta.67",
225
- "oh-my-opencode-darwin-x64": "5.0.0-beta.67",
226
- "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.67",
227
- "oh-my-opencode-linux-arm64": "5.0.0-beta.67",
228
- "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.67",
229
- "oh-my-opencode-linux-x64": "5.0.0-beta.67",
230
- "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.67",
231
- "oh-my-opencode-linux-x64-musl": "5.0.0-beta.67",
232
- "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.67",
233
- "oh-my-opencode-windows-arm64": "5.0.0-beta.67",
234
- "oh-my-opencode-windows-x64": "5.0.0-beta.67",
235
- "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.67"
224
+ "oh-my-opencode-darwin-arm64": "5.0.0-beta.68",
225
+ "oh-my-opencode-darwin-x64": "5.0.0-beta.68",
226
+ "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.68",
227
+ "oh-my-opencode-linux-arm64": "5.0.0-beta.68",
228
+ "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.68",
229
+ "oh-my-opencode-linux-x64": "5.0.0-beta.68",
230
+ "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.68",
231
+ "oh-my-opencode-linux-x64-musl": "5.0.0-beta.68",
232
+ "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.68",
233
+ "oh-my-opencode-windows-arm64": "5.0.0-beta.68",
234
+ "oh-my-opencode-windows-x64": "5.0.0-beta.68",
235
+ "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.68"
236
236
  },
237
237
  "overrides": {
238
238
  "@earendil-works/pi-agent-core": "0.84.2",
@@ -1,5 +1,7 @@
1
1
  import { afterEach, describe, expect, it } from "bun:test";
2
2
 
3
+ import { ControlledClock } from "./controlled-clock-test-support.js";
4
+
3
5
  import {
4
6
  createWorkspaceEditTestHarness,
5
7
  diagnostic,
@@ -14,6 +16,12 @@ afterEach(async () => {
14
16
 
15
17
  describe("LspClient diagnostics concurrency", () => {
16
18
  it("#given concurrent diagnostics on a cold file and an exact didOpen publish #when pull is not advertised #then one didOpen opens the file and both requests receive the current diagnostics", async () => {
19
+ // The freshness window runs on a controlled clock: this case asserts that the exact-version
20
+ // didOpen publish satisfies BOTH requests, and the publish is what wakes them. On a real clock
21
+ // the 500ms window is a second, competing deadline against a real fixture server over a real
22
+ // pipe, so a starved Windows shard resolved the requests clean before the publish landed and
23
+ // the case failed with [] (#8323). Frozen, only the publish can settle it.
24
+ const clock = new ControlledClock();
17
25
  const context = await harness.makeClient(
18
26
  {
19
27
  publishDiagnostics: [
@@ -25,7 +33,7 @@ describe("LspClient diagnostics concurrency", () => {
25
33
  },
26
34
  ],
27
35
  },
28
- { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5 },
36
+ { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
29
37
  );
30
38
 
31
39
  const [first, second] = await Promise.all([
@@ -18,6 +18,27 @@ afterEach(async () => {
18
18
  await harness.cleanup();
19
19
  });
20
20
 
21
+ /**
22
+ * Drives one `diagnostics()` call whose ONLY exit is the freshness window closing: an advertised
23
+ * pull that comes back "method not found" with no publish behind it.
24
+ *
25
+ * That call arms exactly two timers in order - the pull request's own timeout, then the push
26
+ * fallback wait - and the second can only be armed once the first was cleared by the server's
27
+ * reply. Waiting on the second SCHEDULE (not on a delay value, which both share) is therefore the
28
+ * exact signal that the fallback, and not a request timeout, is what the advance closes. On a real
29
+ * clock the same case resolved from whichever of the two real deadlines a starved Windows shard
30
+ * happened to reach first (#8323).
31
+ */
32
+ async function resolveThroughFreshnessWindow<T>(clock: ControlledClock, start: () => Promise<T>): Promise<T> {
33
+ const scheduledBefore = clock.scheduledDelays.length;
34
+ const pending = start();
35
+ await clock.waitForScheduled(scheduledBefore + 2);
36
+ clock.advanceBy(FRESHNESS_WINDOW_MS);
37
+ return pending;
38
+ }
39
+
40
+ const FRESHNESS_WINDOW_MS = 500;
41
+
21
42
  describe("LspClient diagnostics freshness", () => {
22
43
  it("#given a versionless publish that arrives after the current change #when no newer eligible publish arrives before quiescence #then diagnostics wait for that quiescence window and return the versionless payload", async () => {
23
44
  const clock = new ControlledClock();
@@ -159,6 +180,9 @@ describe("LspClient diagnostics freshness", () => {
159
180
  });
160
181
 
161
182
  it("#given a pull response overtaken by a later local change #when the stale full report returns first #then diagnostics restart within the same request and resolve from the current version", async () => {
183
+ // Both pull reports answer this request, so no freshness deadline may fire: a controlled clock
184
+ // removes the 800ms wall-clock race that a starved shard otherwise wins (#8323).
185
+ const clock = new ControlledClock();
162
186
  const context = await harness.makeClient(
163
187
  {
164
188
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -172,12 +196,12 @@ describe("LspClient diagnostics freshness", () => {
172
196
  },
173
197
  ],
174
198
  },
175
- { diagnosticsFreshnessTimeoutMs: 800, versionlessPublishQuiescenceMs: 5 },
199
+ { diagnosticsFreshnessTimeoutMs: 800, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
176
200
  );
177
201
  await context.client.openFile(context.source);
178
202
 
179
203
  const pending = context.client.diagnostics(context.source);
180
- await waitForEventCount(
204
+ await waitForEventCountBySubscription(
181
205
  context.events,
182
206
  (event) => event.type === "clientRequest" && event.method === "textDocument/diagnostic",
183
207
  1,
@@ -196,6 +220,9 @@ describe("LspClient diagnostics freshness", () => {
196
220
  });
197
221
 
198
222
  it("#given a full pull report followed by an unchanged report for the same version and resultId #when diagnostics repeat without a local change #then the cached full items are reused", async () => {
223
+ // Both requests are answered by the server, so a controlled clock keeps the freshness window
224
+ // from becoming a second deadline the shard can starve (#8323).
225
+ const clock = new ControlledClock();
199
226
  const context = await harness.makeClient(
200
227
  {
201
228
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -208,7 +235,7 @@ describe("LspClient diagnostics freshness", () => {
208
235
  },
209
236
  ],
210
237
  },
211
- { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5 },
238
+ { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
212
239
  );
213
240
  await context.client.openFile(context.source);
214
241
 
@@ -220,6 +247,9 @@ describe("LspClient diagnostics freshness", () => {
220
247
  });
221
248
 
222
249
  it("#given a server that claims pull support but closes instead of answering #when diagnostics run #then the transport failure is not labeled clean", async () => {
250
+ // Deliberately left on the real clock: the client only notices the dead server when the
251
+ // in-flight pull request's own timeout aborts it, so a timer must actually fire here. Making
252
+ // the transport reject pending requests from the child's exit event is a separate change.
223
253
  const context = await harness.makeClient(
224
254
  {
225
255
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -233,6 +263,9 @@ describe("LspClient diagnostics freshness", () => {
233
263
  });
234
264
 
235
265
  it("#given an advertised pull method that is explicitly unsupported and a matching push publish #when diagnostics run #then the client falls back to push diagnostics", async () => {
266
+ // The exact-version publish wakes the push-fallback wait, so no deadline may fire; a controlled
267
+ // clock keeps the 500ms window from winning that race on a starved shard (#8323).
268
+ const clock = new ControlledClock();
236
269
  const context = await harness.makeClient(
237
270
  {
238
271
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -246,7 +279,7 @@ describe("LspClient diagnostics freshness", () => {
246
279
  ],
247
280
  diagnosticResponses: [{ error: { code: -32601, message: "Method not found" } }],
248
281
  },
249
- { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5 },
282
+ { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
250
283
  );
251
284
 
252
285
  const pending = context.client.diagnostics(context.source);
@@ -301,6 +334,7 @@ describe("LspClient diagnostics freshness", () => {
301
334
  });
302
335
 
303
336
  it("#given a pull-supported server that cached diagnostics for an older document version #when the file changes and a later pull is rejected as unsupported without any publish #then the fallback resolves empty instead of returning the stale cached diagnostics", async () => {
337
+ const clock = new ControlledClock();
304
338
  const context = await harness.makeClient(
305
339
  {
306
340
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -309,7 +343,7 @@ describe("LspClient diagnostics freshness", () => {
309
343
  { error: { code: -32601, message: "Method not found" } },
310
344
  ],
311
345
  },
312
- { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5 },
346
+ { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
313
347
  );
314
348
 
315
349
  const first = await context.client.diagnostics(context.source);
@@ -318,12 +352,13 @@ describe("LspClient diagnostics freshness", () => {
318
352
  writeFileSync(context.source, "const changed = 1;\n");
319
353
  await context.client.openFile(context.source);
320
354
 
321
- const second = await context.client.diagnostics(context.source);
355
+ const second = await resolveThroughFreshnessWindow(clock, () => context.client.diagnostics(context.source));
322
356
  expect(second.transientError).toBeUndefined();
323
357
  expect(second.items).toEqual([]);
324
358
  });
325
359
 
326
360
  it("#given a pull-supported server with a current cached pull report #when a later pull is rejected as unsupported without any publish and the document is unchanged #then the fallback resolves with the current cached diagnostics", async () => {
361
+ const clock = new ControlledClock();
327
362
  const context = await harness.makeClient(
328
363
  {
329
364
  capabilities: { diagnosticProvider: { interFileDependencies: false, workspaceDiagnostics: false } },
@@ -332,13 +367,13 @@ describe("LspClient diagnostics freshness", () => {
332
367
  { error: { code: -32601, message: "Method not found" } },
333
368
  ],
334
369
  },
335
- { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5 },
370
+ { diagnosticsFreshnessTimeoutMs: 500, versionlessPublishQuiescenceMs: 5, timerProvider: clock },
336
371
  );
337
372
 
338
373
  const first = await context.client.diagnostics(context.source);
339
374
  expect(first.items).toEqual([diagnostic("cached-full")]);
340
375
 
341
- const second = await context.client.diagnostics(context.source);
376
+ const second = await resolveThroughFreshnessWindow(clock, () => context.client.diagnostics(context.source));
342
377
  expect(second.transientError).toBeUndefined();
343
378
  expect(second.items).toEqual([diagnostic("cached-full")]);
344
379
  });
@@ -11,6 +11,11 @@ interface TimerWaiter {
11
11
  readonly resolve: () => void;
12
12
  }
13
13
 
14
+ interface ScheduleWaiter {
15
+ readonly count: number;
16
+ readonly resolve: () => void;
17
+ }
18
+
14
19
  /**
15
20
  * Deterministic TimerProvider for tests: timers fire only through advanceBy(),
16
21
  * and waitForTimer(delayMs) resolves once a timer with exactly that delay is
@@ -20,6 +25,7 @@ export class ControlledClock implements TimerProvider {
20
25
  private readonly timers: ControlledTimer[] = [];
21
26
  private currentTime = 0;
22
27
  private readonly timerWaiters = new Set<TimerWaiter>();
28
+ private readonly scheduleWaiters = new Set<ScheduleWaiter>();
23
29
  readonly scheduledDelays: number[] = [];
24
30
 
25
31
  readonly now = (): number => this.currentTime;
@@ -33,6 +39,12 @@ export class ControlledClock implements TimerProvider {
33
39
  waiter.resolve();
34
40
  }
35
41
  }
42
+ for (const waiter of this.scheduleWaiters) {
43
+ if (this.scheduledDelays.length >= waiter.count) {
44
+ this.scheduleWaiters.delete(waiter);
45
+ waiter.resolve();
46
+ }
47
+ }
36
48
  return timer as unknown as ReturnType<typeof setTimeout>;
37
49
  };
38
50
  readonly clearTimeout = (handle: ReturnType<typeof setTimeout>): void => {
@@ -49,6 +61,23 @@ export class ControlledClock implements TimerProvider {
49
61
  });
50
62
  }
51
63
 
64
+ /**
65
+ * Resolves once this clock has been asked to schedule `count` timers in total.
66
+ *
67
+ * Timer DELAYS are ambiguous whenever the code under test derives several budgets from the same
68
+ * remaining window (an LSP request timeout and the push-fallback wait are both "whatever is left
69
+ * of the freshness window"). The schedule ORDER is not ambiguous: a step that only runs after an
70
+ * earlier request settled can only arm its timer once that request's own timer was cleared. So a
71
+ * test that must fire the LATER timer waits for the later schedule instead of a delay value, and
72
+ * never races the earlier one.
73
+ */
74
+ waitForScheduled(count: number): Promise<void> {
75
+ if (this.scheduledDelays.length >= count) return Promise.resolve();
76
+ return new Promise((resolve) => {
77
+ this.scheduleWaiters.add({ count, resolve });
78
+ });
79
+ }
80
+
52
81
  advanceBy(delayMs: number): void {
53
82
  this.currentTime += delayMs;
54
83
  for (;;) {
@@ -235,6 +235,11 @@ export class WorkspaceDocumentState {
235
235
  waitForDiagnosticsActivity(snapshot: DiagnosticSnapshot, timeoutMs: number): Promise<void> {
236
236
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
237
237
  if (!state || timeoutMs <= 0) return Promise.resolve();
238
+ // A publish that landed between the caller's snapshot and this registration already IS the
239
+ // activity being waited for, and its notifyWaiters ran before this waiter existed. Without
240
+ // this check that wake-up is lost and the wait can only end at the freshness deadline, which
241
+ // turns the deadline from a fallback into the poll interval the result depends on (#8323).
242
+ if (state.publishGeneration !== snapshot.publishGeneration) return Promise.resolve();
238
243
  return new Promise((resolveActivity) => {
239
244
  let settled = false;
240
245
  const finish = () => {
@@ -1770,6 +1770,8 @@ class WorkspaceDocumentState {
1770
1770
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1771
1771
  if (!state || timeoutMs <= 0)
1772
1772
  return Promise.resolve();
1773
+ if (state.publishGeneration !== snapshot.publishGeneration)
1774
+ return Promise.resolve();
1773
1775
  return new Promise((resolveActivity) => {
1774
1776
  let settled = false;
1775
1777
  const finish = () => {
@@ -2428,6 +2428,8 @@ class WorkspaceDocumentState {
2428
2428
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
2429
2429
  if (!state || timeoutMs <= 0)
2430
2430
  return Promise.resolve();
2431
+ if (state.publishGeneration !== snapshot.publishGeneration)
2432
+ return Promise.resolve();
2431
2433
  return new Promise((resolveActivity) => {
2432
2434
  let settled = false;
2433
2435
  const finish = () => {
@@ -1277,6 +1277,8 @@ class WorkspaceDocumentState {
1277
1277
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1278
1278
  if (!state || timeoutMs <= 0)
1279
1279
  return Promise.resolve();
1280
+ if (state.publishGeneration !== snapshot.publishGeneration)
1281
+ return Promise.resolve();
1280
1282
  return new Promise((resolveActivity) => {
1281
1283
  let settled = false;
1282
1284
  const finish = () => {
@@ -1282,6 +1282,8 @@ class WorkspaceDocumentState {
1282
1282
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1283
1283
  if (!state || timeoutMs <= 0)
1284
1284
  return Promise.resolve();
1285
+ if (state.publishGeneration !== snapshot.publishGeneration)
1286
+ return Promise.resolve();
1285
1287
  return new Promise((resolveActivity) => {
1286
1288
  let settled = false;
1287
1289
  const finish = () => {
@@ -1277,6 +1277,8 @@ class WorkspaceDocumentState {
1277
1277
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1278
1278
  if (!state || timeoutMs <= 0)
1279
1279
  return Promise.resolve();
1280
+ if (state.publishGeneration !== snapshot.publishGeneration)
1281
+ return Promise.resolve();
1280
1282
  return new Promise((resolveActivity) => {
1281
1283
  let settled = false;
1282
1284
  const finish = () => {
@@ -1771,6 +1771,8 @@ class WorkspaceDocumentState {
1771
1771
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1772
1772
  if (!state || timeoutMs <= 0)
1773
1773
  return Promise.resolve();
1774
+ if (state.publishGeneration !== snapshot.publishGeneration)
1775
+ return Promise.resolve();
1774
1776
  return new Promise((resolveActivity) => {
1775
1777
  let settled = false;
1776
1778
  const finish = () => {
@@ -1459,6 +1459,8 @@ class WorkspaceDocumentState {
1459
1459
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1460
1460
  if (!state || timeoutMs <= 0)
1461
1461
  return Promise.resolve();
1462
+ if (state.publishGeneration !== snapshot.publishGeneration)
1463
+ return Promise.resolve();
1462
1464
  return new Promise((resolveActivity) => {
1463
1465
  let settled = false;
1464
1466
  const finish = () => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo",
3
- "version": "5.0.0-beta.67",
3
+ "version": "5.0.0-beta.68",
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.67) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) 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.67",
3
+ "version": "5.0.0-beta.68",
4
4
  "description": "Codex SessionStart bootstrap component that provisions LazyCodex runtime dependencies from a detached worker.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -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.67) Checking Comments"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) 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.67",
3
+ "version": "5.0.0-beta.68",
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.67) Recommending Git Bash MCP"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) 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.67) Resetting Git Bash MCP Reminder"
23
+ "statusMessage": "(OmO 5.0.0-beta.68) 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.67",
3
+ "version": "5.0.0-beta.68",
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.67) Verifying LazyCodex Executor Evidence"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) 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.67",
3
+ "version": "5.0.0-beta.68",
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.67",
4
- "inputDigest": "sha256:76bed86edd43712d2370c010737d11da40f9e35e697b1a80af520967434fdc0c",
3
+ "version": "5.0.0-beta.68",
4
+ "inputDigest": "sha256:e94fa1eb1e1363a3c1c13ccfeec67863452fdbfcb2ed88d4a2b2e2cb8fe6581f",
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": "54d3621c04697495054be69fd6328d7b9044034801b295868b23bd92d344a816"
12
+ "sha256": "b8d0f009e0031d015280244ddd2e5abb8db076ffb324c7e500d29d437a345446"
13
13
  },
14
14
  {
15
15
  "path": "codex-hook-cli.d.ts",
@@ -1920,6 +1920,8 @@ class WorkspaceDocumentState {
1920
1920
  const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
1921
1921
  if (!state || timeoutMs <= 0)
1922
1922
  return Promise.resolve();
1923
+ if (state.publishGeneration !== snapshot.publishGeneration)
1924
+ return Promise.resolve();
1923
1925
  return new Promise((resolveActivity) => {
1924
1926
  let settled = false;
1925
1927
  const finish = () => {
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 60,
11
- "statusMessage": "(OmO 5.0.0-beta.67) Checking LSP Diagnostics"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking LSP Diagnostics"
12
12
  }
13
13
  ]
14
14
  }
@@ -21,7 +21,7 @@
21
21
  "type": "command",
22
22
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
23
23
  "timeout": 5,
24
- "statusMessage": "(OmO 5.0.0-beta.67) Resetting LSP Diagnostics Cache"
24
+ "statusMessage": "(OmO 5.0.0-beta.68) Resetting LSP Diagnostics Cache"
25
25
  }
26
26
  ]
27
27
  }