@phnx-labs/agents-cli 1.21.1 → 1.21.3
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/CHANGELOG.md +206 -0
- package/README.md +1 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.d.ts +4 -0
- package/dist/commands/computer-actions.js +34 -0
- package/dist/commands/computer.js +4 -2
- package/dist/commands/doctor.js +5 -2
- package/dist/commands/feed.js +28 -19
- package/dist/commands/hooks.js +9 -45
- package/dist/commands/menubar.js +24 -24
- package/dist/commands/message.js +23 -3
- package/dist/commands/perf.d.ts +29 -0
- package/dist/commands/perf.js +90 -23
- package/dist/commands/projects.d.ts +21 -0
- package/dist/commands/projects.js +169 -23
- package/dist/commands/routines.js +46 -1
- package/dist/commands/sessions-picker.js +17 -2
- package/dist/commands/sessions.d.ts +5 -0
- package/dist/commands/sessions.js +14 -0
- package/dist/commands/ssh.js +69 -0
- package/dist/commands/trends.d.ts +2 -0
- package/dist/commands/trends.js +158 -0
- package/dist/commands/usage.d.ts +4 -4
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +90 -45
- package/dist/index.js +22 -1
- package/dist/lib/agents.js +2 -2
- package/dist/lib/analytics/dashboard.d.ts +11 -0
- package/dist/lib/analytics/dashboard.js +31 -0
- package/dist/lib/analytics/recipes.d.ts +32 -0
- package/dist/lib/analytics/recipes.js +316 -0
- package/dist/lib/analytics/usage-db.d.ts +84 -0
- package/dist/lib/analytics/usage-db.js +301 -0
- package/dist/lib/browser/service.js +31 -0
- package/dist/lib/cli-resources.d.ts +20 -0
- package/dist/lib/cli-resources.js +48 -1
- package/dist/lib/computer/dispatch.d.ts +3 -1
- package/dist/lib/computer/dispatch.js +10 -2
- package/dist/lib/daemon.js +51 -14
- package/dist/lib/devices/health-report.d.ts +5 -0
- package/dist/lib/devices/health-report.js +3 -0
- package/dist/lib/event-stream.d.ts +2 -0
- package/dist/lib/event-stream.js +3 -0
- package/dist/lib/events.d.ts +3 -1
- package/dist/lib/events.js +4 -2
- package/dist/lib/feed-broadcast.d.ts +52 -7
- package/dist/lib/feed-broadcast.js +125 -18
- package/dist/lib/fleet-cache.d.ts +37 -0
- package/dist/lib/fleet-cache.js +40 -0
- package/dist/lib/fleet-status.d.ts +53 -0
- package/dist/lib/fleet-status.js +120 -0
- package/dist/lib/friction-heuristics.d.ts +32 -0
- package/dist/lib/friction-heuristics.js +47 -0
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +36 -0
- package/dist/lib/hooks/cache.js +28 -6
- package/dist/lib/hooks/profile.d.ts +8 -0
- package/dist/lib/hooks/profile.js +15 -18
- package/dist/lib/hooks.js +72 -17
- package/dist/lib/linear-cache.d.ts +63 -0
- package/dist/lib/linear-cache.js +146 -0
- package/dist/lib/linear-project-counts.d.ts +35 -5
- package/dist/lib/linear-project-counts.js +61 -16
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +3 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +7 -0
- package/dist/lib/menubar/install-menubar.js +36 -6
- package/dist/lib/percentile.d.ts +12 -0
- package/dist/lib/percentile.js +24 -0
- package/dist/lib/perf/db.d.ts +7 -3
- package/dist/lib/perf/db.js +37 -19
- package/dist/lib/perf/types.d.ts +10 -0
- package/dist/lib/plugins.js +12 -1
- package/dist/lib/project-doctor.d.ts +36 -0
- package/dist/lib/project-doctor.js +45 -0
- package/dist/lib/project-import.d.ts +11 -1
- package/dist/lib/project-import.js +17 -3
- package/dist/lib/project-status.d.ts +25 -5
- package/dist/lib/project-status.js +48 -6
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +25 -14
- package/dist/lib/rotate.d.ts +27 -0
- package/dist/lib/rotate.js +44 -17
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +26 -0
- package/dist/lib/runner.js +41 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/usage-db.d.ts +3 -63
- package/dist/lib/secrets/usage-db.js +46 -186
- package/dist/lib/session/db.d.ts +17 -1
- package/dist/lib/session/db.js +240 -6
- package/dist/lib/session/discover.d.ts +31 -0
- package/dist/lib/session/discover.js +50 -1
- package/dist/lib/session/highlights.d.ts +24 -4
- package/dist/lib/session/highlights.js +52 -7
- package/dist/lib/session/parse.js +19 -3
- package/dist/lib/session/prompt.d.ts +19 -0
- package/dist/lib/session/prompt.js +43 -0
- package/dist/lib/session/types.d.ts +43 -0
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +31 -3
- package/dist/lib/state.js +53 -10
- package/dist/lib/types.d.ts +22 -4
- package/dist/lib/usage-refresh.d.ts +106 -0
- package/dist/lib/usage-refresh.js +238 -0
- package/dist/lib/usage.d.ts +152 -17
- package/dist/lib/usage.js +393 -79
- package/package.json +1 -1
|
@@ -17,12 +17,32 @@ export interface SkillUse {
|
|
|
17
17
|
count: number;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* True for a tool_use event that is that harness's skill-invocation call (see
|
|
21
|
+
* {@link SKILL_TOOL_NAME_BY_AGENT}). Exported so an INCREMENTAL parser
|
|
22
|
+
* (discover.ts's ClaudeParseState/foldDerivedToolState) can select matching
|
|
23
|
+
* events into a small held array as it streams, then run {@link extractSkills}
|
|
24
|
+
* over just that subset at finalize — without re-parsing the whole transcript.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSkillInvocation(e: SessionEvent): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Skills invoked during the session, from that harness's skill-invocation
|
|
29
|
+
* tool calls (see {@link SKILL_TOOL_NAME_BY_AGENT}). Carries the skill id in
|
|
30
|
+
* `args.skill` (or `args.name`). Sorted by count desc, then name.
|
|
24
31
|
*/
|
|
25
32
|
export declare function extractSkills(events: SessionEvent[]): SkillUse[];
|
|
33
|
+
export interface SlashCommandUse {
|
|
34
|
+
/** WITH the leading slash, e.g. `/recap`, `/code:commit` — matches SessionEvent.slashCommand's shape. */
|
|
35
|
+
name: string;
|
|
36
|
+
count: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Slash commands invoked during the session — either the user typing one
|
|
40
|
+
* (Claude's `<command-name>` wrapper) or the model invoking one via the
|
|
41
|
+
* `SlashCommand` tool (`SessionEvent.slashCommand`, populated by
|
|
42
|
+
* `parseClaudeContent` for both sources — see session/prompt.ts). Sorted by
|
|
43
|
+
* count desc, then name.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractSlashCommands(events: SessionEvent[]): SlashCommandUse[];
|
|
26
46
|
export interface HookUse {
|
|
27
47
|
/** Hook name as configured, e.g. `SessionStart:startup`. */
|
|
28
48
|
name: string;
|
|
@@ -15,17 +15,44 @@ import os from 'node:os';
|
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { isNoisePath } from './digest.js';
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* The tool name a harness uses to invoke a skill, keyed by `SessionAgentId` —
|
|
19
|
+
* a registry, not a single hardcoded string, so a harness whose transcript
|
|
20
|
+
* names the tool differently is one table row away from support instead of a
|
|
21
|
+
* near-identical `if` arm (see CLAUDE.md's registry-over-if-chain convention).
|
|
22
|
+
*
|
|
23
|
+
* Verified entries only: Claude and Kimi both empirically name the tool
|
|
24
|
+
* `Skill` (a plugin-provided skill rides the same tool). No other harness has
|
|
25
|
+
* a confirmed skill-invocation tool name in this codebase's fixtures — an
|
|
26
|
+
* absent entry means "we don't know yet", not "this harness has no skills";
|
|
27
|
+
* extractSkills yields `[]` for it rather than guessing a name and silently
|
|
28
|
+
* mismatching (or worse, matching a coincidental tool with the same name).
|
|
29
|
+
*/
|
|
30
|
+
const SKILL_TOOL_NAME_BY_AGENT = {
|
|
31
|
+
claude: 'Skill',
|
|
32
|
+
kimi: 'Skill',
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* True for a tool_use event that is that harness's skill-invocation call (see
|
|
36
|
+
* {@link SKILL_TOOL_NAME_BY_AGENT}). Exported so an INCREMENTAL parser
|
|
37
|
+
* (discover.ts's ClaudeParseState/foldDerivedToolState) can select matching
|
|
38
|
+
* events into a small held array as it streams, then run {@link extractSkills}
|
|
39
|
+
* over just that subset at finalize — without re-parsing the whole transcript.
|
|
40
|
+
*/
|
|
41
|
+
export function isSkillInvocation(e) {
|
|
42
|
+
if (e.type !== 'tool_use' || e._local)
|
|
43
|
+
return false;
|
|
44
|
+
const skillTool = SKILL_TOOL_NAME_BY_AGENT[e.agent];
|
|
45
|
+
return !!skillTool && e.tool === skillTool;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Skills invoked during the session, from that harness's skill-invocation
|
|
49
|
+
* tool calls (see {@link SKILL_TOOL_NAME_BY_AGENT}). Carries the skill id in
|
|
50
|
+
* `args.skill` (or `args.name`). Sorted by count desc, then name.
|
|
22
51
|
*/
|
|
23
52
|
export function extractSkills(events) {
|
|
24
53
|
const counts = new Map();
|
|
25
54
|
for (const e of events) {
|
|
26
|
-
if (e
|
|
27
|
-
continue;
|
|
28
|
-
if (e.tool !== 'Skill')
|
|
55
|
+
if (!isSkillInvocation(e))
|
|
29
56
|
continue;
|
|
30
57
|
const name = e.args?.skill ?? e.args?.name;
|
|
31
58
|
if (typeof name !== 'string' || !name.trim())
|
|
@@ -37,6 +64,24 @@ export function extractSkills(events) {
|
|
|
37
64
|
.map(([name, count]) => ({ name, count }))
|
|
38
65
|
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
39
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Slash commands invoked during the session — either the user typing one
|
|
69
|
+
* (Claude's `<command-name>` wrapper) or the model invoking one via the
|
|
70
|
+
* `SlashCommand` tool (`SessionEvent.slashCommand`, populated by
|
|
71
|
+
* `parseClaudeContent` for both sources — see session/prompt.ts). Sorted by
|
|
72
|
+
* count desc, then name.
|
|
73
|
+
*/
|
|
74
|
+
export function extractSlashCommands(events) {
|
|
75
|
+
const counts = new Map();
|
|
76
|
+
for (const e of events) {
|
|
77
|
+
if (!e.slashCommand)
|
|
78
|
+
continue;
|
|
79
|
+
counts.set(e.slashCommand, (counts.get(e.slashCommand) ?? 0) + 1);
|
|
80
|
+
}
|
|
81
|
+
return [...counts.entries()]
|
|
82
|
+
.map(([name, count]) => ({ name, count }))
|
|
83
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
84
|
+
}
|
|
40
85
|
/**
|
|
41
86
|
* Hooks that fired during the session, folded from `hook` events (parsed from
|
|
42
87
|
* Claude's `hook_success`/`hook_error`/… attachment records; other harnesses
|
|
@@ -9,7 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import { truncate } from '../format.js';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import Database from '../sqlite.js';
|
|
12
|
-
import { isSyntheticUserMessage } from './prompt.js';
|
|
12
|
+
import { isSyntheticUserMessage, extractSlashCommandName, extractSlashCommandFromToolInput } from './prompt.js';
|
|
13
13
|
/**
|
|
14
14
|
* Largest session file we will load into memory. Above this we throw a clean
|
|
15
15
|
* error instead of OOMing or hitting V8's ERR_STRING_TOO_LONG. Aligns with
|
|
@@ -379,6 +379,14 @@ export function parseClaudeContent(content) {
|
|
|
379
379
|
};
|
|
380
380
|
if (isLocal)
|
|
381
381
|
event._local = true;
|
|
382
|
+
// SlashCommand: the MODEL invoking a slash command programmatically
|
|
383
|
+
// (distinct from the <command-name> wrapper below, which is the
|
|
384
|
+
// USER typing one) — see prompt.ts's extractSlashCommandFromToolInput.
|
|
385
|
+
if (toolName === 'SlashCommand') {
|
|
386
|
+
const slashCommand = extractSlashCommandFromToolInput(toolInput);
|
|
387
|
+
if (slashCommand)
|
|
388
|
+
event.slashCommand = slashCommand;
|
|
389
|
+
}
|
|
382
390
|
events.push(event);
|
|
383
391
|
}
|
|
384
392
|
}
|
|
@@ -403,13 +411,21 @@ export function parseClaudeContent(content) {
|
|
|
403
411
|
// Simple user text
|
|
404
412
|
const text = contentBlocks.trim();
|
|
405
413
|
if (text) {
|
|
406
|
-
|
|
414
|
+
const event = {
|
|
407
415
|
type: 'message',
|
|
408
416
|
agent: 'claude',
|
|
409
417
|
timestamp,
|
|
410
418
|
role: 'user',
|
|
411
419
|
content: text,
|
|
412
|
-
}
|
|
420
|
+
};
|
|
421
|
+
// The USER typing a slash command — Claude injects a <command-name>
|
|
422
|
+
// wrapper as the message content (see prompt.ts's
|
|
423
|
+
// extractSlashCommandName; distinct from the SlashCommand tool-use
|
|
424
|
+
// above, which is the model invoking one programmatically).
|
|
425
|
+
const slashCommand = extractSlashCommandName(text);
|
|
426
|
+
if (slashCommand)
|
|
427
|
+
event.slashCommand = slashCommand;
|
|
428
|
+
events.push(event);
|
|
413
429
|
}
|
|
414
430
|
}
|
|
415
431
|
else if (Array.isArray(contentBlocks)) {
|
|
@@ -12,6 +12,25 @@
|
|
|
12
12
|
* own scaffolding at parse time simply never reach here.
|
|
13
13
|
*/
|
|
14
14
|
export declare function isSyntheticUserMessage(raw: string | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Extract the invoked slash-command name from a raw `role=user` message, or
|
|
17
|
+
* undefined when the message carries no `<command-name>` wrapper. Used by
|
|
18
|
+
* `parseClaudeContent` to populate `SessionEvent.slashCommand`.
|
|
19
|
+
*
|
|
20
|
+
* Normalizes a leading `/` onto the result: current Claude Code wraps the
|
|
21
|
+
* name WITH the slash already (`/recap`), but the wrapper predates that and
|
|
22
|
+
* older transcripts carry the bare name (`recap`) — normalizing here means
|
|
23
|
+
* every consumer sees one consistent `/name` shape regardless of vintage.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractSlashCommandName(raw: string | undefined): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the invoked slash-command name from the `SlashCommand` tool's input
|
|
28
|
+
* — the MODEL invoking a command programmatically, not the user typing it.
|
|
29
|
+
* The tool's `command` field carries the full invocation text (name plus any
|
|
30
|
+
* args, e.g. `/code:commit fix the bug`); only the leading command token is
|
|
31
|
+
* the name, matching {@link extractSlashCommandName}'s shape.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractSlashCommandFromToolInput(toolInput: Record<string, any> | undefined): string | undefined;
|
|
15
34
|
export declare const HEADLESS_PLAN_MODE_PREFIX = "You are running in HEADLESS PLAN MODE.";
|
|
16
35
|
/** Strip framework noise from a raw session prompt, returning only meaningful user text. */
|
|
17
36
|
export declare function cleanSessionPrompt(raw: string): string;
|
|
@@ -63,6 +63,49 @@ export function isSyntheticUserMessage(raw) {
|
|
|
63
63
|
return false;
|
|
64
64
|
return SYNTHETIC_USER_MESSAGE_PATTERNS.some(pattern => pattern.test(raw));
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The `<command-name>` wrapper Claude injects into a `role=user` message when
|
|
68
|
+
* a typed slash command is invoked, e.g.:
|
|
69
|
+
* <command-message>recap</command-message>
|
|
70
|
+
* <command-name>/recap</command-name>
|
|
71
|
+
* Captures the name INCLUDING its leading slash (`/recap`), matching how the
|
|
72
|
+
* `SlashCommand` tool's own `command` input is formatted (see
|
|
73
|
+
* `extractSlashCommandFromToolInput`) so both sources land on the same shape.
|
|
74
|
+
*/
|
|
75
|
+
const COMMAND_NAME_WRAPPER_RE = /<command-name>\s*([^<]*?)\s*<\/command-name>/i;
|
|
76
|
+
/**
|
|
77
|
+
* Extract the invoked slash-command name from a raw `role=user` message, or
|
|
78
|
+
* undefined when the message carries no `<command-name>` wrapper. Used by
|
|
79
|
+
* `parseClaudeContent` to populate `SessionEvent.slashCommand`.
|
|
80
|
+
*
|
|
81
|
+
* Normalizes a leading `/` onto the result: current Claude Code wraps the
|
|
82
|
+
* name WITH the slash already (`/recap`), but the wrapper predates that and
|
|
83
|
+
* older transcripts carry the bare name (`recap`) — normalizing here means
|
|
84
|
+
* every consumer sees one consistent `/name` shape regardless of vintage.
|
|
85
|
+
*/
|
|
86
|
+
export function extractSlashCommandName(raw) {
|
|
87
|
+
if (!raw)
|
|
88
|
+
return undefined;
|
|
89
|
+
const m = raw.match(COMMAND_NAME_WRAPPER_RE);
|
|
90
|
+
const name = m?.[1]?.trim();
|
|
91
|
+
if (!name)
|
|
92
|
+
return undefined;
|
|
93
|
+
return name.startsWith('/') ? name : `/${name}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract the invoked slash-command name from the `SlashCommand` tool's input
|
|
97
|
+
* — the MODEL invoking a command programmatically, not the user typing it.
|
|
98
|
+
* The tool's `command` field carries the full invocation text (name plus any
|
|
99
|
+
* args, e.g. `/code:commit fix the bug`); only the leading command token is
|
|
100
|
+
* the name, matching {@link extractSlashCommandName}'s shape.
|
|
101
|
+
*/
|
|
102
|
+
export function extractSlashCommandFromToolInput(toolInput) {
|
|
103
|
+
const command = toolInput?.command;
|
|
104
|
+
if (typeof command !== 'string')
|
|
105
|
+
return undefined;
|
|
106
|
+
const name = command.trim().split(/\s+/)[0];
|
|
107
|
+
return name || undefined;
|
|
108
|
+
}
|
|
66
109
|
// Prefix prepended to every Claude-in-plan-mode team spawn prompt.
|
|
67
110
|
// Ends at a blank line before the real user task.
|
|
68
111
|
export const HEADLESS_PLAN_MODE_PREFIX = 'You are running in HEADLESS PLAN MODE.';
|
|
@@ -52,6 +52,15 @@ export interface SessionEvent {
|
|
|
52
52
|
hookName?: string;
|
|
53
53
|
/** Lifecycle event the hook fired on (SessionStart, PreToolUse, …). */
|
|
54
54
|
hookEvent?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Slash-command invocation name (e.g. `/recap`, `/code:commit`), captured
|
|
57
|
+
* two ways in a Claude transcript: a `role=user` message whose content is
|
|
58
|
+
* the `<command-name>` wrapper Claude injects for a typed slash command
|
|
59
|
+
* (`parseClaudeContent`, see `prompt.ts`'s `extractSlashCommandName`), or a
|
|
60
|
+
* `tool_use` event for the `SlashCommand` tool (a command the MODEL invoked
|
|
61
|
+
* programmatically, not the user). Undefined for every other event.
|
|
62
|
+
*/
|
|
63
|
+
slashCommand?: string;
|
|
55
64
|
}
|
|
56
65
|
/** A displayable file attachment discovered in a session transcript. */
|
|
57
66
|
export interface SessionAttachment {
|
|
@@ -132,6 +141,7 @@ export interface SessionMeta {
|
|
|
132
141
|
durationMs?: number;
|
|
133
142
|
/** Underlying LLM model observed in the transcript, when the agent records one. */
|
|
134
143
|
model?: string;
|
|
144
|
+
toolCallCount?: number;
|
|
135
145
|
version?: string;
|
|
136
146
|
account?: string;
|
|
137
147
|
topic?: string;
|
|
@@ -185,6 +195,39 @@ export interface SessionMeta {
|
|
|
185
195
|
todos?: TodoProgress;
|
|
186
196
|
/** Most-recent unique directories changed or used as a shell working directory. */
|
|
187
197
|
recentDirectoriesTouched?: string[];
|
|
198
|
+
/**
|
|
199
|
+
* Skills invoked during the session (structurally identical to
|
|
200
|
+
* session/highlights.ts's SkillUse — declared inline here rather than
|
|
201
|
+
* imported, to avoid a circular import: highlights.ts imports SessionEvent
|
|
202
|
+
* from this file). Populated by discover.ts's incremental Claude
|
|
203
|
+
* accumulator (ClaudeParseState.skillEvents, run through extractSkills at
|
|
204
|
+
* finalize) so session/db.ts's upsertSessionsBatch can write
|
|
205
|
+
* session_resource_usage rows WITHOUT re-parsing the whole transcript —
|
|
206
|
+
* the same reason meta.todos/recentDirectoriesTouched are pre-computed by
|
|
207
|
+
* the caller for claude/codex instead of left to db.ts's re-parse path.
|
|
208
|
+
*/
|
|
209
|
+
skillsUsed?: Array<{
|
|
210
|
+
name: string;
|
|
211
|
+
count: number;
|
|
212
|
+
}>;
|
|
213
|
+
/** Sibling of {@link skillsUsed} for slash-command invocations (SessionEvent.slashCommand). */
|
|
214
|
+
slashCommandsUsed?: Array<{
|
|
215
|
+
name: string;
|
|
216
|
+
count: number;
|
|
217
|
+
}>;
|
|
218
|
+
/**
|
|
219
|
+
* Whether this session emitted at least one `browser.navigate` /
|
|
220
|
+
* `browser.screenshot` event, computed at scan time from a sessionId-scoped
|
|
221
|
+
* read of the events log (events.ts `query({ sessionId })`) rather than a
|
|
222
|
+
* transcript re-scan — see `detectToolUsage` in session/db.ts. `undefined`
|
|
223
|
+
* means a legacy row this scanner hasn't computed the field for yet (never
|
|
224
|
+
* collapsed to `false`, so a consumer — e.g. sessions-picker.ts's
|
|
225
|
+
* `classifySessionTool` — knows to fall back to a transcript-derived guess
|
|
226
|
+
* instead of trusting a false negative).
|
|
227
|
+
*/
|
|
228
|
+
usedBrowser?: boolean;
|
|
229
|
+
/** Sibling of {@link usedBrowser} for `computer.action` events. */
|
|
230
|
+
usedComputer?: boolean;
|
|
188
231
|
/** Linear project containing ticketId, resolved lazily and cached in SQLite. */
|
|
189
232
|
linearProject?: string;
|
|
190
233
|
/** Browser URL for linearProject. */
|
|
@@ -71,6 +71,7 @@ export declare const loadFactory: ModuleLoader;
|
|
|
71
71
|
export declare const loadUsage: ModuleLoader;
|
|
72
72
|
export declare const loadCost: ModuleLoader;
|
|
73
73
|
export declare const loadPerf: ModuleLoader;
|
|
74
|
+
export declare const loadTrends: ModuleLoader;
|
|
74
75
|
export declare const loadOutput: ModuleLoader;
|
|
75
76
|
export declare const loadBudget: ModuleLoader;
|
|
76
77
|
export declare const loadAlias: ModuleLoader;
|
|
@@ -49,6 +49,7 @@ export const loadFactory = async () => (await import('../../commands/factory.js'
|
|
|
49
49
|
export const loadUsage = async () => (await import('../../commands/usage.js')).registerUsageCommand;
|
|
50
50
|
export const loadCost = async () => (await import('../../commands/cost.js')).registerCostCommand;
|
|
51
51
|
export const loadPerf = async () => (await import('../../commands/perf.js')).registerPerfCommand;
|
|
52
|
+
export const loadTrends = async () => (await import('../../commands/trends.js')).registerTrendsCommand;
|
|
52
53
|
export const loadOutput = async () => (await import('../../commands/output.js')).registerOutputCommand;
|
|
53
54
|
export const loadBudget = async () => (await import('../../commands/budget.js')).registerBudgetCommand;
|
|
54
55
|
export const loadAlias = async () => (await import('../../commands/alias.js')).registerAliasCommand;
|
|
@@ -163,6 +164,7 @@ export const COMMAND_LOADERS = {
|
|
|
163
164
|
usage: [loadUsage],
|
|
164
165
|
cost: [loadCost],
|
|
165
166
|
perf: [loadPerf],
|
|
167
|
+
trends: [loadTrends],
|
|
166
168
|
output: [loadOutput],
|
|
167
169
|
budget: [loadBudget],
|
|
168
170
|
alias: [loadAlias],
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -117,6 +117,14 @@ export declare function getUserSecretsDir(): string;
|
|
|
117
117
|
* metadata off the real session flow — not a second write path.
|
|
118
118
|
*/
|
|
119
119
|
export declare function getSecretsDbPath(): string;
|
|
120
|
+
/**
|
|
121
|
+
* Path to the durable resource-usage warehouse (~/.agents/.history/analytics/usage.db).
|
|
122
|
+
* Value-free frequency/lifecycle events (secrets, agents, browser, …). Read at CALL
|
|
123
|
+
* time so AGENTS_USAGE_DB can redirect tests. Sync shards may also appear as
|
|
124
|
+
* usage.<machine-id>.db beside this default file.
|
|
125
|
+
*/
|
|
126
|
+
export declare function getAnalyticsDir(): string;
|
|
127
|
+
export declare function getUsageDbPath(): string;
|
|
120
128
|
export declare function getUserPromptcutsPath(): string;
|
|
121
129
|
/** Canonical home anchor (HOME env override or os.homedir()). */
|
|
122
130
|
export declare function getHomeDir(): string;
|
|
@@ -185,9 +193,20 @@ export declare function getActivityDir(): string;
|
|
|
185
193
|
export declare function getVersionsDir(): string;
|
|
186
194
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
|
187
195
|
export declare function getShimsDir(): string;
|
|
188
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Path to generated per-hook caching/timing shims (~/.agents/.cache/shims/hooks/).
|
|
198
|
+
* Read at CALL time — since every hook now resolves through a shim (RUSH-2xxx,
|
|
199
|
+
* pass-through timing for matcher-only hooks), a test that registers hooks
|
|
200
|
+
* in-process (no subprocess HOME override) would otherwise write real shim
|
|
201
|
+
* files into the user's actual ~/.agents/.cache. AGENTS_HOOK_SHIMS_DIR mirrors
|
|
202
|
+
* the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR test-isolation escape hatches;
|
|
203
|
+
* never set in production code.
|
|
204
|
+
*/
|
|
189
205
|
export declare function getHookShimsDir(): string;
|
|
190
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* Path to per-hook stdout cache files (~/.agents/.cache/state/hooks/). Read at
|
|
208
|
+
* CALL time for the same reason as {@link getHookShimsDir} — see its doc.
|
|
209
|
+
*/
|
|
191
210
|
export declare function getHookCacheDir(): string;
|
|
192
211
|
/** Path to per-agent installed CLI binaries (~/.agents/.cache/bin/). */
|
|
193
212
|
export declare function getBinDir(): string;
|
|
@@ -225,11 +244,20 @@ export declare function getDevicesPendingDir(): string;
|
|
|
225
244
|
export declare function getCloudDir(): string;
|
|
226
245
|
/** Path to terminal session metadata (~/.agents/.cache/terminals/). */
|
|
227
246
|
export declare function getTerminalsDir(): string;
|
|
228
|
-
/**
|
|
247
|
+
/**
|
|
248
|
+
* Path to runtime logs (~/.agents/.cache/logs/). Read at CALL time so
|
|
249
|
+
* AGENTS_LOGS_DIR can redirect it in tests — same test-isolation escape hatch
|
|
250
|
+
* as {@link getHookShimsDir}; never set in production code.
|
|
251
|
+
*/
|
|
229
252
|
export declare function getLogsDir(): string;
|
|
230
253
|
/**
|
|
231
254
|
* Path to disposable performance samples (~/.agents/.cache/perf/).
|
|
232
255
|
* Holds `perf.db` + a hook-shim spool. Loss is acceptable — wipe freely.
|
|
256
|
+
* Read at CALL time: AGENTS_PERF_DIR (the same override perf/db.ts and
|
|
257
|
+
* perf/spool.ts already honor for their own internal resolution) redirects
|
|
258
|
+
* this canonical getter too, so a caller that goes through it directly
|
|
259
|
+
* (hooks/cache.ts's shim generator, the OpenCode timeout sample writer in
|
|
260
|
+
* hooks.ts) doesn't leak samples into the user's real perf warehouse either.
|
|
233
261
|
*/
|
|
234
262
|
export declare function getPerfDir(): string;
|
|
235
263
|
/** Path to the perf SQLite warehouse (~/.agents/.cache/perf/perf.db). */
|
package/dist/lib/state.js
CHANGED
|
@@ -96,6 +96,7 @@ const PROJECTS_DIR = path.join(USER_AGENTS_DIR, 'projects');
|
|
|
96
96
|
// History bucket (durable).
|
|
97
97
|
const SESSIONS_DIR = path.join(HISTORY_DIR, 'sessions');
|
|
98
98
|
const SESSIONS_DB_PATH = path.join(SESSIONS_DIR, 'sessions.db');
|
|
99
|
+
const ANALYTICS_DIR = path.join(HISTORY_DIR, 'analytics');
|
|
99
100
|
const VERSIONS_DIR = path.join(HISTORY_DIR, 'versions');
|
|
100
101
|
const RUNS_DIR = path.join(HISTORY_DIR, 'runs');
|
|
101
102
|
// Durable per-monitor state-diff store + fire history (last-seen value/hash,
|
|
@@ -377,6 +378,18 @@ export function getUserSecretsDir() { return USER_SECRETS_DIR; }
|
|
|
377
378
|
export function getSecretsDbPath() {
|
|
378
379
|
return process.env.AGENTS_SECRETS_DB ?? path.join(USER_SECRETS_DIR, 'secrets.db');
|
|
379
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Path to the durable resource-usage warehouse (~/.agents/.history/analytics/usage.db).
|
|
383
|
+
* Value-free frequency/lifecycle events (secrets, agents, browser, …). Read at CALL
|
|
384
|
+
* time so AGENTS_USAGE_DB can redirect tests. Sync shards may also appear as
|
|
385
|
+
* usage.<machine-id>.db beside this default file.
|
|
386
|
+
*/
|
|
387
|
+
export function getAnalyticsDir() {
|
|
388
|
+
return process.env.AGENTS_ANALYTICS_DIR ?? ANALYTICS_DIR;
|
|
389
|
+
}
|
|
390
|
+
export function getUsageDbPath() {
|
|
391
|
+
return process.env.AGENTS_USAGE_DB ?? path.join(getAnalyticsDir(), 'usage.db');
|
|
392
|
+
}
|
|
380
393
|
export function getUserPromptcutsPath() { return USER_PROMPTCUTS_FILE; }
|
|
381
394
|
// ─── User operational path getters ────────────────────────────────────────────
|
|
382
395
|
//
|
|
@@ -459,10 +472,25 @@ export function getActivityDir() { return ACTIVITY_DIR; }
|
|
|
459
472
|
export function getVersionsDir() { return VERSIONS_DIR; }
|
|
460
473
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
|
461
474
|
export function getShimsDir() { return SHIMS_DIR; }
|
|
462
|
-
/**
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Path to generated per-hook caching/timing shims (~/.agents/.cache/shims/hooks/).
|
|
477
|
+
* Read at CALL time — since every hook now resolves through a shim (RUSH-2xxx,
|
|
478
|
+
* pass-through timing for matcher-only hooks), a test that registers hooks
|
|
479
|
+
* in-process (no subprocess HOME override) would otherwise write real shim
|
|
480
|
+
* files into the user's actual ~/.agents/.cache. AGENTS_HOOK_SHIMS_DIR mirrors
|
|
481
|
+
* the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR test-isolation escape hatches;
|
|
482
|
+
* never set in production code.
|
|
483
|
+
*/
|
|
484
|
+
export function getHookShimsDir() {
|
|
485
|
+
return process.env.AGENTS_HOOK_SHIMS_DIR ?? HOOK_SHIMS_DIR;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Path to per-hook stdout cache files (~/.agents/.cache/state/hooks/). Read at
|
|
489
|
+
* CALL time for the same reason as {@link getHookShimsDir} — see its doc.
|
|
490
|
+
*/
|
|
491
|
+
export function getHookCacheDir() {
|
|
492
|
+
return process.env.AGENTS_HOOK_CACHE_DIR ?? HOOK_CACHE_DIR;
|
|
493
|
+
}
|
|
466
494
|
/** Path to per-agent installed CLI binaries (~/.agents/.cache/bin/). */
|
|
467
495
|
export function getBinDir() { return BIN_DIR; }
|
|
468
496
|
/** Path to config backups (~/.agents/.history/backups/). */
|
|
@@ -487,7 +515,9 @@ export function getTrashDir() { return TRASH_DIR; }
|
|
|
487
515
|
/** Path to local session indexer storage (~/.agents/.history/sessions/). */
|
|
488
516
|
export function getSessionsDir() { return SESSIONS_DIR; }
|
|
489
517
|
/** Path to the session index database (~/.agents/.history/sessions/sessions.db). */
|
|
490
|
-
export function getSessionsDbPath() {
|
|
518
|
+
export function getSessionsDbPath() {
|
|
519
|
+
return process.env.AGENTS_SESSIONS_DB ?? SESSIONS_DB_PATH;
|
|
520
|
+
}
|
|
491
521
|
/** Path to teams config + registry (~/.agents/teams/). */
|
|
492
522
|
export function getTeamsDir() { return TEAMS_DIR; }
|
|
493
523
|
/** Path to teams execution history (~/.agents/.history/teams/agents/). */
|
|
@@ -517,17 +547,30 @@ export function getDevicesPendingDir() { return path.join(RUNTIME_STATE_DIR, 'de
|
|
|
517
547
|
export function getCloudDir() { return CLOUD_DIR; }
|
|
518
548
|
/** Path to terminal session metadata (~/.agents/.cache/terminals/). */
|
|
519
549
|
export function getTerminalsDir() { return TERMINALS_DIR; }
|
|
520
|
-
/**
|
|
521
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Path to runtime logs (~/.agents/.cache/logs/). Read at CALL time so
|
|
552
|
+
* AGENTS_LOGS_DIR can redirect it in tests — same test-isolation escape hatch
|
|
553
|
+
* as {@link getHookShimsDir}; never set in production code.
|
|
554
|
+
*/
|
|
555
|
+
export function getLogsDir() {
|
|
556
|
+
return process.env.AGENTS_LOGS_DIR ?? LOGS_DIR;
|
|
557
|
+
}
|
|
522
558
|
/**
|
|
523
559
|
* Path to disposable performance samples (~/.agents/.cache/perf/).
|
|
524
560
|
* Holds `perf.db` + a hook-shim spool. Loss is acceptable — wipe freely.
|
|
561
|
+
* Read at CALL time: AGENTS_PERF_DIR (the same override perf/db.ts and
|
|
562
|
+
* perf/spool.ts already honor for their own internal resolution) redirects
|
|
563
|
+
* this canonical getter too, so a caller that goes through it directly
|
|
564
|
+
* (hooks/cache.ts's shim generator, the OpenCode timeout sample writer in
|
|
565
|
+
* hooks.ts) doesn't leak samples into the user's real perf warehouse either.
|
|
525
566
|
*/
|
|
526
|
-
export function getPerfDir() {
|
|
567
|
+
export function getPerfDir() {
|
|
568
|
+
return process.env.AGENTS_PERF_DIR ?? PERF_DIR;
|
|
569
|
+
}
|
|
527
570
|
/** Path to the perf SQLite warehouse (~/.agents/.cache/perf/perf.db). */
|
|
528
|
-
export function getPerfDbPath() { return path.join(
|
|
571
|
+
export function getPerfDbPath() { return path.join(getPerfDir(), 'perf.db'); }
|
|
529
572
|
/** Path to the hook-shim NDJSON spool drained into perf.db on open. */
|
|
530
|
-
export function getPerfSpoolPath() { return path.join(
|
|
573
|
+
export function getPerfSpoolPath() { return path.join(getPerfDir(), 'spool.jsonl'); }
|
|
531
574
|
/** Path to per-process runtime state (~/.agents/.cache/state/). */
|
|
532
575
|
export function getRuntimeStateDir() { return RUNTIME_STATE_DIR; }
|
|
533
576
|
/** Path to companion-extension scratch (~/.agents/.cache/companion/). */
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -605,6 +605,20 @@ export interface DiscoveredPlugin {
|
|
|
605
605
|
* (e.g. workflow-scoped) — those default to the user marketplace on sync.
|
|
606
606
|
*/
|
|
607
607
|
marketplace?: string;
|
|
608
|
+
/**
|
|
609
|
+
* Absolute path to the DotAgents repo root containing this plugin — the
|
|
610
|
+
* grandparent of `root` (`<repo>/plugins/<name>` → `<repo>`), true for every
|
|
611
|
+
* marketplace kind (user/system/extra/project). DotAgents repos are
|
|
612
|
+
* git-tracked (plugins.ts), so this pairs with {@link snapshotSha}.
|
|
613
|
+
*/
|
|
614
|
+
repoRoot: string;
|
|
615
|
+
/**
|
|
616
|
+
* Short HEAD sha of `repoRoot`'s git checkout, lazily resolved (a getter,
|
|
617
|
+
* not computed at discovery time) and memoized per repoRoot
|
|
618
|
+
* (`git.ts` `resolveSnapshotSha`) — see `ResolvedResource.snapshotSha` for
|
|
619
|
+
* the identical rationale. `undefined` when `repoRoot` isn't a git repo.
|
|
620
|
+
*/
|
|
621
|
+
readonly snapshotSha: string | undefined;
|
|
608
622
|
}
|
|
609
623
|
/**
|
|
610
624
|
* Identifies one DotAgents repo that contributes a plugin marketplace. Each
|
|
@@ -771,10 +785,14 @@ export interface Meta {
|
|
|
771
785
|
/** Spend guardrails (issue #346). User-global caps; project agents.yaml overrides. */
|
|
772
786
|
budget?: BudgetConfig;
|
|
773
787
|
/**
|
|
774
|
-
* `agents feed post` fan-out. `broadcast` maps a sink name to
|
|
775
|
-
* run for each post
|
|
776
|
-
*
|
|
777
|
-
*
|
|
788
|
+
* `agents feed post` fan-out. `broadcast` maps a sink name to either an argv
|
|
789
|
+
* template (`command:`, run for each post) or an in-process channel delivery
|
|
790
|
+
* (`channel:`, the same registry `agents send`/`agents notify` use), so
|
|
791
|
+
* mirroring to a tracker, a messaging CLI, or a channel provider is the
|
|
792
|
+
* operator's config rather than an integration compiled into this CLI. When
|
|
793
|
+
* this is unset/empty, an important-level post falls back to `notify.owner`
|
|
794
|
+
* implicitly (RUSH-2123) — see lib/feed-broadcast.ts and
|
|
795
|
+
* docs/06-observability.md.
|
|
778
796
|
*/
|
|
779
797
|
feed?: {
|
|
780
798
|
broadcast?: FeedBroadcastConfig;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type UsageHeadroom, type UsageSnapshot, type UsageInfo } from './usage.js';
|
|
2
|
+
import type { AgentId } from './types.js';
|
|
3
|
+
/** Floor on the adaptive interval: an account seconds from its cap still isn't
|
|
4
|
+
* polled faster than this. */
|
|
5
|
+
export declare const REFRESH_MIN_MS: number;
|
|
6
|
+
/** Ceiling on the adaptive interval: an idle account is still re-checked at
|
|
7
|
+
* least this often so a cache never silently rots. */
|
|
8
|
+
export declare const REFRESH_MAX_MS: number;
|
|
9
|
+
/** Schedule the next refresh at `minutesToLimit / K` — poll well before the cap,
|
|
10
|
+
* not exactly at it. */
|
|
11
|
+
export declare const REFRESH_BURN_DIVISOR = 4;
|
|
12
|
+
/** At most this many live fetches per account per rolling hour. */
|
|
13
|
+
export declare const HOURLY_CALL_CAP = 6;
|
|
14
|
+
/**
|
|
15
|
+
* One account's refresh state + published headroom. `sessionUsedPercent` /
|
|
16
|
+
* `capturedAt` are the prior sample the NEXT tick projects the burn rate from;
|
|
17
|
+
* `minutesToLimit` / `status` are what the routing hot path reads.
|
|
18
|
+
*/
|
|
19
|
+
export interface HeadroomEntry {
|
|
20
|
+
status: UsageHeadroom['status'];
|
|
21
|
+
minutesToLimit: number | null;
|
|
22
|
+
/** The session window's usedPercent in the last snapshot (the prev sample). */
|
|
23
|
+
sessionUsedPercent: number | null;
|
|
24
|
+
/** Epoch ms the last snapshot was captured. */
|
|
25
|
+
capturedAt: number | null;
|
|
26
|
+
/** Epoch ms this account is next due for a live refresh. */
|
|
27
|
+
nextRefreshAt: number;
|
|
28
|
+
/** Epoch ms of recent live fetches, for the rolling-hour cap. */
|
|
29
|
+
callTimestamps: number[];
|
|
30
|
+
/** Epoch ms this entry was written. */
|
|
31
|
+
computedAt: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function setHeadroomCachePathForTest(cachePath: string | null): string | null;
|
|
34
|
+
/** Read the whole headroom cache (best-effort; missing/corrupt → empty map). */
|
|
35
|
+
export declare function readHeadroomCache(): Record<string, HeadroomEntry>;
|
|
36
|
+
/** Read one account's headroom entry, or null. */
|
|
37
|
+
export declare function readHeadroomEntry(usageKey: string): HeadroomEntry | null;
|
|
38
|
+
/** Merge entries into the cache (best-effort; preserves other accounts' rows). */
|
|
39
|
+
export declare function writeHeadroomEntries(entries: Record<string, HeadroomEntry>): void;
|
|
40
|
+
/**
|
|
41
|
+
* The adaptive interval until the next refresh, clamped to [MIN, MAX]. A
|
|
42
|
+
* shorter `minutesToLimit` (closer to the cap) polls sooner; `null` (unknown /
|
|
43
|
+
* idle / not burning) waits the full ceiling.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeNextRefreshDelayMs(minutesToLimit: number | null, opts?: {
|
|
46
|
+
minMs?: number;
|
|
47
|
+
maxMs?: number;
|
|
48
|
+
divisor?: number;
|
|
49
|
+
}): number;
|
|
50
|
+
/** Recent call timestamps trimmed to the trailing hour. */
|
|
51
|
+
export declare function pruneCallTimestamps(timestamps: number[], now: number, windowMs?: number): number[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether an account may be live-refreshed right now: it is due (past its
|
|
54
|
+
* scheduled `nextRefreshAt`) AND under the rolling-hour call cap. Pure so the
|
|
55
|
+
* cadence + cap arithmetic is unit-tested without a daemon or a network call.
|
|
56
|
+
*/
|
|
57
|
+
export declare function shouldRefreshAccount(entry: HeadroomEntry | null | undefined, now: number, opts?: {
|
|
58
|
+
hourlyCap?: number;
|
|
59
|
+
windowMs?: number;
|
|
60
|
+
}): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Build the next headroom entry after a live refresh: project headroom from the
|
|
63
|
+
* new snapshot against the prior sample, schedule the next refresh from the
|
|
64
|
+
* projection, and record this call for the hourly cap.
|
|
65
|
+
*/
|
|
66
|
+
export declare function nextHeadroomEntry(prev: HeadroomEntry | null | undefined, snapshot: UsageSnapshot | null, now: number): HeadroomEntry;
|
|
67
|
+
/** An account whose credentials live on THIS host — the only ones we refresh. */
|
|
68
|
+
export interface LocalUsageAccount {
|
|
69
|
+
usageKey: string;
|
|
70
|
+
agentId: AgentId;
|
|
71
|
+
/** Live-fetch this account's usage; the daemon passes the real network fetch. */
|
|
72
|
+
fetch: () => Promise<UsageInfo>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Enumerate the network-usage accounts whose credentials live on THIS host — one
|
|
76
|
+
* per unique usage key, deduped to the most-recently-active version (the same
|
|
77
|
+
* canonicalization `getUsageInfoByIdentity` uses). Each carries a closure that
|
|
78
|
+
* live-fetches its usage. This is the daemon's `listAccounts`; because it only
|
|
79
|
+
* ever lists local, signed-in accounts, each host is the sole writer for its own
|
|
80
|
+
* accounts' caches — no cross-host coordination.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildLocalUsageAccounts(): Promise<LocalUsageAccount[]>;
|
|
83
|
+
/** Injectable side effects, so `runUsageRefresh` is drivable without the daemon. */
|
|
84
|
+
export interface UsageRefreshDeps {
|
|
85
|
+
now?: number;
|
|
86
|
+
/** Local-credential accounts to consider (one per unique usage key). */
|
|
87
|
+
listAccounts: () => Promise<LocalUsageAccount[]>;
|
|
88
|
+
/** Persist a fresh snapshot to the usage cache (writeClaudeUsageCache). */
|
|
89
|
+
writeUsageCache: (usageKey: string, snapshot: UsageSnapshot) => void;
|
|
90
|
+
/** Epoch ms a provider is backed off until, or null when free (usageRateLimitedUntil). */
|
|
91
|
+
backoffUntil: (agentId: AgentId) => number | null;
|
|
92
|
+
}
|
|
93
|
+
export interface UsageRefreshResult {
|
|
94
|
+
refreshed: number;
|
|
95
|
+
skippedNotDue: number;
|
|
96
|
+
skippedBackoff: number;
|
|
97
|
+
skippedCap: number;
|
|
98
|
+
failed: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* One refresher tick: for each local account that is due, under its hourly cap,
|
|
102
|
+
* and not provider-backed-off, live-fetch its usage, update the cache, and
|
|
103
|
+
* reschedule from the new burn projection. Never throws — a single account's
|
|
104
|
+
* failed fetch leaves its cache untouched and counts as `failed`.
|
|
105
|
+
*/
|
|
106
|
+
export declare function runUsageRefresh(deps: UsageRefreshDeps): Promise<UsageRefreshResult>;
|