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
@@ -0,0 +1,34 @@
1
+ /**
2
+ * host_smoke MCP tool
3
+ *
4
+ * Runs the headless Copilot CLI smoke check (`omg smoke copilot`) against a
5
+ * plugin root and returns the SmokeReport as JSON. Tier 0 makes no model call;
6
+ * tier 1 spends one premium request; tier 2 drives SDK scenarios (about one
7
+ * premium request each) and is free only with `scenarios: []` (static checks).
8
+ *
9
+ * This tool is also reachable from inside Copilot through the plugin's own `t`
10
+ * MCP server, so it is fenced (the `omg smoke copilot` CLI is not):
11
+ * - `pluginRoot` must be the package root this server runs from (compared by
12
+ * realpath) unless OMC_SMOKE_ALLOW_ANY_ROOT=1;
13
+ * - tier 1 (a live, billed session) and tier 2 with any scenario need
14
+ * OMC_SMOKE_ALLOW_LIVE=1; tier 2 with `scenarios: []` is free and ungated.
15
+ *
16
+ * The smoke library is loaded lazily so listing tools never pays its import cost.
17
+ */
18
+ import { z } from 'zod';
19
+ import type { ToolDefinition } from './types.js';
20
+ export declare const SMOKE_ALLOW_LIVE_ENV = "OMC_SMOKE_ALLOW_LIVE";
21
+ export declare const SMOKE_ALLOW_ANY_ROOT_ENV = "OMC_SMOKE_ALLOW_ANY_ROOT";
22
+ declare const hostSmokeSchema: {
23
+ tier: z.ZodOptional<z.ZodNumber>;
24
+ scenarios: z.ZodOptional<z.ZodArray<z.ZodEnum<["smoke", "guardrail", "skill", "delegate", "all"]>, "many">>;
25
+ pluginRoot: z.ZodOptional<z.ZodString>;
26
+ model: z.ZodOptional<z.ZodString>;
27
+ maxCredits: z.ZodOptional<z.ZodNumber>;
28
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
29
+ keepHome: z.ZodOptional<z.ZodBoolean>;
30
+ delegate: z.ZodOptional<z.ZodBoolean>;
31
+ };
32
+ export declare const hostSmokeTool: ToolDefinition<typeof hostSmokeSchema>;
33
+ export {};
34
+ //# sourceMappingURL=host-smoke-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-smoke-tool.d.ts","sourceRoot":"","sources":["../../src/tools/host-smoke-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAC3D,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;AAMnE,QAAA,MAAM,eAAe;;;;;;;;;CA4CpB,CAAC;AAYF,eAAO,MAAM,aAAa,EAAE,cAAc,CAAC,OAAO,eAAe,CA0DhE,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * host_smoke MCP tool
3
+ *
4
+ * Runs the headless Copilot CLI smoke check (`omg smoke copilot`) against a
5
+ * plugin root and returns the SmokeReport as JSON. Tier 0 makes no model call;
6
+ * tier 1 spends one premium request; tier 2 drives SDK scenarios (about one
7
+ * premium request each) and is free only with `scenarios: []` (static checks).
8
+ *
9
+ * This tool is also reachable from inside Copilot through the plugin's own `t`
10
+ * MCP server, so it is fenced (the `omg smoke copilot` CLI is not):
11
+ * - `pluginRoot` must be the package root this server runs from (compared by
12
+ * realpath) unless OMC_SMOKE_ALLOW_ANY_ROOT=1;
13
+ * - tier 1 (a live, billed session) and tier 2 with any scenario need
14
+ * OMC_SMOKE_ALLOW_LIVE=1; tier 2 with `scenarios: []` is free and ungated.
15
+ *
16
+ * The smoke library is loaded lazily so listing tools never pays its import cost.
17
+ */
18
+ import { realpathSync } from 'fs';
19
+ import { resolve } from 'path';
20
+ import { z } from 'zod';
21
+ import { TOOL_CATEGORIES } from '../constants/names.js';
22
+ export const SMOKE_ALLOW_LIVE_ENV = 'OMC_SMOKE_ALLOW_LIVE';
23
+ export const SMOKE_ALLOW_ANY_ROOT_ENV = 'OMC_SMOKE_ALLOW_ANY_ROOT';
24
+ /** Tier 2 scenario names (mirrors the CLI's `--scenario`); `all` expands to the four. */
25
+ const SCENARIO_NAMES = ['smoke', 'guardrail', 'skill', 'delegate'];
26
+ const hostSmokeSchema = {
27
+ tier: z
28
+ .number()
29
+ .int()
30
+ .min(0)
31
+ .max(2)
32
+ .optional()
33
+ .describe('0 = load check, no model call (default). 1 = tier 0 plus one live session with the prompt on stdin '
34
+ + '(one premium request). 2 = tier 0 plus @github/copilot-sdk static checks plus scenarios. '
35
+ + 'Tier 1 and tier 2 with scenarios require OMC_SMOKE_ALLOW_LIVE=1 in the MCP server env.'),
36
+ scenarios: z
37
+ .array(z.enum([...SCENARIO_NAMES, 'all']))
38
+ .optional()
39
+ .describe('Tier 2 only: scenarios to run (smoke, guardrail, skill, delegate, or all). Default smoke+guardrail '
40
+ + '(~2 premium requests, 1 per scenario). [] = SDK static checks only, no model call, no opt-in needed.'),
41
+ pluginRoot: z
42
+ .string()
43
+ .optional()
44
+ .describe('Plugin root to load with --plugin-dir. Defaults to, and must equal, the running package root unless OMC_SMOKE_ALLOW_ANY_ROOT=1.'),
45
+ model: z.string().optional().describe('Tiers 1-2: model id (default: tier 1 lets the CLI auto-pick; tier 2 a cheap listed model, else auto).'),
46
+ maxCredits: z
47
+ .number()
48
+ .int()
49
+ .min(30)
50
+ .optional()
51
+ .describe('Tiers 1-2: AI credit cap (default and CLI minimum 30). Tier 2 also aborts a scenario and skips the rest once the run passes it.'),
52
+ timeoutMs: z
53
+ .number()
54
+ .int()
55
+ .positive()
56
+ .optional()
57
+ .describe('Timeout in ms per subprocess at tier 0 (default 60000), for the session at tier 1 (default 180000), per scenario at tier 2 (default 120000, then abort); the MCP check is fixed at 10 s.'),
58
+ keepHome: z
59
+ .boolean()
60
+ .optional()
61
+ .describe('Keep the throwaway COPILOT_HOME and project dir for debugging; paths are returned in artifacts.'),
62
+ delegate: z
63
+ .boolean()
64
+ .optional()
65
+ .describe('Tier 1 only: ask for one delegation to oh-my-copilot:architect to exercise subagent.selected.'),
66
+ };
67
+ function canonical(p) {
68
+ let out = resolve(p);
69
+ try {
70
+ out = realpathSync.native(out);
71
+ }
72
+ catch { /* missing: compare the resolved form */ }
73
+ return process.platform === 'win32' ? out.toLowerCase() : out;
74
+ }
75
+ function errorResult(text) {
76
+ return { content: [{ type: 'text', text }], isError: true };
77
+ }
78
+ export const hostSmokeTool = {
79
+ name: 'host_smoke',
80
+ description: 'Run the headless Copilot CLI smoke check against a plugin root (same as `omg smoke copilot`). ' +
81
+ 'Tier 0 verifies the plugin loads (manifest, hooks, plugin/skill list, MCP tools) without a model call; ' +
82
+ 'tier 1 adds one live session (one premium request, needs OMC_SMOKE_ALLOW_LIVE=1); ' +
83
+ 'tier 2 adds @github/copilot-sdk static checks (free with scenarios: []) and SDK scenarios (need OMC_SMOKE_ALLOW_LIVE=1). ' +
84
+ 'Returns the SmokeReport JSON; isError when any check fails.',
85
+ category: TOOL_CATEGORIES.SMOKE,
86
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
87
+ schema: hostSmokeSchema,
88
+ handler: async (args) => {
89
+ try {
90
+ const tier = (args.tier ?? 0);
91
+ if (args.scenarios !== undefined && tier !== 2) {
92
+ return errorResult('host_smoke: scenarios apply to tier 2 only.');
93
+ }
94
+ const scenarios = args.scenarios === undefined
95
+ ? undefined
96
+ : [...new Set(args.scenarios.flatMap((s) => (s === 'all' ? [...SCENARIO_NAMES] : [s])))];
97
+ // Tier 2 without `scenarios` runs the library default (smoke, guardrail): live.
98
+ const live = tier === 1 || (tier === 2 && (scenarios === undefined || scenarios.length > 0));
99
+ if (live && process.env[SMOKE_ALLOW_LIVE_ENV] !== '1') {
100
+ return errorResult(`host_smoke: tier ${tier}${tier === 2 ? ' with scenarios' : ''} runs live, billed Copilot sessions and is disabled for MCP callers. `
101
+ + `Set ${SMOKE_ALLOW_LIVE_ENV}=1 in the MCP server env, pass scenarios: [] for the free tier 2 static checks, `
102
+ + `or run \`omg smoke copilot --tier ${tier}\` from a shell.`);
103
+ }
104
+ const { runCopilotSmoke, resolvePackageRoot } = await import('../smoke/copilot-smoke.js');
105
+ if (args.pluginRoot !== undefined && process.env[SMOKE_ALLOW_ANY_ROOT_ENV] !== '1') {
106
+ const packageRoot = resolvePackageRoot();
107
+ if (!packageRoot || canonical(args.pluginRoot) !== canonical(packageRoot)) {
108
+ return errorResult(`host_smoke: pluginRoot ${JSON.stringify(args.pluginRoot)} is not this server's package root `
109
+ + `(${packageRoot ?? 'unresolved'}). Set ${SMOKE_ALLOW_ANY_ROOT_ENV}=1 to smoke another root.`);
110
+ }
111
+ }
112
+ const opts = {
113
+ tier,
114
+ ...(args.pluginRoot !== undefined ? { pluginRoot: args.pluginRoot } : {}),
115
+ ...(args.model !== undefined ? { model: args.model } : {}),
116
+ ...(args.maxCredits !== undefined ? { maxCredits: args.maxCredits } : {}),
117
+ ...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),
118
+ ...(args.keepHome !== undefined ? { keepHome: args.keepHome } : {}),
119
+ ...(args.delegate !== undefined ? { delegate: args.delegate } : {}),
120
+ ...(scenarios !== undefined ? { scenarios } : {}),
121
+ };
122
+ const report = await runCopilotSmoke(opts);
123
+ return {
124
+ content: [{ type: 'text', text: JSON.stringify(report, null, 2) }],
125
+ ...(report.ok ? {} : { isError: true }),
126
+ };
127
+ }
128
+ catch (error) {
129
+ return errorResult(`host_smoke failed: ${error instanceof Error ? error.message : String(error)}`);
130
+ }
131
+ },
132
+ };
133
+ //# sourceMappingURL=host-smoke-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-smoke-tool.js","sourceRoot":"","sources":["../../src/tools/host-smoke-tool.ts"],"names":[],"mappings":"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"}
package/docs/CLAUDE.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <!-- OMC:START -->
2
- <!-- OMC:VERSION:5.6.2 -->
2
+ <!-- OMC:VERSION:5.7.0 -->
3
3
 
