oh-my-githubcopilot 1.4.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.
- package/.claude-plugin/plugin.json +41 -0
- package/AGENTS.md +107 -0
- package/CHANGELOG.md +104 -0
- package/LICENSE +190 -0
- package/README.de.md +53 -0
- package/README.es.md +53 -0
- package/README.fr.md +53 -0
- package/README.it.md +53 -0
- package/README.ja.md +53 -0
- package/README.ko.md +53 -0
- package/README.md +139 -0
- package/README.pt.md +53 -0
- package/README.ru.md +53 -0
- package/README.tr.md +53 -0
- package/README.vi.md +53 -0
- package/README.zh.md +53 -0
- package/bin/omp.mjs +59 -0
- package/bin/omp.mjs.map +7 -0
- package/dist/hooks/delegation-enforcer.mjs +96 -0
- package/dist/hooks/delegation-enforcer.mjs.map +7 -0
- package/dist/hooks/hud-emitter.mjs +167 -0
- package/dist/hooks/hud-emitter.mjs.map +7 -0
- package/dist/hooks/keyword-detector.mjs +134 -0
- package/dist/hooks/keyword-detector.mjs.map +7 -0
- package/dist/hooks/model-router.mjs +79 -0
- package/dist/hooks/model-router.mjs.map +7 -0
- package/dist/hooks/stop-continuation.mjs +83 -0
- package/dist/hooks/stop-continuation.mjs.map +7 -0
- package/dist/hooks/token-tracker.mjs +181 -0
- package/dist/hooks/token-tracker.mjs.map +7 -0
- package/dist/mcp/server.mjs +28492 -0
- package/dist/mcp/server.mjs.map +7 -0
- package/dist/skills/mcp-setup.mjs +42 -0
- package/dist/skills/mcp-setup.mjs.map +7 -0
- package/dist/skills/setup.mjs +38 -0
- package/dist/skills/setup.mjs.map +7 -0
- package/hooks/hooks.json +47 -0
- package/package.json +70 -0
- package/skills/autopilot/SKILL.md +35 -0
- package/skills/configure-notifications/SKILL.md +35 -0
- package/skills/deep-interview/SKILL.md +35 -0
- package/skills/ecomode/SKILL.md +35 -0
- package/skills/graph-provider/SKILL.md +77 -0
- package/skills/graphify/SKILL.md +51 -0
- package/skills/graphwiki/SKILL.md +66 -0
- package/skills/hud/SKILL.md +35 -0
- package/skills/learner/SKILL.md +35 -0
- package/skills/mcp-setup/SKILL.md +34 -0
- package/skills/note/SKILL.md +35 -0
- package/skills/omp-plan/SKILL.md +35 -0
- package/skills/omp-setup/SKILL.md +37 -0
- package/skills/pipeline/SKILL.md +35 -0
- package/skills/psm/SKILL.md +35 -0
- package/skills/ralph/SKILL.md +35 -0
- package/skills/release/SKILL.md +35 -0
- package/skills/setup/SKILL.md +43 -0
- package/skills/spending/SKILL.md +86 -0
- package/skills/swarm/SKILL.md +35 -0
- package/skills/swe-bench/SKILL.md +35 -0
- package/skills/team/SKILL.md +35 -0
- package/skills/trace/SKILL.md +35 -0
- package/skills/ultrawork/SKILL.md +35 -0
- package/skills/wiki/SKILL.md +35 -0
- package/src/agents/analyst.md +103 -0
- package/src/agents/architect.md +169 -0
- package/src/agents/code-reviewer.md +135 -0
- package/src/agents/critic.md +196 -0
- package/src/agents/debugger.md +132 -0
- package/src/agents/designer.md +103 -0
- package/src/agents/document-specialist.md +111 -0
- package/src/agents/executor.md +120 -0
- package/src/agents/explorer.md +98 -0
- package/src/agents/git-master.md +92 -0
- package/src/agents/orchestrator.md +125 -0
- package/src/agents/planner.md +106 -0
- package/src/agents/qa-tester.md +129 -0
- package/src/agents/researcher.md +102 -0
- package/src/agents/reviewer.md +100 -0
- package/src/agents/scientist.md +150 -0
- package/src/agents/security-reviewer.md +132 -0
- package/src/agents/simplifier.md +109 -0
- package/src/agents/test-engineer.md +124 -0
- package/src/agents/tester.md +102 -0
- package/src/agents/tracer.md +160 -0
- package/src/agents/verifier.md +100 -0
- package/src/agents/writer.md +96 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/hooks/delegation-enforcer.mts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
function getSessionStateDir() {
|
|
7
|
+
const ompDir = join(homedir(), ".omp", "state");
|
|
8
|
+
return ompDir;
|
|
9
|
+
}
|
|
10
|
+
function getCurrentAgent(sessionId) {
|
|
11
|
+
try {
|
|
12
|
+
const stateDir = getSessionStateDir();
|
|
13
|
+
const sessionFile = sessionId ? join(stateDir, "sessions", sessionId, "session.json") : join(stateDir, "session.json");
|
|
14
|
+
const data = JSON.parse(readFileSync(sessionFile, "utf-8"));
|
|
15
|
+
return data.activeAgent || null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var BLOCKED_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
|
|
21
|
+
var BLOCKED_AGENT = "orchestrator";
|
|
22
|
+
function processHook(input) {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const log = [];
|
|
25
|
+
try {
|
|
26
|
+
if (input.hook_type !== "PreToolUse") {
|
|
27
|
+
return {
|
|
28
|
+
status: "skip",
|
|
29
|
+
latencyMs: Date.now() - start,
|
|
30
|
+
mutations: [],
|
|
31
|
+
log: ["Not a PreToolUse hook"]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const agentId = input.agent_id || getCurrentAgent(input.session_id);
|
|
35
|
+
const toolName = input.tool_name;
|
|
36
|
+
if (!agentId || !toolName) {
|
|
37
|
+
return {
|
|
38
|
+
status: "ok",
|
|
39
|
+
latencyMs: Date.now() - start,
|
|
40
|
+
mutations: [],
|
|
41
|
+
log: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (agentId === BLOCKED_AGENT && BLOCKED_TOOLS.has(toolName)) {
|
|
45
|
+
log.push(`ENFORCEMENT: ${agentId} attempted ${toolName} \u2014 blocked`);
|
|
46
|
+
log.push(`Rerouting to appropriate specialist agent`);
|
|
47
|
+
return {
|
|
48
|
+
decision: "deny",
|
|
49
|
+
status: "ok",
|
|
50
|
+
latencyMs: Date.now() - start,
|
|
51
|
+
mutations: [
|
|
52
|
+
{
|
|
53
|
+
type: "reroute_tool",
|
|
54
|
+
toolCall: { tool: toolName, params: input.tool_input },
|
|
55
|
+
toAgent: "executor"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "log",
|
|
59
|
+
level: "warn",
|
|
60
|
+
message: `Delegation enforced: ${agentId} cannot use ${toolName}`
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
log
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
status: "ok",
|
|
68
|
+
latencyMs: Date.now() - start,
|
|
69
|
+
mutations: [],
|
|
70
|
+
log: []
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
status: "error",
|
|
75
|
+
latencyMs: Date.now() - start,
|
|
76
|
+
mutations: [],
|
|
77
|
+
log: [`Error: ${err}`]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
82
|
+
const input = JSON.parse(await readStdin());
|
|
83
|
+
const output = processHook(input);
|
|
84
|
+
console.log(JSON.stringify(output));
|
|
85
|
+
}
|
|
86
|
+
async function readStdin() {
|
|
87
|
+
const chunks = [];
|
|
88
|
+
for await (const chunk of process.stdin) {
|
|
89
|
+
chunks.push(chunk);
|
|
90
|
+
}
|
|
91
|
+
return chunks.join("");
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
processHook
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=delegation-enforcer.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/delegation-enforcer.mts"],
|
|
4
|
+
"sourcesContent": ["/**\n * delegation-enforcer hook\n * Trigger: pre-cycle (PreToolUse equivalent)\n * Priority: 90\n *\n * Blocks orchestrator from using Write/Edit \u2014 forces delegation.\n */\n\nimport { readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface HookInput {\n hook_type: \"PreToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n agent_id?: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\" | \"deny\";\n modifiedArgs?: Record<string, unknown>;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"reroute_tool\"; toolCall: unknown; toAgent: string } | { type: \"log\"; level: \"info\" | \"warn\"; message: string }>;\n log: string[];\n}\n\nfunction getSessionStateDir(): string {\n const ompDir = join(homedir(), \".omp\", \"state\");\n return ompDir;\n}\n\nfunction getCurrentAgent(sessionId?: string): string | null {\n try {\n const stateDir = getSessionStateDir();\n const sessionFile = sessionId\n ? join(stateDir, \"sessions\", sessionId, \"session.json\")\n : join(stateDir, \"session.json\");\n const data = JSON.parse(readFileSync(sessionFile, \"utf-8\"));\n return data.activeAgent || null;\n } catch {\n return null;\n }\n}\n\nconst BLOCKED_TOOLS = new Set([\"Write\", \"Edit\"]);\nconst BLOCKED_AGENT = \"orchestrator\";\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"PreToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"Not a PreToolUse hook\"],\n };\n }\n\n const agentId = input.agent_id || getCurrentAgent(input.session_id);\n const toolName = input.tool_name;\n\n if (!agentId || !toolName) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n if (agentId === BLOCKED_AGENT && BLOCKED_TOOLS.has(toolName)) {\n log.push(`ENFORCEMENT: ${agentId} attempted ${toolName} \u2014 blocked`);\n log.push(`Rerouting to appropriate specialist agent`);\n\n return {\n decision: \"deny\",\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"reroute_tool\",\n toolCall: { tool: toolName, params: input.tool_input },\n toAgent: \"executor\",\n },\n {\n type: \"log\",\n level: \"warn\",\n message: `Delegation enforced: ${agentId} cannot use ${toolName}`,\n },\n ],\n log,\n };\n }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
|
|
5
|
+
"mappings": ";AAQA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,YAAY;AA2GrB,SAAS,qBAAqB;AAxF9B,SAAS,qBAA6B;AACpC,QAAM,SAAS,KAAK,QAAQ,GAAG,QAAQ,OAAO;AAC9C,SAAO;AACT;AAEA,SAAS,gBAAgB,WAAmC;AAC1D,MAAI;AACF,UAAM,WAAW,mBAAmB;AACpC,UAAM,cAAc,YAChB,KAAK,UAAU,YAAY,WAAW,cAAc,IACpD,KAAK,UAAU,cAAc;AACjC,UAAM,OAAO,KAAK,MAAM,aAAa,aAAa,OAAO,CAAC;AAC1D,WAAO,KAAK,eAAe;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,gBAAgB,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAC/C,IAAM,gBAAgB;AAEf,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAgB,CAAC;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,cAAc;AACpC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC,uBAAuB;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,YAAY,gBAAgB,MAAM,UAAU;AAClE,UAAM,WAAW,MAAM;AAEvB,QAAI,CAAC,WAAW,CAAC,UAAU;AACzB,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAEA,QAAI,YAAY,iBAAiB,cAAc,IAAI,QAAQ,GAAG;AAC5D,UAAI,KAAK,gBAAgB,OAAO,cAAc,QAAQ,iBAAY;AAClE,UAAI,KAAK,2CAA2C;AAEpD,aAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW;AAAA,UACT;AAAA,YACE,MAAM;AAAA,YACN,UAAU,EAAE,MAAM,UAAU,QAAQ,MAAM,WAAW;AAAA,YACrD,SAAS;AAAA,UACX;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,OAAO;AAAA,YACP,SAAS,wBAAwB,OAAO,eAAe,QAAQ;AAAA,UACjE;AAAA,QACF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC;AAAA,IACR;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// src/hooks/hud-emitter.mts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
function getStatePath(sessionId) {
|
|
7
|
+
const base = join(homedir(), ".omp", "state");
|
|
8
|
+
if (sessionId) {
|
|
9
|
+
return join(base, "sessions", sessionId, "session.json");
|
|
10
|
+
}
|
|
11
|
+
return join(base, "session.json");
|
|
12
|
+
}
|
|
13
|
+
function getHudLinePath() {
|
|
14
|
+
return join(homedir(), ".omp", "hud.line");
|
|
15
|
+
}
|
|
16
|
+
function ensureDir(path) {
|
|
17
|
+
mkdirSync(path.substring(0, path.lastIndexOf("/")), { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
function formatAge(startedAt) {
|
|
20
|
+
const elapsed = Date.now() - startedAt;
|
|
21
|
+
const mins = Math.floor(elapsed / 6e4);
|
|
22
|
+
if (mins < 60) return `${mins}m`;
|
|
23
|
+
const hours = Math.floor(mins / 60);
|
|
24
|
+
const remainingMins = mins % 60;
|
|
25
|
+
return `${hours}h${remainingMins}m`;
|
|
26
|
+
}
|
|
27
|
+
function formatTokens(tokens) {
|
|
28
|
+
if (tokens >= 1e6) return `~${(tokens / 1e6).toFixed(1)}M`;
|
|
29
|
+
if (tokens >= 1e3) return `~${(tokens / 1e3).toFixed(1)}k`;
|
|
30
|
+
return `~${tokens}`;
|
|
31
|
+
}
|
|
32
|
+
function buildHudLine(state) {
|
|
33
|
+
const age = formatAge(state.started_at);
|
|
34
|
+
const tokens = formatTokens(state.tokens_estimated);
|
|
35
|
+
const ctx = state.context_pct;
|
|
36
|
+
const tools = state.tools_used.length;
|
|
37
|
+
const skills = state.skills_used.length;
|
|
38
|
+
const agents = state.agents_used.length;
|
|
39
|
+
const mode = state.active_mode || "-";
|
|
40
|
+
const model = state.model || "sonnet";
|
|
41
|
+
return `OMP v${state.version} | ${model} | tkn: ${tokens}/${state.token_budget} | ctx: ${ctx}% | session: ${age} | tools: ${tools} | skills: ${skills} | agents: ${agents} | mode: ${mode}`;
|
|
42
|
+
}
|
|
43
|
+
function processSessionStart(input) {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
const log = [];
|
|
46
|
+
const sessionId = input.session_id || "default";
|
|
47
|
+
const state = {
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
session_id: sessionId,
|
|
50
|
+
started_at: Date.now(),
|
|
51
|
+
model: input.model || "claude-sonnet-4.5",
|
|
52
|
+
tokens_estimated: 0,
|
|
53
|
+
token_budget: 2e5,
|
|
54
|
+
context_pct: 0,
|
|
55
|
+
tools_used: [],
|
|
56
|
+
skills_used: [],
|
|
57
|
+
agents_used: [],
|
|
58
|
+
active_mode: null
|
|
59
|
+
};
|
|
60
|
+
const statePath = getStatePath(sessionId);
|
|
61
|
+
ensureDir(statePath);
|
|
62
|
+
writeFileSync(statePath, JSON.stringify(state), "utf-8");
|
|
63
|
+
log.push(`Session initialized: ${sessionId}`);
|
|
64
|
+
const hudLine = buildHudLine(state);
|
|
65
|
+
const hudPath = getHudLinePath();
|
|
66
|
+
ensureDir(hudPath);
|
|
67
|
+
writeFileSync(hudPath, hudLine, "utf-8");
|
|
68
|
+
log.push(`HUD line written: ${hudLine}`);
|
|
69
|
+
return {
|
|
70
|
+
status: "ok",
|
|
71
|
+
latencyMs: Date.now() - start,
|
|
72
|
+
mutations: [
|
|
73
|
+
{
|
|
74
|
+
type: "emit_hud",
|
|
75
|
+
hudEmit: {
|
|
76
|
+
sessionId,
|
|
77
|
+
activeMode: null,
|
|
78
|
+
contextPct: 0,
|
|
79
|
+
tokensUsed: 0,
|
|
80
|
+
tokensTotal: 2e5,
|
|
81
|
+
agentsActive: [],
|
|
82
|
+
lastAgent: "-",
|
|
83
|
+
lastOutput: "",
|
|
84
|
+
taskProgress: 0
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
log
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function processPostToolUse(input) {
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
const log = [];
|
|
94
|
+
const statePath = getStatePath(input.session_id);
|
|
95
|
+
let state;
|
|
96
|
+
try {
|
|
97
|
+
const raw = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
98
|
+
state = {
|
|
99
|
+
...raw,
|
|
100
|
+
tools_used: Array.isArray(raw.tools_used) ? raw.tools_used : [],
|
|
101
|
+
skills_used: Array.isArray(raw.skills_used) ? raw.skills_used : [],
|
|
102
|
+
agents_used: Array.isArray(raw.agents_used) ? raw.agents_used : []
|
|
103
|
+
};
|
|
104
|
+
} catch {
|
|
105
|
+
return processSessionStart(input);
|
|
106
|
+
}
|
|
107
|
+
if (input.tool_name && !state.tools_used.includes(input.tool_name)) {
|
|
108
|
+
state.tools_used.push(input.tool_name);
|
|
109
|
+
}
|
|
110
|
+
const hudLine = buildHudLine(state);
|
|
111
|
+
const hudPath = getHudLinePath();
|
|
112
|
+
ensureDir(hudPath);
|
|
113
|
+
writeFileSync(hudPath, hudLine, "utf-8");
|
|
114
|
+
log.push(`HUD updated: ${hudLine}`);
|
|
115
|
+
writeFileSync(statePath, JSON.stringify(state), "utf-8");
|
|
116
|
+
return {
|
|
117
|
+
status: "ok",
|
|
118
|
+
latencyMs: Date.now() - start,
|
|
119
|
+
mutations: [
|
|
120
|
+
{
|
|
121
|
+
type: "emit_hud",
|
|
122
|
+
hudEmit: {
|
|
123
|
+
sessionId: state.session_id,
|
|
124
|
+
activeMode: state.active_mode,
|
|
125
|
+
contextPct: state.context_pct,
|
|
126
|
+
tokensUsed: state.tokens_estimated,
|
|
127
|
+
tokensTotal: state.token_budget,
|
|
128
|
+
agentsActive: state.agents_used,
|
|
129
|
+
lastAgent: state.agents_used[state.agents_used.length - 1] || "-",
|
|
130
|
+
lastOutput: "",
|
|
131
|
+
taskProgress: 0
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
log
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function processHook(input) {
|
|
139
|
+
if (input.hook_type === "SessionStart") {
|
|
140
|
+
return processSessionStart(input);
|
|
141
|
+
}
|
|
142
|
+
if (input.hook_type === "PostToolUse") {
|
|
143
|
+
return processPostToolUse(input);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
status: "skip",
|
|
147
|
+
latencyMs: 0,
|
|
148
|
+
mutations: [],
|
|
149
|
+
log: ["Unknown hook type"]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
153
|
+
const input = JSON.parse(await readStdin());
|
|
154
|
+
const output = processHook(input);
|
|
155
|
+
console.log(JSON.stringify(output));
|
|
156
|
+
}
|
|
157
|
+
async function readStdin() {
|
|
158
|
+
const chunks = [];
|
|
159
|
+
for await (const chunk of process.stdin) {
|
|
160
|
+
chunks.push(chunk);
|
|
161
|
+
}
|
|
162
|
+
return chunks.join("");
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
processHook
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=hud-emitter.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/hud-emitter.mts"],
|
|
4
|
+
"sourcesContent": ["/**\n * hud-emitter hook\n * Trigger: post-cycle (PostToolUse + SessionStart)\n * Priority: 60\n *\n * Writes HUD statusline to ~/.omp/hud.line after every tool call.\n * Initializes session state on SessionStart.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface HookInput {\n hook_type: \"SessionStart\" | \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n model?: string;\n}\n\nexport interface HookOutput {\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"emit_hud\"; hudEmit: HudEmit } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nexport interface HudEmit {\n sessionId: string;\n activeMode: string | null;\n contextPct: number;\n tokensUsed: number;\n tokensTotal: number;\n agentsActive: string[];\n lastAgent: string;\n lastOutput: string;\n taskProgress: number;\n}\n\ninterface SessionState {\n version: string;\n session_id: string;\n started_at: number;\n model: string;\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n tools_used: string[];\n skills_used: string[];\n agents_used: string[];\n active_mode: string | null;\n}\n\nfunction getStatePath(sessionId?: string): string {\n const base = join(homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, \"session.json\");\n }\n return join(base, \"session.json\");\n}\n\nfunction getHudLinePath(): string {\n return join(homedir(), \".omp\", \"hud.line\");\n}\n\nfunction ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\n}\n\nfunction formatAge(startedAt: number): string {\n const elapsed = Date.now() - startedAt;\n const mins = Math.floor(elapsed / 60000);\n if (mins < 60) return `${mins}m`;\n const hours = Math.floor(mins / 60);\n const remainingMins = mins % 60;\n return `${hours}h${remainingMins}m`;\n}\n\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `~${(tokens / 1_000_000).toFixed(1)}M`;\n if (tokens >= 1_000) return `~${(tokens / 1_000).toFixed(1)}k`;\n return `~${tokens}`;\n}\n\nfunction buildHudLine(state: SessionState): string {\n const age = formatAge(state.started_at);\n const tokens = formatTokens(state.tokens_estimated);\n const ctx = state.context_pct;\n const tools = state.tools_used.length;\n const skills = state.skills_used.length;\n const agents = state.agents_used.length;\n const mode = state.active_mode || \"-\";\n const model = state.model || \"sonnet\";\n\n return `OMP v${state.version} | ${model} | tkn: ${tokens}/${state.token_budget} | ctx: ${ctx}% | session: ${age} | tools: ${tools} | skills: ${skills} | agents: ${agents} | mode: ${mode}`;\n}\n\nfunction processSessionStart(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n const sessionId = input.session_id || \"default\";\n\n const state: SessionState = {\n version: \"1.0.0\",\n session_id: sessionId,\n started_at: Date.now(),\n model: input.model || \"claude-sonnet-4.5\",\n tokens_estimated: 0,\n token_budget: 200_000,\n context_pct: 0,\n tools_used: [],\n skills_used: [],\n agents_used: [],\n active_mode: null,\n };\n\n const statePath = getStatePath(sessionId);\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n log.push(`Session initialized: ${sessionId}`);\n\n const hudLine = buildHudLine(state);\n const hudPath = getHudLinePath();\n ensureDir(hudPath);\n writeFileSync(hudPath, hudLine, \"utf-8\");\n log.push(`HUD line written: ${hudLine}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"emit_hud\",\n hudEmit: {\n sessionId,\n activeMode: null,\n contextPct: 0,\n tokensUsed: 0,\n tokensTotal: 200_000,\n agentsActive: [],\n lastAgent: \"-\",\n lastOutput: \"\",\n taskProgress: 0,\n },\n },\n ],\n log,\n };\n}\n\nfunction processPostToolUse(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n const raw = JSON.parse(readFileSync(statePath, \"utf-8\"));\n state = {\n ...raw,\n tools_used: Array.isArray(raw.tools_used) ? raw.tools_used : [],\n skills_used: Array.isArray(raw.skills_used) ? raw.skills_used : [],\n agents_used: Array.isArray(raw.agents_used) ? raw.agents_used : [],\n };\n } catch {\n // Fall back to session start behavior if no state\n return processSessionStart(input);\n }\n\n // Update tools used\n if (input.tool_name && !state.tools_used.includes(input.tool_name)) {\n state.tools_used.push(input.tool_name);\n }\n\n // Recalculate HUD line\n const hudLine = buildHudLine(state);\n const hudPath = getHudLinePath();\n ensureDir(hudPath);\n writeFileSync(hudPath, hudLine, \"utf-8\");\n log.push(`HUD updated: ${hudLine}`);\n\n // Write updated state\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"emit_hud\",\n hudEmit: {\n sessionId: state.session_id,\n activeMode: state.active_mode,\n contextPct: state.context_pct,\n tokensUsed: state.tokens_estimated,\n tokensTotal: state.token_budget,\n agentsActive: state.agents_used,\n lastAgent: state.agents_used[state.agents_used.length - 1] || \"-\",\n lastOutput: \"\",\n taskProgress: 0,\n },\n },\n ],\n log,\n };\n}\n\nexport function processHook(input: HookInput): HookOutput {\n if (input.hook_type === \"SessionStart\") {\n return processSessionStart(input);\n }\n if (input.hook_type === \"PostToolUse\") {\n return processPostToolUse(input);\n }\n return {\n status: \"skip\",\n latencyMs: 0,\n mutations: [],\n log: [\"Unknown hook type\"],\n };\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/hooks/keyword-detector.mts
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
var KEYWORD_MAP = {
|
|
4
|
+
"autopilot:": "autopilot",
|
|
5
|
+
"ralph:": "ralph",
|
|
6
|
+
"ulw:": "ultrawork",
|
|
7
|
+
"team:": "team",
|
|
8
|
+
"eco:": "ecomode",
|
|
9
|
+
"swarm:": "swarm",
|
|
10
|
+
"pipeline:": "pipeline",
|
|
11
|
+
"plan:": "omp-plan",
|
|
12
|
+
// Aliases (shortcut commands)
|
|
13
|
+
"setup:": "setup",
|
|
14
|
+
"ralplan:": "ralplan",
|
|
15
|
+
"ultraqa:": "ultraqa",
|
|
16
|
+
"mcp:": "mcp-setup",
|
|
17
|
+
"ultrawork:": "ultrawork",
|
|
18
|
+
"ecomode:": "ecomode",
|
|
19
|
+
// Phase 1.1 skill stubs (19 total from plugin.json)
|
|
20
|
+
"/autopilot": "autopilot",
|
|
21
|
+
"/ralph": "ralph",
|
|
22
|
+
"/ulw": "ultrawork",
|
|
23
|
+
"/team": "team",
|
|
24
|
+
"/eco": "ecomode",
|
|
25
|
+
"/swarm": "swarm",
|
|
26
|
+
"/pipeline": "pipeline",
|
|
27
|
+
"/deep-interview": "deep-interview",
|
|
28
|
+
"/omp-plan": "omp-plan",
|
|
29
|
+
"/omp-setup": "omp-setup",
|
|
30
|
+
"/hud": "hud",
|
|
31
|
+
"/wiki": "wiki",
|
|
32
|
+
"/learner": "learner",
|
|
33
|
+
"/note": "note",
|
|
34
|
+
"/trace": "trace",
|
|
35
|
+
"/release": "release",
|
|
36
|
+
"/configure-notifications": "configure-notifications",
|
|
37
|
+
"/psm": "psm",
|
|
38
|
+
"/swe-bench": "swe-bench",
|
|
39
|
+
// v1.2 graph provider + spending skills
|
|
40
|
+
"graphify:": "graphify",
|
|
41
|
+
"graphwiki:": "graphwiki",
|
|
42
|
+
"graph:": "graph-provider",
|
|
43
|
+
"spending:": "spending",
|
|
44
|
+
"/graphify": "graphify",
|
|
45
|
+
"/graphwiki": "graphwiki",
|
|
46
|
+
"/graph-provider": "graph-provider",
|
|
47
|
+
"/spending": "spending"
|
|
48
|
+
};
|
|
49
|
+
function detectKeyword(prompt) {
|
|
50
|
+
const trimmed = prompt.trimStart();
|
|
51
|
+
for (const [keyword, skillId] of Object.entries(KEYWORD_MAP)) {
|
|
52
|
+
if (trimmed.startsWith(keyword)) {
|
|
53
|
+
return {
|
|
54
|
+
keyword,
|
|
55
|
+
skillId,
|
|
56
|
+
position: 0
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const slashPattern = /^\/([a-zA-Z]+)\b/;
|
|
61
|
+
const slashMatch = trimmed.match(slashPattern);
|
|
62
|
+
if (slashMatch) {
|
|
63
|
+
const cmd = slashMatch[1].toLowerCase();
|
|
64
|
+
const skillId = KEYWORD_MAP[`${cmd}:`] || cmd;
|
|
65
|
+
if (skillId !== cmd || Object.values(KEYWORD_MAP).includes(cmd)) {
|
|
66
|
+
return {
|
|
67
|
+
keyword: slashMatch[0],
|
|
68
|
+
skillId,
|
|
69
|
+
position: 0
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function processHook(input) {
|
|
76
|
+
const start = Date.now();
|
|
77
|
+
const log = [];
|
|
78
|
+
try {
|
|
79
|
+
if (input.hook_type !== "UserPromptSubmitted") {
|
|
80
|
+
return {
|
|
81
|
+
status: "skip",
|
|
82
|
+
latencyMs: Date.now() - start,
|
|
83
|
+
mutations: [],
|
|
84
|
+
log: ["Not a UserPromptSubmitted hook"]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const match = detectKeyword(input.prompt);
|
|
88
|
+
if (!match) {
|
|
89
|
+
return {
|
|
90
|
+
status: "ok",
|
|
91
|
+
latencyMs: Date.now() - start,
|
|
92
|
+
mutations: [],
|
|
93
|
+
log: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();
|
|
97
|
+
const rewritten = `/oh-my-githubcopilot:${match.skillId}${taskPart ? ` ${taskPart}` : ""}`;
|
|
98
|
+
log.push(`Keyword detected: "${match.keyword}" \u2192 skill: ${match.skillId}`);
|
|
99
|
+
log.push(`Rewritten: "${rewritten}"`);
|
|
100
|
+
return {
|
|
101
|
+
status: "ok",
|
|
102
|
+
latencyMs: Date.now() - start,
|
|
103
|
+
modifiedPrompt: rewritten,
|
|
104
|
+
mutations: [
|
|
105
|
+
{ type: "set_mode", mode: match.skillId },
|
|
106
|
+
{ type: "log", level: "info", message: `Skill activated: ${match.skillId}` }
|
|
107
|
+
],
|
|
108
|
+
log
|
|
109
|
+
};
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
status: "error",
|
|
113
|
+
latencyMs: Date.now() - start,
|
|
114
|
+
mutations: [],
|
|
115
|
+
log: [`Error: ${err}`]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
120
|
+
const input = JSON.parse(await readStdin());
|
|
121
|
+
const output = processHook(input);
|
|
122
|
+
console.log(JSON.stringify(output));
|
|
123
|
+
}
|
|
124
|
+
async function readStdin() {
|
|
125
|
+
const chunks = [];
|
|
126
|
+
for await (const chunk of process.stdin) {
|
|
127
|
+
chunks.push(chunk);
|
|
128
|
+
}
|
|
129
|
+
return chunks.join("");
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
processHook
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=keyword-detector.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/keyword-detector.mts"],
|
|
4
|
+
"sourcesContent": ["/**\n * keyword-detector hook\n * Trigger: pre-cycle (UserPromptSubmitted equivalent)\n * Priority: 100 (runs first)\n *\n * Scans incoming prompts for magic keywords and rewrites them\n * to skill invocation slash commands.\n */\n\nexport interface KeywordMatch {\n keyword: string;\n skillId: string;\n position: number;\n}\n\nconst KEYWORD_MAP: Record<string, string> = {\n \"autopilot:\": \"autopilot\",\n \"ralph:\": \"ralph\",\n \"ulw:\": \"ultrawork\",\n \"team:\": \"team\",\n \"eco:\": \"ecomode\",\n \"swarm:\": \"swarm\",\n \"pipeline:\": \"pipeline\",\n \"plan:\": \"omp-plan\",\n // Aliases (shortcut commands)\n \"setup:\": \"setup\",\n \"ralplan:\": \"ralplan\",\n \"ultraqa:\": \"ultraqa\",\n \"mcp:\": \"mcp-setup\",\n \"ultrawork:\": \"ultrawork\",\n \"ecomode:\": \"ecomode\",\n // Phase 1.1 skill stubs (19 total from plugin.json)\n \"/autopilot\": \"autopilot\",\n \"/ralph\": \"ralph\",\n \"/ulw\": \"ultrawork\",\n \"/team\": \"team\",\n \"/eco\": \"ecomode\",\n \"/swarm\": \"swarm\",\n \"/pipeline\": \"pipeline\",\n \"/deep-interview\": \"deep-interview\",\n \"/omp-plan\": \"omp-plan\",\n \"/omp-setup\": \"omp-setup\",\n \"/hud\": \"hud\",\n \"/wiki\": \"wiki\",\n \"/learner\": \"learner\",\n \"/note\": \"note\",\n \"/trace\": \"trace\",\n \"/release\": \"release\",\n \"/configure-notifications\": \"configure-notifications\",\n \"/psm\": \"psm\",\n \"/swe-bench\": \"swe-bench\",\n // v1.2 graph provider + spending skills\n \"graphify:\": \"graphify\",\n \"graphwiki:\": \"graphwiki\",\n \"graph:\": \"graph-provider\",\n \"spending:\": \"spending\",\n \"/graphify\": \"graphify\",\n \"/graphwiki\": \"graphwiki\",\n \"/graph-provider\": \"graph-provider\",\n \"/spending\": \"spending\",\n};\n\nexport interface HookInput {\n hook_type: \"UserPromptSubmitted\";\n prompt: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n modifiedPrompt?: string;\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_mode\"; mode: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\nfunction detectKeyword(prompt: string): KeywordMatch | null {\n const trimmed = prompt.trimStart();\n\n // Case-sensitive check for : suffixed forms\n for (const [keyword, skillId] of Object.entries(KEYWORD_MAP)) {\n if (trimmed.startsWith(keyword)) {\n return {\n keyword,\n skillId,\n position: 0,\n };\n }\n }\n\n // Case-insensitive check for slash forms\n const slashPattern = /^\\/([a-zA-Z]+)\\b/;\n const slashMatch = trimmed.match(slashPattern);\n if (slashMatch) {\n const cmd = slashMatch[1].toLowerCase();\n // Map /autopilot -> autopilot, /ralph -> ralph, etc.\n const skillId = KEYWORD_MAP[`${cmd}:`] || cmd;\n if (skillId !== cmd || Object.values(KEYWORD_MAP).includes(cmd)) {\n return {\n keyword: slashMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n return null;\n}\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"UserPromptSubmitted\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"Not a UserPromptSubmitted hook\"],\n };\n }\n\n const match = detectKeyword(input.prompt);\n if (!match) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Rewrite prompt to invoke the skill\n const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();\n const rewritten = `/oh-my-githubcopilot:${match.skillId}${taskPart ? ` ${taskPart}` : \"\"}`;\n\n log.push(`Keyword detected: \"${match.keyword}\" \u2192 skill: ${match.skillId}`);\n log.push(`Rewritten: \"${rewritten}\"`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n modifiedPrompt: rewritten,\n mutations: [\n { type: \"set_mode\", mode: match.skillId },\n { type: \"log\", level: \"info\", message: `Skill activated: ${match.skillId}` },\n ],\n log,\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/hooks/model-router.mts
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
var TIER_RECOMMENDATIONS = {
|
|
4
|
+
high: "model: claude-opus-4.6 or gpt-5 recommended for this task (architecture, security, critical decisions)",
|
|
5
|
+
standard: "model: claude-sonnet-4.5 recommended for this task (standard implementation and review)",
|
|
6
|
+
fast: "model: gpt-5.4-mini or haiku recommended for quick lookups and formatting"
|
|
7
|
+
};
|
|
8
|
+
var DEFAULT_TIER = "standard";
|
|
9
|
+
function processHook(input) {
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
if (input.hook_type !== "PreToolUse") {
|
|
13
|
+
return {
|
|
14
|
+
status: "skip",
|
|
15
|
+
latencyMs: Date.now() - start,
|
|
16
|
+
mutations: [],
|
|
17
|
+
log: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const agentId = input.agent_id;
|
|
21
|
+
if (!agentId) {
|
|
22
|
+
return {
|
|
23
|
+
status: "ok",
|
|
24
|
+
latencyMs: Date.now() - start,
|
|
25
|
+
mutations: [],
|
|
26
|
+
log: []
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const agentTier = getAgentTier(agentId);
|
|
30
|
+
const recommendation = TIER_RECOMMENDATIONS[agentTier] || TIER_RECOMMENDATIONS[DEFAULT_TIER];
|
|
31
|
+
const mutations = [
|
|
32
|
+
{ type: "set_model", model: agentTierToModel(agentTier) }
|
|
33
|
+
];
|
|
34
|
+
return {
|
|
35
|
+
status: "ok",
|
|
36
|
+
latencyMs: Date.now() - start,
|
|
37
|
+
additionalContext: recommendation,
|
|
38
|
+
mutations,
|
|
39
|
+
log: [`${agentId} \u2192 tier: ${agentTier} \u2192 ${agentTierToModel(agentTier)}`]
|
|
40
|
+
};
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
status: "error",
|
|
44
|
+
latencyMs: Date.now() - start,
|
|
45
|
+
mutations: [],
|
|
46
|
+
log: [`Error: ${err}`]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function getAgentTier(agentId) {
|
|
51
|
+
if (["orchestrator", "architect", "planner", "reviewer-security", "critic"].includes(agentId)) {
|
|
52
|
+
return "high";
|
|
53
|
+
}
|
|
54
|
+
if (["explorer", "writer"].includes(agentId)) {
|
|
55
|
+
return "fast";
|
|
56
|
+
}
|
|
57
|
+
return "standard";
|
|
58
|
+
}
|
|
59
|
+
function agentTierToModel(tier) {
|
|
60
|
+
if (tier === "high") return "opus";
|
|
61
|
+
if (tier === "fast") return "haiku";
|
|
62
|
+
return "sonnet";
|
|
63
|
+
}
|
|
64
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
65
|
+
const input = JSON.parse(await readStdin());
|
|
66
|
+
const output = processHook(input);
|
|
67
|
+
console.log(JSON.stringify(output));
|
|
68
|
+
}
|
|
69
|
+
async function readStdin() {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
for await (const chunk of process.stdin) {
|
|
72
|
+
chunks.push(chunk);
|
|
73
|
+
}
|
|
74
|
+
return chunks.join("");
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
processHook
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=model-router.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/model-router.mts"],
|
|
4
|
+
"sourcesContent": ["/**\n * model-router hook\n * Trigger: pre-cycle (PreToolUse equivalent)\n * Priority: 80\n *\n * Reads agent frontmatter model_tier and adds advisory additionalContext.\n */\n\nexport interface HookInput {\n hook_type: \"PreToolUse\";\n tool_name?: string;\n agent_id?: string;\n session_id?: string;\n}\n\nexport interface HookOutput {\n decision?: \"allow\";\n additionalContext?: string;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_model\"; model: \"opus\" | \"sonnet\" | \"haiku\" }>;\n log: string[];\n}\n\n// Model tier recommendations \u2014 advisory only\nconst TIER_RECOMMENDATIONS: Record<string, string> = {\n high: \"model: claude-opus-4.6 or gpt-5 recommended for this task (architecture, security, critical decisions)\",\n standard: \"model: claude-sonnet-4.5 recommended for this task (standard implementation and review)\",\n fast: \"model: gpt-5.4-mini or haiku recommended for quick lookups and formatting\",\n};\n\n// Default if agent tier unknown\nconst DEFAULT_TIER = \"standard\";\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n\n try {\n if (input.hook_type !== \"PreToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const agentId = input.agent_id;\n if (!agentId) {\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Agent tier is determined by agent frontmatter in the agent definition files.\n // This hook reads agent metadata from the session state or agent registry.\n // For now, we use a simple mapping based on known agent tiers.\n const agentTier = getAgentTier(agentId);\n const recommendation = TIER_RECOMMENDATIONS[agentTier] || TIER_RECOMMENDATIONS[DEFAULT_TIER];\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_model\", model: agentTierToModel(agentTier) },\n ];\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n additionalContext: recommendation,\n mutations,\n log: [`${agentId} \u2192 tier: ${agentTier} \u2192 ${agentTierToModel(agentTier)}`],\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\nfunction getAgentTier(agentId: string): string {\n // Tier 1 \u2014 High\n if ([\"orchestrator\", \"architect\", \"planner\", \"reviewer-security\", \"critic\"].includes(agentId)) {\n return \"high\";\n }\n // Tier 3 \u2014 Fast\n if ([\"explorer\", \"writer\"].includes(agentId)) {\n return \"fast\";\n }\n // Tier 2 \u2014 Standard (default)\n return \"standard\";\n}\n\nfunction agentTierToModel(tier: string): \"opus\" | \"sonnet\" | \"haiku\" {\n if (tier === \"high\") return \"opus\";\n if (tier === \"fast\") return \"haiku\";\n return \"sonnet\";\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
|
|
5
|
+
"mappings": ";AAwGA,SAAS,qBAAqB;AA/E9B,IAAM,uBAA+C;AAAA,EACnD,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AACR;AAGA,IAAM,eAAe;AAEd,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,cAAc;AACpC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAEA,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAKA,UAAM,YAAY,aAAa,OAAO;AACtC,UAAM,iBAAiB,qBAAqB,SAAS,KAAK,qBAAqB,YAAY;AAE3F,UAAM,YAAqC;AAAA,MACzC,EAAE,MAAM,aAAa,OAAO,iBAAiB,SAAS,EAAE;AAAA,IAC1D;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,mBAAmB;AAAA,MACnB;AAAA,MACA,KAAK,CAAC,GAAG,OAAO,iBAAY,SAAS,WAAM,iBAAiB,SAAS,CAAC,EAAE;AAAA,IAC1E;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAyB;AAE7C,MAAI,CAAC,gBAAgB,aAAa,WAAW,qBAAqB,QAAQ,EAAE,SAAS,OAAO,GAAG;AAC7F,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY,QAAQ,EAAE,SAAS,OAAO,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAA2C;AACnE,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,OAAQ,QAAO;AAC5B,SAAO;AACT;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/hooks/stop-continuation.mts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
function getModeStatePath(mode, sessionId) {
|
|
7
|
+
const base = join(homedir(), ".omp", "state");
|
|
8
|
+
if (sessionId) {
|
|
9
|
+
return join(base, "sessions", sessionId, `${mode}-state.json`);
|
|
10
|
+
}
|
|
11
|
+
return join(base, `${mode}-state.json`);
|
|
12
|
+
}
|
|
13
|
+
function readModeState(mode, sessionId) {
|
|
14
|
+
try {
|
|
15
|
+
const path = getModeStatePath(mode, sessionId);
|
|
16
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var PERSISTENT_MODES = ["team", "ralph", "ultrawork"];
|
|
22
|
+
function processHook(input) {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const log = [];
|
|
25
|
+
try {
|
|
26
|
+
if (input.hook_type !== "SessionEnd") {
|
|
27
|
+
return {
|
|
28
|
+
status: "skip",
|
|
29
|
+
latencyMs: Date.now() - start,
|
|
30
|
+
mutations: [],
|
|
31
|
+
log: []
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
for (const mode of PERSISTENT_MODES) {
|
|
35
|
+
const state = readModeState(mode, input.session_id);
|
|
36
|
+
if (state?.active) {
|
|
37
|
+
const reason = `${mode} mode is still active.`;
|
|
38
|
+
log.push(`Stop continuation: ${reason}`);
|
|
39
|
+
return {
|
|
40
|
+
status: "ok",
|
|
41
|
+
latencyMs: Date.now() - start,
|
|
42
|
+
mutations: [
|
|
43
|
+
{
|
|
44
|
+
type: "stop",
|
|
45
|
+
reason: `${reason} Use /oh-my-githubcopilot:cancel to end it, or continue the session to keep going.`
|
|
46
|
+
},
|
|
47
|
+
{ type: "log", level: "info", message: reason }
|
|
48
|
+
],
|
|
49
|
+
log
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: "ok",
|
|
55
|
+
latencyMs: Date.now() - start,
|
|
56
|
+
mutations: [],
|
|
57
|
+
log: ["No persistent modes active"]
|
|
58
|
+
};
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
status: "error",
|
|
62
|
+
latencyMs: Date.now() - start,
|
|
63
|
+
mutations: [],
|
|
64
|
+
log: [`Error: ${err}`]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
69
|
+
const input = JSON.parse(await readStdin());
|
|
70
|
+
const output = processHook(input);
|
|
71
|
+
console.log(JSON.stringify(output));
|
|
72
|
+
}
|
|
73
|
+
async function readStdin() {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
for await (const chunk of process.stdin) {
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
}
|
|
78
|
+
return chunks.join("");
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
processHook
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=stop-continuation.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/stop-continuation.mts"],
|
|
4
|
+
"sourcesContent": ["/**\n * stop-continuation hook\n * Trigger: post-message (SessionEnd equivalent)\n * Priority: 50\n *\n * Detects active persistent modes (ralph, ultrawork, team) and\n * returns continue instructions so the user can decide whether\n * to keep going.\n */\n\nimport { readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface HookInput {\n hook_type: \"SessionEnd\";\n session_id?: string;\n message?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"stop\"; reason: string } | { type: \"log\"; level: \"info\"; message: string }>;\n log: string[];\n}\n\ninterface ModeState {\n active?: boolean;\n mode?: string;\n linked_ultrawork?: boolean;\n linked_team?: boolean;\n}\n\nfunction getModeStatePath(mode: string, sessionId?: string): string {\n const base = join(homedir(), \".omp\", \"state\");\n if (sessionId) {\n return join(base, \"sessions\", sessionId, `${mode}-state.json`);\n }\n return join(base, `${mode}-state.json`);\n}\n\nfunction readModeState(mode: string, sessionId?: string): ModeState | null {\n try {\n const path = getModeStatePath(mode, sessionId);\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n// Priority order for checking: team > ralph > ultrawork\nconst PERSISTENT_MODES = [\"team\", \"ralph\", \"ultrawork\"];\n\nexport function processHook(input: HookInput): HookOutput {\n const start = Date.now();\n const log: string[] = [];\n\n try {\n if (input.hook_type !== \"SessionEnd\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n // Check for active persistent modes\n for (const mode of PERSISTENT_MODES) {\n const state = readModeState(mode, input.session_id);\n if (state?.active) {\n const reason = `${mode} mode is still active.`;\n log.push(`Stop continuation: ${reason}`);\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [\n {\n type: \"stop\",\n reason: `${reason} Use /oh-my-githubcopilot:cancel to end it, or continue the session to keep going.`,\n },\n { type: \"log\", level: \"info\", message: reason },\n ],\n log,\n };\n }\n }\n\n return {\n status: \"ok\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [\"No persistent modes active\"],\n };\n } catch (err) {\n return {\n status: \"error\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [`Error: ${err}`],\n };\n }\n}\n\n// Main entry point \u2014 only runs when executed directly (not imported)\nimport { fileURLToPath } from \"url\";\n\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n const input: HookInput = JSON.parse(await readStdin());\n const output = processHook(input);\n console.log(JSON.stringify(output));\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: string[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks.join(\"\");\n}\n"],
|
|
5
|
+
"mappings": ";AAUA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,YAAY;AAgGrB,SAAS,qBAAqB;AAzE9B,SAAS,iBAAiB,MAAc,WAA4B;AAClE,QAAM,OAAO,KAAK,QAAQ,GAAG,QAAQ,OAAO;AAC5C,MAAI,WAAW;AACb,WAAO,KAAK,MAAM,YAAY,WAAW,GAAG,IAAI,aAAa;AAAA,EAC/D;AACA,SAAO,KAAK,MAAM,GAAG,IAAI,aAAa;AACxC;AAEA,SAAS,cAAc,MAAc,WAAsC;AACzE,MAAI;AACF,UAAM,OAAO,iBAAiB,MAAM,SAAS;AAC7C,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW;AAE/C,SAAS,YAAY,OAA8B;AACxD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAgB,CAAC;AAEvB,MAAI;AACF,QAAI,MAAM,cAAc,cAAc;AACpC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,WAAW,CAAC;AAAA,QACZ,KAAK,CAAC;AAAA,MACR;AAAA,IACF;AAGA,eAAW,QAAQ,kBAAkB;AACnC,YAAM,QAAQ,cAAc,MAAM,MAAM,UAAU;AAClD,UAAI,OAAO,QAAQ;AACjB,cAAM,SAAS,GAAG,IAAI;AACtB,YAAI,KAAK,sBAAsB,MAAM,EAAE;AAEvC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW,KAAK,IAAI,IAAI;AAAA,UACxB,WAAW;AAAA,YACT;AAAA,cACE,MAAM;AAAA,cACN,QAAQ,GAAG,MAAM;AAAA,YACnB;AAAA,YACA,EAAE,MAAM,OAAO,OAAO,QAAQ,SAAS,OAAO;AAAA,UAChD;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,4BAA4B;AAAA,IACpC;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,WAAW,CAAC;AAAA,MACZ,KAAK,CAAC,UAAU,GAAG,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAKA,IAAI,QAAQ,KAAK,CAAC,MAAM,cAAc,YAAY,GAAG,GAAG;AACtD,QAAM,QAAmB,KAAK,MAAM,MAAM,UAAU,CAAC;AACrD,QAAM,SAAS,YAAY,KAAK;AAChC,UAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AACpC;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|