oh-my-copilot 5.6.2 → 5.7.0

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 (127) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -0
  4. package/bridge/__pycache__/gyoshu_bridge.cpython-312.pyc +0 -0
  5. package/bridge/claude-md-coordinator.cjs +2 -2
  6. package/bridge/cli.cjs +78578 -75769
  7. package/bridge/mcp-server.cjs +36510 -32259
  8. package/bridge/runtime-cli.cjs +95 -34
  9. package/bridge/team-mcp.cjs +95 -34
  10. package/bridge/team.js +131 -70
  11. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts +2 -0
  12. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts.map +1 -0
  13. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js +68 -0
  14. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js.map +1 -0
  15. package/dist/__tests__/session-end-process-exit.test.js +70 -2
  16. package/dist/__tests__/session-end-process-exit.test.js.map +1 -1
  17. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts +2 -0
  18. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts.map +1 -0
  19. package/dist/cli/commands/__tests__/smoke-tier2.test.js +72 -0
  20. package/dist/cli/commands/__tests__/smoke-tier2.test.js.map +1 -0
  21. package/dist/cli/commands/smoke.d.ts +36 -0
  22. package/dist/cli/commands/smoke.d.ts.map +1 -0
  23. package/dist/cli/commands/smoke.js +169 -0
  24. package/dist/cli/commands/smoke.js.map +1 -0
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +39 -0
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/constants/names.d.ts +1 -0
  29. package/dist/constants/names.d.ts.map +1 -1
  30. package/dist/constants/names.js +1 -0
  31. package/dist/constants/names.js.map +1 -1
  32. package/dist/hooks/session-end/index.d.ts +2 -3
  33. package/dist/hooks/session-end/index.d.ts.map +1 -1
  34. package/dist/hooks/session-end/index.js +3 -10
  35. package/dist/hooks/session-end/index.js.map +1 -1
  36. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts +17 -0
  37. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts.map +1 -0
  38. package/dist/hooks/session-end/wiki-foreground-bootstrap.js +20 -0
  39. package/dist/hooks/session-end/wiki-foreground-bootstrap.js.map +1 -0
  40. package/dist/lib/__tests__/atomic-write.test.js +32 -1
  41. package/dist/lib/__tests__/atomic-write.test.js.map +1 -1
  42. package/dist/lib/__tests__/mode-state-lock.test.js +121 -12
  43. package/dist/lib/__tests__/mode-state-lock.test.js.map +1 -1
  44. package/dist/lib/atomic-write.d.ts +13 -3
  45. package/dist/lib/atomic-write.d.ts.map +1 -1
  46. package/dist/lib/atomic-write.js +29 -15
  47. package/dist/lib/atomic-write.js.map +1 -1
  48. package/dist/lib/mode-state-io.d.ts +2 -2
  49. package/dist/lib/mode-state-io.d.ts.map +1 -1
  50. package/dist/lib/mode-state-io.js +16 -10
  51. package/dist/lib/mode-state-io.js.map +1 -1
  52. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts +2 -0
  53. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts.map +1 -0
  54. package/dist/mcp/__tests__/host-smoke-tool.test.js +174 -0
  55. package/dist/mcp/__tests__/host-smoke-tool.test.js.map +1 -0
  56. package/dist/mcp/disable-tools.d.ts.map +1 -1
  57. package/dist/mcp/disable-tools.js +1 -0
  58. package/dist/mcp/disable-tools.js.map +1 -1
  59. package/dist/mcp/tool-registry.d.ts.map +1 -1
  60. package/dist/mcp/tool-registry.js +2 -0
  61. package/dist/mcp/tool-registry.js.map +1 -1
  62. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts +2 -0
  63. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts.map +1 -0
  64. package/dist/smoke/__tests__/copilot-sdk-driver.test.js +561 -0
  65. package/dist/smoke/__tests__/copilot-sdk-driver.test.js.map +1 -0
  66. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts +2 -0
  67. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts.map +1 -0
  68. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js +311 -0
  69. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js.map +1 -0
  70. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts +2 -0
  71. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts.map +1 -0
  72. package/dist/smoke/__tests__/copilot-session-eval.test.js +264 -0
  73. package/dist/smoke/__tests__/copilot-session-eval.test.js.map +1 -0
  74. package/dist/smoke/__tests__/copilot-smoke.test.d.ts +2 -0
  75. package/dist/smoke/__tests__/copilot-smoke.test.d.ts.map +1 -0
  76. package/dist/smoke/__tests__/copilot-smoke.test.js +482 -0
  77. package/dist/smoke/__tests__/copilot-smoke.test.js.map +1 -0
  78. package/dist/smoke/copilot-binary.d.ts +21 -0
  79. package/dist/smoke/copilot-binary.d.ts.map +1 -0
  80. package/dist/smoke/copilot-binary.js +46 -0
  81. package/dist/smoke/copilot-binary.js.map +1 -0
  82. package/dist/smoke/copilot-sdk-driver.d.ts +186 -0
  83. package/dist/smoke/copilot-sdk-driver.d.ts.map +1 -0
  84. package/dist/smoke/copilot-sdk-driver.js +622 -0
  85. package/dist/smoke/copilot-sdk-driver.js.map +1 -0
  86. package/dist/smoke/copilot-sdk-scenarios.d.ts +159 -0
  87. package/dist/smoke/copilot-sdk-scenarios.d.ts.map +1 -0
  88. package/dist/smoke/copilot-sdk-scenarios.js +510 -0
  89. package/dist/smoke/copilot-sdk-scenarios.js.map +1 -0
  90. package/dist/smoke/copilot-session-eval.d.ts +90 -0
  91. package/dist/smoke/copilot-session-eval.d.ts.map +1 -0
  92. package/dist/smoke/copilot-session-eval.js +323 -0
  93. package/dist/smoke/copilot-session-eval.js.map +1 -0
  94. package/dist/smoke/copilot-smoke.d.ts +228 -0
  95. package/dist/smoke/copilot-smoke.d.ts.map +1 -0
  96. package/dist/smoke/copilot-smoke.js +818 -0
  97. package/dist/smoke/copilot-smoke.js.map +1 -0
  98. package/dist/smoke/process-utils.d.ts +47 -0
  99. package/dist/smoke/process-utils.d.ts.map +1 -0
  100. package/dist/smoke/process-utils.js +99 -0
  101. package/dist/smoke/process-utils.js.map +1 -0
  102. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts +2 -0
  103. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts.map +1 -0
  104. package/dist/team/__tests__/tmux-session.pane-env.test.js +52 -0
  105. package/dist/team/__tests__/tmux-session.pane-env.test.js.map +1 -0
  106. package/dist/team/tmux-session.d.ts +7 -0
  107. package/dist/team/tmux-session.d.ts.map +1 -1
  108. package/dist/team/tmux-session.js +80 -18
  109. package/dist/team/tmux-session.js.map +1 -1
  110. package/dist/tools/host-smoke-tool.d.ts +34 -0
  111. package/dist/tools/host-smoke-tool.d.ts.map +1 -0
  112. package/dist/tools/host-smoke-tool.js +133 -0
  113. package/dist/tools/host-smoke-tool.js.map +1 -0
  114. package/docs/CLAUDE.md +1 -1
  115. package/docs/DEVELOPERS.md +101 -0
  116. package/docs/HOOKS.md +1 -1
  117. package/docs/MIGRATION.md +71 -0
  118. package/docs/REFERENCE.md +42 -1
  119. package/docs/TOOLS.md +32 -1
  120. package/package.json +11 -2
  121. package/plugin.json +1 -1
  122. package/scripts/dev/repro-state-lock.mjs +1 -1
  123. package/scripts/lib/atomic-write.mjs +4 -2
  124. package/scripts/lib/state-lock.mjs +21 -12
  125. package/scripts/run.cjs +13 -3
  126. package/scripts/wiki-session-end.mjs +2 -1
  127. package/templates/hooks/lib/atomic-write.mjs +4 -2
