talon-agent 1.35.0 → 1.37.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/package.json +3 -3
- package/src/backend/claude-sdk/handler.ts +6 -2
- package/src/backend/claude-sdk/options.ts +41 -1
- package/src/backend/codex/handler/events.ts +1 -1
- package/src/backend/codex/handler/message.ts +1 -0
- package/src/backend/kilo/handler/message.ts +1 -0
- package/src/backend/kilo/handler/turn.ts +1 -0
- package/src/backend/openai-agents/handler/events.ts +1 -1
- package/src/backend/openai-agents/handler/message.ts +5 -1
- package/src/backend/opencode/handler/message.ts +1 -0
- package/src/backend/opencode/handler/turn.ts +1 -0
- package/src/backend/remote-server/events.ts +3 -1
- package/src/backend/shared/metrics.ts +26 -51
- package/src/bootstrap.ts +1 -0
- package/src/core/engine/gateway-actions/index.ts +2 -0
- package/src/core/engine/gateway-actions/mesh.ts +27 -0
- package/src/core/engine/gateway-actions/native.ts +607 -0
- package/src/core/mcp-hub/index.ts +3 -0
- package/src/core/mcp-hub/talon-server.ts +3 -0
- package/src/core/mesh/persist.ts +49 -0
- package/src/core/mesh/registry.ts +10 -18
- package/src/core/mesh/service.ts +754 -42
- package/src/core/mesh/teleport.ts +158 -0
- package/src/core/mesh/transfers.ts +186 -0
- package/src/core/tools/bridge.ts +85 -4
- package/src/core/tools/index.ts +23 -2
- package/src/core/tools/mesh.ts +83 -1
- package/src/core/tools/native.ts +138 -0
- package/src/core/tools/types.ts +2 -1
- package/src/frontend/discord/commands/admin.ts +9 -2
- package/src/frontend/discord/helpers.ts +7 -6
- package/src/frontend/native/chats.ts +2 -2
- package/src/frontend/native/index.ts +2 -0
- package/src/frontend/native/server.ts +40 -1
- package/src/frontend/telegram/commands/admin.ts +10 -2
- package/src/frontend/telegram/helpers/diagnostics.ts +7 -6
- package/src/storage/db.ts +6 -1
- package/src/storage/repositories/sessions-repo.ts +20 -1
- package/src/storage/sessions.ts +348 -4
- package/src/storage/sql/db.sql +5 -0
- package/src/storage/sql/schema.sql +2 -1
- package/src/storage/sql/sessions.sql +3 -3
- package/src/storage/sql/statements.generated.ts +8 -4
- package/src/util/config.ts +10 -0
- package/src/util/exec-output.ts +64 -0
- package/src/util/metrics.ts +148 -60
package/src/util/metrics.ts
CHANGED
|
@@ -1,80 +1,168 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Session-backed metrics aggregation. Writes for chat turns live on
|
|
2
|
+
// storage/sessions.ts; this module keeps the public read shape used by
|
|
3
|
+
// /metrics and a small compatibility sink for non-chat legacy counters.
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
import {
|
|
6
|
+
getAllSessions,
|
|
7
|
+
resetAllSessionMetrics,
|
|
8
|
+
todayUtc,
|
|
9
|
+
type MetricsGrain,
|
|
10
|
+
type MetricsLatencyAgg,
|
|
11
|
+
} from "../storage/sessions.js";
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
interface RingBuffer {
|
|
8
|
-
data: number[];
|
|
9
|
-
head: number; // next write position
|
|
10
|
-
size: number; // current number of valid entries (≤ capacity)
|
|
11
|
-
}
|
|
13
|
+
const legacyCounters = new Map<string, number>();
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
export type MetricsSnapshot = {
|
|
16
|
+
counters: Record<string, number>;
|
|
17
|
+
histograms: Record<
|
|
18
|
+
string,
|
|
19
|
+
{ count: number; avg: number; min: number; max: number }
|
|
20
|
+
>;
|
|
21
|
+
};
|
|
15
22
|
|
|
16
|
-
function
|
|
17
|
-
|
|
23
|
+
export function incrementCounter(name: string, amount = 1): void {
|
|
24
|
+
legacyCounters.set(name, (legacyCounters.get(name) ?? 0) + amount);
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (buf.size < buf.data.length) buf.size++;
|
|
27
|
+
export function recordHistogram(_name: string, _value: number): void {
|
|
28
|
+
// The old global histogram ring is intentionally gone. Chat-turn
|
|
29
|
+
// histograms are now latency aggregates on SessionMetrics.
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
function addCounter(
|
|
33
|
+
counters: Record<string, number>,
|
|
34
|
+
name: string,
|
|
35
|
+
amount: number | undefined,
|
|
36
|
+
): void {
|
|
37
|
+
if (typeof amount === "number" && Number.isFinite(amount) && amount !== 0) {
|
|
38
|
+
counters[name] = (counters[name] ?? 0) + amount;
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
function mergeAgg(target: MetricsLatencyAgg, source: MetricsLatencyAgg): void {
|
|
43
|
+
if (!source.count) return;
|
|
44
|
+
target.count += source.count;
|
|
45
|
+
target.sumMs += source.sumMs;
|
|
46
|
+
target.minMs = Math.min(target.minMs, source.minMs);
|
|
47
|
+
target.maxMs = Math.max(target.maxMs, source.maxMs);
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
let buf = histograms.get(name);
|
|
41
|
-
if (!buf) {
|
|
42
|
-
if (histograms.size >= MAX_METRIC_KEYS) return; // cap key count
|
|
43
|
-
buf = createRingBuffer(MAX_HISTOGRAM_SIZE);
|
|
44
|
-
histograms.set(name, buf);
|
|
45
|
-
}
|
|
46
|
-
ringPush(buf, value);
|
|
50
|
+
function emptyAgg(): MetricsLatencyAgg {
|
|
51
|
+
return { count: 0, sumMs: 0, minMs: Infinity, maxMs: 0 };
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} {
|
|
56
|
-
const result: ReturnType<typeof getMetrics> = {
|
|
57
|
-
counters: {},
|
|
58
|
-
histograms: {},
|
|
54
|
+
function snapshotAgg(agg: MetricsLatencyAgg) {
|
|
55
|
+
return {
|
|
56
|
+
count: agg.count,
|
|
57
|
+
avg: Math.round(agg.sumMs / agg.count),
|
|
58
|
+
min: agg.minMs,
|
|
59
|
+
max: agg.maxMs,
|
|
59
60
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildSnapshot(
|
|
64
|
+
grains: MetricsGrain[],
|
|
65
|
+
counters: Record<string, number>,
|
|
66
|
+
): MetricsSnapshot {
|
|
67
|
+
const responseLatency = emptyAgg();
|
|
68
|
+
const toolCallsPerTurn = emptyAgg();
|
|
69
|
+
const apiCallsPerTurn = emptyAgg();
|
|
70
|
+
const cacheHitPercent = emptyAgg();
|
|
71
|
+
const backendLatency = new Map<string, MetricsLatencyAgg>();
|
|
72
|
+
|
|
73
|
+
for (const grain of grains) {
|
|
74
|
+
const c = grain.counters;
|
|
75
|
+
addCounter(counters, "queries_total", c.queries);
|
|
76
|
+
addCounter(counters, "turns_with_tools_total", c.turnsWithTools);
|
|
77
|
+
addCounter(counters, "api_calls_total", c.apiCalls);
|
|
78
|
+
addCounter(counters, "tokens.input_total", c.inputTokens);
|
|
79
|
+
addCounter(counters, "tokens.output_total", c.outputTokens);
|
|
80
|
+
addCounter(counters, "tokens.cache_read_total", c.cacheReadTokens);
|
|
81
|
+
addCounter(counters, "tokens.cache_write_total", c.cacheWriteTokens);
|
|
82
|
+
addCounter(
|
|
83
|
+
counters,
|
|
84
|
+
"scratchpad.trailing_text_dropped",
|
|
85
|
+
c.trailingTextDropped,
|
|
86
|
+
);
|
|
87
|
+
addCounter(
|
|
88
|
+
counters,
|
|
89
|
+
"scratchpad.flow_violation_retried",
|
|
90
|
+
c.flowViolationRetries,
|
|
91
|
+
);
|
|
92
|
+
addCounter(
|
|
93
|
+
counters,
|
|
94
|
+
"scratchpad.flow_violation_cap_exhausted",
|
|
95
|
+
c.flowViolationCapExhausted,
|
|
96
|
+
);
|
|
97
|
+
for (const [name, count] of Object.entries(grain.toolCallsByName)) {
|
|
98
|
+
addCounter(counters, `tool_calls.${name}`, count);
|
|
99
|
+
}
|
|
100
|
+
for (const [backend, bc] of Object.entries(grain.backend)) {
|
|
101
|
+
addCounter(counters, `backend.${backend}.queries`, bc.queries);
|
|
102
|
+
addCounter(counters, `backend.${backend}.tool_calls`, bc.toolCalls);
|
|
103
|
+
addCounter(counters, `backend.${backend}.turn_failed`, bc.failedTurns);
|
|
104
|
+
addCounter(counters, `backend.${backend}.tokens.input`, bc.inputTokens);
|
|
105
|
+
addCounter(counters, `backend.${backend}.tokens.output`, bc.outputTokens);
|
|
106
|
+
addCounter(
|
|
107
|
+
counters,
|
|
108
|
+
`backend.${backend}.tokens.cache_read`,
|
|
109
|
+
bc.cacheReadTokens,
|
|
110
|
+
);
|
|
111
|
+
addCounter(
|
|
112
|
+
counters,
|
|
113
|
+
`backend.${backend}.tokens.cache_write`,
|
|
114
|
+
bc.cacheWriteTokens,
|
|
115
|
+
);
|
|
116
|
+
const agg = backendLatency.get(backend) ?? emptyAgg();
|
|
117
|
+
mergeAgg(agg, bc.latency);
|
|
118
|
+
backendLatency.set(backend, agg);
|
|
119
|
+
}
|
|
120
|
+
mergeAgg(responseLatency, grain.latency);
|
|
121
|
+
mergeAgg(toolCallsPerTurn, grain.toolCallsPerTurn);
|
|
122
|
+
mergeAgg(apiCallsPerTurn, grain.apiCallsPerTurn);
|
|
123
|
+
mergeAgg(cacheHitPercent, grain.cacheHitPercent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const histograms: MetricsSnapshot["histograms"] = {};
|
|
127
|
+
if (responseLatency.count)
|
|
128
|
+
histograms.response_latency_ms = snapshotAgg(responseLatency);
|
|
129
|
+
if (toolCallsPerTurn.count)
|
|
130
|
+
histograms.tool_calls_per_turn = snapshotAgg(toolCallsPerTurn);
|
|
131
|
+
if (apiCallsPerTurn.count)
|
|
132
|
+
histograms.api_calls_per_turn = snapshotAgg(apiCallsPerTurn);
|
|
133
|
+
if (cacheHitPercent.count)
|
|
134
|
+
histograms.cache_hit_percent = snapshotAgg(cacheHitPercent);
|
|
135
|
+
for (const [backend, agg] of backendLatency) {
|
|
136
|
+
if (agg.count)
|
|
137
|
+
histograms[`backend.${backend}.response_latency_ms`] = snapshotAgg(agg);
|
|
73
138
|
}
|
|
74
|
-
|
|
139
|
+
|
|
140
|
+
return { counters, histograms };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Lifetime fleet snapshot: all sessions' cumulative metrics plus the
|
|
144
|
+
* in-process legacy counters. */
|
|
145
|
+
export function getMetrics(): MetricsSnapshot {
|
|
146
|
+
const counters: Record<string, number> = {};
|
|
147
|
+
for (const [key, value] of legacyCounters) addCounter(counters, key, value);
|
|
148
|
+
return buildSnapshot(
|
|
149
|
+
getAllSessions().map(({ info }) => info.metrics.lifetime),
|
|
150
|
+
counters,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Today's (UTC) fleet snapshot, aggregated from the sessions' daily
|
|
155
|
+
* rollup buckets. Legacy counters are process-lifetime, not daily, so
|
|
156
|
+
* they are excluded here. */
|
|
157
|
+
export function getTodayMetrics(): MetricsSnapshot {
|
|
158
|
+
const day = todayUtc();
|
|
159
|
+
return buildSnapshot(
|
|
160
|
+
getAllSessions().flatMap(({ info }) => info.metrics.buckets[day] ?? []),
|
|
161
|
+
{},
|
|
162
|
+
);
|
|
75
163
|
}
|
|
76
164
|
|
|
77
165
|
export function resetMetrics(): void {
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
legacyCounters.clear();
|
|
167
|
+
resetAllSessionMetrics();
|
|
80
168
|
}
|