oh-my-githubcopilot 1.8.1 → 2.0.0-alpha.1cd01f4
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.
- package/.claude-plugin/plugin.json +23 -3
- package/AGENTS.md +29 -25
- package/CHANGELOG.md +49 -336
- package/README.md +7 -2
- package/agents/code-reviewer.agent.md +5 -0
- package/agents/{simplifier.agent.md → code-simplifier.agent.md} +2 -2
- package/agents/debugger.agent.md +1 -1
- package/agents/document-specialist.agent.md +6 -1
- package/agents/{explorer.agent.md → explore.agent.md} +2 -2
- package/agents/security-reviewer.agent.md +1 -1
- package/agents/test-engineer.agent.md +3 -1
- package/bin/omp-statusline.mjs +12 -11
- package/bin/omp-statusline.mjs.map +2 -2
- package/bin/omp.mjs +885 -42
- package/bin/omp.mjs.map +4 -4
- package/dist/hooks/delegation-enforcer.mjs +65 -13
- package/dist/hooks/delegation-enforcer.mjs.map +4 -4
- package/dist/hooks/hud-emitter.mjs +80 -28
- package/dist/hooks/hud-emitter.mjs.map +4 -4
- package/dist/hooks/keyword-detector.mjs +117 -11
- package/dist/hooks/keyword-detector.mjs.map +4 -4
- package/dist/hooks/model-router.mjs +64 -12
- package/dist/hooks/model-router.mjs.map +4 -4
- package/dist/hooks/stop-continuation.mjs +66 -14
- package/dist/hooks/stop-continuation.mjs.map +4 -4
- package/dist/hooks/token-tracker.mjs +81 -19
- package/dist/hooks/token-tracker.mjs.map +4 -4
- package/dist/mcp/server.mjs +17 -16
- package/dist/mcp/server.mjs.map +2 -2
- package/extension/extension.mjs +659 -0
- package/hooks/hooks.json +6 -6
- package/package.json +3 -2
- package/plugin.json +23 -3
- package/skills/build-fix/SKILL.md +35 -0
- package/skills/cancel/SKILL.md +33 -0
- package/skills/ccg/SKILL.md +37 -0
- package/skills/code-review/SKILL.md +33 -0
- package/skills/deep-dive/SKILL.md +33 -0
- package/skills/deepinit/SKILL.md +33 -0
- package/skills/deepsearch/SKILL.md +33 -0
- package/skills/design/SKILL.md +37 -0
- package/skills/external-context/SKILL.md +33 -0
- package/skills/help/SKILL.md +33 -0
- package/skills/omp-doctor/SKILL.md +23 -1
- package/skills/omp-reference/SKILL.md +20 -24
- package/skills/remember/SKILL.md +39 -0
- package/skills/research/SKILL.md +1 -1
- package/skills/sciomc/SKILL.md +35 -0
- package/skills/security-review/SKILL.md +33 -0
- package/skills/self-improve/SKILL.md +35 -0
- package/skills/ultragoal/SKILL.md +33 -0
- package/skills/ultraqa/SKILL.md +33 -0
- package/skills/verify/SKILL.md +33 -0
- package/skills/visual-verdict/SKILL.md +35 -0
- package/skills/web-clone/SKILL.md +35 -0
- package/skills/writer-memory/SKILL.md +37 -0
- package/agents/orchestrator.agent.md +0 -26
- package/agents/researcher.agent.md +0 -18
- package/agents/reviewer.agent.md +0 -23
- package/agents/tester.agent.md +0 -20
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OMP companion extension for GitHub Copilot CLI (>= 1.0.60).
|
|
3
|
+
*
|
|
4
|
+
* Registers every OMP skill as a native slash command and the "omp-hud"
|
|
5
|
+
* canvas via the Copilot SDK extension API (SPEC-omp-2.0 §4 + §5,
|
|
6
|
+
* ADR-0002). Runs standalone inside the Copilot extension process — no
|
|
7
|
+
* tsx/build step — so it carries a self-contained copy of the skill
|
|
8
|
+
* registry and HUD helpers. Keep in sync with src/extension/registry.mts,
|
|
9
|
+
* src/extension/commands.mts, src/extension/hud-canvas.mts, and
|
|
10
|
+
* src/extension/hud-push.mts.
|
|
11
|
+
*
|
|
12
|
+
* Fail-open by design: any failure is logged to stderr and the process
|
|
13
|
+
* exits gracefully so a broken extension never breaks the CLI session.
|
|
14
|
+
* Canvas registration and slash commands fail independently — a broken
|
|
15
|
+
* canvas never takes the commands down, and vice versa.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { pathToFileURL } from "url";
|
|
19
|
+
import { mkdirSync, readFileSync, realpathSync, watch } from "fs";
|
|
20
|
+
import { homedir } from "os";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Self-contained mirror of buildCommands(SKILL_REGISTRY)
|
|
25
|
+
* (skill ids + aliases expanded). Generated from src/extension/registry.mts;
|
|
26
|
+
* parity is enforced by tests/extension/extension-parity.test.mts.
|
|
27
|
+
* @type {Array<{ name: string, skillId: string, description: string }>}
|
|
28
|
+
*/
|
|
29
|
+
export const COMMANDS = [
|
|
30
|
+
{
|
|
31
|
+
name: "autopilot",
|
|
32
|
+
skillId: "autopilot",
|
|
33
|
+
description: "Autonomous end-to-end execution from idea to working code",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "ralph",
|
|
37
|
+
skillId: "ralph",
|
|
38
|
+
description: "Persistence loop with architect verification gate",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "ultrawork",
|
|
42
|
+
skillId: "ultrawork",
|
|
43
|
+
description: "Parallel multi-agent high-throughput implementation",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "ulw",
|
|
47
|
+
skillId: "ultrawork",
|
|
48
|
+
description:
|
|
49
|
+
"Alias for /ultrawork — Parallel multi-agent high-throughput implementation",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "team",
|
|
53
|
+
skillId: "team",
|
|
54
|
+
description: "Coordinated N-agent team with staged pipeline",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "ecomode",
|
|
58
|
+
skillId: "ecomode",
|
|
59
|
+
description: "Cost-optimized execution with low-cost model tier",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "eco",
|
|
63
|
+
skillId: "ecomode",
|
|
64
|
+
description:
|
|
65
|
+
"Alias for /ecomode — Cost-optimized execution with low-cost model tier",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "swarm",
|
|
69
|
+
skillId: "swarm",
|
|
70
|
+
description: "Parallel agent swarm for independent subtasks",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "pipeline",
|
|
74
|
+
skillId: "pipeline",
|
|
75
|
+
description: "Sequential stage-based execution pipeline",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "deep-interview",
|
|
79
|
+
skillId: "deep-interview",
|
|
80
|
+
description: "Socratic deep requirements interview with ambiguity gating",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "di",
|
|
84
|
+
skillId: "deep-interview",
|
|
85
|
+
description:
|
|
86
|
+
"Alias for /deep-interview — Socratic deep requirements interview with ambiguity gating",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "omp-plan",
|
|
90
|
+
skillId: "omp-plan",
|
|
91
|
+
description:
|
|
92
|
+
"Strategic planning with interview, direct, consensus, and review modes",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "plan",
|
|
96
|
+
skillId: "omp-plan",
|
|
97
|
+
description:
|
|
98
|
+
"Alias for /omp-plan — Strategic planning with interview, direct, consensus, and review modes",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "omp-setup",
|
|
102
|
+
skillId: "omp-setup",
|
|
103
|
+
description: "OMP onboarding and configuration wizard",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "hud",
|
|
107
|
+
skillId: "hud",
|
|
108
|
+
description: "Display current HUD session state",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "wiki",
|
|
112
|
+
skillId: "wiki",
|
|
113
|
+
description: "Project wiki operations and management",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "learner",
|
|
117
|
+
skillId: "learner",
|
|
118
|
+
description: "Structured learning and knowledge sessions",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "note",
|
|
122
|
+
skillId: "note",
|
|
123
|
+
description: "Session notes and context management",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "trace",
|
|
127
|
+
skillId: "trace",
|
|
128
|
+
description: "Execution tracing and debugging",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "release",
|
|
132
|
+
skillId: "release",
|
|
133
|
+
description: "Guided release workflow and automation",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "configure-notifications",
|
|
137
|
+
skillId: "configure-notifications",
|
|
138
|
+
description: "Configure session notification settings",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "psm",
|
|
142
|
+
skillId: "psm",
|
|
143
|
+
description: "Plugin State Manager operations",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "swe-bench",
|
|
147
|
+
skillId: "swe-bench",
|
|
148
|
+
description: "SWE-bench evaluation harness runner",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "mcp-setup",
|
|
152
|
+
skillId: "mcp-setup",
|
|
153
|
+
description: "MCP server configuration wizard",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "setup",
|
|
157
|
+
skillId: "setup",
|
|
158
|
+
description: "OMP setup and onboarding wizard",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "graphify",
|
|
162
|
+
skillId: "graphify",
|
|
163
|
+
description: "Convert any input to a knowledge graph",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "graphwiki",
|
|
167
|
+
skillId: "graphwiki",
|
|
168
|
+
description: "GraphWiki CLI operations: query, lint, build",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "graph-provider",
|
|
172
|
+
skillId: "graph-provider",
|
|
173
|
+
description: "Manage and configure the active graph provider",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "spending",
|
|
177
|
+
skillId: "spending",
|
|
178
|
+
description: "Track and reset premium request usage",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "ralplan",
|
|
182
|
+
skillId: "ralplan",
|
|
183
|
+
description:
|
|
184
|
+
"Consensus planning gate for vague ralph/autopilot/team requests",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "research",
|
|
188
|
+
skillId: "research",
|
|
189
|
+
description:
|
|
190
|
+
"Research and investigation workflows (investigate, deep dive)",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "omp-doctor",
|
|
194
|
+
skillId: "omp-doctor",
|
|
195
|
+
description: "Diagnose and fix oh-my-githubcopilot installation issues",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "omp-reference",
|
|
199
|
+
skillId: "omp-reference",
|
|
200
|
+
description:
|
|
201
|
+
"OMP agent catalog, tools, routing, commit protocol, and skills registry",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "ai-slop-cleaner",
|
|
205
|
+
skillId: "ai-slop-cleaner",
|
|
206
|
+
description:
|
|
207
|
+
"Clean AI-generated code slop with a regression-safe, deletion-first workflow",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "tdd",
|
|
211
|
+
skillId: "tdd",
|
|
212
|
+
description: "Test-Driven Development with Red-Green-Refactor cycle",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "improve-codebase-architecture",
|
|
216
|
+
skillId: "improve-codebase-architecture",
|
|
217
|
+
description:
|
|
218
|
+
"Deep exploration and architectural improvement via friction detection",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "skillify",
|
|
222
|
+
skillId: "skillify",
|
|
223
|
+
description:
|
|
224
|
+
"Turn a repeatable session workflow into a reusable OMP skill draft",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: "interview",
|
|
228
|
+
skillId: "interview",
|
|
229
|
+
description: "Socratic interview and ambiguity scoring",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "graph-context",
|
|
233
|
+
skillId: "graph-context",
|
|
234
|
+
description:
|
|
235
|
+
"Load codebase context from the knowledge graph instead of raw files",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "interactive-menu",
|
|
239
|
+
skillId: "interactive-menu",
|
|
240
|
+
description:
|
|
241
|
+
"Numbered-choice selection pattern for OMP's conversational TUI",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "notifications",
|
|
245
|
+
skillId: "notifications",
|
|
246
|
+
description:
|
|
247
|
+
"Send and manage runtime notifications (Telegram, Discord, Slack, Email)",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "doctor",
|
|
251
|
+
skillId: "doctor",
|
|
252
|
+
description: "Diagnose and fix common issues",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "session",
|
|
256
|
+
skillId: "session",
|
|
257
|
+
description: "Worktree and tmux session management",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "verify",
|
|
261
|
+
skillId: "verify",
|
|
262
|
+
description: "Evidence-based completion check via verifier agent",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "cancel",
|
|
266
|
+
skillId: "cancel",
|
|
267
|
+
description: "Ends active execution modes and clears .omp/state/",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "help",
|
|
271
|
+
skillId: "help",
|
|
272
|
+
description: "Command and skill discovery; prints the full skill catalog",
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "code-review",
|
|
276
|
+
skillId: "code-review",
|
|
277
|
+
description: "Trigger the code-reviewer agent lane for structured code review",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "security-review",
|
|
281
|
+
skillId: "security-review",
|
|
282
|
+
description: "Trigger the security-reviewer agent lane for security analysis",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "ultraqa",
|
|
286
|
+
skillId: "ultraqa",
|
|
287
|
+
description: "QA cycle loop with qa-tester agent; runs until all checks pass",
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "ultragoal",
|
|
291
|
+
skillId: "ultragoal",
|
|
292
|
+
description: "Durable goal ledger in .omp/ultragoal/ with fail-closed checkpoints",
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "deep-dive",
|
|
296
|
+
skillId: "deep-dive",
|
|
297
|
+
description: "Trace→deep-interview pipeline for deep investigation",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "external-context",
|
|
301
|
+
skillId: "external-context",
|
|
302
|
+
description: "Load external docs/URLs into session context",
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "deepsearch",
|
|
306
|
+
skillId: "deepsearch",
|
|
307
|
+
description: "Multi-source deep search across codebase and web",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "sciomc",
|
|
311
|
+
skillId: "sciomc",
|
|
312
|
+
description: "Scientific/analytical reasoning workflow — hypothesis→experiment→conclusion",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "remember",
|
|
316
|
+
skillId: "remember",
|
|
317
|
+
description: "Persist key facts/decisions to .omp/memory/",
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "writer-memory",
|
|
321
|
+
skillId: "writer-memory",
|
|
322
|
+
description: "Writing style memory — stores voice/tone preferences",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "deepinit",
|
|
326
|
+
skillId: "deepinit",
|
|
327
|
+
description: "Deep project initialization — full codebase onboarding",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "self-improve",
|
|
331
|
+
skillId: "self-improve",
|
|
332
|
+
description: "OMP self-improvement — analyse own skills/agents and propose improvements",
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "visual-verdict",
|
|
336
|
+
skillId: "visual-verdict",
|
|
337
|
+
description: "Visual diff/screenshot comparison verdict",
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "ccg",
|
|
341
|
+
skillId: "ccg",
|
|
342
|
+
description: "Concurrent code generation via multi-model picker",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "build-fix",
|
|
346
|
+
skillId: "build-fix",
|
|
347
|
+
description: "Diagnose and fix build/CI failures automatically",
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "design",
|
|
351
|
+
skillId: "design",
|
|
352
|
+
description: "UI/UX design and frontend component generation",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "web-clone",
|
|
356
|
+
skillId: "web-clone",
|
|
357
|
+
description: "Clone and adapt a web page/design to the codebase",
|
|
358
|
+
},
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Mirrors buildActivationInstruction() in src/extension/commands.mts.
|
|
363
|
+
* Command handlers run in the extension process and cannot execute the
|
|
364
|
+
* skill directly — they instruct the agent to activate it.
|
|
365
|
+
* @param {string} skillId
|
|
366
|
+
* @param {string} args
|
|
367
|
+
* @returns {string}
|
|
368
|
+
*/
|
|
369
|
+
export function buildActivationInstruction(skillId, args) {
|
|
370
|
+
const trimmed = typeof args === "string" ? args.trim() : "";
|
|
371
|
+
return `Activate the OMP skill "${skillId}" with args: ${trimmed.length > 0 ? trimmed : "(none)"}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// HUD canvas (SPEC-omp-2.0 §5 renderer #1)
|
|
376
|
+
//
|
|
377
|
+
// Self-contained mirrors of src/extension/hud-canvas.mts,
|
|
378
|
+
// src/extension/hud-push.mts, and the HUD paths/default line from
|
|
379
|
+
// src/hud/statusline.mts. Parity is enforced by
|
|
380
|
+
// tests/extension/extension-parity.test.mts.
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
/** Mirrors HUD_CANVAS_* constants in src/extension/hud-canvas.mts. */
|
|
384
|
+
export const HUD_CANVAS = {
|
|
385
|
+
id: "omp-hud",
|
|
386
|
+
displayName: "OMP HUD",
|
|
387
|
+
description:
|
|
388
|
+
"Live OMP session HUD: mode, model, context, tokens, premium requests, and agent activity.",
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** Mirrors DEFAULT_STATUSLINE in src/hud/statusline.mts. */
|
|
392
|
+
export const DEFAULT_STATUSLINE = "OMP | hud: no active session";
|
|
393
|
+
|
|
394
|
+
/** Mirrors HUD_DEBOUNCE_MS in src/extension/hud-push.mts. */
|
|
395
|
+
export const HUD_DEBOUNCE_MS = 250;
|
|
396
|
+
|
|
397
|
+
/** Mirrors HUD_WATCH_FILES in src/extension/hud-push.mts. */
|
|
398
|
+
export const HUD_WATCH_FILES = ["status.json", "display.txt"];
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Mirrors the HUD artifact paths of getStatuslinePaths() in
|
|
402
|
+
* src/hud/statusline.mts (the subset the canvas renderer reads).
|
|
403
|
+
* @param {string} [home]
|
|
404
|
+
*/
|
|
405
|
+
export function getHudPaths(home = process.env.HOME || homedir()) {
|
|
406
|
+
const ompDir = join(home, ".omp");
|
|
407
|
+
const hudDir = join(ompDir, "hud");
|
|
408
|
+
return {
|
|
409
|
+
hudDir,
|
|
410
|
+
displayPath: join(hudDir, "display.txt"),
|
|
411
|
+
legacyLinePath: join(ompDir, "hud.line"),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Reads the pre-rendered HUD line written by the hud-emitter hook /
|
|
417
|
+
* `omp hud --watch`. Follows the cached-file fallback chain of
|
|
418
|
+
* readStatusline() in src/hud/statusline.mts (display.txt → hud.line →
|
|
419
|
+
* default). The live status.json re-render is intentionally skipped here:
|
|
420
|
+
* renderPlain() lives in src/ and the artifacts are rewritten on every
|
|
421
|
+
* hook fire, which is exactly what the watcher reacts to.
|
|
422
|
+
* @param {{ displayPath: string, legacyLinePath: string }} [paths]
|
|
423
|
+
* @returns {string}
|
|
424
|
+
*/
|
|
425
|
+
export function readHudLine(paths = getHudPaths()) {
|
|
426
|
+
for (const filePath of [paths.displayPath, paths.legacyLinePath]) {
|
|
427
|
+
try {
|
|
428
|
+
const line = readFileSync(filePath, "utf-8").trim();
|
|
429
|
+
if (line) return line;
|
|
430
|
+
} catch {
|
|
431
|
+
// Try the next fallback.
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return DEFAULT_STATUSLINE;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Mirrors buildHudCanvasResponse() in src/extension/hud-canvas.mts.
|
|
439
|
+
* @param {string} line
|
|
440
|
+
* @returns {{ title: string, status: string }}
|
|
441
|
+
*/
|
|
442
|
+
export function buildHudCanvasResponse(line) {
|
|
443
|
+
const trimmed = typeof line === "string" ? line.trim() : "";
|
|
444
|
+
return {
|
|
445
|
+
title: HUD_CANVAS.displayName,
|
|
446
|
+
status: trimmed.length > 0 ? trimmed : DEFAULT_STATUSLINE,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Mirrors debounce() in src/extension/hud-push.mts (trailing edge).
|
|
452
|
+
* @param {() => void} fn
|
|
453
|
+
* @param {number} delayMs
|
|
454
|
+
* @returns {{ trigger: () => void, cancel: () => void }}
|
|
455
|
+
*/
|
|
456
|
+
export function debounce(fn, delayMs) {
|
|
457
|
+
let timer = null;
|
|
458
|
+
return {
|
|
459
|
+
trigger() {
|
|
460
|
+
if (timer !== null) clearTimeout(timer);
|
|
461
|
+
timer = setTimeout(() => {
|
|
462
|
+
timer = null;
|
|
463
|
+
fn();
|
|
464
|
+
}, delayMs);
|
|
465
|
+
},
|
|
466
|
+
cancel() {
|
|
467
|
+
if (timer !== null) clearTimeout(timer);
|
|
468
|
+
timer = null;
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Mirrors isHudArtifact() in src/extension/hud-push.mts.
|
|
475
|
+
* @param {string | null | undefined} filename
|
|
476
|
+
* @param {readonly string[]} [files]
|
|
477
|
+
* @returns {boolean}
|
|
478
|
+
*/
|
|
479
|
+
export function isHudArtifact(filename, files = HUD_WATCH_FILES) {
|
|
480
|
+
if (filename === null || filename === undefined) return true;
|
|
481
|
+
return files.includes(String(filename));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Builds the omp-hud Canvas declaration. Returns null when the SDK does
|
|
486
|
+
* not expose createCanvas (older CLI) — fail-open so slash commands still
|
|
487
|
+
* register without the canvas.
|
|
488
|
+
* @param {unknown} createCanvas
|
|
489
|
+
* @param {ReturnType<typeof getHudPaths>} paths
|
|
490
|
+
* @param {Set<string>} openInstances
|
|
491
|
+
*/
|
|
492
|
+
function buildHudCanvas(createCanvas, paths, openInstances) {
|
|
493
|
+
if (typeof createCanvas !== "function") return null;
|
|
494
|
+
return createCanvas({
|
|
495
|
+
id: HUD_CANVAS.id,
|
|
496
|
+
displayName: HUD_CANVAS.displayName,
|
|
497
|
+
description: HUD_CANVAS.description,
|
|
498
|
+
actions: [
|
|
499
|
+
{
|
|
500
|
+
name: "refresh",
|
|
501
|
+
description: "Re-read OMP HUD state and return the refreshed HUD line",
|
|
502
|
+
handler: () => buildHudCanvasResponse(readHudLine(paths)),
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
open: (ctx) => {
|
|
506
|
+
openInstances.add(ctx.instanceId);
|
|
507
|
+
return buildHudCanvasResponse(readHudLine(paths));
|
|
508
|
+
},
|
|
509
|
+
onClose: (ctx) => {
|
|
510
|
+
openInstances.delete(ctx.instanceId);
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Event push (SPEC-omp-2.0 §5): watches the HUD artifact dir (written by
|
|
517
|
+
* the hud-emitter hook) and re-opens every open canvas instance so the
|
|
518
|
+
* host re-renders — re-opening with an existing instanceId is the SDK's
|
|
519
|
+
* documented refresh mechanism (canvas.d.ts). Debounced to coalesce the
|
|
520
|
+
* multiple atomic writes per hook fire. Fail-open: any setup error only
|
|
521
|
+
* disables push; fallback renderers (tmux statusline, `omp hud --watch`)
|
|
522
|
+
* keep polling.
|
|
523
|
+
* @param {{ rpc: { canvas: { open: (params: object) => Promise<unknown> } } }} session
|
|
524
|
+
* @param {ReturnType<typeof getHudPaths>} paths
|
|
525
|
+
* @param {Set<string>} openInstances
|
|
526
|
+
*/
|
|
527
|
+
function startHudPush(session, paths, openInstances) {
|
|
528
|
+
const refresh = debounce(() => {
|
|
529
|
+
for (const instanceId of openInstances) {
|
|
530
|
+
try {
|
|
531
|
+
Promise.resolve(
|
|
532
|
+
session.rpc.canvas.open({ canvasId: HUD_CANVAS.id, instanceId }),
|
|
533
|
+
).catch(() => {
|
|
534
|
+
// Instance gone or host busy — next change retries.
|
|
535
|
+
});
|
|
536
|
+
} catch {
|
|
537
|
+
// rpc surface unavailable — fallback renderers keep polling.
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}, HUD_DEBOUNCE_MS);
|
|
541
|
+
try {
|
|
542
|
+
mkdirSync(paths.hudDir, { recursive: true });
|
|
543
|
+
const watcher = watch(paths.hudDir, (_event, filename) => {
|
|
544
|
+
if (isHudArtifact(filename)) refresh.trigger();
|
|
545
|
+
});
|
|
546
|
+
watcher.on("error", () => {
|
|
547
|
+
refresh.cancel();
|
|
548
|
+
try {
|
|
549
|
+
watcher.close();
|
|
550
|
+
} catch {
|
|
551
|
+
// Already closed — nothing to clean up.
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
} catch (err) {
|
|
555
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
556
|
+
console.error(
|
|
557
|
+
`[omp-extension] HUD push disabled (watch failed): ${message}`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function main() {
|
|
563
|
+
let joinSession;
|
|
564
|
+
let createCanvas;
|
|
565
|
+
try {
|
|
566
|
+
// Resolved by the Copilot CLI inside extension processes only —
|
|
567
|
+
// intentionally not a package.json dependency of this repo.
|
|
568
|
+
({ joinSession, createCanvas } = await import(
|
|
569
|
+
"@github/copilot-sdk/extension"
|
|
570
|
+
));
|
|
571
|
+
} catch (err) {
|
|
572
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
573
|
+
console.error(
|
|
574
|
+
`[omp-extension] Copilot SDK unavailable, slash commands disabled: ${message}`,
|
|
575
|
+
);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const commands = COMMANDS.map(({ name, skillId, description }) => ({
|
|
580
|
+
name,
|
|
581
|
+
description,
|
|
582
|
+
handler: (args) => buildActivationInstruction(skillId, args),
|
|
583
|
+
}));
|
|
584
|
+
|
|
585
|
+
// HUD canvas — fail-open: never let canvas setup break slash commands.
|
|
586
|
+
const hudPaths = getHudPaths();
|
|
587
|
+
/** @type {Set<string>} */
|
|
588
|
+
const openInstances = new Set();
|
|
589
|
+
let canvases;
|
|
590
|
+
try {
|
|
591
|
+
const hudCanvas = buildHudCanvas(createCanvas, hudPaths, openInstances);
|
|
592
|
+
canvases = hudCanvas ? [hudCanvas] : undefined;
|
|
593
|
+
} catch (err) {
|
|
594
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
595
|
+
console.error(`[omp-extension] HUD canvas unavailable: ${message}`);
|
|
596
|
+
canvases = undefined;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
let session;
|
|
600
|
+
try {
|
|
601
|
+
session = await joinSession(
|
|
602
|
+
canvases ? { commands, canvases } : { commands },
|
|
603
|
+
);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
606
|
+
if (!canvases) {
|
|
607
|
+
console.error(
|
|
608
|
+
`[omp-extension] joinSession failed, slash commands disabled: ${message}`,
|
|
609
|
+
);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
// Canvas registration may be the culprit — retry commands-only so a
|
|
613
|
+
// canvas failure never takes slash commands down with it.
|
|
614
|
+
console.error(
|
|
615
|
+
`[omp-extension] joinSession with HUD canvas failed, retrying commands-only: ${message}`,
|
|
616
|
+
);
|
|
617
|
+
canvases = undefined;
|
|
618
|
+
try {
|
|
619
|
+
session = await joinSession({ commands });
|
|
620
|
+
} catch (retryErr) {
|
|
621
|
+
const retryMessage =
|
|
622
|
+
retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
623
|
+
console.error(
|
|
624
|
+
`[omp-extension] joinSession failed, slash commands disabled: ${retryMessage}`,
|
|
625
|
+
);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Event push only makes sense when the canvas registered.
|
|
631
|
+
if (session && canvases) {
|
|
632
|
+
try {
|
|
633
|
+
startHudPush(session, hudPaths, openInstances);
|
|
634
|
+
} catch (err) {
|
|
635
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
636
|
+
console.error(`[omp-extension] HUD push disabled: ${message}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Only join the session when executed directly by the Copilot CLI —
|
|
642
|
+
// importing this module (e.g. from the parity tests) must not connect.
|
|
643
|
+
// argv[1] is realpath-normalized because Node resolves the ESM main entry
|
|
644
|
+
// to its realpath, so symlinked install paths would otherwise never match.
|
|
645
|
+
function isDirectExecution() {
|
|
646
|
+
if (!process.argv[1]) return false;
|
|
647
|
+
try {
|
|
648
|
+
return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
649
|
+
} catch {
|
|
650
|
+
return import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (isDirectExecution()) {
|
|
655
|
+
main().catch((err) => {
|
|
656
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
657
|
+
console.error(`[omp-extension] unexpected failure: ${message}`);
|
|
658
|
+
});
|
|
659
|
+
}
|
package/hooks/hooks.json
CHANGED
|
@@ -5,36 +5,36 @@
|
|
|
5
5
|
{
|
|
6
6
|
"type": "command",
|
|
7
7
|
"bash": "node ./bin/omp.mjs hook keyword-detector",
|
|
8
|
-
"timeoutSec":
|
|
8
|
+
"timeoutSec": 5
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"PreToolUse": [
|
|
12
12
|
{
|
|
13
13
|
"type": "command",
|
|
14
14
|
"bash": "node ./dist/hooks/delegation-enforcer.mjs",
|
|
15
|
-
"timeoutSec":
|
|
15
|
+
"timeoutSec": 5
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
19
|
"bash": "node ./dist/hooks/model-router.mjs",
|
|
20
|
-
"timeoutSec":
|
|
20
|
+
"timeoutSec": 5
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"PostToolUse": [
|
|
24
24
|
{
|
|
25
25
|
"type": "command",
|
|
26
26
|
"bash": "node ./dist/hooks/token-tracker.mjs",
|
|
27
|
-
"timeoutSec":
|
|
27
|
+
"timeoutSec": 5
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"type": "command",
|
|
31
31
|
"bash": "node ./dist/hooks/hud-emitter.mjs",
|
|
32
|
-
"timeoutSec":
|
|
32
|
+
"timeoutSec": 5
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"type": "command",
|
|
36
36
|
"bash": "node ./dist/hooks/stop-continuation.mjs",
|
|
37
|
-
"timeoutSec":
|
|
37
|
+
"timeoutSec": 5
|
|
38
38
|
}
|
|
39
39
|
]
|
|
40
40
|
}
|