@@ -156,33 +156,41 @@ function sameOwner(left, right) {
156
156
  return Boolean(left && right && left.pid === right.pid && left.processStart === right.processStart && left.nonce === right.nonce);
157
157
  }
158
158
 
159
+ // BigInt ids: NTFS file IDs exceed 2^53 once the MFT sequence number reaches
160
+ // 32, and a Number ino rounds distinct files onto the same value.
159
161
  function ownerArtifactIdentity(path) {
160
162
  try {
161
- const stats = statSync(path);
163
+ const stats = statSync(path, { bigint: true });
162
164
  return stats.isFile() ? { dev: stats.dev, ino: stats.ino } : null;
163
165
  } catch {
164
166
  return null;
165
167
  }
166
168
  }
167
169
 
168
- /** Windows reports dev=0 from some stat paths (#4156); compare ino always, dev only when both are known. */
170
+ /** Mirrors sameFileIdentity in src/lib/atomic-write.ts: ino always, dev unless win32 reports 0 (#4156). */
169
171
  function sameArtifactIdentity(a, b) {
170
172
  if (a.ino !== b.ino) return false;
171
- if (process.platform === 'win32' && (a.dev === 0 || b.dev === 0)) return true;
173
+ if (process.platform === 'win32' && (a.dev === 0n || b.dev === 0n)) return true;
172
174
  return a.dev === b.dev;
173
175
  }
