talon-agent 4.5.0 → 4.6.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
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Persistent Memory
|
|
2
|
+
|
|
3
|
+
The following is your memory store's core view — the durable claims that matter most: pinned items, standing directives, who you are talking to, your highest-salience facts, and current status. It is selected and budgeted, not a whole file, so anything not here is not gone. The rest of the store is one query away (`talon memory list`, or `/memory` in chat), and the full rendered projection is at ~/.talon/workspace/memory/memory.md.
|
|
4
|
+
|
|
5
|
+
Durable facts, not live status. Reference it naturally; the Memory and Recall policy in this prompt governs how you add to it and keep it current.
|
|
6
|
+
|
|
7
|
+
{{content}}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **core view** — the store's contribution to the static system
|
|
3
|
+
* prompt (docs/memory-persona-plan.md §3.4).
|
|
4
|
+
*
|
|
5
|
+
* Retrieval has two tiers, and the boundary between them is a
|
|
6
|
+
* prompt-cache invariant:
|
|
7
|
+
*
|
|
8
|
+
* - **Core view** (this module) — pinned directives, the relationship
|
|
9
|
+
* layer and the top durable facts plus fresh `state`, computed
|
|
10
|
+
* **once per session build** and living in `staticText`. Budgeted,
|
|
11
|
+
* never truncated mid-section.
|
|
12
|
+
* - **Turn retrieval** (PR 8) — keyed to the incoming message and
|
|
13
|
+
* injected into the *user turn*, never into `prepareSystemPrompt()`.
|
|
14
|
+
*
|
|
15
|
+
* Because `prepareSystemPrompt` freezes the assembled prompt per
|
|
16
|
+
* `(chatId, sessionEpoch)`, a fact learned at turn 3 cannot appear in
|
|
17
|
+
* that session's core view. That is the design, not a bug: reaching for
|
|
18
|
+
* `notifyPromptInputsChanged()` to "fix" it would force a full-prompt
|
|
19
|
+
* cache write across every live session on every learned fact (plan
|
|
20
|
+
* §3.6). Nothing here may ever invalidate a snapshot.
|
|
21
|
+
*
|
|
22
|
+
* The store is consulted exactly once per build — one `listMemories`
|
|
23
|
+
* call, ranked and filtered in memory — so the cost of the core view is
|
|
24
|
+
* one query per session, not one per kind and not one per turn.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { listMemories, type MemoryRow } from "../../storage/memory.js";
|
|
28
|
+
import { renderMemoryMarkdown } from "./render.js";
|
|
29
|
+
|
|
30
|
+
// ── Tunables ────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Char budget for the whole core view (~2 k tokens). Deliberately well
|
|
34
|
+
* under the 12 k file-injection cap: this block is paid for by every
|
|
35
|
+
* session on every frontend, and the rest of the store is a `talon
|
|
36
|
+
* memory list` away.
|
|
37
|
+
*/
|
|
38
|
+
export const CORE_VIEW_MAX_CHARS = 8_000;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A `state` row older than this is stale and left out — a status
|
|
42
|
+
* snapshot nobody has touched in a week is noise in a prompt, and the
|
|
43
|
+
* heartbeat rewrites the live ones far more often than that. Pinning
|
|
44
|
+
* overrides it, which is what pinning is for.
|
|
45
|
+
*/
|
|
46
|
+
const STATE_FRESH_MS = 7 * 24 * 60 * 60 * 1000;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rows pulled from the store before ranking. Generous: the budget, not
|
|
50
|
+
* this number, is what decides how much reaches the prompt.
|
|
51
|
+
*/
|
|
52
|
+
const CORE_VIEW_ROW_LIMIT = 500;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Kinds eligible for the core view, most durable first. `episode` and
|
|
56
|
+
* `reflection` are absent on purpose (plan §3.1): episodes decay fast
|
|
57
|
+
* and are a retrieval-only source, and the diary is the persona layer
|
|
58
|
+
* and never a fact source. Neither enters the core view — not even
|
|
59
|
+
* pinned, since a pin must not promote a kind past the tier boundary.
|
|
60
|
+
*/
|
|
61
|
+
const CORE_KINDS: readonly MemoryRow["kind"][] = [
|
|
62
|
+
"directive",
|
|
63
|
+
"relationship",
|
|
64
|
+
"fact",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// ── Selection ───────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** Salience first, then recency; id breaks the tie so the order is stable. */
|
|
70
|
+
function byRank(a: MemoryRow, b: MemoryRow): number {
|
|
71
|
+
return b.salience - a.salience || b.lastSeenAt - a.lastSeenAt || a.id - b.id;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function eligible(row: MemoryRow, now: number): boolean {
|
|
75
|
+
if (row.kind === "state")
|
|
76
|
+
return row.pinned || now - row.lastSeenAt <= STATE_FRESH_MS;
|
|
77
|
+
return CORE_KINDS.includes(row.kind);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Take rows until the budget is spent. A coarse pre-cut on row text
|
|
82
|
+
* only: the render does the exact accounting and cuts on section
|
|
83
|
+
* boundaries, so this exists to keep a 500-row store from being ranked
|
|
84
|
+
* into a document that can hold a fraction of it.
|
|
85
|
+
*/
|
|
86
|
+
function withinBudget(rows: readonly MemoryRow[], budget: number): MemoryRow[] {
|
|
87
|
+
const taken: MemoryRow[] = [];
|
|
88
|
+
let spent = 0;
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
if (spent > budget) break;
|
|
91
|
+
spent += row.text.length;
|
|
92
|
+
taken.push(row);
|
|
93
|
+
}
|
|
94
|
+
return taken;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Options shared by the selection and the render. */
|
|
98
|
+
export type CoreViewOptions = {
|
|
99
|
+
/** Char budget for the rendered view. Defaults to `CORE_VIEW_MAX_CHARS`. */
|
|
100
|
+
budget?: number;
|
|
101
|
+
/** Clock for the `state` freshness window (tests pin it). */
|
|
102
|
+
now?: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The ordered rows behind the core view: pinned first (any eligible
|
|
107
|
+
* kind), then directives, then the relationship layer, then facts by
|
|
108
|
+
* salience, then fresh `state`.
|
|
109
|
+
*
|
|
110
|
+
* The render re-sorts with the same ranking (`render.ts` `compareRows`),
|
|
111
|
+
* so this order is what reaches the prompt rather than merely what is
|
|
112
|
+
* handed over — but the selection is asserted here, where the tiers are
|
|
113
|
+
* decided, and not through the rendered bytes.
|
|
114
|
+
*/
|
|
115
|
+
export function selectCoreRows(opts: CoreViewOptions = {}): MemoryRow[] {
|
|
116
|
+
const now = opts.now ?? Date.now();
|
|
117
|
+
const live = listMemories({ limit: CORE_VIEW_ROW_LIMIT }).filter((row) =>
|
|
118
|
+
eligible(row, now),
|
|
119
|
+
);
|
|
120
|
+
const tiers: MemoryRow[][] = [
|
|
121
|
+
live.filter((row) => row.pinned).sort(byRank),
|
|
122
|
+
...CORE_KINDS.map((kind) =>
|
|
123
|
+
live.filter((row) => !row.pinned && row.kind === kind).sort(byRank),
|
|
124
|
+
),
|
|
125
|
+
live.filter((row) => !row.pinned && row.kind === "state").sort(byRank),
|
|
126
|
+
];
|
|
127
|
+
const seen = new Set<number>();
|
|
128
|
+
const ordered = tiers.flat().filter((row) => {
|
|
129
|
+
if (seen.has(row.id)) return false;
|
|
130
|
+
seen.add(row.id);
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
return withinBudget(ordered, opts.budget ?? CORE_VIEW_MAX_CHARS);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Rendering ───────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/** A rendered core view, with what it cost to carry. */
|
|
139
|
+
export type CoreView = {
|
|
140
|
+
text: string;
|
|
141
|
+
/** Rows selected into the view (0 means the store had nothing to say). */
|
|
142
|
+
rows: number;
|
|
143
|
+
/** Rendered size — the number recorded as `prompt.memory_chars`. */
|
|
144
|
+
chars: number;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render the core view as a `memory.md`-shaped document, reusing the
|
|
149
|
+
* projection renderer so the store reads identically in the prompt and
|
|
150
|
+
* in the file. Budgeted, not truncated: sections are dropped whole from
|
|
151
|
+
* the bottom of the ranking and the tail is named.
|
|
152
|
+
*/
|
|
153
|
+
export function renderCoreView(opts: CoreViewOptions = {}): CoreView {
|
|
154
|
+
const budget = opts.budget ?? CORE_VIEW_MAX_CHARS;
|
|
155
|
+
const rows = selectCoreRows({ ...opts, budget });
|
|
156
|
+
const text = renderMemoryMarkdown({ rows, budget });
|
|
157
|
+
return { text, rows: rows.length, chars: text.length };
|
|
158
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one flag that gates the typed memory store's read path
|
|
3
|
+
* (docs/memory-persona-rollout.md, "One flag gates the whole new path").
|
|
4
|
+
*
|
|
5
|
+
* Kept in its own module because `core/prompt/assemble.ts` asks the
|
|
6
|
+
* question but must not reach into `storage/` to answer it: the prompt
|
|
7
|
+
* layer sees the flag and the core view, nothing else.
|
|
8
|
+
*
|
|
9
|
+
* Read per call, never cached: tests flip it between builds, and the
|
|
10
|
+
* cost is an env lookup on a path that already reads files from disk.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** True when `TALON_MEMORY_STORE=1`. Off by default until PR 9 is measured. */
|
|
14
|
+
export function memoryStoreEnabled(): boolean {
|
|
15
|
+
return process.env.TALON_MEMORY_STORE === "1";
|
|
16
|
+
}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* 4. Persistent memory (ranked, capped) prompts/system/persistent-memory.md
|
|
20
20
|
* wrapping ~/.talon/workspace/memory/memory.md
|
|
21
21
|
* via memory-view.ts
|
|
22
|
+
* — or, with TALON_MEMORY_STORE=1 and a
|
|
23
|
+
* non-empty store, the typed store's core
|
|
24
|
+
* view (memory/core-view.ts) wrapped in
|
|
25
|
+
* prompts/system/memory-core-view.md
|
|
22
26
|
* 4.5 Live state (capped) prompts/system/live-state.md
|
|
23
27
|
* wrapping ~/.talon/workspace/memory/state.md
|
|
24
28
|
* (heartbeat-owned, rewritten whole)
|
|
@@ -65,6 +69,9 @@ import { renderMemoryView } from "./memory-view.js";
|
|
|
65
69
|
import { renderWorkspaceListing } from "./workspace-listing.js";
|
|
66
70
|
import { renderSkillsPrompt } from "../../storage/skills.js";
|
|
67
71
|
import { renderStickerLibraryPrompt } from "../../storage/stickers.js";
|
|
72
|
+
import { recordHistogram } from "../../storage/metrics.js";
|
|
73
|
+
import { renderCoreView } from "../memory/core-view.js";
|
|
74
|
+
import { memoryStoreEnabled } from "../memory/flag.js";
|
|
68
75
|
import { getSoul } from "../soul/service.js";
|
|
69
76
|
|
|
70
77
|
// ── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -109,6 +116,14 @@ export function joinSystemPromptParts(parts: SystemPromptParts): string {
|
|
|
109
116
|
*/
|
|
110
117
|
export const STATE_INJECT_MAX_CHARS = 2_000;
|
|
111
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Size of the injected memory block, recorded on every build from both
|
|
121
|
+
* tiers. The whole point of the flag being default-off is that these two
|
|
122
|
+
* populations can be compared before the store becomes the default path
|
|
123
|
+
* (rollout Decision 4).
|
|
124
|
+
*/
|
|
125
|
+
const MEMORY_CHARS_METRIC = "prompt.memory_chars";
|
|
126
|
+
|
|
112
127
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
113
128
|
|
|
114
129
|
function readOptionalFile(path: string): string {
|
|
@@ -122,6 +137,50 @@ function readOptionalFile(path: string): string {
|
|
|
122
137
|
|
|
123
138
|
let lastLoggedPromptKey = "";
|
|
124
139
|
|
|
140
|
+
/** A memory block ready for the static prompt, with the label it logs under. */
|
|
141
|
+
type MemorySection = { section: string; label: string };
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The store's core view, when `TALON_MEMORY_STORE` is on and the store
|
|
145
|
+
* has something to say. Budgeted (not truncated) and computed once per
|
|
146
|
+
* build — this is the session-frozen tier of plan §3.4, so it belongs in
|
|
147
|
+
* `staticText` and nowhere else.
|
|
148
|
+
*
|
|
149
|
+
* An empty store falls through to the file, which is what makes the flag
|
|
150
|
+
* safe to turn on before the import has ever run.
|
|
151
|
+
*/
|
|
152
|
+
function coreViewSection(): MemorySection | undefined {
|
|
153
|
+
if (!memoryStoreEnabled()) return undefined;
|
|
154
|
+
const view = renderCoreView();
|
|
155
|
+
if (view.rows === 0) return undefined;
|
|
156
|
+
recordHistogram(MEMORY_CHARS_METRIC, view.chars);
|
|
157
|
+
return {
|
|
158
|
+
section: loadSystemTemplate("memory-core-view", { content: view.text }),
|
|
159
|
+
label: "memory(store)",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The rendered `memory.md` file, ranked and capped. The path every
|
|
165
|
+
* deployment is on until the flag flips: over the cap the view ranks
|
|
166
|
+
* sections rather than head-slicing, so durable knowledge isn't evicted
|
|
167
|
+
* by whatever happens to sit at the top of the file (memory-view.ts).
|
|
168
|
+
*/
|
|
169
|
+
function memoryFileSection(): MemorySection | undefined {
|
|
170
|
+
const memory = readOptionalFile(pathFiles.memory);
|
|
171
|
+
if (!memory) return undefined;
|
|
172
|
+
const { text, truncated, omitted } = renderMemoryView(memory);
|
|
173
|
+
recordHistogram(MEMORY_CHARS_METRIC, text.length);
|
|
174
|
+
return {
|
|
175
|
+
section: loadSystemTemplate("persistent-memory", {
|
|
176
|
+
content: text,
|
|
177
|
+
truncated: truncated ? "yes" : undefined,
|
|
178
|
+
omitted: omitted || undefined,
|
|
179
|
+
}),
|
|
180
|
+
label: truncated ? "memory(ranked)" : "memory",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
125
184
|
// ── Assembly ────────────────────────────────────────────────────────────────
|
|
126
185
|
|
|
127
186
|
/** Inputs for `assembleSystemPrompt`. */
|
|
@@ -182,22 +241,16 @@ export function assembleSystemPrompt(
|
|
|
182
241
|
loaded.push(frontendFile.replace(".md", ""));
|
|
183
242
|
}
|
|
184
243
|
|
|
185
|
-
// 4. Persistent memory —
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
staticParts.push(
|
|
194
|
-
|
|
195
|
-
content: text,
|
|
196
|
-
truncated: truncated ? "yes" : undefined,
|
|
197
|
-
omitted: omitted || undefined,
|
|
198
|
-
}),
|
|
199
|
-
);
|
|
200
|
-
loaded.push(truncated ? "memory(ranked)" : "memory");
|
|
244
|
+
// 4. Persistent memory — the store's core view when the flag is on and
|
|
245
|
+
// the store has rows, else the ranked, size-capped `memory.md` file,
|
|
246
|
+
// so a memory file that has grown for months can't bloat every
|
|
247
|
+
// session from turn 0. Static either way: both tiers are frozen for
|
|
248
|
+
// the session's lifetime (plan §3.4/§3.6). Anything learned
|
|
249
|
+
// mid-session reaches the model through turn retrieval instead.
|
|
250
|
+
const memorySection = coreViewSection() ?? memoryFileSection();
|
|
251
|
+
if (memorySection) {
|
|
252
|
+
staticParts.push(memorySection.section);
|
|
253
|
+
loaded.push(memorySection.label);
|
|
201
254
|
}
|
|
202
255
|
|
|
203
256
|
// 4.5. Live state — the heartbeat's rewritten-whole status snapshot, kept
|
|
@@ -27,15 +27,16 @@ import asset13 from "../../../prompts/system/daily-memory.md" with { type: "file
|
|
|
27
27
|
import asset14 from "../../../prompts/system/goals.md" with { type: "file" };
|
|
28
28
|
import asset15 from "../../../prompts/system/heartbeat-agent.md" with { type: "file" };
|
|
29
29
|
import asset16 from "../../../prompts/system/live-state.md" with { type: "file" };
|
|
30
|
-
import asset17 from "../../../prompts/system/memory-
|
|
31
|
-
import asset18 from "../../../prompts/system/
|
|
32
|
-
import asset19 from "../../../prompts/system/
|
|
33
|
-
import asset20 from "../../../prompts/system/
|
|
34
|
-
import asset21 from "../../../prompts/system/
|
|
35
|
-
import asset22 from "../../../prompts/
|
|
36
|
-
import asset23 from "../../../prompts/
|
|
37
|
-
import asset24 from "../../../prompts/
|
|
38
|
-
import asset25 from "../../../prompts/
|
|
30
|
+
import asset17 from "../../../prompts/system/memory-core-view.md" with { type: "file" };
|
|
31
|
+
import asset18 from "../../../prompts/system/memory-recall.md" with { type: "file" };
|
|
32
|
+
import asset19 from "../../../prompts/system/persistent-memory.md" with { type: "file" };
|
|
33
|
+
import asset20 from "../../../prompts/system/skills.md" with { type: "file" };
|
|
34
|
+
import asset21 from "../../../prompts/system/triggers.md" with { type: "file" };
|
|
35
|
+
import asset22 from "../../../prompts/system/workspace.md" with { type: "file" };
|
|
36
|
+
import asset23 from "../../../prompts/teams.md" with { type: "file" };
|
|
37
|
+
import asset24 from "../../../prompts/telegram.md" with { type: "file" };
|
|
38
|
+
import asset25 from "../../../prompts/terminal.md" with { type: "file" };
|
|
39
|
+
import asset26 from "../../../prompts/whatsapp.md" with { type: "file" };
|
|
39
40
|
|
|
40
41
|
/** rel path (posix, under prompts/) → embedded file path (/$bunfs/… when compiled). */
|
|
41
42
|
const ASSETS: Record<string, string> = {
|
|
@@ -56,15 +57,16 @@ const ASSETS: Record<string, string> = {
|
|
|
56
57
|
"system/goals.md": asset14,
|
|
57
58
|
"system/heartbeat-agent.md": asset15,
|
|
58
59
|
"system/live-state.md": asset16,
|
|
59
|
-
"system/memory-
|
|
60
|
-
"system/
|
|
61
|
-
"system/
|
|
62
|
-
"system/
|
|
63
|
-
"system/
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
60
|
+
"system/memory-core-view.md": asset17,
|
|
61
|
+
"system/memory-recall.md": asset18,
|
|
62
|
+
"system/persistent-memory.md": asset19,
|
|
63
|
+
"system/skills.md": asset20,
|
|
64
|
+
"system/triggers.md": asset21,
|
|
65
|
+
"system/workspace.md": asset22,
|
|
66
|
+
"teams.md": asset23,
|
|
67
|
+
"telegram.md": asset24,
|
|
68
|
+
"terminal.md": asset25,
|
|
69
|
+
"whatsapp.md": asset26,
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
/** Read an embedded prompt by its rel path (e.g. "system/cron.md"). */
|
package/src/storage/metrics.ts
CHANGED
|
@@ -22,6 +22,14 @@ const PHASE_HISTOGRAM: Record<TurnPhase, string> = {
|
|
|
22
22
|
|
|
23
23
|
const legacyCounters = new Map<string, number>();
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Process-lifetime distributions for values that are not chat-turn
|
|
27
|
+
* latencies and so have nowhere to live on a session record — currently
|
|
28
|
+
* `prompt.memory_chars`, the size of the injected memory block, which is
|
|
29
|
+
* what the `TALON_MEMORY_STORE` before/after comparison reads.
|
|
30
|
+
*/
|
|
31
|
+
const processHistograms = new Map<string, MetricsLatencyAgg>();
|
|
32
|
+
|
|
25
33
|
export type MetricsSnapshot = {
|
|
26
34
|
counters: Record<string, number>;
|
|
27
35
|
histograms: Record<
|
|
@@ -34,6 +42,18 @@ export function incrementCounter(name: string, amount = 1): void {
|
|
|
34
42
|
legacyCounters.set(name, (legacyCounters.get(name) ?? 0) + amount);
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Record one observation of a non-turn distribution. Lifetime-scoped and
|
|
47
|
+
* in-process, like the legacy counters: it surfaces in `getMetrics()`
|
|
48
|
+
* (count / avg / min / max), not in the daily rollup.
|
|
49
|
+
*/
|
|
50
|
+
export function recordHistogram(name: string, value: number): void {
|
|
51
|
+
if (!Number.isFinite(value)) return;
|
|
52
|
+
const agg = processHistograms.get(name) ?? emptyAgg();
|
|
53
|
+
mergeAgg(agg, { count: 1, sumMs: value, minMs: value, maxMs: value });
|
|
54
|
+
processHistograms.set(name, agg);
|
|
55
|
+
}
|
|
56
|
+
|
|
37
57
|
function addCounter(
|
|
38
58
|
counters: Record<string, number>,
|
|
39
59
|
name: string,
|
|
@@ -158,10 +178,14 @@ function buildSnapshot(
|
|
|
158
178
|
export function getMetrics(): MetricsSnapshot {
|
|
159
179
|
const counters: Record<string, number> = {};
|
|
160
180
|
for (const [key, value] of legacyCounters) addCounter(counters, key, value);
|
|
161
|
-
|
|
181
|
+
const snapshot = buildSnapshot(
|
|
162
182
|
getAllSessions().map(({ info }) => info.metrics.lifetime),
|
|
163
183
|
counters,
|
|
164
184
|
);
|
|
185
|
+
for (const [name, agg] of processHistograms) {
|
|
186
|
+
if (agg.count) snapshot.histograms[name] = snapshotAgg(agg);
|
|
187
|
+
}
|
|
188
|
+
return snapshot;
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
/** Today's (UTC) fleet snapshot, aggregated from the sessions' daily
|
|
@@ -177,5 +201,6 @@ export function getTodayMetrics(): MetricsSnapshot {
|
|
|
177
201
|
|
|
178
202
|
export function resetMetrics(): void {
|
|
179
203
|
legacyCounters.clear();
|
|
204
|
+
processHistograms.clear();
|
|
180
205
|
resetAllSessionMetrics();
|
|
181
206
|
}
|