oh-my-copilot 5.6.2 → 5.8.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 (264) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +2 -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 +81405 -76559
  7. package/bridge/mcp-server.cjs +40048 -32187
  8. package/bridge/runtime-cli.cjs +943 -199
  9. package/bridge/team-mcp.cjs +716 -125
  10. package/bridge/team.js +1066 -290
  11. package/dist/__tests__/cli-win32-warning.test.js +7 -0
  12. package/dist/__tests__/cli-win32-warning.test.js.map +1 -1
  13. package/dist/__tests__/run-cjs-copilot-worker-routing.test.d.ts +2 -0
  14. package/dist/__tests__/run-cjs-copilot-worker-routing.test.d.ts.map +1 -0
  15. package/dist/__tests__/run-cjs-copilot-worker-routing.test.js +294 -0
  16. package/dist/__tests__/run-cjs-copilot-worker-routing.test.js.map +1 -0
  17. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts +2 -0
  18. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts.map +1 -0
  19. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js +68 -0
  20. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js.map +1 -0
  21. package/dist/__tests__/run-cjs-session-end-budget.test.d.ts +2 -0
  22. package/dist/__tests__/run-cjs-session-end-budget.test.d.ts.map +1 -0
  23. package/dist/__tests__/run-cjs-session-end-budget.test.js +171 -0
  24. package/dist/__tests__/run-cjs-session-end-budget.test.js.map +1 -0
  25. package/dist/__tests__/session-end-process-exit.test.js +70 -2
  26. package/dist/__tests__/session-end-process-exit.test.js.map +1 -1
  27. package/dist/cli/__tests__/doctor-conflicts-json.test.d.ts +2 -0
  28. package/dist/cli/__tests__/doctor-conflicts-json.test.d.ts.map +1 -0
  29. package/dist/cli/__tests__/doctor-conflicts-json.test.js +46 -0
  30. package/dist/cli/__tests__/doctor-conflicts-json.test.js.map +1 -0
  31. package/dist/cli/__tests__/team.test.js +67 -0
  32. package/dist/cli/__tests__/team.test.js.map +1 -1
  33. package/dist/cli/__tests__/wait-teleport-json.test.d.ts +2 -0
  34. package/dist/cli/__tests__/wait-teleport-json.test.d.ts.map +1 -0
  35. package/dist/cli/__tests__/wait-teleport-json.test.js +64 -0
  36. package/dist/cli/__tests__/wait-teleport-json.test.js.map +1 -0
  37. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts +2 -0
  38. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts.map +1 -0
  39. package/dist/cli/commands/__tests__/smoke-tier2.test.js +110 -0
  40. package/dist/cli/commands/__tests__/smoke-tier2.test.js.map +1 -0
  41. package/dist/cli/commands/__tests__/team-status-json.test.d.ts +2 -0
  42. package/dist/cli/commands/__tests__/team-status-json.test.d.ts.map +1 -0
  43. package/dist/cli/commands/__tests__/team-status-json.test.js +141 -0
  44. package/dist/cli/commands/__tests__/team-status-json.test.js.map +1 -0
  45. package/dist/cli/commands/factory.d.ts +2 -0
  46. package/dist/cli/commands/factory.d.ts.map +1 -1
  47. package/dist/cli/commands/factory.js +8 -1
  48. package/dist/cli/commands/factory.js.map +1 -1
  49. package/dist/cli/commands/smoke.d.ts +36 -0
  50. package/dist/cli/commands/smoke.d.ts.map +1 -0
  51. package/dist/cli/commands/smoke.js +169 -0
  52. package/dist/cli/commands/smoke.js.map +1 -0
  53. package/dist/cli/commands/team.d.ts +2 -0
  54. package/dist/cli/commands/team.d.ts.map +1 -1
  55. package/dist/cli/commands/team.js +124 -10
  56. package/dist/cli/commands/team.js.map +1 -1
  57. package/dist/cli/index.d.ts.map +1 -1
  58. package/dist/cli/index.js +70 -10
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/cli/win32-warning.d.ts +7 -1
  61. package/dist/cli/win32-warning.d.ts.map +1 -1
  62. package/dist/cli/win32-warning.js +9 -1
  63. package/dist/cli/win32-warning.js.map +1 -1
  64. package/dist/constants/names.d.ts +1 -0
  65. package/dist/constants/names.d.ts.map +1 -1
  66. package/dist/constants/names.js +1 -0
  67. package/dist/constants/names.js.map +1 -1
  68. package/dist/factory/__tests__/check-run.test.js +2 -2
  69. package/dist/factory/__tests__/check-run.test.js.map +1 -1
  70. package/dist/factory/__tests__/listener.test.js +1 -1
  71. package/dist/factory/__tests__/listener.test.js.map +1 -1
  72. package/dist/factory/__tests__/status.test.js +42 -0
  73. package/dist/factory/__tests__/status.test.js.map +1 -1
  74. package/dist/factory/listener.d.ts.map +1 -1
  75. package/dist/factory/listener.js +7 -8
  76. package/dist/factory/listener.js.map +1 -1
  77. package/dist/factory/status.d.ts +19 -1
  78. package/dist/factory/status.d.ts.map +1 -1
  79. package/dist/factory/status.js +49 -8
  80. package/dist/factory/status.js.map +1 -1
  81. package/dist/factory/watchdog.d.ts.map +1 -1
  82. package/dist/factory/watchdog.js +14 -0
  83. package/dist/factory/watchdog.js.map +1 -1
  84. package/dist/factory/watchdog.test.js +45 -0
  85. package/dist/factory/watchdog.test.js.map +1 -1
  86. package/dist/hooks/session-end/__tests__/action-runner.test.js +5 -0
  87. package/dist/hooks/session-end/__tests__/action-runner.test.js.map +1 -1
  88. package/dist/hooks/session-end/__tests__/chain-enqueuer.test.js +186 -1
  89. package/dist/hooks/session-end/__tests__/chain-enqueuer.test.js.map +1 -1
  90. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.d.ts +2 -0
  91. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.d.ts.map +1 -0
  92. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.js +49 -0
  93. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.js.map +1 -0
  94. package/dist/hooks/session-end/__tests__/foreground-bootstrap.test.js +30 -1
  95. package/dist/hooks/session-end/__tests__/foreground-bootstrap.test.js.map +1 -1
  96. package/dist/hooks/session-end/__tests__/spawn-next.test.js +104 -3
  97. package/dist/hooks/session-end/__tests__/spawn-next.test.js.map +1 -1
  98. package/dist/hooks/session-end/__tests__/worker.test.js +6 -0
  99. package/dist/hooks/session-end/__tests__/worker.test.js.map +1 -1
  100. package/dist/hooks/session-end/action-runner.d.ts.map +1 -1
  101. package/dist/hooks/session-end/action-runner.js +2 -1
  102. package/dist/hooks/session-end/action-runner.js.map +1 -1
  103. package/dist/hooks/session-end/chain-enqueuer.d.ts +70 -7
  104. package/dist/hooks/session-end/chain-enqueuer.d.ts.map +1 -1
  105. package/dist/hooks/session-end/chain-enqueuer.js +354 -82
  106. package/dist/hooks/session-end/chain-enqueuer.js.map +1 -1
  107. package/dist/hooks/session-end/foreground-bootstrap.d.ts.map +1 -1
  108. package/dist/hooks/session-end/foreground-bootstrap.js +24 -16
  109. package/dist/hooks/session-end/foreground-bootstrap.js.map +1 -1
  110. package/dist/hooks/session-end/index.d.ts +2 -3
  111. package/dist/hooks/session-end/index.d.ts.map +1 -1
  112. package/dist/hooks/session-end/index.js +11 -13
  113. package/dist/hooks/session-end/index.js.map +1 -1
  114. package/dist/hooks/session-end/spawn-next.d.ts +81 -4
  115. package/dist/hooks/session-end/spawn-next.d.ts.map +1 -1
  116. package/dist/hooks/session-end/spawn-next.js +141 -14
  117. package/dist/hooks/session-end/spawn-next.js.map +1 -1
  118. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts +17 -0
  119. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts.map +1 -0
  120. package/dist/hooks/session-end/wiki-foreground-bootstrap.js +20 -0
  121. package/dist/hooks/session-end/wiki-foreground-bootstrap.js.map +1 -0
  122. package/dist/hooks/session-end/worker.d.ts.map +1 -1
  123. package/dist/hooks/session-end/worker.js +11 -6
  124. package/dist/hooks/session-end/worker.js.map +1 -1
  125. package/dist/lib/__tests__/atomic-write.test.js +32 -1
  126. package/dist/lib/__tests__/atomic-write.test.js.map +1 -1
  127. package/dist/lib/__tests__/mode-state-lock.test.js +121 -12
  128. package/dist/lib/__tests__/mode-state-lock.test.js.map +1 -1
  129. package/dist/lib/atomic-write.d.ts +13 -3
  130. package/dist/lib/atomic-write.d.ts.map +1 -1
  131. package/dist/lib/atomic-write.js +29 -15
  132. package/dist/lib/atomic-write.js.map +1 -1
  133. package/dist/lib/mode-state-io.d.ts +2 -2
  134. package/dist/lib/mode-state-io.d.ts.map +1 -1
  135. package/dist/lib/mode-state-io.js +16 -10
  136. package/dist/lib/mode-state-io.js.map +1 -1
  137. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts +2 -0
  138. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts.map +1 -0
  139. package/dist/mcp/__tests__/host-smoke-tool.test.js +174 -0
  140. package/dist/mcp/__tests__/host-smoke-tool.test.js.map +1 -0
  141. package/dist/mcp/disable-tools.d.ts.map +1 -1
  142. package/dist/mcp/disable-tools.js +1 -0
  143. package/dist/mcp/disable-tools.js.map +1 -1
  144. package/dist/mcp/tool-registry.d.ts.map +1 -1
  145. package/dist/mcp/tool-registry.js +2 -0
  146. package/dist/mcp/tool-registry.js.map +1 -1
  147. package/dist/shared/types.d.ts +11 -0
  148. package/dist/shared/types.d.ts.map +1 -1
  149. package/dist/smoke/__tests__/copilot-chain-scenario.test.d.ts +2 -0
  150. package/dist/smoke/__tests__/copilot-chain-scenario.test.d.ts.map +1 -0
  151. package/dist/smoke/__tests__/copilot-chain-scenario.test.js +154 -0
  152. package/dist/smoke/__tests__/copilot-chain-scenario.test.js.map +1 -0
  153. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts +2 -0
  154. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts.map +1 -0
  155. package/dist/smoke/__tests__/copilot-sdk-driver.test.js +591 -0
  156. package/dist/smoke/__tests__/copilot-sdk-driver.test.js.map +1 -0
  157. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts +2 -0
  158. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts.map +1 -0
  159. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js +362 -0
  160. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js.map +1 -0
  161. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts +2 -0
  162. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts.map +1 -0
  163. package/dist/smoke/__tests__/copilot-session-eval.test.js +264 -0
  164. package/dist/smoke/__tests__/copilot-session-eval.test.js.map +1 -0
  165. package/dist/smoke/__tests__/copilot-smoke.test.d.ts +2 -0
  166. package/dist/smoke/__tests__/copilot-smoke.test.d.ts.map +1 -0
  167. package/dist/smoke/__tests__/copilot-smoke.test.js +482 -0
  168. package/dist/smoke/__tests__/copilot-smoke.test.js.map +1 -0
  169. package/dist/smoke/copilot-binary.d.ts +21 -0
  170. package/dist/smoke/copilot-binary.d.ts.map +1 -0
  171. package/dist/smoke/copilot-binary.js +46 -0
  172. package/dist/smoke/copilot-binary.js.map +1 -0
  173. package/dist/smoke/copilot-chain-scenario.d.ts +61 -0
  174. package/dist/smoke/copilot-chain-scenario.d.ts.map +1 -0
  175. package/dist/smoke/copilot-chain-scenario.js +253 -0
  176. package/dist/smoke/copilot-chain-scenario.js.map +1 -0
  177. package/dist/smoke/copilot-sdk-driver.d.ts +189 -0
  178. package/dist/smoke/copilot-sdk-driver.d.ts.map +1 -0
  179. package/dist/smoke/copilot-sdk-driver.js +637 -0
  180. package/dist/smoke/copilot-sdk-driver.js.map +1 -0
  181. package/dist/smoke/copilot-sdk-scenarios.d.ts +200 -0
  182. package/dist/smoke/copilot-sdk-scenarios.d.ts.map +1 -0
  183. package/dist/smoke/copilot-sdk-scenarios.js +620 -0
  184. package/dist/smoke/copilot-sdk-scenarios.js.map +1 -0
  185. package/dist/smoke/copilot-session-env.d.ts +75 -0
  186. package/dist/smoke/copilot-session-env.d.ts.map +1 -0
  187. package/dist/smoke/copilot-session-env.js +154 -0
  188. package/dist/smoke/copilot-session-env.js.map +1 -0
  189. package/dist/smoke/copilot-session-eval.d.ts +90 -0
  190. package/dist/smoke/copilot-session-eval.d.ts.map +1 -0
  191. package/dist/smoke/copilot-session-eval.js +323 -0
  192. package/dist/smoke/copilot-session-eval.js.map +1 -0
  193. package/dist/smoke/copilot-smoke.d.ts +166 -0
  194. package/dist/smoke/copilot-smoke.d.ts.map +1 -0
  195. package/dist/smoke/copilot-smoke.js +688 -0
  196. package/dist/smoke/copilot-smoke.js.map +1 -0
  197. package/dist/smoke/process-utils.d.ts +47 -0
  198. package/dist/smoke/process-utils.d.ts.map +1 -0
  199. package/dist/smoke/process-utils.js +99 -0
  200. package/dist/smoke/process-utils.js.map +1 -0
  201. package/dist/team/__tests__/runtime-v2.monitor.test.js +35 -0
  202. package/dist/team/__tests__/runtime-v2.monitor.test.js.map +1 -1
  203. package/dist/team/__tests__/scaling.test.js +13 -0
  204. package/dist/team/__tests__/scaling.test.js.map +1 -1
  205. package/dist/team/__tests__/sdk-transport.runtime.test.d.ts +2 -0
  206. package/dist/team/__tests__/sdk-transport.runtime.test.d.ts.map +1 -0
  207. package/dist/team/__tests__/sdk-transport.runtime.test.js +96 -0
  208. package/dist/team/__tests__/sdk-transport.runtime.test.js.map +1 -0
  209. package/dist/team/__tests__/sdk-transport.test.d.ts +2 -0
  210. package/dist/team/__tests__/sdk-transport.test.d.ts.map +1 -0
  211. package/dist/team/__tests__/sdk-transport.test.js +650 -0
  212. package/dist/team/__tests__/sdk-transport.test.js.map +1 -0
  213. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts +2 -0
  214. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts.map +1 -0
  215. package/dist/team/__tests__/tmux-session.pane-env.test.js +52 -0
  216. package/dist/team/__tests__/tmux-session.pane-env.test.js.map +1 -0
  217. package/dist/team/api-interop.d.ts.map +1 -1
  218. package/dist/team/api-interop.js +8 -0
  219. package/dist/team/api-interop.js.map +1 -1
  220. package/dist/team/runtime-v2.d.ts +33 -0
  221. package/dist/team/runtime-v2.d.ts.map +1 -1
  222. package/dist/team/runtime-v2.js +338 -24
  223. package/dist/team/runtime-v2.js.map +1 -1
  224. package/dist/team/scaling.d.ts.map +1 -1
  225. package/dist/team/scaling.js +5 -0
  226. package/dist/team/scaling.js.map +1 -1
  227. package/dist/team/sdk-host.d.ts +48 -0
  228. package/dist/team/sdk-host.d.ts.map +1 -0
  229. package/dist/team/sdk-host.js +604 -0
  230. package/dist/team/sdk-host.js.map +1 -0
  231. package/dist/team/sdk-policy.d.ts +79 -0
  232. package/dist/team/sdk-policy.d.ts.map +1 -0
  233. package/dist/team/sdk-policy.js +186 -0
  234. package/dist/team/sdk-policy.js.map +1 -0
  235. package/dist/team/sdk-transport.d.ts +164 -0
  236. package/dist/team/sdk-transport.d.ts.map +1 -0
  237. package/dist/team/sdk-transport.js +266 -0
  238. package/dist/team/sdk-transport.js.map +1 -0
  239. package/dist/team/tmux-session.d.ts +7 -0
  240. package/dist/team/tmux-session.d.ts.map +1 -1
  241. package/dist/team/tmux-session.js +80 -18
  242. package/dist/team/tmux-session.js.map +1 -1
  243. package/dist/team/types.d.ts +2 -0
  244. package/dist/team/types.d.ts.map +1 -1
  245. package/dist/team/types.js.map +1 -1
  246. package/dist/tools/host-smoke-tool.d.ts +34 -0
  247. package/dist/tools/host-smoke-tool.d.ts.map +1 -0
  248. package/dist/tools/host-smoke-tool.js +133 -0
  249. package/dist/tools/host-smoke-tool.js.map +1 -0
  250. package/docs/CLAUDE.md +1 -1
  251. package/docs/DEVELOPERS.md +122 -0
  252. package/docs/HOOKS.md +34 -2
  253. package/docs/MIGRATION.md +194 -3
  254. package/docs/REFERENCE.md +95 -1
  255. package/docs/TOOLS.md +32 -1
  256. package/package.json +11 -2
  257. package/plugin.json +1 -1
  258. package/scripts/dev/repro-state-lock.mjs +1 -1
  259. package/scripts/lib/atomic-write.mjs +4 -2
  260. package/scripts/lib/state-lock.mjs +21 -12
  261. package/scripts/run.cjs +141 -18
  262. package/scripts/session-start.mjs +13 -0
  263. package/scripts/wiki-session-end.mjs +2 -1
  264. package/templates/hooks/lib/atomic-write.mjs +4 -2
