oh-my-codex 0.21.13 → 0.21.15

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 (141) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +17 -0
  4. package/dist/adapt/herdr/__tests__/wiring.test.js +21 -0
  5. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -1
  6. package/dist/adapt/herdr/wiring.d.ts.map +1 -1
  7. package/dist/adapt/herdr/wiring.js +5 -0
  8. package/dist/adapt/herdr/wiring.js.map +1 -1
  9. package/dist/auth/__tests__/hotswap.test.js +28 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  11. package/dist/auth/hotswap.d.ts +1 -0
  12. package/dist/auth/hotswap.d.ts.map +1 -1
  13. package/dist/auth/hotswap.js +4 -1
  14. package/dist/auth/hotswap.js.map +1 -1
  15. package/dist/cli/__tests__/codex-plugin-layout.test.js +196 -0
  16. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -1
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +14 -1
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/session-timeline.test.d.ts +2 -0
  22. package/dist/cli/__tests__/session-timeline.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/session-timeline.test.js +155 -0
  24. package/dist/cli/__tests__/session-timeline.test.js.map +1 -0
  25. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +1 -0
  26. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  27. package/dist/cli/doctor.d.ts.map +1 -1
  28. package/dist/cli/doctor.js +1 -0
  29. package/dist/cli/doctor.js.map +1 -1
  30. package/dist/cli/index.d.ts +1 -0
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +11 -0
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/session-search.d.ts.map +1 -1
  35. package/dist/cli/session-search.js +7 -0
  36. package/dist/cli/session-search.js.map +1 -1
  37. package/dist/cli/session-timeline.d.ts +9 -0
  38. package/dist/cli/session-timeline.d.ts.map +1 -0
  39. package/dist/cli/session-timeline.js +174 -0
  40. package/dist/cli/session-timeline.js.map +1 -0
  41. package/dist/config/__tests__/codex-hooks.test.js +3 -1
  42. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  43. package/dist/config/__tests__/generator-idempotent.test.js +1 -0
  44. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  45. package/dist/config/codex-hooks.d.ts +1 -1
  46. package/dist/config/codex-hooks.d.ts.map +1 -1
  47. package/dist/config/codex-hooks.js +5 -0
  48. package/dist/config/codex-hooks.js.map +1 -1
  49. package/dist/evals/astra-defaults/__tests__/collector.test.js +51 -23
  50. package/dist/evals/astra-defaults/__tests__/collector.test.js.map +1 -1
  51. package/dist/evals/astra-defaults/collector.d.ts +2 -0
  52. package/dist/evals/astra-defaults/collector.d.ts.map +1 -1
  53. package/dist/evals/astra-defaults/collector.js +19 -8
  54. package/dist/evals/astra-defaults/collector.js.map +1 -1
  55. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +143 -0
  56. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  57. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  58. package/dist/hooks/extensibility/dispatcher.js +120 -28
  59. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  60. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  61. package/dist/hooks/extensibility/runtime.js +1 -0
  62. package/dist/hooks/extensibility/runtime.js.map +1 -1
  63. package/dist/hooks/extensibility/types.d.ts +2 -0
  64. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  65. package/dist/hooks/native/types.d.ts +1 -1
  66. package/dist/hooks/native/types.d.ts.map +1 -1
  67. package/dist/hooks/native/types.js.map +1 -1
  68. package/dist/notifications/__tests__/lifecycle-dedupe.test.js +200 -2
  69. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  70. package/dist/notifications/lifecycle-dedupe.d.ts +10 -0
  71. package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -1
  72. package/dist/notifications/lifecycle-dedupe.js +175 -19
  73. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  74. package/dist/scripts/__tests__/codex-native-hook.test.js +278 -6
  75. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  76. package/dist/scripts/__tests__/native-hook-protocol.test.d.ts +2 -0
  77. package/dist/scripts/__tests__/native-hook-protocol.test.d.ts.map +1 -0
  78. package/dist/scripts/__tests__/native-hook-protocol.test.js +329 -0
  79. package/dist/scripts/__tests__/native-hook-protocol.test.js.map +1 -0
  80. package/dist/scripts/__tests__/smoke-packed-install.test.js +7 -6
  81. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  82. package/dist/scripts/codex-execution-surface.d.ts +1 -1
  83. package/dist/scripts/codex-execution-surface.d.ts.map +1 -1
  84. package/dist/scripts/codex-execution-surface.js.map +1 -1
  85. package/dist/scripts/codex-native-hook.d.ts +1 -1
  86. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  87. package/dist/scripts/codex-native-hook.js +124 -13
  88. package/dist/scripts/codex-native-hook.js.map +1 -1
  89. package/dist/scripts/hook-payload-guard.d.ts +1 -1
  90. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  91. package/dist/scripts/hook-payload-guard.js +1 -0
  92. package/dist/scripts/hook-payload-guard.js.map +1 -1
  93. package/dist/scripts/smoke-packed-install.d.ts +2 -2
  94. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  95. package/dist/scripts/smoke-packed-install.js +12 -1
  96. package/dist/scripts/smoke-packed-install.js.map +1 -1
  97. package/dist/session-history/__tests__/timeline.test.d.ts +2 -0
  98. package/dist/session-history/__tests__/timeline.test.d.ts.map +1 -0
  99. package/dist/session-history/__tests__/timeline.test.js +459 -0
  100. package/dist/session-history/__tests__/timeline.test.js.map +1 -0
  101. package/dist/session-history/export.d.ts +2 -3
  102. package/dist/session-history/export.d.ts.map +1 -1
  103. package/dist/session-history/export.js +5 -66
  104. package/dist/session-history/export.js.map +1 -1
  105. package/dist/session-history/timeline.d.ts +97 -0
  106. package/dist/session-history/timeline.d.ts.map +1 -0
  107. package/dist/session-history/timeline.js +325 -0
  108. package/dist/session-history/timeline.js.map +1 -0
  109. package/dist/session-history/transcript.d.ts +16 -0
  110. package/dist/session-history/transcript.d.ts.map +1 -0
  111. package/dist/session-history/transcript.js +111 -0
  112. package/dist/session-history/transcript.js.map +1 -0
  113. package/dist/subagents/tracker.d.ts +6 -0
  114. package/dist/subagents/tracker.d.ts.map +1 -1
  115. package/dist/subagents/tracker.js +32 -0
  116. package/dist/subagents/tracker.js.map +1 -1
  117. package/dist/team/__tests__/runtime.test.js +12 -3
  118. package/dist/team/__tests__/runtime.test.js.map +1 -1
  119. package/dist/team/__tests__/scaling.test.js +25 -0
  120. package/dist/team/__tests__/scaling.test.js.map +1 -1
  121. package/dist/team/__tests__/tmux-session.test.js +30 -0
  122. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  123. package/dist/team/tmux-session.d.ts.map +1 -1
  124. package/dist/team/tmux-session.js +9 -1
  125. package/dist/team/tmux-session.js.map +1 -1
  126. package/dist/wiki/__tests__/session-hooks.test.js +26 -0
  127. package/dist/wiki/__tests__/session-hooks.test.js.map +1 -1
  128. package/dist/wiki/lifecycle.d.ts.map +1 -1
  129. package/dist/wiki/lifecycle.js +6 -1
  130. package/dist/wiki/lifecycle.js.map +1 -1
  131. package/package.json +1 -1
  132. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  133. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +85 -18
  134. package/plugins/oh-my-codex/hooks/hooks.json +10 -0
  135. package/src/scripts/__tests__/codex-native-hook.test.ts +273 -5
  136. package/src/scripts/__tests__/native-hook-protocol.test.ts +362 -0
  137. package/src/scripts/__tests__/smoke-packed-install.test.ts +7 -6
  138. package/src/scripts/codex-execution-surface.ts +1 -0
  139. package/src/scripts/codex-native-hook.ts +130 -11
  140. package/src/scripts/hook-payload-guard.ts +1 -0
  141. package/src/scripts/smoke-packed-install.ts +12 -1
