@os-eco/overstory-cli 0.7.7 → 0.7.9
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/README.md +105 -3
- package/package.json +1 -1
- package/src/agents/manifest.test.ts +168 -1
- package/src/agents/manifest.ts +23 -2
- package/src/commands/agents.ts +1 -0
- package/src/commands/coordinator.test.ts +131 -2
- package/src/commands/coordinator.ts +40 -9
- package/src/commands/costs.test.ts +5 -0
- package/src/commands/costs.ts +1 -1
- package/src/commands/init.test.ts +1 -0
- package/src/commands/init.ts +1 -0
- package/src/commands/log.ts +2 -0
- package/src/commands/prime.test.ts +1 -0
- package/src/commands/sling.test.ts +63 -1
- package/src/commands/sling.ts +37 -2
- package/src/config.test.ts +68 -0
- package/src/config.ts +16 -0
- package/src/doctor/structure.test.ts +1 -0
- package/src/doctor/structure.ts +1 -0
- package/src/index.ts +2 -1
- package/src/metrics/pricing.test.ts +258 -0
- package/src/metrics/store.test.ts +227 -0
- package/src/metrics/store.ts +40 -5
- package/src/runtimes/gemini.test.ts +537 -0
- package/src/runtimes/gemini.ts +235 -0
- package/src/runtimes/registry.test.ts +15 -1
- package/src/runtimes/registry.ts +2 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/types.ts +8 -0
- package/src/worktree/tmux.test.ts +49 -0
- package/src/worktree/tmux.ts +33 -0
|
@@ -535,6 +535,7 @@ describe("token snapshots", () => {
|
|
|
535
535
|
cacheCreationTokens: 100,
|
|
536
536
|
estimatedCostUsd: 0.15,
|
|
537
537
|
modelUsed: "claude-sonnet-4-5",
|
|
538
|
+
runId: null,
|
|
538
539
|
createdAt: new Date().toISOString(),
|
|
539
540
|
};
|
|
540
541
|
|
|
@@ -558,6 +559,7 @@ describe("token snapshots", () => {
|
|
|
558
559
|
cacheCreationTokens: 0,
|
|
559
560
|
estimatedCostUsd: 0.01,
|
|
560
561
|
modelUsed: "claude-sonnet-4-5",
|
|
562
|
+
runId: null,
|
|
561
563
|
createdAt: new Date(now - 60_000).toISOString(), // 1 min ago
|
|
562
564
|
});
|
|
563
565
|
|
|
@@ -569,6 +571,7 @@ describe("token snapshots", () => {
|
|
|
569
571
|
cacheCreationTokens: 0,
|
|
570
572
|
estimatedCostUsd: 0.02,
|
|
571
573
|
modelUsed: "claude-sonnet-4-5",
|
|
574
|
+
runId: null,
|
|
572
575
|
createdAt: new Date(now).toISOString(), // now (most recent)
|
|
573
576
|
});
|
|
574
577
|
|
|
@@ -580,6 +583,7 @@ describe("token snapshots", () => {
|
|
|
580
583
|
cacheCreationTokens: 0,
|
|
581
584
|
estimatedCostUsd: 0.03,
|
|
582
585
|
modelUsed: "claude-sonnet-4-5",
|
|
586
|
+
runId: null,
|
|
583
587
|
createdAt: new Date(now - 30_000).toISOString(), // 30s ago
|
|
584
588
|
});
|
|
585
589
|
|
|
@@ -606,6 +610,7 @@ describe("token snapshots", () => {
|
|
|
606
610
|
cacheCreationTokens: 0,
|
|
607
611
|
estimatedCostUsd: null,
|
|
608
612
|
modelUsed: null,
|
|
613
|
+
runId: null,
|
|
609
614
|
createdAt: time1,
|
|
610
615
|
});
|
|
611
616
|
|
|
@@ -617,6 +622,7 @@ describe("token snapshots", () => {
|
|
|
617
622
|
cacheCreationTokens: 0,
|
|
618
623
|
estimatedCostUsd: null,
|
|
619
624
|
modelUsed: null,
|
|
625
|
+
runId: null,
|
|
620
626
|
createdAt: time2,
|
|
621
627
|
});
|
|
622
628
|
|
|
@@ -638,6 +644,7 @@ describe("token snapshots", () => {
|
|
|
638
644
|
cacheCreationTokens: 0,
|
|
639
645
|
estimatedCostUsd: null,
|
|
640
646
|
modelUsed: null,
|
|
647
|
+
runId: null,
|
|
641
648
|
createdAt: new Date().toISOString(),
|
|
642
649
|
});
|
|
643
650
|
|
|
@@ -649,6 +656,7 @@ describe("token snapshots", () => {
|
|
|
649
656
|
cacheCreationTokens: 0,
|
|
650
657
|
estimatedCostUsd: null,
|
|
651
658
|
modelUsed: null,
|
|
659
|
+
runId: null,
|
|
652
660
|
createdAt: new Date().toISOString(),
|
|
653
661
|
});
|
|
654
662
|
|
|
@@ -666,6 +674,7 @@ describe("token snapshots", () => {
|
|
|
666
674
|
cacheCreationTokens: 0,
|
|
667
675
|
estimatedCostUsd: null,
|
|
668
676
|
modelUsed: null,
|
|
677
|
+
runId: null,
|
|
669
678
|
createdAt: new Date().toISOString(),
|
|
670
679
|
});
|
|
671
680
|
|
|
@@ -677,6 +686,7 @@ describe("token snapshots", () => {
|
|
|
677
686
|
cacheCreationTokens: 0,
|
|
678
687
|
estimatedCostUsd: null,
|
|
679
688
|
modelUsed: null,
|
|
689
|
+
runId: null,
|
|
680
690
|
createdAt: new Date().toISOString(),
|
|
681
691
|
});
|
|
682
692
|
|
|
@@ -698,6 +708,7 @@ describe("token snapshots", () => {
|
|
|
698
708
|
cacheCreationTokens: 0,
|
|
699
709
|
estimatedCostUsd: null,
|
|
700
710
|
modelUsed: null,
|
|
711
|
+
runId: null,
|
|
701
712
|
createdAt: new Date(now - 120_000).toISOString(), // 2 min ago
|
|
702
713
|
});
|
|
703
714
|
|
|
@@ -709,6 +720,7 @@ describe("token snapshots", () => {
|
|
|
709
720
|
cacheCreationTokens: 0,
|
|
710
721
|
estimatedCostUsd: null,
|
|
711
722
|
modelUsed: null,
|
|
723
|
+
runId: null,
|
|
712
724
|
createdAt: new Date(now - 10_000).toISOString(), // 10s ago (recent)
|
|
713
725
|
});
|
|
714
726
|
|
|
@@ -729,6 +741,7 @@ describe("token snapshots", () => {
|
|
|
729
741
|
cacheCreationTokens: 0,
|
|
730
742
|
estimatedCostUsd: null,
|
|
731
743
|
modelUsed: null,
|
|
744
|
+
runId: null,
|
|
732
745
|
createdAt: new Date().toISOString(),
|
|
733
746
|
});
|
|
734
747
|
|
|
@@ -740,6 +753,220 @@ describe("token snapshots", () => {
|
|
|
740
753
|
expect(snapshots).toHaveLength(1);
|
|
741
754
|
expect(snapshots[0]?.agentName).toBe("test-agent");
|
|
742
755
|
});
|
|
756
|
+
|
|
757
|
+
test("runId roundtrips correctly through snapshot record and retrieval", () => {
|
|
758
|
+
const now = Date.now();
|
|
759
|
+
store.recordSnapshot({
|
|
760
|
+
agentName: "agent-a",
|
|
761
|
+
inputTokens: 100,
|
|
762
|
+
outputTokens: 50,
|
|
763
|
+
cacheReadTokens: 0,
|
|
764
|
+
cacheCreationTokens: 0,
|
|
765
|
+
estimatedCostUsd: null,
|
|
766
|
+
modelUsed: null,
|
|
767
|
+
runId: "run-abc",
|
|
768
|
+
createdAt: new Date(now).toISOString(),
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
store.recordSnapshot({
|
|
772
|
+
agentName: "agent-b",
|
|
773
|
+
inputTokens: 200,
|
|
774
|
+
outputTokens: 100,
|
|
775
|
+
cacheReadTokens: 0,
|
|
776
|
+
cacheCreationTokens: 0,
|
|
777
|
+
estimatedCostUsd: null,
|
|
778
|
+
modelUsed: null,
|
|
779
|
+
runId: null,
|
|
780
|
+
createdAt: new Date(now).toISOString(),
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
const snapshots = store.getLatestSnapshots();
|
|
784
|
+
const agentA = snapshots.find((s) => s.agentName === "agent-a");
|
|
785
|
+
const agentB = snapshots.find((s) => s.agentName === "agent-b");
|
|
786
|
+
|
|
787
|
+
expect(agentA?.runId).toBe("run-abc");
|
|
788
|
+
expect(agentB?.runId).toBeNull();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("getLatestSnapshots(runId) returns only snapshots matching that run", () => {
|
|
792
|
+
const now = Date.now();
|
|
793
|
+
store.recordSnapshot({
|
|
794
|
+
agentName: "agent-a",
|
|
795
|
+
inputTokens: 100,
|
|
796
|
+
outputTokens: 50,
|
|
797
|
+
cacheReadTokens: 0,
|
|
798
|
+
cacheCreationTokens: 0,
|
|
799
|
+
estimatedCostUsd: null,
|
|
800
|
+
modelUsed: null,
|
|
801
|
+
runId: "run-001",
|
|
802
|
+
createdAt: new Date(now).toISOString(),
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
store.recordSnapshot({
|
|
806
|
+
agentName: "agent-b",
|
|
807
|
+
inputTokens: 200,
|
|
808
|
+
outputTokens: 100,
|
|
809
|
+
cacheReadTokens: 0,
|
|
810
|
+
cacheCreationTokens: 0,
|
|
811
|
+
estimatedCostUsd: null,
|
|
812
|
+
modelUsed: null,
|
|
813
|
+
runId: "run-001",
|
|
814
|
+
createdAt: new Date(now).toISOString(),
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
store.recordSnapshot({
|
|
818
|
+
agentName: "agent-c",
|
|
819
|
+
inputTokens: 300,
|
|
820
|
+
outputTokens: 150,
|
|
821
|
+
cacheReadTokens: 0,
|
|
822
|
+
cacheCreationTokens: 0,
|
|
823
|
+
estimatedCostUsd: null,
|
|
824
|
+
modelUsed: null,
|
|
825
|
+
runId: "run-002",
|
|
826
|
+
createdAt: new Date(now).toISOString(),
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const run001Snapshots = store.getLatestSnapshots("run-001");
|
|
830
|
+
expect(run001Snapshots).toHaveLength(2);
|
|
831
|
+
expect(run001Snapshots.every((s) => s.runId === "run-001")).toBe(true);
|
|
832
|
+
|
|
833
|
+
const run002Snapshots = store.getLatestSnapshots("run-002");
|
|
834
|
+
expect(run002Snapshots).toHaveLength(1);
|
|
835
|
+
expect(run002Snapshots[0]?.agentName).toBe("agent-c");
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
test("getLatestSnapshots(runId) returns empty array for unknown run", () => {
|
|
839
|
+
store.recordSnapshot({
|
|
840
|
+
agentName: "agent-a",
|
|
841
|
+
inputTokens: 100,
|
|
842
|
+
outputTokens: 50,
|
|
843
|
+
cacheReadTokens: 0,
|
|
844
|
+
cacheCreationTokens: 0,
|
|
845
|
+
estimatedCostUsd: null,
|
|
846
|
+
modelUsed: null,
|
|
847
|
+
runId: "run-001",
|
|
848
|
+
createdAt: new Date().toISOString(),
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
const snapshots = store.getLatestSnapshots("run-nonexistent");
|
|
852
|
+
expect(snapshots).toEqual([]);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
test("getLatestSnapshots(runId) excludes snapshots with null run_id", () => {
|
|
856
|
+
const now = Date.now();
|
|
857
|
+
store.recordSnapshot({
|
|
858
|
+
agentName: "agent-a",
|
|
859
|
+
inputTokens: 100,
|
|
860
|
+
outputTokens: 50,
|
|
861
|
+
cacheReadTokens: 0,
|
|
862
|
+
cacheCreationTokens: 0,
|
|
863
|
+
estimatedCostUsd: null,
|
|
864
|
+
modelUsed: null,
|
|
865
|
+
runId: null, // no run
|
|
866
|
+
createdAt: new Date(now).toISOString(),
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
store.recordSnapshot({
|
|
870
|
+
agentName: "agent-b",
|
|
871
|
+
inputTokens: 200,
|
|
872
|
+
outputTokens: 100,
|
|
873
|
+
cacheReadTokens: 0,
|
|
874
|
+
cacheCreationTokens: 0,
|
|
875
|
+
estimatedCostUsd: null,
|
|
876
|
+
modelUsed: null,
|
|
877
|
+
runId: "run-001",
|
|
878
|
+
createdAt: new Date(now).toISOString(),
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
const run001Snapshots = store.getLatestSnapshots("run-001");
|
|
882
|
+
expect(run001Snapshots).toHaveLength(1);
|
|
883
|
+
expect(run001Snapshots[0]?.agentName).toBe("agent-b");
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test("getLatestSnapshots(runId) returns latest per agent within the run", () => {
|
|
887
|
+
const now = Date.now();
|
|
888
|
+
// Two snapshots for agent-a in run-001: should only get the latest
|
|
889
|
+
store.recordSnapshot({
|
|
890
|
+
agentName: "agent-a",
|
|
891
|
+
inputTokens: 100,
|
|
892
|
+
outputTokens: 50,
|
|
893
|
+
cacheReadTokens: 0,
|
|
894
|
+
cacheCreationTokens: 0,
|
|
895
|
+
estimatedCostUsd: null,
|
|
896
|
+
modelUsed: null,
|
|
897
|
+
runId: "run-001",
|
|
898
|
+
createdAt: new Date(now - 30_000).toISOString(), // older
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
store.recordSnapshot({
|
|
902
|
+
agentName: "agent-a",
|
|
903
|
+
inputTokens: 500,
|
|
904
|
+
outputTokens: 250,
|
|
905
|
+
cacheReadTokens: 0,
|
|
906
|
+
cacheCreationTokens: 0,
|
|
907
|
+
estimatedCostUsd: null,
|
|
908
|
+
modelUsed: null,
|
|
909
|
+
runId: "run-001",
|
|
910
|
+
createdAt: new Date(now).toISOString(), // latest
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
const snapshots = store.getLatestSnapshots("run-001");
|
|
914
|
+
expect(snapshots).toHaveLength(1);
|
|
915
|
+
expect(snapshots[0]?.inputTokens).toBe(500); // most recent
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
test("migration adds run_id to existing token_snapshots table", () => {
|
|
919
|
+
store.close();
|
|
920
|
+
|
|
921
|
+
// Create a DB with old token_snapshots schema (no run_id column)
|
|
922
|
+
const { Database } = require("bun:sqlite");
|
|
923
|
+
const oldDb = new Database(dbPath);
|
|
924
|
+
oldDb.exec("DROP TABLE IF EXISTS token_snapshots");
|
|
925
|
+
oldDb.exec(`
|
|
926
|
+
CREATE TABLE token_snapshots (
|
|
927
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
928
|
+
agent_name TEXT NOT NULL,
|
|
929
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
930
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
931
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
932
|
+
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
|
|
933
|
+
estimated_cost_usd REAL,
|
|
934
|
+
model_used TEXT,
|
|
935
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
936
|
+
)
|
|
937
|
+
`);
|
|
938
|
+
oldDb.exec(`
|
|
939
|
+
INSERT INTO token_snapshots (agent_name, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, created_at)
|
|
940
|
+
VALUES ('old-agent', 100, 50, 0, 0, '2026-01-01T00:00:00.000Z')
|
|
941
|
+
`);
|
|
942
|
+
oldDb.close();
|
|
943
|
+
|
|
944
|
+
// Re-open with createMetricsStore which should migrate
|
|
945
|
+
store = createMetricsStore(dbPath);
|
|
946
|
+
|
|
947
|
+
// Old row should be readable with null run_id
|
|
948
|
+
const snapshots = store.getLatestSnapshots();
|
|
949
|
+
expect(snapshots).toHaveLength(1);
|
|
950
|
+
expect(snapshots[0]?.agentName).toBe("old-agent");
|
|
951
|
+
expect(snapshots[0]?.runId).toBeNull();
|
|
952
|
+
|
|
953
|
+
// New rows with run_id should work
|
|
954
|
+
store.recordSnapshot({
|
|
955
|
+
agentName: "new-agent",
|
|
956
|
+
inputTokens: 200,
|
|
957
|
+
outputTokens: 100,
|
|
958
|
+
cacheReadTokens: 0,
|
|
959
|
+
cacheCreationTokens: 0,
|
|
960
|
+
estimatedCostUsd: null,
|
|
961
|
+
modelUsed: null,
|
|
962
|
+
runId: "run-xyz",
|
|
963
|
+
createdAt: new Date().toISOString(),
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
const newSnapshots = store.getLatestSnapshots("run-xyz");
|
|
967
|
+
expect(newSnapshots).toHaveLength(1);
|
|
968
|
+
expect(newSnapshots[0]?.runId).toBe("run-xyz");
|
|
969
|
+
});
|
|
743
970
|
});
|
|
744
971
|
|
|
745
972
|
// === close ===
|
package/src/metrics/store.ts
CHANGED
|
@@ -21,8 +21,9 @@ export interface MetricsStore {
|
|
|
21
21
|
purge(options: { all?: boolean; agent?: string }): number;
|
|
22
22
|
/** Record a token usage snapshot for a running agent. */
|
|
23
23
|
recordSnapshot(snapshot: TokenSnapshot): void;
|
|
24
|
-
/** Get the most recent snapshot per active agent (one row per agent).
|
|
25
|
-
|
|
24
|
+
/** Get the most recent snapshot per active agent (one row per agent).
|
|
25
|
+
* When runId is provided, restricts to snapshots recorded for that run. */
|
|
26
|
+
getLatestSnapshots(runId?: string): TokenSnapshot[];
|
|
26
27
|
/** Get the timestamp of the most recent snapshot for an agent, or null. */
|
|
27
28
|
getLatestSnapshotTime(agentName: string): string | null;
|
|
28
29
|
/** Delete snapshots matching criteria. Returns number of rows deleted. */
|
|
@@ -60,6 +61,7 @@ interface SnapshotRow {
|
|
|
60
61
|
cache_creation_tokens: number;
|
|
61
62
|
estimated_cost_usd: number | null;
|
|
62
63
|
model_used: string | null;
|
|
64
|
+
run_id: string | null;
|
|
63
65
|
created_at: string;
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -94,6 +96,7 @@ CREATE TABLE IF NOT EXISTS token_snapshots (
|
|
|
94
96
|
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
|
|
95
97
|
estimated_cost_usd REAL,
|
|
96
98
|
model_used TEXT,
|
|
99
|
+
run_id TEXT,
|
|
97
100
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
98
101
|
)`;
|
|
99
102
|
|
|
@@ -136,6 +139,18 @@ function migrateRunIdColumn(db: Database): void {
|
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Migrate an existing token_snapshots table to include the run_id column.
|
|
144
|
+
* Safe to call multiple times — only adds the column if missing.
|
|
145
|
+
*/
|
|
146
|
+
function migrateSnapshotRunIdColumn(db: Database): void {
|
|
147
|
+
const rows = db.prepare("PRAGMA table_info(token_snapshots)").all() as Array<{ name: string }>;
|
|
148
|
+
const existingColumns = new Set(rows.map((r) => r.name));
|
|
149
|
+
if (!existingColumns.has("run_id")) {
|
|
150
|
+
db.exec("ALTER TABLE token_snapshots ADD COLUMN run_id TEXT");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
139
154
|
/**
|
|
140
155
|
* Migrate an existing sessions table to include token columns.
|
|
141
156
|
* Safe to call multiple times — only adds columns that are missing.
|
|
@@ -183,6 +198,7 @@ function rowToSnapshot(row: SnapshotRow): TokenSnapshot {
|
|
|
183
198
|
cacheCreationTokens: row.cache_creation_tokens,
|
|
184
199
|
estimatedCostUsd: row.estimated_cost_usd,
|
|
185
200
|
modelUsed: row.model_used,
|
|
201
|
+
runId: row.run_id,
|
|
186
202
|
createdAt: row.created_at,
|
|
187
203
|
};
|
|
188
204
|
}
|
|
@@ -210,6 +226,7 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
210
226
|
migrateBeadIdToTaskId(db);
|
|
211
227
|
migrateTokenColumns(db);
|
|
212
228
|
migrateRunIdColumn(db);
|
|
229
|
+
migrateSnapshotRunIdColumn(db);
|
|
213
230
|
|
|
214
231
|
// Prepare statements for all queries
|
|
215
232
|
const insertStmt = db.prepare<
|
|
@@ -282,13 +299,14 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
282
299
|
$cache_creation_tokens: number;
|
|
283
300
|
$estimated_cost_usd: number | null;
|
|
284
301
|
$model_used: string | null;
|
|
302
|
+
$run_id: string | null;
|
|
285
303
|
$created_at: string;
|
|
286
304
|
}
|
|
287
305
|
>(`
|
|
288
306
|
INSERT INTO token_snapshots
|
|
289
|
-
(agent_name, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, estimated_cost_usd, model_used, created_at)
|
|
307
|
+
(agent_name, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, estimated_cost_usd, model_used, run_id, created_at)
|
|
290
308
|
VALUES
|
|
291
|
-
($agent_name, $input_tokens, $output_tokens, $cache_read_tokens, $cache_creation_tokens, $estimated_cost_usd, $model_used, $created_at)
|
|
309
|
+
($agent_name, $input_tokens, $output_tokens, $cache_read_tokens, $cache_creation_tokens, $estimated_cost_usd, $model_used, $run_id, $created_at)
|
|
292
310
|
`);
|
|
293
311
|
|
|
294
312
|
const latestSnapshotsStmt = db.prepare<SnapshotRow, Record<string, never>>(`
|
|
@@ -301,6 +319,18 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
301
319
|
) latest ON s.agent_name = latest.agent_name AND s.created_at = latest.max_created_at
|
|
302
320
|
`);
|
|
303
321
|
|
|
322
|
+
const latestSnapshotsByRunStmt = db.prepare<SnapshotRow, { $run_id: string }>(`
|
|
323
|
+
SELECT s.*
|
|
324
|
+
FROM token_snapshots s
|
|
325
|
+
INNER JOIN (
|
|
326
|
+
SELECT agent_name, MAX(created_at) as max_created_at
|
|
327
|
+
FROM token_snapshots
|
|
328
|
+
WHERE run_id = $run_id
|
|
329
|
+
GROUP BY agent_name
|
|
330
|
+
) latest ON s.agent_name = latest.agent_name AND s.created_at = latest.max_created_at
|
|
331
|
+
WHERE s.run_id = $run_id
|
|
332
|
+
`);
|
|
333
|
+
|
|
304
334
|
const latestSnapshotTimeStmt = db.prepare<
|
|
305
335
|
{ created_at: string } | null,
|
|
306
336
|
{ $agent_name: string }
|
|
@@ -401,11 +431,16 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
401
431
|
$cache_creation_tokens: snapshot.cacheCreationTokens,
|
|
402
432
|
$estimated_cost_usd: snapshot.estimatedCostUsd,
|
|
403
433
|
$model_used: snapshot.modelUsed,
|
|
434
|
+
$run_id: snapshot.runId,
|
|
404
435
|
$created_at: snapshot.createdAt,
|
|
405
436
|
});
|
|
406
437
|
},
|
|
407
438
|
|
|
408
|
-
getLatestSnapshots(): TokenSnapshot[] {
|
|
439
|
+
getLatestSnapshots(runId?: string): TokenSnapshot[] {
|
|
440
|
+
if (runId !== undefined) {
|
|
441
|
+
const rows = latestSnapshotsByRunStmt.all({ $run_id: runId });
|
|
442
|
+
return rows.map(rowToSnapshot);
|
|
443
|
+
}
|
|
409
444
|
const rows = latestSnapshotsStmt.all({});
|
|
410
445
|
return rows.map(rowToSnapshot);
|
|
411
446
|
},
|