podwatch 1.0.2 → 1.1.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Config monitor — snapshots and diffs the gateway config for drift/tampering detection.
3
+ *
4
+ * Monitors security-relevant config fields and emits config_change events
5
+ * when drift is detected. Called on startup, every pulse, and gateway_start.
6
+ *
7
+ * Replaces the single-field model tracking that was in lifecycle.ts.
8
+ */
9
+ export interface MonitoredPath {
10
+ /** Dot-separated path into api.config (e.g. "agents.defaults.model") */
11
+ path: string;
12
+ /** Human-readable label used as prefix in change events */
13
+ label: string;
14
+ /** Keys to redact from snapshots (e.g. apiKey) — prevents secrets in events */
15
+ redactKeys?: string[];
16
+ }
17
+ export declare const MONITORED_PATHS: MonitoredPath[];
18
+ /**
19
+ * Get a nested value from an object by dot-separated path.
20
+ */
21
+ export declare function deepGet(obj: unknown, path: string): unknown;
22
+ /**
23
+ * Deep clone a value, replacing values at sensitive keys with "***".
24
+ */
25
+ export declare function deepClone(value: unknown, redactKeys?: string[]): unknown;
26
+ /**
27
+ * Deep equality check for two values.
28
+ */
29
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
30
+ export interface ConfigChange {
31
+ field: string;
32
+ oldValue: unknown;
33
+ newValue: unknown;
34
+ }
35
+ /**
36
+ * Diff two values recursively. Returns a list of changes with dot-path field names.
37
+ * For arrays, reports the whole array as changed (no element-level diff).
38
+ */
39
+ export declare function diffValues(oldVal: unknown, newVal: unknown, prefix: string): ConfigChange[];
40
+ /**
41
+ * Extract monitored config sections from the full gateway config.
42
+ * Redacts sensitive keys (apiKey, etc.) so they don't appear in events.
43
+ */
44
+ export declare function extractMonitoredConfig(config: Record<string, unknown>, paths?: MonitoredPath[]): Record<string, unknown>;
45
+ /**
46
+ * Initialize the config snapshot. Called once on register().
47
+ * Does NOT emit events (baseline capture).
48
+ */
49
+ export declare function initSnapshot(config: Record<string, unknown>): void;
50
+ /**
51
+ * Check for config changes against the stored snapshot.
52
+ * Emits config_change events for each changed field, then updates snapshot.
53
+ *
54
+ * If no snapshot exists yet, initializes one (no events emitted).
55
+ */
56
+ export declare function checkConfigChanges(config: Record<string, unknown>): ConfigChange[];
57
+ /**
58
+ * Reset the snapshot (for testing or re-register).
59
+ */
60
+ export declare function resetSnapshot(): void;
61
+ /**
62
+ * Get the current snapshot (for testing/diagnostics).
63
+ */
64
+ export declare function getSnapshot(): Record<string, unknown> | null;
65
+ //# sourceMappingURL=config-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-monitor.d.ts","sourceRoot":"","sources":["../src/config-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EAQ1C,CAAC;AAMF;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAS3D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAcxE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAsBzD;AAMD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CA6C3F;AAMD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,GAAE,aAAa,EAAoB,GACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAQD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,EAAE,CAgClF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE5D"}
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**
3
+ * Config monitor — snapshots and diffs the gateway config for drift/tampering detection.
4
+ *
5
+ * Monitors security-relevant config fields and emits config_change events
6
+ * when drift is detected. Called on startup, every pulse, and gateway_start.
7
+ *
8
+ * Replaces the single-field model tracking that was in lifecycle.ts.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MONITORED_PATHS = void 0;
12
+ exports.deepGet = deepGet;
13
+ exports.deepClone = deepClone;
14
+ exports.deepEqual = deepEqual;
15
+ exports.diffValues = diffValues;
16
+ exports.extractMonitoredConfig = extractMonitoredConfig;
17
+ exports.initSnapshot = initSnapshot;
18
+ exports.checkConfigChanges = checkConfigChanges;
19
+ exports.resetSnapshot = resetSnapshot;
20
+ exports.getSnapshot = getSnapshot;
21
+ const transmitter_js_1 = require("./transmitter.js");
22
+ exports.MONITORED_PATHS = [
23
+ { path: "agents.defaults.model", label: "model" },
24
+ { path: "agents.defaults.models", label: "models" },
25
+ { path: "tools.exec", label: "tools.exec" },
26
+ { path: "tools.elevated", label: "tools.elevated" },
27
+ { path: "models.providers", label: "providers", redactKeys: ["apiKey", "apiSecret", "secret", "token"] },
28
+ { path: "plugins", label: "plugins" },
29
+ { path: "session", label: "session" },
30
+ ];
31
+ // ---------------------------------------------------------------------------
32
+ // Pure utility functions (exported for testing)
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Get a nested value from an object by dot-separated path.
36
+ */
37
+ function deepGet(obj, path) {
38
+ if (obj == null || typeof obj !== "object")
39
+ return undefined;
40
+ const parts = path.split(".");
41
+ let current = obj;
42
+ for (const part of parts) {
43
+ if (current == null || typeof current !== "object")
44
+ return undefined;
45
+ current = current[part];
46
+ }
47
+ return current;
48
+ }
49
+ /**
50
+ * Deep clone a value, replacing values at sensitive keys with "***".
51
+ */
52
+ function deepClone(value, redactKeys) {
53
+ if (value == null)
54
+ return value;
55
+ if (typeof value !== "object")
56
+ return value;
57
+ if (Array.isArray(value))
58
+ return value.map((v) => deepClone(v, redactKeys));
59
+ const result = {};
60
+ for (const [k, v] of Object.entries(value)) {
61
+ if (redactKeys?.includes(k) && v != null && v !== "") {
62
+ result[k] = "***";
63
+ }
64
+ else {
65
+ result[k] = deepClone(v, redactKeys);
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ /**
71
+ * Deep equality check for two values.
72
+ */
73
+ function deepEqual(a, b) {
74
+ if (a === b)
75
+ return true;
76
+ if (a == null && b == null)
77
+ return true;
78
+ if (a == null || b == null)
79
+ return false;
80
+ if (typeof a !== typeof b)
81
+ return false;
82
+ if (typeof a !== "object")
83
+ return false;
84
+ if (Array.isArray(a) !== Array.isArray(b))
85
+ return false;
86
+ if (Array.isArray(a)) {
87
+ const arrB = b;
88
+ if (a.length !== arrB.length)
89
+ return false;
90
+ return a.every((v, i) => deepEqual(v, arrB[i]));
91
+ }
92
+ const objA = a;
93
+ const objB = b;
94
+ const keysA = Object.keys(objA).sort();
95
+ const keysB = Object.keys(objB).sort();
96
+ if (keysA.length !== keysB.length)
97
+ return false;
98
+ if (!keysA.every((k, i) => k === keysB[i]))
99
+ return false;
100
+ return keysA.every((k) => deepEqual(objA[k], objB[k]));
101
+ }
102
+ /**
103
+ * Diff two values recursively. Returns a list of changes with dot-path field names.
104
+ * For arrays, reports the whole array as changed (no element-level diff).
105
+ */
106
+ function diffValues(oldVal, newVal, prefix) {
107
+ const changes = [];
108
+ // Both null/undefined — no change
109
+ if (oldVal == null && newVal == null)
110
+ return changes;
111
+ // One is null/undefined — whole path changed
112
+ if (oldVal == null || newVal == null) {
113
+ changes.push({ field: prefix, oldValue: oldVal ?? null, newValue: newVal ?? null });
114
+ return changes;
115
+ }
116
+ // Different types
117
+ if (typeof oldVal !== typeof newVal) {
118
+ changes.push({ field: prefix, oldValue: oldVal, newValue: newVal });
119
+ return changes;
120
+ }
121
+ // Primitives
122
+ if (typeof oldVal !== "object") {
123
+ if (oldVal !== newVal) {
124
+ changes.push({ field: prefix, oldValue: oldVal, newValue: newVal });
125
+ }
126
+ return changes;
127
+ }
128
+ // Arrays — report as whole if different
129
+ if (Array.isArray(oldVal) || Array.isArray(newVal)) {
130
+ if (!deepEqual(oldVal, newVal)) {
131
+ changes.push({ field: prefix, oldValue: oldVal, newValue: newVal });
132
+ }
133
+ return changes;
134
+ }
135
+ // Objects — recurse into each key
136
+ const oldRecord = oldVal;
137
+ const newRecord = newVal;
138
+ const allKeys = new Set([...Object.keys(oldRecord), ...Object.keys(newRecord)]);
139
+ for (const key of allKeys) {
140
+ const childPath = prefix ? `${prefix}.${key}` : key;
141
+ changes.push(...diffValues(oldRecord[key], newRecord[key], childPath));
142
+ }
143
+ return changes;
144
+ }
145
+ // ---------------------------------------------------------------------------
146
+ // Snapshot extraction
147
+ // ---------------------------------------------------------------------------
148
+ /**
149
+ * Extract monitored config sections from the full gateway config.
150
+ * Redacts sensitive keys (apiKey, etc.) so they don't appear in events.
151
+ */
152
+ function extractMonitoredConfig(config, paths = exports.MONITORED_PATHS) {
153
+ const result = {};
154
+ for (const { path, label, redactKeys } of paths) {
155
+ const value = deepGet(config, path);
156
+ result[label] = deepClone(value, redactKeys);
157
+ }
158
+ return result;
159
+ }
160
+ // ---------------------------------------------------------------------------
161
+ // Stateful config monitor
162
+ // ---------------------------------------------------------------------------
163
+ let snapshot = null;
164
+ /**
165
+ * Initialize the config snapshot. Called once on register().
166
+ * Does NOT emit events (baseline capture).
167
+ */
168
+ function initSnapshot(config) {
169
+ snapshot = extractMonitoredConfig(config);
170
+ }
171
+ /**
172
+ * Check for config changes against the stored snapshot.
173
+ * Emits config_change events for each changed field, then updates snapshot.
174
+ *
175
+ * If no snapshot exists yet, initializes one (no events emitted).
176
+ */
177
+ function checkConfigChanges(config) {
178
+ if (!snapshot) {
179
+ initSnapshot(config);
180
+ return [];
181
+ }
182
+ const current = extractMonitoredConfig(config);
183
+ const allChanges = [];
184
+ for (const { label } of exports.MONITORED_PATHS) {
185
+ const changes = diffValues(snapshot[label], current[label], label);
186
+ for (const change of changes) {
187
+ allChanges.push(change);
188
+ transmitter_js_1.transmitter.enqueue({
189
+ type: "config_change",
190
+ ts: Date.now(),
191
+ field: change.field,
192
+ value: change.newValue,
193
+ previousValue: change.oldValue,
194
+ params: {
195
+ field: change.field,
196
+ value: change.newValue,
197
+ previousValue: change.oldValue,
198
+ },
199
+ });
200
+ }
201
+ }
202
+ // Update snapshot after emitting
203
+ snapshot = current;
204
+ return allChanges;
205
+ }
206
+ /**
207
+ * Reset the snapshot (for testing or re-register).
208
+ */
209
+ function resetSnapshot() {
210
+ snapshot = null;
211
+ }
212
+ /**
213
+ * Get the current snapshot (for testing/diagnostics).
214
+ */
215
+ function getSnapshot() {
216
+ return snapshot ? { ...snapshot } : null;
217
+ }
218
+ //# sourceMappingURL=config-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-monitor.js","sourceRoot":"","sources":["../src/config-monitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Budget hard stop hooks — before_model_resolve and before_prompt_build.
3
+ *
4
+ * When the server signals a hard stop (budget exceeded + hard stop enabled),
5
+ * these hooks:
6
+ * 1. Try to downgrade to a cheaper model (best-effort)
7
+ * 2. Prepend context telling the LLM to inform the user about the budget limit
8
+ */
9
+ import type { PodwatchConfig } from "../index.js";
10
+ import type { PluginApi } from "../types.js";
11
+ /**
12
+ * Find the cheapest available model from the gateway config.
13
+ * Returns { model, provider } or null if only one model is configured.
14
+ */
15
+ export declare function findCheapestModel(config: Record<string, unknown>): {
16
+ model: string;
17
+ provider: string;
18
+ } | null;
19
+ export declare function registerBudgetHooks(api: PluginApi, config: PodwatchConfig): void;
20
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/hooks/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA6B7C;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0D5C;AAMD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CA6ChF"}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Budget hard stop hooks — before_model_resolve and before_prompt_build.
4
+ *
5
+ * When the server signals a hard stop (budget exceeded + hard stop enabled),
6
+ * these hooks:
7
+ * 1. Try to downgrade to a cheaper model (best-effort)
8
+ * 2. Prepend context telling the LLM to inform the user about the budget limit
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.findCheapestModel = findCheapestModel;
12
+ exports.registerBudgetHooks = registerBudgetHooks;
13
+ const transmitter_js_1 = require("../transmitter.js");
14
+ // ---------------------------------------------------------------------------
15
+ // Model cost heuristic — cheaper models first
16
+ // ---------------------------------------------------------------------------
17
+ /** Known cheap model patterns (ordered cheapest first). */
18
+ const CHEAP_MODEL_PATTERNS = [
19
+ "haiku",
20
+ "flash",
21
+ "mini",
22
+ "nano",
23
+ "lite",
24
+ "sonnet",
25
+ ];
26
+ /**
27
+ * Score a model name by cost (lower = cheaper).
28
+ * Known cheap patterns get low scores, unknown models get a high default.
29
+ */
30
+ function modelCostScore(modelName) {
31
+ const lower = modelName.toLowerCase();
32
+ for (let i = 0; i < CHEAP_MODEL_PATTERNS.length; i++) {
33
+ if (lower.includes(CHEAP_MODEL_PATTERNS[i]))
34
+ return i;
35
+ }
36
+ return 100; // unknown = expensive
37
+ }
38
+ /**
39
+ * Find the cheapest available model from the gateway config.
40
+ * Returns { model, provider } or null if only one model is configured.
41
+ */
42
+ function findCheapestModel(config) {
43
+ const candidates = [];
44
+ // Check models.providers for available models
45
+ const providers = config?.models?.providers;
46
+ if (providers && typeof providers === "object") {
47
+ for (const [providerName, providerCfg] of Object.entries(providers)) {
48
+ const cfg = providerCfg;
49
+ const models = cfg.models;
50
+ if (Array.isArray(models)) {
51
+ for (const m of models) {
52
+ if (typeof m === "string") {
53
+ candidates.push({
54
+ model: `${providerName}/${m}`,
55
+ provider: providerName,
56
+ score: modelCostScore(m),
57
+ });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ // Also check agents.defaults.models for configured model aliases
64
+ const agentModels = config?.agents?.defaults?.models;
65
+ if (agentModels && typeof agentModels === "object") {
66
+ for (const modelId of Object.keys(agentModels)) {
67
+ if (typeof modelId === "string") {
68
+ const parts = modelId.split("/");
69
+ const modelName = parts[parts.length - 1] ?? modelId;
70
+ const provider = parts.length > 1 ? parts[0] : "";
71
+ candidates.push({
72
+ model: modelId,
73
+ provider,
74
+ score: modelCostScore(modelName),
75
+ });
76
+ }
77
+ }
78
+ }
79
+ // Deduplicate by model name
80
+ const seen = new Set();
81
+ const unique = candidates.filter((c) => {
82
+ if (seen.has(c.model))
83
+ return false;
84
+ seen.add(c.model);
85
+ return true;
86
+ });
87
+ if (unique.length <= 1)
88
+ return null;
89
+ // Sort by score (cheapest first) and pick the cheapest
90
+ unique.sort((a, b) => a.score - b.score);
91
+ // Get the current primary model to avoid returning the same one
92
+ const primaryModel = config?.agents?.defaults?.model?.primary;
93
+ const cheapest = unique.find((c) => c.model !== primaryModel) ?? unique[0];
94
+ return { model: cheapest.model, provider: cheapest.provider };
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Hook registration
98
+ // ---------------------------------------------------------------------------
99
+ function registerBudgetHooks(api, config) {
100
+ // -----------------------------------------------------------------------
101
+ // before_model_resolve — downgrade to cheaper model on hard stop
102
+ // -----------------------------------------------------------------------
103
+ api.registerHook("before_model_resolve", async () => {
104
+ if (!config.enableBudgetEnforcement)
105
+ return;
106
+ const budget = transmitter_js_1.transmitter.getCachedBudget();
107
+ if (!budget?.hardStopActive)
108
+ return;
109
+ const cheaper = findCheapestModel(api.config);
110
+ if (!cheaper)
111
+ return; // only one model configured, skip
112
+ return {
113
+ modelOverride: cheaper.model,
114
+ providerOverride: cheaper.provider,
115
+ };
116
+ }, { name: "podwatch-budget-model-resolve" });
117
+ // -----------------------------------------------------------------------
118
+ // before_prompt_build — inject budget warning into prompt
119
+ // -----------------------------------------------------------------------
120
+ api.registerHook("before_prompt_build", async () => {
121
+ if (!config.enableBudgetEnforcement)
122
+ return;
123
+ const budget = transmitter_js_1.transmitter.getCachedBudget();
124
+ if (!budget?.hardStopActive)
125
+ return;
126
+ return {
127
+ prependContext: "BUDGET HARD STOP ACTIVE. Your spending limit has been reached. " +
128
+ "Reply ONLY with a brief message telling the user their budget is exceeded " +
129
+ "and to visit podwatch.app/costs to resume. Do not use any tools. Do not perform any analysis.",
130
+ };
131
+ }, { name: "podwatch-budget-prompt-build" });
132
+ api.logger.info("[podwatch/budget] Hard stop hooks registered");
133
+ }
134
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/hooks/budget.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,8 @@
9
9
  * Dedup strategy: lastSeenIndex tracks how far we've read into event.messages.
10
10
  * Each invocation only processes messages from lastSeenIndex onwards, then
11
11
  * advances the pointer. Zero memory growth, O(1) bookkeeping.
12
+ *
13
+ * Cost events are correlated per LLM turn (not per tool call) using turn_id.
12
14
  */
13
15
  import type { PodwatchConfig } from "../index.js";
14
16
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,cAAc,EACtB,kBAAkB,EAAE,OAAO,GAC1B,IAAI,CA4EN"}
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAclD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,cAAc,EACtB,kBAAkB,EAAE,OAAO,GAC1B,IAAI,CAiFN"}
@@ -10,6 +10,8 @@
10
10
  * Dedup strategy: lastSeenIndex tracks how far we've read into event.messages.
11
11
  * Each invocation only processes messages from lastSeenIndex onwards, then
12
12
  * advances the pointer. Zero memory growth, O(1) bookkeeping.
13
+ *
14
+ * Cost events are correlated per LLM turn (not per tool call) using turn_id.
13
15
  */
14
16
  Object.defineProperty(exports, "__esModule", { value: true });
15
17
  exports._resetCostState = _resetCostState;
@@ -18,6 +20,13 @@ exports.registerCostHandler = registerCostHandler;
18
20
  const transmitter_js_1 = require("../transmitter.js");
19
21
  // Track how far into event.messages we've already processed — per session
20
22
  const lastSeenIndexMap = new Map();
23
+ /**
24
+ * Generate a turn-based correlation ID for cost events.
25
+ * Cost events correlate to LLM turns, not individual tool calls.
26
+ */
27
+ function generateTurnId() {
28
+ return `turn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
29
+ }
21
30
  /**
22
31
  * Reset all dedup state (exported for testing).
23
32
  */
@@ -63,6 +72,9 @@ function registerCostHandler(api, config, diagnosticsEnabled) {
63
72
  break; // only check the last user message
64
73
  }
65
74
  }
75
+ // Generate a turn_id for this batch of cost events (per before_agent_start invocation)
76
+ // This links all cost events from the same LLM turn together
77
+ const turnId = generateTurnId();
66
78
  for (const msg of newMessages) {
67
79
  // Only assistant messages have usage data
68
80
  if (msg.role !== "assistant")
@@ -90,6 +102,7 @@ function registerCostHandler(api, config, diagnosticsEnabled) {
90
102
  costUsd: costTotal,
91
103
  costBreakdown: msg.usage.cost, // full {input, output, cacheRead, cacheWrite, total} object
92
104
  durationMs: undefined,
105
+ correlationId: turnId, // Link cost events per turn
93
106
  // Tag heartbeat-triggered cost events so the dashboard can distinguish them
94
107
  ...(isHeartbeat ? { sessionType: "heartbeat" } : {}),
95
108
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/hooks/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAwElD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAwHhF"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/hooks/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA4BlD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CA4HhF"}
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.registerLifecycleHandlers = registerLifecycleHandlers;
47
47
  const transmitter_js_1 = require("../transmitter.js");
48
48
  const scanner_js_1 = require("../scanner.js");
49
+ const config_monitor_js_1 = require("../config-monitor.js");
49
50
  const fs = __importStar(require("node:fs"));
50
51
  const path = __importStar(require("node:path"));
51
52
  const PLUGIN_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf-8")).version;
@@ -55,53 +56,7 @@ let scanTimer = null;
55
56
  let pulseFailureCount = 0;
56
57
  const PULSE_FAILURE_THRESHOLD = 3; // Start backoff after N consecutive failures
57
58
  const PULSE_MAX_INTERVAL_MS = 3_600_000; // 60 min cap
58
- // Config change detection state
59
- let knownPrimaryModel = null;
60
- /**
61
- * Read the primary model from the OpenClaw gateway config.
62
- * Handles both string and object shapes for `agents.defaults.model`.
63
- */
64
- function readPrimaryModel(api) {
65
- try {
66
- const modelCfg = api.config?.agents?.defaults?.model;
67
- if (!modelCfg)
68
- return null;
69
- if (typeof modelCfg === "string")
70
- return modelCfg;
71
- if (typeof modelCfg === "object" && modelCfg.primary)
72
- return String(modelCfg.primary);
73
- return null;
74
- }
75
- catch {
76
- return null;
77
- }
78
- }
79
- /**
80
- * Check if the primary model changed and emit a config_change event if so.
81
- */
82
- function checkModelConfigChange(api) {
83
- const currentModel = readPrimaryModel(api);
84
- if (currentModel === knownPrimaryModel)
85
- return;
86
- const previousValue = knownPrimaryModel;
87
- knownPrimaryModel = currentModel;
88
- // Don't emit on first read if null
89
- if (currentModel === null && previousValue === null)
90
- return;
91
- transmitter_js_1.transmitter.enqueue({
92
- type: "config_change",
93
- ts: Date.now(),
94
- field: "model.primary",
95
- value: currentModel,
96
- previousValue,
97
- // Pass as params so they appear in toolArgs on the server
98
- params: {
99
- field: "model.primary",
100
- value: currentModel,
101
- previousValue,
102
- },
103
- });
104
- }
59
+ // Config change detection is now handled by config-monitor.ts
105
60
  /**
106
61
  * Register lifecycle hook handlers.
107
62
  */
@@ -115,9 +70,9 @@ function registerLifecycleHandlers(api, config) {
115
70
  const basePulseIntervalMs = config.pulseIntervalMs ?? 300_000;
116
71
  // Reset pulse backoff state
117
72
  pulseFailureCount = 0;
118
- // Read initial primary model config and send config_change event
119
- knownPrimaryModel = null; // Reset on re-register
120
- checkModelConfigChange(api);
73
+ // Initialize config monitor snapshot (baseline no events emitted)
74
+ (0, config_monitor_js_1.resetSnapshot)();
75
+ (0, config_monitor_js_1.initSnapshot)(api.config ?? {});
121
76
  // Send initial pulse right now
122
77
  void sendPulseWithBackoff(endpoint, apiKey, basePulseIntervalMs, api);
123
78
  // Initial skill/plugin scan — delayed 30s to let gateway fully settle
@@ -135,6 +90,10 @@ function registerLifecycleHandlers(api, config) {
135
90
  // gateway_start — best-effort re-scan (in case it ever fires)
136
91
  // -----------------------------------------------------------------------
137
92
  api.on("gateway_start", async (event) => {
93
+ // Check config changes on gateway restart (config may have changed)
94
+ if (api.config) {
95
+ (0, config_monitor_js_1.checkConfigChanges)(api.config);
96
+ }
138
97
  // Re-run scan as best-effort; pulse is already running
139
98
  void runScan(api.config?.agents?.defaults?.workspace);
140
99
  }, { name: "podwatch-gateway-start" });
@@ -206,8 +165,8 @@ function registerLifecycleHandlers(api, config) {
206
165
  */
207
166
  async function sendPulseWithBackoff(endpoint, apiKey, baseIntervalMs, api) {
208
167
  // Check for config changes on each pulse
209
- if (api) {
210
- checkModelConfigChange(api);
168
+ if (api?.config) {
169
+ (0, config_monitor_js_1.checkConfigChanges)(api.config);
211
170
  }
212
171
  let success = false;
213
172
  try {