codehere 0.8.0 → 1.0.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.
Files changed (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,214 +0,0 @@
1
- /**
2
- * Title backfill — v0.6.0 follow-up to M9.
3
- *
4
- * The M9 hidden Title agent (generate-thread-title.ts) only fires on the FIRST
5
- * task entry written to a project's thread.jsonl. Projects seeded by the v0.6
6
- * migration (project-thread-migration.ts) bypass that path — they get a
7
- * project-meta header but no Title agent invocation. Result: 5/7 projects on
8
- * a typical dogfood machine show "(untitled)" in the /threads switcher even
9
- * though their first task carries enough signal to title from.
10
- *
11
- * This backfill closes that gap. For every project with title=null and at
12
- * least one task entry, it fires the Title agent on the first task's prompt +
13
- * cwd and appends a `{_meta: 'project-update', title: ...}` line. Reader
14
- * (project-thread-reader.ts) takes the latest project-update title.
15
- *
16
- * Idempotent: skips projects that already have a title (or that have NO tasks
17
- * to title from). Safe to run on every daemon boot. Best-effort: any LLM
18
- * failure (provider missing, timeout, rate limit) skips that project silently.
19
- *
20
- * Cost budget: ~1 LLM call per untitled project on first run after this lands.
21
- * Subsequent boots are no-ops since titles persist.
22
- */
23
- import { existsSync, readdirSync, readFileSync, appendFileSync, statSync } from 'fs';
24
- import { join } from 'path';
25
- import { homedir } from 'os';
26
- import { generateThreadTitle } from '../../application/use-cases/generate-thread-title.js';
27
- const DEFAULT_PROJECTS_DIR = join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'projects');
28
- function scanThread(slug, file) {
29
- let title = null;
30
- let firstPrompt = null;
31
- let firstCwd = null;
32
- let firstStartedAt = Number.MAX_SAFE_INTEGER;
33
- let content;
34
- try {
35
- content = readFileSync(file, 'utf-8');
36
- }
37
- catch {
38
- return { slug, title, firstPrompt, firstCwd };
39
- }
40
- for (const line of content.split('\n')) {
41
- const trimmed = line.trim();
42
- if (!trimmed)
43
- continue;
44
- let rec;
45
- try {
46
- rec = JSON.parse(trimmed);
47
- }
48
- catch {
49
- continue;
50
- }
51
- if (rec._meta === 'project') {
52
- if (typeof rec.title === 'string')
53
- title = rec.title;
54
- }
55
- else if (rec._meta === 'project-update') {
56
- if (typeof rec.title === 'string')
57
- title = rec.title;
58
- }
59
- else if (rec.type === 'task') {
60
- const startedAt = typeof rec.startedAt === 'number' ? rec.startedAt : Number.MAX_SAFE_INTEGER;
61
- if (startedAt < firstStartedAt) {
62
- firstStartedAt = startedAt;
63
- if (typeof rec.prompt === 'string')
64
- firstPrompt = rec.prompt;
65
- if (typeof rec.cwd === 'string')
66
- firstCwd = rec.cwd;
67
- }
68
- }
69
- }
70
- return { slug, title, firstPrompt, firstCwd };
71
- }
72
- /**
73
- * Backfill missing titles. Returns counts; never throws (best-effort per project).
74
- *
75
- * Disable via CODEHERE_DISABLE_TITLE_AGENT=1 (same env var that gates M9 itself —
76
- * keeps the toggle surface minimal).
77
- */
78
- export async function backfillThreadTitles(options) {
79
- const start = Date.now();
80
- const result = {
81
- scanned: 0,
82
- alreadyTitled: 0,
83
- noTasks: 0,
84
- titlesGenerated: 0,
85
- titleAgentFailed: 0,
86
- durationMs: 0,
87
- };
88
- if (process.env.CODEHERE_DISABLE_TITLE_AGENT === '1') {
89
- result.durationMs = Date.now() - start;
90
- return result;
91
- }
92
- const root = options?.projectsDir ?? DEFAULT_PROJECTS_DIR;
93
- if (!existsSync(root)) {
94
- result.durationMs = Date.now() - start;
95
- return result;
96
- }
97
- let stat;
98
- try {
99
- stat = statSync(root);
100
- }
101
- catch {
102
- result.durationMs = Date.now() - start;
103
- return result;
104
- }
105
- if (!stat.isDirectory()) {
106
- result.durationMs = Date.now() - start;
107
- return result;
108
- }
109
- let slugs;
110
- try {
111
- slugs = readdirSync(root);
112
- }
113
- catch {
114
- result.durationMs = Date.now() - start;
115
- return result;
116
- }
117
- const cap = options?.maxProjects ?? Number.MAX_SAFE_INTEGER;
118
- let backfilledCount = 0;
119
- for (const slug of slugs) {
120
- if (backfilledCount >= cap)
121
- break;
122
- const projectDir = join(root, slug);
123
- let s;
124
- try {
125
- s = statSync(projectDir);
126
- }
127
- catch {
128
- continue;
129
- }
130
- if (!s.isDirectory())
131
- continue;
132
- const file = join(projectDir, 'thread.jsonl');
133
- if (!existsSync(file))
134
- continue;
135
- result.scanned++;
136
- const scanned = scanThread(slug, file);
137
- if (scanned.title) {
138
- result.alreadyTitled++;
139
- continue;
140
- }
141
- if (!scanned.firstPrompt || !scanned.firstCwd) {
142
- // Nothing to title from — skip. (Migration-seeded headers without task
143
- // entries fall here, as do projects where the first task lost its prompt
144
- // somehow.)
145
- result.noTasks++;
146
- continue;
147
- }
148
- let title;
149
- try {
150
- title = await generateThreadTitle(scanned.firstPrompt, scanned.firstCwd);
151
- }
152
- catch {
153
- // Title agent should never throw (it returns null on failure), but guard
154
- // anyway so one bad call doesn't sink the whole backfill.
155
- title = null;
156
- }
157
- if (!title) {
158
- result.titleAgentFailed++;
159
- continue;
160
- }
161
- // Race guard: backfill scanned BEFORE the LLM call, but the LLM blocks
162
- // for seconds. A user rename in that window leaves a userOverride:true
163
- // marker the pre-scan never saw. Re-read the same file just before the
164
- // write and skip if the user got there first. (Inlined rather than calling
165
- // hasManualOverride() because that helper hardcodes the production
166
- // projects dir; backfill supports a custom projectsDir for tests.)
167
- let userBeatUs = false;
168
- try {
169
- const recheck = readFileSync(file, 'utf-8');
170
- for (const line of recheck.split('\n')) {
171
- const t = line.trim();
172
- if (!t)
173
- continue;
174
- try {
175
- const rec = JSON.parse(t);
176
- if (rec._meta === 'project-update' && rec.userOverride === true) {
177
- userBeatUs = true;
178
- break;
179
- }
180
- }
181
- catch {
182
- /* skip malformed */
183
- }
184
- }
185
- }
186
- catch {
187
- /* if we can't read, fall through and let the append fail */
188
- }
189
- if (userBeatUs) {
190
- result.alreadyTitled++;
191
- continue;
192
- }
193
- try {
194
- const updateLine = JSON.stringify({
195
- _meta: 'project-update',
196
- slug,
197
- title,
198
- cwd: scanned.firstCwd,
199
- backfilledAt: Date.now(),
200
- updatedAt: Date.now(),
201
- });
202
- appendFileSync(file, updateLine + '\n', 'utf-8');
203
- result.titlesGenerated++;
204
- backfilledCount++;
205
- }
206
- catch {
207
- // Append failure is logged via counter but doesn't sink the loop.
208
- result.titleAgentFailed++;
209
- }
210
- }
211
- result.durationMs = Date.now() - start;
212
- return result;
213
- }
214
- //# sourceMappingURL=project-thread-title-backfill.js.map
@@ -1,206 +0,0 @@
1
- /**
2
- * Infrastructure: Task Repository
3
- * Task storage and retrieval using JSONL format
4
- *
5
- * Phase 1: v0.5.0-alpha
6
- * See: .internal/ARCHITECTURE.md (internal docs)
7
- */
8
- import { appendFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
9
- import { join, dirname } from 'path';
10
- import { findCodebaseRoot } from '../../domain/services/codebase-detector.js';
11
- /**
12
- * JSONL-based task repository
13
- * Stores tasks in .codehere/tasks.jsonl at codebase root
14
- */
15
- export class JsonlTaskRepository {
16
- tasksPath;
17
- tasksCache = new Map();
18
- cacheLoaded = false;
19
- constructor(customPath) {
20
- if (customPath) {
21
- this.tasksPath = customPath;
22
- }
23
- else {
24
- // Use environment variable or detect from codebase root
25
- const envPath = process.env.CODEHERE_TASKS_PATH;
26
- if (envPath) {
27
- this.tasksPath = envPath;
28
- }
29
- else {
30
- // Detect codebase root and use .codehere/tasks.jsonl
31
- const codebaseInfo = findCodebaseRoot(process.cwd());
32
- const codebaseRoot = codebaseInfo?.rootPath || process.cwd();
33
- const codehereDir = join(codebaseRoot, '.codehere');
34
- // Ensure .codehere directory exists
35
- if (!existsSync(codehereDir)) {
36
- mkdirSync(codehereDir, { recursive: true });
37
- }
38
- this.tasksPath = join(codehereDir, 'tasks.jsonl');
39
- }
40
- }
41
- // Ensure directory exists
42
- const tasksDir = dirname(this.tasksPath);
43
- if (!existsSync(tasksDir)) {
44
- mkdirSync(tasksDir, { recursive: true });
45
- }
46
- }
47
- /**
48
- * Load tasks from JSONL file into cache
49
- * For duplicate IDs, keeps the latest version (by updatedAt)
50
- */
51
- loadTasks() {
52
- if (this.cacheLoaded) {
53
- return;
54
- }
55
- if (!existsSync(this.tasksPath)) {
56
- this.cacheLoaded = true;
57
- return;
58
- }
59
- try {
60
- const content = readFileSync(this.tasksPath, 'utf-8');
61
- const lines = content
62
- .trim()
63
- .split('\n')
64
- .filter((line) => line.trim());
65
- // Load all tasks, then keep only the latest version of each ID
66
- const allTasks = [];
67
- for (const line of lines) {
68
- try {
69
- const task = JSON.parse(line);
70
- allTasks.push(task);
71
- }
72
- catch {
73
- // Skip invalid lines
74
- }
75
- }
76
- // Group by ID and keep latest version
77
- const taskMap = new Map();
78
- for (const task of allTasks) {
79
- const existing = taskMap.get(task.id);
80
- if (!existing || task.updatedAt > existing.updatedAt) {
81
- taskMap.set(task.id, task);
82
- }
83
- }
84
- this.tasksCache = taskMap;
85
- this.cacheLoaded = true;
86
- }
87
- catch (error) {
88
- // Silent failure - don't break user workflow
89
- console.error('[TaskRepository] Failed to load tasks:', error);
90
- this.cacheLoaded = true;
91
- }
92
- }
93
- /**
94
- * Create a new task
95
- */
96
- async create(task) {
97
- // Ensure cache is loaded
98
- this.loadTasks();
99
- // Set timestamps if not provided
100
- const now = Date.now();
101
- const taskToCreate = {
102
- ...task,
103
- createdAt: task.createdAt || now,
104
- updatedAt: task.updatedAt || now,
105
- };
106
- // Add to cache
107
- this.tasksCache.set(taskToCreate.id, taskToCreate);
108
- // Append to JSONL file (append-only, immutable history)
109
- try {
110
- appendFileSync(this.tasksPath, JSON.stringify(taskToCreate) + '\n', 'utf-8');
111
- }
112
- catch (error) {
113
- // Silent failure - remove from cache if write failed
114
- this.tasksCache.delete(taskToCreate.id);
115
- console.error('[TaskRepository] Failed to create task:', error);
116
- throw error;
117
- }
118
- }
119
- /**
120
- * Get task by ID (returns latest version if multiple exist)
121
- */
122
- async getById(id) {
123
- this.loadTasks();
124
- // Load all tasks and find the latest version of this ID
125
- const allTasks = Array.from(this.tasksCache.values());
126
- const tasksWithId = allTasks.filter((task) => task.id === id);
127
- if (tasksWithId.length === 0) {
128
- return null;
129
- }
130
- // Return the task with the latest updatedAt timestamp
131
- return tasksWithId.reduce((latest, current) => {
132
- return current.updatedAt > latest.updatedAt ? current : latest;
133
- });
134
- }
135
- /**
136
- * Get tasks by status
137
- */
138
- async getByStatus(status) {
139
- this.loadTasks();
140
- const tasks = Array.from(this.tasksCache.values());
141
- return tasks.filter((task) => task.status === status);
142
- }
143
- /**
144
- * Get tasks by mode
145
- */
146
- async getByMode(mode) {
147
- this.loadTasks();
148
- const tasks = Array.from(this.tasksCache.values());
149
- return tasks.filter((task) => task.mode === mode);
150
- }
151
- /**
152
- * Get recent tasks (latest updated first)
153
- */
154
- async getRecent(limit = 10) {
155
- this.loadTasks();
156
- const tasks = Array.from(this.tasksCache.values());
157
- return tasks.sort((a, b) => b.updatedAt - a.updatedAt).slice(0, limit);
158
- }
159
- /**
160
- * Get all tasks (latest version of each)
161
- */
162
- async getAll() {
163
- this.loadTasks();
164
- return Array.from(this.tasksCache.values());
165
- }
166
- /**
167
- * Update existing task
168
- * Creates a new entry in JSONL (append-only)
169
- */
170
- async update(task) {
171
- // Ensure cache is loaded
172
- this.loadTasks();
173
- // Update timestamp
174
- const updatedTask = {
175
- ...task,
176
- updatedAt: Date.now(),
177
- };
178
- // Update cache
179
- this.tasksCache.set(updatedTask.id, updatedTask);
180
- // Append updated version to JSONL (append-only, immutable history)
181
- try {
182
- appendFileSync(this.tasksPath, JSON.stringify(updatedTask) + '\n', 'utf-8');
183
- }
184
- catch (error) {
185
- // Revert cache if write failed
186
- const original = await this.getById(task.id);
187
- if (original) {
188
- this.tasksCache.set(task.id, original);
189
- }
190
- console.error('[TaskRepository] Failed to update task:', error);
191
- throw error;
192
- }
193
- }
194
- }
195
- // Singleton instance (lazy initialization with codebase detection)
196
- let taskRepositoryInstance = null;
197
- /**
198
- * Get task repository instance
199
- */
200
- export function getTaskRepository(customPath) {
201
- if (!taskRepositoryInstance || customPath) {
202
- taskRepositoryInstance = new JsonlTaskRepository(customPath);
203
- }
204
- return taskRepositoryInstance;
205
- }
206
- //# sourceMappingURL=task-repository.js.map
@@ -1,212 +0,0 @@
1
- /**
2
- * Enterprise Monitoring & Health Check System
3
- * Ensures 24/7 operation visibility
4
- */
5
- import { existsSync, mkdirSync } from 'fs';
6
- import { join, dirname } from 'path';
7
- import { fileURLToPath } from 'url';
8
- import { cohereCircuitBreaker } from './error-handler.js';
9
- class MetricsCollector {
10
- operations = {
11
- total: 0,
12
- successful: 0,
13
- failed: 0,
14
- retries: 0,
15
- };
16
- performance = {
17
- responseTimes: [],
18
- retryTimes: [],
19
- apiCalls: 0,
20
- cacheHits: 0,
21
- };
22
- errors = {
23
- byType: {},
24
- recent: [],
25
- };
26
- recordOperation(success, responseTime, retries) {
27
- this.operations.total++;
28
- if (success) {
29
- this.operations.successful++;
30
- }
31
- else {
32
- this.operations.failed++;
33
- }
34
- if (retries) {
35
- this.operations.retries += retries;
36
- }
37
- if (responseTime) {
38
- this.performance.responseTimes.push(responseTime);
39
- // Keep only last 1000 measurements
40
- if (this.performance.responseTimes.length > 1000) {
41
- this.performance.responseTimes.shift();
42
- }
43
- }
44
- }
45
- recordApiCall() {
46
- this.performance.apiCalls++;
47
- }
48
- recordCacheHit() {
49
- this.performance.cacheHits++;
50
- }
51
- recordError(type, message) {
52
- this.errors.byType[type] = (this.errors.byType[type] || 0) + 1;
53
- this.errors.recent.push({
54
- timestamp: new Date().toISOString(),
55
- type,
56
- message,
57
- });
58
- // Keep only last 100 errors
59
- if (this.errors.recent.length > 100) {
60
- this.errors.recent.shift();
61
- }
62
- }
63
- getMetrics() {
64
- const avgResponseTime = this.performance.responseTimes.length > 0
65
- ? this.performance.responseTimes.reduce((a, b) => a + b, 0) / this.performance.responseTimes.length
66
- : 0;
67
- const avgRetryTime = this.performance.retryTimes.length > 0
68
- ? this.performance.retryTimes.reduce((a, b) => a + b, 0) / this.performance.retryTimes.length
69
- : 0;
70
- return {
71
- operations: { ...this.operations },
72
- performance: {
73
- avgResponseTime,
74
- avgRetryTime,
75
- apiCalls: this.performance.apiCalls,
76
- cacheHits: this.performance.cacheHits,
77
- },
78
- errors: {
79
- byType: { ...this.errors.byType },
80
- recent: [...this.errors.recent],
81
- },
82
- };
83
- }
84
- reset() {
85
- this.operations = { total: 0, successful: 0, failed: 0, retries: 0 };
86
- this.performance = { responseTimes: [], retryTimes: [], apiCalls: 0, cacheHits: 0 };
87
- this.errors = { byType: {}, recent: [] };
88
- }
89
- }
90
- export const metricsCollector = new MetricsCollector();
91
- /**
92
- * Check API health
93
- */
94
- function checkApi() {
95
- const circuitState = cohereCircuitBreaker.getState();
96
- if (circuitState === 'OPEN') {
97
- return { status: 'circuit_open', message: 'API circuit breaker is OPEN - too many failures' };
98
- }
99
- if (circuitState === 'HALF_OPEN') {
100
- return { status: 'error', message: 'API circuit breaker is HALF_OPEN - recovering' };
101
- }
102
- // Check if any API key is configured
103
- const hasKey = !!(process.env.ANTHROPIC_API_KEY ||
104
- process.env.OPENAI_API_KEY ||
105
- process.env.COHERE_API_KEY ||
106
- process.env.OPENROUTER_API_KEY);
107
- if (!hasKey) {
108
- return { status: 'error', message: 'No API key configured' };
109
- }
110
- return { status: 'ok', message: 'API accessible' };
111
- }
112
- /**
113
- * Check filesystem health
114
- */
115
- function checkFilesystem() {
116
- try {
117
- const __filename = fileURLToPath(import.meta.url);
118
- const __dirname = dirname(__filename);
119
- const agentDir = join(__dirname, '..');
120
- const dataDir = join(agentDir, 'data');
121
- // Check if data directory exists or can be created
122
- if (!existsSync(dataDir)) {
123
- // Try to create it
124
- try {
125
- mkdirSync(dataDir, { recursive: true });
126
- }
127
- catch {
128
- return { status: 'error', message: 'Cannot create data directory' };
129
- }
130
- }
131
- return { status: 'ok', message: 'Filesystem accessible' };
132
- }
133
- catch (error) {
134
- return {
135
- status: 'error',
136
- message: `Filesystem check failed: ${error instanceof Error ? error.message : String(error)}`,
137
- };
138
- }
139
- }
140
- /**
141
- * Check memory usage
142
- */
143
- function checkMemory() {
144
- try {
145
- const usage = process.memoryUsage();
146
- const heapUsedMB = usage.heapUsed / 1024 / 1024;
147
- const heapTotalMB = usage.heapTotal / 1024 / 1024;
148
- const usagePercent = (heapUsedMB / heapTotalMB) * 100;
149
- if (usagePercent > 90) {
150
- return {
151
- status: 'warning',
152
- message: `High memory usage: ${usagePercent.toFixed(1)}%`,
153
- usage: usagePercent,
154
- };
155
- }
156
- return {
157
- status: 'ok',
158
- message: `Memory usage: ${usagePercent.toFixed(1)}%`,
159
- usage: usagePercent,
160
- };
161
- }
162
- catch {
163
- return {
164
- status: 'ok',
165
- message: 'Memory check unavailable',
166
- };
167
- }
168
- }
169
- /**
170
- * Perform health check
171
- */
172
- export function performHealthCheck() {
173
- const apiCheck = checkApi();
174
- const fsCheck = checkFilesystem();
175
- const memCheck = checkMemory();
176
- const checks = {
177
- api: apiCheck,
178
- filesystem: fsCheck,
179
- memory: memCheck,
180
- };
181
- let status = 'healthy';
182
- if (apiCheck.status === 'circuit_open' || fsCheck.status === 'error') {
183
- status = 'unhealthy';
184
- }
185
- else if (apiCheck.status === 'error' || memCheck.status === 'warning') {
186
- status = 'degraded';
187
- }
188
- return {
189
- status,
190
- timestamp: new Date().toISOString(),
191
- checks,
192
- };
193
- }
194
- /**
195
- * Get current metrics
196
- */
197
- export function getMetrics() {
198
- return metricsCollector.getMetrics();
199
- }
200
- /**
201
- * Format health status for display
202
- */
203
- export function formatHealthStatus(health) {
204
- const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '⚠' : '✗';
205
- let output = ` ${statusIcon} ${health.status.toUpperCase()}\n\n`;
206
- for (const [component, check] of Object.entries(health.checks)) {
207
- const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '⚠' : '✗';
208
- output += ` ${icon} ${component.padEnd(12)} ${check.message}\n`;
209
- }
210
- return output;
211
- }
212
- //# sourceMappingURL=monitoring.js.map