@@ -154,6 +154,7 @@ export interface HookDispatchResult {
154
154
  export type HookDispatchSummary = HookDispatchResult;
155
155
  export interface HookDispatchOptions {
156
156
  cwd?: string;
157
+ pluginDiscoveryCwd?: string;
157
158
  stateRoot?: string;
158
159
  event?: HookEventEnvelope;
159
160
  env?: NodeJS.ProcessEnv;
@@ -170,6 +171,7 @@ export interface HookValidateOptions {
170
171
  }
171
172
  export interface HookRuntimeDispatchInput {
172
173
  cwd: string;
174
+ pluginDiscoveryCwd?: string;
173
175
  event: HookEventEnvelope;
174
176
  allowTeamWorkerSideEffects?: boolean;
175
177
  sideEffectsEnabled?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hooks/extensibility/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hooks/extensibility/types.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  * notifications only. PreToolUse is advisory-only except authority-decreasing
5
5
  * cancel interception.
6
6
  */
7
- export type CodexHookEventName = "SessionStart" | "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "PreCompact" | "PostCompact" | "Stop";
7
+ export type CodexHookEventName = "SessionStart" | "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "PreCompact" | "PostCompact" | "SubagentStop" | "Stop";
8
8
  export type CodexHookPayload = Record<string, unknown>;
9
9
  export interface NativeHookDispatchOptions {
10
10
  cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hooks/native/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,YAAY,GACZ,aAAa,GACb,MAAM,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,mBAAmB,CAAC,EAAE;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3B,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1D,CAAC;IACF,6BAA6B,CAAC,EAAE,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAC3D,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AAED,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,aAa1C,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hooks/native/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,YAAY,GACZ,aAAa,GACb,cAAc,GACd,MAAM,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,mBAAmB,CAAC,EAAE;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3B,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1D,CAAC;IACF,6BAA6B,CAAC,EAAE,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAC3D,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AAED,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,aAa1C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/hooks/native/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkCH,sFAAsF;AACtF,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IACrD,yBAAyB;IACzB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,cAAc;IACd,kBAAkB;IAClB,mBAAmB;IACnB,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,mBAAmB;CACpB,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/hooks/native/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmCH,sFAAsF;AACtF,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IACrD,yBAAyB;IACzB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,cAAc;IACd,kBAAkB;IAClB,mBAAmB;IACnB,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,mBAAmB;CACpB,CAAC,CAAC"}
@@ -1,9 +1,11 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { spawn } from 'node:child_process';
4
+ import { existsSync } from 'node:fs';
5
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
6
  import { tmpdir } from 'node:os';
5
7
  import { join } from 'node:path';
6
- import { shouldSendLifecycleNotification, recordLifecycleNotificationSent, shouldSendLifecycleHookBroadcast, recordLifecycleHookBroadcastSent, } from '../lifecycle-dedupe.js';
8
+ import { claimLifecycleHookBroadcast, completeLifecycleHookBroadcast, shouldSendLifecycleNotification, recordLifecycleNotificationSent, shouldSendLifecycleHookBroadcast, recordLifecycleHookBroadcastSent, renewLifecycleHookBroadcastClaim, releaseLifecycleHookBroadcast, } from '../lifecycle-dedupe.js';
7
9
  function buildPayload(overrides = {}) {
8
10
  return {
9
11
  event: 'session-end',
@@ -16,7 +18,203 @@ function buildPayload(overrides = {}) {
16
18
  ...overrides,
17
19
  };
18
20
  }
21
+ async function waitForFiles(paths) {
22
+ const deadline = Date.now() + 10_000;
23
+ while (!paths.every(existsSync)) {
24
+ if (Date.now() >= deadline)
25
+ throw new Error(`Timed out waiting for files: ${paths.join(', ')}`);
26
+ await new Promise((resolve) => setTimeout(resolve, 5));
27
+ }
28
+ }
29
+ function waitForChildExit(child) {
30
+ return new Promise((resolve, reject) => {
31
+ child.once('error', reject);
32
+ child.once('close', (code, signal) => {
33
+ if (code === 0)
34
+ resolve();
35
+ else
36
+ reject(new Error(`Child process exited with ${code ?? signal}`));
37
+ });
38
+ });
39
+ }
19
40
  describe('lifecycle notification dedupe', () => {
41
+ it('atomically claims duplicate child-hook deliveries and preserves other children across processes', async () => {
42
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-concurrent-hook-'));
43
+ const sessionId = 'parallel-session';
44
+ const statePath = join(stateDir, 'sessions', sessionId, 'lifecycle-notif-state.json');
45
+ const readyPath = join(stateDir, 'lock-ready');
46
+ const releasePath = join(stateDir, 'lock-release');
47
+ const trackerModuleUrl = new URL('../../subagents/tracker.js', import.meta.url).href;
48
+ const lifecycleModuleUrl = new URL('../lifecycle-dedupe.js', import.meta.url).href;
49
+ const holderSource = `
50
+ import { existsSync, writeFileSync } from 'node:fs';
51
+ import { withCrossProcessFileLockSync } from ${JSON.stringify(trackerModuleUrl)};
52
+ withCrossProcessFileLockSync(${JSON.stringify(statePath)}, () => {
53
+ writeFileSync(${JSON.stringify(readyPath)}, 'ready');
54
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
55
+ const deadline = Date.now() + 10_000;
56
+ while (!existsSync(${JSON.stringify(releasePath)}) && Date.now() < deadline) {
57
+ Atomics.wait(waitArray, 0, 0, 5);
58
+ }
59
+ });
60
+ `;
61
+ const holder = spawn(process.execPath, ['--input-type=module', '-e', holderSource], { stdio: 'ignore' });
62
+ const holderExit = waitForChildExit(holder);
63
+ const children = [];
64
+ try {
65
+ await waitForFiles([readyPath]);
66
+ const writers = [
67
+ { writerId: 'child-a-first', childId: 'child-a' },
68
+ { writerId: 'child-a-replay', childId: 'child-a' },
69
+ { writerId: 'child-b-first', childId: 'child-b' },
70
+ ];
71
+ for (const { writerId, childId } of writers) {
72
+ const startedPath = join(stateDir, `${writerId}.started`);
73
+ const donePath = join(stateDir, `${writerId}.done`);
74
+ const eventKey = `subagent-stop:${JSON.stringify([childId, 'turn-1'])}`;
75
+ const writerSource = `
76
+ import { writeFileSync } from 'node:fs';
77
+ import { claimLifecycleHookBroadcast } from ${JSON.stringify(lifecycleModuleUrl)};
78
+ writeFileSync(${JSON.stringify(startedPath)}, 'started');
79
+ const claim = claimLifecycleHookBroadcast(${JSON.stringify(stateDir)}, ${JSON.stringify(sessionId)}, ${JSON.stringify(eventKey)}, ${JSON.stringify(childId)});
80
+ writeFileSync(${JSON.stringify(donePath)}, JSON.stringify(claim));
81
+ `;
82
+ const child = spawn(process.execPath, ['--input-type=module', '-e', writerSource], { stdio: 'ignore' });
83
+ children.push({ child, childId, startedPath, donePath, exit: waitForChildExit(child) });
84
+ }
85
+ await waitForFiles(children.map(({ startedPath }) => startedPath));
86
+ await new Promise((resolve) => setTimeout(resolve, 50));
87
+ assert.deepEqual(children.map(({ donePath }) => existsSync(donePath)), [false, false, false]);
88
+ await writeFile(releasePath, 'release');
89
+ await Promise.all(children.map(({ exit }) => exit));
90
+ await holderExit;
91
+ const results = await Promise.all(children.map(async (writer) => ({
92
+ ...writer,
93
+ ...JSON.parse(await readFile(writer.donePath, 'utf8')),
94
+ })));
95
+ assert.equal(results.filter(({ childId, acquired }) => childId === 'child-a' && acquired).length, 1);
96
+ assert.equal(results.filter(({ childId, acquired }) => childId === 'child-b' && acquired).length, 1);
97
+ const state = JSON.parse(await readFile(statePath, 'utf8'));
98
+ for (const childId of ['child-a', 'child-b']) {
99
+ const eventKey = `subagent-stop:${JSON.stringify([childId, 'turn-1'])}`;
100
+ assert.equal(state.hookEvents[eventKey].fingerprint, childId);
101
+ assert.ok(state.hookEvents[eventKey].claimId);
102
+ }
103
+ }
104
+ finally {
105
+ await writeFile(releasePath, 'release').catch(() => { });
106
+ for (const { child } of children)
107
+ child.kill();
108
+ holder.kill();
109
+ await Promise.allSettled([...children.map(({ exit }) => exit), holderExit]);
110
+ await rm(stateDir, { recursive: true, force: true });
111
+ }
112
+ });
113
+ it('releases failed child-hook claims and records successful dispatches', async () => {
114
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-hook-claim-'));
115
+ const sessionId = 'claim-session';
116
+ const eventKey = 'subagent-stop:["child-a","turn-1"]';
117
+ const startMs = Date.parse('2026-04-12T00:00:00.000Z');
118
+ try {
119
+ const failedDispatchClaim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs);
120
+ assert.equal(failedDispatchClaim.acquired, true);
121
+ assert.ok(failedDispatchClaim.claimId);
122
+ assert.equal(claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 1).acquired, false);
123
+ releaseLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', failedDispatchClaim.claimId, startMs + 2);
124
+ const retryClaim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 3);
125
+ assert.equal(retryClaim.acquired, true);
126
+ assert.ok(retryClaim.claimId);
127
+ completeLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', retryClaim.claimId, startMs + 4);
128
+ assert.equal(claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 5).acquired, false);
129
+ assert.equal(shouldSendLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 5_003), false);
130
+ assert.equal(shouldSendLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 5_004), true);
131
+ assert.equal(claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 5_004).acquired, true);
132
+ }
133
+ finally {
134
+ await rm(stateDir, { recursive: true, force: true });
135
+ }
136
+ });
137
+ it('allows a replay to reclaim a child-hook receipt after an abandoned claim expires', async () => {
138
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-hook-claim-expiry-'));
139
+ const sessionId = 'claim-expiry-session';
140
+ const eventKey = 'subagent-stop:["child-a","turn-1"]';
141
+ const startMs = Date.parse('2026-04-12T00:00:00.000Z');
142
+ try {
143
+ const abandonedClaim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs);
144
+ assert.equal(abandonedClaim.acquired, true);
145
+ assert.equal(claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 119_999).acquired, false);
146
+ const recoveredClaim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 120_000);
147
+ assert.equal(recoveredClaim.acquired, true);
148
+ assert.ok(recoveredClaim.claimId);
149
+ releaseLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', recoveredClaim.claimId, startMs + 120_001);
150
+ }
151
+ finally {
152
+ await rm(stateDir, { recursive: true, force: true });
153
+ }
154
+ });
155
+ it('fails closed on non-record lifecycle receipt state', async () => {
156
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-invalid-hook-state-'));
157
+ const sessionId = 'invalid-hook-state-session';
158
+ const statePath = join(stateDir, 'sessions', sessionId, 'lifecycle-notif-state.json');
159
+ try {
160
+ await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
161
+ for (const contents of ['[]', JSON.stringify({ hookEvents: [] })]) {
162
+ await writeFile(statePath, contents);
163
+ assert.deepEqual(claimLifecycleHookBroadcast(stateDir, sessionId, 'subagent-stop:["child-a","turn-1"]', 'fingerprint'), { acquired: false, reason: 'unavailable' });
164
+ assert.equal(await readFile(statePath, 'utf8'), contents);
165
+ }
166
+ }
167
+ finally {
168
+ await rm(stateDir, { recursive: true, force: true });
169
+ }
170
+ });
171
+ it('renews an active child-hook claim and rejects renewal by a stale owner', async () => {
172
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-hook-claim-renewal-'));
173
+ const sessionId = 'claim-renewal-session';
174
+ const eventKey = 'subagent-stop:["child-a","turn-1"]';
175
+ const startMs = Date.parse('2026-04-12T00:00:00.000Z');
176
+ try {
177
+ const claim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs);
178
+ assert.equal(claim.acquired, true);
179
+ assert.ok(claim.claimId);
180
+ assert.equal(renewLifecycleHookBroadcastClaim(stateDir, sessionId, eventKey, 'fingerprint', 'stale-claim', startMs + 60_000), false);
181
+ assert.equal(renewLifecycleHookBroadcastClaim(stateDir, sessionId, eventKey, 'fingerprint', claim.claimId, startMs + 60_000), true);
182
+ assert.deepEqual(claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 120_000), { acquired: false, reason: 'duplicate' });
183
+ const expiredClaim = claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', startMs + 180_000);
184
+ assert.equal(expiredClaim.acquired, true);
185
+ assert.ok(expiredClaim.claimId);
186
+ releaseLifecycleHookBroadcast(stateDir, sessionId, eventKey, 'fingerprint', expiredClaim.claimId, startMs + 180_001);
187
+ }
188
+ finally {
189
+ await rm(stateDir, { recursive: true, force: true });
190
+ }
191
+ });
192
+ it('prunes expired child-turn keys while retaining live keys and other lifecycle state', async () => {
193
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-child-expiry-'));
194
+ try {
195
+ const payload = buildPayload();
196
+ const startMs = Date.parse('2026-04-12T00:00:00.000Z');
197
+ const oldKey = 'subagent-stop:["child-a","turn-1"]';
198
+ const liveKey = 'subagent-stop:["child-b","turn-1"]';
199
+ const nextKey = 'subagent-stop:["child-a","turn-2"]';
200
+ recordLifecycleNotificationSent(stateDir, payload, startMs);
201
+ recordLifecycleHookBroadcastSent(stateDir, payload.sessionId, 'stop', 'leader', startMs);
202
+ recordLifecycleHookBroadcastSent(stateDir, payload.sessionId, oldKey, 'old', startMs);
203
+ recordLifecycleHookBroadcastSent(stateDir, payload.sessionId, liveKey, 'live', startMs + 1_000);
204
+ recordLifecycleHookBroadcastSent(stateDir, payload.sessionId, nextKey, 'next', startMs + 5_000);
205
+ const state = JSON.parse(await readFile(join(stateDir, 'sessions', payload.sessionId, 'lifecycle-notif-state.json'), 'utf8'));
206
+ assert.equal(state.hookEvents[oldKey], undefined);
207
+ assert.equal(state.hookEvents[liveKey].fingerprint, 'live');
208
+ assert.equal(state.hookEvents[nextKey].fingerprint, 'next');
209
+ assert.equal(state.hookEvents.stop.fingerprint, 'leader');
210
+ assert.ok(state.events['session-end']);
211
+ assert.equal(shouldSendLifecycleHookBroadcast(stateDir, payload.sessionId, liveKey, 'live', startMs + 5_999), false);
212
+ assert.equal(shouldSendLifecycleHookBroadcast(stateDir, payload.sessionId, liveKey, 'live', startMs + 6_000), true);
213
+ }
214
+ finally {
215
+ await rm(stateDir, { recursive: true, force: true });
216
+ }
217
+ });
20
218
  it('suppresses duplicate session lifecycle notifications for the same session transition', async () => {
21
219
  const stateDir = await mkdtemp(join(tmpdir(), 'omx-lifecycle-dedupe-'));
22
220
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-dedupe.test.js","sourceRoot":"","sources":["../../../src/notifications/__tests__/lifecycle-dedupe.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,+BAA+B,EAC/B,+BAA+B,EAC/B,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAGhC,SAAS,YAAY,CAAC,SAAS,GAAqC,EAAE;IACpE,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,0BAA0B;QACrC,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,SAAS;QACtB,MAAM,EAAE,cAAc;QACtB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;YACvE,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1E,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,8BAA8B,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;YAC/B,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACvG,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,+BAA+B,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YACjF,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAEnD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,CAAC,CAAC;YAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAE5D,CAAC;YAEF,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC5E,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAClI,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,8BAA8B,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAEvD,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAE5D,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;YACzF,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1F,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,4BAA4B,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;YACpF,MAAM,eAAe,GAAG,4BAA4B,CAAC;YAErD,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,CAAC,KAAK,CACV,gCAAgC,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE,kBAAkB,EAAE,eAAe,CAAC,EAClG,IAAI,CACL,CAAC;YAEF,gCAAgC,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE,kBAAkB,EAAE,eAAe,CAAC,CAAC;YACnG,MAAM,CAAC,KAAK,CACV,gCAAgC,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE,kBAAkB,EAAE,eAAe,CAAC,EAClG,KAAK,CACN,CAAC;YACF,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1E,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"lifecycle-dedupe.test.js","sourceRoot":"","sources":["../../../src/notifications/__tests__/lifecycle-dedupe.test.ts"],"names":[],"mappings":"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"}
@@ -4,5 +4,15 @@ export declare function createLifecycleBroadcastFingerprint(value: unknown): str
4
4
  export declare function shouldSendLifecycleNotification(stateDir: string, payload: FullNotificationPayload, nowMs?: number): boolean;
5
5
  export declare function recordLifecycleNotificationSent(stateDir: string, payload: FullNotificationPayload, nowMs?: number): void;
6
6
  export declare function shouldSendLifecycleHookBroadcast(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, nowMs?: number): boolean;
7
+ export declare function claimLifecycleHookBroadcast(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, nowMs?: number): {
8
+ acquired: true;
9
+ claimId?: string;
10
+ } | {
11
+ acquired: false;
12
+ reason: 'duplicate' | 'unavailable';
13
+ };
14
+ export declare function renewLifecycleHookBroadcastClaim(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, claimId: string, nowMs?: number): boolean;
15
+ export declare function completeLifecycleHookBroadcast(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, claimId: string, nowMs?: number): void;
16
+ export declare function releaseLifecycleHookBroadcast(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, claimId: string, nowMs?: number): void;
7
17
  export declare function recordLifecycleHookBroadcastSent(stateDir: string, sessionId: string | undefined, eventKey: string, fingerprint: string, nowMs?: number): void;
8
18
  //# sourceMappingURL=lifecycle-dedupe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-dedupe.d.ts","sourceRoot":"","sources":["../../src/notifications/lifecycle-dedupe.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAqD7E,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEnF;AAuED,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE1E;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,MAAmB,GACzB,OAAO,CAUT;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,MAAmB,GACzB,IAAI,CAUN;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAST;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAmB,GACzB,IAAI,CASN"}
1
+ {"version":3,"file":"lifecycle-dedupe.d.ts","sourceRoot":"","sources":["../../src/notifications/lifecycle-dedupe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAiF7E,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEnF;AA6FD,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE1E;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,MAAmB,GACzB,OAAO,CAUT;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,MAAmB,GACzB,IAAI,CAUN;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAST;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAmB,GACzB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAAA;CAAE,CAmCjG;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAmB,GACzB,OAAO,CAsBT;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAmB,GACzB,IAAI,CAsBN;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAmB,GACzB,IAAI,CAoBN;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAmB,GACzB,IAAI,CASN"}
@@ -1,8 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
2
3
  import { dirname, join } from 'node:path';
4
+ import { withCrossProcessFileLockSync } from '../subagents/tracker.js';
3
5
  const SESSION_ID_SAFE_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/;
4
6
  const LIFECYCLE_DEDUPE_FILE = 'lifecycle-notif-state.json';
5
7
  const LIFECYCLE_DEDUPE_WINDOW_MS = 5_000;
8
+ const LIFECYCLE_HOOK_CLAIM_WINDOW_MS = 120_000;
6
9
  const DEDUPED_EVENTS = new Set(['session-start', 'session-stop', 'session-end']);
7
10
  function normalizeFingerprint(payload) {
8
11
  return JSON.stringify({
@@ -19,24 +22,40 @@ function getStatePath(stateDir, sessionId) {
19
22
  }
20
23
  return join(stateDir, LIFECYCLE_DEDUPE_FILE);
21
24
  }
22
- function readState(path) {
25
+ function isRecord(value) {
26
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
27
+ }
28
+ function readStateFromDisk(path) {
23
29
  try {
24
30
  if (!existsSync(path))
25
31
  return {};
26
32
  const parsed = JSON.parse(readFileSync(path, 'utf-8'));
27
- return parsed && typeof parsed === 'object' ? parsed : {};
33
+ if (!isRecord(parsed)
34
+ || (parsed.events !== undefined && !isRecord(parsed.events))
35
+ || (parsed.hookEvents !== undefined && !isRecord(parsed.hookEvents)))
36
+ return null;
37
+ return parsed;
28
38
  }
29
39
  catch {
30
- return {};
40
+ return null;
31
41
  }
32
42
  }
33
- function writeState(path, state) {
43
+ function readState(path) {
44
+ return readStateFromDisk(path) ?? {};
45
+ }
46
+ function readBucketState(state, bucket) {
47
+ const bucketState = state[bucket];
48
+ return isRecord(bucketState) ? bucketState : {};
49
+ }
50
+ function writeState(path, state, publish) {
34
51
  try {
35
52
  mkdirSync(dirname(path), { recursive: true });
36
- writeFileSync(path, JSON.stringify(state, null, 2));
53
+ publish(JSON.stringify(state, null, 2));
54
+ return true;
37
55
  }
38
56
  catch {
39
57
  // best effort
58
+ return false;
40
59
  }
41
60
  }
42
61
  export function shouldDedupeLifecycleNotification(event) {
@@ -65,30 +84,52 @@ function shouldSendFingerprint(previous, fingerprint, nowMs) {
65
84
  return false;
66
85
  return nowMs - previousMs >= LIFECYCLE_DEDUPE_WINDOW_MS;
67
86
  }
87
+ function pruneExpiredChildTurnEntries(hookEvents, nowMs) {
88
+ for (const [key, entry] of Object.entries(hookEvents)) {
89
+ if (!key.startsWith('subagent-stop:'))
90
+ continue;
91
+ if (entry?.claimId) {
92
+ const claimedAtMs = Date.parse(entry.claimedAt ?? '');
93
+ if (!Number.isFinite(claimedAtMs) || nowMs - claimedAtMs >= LIFECYCLE_HOOK_CLAIM_WINDOW_MS)
94
+ delete hookEvents[key];
95
+ continue;
96
+ }
97
+ const sentAtMs = Date.parse(entry?.sentAt ?? '');
98
+ if (!Number.isFinite(sentAtMs) || nowMs - sentAtMs >= LIFECYCLE_DEDUPE_WINDOW_MS)
99
+ delete hookEvents[key];
100
+ }
101
+ }
68
102
  function shouldSendScopedLifecycleBroadcast(stateDir, sessionId, bucket, eventKey, fingerprint, nowMs = Date.now()) {
69
103
  if (!sessionId || !stateDir)
70
104
  return true;
71
105
  const path = getStatePath(stateDir, sessionId);
72
106
  const state = readState(path);
73
- const bucketState = state[bucket] && typeof state[bucket] === 'object'
74
- ? state[bucket]
75
- : {};
107
+ const bucketState = readBucketState(state, bucket);
76
108
  return shouldSendFingerprint(bucketState?.[eventKey], fingerprint, nowMs);
77
109
  }
78
110
  function recordScopedLifecycleBroadcastSent(stateDir, sessionId, bucket, eventKey, fingerprint, nowMs = Date.now()) {
79
111
  if (!sessionId || !stateDir)
80
112
  return;
81
113
  const path = getStatePath(stateDir, sessionId);
82
- const state = readState(path);
83
- const bucketState = state[bucket] && typeof state[bucket] === 'object'
84
- ? state[bucket]
85
- : {};
86
- bucketState[eventKey] = {
87
- fingerprint,
88
- sentAt: new Date(nowMs).toISOString(),
89
- };
90
- state[bucket] = bucketState;
91
- writeState(path, state);
114
+ try {
115
+ withCrossProcessFileLockSync(path, (lock) => {
116
+ const state = readState(path);
117
+ const bucketState = readBucketState(state, bucket);
118
+ if (bucket === 'hookEvents') {
119
+ pruneExpiredChildTurnEntries(bucketState, nowMs);
120
+ }
121
+ bucketState[eventKey] = {
122
+ fingerprint,
123
+ sentAt: new Date(nowMs).toISOString(),
124
+ };
125
+ state[bucket] = bucketState;
126
+ lock.assertOwnership();
127
+ writeState(path, state, lock.publish);
128
+ });
129
+ }
130
+ catch {
131
+ // Lifecycle receipts are best effort; lock failure must not break hook dispatch.
132
+ }
92
133
  }
93
134
  export function createLifecycleBroadcastFingerprint(value) {
94
135
  return stableSerialize(value);
@@ -106,6 +147,121 @@ export function recordLifecycleNotificationSent(stateDir, payload, nowMs = Date.
106
147
  export function shouldSendLifecycleHookBroadcast(stateDir, sessionId, eventKey, fingerprint, nowMs = Date.now()) {
107
148
  return shouldSendScopedLifecycleBroadcast(stateDir, sessionId, 'hookEvents', eventKey, fingerprint, nowMs);
108
149
  }
150
+ export function claimLifecycleHookBroadcast(stateDir, sessionId, eventKey, fingerprint, nowMs = Date.now()) {
151
+ if (!sessionId || !stateDir)
152
+ return { acquired: true };
153
+ const path = getStatePath(stateDir, sessionId);
154
+ const claimId = randomUUID();
155
+ try {
156
+ return withCrossProcessFileLockSync(path, (lock) => {
157
+ const state = readStateFromDisk(path);
158
+ if (!state)
159
+ return { acquired: false, reason: 'unavailable' };
160
+ const hookEvents = readBucketState(state, 'hookEvents');
161
+ pruneExpiredChildTurnEntries(hookEvents, nowMs);
162
+ const previous = hookEvents[eventKey];
163
+ if (previous?.claimId) {
164
+ const claimedAtMs = Date.parse(previous.claimedAt ?? '');
165
+ if (Number.isFinite(claimedAtMs) && nowMs - claimedAtMs < LIFECYCLE_HOOK_CLAIM_WINDOW_MS) {
166
+ return { acquired: false, reason: 'duplicate' };
167
+ }
168
+ }
169
+ else if (!shouldSendFingerprint(previous, fingerprint, nowMs)) {
170
+ return { acquired: false, reason: 'duplicate' };
171
+ }
172
+ hookEvents[eventKey] = {
173
+ fingerprint,
174
+ claimId,
175
+ claimedAt: new Date(nowMs).toISOString(),
176
+ };
177
+ state.hookEvents = hookEvents;
178
+ lock.assertOwnership();
179
+ if (!writeState(path, state, lock.publish))
180
+ return { acquired: false, reason: 'unavailable' };
181
+ return { acquired: true, claimId };
182
+ });
183
+ }
184
+ catch {
185
+ // Do not run side-effecting hooks without an atomic child-completion claim.
186
+ return { acquired: false, reason: 'unavailable' };
187
+ }
188
+ }
189
+ export function renewLifecycleHookBroadcastClaim(stateDir, sessionId, eventKey, fingerprint, claimId, nowMs = Date.now()) {
190
+ if (!sessionId || !stateDir || !claimId)
191
+ return false;
192
+ const path = getStatePath(stateDir, sessionId);
193
+ try {
194
+ return withCrossProcessFileLockSync(path, (lock) => {
195
+ const state = readStateFromDisk(path);
196
+ if (!state)
197
+ return false;
198
+ const hookEvents = readBucketState(state, 'hookEvents');
199
+ const previous = hookEvents[eventKey];
200
+ if (previous?.claimId !== claimId || previous.fingerprint !== fingerprint)
201
+ return false;
202
+ hookEvents[eventKey] = {
203
+ ...previous,
204
+ claimedAt: new Date(nowMs).toISOString(),
205
+ };
206
+ state.hookEvents = hookEvents;
207
+ lock.assertOwnership();
208
+ return writeState(path, state, lock.publish);
209
+ });
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
215
+ export function completeLifecycleHookBroadcast(stateDir, sessionId, eventKey, fingerprint, claimId, nowMs = Date.now()) {
216
+ if (!sessionId || !stateDir || !claimId)
217
+ return;
218
+ const path = getStatePath(stateDir, sessionId);
219
+ try {
220
+ withCrossProcessFileLockSync(path, (lock) => {
221
+ const state = readState(path);
222
+ const hookEvents = readBucketState(state, 'hookEvents');
223
+ const previous = hookEvents[eventKey];
224
+ if (previous?.claimId !== claimId || previous.fingerprint !== fingerprint)
225
+ return;
226
+ hookEvents[eventKey] = {
227
+ fingerprint,
228
+ sentAt: new Date(nowMs).toISOString(),
229
+ };
230
+ state.hookEvents = hookEvents;
231
+ pruneExpiredChildTurnEntries(hookEvents, nowMs);
232
+ lock.assertOwnership();
233
+ writeState(path, state, lock.publish);
234
+ });
235
+ }
236
+ catch {
237
+ // Lifecycle receipts are best effort; lock failure must not break hook dispatch.
238
+ }
239
+ }
240
+ export function releaseLifecycleHookBroadcast(stateDir, sessionId, eventKey, fingerprint, claimId, nowMs = Date.now()) {
241
+ if (!sessionId || !stateDir || !claimId)
242
+ return;
243
+ const path = getStatePath(stateDir, sessionId);
244
+ try {
245
+ withCrossProcessFileLockSync(path, (lock) => {
246
+ const state = readState(path);
247
+ const hookEvents = readBucketState(state, 'hookEvents');
248
+ const previous = hookEvents[eventKey];
249
+ if (previous?.claimId !== claimId || previous.fingerprint !== fingerprint)
250
+ return;
251
+ delete hookEvents[eventKey];
252
+ if (Object.keys(hookEvents).length > 0)
253
+ state.hookEvents = hookEvents;
254
+ else
255
+ delete state.hookEvents;
256
+ pruneExpiredChildTurnEntries(hookEvents, nowMs);
257
+ lock.assertOwnership();
258
+ writeState(path, state, lock.publish);
259
+ });
260
+ }
261
+ catch {
262
+ // Lifecycle receipts are best effort; lock failure must not break hook dispatch.
263
+ }
264
+ }
109
265
  export function recordLifecycleHookBroadcastSent(stateDir, sessionId, eventKey, fingerprint, nowMs = Date.now()) {
110
266
  recordScopedLifecycleBroadcastSent(stateDir, sessionId, 'hookEvents', eventKey, fingerprint, nowMs);
111
267
  }