174
176
 
175
177
  /** Remove only the exact dead publication that was inspected. */
176
178
  function reclaimDeadOwner(path, observed, identity) {
177
- // Fork fix: the liveness verdict can be seconds old (win32 probe), and the owner may have
178
- // exited and been replaced meanwhile. Renaming a live replacement into quarantine lets a third
179
- // contender publish before the restore, leaving two holders, so re-verify the exact artifact
180
- // (owner record AND dev/ino, upstream #4149 checks only the latter) immediately before the rename.
181
- const current = readOwner(path);
182
- const currentIdentity = ownerArtifactIdentity(path);
183
- if (current === 'absent' || !current || !sameOwner(current, observed) || !currentIdentity ||
184
- !sameArtifactIdentity(currentIdentity, identity)) return 'changed';
185
179
  const quarantinePath = `${path}.reclaim.${process.pid}.${randomUUID()}`;
180
+ // The liveness verdict can be seconds old (the win32 probe spawns PowerShell),
181
+ // and the identity alone cannot tell a replacement apart when its inode reuses
182
+ // the old one (or when the identity was captured after the probe). Renaming a
183
+ // live replacement into quarantine opens a window in which a third contender
184
+ // publishes, leaving two holders. Re-verify the exact artifact, owner record
185
+ // AND file identity, immediately before the rename. Bracketing the read with
186
+ // two stats binds the record that was read to the identity that was checked.
187
+ const before = ownerArtifactIdentity(path);
188
+ const current = readOwner(path);
189
+ const after = ownerArtifactIdentity(path);
190
+ if (current === 'absent' || !current || !sameOwner(current, observed) ||
191
+ !before || !after || !sameArtifactIdentity(before, identity) || !sameArtifactIdentity(after, identity)) {
192
+ return 'changed';
193
+ }
186
194
  try {
187
195
  renameSync(path, quarantinePath);
188
196
  } catch (error) {
@@ -194,7 +202,8 @@ function reclaimDeadOwner(path, observed, identity) {
194
202
  try {
195
203
  moved = readOwner(quarantinePath);
196
204
  movedIdentity = ownerArtifactIdentity(quarantinePath);
197
- if (moved !== 'absent' && moved && movedIdentity && sameArtifactIdentity(movedIdentity, identity) &&
205
+ if (moved !== 'absent' && moved && movedIdentity &&
206
+ sameArtifactIdentity(movedIdentity, identity) &&
198
207
  sameOwner(moved, observed)) {
199
208
  try {
200
209
  unlinkSync(quarantinePath);
package/scripts/run.cjs CHANGED
@@ -271,8 +271,17 @@ function resolveTrustedSessionEndTarget(resolution, extraArgs) {
271
271
  }
272
272
 
273
273
 
274
+ // oh-my-copilot fail-closed contract (fork-only): with OMC_HOOK_FAIL_CLOSED=1
275
+ // a hook timeout exits 124 so the host records a failed hook. Unset keeps the
276
+ // upstream fail-open exit 0.
277
+ const HOOK_TIMEOUT_FAIL_CLOSED_STATUS = 124;
278
+ function hookTimeoutStatus() {
279
+ return process.env.OMC_HOOK_FAIL_CLOSED === '1' ? HOOK_TIMEOUT_FAIL_CLOSED_STATUS : 0;
280
+ }
281
+
274
282
  function writeTimeoutDiagnostic(targetPath, manifestHook, timeoutMs, sink) {
275
- const message = `[run.cjs] Hook ${basename(targetPath)} timed out after ${timeoutMs}ms; exiting fail-open.\n`;
283
+ const outcome = hookTimeoutStatus() === 0 ? 'fail-open' : `fail-closed (${HOOK_TIMEOUT_FAIL_CLOSED_STATUS})`;
284
+ const message = `[run.cjs] Hook ${basename(targetPath)} timed out after ${timeoutMs}ms; exiting ${outcome}.\n`;
276
285
  if (manifestHook?.event !== 'UserPromptSubmit' || isDebugHooksEnabled()) {
277
286
  if (sink) return sink.write(process.stderr, Buffer.from(message));
278
287
  try { process.stderr.write(message); } catch { /* protocol dest may already be closed */ }
@@ -842,7 +851,7 @@ function runGenericChild(targetPath, extraArgs, timeoutMs, manifestHook, options
842
851
  if (require.main === module) {
843
852
  sink.closeDestinations();
844
853
  }
845
- finish(0);
854
+ finish(hookTimeoutStatus()); // fork: fail-closed contract, see hookTimeoutStatus
846
855
  });
847
856
  }, timeoutMs);
848
857
 
@@ -996,7 +1005,8 @@ async function runWorker(targetPath, manifestHook, timeoutMs) {
996
1005
  }
997
1006
  await writeTimeoutDiagnostic(targetPath, manifestHook, timeoutMs, sink);
998
1007
  sink.uninstall();
999
- resolve(0);
1008
+ // Also the SessionEnd foreground-budget path. Fork: fail-closed contract, see hookTimeoutStatus.
1009
+ resolve(hookTimeoutStatus());
1000
1010
  }, timeoutMs);
1001
1011
 
1002
1012
  try {
@@ -15,7 +15,8 @@ export async function runWikiSessionEndHook() {
15
15
  }
16
16
 
17
17
  try {
18
- const { processWikiSessionEnd } = await import('../dist/hooks/session-end/index.js');
18
+ // Lean entry (not index.js): the full SessionEnd graph costs ~100ms of the 300ms budget.
19
+ const { processWikiSessionEnd } = await import('../dist/hooks/session-end/wiki-foreground-bootstrap.js');
19
20
  const result = await processWikiSessionEnd(frame.value);
20
21
  console.log(JSON.stringify(result));
21
22
  } catch (error) {
@@ -137,9 +137,11 @@ function readEmergencyJournal(path) {
137
137
  } catch { return null; }
138
138
  }
139
139
 
140
+ // BigInt ids: NTFS file IDs exceed 2^53 once the MFT sequence number reaches
141
+ // 32, and a Number ino rounds distinct files onto the same value.
140
142
  function fileIdentity(path) {
141
143
  try {
142
- const stat = statSync(path);
144
+ const stat = statSync(path, { bigint: true });
143
145
  return { dev: stat.dev, ino: stat.ino };
144
146
  } catch { return null; }
145
147
  }
@@ -157,7 +159,7 @@ function sameFileIdentity(a, b) {
157
159
  // On Windows, lstat returns dev=0, so skip dev comparison when on Windows
158
160
  // unless both are non-zero (indicating a real comparison is possible)
159
161
  const isWindows = process.platform === 'win32';
160
- if (isWindows && (a.dev === 0 || b.dev === 0)) {
162
+ if (isWindows && (a.dev === 0n || b.dev === 0n)) {
161
163
  return true; // Skip dev comparison on Windows when either is 0
162
164
  }
163
165