package/docs/MIGRATION.md CHANGED
@@ -8,6 +8,8 @@ This guide covers all migration paths for oh-my-copilot. Find your current versi
8
8
 
9
9
  - [Unreleased: Team Instance Ownership](#unreleased-team-instance-ownership)
10
10
  - [Unreleased: Cancellation Scope](#unreleased-cancellation-scope)
11
+ - [v5.7.0 → v5.8.0: SDK Team Transport](#v570--v580-sdk-team-transport)
12
+ - [v5.6.2 → v5.7.0: Smoke Harness](#v562--v570-smoke-harness)
11
13
  - [v5.6.1 → v5.6.2: Postinstall Hook](#v561--v562-postinstall-hook)
12
14
  - [v5.5.0 → v5.6.1: Fork Upgrade Guide](#v550--v561-fork-upgrade-guide)
13
15
  - [v5.1.0 → v5.5.0: Fork Upgrade Guide](#v510--v550-fork-upgrade-guide)
@@ -122,6 +124,194 @@ locations and descendants of system temp/OS roots are never used as roots.
122
124
 
123
125
  ---
124
126
 
127
+ ## v5.7.0 → v5.8.0: SDK Team Transport
128
+
129
+ Fork **v5.8.0** adds an experimental headless SDK transport for `omg team`,
130
+ factory chains on Copilot, faster Copilot hooks and a Copilot smoke job in CI.
131
+ There is nothing to migrate: no config, command or skill is renamed or
132
+ removed, and every default is unchanged. The team transport stays `pane`, the
133
+ SessionEnd budget under Claude Code stays 300 ms, and the CI smoke job skips
134
+ without its secret.
135
+
136
+ - Update with `npm install -g oh-my-copilot@5.8.0` or
137
+ `copilot plugin update oh-my-copilot@omc`.
138
+
139
+ ### Adopting `--transport sdk`
140
+
141
+ The transport is experimental and opt-in. It needs `copilot` on PATH and the
142
+ optional SDK, installed the same way as for smoke tier 2:
143
+
144
+ ```bash
145
+ npm i -g @github/copilot-sdk --omit=optional --ignore-scripts
146
+ omg team 2:copilot --transport sdk "<task>"
147
+ ```
148
+
149
+ To opt in for every team, set it in `.copilot/omg.jsonc`:
150
+
151
+ ```jsonc
152
+ {
153
+ "team": {
154
+ "transport": "sdk",
155
+ "sdk": { "maxCreditsPerWorker": 10 }
156
+ }
157
+ }
158
+ ```
159
+
160
+ - **`team.sdk.maxCreditsPerWorker`** caps each worker (default 10, a
161
+ positive number; env `OMC_TEAM_SDK_MAX_CREDITS`). At the cap the host aborts
162
+ the turn, takes no more prompts, and `omg team status` shows the worker as
163
+ `capped`.
164
+ - **Permissions are a policy, not a sandbox.** There is no allow-all. The
165
+ host allows writes only in the worker's worktree and the team state root,
166
+ and MCP calls only to the plugin server. The shell check is a denylist: it
167
+ blocks team control other than `team api`, `omg smoke`, `tmux`/`psmux`,
168
+ `git push`, and the `shell(<prefix>)` entries of `workerDenyTools`, but a
169
+ command built indirectly, such as a script file or an alias, is not caught.
170
+ - **Excluded tools.** `task`, `run_dynamic_workflow`,
171
+ `dynamic_workflows_manage`, `write_agent`, `read_agent`, `list_agents`,
172
+ `search_code_subagent` and the plugin's `host_smoke` are removed from each
173
+ worker session.
174
+ - **Rejected at start:** `--auto-merge`, non-copilot workers, scaling, and an
175
+ explicitly assigned reviewer-contract role (`critic`, `code-reviewer`,
176
+ `security-reviewer`, `test-engineer`). Keep those teams on `pane`.
177
+
178
+ Details are in
179
+ [Headless SDK workers](./REFERENCE.md#headless-sdk-workers---transport-sdk-experimental).
180
+
181
+ ### Factory chains on Copilot
182
+
183
+ Software-factory chains now run past their first link on Copilot CLI. Before,
184
+ a Copilot link got a random session id (Copilot has no `--session-id`), its
185
+ SessionEnd found no ledger, and the chain stopped silently after one link.
186
+ Nothing to configure:
187
+
188
+ - The spawner (factory listener or SessionEnd worker) sets
189
+ `OMC_CHAIN_LINK=<link id>` on each Copilot link. SessionEnd resolves the
190
+ link as `OMC_CHAIN_LINK`, then the host session id, and trusts the variable
191
+ only when it names a `host: "copilot"` ledger bound to the ending session
192
+ by that link's SessionStart, so a nested session cannot end its parent's
193
+ link. A duplicate SessionEnd gets the same chain back. Claude links keep
194
+ `--session-id`.
195
+ - Copilot's SessionEnd reason `complete` now counts as success, so route keys
196
+ such as `success:*` match a finished Copilot link.
197
+ - Link ledgers record `chainLink`, `host`, `createdAt` and `parentLink`, and
198
+ each link's SessionEnd closes its ledger once (`closedAt`, `hostSessionId`,
199
+ `outcome`, `decision`). The watchdog skips a closed ledger only when it
200
+ advanced the chain or left a stop marker, and `omg factory status` lists
201
+ each chain's links.
202
+ - A ledger's `maxStageVisits` now applies to the whole chain, not just the
203
+ first link; values above 99 clamp to 99 (before, they fell back to 2 on
204
+ later links).
205
+ - Under a dev plugin root (`omg --plugin-dir`), Copilot links get
206
+ `--plugin-dir` too, and the SessionEnd worker forwards `OMC_PLUGIN_ROOT`,
207
+ unless the root overlaps the link's working directory.
208
+ - `omg smoke copilot --tier 2 --scenario chain` runs a real two-link chain
209
+ (about 2 premium requests). It is opt-in and not part of `all`.
210
+
211
+ Copilot fires `sessionEnd` after every turn, so a link that a Stop hook
212
+ continues into a second turn hands off after its first turn; the closed
213
+ ledger prevents a second hand-off. Ledgers written by an older version have
214
+ no `host` field: a Copilot link
215
+ spawned before the upgrade still ends its chain after one link. Guardrails
216
+ (serial lock, daily cap), `COPILOT_ALLOW_ALL=false` and the AFK permission
217
+ profile are unchanged. See
218
+ [Factory chains on Copilot](./REFERENCE.md#factory-chains-on-copilot).
219
+
220
+ ### Hook environment knobs
221
+
222
+ Under Copilot the SessionEnd foreground budget is now 1500 ms and the audited
223
+ generic hooks run in a Worker thread. Claude Code is unchanged. Two variables
224
+ tune this:
225
+
226
+ | Variable | Effect |
227
+ | --- | --- |
228
+ | `OMC_SESSION_END_BUDGET_MS` | Overrides the SessionEnd foreground budget on both hosts, an integer from 1 to 60000. Default 1500 under Copilot, 300 under Claude Code. |
229
+ | `OMC_COPILOT_HOOK_WORKER=0` | Turns off Worker routing under Copilot, so every generic hook takes the child path again. |
230
+
231
+ See [SessionEnd](./HOOKS.md#sessionend) in the hooks reference.
232
+
233
+ ### CI secret for the Copilot smoke job
234
+
235
+ The new `smoke` job in `.github/workflows/ci.yml` needs the repository secret
236
+ `COPILOT_GITHUB_TOKEN`, a fine-grained personal access token of a user with
237
+ an active Copilot subscription and the "Copilot Requests" account
238
+ permission. Without it the job passes with a skip notice. With it, PRs and
239
+ branch pushes run the free static checks, and pushed `v*` tags also run the
240
+ default scenarios, about 2 premium requests billed to that user. Setup is in
241
+ [DEVELOPERS.md](DEVELOPERS.md#tier-2-in-ci).
242
+
243
+ ---
244
+
245
+ ## v5.6.2 → v5.7.0: Smoke Harness
246
+
247
+ Fork **v5.7.0** adds a headless smoke harness and ports upstream
248
+ oh-my-claudecode `dev` 486b85bbb..bcaceb136. There is nothing to migrate: no
249
+ config, command or skill is renamed or removed.
250
+
251
+ - Update with `npm install -g oh-my-copilot@5.7.0` or
252
+ `copilot plugin update oh-my-copilot@omc`.
253
+
254
+ ### Adopting `omg smoke copilot` and `host_smoke`
255
+
256
+ Build first (`npm run build`); the MCP check spawns
257
+ `dist/mcp/standalone-server.js`.
258
+
259
+ | Tier | Command | Cost |
260
+ | --- | --- | --- |
261
+ | 0 | `omg smoke copilot --tier 0` | Free, no model call |
262
+ | 1 | `omg smoke copilot --tier 1` | 1 premium request |
263
+ | 2 static | `omg smoke copilot --sdk-static` | Free, no model call |
264
+ | 2 | `omg smoke copilot --tier 2 [--scenario all]` | About 1 premium request per scenario; the default `smoke` + `guardrail` costs about 2 |
265
+
266
+ `--max-credits` (default and minimum 30) caps a run. The CLI has no opt-in
267
+ gate. The MCP tool `host_smoke` has two, set as env vars on the MCP server:
268
+
269
+ - **`OMC_SMOKE_ALLOW_LIVE=1`** allows `{ "tier": 1 }` and tier 2 with
270
+ scenarios. `{ "tier": 2, "scenarios": [] }` runs the free static checks
271
+ without it.
272
+ - **`OMC_SMOKE_ALLOW_ANY_ROOT=1`** allows a `pluginRoot` other than the
273
+ server's own package root.
274
+
275
+ `OMC_DISABLE_TOOLS=smoke` hides the tool. From vitest, `npm run test:live`
276
+ runs tier 0 whenever the `copilot` binary resolves. It runs tier 1 only with
277
+ `OMC_LIVE_SMOKE=1` and the tier 2 scenarios only with `OMC_LIVE_SMOKE=2`.
278
+ Details are in
279
+ [DEVELOPERS.md](DEVELOPERS.md#headless-smoke-against-a-local-build).
280
+
281
+ ### Optional SDK for tier 2
282
+
283
+ `@github/copilot-sdk` is an optional peer dependency; installing
284
+ oh-my-copilot never pulls it in. Install it only if you want tier 2:
285
+
286
+ ```bash
287
+ npm i -g @github/copilot-sdk --omit=optional --ignore-scripts
288
+ ```
289
+
290
+ `--ignore-scripts` avoids a CMake source build on Windows. If a global
291
+ install still adds the SDK's bundled platform runtime (about 128 MB), delete
292
+ it; tier 2 always uses the installed `copilot` binary.
293
+
294
+ ### `OMC_HOOK_FAIL_CLOSED=1`
295
+
296
+ The variable is opt-in and unset by default. Without it, the Copilot hook
297
+ adapter turns a hook's non-zero exit into `0` plus one `[omg-hook]` stderr
298
+ line, and a hook that times out in `scripts/run.cjs` exits `0`, so hooks fail
299
+ open. With `OMC_HOOK_FAIL_CLOSED=1` the adapter keeps the hook's exit code,
300
+ and a hook timeout in `scripts/run.cjs` exits `124`. Exit `2` on
301
+ PermissionRequest means deny in both modes. The smoke harness sets the
302
+ variable for its own sessions.
303
+
304
+ ### Team env passthrough on POSIX (#4236)
305
+
306
+ `OMC_TEAM_WORKER_ENV_PASSTHROUGH` keeps the same configuration. On POSIX tmux
307
+ teams its values are no longer inlined into the worker pane's command line.
308
+ They are written to a private `0600` file in a `0700` directory, which the
309
+ pane's `/bin/sh` sources and removes before it execs the login shell. Nothing
310
+ changes for you, but the values no longer appear in `ps` output or tmux
311
+ `pane_start_command`.
312
+
313
+ ---
314
+
125
315
  ## v5.6.1 → v5.6.2: Postinstall Hook
126
316
 
127
317
  Fork **v5.6.2** ports upstream oh-my-claudecode `dev` 4280efb1f..486b85bbb.
@@ -240,9 +430,10 @@ it there.
240
430
 
241
431
  ### Known limitations
242
432
 
243
- - **Copilot factory chains stop after one link.** Copilot CLI has no
244
- `--session-id`, so the next link cannot be pinned; `omg factory status` and
245
- the watchdog report the stalled chain.
433
+ - **Copilot factory chains stop after one link** (fixed in v5.8.0, see
434
+ [Factory chains on Copilot](#factory-chains-on-copilot)).
435
+ Copilot CLI has no `--session-id`, so the next link cannot be pinned;
436
+ `omg factory status` and the watchdog report the stalled chain.
246
437
  - **git-guardrails under Copilot on Windows is not live-verified.** The hook
247
438
  is projected and unit-tested, but its PreToolUse payload has not been
248
439
  exercised in a live Copilot session on Windows. Plant a violation (for
package/docs/REFERENCE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Complete reference for oh-my-copilot. For quick start, see the main [README.md](../README.md).
4
4
 
5
- For v5.6.2, the plugin ships 20 agents, 61 skills, 21 command files, and one configured MCP server exposing exactly 55 tools.
5
+ For v5.8.0, the plugin ships 20 agents, 61 skills, 21 command files, and one configured MCP server exposing exactly 56 tools.
6
6
 
7
7
  ---
8
8
 
@@ -139,6 +139,7 @@ If both configurations exist, **project-scoped takes precedence** over global:
139
139
  | `OMC_FREE_MEMORY_THRESHOLD` | `256` | Free memory threshold in MB. Gate blocks operations when free memory falls below this. Set to negative value to disable. |
140
140
  | `OMC_MAX_SIBLING_SESSIONS` | `8` | Maximum concurrent OMC sessions before gating expensive operations. Set to negative value to disable. |
141
141
  | `OMC_HOST_LOAD_GATE_DISABLED` | _(unset)_ | Set to any value to disable the host load gate entirely. |
142
+ | `OMC_CHAIN_LINK` | _(set by the spawner)_ | Internal. The chain-link id of a Copilot factory link, set on the spawned `copilot` process by the factory listener or the SessionEnd worker. Do not set it yourself: SessionEnd only trusts it when it names a `host: "copilot"` ledger, and a finished link is a no-op. See [Factory chains on Copilot](#factory-chains-on-copilot). |
142
143
 
143
144
  #### Host Load Gate
144
145
 
@@ -609,12 +610,22 @@ omg ask claude --agent-prompt executor --prompt "create an implementation plan"
609
610
  ```bash
610
611
  omg team 2:codex "review auth flow"
611
612
  omg team status review-auth-flow
613
+ omg team status review-auth-flow --json
614
+ omg team status --json # auto-resolves the team name when exactly one team is active
612
615
  omg team shutdown review-auth-flow --force
613
616
  omg team api claim-task --input '{"team_name":"auth-review","task_id":"1","worker":"worker-1"}' --json
614
617
  ```
615
618
 
616
619
  Supported entrypoints: direct start (`omg team [N:agent] "<task>"`), `status`, `shutdown`, and `api`.
617
620
 
621
+ `team status [team-name] --json` prints the same status data as the human-readable
622
+ report (team/instance id, phase, workspace/worktree mode, worker list with worktree
623
+ metadata, SDK worker fields — provider liveness, state, turns, credits, premium
624
+ requests — task counts, and leader guidance) as a single JSON document instead of
625
+ text, with `ok: false` when no team state is found. `--json` can appear before or
626
+ after the team name and is never mistaken for one; when the team name is omitted
627
+ and exactly one team is active, it is resolved automatically.
628
+
618
629
  Startup reserves the team name for an immutable instance. Shutdown and job cleanup
619
630
  require matching instance and worker-launch evidence; `--force` skips graceful
620
631
  waits but does not bypass ownership checks. Missing or corrupt evidence preserves
@@ -676,6 +687,16 @@ Worker panes run unattended, so each provider launches with its own auto-approve
676
687
 
677
688
  - `disableExternalLLM` / `OMC_SECURITY=strict` allows only the current host CLI's workers. See [SECURITY.md](../SECURITY.md).
678
689
 
690
+ #### Headless SDK workers (`--transport sdk`, experimental)
691
+
692
+ `omg team 2:copilot --transport sdk "<task>"` (or `team.transport: "sdk"`) runs each copilot worker as a detached `omg team sdk-host` process that owns one `@github/copilot-sdk` session, with no pane and no multiplexer. It needs `@github/copilot-sdk` installed (`npm i -g @github/copilot-sdk --omit=optional --ignore-scripts`).
693
+
694
+ - **No allow-all.** The host answers every permission request. Writes are allowed only inside the worker's worktree and the team state root. Reads also allow the plugin root. MCP calls are allowed only to the plugin server.
695
+ - **The shell policy is a denylist, not a sandbox.** It rejects team control other than `team api`, `omg smoke`, `tmux`/`psmux` and `git push`, plus `shell(<prefix>)` entries of `workerDenyTools`, including quoted spellings such as `"omg" team shutdown`. A command built indirectly (a script file, an alias, `Invoke-Expression`, an encoded command) is not caught. Treat the write-path check, the credit cap and the per-worker `COPILOT_HOME` as the boundaries.
696
+ - **Sub-agent tools are removed**: `task`, `run_dynamic_workflow`, `dynamic_workflows_manage`, `write_agent`, `read_agent`, `list_agents`, `search_code_subagent` and the plugin's `host_smoke`.
697
+ - **Credit cap.** `team.sdk.maxCreditsPerWorker` (default 10, must be a positive number; env `OMC_TEAM_SDK_MAX_CREDITS`) is enforced by the host: at the cap it aborts the turn and takes no more prompts, and `omg team status` shows the worker as `capped` with a reassign recommendation.
698
+ - **Unsupported under sdk:** `--auto-merge`, non-copilot workers, scaling, dead-worker recovery, and an **explicitly** assigned reviewer role with a verdict contract (`critic`, `code-reviewer`, `security-reviewer`, `test-engineer` — set directly via a task's `role` or via `team.roleRouting`); startup rejects them. A contract role merely *inferred* from task text (with no explicit assignment) is not rejected: the role is dropped and the worker runs as a plain executor instead, since an SDK host never exits between turns and so could never have its verdict file read anyway.
699
+
679
700
  ### `omg session search`
680
701
 
681
702
  ```bash
@@ -732,6 +753,73 @@ omg lookout scan --strict # exit 1 when review is recommended (for scripts)
732
753
  - `--json` emits the full report; exit codes: `0` for every successful non-strict scan and for strict scans without a review-recommended verdict, `1` for `--strict` with a review-recommended verdict, `2` for a usage/scan error
733
754
  - High-risk verdicts pair with approval gates and checkpoints: `omg graph run --approval-mode remote --checkpoint`, `omg checkpoint create`
734
755
 
756
+ ### `omg factory`
757
+
758
+ `omg factory init` seeds the project route table `.omg/factory-routes.json`, `omg factory listen` runs the HMAC-verified tracker intake daemon, and `omg factory status [--json]` is the read-only chain audit. A chain is a series of headless AFK links: when a link's session ends, the SessionEnd hook routes its `outcome:reason` through the route table and the detached SessionEnd worker spawns the next link. Guardrails apply per link: one active link per intent (serial lock) and a daily cap of 10 (`OMC_DAILY_CHAIN_LIMIT`).
759
+
760
+ #### Factory chains on Copilot
761
+
762
+ Each link has a chain-link id and a ledger `.omg/state/factory/chain-<id>.json`, written by whoever spawns the link (the listener for the first link, the SessionEnd worker for later ones). The SessionEnd hook must find that ledger, so the link id has to reach the ending session:
763
+
764
+ - **Claude Code** pins it with `--session-id <id>`; the host session id is the link id.
765
+ - **Copilot CLI** has no `--session-id`, so the spawner sets `OMC_CHAIN_LINK=<id>` on the `copilot` process. Copilot passes its env to hook processes, and SessionEnd resolves the link as `OMC_CHAIN_LINK` first, then the host session id.
766
+
767
+ The link's own SessionStart binds the link to its host session: the first session that starts with `OMC_CHAIN_LINK` naming an open Copilot ledger records itself as `boundSessionId` (an exclusive `chain-<id>.json.bind` file). `OMC_CHAIN_LINK` is trusted at SessionEnd only when `chain-<id>.json` exists, records `host: "copilot"` and `chainLink: <id>`, is bound to the ending host session, and the ending session is not a Claude session (`CLAUDE_CODE_ENTRYPOINT` unset). A nested session that inherited the variable (a verify command's `copilot -p`, an MCP child, a team worker) therefore cannot end its parent's link. Otherwise SessionEnd falls back to the host session id and records `chain-link-rejected` in `chain-decisions.jsonl`. A link is decided once: a duplicate SessionEnd of the same host session gets the chain it enqueued back (`already-closed`, `replayed: enqueued`), so whichever hook's manifest wins still carries the chain, and the one job per host session spawns it once. A concurrent duplicate waits briefly for the deciding hook (`chain-<id>.json.claim`), then records `duplicate-session-end`. So setting the variable alone can neither inject a chain nor replay a finished link. If the hand-off fails after the decision, the ledger's `decision` is corrected to `manifest-unavailable`, `worker-spawn-failed`, or `enqueued-failed`, and the stored chain is dropped so it is never replayed. The detached SessionEnd worker never forwards `OMC_CHAIN_LINK`: the chain identity rides the durable manifest payload, and every spawned link gets its own value. `COPILOT_ALLOW_ALL` stays forced to `false` on every link, and the AFK permission profile is unchanged.
768
+
769
+ | Ledger field | Meaning |
770
+ |---|---|
771
+ | `chainLink`, `host`, `createdAt`, `parentLink` | Written at spawn: the link id, `copilot` or `claude`, and the link that spawned it. |
772
+ | `boundSessionId`, `boundAt` | Copilot only: the host session that started the link. |
773
+ | `closedAt`, `hostSessionId`, `endReason`, `outcome`, `decision`, `enqueuedChain` | Written once by the link's SessionEnd. The watchdog skips a closed ledger only when its decision advanced the chain or left a stop marker (`enqueued`, `chain-terminal`, `chain-loop-capped`, `human-gate`, `no-route` on failure); a link dropped by a guardrail, an invalid ledger, or an error is still flagged. |
774
+ | `maxStageVisits` | Stage-visit cap, carried from the first ledger to every later link (default 2, values above 99 clamp to 99). |
775
+
776
+ Copilot's SessionEnd reason for a finished `-p` link is `complete` (CLI 1.0.91), which counts as `success` alongside Claude's `other`, `prompt_input_exit`, and `logout`; route keys such as `success:*` match both hosts. When OMC runs from a dev plugin root (`omg --plugin-dir`, which sets `OMC_PLUGIN_ROOT`), Copilot links also get `--plugin-dir <root>`, and the SessionEnd worker forwards `OMC_PLUGIN_ROOT`, so every link loads the same plugin. The root is dropped when it overlaps the link's working directory, because an AFK link may write there and could rewrite the plugin's hooks; a chain over the plugin's own repo needs the plugin installed. On Windows a root containing `%` is dropped as well. Limitation: Copilot fires `sessionEnd` after every turn, also in `-p` mode, so a link that a Stop hook keeps going into a second turn hands off after its first turn. The closed ledger stops later turns from enqueuing again, but the next link may start while this one still runs. A single-prompt link is not affected. `omg factory status` lists each intent's links in spawn order with host, link id, and closeout, for example `环 2 link-2 [copilot] link=<id> 已结束 success→chain-loop-capped session=<host session>`.
777
+
778
+ ### `omg smoke copilot`
779
+
780
+ Headless smoke check of a plugin root against the real GitHub Copilot CLI, in a throwaway `COPILOT_HOME`. Use it on a local build before opening a PR or tagging a release.
781
+
782
+ ```bash
783
+ omg smoke copilot # tier 0: load check, no model call
784
+ omg smoke copilot --tier 1 # tier 0 + one live session, prompt on stdin (one premium request)
785
+ omg smoke copilot --plugin-root /path/to/oh-my-copilot --json
786
+ omg smoke copilot --tier 1 --keep-home --delegate --model <id> --max-credits 30 --timeout 180000
787
+ omg smoke copilot --sdk-static # tier 2 SDK static checks only, no model call
788
+ omg smoke copilot --tier 2 # tier 2 default scenarios smoke,guardrail (~2 premium requests, 1 per scenario)
789
+ omg smoke copilot --tier 2 --scenario skill,delegate # or --scenario all
790
+ omg smoke copilot --tier 2 --scenario chain # opt-in: a real two-link factory chain (~2 premium requests)
791
+ ```
792
+
793
+ - **Tier 0** checks: `plugin.manifest`, `plugin.hooks`, `copilot.binary`, `copilot.plugin_list`, `copilot.skill_list`, `copilot.agents`, `mcp.list_tools`. `mcp.list_tools` spawns `dist/mcp/standalone-server.js`, so run `npm run build` first.
794
+ - **Tier 1** adds `session.exit`, `session.events`, `hooks.*` (including `hooks.adapter_errors`), `plugins.loaded`, `mcp.server_loaded`, `state.written`, and with `--delegate` `subagent.selected`. Hooks run with `OMC_HOOK_FAIL_CLOSED=1`; a `hook.end` without `success: true` fails its check, and `hooks.adapter_errors` fails on any `[omg-hook]` error line.
795
+ - **Tier 2** drives the installed `copilot` binary through the optional peer `@github/copilot-sdk` (install it with `npm i -g @github/copilot-sdk --omit=optional --ignore-scripts`; a nested `@github/copilot-sdk-<platform>` runtime package is unused and can be deleted, see [DEVELOPERS.md](./DEVELOPERS.md#tier-2--sdk-scenarios)). It runs the tier 0 checks, then the SDK static checks `sdk.available`, `sdk.runtime`, `sdk.plugins`, `sdk.skills`, `sdk.agents`, `sdk.mcp`, `sdk.tools_excluded` (our `<server>-host_smoke` tool is absent from the session's tool list), then the chosen scenarios. It does not run the tier 1 `-p` session. `--sdk-static` runs no scenario and makes no model call.
796
+ - **Tier 2 scenarios** yield checks prefixed `scn.<name>.`. Each one also gets `scn.<name>.exit`, `scn.<name>.adapter_errors` (fails on any `[omg-hook]` or `[run.cjs]` hook stderr line, such as a hook timeout), and `scn.<name>.cost`. Each scenario costs about one premium request, so the default run costs about two.
797
+
798
+ | Scenario | Prompt exercises | Key checks |
799
+ |---|---|---|
800
+ | `smoke` (default) | a plain reply, no tools | `scn.smoke.reply`, `scn.smoke.hooks`, `scn.smoke.no_tools` |
801
+ | `guardrail` (default) | `git push --force` with `OMC_GIT_GUARDRAILS=1` against a bare remote | `scn.guardrail.denied`, `scn.guardrail.no_push`, `scn.guardrail.hooks` |
802
+ | `skill` | `/oh-my-copilot:plan` | `scn.skill.invoked` |
803
+ | `delegate` | a hand-off to `oh-my-copilot:architect` | `scn.delegate.selected`, `scn.delegate.hooks`, `scn.delegate.completed` |
804
+ | `chain` (opt-in, never in `all`) | two real `copilot -p` factory links: link 1 replies with one token, link 2 runs the project skill `/chain-ack` | `scn.chain.link1`, `scn.chain.spawned`, `scn.chain.inherited`, `scn.chain.closed`, `scn.chain.premium` |
805
+
806
+ - **`chain` scenario.** It does not use an SDK session. It seeds the sandbox with `omg factory init` plus a `success:*` route to stage `link-2`, pre-writes link 1's ledger, and spawns link 1 like a factory link (AFK profile, `--plugin-dir`, `OMC_CHAIN_LINK`). The chain then runs itself through the SessionEnd hook and worker, and stops at the stage-visit cap after link 2. The checks read the ledgers, decisions, stop marker, and both link sessions' `events.jsonl`: link 1 was resolved by `OMC_CHAIN_LINK`, link 2 was spawned and ended under its own inherited identity, the chain closed out (`loop-capped:link-2`, no open ledger), and both links together cost at most 2 premium requests. The links run on the CLI's default model without `--max-ai-credits`, because a factory link's argv carries no credit cap, so `--max-credits` does not bound them. `--timeout` applies to link 1 and again to the wait for link 2.
807
+
808
+ - **Tier 2 report** adds `sdk` (SDK version, runtime version, protocol version), `cost` (premium requests and credits summed over scenarios), and `artifacts.events` with one events JSONL path per scenario. Human output groups the `sdk.*` checks and each scenario's `scn.<name>.*` checks under a heading.
809
+ - **Model:** `--model` is optional. Copilot auto-selects when it is omitted (1.0.91 picked `mai-code-1.1-flash`); explicit ids may be rejected as unavailable.
810
+ - **Auth:** tier 1 reuses the login identity from your Copilot config, so `copilot /login` is enough. When a login is found the session drops `GH_TOKEN` and `GITHUB_TOKEN`, which would otherwise shadow it. `COPILOT_GITHUB_TOKEN` always passes through, and without a stored login (CI) all three do.
811
+ - `--max-credits` must be at least 30, the Copilot CLI minimum. At tier 1 it is passed as `--max-ai-credits`. At tier 2 it caps the whole run: the runtime also gets `--max-ai-credits`, a scenario whose credits pass what is left is aborted, and the remaining scenarios fail with `skipped: credit cap reached`. `--model` applies to tiers 1 and 2. `--timeout` applies per subprocess at tier 0, to the session at tier 1, and per scenario at tier 2; the MCP check is fixed at 10 s.
812
+ - Under a test runner (`VITEST` or `NODE_ENV=test`), a run that would call a model is refused with one `cli.guard` check before anything is spawned, unless `OMC_LIVE_SMOKE=1` (tier 1) or `OMC_LIVE_SMOKE=2` (tier 2 scenarios) is set.
813
+ - Exit codes: `0` all checks pass, `1` a check failed or an option was invalid, `2` skipped because the `copilot` binary or, at tier 2, `@github/copilot-sdk` did not resolve and nothing else failed. Resolution order is PATH, then `COPILOT_CLI_PATH`, then the WinGet install location on Windows, so a `copilot` on PATH wins over `COPILOT_CLI_PATH`.
814
+ - `--json` always prints a `SmokeReport`, with one `cli.error` check for invalid options. `--keep-home` keeps the temp homes and project and reports them as `artifacts.listHome` (tier 0) and `artifacts.copilotHome` (tier 1).
815
+ - The same check is exposed as the MCP tool `host_smoke` (arguments `tier`, `pluginRoot`, `model`, `maxCredits`, `timeoutMs`, `keepHome`, `delegate`, `scenarios`). It returns the report JSON with `isError` set when any check fails. Tier 1 and tier 2 with scenarios need `OMC_SMOKE_ALLOW_LIVE=1` in the MCP server env; tier 2 with `scenarios: []` is free and needs no opt-in, and a `pluginRoot` other than the server's own package root needs `OMC_SMOKE_ALLOW_ANY_ROOT=1`. To call it from Claude Code against a local build, register the standalone server:
816
+
817
+ ```bash
818
+ claude mcp add omg-dev -- node C:/Code/OMC/dist/mcp/standalone-server.js
819
+ ```
820
+
821
+ `OMC_DISABLE_TOOLS=smoke` hides `host_smoke`. See [DEVELOPERS.md](./DEVELOPERS.md#headless-smoke-against-a-local-build) for the developer workflow.
822
+
735
823
  ### Graph approval gates (remote approvals)
736
824
 
737
825
  Graph runtime `human-approval` nodes support two gate styles via `omg graph run`:
@@ -1438,6 +1526,12 @@ From a shell, `omg doctor conflicts` checks for configuration conflicts. Among i
1438
1526
  - **Legacy `COPILOT_CONFIG_DIR`**: warns when it is set. OMC reads only `COPILOT_HOME`, Copilot CLI's own variable; rename it.
1439
1527
  - **`node` on PATH**: every hook runs `node`, and under Copilot a PreToolUse hook that cannot start denies the tool call, so a missing `node` blocks every tool.
1440
1528
 
1529
+ `omg doctor conflicts --json` prints the full conflict report (every check above, plus
1530
+ `hasConflicts`) as a single JSON document on stdout instead of the coloured human
1531
+ report; the human report is unchanged when `--json` is omitted. `--json` and
1532
+ `--plugin-dir` work both on `omg doctor` (flag form, e.g. `omg doctor --team-routing
1533
+ --json`) and on `omg doctor conflicts`/`omg doctor team-routing` directly.
1534
+
1441
1535
  ### Configure HUD Statusline
1442
1536
 
1443
1537
  ```bash
package/docs/TOOLS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > OMC provides MCP tools for state management, code intelligence, and data analysis.
4
4
 
5
- At v5.3.0, the configured MCP server exposes exactly 55 tools.
5
+ The configured MCP server exposes exactly 56 tools.
6
6
 
7
7
  Unlike skills that users invoke directly, tools are used internally by agents during task execution.
8
8
 
@@ -19,6 +19,7 @@ Unlike skills that users invoke directly, tools are used internally by agents du
19
19
  - [Shared Memory](#shared-memory) — Cross-agent shared memory for team coordination
20
20
  - [Skills](#skills) — Internal skill management tools
21
21
  - [Deepinit Manifest](#deepinit-manifest) — Incremental AGENTS.md regeneration manifest
22
+ - [Host Smoke](#host-smoke) — Headless Copilot CLI smoke check of a plugin root
22
23
 
23
24
  ---
24
25
 
@@ -768,3 +769,33 @@ deepinit_manifest(action="check")
768
769
  ```
769
770
 
770
771
  Used internally by the `deepinit` skill (`/oh-my-copilot:deepinit`) to enable incremental AGENTS.md regeneration instead of full re-scans.
772
+
773
+ ---
774
+
775
+ ## Host Smoke
776
+
777
+ Runs the same check as `omg smoke copilot` against a plugin root and returns the `SmokeReport` JSON. The result has `isError` set when any check fails. `OMC_DISABLE_TOOLS=smoke` hides it.
778
+
779
+ ### Tool
780
+
781
+ #### `host_smoke`
782
+
783
+ | Argument | Type | Meaning |
784
+ | ------------ | ------- | ----------------------------------------------------------------------- |
785
+ | `tier` | 0, 1 or 2 | `0` load check without a model call (default); `1` adds one live session, one premium request; `2` adds `@github/copilot-sdk` static checks and SDK scenarios. Tier 1 needs `OMC_SMOKE_ALLOW_LIVE=1` in the server env |
786
+ | `scenarios` | string[] | Tier 2 only: `smoke`, `guardrail`, `skill`, `delegate`, or `all`. Omitted runs `smoke` and `guardrail`, about two premium requests (one per scenario), and needs `OMC_SMOKE_ALLOW_LIVE=1`. `[]` runs the SDK static checks only, with no model call and no opt-in |
787
+ | `pluginRoot` | string | Plugin root passed to `--plugin-dir`; defaults to the package root. Any other root needs `OMC_SMOKE_ALLOW_ANY_ROOT=1` |
788
+ | `model` | string | Tiers 1-2 model id; omitted means Copilot auto-selects at tier 1 and a cheap listed model, else auto, at tier 2 |
789
+ | `maxCredits` | integer | Tiers 1-2 AI credit cap, default and minimum 30. Tier 1 passes `--max-ai-credits`; tier 2 also aborts a scenario and skips the rest once the run passes it |
790
+ | `timeoutMs` | integer | Per subprocess at tier 0 (default 60000), for the session at tier 1 (default 180000), or per scenario at tier 2 (default 120000); the MCP check is fixed at 10 s |
791
+ | `keepHome` | boolean | Keep the temp `COPILOT_HOME` and project; paths are in `artifacts` |
792
+ | `delegate` | boolean | Tier 1: also exercise one delegation to `oh-my-copilot:architect` |
793
+
794
+ ```
795
+ host_smoke(tier=0)
796
+ host_smoke(tier=2, scenarios=[]) # SDK static checks, free
797
+ ```
798
+
799
+ Tier 2 needs the optional peer `@github/copilot-sdk` next to the package. Without it the `sdk.*` checks fail with an install hint and the report is marked `skipped`.
800
+
801
+ See [DEVELOPERS.md](./DEVELOPERS.md#headless-smoke-against-a-local-build) for the check list and how to register a local build in Claude Code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-copilot",
3
- "version": "5.6.2",
3
+ "version": "5.8.0",
4
4
  "description": "Multi-agent orchestration system for Copilot CLI - Inspired by oh-my-opencode",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -74,6 +74,7 @@
74
74
  "test:ui": "vitest --ui --exclude \"tests/perf/**\"",
75
75
  "test:run": "vitest run --exclude \"tests/perf/**\"",
76
76
  "test:coverage": "vitest run --coverage --exclude \"tests/perf/**\"",
77
+ "test:live": "vitest run --config vitest.live.config.ts",
77
78
  "prepack": "npm run build",
78
79
  "lint": "eslint src",
79
80
  "format": "prettier --write src/**/*.ts",
@@ -131,6 +132,14 @@
131
132
  "typescript-eslint": "^8.53.0",
132
133
  "vitest": "^4.1.11"
133
134
  },
135
+ "peerDependencies": {
136
+ "@github/copilot-sdk": "^1.0.16"
137
+ },
138
+ "peerDependenciesMeta": {
139
+ "@github/copilot-sdk": {
140
+ "optional": true
141
+ }
142
+ },
134
143
  "engines": {
135
144
  "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
136
145
  },
@@ -159,5 +168,5 @@
159
168
  "publishConfig": {
160
169
  "access": "public"
161
170
  },
162
- "gitHead": "dff284086ef69f7bd488ba13346e66a90180d671"
171
+ "gitHead": "a63b41dee653b2fad61a2fdd840a24dd2350ffc5"
163
172
  }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-copilot",
3
- "version": "5.6.2",
3
+ "version": "5.8.0",
4
4
  "description": "Multi-agent orchestration system for GitHub Copilot CLI",
5
5
  "author": {
6
6
  "name": "oh-my-copilot contributors"
@@ -1,4 +1,4 @@
1
- // Repro for upstream issue #4146: mutual exclusion of the owner-file fallback in
1
+ // Repro for issue #4146: mutual exclusion of the owner-file fallback in
2
2
  // scripts/lib/state-lock.mjs (owner releases/exits during a reclaimer's liveness
3
3
  // probe; the reclaimer must not quarantine a live replacement owner).
4
4
  //
@@ -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
 
@@ -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);