@damian87/omp 0.10.0 → 0.12.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/.github/copilot-instructions.md +16 -0
- package/.github/skills/jira-ticket/SKILL.md +4 -4
- package/.github/skills/omp-autopilot/SKILL.md +4 -0
- package/.github/skills/research-codebase/SKILL.md +4 -0
- package/.github/skills/schedule/SKILL.md +4 -0
- package/.github/skills/team/SKILL.md +4 -0
- package/.github/skills/ultrawork/SKILL.md +4 -0
- package/.github/skills/weighted-consensus/SKILL.md +4 -0
- package/README.md +4 -1
- package/dist/src/cli.js +10 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/copilot/doctor.d.ts +1 -0
- package/dist/src/copilot/doctor.js +226 -27
- package/dist/src/copilot/doctor.js.map +1 -1
- package/dist/src/copilot/launch.js +13 -5
- package/dist/src/copilot/launch.js.map +1 -1
- package/dist/src/copilot/setup.js +13 -0
- package/dist/src/copilot/setup.js.map +1 -1
- package/dist/src/cost/index.d.ts +3 -0
- package/dist/src/cost/index.js +4 -0
- package/dist/src/cost/index.js.map +1 -0
- package/dist/src/cost/ledger.d.ts +21 -0
- package/dist/src/cost/ledger.js +72 -0
- package/dist/src/cost/ledger.js.map +1 -0
- package/dist/src/cost/summary.d.ts +22 -0
- package/dist/src/cost/summary.js +68 -0
- package/dist/src/cost/summary.js.map +1 -0
- package/dist/src/cost/tokenize.d.ts +7 -0
- package/dist/src/cost/tokenize.js +24 -0
- package/dist/src/cost/tokenize.js.map +1 -0
- package/dist/src/instructions-memory.js +1 -1
- package/dist/src/instructions-memory.js.map +1 -1
- package/docs/general-skills.md +1 -0
- package/hooks/hooks.json +9 -2
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/error.mjs +9 -7
- package/scripts/lib/cost-ledger.mjs +91 -0
- package/scripts/lib/hook-input.mjs +51 -0
- package/scripts/lib/hook-output.mjs +53 -11
- package/scripts/lib/minify.mjs +80 -0
- package/scripts/post-tool-use-failure.mjs +21 -0
- package/scripts/post-tool-use.mjs +71 -8
- package/scripts/pre-tool-use.mjs +8 -6
- package/scripts/prompt-submit.mjs +12 -5
- package/scripts/session-end.mjs +7 -5
- package/scripts/session-start.mjs +5 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readCostRecords } from "./ledger.js";
|
|
2
|
+
import { ompRoot } from "../omp-root.js";
|
|
3
|
+
function emptyBucket() {
|
|
4
|
+
return { inTokens: 0, outTokens: 0, totalTokens: 0, records: 0 };
|
|
5
|
+
}
|
|
6
|
+
function addTo(bucket, record) {
|
|
7
|
+
const inTokens = record.inTokens ?? 0;
|
|
8
|
+
const outTokens = record.outTokens ?? 0;
|
|
9
|
+
bucket.inTokens += inTokens;
|
|
10
|
+
bucket.outTokens += outTokens;
|
|
11
|
+
bucket.totalTokens += inTokens + outTokens;
|
|
12
|
+
bucket.records += 1;
|
|
13
|
+
}
|
|
14
|
+
function addGroup(groups, key, record) {
|
|
15
|
+
if (!key)
|
|
16
|
+
return;
|
|
17
|
+
groups[key] ??= emptyBucket();
|
|
18
|
+
addTo(groups[key], record);
|
|
19
|
+
}
|
|
20
|
+
export function summarizeCost(cwd, options = {}) {
|
|
21
|
+
const root = ompRoot(cwd);
|
|
22
|
+
const records = readCostRecords(root, options);
|
|
23
|
+
const totals = emptyBucket();
|
|
24
|
+
const byEvent = {};
|
|
25
|
+
const byTool = {};
|
|
26
|
+
const byModel = {};
|
|
27
|
+
const sessions = new Set();
|
|
28
|
+
for (const record of records) {
|
|
29
|
+
sessions.add(record.sessionId);
|
|
30
|
+
addTo(totals, record);
|
|
31
|
+
addGroup(byEvent, record.event, record);
|
|
32
|
+
addGroup(byTool, record.toolName, record);
|
|
33
|
+
addGroup(byModel, record.model, record);
|
|
34
|
+
}
|
|
35
|
+
const toolSinks = Object.entries(byTool).map(([key, bucket]) => ({ label: `tool:${key}`, ...bucket }));
|
|
36
|
+
const eventSinks = Object.entries(byEvent).map(([key, bucket]) => ({ label: `event:${key}`, ...bucket }));
|
|
37
|
+
const topSinks = [...toolSinks, ...eventSinks]
|
|
38
|
+
.sort((a, b) => b.totalTokens - a.totalTokens ||
|
|
39
|
+
Number(b.label.startsWith("tool:")) - Number(a.label.startsWith("tool:")) ||
|
|
40
|
+
a.label.localeCompare(b.label))
|
|
41
|
+
.slice(0, 10);
|
|
42
|
+
return {
|
|
43
|
+
root,
|
|
44
|
+
records: records.length,
|
|
45
|
+
sessions: [...sessions].sort(),
|
|
46
|
+
totals,
|
|
47
|
+
byEvent,
|
|
48
|
+
byTool,
|
|
49
|
+
byModel,
|
|
50
|
+
topSinks,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function formatCostSummary(summary) {
|
|
54
|
+
const lines = [
|
|
55
|
+
`Cost ledger: ${summary.root}`,
|
|
56
|
+
`records: ${summary.records}`,
|
|
57
|
+
`sessions: ${summary.sessions.length ? summary.sessions.join(", ") : "(none)"}`,
|
|
58
|
+
`tokens: ${summary.totals.totalTokens} (in ${summary.totals.inTokens}, out ${summary.totals.outTokens})`,
|
|
59
|
+
];
|
|
60
|
+
if (summary.topSinks.length > 0) {
|
|
61
|
+
lines.push("top sinks:");
|
|
62
|
+
for (const sink of summary.topSinks.slice(0, 5)) {
|
|
63
|
+
lines.push(` ${sink.label}: ${sink.totalTokens} tokens (${sink.records} record${sink.records === 1 ? "" : "s"})`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../../src/cost/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAyC,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAwBzC,SAAS,WAAW;IAClB,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,KAAK,CAAC,MAAkB,EAAE,MAAkB;IACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC;IAC5B,MAAM,CAAC,SAAS,IAAI,SAAS,CAAC;IAC9B,MAAM,CAAC,WAAW,IAAI,QAAQ,GAAG,SAAS,CAAC;IAC3C,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,SAAS,QAAQ,CAAC,MAAkC,EAAE,GAAuB,EAAE,MAAkB;IAC/F,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,CAAC,GAAG,CAAC,KAAK,WAAW,EAAE,CAAC;IAC9B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,UAA2B,EAAE;IACtE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,MAAM,MAAM,GAA+B,EAAE,CAAC;IAC9C,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;IACvG,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1G,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC;SAC3C,IAAI,CACH,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;QAC7B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACzE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CACjC;SACA,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEhB,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,OAAO,CAAC,MAAM;QACvB,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE;QAC9B,MAAM;QACN,OAAO;QACP,MAAM;QACN,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAoB;IACpD,MAAM,KAAK,GAAG;QACZ,gBAAgB,OAAO,CAAC,IAAI,EAAE;QAC9B,YAAY,OAAO,CAAC,OAAO,EAAE;QAC7B,aAAa,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QAC/E,WAAW,OAAO,CAAC,MAAM,CAAC,WAAW,QAAQ,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,GAAG;KACzG,CAAC;IACF,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,WAAW,YAAY,IAAI,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function normalizeTokenInput(value: unknown): string;
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight local token estimate. It is intentionally deterministic and
|
|
4
|
+
* dependency-free; USD reporting must label this as estimated unless a real
|
|
5
|
+
* provider usage source is added later.
|
|
6
|
+
*/
|
|
7
|
+
export declare function countTokens(value: unknown): number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function normalizeTokenInput(value) {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return "";
|
|
4
|
+
if (typeof value === "string")
|
|
5
|
+
return value;
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(value) ?? "";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Lightweight local token estimate. It is intentionally deterministic and
|
|
15
|
+
* dependency-free; USD reporting must label this as estimated unless a real
|
|
16
|
+
* provider usage source is added later.
|
|
17
|
+
*/
|
|
18
|
+
export function countTokens(value) {
|
|
19
|
+
const text = normalizeTokenInput(value);
|
|
20
|
+
if (text.length === 0)
|
|
21
|
+
return 0;
|
|
22
|
+
return Math.ceil(text.length / 4);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=tokenize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenize.js","sourceRoot":"","sources":["../../../src/cost/tokenize.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -20,7 +20,7 @@ function renderBlock(cwd) {
|
|
|
20
20
|
const lines = [START, "## oh-my-copilot project context"];
|
|
21
21
|
if (goal)
|
|
22
22
|
lines.push("", `**Repo goal:** ${goal}`);
|
|
23
|
-
lines.push("", "Project memory is available on demand:", "- `omp project-memory read` for project hints and the note index", "- `omp project-memory read <id>` for a specific note body", "- `omp daily-log read` for recent daily context", "", `Available note index: ${notes.length} note${notes.length === 1 ? "" : "s"}.`, END);
|
|
23
|
+
lines.push("", "Project memory is available on demand:", "- `omp project-memory read` for project hints and the note index", "- `omp project-memory read <id>` for a specific note body", "- `omp daily-log read --days 7` for recent daily context", "", `Available note index: ${notes.length} note${notes.length === 1 ? "" : "s"}.`, END);
|
|
24
24
|
return lines.join("\n");
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions-memory.js","sourceRoot":"","sources":["../../src/instructions-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,kFAAkF;AAClF,8EAA8E;AAC9E,iFAAiF;AACjF,8EAA8E;AAC9E,8EAA8E;AAC9E,mEAAmE;AAEnE,MAAM,KAAK,GAAG,2BAA2B,CAAC;AAC1C,MAAM,GAAG,GAAG,yBAAyB,CAAC;AAEtC,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,yBAAyB,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAa,CAAC,KAAK,EAAE,kCAAkC,CAAC,CAAC;IACpE,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,kBAAkB,IAAI,EAAE,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CACR,EAAE,EACF,wCAAwC,EACxC,kEAAkE,EAClE,2DAA2D,EAC3D,
|
|
1
|
+
{"version":3,"file":"instructions-memory.js","sourceRoot":"","sources":["../../src/instructions-memory.ts"],"names":[],"mappings":"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"}
|
package/docs/general-skills.md
CHANGED
|
@@ -39,6 +39,7 @@ Rules:
|
|
|
39
39
|
- Keep each `SKILL.md` small: YAML frontmatter (`name`, `description`) plus focused Markdown instructions.
|
|
40
40
|
- Optional `references/`, `scripts/`, or `assets/` may live beside `SKILL.md` when a fetched skill needs progressive disclosure.
|
|
41
41
|
- Do not add runtime state to the lite skills.
|
|
42
|
+
- Do not duplicate global cost/token boilerplate in every skill. The plugin-level hooks and `.github/copilot-instructions.md` provide estimate-only cost visibility; add only concise reminders to genuinely high-cost skills.
|
|
42
43
|
|
|
43
44
|
## Fetched skills
|
|
44
45
|
|
package/hooks/hooks.json
CHANGED
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"timeoutSec": 5
|
|
30
30
|
}
|
|
31
31
|
],
|
|
32
|
-
"
|
|
32
|
+
"postToolUseFailure": [
|
|
33
33
|
{
|
|
34
34
|
"type": "command",
|
|
35
|
-
"bash": "node \"${COPILOT_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-${OMP_PLUGIN_ROOT:-$OMC_PLUGIN_ROOT}}}}\"/scripts/
|
|
35
|
+
"bash": "node \"${COPILOT_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-${OMP_PLUGIN_ROOT:-$OMC_PLUGIN_ROOT}}}}\"/scripts/post-tool-use-failure.mjs",
|
|
36
36
|
"timeoutSec": 5
|
|
37
37
|
}
|
|
38
38
|
],
|
|
@@ -49,6 +49,13 @@
|
|
|
49
49
|
"bash": "node \"${COPILOT_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-${OMP_PLUGIN_ROOT:-$OMC_PLUGIN_ROOT}}}}\"/scripts/agent-stop.mjs",
|
|
50
50
|
"timeoutSec": 10
|
|
51
51
|
}
|
|
52
|
+
],
|
|
53
|
+
"errorOccurred": [
|
|
54
|
+
{
|
|
55
|
+
"type": "command",
|
|
56
|
+
"bash": "node \"${COPILOT_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-${OMP_PLUGIN_ROOT:-$OMC_PLUGIN_ROOT}}}}\"/scripts/error.mjs",
|
|
57
|
+
"timeoutSec": 5
|
|
58
|
+
}
|
|
52
59
|
]
|
|
53
60
|
}
|
|
54
61
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damian87/omp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Multi-agent orchestration for GitHub Copilot CLI — autonomous loops (Autopilot, Ralph, UltraQA, Ultrawork), parallel tmux agent teams, a weighted-consensus model council, a Slack chat bridge, durable scheduled jobs, and in-session skills + custom agents. Zero learning curve.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-copilot",
|
|
3
3
|
"description": "Multi-agent orchestration skills for GitHub Copilot CLI — autopilot, ralph, ultrawork, ultraqa, team, council, code-review and more as in-session slash skills + custom agents.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Borek",
|
|
7
7
|
"email": "borekdamian@yahoo.pl"
|
package/scripts/error.mjs
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { readStdin } from "./lib/stdin.mjs";
|
|
5
|
+
import { failOpen } from "./lib/hook-output.mjs";
|
|
6
|
+
import { parseHookInput } from "./lib/hook-input.mjs";
|
|
5
7
|
|
|
6
8
|
const HOOK_NAME = "Error";
|
|
7
9
|
|
|
8
10
|
(async () => {
|
|
9
11
|
try {
|
|
10
12
|
const raw = await readStdin();
|
|
11
|
-
const
|
|
12
|
-
const sessionId =
|
|
13
|
-
const directory =
|
|
14
|
-
const toolName =
|
|
15
|
-
const errorMessage =
|
|
13
|
+
const input = parseHookInput(raw);
|
|
14
|
+
const sessionId = input.sessionId;
|
|
15
|
+
const directory = input.cwd;
|
|
16
|
+
const toolName = input.toolName;
|
|
17
|
+
const errorMessage = input.error ?? "unknown";
|
|
16
18
|
const logFile = join(directory, ".omp", "state", "hooks.log");
|
|
17
19
|
try {
|
|
18
20
|
mkdirSync(dirname(logFile), { recursive: true });
|
|
@@ -23,9 +25,9 @@ const HOOK_NAME = "Error";
|
|
|
23
25
|
} catch {
|
|
24
26
|
// best effort
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
failOpen();
|
|
27
29
|
} catch (err) {
|
|
28
30
|
console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
|
|
29
|
-
|
|
31
|
+
failOpen();
|
|
30
32
|
}
|
|
31
33
|
})();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ompRoot } from "./omp-root.mjs";
|
|
4
|
+
|
|
5
|
+
export function normalizeTokenInput(value) {
|
|
6
|
+
if (value == null) return "";
|
|
7
|
+
if (typeof value === "string") return value;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.stringify(value) ?? "";
|
|
10
|
+
} catch {
|
|
11
|
+
return String(value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function countTokens(value) {
|
|
16
|
+
const text = normalizeTokenInput(value);
|
|
17
|
+
if (text.length === 0) return 0;
|
|
18
|
+
return Math.ceil(text.length / 4);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function costDir(cwd) {
|
|
22
|
+
return join(ompRoot(cwd), ".omp", "state", "cost");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function safeSessionId(sessionId) {
|
|
26
|
+
return (
|
|
27
|
+
String(sessionId || "unknown")
|
|
28
|
+
.trim()
|
|
29
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
30
|
+
.replace(/^-+|-+$/g, "") || "unknown"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function costLedgerPath(cwd, sessionId) {
|
|
35
|
+
return join(costDir(cwd), `${safeSessionId(sessionId)}.jsonl`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeRecord(record = {}) {
|
|
39
|
+
return {
|
|
40
|
+
ts: record.ts ?? new Date().toISOString(),
|
|
41
|
+
sessionId: record.sessionId || "unknown",
|
|
42
|
+
event: record.event,
|
|
43
|
+
toolName: record.toolName,
|
|
44
|
+
model: record.model,
|
|
45
|
+
inTokens: Number.isFinite(record.inTokens) ? Math.max(0, Number(record.inTokens)) : 0,
|
|
46
|
+
outTokens: Number.isFinite(record.outTokens) ? Math.max(0, Number(record.outTokens)) : 0,
|
|
47
|
+
rawOutTokens: Number.isFinite(record.rawOutTokens) ? Math.max(0, Number(record.rawOutTokens)) : undefined,
|
|
48
|
+
savedTokens: Number.isFinite(record.savedTokens) ? Math.max(0, Number(record.savedTokens)) : undefined,
|
|
49
|
+
rawPath: typeof record.rawPath === "string" ? record.rawPath : undefined,
|
|
50
|
+
estUSD: Number.isFinite(record.estUSD) ? Number(record.estUSD) : undefined,
|
|
51
|
+
note: record.note,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function appendCostRecord(cwd, record) {
|
|
56
|
+
const normalized = normalizeRecord(record);
|
|
57
|
+
const file = costLedgerPath(cwd, normalized.sessionId);
|
|
58
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
59
|
+
appendFileSync(file, `${JSON.stringify(normalized)}\n`, "utf8");
|
|
60
|
+
return file;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readFileRecords(file) {
|
|
64
|
+
if (!existsSync(file)) return [];
|
|
65
|
+
const records = [];
|
|
66
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
67
|
+
if (!line.trim()) continue;
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(line);
|
|
70
|
+
if (parsed && typeof parsed === "object" && typeof parsed.event === "string") records.push(normalizeRecord(parsed));
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore corrupt rows
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return records;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function readCostRecords(cwd, options = {}) {
|
|
79
|
+
const dir = costDir(cwd);
|
|
80
|
+
const todayPrefix = new Date().toISOString().slice(0, 10);
|
|
81
|
+
const files = options.sessionId
|
|
82
|
+
? [costLedgerPath(cwd, options.sessionId)]
|
|
83
|
+
: existsSync(dir)
|
|
84
|
+
? readdirSync(dir)
|
|
85
|
+
.filter((file) => file.endsWith(".jsonl"))
|
|
86
|
+
.sort()
|
|
87
|
+
.map((file) => join(dir, file))
|
|
88
|
+
: [];
|
|
89
|
+
const records = files.flatMap(readFileRecords);
|
|
90
|
+
return options.today ? records.filter((record) => String(record.ts ?? "").startsWith(todayPrefix)) : records;
|
|
91
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function parseMaybeJson(value) {
|
|
2
|
+
if (typeof value !== "string") return value;
|
|
3
|
+
const trimmed = value.trim();
|
|
4
|
+
if (!trimmed) return value;
|
|
5
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(trimmed);
|
|
8
|
+
} catch {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeResult(raw) {
|
|
14
|
+
if (!raw || typeof raw !== "object") return undefined;
|
|
15
|
+
const resultType = raw.resultType ?? raw.result_type;
|
|
16
|
+
const textResultForLlm = raw.textResultForLlm ?? raw.text_result_for_llm;
|
|
17
|
+
if (resultType == null && textResultForLlm == null) return undefined;
|
|
18
|
+
return {
|
|
19
|
+
resultType: resultType ?? "success",
|
|
20
|
+
textResultForLlm: textResultForLlm == null ? "" : String(textResultForLlm),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function normalizeHookInput(data = {}, options = {}) {
|
|
25
|
+
const payload = data && typeof data === "object" ? data : {};
|
|
26
|
+
const cwd = payload.cwd ?? payload.directory ?? options.cwd ?? process.cwd();
|
|
27
|
+
const toolResult = normalizeResult(payload.toolResult ?? payload.tool_result ?? payload.toolOutput);
|
|
28
|
+
const error = payload.error?.message ?? payload.error ?? payload.message;
|
|
29
|
+
return {
|
|
30
|
+
raw: payload,
|
|
31
|
+
hookEventName: payload.hookEventName ?? payload.hook_event_name,
|
|
32
|
+
sessionId: payload.sessionId ?? payload.session_id ?? "unknown",
|
|
33
|
+
timestamp: payload.timestamp,
|
|
34
|
+
cwd,
|
|
35
|
+
directory: cwd,
|
|
36
|
+
prompt: payload.prompt ?? payload.message?.content ?? "",
|
|
37
|
+
toolName: payload.toolName ?? payload.tool_name ?? "unknown",
|
|
38
|
+
toolArgs: parseMaybeJson(payload.toolArgs ?? payload.tool_input ?? payload.toolInput),
|
|
39
|
+
toolResult,
|
|
40
|
+
error: error == null ? undefined : String(error),
|
|
41
|
+
transcriptPath: payload.transcriptPath ?? payload.transcript_path,
|
|
42
|
+
stopReason: payload.stopReason ?? payload.stop_reason,
|
|
43
|
+
trigger: payload.trigger,
|
|
44
|
+
customInstructions: payload.customInstructions ?? payload.custom_instructions,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseHookInput(raw, options = {}) {
|
|
49
|
+
const data = raw ? JSON.parse(raw) : {};
|
|
50
|
+
return normalizeHookInput(data, options);
|
|
51
|
+
}
|
|
@@ -4,23 +4,65 @@ import { ompRoot } from "./omp-root.mjs";
|
|
|
4
4
|
|
|
5
5
|
// Hook scripts run under BOTH GitHub Copilot CLI (camelCase events, top-level
|
|
6
6
|
// `additionalContext` / `{decision,reason}` / `{permissionDecision}`) and Claude
|
|
7
|
-
// Code (`{continue, hookSpecificOutput}` / `{decision, reason}`).
|
|
8
|
-
// every output object carries both
|
|
9
|
-
// it does not recognize. See
|
|
7
|
+
// Code (`{continue, hookSpecificOutput}` / `{decision, reason}`). The injection
|
|
8
|
+
// path (`printContinue`) dual-emits: every output object carries both
|
|
9
|
+
// vocabularies, and each host ignores the keys it does not recognize. See
|
|
10
|
+
// docs/plans/copilot-native-hooks.md.
|
|
11
|
+
//
|
|
12
|
+
// The cost/minification path uses the documented Copilot builder shapes
|
|
13
|
+
// (`buildContinueOutput`/`buildAdditionalContextOutput`/`buildModifiedResultOutput`/
|
|
14
|
+
// `buildPermissionDecisionOutput`). An empty `{}` is a no-op "continue" for both
|
|
15
|
+
// hosts, so these builders coexist with the dual-emit injection path.
|
|
10
16
|
|
|
11
17
|
/** Project directory from hook input — Copilot sends `cwd`, Claude sends `directory`. */
|
|
12
18
|
export function hookCwd(data) {
|
|
13
19
|
return data?.cwd ?? data?.directory ?? process.cwd();
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
export function buildContinueOutput() {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildAdditionalContextOutput(additionalContext = "") {
|
|
27
|
+
return additionalContext ? { additionalContext } : buildContinueOutput();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildModifiedResultOutput(textResultForLlm, additionalContext = "", resultType = "success") {
|
|
31
|
+
return {
|
|
32
|
+
modifiedResult: {
|
|
33
|
+
resultType,
|
|
34
|
+
textResultForLlm,
|
|
35
|
+
},
|
|
36
|
+
...(additionalContext ? { additionalContext } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildPermissionDecisionOutput(permissionDecision, permissionDecisionReason, modifiedArgs) {
|
|
41
|
+
return {
|
|
42
|
+
permissionDecision,
|
|
43
|
+
...(permissionDecisionReason ? { permissionDecisionReason } : {}),
|
|
44
|
+
...(modifiedArgs == null ? {} : { modifiedArgs }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* sessionStart / userPromptSubmitted injection. When there is context to inject,
|
|
50
|
+
* dual-emit it for both hosts (Copilot top-level `additionalContext` + Claude
|
|
51
|
+
* `continue`/`hookSpecificOutput`). With nothing to inject, emit an empty `{}` —
|
|
52
|
+
* a no-op "continue" understood by both hosts and the zero-cost default.
|
|
53
|
+
*/
|
|
17
54
|
export function printContinue(hookEventName, additionalContext = "") {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
output.hookSpecificOutput = { hookEventName, additionalContext }; // Claude Code
|
|
55
|
+
if (!additionalContext) {
|
|
56
|
+
console.log(JSON.stringify(buildContinueOutput()));
|
|
57
|
+
return;
|
|
22
58
|
}
|
|
23
|
-
console.log(
|
|
59
|
+
console.log(
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
continue: true,
|
|
62
|
+
additionalContext, // Copilot CLI
|
|
63
|
+
hookSpecificOutput: { hookEventName, additionalContext }, // Claude Code
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
24
66
|
}
|
|
25
67
|
|
|
26
68
|
/** agentStop (Copilot) / Stop (Claude): both honor {decision, reason}. */
|
|
@@ -39,11 +81,11 @@ export function printPermission(permissionDecision, reason = "", modifiedArgs) {
|
|
|
39
81
|
}
|
|
40
82
|
|
|
41
83
|
export function printBlock(reason) {
|
|
42
|
-
console.log(JSON.stringify(
|
|
84
|
+
console.log(JSON.stringify(buildPermissionDecisionOutput("deny", reason)));
|
|
43
85
|
}
|
|
44
86
|
|
|
45
87
|
export function failOpen() {
|
|
46
|
-
console.log(JSON.stringify(
|
|
88
|
+
console.log(JSON.stringify(buildContinueOutput()));
|
|
47
89
|
}
|
|
48
90
|
|
|
49
91
|
export function appendHookLog(directory, hookName, payload) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { countTokens } from "./cost-ledger.mjs";
|
|
2
|
+
|
|
3
|
+
const ANSI_RE = /\u001b\[[0-9;]*m/g;
|
|
4
|
+
const DIAGNOSTIC_RE = /\b(fail(?:ed|ure)?|error|exception|assertion|traceback|expected|cannot find|no-undef|TS\d{4})\b/i;
|
|
5
|
+
|
|
6
|
+
function stripAnsi(text) {
|
|
7
|
+
return text.replace(ANSI_RE, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function dedupConsecutiveLines(lines) {
|
|
11
|
+
const out = [];
|
|
12
|
+
let previous = "";
|
|
13
|
+
let repeated = 0;
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
if (line === previous) {
|
|
16
|
+
repeated += 1;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (repeated > 0) out.push(`[omp] repeated previous line ${repeated} time${repeated === 1 ? "" : "s"}`);
|
|
20
|
+
out.push(line);
|
|
21
|
+
previous = line;
|
|
22
|
+
repeated = 0;
|
|
23
|
+
}
|
|
24
|
+
if (repeated > 0) out.push(`[omp] repeated previous line ${repeated} time${repeated === 1 ? "" : "s"}`);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function minifyToolOutput(value, options = {}) {
|
|
29
|
+
const rawText = value == null ? "" : String(value);
|
|
30
|
+
const rawTokens = countTokens(rawText);
|
|
31
|
+
const thresholdTokens = options.thresholdTokens ?? 800;
|
|
32
|
+
if (rawTokens <= thresholdTokens) {
|
|
33
|
+
return {
|
|
34
|
+
changed: false,
|
|
35
|
+
text: rawText,
|
|
36
|
+
rawTokens,
|
|
37
|
+
modelTokens: rawTokens,
|
|
38
|
+
savedTokens: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const headLines = options.headLines ?? 80;
|
|
43
|
+
const tailLines = options.tailLines ?? 40;
|
|
44
|
+
const normalized = dedupConsecutiveLines(stripAnsi(rawText).split("\n"));
|
|
45
|
+
const omitted = Math.max(0, normalized.length - headLines - tailLines);
|
|
46
|
+
const head = normalized.slice(0, headLines);
|
|
47
|
+
const tail = omitted > 0 ? normalized.slice(-tailLines) : [];
|
|
48
|
+
const tailStart = omitted > 0 ? normalized.length - tailLines : normalized.length;
|
|
49
|
+
const diagnosticLines = normalized
|
|
50
|
+
.map((line, index) => ({ line, index }))
|
|
51
|
+
.filter(({ line, index }) => index >= headLines && index < tailStart && DIAGNOSTIC_RE.test(line))
|
|
52
|
+
.slice(0, options.maxDiagnosticLines ?? 40)
|
|
53
|
+
.map(({ line }) => line);
|
|
54
|
+
const text = [
|
|
55
|
+
`[omp] output trimmed from ${rawTokens} estimated tokens; full raw output is saved on disk.`,
|
|
56
|
+
...head,
|
|
57
|
+
...(omitted > 0 ? [`[omp] … omitted ${omitted} middle line${omitted === 1 ? "" : "s"} …`] : []),
|
|
58
|
+
...(diagnosticLines.length > 0 ? ["[omp] preserved diagnostic lines from omitted output:", ...diagnosticLines] : []),
|
|
59
|
+
...tail,
|
|
60
|
+
].join("\n");
|
|
61
|
+
const modelTokens = countTokens(text);
|
|
62
|
+
|
|
63
|
+
if (modelTokens >= rawTokens) {
|
|
64
|
+
return {
|
|
65
|
+
changed: false,
|
|
66
|
+
text: rawText,
|
|
67
|
+
rawTokens,
|
|
68
|
+
modelTokens: rawTokens,
|
|
69
|
+
savedTokens: 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
changed: true,
|
|
75
|
+
text,
|
|
76
|
+
rawTokens,
|
|
77
|
+
modelTokens,
|
|
78
|
+
savedTokens: rawTokens - modelTokens,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readStdin } from "./lib/stdin.mjs";
|
|
3
|
+
import { parseHookInput } from "./lib/hook-input.mjs";
|
|
4
|
+
import { appendHookLog, failOpen } from "./lib/hook-output.mjs";
|
|
5
|
+
|
|
6
|
+
const HOOK_NAME = "postToolUseFailure";
|
|
7
|
+
|
|
8
|
+
(async () => {
|
|
9
|
+
try {
|
|
10
|
+
const input = parseHookInput(await readStdin());
|
|
11
|
+
appendHookLog(input.cwd, HOOK_NAME, {
|
|
12
|
+
sessionId: input.sessionId,
|
|
13
|
+
toolName: input.toolName,
|
|
14
|
+
error: input.error ?? "unknown",
|
|
15
|
+
});
|
|
16
|
+
failOpen();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
|
|
19
|
+
failOpen();
|
|
20
|
+
}
|
|
21
|
+
})();
|