@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.
Files changed (47) hide show
  1. package/.github/copilot-instructions.md +16 -0
  2. package/.github/skills/jira-ticket/SKILL.md +4 -4
  3. package/.github/skills/omp-autopilot/SKILL.md +4 -0
  4. package/.github/skills/research-codebase/SKILL.md +4 -0
  5. package/.github/skills/schedule/SKILL.md +4 -0
  6. package/.github/skills/team/SKILL.md +4 -0
  7. package/.github/skills/ultrawork/SKILL.md +4 -0
  8. package/.github/skills/weighted-consensus/SKILL.md +4 -0
  9. package/README.md +4 -1
  10. package/dist/src/cli.js +10 -1
  11. package/dist/src/cli.js.map +1 -1
  12. package/dist/src/copilot/doctor.d.ts +1 -0
  13. package/dist/src/copilot/doctor.js +226 -27
  14. package/dist/src/copilot/doctor.js.map +1 -1
  15. package/dist/src/copilot/launch.js +13 -5
  16. package/dist/src/copilot/launch.js.map +1 -1
  17. package/dist/src/copilot/setup.js +13 -0
  18. package/dist/src/copilot/setup.js.map +1 -1
  19. package/dist/src/cost/index.d.ts +3 -0
  20. package/dist/src/cost/index.js +4 -0
  21. package/dist/src/cost/index.js.map +1 -0
  22. package/dist/src/cost/ledger.d.ts +21 -0
  23. package/dist/src/cost/ledger.js +72 -0
  24. package/dist/src/cost/ledger.js.map +1 -0
  25. package/dist/src/cost/summary.d.ts +22 -0
  26. package/dist/src/cost/summary.js +68 -0
  27. package/dist/src/cost/summary.js.map +1 -0
  28. package/dist/src/cost/tokenize.d.ts +7 -0
  29. package/dist/src/cost/tokenize.js +24 -0
  30. package/dist/src/cost/tokenize.js.map +1 -0
  31. package/dist/src/instructions-memory.js +1 -1
  32. package/dist/src/instructions-memory.js.map +1 -1
  33. package/docs/general-skills.md +1 -0
  34. package/hooks/hooks.json +9 -2
  35. package/package.json +1 -1
  36. package/plugin.json +1 -1
  37. package/scripts/error.mjs +9 -7
  38. package/scripts/lib/cost-ledger.mjs +91 -0
  39. package/scripts/lib/hook-input.mjs +51 -0
  40. package/scripts/lib/hook-output.mjs +53 -11
  41. package/scripts/lib/minify.mjs +80 -0
  42. package/scripts/post-tool-use-failure.mjs +21 -0
  43. package/scripts/post-tool-use.mjs +71 -8
  44. package/scripts/pre-tool-use.mjs +8 -6
  45. package/scripts/prompt-submit.mjs +12 -5
  46. package/scripts/session-end.mjs +7 -5
  47. 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":"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"}
@@ -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":"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"}
1
+ {"version":3,"file":"instructions-memory.js","sourceRoot":"","sources":["../../src/instructions-memory.ts"],"names":[],"mappings":"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"}
@@ -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
- "errorOccurred": [
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/error.mjs",
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.10.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.10.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 data = raw ? JSON.parse(raw) : {};
12
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
13
- const directory = data.cwd ?? data.directory ?? process.cwd();
14
- const toolName = data.toolName ?? data.tool_name ?? "unknown";
15
- const errorMessage = data.error?.message ?? data.message ?? "unknown";
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
- console.log(JSON.stringify({ continue: true }));
28
+ failOpen();
27
29
  } catch (err) {
28
30
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
29
- console.log(JSON.stringify({ continue: true }));
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}`). We dual-emit:
8
- // every output object carries both vocabularies, and each host ignores the keys
9
- // it does not recognize. See docs/plans/copilot-native-hooks.md.
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
- /** sessionStart / postToolUse style: inject `additionalContext` (or a plain continue). */
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
- const output = { continue: true };
19
- if (additionalContext) {
20
- output.additionalContext = additionalContext; // Copilot CLI
21
- output.hookSpecificOutput = { hookEventName, additionalContext }; // Claude Code
55
+ if (!additionalContext) {
56
+ console.log(JSON.stringify(buildContinueOutput()));
57
+ return;
22
58
  }
23
- console.log(JSON.stringify(output));
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({ continue: false, reason }));
84
+ console.log(JSON.stringify(buildPermissionDecisionOutput("deny", reason)));
43
85
  }
44
86
 
45
87
  export function failOpen() {
46
- console.log(JSON.stringify({ continue: true }));
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
+ })();