@onlooker-community/ecosystem 0.15.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +39 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.release-please-manifest.json +5 -2
- package/CHANGELOG.md +15 -0
- package/CLAUDE.md +88 -0
- package/package.json +3 -3
- package/plugins/compass/.claude-plugin/plugin.json +14 -0
- package/plugins/compass/CHANGELOG.md +8 -0
- package/plugins/compass/config.json +71 -0
- package/plugins/compass/docs/adr/001-evaluate-prompts-in-context.md +82 -0
- package/plugins/compass/docs/design.md +421 -0
- package/plugins/compass/hooks/hooks.json +82 -0
- package/plugins/compass/scripts/hooks/compass-bash-gate.sh +95 -0
- package/plugins/compass/scripts/hooks/compass-pre-tool-use.sh +86 -0
- package/plugins/compass/scripts/hooks/compass-record-write.sh +97 -0
- package/plugins/compass/scripts/hooks/compass-session-start.sh +77 -0
- package/plugins/compass/scripts/lib/compass-config.sh +72 -0
- package/plugins/compass/scripts/lib/compass-evaluator.sh +374 -0
- package/plugins/compass/scripts/lib/compass-events.sh +81 -0
- package/plugins/compass/scripts/lib/compass-gate.sh +465 -0
- package/plugins/compass/scripts/lib/compass-sanitizer.sh +82 -0
- package/plugins/compass/scripts/lib/compass-transcript.sh +135 -0
- package/plugins/governor/.claude-plugin/plugin.json +14 -0
- package/plugins/governor/CHANGELOG.md +22 -0
- package/plugins/governor/config.json +19 -0
- package/plugins/governor/hooks/hooks.json +48 -0
- package/plugins/governor/scripts/hooks/governor-post-tool-use.sh +147 -0
- package/plugins/governor/scripts/hooks/governor-pre-tool-use.sh +199 -0
- package/plugins/governor/scripts/hooks/governor-session-start.sh +109 -0
- package/plugins/governor/scripts/hooks/governor-stop.sh +108 -0
- package/plugins/governor/scripts/lib/governor-config.sh +79 -0
- package/plugins/governor/scripts/lib/governor-estimate.sh +116 -0
- package/plugins/governor/scripts/lib/governor-events.sh +81 -0
- package/plugins/governor/scripts/lib/governor-ledger.sh +172 -0
- package/plugins/scribe/.claude-plugin/plugin.json +12 -0
- package/plugins/scribe/CHANGELOG.md +8 -0
- package/plugins/scribe/config.json +20 -0
- package/plugins/scribe/hooks/hooks.json +37 -0
- package/plugins/scribe/scripts/hooks/scribe-capture.sh +76 -0
- package/plugins/scribe/scripts/hooks/scribe-session-start.sh +58 -0
- package/plugins/scribe/scripts/hooks/scribe-stop.sh +67 -0
- package/plugins/scribe/scripts/lib/scribe-config.sh +72 -0
- package/plugins/scribe/scripts/lib/scribe-distill.sh +239 -0
- package/plugins/scribe/scripts/lib/scribe-events.sh +80 -0
- package/plugins/scribe/scripts/lib/scribe-extract.sh +147 -0
- package/plugins/scribe/scripts/lib/scribe-project-key.sh +89 -0
- package/plugins/scribe/scripts/lib/scribe-ulid.sh +50 -0
- package/release-please-config.json +48 -0
- package/test/bats/governor-config.bats +106 -0
- package/test/bats/governor-estimate.bats +86 -0
- package/test/bats/governor-events.bats +238 -0
- package/test/bats/governor-ledger.bats +220 -0
- package/test/bats/scribe-extract.bats +102 -0
- package/test/bats/scribe-project-key.bats +75 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "governor",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Resource governance and budget enforcement for the Onlooker ecosystem. Tracks per-session token and cost spend, gates Task spawns before they exceed a configurable budget ceiling, and emits governor.* events for audit. Named for the steam-engine governor — a device that regulates output. Builds on the Onlooker ecosystem plugin.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Onlooker Community",
|
|
7
|
+
"url": "https://onlooker.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://onlooker.dev",
|
|
10
|
+
"repository": "https://github.com/onlooker-community/ecosystem",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"skills": [],
|
|
13
|
+
"agents": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/onlooker-community/ecosystem/compare/governor-v0.1.0...governor-v0.2.0) (2026-05-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **governor:** resource governance and budget enforcement plugin :rocket: ([#43](https://github.com/onlooker-community/ecosystem/issues/43)) ([04e6d70](https://github.com/onlooker-community/ecosystem/commit/04e6d7051f27db752bb121d389d65b4d8ade04ad))
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-05-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial plugin scaffold: `config.json`, `plugin.json`, `hooks.json`
|
|
15
|
+
- `governor-config.sh` — three-layer config resolution (plugin defaults → user settings → repo settings)
|
|
16
|
+
- `governor-events.sh` — canonical `governor.*` event emission via ecosystem `onlooker-event.mjs`
|
|
17
|
+
- `governor-ledger.sh` — JSONL ledger read/write with `portable-lock.sh` atomic guard
|
|
18
|
+
- `governor-estimate.sh` — tier-table token estimation with configurable safety margin
|
|
19
|
+
- `governor-session-start.sh` — SessionStart hook: setup storage, load budget contract, sweep stale locks, check global policy hash
|
|
20
|
+
- `governor-pre-tool-use.sh` — PreToolUse hook on Task: pre-call gate via check-and-reserve with `portable-lock.sh`
|
|
21
|
+
- `governor-post-tool-use.sh` — PostToolUse hook on Task: record call duration and estimated tokens to JSONL ledger
|
|
22
|
+
- `governor-stop.sh` — Stop hook: emit `governor.session.complete` with cumulative spend summary
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugin_name": "governor",
|
|
3
|
+
"storage_path": "~/.onlooker",
|
|
4
|
+
"governor": {
|
|
5
|
+
"enabled": false,
|
|
6
|
+
"enforcement": "soft",
|
|
7
|
+
"global_policy_path": "~/.onlooker/governance/global-policy.yaml",
|
|
8
|
+
"session": {
|
|
9
|
+
"tokens_default": 100000,
|
|
10
|
+
"cost_usd_default": 1.0,
|
|
11
|
+
"reserve_pct": 10
|
|
12
|
+
},
|
|
13
|
+
"estimation": {
|
|
14
|
+
"safety_margin": 1.3,
|
|
15
|
+
"hard_stop_margin": 1.5,
|
|
16
|
+
"method": "tier_table"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/governor-session-start.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Task",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/governor-pre-tool-use.sh"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PostToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Task",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/governor-post-tool-use.sh"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"Stop": [
|
|
37
|
+
{
|
|
38
|
+
"matcher": "*",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/governor-stop.sh"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Governor PostToolUse hook (matcher: Task).
|
|
3
|
+
#
|
|
4
|
+
# Records each completed Task call in the JSONL ledger. Validates whether
|
|
5
|
+
# the PostToolUse payload includes actual usage counts (Q1 from issue #40).
|
|
6
|
+
#
|
|
7
|
+
# Hook contract:
|
|
8
|
+
# - Always exits 0. Recording failure must never block the session.
|
|
9
|
+
# - Skips silently when governor.enabled is false.
|
|
10
|
+
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
15
|
+
|
|
16
|
+
_ECOSYSTEM_ROOT="${ONLOOKER_ECOSYSTEM_ROOT:-}"
|
|
17
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
18
|
+
_candidate="$(cd "${PLUGIN_ROOT}/../.." 2>/dev/null && pwd)"
|
|
19
|
+
if [[ -f "${_candidate}/scripts/lib/validate-path.sh" ]]; then
|
|
20
|
+
_ECOSYSTEM_ROOT="$_candidate"
|
|
21
|
+
fi
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
25
|
+
# shellcheck disable=SC1091
|
|
26
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
27
|
+
# shellcheck disable=SC1091
|
|
28
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
32
|
+
|
|
33
|
+
# shellcheck source=../lib/governor-config.sh
|
|
34
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
35
|
+
# shellcheck source=../lib/governor-events.sh
|
|
36
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-events.sh"
|
|
37
|
+
# shellcheck source=../lib/governor-estimate.sh
|
|
38
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-estimate.sh"
|
|
39
|
+
# shellcheck source=../lib/governor-ledger.sh
|
|
40
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-ledger.sh"
|
|
41
|
+
|
|
42
|
+
_done() { exit 0; }
|
|
43
|
+
|
|
44
|
+
INPUT=$(cat)
|
|
45
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
|
|
46
|
+
[[ -z "$SESSION_ID" ]] && SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
|
|
47
|
+
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
|
|
48
|
+
|
|
49
|
+
governor_config_load "$CWD"
|
|
50
|
+
|
|
51
|
+
if ! governor_config_enabled; then
|
|
52
|
+
_done
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# -----------------------------------------------------------------------
|
|
56
|
+
# Extract hook fields.
|
|
57
|
+
# -----------------------------------------------------------------------
|
|
58
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) || TOOL_NAME=""
|
|
59
|
+
TOOL_INPUT=$(printf '%s' "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null) || TOOL_INPUT="{}"
|
|
60
|
+
TOOL_RESPONSE=$(printf '%s' "$INPUT" | jq -c '.tool_response // {}' 2>/dev/null) || TOOL_RESPONSE="{}"
|
|
61
|
+
DURATION_MS=$(printf '%s' "$INPUT" | jq -r '.duration_ms // 0' 2>/dev/null) || DURATION_MS=0
|
|
62
|
+
|
|
63
|
+
# Check if actual usage counts are present (Q1 validation).
|
|
64
|
+
ACTUAL_INPUT_TOKENS=$(printf '%s' "$TOOL_RESPONSE" \
|
|
65
|
+
| jq -r '.usage.input_tokens // .usage.input_tokens_total // empty' 2>/dev/null) \
|
|
66
|
+
|| ACTUAL_INPUT_TOKENS=""
|
|
67
|
+
ACTUAL_OUTPUT_TOKENS=$(printf '%s' "$TOOL_RESPONSE" \
|
|
68
|
+
| jq -r '.usage.output_tokens // .usage.output_tokens_total // empty' 2>/dev/null) \
|
|
69
|
+
|| ACTUAL_OUTPUT_TOKENS=""
|
|
70
|
+
|
|
71
|
+
# Estimate tokens from the input we sent.
|
|
72
|
+
ESTIMATED_TOKENS=$(governor_estimate_tokens "$TOOL_INPUT")
|
|
73
|
+
ESTIMATED_COST=$(governor_estimate_cost "$ESTIMATED_TOKENS")
|
|
74
|
+
ESTIMATION_METHOD=$(governor_estimate_method)
|
|
75
|
+
|
|
76
|
+
# Build the completion ledger record.
|
|
77
|
+
# estimated_tokens is negated to cancel the reservation written by PreToolUse.
|
|
78
|
+
# actual_tokens (when present) complete the two-phase accounting so the running
|
|
79
|
+
# total converges to real spend: N_est + (-N_est) + N_act = N_act.
|
|
80
|
+
AGENT_TYPE="${TOOL_NAME:-Task}"
|
|
81
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null) || TS="1970-01-01T00:00:00Z"
|
|
82
|
+
NEG_ESTIMATED=$(( -ESTIMATED_TOKENS ))
|
|
83
|
+
|
|
84
|
+
RECORD=$(jq -n \
|
|
85
|
+
--arg ts "$TS" \
|
|
86
|
+
--arg sid "$SESSION_ID" \
|
|
87
|
+
--arg aid "${CLAUDE_SESSION_ID:-unknown}" \
|
|
88
|
+
--arg at "$AGENT_TYPE" \
|
|
89
|
+
--argjson est "$NEG_ESTIMATED" \
|
|
90
|
+
--argjson cost "$ESTIMATED_COST" \
|
|
91
|
+
--argjson dur "$DURATION_MS" \
|
|
92
|
+
'{
|
|
93
|
+
ts: $ts,
|
|
94
|
+
session_id: $sid,
|
|
95
|
+
agent_id: $aid,
|
|
96
|
+
agent_type: $at,
|
|
97
|
+
estimated_tokens: $est,
|
|
98
|
+
cost_usd_estimated: $cost,
|
|
99
|
+
duration_ms: $dur
|
|
100
|
+
}' 2>/dev/null) || RECORD="{}"
|
|
101
|
+
|
|
102
|
+
# Compute actual total once; used for both the ledger record and the event payload.
|
|
103
|
+
ACTUAL_TOTAL=""
|
|
104
|
+
if [[ -n "$ACTUAL_INPUT_TOKENS" && -n "$ACTUAL_OUTPUT_TOKENS" ]]; then
|
|
105
|
+
ACTUAL_TOTAL=$(( ACTUAL_INPUT_TOKENS + ACTUAL_OUTPUT_TOKENS ))
|
|
106
|
+
RECORD=$(printf '%s' "$RECORD" | jq \
|
|
107
|
+
--argjson actual "$ACTUAL_TOTAL" \
|
|
108
|
+
'. + {actual_tokens: $actual}' 2>/dev/null) || true
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
governor_ledger_append "$SESSION_ID" "$RECORD" || true
|
|
112
|
+
|
|
113
|
+
# Build the governor.call.recorded payload.
|
|
114
|
+
CALL_PAYLOAD=$(jq -n \
|
|
115
|
+
--arg sid "$SESSION_ID" \
|
|
116
|
+
--arg aid "${CLAUDE_SESSION_ID:-unknown}" \
|
|
117
|
+
--arg at "$AGENT_TYPE" \
|
|
118
|
+
--argjson est "$ESTIMATED_TOKENS" \
|
|
119
|
+
--argjson cost "$ESTIMATED_COST" \
|
|
120
|
+
--argjson dur "$DURATION_MS" \
|
|
121
|
+
'{
|
|
122
|
+
session_id: $sid,
|
|
123
|
+
agent_id: $aid,
|
|
124
|
+
agent_type: $at,
|
|
125
|
+
estimated_tokens: $est,
|
|
126
|
+
cost_usd_estimated: $cost,
|
|
127
|
+
duration_ms: $dur
|
|
128
|
+
}' 2>/dev/null) || CALL_PAYLOAD="{}"
|
|
129
|
+
|
|
130
|
+
if [[ -n "$ACTUAL_TOTAL" ]]; then
|
|
131
|
+
ESTIMATION_ERROR=""
|
|
132
|
+
if (( ACTUAL_TOTAL > 0 )); then
|
|
133
|
+
ESTIMATION_ERROR=$(awk \
|
|
134
|
+
"BEGIN { printf \"%.2f\", (($ESTIMATED_TOKENS - $ACTUAL_TOTAL) / $ACTUAL_TOTAL) * 100 }" \
|
|
135
|
+
2>/dev/null) || ESTIMATION_ERROR=""
|
|
136
|
+
fi
|
|
137
|
+
CALL_PAYLOAD=$(printf '%s' "$CALL_PAYLOAD" | jq \
|
|
138
|
+
--argjson actual "$ACTUAL_TOTAL" \
|
|
139
|
+
--arg err "${ESTIMATION_ERROR:-}" \
|
|
140
|
+
'. + {actual_tokens: $actual, tokens_returned_to_pool: 0}
|
|
141
|
+
+ (if $err != "" then {estimation_error_pct: ($err | tonumber)} else {} end)' \
|
|
142
|
+
2>/dev/null) || true
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
governor_emit_event "governor.call.recorded" "$CALL_PAYLOAD" || true
|
|
146
|
+
|
|
147
|
+
_done
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Governor PreToolUse hook (matcher: Task).
|
|
3
|
+
#
|
|
4
|
+
# Gates Task spawns before they exceed the session budget. Uses
|
|
5
|
+
# portable-lock.sh for an atomic check-and-reserve so concurrent spawns
|
|
6
|
+
# cannot both pass a budget check simultaneously.
|
|
7
|
+
#
|
|
8
|
+
# Decision logic:
|
|
9
|
+
# - Estimate tokens for the spawn.
|
|
10
|
+
# - Read current consumed tokens from the JSONL ledger.
|
|
11
|
+
# - Allow if (consumed + estimated) <= budget_tokens.
|
|
12
|
+
# - Emit governor.gate.checked with decision and reason.
|
|
13
|
+
# - In "soft" enforcement: always allow, only emit the event.
|
|
14
|
+
# - In "hard" enforcement: block by returning {"decision": "block"} on
|
|
15
|
+
# stdout with exit 0 (Claude Code PreToolUse block protocol).
|
|
16
|
+
#
|
|
17
|
+
# Hook contract:
|
|
18
|
+
# - Exit 0 always.
|
|
19
|
+
# - To block: write {"decision": "block", "reason": "..."} to stdout.
|
|
20
|
+
# - To allow: write nothing (or {"decision": "allow"}) to stdout.
|
|
21
|
+
# - Skips silently when governor.enabled is false.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
27
|
+
|
|
28
|
+
_ECOSYSTEM_ROOT="${ONLOOKER_ECOSYSTEM_ROOT:-}"
|
|
29
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
30
|
+
_candidate="$(cd "${PLUGIN_ROOT}/../.." 2>/dev/null && pwd)"
|
|
31
|
+
if [[ -f "${_candidate}/scripts/lib/validate-path.sh" ]]; then
|
|
32
|
+
_ECOSYSTEM_ROOT="$_candidate"
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
37
|
+
# shellcheck disable=SC1091
|
|
38
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
39
|
+
# shellcheck disable=SC1091
|
|
40
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
44
|
+
|
|
45
|
+
# shellcheck source=../lib/governor-config.sh
|
|
46
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
47
|
+
# shellcheck source=../lib/governor-events.sh
|
|
48
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-events.sh"
|
|
49
|
+
# shellcheck source=../lib/governor-estimate.sh
|
|
50
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-estimate.sh"
|
|
51
|
+
# shellcheck source=../lib/governor-ledger.sh
|
|
52
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-ledger.sh"
|
|
53
|
+
|
|
54
|
+
_allow() { exit 0; }
|
|
55
|
+
|
|
56
|
+
_block() {
|
|
57
|
+
local reason="${1:-budget_exceeded}"
|
|
58
|
+
printf '{"decision":"block","reason":"%s"}\n' "$reason"
|
|
59
|
+
exit 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
INPUT=$(cat)
|
|
63
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
|
|
64
|
+
[[ -z "$SESSION_ID" ]] && SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
|
|
65
|
+
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
|
|
66
|
+
|
|
67
|
+
governor_config_load "$CWD"
|
|
68
|
+
|
|
69
|
+
if ! governor_config_enabled; then
|
|
70
|
+
_allow
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# -----------------------------------------------------------------------
|
|
74
|
+
# Read config.
|
|
75
|
+
# -----------------------------------------------------------------------
|
|
76
|
+
ENFORCEMENT=$(governor_config_enforcement)
|
|
77
|
+
TOKENS_BUDGET=$(governor_config_get '.governor.session.tokens_default')
|
|
78
|
+
TOKENS_BUDGET="${TOKENS_BUDGET:-100000}"
|
|
79
|
+
SAFETY_MARGIN=$(governor_config_get '.governor.estimation.safety_margin')
|
|
80
|
+
SAFETY_MARGIN="${SAFETY_MARGIN:-1.3}"
|
|
81
|
+
HARD_STOP_MARGIN=$(governor_config_get '.governor.estimation.hard_stop_margin')
|
|
82
|
+
HARD_STOP_MARGIN="${HARD_STOP_MARGIN:-1.5}"
|
|
83
|
+
|
|
84
|
+
# Respect env-var budget overrides set by orchestrating agents.
|
|
85
|
+
if [[ -n "${ONLOOKER_SESSION_BUDGET_TOKENS:-}" ]]; then
|
|
86
|
+
TOKENS_BUDGET="$ONLOOKER_SESSION_BUDGET_TOKENS"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
TOOL_INPUT=$(printf '%s' "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null) || TOOL_INPUT="{}"
|
|
90
|
+
AGENT_TYPE=$(printf '%s' "$INPUT" | jq -r '.tool_name // "Task"' 2>/dev/null) || AGENT_TYPE="Task"
|
|
91
|
+
|
|
92
|
+
# -----------------------------------------------------------------------
|
|
93
|
+
# Estimate tokens for this spawn.
|
|
94
|
+
# -----------------------------------------------------------------------
|
|
95
|
+
ESTIMATED_TOKENS=$(governor_estimate_tokens "$TOOL_INPUT" "$SAFETY_MARGIN")
|
|
96
|
+
ESTIMATED_COST=$(governor_estimate_cost "$ESTIMATED_TOKENS")
|
|
97
|
+
ESTIMATION_METHOD=$(governor_estimate_method)
|
|
98
|
+
|
|
99
|
+
# -----------------------------------------------------------------------
|
|
100
|
+
# Atomic check-and-reserve via the ledger lock.
|
|
101
|
+
# -----------------------------------------------------------------------
|
|
102
|
+
LEDGER_PATH=$(governor_ledger_path "$SESSION_ID")
|
|
103
|
+
GATE_LOCK="${LEDGER_PATH}.gate.lock"
|
|
104
|
+
|
|
105
|
+
DECISION="allow"
|
|
106
|
+
REASON=""
|
|
107
|
+
TOKENS_CONSUMED=0
|
|
108
|
+
|
|
109
|
+
if lock_acquire "$GATE_LOCK" 3; then
|
|
110
|
+
TOKENS_CONSUMED=$(governor_ledger_total_tokens "$SESSION_ID")
|
|
111
|
+
PROJECTED=$(( TOKENS_CONSUMED + ESTIMATED_TOKENS ))
|
|
112
|
+
|
|
113
|
+
# Hard stop: unconditionally block when projected exceeds budget * hard_stop_margin.
|
|
114
|
+
HARD_STOP_THRESHOLD=$(awk "BEGIN { printf \"%d\", int($TOKENS_BUDGET * $HARD_STOP_MARGIN) }" 2>/dev/null) \
|
|
115
|
+
|| HARD_STOP_THRESHOLD=$(( TOKENS_BUDGET * 2 ))
|
|
116
|
+
|
|
117
|
+
if (( PROJECTED > HARD_STOP_THRESHOLD )); then
|
|
118
|
+
DECISION="block"
|
|
119
|
+
REASON="ceiling_exceeded"
|
|
120
|
+
elif (( PROJECTED > TOKENS_BUDGET )); then
|
|
121
|
+
DECISION="block"
|
|
122
|
+
REASON="budget_exceeded"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Write reservation inside the gate lock so concurrent spawns see in-flight cost.
|
|
126
|
+
if [[ "$DECISION" == "allow" ]]; then
|
|
127
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null) || TS="1970-01-01T00:00:00Z"
|
|
128
|
+
RESERVATION=$(jq -n \
|
|
129
|
+
--arg ts "$TS" \
|
|
130
|
+
--arg sid "$SESSION_ID" \
|
|
131
|
+
--arg aid "${CLAUDE_SESSION_ID:-unknown}" \
|
|
132
|
+
--arg at "$AGENT_TYPE" \
|
|
133
|
+
--argjson est "$ESTIMATED_TOKENS" \
|
|
134
|
+
--argjson cost "$ESTIMATED_COST" \
|
|
135
|
+
'{
|
|
136
|
+
ts: $ts,
|
|
137
|
+
session_id: $sid,
|
|
138
|
+
agent_id: $aid,
|
|
139
|
+
agent_type: $at,
|
|
140
|
+
estimated_tokens: $est,
|
|
141
|
+
cost_usd_estimated: $cost,
|
|
142
|
+
record_type: "reservation"
|
|
143
|
+
}' 2>/dev/null) || RESERVATION="{}"
|
|
144
|
+
governor_ledger_write_direct "$LEDGER_PATH" "$RESERVATION" || true
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
lock_release "$GATE_LOCK"
|
|
148
|
+
else
|
|
149
|
+
# Could not acquire gate lock — treat as block to be safe in hard mode.
|
|
150
|
+
DECISION="block"
|
|
151
|
+
REASON="lock_timeout"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
TOKENS_AVAILABLE=$(( TOKENS_BUDGET - TOKENS_CONSUMED ))
|
|
155
|
+
(( TOKENS_AVAILABLE < 0 )) && TOKENS_AVAILABLE=0
|
|
156
|
+
|
|
157
|
+
# -----------------------------------------------------------------------
|
|
158
|
+
# Emit governor.gate.checked.
|
|
159
|
+
# -----------------------------------------------------------------------
|
|
160
|
+
GATE_PAYLOAD=$(jq -n \
|
|
161
|
+
--arg sid "$SESSION_ID" \
|
|
162
|
+
--arg aid "${CLAUDE_SESSION_ID:-unknown}" \
|
|
163
|
+
--arg at "$AGENT_TYPE" \
|
|
164
|
+
--arg dec "$DECISION" \
|
|
165
|
+
--argjson est "$ESTIMATED_TOKENS" \
|
|
166
|
+
--argjson avail "$TOKENS_AVAILABLE" \
|
|
167
|
+
--arg method "$ESTIMATION_METHOD" \
|
|
168
|
+
--argjson margin "$SAFETY_MARGIN" \
|
|
169
|
+
'{
|
|
170
|
+
session_id: $sid,
|
|
171
|
+
agent_id: $aid,
|
|
172
|
+
agent_type: $at,
|
|
173
|
+
decision: $dec,
|
|
174
|
+
estimated_tokens: $est,
|
|
175
|
+
tokens_available: $avail,
|
|
176
|
+
estimation_method: $method,
|
|
177
|
+
safety_margin: $margin
|
|
178
|
+
}' 2>/dev/null) || GATE_PAYLOAD="{}"
|
|
179
|
+
|
|
180
|
+
if [[ -n "$REASON" ]]; then
|
|
181
|
+
GATE_PAYLOAD=$(printf '%s' "$GATE_PAYLOAD" \
|
|
182
|
+
| jq --arg r "$REASON" '. + {reason: $r}' 2>/dev/null) \
|
|
183
|
+
|| true
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
governor_emit_event "governor.gate.checked" "$GATE_PAYLOAD" || true
|
|
187
|
+
|
|
188
|
+
# -----------------------------------------------------------------------
|
|
189
|
+
# Enforce decision.
|
|
190
|
+
# -----------------------------------------------------------------------
|
|
191
|
+
# ceiling_exceeded always blocks regardless of enforcement mode.
|
|
192
|
+
# budget_exceeded and lock_timeout only block in hard enforcement mode.
|
|
193
|
+
if [[ "$DECISION" == "block" ]]; then
|
|
194
|
+
if [[ "$REASON" == "ceiling_exceeded" || "$ENFORCEMENT" == "hard" ]]; then
|
|
195
|
+
_block "${REASON:-budget_exceeded}"
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
_allow
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Governor SessionStart hook.
|
|
3
|
+
#
|
|
4
|
+
# Fires at every session start. Responsibilities:
|
|
5
|
+
# 1. Skip silently when governor.enabled is false.
|
|
6
|
+
# 2. Create governance storage directories.
|
|
7
|
+
# 3. Sweep stale lock files left by crashed prior sessions.
|
|
8
|
+
# 4. Check global-policy.yaml exists (warn if missing, don't block).
|
|
9
|
+
# 5. Emit governor.lock.stale_cleared for each stale lock removed.
|
|
10
|
+
#
|
|
11
|
+
# Hook contract:
|
|
12
|
+
# - Always exits 0. Never blocks SessionStart.
|
|
13
|
+
# - Errors are written to stderr only; stdout is kept clean.
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
19
|
+
|
|
20
|
+
_ECOSYSTEM_ROOT="${ONLOOKER_ECOSYSTEM_ROOT:-}"
|
|
21
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
22
|
+
_candidate="$(cd "${PLUGIN_ROOT}/../.." 2>/dev/null && pwd)"
|
|
23
|
+
if [[ -f "${_candidate}/scripts/lib/validate-path.sh" ]]; then
|
|
24
|
+
_ECOSYSTEM_ROOT="$_candidate"
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
29
|
+
# shellcheck disable=SC1091
|
|
30
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
31
|
+
# shellcheck disable=SC1091
|
|
32
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
36
|
+
|
|
37
|
+
# shellcheck source=../lib/governor-config.sh
|
|
38
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
39
|
+
# shellcheck source=../lib/governor-events.sh
|
|
40
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-events.sh"
|
|
41
|
+
|
|
42
|
+
INPUT=$(cat)
|
|
43
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
|
|
44
|
+
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
|
|
45
|
+
|
|
46
|
+
_done() { exit 0; }
|
|
47
|
+
|
|
48
|
+
governor_config_load "$CWD"
|
|
49
|
+
|
|
50
|
+
if ! governor_config_enabled; then
|
|
51
|
+
_done
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# -----------------------------------------------------------------------
|
|
55
|
+
# 1. Ensure storage directories exist.
|
|
56
|
+
# -----------------------------------------------------------------------
|
|
57
|
+
ONLOOKER_DIR="${ONLOOKER_DIR:-${HOME}/.onlooker}"
|
|
58
|
+
GOVERNANCE_DIR="${ONLOOKER_DIR}/governance"
|
|
59
|
+
LEDGER_DIR="${GOVERNANCE_DIR}/ledgers"
|
|
60
|
+
mkdir -p "$LEDGER_DIR" 2>/dev/null || true
|
|
61
|
+
|
|
62
|
+
# -----------------------------------------------------------------------
|
|
63
|
+
# 2. Sweep stale lock directories.
|
|
64
|
+
# A lock dir is "stale" if it is older than 60 seconds.
|
|
65
|
+
# We only clean .lock.d directories under the ledgers directory.
|
|
66
|
+
# -----------------------------------------------------------------------
|
|
67
|
+
STALE_AGE=60
|
|
68
|
+
|
|
69
|
+
if [[ -d "$LEDGER_DIR" ]]; then
|
|
70
|
+
while IFS= read -r -d '' lockdir; do
|
|
71
|
+
[[ -d "$lockdir" ]] || continue
|
|
72
|
+
|
|
73
|
+
lock_age=0
|
|
74
|
+
if command -v stat >/dev/null 2>&1; then
|
|
75
|
+
# macOS stat: -f %m; GNU stat: -c %Y
|
|
76
|
+
mtime=$(stat -f '%m' "$lockdir" 2>/dev/null) \
|
|
77
|
+
|| mtime=$(stat -c '%Y' "$lockdir" 2>/dev/null) \
|
|
78
|
+
|| mtime=0
|
|
79
|
+
now=$(date +%s 2>/dev/null) || now=0
|
|
80
|
+
lock_age=$(( now - mtime ))
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if (( lock_age >= STALE_AGE )); then
|
|
84
|
+
rmdir "$lockdir" 2>/dev/null || true
|
|
85
|
+
cleared_payload=$(jq -n \
|
|
86
|
+
--arg lp "$lockdir" \
|
|
87
|
+
--argjson age "$lock_age" \
|
|
88
|
+
'{
|
|
89
|
+
lock_path: $lp,
|
|
90
|
+
lock_age_seconds: $age,
|
|
91
|
+
pid_verified_dead: false
|
|
92
|
+
}' 2>/dev/null) || cleared_payload="{}"
|
|
93
|
+
governor_emit_event "governor.lock.stale_cleared" "$cleared_payload" || true
|
|
94
|
+
fi
|
|
95
|
+
done < <(find "$LEDGER_DIR" -maxdepth 2 -name '*.lock.d' -print0 2>/dev/null)
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# -----------------------------------------------------------------------
|
|
99
|
+
# 3. Global policy file check (advisory only).
|
|
100
|
+
# -----------------------------------------------------------------------
|
|
101
|
+
POLICY_PATH=$(governor_config_get '.governor.global_policy_path')
|
|
102
|
+
POLICY_PATH="${POLICY_PATH/#\~/$HOME}"
|
|
103
|
+
|
|
104
|
+
if [[ -n "$POLICY_PATH" && ! -f "$POLICY_PATH" ]]; then
|
|
105
|
+
printf 'governor: global-policy.yaml not found at %s — running without global ceiling\n' \
|
|
106
|
+
"$POLICY_PATH" >&2
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
_done
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Governor Stop hook.
|
|
3
|
+
#
|
|
4
|
+
# Fires at session end. Emits governor.session.complete with cumulative
|
|
5
|
+
# spend totals from the JSONL ledger.
|
|
6
|
+
#
|
|
7
|
+
# Hook contract:
|
|
8
|
+
# - Always exits 0. Never blocks Stop.
|
|
9
|
+
# - Skips silently when governor.enabled is false.
|
|
10
|
+
# - Errors from ledger reads are swallowed; emits best-effort totals.
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
_ECOSYSTEM_ROOT="${ONLOOKER_ECOSYSTEM_ROOT:-}"
|
|
18
|
+
if [[ -z "$_ECOSYSTEM_ROOT" ]]; then
|
|
19
|
+
_candidate="$(cd "${PLUGIN_ROOT}/../.." 2>/dev/null && pwd)"
|
|
20
|
+
if [[ -f "${_candidate}/scripts/lib/validate-path.sh" ]]; then
|
|
21
|
+
_ECOSYSTEM_ROOT="$_candidate"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ -n "$_ECOSYSTEM_ROOT" && -f "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh" ]]; then
|
|
26
|
+
# shellcheck disable=SC1091
|
|
27
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/validate-path.sh"
|
|
28
|
+
# shellcheck disable=SC1091
|
|
29
|
+
CLAUDE_PLUGIN_ROOT="$_ECOSYSTEM_ROOT" source "${_ECOSYSTEM_ROOT}/scripts/lib/portable-lock.sh"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
33
|
+
|
|
34
|
+
# shellcheck source=../lib/governor-config.sh
|
|
35
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-config.sh"
|
|
36
|
+
# shellcheck source=../lib/governor-events.sh
|
|
37
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-events.sh"
|
|
38
|
+
# shellcheck source=../lib/governor-ledger.sh
|
|
39
|
+
source "${PLUGIN_ROOT}/scripts/lib/governor-ledger.sh"
|
|
40
|
+
|
|
41
|
+
_done() { exit 0; }
|
|
42
|
+
|
|
43
|
+
INPUT=$(cat)
|
|
44
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
|
|
45
|
+
[[ -z "$SESSION_ID" ]] && SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
|
|
46
|
+
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
|
|
47
|
+
|
|
48
|
+
governor_config_load "$CWD"
|
|
49
|
+
|
|
50
|
+
if ! governor_config_enabled; then
|
|
51
|
+
_done
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# -----------------------------------------------------------------------
|
|
55
|
+
# Read session totals from the ledger.
|
|
56
|
+
# -----------------------------------------------------------------------
|
|
57
|
+
TOTAL_TOKENS=$(governor_ledger_total_tokens "$SESSION_ID")
|
|
58
|
+
TOTAL_COST=$(governor_ledger_total_cost "$SESSION_ID")
|
|
59
|
+
TOTAL_CALLS=$(governor_ledger_call_count "$SESSION_ID")
|
|
60
|
+
LEDGER_POISONED=$(governor_ledger_is_poisoned "$SESSION_ID" && printf 'true' || printf 'false')
|
|
61
|
+
|
|
62
|
+
TOKENS_BUDGET=$(governor_config_get '.governor.session.tokens_default')
|
|
63
|
+
TOKENS_BUDGET="${TOKENS_BUDGET:-100000}"
|
|
64
|
+
COST_BUDGET=$(governor_config_get '.governor.session.cost_usd_default')
|
|
65
|
+
COST_BUDGET="${COST_BUDGET:-1.0}"
|
|
66
|
+
|
|
67
|
+
if [[ -n "${ONLOOKER_SESSION_BUDGET_TOKENS:-}" ]]; then
|
|
68
|
+
TOKENS_BUDGET="$ONLOOKER_SESSION_BUDGET_TOKENS"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
UNDER_BUDGET="true"
|
|
72
|
+
TOTAL_TOKENS_INT=$(printf '%s' "${TOTAL_TOKENS:-0}" | grep -oE '^[0-9]+' || printf '0')
|
|
73
|
+
TOKENS_BUDGET_INT=$(printf '%s' "${TOKENS_BUDGET:-0}" | grep -oE '^[0-9]+' || printf '0')
|
|
74
|
+
(( TOTAL_TOKENS_INT > TOKENS_BUDGET_INT )) && UNDER_BUDGET="false"
|
|
75
|
+
|
|
76
|
+
# Also check the cost dimension (float comparison via awk).
|
|
77
|
+
if [[ "$UNDER_BUDGET" == "true" ]]; then
|
|
78
|
+
COST_OVER=$(awk "BEGIN { print (${TOTAL_COST:-0} > ${COST_BUDGET:-1.0}) ? 1 : 0 }" 2>/dev/null) || COST_OVER=0
|
|
79
|
+
[[ "$COST_OVER" == "1" ]] && UNDER_BUDGET="false"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
SESSION_PAYLOAD=$(jq -n \
|
|
83
|
+
--argjson total_cost "${TOTAL_COST:-0}" \
|
|
84
|
+
--argjson budget_usd "${COST_BUDGET:-1.0}" \
|
|
85
|
+
--argjson under "$( [[ "$UNDER_BUDGET" == "true" ]] && printf 'true' || printf 'false')" \
|
|
86
|
+
--arg sid "$SESSION_ID" \
|
|
87
|
+
--argjson total_tokens "${TOTAL_TOKENS_INT:-0}" \
|
|
88
|
+
--argjson total_calls "${TOTAL_CALLS:-0}" \
|
|
89
|
+
--argjson dur 0 \
|
|
90
|
+
--argjson calls_blocked 0 \
|
|
91
|
+
--argjson calls_warned 0 \
|
|
92
|
+
--argjson poisoned "$( [[ "$LEDGER_POISONED" == "true" ]] && printf 'true' || printf 'false')" \
|
|
93
|
+
'{
|
|
94
|
+
total_cost_usd: $total_cost,
|
|
95
|
+
budget_usd: $budget_usd,
|
|
96
|
+
under_budget: $under,
|
|
97
|
+
session_id: $sid,
|
|
98
|
+
total_tokens: $total_tokens,
|
|
99
|
+
total_api_calls: $total_calls,
|
|
100
|
+
duration_ms: $dur,
|
|
101
|
+
calls_blocked: $calls_blocked,
|
|
102
|
+
calls_warned: $calls_warned,
|
|
103
|
+
ledger_poisoned: $poisoned
|
|
104
|
+
}' 2>/dev/null) || SESSION_PAYLOAD="{}"
|
|
105
|
+
|
|
106
|
+
governor_emit_event "governor.session.complete" "$SESSION_PAYLOAD" || true
|
|
107
|
+
|
|
108
|
+
_done
|