codehere 0.2.2 → 0.3.0-rc.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 (235) hide show
  1. package/README.md +88 -68
  2. package/dist/domain/entities/task.js +4 -4
  3. package/dist/error-handler.js +4 -9
  4. package/dist/index.js +253 -1219
  5. package/dist/infrastructure/config/product-context.js +7 -6
  6. package/dist/infrastructure/hooks/claude-sdk-hook.js +348 -72
  7. package/dist/infrastructure/observability/execution-tracer.js +11 -6
  8. package/dist/infrastructure/orchestration/agent-adapter.js +13 -0
  9. package/dist/infrastructure/orchestration/agent-installer.js +101 -0
  10. package/dist/infrastructure/orchestration/agents-config-seed.js +56 -0
  11. package/dist/infrastructure/orchestration/aider-adapter.js +319 -0
  12. package/dist/infrastructure/orchestration/artifacts-extractor.js +175 -0
  13. package/dist/infrastructure/orchestration/claude-code-adapter.js +352 -0
  14. package/dist/infrastructure/orchestration/codex-adapter.js +310 -0
  15. package/dist/infrastructure/orchestration/generic-cli-adapter.js +553 -0
  16. package/dist/infrastructure/orchestration/opencode-adapter.js +275 -0
  17. package/dist/infrastructure/orchestration/progress-aggregator.js +189 -0
  18. package/dist/infrastructure/orchestration/readiness.js +106 -0
  19. package/dist/infrastructure/orchestration/session-manager.js +272 -0
  20. package/dist/infrastructure/orchestration/session-persistence.js +109 -0
  21. package/dist/infrastructure/protocols/mcp-helpers.js +10 -30
  22. package/dist/infrastructure/protocols/mcp-server-impl.js +359 -21
  23. package/dist/infrastructure/protocols/ui-page.js +1827 -0
  24. package/dist/infrastructure/security/file-collection.js +115 -0
  25. package/dist/infrastructure/security/trust-scorer.js +301 -56
  26. package/dist/infrastructure/storage/task-repository.js +8 -7
  27. package/dist/monitoring.js +9 -38
  28. package/dist/presentation/cli/commands/help-command.js +96 -139
  29. package/dist/presentation/cli/commands/hook-command.js +17 -24
  30. package/dist/presentation/cli/commands/report-command.js +59 -72
  31. package/dist/presentation/cli/commands/setup-command.js +4 -4
  32. package/dist/presentation/cli/commands/status-command.js +321 -30
  33. package/dist/presentation/cli/commands/trust-command.js +84 -72
  34. package/dist/presentation/cli/error-display.js +17 -21
  35. package/dist/ui.js +11 -11
  36. package/package.json +24 -62
  37. package/dist/agents/base.js +0 -101
  38. package/dist/agents/orchestrator-model-driven.js +0 -141
  39. package/dist/agents/react-orchestrator-agent.js +0 -54
  40. package/dist/application/agents/execution-agent.js +0 -508
  41. package/dist/application/agents/multi-agent-orchestrator.js +0 -295
  42. package/dist/application/agents/planning-agent.js +0 -433
  43. package/dist/application/agents/validation-agent.js +0 -317
  44. package/dist/application/architectures/plan-and-act.js +0 -242
  45. package/dist/application/index.js +0 -12
  46. package/dist/application/services/agent-factory.js +0 -28
  47. package/dist/application/services/dependency-container.js +0 -596
  48. package/dist/application/services/git-context-service.js +0 -83
  49. package/dist/application/services/intelligent-router.js +0 -934
  50. package/dist/application/services/tool-executor-service.js +0 -94
  51. package/dist/application/use-cases/ask-question-use-case.js +0 -338
  52. package/dist/application/use-cases/edit-file-use-case.js +0 -830
  53. package/dist/application/use-cases/explain-file-use-case.js +0 -50
  54. package/dist/application/use-cases/index-codebase-use-case.js +0 -113
  55. package/dist/application/use-cases/planning-use-case.js +0 -276
  56. package/dist/application/use-cases/react-orchestration-use-case.js +0 -329
  57. package/dist/application/use-cases/reflexion-use-case.js +0 -30
  58. package/dist/application/workflows/deterministic-workflows.js +0 -189
  59. package/dist/ast-parser.js +0 -331
  60. package/dist/cache.js +0 -180
  61. package/dist/chat.js +0 -251
  62. package/dist/code-analysis.js +0 -327
  63. package/dist/context.js +0 -187
  64. package/dist/docs.js +0 -146
  65. package/dist/domain/entities/code-chunk.js +0 -6
  66. package/dist/domain/entities/decision.js +0 -9
  67. package/dist/domain/entities/edit.js +0 -6
  68. package/dist/domain/entities/knowledge-node.js +0 -9
  69. package/dist/domain/entities/knowledge-preservation.js +0 -10
  70. package/dist/domain/entities/memory.js +0 -6
  71. package/dist/domain/entities/pattern.js +0 -7
  72. package/dist/domain/entities/plan.js +0 -6
  73. package/dist/domain/entities/query.js +0 -6
  74. package/dist/domain/errors/embeddings-not-found-error.js +0 -13
  75. package/dist/domain/index.js +0 -19
  76. package/dist/domain/interfaces/ai-service.interface.js +0 -6
  77. package/dist/domain/interfaces/code-analyzer.interface.js +0 -6
  78. package/dist/domain/interfaces/decision-repository.interface.js +0 -9
  79. package/dist/domain/interfaces/embedding-repository.interface.js +0 -6
  80. package/dist/domain/interfaces/knowledge-graph-repository.interface.js +0 -9
  81. package/dist/domain/interfaces/knowledge-preservation-repository.interface.js +0 -8
  82. package/dist/domain/interfaces/memory-repository.interface.js +0 -6
  83. package/dist/domain/interfaces/pattern-repository.interface.js +0 -6
  84. package/dist/domain/interfaces/verification-service.interface.js +0 -6
  85. package/dist/domain/services/cross-team-pattern-sharing.js +0 -125
  86. package/dist/domain/services/decision-recorder.js +0 -223
  87. package/dist/domain/services/decision-retriever.js +0 -133
  88. package/dist/domain/services/environment-mode-manager.js +0 -22
  89. package/dist/domain/services/graded-memory-activation.js +0 -69
  90. package/dist/domain/services/knowledge-access.js +0 -132
  91. package/dist/domain/services/knowledge-extractor.js +0 -247
  92. package/dist/domain/services/knowledge-graph-builder.js +0 -268
  93. package/dist/domain/services/knowledge-graph-query.js +0 -187
  94. package/dist/domain/services/memory-retriever.js +0 -82
  95. package/dist/domain/services/memory-synthesizer.js +0 -91
  96. package/dist/domain/services/pattern-extractor.js +0 -442
  97. package/dist/domain/services/query-intent-classifier.js +0 -140
  98. package/dist/domain/services/react-loop.js +0 -186
  99. package/dist/domain/services/risk-tier-detector.js +0 -78
  100. package/dist/edit.js +0 -319
  101. package/dist/embed.js +0 -459
  102. package/dist/formatter.js +0 -168
  103. package/dist/git.js +0 -198
  104. package/dist/infrastructure/ai/cohere-ai-service.js +0 -607
  105. package/dist/infrastructure/ai/command-r-evaluation.js +0 -206
  106. package/dist/infrastructure/ai/model-config.js +0 -84
  107. package/dist/infrastructure/ai/provider-ai-service.js +0 -477
  108. package/dist/infrastructure/cache/embedding-cache.js +0 -175
  109. package/dist/infrastructure/cache/query-result-cache.js +0 -66
  110. package/dist/infrastructure/cache/response-cache.js +0 -48
  111. package/dist/infrastructure/cache/security-scan-cache.js +0 -47
  112. package/dist/infrastructure/chunking/ast-code-chunker.js +0 -19
  113. package/dist/infrastructure/chunking/code-aware-chunker.js +0 -397
  114. package/dist/infrastructure/context/context-compressor.js +0 -329
  115. package/dist/infrastructure/context/conversation-context.js +0 -128
  116. package/dist/infrastructure/context/session-manager.js +0 -94
  117. package/dist/infrastructure/cost/cost-tracker.js +0 -360
  118. package/dist/infrastructure/file-scanner/file-scanner.js +0 -106
  119. package/dist/infrastructure/governance/nist-ai-rmf.js +0 -292
  120. package/dist/infrastructure/governance/policy-as-code.js +0 -258
  121. package/dist/infrastructure/governance/prompt-to-code-lineage.js +0 -239
  122. package/dist/infrastructure/index.js +0 -7
  123. package/dist/infrastructure/observability/prompt-versioner.js +0 -145
  124. package/dist/infrastructure/protocols/a2a-metadata.js +0 -137
  125. package/dist/infrastructure/protocols/a2a-protocol.js +0 -314
  126. package/dist/infrastructure/protocols/mcp-oauth.js +0 -184
  127. package/dist/infrastructure/protocols/mcp-server.js +0 -333
  128. package/dist/infrastructure/protocols/streaming-handler.js +0 -103
  129. package/dist/infrastructure/providers/cohere-provider.js +0 -444
  130. package/dist/infrastructure/providers/fake-provider.js +0 -130
  131. package/dist/infrastructure/providers/local-llm-provider.js +0 -330
  132. package/dist/infrastructure/providers/model-config.types.js +0 -9
  133. package/dist/infrastructure/providers/model-provider.interface.js +0 -8
  134. package/dist/infrastructure/providers/openai-provider.js +0 -413
  135. package/dist/infrastructure/providers/openrouter-provider.js +0 -399
  136. package/dist/infrastructure/providers/operation-provider-resolver.js +0 -105
  137. package/dist/infrastructure/providers/provider-config-loader.js +0 -120
  138. package/dist/infrastructure/providers/provider-registry.js +0 -88
  139. package/dist/infrastructure/safety/pattern-detector.js +0 -133
  140. package/dist/infrastructure/security/ai-sast-scanner.js +0 -290
  141. package/dist/infrastructure/security/docker-sandbox.js +0 -178
  142. package/dist/infrastructure/security/enhanced-security-gate.js +0 -151
  143. package/dist/infrastructure/security/license-scanner.js +0 -167
  144. package/dist/infrastructure/security/provider-bias-detector.js +0 -164
  145. package/dist/infrastructure/security/sandbox-executor.js +0 -64
  146. package/dist/infrastructure/serialization/toon-serializer.js +0 -119
  147. package/dist/infrastructure/storage/memory-stream-service.js +0 -204
  148. package/dist/infrastructure/storage/plan-repository.js +0 -96
  149. package/dist/infrastructure/storage/sqlite-decision-repository.js +0 -232
  150. package/dist/infrastructure/storage/sqlite-embedding-repository.js +0 -311
  151. package/dist/infrastructure/storage/sqlite-knowledge-graph-repository.js +0 -417
  152. package/dist/infrastructure/storage/sqlite-knowledge-preservation-repository.js +0 -230
  153. package/dist/infrastructure/storage/sqlite-memory-repository.js +0 -229
  154. package/dist/infrastructure/storage/sqlite-pattern-repository.js +0 -249
  155. package/dist/infrastructure/storage/task-helpers.js +0 -197
  156. package/dist/infrastructure/telemetry/telemetry-service.js +0 -167
  157. package/dist/infrastructure/ux/appropriate-friction.js +0 -213
  158. package/dist/infrastructure/ux/bi-directional-learning.js +0 -337
  159. package/dist/infrastructure/ux/capability-boundaries.js +0 -42
  160. package/dist/infrastructure/ux/comprehensive-formatter.js +0 -117
  161. package/dist/infrastructure/ux/contextual-feature-discovery.js +0 -10
  162. package/dist/infrastructure/ux/expectation-management.js +0 -327
  163. package/dist/infrastructure/ux/feature-discovery.js +0 -14
  164. package/dist/infrastructure/ux/feedback-prompt.js +0 -45
  165. package/dist/infrastructure/ux/hitl-validation.js +0 -297
  166. package/dist/infrastructure/ux/progress-indicator.js +0 -116
  167. package/dist/infrastructure/ux/progressive-disclosure.js +0 -236
  168. package/dist/infrastructure/ux/review-checkpoint.js +0 -72
  169. package/dist/infrastructure/ux/staged-feature-intro.js +0 -24
  170. package/dist/infrastructure/ux/stream-markdown.js +0 -151
  171. package/dist/infrastructure/ux/syntax-highlighter.js +0 -91
  172. package/dist/infrastructure/ux/team-standards.js +0 -196
  173. package/dist/infrastructure/ux/transparency.js +0 -71
  174. package/dist/infrastructure/validation/agent-feedback.js +0 -55
  175. package/dist/infrastructure/validation/agent-validation-helper.js +0 -137
  176. package/dist/infrastructure/validation/review-handler.js +0 -40
  177. package/dist/infrastructure/validation/summary-aggregator.js +0 -70
  178. package/dist/infrastructure/validation/summary-extractor.js +0 -45
  179. package/dist/infrastructure/validation/trace-summary.js +0 -52
  180. package/dist/infrastructure/verification/verification-service.js +0 -47
  181. package/dist/infrastructure/xai/attention-visualizer.js +0 -172
  182. package/dist/infrastructure/xai/cot-visualizer.js +0 -283
  183. package/dist/infrastructure/xai/decision-tree-log.js +0 -212
  184. package/dist/infrastructure/xai/interactive-xai.js +0 -260
  185. package/dist/infrastructure/xai/uncertainty-quantifier.js +0 -198
  186. package/dist/intelligent-retrieval.js +0 -272
  187. package/dist/intent/classifier.js +0 -2
  188. package/dist/intent/context.js +0 -2
  189. package/dist/iterative-refinement.js +0 -189
  190. package/dist/learning.js +0 -232
  191. package/dist/log.js +0 -31
  192. package/dist/migrate.js +0 -133
  193. package/dist/parallel-processor.js +0 -534
  194. package/dist/planner.js +0 -198
  195. package/dist/policy.js +0 -26
  196. package/dist/presentation/cli/commands/ask-command.js +0 -314
  197. package/dist/presentation/cli/commands/cost-command.js +0 -191
  198. package/dist/presentation/cli/commands/decision-command.js +0 -207
  199. package/dist/presentation/cli/commands/docs-command.js +0 -25
  200. package/dist/presentation/cli/commands/fix-command.js +0 -53
  201. package/dist/presentation/cli/commands/knowledge-command.js +0 -129
  202. package/dist/presentation/cli/commands/learning-dashboard-command.js +0 -183
  203. package/dist/presentation/cli/commands/models-command.js +0 -194
  204. package/dist/presentation/cli/commands/orchestrate-command.js +0 -207
  205. package/dist/presentation/cli/commands/plan-command.js +0 -246
  206. package/dist/presentation/cli/commands/product-command.js +0 -199
  207. package/dist/presentation/cli/commands/react-command.js +0 -181
  208. package/dist/presentation/cli/commands/review-command.js +0 -154
  209. package/dist/presentation/cli/commands/run-command.js +0 -188
  210. package/dist/presentation/cli/commands/smart-command.js +0 -395
  211. package/dist/presentation/cli/commands/suggest-command.js +0 -138
  212. package/dist/presentation/cli/commands/trace-command.js +0 -129
  213. package/dist/presentation/cli/commands/undo-command.js +0 -150
  214. package/dist/query-optimizer.js +0 -205
  215. package/dist/refactor.js +0 -116
  216. package/dist/review.js +0 -206
  217. package/dist/search.js +0 -163
  218. package/dist/session.js +0 -704
  219. package/dist/task-verification.js +0 -336
  220. package/dist/testgen.js +0 -166
  221. package/dist/token-optimizer.js +0 -277
  222. package/dist/tools/bash.js +0 -213
  223. package/dist/tools/edit.js +0 -235
  224. package/dist/tools/executor.js +0 -153
  225. package/dist/tools/git-tool.js +0 -292
  226. package/dist/tools/parser.js +0 -158
  227. package/dist/tools/registry.js +0 -302
  228. package/dist/tools/search.js +0 -100
  229. package/dist/types.js +0 -2
  230. package/dist/utils/check-api-status.js +0 -100
  231. package/dist/utils/diff-parser.js +0 -162
  232. package/dist/verification/backup.js +0 -259
  233. package/dist/verification/compiler.js +0 -264
  234. package/dist/verification/index.js +0 -273
  235. package/dist/verification/test-runner.js +0 -379
