talon-agent 3.0.5 → 3.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.
- package/package.json +6 -1
- package/src/backend/kilo/models/index.ts +4 -1
- package/src/backend/kilo/server.ts +17 -4
- package/src/backend/openai-agents/session.ts +1 -1
- package/src/backend/opencode/models/index.ts +4 -1
- package/src/backend/opencode/server.ts +17 -4
- package/src/backend/shared/system-prompt.ts +5 -0
- package/src/cli/events.ts +1 -1
- package/src/cli/tasks.ts +5 -1
- package/src/core/background/triggers/pid.ts +28 -0
- package/src/core/background/triggers/resume.ts +4 -24
- package/src/core/background/triggers/spawn.ts +1 -1
- package/src/core/engine/gateway-actions/plugins.ts +3 -3
- package/src/core/prompt/invalidation.ts +23 -0
- package/src/core/types.ts +2 -2
- package/src/frontend/discord/callbacks/components.ts +1 -1
- package/src/frontend/discord/commands/settings.ts +1 -1
- package/src/frontend/native/extensions.ts +2 -2
- package/src/frontend/telegram/callbacks/model.ts +1 -1
- package/src/frontend/telegram/commands/settings.ts +1 -1
- package/src/frontend/terminal/commands.ts +1 -1
- package/src/plugins/github/index.ts +1 -1
- package/src/plugins/mem0/index.ts +1 -1
- package/src/plugins/mempalace/index.ts +1 -1
- package/src/plugins/playwright/index.ts +1 -1
- package/src/storage/chat-settings.ts +3 -60
- package/src/storage/cron-store.ts +6 -80
- package/src/storage/goal-store.ts +10 -19
- package/src/storage/history.ts +2 -13
- package/src/storage/journal.ts +20 -9
- package/src/storage/media-index.ts +2 -22
- package/src/storage/repositories/chat-settings-repo.ts +55 -1
- package/src/storage/repositories/cron-repo.ts +82 -6
- package/src/storage/repositories/goals-repo.ts +20 -1
- package/src/storage/repositories/history-repo.ts +14 -1
- package/src/storage/repositories/media-index-repo.ts +23 -1
- package/src/storage/repositories/scripts-repo.ts +16 -1
- package/src/storage/repositories/sessions-repo.ts +1 -1
- package/src/storage/repositories/triggers-repo.ts +60 -5
- package/src/storage/script-store.ts +2 -15
- package/src/storage/session-record.ts +220 -0
- package/src/storage/sessions.ts +22 -210
- package/src/storage/trigger-store.ts +6 -60
- package/src/types/assets.d.ts +9 -0
- package/src/types/effort.ts +12 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session record shapes + their constructors and normalisers — the
|
|
3
|
+
* vocabulary both the public store (storage/sessions.ts) and the
|
|
4
|
+
* repository (repositories/sessions-repo.ts) speak, extracted so the
|
|
5
|
+
* repo never has to import the store (which imports the repo).
|
|
6
|
+
*
|
|
7
|
+
* Mutation logic (fold/add/bucket) stays in the store; this module owns
|
|
8
|
+
* only "what a well-formed record looks like" and "how a raw JSON blob
|
|
9
|
+
* becomes one".
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type SessionUsage = {
|
|
13
|
+
totalInputTokens: number;
|
|
14
|
+
totalOutputTokens: number;
|
|
15
|
+
totalCacheRead: number;
|
|
16
|
+
totalCacheWrite: number;
|
|
17
|
+
/** Last turn's total prompt tokens (cumulative across all API calls in the turn, including tool-use loops). */
|
|
18
|
+
lastPromptTokens: number;
|
|
19
|
+
/** Actual context window fill from the last API call (last iteration's prompt tokens). */
|
|
20
|
+
contextTokens: number;
|
|
21
|
+
/** Model's context window size in tokens (from SDK modelUsage). */
|
|
22
|
+
contextWindow: number;
|
|
23
|
+
/** Number of API round-trips in the last turn (tool-use steps). */
|
|
24
|
+
numApiCalls: number;
|
|
25
|
+
/** Estimated cost in USD. */
|
|
26
|
+
estimatedCostUsd: number;
|
|
27
|
+
/** Total response time in ms (for averaging). */
|
|
28
|
+
totalResponseMs: number;
|
|
29
|
+
/** Last response time in ms. */
|
|
30
|
+
lastResponseMs: number;
|
|
31
|
+
/** Fastest response time in ms. */
|
|
32
|
+
fastestResponseMs: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type MetricsCounterSet = {
|
|
36
|
+
queries: number;
|
|
37
|
+
toolCalls: number;
|
|
38
|
+
turnsWithTools: number;
|
|
39
|
+
apiCalls: number;
|
|
40
|
+
inputTokens: number;
|
|
41
|
+
outputTokens: number;
|
|
42
|
+
cacheReadTokens: number;
|
|
43
|
+
cacheWriteTokens: number;
|
|
44
|
+
failedTurns: number;
|
|
45
|
+
flowViolationRetries: number;
|
|
46
|
+
flowViolationCapExhausted: number;
|
|
47
|
+
trailingTextDropped: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type MetricsLatencyAgg = {
|
|
51
|
+
count: number;
|
|
52
|
+
sumMs: number;
|
|
53
|
+
minMs: number;
|
|
54
|
+
maxMs: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type MetricsGrain = {
|
|
58
|
+
counters: MetricsCounterSet;
|
|
59
|
+
latency: MetricsLatencyAgg;
|
|
60
|
+
toolCallsByName: Record<string, number>;
|
|
61
|
+
backend: Record<string, MetricsCounterSet & { latency: MetricsLatencyAgg }>;
|
|
62
|
+
cacheHitPercent: MetricsLatencyAgg;
|
|
63
|
+
toolCallsPerTurn: MetricsLatencyAgg;
|
|
64
|
+
apiCallsPerTurn: MetricsLatencyAgg;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type SessionMetrics = {
|
|
68
|
+
lifetime: MetricsGrain;
|
|
69
|
+
buckets: Record<string, MetricsGrain>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type SessionState = {
|
|
73
|
+
/** Backend server-side session ID. */
|
|
74
|
+
sessionId: string | undefined;
|
|
75
|
+
/** Turn count. */
|
|
76
|
+
turns: number;
|
|
77
|
+
/** Last activity timestamp. */
|
|
78
|
+
lastActive: number;
|
|
79
|
+
/** Created timestamp. */
|
|
80
|
+
createdAt: number;
|
|
81
|
+
/** Cumulative usage stats. */
|
|
82
|
+
usage: SessionUsage;
|
|
83
|
+
/** Persistent per-session metrics, bucketed by UTC day. */
|
|
84
|
+
metrics: SessionMetrics;
|
|
85
|
+
/** ID of the last message sent by the bot in this chat. */
|
|
86
|
+
lastBotMessageId?: number;
|
|
87
|
+
/** Descriptive session name derived from first message. */
|
|
88
|
+
sessionName?: string;
|
|
89
|
+
/** Model used for this session's cost tracking. */
|
|
90
|
+
lastModel?: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const emptyUsage = (): SessionUsage => ({
|
|
94
|
+
totalInputTokens: 0,
|
|
95
|
+
totalOutputTokens: 0,
|
|
96
|
+
totalCacheRead: 0,
|
|
97
|
+
totalCacheWrite: 0,
|
|
98
|
+
lastPromptTokens: 0,
|
|
99
|
+
contextTokens: 0,
|
|
100
|
+
contextWindow: 0,
|
|
101
|
+
numApiCalls: 0,
|
|
102
|
+
estimatedCostUsd: 0,
|
|
103
|
+
totalResponseMs: 0,
|
|
104
|
+
lastResponseMs: 0,
|
|
105
|
+
fastestResponseMs: Infinity,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export const emptyCounters = (): MetricsCounterSet => ({
|
|
109
|
+
queries: 0,
|
|
110
|
+
toolCalls: 0,
|
|
111
|
+
turnsWithTools: 0,
|
|
112
|
+
apiCalls: 0,
|
|
113
|
+
inputTokens: 0,
|
|
114
|
+
outputTokens: 0,
|
|
115
|
+
cacheReadTokens: 0,
|
|
116
|
+
cacheWriteTokens: 0,
|
|
117
|
+
failedTurns: 0,
|
|
118
|
+
flowViolationRetries: 0,
|
|
119
|
+
flowViolationCapExhausted: 0,
|
|
120
|
+
trailingTextDropped: 0,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const emptyLatency = (): MetricsLatencyAgg => ({
|
|
124
|
+
count: 0,
|
|
125
|
+
sumMs: 0,
|
|
126
|
+
minMs: Infinity,
|
|
127
|
+
maxMs: 0,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const emptyGrain = (): MetricsGrain => ({
|
|
131
|
+
counters: emptyCounters(),
|
|
132
|
+
latency: emptyLatency(),
|
|
133
|
+
toolCallsByName: {},
|
|
134
|
+
backend: {},
|
|
135
|
+
cacheHitPercent: emptyLatency(),
|
|
136
|
+
toolCallsPerTurn: emptyLatency(),
|
|
137
|
+
apiCallsPerTurn: emptyLatency(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export const emptyMetrics = (): SessionMetrics => ({
|
|
141
|
+
lifetime: emptyGrain(),
|
|
142
|
+
buckets: {},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
function normaliseLatency(raw: unknown): MetricsLatencyAgg {
|
|
146
|
+
const r = raw as Partial<MetricsLatencyAgg> | undefined;
|
|
147
|
+
const count =
|
|
148
|
+
typeof r?.count === "number" && Number.isFinite(r.count) ? r.count : 0;
|
|
149
|
+
return {
|
|
150
|
+
count,
|
|
151
|
+
sumMs:
|
|
152
|
+
typeof r?.sumMs === "number" && Number.isFinite(r.sumMs) ? r.sumMs : 0,
|
|
153
|
+
minMs:
|
|
154
|
+
typeof r?.minMs === "number" && Number.isFinite(r.minMs)
|
|
155
|
+
? r.minMs
|
|
156
|
+
: count > 0
|
|
157
|
+
? 0
|
|
158
|
+
: Infinity,
|
|
159
|
+
maxMs:
|
|
160
|
+
typeof r?.maxMs === "number" && Number.isFinite(r.maxMs) ? r.maxMs : 0,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normaliseCounters(raw: unknown): MetricsCounterSet {
|
|
165
|
+
const base = emptyCounters();
|
|
166
|
+
const r = raw as Partial<MetricsCounterSet> | undefined;
|
|
167
|
+
for (const key of Object.keys(base) as Array<keyof MetricsCounterSet>) {
|
|
168
|
+
const value = r?.[key];
|
|
169
|
+
if (typeof value === "number" && Number.isFinite(value)) base[key] = value;
|
|
170
|
+
}
|
|
171
|
+
return base;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normaliseGrain(raw: unknown): MetricsGrain {
|
|
175
|
+
const r = raw as Partial<MetricsGrain> | undefined;
|
|
176
|
+
const grain = emptyGrain();
|
|
177
|
+
grain.counters = normaliseCounters(r?.counters);
|
|
178
|
+
grain.latency = normaliseLatency(r?.latency);
|
|
179
|
+
grain.cacheHitPercent = normaliseLatency(r?.cacheHitPercent);
|
|
180
|
+
grain.toolCallsPerTurn = normaliseLatency(r?.toolCallsPerTurn);
|
|
181
|
+
grain.apiCallsPerTurn = normaliseLatency(r?.apiCallsPerTurn);
|
|
182
|
+
if (r?.toolCallsByName && typeof r.toolCallsByName === "object") {
|
|
183
|
+
for (const [key, value] of Object.entries(r.toolCallsByName)) {
|
|
184
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
185
|
+
grain.toolCallsByName[key] = value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (r?.backend && typeof r.backend === "object") {
|
|
190
|
+
for (const [key, value] of Object.entries(r.backend)) {
|
|
191
|
+
grain.backend[key] = {
|
|
192
|
+
...normaliseCounters(value),
|
|
193
|
+
latency: normaliseLatency((value as { latency?: unknown }).latency),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return grain;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Coerce an untrusted/persisted metrics blob into a well-formed
|
|
202
|
+
* SessionMetrics: missing fields backfilled, non-finite numbers dropped,
|
|
203
|
+
* JSON's `minMs: null` (Infinity doesn't survive JSON.stringify) restored
|
|
204
|
+
* to the domain sentinel. Runs once at the load boundary (sessions-repo);
|
|
205
|
+
* in-memory metrics stay well-formed by construction after that.
|
|
206
|
+
*/
|
|
207
|
+
export function normaliseMetrics(metrics: unknown): SessionMetrics {
|
|
208
|
+
const raw = metrics as Partial<SessionMetrics> | undefined;
|
|
209
|
+
const result: SessionMetrics = {
|
|
210
|
+
lifetime: normaliseGrain(raw?.lifetime),
|
|
211
|
+
buckets: {},
|
|
212
|
+
};
|
|
213
|
+
if (raw?.buckets && typeof raw.buckets === "object") {
|
|
214
|
+
for (const [day, grain] of Object.entries(raw.buckets)) {
|
|
215
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(day))
|
|
216
|
+
result.buckets[day] = normaliseGrain(grain);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
package/src/storage/sessions.ts
CHANGED
|
@@ -24,87 +24,28 @@ import { files } from "../util/paths.js";
|
|
|
24
24
|
import { importLegacyJson } from "./legacy-import.js";
|
|
25
25
|
import * as repo from "./repositories/sessions-repo.js";
|
|
26
26
|
|
|
27
|
-
export type
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type MetricsCounterSet = {
|
|
51
|
-
queries: number;
|
|
52
|
-
toolCalls: number;
|
|
53
|
-
turnsWithTools: number;
|
|
54
|
-
apiCalls: number;
|
|
55
|
-
inputTokens: number;
|
|
56
|
-
outputTokens: number;
|
|
57
|
-
cacheReadTokens: number;
|
|
58
|
-
cacheWriteTokens: number;
|
|
59
|
-
failedTurns: number;
|
|
60
|
-
flowViolationRetries: number;
|
|
61
|
-
flowViolationCapExhausted: number;
|
|
62
|
-
trailingTextDropped: number;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export type MetricsLatencyAgg = {
|
|
66
|
-
count: number;
|
|
67
|
-
sumMs: number;
|
|
68
|
-
minMs: number;
|
|
69
|
-
maxMs: number;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export type MetricsGrain = {
|
|
73
|
-
counters: MetricsCounterSet;
|
|
74
|
-
latency: MetricsLatencyAgg;
|
|
75
|
-
toolCallsByName: Record<string, number>;
|
|
76
|
-
backend: Record<string, MetricsCounterSet & { latency: MetricsLatencyAgg }>;
|
|
77
|
-
cacheHitPercent: MetricsLatencyAgg;
|
|
78
|
-
toolCallsPerTurn: MetricsLatencyAgg;
|
|
79
|
-
apiCallsPerTurn: MetricsLatencyAgg;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export type SessionMetrics = {
|
|
83
|
-
lifetime: MetricsGrain;
|
|
84
|
-
buckets: Record<string, MetricsGrain>;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export type SessionState = {
|
|
88
|
-
/** Backend server-side session ID. */
|
|
89
|
-
sessionId: string | undefined;
|
|
90
|
-
/** Turn count. */
|
|
91
|
-
turns: number;
|
|
92
|
-
/** Last activity timestamp. */
|
|
93
|
-
lastActive: number;
|
|
94
|
-
/** Created timestamp. */
|
|
95
|
-
createdAt: number;
|
|
96
|
-
/** Cumulative usage stats. */
|
|
97
|
-
usage: SessionUsage;
|
|
98
|
-
/** Persistent per-session metrics, bucketed by UTC day. */
|
|
99
|
-
metrics: SessionMetrics;
|
|
100
|
-
/** ID of the last message sent by the bot in this chat. */
|
|
101
|
-
lastBotMessageId?: number;
|
|
102
|
-
/** Descriptive session name derived from first message. */
|
|
103
|
-
sessionName?: string;
|
|
104
|
-
/** Model used for this session's cost tracking. */
|
|
105
|
-
lastModel?: string;
|
|
106
|
-
};
|
|
107
|
-
|
|
27
|
+
export type {
|
|
28
|
+
MetricsCounterSet,
|
|
29
|
+
MetricsGrain,
|
|
30
|
+
MetricsLatencyAgg,
|
|
31
|
+
SessionMetrics,
|
|
32
|
+
SessionState,
|
|
33
|
+
SessionUsage,
|
|
34
|
+
} from "./session-record.js";
|
|
35
|
+
export { emptyMetrics, normaliseMetrics } from "./session-record.js";
|
|
36
|
+
import {
|
|
37
|
+
emptyCounters,
|
|
38
|
+
emptyGrain,
|
|
39
|
+
emptyLatency,
|
|
40
|
+
emptyMetrics,
|
|
41
|
+
emptyUsage,
|
|
42
|
+
type MetricsCounterSet,
|
|
43
|
+
type MetricsGrain,
|
|
44
|
+
type MetricsLatencyAgg,
|
|
45
|
+
type SessionMetrics,
|
|
46
|
+
type SessionState,
|
|
47
|
+
type SessionUsage,
|
|
48
|
+
} from "./session-record.js";
|
|
108
49
|
// In-memory cache over the sessions table. Reads serve live references
|
|
109
50
|
// from here; writes go through persist() so each mutation commits.
|
|
110
51
|
const cache = new Map<string, SessionState>();
|
|
@@ -273,135 +214,6 @@ function persist(chatId: string, session: SessionState): void {
|
|
|
273
214
|
|
|
274
215
|
// ── Core operations ─────────────────────────────────────────────────────────
|
|
275
216
|
|
|
276
|
-
const emptyUsage = (): SessionUsage => ({
|
|
277
|
-
totalInputTokens: 0,
|
|
278
|
-
totalOutputTokens: 0,
|
|
279
|
-
totalCacheRead: 0,
|
|
280
|
-
totalCacheWrite: 0,
|
|
281
|
-
lastPromptTokens: 0,
|
|
282
|
-
contextTokens: 0,
|
|
283
|
-
contextWindow: 0,
|
|
284
|
-
numApiCalls: 0,
|
|
285
|
-
estimatedCostUsd: 0,
|
|
286
|
-
totalResponseMs: 0,
|
|
287
|
-
lastResponseMs: 0,
|
|
288
|
-
fastestResponseMs: Infinity,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const emptyCounters = (): MetricsCounterSet => ({
|
|
292
|
-
queries: 0,
|
|
293
|
-
toolCalls: 0,
|
|
294
|
-
turnsWithTools: 0,
|
|
295
|
-
apiCalls: 0,
|
|
296
|
-
inputTokens: 0,
|
|
297
|
-
outputTokens: 0,
|
|
298
|
-
cacheReadTokens: 0,
|
|
299
|
-
cacheWriteTokens: 0,
|
|
300
|
-
failedTurns: 0,
|
|
301
|
-
flowViolationRetries: 0,
|
|
302
|
-
flowViolationCapExhausted: 0,
|
|
303
|
-
trailingTextDropped: 0,
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const emptyLatency = (): MetricsLatencyAgg => ({
|
|
307
|
-
count: 0,
|
|
308
|
-
sumMs: 0,
|
|
309
|
-
minMs: Infinity,
|
|
310
|
-
maxMs: 0,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const emptyGrain = (): MetricsGrain => ({
|
|
314
|
-
counters: emptyCounters(),
|
|
315
|
-
latency: emptyLatency(),
|
|
316
|
-
toolCallsByName: {},
|
|
317
|
-
backend: {},
|
|
318
|
-
cacheHitPercent: emptyLatency(),
|
|
319
|
-
toolCallsPerTurn: emptyLatency(),
|
|
320
|
-
apiCallsPerTurn: emptyLatency(),
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
export const emptyMetrics = (): SessionMetrics => ({
|
|
324
|
-
lifetime: emptyGrain(),
|
|
325
|
-
buckets: {},
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
function normaliseLatency(raw: unknown): MetricsLatencyAgg {
|
|
329
|
-
const r = raw as Partial<MetricsLatencyAgg> | undefined;
|
|
330
|
-
const count =
|
|
331
|
-
typeof r?.count === "number" && Number.isFinite(r.count) ? r.count : 0;
|
|
332
|
-
return {
|
|
333
|
-
count,
|
|
334
|
-
sumMs:
|
|
335
|
-
typeof r?.sumMs === "number" && Number.isFinite(r.sumMs) ? r.sumMs : 0,
|
|
336
|
-
minMs:
|
|
337
|
-
typeof r?.minMs === "number" && Number.isFinite(r.minMs)
|
|
338
|
-
? r.minMs
|
|
339
|
-
: count > 0
|
|
340
|
-
? 0
|
|
341
|
-
: Infinity,
|
|
342
|
-
maxMs:
|
|
343
|
-
typeof r?.maxMs === "number" && Number.isFinite(r.maxMs) ? r.maxMs : 0,
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function normaliseCounters(raw: unknown): MetricsCounterSet {
|
|
348
|
-
const base = emptyCounters();
|
|
349
|
-
const r = raw as Partial<MetricsCounterSet> | undefined;
|
|
350
|
-
for (const key of Object.keys(base) as Array<keyof MetricsCounterSet>) {
|
|
351
|
-
const value = r?.[key];
|
|
352
|
-
if (typeof value === "number" && Number.isFinite(value)) base[key] = value;
|
|
353
|
-
}
|
|
354
|
-
return base;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function normaliseGrain(raw: unknown): MetricsGrain {
|
|
358
|
-
const r = raw as Partial<MetricsGrain> | undefined;
|
|
359
|
-
const grain = emptyGrain();
|
|
360
|
-
grain.counters = normaliseCounters(r?.counters);
|
|
361
|
-
grain.latency = normaliseLatency(r?.latency);
|
|
362
|
-
grain.cacheHitPercent = normaliseLatency(r?.cacheHitPercent);
|
|
363
|
-
grain.toolCallsPerTurn = normaliseLatency(r?.toolCallsPerTurn);
|
|
364
|
-
grain.apiCallsPerTurn = normaliseLatency(r?.apiCallsPerTurn);
|
|
365
|
-
if (r?.toolCallsByName && typeof r.toolCallsByName === "object") {
|
|
366
|
-
for (const [key, value] of Object.entries(r.toolCallsByName)) {
|
|
367
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
368
|
-
grain.toolCallsByName[key] = value;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (r?.backend && typeof r.backend === "object") {
|
|
373
|
-
for (const [key, value] of Object.entries(r.backend)) {
|
|
374
|
-
grain.backend[key] = {
|
|
375
|
-
...normaliseCounters(value),
|
|
376
|
-
latency: normaliseLatency((value as { latency?: unknown }).latency),
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return grain;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Coerce an untrusted/persisted metrics blob into a well-formed
|
|
385
|
-
* SessionMetrics: missing fields backfilled, non-finite numbers dropped,
|
|
386
|
-
* JSON's `minMs: null` (Infinity doesn't survive JSON.stringify) restored
|
|
387
|
-
* to the domain sentinel. Runs once at the load boundary (sessions-repo);
|
|
388
|
-
* in-memory metrics stay well-formed by construction after that.
|
|
389
|
-
*/
|
|
390
|
-
export function normaliseMetrics(metrics: unknown): SessionMetrics {
|
|
391
|
-
const raw = metrics as Partial<SessionMetrics> | undefined;
|
|
392
|
-
const result: SessionMetrics = {
|
|
393
|
-
lifetime: normaliseGrain(raw?.lifetime),
|
|
394
|
-
buckets: {},
|
|
395
|
-
};
|
|
396
|
-
if (raw?.buckets && typeof raw.buckets === "object") {
|
|
397
|
-
for (const [day, grain] of Object.entries(raw.buckets)) {
|
|
398
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(day))
|
|
399
|
-
result.buckets[day] = normaliseGrain(grain);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return result;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
217
|
function addLatency(agg: MetricsLatencyAgg, value: number): void {
|
|
406
218
|
if (!Number.isFinite(value)) return;
|
|
407
219
|
agg.count += 1;
|
|
@@ -27,66 +27,12 @@ import * as repo from "./repositories/triggers-repo.js";
|
|
|
27
27
|
|
|
28
28
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
30
|
-
export type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
| "errored" // exited non-zero — fired error wake message
|
|
37
|
-
| "cancelled" // killed by user (trigger_cancel)
|
|
38
|
-
| "timed_out" // killed by hard timeout
|
|
39
|
-
| "terminated"; // killed by Talon shutdown / restart
|
|
40
|
-
|
|
41
|
-
export type Trigger = {
|
|
42
|
-
id: string;
|
|
43
|
-
chatId: string;
|
|
44
|
-
numericChatId: number;
|
|
45
|
-
name: string;
|
|
46
|
-
language: TriggerLanguage;
|
|
47
|
-
/** Absolute path to the script body on disk. */
|
|
48
|
-
scriptPath: string;
|
|
49
|
-
/** Absolute path to the run log (interleaved stdout+stderr). */
|
|
50
|
-
logPath: string;
|
|
51
|
-
description?: string;
|
|
52
|
-
status: TriggerStatus;
|
|
53
|
-
createdAt: number;
|
|
54
|
-
startedAt?: number;
|
|
55
|
-
endedAt?: number;
|
|
56
|
-
/** PID of the child process while running (cleared on exit). */
|
|
57
|
-
pid?: number;
|
|
58
|
-
/** Linux /proc/<pid>/stat field 22 (start time in jiffies) captured at
|
|
59
|
-
* spawn. Used by killOrphan to defend against PID reuse — start time is
|
|
60
|
-
* monotonic per boot and unchanged by exec(), so a match guarantees the
|
|
61
|
-
* current owner of the PID is the same process we spawned. Undefined on
|
|
62
|
-
* non-Linux platforms. */
|
|
63
|
-
pidStarttime?: number;
|
|
64
|
-
/** Hard timeout in seconds. Default 24h, max 7d. */
|
|
65
|
-
timeoutSeconds: number;
|
|
66
|
-
/** Exit code on terminal status. */
|
|
67
|
-
exitCode?: number;
|
|
68
|
-
/** Total wake-ups fired for this trigger — sum of mid-run TALON_FIRE: lines
|
|
69
|
-
* plus the terminal exit fire. Incremented every time fireWake() runs. */
|
|
70
|
-
fireCount: number;
|
|
71
|
-
lastFireAt?: number;
|
|
72
|
-
/** Truncated tail of the most recent fire payload (for diagnostics). */
|
|
73
|
-
lastFirePayload?: string;
|
|
74
|
-
lastError?: string;
|
|
75
|
-
/** If true, the trigger is respawned on Talon startup if it was still
|
|
76
|
-
* active when Talon went down. Triggers in any terminal state
|
|
77
|
-
* (fired/errored/cancelled) are NOT respawned — only ones interrupted
|
|
78
|
-
* by Talon shutdown or crash. (Persistent triggers have no hard timeout,
|
|
79
|
-
* so timed_out is unreachable for them — see spawnTrigger.) */
|
|
80
|
-
persistent?: boolean;
|
|
81
|
-
/**
|
|
82
|
-
* Optional model override for the wake-up turn — a model id valid on the
|
|
83
|
-
* chat's own backend. Unset = inherit the chat's model (preferred). When set,
|
|
84
|
-
* the fired wake-up runs on this (typically cheaper) model instead, while
|
|
85
|
-
* still resuming the chat session (restricted to the same backend so
|
|
86
|
-
* continuity is preserved).
|
|
87
|
-
*/
|
|
88
|
-
model?: string;
|
|
89
|
-
};
|
|
30
|
+
export type {
|
|
31
|
+
Trigger,
|
|
32
|
+
TriggerLanguage,
|
|
33
|
+
TriggerStatus,
|
|
34
|
+
} from "./repositories/triggers-repo.js";
|
|
35
|
+
import type { Trigger, TriggerLanguage } from "./repositories/triggers-repo.js";
|
|
90
36
|
|
|
91
37
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
92
38
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Ambient declaration for prompt file-asset imports. The Bun-only
|
|
2
|
+
// `import x from "./foo.md" with { type: "file" }` form (used only by the
|
|
3
|
+
// generated embedded-prompts.ts, reached through the `bun` condition of
|
|
4
|
+
// the `#prompt-assets` package import) resolves to the embedded file path
|
|
5
|
+
// as a string. tsx/node never load that module — they use disk-prompts.ts.
|
|
6
|
+
declare module "*.md" {
|
|
7
|
+
const path: string;
|
|
8
|
+
export default path;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reasoning-effort vocabulary — a dependency-free primitive shared by
|
|
3
|
+
* every layer (storage persists it, core resolves it, backends map it
|
|
4
|
+
* to provider knobs, frontends render pickers over it).
|
|
5
|
+
*
|
|
6
|
+
* Lives in src/types/ (a leaf: imports nothing from src/) so the layers
|
|
7
|
+
* below core/ can name the type without importing upward. core/types.ts
|
|
8
|
+
* re-exports it for the engine-side importers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type ReasoningEffortLevel =
|
|
12
|
+
"off" | "minimal" | "low" | "medium" | "high" | "max" | "xhigh";
|
package/tsconfig.json
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
"moduleResolution": "bundler",
|
|
6
6
|
"resolveJsonModule": true,
|
|
7
7
|
"strict": true,
|
|
8
|
+
// Beyond-strict gates. Two more are measured but not yet on — the fix
|
|
9
|
+
// waves are their own PRs: noUncheckedIndexedAccess (666 errors),
|
|
10
|
+
// exactOptionalPropertyTypes (303 errors).
|
|
11
|
+
"noImplicitOverride": true,
|
|
12
|
+
"noFallthroughCasesInSwitch": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
8
14
|
"esModuleInterop": true,
|
|
9
15
|
"skipLibCheck": true,
|
|
10
16
|
"outDir": "dist",
|