heyio 3.0.2 → 3.0.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/dist/api/server.js +1 -1
- package/dist/api/server.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +13 -1
- package/dist/logging/logger.js.map +1 -1
- package/node_modules/@io/shared/package.json +1 -1
- package/package.json +7 -2
- package/public/assets/index-2RY89H3W.js +336 -0
- package/public/assets/index-2RY89H3W.js.map +1 -0
- package/public/assets/index-D3cGfBsj.css +1 -0
- package/public/index.html +14 -0
- package/src/api/middleware/auth.ts +0 -76
- package/src/api/notifications.ts +0 -122
- package/src/api/routes/activity.ts +0 -29
- package/src/api/routes/attachments.ts +0 -93
- package/src/api/routes/config.ts +0 -115
- package/src/api/routes/conversations.ts +0 -87
- package/src/api/routes/health.ts +0 -18
- package/src/api/routes/inbox.ts +0 -98
- package/src/api/routes/schedules.ts +0 -121
- package/src/api/routes/skills.ts +0 -105
- package/src/api/routes/squads.ts +0 -145
- package/src/api/routes/usage.ts +0 -57
- package/src/api/routes/wiki.ts +0 -49
- package/src/api/server.ts +0 -186
- package/src/config.ts +0 -3
- package/src/copilot/client.ts +0 -42
- package/src/copilot/health-monitor.ts +0 -85
- package/src/copilot/orchestrator.ts +0 -222
- package/src/copilot/tools.ts +0 -707
- package/src/index.ts +0 -113
- package/src/logging/logger.ts +0 -26
- package/src/models/index.ts +0 -11
- package/src/models/pricing.ts +0 -121
- package/src/models/registry.ts +0 -131
- package/src/models/token-tracker.ts +0 -151
- package/src/scheduler/engine.ts +0 -146
- package/src/skills/index.ts +0 -13
- package/src/skills/store.ts +0 -188
- package/src/squad/agent.ts +0 -326
- package/src/squad/autonomy.ts +0 -78
- package/src/squad/event-bus.ts +0 -71
- package/src/squad/execution/index.ts +0 -17
- package/src/squad/execution/instance.ts +0 -186
- package/src/squad/execution/meeting.ts +0 -191
- package/src/squad/execution/pr.ts +0 -127
- package/src/squad/execution/runner.ts +0 -97
- package/src/squad/execution/tasks.ts +0 -111
- package/src/squad/execution/worktree.ts +0 -138
- package/src/squad/hiring.ts +0 -222
- package/src/squad/index.ts +0 -17
- package/src/squad/manager.ts +0 -337
- package/src/squad/name-generator.ts +0 -135
- package/src/squad/roles/templates.ts +0 -104
- package/src/squad/skill-parser.ts +0 -120
- package/src/squad/source-resolver.ts +0 -57
- package/src/store/activity.ts +0 -176
- package/src/store/db.ts +0 -237
- package/src/store/inbox.ts +0 -199
- package/src/store/schedules.ts +0 -199
- package/src/wiki/index.ts +0 -12
- package/src/wiki/store.ts +0 -139
- package/tsconfig.json +0 -9
package/src/index.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { mkdirSync } from 'node:fs';
|
|
4
|
-
import { initNotifications } from './api/notifications.js';
|
|
5
|
-
import { createApiServer } from './api/server.js';
|
|
6
|
-
import { loadConfig } from './config.js';
|
|
7
|
-
import { stopClient } from './copilot/client.js';
|
|
8
|
-
import { startHealthMonitor, stopHealthMonitor } from './copilot/health-monitor.js';
|
|
9
|
-
import { destroyOrchestrator, initOrchestrator } from './copilot/orchestrator.js';
|
|
10
|
-
import { getLogger, initLogger } from './logging/logger.js';
|
|
11
|
-
import { seedPricing } from './models/index.js';
|
|
12
|
-
import { startScheduler, stopScheduler } from './scheduler/engine.js';
|
|
13
|
-
import { initSkills } from './skills/index.js';
|
|
14
|
-
import { getEventBus } from './squad/event-bus.js';
|
|
15
|
-
import { initActivityLogger } from './store/activity.js';
|
|
16
|
-
import { closeDatabase, initDatabase } from './store/db.js';
|
|
17
|
-
import { initWiki } from './wiki/index.js';
|
|
18
|
-
|
|
19
|
-
const config = loadConfig();
|
|
20
|
-
|
|
21
|
-
// Ensure data directory exists
|
|
22
|
-
mkdirSync(config.dataDir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
// Initialize logger first — other modules depend on it
|
|
25
|
-
const logger = initLogger(config);
|
|
26
|
-
|
|
27
|
-
// Initialize wiki directory structure
|
|
28
|
-
initWiki(config.dataDir);
|
|
29
|
-
|
|
30
|
-
// Initialize skills directory
|
|
31
|
-
initSkills(config.dataDir);
|
|
32
|
-
logger.info({ config: { ...config, dataDir: config.dataDir } }, 'IO daemon starting');
|
|
33
|
-
|
|
34
|
-
// Create API server
|
|
35
|
-
const apiServer = createApiServer(config);
|
|
36
|
-
|
|
37
|
-
async function start(): Promise<void> {
|
|
38
|
-
// Initialize database
|
|
39
|
-
await initDatabase(config.dataDir);
|
|
40
|
-
|
|
41
|
-
// Seed model pricing defaults
|
|
42
|
-
await seedPricing();
|
|
43
|
-
|
|
44
|
-
// Initialize notification system (event bus → WebSocket broadcast)
|
|
45
|
-
initNotifications();
|
|
46
|
-
|
|
47
|
-
// Initialize activity logger (event bus → SQLite)
|
|
48
|
-
initActivityLogger(getEventBus());
|
|
49
|
-
|
|
50
|
-
// Initialize Copilot orchestrator
|
|
51
|
-
await initOrchestrator(config);
|
|
52
|
-
|
|
53
|
-
// Start health monitoring
|
|
54
|
-
startHealthMonitor();
|
|
55
|
-
|
|
56
|
-
// Start schedule engine (evaluates cron schedules every 60s)
|
|
57
|
-
startScheduler();
|
|
58
|
-
|
|
59
|
-
await apiServer.start();
|
|
60
|
-
logger.info('IO daemon ready');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Graceful shutdown
|
|
64
|
-
let shuttingDown = false;
|
|
65
|
-
async function shutdown(signal: string): Promise<void> {
|
|
66
|
-
if (shuttingDown) return;
|
|
67
|
-
shuttingDown = true;
|
|
68
|
-
|
|
69
|
-
const log = getLogger();
|
|
70
|
-
log.info({ signal }, 'Shutting down...');
|
|
71
|
-
|
|
72
|
-
// Stop accepting new requests
|
|
73
|
-
stopHealthMonitor();
|
|
74
|
-
stopScheduler();
|
|
75
|
-
|
|
76
|
-
// Stop API server (closes WebSocket connections)
|
|
77
|
-
await apiServer.stop();
|
|
78
|
-
|
|
79
|
-
// Destroy orchestrator session (drains queue)
|
|
80
|
-
await destroyOrchestrator();
|
|
81
|
-
|
|
82
|
-
// Stop Copilot SDK client
|
|
83
|
-
await stopClient();
|
|
84
|
-
|
|
85
|
-
// Clear event bus
|
|
86
|
-
getEventBus().clear();
|
|
87
|
-
|
|
88
|
-
// Close database
|
|
89
|
-
closeDatabase();
|
|
90
|
-
|
|
91
|
-
log.info('Shutdown complete');
|
|
92
|
-
process.exit(0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
96
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
97
|
-
|
|
98
|
-
// Handle uncaught errors gracefully
|
|
99
|
-
process.on('uncaughtException', (err) => {
|
|
100
|
-
const log = getLogger();
|
|
101
|
-
log.fatal({ err }, 'Uncaught exception');
|
|
102
|
-
shutdown('uncaughtException');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
process.on('unhandledRejection', (reason) => {
|
|
106
|
-
const log = getLogger();
|
|
107
|
-
log.error({ err: reason }, 'Unhandled rejection');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
start().catch((err) => {
|
|
111
|
-
logger.fatal({ err }, 'Failed to start IO daemon');
|
|
112
|
-
process.exit(1);
|
|
113
|
-
});
|
package/src/logging/logger.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import pino from 'pino';
|
|
2
|
-
import type { IOConfig } from '../config.js';
|
|
3
|
-
|
|
4
|
-
let rootLogger: pino.Logger;
|
|
5
|
-
|
|
6
|
-
export function initLogger(config: IOConfig): pino.Logger {
|
|
7
|
-
rootLogger = pino({
|
|
8
|
-
level: config.logLevel,
|
|
9
|
-
transport:
|
|
10
|
-
process.env.NODE_ENV !== 'production'
|
|
11
|
-
? { target: 'pino-pretty', options: { colorize: true } }
|
|
12
|
-
: undefined,
|
|
13
|
-
});
|
|
14
|
-
return rootLogger;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function getLogger(): pino.Logger {
|
|
18
|
-
if (!rootLogger) {
|
|
19
|
-
throw new Error('Logger not initialized. Call initLogger() first.');
|
|
20
|
-
}
|
|
21
|
-
return rootLogger;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function createChildLogger(name: string, meta?: Record<string, unknown>): pino.Logger {
|
|
25
|
-
return getLogger().child({ component: name, ...meta });
|
|
26
|
-
}
|
package/src/models/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
MODEL_REGISTRY,
|
|
3
|
-
DEFAULT_MODELS,
|
|
4
|
-
getModel,
|
|
5
|
-
getModelsByTier,
|
|
6
|
-
selectModelForTask,
|
|
7
|
-
} from './registry.js';
|
|
8
|
-
export type { ModelInfo, ModelTier } from './registry.js';
|
|
9
|
-
export { recordTokenUsage, queryUsage } from './token-tracker.js';
|
|
10
|
-
export type { TokenUsageRecord } from './token-tracker.js';
|
|
11
|
-
export { seedPricing, updatePricing, getAllPricing, refreshPricing } from './pricing.js';
|
package/src/models/pricing.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { createChildLogger } from '../logging/logger.js';
|
|
2
|
-
import { getDatabase } from '../store/db.js';
|
|
3
|
-
import { MODEL_REGISTRY, type ModelInfo } from './registry.js';
|
|
4
|
-
|
|
5
|
-
const logger = () => createChildLogger('pricing');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Default pricing estimates (USD per 1M tokens) when GitHub pricing is unavailable.
|
|
9
|
-
* These are rough estimates based on publicly available pricing.
|
|
10
|
-
*/
|
|
11
|
-
const DEFAULT_PRICING: Record<string, { input: number; output: number }> = {
|
|
12
|
-
'claude-haiku-4.5': { input: 0.8, output: 4.0 },
|
|
13
|
-
'gpt-5-mini': { input: 1.5, output: 6.0 },
|
|
14
|
-
'gpt-5.4-mini': { input: 1.5, output: 6.0 },
|
|
15
|
-
'claude-sonnet-4.5': { input: 3.0, output: 15.0 },
|
|
16
|
-
'claude-sonnet-4.6': { input: 3.0, output: 15.0 },
|
|
17
|
-
'gpt-5.2': { input: 5.0, output: 15.0 },
|
|
18
|
-
'gpt-5.4': { input: 5.0, output: 15.0 },
|
|
19
|
-
'gpt-5.5': { input: 5.0, output: 15.0 },
|
|
20
|
-
'gpt-4.1': { input: 2.0, output: 8.0 },
|
|
21
|
-
'claude-opus-4.5': { input: 15.0, output: 75.0 },
|
|
22
|
-
'claude-opus-4.6': { input: 15.0, output: 75.0 },
|
|
23
|
-
'claude-opus-4.7': { input: 15.0, output: 75.0 },
|
|
24
|
-
'claude-opus-4.8': { input: 15.0, output: 75.0 },
|
|
25
|
-
'gpt-5.2-codex': { input: 10.0, output: 30.0 },
|
|
26
|
-
'gpt-5.3-codex': { input: 10.0, output: 30.0 },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Seed the model_pricing table with default values.
|
|
31
|
-
* Only inserts if the table is empty.
|
|
32
|
-
*/
|
|
33
|
-
export async function seedPricing(): Promise<void> {
|
|
34
|
-
const log = logger();
|
|
35
|
-
const db = getDatabase();
|
|
36
|
-
|
|
37
|
-
const existing = await db.execute('SELECT COUNT(*) as cnt FROM model_pricing');
|
|
38
|
-
if ((existing.rows[0]?.cnt as number) > 0) {
|
|
39
|
-
return; // Already seeded
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const [model, prices] of Object.entries(DEFAULT_PRICING)) {
|
|
43
|
-
const info = MODEL_REGISTRY.find((m) => m.id === model);
|
|
44
|
-
await db.execute({
|
|
45
|
-
sql: `INSERT OR REPLACE INTO model_pricing (model, input_cost_per_1m, output_cost_per_1m, tier, last_updated)
|
|
46
|
-
VALUES (?, ?, ?, ?, datetime('now'))`,
|
|
47
|
-
args: [model, prices.input, prices.output, info?.tier ?? 'standard'],
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
log.info('Model pricing seeded with defaults');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Update pricing for a specific model.
|
|
56
|
-
*/
|
|
57
|
-
export async function updatePricing(
|
|
58
|
-
model: string,
|
|
59
|
-
inputCostPer1M: number,
|
|
60
|
-
outputCostPer1M: number,
|
|
61
|
-
): Promise<void> {
|
|
62
|
-
const db = getDatabase();
|
|
63
|
-
const info = MODEL_REGISTRY.find((m) => m.id === model);
|
|
64
|
-
|
|
65
|
-
await db.execute({
|
|
66
|
-
sql: `INSERT OR REPLACE INTO model_pricing (model, input_cost_per_1m, output_cost_per_1m, tier, last_updated)
|
|
67
|
-
VALUES (?, ?, ?, ?, datetime('now'))`,
|
|
68
|
-
args: [model, inputCostPer1M, outputCostPer1M, info?.tier ?? 'standard'],
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get all current pricing.
|
|
74
|
-
*/
|
|
75
|
-
export async function getAllPricing(): Promise<
|
|
76
|
-
Array<{
|
|
77
|
-
model: string;
|
|
78
|
-
inputCostPer1M: number;
|
|
79
|
-
outputCostPer1M: number;
|
|
80
|
-
tier: string;
|
|
81
|
-
lastUpdated: string;
|
|
82
|
-
}>
|
|
83
|
-
> {
|
|
84
|
-
const db = getDatabase();
|
|
85
|
-
const result = await db.execute('SELECT * FROM model_pricing ORDER BY model');
|
|
86
|
-
|
|
87
|
-
return result.rows.map((row) => ({
|
|
88
|
-
model: row.model as string,
|
|
89
|
-
inputCostPer1M: row.input_cost_per_1m as number,
|
|
90
|
-
outputCostPer1M: row.output_cost_per_1m as number,
|
|
91
|
-
tier: (row.tier as string) ?? 'standard',
|
|
92
|
-
lastUpdated: row.last_updated as string,
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Refresh pricing — in future this could fetch from GitHub docs.
|
|
98
|
-
* For now, re-seeds defaults for any missing models.
|
|
99
|
-
*/
|
|
100
|
-
export async function refreshPricing(): Promise<void> {
|
|
101
|
-
const log = logger();
|
|
102
|
-
const db = getDatabase();
|
|
103
|
-
|
|
104
|
-
for (const [model, prices] of Object.entries(DEFAULT_PRICING)) {
|
|
105
|
-
const existing = await db.execute({
|
|
106
|
-
sql: 'SELECT model FROM model_pricing WHERE model = ?',
|
|
107
|
-
args: [model],
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (existing.rows.length === 0) {
|
|
111
|
-
const info = MODEL_REGISTRY.find((m) => m.id === model);
|
|
112
|
-
await db.execute({
|
|
113
|
-
sql: `INSERT INTO model_pricing (model, input_cost_per_1m, output_cost_per_1m, tier, last_updated)
|
|
114
|
-
VALUES (?, ?, ?, ?, datetime('now'))`,
|
|
115
|
-
args: [model, prices.input, prices.output, info?.tier ?? 'standard'],
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
log.info('Pricing refreshed');
|
|
121
|
-
}
|
package/src/models/registry.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model registry — defines available models, tiers, and selection logic.
|
|
3
|
-
* Models are sourced from the GitHub Copilot SDK's available model list.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type ModelTier = 'fast' | 'standard' | 'reasoning';
|
|
7
|
-
|
|
8
|
-
export interface ModelInfo {
|
|
9
|
-
id: string;
|
|
10
|
-
tier: ModelTier;
|
|
11
|
-
description: string;
|
|
12
|
-
maxContext: number; // approximate token window
|
|
13
|
-
inputCostPer1M?: number; // USD per 1M input tokens
|
|
14
|
-
outputCostPer1M?: number; // USD per 1M output tokens
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Known models available via GitHub Copilot SDK (as of 2026-05).
|
|
19
|
-
*/
|
|
20
|
-
export const MODEL_REGISTRY: ModelInfo[] = [
|
|
21
|
-
// Fast tier — low-latency, cost-effective
|
|
22
|
-
{
|
|
23
|
-
id: 'claude-haiku-4.5',
|
|
24
|
-
tier: 'fast',
|
|
25
|
-
description: 'Claude Haiku 4.5 — fast, cheap',
|
|
26
|
-
maxContext: 200_000,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: 'gpt-5-mini',
|
|
30
|
-
tier: 'fast',
|
|
31
|
-
description: 'GPT-5 Mini — fast responses',
|
|
32
|
-
maxContext: 128_000,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 'gpt-5.4-mini',
|
|
36
|
-
tier: 'fast',
|
|
37
|
-
description: 'GPT-5.4 Mini — fast, latest',
|
|
38
|
-
maxContext: 128_000,
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
// Standard tier — balanced capability/cost
|
|
42
|
-
{
|
|
43
|
-
id: 'claude-sonnet-4.5',
|
|
44
|
-
tier: 'standard',
|
|
45
|
-
description: 'Claude Sonnet 4.5',
|
|
46
|
-
maxContext: 200_000,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 'claude-sonnet-4.6',
|
|
50
|
-
tier: 'standard',
|
|
51
|
-
description: 'Claude Sonnet 4.6',
|
|
52
|
-
maxContext: 200_000,
|
|
53
|
-
},
|
|
54
|
-
{ id: 'gpt-5.2', tier: 'standard', description: 'GPT-5.2', maxContext: 128_000 },
|
|
55
|
-
{ id: 'gpt-5.4', tier: 'standard', description: 'GPT-5.4', maxContext: 128_000 },
|
|
56
|
-
{
|
|
57
|
-
id: 'gpt-5.5',
|
|
58
|
-
tier: 'standard',
|
|
59
|
-
description: 'GPT-5.5 — latest standard',
|
|
60
|
-
maxContext: 128_000,
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: 'gpt-4.1',
|
|
64
|
-
tier: 'standard',
|
|
65
|
-
description: 'GPT-4.1 — legacy standard',
|
|
66
|
-
maxContext: 128_000,
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
// Reasoning tier — highest capability, most expensive
|
|
70
|
-
{ id: 'claude-opus-4.5', tier: 'reasoning', description: 'Claude Opus 4.5', maxContext: 200_000 },
|
|
71
|
-
{ id: 'claude-opus-4.6', tier: 'reasoning', description: 'Claude Opus 4.6', maxContext: 200_000 },
|
|
72
|
-
{ id: 'claude-opus-4.7', tier: 'reasoning', description: 'Claude Opus 4.7', maxContext: 200_000 },
|
|
73
|
-
{
|
|
74
|
-
id: 'claude-opus-4.8',
|
|
75
|
-
tier: 'reasoning',
|
|
76
|
-
description: 'Claude Opus 4.8 — latest',
|
|
77
|
-
maxContext: 200_000,
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
id: 'gpt-5.2-codex',
|
|
81
|
-
tier: 'reasoning',
|
|
82
|
-
description: 'GPT-5.2 Codex — code-focused reasoning',
|
|
83
|
-
maxContext: 128_000,
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'gpt-5.3-codex',
|
|
87
|
-
tier: 'reasoning',
|
|
88
|
-
description: 'GPT-5.3 Codex — code reasoning',
|
|
89
|
-
maxContext: 128_000,
|
|
90
|
-
},
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
/** Get all models in a tier */
|
|
94
|
-
export function getModelsByTier(tier: ModelTier): ModelInfo[] {
|
|
95
|
-
return MODEL_REGISTRY.filter((m) => m.tier === tier);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Get a specific model by ID */
|
|
99
|
-
export function getModel(modelId: string): ModelInfo | undefined {
|
|
100
|
-
return MODEL_REGISTRY.find((m) => m.id === modelId);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Default model per tier */
|
|
104
|
-
export const DEFAULT_MODELS: Record<ModelTier, string> = {
|
|
105
|
-
fast: 'gpt-5.4-mini',
|
|
106
|
-
standard: 'claude-sonnet-4.6',
|
|
107
|
-
reasoning: 'claude-opus-4.6',
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Select the appropriate model tier for a task based on complexity.
|
|
112
|
-
* Used by team leads to choose models for agent task execution.
|
|
113
|
-
*/
|
|
114
|
-
export function selectModelForTask(params: {
|
|
115
|
-
taskDescription: string;
|
|
116
|
-
isCodeGeneration?: boolean;
|
|
117
|
-
requiresReasoning?: boolean;
|
|
118
|
-
preferFast?: boolean;
|
|
119
|
-
}): string {
|
|
120
|
-
if (params.preferFast) {
|
|
121
|
-
return DEFAULT_MODELS.fast;
|
|
122
|
-
}
|
|
123
|
-
if (params.requiresReasoning) {
|
|
124
|
-
return params.isCodeGeneration ? 'gpt-5.3-codex' : DEFAULT_MODELS.reasoning;
|
|
125
|
-
}
|
|
126
|
-
if (params.isCodeGeneration) {
|
|
127
|
-
return DEFAULT_MODELS.standard;
|
|
128
|
-
}
|
|
129
|
-
// Default: standard tier
|
|
130
|
-
return DEFAULT_MODELS.standard;
|
|
131
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { createChildLogger } from '../logging/logger.js';
|
|
2
|
-
import { getDatabase } from '../store/db.js';
|
|
3
|
-
|
|
4
|
-
const logger = () => createChildLogger('token-tracker');
|
|
5
|
-
|
|
6
|
-
export interface TokenUsageRecord {
|
|
7
|
-
squadId?: string;
|
|
8
|
-
instanceId?: string;
|
|
9
|
-
agentRole?: string;
|
|
10
|
-
model: string;
|
|
11
|
-
inputTokens: number;
|
|
12
|
-
outputTokens: number;
|
|
13
|
-
estimatedCostUsd?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Record token usage from an LLM call.
|
|
18
|
-
*/
|
|
19
|
-
export async function recordTokenUsage(record: TokenUsageRecord): Promise<void> {
|
|
20
|
-
const db = getDatabase();
|
|
21
|
-
|
|
22
|
-
// Estimate cost if pricing is available
|
|
23
|
-
let cost = record.estimatedCostUsd;
|
|
24
|
-
if (cost === undefined) {
|
|
25
|
-
cost = await estimateCost(record.model, record.inputTokens, record.outputTokens);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
await db.execute({
|
|
29
|
-
sql: `INSERT INTO token_usage (squad_id, instance_id, agent_role, model, input_tokens, output_tokens, estimated_cost_usd)
|
|
30
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
31
|
-
args: [
|
|
32
|
-
record.squadId ?? null,
|
|
33
|
-
record.instanceId ?? null,
|
|
34
|
-
record.agentRole ?? null,
|
|
35
|
-
record.model,
|
|
36
|
-
record.inputTokens,
|
|
37
|
-
record.outputTokens,
|
|
38
|
-
cost ?? null,
|
|
39
|
-
],
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Estimate cost for a call based on stored pricing.
|
|
45
|
-
*/
|
|
46
|
-
async function estimateCost(
|
|
47
|
-
model: string,
|
|
48
|
-
inputTokens: number,
|
|
49
|
-
outputTokens: number,
|
|
50
|
-
): Promise<number | undefined> {
|
|
51
|
-
const db = getDatabase();
|
|
52
|
-
const result = await db.execute({
|
|
53
|
-
sql: 'SELECT input_cost_per_1m, output_cost_per_1m FROM model_pricing WHERE model = ?',
|
|
54
|
-
args: [model],
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (result.rows.length === 0) return undefined;
|
|
58
|
-
|
|
59
|
-
const row = result.rows[0];
|
|
60
|
-
const inputCost = ((row.input_cost_per_1m as number) / 1_000_000) * inputTokens;
|
|
61
|
-
const outputCost = ((row.output_cost_per_1m as number) / 1_000_000) * outputTokens;
|
|
62
|
-
return inputCost + outputCost;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Query token usage with filters.
|
|
67
|
-
*/
|
|
68
|
-
export async function queryUsage(filters?: {
|
|
69
|
-
squadId?: string;
|
|
70
|
-
agentRole?: string;
|
|
71
|
-
model?: string;
|
|
72
|
-
since?: string; // ISO date
|
|
73
|
-
until?: string; // ISO date
|
|
74
|
-
}): Promise<{
|
|
75
|
-
records: Array<{
|
|
76
|
-
model: string;
|
|
77
|
-
inputTokens: number;
|
|
78
|
-
outputTokens: number;
|
|
79
|
-
estimatedCostUsd: number | null;
|
|
80
|
-
timestamp: string;
|
|
81
|
-
squadId: string | null;
|
|
82
|
-
agentRole: string | null;
|
|
83
|
-
}>;
|
|
84
|
-
totals: {
|
|
85
|
-
totalInputTokens: number;
|
|
86
|
-
totalOutputTokens: number;
|
|
87
|
-
totalCostUsd: number;
|
|
88
|
-
callCount: number;
|
|
89
|
-
};
|
|
90
|
-
}> {
|
|
91
|
-
const db = getDatabase();
|
|
92
|
-
const conditions: string[] = [];
|
|
93
|
-
const args: (string | null)[] = [];
|
|
94
|
-
|
|
95
|
-
if (filters?.squadId) {
|
|
96
|
-
conditions.push('squad_id = ?');
|
|
97
|
-
args.push(filters.squadId);
|
|
98
|
-
}
|
|
99
|
-
if (filters?.agentRole) {
|
|
100
|
-
conditions.push('agent_role = ?');
|
|
101
|
-
args.push(filters.agentRole);
|
|
102
|
-
}
|
|
103
|
-
if (filters?.model) {
|
|
104
|
-
conditions.push('model = ?');
|
|
105
|
-
args.push(filters.model);
|
|
106
|
-
}
|
|
107
|
-
if (filters?.since) {
|
|
108
|
-
conditions.push('timestamp >= ?');
|
|
109
|
-
args.push(filters.since);
|
|
110
|
-
}
|
|
111
|
-
if (filters?.until) {
|
|
112
|
-
conditions.push('timestamp <= ?');
|
|
113
|
-
args.push(filters.until);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
117
|
-
|
|
118
|
-
const result = await db.execute({
|
|
119
|
-
sql: `SELECT model, input_tokens, output_tokens, estimated_cost_usd, timestamp, squad_id, agent_role
|
|
120
|
-
FROM token_usage ${where} ORDER BY timestamp DESC LIMIT 500`,
|
|
121
|
-
args,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const records = result.rows.map((row) => ({
|
|
125
|
-
model: row.model as string,
|
|
126
|
-
inputTokens: row.input_tokens as number,
|
|
127
|
-
outputTokens: row.output_tokens as number,
|
|
128
|
-
estimatedCostUsd: row.estimated_cost_usd as number | null,
|
|
129
|
-
timestamp: row.timestamp as string,
|
|
130
|
-
squadId: row.squad_id as string | null,
|
|
131
|
-
agentRole: row.agent_role as string | null,
|
|
132
|
-
}));
|
|
133
|
-
|
|
134
|
-
// Totals
|
|
135
|
-
const totalsResult = await db.execute({
|
|
136
|
-
sql: `SELECT COUNT(*) as cnt, COALESCE(SUM(input_tokens), 0) as total_in,
|
|
137
|
-
COALESCE(SUM(output_tokens), 0) as total_out, COALESCE(SUM(estimated_cost_usd), 0) as total_cost
|
|
138
|
-
FROM token_usage ${where}`,
|
|
139
|
-
args,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const totalsRow = totalsResult.rows[0];
|
|
143
|
-
const totals = {
|
|
144
|
-
callCount: (totalsRow?.cnt as number) ?? 0,
|
|
145
|
-
totalInputTokens: (totalsRow?.total_in as number) ?? 0,
|
|
146
|
-
totalOutputTokens: (totalsRow?.total_out as number) ?? 0,
|
|
147
|
-
totalCostUsd: (totalsRow?.total_cost as number) ?? 0,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
return { records, totals };
|
|
151
|
-
}
|
package/src/scheduler/engine.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { sendMessage } from '../copilot/orchestrator.js';
|
|
2
|
-
import { createChildLogger } from '../logging/logger.js';
|
|
3
|
-
import { getEventBus } from '../squad/event-bus.js';
|
|
4
|
-
import { runInstance } from '../squad/execution/runner.js';
|
|
5
|
-
import { getSquadByName, listSquads } from '../squad/manager.js';
|
|
6
|
-
import { addInboxEntry } from '../store/inbox.js';
|
|
7
|
-
import { type Schedule, getDueSchedules, markScheduleFired } from '../store/schedules.js';
|
|
8
|
-
|
|
9
|
-
const logger = () => createChildLogger('scheduler');
|
|
10
|
-
|
|
11
|
-
let intervalHandle: ReturnType<typeof setInterval> | null = null;
|
|
12
|
-
let running = false;
|
|
13
|
-
|
|
14
|
-
const TICK_INTERVAL_MS = 60_000; // Check every minute
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Start the schedule engine. Evaluates due schedules every 60 seconds.
|
|
18
|
-
*/
|
|
19
|
-
export function startScheduler(): void {
|
|
20
|
-
const log = logger();
|
|
21
|
-
log.info('Scheduler started');
|
|
22
|
-
|
|
23
|
-
// Initial tick
|
|
24
|
-
tick();
|
|
25
|
-
|
|
26
|
-
intervalHandle = setInterval(() => {
|
|
27
|
-
tick();
|
|
28
|
-
}, TICK_INTERVAL_MS);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Stop the schedule engine.
|
|
33
|
-
*/
|
|
34
|
-
export function stopScheduler(): void {
|
|
35
|
-
if (intervalHandle) {
|
|
36
|
-
clearInterval(intervalHandle);
|
|
37
|
-
intervalHandle = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function tick(): Promise<void> {
|
|
42
|
-
if (running) return; // Prevent overlapping ticks
|
|
43
|
-
running = true;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const due = await getDueSchedules();
|
|
47
|
-
if (due.length === 0) {
|
|
48
|
-
running = false;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const log = logger();
|
|
53
|
-
log.info({ count: due.length }, 'Firing due schedules');
|
|
54
|
-
|
|
55
|
-
for (const schedule of due) {
|
|
56
|
-
await fireSchedule(schedule);
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
logger().error({ err }, 'Scheduler tick error');
|
|
60
|
-
} finally {
|
|
61
|
-
running = false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function fireSchedule(schedule: Schedule): Promise<void> {
|
|
66
|
-
const log = logger();
|
|
67
|
-
const bus = getEventBus();
|
|
68
|
-
|
|
69
|
-
// Emit fired event
|
|
70
|
-
bus.emit({
|
|
71
|
-
type: 'schedule:fired',
|
|
72
|
-
id: crypto.randomUUID(),
|
|
73
|
-
timestamp: new Date(),
|
|
74
|
-
data: { scheduleId: schedule.id, name: schedule.name },
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
log.info({ scheduleId: schedule.id, name: schedule.name }, 'Firing schedule');
|
|
78
|
-
|
|
79
|
-
// Mark as fired immediately (update next_run)
|
|
80
|
-
await markScheduleFired(schedule.id, schedule.cron);
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
let result: string;
|
|
84
|
-
|
|
85
|
-
if (schedule.targetType === 'orchestrator') {
|
|
86
|
-
// Send as if user typed it
|
|
87
|
-
result = await sendMessage(schedule.prompt, 'web', () => {});
|
|
88
|
-
} else {
|
|
89
|
-
// Squad target — run instance
|
|
90
|
-
const squads = await listSquads();
|
|
91
|
-
const squad = squads.find((s) => s.id === schedule.targetId || s.name === schedule.targetId);
|
|
92
|
-
|
|
93
|
-
if (!squad) {
|
|
94
|
-
throw new Error(`Target squad not found: ${schedule.targetId}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const runResult = await runInstance({
|
|
98
|
-
squad,
|
|
99
|
-
objective: schedule.prompt,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (runResult.success) {
|
|
103
|
-
result = runResult.pr
|
|
104
|
-
? `Completed successfully. PR: ${runResult.pr.url}`
|
|
105
|
-
: 'Completed successfully (no PR created).';
|
|
106
|
-
} else {
|
|
107
|
-
result = `Failed: ${runResult.error ?? 'unknown error'}`;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Post result to inbox as deliverable
|
|
112
|
-
const squadId =
|
|
113
|
-
schedule.targetType === 'squad' && schedule.targetId ? schedule.targetId : 'orchestrator';
|
|
114
|
-
|
|
115
|
-
// Only post to inbox if we have a real squad target
|
|
116
|
-
if (schedule.targetType === 'squad' && schedule.targetId) {
|
|
117
|
-
await addInboxEntry({
|
|
118
|
-
squadId: schedule.targetId,
|
|
119
|
-
kind: 'deliverable',
|
|
120
|
-
title: `Schedule: ${schedule.name}`,
|
|
121
|
-
content: result,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Emit completed event
|
|
126
|
-
bus.emit({
|
|
127
|
-
type: 'schedule:completed',
|
|
128
|
-
id: crypto.randomUUID(),
|
|
129
|
-
timestamp: new Date(),
|
|
130
|
-
data: { scheduleId: schedule.id, name: schedule.name, result: result.slice(0, 200) },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
log.info({ scheduleId: schedule.id }, 'Schedule completed');
|
|
134
|
-
} catch (err) {
|
|
135
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
136
|
-
log.error({ err, scheduleId: schedule.id }, 'Schedule execution failed');
|
|
137
|
-
|
|
138
|
-
// Emit failed event
|
|
139
|
-
bus.emit({
|
|
140
|
-
type: 'schedule:failed',
|
|
141
|
-
id: crypto.randomUUID(),
|
|
142
|
-
timestamp: new Date(),
|
|
143
|
-
data: { scheduleId: schedule.id, name: schedule.name, error: errorMsg },
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|