@@ -19,10 +19,16 @@
19
19
  * codehere serve --port 8080 # custom port
20
20
  */
21
21
  import http from 'http';
22
- import { readFileSync, existsSync } from 'fs';
22
+ import { readFileSync, existsSync, statSync, watchFile, unwatchFile } from 'fs';
23
23
  import { join, relative } from 'path';
24
24
  import { collectFilesForMCP, scoreFileForMCP } from './mcp-helpers.js';
25
- import { scanPayload, calculateRiskScore } from '../hooks/claude-sdk-hook.js';
25
+ import { scanPayload, calculateRiskScore, AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
26
+ import { getVersion } from '../../utils/version.js';
27
+ import { TASK_HTML, HISTORY_HTML } from './ui-page.js';
28
+ import { SessionManager, availableAgents, readinessForAllAgents, getAdapter, allAdapters, } from '../orchestration/session-manager.js';
29
+ import { isAbsolute } from 'path';
30
+ import { installAgent } from '../orchestration/agent-installer.js';
31
+ import { detectReadiness } from '../orchestration/readiness.js';
26
32
  // ── Tool definitions ─────────────────────────────────────────────────────────
27
33
  const TOOLS = [
28
34
  {
@@ -100,8 +106,8 @@ function handleTrust(params) {
100
106
  score: result.score,
101
107
  grade: result.grade,
102
108
  summary: result.summary,
103
- criticalCount: result.findings.filter(f => f.severity === 'critical').length,
104
- highCount: result.findings.filter(f => f.severity === 'high').length,
109
+ criticalCount: result.findings.filter((f) => f.severity === 'critical').length,
110
+ highCount: result.findings.filter((f) => f.severity === 'high').length,
105
111
  recommendation: result.score >= 75
106
112
  ? 'Safe — minor issues can be addressed in follow-up'
107
113
  : result.score >= 60
@@ -115,7 +121,7 @@ function handleScan(params) {
115
121
  const text = String(params.text ?? '');
116
122
  const findings = scanPayload(text);
117
123
  const riskScore = calculateRiskScore(findings);
118
- const criticalFindings = findings.filter(f => f.severity === 'critical');
124
+ const criticalFindings = findings.filter((f) => f.severity === 'critical');
119
125
  const blocked = criticalFindings.length > 0 || riskScore >= 60;
120
126
  return JSON.stringify({
121
127
  blocked,
@@ -137,7 +143,7 @@ function handleReview(params) {
137
143
  if (files.length === 0) {
138
144
  return JSON.stringify({ message: 'No code files found', path: targetPath });
139
145
  }
140
- const results = files.map(f => scoreFileForMCP(relative(targetPath, f.filepath), f.code));
146
+ const results = files.map((f) => scoreFileForMCP(relative(targetPath, f.filepath), f.code));
141
147
  results.sort((a, b) => a.score - b.score); // worst first
142
148
  const top = results.slice(0, topN);
143
149
  const overallScore = Math.round(results.reduce((s, r) => s + r.score, 0) / results.length);
@@ -145,13 +151,13 @@ function handleReview(params) {
145
151
  overallScore,
146
152
  grade: overallScore >= 90 ? 'A' : overallScore >= 75 ? 'B' : overallScore >= 60 ? 'C' : overallScore >= 40 ? 'D' : 'F',
147
153
  totalFiles: results.length,
148
- riskiestFiles: top.map(r => ({
154
+ riskiestFiles: top.map((r) => ({
149
155
  filepath: r.filepath,
150
156
  score: r.score,
151
157
  grade: r.grade,
152
158
  summary: r.summary,
153
- criticalCount: r.findings.filter(f => f.severity === 'critical').length,
154
- highCount: r.findings.filter(f => f.severity === 'high').length,
159
+ criticalCount: r.findings.filter((f) => f.severity === 'critical').length,
160
+ highCount: r.findings.filter((f) => f.severity === 'high').length,
155
161
  })),
156
162
  });
157
163
  }
@@ -163,19 +169,24 @@ function handleAudit(params) {
163
169
  const limit = Number(params.limit ?? 20);
164
170
  const blockedOnly = Boolean(params.blockedOnly);
165
171
  if (!existsSync(AUDIT_FILE_PATH)) {
166
- return JSON.stringify({ message: 'No audit log yet — install hooks first with: codehere hook install', entries: [] });
172
+ return JSON.stringify({
173
+ message: 'No audit log yet — install hooks first with: codehere hook install',
174
+ entries: [],
175
+ });
167
176
  }
168
177
  const lines = readFileSync(AUDIT_FILE_PATH, 'utf-8')
169
178
  .split('\n')
170
179
  .filter(Boolean)
171
180
  .slice(-Math.min(limit * 3, 500)); // read more than needed to account for filter
172
181
  const entries = lines
173
- .map(l => { try {
174
- return JSON.parse(l);
175
- }
176
- catch {
177
- return null;
178
- } })
182
+ .map((l) => {
183
+ try {
184
+ return JSON.parse(l);
185
+ }
186
+ catch {
187
+ return null;
188
+ }
189
+ })
179
190
  .filter(Boolean)
180
191
  .filter((e) => !blockedOnly || e.blocked)
181
192
  .slice(-limit);
@@ -189,11 +200,12 @@ function dispatch(req) {
189
200
  switch (req.method) {
190
201
  case 'initialize':
191
202
  return {
192
- jsonrpc: '2.0', id,
203
+ jsonrpc: '2.0',
204
+ id,
193
205
  result: {
194
206
  protocolVersion: '2024-11-05',
195
207
  capabilities: { tools: {} },
196
- serverInfo: { name: 'codehere', version: '0.2.0' },
208
+ serverInfo: { name: 'codehere', version: getVersion() },
197
209
  },
198
210
  };
199
211
  case 'tools/list':
@@ -219,7 +231,8 @@ function dispatch(req) {
219
231
  return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${toolName}` } };
220
232
  }
221
233
  return {
222
- jsonrpc: '2.0', id,
234
+ jsonrpc: '2.0',
235
+ id,
223
236
  result: { content: [{ type: 'text', text: content }] },
224
237
  };
225
238
  }
@@ -250,13 +263,338 @@ export function startMCPServer(options = {}) {
250
263
  // Health check
251
264
  if (req.method === 'GET' && req.url === '/health') {
252
265
  res.writeHead(200, { 'Content-Type': 'application/json' });
253
- res.end(JSON.stringify({ status: 'ok', server: 'codehere-mcp', version: '0.2.0', tools: TOOLS.length }));
266
+ res.end(JSON.stringify({ status: 'ok', server: 'codehere-mcp', version: getVersion(), tools: TOOLS.length }));
267
+ return;
268
+ }
269
+ // v0.3 primary UI: task intake + active sessions
270
+ if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
271
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
272
+ res.end(TASK_HTML);
273
+ return;
274
+ }
275
+ // History / audit feed (what we built Day 0; now secondary)
276
+ if (req.method === 'GET' && (req.url === '/history' || req.url === '/history/')) {
277
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
278
+ res.end(HISTORY_HTML);
279
+ return;
280
+ }
281
+ // Backwards-compat: /ui used to be the Mission Control audit view — now at /history
282
+ if (req.method === 'GET' && (req.url === '/ui' || req.url === '/ui/')) {
283
+ res.writeHead(302, { Location: '/history' });
284
+ res.end();
285
+ return;
286
+ }
287
+ // Live audit event stream (SSE)
288
+ if (req.method === 'GET' && req.url === '/events') {
289
+ res.writeHead(200, {
290
+ 'Content-Type': 'text/event-stream',
291
+ 'Cache-Control': 'no-cache',
292
+ Connection: 'keep-alive',
293
+ });
294
+ // Replay last 50 events on connect so the UI isn't empty
295
+ let lastSize = 0;
296
+ if (existsSync(AUDIT_FILE)) {
297
+ try {
298
+ const content = readFileSync(AUDIT_FILE, 'utf-8');
299
+ lastSize = Buffer.byteLength(content, 'utf-8');
300
+ const lines = content.split('\n').filter((l) => l.trim().length > 0);
301
+ for (const line of lines.slice(-50)) {
302
+ res.write(`data: ${line}\n\n`);
303
+ }
304
+ }
305
+ catch {
306
+ /* no audit yet */
307
+ }
308
+ }
309
+ // Tail the audit file for new events
310
+ const onChange = () => {
311
+ try {
312
+ if (!existsSync(AUDIT_FILE))
313
+ return;
314
+ const currentSize = statSync(AUDIT_FILE).size;
315
+ if (currentSize <= lastSize)
316
+ return;
317
+ const fd = readFileSync(AUDIT_FILE, 'utf-8');
318
+ const fresh = fd.slice(lastSize);
319
+ lastSize = Buffer.byteLength(fd, 'utf-8');
320
+ for (const line of fresh.split('\n').filter((l) => l.trim().length > 0)) {
321
+ res.write(`data: ${line}\n\n`);
322
+ }
323
+ }
324
+ catch {
325
+ /* transient read errors are fine */
326
+ }
327
+ };
328
+ watchFile(AUDIT_FILE, { interval: 500 }, onChange);
329
+ // Keep-alive ping every 15s so proxies don't kill the stream
330
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
331
+ req.on('close', () => {
332
+ clearInterval(ping);
333
+ unwatchFile(AUDIT_FILE, onChange);
334
+ });
335
+ return;
336
+ }
337
+ // ── F2 agent registry ─────────────────────────────────────────────────────
338
+ if (req.method === 'GET' && req.url === '/api/agents') {
339
+ res.writeHead(200, { 'Content-Type': 'application/json' });
340
+ res.end(JSON.stringify({ agents: availableAgents() }));
341
+ return;
342
+ }
343
+ // ── Gate B3 · Audit summary (moat visible on main page) ───────────────────
344
+ // Principle #3 status-at-a-glance + #7 agent-provenance. Reads the hook audit
345
+ // log's last 24h, aggregates: how many actions fired across all agents, how
346
+ // many codehere blocked, mean trust (= 100 - mean riskScore).
347
+ if (req.method === 'GET' && req.url === '/api/audit/summary') {
348
+ const windowMs = 24 * 60 * 60 * 1000;
349
+ const cutoff = Date.now() - windowMs;
350
+ let actions = 0, blocked = 0, riskSum = 0, riskN = 0;
351
+ const sessionIds = new Set();
352
+ try {
353
+ if (existsSync(AUDIT_FILE)) {
354
+ const content = readFileSync(AUDIT_FILE, 'utf-8');
355
+ const lines = content.split('\n');
356
+ // Walk backwards; stop at first line older than cutoff (log is append-chronological)
357
+ for (let i = lines.length - 1; i >= 0; i--) {
358
+ const line = lines[i].trim();
359
+ if (!line)
360
+ continue;
361
+ let entry;
362
+ try {
363
+ entry = JSON.parse(line);
364
+ }
365
+ catch {
366
+ continue;
367
+ }
368
+ const ts = typeof entry.timestamp === 'number'
369
+ ? entry.timestamp
370
+ : entry.timestamp
371
+ ? Date.parse(String(entry.timestamp))
372
+ : 0;
373
+ if (!ts || ts < cutoff)
374
+ break;
375
+ actions += 1;
376
+ if (entry.blocked)
377
+ blocked += 1;
378
+ if (typeof entry.riskScore === 'number') {
379
+ riskSum += entry.riskScore;
380
+ riskN += 1;
381
+ }
382
+ if (entry.sessionId)
383
+ sessionIds.add(entry.sessionId);
384
+ }
385
+ }
386
+ }
387
+ catch {
388
+ /* empty audit — zeros are fine */
389
+ }
390
+ const avgTrust = riskN > 0 ? Math.max(0, Math.round(100 - riskSum / riskN)) : null;
391
+ res.writeHead(200, { 'Content-Type': 'application/json' });
392
+ res.end(JSON.stringify({
393
+ windowHours: 24,
394
+ actions,
395
+ blocked,
396
+ agents: sessionIds.size,
397
+ avgTrust,
398
+ }));
399
+ return;
400
+ }
401
+ // ── F1 · Agent install (zero-terminal setup — principle #10) ──────────────
402
+ // POST /api/agents/:id/install → SSE stream of install output.
403
+ // The command comes from the adapter's checkReadiness().installHint — never
404
+ // from the user over the wire. codehere is the thing that runs the install,
405
+ // not a page that tells the user how to run it.
406
+ const installMatch = req.url?.match(/^\/api\/agents\/([a-z0-9-]+)\/install$/i);
407
+ if (req.method === 'POST' && installMatch) {
408
+ const adapterId = installMatch[1];
409
+ const adapter = getAdapter(adapterId);
410
+ if (!adapter) {
411
+ res.writeHead(404, { 'Content-Type': 'application/json' });
412
+ res.end(JSON.stringify({ error: `No such agent: ${adapterId}` }));
413
+ return;
414
+ }
415
+ const session = installAgent(adapter);
416
+ if (!session) {
417
+ res.writeHead(400, { 'Content-Type': 'application/json' });
418
+ res.end(JSON.stringify({ error: 'Agent already installed or has no install hint' }));
419
+ return;
420
+ }
421
+ res.writeHead(200, {
422
+ 'Content-Type': 'text/event-stream',
423
+ 'Cache-Control': 'no-cache',
424
+ Connection: 'keep-alive',
425
+ });
426
+ const unsubscribe = session.subscribe((evt) => {
427
+ res.write(`data: ${JSON.stringify(evt)}\n\n`);
428
+ });
429
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
430
+ req.on('close', () => {
431
+ clearInterval(ping);
432
+ unsubscribe();
433
+ });
434
+ return;
435
+ }
436
+ // ── E2 · Per-agent readiness (onboarding surface) ─────────────────────────
437
+ // Full registry snapshot: install state + auth state + hints. Powers the
438
+ // setup-aware agent dropdown and the one-click swap flow on auth errors.
439
+ if (req.method === 'GET' && req.url === '/api/readiness/agents') {
440
+ try {
441
+ res.writeHead(200, { 'Content-Type': 'application/json' });
442
+ res.end(JSON.stringify({ agents: readinessForAllAgents() }));
443
+ }
444
+ catch (e) {
445
+ res.writeHead(500, { 'Content-Type': 'application/json' });
446
+ res.end(JSON.stringify({ error: 'readiness_check_failed', detail: String(e) }));
447
+ }
448
+ return;
449
+ }
450
+ // ── F1a readiness check ───────────────────────────────────────────────────
451
+ if (req.method === 'GET' && req.url === '/api/readiness') {
452
+ try {
453
+ const report = detectReadiness();
454
+ res.writeHead(200, { 'Content-Type': 'application/json' });
455
+ res.end(JSON.stringify(report));
456
+ }
457
+ catch (e) {
458
+ res.writeHead(500, { 'Content-Type': 'application/json' });
459
+ res.end(JSON.stringify({ error: 'readiness_check_failed', detail: String(e) }));
460
+ }
461
+ return;
462
+ }
463
+ // ── F1 task orchestration routes ─────────────────────────────────────────
464
+ // POST /api/task — { prompt, cwd? } → { sessionId }
465
+ // GET /api/tasks — list all sessions
466
+ // GET /api/task/:id/events — SSE stream
467
+ // POST /api/task/:id/kill — terminate
468
+ if (req.url && req.url.startsWith('/api/task')) {
469
+ const manager = SessionManager.getInstance();
470
+ if (req.method === 'POST' && req.url === '/api/task') {
471
+ let body = '';
472
+ req.on('data', (c) => {
473
+ body += c;
474
+ });
475
+ req.on('end', () => {
476
+ try {
477
+ const { prompt, cwd, agent } = JSON.parse(body || '{}');
478
+ if (!prompt || typeof prompt !== 'string') {
479
+ res.writeHead(400, { 'Content-Type': 'application/json' });
480
+ res.end(JSON.stringify({ error: 'prompt (string) required' }));
481
+ return;
482
+ }
483
+ // CRIT-5 fix · whitelist agent ID against the registry BEFORE any
484
+ // log/spawn. Stops log injection (`agent: "claude-code\n[FAKE]"`),
485
+ // future-proofs against partial-match exploits, and fails fast
486
+ // with a clear 400 instead of letting SessionManager.spawn throw.
487
+ const validAgentIds = new Set(allAdapters().map((a) => a.id));
488
+ const agentId = agent ?? 'claude-code';
489
+ if (typeof agentId !== 'string' || !validAgentIds.has(agentId)) {
490
+ res.writeHead(400, { 'Content-Type': 'application/json' });
491
+ res.end(JSON.stringify({
492
+ error: `Unknown agent: ${typeof agentId === 'string' ? agentId.slice(0, 64) : '<non-string>'}`,
493
+ valid: [...validAgentIds],
494
+ }));
495
+ return;
496
+ }
497
+ // Path-traversal + null-byte defense · cwd must be absolute and
498
+ // free of NUL bytes (some filesystems treat \0 as terminator).
499
+ if (cwd !== undefined) {
500
+ if (typeof cwd !== 'string' || cwd.includes('\0') || !isAbsolute(cwd)) {
501
+ res.writeHead(400, { 'Content-Type': 'application/json' });
502
+ res.end(JSON.stringify({ error: 'cwd must be an absolute path with no NUL bytes' }));
503
+ return;
504
+ }
505
+ }
506
+ const session = manager.spawn(prompt, agentId, { cwd });
507
+ res.writeHead(200, { 'Content-Type': 'application/json' });
508
+ res.end(JSON.stringify({
509
+ sessionId: session.sessionId,
510
+ startedAt: session.startedAt,
511
+ status: session.status,
512
+ agent: session.agent,
513
+ }));
514
+ }
515
+ catch (e) {
516
+ res.writeHead(400, { 'Content-Type': 'application/json' });
517
+ res.end(JSON.stringify({ error: 'Invalid JSON body', detail: String(e) }));
518
+ }
519
+ });
520
+ return;
521
+ }
522
+ if (req.method === 'GET' && req.url === '/api/tasks') {
523
+ const spawned = manager.list().map((s) => ({
524
+ sessionId: s.sessionId,
525
+ prompt: s.prompt,
526
+ startedAt: s.startedAt,
527
+ status: s.status,
528
+ agent: s.agent,
529
+ steps: s.steps ?? [],
530
+ artifacts: s.artifacts,
531
+ provenance: 'spawned',
532
+ }));
533
+ const activeAgents = manager.listActiveAgents();
534
+ const persisted = manager.listPersisted();
535
+ // Layer 7: timestamp of most recent hook-audit line (= "last edit on this machine")
536
+ let lastEditAt = null;
537
+ try {
538
+ if (existsSync(AUDIT_FILE))
539
+ lastEditAt = statSync(AUDIT_FILE).mtimeMs;
540
+ }
541
+ catch {
542
+ /* no audit yet — null */
543
+ }
544
+ res.writeHead(200, { 'Content-Type': 'application/json' });
545
+ res.end(JSON.stringify({
546
+ spawned,
547
+ persisted,
548
+ activeAgents,
549
+ lastEditAt,
550
+ sessions: spawned, // back-compat for earlier UI code
551
+ observed: manager.listObserved(), // legacy, kept for /history or future use
552
+ }));
553
+ return;
554
+ }
555
+ // /api/task/:id/events or /api/task/:id/kill
556
+ const m = req.url.match(/^\/api\/task\/([^/]+)\/(events|kill)$/);
557
+ if (m) {
558
+ const [, sessionId, verb] = m;
559
+ const session = manager.get(sessionId);
560
+ if (!session) {
561
+ res.writeHead(404, { 'Content-Type': 'application/json' });
562
+ res.end(JSON.stringify({ error: `No session: ${sessionId}` }));
563
+ return;
564
+ }
565
+ if (verb === 'events' && req.method === 'GET') {
566
+ res.writeHead(200, {
567
+ 'Content-Type': 'text/event-stream',
568
+ 'Cache-Control': 'no-cache',
569
+ Connection: 'keep-alive',
570
+ });
571
+ const unsubscribe = session.subscribe((event) => {
572
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
573
+ });
574
+ const ping = setInterval(() => res.write(': ping\n\n'), 15000);
575
+ req.on('close', () => {
576
+ clearInterval(ping);
577
+ unsubscribe();
578
+ });
579
+ return;
580
+ }
581
+ if (verb === 'kill' && req.method === 'POST') {
582
+ session.kill();
583
+ res.writeHead(200, { 'Content-Type': 'application/json' });
584
+ res.end(JSON.stringify({ sessionId, status: session.status }));
585
+ return;
586
+ }
587
+ }
588
+ res.writeHead(405, { 'Content-Type': 'application/json' });
589
+ res.end(JSON.stringify({ error: 'Method not allowed for this path' }));
254
590
  return;
255
591
  }
256
592
  // MCP endpoint
257
593
  if (req.method === 'POST' && req.url === '/mcp') {
258
594
  let body = '';
259
- req.on('data', chunk => { body += chunk; });
595
+ req.on('data', (chunk) => {
596
+ body += chunk;
597
+ });
260
598
  req.on('end', () => {
261
599
  try {
262
600
  const mcpReq = JSON.parse(body);