4
4
  # oh-my-copilot - Intelligent Multi-Agent Orchestration
5
5
 
@@ -1,3 +1,104 @@
1
1
  # Developers
2
2
 
3
3
  See [CONTRIBUTING.md](../CONTRIBUTING.md) for the full developer guide.
4
+
5
+ ## Headless smoke against a local build
6
+
7
+ `omg smoke copilot` loads a plugin root into the real GitHub Copilot CLI inside a throwaway `COPILOT_HOME` and reports one line per check. It replaces the manual "install the plugin and open Copilot" step. Build first: the MCP check spawns `dist/mcp/standalone-server.js`.
8
+
9
+ ```bash
10
+ npm run build
11
+ omg smoke copilot --tier 0 # free: no model call
12
+ omg smoke copilot --tier 1 # costs one premium request
13
+ ```
14
+
15
+ ### The two tiers
16
+
17
+ - **Tier 0 is a free load check.** It verifies the manifest and version, the generated hooks and agents, `plugin list` and `skill list` as Copilot sees them, and the MCP server's tool count. Run it after every generator change.
18
+ - **Tier 1 adds one live session with the prompt on stdin.** It asserts the session exits cleanly, the session events log parses, our hooks fired, the plugin and MCP server loaded, and the hooks wrote state under `.omg/` in a temp project. Each run is one premium request capped by `--max-credits` (default 30, which is Copilot's minimum).
19
+
20
+ **Model.** `--model` is optional. Without it Copilot auto-selects a model; 1.0.91 picked `mai-code-1.1-flash`. Explicit ids may be rejected as unavailable for your account, and the `session.exit` detail then says to omit `--model`.
21
+
22
+ **Auth.** Tier 1 copies only the login identity from your real Copilot config, so a normal `copilot /login` is enough. Do not export a token for it. `GH_TOKEN` and `GITHUB_TOKEN` take precedence over a stored login, and a `gh` token is not entitled to Copilot models, so the smoke drops both from the session when it found a login. `COPILOT_GITHUB_TOKEN` is always passed through. Without a stored login, as in CI, all three pass through and one of them must hold a Copilot-entitled token.
23
+
24
+ **Hooks fail closed.** The session runs with `OMC_HOOK_FAIL_CLOSED=1`, so a crashing hook shows up as a failed `hooks.*` check instead of being swallowed by the hook adapter. The `hooks.adapter_errors` check also fails on any `[omg-hook]` error line in the debug log, session stderr, events, or stdout. With `OMC_HOOK_FAIL_CLOSED=1` a hook timeout in `scripts/run.cjs` also exits `124` instead of failing open.
25
+
26
+ **Sandbox.** The session denies `t(host_smoke)` so the model cannot recurse into the smoke, and passes `--disable-builtin-mcps`. The default prompt also denies `shell` and `write`; `--delegate` keeps them because the hand-off is a tool call. The env drops `OMC_*`, Claude Code host markers, `NODE_OPTIONS`, `COPILOT_MODEL`, `COPILOT_OFFLINE`, and `COPILOT_PROVIDER_*` before spawning Copilot.
27
+
28
+ Use `--keep-home` to keep the temp homes and project for debugging, and `--json` for the full report. The report's `artifacts.listHome` is the tier 0 home and `artifacts.copilotHome` is the tier 1 session home. `--json` always prints a report; a bad option yields `ok: false` with one `cli.error` check. Exit code `2` means the `copilot` binary was not found and nothing else failed. The binary resolves from PATH first, then `COPILOT_CLI_PATH`, then the WinGet install location on Windows. Set `COPILOT_CLI_PATH` when it is not on PATH; a `copilot` on PATH still wins.
29
+
30
+ ### From vitest
31
+
32
+ `npm run test:live` runs `tests/live/copilot-smoke.test.ts`. Tier 0 runs whenever the binary resolves and skips otherwise. Tier 1 runs only with `OMC_LIVE_SMOKE=1`, so it never spends a request by accident. The free tier 2 static checks run whenever the binary and `@github/copilot-sdk` resolve. The tier 2 default scenarios run only with `OMC_LIVE_SMOKE=2`. The default `npm test` run and CI exclude `tests/live/**`.
33
+
34
+ ### From Claude Code (MCP tool `host_smoke`)
35
+
36
+ The standalone MCP server exposes the same check as `host_smoke`. Register the local build once:
37
+
38
+ ```bash
39
+ claude mcp add omg-dev -- node C:/Code/OMC/dist/mcp/standalone-server.js
40
+ ```
41
+
42
+ Then ask Claude Code to call `host_smoke` with `{ "tier": 0 }`. It returns the report JSON and sets `isError` when any check fails. Rebuild after source changes, because the server runs from `dist/`.
43
+
44
+ The tool is also reachable from inside Copilot through the plugin's own MCP server, so it is fenced. Two env vars on the MCP server lift the fences:
45
+
46
+ - **`OMC_SMOKE_ALLOW_LIVE=1`** allows `{ "tier": 1 }` and tier 2 with scenarios. Without it the tool refuses both, because they are billed sessions. `{ "tier": 2, "scenarios": [] }` runs the free SDK static checks and needs no opt-in.
47
+ - **`OMC_SMOKE_ALLOW_ANY_ROOT=1`** allows a `pluginRoot` other than the package root the server runs from. Paths are compared after resolving symlinks.
48
+
49
+ ```bash
50
+ claude mcp add omg-dev -e OMC_SMOKE_ALLOW_LIVE=1 -- node C:/Code/OMC/dist/mcp/standalone-server.js
51
+ ```
52
+
53
+ The `omg smoke copilot` CLI has no such gate.
54
+
55
+ ### Tier 2 — SDK scenarios
56
+
57
+ Tier 2 drives Copilot through `@github/copilot-sdk` instead of `copilot -p`. The SDK sees the full event stream and the runtime's own plugin, skill, agent, and MCP lists, so it can assert behaviour that tier 1 cannot: a guardrail deny, a skill invocation, a delegation.
58
+
59
+ ```bash
60
+ npm i -g @github/copilot-sdk --omit=optional --ignore-scripts
61
+ omg smoke copilot --sdk-static # free: SDK static checks, no model call
62
+ omg smoke copilot --tier 2 # smoke + guardrail, about 2 premium requests (1 per scenario)
63
+ omg smoke copilot --tier 2 --scenario all # adds skill + delegate
64
+ ```
65
+
66
+ **Install.** The SDK is an optional peer dependency, so `npm i -g oh-my-copilot` never installs it. Install it next to `omg` with the command above. `--ignore-scripts` is required on Windows with `--omit=optional`, because koffi's install script otherwise falls back to a source build that needs CMake. Without the SDK every `sdk.*` check fails with that install hint and the run exits `2`.
67
+
68
+ **Bundled runtime.** A global install can still pull the SDK's platform runtime, about 128 MB, despite `--omit=optional`. It lands nested at `<npm root -g>/@github/copilot-sdk/node_modules/@github/copilot-sdk-<platform>`. Delete that folder if it appears; tier 2 never uses it. The lean alternative is a project-local install in the plugin root, which the spike measured at 11 MB:
69
+
70
+ ```bash
71
+ npm i --no-save --omit=optional --ignore-scripts @github/copilot-sdk
72
+ ```
73
+
74
+ **Installed-exe policy.** Tier 2 always connects the SDK to the installed `copilot` binary, resolved exactly as for tier 0 and 1. It never uses the SDK's bundled runtime, because the installed CLI is what users run and plugin loading differs between the two. `sdk.runtime` checks that the runtime reports the tier 0 binary version and protocol 3 or later.
75
+
76
+ **Scenarios.** `smoke` and `guardrail` are the default; `--scenario` takes a comma-separated list or `all`. Each scenario runs in its own session in a temp git repo with an isolated `COPILOT_HOME`, a per-scenario timeout, and its full event stream kept as `artifacts.events.<name>`. `scn.<name>.cost` reports premium requests and credits.
77
+
78
+ **Credit cap.** `--max-credits` (default and minimum 30) caps the whole tier 2 run. The runtime gets `--max-ai-credits` with the same value; the headless runtime accepts and validates the flag (CLI 1.0.91), but whether it enforces it on SDK sessions is unverified. The smoke therefore enforces the cap itself: it sums `assistant.usage` credits per scenario, aborts the scenario that passes what is left (`scn.<name>.exit` and `.cost` fail), and reports the remaining scenarios as `skipped: credit cap reached`.
79
+
80
+ **No billing from unit tests.** Under vitest (`VITEST` or `NODE_ENV=test`) `runCopilotSmoke` refuses any run that would call a model, with one `cli.guard` check and no spawn, unless `OMC_LIVE_SMOKE=1` (tier 1) or `OMC_LIVE_SMOKE=2` (tier 2 scenarios) is set or the test injects the seam that would bill (`deps.spawn` at tier 1, `deps.loadSdk` at tier 2). `--sdk-static` is never refused.
81
+
82
+ **Runtime teardown.** After `client.stop()` the smoke checks whether the runtime pid is still alive and kills its process tree. A scenario that does not go idle even after `abort()` marks the runtime wedged: the remaining scenarios are skipped and the tree is killed before `stop()`, so hook and MCP children are not orphaned. On POSIX the SDK does not start the runtime as a process-group leader, so the kill reaches the runtime itself but not always its children.
83
+
84
+ **Quirks verified on SDK 1.0.16 with Copilot CLI 1.0.91.**
85
+
86
+ - **`sessionStart` is lazy.** It fires after the first `userPromptSubmitted`, not at session creation.
87
+ - **`sessionEnd` fires after every turn.** In SDK and headless mode OMC's SessionEnd hooks therefore run per turn, also on abort.
88
+ - **Skill name differs from its path.** `skill.invoked` reports the frontmatter name, such as `omc-plan`, not `oh-my-copilot:plan`. Assert on `path` ending in `skills/plan/SKILL.md` and on `pluginName`.
89
+ - **MCP tool names are `<server>-<tool>`.** Excluding `t-host_smoke` works; `t:host_smoke` and `t(host_smoke)` match nothing in `excludedTools`. The smoke takes the server name from the first `.mcp.json` key, and `sdk.tools_excluded` proves the exclusion against `session.rpc.tools.getCurrentMetadata` without a model call.
90
+ - **Env is per client, not per session.** Hook processes inherit the runtime's env, so a scenario that needs a different env, such as `OMC_GIT_GUARDRAILS=1`, needs its own client.
91
+ - **Hook stderr is only in the debug log.** It appears in `COPILOT_HOME/logs/*.log` as `[hook stderr]` lines and only at log level `debug`. `scn.<name>.adapter_errors` reads that log for `[omg-hook]` lines from the adapter and `[run.cjs]` lines from the hook runner, such as a hook timeout. Lines are attributed to a scenario by their timestamp, so a late SessionEnd line lands in the scenario that caused it.
92
+ - **Windows npm shim.** The SDK spawns the runtime without a shell, so a `copilot.cmd` or `.ps1` shim is resolved to the `.js` or `.exe` it launches. If that fails, every `sdk.*` and `scn.*` check fails with a hint to pass `--copilot-bin`.
93
+
94
+ ### Copilot CLI facts the smoke relies on
95
+
96
+ These were verified against Copilot CLI 1.0.91.
97
+
98
+ - **Free load checks.** `copilot --plugin-dir <root> plugin list --json` and `copilot --plugin-dir <root> skill list --json` make no model call and reflect `--plugin-dir`.
99
+ - **A bad `--plugin-dir` only warns.** The command still succeeds and `plugin list` returns `[]`, so assert on the exact entry, never on the exit code.
100
+ - **Manifest locations.** Copilot accepts the plugin manifest at `plugin.json`, `.github/plugin/plugin.json`, or `.claude-plugin/plugin.json` under the root.
101
+ - **`plugin install` takes no local path.** Use `--plugin-dir` to load a working tree.
102
+ - **An isolated `COPILOT_HOME` needs a token.** A fresh home has no stored login, so pass a token through the environment for any model call.
103
+ - **Trusted folders are camelCase.** Write `trustedFolders` in `$COPILOT_HOME/config.json` so `-p` runs in the temp project without a trust prompt.
104
+ - **No `installed_plugins.json`.** Installed plugins live under `~/.copilot/installed-plugins/`.
package/docs/HOOKS.md CHANGED
@@ -86,7 +86,7 @@ The adapter fails **open** on a hook's internal error. Copilot treats a PreToolU
86
86
  | Variable | Effect |
87
87
  |---|---|
88
88
  | `OMC_HOOK_EVENT` | Set by `copilot/hooks.json`; activates the adapter. |
89
- | `OMC_HOOK_FAIL_CLOSED=1` | Keep the hook's original non-zero exit code (fail closed). |
89
+ | `OMC_HOOK_FAIL_CLOSED=1` | Keep the hook's original non-zero exit code (fail closed). The hook runner `scripts/run.cjs` also exits `124` when a hook times out, instead of the default fail-open `0`, and its stderr line says `exiting fail-closed (124)`. |
90
90
  | `OMC_HOOK_STRICT=1` | A hook target that is missing or not a file exits 1 instead of 0 plus a stderr line. |
91
91
  | `OMC_DEBUG_HOOKS` | Log adapter decisions, such as a dropped `updatedInput`, to stderr. |
92
92
 
package/docs/MIGRATION.md CHANGED
@@ -8,6 +8,7 @@ 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.6.2 → v5.7.0: Smoke Harness](#v562--v570-smoke-harness)
11
12
  - [v5.6.1 → v5.6.2: Postinstall Hook](#v561--v562-postinstall-hook)
12
13
  - [v5.5.0 → v5.6.1: Fork Upgrade Guide](#v550--v561-fork-upgrade-guide)
13
14
  - [v5.1.0 → v5.5.0: Fork Upgrade Guide](#v510--v550-fork-upgrade-guide)
@@ -122,6 +123,76 @@ locations and descendants of system temp/OS roots are never used as roots.
122
123
 
123
124
  ---
124
125
 
126
+ ## v5.6.2 → v5.7.0: Smoke Harness
127
+
128
+ Fork **v5.7.0** adds a headless smoke harness and ports upstream
129
+ oh-my-claudecode `dev` 486b85bbb..bcaceb136. There is nothing to migrate: no
130
+ config, command or skill is renamed or removed.
131
+
132
+ - Update with `npm install -g oh-my-copilot@5.7.0` or
133
+ `copilot plugin update oh-my-copilot@omc`.
134
+
135
+ ### Adopting `omg smoke copilot` and `host_smoke`
136
+
137
+ Build first (`npm run build`); the MCP check spawns
138
+ `dist/mcp/standalone-server.js`.
139
+
140
+ | Tier | Command | Cost |
141
+ | --- | --- | --- |
142
+ | 0 | `omg smoke copilot --tier 0` | Free, no model call |
143
+ | 1 | `omg smoke copilot --tier 1` | 1 premium request |
144
+ | 2 static | `omg smoke copilot --sdk-static` | Free, no model call |
145
+ | 2 | `omg smoke copilot --tier 2 [--scenario all]` | About 1 premium request per scenario; the default `smoke` + `guardrail` costs about 2 |
146
+
147
+ `--max-credits` (default and minimum 30) caps a run. The CLI has no opt-in
148
+ gate. The MCP tool `host_smoke` has two, set as env vars on the MCP server:
149
+
150
+ - **`OMC_SMOKE_ALLOW_LIVE=1`** allows `{ "tier": 1 }` and tier 2 with
151
+ scenarios. `{ "tier": 2, "scenarios": [] }` runs the free static checks
152
+ without it.
153
+ - **`OMC_SMOKE_ALLOW_ANY_ROOT=1`** allows a `pluginRoot` other than the
154
+ server's own package root.
155
+
156
+ `OMC_DISABLE_TOOLS=smoke` hides the tool. From vitest, `npm run test:live`
157
+ runs tier 0 whenever the `copilot` binary resolves. It runs tier 1 only with
158
+ `OMC_LIVE_SMOKE=1` and the tier 2 scenarios only with `OMC_LIVE_SMOKE=2`.
159
+ Details are in
160
+ [DEVELOPERS.md](DEVELOPERS.md#headless-smoke-against-a-local-build).
161
+
162
+ ### Optional SDK for tier 2
163
+
164
+ `@github/copilot-sdk` is an optional peer dependency; installing
165
+ oh-my-copilot never pulls it in. Install it only if you want tier 2:
166
+
167
+ ```bash
168
+ npm i -g @github/copilot-sdk --omit=optional --ignore-scripts
169
+ ```
170
+
171
+ `--ignore-scripts` avoids a CMake source build on Windows. If a global
172
+ install still adds the SDK's bundled platform runtime (about 128 MB), delete
173
+ it; tier 2 always uses the installed `copilot` binary.
174
+
175
+ ### `OMC_HOOK_FAIL_CLOSED=1`
176
+
177
+ The variable is opt-in and unset by default. Without it, the Copilot hook
178
+ adapter turns a hook's non-zero exit into `0` plus one `[omg-hook]` stderr
179
+ line, and a hook that times out in `scripts/run.cjs` exits `0`, so hooks fail
180
+ open. With `OMC_HOOK_FAIL_CLOSED=1` the adapter keeps the hook's exit code,
181
+ and a hook timeout in `scripts/run.cjs` exits `124`. Exit `2` on
182
+ PermissionRequest means deny in both modes. The smoke harness sets the
183
+ variable for its own sessions.
184
+
185
+ ### Team env passthrough on POSIX (#4236)
186
+
187
+ `OMC_TEAM_WORKER_ENV_PASSTHROUGH` keeps the same configuration. On POSIX tmux
188
+ teams its values are no longer inlined into the worker pane's command line.
189
+ They are written to a private `0600` file in a `0700` directory, which the
190
+ pane's `/bin/sh` sources and removes before it execs the login shell. Nothing
191
+ changes for you, but the values no longer appear in `ps` output or tmux
192
+ `pane_start_command`.
193
+
194
+ ---
195
+
125
196
  ## v5.6.1 → v5.6.2: Postinstall Hook
126
197
 
127
198
  Fork **v5.6.2** ports upstream oh-my-claudecode `dev` 4280efb1f..486b85bbb.
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.7.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
 
@@ -732,6 +732,47 @@ omg lookout scan --strict # exit 1 when review is recommended (for scripts)
732
732
  - `--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
733
  - High-risk verdicts pair with approval gates and checkpoints: `omg graph run --approval-mode remote --checkpoint`, `omg checkpoint create`
734
734
 
735
+ ### `omg smoke copilot`
736
+
737
+ 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.
738
+
739
+ ```bash
740
+ omg smoke copilot # tier 0: load check, no model call
741
+ omg smoke copilot --tier 1 # tier 0 + one live session, prompt on stdin (one premium request)
742
+ omg smoke copilot --plugin-root /path/to/oh-my-copilot --json
743
+ omg smoke copilot --tier 1 --keep-home --delegate --model <id> --max-credits 30 --timeout 180000
744
+ omg smoke copilot --sdk-static # tier 2 SDK static checks only, no model call
745
+ omg smoke copilot --tier 2 # tier 2 default scenarios smoke,guardrail (~2 premium requests, 1 per scenario)
746
+ omg smoke copilot --tier 2 --scenario skill,delegate # or --scenario all
747
+ ```
748
+
749
+ - **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.
750
+ - **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.
751
+ - **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.
752
+ - **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.
753
+
754
+ | Scenario | Prompt exercises | Key checks |
755
+ |---|---|---|
756
+ | `smoke` (default) | a plain reply, no tools | `scn.smoke.reply`, `scn.smoke.hooks`, `scn.smoke.no_tools` |
757
+ | `guardrail` (default) | `git push --force` with `OMC_GIT_GUARDRAILS=1` against a bare remote | `scn.guardrail.denied`, `scn.guardrail.no_push`, `scn.guardrail.hooks` |
758
+ | `skill` | `/oh-my-copilot:plan` | `scn.skill.invoked` |
759
+ | `delegate` | a hand-off to `oh-my-copilot:architect` | `scn.delegate.selected`, `scn.delegate.hooks`, `scn.delegate.completed` |
760
+
761
+ - **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.
762
+ - **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.
763
+ - **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.
764
+ - `--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.
765
+ - 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.
766
+ - 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`.
767
+ - `--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).
768
+ - 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:
769
+
770
+ ```bash
771
+ claude mcp add omg-dev -- node C:/Code/OMC/dist/mcp/standalone-server.js
772
+ ```
773
+
774
+ `OMC_DISABLE_TOOLS=smoke` hides `host_smoke`. See [DEVELOPERS.md](./DEVELOPERS.md#headless-smoke-against-a-local-build) for the developer workflow.
775
+
735
776
  ### Graph approval gates (remote approvals)
736
777
 
737
778
  Graph runtime `human-approval` nodes support two gate styles via `omg graph run`:
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.7.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": "58eabdf4c1eb5c63573ce906adb8bfc4fa87ac78"
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.7.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