oh-my-githubcopilot 1.4.1 → 1.5.7
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 +11 -3
- package/.mcp.json +17 -0
- package/CHANGELOG.md +124 -1
- package/README.md +162 -82
- package/agents/analyst.agent.md +27 -0
- package/agents/architect.agent.md +24 -0
- package/agents/code-reviewer.agent.md +24 -0
- package/agents/critic.agent.md +24 -0
- package/agents/debugger.agent.md +24 -0
- package/agents/designer.agent.md +24 -0
- package/agents/document-specialist.agent.md +24 -0
- package/agents/executor.agent.md +27 -0
- package/agents/explorer.agent.md +23 -0
- package/agents/git-master.agent.md +24 -0
- package/agents/orchestrator.agent.md +26 -0
- package/agents/planner.agent.md +24 -0
- package/agents/qa-tester.agent.md +24 -0
- package/agents/researcher.agent.md +18 -0
- package/agents/reviewer.agent.md +23 -0
- package/agents/scientist.agent.md +20 -0
- package/agents/security-reviewer.agent.md +20 -0
- package/agents/simplifier.agent.md +20 -0
- package/agents/test-engineer.agent.md +20 -0
- package/agents/tester.agent.md +20 -0
- package/agents/tracer.agent.md +24 -0
- package/agents/verifier.agent.md +19 -0
- package/agents/writer.agent.md +24 -0
- package/bin/omp-statusline.mjs +179 -0
- package/bin/omp-statusline.mjs.map +7 -0
- package/bin/omp-statusline.sh +21 -0
- package/bin/omp.mjs +302 -13
- package/bin/omp.mjs.map +4 -4
- package/dist/hooks/hud-emitter.mjs +268 -82
- package/dist/hooks/hud-emitter.mjs.map +4 -4
- package/dist/hooks/keyword-detector.mjs +83 -21
- package/dist/hooks/keyword-detector.mjs.map +2 -2
- package/dist/hooks/model-router.mjs +1 -1
- package/dist/hooks/model-router.mjs.map +1 -1
- package/dist/hooks/stop-continuation.mjs +1 -1
- package/dist/hooks/stop-continuation.mjs.map +1 -1
- package/dist/hooks/token-tracker.mjs +2 -1
- package/dist/hooks/token-tracker.mjs.map +2 -2
- package/dist/mcp/server.mjs +57 -41
- package/dist/mcp/server.mjs.map +4 -4
- package/dist/skills/setup.mjs +39 -27
- package/dist/skills/setup.mjs.map +4 -4
- package/hooks/hooks.json +39 -45
- package/package.json +7 -3
- package/plugin.json +49 -0
- package/skills/autopilot/SKILL.md +6 -0
- package/skills/configure-notifications/SKILL.md +6 -0
- package/skills/deep-interview/SKILL.md +6 -0
- package/skills/ecomode/SKILL.md +6 -0
- package/skills/graph-provider/SKILL.md +6 -0
- package/skills/graphify/SKILL.md +6 -0
- package/skills/graphwiki/SKILL.md +6 -0
- package/skills/hud/SKILL.md +6 -0
- package/skills/learner/SKILL.md +6 -0
- package/skills/mcp-setup/SKILL.md +6 -0
- package/skills/note/SKILL.md +6 -0
- package/skills/omp-plan/SKILL.md +6 -0
- package/skills/omp-setup/SKILL.md +15 -1
- package/skills/pipeline/SKILL.md +6 -0
- package/skills/psm/SKILL.md +6 -0
- package/skills/ralph/SKILL.md +6 -0
- package/skills/release/SKILL.md +6 -0
- package/skills/setup/SKILL.md +6 -0
- package/skills/spending/SKILL.md +6 -0
- package/skills/swarm/SKILL.md +6 -0
- package/skills/swe-bench/SKILL.md +6 -0
- package/skills/team/SKILL.md +6 -0
- package/skills/trace/SKILL.md +6 -0
- package/skills/ultrawork/SKILL.md +6 -0
- package/skills/wiki/SKILL.md +6 -0
- package/src/agents/analyst.md +0 -103
- package/src/agents/architect.md +0 -169
- package/src/agents/code-reviewer.md +0 -135
- package/src/agents/critic.md +0 -196
- package/src/agents/debugger.md +0 -132
- package/src/agents/designer.md +0 -103
- package/src/agents/document-specialist.md +0 -111
- package/src/agents/executor.md +0 -120
- package/src/agents/explorer.md +0 -98
- package/src/agents/git-master.md +0 -92
- package/src/agents/orchestrator.md +0 -125
- package/src/agents/planner.md +0 -106
- package/src/agents/qa-tester.md +0 -129
- package/src/agents/researcher.md +0 -102
- package/src/agents/reviewer.md +0 -100
- package/src/agents/scientist.md +0 -150
- package/src/agents/security-reviewer.md +0 -132
- package/src/agents/simplifier.md +0 -109
- package/src/agents/test-engineer.md +0 -124
- package/src/agents/tester.md +0 -102
- package/src/agents/tracer.md +0 -160
- package/src/agents/verifier.md +0 -100
- package/src/agents/writer.md +0 -96
|
@@ -2,53 +2,99 @@
|
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
var KEYWORD_MAP = {
|
|
4
4
|
"autopilot:": "autopilot",
|
|
5
|
+
"/autopilot": "autopilot",
|
|
6
|
+
"/omp:autopilot": "autopilot",
|
|
5
7
|
"ralph:": "ralph",
|
|
8
|
+
"/ralph": "ralph",
|
|
9
|
+
"/omp:ralph": "ralph",
|
|
6
10
|
"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
11
|
"ultrawork:": "ultrawork",
|
|
18
|
-
"ecomode:": "ecomode",
|
|
19
|
-
// Phase 1.1 skill stubs (19 total from plugin.json)
|
|
20
|
-
"/autopilot": "autopilot",
|
|
21
|
-
"/ralph": "ralph",
|
|
22
12
|
"/ulw": "ultrawork",
|
|
13
|
+
"/ultrawork": "ultrawork",
|
|
14
|
+
"/omp:ulw": "ultrawork",
|
|
15
|
+
"/omp:ultrawork": "ultrawork",
|
|
16
|
+
"team:": "team",
|
|
23
17
|
"/team": "team",
|
|
18
|
+
"/omp:team": "team",
|
|
19
|
+
"eco:": "ecomode",
|
|
20
|
+
"ecomode:": "ecomode",
|
|
24
21
|
"/eco": "ecomode",
|
|
22
|
+
"/ecomode": "ecomode",
|
|
23
|
+
"/omp:eco": "ecomode",
|
|
24
|
+
"/omp:ecomode": "ecomode",
|
|
25
|
+
"swarm:": "swarm",
|
|
25
26
|
"/swarm": "swarm",
|
|
27
|
+
"/omp:swarm": "swarm",
|
|
28
|
+
"pipeline:": "pipeline",
|
|
26
29
|
"/pipeline": "pipeline",
|
|
30
|
+
"/omp:pipeline": "pipeline",
|
|
31
|
+
"deep interview:": "deep-interview",
|
|
27
32
|
"/deep-interview": "deep-interview",
|
|
33
|
+
"/omp:deep-interview": "deep-interview",
|
|
34
|
+
"plan:": "omp-plan",
|
|
35
|
+
"/plan": "omp-plan",
|
|
28
36
|
"/omp-plan": "omp-plan",
|
|
37
|
+
"/omp:plan": "omp-plan",
|
|
38
|
+
"setup:": "omp-setup",
|
|
39
|
+
"/setup": "omp-setup",
|
|
29
40
|
"/omp-setup": "omp-setup",
|
|
41
|
+
"/omp:setup": "omp-setup",
|
|
42
|
+
"mcp:": "mcp-setup",
|
|
43
|
+
"mcp-setup:": "mcp-setup",
|
|
44
|
+
"/mcp": "mcp-setup",
|
|
45
|
+
"/mcp-setup": "mcp-setup",
|
|
46
|
+
"/omp:mcp-setup": "mcp-setup",
|
|
30
47
|
"/hud": "hud",
|
|
48
|
+
"hud:": "hud",
|
|
49
|
+
"/omp:hud": "hud",
|
|
31
50
|
"/wiki": "wiki",
|
|
51
|
+
"wiki:": "wiki",
|
|
52
|
+
"/omp:wiki": "wiki",
|
|
32
53
|
"/learner": "learner",
|
|
54
|
+
"learner:": "learner",
|
|
55
|
+
"/omp:learner": "learner",
|
|
33
56
|
"/note": "note",
|
|
57
|
+
"note:": "note",
|
|
58
|
+
"/omp:note": "note",
|
|
34
59
|
"/trace": "trace",
|
|
60
|
+
"trace:": "trace",
|
|
61
|
+
"/omp:trace": "trace",
|
|
35
62
|
"/release": "release",
|
|
63
|
+
"release:": "release",
|
|
64
|
+
"/omp:release": "release",
|
|
36
65
|
"/configure-notifications": "configure-notifications",
|
|
66
|
+
"configure-notifications:": "configure-notifications",
|
|
67
|
+
"/omp:configure-notifications": "configure-notifications",
|
|
37
68
|
"/psm": "psm",
|
|
69
|
+
"psm:": "psm",
|
|
70
|
+
"/omp:psm": "psm",
|
|
38
71
|
"/swe-bench": "swe-bench",
|
|
39
|
-
|
|
72
|
+
"swe-bench:": "swe-bench",
|
|
73
|
+
"/omp:swe-bench": "swe-bench",
|
|
40
74
|
"graphify:": "graphify",
|
|
75
|
+
"graph build": "graphify",
|
|
76
|
+
"build graph": "graphify",
|
|
41
77
|
"graphwiki:": "graphwiki",
|
|
42
78
|
"graph:": "graph-provider",
|
|
43
79
|
"spending:": "spending",
|
|
44
80
|
"/graphify": "graphify",
|
|
81
|
+
"/omp:graphify": "graphify",
|
|
45
82
|
"/graphwiki": "graphwiki",
|
|
83
|
+
"/omp:graphwiki": "graphwiki",
|
|
46
84
|
"/graph-provider": "graph-provider",
|
|
47
|
-
"/
|
|
85
|
+
"/omp:graph-provider": "graph-provider",
|
|
86
|
+
"/spending": "spending",
|
|
87
|
+
"/omp:spending": "spending"
|
|
88
|
+
};
|
|
89
|
+
var KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);
|
|
90
|
+
var CANONICAL_COMMAND_MAP = {
|
|
91
|
+
"omp-plan": "/omp:plan",
|
|
92
|
+
"omp-setup": "/setup",
|
|
93
|
+
"mcp-setup": "/mcp"
|
|
48
94
|
};
|
|
49
95
|
function detectKeyword(prompt) {
|
|
50
96
|
const trimmed = prompt.trimStart();
|
|
51
|
-
for (const [keyword, skillId] of
|
|
97
|
+
for (const [keyword, skillId] of KEYWORD_ENTRIES) {
|
|
52
98
|
if (trimmed.startsWith(keyword)) {
|
|
53
99
|
return {
|
|
54
100
|
keyword,
|
|
@@ -57,12 +103,12 @@ function detectKeyword(prompt) {
|
|
|
57
103
|
};
|
|
58
104
|
}
|
|
59
105
|
}
|
|
60
|
-
const slashPattern = /^\/([a-zA-Z]
|
|
106
|
+
const slashPattern = /^\/((?:omp:)?[a-zA-Z][a-zA-Z0-9-]*)\b/;
|
|
61
107
|
const slashMatch = trimmed.match(slashPattern);
|
|
62
108
|
if (slashMatch) {
|
|
63
109
|
const cmd = slashMatch[1].toLowerCase();
|
|
64
|
-
const skillId = KEYWORD_MAP[
|
|
65
|
-
if (skillId
|
|
110
|
+
const skillId = KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];
|
|
111
|
+
if (skillId) {
|
|
66
112
|
return {
|
|
67
113
|
keyword: slashMatch[0],
|
|
68
114
|
skillId,
|
|
@@ -70,8 +116,24 @@ function detectKeyword(prompt) {
|
|
|
70
116
|
};
|
|
71
117
|
}
|
|
72
118
|
}
|
|
119
|
+
const longNamespacePattern = /^\/?oh-my-githubcopilot:([a-zA-Z][a-zA-Z0-9-]*)\b/i;
|
|
120
|
+
const longNamespaceMatch = trimmed.match(longNamespacePattern);
|
|
121
|
+
if (longNamespaceMatch) {
|
|
122
|
+
const cmd = longNamespaceMatch[1].toLowerCase();
|
|
123
|
+
const skillId = KEYWORD_MAP[`/omp:${cmd}`] ?? KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];
|
|
124
|
+
if (skillId) {
|
|
125
|
+
return {
|
|
126
|
+
keyword: longNamespaceMatch[0],
|
|
127
|
+
skillId,
|
|
128
|
+
position: 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
73
132
|
return null;
|
|
74
133
|
}
|
|
134
|
+
function getCanonicalCommand(skillId) {
|
|
135
|
+
return CANONICAL_COMMAND_MAP[skillId] ?? `/omp:${skillId}`;
|
|
136
|
+
}
|
|
75
137
|
function processHook(input) {
|
|
76
138
|
const start = Date.now();
|
|
77
139
|
const log = [];
|
|
@@ -94,7 +156,7 @@ function processHook(input) {
|
|
|
94
156
|
};
|
|
95
157
|
}
|
|
96
158
|
const taskPart = input.prompt.slice(match.position + match.keyword.length).trim();
|
|
97
|
-
const rewritten =
|
|
159
|
+
const rewritten = `${getCanonicalCommand(match.skillId)}${taskPart ? ` ${taskPart}` : ""}`;
|
|
98
160
|
log.push(`Keyword detected: "${match.keyword}" \u2192 skill: ${match.skillId}`);
|
|
99
161
|
log.push(`Rewritten: "${rewritten}"`);
|
|
100
162
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 \"
|
|
5
|
-
"mappings": ";
|
|
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 \"/autopilot\": \"autopilot\",\n \"/omp:autopilot\": \"autopilot\",\n \"ralph:\": \"ralph\",\n \"/ralph\": \"ralph\",\n \"/omp:ralph\": \"ralph\",\n \"ulw:\": \"ultrawork\",\n \"ultrawork:\": \"ultrawork\",\n \"/ulw\": \"ultrawork\",\n \"/ultrawork\": \"ultrawork\",\n \"/omp:ulw\": \"ultrawork\",\n \"/omp:ultrawork\": \"ultrawork\",\n \"team:\": \"team\",\n \"/team\": \"team\",\n \"/omp:team\": \"team\",\n \"eco:\": \"ecomode\",\n \"ecomode:\": \"ecomode\",\n \"/eco\": \"ecomode\",\n \"/ecomode\": \"ecomode\",\n \"/omp:eco\": \"ecomode\",\n \"/omp:ecomode\": \"ecomode\",\n \"swarm:\": \"swarm\",\n \"/swarm\": \"swarm\",\n \"/omp:swarm\": \"swarm\",\n \"pipeline:\": \"pipeline\",\n \"/pipeline\": \"pipeline\",\n \"/omp:pipeline\": \"pipeline\",\n \"deep interview:\": \"deep-interview\",\n \"/deep-interview\": \"deep-interview\",\n \"/omp:deep-interview\": \"deep-interview\",\n \"plan:\": \"omp-plan\",\n \"/plan\": \"omp-plan\",\n \"/omp-plan\": \"omp-plan\",\n \"/omp:plan\": \"omp-plan\",\n \"setup:\": \"omp-setup\",\n \"/setup\": \"omp-setup\",\n \"/omp-setup\": \"omp-setup\",\n \"/omp:setup\": \"omp-setup\",\n \"mcp:\": \"mcp-setup\",\n \"mcp-setup:\": \"mcp-setup\",\n \"/mcp\": \"mcp-setup\",\n \"/mcp-setup\": \"mcp-setup\",\n \"/omp:mcp-setup\": \"mcp-setup\",\n \"/hud\": \"hud\",\n \"hud:\": \"hud\",\n \"/omp:hud\": \"hud\",\n \"/wiki\": \"wiki\",\n \"wiki:\": \"wiki\",\n \"/omp:wiki\": \"wiki\",\n \"/learner\": \"learner\",\n \"learner:\": \"learner\",\n \"/omp:learner\": \"learner\",\n \"/note\": \"note\",\n \"note:\": \"note\",\n \"/omp:note\": \"note\",\n \"/trace\": \"trace\",\n \"trace:\": \"trace\",\n \"/omp:trace\": \"trace\",\n \"/release\": \"release\",\n \"release:\": \"release\",\n \"/omp:release\": \"release\",\n \"/configure-notifications\": \"configure-notifications\",\n \"configure-notifications:\": \"configure-notifications\",\n \"/omp:configure-notifications\": \"configure-notifications\",\n \"/psm\": \"psm\",\n \"psm:\": \"psm\",\n \"/omp:psm\": \"psm\",\n \"/swe-bench\": \"swe-bench\",\n \"swe-bench:\": \"swe-bench\",\n \"/omp:swe-bench\": \"swe-bench\",\n \"graphify:\": \"graphify\",\n \"graph build\": \"graphify\",\n \"build graph\": \"graphify\",\n \"graphwiki:\": \"graphwiki\",\n \"graph:\": \"graph-provider\",\n \"spending:\": \"spending\",\n \"/graphify\": \"graphify\",\n \"/omp:graphify\": \"graphify\",\n \"/graphwiki\": \"graphwiki\",\n \"/omp:graphwiki\": \"graphwiki\",\n \"/graph-provider\": \"graph-provider\",\n \"/omp:graph-provider\": \"graph-provider\",\n \"/spending\": \"spending\",\n \"/omp:spending\": \"spending\",\n};\n\nconst KEYWORD_ENTRIES = Object.entries(KEYWORD_MAP).sort(([a], [b]) => b.length - a.length);\nconst CANONICAL_COMMAND_MAP: Record<string, string> = {\n \"omp-plan\": \"/omp:plan\",\n \"omp-setup\": \"/setup\",\n \"mcp-setup\": \"/mcp\",\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 // Prefer the longest literal alias match first so /mcp-setup wins over /mcp.\n for (const [keyword, skillId] of KEYWORD_ENTRIES) {\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 = /^\\/((?:omp:)?[a-zA-Z][a-zA-Z0-9-]*)\\b/;\n const slashMatch = trimmed.match(slashPattern);\n if (slashMatch) {\n const cmd = slashMatch[1].toLowerCase();\n const skillId = KEYWORD_MAP[`/${cmd}`] ?? KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: slashMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n // Compatibility: support long namespace aliases like\n // \"oh-my-githubcopilot:ralph\" (or \"/oh-my-githubcopilot:ralph\")\n const longNamespacePattern = /^\\/?oh-my-githubcopilot:([a-zA-Z][a-zA-Z0-9-]*)\\b/i;\n const longNamespaceMatch = trimmed.match(longNamespacePattern);\n if (longNamespaceMatch) {\n const cmd = longNamespaceMatch[1].toLowerCase();\n const skillId =\n KEYWORD_MAP[`/omp:${cmd}`] ??\n KEYWORD_MAP[`/${cmd}`] ??\n KEYWORD_MAP[`${cmd}:`];\n if (skillId) {\n return {\n keyword: longNamespaceMatch[0],\n skillId,\n position: 0,\n };\n }\n }\n\n return null;\n}\n\nfunction getCanonicalCommand(skillId: string): string {\n return CANONICAL_COMMAND_MAP[skillId] ?? `/omp:${skillId}`;\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 = `${getCanonicalCommand(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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
var TIER_RECOMMENDATIONS = {
|
|
4
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
|
+
standard: "model: claude-sonnet-4.6 recommended for this task (standard implementation and review)",
|
|
6
6
|
fast: "model: gpt-5.4-mini or haiku recommended for quick lookups and formatting"
|
|
7
7
|
};
|
|
8
8
|
var DEFAULT_TIER = "standard";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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.
|
|
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.6 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
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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -42,7 +42,7 @@ function processHook(input) {
|
|
|
42
42
|
mutations: [
|
|
43
43
|
{
|
|
44
44
|
type: "stop",
|
|
45
|
-
reason: `${reason} Use /
|
|
45
|
+
reason: `${reason} Use /cancel to end it, or continue the session to keep going.`
|
|
46
46
|
},
|
|
47
47
|
{ type: "log", level: "info", message: reason }
|
|
48
48
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 /
|
|
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 /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
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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -111,9 +111,10 @@ function processHook(input) {
|
|
|
111
111
|
try {
|
|
112
112
|
state = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
113
113
|
} catch {
|
|
114
|
+
const fallbackModel = input.model ?? "default";
|
|
114
115
|
state = {
|
|
115
116
|
tokens_estimated: 0,
|
|
116
|
-
token_budget: 2e5,
|
|
117
|
+
token_budget: MODEL_CONTEXTS[fallbackModel] ?? MODEL_CONTEXTS["default"] ?? 2e5,
|
|
117
118
|
context_pct: 0,
|
|
118
119
|
warnings_issued: /* @__PURE__ */ new Set()
|
|
119
120
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/hooks/token-tracker.mts", "../../src/spending/tracker.mts"],
|
|
4
|
-
"sourcesContent": ["/**\n * token-tracker hook\n * Trigger: post-message (PostToolUse equivalent)\n * Priority: 70\n *\n * Estimates token usage from character counts (1 token \u2248 4 chars).\n * Accumulates in session state. Warns at 60%, 80%, 90% thresholds.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { incrementSpending } from \"../spending/tracker.mjs\";\n\nexport interface HookInput {\n hook_type: \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_token_budget\"; budget: number } | { type: \"emit_hud\"; hudEmit: unknown } | { type: \"log\"; level: \"info\" | \"warn\" | \"error\"; message: string }>;\n log: string[];\n}\n\ninterface SessionState {\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n warnings_issued: Set<string>;\n}\n\n// Model context windows in tokens (for future model-specific budget lookup)\n// Exported for potential external use\nexport const MODEL_CONTEXTS = {\n \"claude-sonnet-4.5\": 200_000,\n \"claude-sonnet-4\": 200_000,\n \"claude-sonnet-4.6\": 200_000,\n \"claude-opus-4.6\": 200_000,\n \"gpt-5\": 128_000,\n \"gpt-5.4-mini\": 128_000,\n \"gemini-3-pro\": 128_000,\n default: 200_000,\n};\n\nconst WARNING_THRESHOLDS = [60, 80, 90];\n\nexport function estimateTokens(input: unknown): number {\n if (!input) return 0;\n try {\n const str = typeof input === \"string\" ? input : JSON.stringify(input);\n return Math.ceil(str.length / 4);\n } catch {\n return 0;\n }\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 ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\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 !== \"PostToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n state = JSON.parse(readFileSync(statePath, \"utf-8\"));\n } catch {\n // Initialize state if not found\n state = {\n tokens_estimated: 0,\n token_budget: 200_000,\n context_pct: 0,\n warnings_issued: new Set(),\n };\n }\n\n const inputTokens = estimateTokens(input.tool_input);\n const outputTokens = estimateTokens(input.tool_output);\n const delta = inputTokens + outputTokens;\n\n state.tokens_estimated += delta;\n state.context_pct = Math.min(100, Math.round((state.tokens_estimated / state.token_budget) * 100));\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_token_budget\", budget: state.token_budget },\n ];\n\n // Check warning thresholds\n for (const threshold of WARNING_THRESHOLDS) {\n const key = `warn_${threshold}`;\n if (state.context_pct >= threshold && !state.warnings_issued.has(key)) {\n state.warnings_issued.add(key);\n const message =\n threshold >= 90\n ? `CRITICAL: Context at ${state.context_pct}%. Tokens near budget limit.`\n : threshold >= 80\n ? `WARNING: Context at ${state.context_pct}%. Consider enabling ecomode.`\n : `INFO: Context at ${state.context_pct}%.`;\n mutations.push({ type: \"log\", level: threshold >= 80 ? \"warn\" : \"info\", message });\n log.push(message);\n }\n }\n\n // Write state back\n try {\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n } catch (e) {\n log.push(`Failed to write state: ${e}`);\n }\n\n // Track premium request spending\n const sessionId = input.session_id ?? `omp-${Date.now()}`;\n try { incrementSpending(sessionId); } catch { /* non-blocking */ }\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", "/**\n * Spending tracker for OMP.\n * Tracks premium API requests per session and per calendar month.\n * Persists to ~/.omp/state/spending-monthly.json\n *\n * // v1.1 known limitation: no /omp:spending reset command. To reset monthly counter manually: rm ~/.omp/state/spending-monthly.json\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, dirname } from \"path\";\nimport type { SpendingState } from \"./types.mjs\";\n\nconst SPENDING_PATH = join(homedir(), \".omp\", \"state\", \"spending-monthly.json\");\n\nfunction currentMonth(): string {\n const now = new Date();\n return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}`;\n}\n\nexport function loadSpending(sessionId: string): SpendingState {\n let raw: SpendingState;\n try {\n raw = JSON.parse(readFileSync(SPENDING_PATH, \"utf-8\")) as SpendingState;\n } catch {\n // Missing or malformed file \u2014 start fresh\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month: currentMonth(),\n monthlyPremiumRequests: 0,\n };\n }\n\n const month = currentMonth();\n\n // Reset monthly counter when month rolls over\n if (raw.month !== month) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: 0,\n };\n }\n\n // Reset session counter when session changes\n if (raw.sessionId !== sessionId) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: raw.monthlyPremiumRequests,\n };\n }\n\n return { ...raw, version: 1 };\n}\n\nexport function saveSpending(state: SpendingState): void {\n try {\n mkdirSync(dirname(SPENDING_PATH), { recursive: true });\n writeFileSync(SPENDING_PATH, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n console.warn(`[OMP] spending: failed to save state: ${e}`);\n }\n}\n\nexport function incrementSpending(sessionId: string): SpendingState {\n const state = loadSpending(sessionId);\n state.sessionPremiumRequests += 1;\n state.monthlyPremiumRequests += 1;\n saveSpending(state);\n return state;\n}\n"],
|
|
5
|
-
"mappings": ";AASA,SAAS,gBAAAA,eAAc,iBAAAC,gBAAe,aAAAC,kBAAiB;AACvD,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;;;ACHrB,SAAS,cAAc,eAAe,iBAAiB;AACvD,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAG9B,IAAM,gBAAgB,KAAK,QAAQ,GAAG,QAAQ,SAAS,uBAAuB;AAE9E,SAAS,eAAuB;AAC9B,QAAM,MAAM,oBAAI,KAAK;AACrB,SAAO,GAAG,IAAI,YAAY,CAAC,IAAI,OAAO,IAAI,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAC5E;AAEO,SAAS,aAAa,WAAkC;AAC7D,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,aAAa,eAAe,OAAO,CAAC;AAAA,EACvD,QAAQ;AAEN,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB,OAAO,aAAa;AAAA,MACpB,wBAAwB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,QAAQ,aAAa;AAG3B,MAAI,IAAI,UAAU,OAAO;AACvB,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB;AAAA,MACA,wBAAwB;AAAA,IAC1B;AAAA,EACF;AAGA,MAAI,IAAI,cAAc,WAAW;AAC/B,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,wBAAwB;AAAA,MACxB;AAAA,MACA,wBAAwB,IAAI;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,EAAE,GAAG,KAAK,SAAS,EAAE;AAC9B;AAEO,SAAS,aAAa,OAA4B;AACvD,MAAI;AACF,cAAU,QAAQ,aAAa,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,kBAAc,eAAe,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AAAA,EACtE,SAAS,GAAG;AACV,YAAQ,KAAK,yCAAyC,CAAC,EAAE;AAAA,EAC3D;AACF;AAEO,SAAS,kBAAkB,WAAkC;AAClE,QAAM,QAAQ,aAAa,SAAS;AACpC,QAAM,0BAA0B;AAChC,QAAM,0BAA0B;AAChC,eAAa,KAAK;AAClB,SAAO;AACT;;;
|
|
4
|
+
"sourcesContent": ["/**\n * token-tracker hook\n * Trigger: post-message (PostToolUse equivalent)\n * Priority: 70\n *\n * Estimates token usage from character counts (1 token \u2248 4 chars).\n * Accumulates in session state. Warns at 60%, 80%, 90% thresholds.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { incrementSpending } from \"../spending/tracker.mjs\";\n\nexport interface HookInput {\n hook_type: \"PostToolUse\";\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n session_id?: string;\n}\n\nexport interface HookOutput {\n modifiedResult?: unknown;\n status: \"ok\" | \"skip\" | \"error\";\n latencyMs: number;\n mutations: Array<{ type: \"set_token_budget\"; budget: number } | { type: \"emit_hud\"; hudEmit: unknown } | { type: \"log\"; level: \"info\" | \"warn\" | \"error\"; message: string }>;\n log: string[];\n}\n\ninterface SessionState {\n tokens_estimated: number;\n token_budget: number;\n context_pct: number;\n warnings_issued: Set<string>;\n}\n\n// Model context windows in tokens (for future model-specific budget lookup)\n// Exported for potential external use\nexport const MODEL_CONTEXTS = {\n \"claude-sonnet-4.5\": 200_000,\n \"claude-sonnet-4\": 200_000,\n \"claude-sonnet-4.6\": 200_000,\n \"claude-opus-4.6\": 200_000,\n \"gpt-5\": 128_000,\n \"gpt-5.4-mini\": 128_000,\n \"gemini-3-pro\": 128_000,\n default: 200_000,\n};\n\nconst WARNING_THRESHOLDS = [60, 80, 90];\n\nexport function estimateTokens(input: unknown): number {\n if (!input) return 0;\n try {\n const str = typeof input === \"string\" ? input : JSON.stringify(input);\n return Math.ceil(str.length / 4);\n } catch {\n return 0;\n }\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 ensureDir(path: string): void {\n mkdirSync(path.substring(0, path.lastIndexOf(\"/\")), { recursive: true });\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 !== \"PostToolUse\") {\n return {\n status: \"skip\",\n latencyMs: Date.now() - start,\n mutations: [],\n log: [],\n };\n }\n\n const statePath = getStatePath(input.session_id);\n let state: SessionState;\n\n try {\n state = JSON.parse(readFileSync(statePath, \"utf-8\"));\n } catch {\n // Initialize state if not found \u2014 budget derived from model when available\n const fallbackModel = (input as { model?: string }).model ?? \"default\";\n state = {\n tokens_estimated: 0,\n token_budget: (MODEL_CONTEXTS as Record<string, number>)[fallbackModel] ?? MODEL_CONTEXTS[\"default\"] ?? 200_000,\n context_pct: 0,\n warnings_issued: new Set(),\n };\n }\n\n const inputTokens = estimateTokens(input.tool_input);\n const outputTokens = estimateTokens(input.tool_output);\n const delta = inputTokens + outputTokens;\n\n state.tokens_estimated += delta;\n state.context_pct = Math.min(100, Math.round((state.tokens_estimated / state.token_budget) * 100));\n\n const mutations: HookOutput[\"mutations\"] = [\n { type: \"set_token_budget\", budget: state.token_budget },\n ];\n\n // Check warning thresholds\n for (const threshold of WARNING_THRESHOLDS) {\n const key = `warn_${threshold}`;\n if (state.context_pct >= threshold && !state.warnings_issued.has(key)) {\n state.warnings_issued.add(key);\n const message =\n threshold >= 90\n ? `CRITICAL: Context at ${state.context_pct}%. Tokens near budget limit.`\n : threshold >= 80\n ? `WARNING: Context at ${state.context_pct}%. Consider enabling ecomode.`\n : `INFO: Context at ${state.context_pct}%.`;\n mutations.push({ type: \"log\", level: threshold >= 80 ? \"warn\" : \"info\", message });\n log.push(message);\n }\n }\n\n // Write state back\n try {\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state), \"utf-8\");\n } catch (e) {\n log.push(`Failed to write state: ${e}`);\n }\n\n // Track premium request spending\n const sessionId = input.session_id ?? `omp-${Date.now()}`;\n try { incrementSpending(sessionId); } catch { /* non-blocking */ }\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", "/**\n * Spending tracker for OMP.\n * Tracks premium API requests per session and per calendar month.\n * Persists to ~/.omp/state/spending-monthly.json\n *\n * // v1.1 known limitation: no /omp:spending reset command. To reset monthly counter manually: rm ~/.omp/state/spending-monthly.json\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, dirname } from \"path\";\nimport type { SpendingState } from \"./types.mjs\";\n\nconst SPENDING_PATH = join(homedir(), \".omp\", \"state\", \"spending-monthly.json\");\n\nfunction currentMonth(): string {\n const now = new Date();\n return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}`;\n}\n\nexport function loadSpending(sessionId: string): SpendingState {\n let raw: SpendingState;\n try {\n raw = JSON.parse(readFileSync(SPENDING_PATH, \"utf-8\")) as SpendingState;\n } catch {\n // Missing or malformed file \u2014 start fresh\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month: currentMonth(),\n monthlyPremiumRequests: 0,\n };\n }\n\n const month = currentMonth();\n\n // Reset monthly counter when month rolls over\n if (raw.month !== month) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: 0,\n };\n }\n\n // Reset session counter when session changes\n if (raw.sessionId !== sessionId) {\n return {\n version: 1,\n sessionId,\n sessionPremiumRequests: 0,\n month,\n monthlyPremiumRequests: raw.monthlyPremiumRequests,\n };\n }\n\n return { ...raw, version: 1 };\n}\n\nexport function saveSpending(state: SpendingState): void {\n try {\n mkdirSync(dirname(SPENDING_PATH), { recursive: true });\n writeFileSync(SPENDING_PATH, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n console.warn(`[OMP] spending: failed to save state: ${e}`);\n }\n}\n\nexport function incrementSpending(sessionId: string): SpendingState {\n const state = loadSpending(sessionId);\n state.sessionPremiumRequests += 1;\n state.monthlyPremiumRequests += 1;\n saveSpending(state);\n return state;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["readFileSync", "writeFileSync", "mkdirSync", "homedir", "join", "join", "homedir", "mkdirSync", "readFileSync", "writeFileSync"]
|
|
7
7
|
}
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -28224,58 +28224,71 @@ var StdioServerTransport = class {
|
|
|
28224
28224
|
};
|
|
28225
28225
|
|
|
28226
28226
|
// src/mcp/server.mts
|
|
28227
|
-
import Database from "better-sqlite3";
|
|
28228
28227
|
import { readFileSync as readFileSync2, mkdirSync } from "fs";
|
|
28229
28228
|
import { homedir } from "os";
|
|
28230
28229
|
import { join as join2, dirname } from "path";
|
|
28231
28230
|
import { randomUUID } from "crypto";
|
|
28231
|
+
|
|
28232
|
+
// src/mcp/db-loader.mts
|
|
28233
|
+
import { createRequire } from "module";
|
|
28234
|
+
var SqliteConstructor = null;
|
|
28235
|
+
try {
|
|
28236
|
+
SqliteConstructor = createRequire(import.meta.url)("better-sqlite3");
|
|
28237
|
+
} catch {
|
|
28238
|
+
}
|
|
28239
|
+
|
|
28240
|
+
// src/mcp/server.mts
|
|
28232
28241
|
function getDbPath() {
|
|
28233
28242
|
const envPath = process.env["OMP_STATE_DB"];
|
|
28234
28243
|
if (envPath) return envPath.replace("~", homedir());
|
|
28235
28244
|
return join2(homedir(), ".omp", "state", "omp.db");
|
|
28236
28245
|
}
|
|
28237
|
-
function ensureDbDir(
|
|
28238
|
-
mkdirSync(dirname(
|
|
28239
|
-
}
|
|
28240
|
-
var
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
|
|
28249
|
-
|
|
28250
|
-
|
|
28251
|
-
|
|
28252
|
-
|
|
28246
|
+
function ensureDbDir(dbPath) {
|
|
28247
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
28248
|
+
}
|
|
28249
|
+
var db = null;
|
|
28250
|
+
if (SqliteConstructor) {
|
|
28251
|
+
const dbPath = getDbPath();
|
|
28252
|
+
ensureDbDir(dbPath);
|
|
28253
|
+
db = new SqliteConstructor(dbPath);
|
|
28254
|
+
db.pragma("journal_mode = WAL");
|
|
28255
|
+
db.exec(`
|
|
28256
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
28257
|
+
id TEXT PRIMARY KEY,
|
|
28258
|
+
worktree_id TEXT,
|
|
28259
|
+
state_json TEXT NOT NULL,
|
|
28260
|
+
created_at INTEGER NOT NULL,
|
|
28261
|
+
updated_at INTEGER NOT NULL
|
|
28262
|
+
);
|
|
28263
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_worktree ON sessions(worktree_id);
|
|
28264
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at);
|
|
28253
28265
|
|
|
28254
|
-
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28258
|
-
|
|
28259
|
-
|
|
28260
|
-
|
|
28261
|
-
|
|
28262
|
-
|
|
28263
|
-
|
|
28266
|
+
CREATE TABLE IF NOT EXISTS memory (
|
|
28267
|
+
key TEXT PRIMARY KEY,
|
|
28268
|
+
value TEXT NOT NULL,
|
|
28269
|
+
category TEXT,
|
|
28270
|
+
session_id TEXT,
|
|
28271
|
+
created_at INTEGER NOT NULL,
|
|
28272
|
+
updated_at INTEGER NOT NULL
|
|
28273
|
+
);
|
|
28274
|
+
CREATE INDEX IF NOT EXISTS idx_memory_category ON memory(category);
|
|
28275
|
+
CREATE INDEX IF NOT EXISTS idx_memory_session ON memory(session_id);
|
|
28264
28276
|
|
|
28265
|
-
|
|
28266
|
-
|
|
28267
|
-
|
|
28268
|
-
|
|
28269
|
-
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
|
|
28274
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28277
|
-
|
|
28278
|
-
`);
|
|
28277
|
+
CREATE TABLE IF NOT EXISTS trace (
|
|
28278
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28279
|
+
session_id TEXT NOT NULL,
|
|
28280
|
+
hook_id TEXT,
|
|
28281
|
+
agent_id TEXT,
|
|
28282
|
+
event_type TEXT NOT NULL,
|
|
28283
|
+
payload TEXT,
|
|
28284
|
+
duration_ms INTEGER,
|
|
28285
|
+
timestamp INTEGER NOT NULL
|
|
28286
|
+
);
|
|
28287
|
+
CREATE INDEX IF NOT EXISTS idx_trace_session ON trace(session_id);
|
|
28288
|
+
CREATE INDEX IF NOT EXISTS idx_trace_hook ON trace(hook_id);
|
|
28289
|
+
CREATE INDEX IF NOT EXISTS idx_trace_agent ON trace(agent_id);
|
|
28290
|
+
`);
|
|
28291
|
+
}
|
|
28279
28292
|
var TOOLS = [
|
|
28280
28293
|
// State tools
|
|
28281
28294
|
{
|
|
@@ -28397,12 +28410,14 @@ function handleListTools() {
|
|
|
28397
28410
|
async function handleCallTool(name, args) {
|
|
28398
28411
|
switch (name) {
|
|
28399
28412
|
case "omp_get_session_state": {
|
|
28413
|
+
if (!db) return { content: [{ type: "text", text: "null" }] };
|
|
28400
28414
|
const sessions = db.prepare("SELECT * FROM sessions ORDER BY updated_at DESC LIMIT 1").all();
|
|
28401
28415
|
return { content: [{ type: "text", text: JSON.stringify(sessions[0] || null, null, 2) }] };
|
|
28402
28416
|
}
|
|
28403
28417
|
case "omp_save_session": {
|
|
28404
28418
|
const sessionId = args.sessionId || randomUUID();
|
|
28405
28419
|
const stateJson = args.stateJson || JSON.stringify({});
|
|
28420
|
+
if (!db) return { content: [{ type: "text", text: JSON.stringify({ status: "ok", sessionId, note: "SQLite unavailable; state not persisted" }) }] };
|
|
28406
28421
|
const now = Date.now();
|
|
28407
28422
|
db.prepare(
|
|
28408
28423
|
"INSERT OR REPLACE INTO sessions (id, worktree_id, state_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
|
@@ -28410,6 +28425,7 @@ async function handleCallTool(name, args) {
|
|
|
28410
28425
|
return { content: [{ type: "text", text: JSON.stringify({ status: "ok", sessionId }) }] };
|
|
28411
28426
|
}
|
|
28412
28427
|
case "omp_list_sessions": {
|
|
28428
|
+
if (!db) return { content: [{ type: "text", text: "[]" }] };
|
|
28413
28429
|
const sessions = db.prepare("SELECT id, created_at, updated_at FROM sessions ORDER BY updated_at DESC").all();
|
|
28414
28430
|
return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
|
|
28415
28431
|
}
|