hungry-ghost-hive 0.52.0 → 0.52.2
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/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +4 -2
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/manager/token-capture.d.ts.map +1 -1
- package/dist/cli/commands/manager/token-capture.js +6 -3
- package/dist/cli/commands/manager/token-capture.js.map +1 -1
- package/dist/cli/commands/msg.d.ts.map +1 -1
- package/dist/cli/commands/msg.js +7 -2
- package/dist/cli/commands/msg.js.map +1 -1
- package/dist/cli/commands/msg.test.js +28 -0
- package/dist/cli/commands/msg.test.js.map +1 -1
- package/dist/cli/commands/status.js +4 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tokens.js +2 -2
- package/dist/cli/commands/tokens.js.map +1 -1
- package/dist/db/client.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/postgres-provider.d.ts +19 -0
- package/dist/db/postgres-provider.d.ts.map +1 -1
- package/dist/db/postgres-provider.js +10 -3
- package/dist/db/postgres-provider.js.map +1 -1
- package/dist/db/postgres-provider.test.js +54 -1
- package/dist/db/postgres-provider.test.js.map +1 -1
- package/dist/db/queries/token-usage.d.ts.map +1 -1
- package/dist/db/queries/token-usage.js +10 -2
- package/dist/db/queries/token-usage.js.map +1 -1
- package/dist/parsers/token-usage-parser.d.ts.map +1 -1
- package/dist/parsers/token-usage-parser.js +33 -4
- package/dist/parsers/token-usage-parser.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/agents.ts +6 -2
- package/src/cli/commands/manager/token-capture.ts +9 -3
- package/src/cli/commands/msg.test.ts +30 -0
- package/src/cli/commands/msg.ts +9 -4
- package/src/cli/commands/status.ts +6 -2
- package/src/cli/commands/tokens.ts +2 -2
- package/src/db/client.ts +1 -1
- package/src/db/postgres-provider.test.ts +86 -1
- package/src/db/postgres-provider.ts +12 -3
- package/src/db/queries/token-usage.ts +14 -8
- package/src/parsers/token-usage-parser.ts +36 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { convertParams } from './postgres-provider.js';
|
|
3
|
+
import { convertParams, injectInsertWorkspaceId, injectWhereWorkspaceId, } from './postgres-provider.js';
|
|
4
4
|
describe('PostgresProvider utilities', () => {
|
|
5
5
|
describe('convertParams', () => {
|
|
6
6
|
it('should convert ? to $1, $2, $3', () => {
|
|
@@ -52,6 +52,59 @@ describe('PostgresProvider utilities', () => {
|
|
|
52
52
|
expect(expectedTables).toHaveLength(10);
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
describe('injectWhereWorkspaceId', () => {
|
|
56
|
+
const ws = 'ws-123';
|
|
57
|
+
it('should inject WHERE clause when none exists', () => {
|
|
58
|
+
const result = injectWhereWorkspaceId('SELECT * FROM agent_logs ORDER BY timestamp DESC LIMIT ?', ws, [50]);
|
|
59
|
+
expect(result.sql).toContain('WHERE workspace_id = ?');
|
|
60
|
+
expect(result.sql).toContain('ORDER BY timestamp DESC LIMIT ?');
|
|
61
|
+
expect(result.params).toEqual([ws, 50]);
|
|
62
|
+
});
|
|
63
|
+
it('should append to existing WHERE with correct param order before LIMIT', () => {
|
|
64
|
+
const result = injectWhereWorkspaceId('SELECT * FROM agent_logs WHERE agent_id = ? ORDER BY timestamp DESC LIMIT ?', ws, ['agent-1', 100]);
|
|
65
|
+
expect(result.sql).toBe('SELECT * FROM agent_logs WHERE agent_id = ? AND workspace_id = ? ORDER BY timestamp DESC LIMIT ?');
|
|
66
|
+
expect(result.params).toEqual(['agent-1', ws, 100]);
|
|
67
|
+
});
|
|
68
|
+
it('should handle WHERE with no trailing clauses', () => {
|
|
69
|
+
const result = injectWhereWorkspaceId('SELECT * FROM agent_logs WHERE story_id = ?', ws, [
|
|
70
|
+
'story-1',
|
|
71
|
+
]);
|
|
72
|
+
expect(result.sql).toBe('SELECT * FROM agent_logs WHERE story_id = ? AND workspace_id = ?');
|
|
73
|
+
expect(result.params).toEqual(['story-1', ws]);
|
|
74
|
+
});
|
|
75
|
+
it('should handle WHERE with multiple conditions and LIMIT', () => {
|
|
76
|
+
const result = injectWhereWorkspaceId("SELECT COUNT(*) as count FROM agent_logs WHERE story_id = ? AND event_type = 'STORY_QA_FAILED'", ws, ['story-1']);
|
|
77
|
+
expect(result.sql).toContain('AND workspace_id = ?');
|
|
78
|
+
expect(result.params).toEqual(['story-1', ws]);
|
|
79
|
+
});
|
|
80
|
+
it('should handle DELETE with WHERE and no trailing', () => {
|
|
81
|
+
const result = injectWhereWorkspaceId('DELETE FROM agent_logs WHERE timestamp < ?', ws, [
|
|
82
|
+
'2024-01-01',
|
|
83
|
+
]);
|
|
84
|
+
expect(result.sql).toBe('DELETE FROM agent_logs WHERE timestamp < ? AND workspace_id = ?');
|
|
85
|
+
expect(result.params).toEqual(['2024-01-01', ws]);
|
|
86
|
+
});
|
|
87
|
+
it('should skip injection when workspace_id already present', () => {
|
|
88
|
+
const sql = 'SELECT * FROM agent_logs WHERE workspace_id = ? AND agent_id = ?';
|
|
89
|
+
const result = injectWhereWorkspaceId(sql, ws, [ws, 'agent-1']);
|
|
90
|
+
expect(result.sql).toBe(sql);
|
|
91
|
+
expect(result.params).toEqual([ws, 'agent-1']);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('injectInsertWorkspaceId', () => {
|
|
95
|
+
const ws = 'ws-123';
|
|
96
|
+
it('should add workspace_id column and value to INSERT', () => {
|
|
97
|
+
const result = injectInsertWorkspaceId('INSERT INTO agent_logs (agent_id, event_type) VALUES (?, ?)', ws, ['agent-1', 'AGENT_SPAWNED']);
|
|
98
|
+
expect(result.sql).toContain('workspace_id');
|
|
99
|
+
expect(result.params).toEqual(['agent-1', 'AGENT_SPAWNED', ws]);
|
|
100
|
+
});
|
|
101
|
+
it('should skip injection when workspace_id already present', () => {
|
|
102
|
+
const sql = 'INSERT INTO agent_logs (agent_id, workspace_id) VALUES (?, ?)';
|
|
103
|
+
const result = injectInsertWorkspaceId(sql, ws, ['agent-1', ws]);
|
|
104
|
+
expect(result.sql).toBe(sql);
|
|
105
|
+
expect(result.params).toEqual(['agent-1', ws]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
55
108
|
describe('convertParams edge cases', () => {
|
|
56
109
|
it('should handle empty string', () => {
|
|
57
110
|
expect(convertParams('')).toBe('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-provider.test.js","sourceRoot":"","sources":["../../src/db/postgres-provider.test.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"postgres-provider.test.js","sourceRoot":"","sources":["../../src/db/postgres-provider.test.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,aAAa,CAAC,2CAA2C,CAAC,CAAC,CAAC,IAAI,CACrE,6CAA6C,CAC9C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC3D,MAAM,CAAC,aAAa,CAAC,6CAA6C,CAAC,CAAC,CAAC,IAAI,CACvE,8CAA8C,CAC/C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,MAAM,CAAC,aAAa,CAAC,mCAAmC,CAAC,CAAC,CAAC,IAAI,CAC7D,oCAAoC,CACrC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,aAAa,CAAC,0CAA0C,CAAC,CAAC,CAAC,IAAI,CACpE,6CAA6C,CAC9C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,GAAG,GAAG;;;OAGX,CAAC;YACF,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;;;OAG/B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;QAC7D,6EAA6E;QAC7E,oFAAoF;QACpF,6EAA6E;QAE7E,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,qEAAqE;YACrE,MAAM,cAAc,GAAG;gBACrB,OAAO;gBACP,QAAQ;gBACR,cAAc;gBACd,SAAS;gBACT,oBAAoB;gBACpB,YAAY;gBACZ,aAAa;gBACb,eAAe;gBACf,UAAU;gBACV,kBAAkB;aACnB,CAAC;YAEF,sEAAsE;YACtE,qEAAqE;YACrE,MAAM,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,MAAM,EAAE,GAAG,QAAQ,CAAC;QAEpB,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,MAAM,GAAG,sBAAsB,CACnC,0DAA0D,EAC1D,EAAE,EACF,CAAC,EAAE,CAAC,CACL,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;YAChE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC/E,MAAM,MAAM,GAAG,sBAAsB,CACnC,6EAA6E,EAC7E,EAAE,EACF,CAAC,SAAS,EAAE,GAAG,CAAC,CACjB,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CACrB,kGAAkG,CACnG,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,sBAAsB,CAAC,6CAA6C,EAAE,EAAE,EAAE;gBACvF,SAAS;aACV,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;YAC5F,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;YAChE,MAAM,MAAM,GAAG,sBAAsB,CACnC,gGAAgG,EAChG,EAAE,EACF,CAAC,SAAS,CAAC,CACZ,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;YACrD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,MAAM,GAAG,sBAAsB,CAAC,4CAA4C,EAAE,EAAE,EAAE;gBACtF,YAAY;aACb,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;YAC3F,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,GAAG,GAAG,kEAAkE,CAAC;YAC/E,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;YAChE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,GAAG,QAAQ,CAAC;QAEpB,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,MAAM,GAAG,uBAAuB,CACpC,6DAA6D,EAC7D,EAAE,EACF,CAAC,SAAS,EAAE,eAAe,CAAC,CAC7B,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,GAAG,GAAG,+DAA+D,CAAC;YAC5E,MAAM,MAAM,GAAG,uBAAuB,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;YACjE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,aAAa,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC9F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9E,MAAM,CAAC,aAAa,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CACxD,yBAAyB,QAAQ,GAAG,CACrC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,CAAC,aAAa,CAAC,iDAAiD,CAAC,CAAC,CAAC,IAAI,CAC3E,kDAAkD,CACnD,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-usage.d.ts","sourceRoot":"","sources":["../../../src/db/queries/token-usage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,gBAAgB,EAC1B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"token-usage.d.ts","sourceRoot":"","sources":["../../../src/db/queries/token-usage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,gBAAgB,EAC1B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,EAAE,CAAC,CAS1B;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,iBAAiB,CAAC,CA0C5B"}
|
|
@@ -62,11 +62,19 @@ export async function getTotalTokens(provider, options) {
|
|
|
62
62
|
FROM token_usage
|
|
63
63
|
${whereClause}
|
|
64
64
|
`, params);
|
|
65
|
-
return
|
|
65
|
+
// Postgres SUM/COUNT return bigint, which node-postgres converts to strings.
|
|
66
|
+
// Coerce to numbers for consistent behavior across SQLite and Postgres.
|
|
67
|
+
const raw = result || {
|
|
66
68
|
total_input_tokens: 0,
|
|
67
69
|
total_output_tokens: 0,
|
|
68
70
|
total_tokens: 0,
|
|
69
71
|
record_count: 0,
|
|
70
|
-
}
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
total_input_tokens: Number(raw.total_input_tokens),
|
|
75
|
+
total_output_tokens: Number(raw.total_output_tokens),
|
|
76
|
+
total_tokens: Number(raw.total_tokens),
|
|
77
|
+
record_count: Number(raw.record_count),
|
|
78
|
+
};
|
|
71
79
|
}
|
|
72
80
|
//# sourceMappingURL=token-usage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-usage.js","sourceRoot":"","sources":["../../../src/db/queries/token-usage.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAmC7D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,KAA4B;IAE5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CACpC;;;;GAID,EACC;QACE,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,OAAO,IAAI,IAAI;QACrB,KAAK,CAAC,aAAa,IAAI,IAAI;QAC3B,KAAK,CAAC,WAAW;QACjB,KAAK,CAAC,YAAY;QAClB,KAAK,CAAC,WAAW;QACjB,KAAK,CAAC,KAAK,IAAI,IAAI;QACnB,KAAK,CAAC,SAAS,IAAI,IAAI;QACvB,GAAG;KACJ,CACF,CAAC;IAEF,OAAO,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAgB,wCAAwC,EAAE;QACvF,MAAM,EAAE,EAAE,IAAI,CAAC;KAChB,CAAC,CAAE,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,OAAe;IAEf,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,OAAO,CAAC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,OAAe;IAEf,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,OAAO,CAAC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA0B,EAC1B,aAAqB;IAErB,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,aAAa,CAAC,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAA0B,EAC1B,OAA4C;IAE5C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAErF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CACpC;;;;;;;MAOE,WAAW;GACd,EACC,MAAM,CACP,CAAC;IAEF,
|
|
1
|
+
{"version":3,"file":"token-usage.js","sourceRoot":"","sources":["../../../src/db/queries/token-usage.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAmC7D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,KAA4B;IAE5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CACpC;;;;GAID,EACC;QACE,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,OAAO,IAAI,IAAI;QACrB,KAAK,CAAC,aAAa,IAAI,IAAI;QAC3B,KAAK,CAAC,WAAW;QACjB,KAAK,CAAC,YAAY;QAClB,KAAK,CAAC,WAAW;QACjB,KAAK,CAAC,KAAK,IAAI,IAAI;QACnB,KAAK,CAAC,SAAS,IAAI,IAAI;QACvB,GAAG;KACJ,CACF,CAAC;IAEF,OAAO,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAgB,wCAAwC,EAAE;QACvF,MAAM,EAAE,EAAE,IAAI,CAAC;KAChB,CAAC,CAAE,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,OAAe;IAEf,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,OAAO,CAAC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,OAAe;IAEf,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,OAAO,CAAC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA0B,EAC1B,aAAqB;IAErB,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAC5B;;;;GAID,EACC,CAAC,aAAa,CAAC,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAA0B,EAC1B,OAA4C;IAE5C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAErF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CACpC;;;;;;;MAOE,WAAW;GACd,EACC,MAAM,CACP,CAAC;IAEF,6EAA6E;IAC7E,wEAAwE;IACxE,MAAM,GAAG,GAAG,MAAM,IAAI;QACpB,kBAAkB,EAAE,CAAC;QACrB,mBAAmB,EAAE,CAAC;QACtB,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,CAAC;KAChB,CAAC;IACF,OAAO;QACL,kBAAkB,EAAE,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAClD,mBAAmB,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACpD,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;QACtC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-usage-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/token-usage-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-usage-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/token-usage-parser.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAsKD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAOvE;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAkClF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAmB9E"}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* Supports Claude Code, Codex, and Gemini output formats.
|
|
7
7
|
* Also reads token usage directly from Claude Code's JSONL conversation logs.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
11
|
+
import { homedir, userInfo } from 'os';
|
|
11
12
|
import { join, resolve } from 'path';
|
|
12
13
|
/**
|
|
13
14
|
* Parse a number string that may contain commas (e.g. "12,345" -> 12345)
|
|
@@ -226,12 +227,40 @@ export function getTokenUsageForAgent(workDir) {
|
|
|
226
227
|
return null;
|
|
227
228
|
return { inputTokens: totalInput, outputTokens: totalOutput, totalTokens };
|
|
228
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Resolve the home directory for the OS user that owns a given path.
|
|
232
|
+
* Falls back to the current process's homedir() if detection fails.
|
|
233
|
+
*/
|
|
234
|
+
function resolveHomeDirForPath(dirPath) {
|
|
235
|
+
try {
|
|
236
|
+
const stat = statSync(dirPath);
|
|
237
|
+
const currentUid = userInfo().uid;
|
|
238
|
+
// If the directory is owned by the current user, just use homedir()
|
|
239
|
+
if (stat.uid === currentUid) {
|
|
240
|
+
return homedir();
|
|
241
|
+
}
|
|
242
|
+
// Look up the owning user's home directory via getent (POSIX)
|
|
243
|
+
const passwd = execSync(`getent passwd ${stat.uid}`, { encoding: 'utf-8' }).trim();
|
|
244
|
+
const fields = passwd.split(':');
|
|
245
|
+
if (fields.length >= 6 && fields[5]) {
|
|
246
|
+
return fields[5];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Fall through to default
|
|
251
|
+
}
|
|
252
|
+
return homedir();
|
|
253
|
+
}
|
|
229
254
|
/**
|
|
230
255
|
* Find all JSONL session files for a given working directory.
|
|
256
|
+
* Resolves the Claude config directory from the user that owns the workDir,
|
|
257
|
+
* so the manager can read JSONL logs even when running as a different user.
|
|
231
258
|
*/
|
|
232
259
|
function findAllSessionFiles(workDir) {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
260
|
+
const resolvedWorkDir = resolve(workDir);
|
|
261
|
+
const projectDir = pathToClaudeProjectDir(resolvedWorkDir);
|
|
262
|
+
const home = resolveHomeDirForPath(resolvedWorkDir);
|
|
263
|
+
const claudeProjectsDir = join(home, '.claude', 'projects', projectDir);
|
|
235
264
|
try {
|
|
236
265
|
return readdirSync(claudeProjectsDir)
|
|
237
266
|
.filter(f => f.endsWith('.jsonl'))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-usage-parser.js","sourceRoot":"","sources":["../../src/parsers/token-usage-parser.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"token-usage-parser.js","sourceRoot":"","sources":["../../src/parsers/token-usage-parser.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -86,7 +86,9 @@ agentsCommand
|
|
|
86
86
|
console.log(chalk.bold(`\nLogs for ${agentId}:\n`));
|
|
87
87
|
|
|
88
88
|
for (const log of logs) {
|
|
89
|
-
const
|
|
89
|
+
const tsStr =
|
|
90
|
+
log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp);
|
|
91
|
+
const time = tsStr.substring(0, 19).replace('T', ' ');
|
|
90
92
|
const storyInfo = log.story_id ? chalk.cyan(` [${log.story_id}]`) : '';
|
|
91
93
|
const message = log.message ? `: ${log.message}` : '';
|
|
92
94
|
|
|
@@ -137,7 +139,9 @@ agentsCommand
|
|
|
137
139
|
if (logs.length > 0) {
|
|
138
140
|
console.log(chalk.bold('\nRecent Activity:'));
|
|
139
141
|
for (const log of logs) {
|
|
140
|
-
const
|
|
142
|
+
const tsStr =
|
|
143
|
+
log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp);
|
|
144
|
+
const time = tsStr.substring(11, 19);
|
|
141
145
|
const message = log.message ? `: ${log.message.substring(0, 50)}` : '';
|
|
142
146
|
console.log(chalk.gray(` ${time} | ${log.event_type}${message}`));
|
|
143
147
|
}
|
|
@@ -63,7 +63,8 @@ export async function captureAndPersistTokenUsage(
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
return await parseAndPersistTokenUsage(output, ctx, agentId, storyId);
|
|
66
|
-
} catch {
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(`[token-capture] captureAndPersistTokenUsage failed for agent=${agentId}:`, err);
|
|
67
68
|
return { captured: false, tokens: null, persisted: false };
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -104,7 +105,8 @@ export async function parseAndPersistTokenUsage(
|
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
return { captured: true, tokens, persisted: true };
|
|
107
|
-
} catch {
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`[token-capture] parseAndPersistTokenUsage failed for agent=${agentId}:`, err);
|
|
108
110
|
return { captured: false, tokens: null, persisted: false };
|
|
109
111
|
}
|
|
110
112
|
}
|
|
@@ -171,7 +173,11 @@ export async function parseAndPersistTokenUsageIfChanged(
|
|
|
171
173
|
});
|
|
172
174
|
|
|
173
175
|
return { captured: true, tokens, persisted: true, changed: true };
|
|
174
|
-
} catch {
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error(
|
|
178
|
+
`[token-capture] parseAndPersistTokenUsageIfChanged failed for agent=${agentId}:`,
|
|
179
|
+
err
|
|
180
|
+
);
|
|
175
181
|
return { captured: false, tokens: null, persisted: false, changed: false };
|
|
176
182
|
}
|
|
177
183
|
}
|
|
@@ -82,5 +82,35 @@ describe('msg command', () => {
|
|
|
82
82
|
const allOpt = inboxCmd?.options.find(opt => opt.long === '--all');
|
|
83
83
|
expect(allOpt).toBeDefined();
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it('should not crash when created_at is a Date object (Postgres mode)', async () => {
|
|
87
|
+
const { withReadOnlyHiveContext } = await import('../../utils/with-hive-context.js');
|
|
88
|
+
const mockMsg = {
|
|
89
|
+
id: 'msg-test1',
|
|
90
|
+
from_session: 'sender',
|
|
91
|
+
to_session: 'receiver',
|
|
92
|
+
subject: 'test',
|
|
93
|
+
body: 'hello',
|
|
94
|
+
reply: null,
|
|
95
|
+
status: 'pending' as const,
|
|
96
|
+
created_at: new Date('2024-01-15T10:30:00.000Z'),
|
|
97
|
+
replied_at: null,
|
|
98
|
+
};
|
|
99
|
+
vi.mocked(withReadOnlyHiveContext).mockImplementationOnce(async callback => {
|
|
100
|
+
await callback({
|
|
101
|
+
db: {
|
|
102
|
+
db: {},
|
|
103
|
+
provider: { queryAll: vi.fn().mockResolvedValue([mockMsg]) },
|
|
104
|
+
save: vi.fn(),
|
|
105
|
+
},
|
|
106
|
+
paths: { hiveDir: '/tmp/test-hive' },
|
|
107
|
+
} as unknown as Parameters<typeof callback>[0]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
111
|
+
const inboxCmd = msgCommand.commands.find(cmd => cmd.name() === 'inbox')!;
|
|
112
|
+
await expect(inboxCmd.parseAsync(['receiver'], { from: 'user' })).resolves.not.toThrow();
|
|
113
|
+
consoleSpy.mockRestore();
|
|
114
|
+
});
|
|
85
115
|
});
|
|
86
116
|
});
|
package/src/cli/commands/msg.ts
CHANGED
|
@@ -14,8 +14,13 @@ interface MessageRow {
|
|
|
14
14
|
body: string;
|
|
15
15
|
reply: string | null;
|
|
16
16
|
status: 'pending' | 'read' | 'replied';
|
|
17
|
-
created_at: string;
|
|
18
|
-
replied_at: string | null;
|
|
17
|
+
created_at: string | Date;
|
|
18
|
+
replied_at: string | Date | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toISOString(value: string | Date | null): string {
|
|
22
|
+
if (value === null) return '';
|
|
23
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export const msgCommand = new Command('msg').description('Inter-agent messaging');
|
|
@@ -79,7 +84,7 @@ msgCommand
|
|
|
79
84
|
: msg.status === 'replied'
|
|
80
85
|
? chalk.green('✓')
|
|
81
86
|
: chalk.gray('○');
|
|
82
|
-
const time = msg.created_at.substring(0, 16).replace('T', ' ');
|
|
87
|
+
const time = toISOString(msg.created_at).substring(0, 16).replace('T', ' ');
|
|
83
88
|
|
|
84
89
|
console.log(`${statusIcon} ${chalk.cyan(msg.id)} from ${chalk.bold(msg.from_session)}`);
|
|
85
90
|
console.log(` ${chalk.gray(time)} ${msg.subject || ''}`);
|
|
@@ -120,7 +125,7 @@ msgCommand
|
|
|
120
125
|
console.log(`From: ${chalk.cyan(msg.from_session)}`);
|
|
121
126
|
console.log(`To: ${msg.to_session}`);
|
|
122
127
|
console.log(`Subject: ${msg.subject || '(none)'}`);
|
|
123
|
-
console.log(`Time: ${msg.created_at}`);
|
|
128
|
+
console.log(`Time: ${toISOString(msg.created_at)}`);
|
|
124
129
|
console.log(`Status: ${msg.status}`);
|
|
125
130
|
console.log(chalk.gray('─'.repeat(50)));
|
|
126
131
|
console.log(msg.body);
|
|
@@ -147,7 +147,9 @@ async function showOverallStatus(db: DatabaseProvider, json?: boolean): Promise<
|
|
|
147
147
|
if (recentLogs.length > 0) {
|
|
148
148
|
console.log(chalk.bold('Recent Activity:'));
|
|
149
149
|
for (const log of recentLogs) {
|
|
150
|
-
const
|
|
150
|
+
const ts =
|
|
151
|
+
log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp);
|
|
152
|
+
const time = ts.substring(11, 19);
|
|
151
153
|
console.log(chalk.gray(` ${time} | ${log.agent_id.padEnd(15)} | ${log.event_type}`));
|
|
152
154
|
}
|
|
153
155
|
console.log();
|
|
@@ -326,7 +328,9 @@ async function showStoryStatus(
|
|
|
326
328
|
if (logs.length > 0) {
|
|
327
329
|
console.log(chalk.bold('Recent Activity:'));
|
|
328
330
|
for (const log of logs) {
|
|
329
|
-
const
|
|
331
|
+
const ts =
|
|
332
|
+
log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp);
|
|
333
|
+
const time = ts.substring(11, 19);
|
|
330
334
|
const msg = log.message ? `: ${log.message.substring(0, 40)}` : '';
|
|
331
335
|
console.log(chalk.gray(` ${time} | ${log.agent_id.padEnd(15)} | ${log.event_type}${msg}`));
|
|
332
336
|
}
|
|
@@ -69,7 +69,7 @@ tokensCommand
|
|
|
69
69
|
console.log(chalk.gray('─'.repeat(90)));
|
|
70
70
|
|
|
71
71
|
for (const row of rows) {
|
|
72
|
-
const time = row.recorded_at.substring(0, 19).replace('T', ' ');
|
|
72
|
+
const time = new Date(row.recorded_at).toISOString().substring(0, 19).replace('T', ' ');
|
|
73
73
|
const story = (row.story_id || '-').padEnd(20);
|
|
74
74
|
const model = (row.model || '-').padEnd(15);
|
|
75
75
|
console.log(
|
|
@@ -119,7 +119,7 @@ tokensCommand
|
|
|
119
119
|
console.log(chalk.gray('─'.repeat(95)));
|
|
120
120
|
|
|
121
121
|
for (const row of rows) {
|
|
122
|
-
const time = row.recorded_at.substring(0, 19).replace('T', ' ');
|
|
122
|
+
const time = new Date(row.recorded_at).toISOString().substring(0, 19).replace('T', ' ');
|
|
123
123
|
const agent = row.agent_id.padEnd(25);
|
|
124
124
|
const model = (row.model || '-').padEnd(15);
|
|
125
125
|
console.log(
|
package/src/db/client.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
convertParams,
|
|
6
|
+
injectInsertWorkspaceId,
|
|
7
|
+
injectWhereWorkspaceId,
|
|
8
|
+
} from './postgres-provider.js';
|
|
5
9
|
|
|
6
10
|
describe('PostgresProvider utilities', () => {
|
|
7
11
|
describe('convertParams', () => {
|
|
@@ -71,6 +75,87 @@ describe('PostgresProvider utilities', () => {
|
|
|
71
75
|
});
|
|
72
76
|
});
|
|
73
77
|
|
|
78
|
+
describe('injectWhereWorkspaceId', () => {
|
|
79
|
+
const ws = 'ws-123';
|
|
80
|
+
|
|
81
|
+
it('should inject WHERE clause when none exists', () => {
|
|
82
|
+
const result = injectWhereWorkspaceId(
|
|
83
|
+
'SELECT * FROM agent_logs ORDER BY timestamp DESC LIMIT ?',
|
|
84
|
+
ws,
|
|
85
|
+
[50]
|
|
86
|
+
);
|
|
87
|
+
expect(result.sql).toContain('WHERE workspace_id = ?');
|
|
88
|
+
expect(result.sql).toContain('ORDER BY timestamp DESC LIMIT ?');
|
|
89
|
+
expect(result.params).toEqual([ws, 50]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should append to existing WHERE with correct param order before LIMIT', () => {
|
|
93
|
+
const result = injectWhereWorkspaceId(
|
|
94
|
+
'SELECT * FROM agent_logs WHERE agent_id = ? ORDER BY timestamp DESC LIMIT ?',
|
|
95
|
+
ws,
|
|
96
|
+
['agent-1', 100]
|
|
97
|
+
);
|
|
98
|
+
expect(result.sql).toBe(
|
|
99
|
+
'SELECT * FROM agent_logs WHERE agent_id = ? AND workspace_id = ? ORDER BY timestamp DESC LIMIT ?'
|
|
100
|
+
);
|
|
101
|
+
expect(result.params).toEqual(['agent-1', ws, 100]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should handle WHERE with no trailing clauses', () => {
|
|
105
|
+
const result = injectWhereWorkspaceId('SELECT * FROM agent_logs WHERE story_id = ?', ws, [
|
|
106
|
+
'story-1',
|
|
107
|
+
]);
|
|
108
|
+
expect(result.sql).toBe('SELECT * FROM agent_logs WHERE story_id = ? AND workspace_id = ?');
|
|
109
|
+
expect(result.params).toEqual(['story-1', ws]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should handle WHERE with multiple conditions and LIMIT', () => {
|
|
113
|
+
const result = injectWhereWorkspaceId(
|
|
114
|
+
"SELECT COUNT(*) as count FROM agent_logs WHERE story_id = ? AND event_type = 'STORY_QA_FAILED'",
|
|
115
|
+
ws,
|
|
116
|
+
['story-1']
|
|
117
|
+
);
|
|
118
|
+
expect(result.sql).toContain('AND workspace_id = ?');
|
|
119
|
+
expect(result.params).toEqual(['story-1', ws]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle DELETE with WHERE and no trailing', () => {
|
|
123
|
+
const result = injectWhereWorkspaceId('DELETE FROM agent_logs WHERE timestamp < ?', ws, [
|
|
124
|
+
'2024-01-01',
|
|
125
|
+
]);
|
|
126
|
+
expect(result.sql).toBe('DELETE FROM agent_logs WHERE timestamp < ? AND workspace_id = ?');
|
|
127
|
+
expect(result.params).toEqual(['2024-01-01', ws]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should skip injection when workspace_id already present', () => {
|
|
131
|
+
const sql = 'SELECT * FROM agent_logs WHERE workspace_id = ? AND agent_id = ?';
|
|
132
|
+
const result = injectWhereWorkspaceId(sql, ws, [ws, 'agent-1']);
|
|
133
|
+
expect(result.sql).toBe(sql);
|
|
134
|
+
expect(result.params).toEqual([ws, 'agent-1']);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('injectInsertWorkspaceId', () => {
|
|
139
|
+
const ws = 'ws-123';
|
|
140
|
+
|
|
141
|
+
it('should add workspace_id column and value to INSERT', () => {
|
|
142
|
+
const result = injectInsertWorkspaceId(
|
|
143
|
+
'INSERT INTO agent_logs (agent_id, event_type) VALUES (?, ?)',
|
|
144
|
+
ws,
|
|
145
|
+
['agent-1', 'AGENT_SPAWNED']
|
|
146
|
+
);
|
|
147
|
+
expect(result.sql).toContain('workspace_id');
|
|
148
|
+
expect(result.params).toEqual(['agent-1', 'AGENT_SPAWNED', ws]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should skip injection when workspace_id already present', () => {
|
|
152
|
+
const sql = 'INSERT INTO agent_logs (agent_id, workspace_id) VALUES (?, ?)';
|
|
153
|
+
const result = injectInsertWorkspaceId(sql, ws, ['agent-1', ws]);
|
|
154
|
+
expect(result.sql).toBe(sql);
|
|
155
|
+
expect(result.params).toEqual(['agent-1', ws]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
74
159
|
describe('convertParams edge cases', () => {
|
|
75
160
|
it('should handle empty string', () => {
|
|
76
161
|
expect(convertParams('')).toBe('');
|
|
@@ -157,7 +157,7 @@ function needsWorkspaceScope(sql: string): boolean {
|
|
|
157
157
|
* Transforms: INSERT INTO table (col1, col2) VALUES (?, ?)
|
|
158
158
|
* Into: INSERT INTO table (col1, col2, workspace_id) VALUES ($1, $2, $3)
|
|
159
159
|
*/
|
|
160
|
-
function injectInsertWorkspaceId(
|
|
160
|
+
export function injectInsertWorkspaceId(
|
|
161
161
|
sql: string,
|
|
162
162
|
workspaceId: string,
|
|
163
163
|
params: unknown[]
|
|
@@ -187,7 +187,7 @@ function injectInsertWorkspaceId(
|
|
|
187
187
|
* When the query uses JOINs, qualifies workspace_id with the primary table alias
|
|
188
188
|
* to avoid ambiguous column references.
|
|
189
189
|
*/
|
|
190
|
-
function injectWhereWorkspaceId(
|
|
190
|
+
export function injectWhereWorkspaceId(
|
|
191
191
|
sql: string,
|
|
192
192
|
workspaceId: string,
|
|
193
193
|
params: unknown[]
|
|
@@ -236,9 +236,18 @@ function injectWhereWorkspaceId(
|
|
|
236
236
|
|
|
237
237
|
const whereBody = after.substring(0, trailingPos);
|
|
238
238
|
const trailing = after.substring(trailingPos);
|
|
239
|
+
|
|
240
|
+
// Count ? placeholders before the injection point (before + whereBody) to
|
|
241
|
+
// splice workspace_id into the correct position in the params array.
|
|
242
|
+
// Appending to the end is wrong when trailing clauses (LIMIT, OFFSET) have
|
|
243
|
+
// their own positional params.
|
|
244
|
+
const questionsBefore = ((before + whereBody).match(/\?/g) || []).length;
|
|
245
|
+
const newParams = [...params];
|
|
246
|
+
newParams.splice(questionsBefore, 0, workspaceId);
|
|
247
|
+
|
|
239
248
|
return {
|
|
240
249
|
sql: `${before}${whereBody} AND ${wsCol} = ?${trailing}`,
|
|
241
|
-
params:
|
|
250
|
+
params: newParams,
|
|
242
251
|
};
|
|
243
252
|
}
|
|
244
253
|
|
|
@@ -136,12 +136,18 @@ export async function getTotalTokens(
|
|
|
136
136
|
params
|
|
137
137
|
);
|
|
138
138
|
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
// Postgres SUM/COUNT return bigint, which node-postgres converts to strings.
|
|
140
|
+
// Coerce to numbers for consistent behavior across SQLite and Postgres.
|
|
141
|
+
const raw = result || {
|
|
142
|
+
total_input_tokens: 0,
|
|
143
|
+
total_output_tokens: 0,
|
|
144
|
+
total_tokens: 0,
|
|
145
|
+
record_count: 0,
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
total_input_tokens: Number(raw.total_input_tokens),
|
|
149
|
+
total_output_tokens: Number(raw.total_output_tokens),
|
|
150
|
+
total_tokens: Number(raw.total_tokens),
|
|
151
|
+
record_count: Number(raw.record_count),
|
|
152
|
+
};
|
|
147
153
|
}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Also reads token usage directly from Claude Code's JSONL conversation logs.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
13
|
+
import { homedir, userInfo } from 'os';
|
|
13
14
|
import { join, resolve } from 'path';
|
|
14
15
|
|
|
15
16
|
export interface ParsedTokenUsage {
|
|
@@ -274,12 +275,43 @@ export function getTokenUsageForAgent(workDir: string): ParsedTokenUsage | null
|
|
|
274
275
|
return { inputTokens: totalInput, outputTokens: totalOutput, totalTokens };
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Resolve the home directory for the OS user that owns a given path.
|
|
280
|
+
* Falls back to the current process's homedir() if detection fails.
|
|
281
|
+
*/
|
|
282
|
+
function resolveHomeDirForPath(dirPath: string): string {
|
|
283
|
+
try {
|
|
284
|
+
const stat = statSync(dirPath);
|
|
285
|
+
const currentUid = userInfo().uid;
|
|
286
|
+
|
|
287
|
+
// If the directory is owned by the current user, just use homedir()
|
|
288
|
+
if (stat.uid === currentUid) {
|
|
289
|
+
return homedir();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Look up the owning user's home directory via getent (POSIX)
|
|
293
|
+
const passwd = execSync(`getent passwd ${stat.uid}`, { encoding: 'utf-8' }).trim();
|
|
294
|
+
const fields = passwd.split(':');
|
|
295
|
+
if (fields.length >= 6 && fields[5]) {
|
|
296
|
+
return fields[5];
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
// Fall through to default
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return homedir();
|
|
303
|
+
}
|
|
304
|
+
|
|
277
305
|
/**
|
|
278
306
|
* Find all JSONL session files for a given working directory.
|
|
307
|
+
* Resolves the Claude config directory from the user that owns the workDir,
|
|
308
|
+
* so the manager can read JSONL logs even when running as a different user.
|
|
279
309
|
*/
|
|
280
310
|
function findAllSessionFiles(workDir: string): string[] {
|
|
281
|
-
const
|
|
282
|
-
const
|
|
311
|
+
const resolvedWorkDir = resolve(workDir);
|
|
312
|
+
const projectDir = pathToClaudeProjectDir(resolvedWorkDir);
|
|
313
|
+
const home = resolveHomeDirForPath(resolvedWorkDir);
|
|
314
|
+
const claudeProjectsDir = join(home, '.claude', 'projects', projectDir);
|
|
283
315
|
|
|
284
316
|
try {
|
|
285
317
|
return readdirSync(claudeProjectsDir)
|