aw-ecc 1.4.32 → 1.4.47
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 +1 -1
- package/.codex/hooks/aw-post-tool-use.sh +8 -2
- package/.codex/hooks/aw-session-start.sh +11 -4
- package/.codex/hooks/aw-stop.sh +8 -2
- package/.codex/hooks/aw-user-prompt-submit.sh +10 -2
- package/.codex/hooks.json +8 -8
- package/.cursor/INSTALL.md +7 -5
- package/.cursor/hooks/adapter.js +41 -4
- package/.cursor/hooks/after-agent-response.js +62 -0
- package/.cursor/hooks/before-submit-prompt.js +7 -1
- package/.cursor/hooks/post-tool-use-failure.js +21 -0
- package/.cursor/hooks/post-tool-use.js +39 -0
- package/.cursor/hooks/shared/aw-phase-definitions.js +53 -0
- package/.cursor/hooks/shared/aw-phase-runner.js +3 -1
- package/.cursor/hooks/subagent-start.js +22 -4
- package/.cursor/hooks/subagent-stop.js +18 -1
- package/.cursor/hooks.json +23 -2
- package/.opencode/package.json +1 -1
- package/AGENTS.md +3 -3
- package/README.md +5 -5
- package/commands/adk.md +52 -0
- package/commands/build.md +22 -9
- package/commands/deploy.md +12 -0
- package/commands/execute.md +9 -0
- package/commands/feature.md +333 -0
- package/commands/investigate.md +18 -5
- package/commands/plan.md +23 -9
- package/commands/publish.md +65 -0
- package/commands/review.md +12 -0
- package/commands/ship.md +12 -0
- package/commands/test.md +12 -0
- package/commands/verify.md +9 -0
- package/hooks/hooks.json +36 -0
- package/manifests/install-components.json +8 -0
- package/manifests/install-modules.json +83 -0
- package/manifests/install-profiles.json +7 -0
- package/package.json +1 -1
- package/scripts/ci/validate-rules.js +51 -0
- package/scripts/cursor-aw-home/hooks.json +23 -2
- package/scripts/cursor-aw-hooks/adapter.js +41 -4
- package/scripts/cursor-aw-hooks/before-submit-prompt.js +7 -1
- package/scripts/hooks/aw-usage-commit-created.js +32 -0
- package/scripts/hooks/aw-usage-post-tool-use-failure.js +56 -0
- package/scripts/hooks/aw-usage-post-tool-use.js +242 -0
- package/scripts/hooks/aw-usage-prompt-submit.js +112 -0
- package/scripts/hooks/aw-usage-session-start.js +48 -0
- package/scripts/hooks/aw-usage-stop.js +182 -0
- package/scripts/hooks/aw-usage-telemetry-send.js +84 -0
- package/scripts/hooks/cost-tracker.js +3 -23
- package/scripts/hooks/shared/aw-phase-definitions.js +53 -0
- package/scripts/hooks/shared/aw-phase-runner.js +3 -1
- package/scripts/lib/aw-hook-contract.js +2 -2
- package/scripts/lib/aw-pricing.js +306 -0
- package/scripts/lib/aw-usage-telemetry.js +472 -0
- package/scripts/lib/codex-hook-config.js +8 -8
- package/scripts/lib/cursor-hook-config.js +25 -10
- package/scripts/lib/install-targets/cursor-project.js +3 -0
- package/scripts/lib/install-targets/helpers.js +20 -3
- package/skills/aw-adk/SKILL.md +317 -0
- package/skills/aw-adk/agents/analyzer.md +113 -0
- package/skills/aw-adk/agents/comparator.md +113 -0
- package/skills/aw-adk/agents/grader.md +115 -0
- package/skills/aw-adk/assets/eval_review.html +76 -0
- package/skills/aw-adk/eval-viewer/generate_review.py +164 -0
- package/skills/aw-adk/eval-viewer/viewer.html +181 -0
- package/skills/aw-adk/evals/eval-colocated-placement.md +84 -0
- package/skills/aw-adk/evals/eval-create-agent.md +90 -0
- package/skills/aw-adk/evals/eval-create-command.md +98 -0
- package/skills/aw-adk/evals/eval-create-eval.md +89 -0
- package/skills/aw-adk/evals/eval-create-rule.md +99 -0
- package/skills/aw-adk/evals/eval-create-skill.md +97 -0
- package/skills/aw-adk/evals/eval-delete-agent.md +79 -0
- package/skills/aw-adk/evals/eval-delete-command.md +89 -0
- package/skills/aw-adk/evals/eval-delete-rule.md +86 -0
- package/skills/aw-adk/evals/eval-delete-skill.md +90 -0
- package/skills/aw-adk/evals/eval-meta-eval-coverage.md +78 -0
- package/skills/aw-adk/evals/eval-meta-eval-determinism.md +81 -0
- package/skills/aw-adk/evals/eval-meta-eval-false-pass.md +81 -0
- package/skills/aw-adk/evals/eval-score-accuracy.md +95 -0
- package/skills/aw-adk/evals/eval-type-redirect.md +68 -0
- package/skills/aw-adk/evals/evals.json +96 -0
- package/skills/aw-adk/references/artifact-wiring.md +162 -0
- package/skills/aw-adk/references/cross-ide-mapping.md +71 -0
- package/skills/aw-adk/references/eval-placement-guide.md +183 -0
- package/skills/aw-adk/references/external-resources.md +75 -0
- package/skills/aw-adk/references/getting-started.md +66 -0
- package/skills/aw-adk/references/registry-structure.md +152 -0
- package/skills/aw-adk/references/rubric-agent.md +36 -0
- package/skills/aw-adk/references/rubric-command.md +36 -0
- package/skills/aw-adk/references/rubric-eval.md +36 -0
- package/skills/aw-adk/references/rubric-meta-eval.md +132 -0
- package/skills/aw-adk/references/rubric-rule.md +36 -0
- package/skills/aw-adk/references/rubric-skill.md +36 -0
- package/skills/aw-adk/references/schemas.md +222 -0
- package/skills/aw-adk/references/template-agent.md +251 -0
- package/skills/aw-adk/references/template-command.md +279 -0
- package/skills/aw-adk/references/template-eval.md +176 -0
- package/skills/aw-adk/references/template-rule.md +119 -0
- package/skills/aw-adk/references/template-skill.md +123 -0
- package/skills/aw-adk/references/type-classifier.md +98 -0
- package/skills/aw-adk/references/writing-good-agents.md +227 -0
- package/skills/aw-adk/references/writing-good-commands.md +258 -0
- package/skills/aw-adk/references/writing-good-evals.md +271 -0
- package/skills/aw-adk/references/writing-good-rules.md +214 -0
- package/skills/aw-adk/references/writing-good-skills.md +159 -0
- package/skills/aw-adk/scripts/aggregate-benchmark.py +190 -0
- package/skills/aw-adk/scripts/lint-artifact.sh +211 -0
- package/skills/aw-adk/scripts/score-artifact.sh +179 -0
- package/skills/aw-adk/scripts/trigger-eval.py +192 -0
- package/skills/aw-build/SKILL.md +19 -2
- package/skills/aw-deploy/SKILL.md +65 -3
- package/skills/aw-design/SKILL.md +156 -0
- package/skills/aw-design/references/highrise-tokens.md +394 -0
- package/skills/aw-design/references/micro-interactions.md +76 -0
- package/skills/aw-design/references/prompt-template.md +160 -0
- package/skills/aw-design/references/quality-checklist.md +70 -0
- package/skills/aw-design/references/self-review.md +497 -0
- package/skills/aw-design/references/stitch-workflow.md +127 -0
- package/skills/aw-feature/SKILL.md +293 -0
- package/skills/aw-investigate/SKILL.md +17 -0
- package/skills/aw-plan/SKILL.md +34 -3
- package/skills/aw-publish/SKILL.md +300 -0
- package/skills/aw-publish/evals/eval-confirmation-gate.md +60 -0
- package/skills/aw-publish/evals/eval-intent-detection.md +111 -0
- package/skills/aw-publish/evals/eval-push-modes.md +67 -0
- package/skills/aw-publish/evals/eval-rules-push.md +60 -0
- package/skills/aw-publish/evals/evals.json +29 -0
- package/skills/aw-publish/references/push-modes.md +38 -0
- package/skills/aw-review/SKILL.md +88 -9
- package/skills/aw-rules-review/SKILL.md +124 -0
- package/skills/aw-rules-review/agents/openai.yaml +3 -0
- package/skills/aw-rules-review/scripts/generate-review-template.mjs +323 -0
- package/skills/aw-ship/SKILL.md +16 -0
- package/skills/aw-spec/SKILL.md +15 -0
- package/skills/aw-tasks/SKILL.md +15 -0
- package/skills/aw-test/SKILL.md +16 -0
- package/skills/aw-yolo/SKILL.md +4 -0
- package/skills/diagnose/SKILL.md +121 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/finish-only-when-green/SKILL.md +265 -0
- package/skills/grill-me/SKILL.md +24 -0
- package/skills/grill-with-docs/SKILL.md +92 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +67 -0
- package/skills/improve-codebase-architecture/SKILL.md +75 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/local-ghl-setup-from-screenshot/SKILL.md +538 -0
- package/skills/tdd/SKILL.md +115 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
- package/skills/to-issues/SKILL.md +62 -0
- package/skills/to-prd/SKILL.md +75 -0
- package/skills/using-aw-skills/SKILL.md +170 -237
- package/skills/using-aw-skills/hooks/session-start.sh +11 -41
- package/skills/zoom-out/SKILL.md +24 -0
- package/.cursor/rules/common-agents.md +0 -53
- package/.cursor/rules/common-aw-routing.md +0 -43
- package/.cursor/rules/common-coding-style.md +0 -52
- package/.cursor/rules/common-development-workflow.md +0 -33
- package/.cursor/rules/common-git-workflow.md +0 -28
- package/.cursor/rules/common-hooks.md +0 -34
- package/.cursor/rules/common-patterns.md +0 -35
- package/.cursor/rules/common-performance.md +0 -59
- package/.cursor/rules/common-security.md +0 -33
- package/.cursor/rules/common-testing.md +0 -33
- package/.cursor/skills/api-and-interface-design/SKILL.md +0 -75
- package/.cursor/skills/article-writing/SKILL.md +0 -85
- package/.cursor/skills/aw-brainstorm/SKILL.md +0 -115
- package/.cursor/skills/aw-build/SKILL.md +0 -152
- package/.cursor/skills/aw-build/evals/build-stage-cases.json +0 -28
- package/.cursor/skills/aw-debug/SKILL.md +0 -49
- package/.cursor/skills/aw-deploy/SKILL.md +0 -101
- package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +0 -32
- package/.cursor/skills/aw-execute/SKILL.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-code.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-docs.md +0 -28
- package/.cursor/skills/aw-execute/references/mode-infra.md +0 -44
- package/.cursor/skills/aw-execute/references/mode-migration.md +0 -58
- package/.cursor/skills/aw-execute/references/worker-implementer.md +0 -26
- package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +0 -229
- package/.cursor/skills/aw-finish/SKILL.md +0 -111
- package/.cursor/skills/aw-investigate/SKILL.md +0 -109
- package/.cursor/skills/aw-plan/SKILL.md +0 -368
- package/.cursor/skills/aw-prepare/SKILL.md +0 -118
- package/.cursor/skills/aw-review/SKILL.md +0 -118
- package/.cursor/skills/aw-ship/SKILL.md +0 -115
- package/.cursor/skills/aw-spec/SKILL.md +0 -104
- package/.cursor/skills/aw-tasks/SKILL.md +0 -138
- package/.cursor/skills/aw-test/SKILL.md +0 -118
- package/.cursor/skills/aw-verify/SKILL.md +0 -51
- package/.cursor/skills/aw-yolo/SKILL.md +0 -111
- package/.cursor/skills/browser-testing-with-devtools/SKILL.md +0 -81
- package/.cursor/skills/bun-runtime/SKILL.md +0 -84
- package/.cursor/skills/ci-cd-and-automation/SKILL.md +0 -71
- package/.cursor/skills/code-simplification/SKILL.md +0 -74
- package/.cursor/skills/content-engine/SKILL.md +0 -88
- package/.cursor/skills/context-engineering/SKILL.md +0 -74
- package/.cursor/skills/deprecation-and-migration/SKILL.md +0 -75
- package/.cursor/skills/documentation-and-adrs/SKILL.md +0 -75
- package/.cursor/skills/documentation-lookup/SKILL.md +0 -90
- package/.cursor/skills/frontend-slides/SKILL.md +0 -184
- package/.cursor/skills/frontend-slides/STYLE_PRESETS.md +0 -330
- package/.cursor/skills/frontend-ui-engineering/SKILL.md +0 -68
- package/.cursor/skills/git-workflow-and-versioning/SKILL.md +0 -75
- package/.cursor/skills/idea-refine/SKILL.md +0 -84
- package/.cursor/skills/incremental-implementation/SKILL.md +0 -75
- package/.cursor/skills/investor-materials/SKILL.md +0 -96
- package/.cursor/skills/investor-outreach/SKILL.md +0 -76
- package/.cursor/skills/market-research/SKILL.md +0 -75
- package/.cursor/skills/mcp-server-patterns/SKILL.md +0 -67
- package/.cursor/skills/nextjs-turbopack/SKILL.md +0 -44
- package/.cursor/skills/performance-optimization/SKILL.md +0 -77
- package/.cursor/skills/security-and-hardening/SKILL.md +0 -70
- package/.cursor/skills/using-aw-skills/SKILL.md +0 -290
- package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +0 -25
- package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +0 -171
- package/.cursor/skills/using-aw-skills/hooks/hooks.json +0 -9
- package/.cursor/skills/using-aw-skills/hooks/session-start.sh +0 -67
- package/.cursor/skills/using-platform-skills/SKILL.md +0 -163
- package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +0 -52
- /package/.cursor/rules/{golang-coding-style.md → golang-coding-style.mdc} +0 -0
- /package/.cursor/rules/{golang-hooks.md → golang-hooks.mdc} +0 -0
- /package/.cursor/rules/{golang-patterns.md → golang-patterns.mdc} +0 -0
- /package/.cursor/rules/{golang-security.md → golang-security.mdc} +0 -0
- /package/.cursor/rules/{golang-testing.md → golang-testing.mdc} +0 -0
- /package/.cursor/rules/{kotlin-coding-style.md → kotlin-coding-style.mdc} +0 -0
- /package/.cursor/rules/{kotlin-hooks.md → kotlin-hooks.mdc} +0 -0
- /package/.cursor/rules/{kotlin-patterns.md → kotlin-patterns.mdc} +0 -0
- /package/.cursor/rules/{kotlin-security.md → kotlin-security.mdc} +0 -0
- /package/.cursor/rules/{kotlin-testing.md → kotlin-testing.mdc} +0 -0
- /package/.cursor/rules/{php-coding-style.md → php-coding-style.mdc} +0 -0
- /package/.cursor/rules/{php-hooks.md → php-hooks.mdc} +0 -0
- /package/.cursor/rules/{php-patterns.md → php-patterns.mdc} +0 -0
- /package/.cursor/rules/{php-security.md → php-security.mdc} +0 -0
- /package/.cursor/rules/{php-testing.md → php-testing.mdc} +0 -0
- /package/.cursor/rules/{python-coding-style.md → python-coding-style.mdc} +0 -0
- /package/.cursor/rules/{python-hooks.md → python-hooks.mdc} +0 -0
- /package/.cursor/rules/{python-patterns.md → python-patterns.mdc} +0 -0
- /package/.cursor/rules/{python-security.md → python-security.mdc} +0 -0
- /package/.cursor/rules/{python-testing.md → python-testing.mdc} +0 -0
- /package/.cursor/rules/{swift-coding-style.md → swift-coding-style.mdc} +0 -0
- /package/.cursor/rules/{swift-hooks.md → swift-hooks.mdc} +0 -0
- /package/.cursor/rules/{swift-patterns.md → swift-patterns.mdc} +0 -0
- /package/.cursor/rules/{swift-security.md → swift-security.mdc} +0 -0
- /package/.cursor/rules/{swift-testing.md → swift-testing.mdc} +0 -0
- /package/.cursor/rules/{typescript-coding-style.md → typescript-coding-style.mdc} +0 -0
- /package/.cursor/rules/{typescript-hooks.md → typescript-hooks.mdc} +0 -0
- /package/.cursor/rules/{typescript-patterns.md → typescript-patterns.mdc} +0 -0
- /package/.cursor/rules/{typescript-security.md → typescript-security.mdc} +0 -0
- /package/.cursor/rules/{typescript-testing.md → typescript-testing.mdc} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Usage telemetry — PostToolUseFailure hook.
|
|
4
|
+
*
|
|
5
|
+
* Claude: error (string/object) + is_interrupt (boolean).
|
|
6
|
+
* Cursor: error_message (string) + failure_type ("timeout"/"error"/"permission_denied").
|
|
7
|
+
* Codex: does NOT support PostToolUseFailure — this hook won't fire.
|
|
8
|
+
*
|
|
9
|
+
* Outputs {} on stdout.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const { buildEvent, sendAsync } = require('../lib/aw-usage-telemetry');
|
|
15
|
+
|
|
16
|
+
const MAX_STDIN = 1024 * 1024;
|
|
17
|
+
let raw = '';
|
|
18
|
+
|
|
19
|
+
process.stdin.setEncoding('utf8');
|
|
20
|
+
process.stdin.on('data', chunk => {
|
|
21
|
+
if (raw.length < MAX_STDIN) {
|
|
22
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.stdin.on('end', () => {
|
|
27
|
+
try {
|
|
28
|
+
const input = JSON.parse(raw);
|
|
29
|
+
const toolName = input.tool_name || 'unknown';
|
|
30
|
+
|
|
31
|
+
// Normalize error fields across Claude and Cursor
|
|
32
|
+
let errorMessage;
|
|
33
|
+
let failureType;
|
|
34
|
+
|
|
35
|
+
if (input.error_message !== undefined) {
|
|
36
|
+
// Cursor format
|
|
37
|
+
errorMessage = String(input.error_message || '');
|
|
38
|
+
failureType = input.failure_type || 'error';
|
|
39
|
+
} else {
|
|
40
|
+
// Claude format
|
|
41
|
+
const err = input.error;
|
|
42
|
+
errorMessage = typeof err === 'string' ? err : (err?.message || JSON.stringify(err) || 'unknown');
|
|
43
|
+
failureType = input.is_interrupt ? 'interrupt' : 'error';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
sendAsync(buildEvent(input, 'tool_error', {
|
|
47
|
+
tool_name: toolName,
|
|
48
|
+
error_message: errorMessage.slice(0, 500),
|
|
49
|
+
failure_type: failureType,
|
|
50
|
+
}));
|
|
51
|
+
} catch {
|
|
52
|
+
// Non-blocking.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.stdout.write('{}');
|
|
56
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Usage telemetry — PostToolUse hook.
|
|
4
|
+
*
|
|
5
|
+
* Detects: skill invocations, agent spawns, skill pushes.
|
|
6
|
+
* PR contribution is tracked via Co-Authored-By trailer in prepare-commit-msg hook.
|
|
7
|
+
* Outputs {} on stdout (Claude/Codex parse stdout as JSON).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
buildEvent,
|
|
14
|
+
detectHarness,
|
|
15
|
+
sendAsync,
|
|
16
|
+
readSessionSkill,
|
|
17
|
+
tryAcquireDedupe,
|
|
18
|
+
} = require('../lib/aw-usage-telemetry');
|
|
19
|
+
|
|
20
|
+
const MAX_STDIN = 1024 * 1024;
|
|
21
|
+
function parseMaybeJsonObject(value) {
|
|
22
|
+
if (typeof value !== 'string') return value;
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
if (!trimmed || !/^[{[]/.test(trimmed)) return value;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(trimmed);
|
|
27
|
+
} catch {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toObject(value) {
|
|
33
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getSessionId(input) {
|
|
37
|
+
return input?.session_id || input?.conversation_id || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getCommand(input) {
|
|
41
|
+
return String(
|
|
42
|
+
input?.tool_input?.command
|
|
43
|
+
|| input?.tool_input?.args?.command
|
|
44
|
+
|| ''
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeToolResult(input) {
|
|
49
|
+
const rawToolResponse = parseMaybeJsonObject(input?.tool_response);
|
|
50
|
+
const rawToolOutput = parseMaybeJsonObject(input?.tool_output);
|
|
51
|
+
const toolResponse = toObject(rawToolResponse);
|
|
52
|
+
const toolOutput = toObject(rawToolOutput);
|
|
53
|
+
const exitCode = [
|
|
54
|
+
input?.exit_code,
|
|
55
|
+
toolResponse.exit_code,
|
|
56
|
+
toolResponse.exitCode,
|
|
57
|
+
toolOutput.exit_code,
|
|
58
|
+
toolOutput.exitCode,
|
|
59
|
+
].find(value => value !== undefined && value !== null && value !== '');
|
|
60
|
+
const messageParts = [
|
|
61
|
+
toolResponse.stderr,
|
|
62
|
+
toolOutput.stderr,
|
|
63
|
+
toolResponse.output,
|
|
64
|
+
toolOutput.output,
|
|
65
|
+
typeof rawToolResponse === 'string' ? rawToolResponse : '',
|
|
66
|
+
typeof rawToolOutput === 'string' ? rawToolOutput : '',
|
|
67
|
+
input?.stderr,
|
|
68
|
+
input?.output,
|
|
69
|
+
].filter(value => typeof value === 'string' && value.trim());
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
exitCode: exitCode === undefined ? null : Number(exitCode),
|
|
73
|
+
errorMessage: messageParts.join('\n').slice(0, 500),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isExplicitFailureExitCode(exitCode) {
|
|
78
|
+
return exitCode !== null && Number.isFinite(exitCode) && exitCode !== 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function inferShellFailureFromMessage(toolName, errorMessage) {
|
|
82
|
+
if (toolName !== 'Shell' && toolName !== 'Bash') return false;
|
|
83
|
+
if (typeof errorMessage !== 'string' || !errorMessage.trim()) return false;
|
|
84
|
+
const patterns = [
|
|
85
|
+
/(?:^|\n)[^:\n]+:\s.*\bNo such file or directory\b/i,
|
|
86
|
+
/(?:^|\n)[^:\n]+:\s.*\bPermission denied\b/i,
|
|
87
|
+
/(?:^|\n)[^:\n]+:\s.*\bOperation not permitted\b/i,
|
|
88
|
+
/(?:^|\n)(?:bash|zsh|sh): .*?\bcommand not found\b/i,
|
|
89
|
+
/(?:^|\n)[^:\n]+:\s.*\bcannot access\b/i,
|
|
90
|
+
/(?:^|\n)[^:\n]+:\s.*\bis a directory\b/i,
|
|
91
|
+
];
|
|
92
|
+
return patterns.some(pattern => pattern.test(errorMessage));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function shouldEmitSkillName(skillName) {
|
|
96
|
+
return Boolean(skillName) && skillName !== 'using-aw-skills';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectPostToolUseEvents(input, options = {}) {
|
|
100
|
+
const events = [];
|
|
101
|
+
const toolName = input?.tool_name || '';
|
|
102
|
+
const promptSkillOverride = options.promptSkillOverride || null;
|
|
103
|
+
|
|
104
|
+
// Skill detection: Claude uses tool_name='Skill', Cursor reads SKILL.md via 'Read' tool.
|
|
105
|
+
const filePath = input?.tool_input?.file_path || '';
|
|
106
|
+
const isSkillRead = toolName === 'Read' && /\/SKILL\.md$/i.test(filePath);
|
|
107
|
+
|
|
108
|
+
if (toolName === 'Skill' || isSkillRead) {
|
|
109
|
+
let skillName = input?.tool_input?.skill || input?.tool_input?.args?.skill || '';
|
|
110
|
+
// For Cursor: extract skill name from path (e.g. .../skills/<skill-name>/SKILL.md)
|
|
111
|
+
if (!skillName && isSkillRead) {
|
|
112
|
+
const pathMatch = filePath.match(/\/skills\/([^/]+)\/SKILL\.md$/i);
|
|
113
|
+
skillName = pathMatch ? pathMatch[1] : filePath.split('/').slice(-2, -1)[0] || '';
|
|
114
|
+
}
|
|
115
|
+
if (shouldEmitSkillName(skillName)) {
|
|
116
|
+
events.push({
|
|
117
|
+
eventType: 'skill_invoked',
|
|
118
|
+
payload: {
|
|
119
|
+
skill_name: skillName,
|
|
120
|
+
args: input?.tool_input?.args || '',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} else if (toolName === 'Agent') {
|
|
125
|
+
events.push({
|
|
126
|
+
eventType: 'agent_spawned',
|
|
127
|
+
payload: {
|
|
128
|
+
agent_type: input?.tool_input?.subagent_type || 'general-purpose',
|
|
129
|
+
description: input?.tool_input?.description || '',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const toolResult = normalizeToolResult(input);
|
|
135
|
+
if (isExplicitFailureExitCode(toolResult.exitCode)
|
|
136
|
+
|| inferShellFailureFromMessage(toolName, toolResult.errorMessage)) {
|
|
137
|
+
const payload = {
|
|
138
|
+
tool_name: toolName || 'unknown',
|
|
139
|
+
error_message: toolResult.errorMessage,
|
|
140
|
+
failure_type: 'error',
|
|
141
|
+
};
|
|
142
|
+
if (isExplicitFailureExitCode(toolResult.exitCode)) {
|
|
143
|
+
payload.exit_code = toolResult.exitCode;
|
|
144
|
+
}
|
|
145
|
+
events.push({
|
|
146
|
+
eventType: 'tool_error',
|
|
147
|
+
payload,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (toolName === 'Shell' || toolName === 'Bash') {
|
|
152
|
+
const cmd = getCommand(input);
|
|
153
|
+
// Codex skill detection: Bash commands that read SKILL.md files.
|
|
154
|
+
if (!promptSkillOverride) {
|
|
155
|
+
const skillCmdMatch = cmd.match(/\/skills\/([^/]+)\/SKILL\.md/i);
|
|
156
|
+
if (skillCmdMatch && shouldEmitSkillName(skillCmdMatch[1])) {
|
|
157
|
+
events.push({
|
|
158
|
+
eventType: 'skill_invoked',
|
|
159
|
+
payload: {
|
|
160
|
+
skill_name: skillCmdMatch[1],
|
|
161
|
+
args: '',
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (/\baw\s+push\b/.test(cmd)) {
|
|
167
|
+
const skillMatch = cmd.match(/aw\s+push\s+(\S+)/);
|
|
168
|
+
events.push({
|
|
169
|
+
eventType: 'skill_pushed',
|
|
170
|
+
payload: {
|
|
171
|
+
skill_name: skillMatch ? skillMatch[1] : '',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return events;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function processPostToolUseInput(input, deps = {}) {
|
|
181
|
+
const emit = typeof deps.emit === 'function' ? deps.emit : () => {};
|
|
182
|
+
const promptSkillOverride = deps.promptSkillOverride || readSessionSkill(
|
|
183
|
+
getSessionId(input),
|
|
184
|
+
input?.turn_id || null,
|
|
185
|
+
);
|
|
186
|
+
const events = collectPostToolUseEvents(input, { promptSkillOverride });
|
|
187
|
+
for (const event of events) {
|
|
188
|
+
emit(event.eventType, event.payload);
|
|
189
|
+
}
|
|
190
|
+
return events;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function main() {
|
|
194
|
+
let raw = '';
|
|
195
|
+
|
|
196
|
+
process.stdin.setEncoding('utf8');
|
|
197
|
+
process.stdin.on('data', chunk => {
|
|
198
|
+
if (raw.length < MAX_STDIN) {
|
|
199
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
process.stdin.on('end', () => {
|
|
204
|
+
try {
|
|
205
|
+
const input = JSON.parse(raw);
|
|
206
|
+
const shouldDedup = detectHarness(input) === 'codex' || Boolean(input?.tool_use_id);
|
|
207
|
+
if (!shouldDedup || tryAcquireDedupe('post-tool-use', [
|
|
208
|
+
getSessionId(input),
|
|
209
|
+
input?.turn_id || '',
|
|
210
|
+
input?.tool_use_id || '',
|
|
211
|
+
input?.tool_name || '',
|
|
212
|
+
getCommand(input),
|
|
213
|
+
input?.tool_input?.file_path || '',
|
|
214
|
+
input?.tool_input?.description || '',
|
|
215
|
+
])) {
|
|
216
|
+
processPostToolUseInput(input, {
|
|
217
|
+
emit(eventType, payload) {
|
|
218
|
+
sendAsync(buildEvent(input, eventType, payload));
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// Non-blocking — never fail the hook.
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
process.stdout.write('{}');
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (require.main === module) {
|
|
231
|
+
main();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
collectPostToolUseEvents,
|
|
236
|
+
inferShellFailureFromMessage,
|
|
237
|
+
isExplicitFailureExitCode,
|
|
238
|
+
normalizeToolResult,
|
|
239
|
+
parseMaybeJsonObject,
|
|
240
|
+
processPostToolUseInput,
|
|
241
|
+
shouldEmitSkillName,
|
|
242
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Usage telemetry — UserPromptSubmit hook.
|
|
4
|
+
*
|
|
5
|
+
* Emits prompt_submitted as a boundary marker.
|
|
6
|
+
* No matchers on any harness — always fires.
|
|
7
|
+
*
|
|
8
|
+
* Outputs {} on stdout.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
buildEvent,
|
|
15
|
+
sendAsync,
|
|
16
|
+
persistSessionSkill,
|
|
17
|
+
resolvePromptText,
|
|
18
|
+
tryAcquireDedupe,
|
|
19
|
+
isCodexInternalTaskTitlePrompt,
|
|
20
|
+
} = require('../lib/aw-usage-telemetry');
|
|
21
|
+
|
|
22
|
+
const MAX_STDIN = 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
function extractAwSlashCommand(input) {
|
|
25
|
+
const prompt = resolvePromptText(input).trim();
|
|
26
|
+
const match = prompt.match(/^\/(aw:[a-z0-9-]+)(?:\s+([\s\S]*\S))?\s*$/i);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
return {
|
|
29
|
+
skill_name: match[1].toLowerCase(),
|
|
30
|
+
args: match[2] || '',
|
|
31
|
+
source: 'user_prompt',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getSessionId(input) {
|
|
36
|
+
return input?.session_id || input?.conversation_id || 'unknown';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldSkipPromptSubmitTelemetry(input) {
|
|
40
|
+
return isCodexInternalTaskTitlePrompt(input);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function processPromptSubmitInput(input, deps = {}) {
|
|
44
|
+
const emit = typeof deps.emit === 'function' ? deps.emit : () => {};
|
|
45
|
+
const persistSkill = typeof deps.persistSkill === 'function' ? deps.persistSkill : () => {};
|
|
46
|
+
const events = [];
|
|
47
|
+
|
|
48
|
+
events.push({ eventType: 'prompt_submitted', payload: {} });
|
|
49
|
+
|
|
50
|
+
const skill = extractAwSlashCommand(input);
|
|
51
|
+
if (skill) {
|
|
52
|
+
persistSkill(getSessionId(input), input?.turn_id || null, skill);
|
|
53
|
+
events.push({
|
|
54
|
+
eventType: 'skill_invoked',
|
|
55
|
+
payload: {
|
|
56
|
+
skill_name: skill.skill_name,
|
|
57
|
+
args: skill.args,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
emit(event.eventType, event.payload);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return events;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
let raw = '';
|
|
71
|
+
|
|
72
|
+
process.stdin.setEncoding('utf8');
|
|
73
|
+
process.stdin.on('data', chunk => {
|
|
74
|
+
if (raw.length < MAX_STDIN) {
|
|
75
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
process.stdin.on('end', () => {
|
|
80
|
+
try {
|
|
81
|
+
const input = JSON.parse(raw);
|
|
82
|
+
if (!shouldSkipPromptSubmitTelemetry(input)
|
|
83
|
+
&& tryAcquireDedupe('prompt-submit', [
|
|
84
|
+
getSessionId(input),
|
|
85
|
+
input?.turn_id || '',
|
|
86
|
+
resolvePromptText(input),
|
|
87
|
+
])) {
|
|
88
|
+
processPromptSubmitInput(input, {
|
|
89
|
+
emit(eventType, payload) {
|
|
90
|
+
sendAsync(buildEvent(input, eventType, payload));
|
|
91
|
+
},
|
|
92
|
+
persistSkill: persistSessionSkill,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Non-blocking.
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
process.stdout.write('{}');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (require.main === module) {
|
|
104
|
+
main();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
extractAwSlashCommand,
|
|
109
|
+
processPromptSubmitInput,
|
|
110
|
+
resolvePromptText,
|
|
111
|
+
shouldSkipPromptSubmitTelemetry,
|
|
112
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Usage telemetry — SessionStart hook.
|
|
4
|
+
*
|
|
5
|
+
* Captures session_start event and persists the model for later hooks.
|
|
6
|
+
* Claude sends model on SessionStart only — this is the only chance to capture it.
|
|
7
|
+
*
|
|
8
|
+
* Outputs {} on stdout.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const { buildEvent, sendAsync, persistSessionModel, pruneStaleSessionFiles } = require('../lib/aw-usage-telemetry');
|
|
14
|
+
|
|
15
|
+
const MAX_STDIN = 1024 * 1024;
|
|
16
|
+
let raw = '';
|
|
17
|
+
|
|
18
|
+
process.stdin.setEncoding('utf8');
|
|
19
|
+
process.stdin.on('data', chunk => {
|
|
20
|
+
if (raw.length < MAX_STDIN) {
|
|
21
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
process.stdin.on('end', () => {
|
|
26
|
+
try {
|
|
27
|
+
const input = JSON.parse(raw);
|
|
28
|
+
const sessionId = input.session_id
|
|
29
|
+
|| input._cursor?.conversation_id
|
|
30
|
+
|| input.conversation_id
|
|
31
|
+
|| null;
|
|
32
|
+
const model = input.model || input._cursor?.model || null;
|
|
33
|
+
|
|
34
|
+
// Prune stale session files (>24h) to prevent unbounded growth
|
|
35
|
+
pruneStaleSessionFiles();
|
|
36
|
+
|
|
37
|
+
// Persist model so PostToolUse/Stop hooks can read it
|
|
38
|
+
persistSessionModel(sessionId, model);
|
|
39
|
+
|
|
40
|
+
sendAsync(buildEvent(input, 'session_start', {
|
|
41
|
+
model: model || null,
|
|
42
|
+
}));
|
|
43
|
+
} catch {
|
|
44
|
+
// Non-blocking.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.stdout.write('{}');
|
|
48
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Usage telemetry — Stop hook.
|
|
4
|
+
*
|
|
5
|
+
* Captures response_completed per turn (fires after each Claude/Cursor/Codex response).
|
|
6
|
+
*
|
|
7
|
+
* No harness provides token usage in hook input directly. All three provide
|
|
8
|
+
* transcript_path — we parse the transcript JSONL to extract model, stop_reason,
|
|
9
|
+
* and usage for any harness.
|
|
10
|
+
*
|
|
11
|
+
* Pricing is resolved dynamically via OpenRouter API (24h cached) with a
|
|
12
|
+
* hardcoded fallback for when the API is unreachable and no cache exists.
|
|
13
|
+
*
|
|
14
|
+
* Outputs {} on stdout.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
buildEvent,
|
|
21
|
+
sendAsync,
|
|
22
|
+
detectHarness,
|
|
23
|
+
persistSessionModel,
|
|
24
|
+
readLastAssistantFromTranscript,
|
|
25
|
+
tryAcquireDedupe,
|
|
26
|
+
isCodexInternalTaskTitleCompletion,
|
|
27
|
+
} = require('../lib/aw-usage-telemetry');
|
|
28
|
+
const { estimateCost, toNumber } = require('../lib/aw-pricing');
|
|
29
|
+
|
|
30
|
+
const MAX_STDIN = 1024 * 1024;
|
|
31
|
+
function pickFirstNumber(...values) {
|
|
32
|
+
for (const value of values) {
|
|
33
|
+
const n = toNumber(value);
|
|
34
|
+
if (n !== 0) return n;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildResponseCompletedData(input) {
|
|
40
|
+
const harness = detectHarness(input);
|
|
41
|
+
|
|
42
|
+
// Parse transcript for all harnesses — all three provide transcript_path.
|
|
43
|
+
// The shared parser now understands both Claude/Cursor JSONL and Codex
|
|
44
|
+
// response_item + token_count transcript shapes.
|
|
45
|
+
let transcriptData = null;
|
|
46
|
+
if (input.transcript_path) {
|
|
47
|
+
transcriptData = readLastAssistantFromTranscript(input.transcript_path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Normalize stop reason across harnesses.
|
|
51
|
+
let stopReason;
|
|
52
|
+
if (harness === 'cursor') {
|
|
53
|
+
// Cursor adapter maps sessionEnd.reason / stop.status to stop_reason.
|
|
54
|
+
stopReason = input.stop_reason || input.status || input.reason || 'unknown';
|
|
55
|
+
} else if (harness === 'codex') {
|
|
56
|
+
stopReason = input.last_assistant_message ? 'completed' : 'unknown';
|
|
57
|
+
} else {
|
|
58
|
+
// Claude: prefer hook input, fall back to transcript.
|
|
59
|
+
stopReason = input.stop_reason
|
|
60
|
+
|| (transcriptData && transcriptData.stop_reason)
|
|
61
|
+
|| 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Token usage — prefer top-level fields (Cursor sends these directly),
|
|
65
|
+
// then input.usage, then transcript as last resort.
|
|
66
|
+
const hookUsage = input.usage || {};
|
|
67
|
+
const txUsage = (transcriptData && transcriptData.usage) || {};
|
|
68
|
+
const usage = Object.keys(hookUsage).length > 0 ? hookUsage : txUsage;
|
|
69
|
+
|
|
70
|
+
const inputTokens = pickFirstNumber(
|
|
71
|
+
input.input_tokens,
|
|
72
|
+
usage.input_tokens,
|
|
73
|
+
usage.prompt_tokens,
|
|
74
|
+
);
|
|
75
|
+
const outputTokens = pickFirstNumber(
|
|
76
|
+
input.output_tokens,
|
|
77
|
+
usage.output_tokens,
|
|
78
|
+
usage.completion_tokens,
|
|
79
|
+
);
|
|
80
|
+
const cacheReadTokens = pickFirstNumber(
|
|
81
|
+
input.cache_read_tokens,
|
|
82
|
+
usage.cache_read_input_tokens,
|
|
83
|
+
usage.cached_input_tokens,
|
|
84
|
+
);
|
|
85
|
+
const cacheCreateTokens = pickFirstNumber(
|
|
86
|
+
input.cache_write_tokens,
|
|
87
|
+
usage.cache_creation_input_tokens,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Model: prefer hook input → transcript → session file.
|
|
91
|
+
const model = input.model
|
|
92
|
+
|| input._cursor?.model
|
|
93
|
+
|| (transcriptData && transcriptData.model)
|
|
94
|
+
|| null;
|
|
95
|
+
|
|
96
|
+
// Persist model so PostToolUse/UserPromptSubmit hooks can read it.
|
|
97
|
+
// Stop fires every turn (before those hooks), so this stays fresh.
|
|
98
|
+
const sessionId = input.session_id || input.conversation_id || 'unknown';
|
|
99
|
+
if (model && sessionId !== 'unknown') {
|
|
100
|
+
persistSessionModel(sessionId, model);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const payload = { stop_reason: stopReason };
|
|
104
|
+
|
|
105
|
+
if (model) {
|
|
106
|
+
payload.model = model;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (inputTokens || outputTokens) {
|
|
110
|
+
payload.input_tokens = inputTokens;
|
|
111
|
+
payload.output_tokens = outputTokens;
|
|
112
|
+
payload.estimated_cost_usd = estimateCost(model, inputTokens, outputTokens, {
|
|
113
|
+
cacheReadTokens,
|
|
114
|
+
cacheWriteTokens: cacheCreateTokens,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (cacheReadTokens || cacheCreateTokens) {
|
|
119
|
+
payload.cache_read_tokens = cacheReadTokens;
|
|
120
|
+
if (cacheCreateTokens) {
|
|
121
|
+
payload.cache_create_tokens = cacheCreateTokens;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { model, payload, sessionId };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function shouldSkipResponseCompleted(input) {
|
|
129
|
+
return isCodexInternalTaskTitleCompletion(input);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function main() {
|
|
133
|
+
let raw = '';
|
|
134
|
+
|
|
135
|
+
process.stdin.setEncoding('utf8');
|
|
136
|
+
process.stdin.on('data', chunk => {
|
|
137
|
+
if (raw.length < MAX_STDIN) {
|
|
138
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
process.stdin.on('end', () => {
|
|
143
|
+
try {
|
|
144
|
+
const input = JSON.parse(raw);
|
|
145
|
+
if (!shouldSkipResponseCompleted(input)) {
|
|
146
|
+
const { model, payload, sessionId } = buildResponseCompletedData(input);
|
|
147
|
+
|
|
148
|
+
if (tryAcquireDedupe('response-completed', [
|
|
149
|
+
sessionId,
|
|
150
|
+
input?.turn_id || '',
|
|
151
|
+
input?.transcript_path || '',
|
|
152
|
+
input?.last_assistant_message || '',
|
|
153
|
+
input?.model || '',
|
|
154
|
+
payload.stop_reason || '',
|
|
155
|
+
payload.input_tokens || '',
|
|
156
|
+
payload.output_tokens || '',
|
|
157
|
+
])) {
|
|
158
|
+
// Override model in the event envelope too (buildEvent reads from hook input
|
|
159
|
+
// which doesn't have model for Claude — inject it so the top-level field is set).
|
|
160
|
+
const event = buildEvent(input, 'response_completed', payload);
|
|
161
|
+
if (model && !event.model) {
|
|
162
|
+
event.model = model;
|
|
163
|
+
}
|
|
164
|
+
sendAsync(event);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// Non-blocking.
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
process.stdout.write('{}');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (require.main === module) {
|
|
176
|
+
main();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
buildResponseCompletedData,
|
|
181
|
+
shouldSkipResponseCompleted,
|
|
182
|
+
};
|